Repository navigation
‐Home
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.
- Project scope
- Architecture
- Supported controller profiles
- Device model
- Datapoint catalog and metadata
- Reading and polling
- Writing and write access
- Operating modes and control levels
- Native date and time values
- Derived values
- Source hierarchy
- Testing
- Development workflow
- Continuous integration
- Release process
- Current limits and planned work
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.
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
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.
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.
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.
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
| 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.
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
HR40145orCL137, - 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.
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.
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.
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.
A normal application refreshes the device with:
await device.async_update()The library then:
- builds the required read plan,
- performs grouped requests,
- decodes raw values,
- handles invalid-value sentinels,
- updates component properties,
- refreshes derived 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.
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.
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.
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.
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.
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 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.
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.
Raw DDMM and HHMM registers are converted to native Python types.
The controller time is exposed as:
datetime.timeThe controller has minute resolution.
The full controller date is composed from:
- the DDMM register,
- the year register.
It is exposed as:
datetime.dateWhen 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.
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.
Domestic-hot-water disinfection start and stop values are native
datetime.time objects and are validated against the documented controller
range.
Some useful values do not correspond to one physical register. The library may derive them when the calculation is controller-specific and broadly useful.
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 |
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.
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.
Several sources exist for TROVIS register semantics. They do not always agree.
The project follows this priority:
-
Final controller firmware register and coil tables
Authoritative for addresses, ranges, scaling, units, and writeability. -
Manufacturer application layout definitions
Useful for function-block dependencies, visibility rules, and plant configurations. -
Manufacturer application override definitions
Useful for enum labels, formatting, and special conversions. -
Older expert-value definitions
Supplemental reference only when they do not conflict with final firmware. -
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.
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.
uv sync
uv run pytest
uvx prek run --all-filespython -m pip install -e .
python -m pip install "pytest>=8" "pytest-asyncio>=0.24" ruff buildRun tests:
script/libtest.shRun all local release checks:
script/libcheck.shlibcheck.sh performs:
- Ruff formatting check,
- Ruff lint,
- source and test compilation,
- the complete pytest suite,
- source distribution and wheel build.
The scripts resolve the repository root themselves and can be called from any working directory.
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.shThis local setup is useful for coordinated development. CI intentionally tests the package with its published dependency instead.
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
- personal working branch,
- frequent local changes,
- no automatic CI required on every push,
- local checks run manually when a block is ready.
- shared integration and beta branch,
- automatic CI runs here,
- external pull requests should target this branch,
- should remain releasable after successful checks.
- current release branch,
- protected by repository rules,
- only
developmay be used as the source of a pull request, - direct pushes and force pushes should be blocked,
- releases are created from this branch.
Before moving a completed block to develop:
script/libcheck.sh
git status
git add .
git commit -m "Describe the completed block"
git pushThen integrate the working branch into develop using the repository's chosen
linear-history workflow.
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
mainwhen 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.
The intended release sequence is:
- Finish and test changes on
develop_tom. - Run
script/libcheck.sh. - Integrate into
develop. - Wait for the GitHub CI result.
- Update README, wiki, and release notes where necessary.
- Open a pull request from
developtomain. - Confirm CI and branch guard checks.
- Merge the pull request.
- Create a GitHub release and tag.
- 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.
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.
The current release scope deliberately avoids several larger topics.
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.
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.
The controller exposes error and status masks. Full decoding should use
well-documented IntFlag types after all bit meanings have been verified.
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.
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.
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.
When adding a datapoint or behavior:
- Prefer final manufacturer firmware documentation.
- Keep manufacturer references visible in the catalog.
- Add neutral metadata in the library.
- Preserve model and block restrictions.
- Do not make a field writable without a verified write path.
- Add tests for conversion, metadata, and behavior.
- Avoid duplicating presentation logic from one application.
- Prefer native typed values over raw display-oriented values.
- Prefer direct controller values over reconstructed estimates.
- Keep changes small enough to review, but complete enough to test as one coherent feature.
Disclaimer: Any information on this wiki is informal advice only. It is not supported nor endorsed by Samson, Sauter, YADOS, Pewo or any other equipment maker. There is no warranty expressed or implied: you take sole responsibility for everything you do with your heating controller.