Made with ❤️ by Lumi
Edge-compute SCADA platform for smart factories — 8 PLC protocols out of the box, a visual workflow builder, and an AI assistant with tool calling, all running on the edge device itself.
# install (requires Python 3.11+)
uv sync --all-extras
# build the web UI (Node 20+, build-time only)
cd frontend && npm install && npm run build && cd ..
# run — serves API + UI on :8080 (needs NATS: docker compose up nats)
runpilot start -c config/runpilot.yamlOpen http://localhost:8080, log in (admin / $RUNPILOT_ADMIN_PASSWORD;
auto-generated and logged on first boot if unset), and build workflows
visually: drag device tags → transforms → connectors, deploy, watch live
counters.
A full bottling-plant digital twin (5 devices, 38 tags: servo x/y axes, motor parameters, utilities/electrical segment, compressor, production counters) with 24 h of history, an ISA-95 asset tree, and 8 seeded raw→derived workflows:
docker run -d --name nats -p 4222:4222 nats:2-alpine
uv run python scripts/seed_demo.py # one-shot: history + assets
RUNPILOT_ADMIN_PASSWORD=runpilot-local-admin \
runpilot start -c config/runpilot.local.yaml &
uv run python scripts/sim_feed.py demo # live feed (or a scenario)Live scenarios (--list): overheating (filler crosses 85 °C → alarm),
bearing-wear (capper vibration → anomaly), downtime (line stops → OEE
hit), energy-spike (power-factor dip), quality-drift (fill volume out
of 500±3 spec), capper-jam (line backs up → stall alarm),
servo-degradation (x/y axis jitter → derived-KPI anomaly), and demo
(a 14-min cycling tour). --duration N time-boxes a run for scripted
demos; --counts G R continues production counters from the backfill.
The seeded workflows showcase raw→derived transforms: three-phase power
from V·I·pf, stateful kWh accumulation, axis-speed magnitude from servo
velocities, an expression where-filter, and a stall detector — all built
from the same nodes available in the visual builder.
uv run pytest # backend
cd frontend && npx vitest run # graph serializationRunPilot runs on edge hardware (RPi 4/5, Jetson, industrial PCs). Data flows through a validate → enrich → aggregate pipeline over NATS, stores locally in SQLite + DuckDB, and syncs to cloud when connected. The API + web UI are served from the same process (DuckDB is single-process).
PLC ──► Drivers ──► NATS Pipeline ──► Connectors ──► Cloud
(validate/enrich/agg) (MQTT/HTTP)
│ │
│ ├──► Workflow engine (visual builder)
│ ├──► AI agent (chat, tools, advisories)
│ └──► SSE live feed (web UI)
DuckDB (time-series)
SQLite (assets/alarms/workflows/auth/audit)
| Protocol | driver |
Library | Address examples | Notes |
|---|---|---|---|---|
| OPC-UA | opcua |
asyncua | ns=2;s=Motor.Speed |
subscriptions + cert auth |
| Modbus TCP | modbus_tcp |
pymodbus | 40001, 30001, 10001, 1 |
register batching |
| Modbus RTU | modbus_rtu |
pymodbus[serial] | same | host: /dev/ttyUSB0; baud/parity in extra |
| Siemens S7 | s7 |
pys7 (pure Python) | DB1,R40, M54.4, IW22 |
S7-300/400/1200/1500; multi-var reads |
| EtherNet/IP | ethernet_ip |
pycomm3 | Program:Main.Counter, MyTag |
ControlLogix/CompactLogix |
| MELSEC MC | melsec |
pymcprotocol | D100, M100, X10 |
Q/L/iQ-R; port from GX Works required |
| Omron FINS | fins |
stdlib UDP codec | DM100, CIO50, W10 |
word areas; node = last IP octet |
| MQTT ingest | mqtt |
aiomqtt | topic or topic:$.json.path |
push/cache; numeric/bool payloads |
Connection parameters go in each device's extra: dict — see the commented
examples in config/runpilot.yaml.
The web UI edits WorkflowConfig graphs on a React Flow canvas: sources are
device tags, transforms come from TRANSFORM_REGISTRY, destinations are
connectors or PLC write-back nodes. Deploy validates (unknown transforms,
cycles, unknown destinations, write permissions) and hot-reloads the runner;
threshold breaches create deduplicated alarms. Workflows persist in SQLite;
the YAML workflows: section seeds first boot.
- AI nodes:
anomaly_detect(statistical z-score/IQR per tag) andai_analyze(cooldown-gated LLM analysis of batch stats, optionally raised as an info alarm). - Code nodes:
expression— safe one-line Python (simpleeval) to rewrite/filter, e.g.value * 1.8 + 32;python_code— full customtransform(readings, state)logic in a RestrictedPython sandbox (import whitelist, wall-clock timeout,security.custom_code_enabledkill-switch). Both offer ✨ Write with AI: describe the logic, get compile-checked code. - PLC write-back:
plc:{device}.{tag}destinations, off by default — requiresecurity.plc_write_enabled+plc_write_from_workflows, awritabletag, deployer with theplc_writepermission, and every write runs through WriteGuard's rate limit + audit. - Generate with AI: describe the workflow in a sentence; the copilot drafts a validated graph onto the canvas for review before deploy.
Everything is manageable from the browser; the YAML seeds first boot, then SQLite is the source of truth (hot-applied, no restart):
- Devices: add/edit/delete PLCs with per-driver connection forms, a tag
table editor, Test connection, tag browse (OPC-UA / EtherNet-IP),
CSV bulk import, and an AI tag mapper that cleans cryptic addresses
(
N7:0,DB100.DBW4) into readable names + units (POST /api/ai/suggest-tags). - Connectors: add/edit sinks from schema-driven forms; stop-and-recreate on save.
- Users: admin CRUD + role changes; self-service password change; last-admin guards; everything audited.
- System: live health (drivers, connectors, workflows, disk), Prometheus
GET /metrics, LLM provider list + one-click test, DB backup, config export/import.
Any vendor, any API style, configured as an ordered fallback chain — keys
are always env-var names, never literals:
api_style |
Covers |
|---|---|
anthropic |
Anthropic, MiniMax (api.minimax.io/anthropic) |
openai |
OpenAI, Azure AI Foundry serverless, Groq, DeepSeek, vLLM, LM Studio |
azure |
Azure OpenAI / Foundry deployments (api_version) |
ollama |
local models |
The legacy single-provider fields still work (auto-synthesized). See the
llm.providers / llm.chain example in config/runpilot.yaml.
Add providers from the UI: System → "+ Add LLM provider" — base URL, type (anthropic/openai/azure/ollama), API key — then pick the default model from the vendor's own model list ("Models…" button). UI-added providers persist in the local store (0600 perms); keys are never echoed by any API and are masked in config exports. Ollama installs get a live model selector automatically.
knowledge/plant/ ships six manuals matching the simulated line (filler servo specs, capper torque/vibration zones, compressor envelope, electrical tariffs, OEE targets, alarm playbook) — RAG-ingested at boot so the AI assistant answers "what's the capper vibration limit?" from the plant's own documentation.
- Dashboards — built-in tile grid (live value, gauge, sparkline, alarm list, KPI card); browser-editable, YAML-seeded, no Grafana required.
- Assets (digital twin) — ISA-95 tree with per-equipment live mirror: bound values, KPIs, health badge, alarms, sparklines. ✨ AI contextualize proposes the hierarchy + tag-class bindings from your raw tag list; one click applies it.
- KPI framework — bind tags to semantic classes (run_state, counters,
power/energy, quality, vibration…) and the edge computes OEE
(availability × performance × quality, rolled up equipment→line),
energy baselines with overspend alarms, predictive-maintenance
health scores (IsolationForest when sklearn is present, statistical
fallback always), SPC (X-bar/R, EWMA, Cp/Cpk transforms), and an
anomaly feed (
/api/alarms?category=anomaly) — each with optional LLM "why" narration.GET /api/kpi/latest,get_kpisagent tool. - Alarm suite — auto-clear on return-to-normal (hysteresis), shelving,
escalation policies (severity bump + connector notification), per-tag
flood caps; Alarms view with filters and one-click AI shift-handover
report (also
GET /api/reports/daily). - Operator kiosk — fullscreen line view (
?kiosk=1or the Kiosk button): big live tiles, one-tap andon downtime reasons, and the AI copilot grounded in live plant data.
MQTT and HTTP webhooks in core; pip install 'runpilot[streams]' adds
Kafka (also covers Azure Event Hubs' Kafka endpoint, Confluent, MSK,
Redpanda), Azure Event Hubs (native), and AWS Kinesis — all behind
the same store-and-forward buffering. See commented examples in
config/runpilot.yaml.
POST /api/ai/chat streams a tool-calling agent over SSE. Cloud-first chain:
Claude (via litellm, ANTHROPIC_API_KEY env) → local Ollama → rule-based
advisories when fully offline. Tools: timeseries queries, asset tree, alarms,
production/OEE summary, anomaly stats, knowledge search (RAG), workflow
status — plus confirm-gated mutations (acknowledge alarm, PLC write through
WriteGuard). Skills are markdown playbooks in knowledge/skills/; four ship
by default (vibration triage, OEE analysis, Modbus commissioning, alarm-flood
triage). An optional background advisor (ai.agent.proactive_advisories)
turns statistical anomalies into info-level alarms.
| Module | Path | Purpose |
|---|---|---|
| Runtime | src/runpilot/runtime.py |
Composition root — wires drivers→bus→pipeline→API |
| Config | src/runpilot/config/ |
YAML schema, validation, device config |
| Drivers | src/runpilot/drivers/ |
8 protocols, registry, connection manager with backoff |
| Models | src/runpilot/models/ |
ISA-95 assets, alarm state machine, OEE |
| Pipeline | src/runpilot/pipeline/ |
Validator, enricher, aggregator, recorder, protobuf bus |
| Security | src/runpilot/security/ |
bcrypt auth, RBAC, audit log, PLC write guard |
| Workflow | src/runpilot/workflow/ |
DAG engine, transforms + schemas, bus-fed runner |
| AI | src/runpilot/ai/ |
Agent loop + tools + skills, anomaly detection, LLM chain, RAG |
| API | src/runpilot/api/ |
FastAPI + SSE, auth middleware, workflow/AI routes, static UI |
| Frontend | frontend/ |
React Flow workflow builder (Vite → static bundle) |
| Edge | src/runpilot/edge/ |
Bandwidth governor, retention, watchdog, HW profiles |
| Sync | src/runpilot/sync/ |
Store-and-forward queue, delta sync, backup/restore |
| Connectors | src/runpilot/connectors/ |
MQTT, HTTP, Kafka, Event Hubs, Kinesis; ring buffer + WAL |
See docs/ROADMAP.md for the Industry 5.0 accelerator positioning and the phased roadmap.
docker compose -f docker-compose.edge.yml upMIT