nodes: what porting the house needed from the vocabulary
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

Four small things, each with a device behind it.

An MQTT filter now routes what it subscribed to. `+` and `#` reached the
broker and were then looked up in an exact-match dict, so every message a
wildcard subscription received was dropped in silence.

`json_key` lifts a value out of the object a device wraps it in — Victron
publishes `{"value": 47}` on every path, which was otherwise a Python node
per port.

The trigger node learned `passthrough` and `wait_port`, because how long to
wait can be a value rather than a constant: a rollershutter takes 26 seconds
up and 28 down. A wait of zero sends nothing afterwards and still cancels
what the last message scheduled, which is how a stop is commanded once
instead of forever.

The HTTP sender takes fixed `query` parameters, so an API key is a secret
reference rather than a message on the canvas, and `send_inputs` off for a
request whose inputs are only a trigger.

Also: `delay` accepts fractional seconds, and `TZ` reaches the container, so
a cron expression means local time. Left unset it is UTC, as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 14:16:08 +02:00
co-authored by Claude Opus 5
parent 2f0e50fc9f
commit 03ce2b9c73
9 changed files with 286 additions and 17 deletions
+7
View File
@@ -88,6 +88,13 @@ remember it is fixed at build time: changing it means rebuilding that image.
|---|---|---|
| `ENVIRONMENT` | `local` | `local`, `staging` or `production` |
| `PRIVATE_API_ENABLED` | `false` | unauthenticated test-only endpoints; needs `ENVIRONMENT=local` too |
| `TZ` | `UTC` | the timezone every schedule is written in |
`TZ` is the container's own, not a setting the code reads: an `inject` or a
`delay` with a cron expression fires on local time. Left at `UTC`, "off at
02:00" means two in the morning UTC, which in most of the world is neither two
o'clock nor the same hour in summer as in winter. Set it to where the
installation is.
`production` closes `/docs`, `/redoc` and the OpenAPI document, because the
schema enumerates every endpoint the installation serves — including the paths
+19 -2
View File
@@ -40,8 +40,15 @@ A node with *outputs only* subscribes; a node with *inputs* publishes.
| `qos` | `0` | 0, 1 or 2 |
| `retain` | `false` | on published messages |
| `keepalive` | `60` | seconds |
| `json_key` | — | key to lift out of an object payload; one for every port, or `{"port": "key"}` |
Nodes sharing a broker share one connection.
A topic may be a filter: `+` matches one level, `#` the rest. Everything a
filter matches lands on the same port, so use one port per topic where the
difference matters.
`json_key` is for a device that wraps its reading — Victron publishes
`{"value": 47}` on every path. Without it, a payload object is unwrapped only
when it happens to carry the port's own name as a key.
### HTTP
@@ -56,6 +63,8 @@ Outputs only makes it a **webhook**: the engine mounts a route at
| `method` | `POST` | `GET` or `POST` |
| `timeout` | `30` | seconds, sender mode |
| `headers` | `{}` | |
| `query` | `{}` | fixed query parameters; a value may be a secret reference |
| `send_inputs` | `true` | off when the inputs only trigger the request |
| `secret` | — | shared secret appended to the webhook URL; takes a secret reference |
!!! warning "A webhook with no secret is open to anyone who can reach the host."
@@ -137,7 +146,7 @@ presses.
| Setting | Default | Notes |
|---|---|---|
| `delay` | `0` | seconds to hold each message |
| `delay` | `0` | seconds to hold each message, fractional |
| `interval` | `0` | minimum seconds between forwards |
| `mapping` | `{}` | input port → output port; paired in order when empty |
| `cron` | — | five-field expression |
@@ -162,10 +171,18 @@ received.
| `then` | `false` | sent when the wait expires; empty sends nothing |
| `wait` | `60` | seconds of quiet before the second value |
| `extend` | `true` | a value arriving during the wait starts it over |
| `passthrough` | `false` | send the incoming value instead of `first` |
| `wait_port` | — | an input carrying the wait, when it differs per message |
The shape this exists for: *the door opened — turn the light on, and off again
in two minutes unless it opens again.*
`wait_port` covers the case where how long to wait is itself a value: a
rollershutter takes 26 seconds up and 28 down, so the node that decides the
direction says how long to run for as well. A wait of zero or less sends
nothing afterwards — and still cancels whatever the last message scheduled,
which is how a *stop* is commanded exactly once.
## Logic
### Switch