Repository navigation
Conversation
…ed twin Proposes ADR 0012: per-zone target temperatures, a learned thermal twin (heat loss, inertia, control response, dead time) and a Go planner that chooses on/off, setpoint or offset per slot inside the owner's band. Drivers declare what a control does through optional fields on the existing controls block. Status is proposed; nothing ships. Refs srcfl#520 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: hannesb90 <bruhn90@gmail.com>
|
Owner answers (Fredrik, 10 November 2026):
|
|
Correction to the answers above: VISION.md is Fredrik's and takes no changes from outside PRs. Leave VISION.md and docs/roadmap.md out of this PR; Fredrik updates them himself. I have edited point 1 to say so. |
Fredrik answered the open questions on srcfl#1994. The ADR now records them: the twin and planner are a layer that proposes and Core checks; planned control needs a device-side timed hold (device_hold_max_s), otherwise one declared step from input.default; an untrusted twin gives no planned control; no learning steps; release the shared source on a zone conflict; Core names no first device. VISION.md and the roadmap are left to Fredrik. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: hannesb90 <bruhn90@gmail.com>
The owner's answer on srcfl#1994: planned control needs the device to fall back on its own through a device-side timed hold; otherwise planned values stay a small step from the device default. A control may now declare device_hold_max_s, and a number input its default, so Core can own that lease rule. A zero or non-numeric hold, or a default outside min..max or on a non-number input, drops the control. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: hannesb90 <bruhn90@gmail.com>
Step 3 of ADR 0012 (srcfl#1994). For every room sensor a driver declares, FTW fits dT/dt = a(Tout - Tin) + b*P + c by recursive least squares over 30-minute windows: inertia, capacity, heat loss and other gains. The twin is judged by simulating each three-hour stretch from the parameters it had at the start with the inputs that really happened, and reports learning, trusted, drifting or no_heat_input. It reads LatestMetric and the load model's outdoor temperature, persists through modelstate, and is shown at GET /api/thermal/twins with shadow: true. It commands nothing and nothing plans from it. Refs srcfl#520 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: hannesb90 <bruhn90@gmail.com>
miravoss26
left a comment
There was a problem hiding this comment.
Read the full ADR (docs only, 1 file). It proposes target-temperature heat control: a learned per-zone twin in shadow first, a planner that proposes values, Core checking bounds/lease/min on-off/stale release.
- Question, DHW (hot_water example):
target_c: 50withflex_c: 5allows the tank to sit at 45 °C. That is inside the legionella growth range. Worth either a floor onasset: dhwzones or a note that the example is illustrative only. - Nit, Status line: says "accepted; being built" while the PR is still open. Fine if intentional, but it reads ahead of the merge.
- Gap, safety table: the 'measured temperature outside band' reaction is owned by the layer, so if the layer is down that case relies purely on the device-side hold. That is consistent with the lease design, just worth saying explicitly for controls with no hold (they are limited to one step from default, which covers it).
No code, no secrets, no new hosts. Safe to merge from my read as a proposal; the DHW floor is the one thing I would want a human to decide.
From review on srcfl#1994: - A dhw zone never gets a band below 55 °C. Core owns the floor: it rejects such a band in config and releases the tank to the device below it. The value is listed under Open for the owner. The example hot-water zone now targets 60 °C (band 55-65). - Status reads "proposed" until the PR merges. - The safety section says what holds when the layer is down: the lease, and the one-step limit for a control without a hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: hannesb90 <bruhn90@gmail.com>
|
Thanks for the review. All three are in 31029dc:
Follow-up in d25527d: the rule is cited as BFS 2024:8, chapter 8, section 6 (at least 50 °C at the tap). BBR, which I first cited, has been repealed. |
BBR was repealed; the 50 °C at the tap is now BFS 2024:8, chapter 8, section 6. Link Boverket's guidance and note its 60 °C recommendation for the water heater. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: hannesb90 <bruhn90@gmail.com>
A fixed floor forbade waiting for cheap energy after a shower, though Legionella risk comes from water lukewarm for days, not hours. The dhw floor is now 50 °C at the top of the tank; the tank may sit under it for at most 2 hours and never under 45 °C, then Core releases it to the device. The numbers stay open for the owner, with the sources they rest on, and holding off a reheat needs a declared control. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: hannesb90 <bruhn90@gmail.com>
|
One change to the DHW floor since my reply above, in feb9bed. A fixed 55 °C floor would forbid waiting for cheap energy after a shower, while the Legionella risk is water lukewarm for days rather than a dip of a few hours. The floor is now:
The three numbers are listed under Open for Fredrik, with the sources they rest on (industry figures for growth, not measurements). The ADR also notes that delaying a reheat needs a driver control that can block or lower the charge. |
miravoss26
left a comment
There was a problem hiding this comment.
ADR 0012 (docs only): the owner sets a target temperature per zone, a learned thermal twin per zone proposes control values, and Core checks them against declared bounds, a device-side lease and min on/off times.
Read as a design, it holds together. The split of who owns which protection (the safety table in section 6) is the part I liked most. Notes:
- Hot-water floor (Open). 50 °C floor, max 2 h under it, never under 45 °C, enforced in Core. That fits the Legionella reasoning and the BFS figure you cite. Marked as proposed and awaiting Fredrik, so this needs a human call before merge, not mine.
- Status line contradicts itself. It says "proposed" and also "accepted when this PR merges". Fine, but worth one clear word at merge time so the ADR index doesn't show it as proposed forever.
- Shared-source infeasibility. The release-and-tell-the-owner path is right. Worth stating how often the planner re-tries after a release, otherwise a flapping conflict could release/reclaim every slot. Question, not a blocker.
- No-lease controls are limited to one
stepfrominput.default: good, that keeps the layer-down case bounded.
No secrets, code or new network destinations in the diff. Needs a human decision on the hot-water numbers; otherwise fine to land.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks. 5c7346f:
|
… on hardware Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
fd57fb3 adds three things learned on a site with both direct electric floor heating and a heat pump: the planner costs heat per kWh of heat, using efficiency (COP 1 against 3-4), and not per kWh of electricity; a twin bullet on zones that lose heat mostly to their neighbours; and the two device-side holds seen on hardware (Wiser timed override, CTC 1000-series). Where the COP comes from is listed under Open. |
miravoss26
left a comment
There was a problem hiding this comment.
Docs-only: ADR 0012 proposing heat control from a target temperature + flex per zone. Status 'proposed; nothing ships', no code or config touched.
- Security screen: nothing to flag (docs only).
- This is a design call, not something I can sign off: the ADR is where the owner-facing semantics (target + flex, legionella handling, what core may command) get decided, so it needs a human decision rather than a 'safe to merge'.
Read-through shows no internal contradictions with #1995/#1996 as referenced.
Problem and result
FTW can move a heat device only by hand today. Drivers declare
controls,POST /api/drivers/{name}/controlholds a value for at most 24 h, and theHeating view renders it. Core itself never sends a heat command, and
go/internal/thermalis imported by nothing. #520 asks for heat-pump controldriven by price and site state, and splits it into read-only attribution and
a separate opt-in active-heat policy. This PR proposes the shape of that
policy as ADR 0012. Status: proposed; nothing ships.
In short:
comfort sensor. Each zone lists the controls that heat it. A control shared
by several zones, such as one heat pump's curve offset, is planned once for
all of them; local controls such as room thermostats trim each room.
(seeded from
loadmodel), inertia, control response and dead time. It runsin shadow, predicting only, until a confidence gate opens.
the existing price, PV and weather horizon, with the band and the
compressor timings as hard constraints. Preheating and storing PV as heat
follow from the learned inertia. Energyplan can take over later through
the already reserved
thermal_loads/thermal_statefields behind acapability.
asset,strategy,zone,min_interval_s,min_on_s/min_off_sfields and asensorslist. Core never learns a device by name, and existing drivers are
unaffected.
Owner's answers (10 November) are now recorded in the ADR's Decisions
section. The twin and the planner are a layer that proposes values, and
Core checks them, as #1772 describes. Planned control needs a device-side
timed hold (
device_hold_max_s); without one, values stay within onedeclared
stepofinput.default. An untrusted twin gives no plannedcontrol, and
thermal.DecideIntentgoes. There are no learning steps and nozone ranking, and Core names no first device. VISION.md and the roadmap are
left to Fredrik, per his correction.
Implementation, as separate PRs:
asset,strategy,sensors, timings, options)removes the unused
thermalcodeScope and safety
Docs only: one new file,
docs/adr/0012-heat-control.md. Numbered 0012because 0010 is taken by #1632. No open PR touches
thermal,catalog_controls.goorapi_driver_control.go. #1859 touchesweb/heating.js, which this does not change.The proposed protections follow
docs/safety.mdclamp discipline: declaredbounds, stale or missing comfort reading, measurement outside the band,
untrusted or drifting twin, write rate, short cycling and lease expiry. In
each case the device is released to its own
driver_default_mode. Thesite-meter and watchdog paths stay ahead of all of them.
Verification
Not applicable to runtime: this is a proposal. Every claim about current
behaviour was checked against
masterat7350a3e. If accepted, theimplementation would need, in separate PRs:
sensors, exposed on/api/drivers/{name}. No behaviour change.apiso the operator path andthe planner share one owner. No behaviour change.
Evidence: held-out backtests and site data, not simulation alone.
for each protection. A
minorchangeset, for the owner to approve.srcfl/device-driverswith hardware evidence(model and firmware) for the first case.
Checklist
🤖 Generated with Claude Code