Skip to content

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

python
from odevice import App, Device, Number, Boolean, Enum, String, Location, Page, Section, Action

App

  • 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 property
  • automation={"allow_set": True} — automation rules may set this property

3. Data flow

Backend → device.update() → [deadband/rate filter] → state + revision
        → WebSocket delta → frontend patch
UI switch → POST /commands → on_write handler → backend → result → delta
Rule evaluator → command pipeline → on_write handler → backend → result → delta

Manual control and automation use the same execution path — automation never mutates state directly.

4. Schema (manifest)

json
{
  "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) and columns (1-4) control grid layout; precedence Section > Page > default. icon is a semantic token (chart/gauge/temperature/...), not a renderer icon name.

4c. Widgets (16 views)

Semantic view hints (backend says meaning, never renderer/pixel):

CategoryViews
READmetric, gauge, trend, status, text, progress, alarm, badge, image, table, log, map
WRITEswitch, select, slider, input
ACTIONaction (button, stateless)

5. Command round-trip

python
@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)

python
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 command
  • admin: viewer + send commands + manage automation
  • JWT via POST /auth/login, Bearer token in Authorization header
  • 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:

python
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: > >= < <= == != (with duration)
  • Schedule types: once / daily / weekdays
  • cooldown prevents 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 frontend
  • Number(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.

Built for self-hosted IoT runtimes.