Skip to content

‐Home

Tom-Bom-badil edited this page Jul 29, 2026 · 1 revision

Library architecture and development guide

This page gives a consolidated overview of the current trovis-modbus design, the supported controller families, the datapoint model, write handling, testing, and the repository workflow.

It is intentionally broader than the README. As the project grows, the sections below can be moved into separate wiki pages without changing the overall structure.

Note

trovis-modbus is the controller-specific library. It does not create or own a Modbus transport. Home Assistant support is maintained separately in trovis-modbus-hass.


Contents


Project scope

trovis-modbus is an asynchronous Python library for Samson TROVIS 557x heating controllers.

The library provides:

  • model detection,
  • physical sensor detection,
  • register and coil catalogs,
  • scaling and signed-value conversion,
  • invalid-value handling,
  • neutral metadata,
  • grouped reads,
  • TROVIS-specific write access,
  • validated writes,
  • native enum, date, time, and month/day values,
  • heating-circuit and domestic-hot-water abstractions,
  • selected derived operating values.

The library intentionally does not provide application-specific entities or user interfaces. For example, Home Assistant decides whether a datapoint is shown as a sensor, number, select, switch, date, or time entity.

This separation keeps the controller knowledge reusable outside Home Assistant.


Architecture

The library sits between an application and a transport-neutral modbus_connection.ModbusUnit.

flowchart LR
    APP["Application<br/>Home Assistant, CLI, custom service"]
    LIB["trovis-modbus<br/>Controller model, metadata, reads, writes"]
    MC["modbus-connection<br/>Connection and unit abstraction"]
    BACKEND["Backend<br/>tmodbus, pymodbus, other"]
    DEVICE["Samson TROVIS 557x"]

    APP --> LIB
    LIB --> MC
    MC --> BACKEND
    BACKEND --> DEVICE
Loading

The application owns the connection lifecycle. The library receives a unit object and only performs controller-specific operations through that object.

This has several advantages:

  • no dependency on one concrete Modbus implementation,
  • no duplicate connection management,
  • shared connections can be used by multiple consumers,
  • controller logic remains independent of Home Assistant,
  • transport changes do not require rewriting the TROVIS model.

Supported controller profiles

The library currently uses two conservative controller profiles.

Controller family Heating circuits Reference profile
TROVIS 5573, 5573-1, 5575, 5576 2 TROVIS 5573 Rev. 2.54
TROVIS 5578, 5578-E, 5579 3 TROVIS 5578 Rev. 2.62 final

The detected model controls which catalog entries and heating circuits are available.

Known register gaps, reserved areas, and manufacturer block boundaries are preserved. The grouped reader does not bridge these boundaries merely because two addresses appear numerically close.

Model probing

A typical application first probes the controller:

probe = await Trovis557x.async_probe(unit)

The result contains the detected model and physical sensor information needed to construct the device:

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

This avoids exposing unsupported heating circuits or non-existent physical sensors.


Device model

A Trovis557x instance exposes logical controller areas instead of a flat list of raw Modbus addresses.

Trovis557x
├── info
├── controller
├── clock
├── sensors
├── heating_circuit_1
├── heating_circuit_2
├── heating_circuit_3   # model dependent
├── hot_water
└── activity

Main components

Component Purpose
info Model, firmware, hardware version, serial information
controller Controller-wide state, limits, switches, and settings
clock Native controller date and time
sensors Physical temperatures, analog inputs, pulse input, remote values
heating_circuit_1..3 Circuit-specific status, setpoints, curves, pumps, valves
hot_water Domestic-hot-water state, setpoints, charging, disinfection
activity Combined heating and hot-water operating state

device.heating_circuits contains only the circuits supported by the detected controller profile.


Datapoint catalog and metadata

The library uses declarative datapoint definitions rather than scattering raw addresses throughout the code.

A catalog entry describes the technical and semantic properties of a register or coil.

