Commit Graph
7 Commits
Author SHA1 Message Date
stroblmeandClaude Opus 5 4bdc0ea04a Panels for a ten-inch screen, and a motor button that says where it is
Docs / docs (push) Canceled after 0s
Playwright Tests / test-playwright (1, 2) (push) Canceled after 0s
Playwright Tests / test-playwright (2, 2) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Test Backend / test-backend (push) Canceled after 0s
Compose Smoke Test / test-compose (push) Canceled after 0s
Playwright Tests / merge-reports (push) Canceled after 0s
Both screens the house is looked at on are 1280x800, so that is what the three
dashboards are laid out for: twelve columns of 96px, twelve rows of 51px, and
nothing past the bottom, because a panel does not scroll.

The motors are one control each instead of three buttons. A button could only
publish; a segmented control reads back as well — so the motor writes what it
is doing to the same message the control sets, and the segment that is held is
the direction it actually went. Up, Stop, Down for the shutters; Close/Open for
the window and In/Out for the awning, which is what those two are for.

A run stopped part way now leaves the position unknown rather than claiming the
target it never reached, so the next command in either direction moves it.

The preflight gained the two checks this needed. One runs each sample shape
past the port that would receive it. The other is arithmetic: every tile inside
the panel and none on top of another — both silent failures on a screen with no
scrollbar, and both caught before anything is written.

Sizes were settled by looking. A slider needs three rows or its tick labels
fall off; a status icon needs three or it loses the word under the glyph; a
gauge in two rows has no arc worth reading, so the battery is a bar on Home and
a gauge on Energy where there is height for one. A chart spends eighty pixels
on its chrome whatever it is given, so two of them read on this panel and three
did not — the temperature history is the one that went, and `history` still
answers for it.

`capture-panels.mjs` is how that was checked: the three panels at the screen's
own pixels, in both themes, reporting whether anything spilled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 18:50:03 +02:00
stroblmeandClaude Opus 5 13cdfa79b9 power: the Cerbo keepalive sends an empty payload, not the time
An inject left to itself emits the current time. That went into a port
declared `str`, the node raised on every tick, and after five the supervisor
quarantined the whole `power` flow — which is every Victron reading in the
house. It was invisible because Node-RED is still sending its own keepalive,
so the Cerbo kept publishing anyway; the first thing to notice would have
been the data stopping some minutes after Node-RED did.

The preflight now checks what an inject emits against the port that receives
it. Nothing else could: an inject has no source to run, and both halves of
the declaration agreed with each other while disagreeing with reality.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 16:40:44 +02:00
stroblmeandClaude Opus 5 dda1822fa9 hvac: cooling has thresholds of its own, and the stove heats first
Watching it decide — which is what `commands: false` is for — caught two
things a type check never would.

It wanted to cool the house to 21 degrees in August. The comfort band is what
*heating* aims at; the reference cooled above 26 and at night above 24.5, and
this port had collapsed the two into one number. A compressor running every
summer afternoon to reach a heating setpoint is the most expensive kind of
correct-looking bug.

And it was willing to heat while the pellet stove was doing the same job. The
reference only ever let the heat pump heat when the stove reported itself
faulty, which is the right way round: the stove is what heats this house and
the pump is what covers for it.

Both tables now have a check behind them, along with the boilers' — the three
places in the house where a wrong threshold is a bill rather than a mistake.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 16:27:13 +02:00
stroblmeandClaude Opus 5 1b455b5e55 history: one Flux script per chart, and rows are json
Two things the installation found that the checks did not.

A script with three `from()` statements in it produces three results all
called `_result`, and InfluxDB refuses that outright — so the measurements go
into one filter and the rows come back tagged with which one they are.

And the answer a database node hands back holds a *list* of rows, which a
record may not: a record is flat scalars. It was declared one, so every chart
failed on the type check the moment a real answer arrived.

The second one is now caught before anything is pushed: the preflight runs
each sample shape past the port that would receive it, which is what turns
"expected record, got dict" from a runtime surprise into a line of output.

Also records the two engine faults this seeding session surfaced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 16:24:38 +02:00
stroblmeandClaude Opus 5 8734e51ef1 seed: write the shared library before anything points at it
Sharing after the flows were created left every flow between the owner and
the end of the list pointing at a library entry that did not exist yet. A node
that fails to load provides nothing, so one missing entry reported as inputs
nothing carries, in eighteen places.

The library is written *from* a node, so this is one throwaway flow that
carries both sources in and is deleted again — the entries outlive it. Order
stops mattering, which also covers the case that would have broken the
previous fix: two instances of one shared node inside a single flow.

Verified against the installation: nineteen flows, zero validation issues.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 16:06:29 +02:00
stroblmeandClaude Opus 5 3c04c2944d seed: refuse an engine that would ignore what these flows rely on
A node type accepts a setting it does not know and ignores it, which is right
for a node someone is editing and wrong here: seeded against the engine this
installation is running today, every rollershutter would take the trigger's
default minute instead of the twenty-six seconds it actually takes, and every
Victron reading would arrive as an object where a number was declared. It
would look like it had worked.

So the seed asks what the node types know before it writes anything, and says
which rebuild is missing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:49:54 +02:00
stroblmeandClaude Opus 5 436ca7e9af Seed the TinyHouse: nineteen flows in place of eight hundred nodes
The Node-RED installation this replaces is 865 nodes across three tabs, and
roughly a fifth of it is unreachable — the pellet stove's controller, the
scene engine and the awning's logic were all disconnected from the heartbeat
they ran on. What is here is the intent rather than the wiring: nineteen named
flows, 109 nodes, and no heartbeat at all. A sensor value is the event.

The device layer moves with it. `actor/*` and `light/*` were never a device
interface — Node-RED subscribed to its own topics, stamped a DMX channel on
each and encoded one Art-Net universe — so those topics retire with it and the
encoders are five nodes in the `dmx` flow.

Two shared library nodes carry what every actuator needs.

`arbiter` answers the thing this design was missing: a value someone sets on a
screen is not undone by the next evaluation. A manual value wins for a hold,
the house takes over when it expires, and a schedule can force past both — so
"off at two in the morning" still means off. The control binds to the message
the arbiter writes back, so one tile shows what reached the fixture and
setting it is the override.

`motor` is why a stop is now commanded once. A rollershutter has no position
sensor, so time is the only feedback: it says how long to run and a trigger
sends the single STOP that ends it. The reference sent STOP forever.

Everything is seeded stopped, the Art-Net node does not transmit and the heat
pump does not accept commands until house.json says so.

`--dry` checks the whole set without an installation: names nothing provides,
loops, type disagreements, widgets bound to nothing, and every Python node run
once on values of the shape it declared — including whether what it returns
goes anywhere. That last one has already caught a typo that would have
published into silence.

house.json holds this installation's addresses, MAC addresses and DMX map and
is git-ignored, as the Node-RED inventory is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:44:21 +02:00