The timer thread promoted due work on a fixed one-second tick, so every delayed item was 0-1000ms late whatever the load — measured on the house at 705ms mean on a rollershutter stop, which is 2-4% of a 26-second travel and accumulates in the position the motor node believes it is at. It now sleeps to the soonest deadline and is woken when a nearer one is scheduled, which measures 0.9ms end to end through Redis. A promoted timer also went to the back of the queue. It goes into a due lane of its own that `claim` reads first, so work that has waited out a deadline is not held up by work that is merely queued. Beside it, in the same code: seeding a message now bumps its version, so a re-put flow's synchronous nodes no longer wait forever on a value that is sitting in state; the consumer group drops the consumers of engines that are gone (138 had accumulated on this installation); and the cast that closes the long-standing `xclaim` mypy error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
11 KiB
Node types
Every type the canvas can place. Each is configured by filling in a form the editor generates from its parameter schema, so they all behave the same way.
Anything here could be written as a Function node — that is what the function node is for. These exist because the same handful of shapes account for most of a real installation, and a rule you fill in is easier to read on a canvas, and to change, than five lines of code repeated eighty times.
GET /flows/node-types returns this list with each type's full parameter
schema, including any connectors installed on your instance.
Function
python — your own code, run on every incoming message.
The one type with a source file. Its arguments are its input ports and its own settings; its return value is a dict keyed by output ports. See Writing node code.
Settings on a Function node are free-form: you add them by name, and they arrive as keyword arguments.
Integrations
MQTT
mqtt — subscribe to topics, or publish what arrives on its inputs.
A node with outputs only subscribes; a node with inputs publishes.
| Setting | Default | Notes |
|---|---|---|
topic |
* |
one topic for every port, or {"message": "some/topic"} per port |
broker_host |
localhost |
|
broker_port |
1883 |
|
username / password |
— | password takes a secret reference |
client_id |
— | |
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"} |
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
http — receive data on a webhook, or send it to a URL.
Outputs only makes it a webhook: the engine mounts a route at
/hooks/{flow}/{url}/{secret} while the flow runs. Inputs make it a sender.
| Setting | Default | Notes |
|---|---|---|
url |
— | the route path in webhook mode, the full URL in sender mode |
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."
The canvas flags this as an advisory issue rather than stopping the flow,
because a webhook on a private network is a legitimate thing to want.
InfluxDB
influxdb — write measurements to a bucket, or read them back.
| Setting | Notes |
|---|---|
url, token, org, bucket |
connection; token takes a secret reference |
write_precision |
ns, us, ms (default) or s |
query_range |
default window for queries, e.g. -1h |
writes |
per-input: measurement, field, tags |
queries |
per-output: measurement, field, tags, range, aggregation |
{
"writes": {
"living_temperature": {
"measurement": "environment",
"field": "temp_c",
"tags": {"room": "living"}
}
}
}
Query passthrough. An incoming message holding a flux key is run as
written, and the rows come back on the first output port as
{"rows": [{ts, value, field, measurement, tags}], ...}.
That is what keeps a database node a database node: it holds the credentials and the connection and nothing else. Building a query and shaping its rows are ordinary Function nodes on either side, so a dashboard widget never learns which database answered it.
Notification
ntfy — push an incoming value to a phone through ntfy.
| Setting | Default | Notes |
|---|---|---|
server |
https://ntfy.sh |
|
topic |
— | required |
title |
— | |
priority |
default |
min, low, default, high, urgent |
tags |
— | comma-separated ntfy tags |
token |
— | for a protected topic; takes a secret reference |
This is a flow deciding something is worth saying. The engine reporting that it broke is Alerts, which is a different thing configured elsewhere.
Timing
Inject
inject — emit a value on request, on a timer, or when the flow starts.
| Setting | Default | Notes |
|---|---|---|
payload |
current time | what to emit |
payloads |
{} |
per-output-port payloads, keyed by port |
interval |
0 |
emit every n seconds; 0 means never on its own |
cron |
— | a five-field cron expression |
at_start |
false |
emit once when the flow starts |
start_delay |
1.0 |
how long to wait before that first emission |
The most-placed trigger in a real installation — mostly as a button someone presses.
Delay & schedule
delay — hold messages back, limit their rate, or emit on a schedule.
| Setting | Default | Notes |
|---|---|---|
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 |
Order of operations: rate check → delay → forward. With a cron and no inputs
it emits the current time on each tick; with inputs it emits the last value it
received.
The engine sleeps until a delay is due rather than polling for it, so a delay
fires within a few milliseconds of its deadline on an idle engine, and a due
timer is taken off the queue before work that is merely waiting. It is not a
real-time guarantee: the wait ends on a normal thread, and a node that then
needs a busy cascade slot waits for one.
!!! note "Not in a batch flow"
A rate limit holds a value back for a timer to release, and a run has no
timer — the value would be dropped rather than delayed. Submitting a batch
flow with a rate-limited port is refused instead.
Trigger
trigger — send one value now and another once things go quiet.
| Setting | Default | Notes |
|---|---|---|
first |
true |
sent as soon as a value arrives |
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
switch — send a value down one branch or another, by rule.
Each rule names an output port; a matching value leaves through that port.
Comparisons: eq, ne, gt, gte, lt, lte, contains, between.
| Setting | Default | Notes |
|---|---|---|
rules |
[] |
checked in order; each names the output it routes to |
stop_at_first |
true |
leave through the first matching rule only |
otherwise |
— | output for a value that matched nothing |
Comparing a string to a number is a mistake in the rule, not a reason to take the flow down: the rule simply does not match.
Change
change — scale, offset, round or map a value on its way past.
| Setting | Default | Notes |
|---|---|---|
scale |
1.0 |
multiply numbers by this |
offset |
0.0 |
then add this |
round_to |
— | decimal places |
mapping |
{} |
replace a value with another, looked up as text |
default |
— | value when the lookup misses; empty passes it through |
Filter unchanged
rbe — pass a value on only when it has actually changed.
| Setting | Default | Notes |
|---|---|---|
deadband |
0.0 |
ignore numeric changes smaller than this |
deadband_percent |
false |
read the deadband as a percentage |
The cheapest fix for a sensor that reports every second and changes every hour.
Join
join — gather several inputs into one object or list.
| Setting | Default | Notes |
|---|---|---|
mode |
object |
object or array |
System
Command
exec — run a command in the engine's container and read its output.
| Setting | Default | Notes |
|---|---|---|
command |
— | the command to run |
append_payload |
false |
add the incoming value as one argument |
timeout |
30 |
seconds |
fail_on_error |
false |
treat a non-zero exit as a node failure rather than output |
Outputs the command's stdout, stderr and exit code.
!!! warning "Inside the container, not on the host"
A flow ported from something that read the host's journal, or poked a host
script, needs either a mount or a small listener on the host side. This
node cannot see the host.
File
file — read a file into the flow, or write one out of it.
| Setting | Default | Notes |
|---|---|---|
path |
— | relative to the engine's files directory |
mode |
read |
read, write or append |
format |
text |
text or json |
newline |
true |
end each written record with a newline |
Confined to a directory the engine owns. A flow that could name any path would be a way to read the secrets store or overwrite a node's source.
Numeric
Perceptron
mlp — a small neural layer over its numeric inputs.
output = weights @ inputs + biases, with weights drawn from seed so a node
reloads identically.
| Setting | Default | Notes |
|---|---|---|
seed |
0 |
Kept as a worked example of numeric logic rather than as a modelling tool. If you are training something, that is a batch flow and a Function node.
Connectors
Anything else in the palette came from an installed connector package — a
node type written against a published contract and discovered through the
fluksio.node_types entry point group. The editor shows which package supplied
it.
A connector declares its contract version, and one written for a version this engine does not speak is ignored rather than half-loaded. Installing or upgrading one takes effect on the next engine restart, because Python does not re-import a changed module and a rescan would promise more than it delivers.
The contract itself is in The connector contract, and Writing a connector walks through building one.
See also
- Payload types — what a port may carry
- Writing node code — the Function node
- The flow editor — placing and configuring them