Typical metadata includes:

  • manufacturer reference such as HR40145 or CL137,
  • zero-based Modbus address,
  • register or coil type,
  • signed or unsigned encoding,
  • scale,
  • unit,
  • minimum and maximum,
  • write step,
  • enum type,
  • invalid raw values,
  • writable state,
  • model applicability,
  • read grouping information.

The manufacturer-style references remain visible in the source because they are easier to compare with TROVIS documentation. Conversion to zero-based Modbus addresses is centralized.

Why metadata belongs in the library

The library is the authoritative source for controller-specific facts. An application should not need to duplicate:

  • whether a value is writable,
  • which range is valid,
  • how it is scaled,
  • whether it is signed,
  • which enum values are valid,
  • which controller models support it.

Applications may still decide presentation details, but should not redefine controller behavior.

Functional parity

The current catalog covers all direct datapoints used by the former standard Modbus configuration that served as the project reference. It also includes additional 5578/5579 datapoints that were not part of that older setup.

Parity is functional rather than entity-count based. For example, one native date value can replace several old raw and formatting entities.


Reading and polling

Grouped reads

The library groups adjacent fields into bounded Modbus reads. The current planning limit is deliberately conservative:

maximum span: approximately 50 registers or coils

This reduces protocol overhead without creating very large or fragile requests.

The planner respects:

  • register versus coil separation,
  • known manufacturer block boundaries,
  • catalog gaps,
  • controller model restrictions,
  • active heating-circuit count.

Update cycle

A normal application refreshes the device with:

await device.async_update()

The library then:

  1. builds the required read plan,
  2. performs grouped requests,
  3. decodes raw values,
  4. handles invalid-value sentinels,
  5. updates component properties,
  6. refreshes derived values.

Invalid values

TROVIS controllers use special raw values for missing sensors or unavailable data. The library converts these sentinels centrally rather than exposing them as plausible temperatures or numbers.

This is especially important for optional physical sensors and model-dependent inputs.


Writing and write access

TROVIS controllers require explicit write access before protected values can be changed.

A typical sequence is:

await device.async_enable_writing()

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

The default access code is controller-specific and may be overridden by the application.

Generic datapoint writes

Components also support metadata-driven writes:

await component.async_write_datapoint(field, value)

Before writing, the library can perform:

  • writable-state checks,
  • type conversion,
  • enum conversion,
  • range validation,
  • step validation,
  • signed/scaled encoding,
  • TROVIS write-access refresh,
  • field-specific preconditions,
  • one or more ordered register or coil writes.

This keeps application code free from raw register calculations.

Write safety

Not every readable datapoint should automatically become writable. A field is only marked writable when the controller documentation and current implementation support a safe write path.

Special command registers that may trigger controller actions, updates, or restarts remain read-only until their command values and consequences are fully understood.


Operating modes and control levels

A TROVIS heating circuit is not a conventional on/off thermostat.

Its behavior is split across:

  • an operating mode register,
  • a control-level coil,
  • room setpoints,
  • flow setpoints,
  • heating curves,
  • setback behavior,
  • pumps,
  • valves,
  • controller-wide states.

Operating modes

The shared operating-mode enum currently models:

Raw value Meaning
0 Program
1 Automatic
2 Standby
3 Manual
4 Day
5 Night

A complete shared enum is used even when a specific physical selector exposes only a subset of these positions.

Automatic mode

Automatic has special semantics:

  • it changes the corresponding control-level coil to autonomous operation,
  • it does not overwrite the stored operating-mode register.

Other operating modes use the supervisory/control level before writing the mode register.

This behavior is implemented in the library because it is a controller rule, not an application presentation detail.

Readable control state

Control-level coils are also available as readable state so applications can show whether a circuit or domestic-hot-water function is operating autonomously or under supervisory control.


Native date and time values

Raw DDMM and HHMM registers are converted to native Python types.

Controller time

The controller time is exposed as:

datetime.time

The controller has minute resolution.

Controller date

