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
+23 -2
View File
@@ -130,6 +130,8 @@ class HttpNode(Node):
"mode",
"timeout",
"headers",
"query",
"send_inputs",
"secret",
"_route_registered",
)
@@ -141,6 +143,21 @@ class HttpNode(Node):
method: Literal["GET", "POST"] = "POST"
timeout: float = 30.0
headers: dict[str, str] = {}
query: dict[str, Any] = Field(
default_factory=dict,
description=(
"Fixed query parameters, sender mode. A value may be a secret "
"reference, which is how an API key stays out of the flow file."
),
)
send_inputs: bool = Field(
default=True,
description=(
"Send the node's inputs as the request body or query. Off when "
"the inputs are only a trigger and the request is fully "
"described by 'url' and 'query'."
),
)
secret: str = Field(
default="",
description=(
@@ -197,6 +214,8 @@ class HttpNode(Node):
self.method = cfg.method.upper()
self.timeout = cfg.timeout
self.headers = cfg.headers
self.query = cfg.query
self.send_inputs = cfg.send_inputs
self.secret = cfg.secret
self._route_registered = False
@@ -251,18 +270,20 @@ class HttpNode(Node):
:rtype: dict | None
"""
client = shared_client()
payload = dict(kwargs) if self.send_inputs else {}
try:
if self.method == "GET":
response = client.get(
self.url,
params=kwargs,
params={**self.query, **payload},
headers=self.headers,
timeout=self.timeout,
)
else: # POST
response = client.post(
self.url,
json=kwargs,
params=self.query or None,
json=payload,
headers=self.headers,
timeout=self.timeout,
)