This repository contains a set of Python daemons, shell helpers, an Arduino
sketch, and systemd units used to monitor and control a home heating and
power setup through Home Assistant.
The project combines:
- Boiler control (PID-based water temperature setpoint control).
- OpenTherm gateway polling and command dispatch.
- Inverter telemetry collection.
- Mercury 230 power meter polling.
- Furnace/kiln controller polling over Modbus ASCII.
- 1-Wire temperature ingestion.
The system is built around periodic poll/push loops:
- Hardware is read through serial ports or 1-Wire pseudo-files.
- Data is normalized into Home Assistant entities via REST
POSTto/api/states/.... - Control values (for example, boiler setpoint) are read from Home Assistant via REST
GET. cycler.shschedules one-shot scripts as recurring jobs undersystemd.
There are two parallel integration tracks:
- Legacy track: scripts use
tokenandhostfromsecrets. - New track: scripts prefixed with
new_usenew_tokenandnew_host(for a newer HA environment / VM-backed setup).
- Main legacy heating control loop (executed periodically, not a persistent loop itself).
- Reads boiler settings and temperatures from Home Assistant entities.
- Reads thermostat target (
climate.kitchen) and current indoor temperature. - Maintains PI(D)-like state in
/tmp/pid.json(E,I,D, timestamp, output). - Computes target heat carrier temperature (
calc_heatant), clamps to configured min/max. - Applies anti-freeze protection if heat carrier temperature is too low.
- Writes:
- Thermostat hvac state (
heat/off) back to HA. - Boiler setpoint sensor (
sensor.heatant_setpoint) back to HA.
- Thermostat hvac state (
- Includes a failsafe path: on error, forces heating with a high default temperature.
- New-environment equivalent of
boiler_control.py. - Uses fixed local settings in script (instead of reading settings entities at runtime).
- Reads thermostat from
climate.main_climateand heat carrier temp frominput_number.heatant_temp. - Writes setpoint to
input_number.heatant_setpoint. - Keeps same PI regulator pattern and
/tmp/pid.jsonpersistence logic.
- Legacy OpenTherm gateway poller for
/dev/otgw. - Reads desired setpoint from HA (
sensor.heatant_setpoint), with optional CLI override. - Sends gateway shell commands:
s <temp>to set target.gto fetch boiler JSON state.
- Publishes returned values to HA:
- Heat carrier temperature (
sensor.heatant_temp). - Boiler modulation (
sensor.boiler_power). - Boiler fault (
sensor.boiler_fault).
- Heat carrier temperature (
- Retries malformed/non-JSON serial responses up to a max retry count.
- New-environment OpenTherm poller.
- Reads/writes
input_number/input_booleanentities instead of legacysensorentities. - Adds flame state publication (
input_boolean.boiler_flame). - Converts boolean fault/flame fields into HA-compatible
on/offstate strings. - Contains prepared (currently commented) update path for climate state synchronization.
- Arduino firmware implementing the serial-side OpenTherm gateway protocol used
by
otgw.py/new_otgw.py. - Uses OpenTherm and Shell libraries.
- Exposes serial commands:
g-> JSON boiler snapshot (Tout,Tin,Modulation,Fault,Flame, etc.).s <temp>-> set boiler target temperature; disables heating below threshold.p-> ping/status.
- Polls boiler on interval and updates internal state fields used by
g.
- Health-recovery helper for missing
/dev/otgw. - If symlink is absent, power-cycles a specific USB device via
/sys/bus/usb/.../authorized. - Used as an automated self-healing action by
otgw_checker.service.
- Legacy MAP inverter poller for
/dev/ttyMAP(19200 baud). - Sends vendor commands (
PCRD...) and parsesMOk..responses. - Publishes inverter mode and grid/battery measurements into HA
sensor.*entities. - Supports decoded mode string mapping from numeric state.
- New-environment version targeting
input_text/input_numberentities. - Adds additional power metric (
Pgrid) compared to the older flow. - Keeps same serial protocol and command/response strategy.
- Continuous poller for Mercury 230 meter (
/dev/moxaby default). - Implements low-level protocol framing and CRC16 calculation internally.
- Collects:
- Per-phase voltage/current/cos(phi).
- Derived active and reactive power.
- Total active/reactive sums.
- Frequency.
- Energy sums (including T1-T4 style channels).
- Flattens nested measurement tree into HA entity IDs (
sensor.m230_*) and posts them. - Includes safety/failure gates:
- Overvoltage/overcurrent detection.
- Refusal to publish partial/bad payloads if acquisition fails.
- Poller for TRM251-like furnace/kiln controller over Modbus ASCII (
minimalmodbus). - Reads state, temperature, and power from specific registers.
- Publishes to:
sensor.furnance_tempsensor.furnance_powersensor.furnance_state
- Persistent retry loop with high error tolerance and auto-restart behavior.
Note: file/service names use furnance spelling consistently in this repository.
- Reads local 1-Wire temperature files from
/1wire/.../temperature. - Pushes values to HA entities (kitchen and street temperature sensors).
- Acts as a bridge between filesystem-exported sensor values and HA REST states.
- Generic loop runner for one-shot scripts:
- Runs
/usr/local/sbin/<script> - Sleeps
<interval> - Repeats forever
- Runs
- Default:
wire2ha.pyevery 60 seconds. - Used by several
systemdservices to standardize periodic execution.
- Udev persistent symlink rules:
- OpenTherm adapter ->
/dev/otgw - Bluetooth stick ->
/dev/btstick - MAP interface ->
/dev/ttyMAP
- OpenTherm adapter ->
- Stabilizes device discovery across reboots and USB enumeration changes.
- Runs
boiler_control.pyviacycler.shevery 60s. - Depends on
hass.serviceandwire2ha.service.
- Runs
otgw.pyevery 60s. - Depends on
hass.service.
- Runs
mapgw.pyevery 60s. - Depends on
hass.service.
- Runs
wire2ha.pyevery 60s. - Depends on
owserver.serviceandhass.service.
- Runs
m230.pyas a continuously looping process.
- Runs
furnance.pycontinuously. - Adds/removes an NPort Real COM mapping (
mxaddsvr/mxdelsvr) on service start/stop.
- Runs
new_boiler_control.pyevery 60s. - Depends on
qemu-kvm.service.
- Runs
new_otgw.pyevery 60s. - Depends on
qemu-kvm.service.
- Runs
new_mapgw.pyevery 60s. - Depends on
qemu-kvm.service.
- Runs
otgw_checker.shevery 60s viacycler.sh. - Depends on
qemu-kvm.service.
All Python integration scripts follow the same pattern:
- Import
token/hostornew_token/new_hostfromsecrets. - Use HA REST API headers:
Authorization: <token>content-type: application/json
- Use
/api/states/<entity_id>for both reads and writes.
Expected local dependency:
- A local
secrets.py(not committed) containing API endpoint and auth token values.
Expected device paths:
/dev/otgwfor OpenTherm adapter./dev/ttyMAPfor inverter interface./dev/moxa(or configured equivalent) for Mercury 230 meter./dev/ttyr00for furnace controller./1wire/...filesystem for temperature sensors.
Key milestones from project history:
- Initial platform bootstrap: May 2020
- Added boiler, OpenTherm, inverter, 1-Wire scripts and basic services in the first commit.
- Device stability and platform hardening: June 2020 to January 2021
- Added persistent USB aliasing (
99-usb-serial.rules). - Removed unnecessary service dependency from
otgw.service.
- Mercury 230 growth: June 2020 to May 2023
- Introduced as a separate module, then iteratively expanded.
- Added Home Assistant posting, friendly names/units, energy counters, reactive power, and data-quality guards.
- Added overcurrent and tighter overvoltage checks.
- Boiler control maturation: January 2021 to December 2021
- Switched to PI-based regulation and tuned PID constants.
- Increased debug visibility and refined behavior.
- Furnace integration: May 2021 to July 2021
- Added dedicated monitor script and service.
- Migrated to Python 3, Modbus ASCII, and NPort start/stop hooks.
- OpenTherm firmware support: September 2021
- Added Arduino sketch implementing gateway protocol consumed by Python pollers.
- New-environment migration: September 2025 to November 2025
- Added
new_*gateway/control scripts and services tied toqemu-kvm.service. - Mapped outputs to alternate HA entity model (
input_number,input_boolean,input_text). - Extended new inverter flow with
Pgrid.
- Self-healing operations: November 2025
- Added
otgw_checker.sh+ service to recover missing OpenTherm USB path.
- Most scripts are intentionally simple and state-light; reliability is delegated to:
systemdrestart policies.cycler.shperiodic execution.- Retries and fail-safe defaults inside scripts.
- Debug logging is widely enabled in current source (
debug = True/DEBUG = Truein many files), useful for diagnostics but potentially noisy in production logs. - Several entities are written directly under
/api/states, which creates state objects but does not replace proper native integrations where available.
Typical deployment model inferred from service files:
- Install scripts to
/usr/local/sbin/. - Install service units to
/etc/systemd/system/. - Reload and enable units:
systemctl daemon-reloadsystemctl enable --now <service>
- Ensure device rules and dependencies are present (
udev, serial drivers,minimalmodbus,requests, Home Assistant availability).
- Boiler control:
boiler_control.py,new_boiler_control.py,boiler_control.service,new_boiler_control.service - OpenTherm:
otgw.py,new_otgw.py,opentherm.ino,otgw.service,new_otgw.service,otgw_checker.sh,otgw_checker.service - Inverter:
mapgw.py,new_mapgw.py,mapgw.service,new_mapgw.service - Power meter:
m230.py,m230.service - Furnace:
furnance.py,furnance.service - 1-Wire:
wire2ha.py,wire2ha.service - Infrastructure:
cycler.sh,99-usb-serial.rules