platform
Your sensors report.Your rules decide.Your actuators move.
iodriven ingests sensor telemetry over MQTT, evaluates every threshold and rule in memory, the moment a message arrives, and drives the response — an actuator command, a notification, or a webhook — in under two seconds.
what it does
A rules engine wired straight to your hardware.
acquire
ESP32-class devices publish readings over MQTT/TLS. Everything lands in TimescaleDB and streams to the browser over WebSocket for live dashboards.
decide
A rule watches one metric — a threshold, or a rolling window — on the reading itself, the instant it arrives. No polling, no cron.
act
A firing rule sends an actuator command, raises a notification, or calls a webhook. One rule, one of three outcomes.
the other end
Real relays are on the other end of the command.
Duration, hysteresis, and cooldown are part of every rule — not an advanced tab you forget to open. A rule holds its condition before it fires, waits for the reading to fall back past a margin before it re-arms, and rate-limits itself. A noisy sensor will never chatter your relay to death.
for_duration
The condition must hold this long before the rule fires — brief spikes are ignored.
hysteresis
The reading must fall back past a margin before the rule can fire again.
cooldown
A hard floor on the time between two firings of the same rule.
hot-path
The decision never waits on a disk.
Hot path
Rules run in the worker’s memory with no database read. A breach goes straight to the command service, out through EMQX, and onto the device.
reading → evaluate → command → EMQX → actuator
Storage path
Telemetry is pushed to a Redis stream and drained by a writer that batches inserts into TimescaleDB. Built for throughput, not latency.
reading → Redis stream → batched writer → TimescaleDB
A batch flush is never between a reading and a decision. That’s the rule that keeps the two-second budget honest.
onboarding
From sign-up to live data in ten minutes.
Create a workspace. The tenant is provisioned in the same step — there’s no separate setup.
Define a device template. The metrics a device reports, the actuators it drives, the units and ranges.
Copy the generated Arduino sketch. Topics, TLS, and the per-device credential are already filled in.
Flash it. Readings appear on the live chart, and any rule you armed is already watching.
No SDK to vendor, no sales call. A device speaks four MQTT topics and it’s done.
device contract
The contract a device speaks is small and fixed.
{tenant}/{device}/{metric} telemetry device → platform {tenant}/{device}/cmd/{actuator} command platform → device, QoS 1 {tenant}/{device}/state/{actuator} desired state retained {tenant}/{device}/ack/{actuator} acknowledgement device → platform
MQTT over TLS. Per-device tokens, stored hashed with argon2id — never in plaintext. One firmware build works against the multi-tenant platform or a single-tenant deployment, because the contract is the same on both.
one host
Runs on one box. Isolated by the database.
Sized for 500 to 1,000 devices on one Linux VPS — Docker Compose, daily backups with a restore runbook that has actually been run, no cluster to keep alive at 3am. Every table carries a tenant id with a Postgres row-level-security policy behind it, so a forgotten WHERE clause is a bug, not a data breach. Anomaly detection is next, and it arrives as another evaluator on the same in-memory hot path — not another service to operate.