| name | iot-operations |
| description | Use CTOX's native IoT engine from agent work items to inspect assets, read telemetry, interpret alarms and rulesets, and perform explicitly approved device writes through `ctox iot`. Use when a CTOX queue task, alarm, or operator request references IoT assets, attributes, datapoints, protocol agents, or `ctox.iot.*` commands. |
| cluster | host_ops |
IoT Operations
Runtime Contract
- Treat physical-world writes as controlled actions. Never write a device-backed attribute unless the current task explicitly asks for that write or carries clear operator approval for the exact device/action class.
- Read operations are allowed when they support the current task: list/show assets, read attributes, query datapoints, list alarms, list rulesets, and inspect agent status.
- All IoT actions must go through the native CLI (
ctox iot ...) or the Business OS command path. Do not call device protocols directly from the agent.
- Do not use HTTP bridges to the browser. The browser reads
iot_* projections over RxDB/WebRTC and writes business_commands; the agent uses the CLI.
- Keep the loop bounded: read current state, reason from concrete evidence, act only if authorized, then verify with a read-back or queue/task outcome.
- Do not persist or print credentials. MQTT/HTTP/WebSocket secrets live in CTOX runtime config/secret store and must stay redacted.
Command Map
Use these commands as the primary interface:
ctox iot asset list --realm <realm>
ctox iot asset show --id <asset-id>
ctox iot asset upsert --realm <realm> --type <type> --name <name> [--id <id>] [--parent <id>]
ctox iot asset delete --id <asset-id>
ctox iot attribute read --asset <asset-id> --name <attribute>
ctox iot attribute write --asset <asset-id> --name <attribute> --value '<json-or-string>' [--ts <epoch-ms>]
ctox iot datapoints query --asset <asset-id> --name <attribute> --from <epoch-ms> --to <epoch-ms> --shape all
ctox iot datapoints query --asset <asset-id> --name <attribute> --from <epoch-ms> --to <epoch-ms> --shape interval --interval <ms>
ctox iot datapoints query --asset <asset-id> --name <attribute> --from <epoch-ms> --to <epoch-ms> --shape lttb --threshold <n>
ctox iot alarm list --realm <realm>
ctox iot alarm ack --id <alarm-id>
ctox iot alarm resolve --id <alarm-id>
ctox iot rules list --realm <realm>
ctox iot rules save --realm <realm> --name <name> --data '<json>'
ctox iot rules toggle --id <ruleset-id> --enabled true|false
ctox iot agent list --realm <realm>
ctox iot agent configure --realm <realm> --name <name> --kind mqtt --data '<json>'
ctox iot agent status --id <agent-id>
ctox iot dashboard list --realm <realm>
ctox iot dashboard upsert --realm <realm> --name <name> [--id <id>] [--scope asset|realm] [--scope-ref <id>]
ctox iot dashboard delete --id <dashboard-id>
ctox iot widget list --dashboard <dashboard-id>
ctox iot widget upsert --dashboard <id> --realm <realm> --signal <asset_id>::<attribute> [--id <id>] [--when '<freitext>'] [--then '<auftrags-prompt>'] [--trigger-code '<rhai>'] [--render-code '<js>']
ctox iot widget delete --id <widget-id>
ctox iot widget arrange --id <widget-id> --x <n> --y <n> --w <n> --h <n>
ctox iot widget compile-trigger --id <widget-id>
ctox iot widget generate-render --id <widget-id>
ctox iot webhook register --realm <realm> --signal <asset_id>::<attribute> [--id <id>] [--path <dot.path>]
ctox iot webhook ingest --signal <asset_id>::<attribute> --payload '<json>' [--path <dot.path>] [--ts <epoch-ms>]
ctox iot webhook send --url <url> --payload '<json>' [--secret-ref <secret-name>] [--header key=value ...]
ctox iot project all
Webhooks are first-class I/O. Inbound: webhook register mints a token-gated URL bound to ONE signal (the token is stored in the secret store) — give the operator the returned ingest_path + token; external senders then POST /ctox/iot/webhook/<id> with header X-Webhook-Token: <token> and the daemon ingests to that signal (running conditions + widget watchers). webhook ingest is the same mapping for a trusted/local caller. Outbound (webhook send) is how you fulfil a "Dann" like "…und meld's per Webhook ans ERP": POST the payload, attaching Authorization: Bearer <secret> when --secret-ref resolves in the secret store. Never put a webhook secret in iot_agents.data or an env var.
Read Workflow
- Identify the realm, asset id, attribute name, alarm id, or ruleset id from the task.
- Read the narrowest relevant state first.
- If state is ambiguous, inspect adjacent context: parent asset, agent status, alarm lifecycle, and recent datapoints.
- Base conclusions on CLI output, not stale prose or screenshots.
- Summarize exact ids, values, timestamps, and status names when reporting back.
Write Workflow
- Confirm the task authorizes the write. If authorization is missing or vague, ask before writing.
- Read the target asset and attribute first.
- Validate that the attribute name and value type match the intended action.
- Execute one narrow
ctox iot attribute write.
- Verify with
ctox iot attribute read, datapoint query, device echo, or the resulting queue/task outcome.
- Report the before value, requested value, after value, and any alarm/task side effects.
Alarm And Ruleset Workflow
- An IoT alarm is a durable event source. Inspect the linked asset and current attribute before diagnosing.
- A queue task spawned from an IoT alarm is bounded by CTOX's spawn budget. Do not spawn another task for the same condition unless the parent task explicitly requires a distinct bounded follow-up.
- JSON attribute conditions are evaluated by the native IoT condition layer. Firing, dedup, recurrence, and loop bounding are CTOX mission/queue/schedule responsibilities.
- Groovy, JavaScript, Flow rules, forecasting, gateway federation, and non-MQTT production protocol bring-up are deferred scope unless the task explicitly says to work on those capabilities.
Widget Codegen Workflow (RFC 0011)
An IoT automation widget is one standing order, programmed by CTOX in three parts: ① Trigger-Logik (a Rhai watcher in the backend) · ② Widget-Code (render_code, sandboxed in the browser) · ③ Auftrags-Prompt (action_prompt, spawns a chat when the watcher fires). The human writes prompts (Wenn/Dann); you write the code. A queue task whose metadata carries kind: "iot_trigger_code" or kind: "iot_render_code" is asking you to author part ① or ② and write it back. Never use a heuristic template — author it for the specific Wenn.
Trigger-Logik (kind = iot_trigger_code)
Write a small Rhai program that runs stateful per datapoint and calls fire(grund) when the widget's free-text "Wenn" holds. Read-only signal API ONLY:
signal.last() · signal.has_data() · signal.age_ms()
signal.window("15m") → array · signal.avg("15m") / .min / .max / .count · signal.rate("15m") (per second)
signals("name") → another bound signal
state → a map persisted across calls (for "seit X Minuten", hysteresis, counters): state.streak = (state.streak ?? 0) + 1
fire(grund) → report the condition holds
Windows accept ms/s/m/h/d. No file/net/eval; hard op/time limits. The intelligence goes into the code ONCE — there is no LLM per datapoint. Write it back:
ctox iot widget upsert --id <id> --dashboard <id> --realm <realm> --signal <ref> --trigger-code '<rhai>'
The upsert validates the program: a runnable one lands trigger_status = armed, a broken one needs_attention — if so, read the cause and regenerate (self-repair).
Widget-Code (kind = iot_render_code)
Write the body of a JS function render(host, api) that renders into the tile element host. Sandboxed API ONLY: api.signal.last()/.window("15m")/.rate("15m"), api.draw.line/value/gauge/grid, api.fmt. No window/document/parent/fetch/eval/import. Keep it minimal — the visualization is subordinate to the order (a value + sparkline usually suffices). Write back with --render-code '<js>'.
The watcher runs in the Rust backend per datapoint; on fire it spawns a chat seeded with the action_prompt + signal references. You only author the code — you do not run a watch loop yourself.
Agent Safety Checks
Before any physical-world write, confirm:
- the asset id and attribute name are exact
- the realm is correct
- the task authorizes the write
- the value is bounded and reversible or otherwise operator-approved
- the expected verification path is known
If any item is missing, do not write. Read state and ask for the missing approval or constraint.