11 KiB
Work Plan — uopi
Phases Overview
| Phase | Name | Focus | Milestone |
|---|---|---|---|
| 0 | Scaffold | Repo structure, build toolchain, CI | Empty binary serves embedded index page |
| 1 | Core Backend | Broker, WebSocket protocol, REST skeleton | Live signal fan-out working end-to-end |
| 2 | EPICS Data Source | CA connect, monitor, metadata, write | Real EPICS PVs visible in browser console |
| 3 | Synthetic Data Source | DSP engine, Lua sandbox, signal composition | Synthetic signals computed and streamed |
| 4 | Frontend Foundation | Svelte skeleton, WS client, signal stores, view mode shell | Subscribed values rendered in browser |
| 5 | View Mode Widgets | All widget types rendered in view mode | Full interactive HMI panel |
| 6 | Edit Mode | Konva canvas, drag-and-drop, resize, properties pane | Can build and save a panel |
| 7 | Edit Mode — Advanced | Multi-select, align/distribute, undo/redo | Complete editor UX |
| 8 | Historical Data | Archive integration, time navigation UI | Replay past data in widgets |
| 9 | Signal Discovery | Signal tree, CSV import, EPICS Channel Finder | Usable without manual PV entry |
| 10 | Hardening | Docs, packaging, integration tests, performance | Production-ready binary |
Phase 0 — Scaffold
Goal: working build pipeline, empty binary that serves a placeholder page.
- Initialise Go module (
go mod init github.com/org/uopi) - Create directory layout:
cmd/uopi/,internal/,web/ - Svelte + Vite + TypeScript project in
web/ //go:embed web/distin backend;Makefilebuilds frontend then backend- HTTP server on configurable port; serves embedded frontend
- TOML config loading (
BurntSushi/tomlorpelletier/go-toml) - GitHub Actions CI:
make teston push README.mdwith quick-start instructions
Done when: ./dist/uopi starts and serves an "under construction" page.
Phase 1 — Core Backend
Goal: signal broker and WebSocket protocol working with a stub data source.
- Define
DataSourceinterface (internal/datasource/iface.go) - Implement in-memory stub data source (sine wave emitter) for development
- Implement
Broker(internal/broker/):- Subscribe / unsubscribe with reference counting
- Fan-out goroutine per signal
- Clean teardown when last subscriber leaves
- WebSocket handler (
internal/server/ws.go):subscribe/unsubscribe/write/historymessages- Pushes
updateandmetamessages to client - Graceful close on disconnect
- REST API skeleton (
internal/api/): datasources, signals, interfaces endpoints (stub responses) - Unit tests: broker fan-out, subscribe/unsubscribe lifecycle
Done when: a wscat client can subscribe to the stub source and receive timed updates.
Phase 2 — EPICS Data Source
Goal: real EPICS PVs readable and writable through the broker.
- CGo wrapper for EPICS
libca(internal/datasource/epics/):- Context init and cleanup
ca_create_channelwith connection callbackca_add_eventmonitor with value callbackca_putfor writes- DBR_CTRL get for metadata (units, limits, enum strings)
- Map CA DBR types to internal
DataTypeenum - Implement
DataSourceinterface for EPICS - Config:
ca_addr_list, reconnect interval - Handle disconnected/reconnected channels gracefully (quality = Bad/Uncertain)
- Integration test with SoftIOC (gated on
EPICS_BASEenv var)
Done when: a PV subscription round-trips from IOC → broker → WebSocket client with correct timestamp and units.
Phase 3 — Synthetic Data Source
Goal: users can define computed signals from existing signals.
- Define synthetic signal definition schema (JSON)
- Implement DAG evaluator: nodes re-evaluated on upstream change
- Built-in node types (
internal/dsp/):- Arithmetic: gain, offset, add, subtract, multiply, divide
- Statistics: moving average (by count, by time), RMS
- Filters: IIR lowpass/highpass/bandpass (via gonum or biquad)
- Calculus: finite-difference derivative, cumulative integral
- FFT / inverse FFT (gonum/dft)
- Threshold / clamp
- Custom expression (simple formula parser)
- Lua node: sandboxed
gopher-luastate per signal; inputs as globals - Load synthetic definitions from
synthetic.jsonat startup - REST endpoint to CRUD synthetic signal definitions (persists to JSON file)
- Unit tests for each DSP node type
Done when: a synthetic moving-average of an EPICS PV is visible in the WebSocket stream.
Phase 4 — Frontend Foundation
Goal: Svelte app connects to WebSocket and reactively displays values.
- Svelte project structure: routes for view (
/) and edit (/edit) - WebSocket client (
ws.ts): connect, reconnect, message dispatch - Signal store factory (
stores.ts):getStore(signalName)returns reactive store - Subscription manager: reference counting mirrors broker's
- View mode shell: collapsible interface list pane + empty canvas area
- Fetch and list interfaces from REST API
- Load interface XML; parse widget definitions
- Render a minimal text-view widget reactively from signal store
- Basic responsive layout; DPI adaptation for canvas
Done when: loading a manually crafted XML interface displays live PV values.
Phase 5 — View Mode Widgets
Goal: all widget types rendered and interactive in view mode.
- Text view widget
- Gauge widget (SVG arc, configurable range/thresholds)
- Vertical / horizontal bar widget
- LED widget (condition evaluator, configurable colours)
- Multi-LED widget (bitset, per-bit labels)
- Set-value widget (input + Set button; sends
writeover WS) - Button widget (sends fixed value on click)
- Plot widget:
- Time-series (uPlot, streaming buffer of N points)
- FFT, waterfall, histogram, bar chart, logic analyser (ECharts)
- Multi-signal support; legend
- Text label (static)
- Image widget (base64 or server URL)
- Link widget (navigates to another interface)
- Right-click context menu: signal info dialog, copy name, export CSV
- Svelte component tests for each widget type (mock store)
Done when: a complete HMI panel with multiple widget types runs smoothly at 60 fps.
Phase 6 — Edit Mode (Core)
Goal: users can build and save an interface from scratch.
- Konva stage setup in edit mode;
devicePixelRatioscaling - Widget renderer adapter: same widget components rendered on Konva layer
- Signal tree pane: fetch signal list, tree display, filter/search
- Drag signal from tree → drop on canvas → widget type picker
- Place widget at drop coordinates with default size
- Single selection: bounding box + resize handles via Konva
Transformer - Move widget by dragging body
- Delete widget (× button or Del key)
- Properties pane: common options (label, position, size)
- Per-widget property editors (range, colour, plot type, etc.)
- Save interface to server (POST/PUT XML)
- Load interface from server (GET XML, populate canvas)
- Export / import local XML file
Done when: an engineer can create a panel with 5+ widgets, save it, reload it, and see live data.
Phase 7 — Edit Mode (Advanced)
Goal: complete editing UX matching the functional spec.
- Multi-select: Ctrl+click toggle; rubber-band area select
- Group move: drag any selected widget to move all
- Group delete: Del key on selection
- Align toolbar: left / center-H / right / top / center-V / bottom
- Distribute toolbar: evenly by center / by gap (H and V)
- Undo / redo: command pattern, Ctrl+Z / Ctrl+Shift+Z
- Snap-to-grid (optional, toggle in toolbar)
- Add text label tool
- Add image tool (upload to server or embed base64)
- Add link tool
- Collapsible signal tree pane and properties pane (toggle buttons)
- Right-click on interface in list: Edit / Clone / Delete
Done when: the editor feels complete and the undo stack works reliably.
Phase 8 — Historical Data
Goal: users can navigate to past timestamps using archive data.
- EPICS Archive Appliance HTTP API client (
internal/datasource/epics/archive.go) - Broker
History()dispatch: route to correct data source'sHistory()impl - WebSocket
historyrequest / response handling - Frontend: time range picker in view mode toolbar
- "Live" button: flushes history state, re-subscribes to live updates
- Plot widget: switch between streaming and historical range mode
- Point-value widgets: show value at selected timestamp
Done when: a plot can display 24 hours of archived data with a time slider.
Phase 9 — Signal Discovery
Goal: users can find signals without knowing their names in advance.
- EPICS: attempt Channel Finder or
cainfoenumeration; fall back to manual entry - Signal tree: lazy-load children on expand
- Manual add custom PV (EPICS): text input in signal tree
- New synthetic signal wizard: name, inputs, node graph UI
- CSV import: parse
NAME, DataSource, DS_PARAMETERS; add to tree
Done when: a new user can find and subscribe to a PV without prior knowledge.
Phase 10 — Hardening
Goal: production-quality binary, docs, performance validation.
- End-to-end integration tests (real SoftIOC, headless browser via Playwright)
- Benchmark: 20 clients × 100 signals; verify < 5 ms fan-out latency
- Benchmark: frontend at 10 Hz update rate; verify 60 fps
- Static binary validation: run on RHEL 7 (Docker image
centos:7) - Security: Lua sandbox audit; XML XXE protection; write-permission guard
- Graceful shutdown: drain WS connections, flush pending writes
- Structured logging (
log/slog) /metricsendpoint (Prometheus format) for server monitoring- Complete
README.mdand operator docs - Release:
goreleaserconfig for Linux amd64 + arm64 binaries
Done when: binary passes integration tests on CentOS 7 container with a SoftIOC.
Estimated Effort
These are rough single-developer estimates. Parallel work across backend and frontend where possible will reduce calendar time.
| Phase | Effort |
|---|---|
| 0 | 1–2 days |
| 1 | 3–5 days |
| 2 | 1–2 weeks |
| 3 | 1–2 weeks |
| 4 | 3–5 days |
| 5 | 2–3 weeks |
| 6 | 2–3 weeks |
| 7 | 1–2 weeks |
| 8 | 1 week |
| 9 | 1 week |
| 10 | 1–2 weeks |
| Total | ~14–20 weeks |