Skip to content

feat(lk_arc): read-only LK Systems LK Arc gateway over Modbus TCP - #223

Open
hannesb90 wants to merge 2 commits into
srcfl:mainfrom
hannesb90:feat/lk-arc
Open

hannesb90 wants to merge 2 commits into
srcfl:mainfrom
hannesb90:feat/lk-arc

Conversation

@hannesb90

@hannesb90 hannesb90 commented Nov 10, 2026 •

Copy link
Copy Markdown

Summary

A new read-only driver for the LK Systems LK Arc underfloor heating gateway
over Modbus TCP. Toward srcfl/ftw#520: room temperatures from wall
thermostats on a heating system FTW cannot read today, for a later heat
planner to judge comfort by.

It emits metrics only, no power stream. Per zone with a paired thermostat:

  • zone_<n>_temp_c, zone_<n>_humidity_pct, zone_<n>_setpoint_c
  • zone_<n>_battery_pct, zone_<n>_signal_dbm
  • zone_<n>_actuators_connected, zone_<n>_actuators_open_pct,
    zone_<n>_heating (any of the zone's actuators open)
  • zone_<n>_override, zone_<n>_override_level_pct

Gateway-wide: actuator_<a>_open for each allocated actuator,
lk_operating_mode (1 heating, 2 cooling) and lk_poll_ok.

Driver evidence

  • Driver id: lk_arc 0.1.0, community tier.
  • Device make/model: LK Arc gateway with three paired zones and seven
    actuators.
  • Firmware tested: gateway software 0.53.0 (input registers 4-6).
  • Protocol/register source: LK Systems "LK Arc Modbus Registers"
    (SENOFIEN.33.J.4, 2026-10-09), in the manifest's upstream_docs. The list
    numbers registers from 1 (3:00101), so the wire address is the listed
    number minus one. Confirmed on hardware: input 100 held zone 1's room
    temperature and 101 its humidity.
  • Hardware test performed, 2026-11-10:
    • The driver ran in FTW's own Lua engine (drivers.NewLuaDriver with a
      real Modbus TCP client, from a temporary program that is not part of
      this PR) against the gateway, for two polls: all three zones reported
      temperature, humidity, setpoint, battery, signal and actuator state, and
      empty zones 4-12 were skipped. The temperatures (21.1, 20.8 and 19.0 °C)
      matched a direct register read made a minute later.
    • FTW's POST /api/drivers/test could not be used. It waits 8 s for the
      first telemetry, and one poll against this gateway takes longer (see
      below). Telemetry reaches the store only when a poll ends.
    • make test-driver ID=lk_arc and make check pass.
      absent-register-report: every register settles.
    • The harness test also counts requests: eleven on the scan poll, three
      on an ordinary poll, settings every fifth poll. Deliberate breaks of the
      zone pairing and of the settings cadence fail it.
      invented-number-report: nothing invented.
    • A Lua harness test uses that gateway's register values, with the gateway
      serial and thermostat addresses replaced by made-up numbers. It also
      covers closed actuators, a negative temperature, an active override,
      out-of-range values, no answer at all, one zone that stops answering,
      and a register that keeps failing and is retried at the next scan.
      Deliberate breaks of the give-up and the empty-zone check fail it.
  • Known limits:
    • The gateway is slow. It is on Wi-Fi here, with ping round trips of
      0.6-1.8 s, and a single read took up to about 3 s, sometimes past the
      host's 5 s request timeout. The host withholds a whole poll's telemetry
      when any read in it fails to reach the device, and the first version
      of this PR (eight requests per poll) failed about every other poll in
      FTW. The driver therefore keeps requests few:

      • two neighbouring zones are one request: 108 input registers from zone
        n's base cover zone n and n+1, within Modbus' 125 (the gateway
        answers zeros, not an exception, for the undocumented addresses in
        between, checked across 0-5000);
      • setpoints, overrides and the operating mode are read every fifth
        poll, and not re-emitted from an earlier read in between;
      • serial and software version are one request;
      • zones are scanned once and re-checked every 30 polls; a scan in which
        some zones did not answer keeps what the last scan said about them
        and is repeated after five polls;
      • it polls once a minute via host.set_poll_interval (an interval
        returned from driver_poll is capped at 30 s by the host) and asks
        for a 180 s watchdog window via host.set_watchdog_timeout_s;
      • a register that fails three polls in a row is left alone until the
        next scan.

      With three zones an ordinary poll is three requests (actuators, zones
      1-2, zone 3). After that change the driver ran as a configured FTW
      driver for ten minutes: every poll succeeded, none failed.

    • The gateway accepts one TCP connection at a time, and closes an
      idle one between polls (FTW logs a reconnect before each poll, with no
      failed read). A second Modbus client on the same gateway makes FTW's
      reads time out.

    • "Gateway number of zones" (3:00051) read 0 on a gateway with three
      paired zones, so it is not used. A zone counts as present when its
      thermostat address is non-zero. An empty zone answers all zeros.

    • How often a thermostat updates its reading is not measured yet. The
      readings changed between polls minutes apart, so it is not hours as with
      some battery sensors.

    • What the gateway reports for a thermostat that has lost contact is
      unknown.
      The list documents no stale flag, and none of the test
      thermostats dropped out.

    • The gateway answered any unit id.

    • ders: [heatpump] is the closest of the allowed DER kinds. The gateway
      is a heating controller, not a heat pump.

