f7f297c3df
Adds full array/waveform support through the synthetic DSP engine: a dsp.Sample value model (scalar or []float64), array ops (index, slice, sum, mean, min, max, length, fft) with an in-tree radix-2 FFT, and static type propagation (OpOutputType) that the editor mirrors to colour wires by data type and flag invalid wirings. Stateful filters and lua stay scalar-only. Adds a waveform plot mode (x-vs-index trace). Also: errored-node hover reasons, S/N add-signal/add-node HUD shortcuts in the synthetic editor, and view-mode widgets that blend with the canvas background (chrome kept in edit mode). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
360 lines
16 KiB
Markdown
360 lines
16 KiB
Markdown
# Functional Specification — uopi
|
||
|
||
## 1. Purpose
|
||
|
||
uopi is a web-based HMI (Human-Machine Interface) for monitoring and controlling industrial/scientific systems, primarily EPICS-based control systems. It runs as a single server process and is accessed entirely through a web browser, making it suitable for SSH-tunnelled remote access.
|
||
|
||
---
|
||
|
||
## 2. Users and Roles
|
||
|
||
User identity is established from a header set by a trusted authenticating reverse
|
||
proxy (`server.trusted_user_header`), with a configurable `default_user` fallback for
|
||
unproxied/LAN deployments. There is no login page inside the application.
|
||
|
||
**Global access levels.** Every user is trusted with full **write** access by default. A
|
||
configuration blacklist can downgrade specific users to **read-only** (view only, no
|
||
writes) or **no access** (denied). Named **groups** are defined in configuration and
|
||
referenced by per-panel sharing.
|
||
|
||
**Per-panel access.** Panels are owned by their creator and private by default. Owners
|
||
share a panel with specific users/groups (read or write) or make it public; the owner's
|
||
global level always caps the per-panel permission. Panels are organised into nested
|
||
folders whose permissions inherit down the chain. See §9.
|
||
|
||
**Logic-edit restriction.** Adding/editing panel logic and server-side control logic can
|
||
optionally be limited to an allowlist of users/groups (`server.logic_editors`); when
|
||
unset, any writer may edit logic.
|
||
|
||
---
|
||
|
||
## 3. System Modes
|
||
|
||
### 3.1 View Mode (default)
|
||
|
||
The default mode when opening the application.
|
||
|
||
**Interface list pane (left, collapsible, resizable)**
|
||
- Displays all interfaces saved on the server.
|
||
- Right-click on an interface: options to open in Edit mode or clone it.
|
||
- "New interface" button opens Edit mode with a blank canvas.
|
||
- The pane width can be adjusted by dragging the resize handle on its right edge.
|
||
|
||
**HMI canvas (center)**
|
||
- Renders the selected interface as a live, interactive panel.
|
||
- Widgets display real-time data; controls (set-value, buttons) are active.
|
||
- Panel logic (if any) runs while the panel is open (see §6).
|
||
- No drag, resize, or layout operations are possible in this mode.
|
||
- Right-clicking any widget opens a context menu:
|
||
- **Signal info** — shows DS name, type, unit, range, current value and timestamp.
|
||
- **Copy signal name** — copies the signal identifier to the clipboard.
|
||
- **Export data to CSV** — downloads buffered data for the signal(s) used by the widget.
|
||
|
||
> Dedicated multi-signal plotting is provided by **plot panels** — a special interface
|
||
> kind whose plots fill the viewport (see §3.3) — rather than a separate live "Plot tab".
|
||
|
||
**Top toolbar**
|
||
- Show/hide interface list pane.
|
||
- **⏱ History** button: toggle historical time navigation bar.
|
||
- **⚙ Control logic** button: open the server-side control-logic editor (see §7).
|
||
Shown only to users permitted to edit logic.
|
||
- Zoom control (A− / % / A+): adjust the UI scale (see §3.4).
|
||
- Edit button: switch to Edit mode for the current interface.
|
||
- Connection status indicator and signed-in user chip.
|
||
|
||
**Historical time navigation bar** (shown when History is active)
|
||
- Date/time range pickers (start and end).
|
||
- **Load** button fetches archive data and replaces live data in all widgets.
|
||
- **Live** button discards archive data and resumes real-time streaming.
|
||
- Status shown on plot widgets: "Loading history…", "No archive data for this range", "Archive unavailable".
|
||
|
||
### 3.2 Edit Mode
|
||
|
||
Activated via the "New interface" button or by clicking Edit in the toolbar.
|
||
|
||
**Signal tree pane (left, resizable and collapsible)**
|
||
- Shows all signals known to each connected data source.
|
||
- Sources are shown as top-level nodes; signals are nested within.
|
||
- User can add custom entries:
|
||
- For EPICS: manually enter a PV name.
|
||
- For Synthetic: define a new synthetic signal via the Synthetic Wizard (see §5.2).
|
||
- Filter/search box to narrow the list.
|
||
- Synthetic signals show an edit (✎) button to reopen the wizard.
|
||
|
||
**Center area — Layout / Logic tabs**
|
||
- **Layout**: free-form canvas where widgets can be placed at arbitrary pixel positions,
|
||
over a background grid with snap-to-grid. (For plot panels this is replaced by the
|
||
split-layout editor; see §3.3.)
|
||
- **Logic**: a node-graph flow editor for panel behaviour (see §6). Hidden when the user
|
||
is not permitted to edit logic.
|
||
|
||
**Local variables**
|
||
- A panel may declare panel-scoped variables (data source `local`) with initial values.
|
||
- They are written by set-value widgets, buttons and logic actions, and referenced
|
||
anywhere a signal is. Added from the signal tree's *Local* group / the Logic palette.
|
||
|
||
**Properties pane (right, resizable and collapsible)**
|
||
- Appears when one or more widgets are selected.
|
||
- Displays and edits all options for the selected widget (see §4).
|
||
- Width is adjustable by dragging the resize handle on its left edge.
|
||
|
||
**Top toolbar**
|
||
- Show/hide signal pane / properties pane.
|
||
- Undo / Redo (also Ctrl+Z / Ctrl+Shift+Z).
|
||
- Import / Export interface to/from local XML file.
|
||
- Save interface to server.
|
||
- Close (return to View mode).
|
||
- Zoom control (A− / % / A+).
|
||
|
||
### 3.3 Plot Panels (Split Layout)
|
||
|
||
A **plot panel** is a special interface kind dedicated to charts. Rather than free-form
|
||
widget boxes, plots **fill the viewport** and the user divides the space between them with
|
||
a recursive split layout (tmux/IDE-style tiling). Created via **+ Plot** in the interface
|
||
list, opening with a single full-viewport empty plot.
|
||
|
||
**Editing (Edit mode):**
|
||
- Hover a pane and use its split buttons (**⬌** vertical / **⬍** horizontal) to divide it;
|
||
a new empty plot fills the freed half. Nesting is unlimited.
|
||
- Drag the divider between two panes to resize them.
|
||
- Click a pane to select it and configure its plot in the Properties pane (plot sub-type,
|
||
Y range, time window, legend, per-signal colour). Drag a signal onto a pane to add it.
|
||
- A pane's **✕** removes it; the layout collapses onto its sibling.
|
||
|
||
**View mode:** the saved split layout fills the screen with live, streaming plots; the
|
||
per-widget right-click menu (signal info / copy / CSV) and historical time navigation
|
||
apply as for any time-series plot.
|
||
|
||
Per-plot configuration reuses the standard Plot widget (§4.4), so all plot sub-types and
|
||
options are available within a pane.
|
||
|
||
### 3.4 UI Zoom
|
||
|
||
The toolbar in both modes contains a zoom control (**A−** / **nn%** / **A+**) that adjusts the base font size of the entire UI:
|
||
|
||
- 11 zoom steps from 50% to 250% (50, 60, 75, 85, 100, 115, 130, 150, 175, 200, 250%).
|
||
- The selected zoom level is persisted in `localStorage` and restored on the next page load.
|
||
- At 100%, the base font size adapts to viewport height via `clamp(13px, 1.5vh, 18px)`, making the UI naturally usable on 4K displays without any manual adjustment.
|
||
|
||
---
|
||
|
||
## 4. Widgets
|
||
|
||
### 4.1 Creating Widgets
|
||
|
||
Drag a signal from the signal tree and drop it onto the canvas. A picker appears showing all widget types compatible with the signal's data type. The user selects one and the widget is placed at the drop location with default size.
|
||
|
||
### 4.2 Selecting Widgets
|
||
|
||
- Single click: select one widget (deselects others).
|
||
- Ctrl+click: add/remove a widget from the current selection.
|
||
- Click-drag on empty canvas area: rubber-band area select.
|
||
|
||
When a widget is selected, a bounding box appears with:
|
||
- 8 resize handles (corners + midpoints).
|
||
- A delete button (×) in the top-right corner.
|
||
- The widget can be moved by dragging its body.
|
||
|
||
### 4.3 Multi-selection Operations
|
||
|
||
When multiple widgets are selected:
|
||
- Drag any selected widget to move them all together.
|
||
- Del key deletes all selected widgets.
|
||
- An align/distribute toolbar appears above the canvas with:
|
||
- Align left / center horizontal / right.
|
||
- Align top / center vertical / bottom.
|
||
- Distribute evenly (horizontal/vertical).
|
||
|
||
### 4.4 Widget Catalogue
|
||
|
||
| Widget | Compatible signal types | Description |
|
||
|--------|-------------------------|-------------|
|
||
| Text view | any scalar | Displays `name: value unit` |
|
||
| Gauge | numeric scalar | Circular or arc gauge with configurable range |
|
||
| Vertical bar | numeric scalar | Vertical level indicator |
|
||
| Horizontal bar | numeric scalar | Horizontal level indicator |
|
||
| Set value | numeric, string, or enum; writable | Input field or enum dropdown + Set button |
|
||
| LED | boolean / numeric | Coloured indicator with configurable condition and label |
|
||
| Multi-LED | integer (bitset) | One LED per bit with individual labels and conditions |
|
||
| Button | writable | Sends a fixed value or command on click |
|
||
| Plot | numeric scalar or array | Multi-signal plot; sub-types below |
|
||
| Text label | — | Static text annotation |
|
||
| Image | — | Static image |
|
||
| Link | — | Button navigating to another interface |
|
||
|
||
**Plot sub-types:**
|
||
|
||
| Sub-type | Signal requirement |
|
||
|----------|--------------------|
|
||
| Time series | numeric scalar(s) |
|
||
| FFT | 1-D numeric array |
|
||
| Waterfall | 1-D numeric array (repeated) |
|
||
| Histogram | numeric scalar(s) |
|
||
| Bar chart | numeric scalar(s) |
|
||
| Logic analyser | boolean / integer (bitset) |
|
||
| Waveform | 1-D numeric array (latest sample, x-vs-index) |
|
||
|
||
### 4.5 Widget Properties (Properties Pane)
|
||
|
||
Common to all:
|
||
- Label text, font size, text colour.
|
||
- Position (X, Y) and size (W, H) — editable numerically.
|
||
- Data source and signal name.
|
||
|
||
Per type:
|
||
- **Gauge / Bar**: min value, max value, alert thresholds with colours, unit label.
|
||
- **LED / Multi-LED**: condition expression (e.g. `value > 0`), colours for true/false states, label.
|
||
- **Plot**: plot sub-type selector, Y-axis range (auto or manual min/max), time window duration, legend position (top / bottom / none), value format string.
|
||
- **Set value**: no special options — enum mode is detected automatically from signal metadata.
|
||
- **Link**: target interface name.
|
||
|
||
### 4.6 Set-value Widget — Enum Mode
|
||
|
||
When the signal's metadata reports enum strings (e.g. EPICS mbbi/mbbo records):
|
||
- The input field is replaced by a `<select>` dropdown showing all enum options.
|
||
- The current live value is shown as the pre-selected option (display only).
|
||
- The user selects a value from the dropdown, then clicks **Set** to write it.
|
||
- The Set button is always required; changing the dropdown does not write immediately.
|
||
|
||
---
|
||
|
||
## 5. Data Sources
|
||
|
||
### 5.1 EPICS
|
||
|
||
- Connects to an EPICS environment via Channel Access (CA).
|
||
- On connect, retrieves full metadata from the PV name: data type, engineering units, display range, alarm limits, enum strings (for mbbi/mbbo records), read/write mode.
|
||
- Uses `ca_add_event` monitors — no polling.
|
||
- When an EPICS Archive Appliance is configured, the server can satisfy historical data requests from the toolbar time navigator.
|
||
|
||
### 5.2 Synthetic Signals
|
||
|
||
A signal defined by composing one or more input signals through a chain of processing nodes.
|
||
|
||
**Two authoring surfaces:**
|
||
- **Wizard** — for the common single-input case: name the signal, pick an input and a
|
||
processing node, set parameters, Create.
|
||
- **Node-graph editor** — a visual editor that wires one or more inputs through a chain of
|
||
DSP blocks, for multi-input pipelines. It compiles to the same inputs + pipeline model.
|
||
|
||
**Visibility scope:** each synthetic signal is scoped as *panel* (visible only to the panel
|
||
that created it), *user*, or *global* (shared with everyone).
|
||
|
||
The dialogs are resizable and default to a width that accommodates the Lua editor.
|
||
|
||
**Editing an existing synthetic signal:** click the ✎ button next to the signal in the tree to reopen the editor.
|
||
|
||
**Built-in node types:**
|
||
|
||
| Node | Parameters | Description |
|
||
|------|------------|-------------|
|
||
| Source | DS, signal name | Input from any data source |
|
||
| Gain | factor | Multiply by constant |
|
||
| Offset | value | Add constant |
|
||
| Moving average | window (samples) | Rolling mean |
|
||
| Low-pass filter | frequency (Hz), order (1–8) | IIR low-pass; correct for non-uniform sample rates |
|
||
| Formula | expression | Inline math (variables: `a`, `b`, …) |
|
||
| Lua script | script | Arbitrary Lua 5.1 with persistent state table |
|
||
|
||
**Lua editor:** the Lua node parameter uses a code editor with syntax highlighting (keywords, strings, comments, numbers rendered in distinct colours). The editor is a full-height multi-line input that grows with the dialog.
|
||
|
||
---
|
||
|
||
## 6. Panel Logic
|
||
|
||
The **Logic** tab in the panel editor is a node-graph (Node-RED-style) flow editor that
|
||
gives a panel interactive behaviour. The flow runs **client-side** while the panel is open
|
||
in View mode; it is saved as part of the interface XML. The tab and any logic editing are
|
||
gated by the logic-edit restriction (§2).
|
||
|
||
**Authoring:** drag blocks from the palette (or click to add), connect output ports to
|
||
input ports, and edit the selected node in the inspector. Expression fields reference a
|
||
signal as `{ds:name}` and a panel-local variable by its bare name, and support arithmetic,
|
||
comparisons, boolean logic and common math functions. The editor has its own undo/redo and
|
||
copy/paste.
|
||
|
||
**Node categories:**
|
||
|
||
| Category | Nodes |
|
||
|----------|-------|
|
||
| Triggers | Button press, threshold crossing, value change, timer/interval, panel loop, On-open / On-close lifecycle |
|
||
| Logic | AND gate, If (then/else), Loop (count or while) |
|
||
| Actions | Write to signal/variable, Delay, Log; Accumulate / Export-CSV / Clear for in-memory data arrays |
|
||
| Dialogs | Info and Error pop-ups; Set-point prompt (asks the user for a number and writes it) |
|
||
|
||
**System helpers in expressions:** `{sys:time}` (epoch seconds) and `{sys:dt}` (seconds
|
||
since the firing trigger last fired).
|
||
|
||
---
|
||
|
||
## 7. Control Logic
|
||
|
||
**Control logic** is server-side automation: flow graphs that run continuously on the
|
||
server, independent of any connected client (unlike panel logic, which only runs while a
|
||
panel is open). Opened with the **⚙ Control logic** button in the View-mode toolbar and
|
||
managed through the REST API.
|
||
|
||
- Triggers include *cron* schedules and signal *alarm*/threshold conditions.
|
||
- A **Lua** block provides custom logic; results are written back to signals.
|
||
- Each graph can be enabled/disabled independently; saving reloads the engine live.
|
||
- Editing is gated by the logic-edit restriction (§2).
|
||
|
||
---
|
||
|
||
## 8. Interface Persistence
|
||
|
||
- Interfaces are saved to the server in XML format and are available to all connected clients.
|
||
- Per-panel access rules and panel-folder placement are stored server-side (sidecar JSON).
|
||
- Saved versions are retained; a panel's version history can be listed, tagged and promoted.
|
||
- Export/Import allows local file exchange of XML files.
|
||
- The XML schema records: interface kind (panel/plot) and split layout, widget type,
|
||
position, size, signal bindings, all property values, local variables, and panel logic.
|
||
|
||
---
|
||
|
||
## 9. Access Control, Sharing & Folders
|
||
|
||
**Identity** is resolved per request from the trusted proxy header, falling back to
|
||
`default_user`. The `/api/v1/me` endpoint reports the caller's identity, global level,
|
||
group memberships and whether they may edit logic; the UI hides affordances accordingly.
|
||
|
||
**Global levels** (§2): write (default), read-only, or no-access via the config blacklist.
|
||
|
||
**Per-panel sharing:** the **Share** dialog on a panel grants read or write to specific
|
||
users or groups, or marks the panel public. New panels are private to their owner. The
|
||
owner's global level caps any per-panel grant.
|
||
|
||
**Folders:** panels are organised into nested folders; permissions inherit down the folder
|
||
chain. Panels can be dragged to reorder or move between folders.
|
||
|
||
**Logic-editor allowlist:** when `server.logic_editors` is set, only the listed users/
|
||
groups may add or change panel logic and control logic; everyone else keeps full access to
|
||
the rest of the application.
|
||
|
||
---
|
||
|
||
## 10. Historical Data Navigation
|
||
|
||
When the server has archive access configured:
|
||
- The **⏱ History** button in the View mode toolbar reveals a time range bar.
|
||
- Users set a start and end date/time and click **Load**.
|
||
- Plot widgets fetch and display the archived data for the selected range.
|
||
- Point-value widgets (text view, gauge, bar, LED) show the value at the end of the selected range.
|
||
- The **Live** button resumes real-time streaming.
|
||
- Status overlays on plot widgets indicate loading, empty, or error states.
|
||
|
||
---
|
||
|
||
## 11. Non-functional Requirements
|
||
|
||
| Requirement | Target |
|
||
|-------------|--------|
|
||
| Server binary | Single statically-linked executable; no runtime dependencies |
|
||
| Target platform | Linux x86-64; also aarch64 optional |
|
||
| Minimum server OS | RHEL/CentOS 7 (glibc 2.17) or equivalent |
|
||
| Concurrent clients | ≥ 20 simultaneous browser clients |
|
||
| Data fan-out latency | < 5 ms added latency vs. raw EPICS update rate |
|
||
| Frontend responsiveness | 60 fps canvas rendering during live updates |
|
||
| Screen DPI | UI scales with viewport height; manual zoom 50–250% |
|
||
| Plot buffer | 200,000 samples per signal retained in-browser |
|