Untrack generated runtime state under workspace/data (SQLite audit db + WAL/SHM, config/control-logic/synthetic versions, trash, acl) plus dev logs and iocsh history; these are regenerated on each run. Curated fixtures (demo.xml, epics_test.xml, the EPICS test IOC db, run.sh, st.cmd) stay tracked. Extend .gitignore so they don't return. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
uopi
A web-based HMI (Human-Machine Interface) for monitoring and controlling EPICS-based scientific and industrial control systems.
uopi runs as a single portable binary with no runtime dependencies. Point any browser at it — including over an SSH tunnel — and you get a full drag-and-drop HMI builder and live data viewer.
Features
| Feature | Details |
|---|---|
| Live data | EPICS Channel Access (CA) / PVAccess and user-defined synthetic signals |
| HMI editor | Drag-and-drop widgets, resize, align/distribute, undo/redo, saved as XML |
| Widget library | Text view/label, gauge, LED, multi-LED, bar, set-point control, button, image, link, plots |
| Plot types | Time-series (uPlot), FFT, waterfall, histogram, bar chart, logic analyser |
| Plot panels | Dedicated chart panels with a recursive split layout (tmux/IDE-style) where plots fill the viewport |
| Panel logic | In-editor node-graph flow editor (triggers → actions, dialogs) that runs client-side in view mode |
| Control logic | Server-side always-on flow graphs with cron/alarm triggers and Lua blocks |
| Configuration manager | Versioned configuration sets (typed signal schemas, grouped) and instances (values) with validation, array/CSV editing, apply-to-signals, and JSON import/export |
| Version history & diff | Git-style history for panels, synthetic signals, control logic and config sets/instances: view, fork, promote any revision, with unified / side-by-side diff |
| Local variables | Panel-scoped state variables for set-points, toggles and counters used by logic |
| Historical data | EPICS Archive Appliance integration via REST; time range picker in UI |
| Synthetic signals | Compose, filter, and transform signals via a wizard or visual node-graph editor (gain, offset, moving average, lowpass, highpass, derivative, integral, clamp, formula); panel/user/global visibility scopes |
| Access control | Identity via trusted proxy header; per-user global level (write/read-only/no-access); per-panel ownership + sharing; nested folders; optional logic-editor allowlist |
| Multi-client | Subscriptions are multiplexed — N clients share one upstream connection per signal |
| Signal discovery | Filter/search, CSV import, manual add, Channel Finder proxy |
| Observability | Prometheus-format metrics at /metrics |
| Single binary | Frontend assets embedded at build time — no web server needed |
Quick Start
Prerequisites
- Go 1.22+ — the only build dependency (the frontend is bundled by a pure-Go esbuild wrapper)
- EPICS Base (
libca) — only required for theepicsbuild tag; the stub and synthetic data sources work without it
Build
# Full build — bundles the frontend then compiles the binary
make all
The binary is written to dist/uopi.
Run
./dist/uopi # uses default config (stub data source only)
./dist/uopi --config uopi.toml # explicit config file
Then open http://localhost:8080 in your browser.
SSH tunnel example:
ssh -L 8080:localhost:8080 user@controlhost
# then open http://localhost:8080 locally
Configuration
Create uopi.toml (all fields are optional — shown values are defaults):
[server]
listen = ":8080"
storage_dir = "./interfaces" # where interface XML, ACL and logic data are stored
# Identity & access control (all optional)
trusted_user_header = "" # HTTP header carrying the proxy-authenticated user
default_user = "" # identity used when the header is absent (LAN/dev)
# logic_editors = [] # restrict who may edit panel/control logic (users or groups)
# [[server.blacklist]] # downgrade a user: level = "readonly" or "noaccess"
# user = "guest"
# level = "readonly"
# [[groups]] # named user sets, referenced by panel sharing
# name = "operators"
# members = ["alice", "bob"]
[datasource.epics]
enabled = false # requires build with -tags epics
ca_addr_list = "" # overrides EPICS_CA_ADDR_LIST if non-empty
archive_url = "" # EPICS Archive Appliance base URL, e.g. http://archiver:17665
channel_finder_url = "" # EPICS Channel Finder base URL (optional)
[datasource.synthetic]
enabled = true
All settings can be overridden with environment variables using the UOPI_ prefix and double underscores for nesting:
UOPI_SERVER_LISTEN=":9090" UOPI_DATASOURCE_EPICS_ENABLED=true ./dist/uopi
Building with EPICS Support
The EPICS data source uses CGo bindings to libca. First set up the EPICS environment variables, then:
export EPICS_BASE=/path/to/epics/base
export CGO_CFLAGS="-I$EPICS_BASE/include -I$EPICS_BASE/include/os/Linux"
export CGO_LDFLAGS="-L$EPICS_BASE/lib/linux-x86_64 -lca -lCom"
make backend-epics
Then enable it in uopi.toml:
[datasource.epics]
enabled = true
API
The REST API is available under /api/v1. Key endpoints:
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/datasources |
List registered data sources |
GET |
/api/v1/signals?ds=<name> |
List signals for a data source |
GET |
/api/v1/signals/search?q=<query> |
Search signals across all sources |
GET |
/api/v1/channel-finder?q=<query> |
Proxy to EPICS Channel Finder |
GET/POST |
/api/v1/interfaces |
List or create HMI interfaces |
GET/PUT/DELETE |
/api/v1/interfaces/{id} |
Read, update, or delete an interface |
POST |
/api/v1/interfaces/{id}/clone |
Duplicate an interface |
POST |
/api/v1/interfaces/reorder |
Reorder panels / move them between folders |
GET/PUT |
/api/v1/interfaces/{id}/acl |
Read or set a panel's sharing rules |
GET/POST |
/api/v1/folders |
List or create panel folders |
PUT/DELETE |
/api/v1/folders/{id} |
Rename/reparent or delete a folder |
GET/POST/DELETE |
/api/v1/synthetic |
Manage synthetic signal definitions |
GET/POST |
/api/v1/controllogic |
List or create server-side control-logic graphs |
GET/PUT/DELETE |
/api/v1/controllogic/{id} |
Read, update, or delete a control-logic graph |
GET/POST |
/api/v1/config/sets |
List or create configuration sets (schemas) |
GET/PUT/DELETE |
/api/v1/config/sets/{id} |
Read, update, or delete a config set |
GET/POST |
/api/v1/config/instances |
List or create configuration instances (values) |
GET/PUT/DELETE |
/api/v1/config/instances/{id} |
Read, update, or delete a config instance |
POST |
/api/v1/config/instances/{id}/apply |
Write an instance's values to their target signals |
GET |
/api/v1/config/{sets|instances}/diff |
Structural diff between two revisions |
GET |
/api/v1/{interfaces|synthetic|controllogic|config/sets|config/instances}/{id}/versions |
List revisions; …/{version} fetches one |
POST |
…/{id}/versions/{v}/promote |
Promote a revision to current |
POST |
…/{id}/versions/{v}/fork |
Fork a revision into a new document |
GET |
/api/v1/me |
Caller identity, access level, groups, logic-edit permission |
GET |
/api/v1/usergroups |
List configured users and groups (for sharing) |
GET |
/metrics |
Prometheus-format server metrics |
GET |
/healthz |
Health check |
WebSocket live data is at ws://<host>/ws. See docs/TECHNICAL_SPEC.md for the message protocol.
Development
# Build frontend only (output → web/dist/)
make frontend
# Build backend only (frontend must already be built)
make backend
# Run the server directly without a binary (uses stub data source)
go run ./cmd/uopi
# Run all tests
make test
# Run tests with race detector
make race
# Run benchmarks
make bench
# Static analysis
make lint
# Clean build artefacts
make clean
Observability
The /metrics endpoint exposes Prometheus-format counters and gauges:
uopi_uptime_seconds — seconds since start
uopi_ws_connections — current open WebSocket connections
uopi_signal_subscriptions — current unique upstream signal subscriptions
uopi_ws_messages_in_total — total messages received from clients
uopi_ws_messages_out_total — total messages sent to clients
uopi_write_ops_total — total signal write operations
uopi_history_requests_total — total historical data requests
Scrape with any Prometheus-compatible tool or view directly in the browser.
Docs
| Document | Description |
|---|---|
docs/FUNCTIONAL_SPEC.md |
User-facing feature specification |
docs/TECHNICAL_SPEC.md |
Architecture, WebSocket protocol, API design |
docs/WORK_PLAN.md |
Phased development plan with milestones |
Release
Releases are built with goreleaser:
goreleaser release --clean
See .goreleaser.yaml. Note that release binaries are built without EPICS support (CGO_ENABLED=0) for maximum portability. Build from source with -tags epics for CA connectivity.
License
TBD