feat(nibe_local): opt-in Solar PV surplus write path (srcfl/ftw#537) - #46
Draft
HuggeK wants to merge 1 commit into
Draft
feat(nibe_local): opt-in Solar PV surplus write path (srcfl/ftw#537)#46HuggeK wants to merge 1 commit into
HuggeK wants to merge 1 commit into
Conversation
The NIBE local driver gains its first write path, and deliberately its only one: the pump's native Solar PV surplus feed. The S-series was built to take a live "available solar power" number from NIBE's Modbus accessory (registers 2107 enable / 2109 available power) and soak the surplus into heating and hot water using owner-tuned offsets. FTW now acts as that accessory. Control by hint — the pump's firmware decides what to do with the number, so a wrong value degrades to wasted comfort, never to unsafe operation. Off by default and triple-gated: the host `capabilities.http.allow_write` grant, the driver's `write.solar_pv: true` with a mandatory `write.max_w` clamp ceiling, and the owner-side enable on the pump itself (register 2107). Local-API variableIds are resolved from each point's modbusRegisterID at poll time, so 2107/2109 are never hard-coded. Safety posture (each clamp answers a quantified risk): - value clamped to [0, max_w] so a sign bug or telemetry spike cannot tell the pump there is 100 kW of surplus; - deadband + at-most-one-write-per-interval against register churn; a decrease past the deadband is written immediately (surplus collapsed = safety direction); - dead-man's switch clears the feed when commands stop — the pump's timeout for a silently stopped feed is undocumented, so the driver does not lean on it; - driver_default_mode clears the feed on watchdog trip / stale site meter / driver stop, rate-limited and idempotent; - a startup sweep clears a non-zero feed a crashed run left standing; - the clearing machinery stays armed even when a config mistake (missing max_w) refuses NEW writes, so a mistake never strands a standing feed. The Local REST API's most dangerous habit is covered: it rejects a write *inside* an HTTP 200 with a per-point "error: read only value" result. The driver judges success on the body, never the status code, and surfaces a read-only pump as an actionable error naming the installer menu (7.5.15). Requires `host.http_patch` from the FTW core; on older cores the driver says so and stays read-only. The header documents the decommission step (turn 2107 off, or set the API read-only) for the day FTW is no longer there to clear the feed. Editing this promoted driver drops its byte-identity exemption from baselines/ftw, so the full catalog convention suite now applies to it. Brought it into compliance without changing behaviour: - wrapped the read path's http_get and json_decode in pcall; - lowercased the three mutable interval locals so driver_poll's returns match the poll-interval convention (they are reassigned from config, so lowercase is the honest style; genuine constants stay UPPERCASE); - renamed resolve_pv_registers -> resolve_pv_points: the `_registers` suffix made test_no_undefined_locals read a declared local function as an undeclared register guard. authors is set to "Claude Code (with the help of HuggeK)" per request. Tests: a fake-pump Lua harness (test_nibe_local_ftw.lua) exercising the clamp, deadband, sign conversion, pump-side gates, dead-man's switch, orphan clear, sentinel skip and default-mode idempotence; host_mock gains an http_patch mock; spec/host-api-profile.json declares http_patch. Signed-off-by: Hugo Karlsson <48095810+HuggeK@users.noreply.github.com> Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.com>
Member
|
Automated maintenance pass: not merging this one, and flagging it for a maintainer rather than acting further. Reasons, in order of weight:
The safety reasoning in the PR body (register risk classification, triple-gating, dead-man's switch) reads carefully thought through, and the write-path design looks sound on review. Flagging for a maintainer to: (a) approve CI so it actually runs, (b) decide when/whether to pursue HIL acceptance on real hardware, and (c) coordinate the FTW-side dependency before this leaves draft. Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
nibe_localgains its first write path, and deliberately its only one: thepump's native Solar PV surplus feed (registers 2107 enable / 2109
available power). The NIBE S-series was built to take a live "available solar
power" number from NIBE's own Modbus accessory and soak the surplus into
heating and hot water using owner-tuned offsets. FTW now acts as that
accessory — control by hint: the pump's firmware decides what to do with
the number, so a wrong value degrades to wasted comfort, never to unsafe
operation.
Tracks srcfl/ftw#537.
Safety posture
Off by default and triple-gated:
capabilities.http.allow_write(the new FTW-core write grant),write.solar_pv: truewith a mandatorywrite.max_wclamp ceiling,Every clamp answers a quantified risk:
[0, max_w]— a sign bug or telemetry spike must not tell the pump there are 100 kW of surplus;driver_default_modeclears the feed on watchdog trip / stale site meter / driver stop (idempotent, rate-limited);max_w) refuses new writes, so a mistake never strands a standing feed.The Local REST API's most dangerous habit is covered: it rejects a write
inside an HTTP 200 with a per-point
error: read only valueresult. Thedriver judges success on the body, never the status code, and surfaces a
read-only pump as an actionable error naming installer menu 7.5.15. The
driver header documents the decommission step (turn 2107 off, or set the API
read-only) for the day FTW is no longer there to clear the feed.
Requires FTW core
host.http_patchThe write uses a new host verb,
host.http_patch, added in a companionsrcfl/ftw PR (host-side gate
capabilities.http.allow_write). On a corewithout it the driver detects the absence, says so, and stays read-only —
so this driver is safe to publish ahead of the core change.
Catalog-convention compliance (why the driver body changed beyond the write path)
Editing this promoted driver drops its byte-identity exemption from
baselines/ftw, so the full catalog convention suite now applies to it.Brought it into compliance without changing behaviour:
http_getandjson_decodeinpcall(matches theesphome-dsmridiom);setup_retry_ms,poll_interval_ms,full_refresh_ms) sodriver_poll's returns match the poll-interval convention — they are reassigned from config, so lowercase is the honest style; genuine constants stayUPPERCASE;resolve_pv_registers→resolve_pv_points: the_registerssuffix madetest_no_undefined_localsread a declared local function as an undeclared register guard.baselines/ftwis intentionally left untouched — the baseline means "field-proven, Go-tested", which this new write path is not yet.Why 2107/2109 specifically (register risk classification)
The Solar PV feed is the safest writable register class on the S-series:
it is control-by-hint and self-clearing in intent — the whole design
point is that FTW keeps feeding a number the pump interprets, and stopping the
feed (or the dead-man's clear) returns the pump to its own behaviour. Contrast
with persistent-state registers (degree minutes → short-cycling, operating
mode, DHW/legionella, heat curve) which latch and can harm the pump or comfort,
and forbidden ones (alarm/fault reset, sensor spoofing, defrost) which the
driver never exposes. 2108 ("include own consumption") and the offset
aggressiveness stay owner-tuned on the pump and are never written.
Tests
test_nibe_local_ftw.lua: clamp, deadband, sign conversion, pump-side gates (2107 disabled, read-only 200 rejection), dead-man's switch, orphan clear,u16sentinel skip, rate-limit decrease bypass, default-mode idempotence, invalid-max_wkeeps clearing armed.host_mock.luagains anhttp_patchmock;spec/host-api-profile.jsondeclareshttp_patch.nibe_local(convention +test_no_undefined_locals);make checkvalidators (sync/validate/host-api/history/baseline/sandbox) pass.🤖 Generated with Claude Code