Safety

  • The driver starts read-only, or this is a separately reviewed control change.
  • Vendor signs are converted at the driver boundary. (No power stream is emitted.)
  • Cached telemetry becomes stale instead of being re-emitted as fresh.
  • No credentials, serial numbers, private addresses or site data are included.

The thermostat addresses (SN/MAC) are read only to tell a paired zone from an
empty one, and are never emitted or logged. Only read function codes 3 and 4
are used.

Control evidence

  • HIL evidence or required: not applicable, read-only.
  • Control changes include default-mode, lease-expiry and HIL evidence.

The register list has per-zone target temperature, override and override
level (0-255 = 0-100 % actuator opening). They are noted at the end of the
driver for a later control step. The list says nothing about the gateway
taking control back by itself, so that step needs its own lease and a
default that clears the override.

Checks

  • Commits include Signed-off-by.
  • make test-driver ID=lk_arc
  • make check

🤖 Generated with Claude Code

hannesb90 and others added 2 commits November 10, 2026 15:39
Reads LK's published Arc register list over Modbus TCP: per zone the
room temperature, humidity, setpoint, override state, thermostat battery
and signal strength, and how many of the zone's actuators are open.

The list numbers registers from 1, so the wire address is the listed
number minus one. Zones are found from a non-zero thermostat address,
since an empty zone answers all zeros, and re-checked every half hour.
The gateway is slow (up to about 3 s a read), so it is polled once a
minute via set_poll_interval with a 180 s watchdog window, each zone is
emitted as soon as it is read, and a register that fails three polls in
a row is left alone until the next scan. Thermostat addresses are never
emitted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: hannesb90 <bruhn90@gmail.com>
In FTW the driver failed about every other poll. The test gateway is on
Wi-Fi with 0.6-1.8 s round trips, so a read sometimes exceeds the host's
5 s request timeout, and the host then withholds the whole poll.

- Two neighbouring zones are one request: 108 input registers from zone
  n's base cover zone n and n+1 (the gateway answers zeros between).
- Setpoints, overrides and the operating mode are read every fifth poll.
  They are not re-emitted from an earlier read in between.
- Serial and software version are one request.
- A scan in which some zones did not answer keeps what the last scan
  said about them and is repeated after five polls.

With three zones an ordinary poll is three requests instead of eight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: hannesb90 <bruhn90@gmail.com>
@hannesb90
hannesb90 marked this pull request as ready for review November 10, 2026 16:15

@miravoss26 miravoss26 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds a read-only LK Systems LK Arc gateway driver over Modbus TCP (zone temp, humidity, setpoint, override level).

  • Security: nothing found. No writes, secrets, new dependencies or hosts. Control registers are only documented in a trailing comment, with a sensible note that a control step needs its own bounded lease.
  • Correctness: range guards on temp, humidity and setpoint keep garbage register values out of the metrics. The override level conversion (255 to percent) rounds sensibly. Failure bookkeeping is per kind and address, so one dead register does not cost a timeout every poll.
  • Note: verified on one gateway with three zones. experimental is the right status. No CI checks were attached when I looked.

Safe to merge from my read. I only skimmed the register map against the LK list, so a human should check that part.

@hannesb90

Copy link
Copy Markdown
Author

The register map has been checked by a human against the LK list.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants