uopi is a web-based HMI for monitoring and controlling EPICS-based scientific and industrial systems.
It runs as a single binary — open a browser, no installation required.
How it works
The server connects to your control system (EPICS, synthetic signals).
Open http://<host>:8080 in any browser.
Pick an HMI panel from the left sidebar to view live data.
Switch to Edit mode to build or modify panels.
Two modes
👁 View Mode
Monitor live signals in read-only HMI panels. Right-click any widget for signal info or to export data as CSV.
✏ Edit Mode
Drag signals from the tree onto the canvas, resize and configure widgets, then save your panel to the server.
Quick navigation
Click an interface in the left list to open it in view mode.
Click Edit (top-right) to switch to the editor.
Click ✕ Close in Edit mode to return to View mode.
Press ? anywhere to open this help.
);
}
function SectionView() {
return (
View mode displays a live HMI panel. Widget values update in real time via WebSocket.
View mode layout: ① interface list · ② live canvas · WebSocket status top-right.
Interface list
Click any panel name to load it on the canvas.
Use the action buttons (edit ✎, clone ⧉, delete ✕) that appear on hover.
Collapse the list by clicking the ◀ button at the top.
Context menu
Right-click any widget to:
View the signal name and current value.
Copy the signal name to the clipboard.
Export the last 1 000 samples as a CSV file.
Connection status
The coloured chip in the top-right shows the WebSocket connection state.
A red "disconnected" banner appears at the top while reconnecting — live values will resume automatically.
);
}
function SectionEdit() {
return (
Edit mode gives you a drag-and-drop canvas, a signal tree on the left, and a properties pane on the right.
Edit mode layout: ① signal tree · ② canvas with snap grid · ③ properties pane.
Adding widgets
Drag a signal from the tree → drop on canvas → choose a widget type.
Selecting and moving
Click a widget to select it — resize handles appear at the corners and edges.
Drag the widget body to move it; drag a handle to resize.
Ctrl+click to add/remove widgets from the selection.
Drag on empty canvas to rubber-band select multiple widgets.
Arrow keys nudge the selection by 1 px (or grid size with Shift).
Align & distribute
When 2+ widgets are selected the alignment toolbar appears. Buttons align edges or centres; with 3+ selected you can distribute spacing evenly.
Saving
Save — stores the panel on the server (XML). A ● indicates unsaved changes.
Export — downloads the XML file locally.
Import — loads an XML file from disk (replaces the current canvas).
);
}
function SectionWidgets() {
return (
Each widget is configured in the Properties pane on the right.
The available options depend on the widget type.
Widget type overview — drag a signal from the tree to place one.
{[
{ type: 'Text View', desc: 'Shows the current numeric or string value. Supports alarm colouring.' },
{ type: 'Gauge', desc: 'Circular arc gauge with configurable range and colour zones.' },
{ type: 'Bar (H / V)', desc: 'Horizontal or vertical filled bar, great for percentages or levels.' },
{ type: 'LED', desc: 'Coloured indicator. Turns green/red based on a threshold or enum value.' },
{ type: 'Multi-LED', desc: 'Bitset indicator — shows individual bits of an integer signal as LEDs.' },
{ type: 'Set Value', desc: 'Text input that writes a value back to a writable signal on Enter or click.' },
{ type: 'Button', desc: 'Sends a fixed value to a signal when clicked.' },
{ type: 'Plot', desc: 'Multi-signal chart. Supports time-series (uPlot), FFT, waterfall, histogram, bar chart, logic analyser.' },
{ type: 'Text Label', desc: 'Static annotation text — no signal required.' },
{ type: 'Image', desc: 'Static image loaded from a URL.' },
{ type: 'Link', desc: 'Navigates to another interface when clicked.' },
].map(w => (
{w.type}{w.desc}
))}
Plot widget details
Add multiple signals via the Signals section in the Properties pane.
Choose plot type: timeseries, FFT, waterfall, histogram, bar, or logic.
Set Time window to control how many seconds of live data are shown.
In Historical mode the entire requested range is loaded at once.
);
}
function SectionSignals() {
return (
The signal tree (left pane in Edit mode) lets you browse, search, and manage the signals available from each data source.
Finding signals
Type in the Filter signals… box to search by name or description.
Click a group header to collapse or expand that data source.
Drag any signal onto the canvas to create a widget.
Adding custom signals
Click + next to a group name to type a PV name manually.
Custom signals are saved in your browser (localStorage) and persist across reloads.
Click ✕ on a custom signal to remove it.
CSV import
Click CSV to import a list of signals from a file. Each line should be either:
PV_NAME — added to the epics data source.
ds,PV_NAME — added to the specified data source.
Lines starting with # are treated as comments.
Channel Finder
If your server is configured with an EPICS Channel Finder URL,
a 🔍 button appears when you type in the filter box.
Click it to search Channel Finder and import matching PV names.
Synthetic signals
Click + Synthetic to open the wizard. Choose an input signal, a processing node (gain, moving average, lowpass filter, …), and optional metadata. The new signal appears in the tree immediately.
);
}
function SectionHistory() {
return (
Historical data is retrieved from EPICS Archive Appliance when the server is configured with an archive_url.
Only the Plot widget in time-series mode supports historical ranges.
Time navigation toolbar: ① Live button · ② start · ③ end datetime · ④ Load.
How to load historical data
Set the start and end date/time fields in the toolbar.
Click Load — all plot widgets on the current panel will fetch the requested range.
While loading, a "Loading history…" overlay appears on each plot.
If no data is available, the overlay shows "No archive data for this range".
Click ● Live (highlighted when active) to return to streaming mode.
Notes
Historical data is requested over the WebSocket connection (not REST).
Requests time out after 15 seconds if the archive does not respond.
Non-timeseries plot types (FFT, waterfall, etc.) remain in live mode regardless of the time range.
Point-value widgets (gauge, bar, LED, …) are unaffected by the time range selection.