Skip to main content

aiorehom

Unofficial async Python client for the local API of the Rehom Radiax controller ("RadiaxWeb" web server): radiant floor heating and cooling, VMC units and dehumidifiers. It is the library behind the ha-rehom Home Assistant integration.

Status: alpha, read-only. Version 0.2 reads the plant state (REST snapshots plus the controller's WebSocket). Writing (setpoints, modes, schedules) is not implemented yet. The API may change before 1.0.

This is an independent interoperability project. It is not affiliated with, endorsed by or supported by Rehom S.r.l.; see the disclaimer.

Features

  • RehomClient keeps a live, immutable model of one controller (zones, VMCs, actuators, fancoils, alarms, health, weather) and publishes batched StateUpdate notifications.
  • WebSocket-first sync with REST snapshots, periodic resyncs, a REST fallback when the socket is down, and time-driven rebuilds at schedule boundaries.
  • Read-only by construction: an exact request allowlist, one request at a time, and a receive-only WebSocket (see Safety).
  • Fully typed (py.typed, mypy --strict), asyncio and aiohttp based.
  • rehom-probe, a read-only command-line probe that captures, inspects and replays controller sessions offline.

Requires Python 3.13 or later. Runtime dependencies: aiohttp (3.12 or later) and yarl.

Installation

pip install aiorehom
# or
uv add aiorehom

Quick start

import asyncio

from aiorehom import RehomClient, StateUpdate


async def main() -> None:
    async with RehomClient("192.0.2.10", username="homeassistant", password="...") as client:

        def on_update(update: StateUpdate) -> None:
            print(update.reason.value, sorted(update.changed))

        client.subscribe(on_update)
        await client.connect()  # WebSocket first, then the REST snapshot

        state = client.state
        print(state.plant.preset, state.plant.setpoint_mismatch)
        for zone in state.zones.values():
            print(zone.id, zone.name, zone.temperature, zone.target, zone.hvac_action)

        await asyncio.sleep(3600)


asyncio.run(main())

Pass session= to reuse an existing aiohttp.ClientSession (for example Home Assistant's shared session). A session you pass in is never modified or closed.

The model

  • client.state is an immutable RehomState snapshot. Every change is published as StateUpdate(state, previous, reason, changed=<record paths>, ...). A value that is numerically unchanged ("24" vs "24.0") never notifies, and neither does a resync that finds no difference.
  • The client also rebuilds the state at state.next_change_at: schedule slot boundaries, override expiry, alarm debounce and heartbeat deadlines.
  • state.alarms_debounced is debounced on both edges (ClientOptions.alarm_debounce, 60 s by default).
  • client.connection_state is CONNECTED, DEGRADED (WebSocket down, REST only), UNAVAILABLE or CLOSED. The socket is receive-only, so a half-open connection is detected by silence (ClientOptions.ws_idle_timeout, 180 s) and reconnected.
  • A failed resync is retried with exponential backoff up to the resync interval; client.stats exposes failure counters and the last error class.
  • client.get_history(...) fetches history in chunks of 24 hours or less.
  • client.dump() returns diagnostics with secrets redacted (personal data such as zone names is not masked). state.as_dict() is a JSON-ready copy of the model.
  • There is no write API and no re-login in 0.2: an HTTP 401/403 raises RehomAuthenticationError.
Module Contents
aiorehom.client RehomClient, ClientOptions
aiorehom.models Frozen dataclasses of the state (RehomState, Zone, Vmc, Alarm, ...)
aiorehom.enums, aiorehom.values Public enums; numeric-aware parsing and comparison of raw values
aiorehom.logic Pure derived logic: presence, schedules, running program, overrides, zone target and mode, master preset, lock, VMC, alarms, heartbeat, weather
aiorehom.transport ReadOnlyTransport, the only code that issues HTTP requests
aiorehom.websocket Receive-only WebSocket connections
aiorehom.replay Offline replay of a capture through the real client
aiorehom.redact, aiorehom.pseudonymise Secret redaction; pseudonymisation of personal data for test fixtures
aiorehom.cli The rehom-probe command

Safety

aiorehom is designed to look without touching anything. These guarantees are enforced in code and covered by tests (tests/test_transport_allowlist.py, tests/test_websocket.py, tests/test_e2e_no_writes.py).

  • Exact allowlist. Each permitted request has its own typed method on ReadOnlyTransport; there is no generic request method. The allowlist contains only GETs of read endpoints (with fixed query keys where queries are allowed) and at most one login per transport. Any other method, path or query raises ForbiddenRequestError before a socket is opened. Paths are validated strictly and compared exactly (no prefix or glob matching, no .., //, percent-encoding, backslashes or non-ASCII characters).
  • No writes. Nothing that changes the controller's state can be sent.
  • One request per call. Redirects are never followed (any 3xx raises), and a per-request middleware refuses any second hop, including aiohttp's transparent resend of idempotent requests.
  • Receive-only WebSocket. The socket is wrapped so that every send_*, ping and pong method raises; only the automatic pong to server pings and a CLOSE frame at shutdown ever go out.
  • Pacing. Requests run strictly one at a time, at least 1 s apart. The controller is a small single-board computer.
  • Secrets. The API token is held only in memory and never appears in logs, exceptions, repr() or files. Error messages never include response bodies or headers.
  • Redaction. Before anything reaches disk or diagnostics, every value that looks like a credential (passwords, keys, tokens) is replaced by a marker that keeps only its presence and type, for example <redacted len=8>.

Security advice

The controller's local API is plain HTTP and is meant for a trusted LAN.

  • Keep the controller on a trusted, preferably isolated, network segment (for example an IoT VLAN that only your Home Assistant host can reach).
  • Never expose the controller's ports (8000, 1337) to the internet, and do not port-forward them.
  • Use a dedicated controller account for aiorehom or Home Assistant, with the least privileges available, and a unique password.

rehom-probe

rehom-probe is a read-only diagnostic CLI. It uses the same allowlisted transport, so it cannot write anything either.

rehom-probe [--host rehomserver.local] [--port 8000] [--out DIR]
            [--username NAME] [--service rehom-api] [--no-login] COMMAND ...
Command Network What it does
session [--steps 1,3-5] allowlisted GETs, at most one login Runs the capture steps (below) and saves redacted results
watch [--minutes 30] [--with-latency] receive-only WebSocket (+ /api/alive/ every 15 s) Saves WebSocket frames to ws.jsonl (maximum 120 min)
latency [--seconds 120] /api/alive/ every 2 s Measures baseline latency (maximum 600 s)
inventory CAPTURE_DIR [--catalog FILE] [--show-names] [--json] none Human summary of a capture, with optional coverage against a record catalogue
diff DIR_A DIR_B [--ignore-volatile FILE] [--show-personal] [--json] none Record-level diff of two captures
redact-har IN.har OUT.har [--controller-host H] none Sanitises a browser HAR file
sanitize-fixtures CAPTURE_DIR OUT_DIR (--salt S | --random-salt) none Redacts again, then pseudonymises, to produce test fixtures
replay FIXTURE_DIR [--instant | --speed X] [--json] [--show-names] ... none Replays a capture through the real client on a virtual clock and prints the model changes

With --no-login, session skips every step that needs a login.

Session steps: (1) /api/alive/; (2) login and /api/me/; (3) /api/config/ (never with a query string); (4) /api/interface/; (5) /api/overrides/ and /api/plant/conf/; (6) the four domotica GETs; (7) 6 hours of history for a few plant and zone series; (8) two filtered interface GETs.

Output hygiene:

  • Every response is redacted before it is written. Files are created with mode 0600 and directories with mode 0700. The request log records method, path, status, latency and size, never headers or bodies.
  • inventory, diff and replay print to the terminal, which may end up in a chat transcript or an issue. They re-redact every file they read and mask personal data by default (zone and VMC names, locality, GPS position, MAC and IP addresses, serial numbers, user names); --show-names and --show-personal opt out.
  • sanitize-fixtures is deterministic for a salt (--salt or $REHOM_FIXTURE_SALT). Always review fixtures yourself before sharing them.

Default output directory: $REHOM_PROBE_CAPTURE_ROOT/<UTC timestamp>/, else ./captures/<UTC timestamp>/; --out overrides both. The record catalogue for inventory is --catalog, else $REHOM_PROBE_CATALOG; without one, coverage is skipped.

Credentials (macOS)

The probe reads the controller login from the macOS Keychain item rehom-api. Create it in your own terminal, so the password never appears in a shell history or a transcript:

security add-generic-password -s rehom-api -a <username> -w
# -w as the last argument makes `security` prompt for the password

Use --username NAME for a different account and --service NAME for a different Keychain item. On other systems, use the library directly: it takes the username and password as arguments.

Development

Everything runs offline: tests use aioresponses mocks and aiohttp test servers bound to 127.0.0.1, and a guard in tests/conftest.py blocks every non-loopback connection.

uv sync
uv run pytest -q                 # add --cov=aiorehom for coverage (>= 95 %)
uv run mypy --strict src
uv run ruff check . && uv run ruff format --check .
uv run rehom-probe replay tests/fixtures/20260925T102117Z   # the replay timeline, by eye

Test data:

  • tests/fixtures/20260925T102117Z/ is a sanitised (redacted and pseudonymised) 30-minute capture of one plant; see its README.md. tests/test_e2e_fixture.py replays it through the real client.
  • tests/golden/*.json are input/output vectors for the *_compat functions, which reimplement calculations of the controller's web UI. They were generated by running the web UI's JavaScript on seeded inputs. Only the generated JSON is stored; no vendor code is included. See tools/golden/README.md.

Disclaimer

aiorehom is an unofficial, independent project written for interoperability. It is not affiliated with, endorsed by or supported by Rehom S.r.l. "Rehom", "Radiax" and "RadiaxWeb" are trademarks of their respective owners and are used only to identify the compatible equipment. Use it at your own risk: the software is provided "as is", without warranty of any kind.

License

MIT, see LICENSE.

Metadata

Release files for aiorehom 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiorehom 0.2.0
File Size Uploaded
aiorehom-0.2.0.tar.gz 139.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aiorehom 0.2.0
File Interpreter ABI Platform
aiorehom-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 298.6 kB

Release files / aiorehom-0.2.0.tar.gz

Download URL aiorehom-0.2.0.tar.gz
Size 139.5 kB
Tags Source
SHA-256 checksum
How to use checksums
b6ace7c02adc09e7fdf6e630fb0442bc6c3e87cd7e8ff637868a68a0f4df2348
BLAKE2b-256 checksum
How to use checksums
abeac7190eb61fc329a1c3bb60c3992d29b923e044c1e8ee97f21dcb2cd0b1b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / aiorehom-0.2.0-py3-none-any.whl

Download URL aiorehom-0.2.0-py3-none-any.whl
Size 159.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cef13d4d3107f36f3c5daacc8300a11882e9fa0cbe60cb7d2e4dce67b1365ce1
BLAKE2b-256 checksum
How to use checksums
d09beb45b721e3518f91b7da19b05111d1435e0b80131d58879ccd8eed5732ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

2 release files

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page