The full controller date is composed from:

  • the DDMM register,
  • the year register.

It is exposed as:

datetime.date

When writing a complete date, the library writes the year first and then the day/month value. This ordering avoids transient invalid combinations around month lengths and leap years.

Recurring month/day values

Summer start and summer end are year-independent recurring dates. They are represented by the library's MonthDay type instead of inventing an arbitrary year.

Example concept:

MonthDay(month=5, day=15)

The application decides how to present a yearless recurring date in its own user interface.

Thermal disinfection times

Domestic-hot-water disinfection start and stop values are native datetime.time objects and are validated against the documented controller range.


Derived values

Some useful values do not correspond to one physical register. The library may derive them when the calculation is controller-specific and broadly useful.

Plant activity

device.activity combines heating and domestic-hot-water operation into a single enum-like state:

State Meaning
IDLE Neither heating nor hot-water charging active
HEATING At least one heating circuit active
HOT_WATER Domestic-hot-water operation active
HEATING_AND_HOT_WATER Both active

Hot-water temperature ranges

The library exposes calculated day and night temperature ranges based on the relevant setpoints, switching differential, and hold values.

These replace old presentation templates while keeping the calculation close to the controller semantics.

Prefer direct values when available

A derived estimate should not duplicate a better direct register. For example, the active charging setpoint is available directly and should be preferred over reconstructing it from sensor temperature and configured elevation.


Source hierarchy

Several sources exist for TROVIS register semantics. They do not always agree.

The project follows this priority:

  1. Final controller firmware register and coil tables
    Authoritative for addresses, ranges, scaling, units, and writeability.

  2. Manufacturer application layout definitions
    Useful for function-block dependencies, visibility rules, and plant configurations.

  3. Manufacturer application override definitions
    Useful for enum labels, formatting, and special conversions.

  4. Older expert-value definitions
    Supplemental reference only when they do not conflict with final firmware.

  5. Existing working configurations and live-controller tests
    Important validation sources, but not a replacement for final firmware documentation.

When sources conflict, the final controller firmware table wins.


Testing

The test suite uses the modbus-connection pytest backend and does not require a physical controller or a live Modbus server.

Current test areas include:

  • canonical catalog parity,
  • model-specific catalog selection,
  • register and coil metadata,
  • grouped read behavior,
  • signed and scaled conversion,
  • invalid-value handling,
  • device probing,
  • operating-mode writes,
  • control-level behavior,
  • operational datapoints,
  • date and time decoding and encoding,
  • write ordering,
  • enum options,
  • command-line query behavior.

The current development baseline passes the full suite with expected skips for explicitly documented exceptions.

Using uv

uv sync
uv run pytest
uvx prek run --all-files

Using a normal Python installation

python -m pip install -e .
python -m pip install "pytest>=8" "pytest-asyncio>=0.24" ruff build

Run tests:

script/libtest.sh

Run all local release checks:

script/libcheck.sh

libcheck.sh performs:

  1. Ruff formatting check,
  2. Ruff lint,
  3. source and test compilation,
  4. the complete pytest suite,
  5. source distribution and wheel build.

The scripts resolve the repository root themselves and can be called from any working directory.

Local development checkout of modbus-connection

The project development environment may use a local checkout of modbus-connection.

The default path used by the test script is:

/config/dev/modbus-connection/src

It can be overridden with:

MODBUS_CONNECTION_SRC=/another/path script/libtest.sh

This local setup is useful for coordinated development. CI intentionally tests the package with its published dependency instead.


Development workflow

The repository uses three main branches.

flowchart TB
    MAIN["main<br/>Release branch"]
    DEVELOP["develop<br/>Shared integration branch"]
    WORK["develop_tom<br/>Maintainer working branch"]
    CONTRIBUTORS["Contributor branches / forks"]

    WORK --> DEVELOP
    CONTRIBUTORS --> DEVELOP
    DEVELOP --> MAIN
Loading

develop_tom

  • personal working branch,
  • frequent local changes,
  • no automatic CI required on every push,
  • local checks run manually when a block is ready.

develop

  • shared integration and beta branch,
  • automatic CI runs here,
  • external pull requests should target this branch,
  • should remain releasable after successful checks.

main

  • current release branch,
  • protected by repository rules,
  • only develop may be used as the source of a pull request,
  • direct pushes and force pushes should be blocked,
  • releases are created from this branch.

Recommended local sequence

Before moving a completed block to develop:

script/libcheck.sh
git status
git add .
git commit -m "Describe the completed block"
git push

Then integrate the working branch into develop using the repository's chosen linear-history workflow.


Continuous integration

The CI workflow is intentionally small and uses standard Python tooling.

It runs on:

  • pushes to develop,
  • pushes to main,
  • pull requests targeting develop,
  • pull requests targeting main when a final pre-merge check is required.

Typical CI steps are:

checkout
setup Python
install package and test tools
ruff format --check
ruff check
compileall
pytest
build sdist and wheel

The CI runner installs the project normally and therefore verifies compatibility with the publicly available modbus-connection dependency.

A separate guard workflow rejects pull requests to main unless their source is the repository's local develop branch.

The GitHub ruleset should make the relevant CI and guard jobs required before merging to main.


Release process

The intended release sequence is:

  1. Finish and test changes on develop_tom.
  2. Run script/libcheck.sh.
  3. Integrate into develop.
  4. Wait for the GitHub CI result.
  5. Update README, wiki, and release notes where necessary.
  6. Open a pull request from develop to main.
  7. Confirm CI and branch guard checks.
  8. Merge the pull request.
  9. Create a GitHub release and tag.
  10. Let the publish workflow build and publish the package.

The source tree keeps the development version at 0.0.0. The publish workflow injects the release tag into the package build.

Versioning

The project follows semantic versioning in principle:

  • patch release for compatible fixes,
  • minor release for compatible new public features,
  • major release for intentional breaking API changes.

Native date/time values, new enums, new datapoints, and additional derived properties are suitable for a minor release when they do not break existing callers.


Current limits and planned work

The current release scope deliberately avoids several larger topics.

Time programs

Complete weekly schedules require their own model for:

  • schedule blocks,
  • day selection,
  • time periods,
  • validation,
  • ordered writes,
  • controller-specific limits.

They should not be added as a collection of unrelated raw registers.

Heat-meter values

Some heat-meter values span multiple registers or require unit-dependent interpretation. These should be modeled as coherent typed values rather than exposed as unrelated words.

Error bit fields

The controller exposes error and status masks. Full decoding should use well-documented IntFlag types after all bit meanings have been verified.

Function-block-aware availability

The library already preserves model-specific and known catalog restrictions. Future work may additionally use plant identifiers and function blocks to expose only datapoints relevant to the configured installation.

Complex write paths

A small number of controller functions require more than a normal register or coil write. These remain deferred until their full sequence, validation, and failure behavior are known.

Home Assistant entities

The Home Assistant integration still needs to map newly available library fields to native entities, including:

  • date,
  • time,
  • yearless month/day representation,
  • enum sensors and selects,
  • additional sensors, numbers, switches, and binary sensors,
  • removal or redesign of abstractions that do not match TROVIS semantics.

These are integration tasks and should not be implemented inside the library.


Contribution principles

When adding a datapoint or behavior:

  1. Prefer final manufacturer firmware documentation.
  2. Keep manufacturer references visible in the catalog.
  3. Add neutral metadata in the library.
  4. Preserve model and block restrictions.
  5. Do not make a field writable without a verified write path.
  6. Add tests for conversion, metadata, and behavior.
  7. Avoid duplicating presentation logic from one application.
  8. Prefer native typed values over raw display-oriented values.
  9. Prefer direct controller values over reconstructed estimates.
  10. Keep changes small enough to review, but complete enough to test as one coherent feature.

Clone this wiki locally