Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
15 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 can also be written as a Function node. These types cover the shapes that come up most often, as a form to fill in rather than repeated code.
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 |
timeout |
10.0 |
seconds before a broker operation is given up on |
publish_queue_size |
256 |
payloads that may wait for the broker; past this the oldest is dropped and the node reports degraded |
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}], ...}.
A database node holds the credentials and the connection, nothing else. Building a query and shaping its rows are Function nodes on either side.
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 scheduler: a cron expression here is what makes a flow run by the clock.
It is also the most-placed node in a real instance, 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, since 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. Despite
the name, a debounce and hold rather than a scheduler: everything it sends
starts from a value arriving. For a cron tick, see inject.
| 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, so 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 |
A worked example of numeric logic, not a modelling tool. To train something, use a batch flow and a Function node.
Language
Wren
wren — turn a typed command into values for the devices wired to it.
Wren is a small language model that runs
on the engine's CPU: about 40 MB, a fifth of a second per command. It reads the
house as text, the areas, the devices in each and their states, and answers a
command with calls on those devices. It is a separate package, installed into
the engine with pip install wren; without it the node says so on the canvas.
| Input | Type | Notes |
|---|---|---|
prompt |
str |
the command, and the only input that runs the node |
presence |
str |
the area the user is in; anything else reads as away |
outdoor |
float |
°C; 20 without it |
dark |
bool |
without it, dark before 07:00 and from 18:00 |
The outputs are actions, a list of records, and any number of targets. A
target is an output named <area>_<kind> and bound to the message its device
listens on: living_room_setpoint bound to home.setpoint is the living
room's heating. The wiring is the allowlist, so the model can write nothing
that is not wired to the node.
- area:
living_room,kitchen,bathroom,bedroom,office,garden,patio,garage,driveway,balcony - kind:
light,lamp,brightnessfor a light;setpoint,heating,heat,boostfor heating;media,music,tv;shutter,blind,cover;water,watering,sprinkler
A port naming anything else is refused when the flow is built. The editor
names a port after its message, so there a target's message carries the name;
binding a port to a message named otherwise, as {"name": "setpoint", "port": "living_room_setpoint"}, takes the flow API.
Targets of one area and kind are one device to the model, and each takes what a call means for its type. A target's current value is the state the model reads.
| Call | bool |
number | str |
|---|---|---|---|
lights.on / off |
true / false |
100 / 0 |
on / off |
media.play / stop |
true / false |
— | play / stop |
heating.set |
— | the temperature | — |
heating.boost / off |
true / false |
— | boost / off |
shutters.up / down |
true / false |
100 / 0 |
up / down |
shutters.stop |
— | — | stop |
watering.start / stop |
true / false |
— | on / off |
| Setting | Default | Notes |
|---|---|---|
apply |
false |
write to the targets; off, the node only reports |
min_confidence |
0.93 |
below this nothing is written |
Each record in actions is {message, value, applied, delay_s, call, confidence}, one per call and target. A call nothing is wired for, such as
sensors.temperature or a light in a room without one, gets a record with an
empty message. The confidence is the lowest probability the model gave any
part of its answer: at the default threshold, nine in ten right answers pass,
and 38 of the 40 wrong ones in the model's own evaluation do not.
A call with a delay ("in 10m") is held by the engine's work queue, like the Trigger node's second value: it survives a restart where the queue is in Redis, and not otherwise.
Every house the model was trained on had a living room with a light, media and heating, and a light in every other area. The node fills in what is not wired, so the model reads a house it knows; a call on one of those is reported, never applied.
!!! note "What the current model cannot do"
No levels: a light is on or off, never dimmed, coloured or half open.
Delays are whole minutes. Only one device per kind and area. Only the ten
areas above: one it was not trained on is answered confidently and wrongly,
which is why such a port is refused rather than tried. Commands are English,
phrased roughly like "turn on the lights in the kitchen".
The chat's Control tab sends commands by hand.
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