# Node types
Built-in nodes cover common integrations, timing, and logic. Configure their
settings in the node panel; use a **Function** node for your own Python.
`GET /flows/node-types` returns this list with each type's full parameter
schema, including any [connectors](#connectors) installed on your instance.
MQTT and InfluxDB need `pip install 'fluksio[server]'` on a Python installation.
The Docker image includes these dependencies. Run the broker and database
separately, or use the development stack's test services.
## Function
**`python`** — run your own code when its triggering inputs arrive.
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](../code/nodes.md).
Settings on a Function node are free-form: you add them by name, and they
arrive as keyword arguments.
## Integrations
### MQTT
**`mqtt`** — listen on topics, or send to them.
The ports pick the direction: outputs only make a **listener**, inputs a
**sender**. A listener subscribes to its topics, a sender publishes its inputs
to them.
| 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 |
| `send_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.
Set an explicit topic: the default `*` is a literal topic, not MQTT's
match-all filter.
For `{"value": 47}`, set `json_key` to `value`. Without it, an object is
unwrapped only if it contains the port's name as a key.
### HTTP
**`http`** — listen on a webhook, or send to a URL and pass the reply on.
The ports pick the direction: outputs only make a **listener**, inputs a
**sender**. A listener is a **webhook**: the engine mounts a route at
`/hooks/{flow}/{url}/{secret}` while the flow runs. A sender requests `url`
and publishes a JSON object response on its outputs, one key per output port.
Keys no port declares are dropped, and a response that is not an object
publishes nothing.
| Setting | Default | Notes |
|---|---|---|
| `url` | — | the route path for a webhook, the full URL for a sender |
| `method` | `POST` | `GET` or `POST` |
| `timeout` | `30` | seconds, sender only |
| `headers` | `{}` | request headers, sender only; a value may be a secret reference |
| `query` | `{}` | fixed query parameters, sender only; 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.
### WebSocket
**`websocket`** — listen on a socket, or send to it, as a client or a server.
The URL picks the role: `ws://…` or `wss://…` connects to a server and
reconnects when it drops; a path such as `/ticker` is served at
`/hooks/{flow}/{url}/{secret}` while the flow runs. The ports pick the
direction: outputs only make a **listener**, inputs a **sender**. A served
sender sends to every connected client.
| Setting | Default | Notes |
|---|---|---|
| `url` | — | `ws://` or `wss://` URL, or the path to serve |
| `headers` | `{}` | handshake headers, client only; a value may be a secret reference |
| `on_connect` | `[]` | frames sent after every (re)connect, client only — a subscribe or auth message; an item may be a secret reference |
| `secret` | — | shared secret appended to a served socket's URL; takes a secret reference |
| `timeout` | `10.0` | seconds for the handshake, and for a send to one client of a served socket |
| `send_queue_size` | `256` | messages that may wait to be sent; past this the oldest is dropped and the node reports degraded |
| `json_key` | — | as on `mqtt` |
Frames are text. An incoming frame is parsed as JSON: an object feeds the
ports it names, and a node with a single output takes any other frame whole.
A single input is sent as it stands — a string raw, anything else as JSON —
and several go as one object keyed by port.
### 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` |
| `timeout` | operation timeout in seconds; default `10.0` |
| `writes` | per-input: `measurement`, `field`, `tags` |
| `queries` | per-output: `measurement`, `field`, `tags`, `range`, `aggregation` |
```json
{
"writes": {
"living_temperature": {
"measurement": "environment",
"field": "temp_c",
"tags": {"room": "living"}
}
}
}
```
**Query passthrough.** An incoming object with a `flux` key supplies the query.
The first output receives an object with `rows`, each containing `ts`, `value`,
`field`, `measurement`, and `tags`. Use Function nodes to build queries and
shape results for charts.
### Notification
**`ntfy`** — push an incoming value to a phone through [ntfy](https://ntfy.sh).
| 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 |
| `timeout` | `10.0` | request timeout in seconds |
For engine failures rather than flow messages, configure
[Alerts](../interface/operations.md#alerts).
## 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 |
Use `cron` for scheduled emissions, or trigger the node by hand for testing.
### 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, per output |
| `mapping` | `{}` | input port → output port; paired in order when empty |
| `cron` | — | five-field expression |
Order of operations: delay → rate check → forward. A message arriving inside
the `interval` window is held, and when the window ends the latest one held is
sent. With a `cron` and no inputs it emits the current time on each tick; with
inputs it emits the last value it received.
Timers wait until their deadline; busy workers can delay execution.
!!! note "Not in a batch flow"
Batch flows reject rate-limited non-streaming ports. Runs have no live
timer to release a held value.
### Debounce
**`debounce`** — send one value now and another once things go quiet. A
debounce and hold rather than a scheduler: everything it sends starts from a
value arriving. For a cron tick, see [`inject`](#inject). Node-RED calls this
node *trigger*.
| 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 |
For example: turn a light on when the door opens, then off after two quiet
minutes.
Use `wait_port` when the delay comes from an input. A wait of zero or less
cancels the pending second value and schedules nothing new.
## 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 where the engine runs and read its output.
Adding one or changing its settings requires a superuser.
| 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.
In Docker, the command runs inside the backend container. Mount host files
explicitly if the command needs them.
### 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 |
Paths stay inside the engine's files directory.
## 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](../concepts/runs.md) and a Function node.
## Language
### Wren
**`wren`** — turn a typed command into values for the devices wired to it.
[Wren](https://github.com/stroblme/wren) is an optional local language model
for device commands. Install it in the engine's environment with
`pip install wren`; the node reports a missing dependency if it is absent.
| 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` or `int` | °C; 20 without it, clamped to −5…30 |
| `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 `_` 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`, `brightness` for a light; `setpoint`, `heating`,
`heat`, `boost` for heating; `media`, `music`, `tv`; `shutter`, `blind`,
`cover`; `water`, `watering`, `sprinkler`
A port naming anything else is refused when the flow is built. A port is
named after its message unless it is given a name of its own, in the node
panel's port name field or as `{"name": "setpoint", "port":
"living_room_setpoint"}` through 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` contains `message`, `value`, `applied`, `delay_s`,
`call`, and `confidence`. Unwired calls have an empty `message` and are not
applied. Keep `apply=false` while checking how the model interprets commands.
A call with a delay ("in 10m") is held by the engine's work queue, like the
Debounce node's second value: it survives a restart where the queue is in
Redis, and not otherwise.
The node adds default devices to the model's view for missing expected devices.
They are reported but cannot receive writes unless wired.
!!! note "What the current model cannot do"
Commands are English, such as "turn on the lights in the kitchen".
The model uses the areas above, one device per kind and area, on/off
actions, and whole-minute delays. It does not dim lights or set colors.
The chat's [Control tab](../code/agents.md#control) sends commands by hand.
## Connectors
Installed connector packages add types through the `fluksio.node_types`
entry point group. The editor shows the source package. Incompatible contract
versions are skipped; installing or upgrading a connector requires an engine
restart.
The contract itself is in [The connector contract](connector-contract.md), and
[Writing a connector](../code/connectors.md) walks through building one.
## See also
- [Payload types](payload-types.md) — what a port may carry
- [Writing node code](../code/nodes.md) — the Function node
- [The flow editor](../interface/flow-editor.md) — placing and configuring them