Repository navigation
Conversation
A control may now declare asset, strategy, zone and write timings in the
thermal contract's vocabulary, and a driver may list the metrics that are
a heat store's temperature. /api/drivers/{name} exposes both. Nothing
plans for or sends a heat command because of them; this is the
declaration step of the proposal in srcfl#1994.
A string control may declare a closed set of options, which the control
API now enforces. A misdeclared thermal field drops the control, as an
unrenderable declaration already does. nestedBlock now requires the whole
field name, so temp_sensors is not read as sensors.
Refs srcfl#520
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: hannesb90 <bruhn90@gmail.com>
5 of 6 tasks
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>
hannesb90
force-pushed
the
feat/thermal-twin-shadow
branch
from
November 10, 2026 09:14
3e1efa8 to
5150469
Compare
6 tasks done
TestWatchedPathsCoverImports fails once drivers imports thermal, since a change there would skip the rig. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 3dc3014)
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.
Problem and result
Step 3 of ADR 0012 (#1994), following the owner's answer there: "Go ahead, starting with the twin in shadow." Planning heat needs to know how long a room coasts and how hard the heat source moves it. FTW measures neither today. This learns both, per declared room sensor, and commands nothing.
Model. One first-order twin per zone, fitted online by recursive least squares over 30-minute windows:
atau_h)bcapacity_kwh_per_k)a/bua_w_per_k)cother_gain_k_per_h)Inputs.
T_inis a sensor declared withasset = "space_heat"(feat(drivers): let a control declare what it does to heat #1995).T_outcomes from the same forecast cacheloadmodellearns from.Pishp_power_wfrom the sensor's own driver, or from the single other driver that reports it. With two candidates the twin does not guess: it learns without heat input and says so.Judgement. The twin is never judged by its own fit. Each three-hour stretch is simulated from the parameters it had at the start, using the inputs that really happened, and compared with the measured temperature. Status is
learning, thentrustedonce there is a week of windows, a day of judgements, physical parameters and a mean error ≤ 0.3 K. It becomesdriftingabove 0.5 K, orno_heat_inputwhen no heat input is available.GET /api/thermal/twinsreturns every twin withshadow: true. The route is also listed for analysis agents.Scope and safety
Commands nothing. No new path to
Registry. Per the owner's answer 3, an untrusted twin gives no planned control, and today no twin gives any.Layer, not core (docs(vision): a hard, simple core with layers on top #1772). The twin runs in its own goroutine and reads only
LatestMetric(noRawW/SmoothedW/ReadingsBy*) and the forecast cache. It never holds up the control tick. It fails soft: a missing catalog, sensor or outdoor temperature just skips windows.Bad data is rejected, not learned:
A changed source restarts the twin, because a fit to one source means nothing against another.
Persistence goes through
modelstatewith a feature fingerprint, so state fitted to another model form cold-starts.Learning steps: none, per the owner's answer 4. The twin learns only from ordinary operation and owner holds.
Only
space_heatzones. A tank or buffer has the same form, but it loses heat to the room it stands in, which no sensor here gives yet.Known limits, measured in tests rather than assumed:
TestTwinFollowsAChangedHousepins this.bis learned against electrical input, so it includes COP, which varies with outdoor temperature. That is good enough for a shadow twin, and it is a known refinement before planning.Verification
Raspberry Pi 4 (linux/arm64), Go 1.26.10, pinned drivers fetched:
make verifylocally.Checklist
🤖 Generated with Claude Code