nodes: what porting the house needed from the vocabulary
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:
@@ -23,7 +23,10 @@ class TriggerNode(Node):
|
||||
"""Emit ``first`` on arrival, then ``then`` once the wait runs out.
|
||||
|
||||
A value arriving during the wait extends it, so the second emission only
|
||||
happens when things have actually gone quiet.
|
||||
happens when things have actually gone quiet. ``passthrough`` sends the
|
||||
incoming value instead of ``first``, and ``wait_port`` names an input
|
||||
carrying the wait, for the case where how long to wait is itself a value —
|
||||
a shutter that takes 26 s up and 28 s down.
|
||||
"""
|
||||
|
||||
class Params(BaseModel):
|
||||
@@ -41,6 +44,17 @@ class TriggerNode(Node):
|
||||
default=True,
|
||||
description="A value arriving during the wait starts it over.",
|
||||
)
|
||||
passthrough: bool = Field(
|
||||
default=False,
|
||||
description="Send the incoming value instead of 'first'.",
|
||||
)
|
||||
wait_port: str = Field(
|
||||
default="",
|
||||
description=(
|
||||
"An input port carrying the wait in seconds, for a wait that "
|
||||
"differs per message. Zero or less sends nothing afterwards."
|
||||
),
|
||||
)
|
||||
|
||||
__slots__ = ("cfg",)
|
||||
|
||||
@@ -67,23 +81,30 @@ class TriggerNode(Node):
|
||||
if not kwargs or not self.output_ports:
|
||||
return None
|
||||
|
||||
wait = self.cfg.wait
|
||||
if self.cfg.wait_port:
|
||||
# A value port rather than a setting: a shutter's run-time differs
|
||||
# by direction, and that is a message the graph carries.
|
||||
wait = float(kwargs.get(self.cfg.wait_port, wait) or 0)
|
||||
|
||||
armed = self.recall("armed", 0)
|
||||
if armed and not self.cfg.extend:
|
||||
# Already counting down and not extending: ignore the new value.
|
||||
return None
|
||||
|
||||
# A generation counter, so a value arriving mid-wait invalidates the
|
||||
# deferred emission that was already scheduled.
|
||||
# deferred emission that was already scheduled. Bumped even when this
|
||||
# message asks for no second value, which is how a wait is cancelled.
|
||||
generation = int(self.recall("generation", 0)) + 1
|
||||
self.remember("generation", generation)
|
||||
self.remember("armed", 1)
|
||||
self.remember("armed", 1 if wait > 0 else 0)
|
||||
|
||||
if self.cfg.then is not None and self._pipeline is not None:
|
||||
if self.cfg.then is not None and self._pipeline is not None and wait > 0:
|
||||
deferred = self._outputs(self.cfg.then)
|
||||
scheduled = self._pipeline.defer(
|
||||
self,
|
||||
self._to_messages(deferred) or {},
|
||||
self.cfg.wait,
|
||||
wait,
|
||||
guard=("generation", generation),
|
||||
)
|
||||
if not scheduled:
|
||||
@@ -94,4 +115,14 @@ class TriggerNode(Node):
|
||||
)
|
||||
self.remember("armed", 0)
|
||||
|
||||
if self.cfg.passthrough:
|
||||
# Whatever woke the node, minus the port that only said how long
|
||||
# to wait. Several inputs are ambiguous, so the first one wins.
|
||||
values = {
|
||||
port: value
|
||||
for port, value in kwargs.items()
|
||||
if port != self.cfg.wait_port
|
||||
}
|
||||
if values:
|
||||
return self._outputs(next(iter(values.values())))
|
||||
return self._outputs(self.cfg.first)
|
||||
|
||||
Reference in New Issue
Block a user