ODevice — Full Context
Complete reference for LLM-assisted development against ODevice.
1. What it is
ODevice lets IoT developers generate customer-facing Web + Mobile UIs from Python. Define a device schema, connect existing backend data via device.update(), and the UI renders automatically. No React/Vue/Swift/Kotlin required.
Beyond observation + control, ODevice adds automation (warnings, condition rules, schedules) that runs at the Python runtime — even when the app is closed.
2. Public API
from odevice import App, Device, Number, Boolean, Enum, String, Location, Page, Section, ActionApp
App(name, port=8080, host="0.0.0.0", telemetry_file="odevice_telemetry.jsonl").add(device)— register a device.add_page(Page(...))— backend-declared page (extra tab).add_warning(entity, property, operator, value, duration, severity, enabled)— warning rule.add_automation(name, when, then, cooldown, enabled)— condition → command rule.add_schedule(name, timezone, schedule, action, enabled)— time-based rule.run()— start server (blocking).run_async()— return FastAPI app (non-blocking)
Device
Device(id, name, properties, type=None).update(**kwargs)— update state, returns delta or None.on_write(prop)— decorator for command handler.action(id, label, icon, confirm, severity)— declare a stateless action.on_action(id)— decorator for action handler
Property constructors
Number(unit, min, max, max_rate, deadband, writable, view, label, configurable, automation)Boolean(writable, view, label, configurable, automation)Enum(options, writable, view, label, configurable, automation)String(max_length, writable, view, label, configurable, automation)Location(lat, lng)— map widget
Property metadata
label=— human-readable name (frontend auto title-cases keys otherwise)configurable={"warning": True}— user can set a warning on this propertyautomation={"allow_set": True}— automation rules may set this property
3. Data flow
Backend → device.update() → [deadband/rate filter] → state + revision
→ WebSocket delta → frontend patchUI switch → POST /commands → on_write handler → backend → result → deltaRule evaluator → command pipeline → on_write handler → backend → result → deltaManual control and automation use the same execution path — automation never mutates state directly.
4. Schema (manifest)
{
"workspace": { "name": "...", "id": "..." },
"version": 1,
"entities": [
{
"id": "pump-01",
"type": "pump",
"name": "Main Pump",
"properties": {
"pressure": {
"type": "number", "unit": "bar", "min": 0, "max": 10,
"writable": false, "presentation": { "preferred": "gauge" },
"configurable": { "warning": true }
}
},
"actions": [
{ "id": "reset", "label": "Reset Pump", "severity": "normal" }
]
}
],
"pages": [
{ "id": "reports", "title": "Reports", "columns": 2, "density": "loose",
"sections": [ { "title": "...", "items": [ {"entity": "...", "property": "..."} ] } ] }
]
}4b. Pages, labels, density
Page(id, title, icon, columns, density, entities|sections)+Section(title, entities, properties, columns, density)→ rendered as extra bottom tabs.density(compact/normal/loose) andcolumns(1-4) control grid layout; precedence Section > Page > default.iconis a semantic token (chart/gauge/temperature/...), not a renderer icon name.
4c. Widgets (16 views)
Semantic view hints (backend says meaning, never renderer/pixel):
| Category | Views |
|---|---|
| READ | metric, gauge, trend, status, text, progress, alarm, badge, image, table, log, map |
| WRITE | switch, select, slider, input |
| ACTION | action (button, stateless) |
5. Command round-trip
@pump.on_write("power")
async def set_power(value: bool):
await backend.set_power(value)
return value- Timeout 10s
- Return value becomes new state
- Only
writable=True+ admin role can command
5b. Actions (stateless)
pump.action("reset", label="Reset", icon="reset", severity="danger", confirm="Sure?")
@pump.on_action("reset")
async def do_reset(): ...Sent via POST /actions. No state, no value — a button that runs a handler.
6. Roles & auth
viewer: view state, personalize UI, cannot commandadmin: viewer + send commands + manage automation- JWT via
POST /auth/login, Bearer token inAuthorizationheader - Server enforces authorization (not just UI hiding)
6b. Automation Core
Runs at the Python runtime (async loop on the event loop), not on mobile. Three primitives:
app.add_warning("pump-01", "pressure", ">", 8.0, duration=5, severity="warning")
app.add_automation("High Pressure Stop",
when={"entity": "pump-01", "property": "pressure", "operator": ">", "value": 9, "duration": 3},
then={"entity": "pump-01", "property": "power", "operation": "set", "value": False},
cooldown=30)
app.add_schedule("Morning Start", "Asia/Bangkok",
schedule={"type": "daily", "time": "08:00"},
action={"entity": "pump-01", "property": "power", "operation": "set", "value": True})- Warning state machine:
inactive → pending → active → acknowledged → resolved - Operators:
> >= < <= == !=(withduration) - Schedule types:
once/daily/weekdays cooldownprevents command storms- Execution goes through the command pipeline (
handle_command → on_write → audit → broadcast) - Automation permissions gated by
automation={"allow_set": True}on the property
Endpoints: GET /automation, POST /automation/{warning|automation|schedule}, PUT/DELETE /automation/{kind}/{id} (admin).
7. User config overlay
Users can rename labels, hide/show, reorder, choose visualization, favorite devices. Cannot change type, writable, backend binding, min/max safety contract, permissions.
Resolved UI = Base Schema + User Preference overlay.
Endpoints: GET/PUT /config (entity label/favorite), PUT /config/property (hidden/order/visualization/label). GET /audit returns the command audit. GET /telemetry returns validation metrics summary.
8. Bandwidth controls
Number(max_rate=2)→ ≤2 updates/sec to frontendNumber(deadband=0.1)→ skip changes <0.1- Only throttles runtime→frontend emission
9. Mobile specifics
- Cached manifest + last known state (offline view)
- Auto-reconnect with exponential backoff
- Offline/stale indicator
- Commands disabled while disconnected
- QR connect:
{v:1, endpoint, token}with short-lived token - Map widget uses Leaflet + OpenStreetMap tiles (no API key)
10. Architecture
Existing IoT Backend → Python SDK → IoT Runtime (FastAPI)
→ HTTPS + WebSocket → Ionic Vue Web + Mobile (Capacitor)- Web + Mobile share one UI runtime (
shared/src/) - npm workspaces hoist node_modules to repo root
- Automation Core runs inside the runtime (async loop)
11. Testing
- Backend: pytest (40 tests) — full HTTP/WebSocket loop + widget schema + automation
- Frontend: Vitest (34 tests) — resolveWidget mapping + widget render/emit
- CI: pytest + vitest + build before publish
12. Non-goals (MVP)
No MQTT broker, device cloud, OTA, BLE, NFC, multi-condition AND/OR automation graphs, webhook/email/SMS, geofence, sunrise/sunset, AI automation, visual flow builder, time-series DB, drag-and-drop builder, component marketplace, SSO, enterprise RBAC, white-label pipeline. Developers bridge integrations themselves via Python.