Skip to content

Usage and Examples

Tom Bombadil edited this page Jul 29, 2026 · 1 revision

This page shows how an application installs the library, creates a transport, probes a controller, reads values, checks hydronic and sensor availability, and performs validated writes.

Requirements

  • Python 3.12 or newer
  • trovis-modbus
  • a modbus-connection backend suitable for the selected transport

Install the published library:

python -m pip install trovis-modbus

For the repository command-line query tool, install the optional CLI backend:

python -m pip install "trovis-modbus[cli]"

For an editable source checkout:

python -m pip install -e .

Connection model

trovis-modbus does not open the transport itself. The application:

  1. opens a modbus-connection connection,
  2. obtains a ModbusUnit for the controller station address,
  3. passes that unit to Trovis557x,
  4. closes the connection when finished.

Common transport forms are:

Transport Typical use Framer
RTU over TCP Transparent serial gateway rtu
Native Modbus TCP Device or gateway using MBAP framing socket
Serial RTU Local USB/RS-485 adapter configured by serial parameters

The default TROVIS Modbus station address is commonly 246, but the application must use the address configured on the actual controller.

Complete RTU-over-TCP example

This example uses the pymodbus backend supplied by modbus-connection.

import asyncio

from modbus_connection.pymodbus import connect_tcp
from trovis_modbus import Trovis557x


async def main() -> None:
    connection = await connect_tcp(
        "192.168.1.50",
        port=502,
        framer="rtu",
    )

    try:
        unit = connection.for_unit(246)

        probe = await Trovis557x.async_probe(unit)
        device = Trovis557x(
            unit,
            model=probe.model,
            detected_sensors=probe.detected_sensors,
        )

        await device.async_update()

        print("Model:", device.model_definition.model.value)
        print("System code:", device.info.system_code)
        print("Outside temperature:", device.sensors.af1)
        print("Controller date:", device.clock.date)
        print("System activity:", device.system_activity)
    finally:
        await connection.close()


asyncio.run(main())

Use framer="socket" for native Modbus TCP with MBAP framing.

Serial example

import asyncio

from modbus_connection.pymodbus import connect_serial
from trovis_modbus import Trovis557x


async def main() -> None:
    connection = await connect_serial(
        "/dev/ttyUSB0",
        baudrate=19200,
        parity="N",
        stopbits=1,
        bytesize=8,
    )

    try:
        unit = connection.for_unit(246)
        probe = await Trovis557x.async_probe(unit)
        device = Trovis557x(
            unit,
            model=probe.model,
            detected_sensors=probe.detected_sensors,
        )
        await device.async_update()
        print(device.info)
    finally:
        await connection.close()


asyncio.run(main())

Use the serial settings configured on the controller. Supported baud rates and interface options differ by TROVIS model and installed communication module.

Probe before constructing the device

The safe probe is the preferred setup sequence:

probe = await Trovis557x.async_probe(unit)

The result contains:

probe.model
probe.model_name
probe.detected_sensors

Construct the full object with the probe result:

device = Trovis557x(
    unit,
    model=probe.model,
    detected_sensors=probe.detected_sensors,
)

Do not hard-code a default model in an application when the controller can be probed.

Reading the controller

Refresh all active components in one grouped update:

await device.async_update()

Examples of normal values:

model = device.model_definition.model
firmware = device.info.firmware_version
system_code = device.info.system_code
outside_temperature = device.sensors.af1
controller_date = device.clock.date
controller_time = device.clock.time
activity = device.system_activity

A value can be None when it is unavailable, not active, not installed, or represented by a TROVIS invalid-value sentinel.

Working with Rk roles

Do not assume that every built-in Rk1-Rk3 slot is a room-heating circuit. Inspect the current hydronic role:

for index in device.control_circuit_indices:
    print(index, device.control_circuit_role(index))

Room-heating slots are available separately:

for index in device.room_heating_circuit_indices:
    circuit = getattr(device, f"rk{index}")
    print(index, circuit.flow_setpoint)

Optional topology checks:

if device.has_rk4:
    print(device.rk4)

if device.has_buffer_tank_circuit:
    print(device.buffer_tank)

if device.has_solar:
    print(device.solar)

The objects remain stable API attributes; the availability properties decide whether they belong to the configured installation.

Sensor availability and variants

Detected physical values and logical sensor roles are separate concepts. Applications should normally expose only:

device.available_sensor_keys

Diagnostic sets are also available:

device.unresolved_detected_sensor_keys
device.inactive_detected_sensor_keys
device.unsupported_detected_sensors

Detailed resolver information:

resolution = device.sensor_variant_resolution

for result in resolution.variants:
    print(
        result.variant_sensor_keys,
        result.status,
        result.selected_sensor_key,
        result.reason,
        result.evidence,
    )

An application should not choose between alternative roles merely because one value looks like a plausible temperature.

Writing values

Writing must be explicitly enabled:

await device.async_enable_writing()

try:
    await device.rk1.set_room_setpoint_day(21.5)
finally:
    await device.async_disable_writing()

A custom controller access code can be supplied:

await device.async_enable_writing(access_code=1234)

Generic metadata-driven writes are also available on components:

await component.async_write_datapoint(field, value)

The library validates type, range, step, enum options, writeability, and controller-specific preconditions before sending the write.

Use a user-facing write-enable control in applications. Enabling write access in the controller does not by itself define application authorization.

Checking write access

Read the current controller state directly:

is_enabled = await device.async_read_writing_enabled()

The local property:

device.writing_enabled

tracks whether the current library object enabled writing. Another client may change the real controller state, so use the direct read when that distinction matters.

Command-line query tool

The repository contains script/query.py for checking a physical controller without Home Assistant. It reads the complete device once and prints all available subsystems plus sensor-variant diagnostics.

Install the CLI extra in a source checkout:

python -m pip install -e ".[cli]"

RTU over TCP through a transparent gateway:

python script/query.py tcp 192.168.1.50 --unit 246

Native Modbus TCP:

python script/query.py tcp 192.168.1.50 --unit 246 --framer socket

Serial RTU:

python script/query.py serial /dev/ttyUSB0 --unit 246 --baudrate 19200

The query output includes:

  • device information,
  • controller values,
  • date and time,
  • functions and parameters,
  • measurements,
  • Rk1 through Rk4,
  • buffer-tank data when applicable,
  • solar data when applicable,
  • sensor selection and unresolved variants,
  • elapsed time and Modbus read count.

Error handling

Transport errors come from modbus-connection and its selected backend. Controller validation and write errors come from trovis-modbus.

A normal application should handle both categories:

from modbus_connection import ModbusError
from trovis_modbus import TrovisValueValidationError

try:
    await device.async_update()
except ModbusError as err:
    print(f"Communication failed: {err}")

try:
    await device.rk1.set_room_setpoint_day(100.0)
except TrovisValueValidationError as err:
    print(f"Value rejected: {err}")

The exact exception classes available to callers are part of the library API and should be preferred over matching error-message text.

Home Assistant

The library does not create Home Assistant entities. The separate trovis-modbus-hass project owns connection setup, device and entity creation, update coordination, and Home Assistant presentation behavior.