phase 10
This commit is contained in:
@@ -2,19 +2,24 @@
|
||||
|
||||
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. Access it from any browser — including over an SSH tunnel — with no client-side installation required.
|
||||
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
|
||||
|
||||
- **Live data** from EPICS (Channel Access / PVAccess) and user-defined synthetic signals
|
||||
- **Web-based HMI editor** — drag-and-drop widgets, resize, align, undo/redo — saved as XML
|
||||
- **Rich widget library** — gauges, LEDs, bars, plots (time series, FFT, waterfall, histogram), controls
|
||||
- **Multiple concurrent clients** sharing the same data subscriptions
|
||||
- **Historical data** navigation via EPICS Archive Appliance integration
|
||||
- **Synthetic signals** — compose, filter, and transform existing signals with a DSP pipeline or Lua scripts
|
||||
- **Single binary** with embedded frontend; no runtime dependencies
|
||||
| Feature | Details |
|
||||
|---------|---------|
|
||||
| **Live data** | EPICS Channel Access (CA) and user-defined synthetic signals |
|
||||
| **HMI editor** | Drag-and-drop widgets, resize, align/distribute, undo/redo, saved as XML |
|
||||
| **Widget library** | Text view, gauge, LED, bar, set-point control, button, plots |
|
||||
| **Plot types** | Time-series (uPlot), FFT, waterfall, histogram, bar chart, logic analyser |
|
||||
| **Historical data** | EPICS Archive Appliance integration via REST; time range picker in UI |
|
||||
| **Synthetic signals** | Compose, filter, and transform signals with a DSP pipeline (gain, offset, moving average, lowpass, highpass, derivative, integral, clamp, formula) |
|
||||
| **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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -22,13 +27,13 @@ uopi runs as a single portable binary. Access it from any browser — including
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- EPICS Base installed (for the EPICS data source; `libca` must be linkable)
|
||||
- Go 1.22+
|
||||
- Node.js 20+ and npm
|
||||
- **Go 1.22+** — the only build dependency (the frontend is bundled by a pure-Go esbuild wrapper)
|
||||
- EPICS Base (`libca`) — only required for the `epics` build tag; the stub and synthetic data sources work without it
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
# Full build — bundles the frontend then compiles the binary
|
||||
make all
|
||||
```
|
||||
|
||||
@@ -37,10 +42,11 @@ The binary is written to `dist/uopi`.
|
||||
### Run
|
||||
|
||||
```bash
|
||||
./dist/uopi --config uopi.toml
|
||||
./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.
|
||||
Then open [http://localhost:8080](http://localhost:8080) in your browser.
|
||||
|
||||
**SSH tunnel example:**
|
||||
|
||||
@@ -53,69 +59,143 @@ ssh -L 8080:localhost:8080 user@controlhost
|
||||
|
||||
## Configuration
|
||||
|
||||
Create `uopi.toml` (or copy and edit the example):
|
||||
Create `uopi.toml` (all fields are optional — shown values are defaults):
|
||||
|
||||
```toml
|
||||
[server]
|
||||
listen = ":8080"
|
||||
storage_dir = "./interfaces" # where interface XML files are stored
|
||||
listen = ":8080"
|
||||
storage_dir = "./data" # where interface XML files and synthetic.json are stored
|
||||
|
||||
[datasource.epics]
|
||||
enabled = true
|
||||
ca_addr_list = "" # overrides EPICS_CA_ADDR_LIST if non-empty
|
||||
archive_url = "" # EPICS Archive Appliance base URL
|
||||
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
|
||||
definitions_file = "./synthetic.json"
|
||||
enabled = true
|
||||
```
|
||||
|
||||
All settings can be overridden with environment variables using the prefix `UOPI_` and double underscores for nesting, e.g. `UOPI_SERVER_LISTEN=":9090"`.
|
||||
All settings can be overridden with environment variables using the `UOPI_` prefix and double underscores for nesting:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
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`:
|
||||
|
||||
```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 |
|
||||
| `GET/POST/DELETE` | `/api/v1/synthetic` | Manage synthetic signal definitions |
|
||||
| `GET` | `/metrics` | Prometheus-format server metrics |
|
||||
| `GET` | `/healthz` | Health check |
|
||||
|
||||
WebSocket live data is at `ws://<host>/ws`. See [`docs/TECHNICAL_SPEC.md`](docs/TECHNICAL_SPEC.md) for the message protocol.
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
See [`docs/TECHNICAL_SPEC.md`](docs/TECHNICAL_SPEC.md) for full architecture details.
|
||||
|
||||
```bash
|
||||
# Backend only (with stub data source)
|
||||
# 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
|
||||
|
||||
# Frontend dev server (proxies /api to backend at :8080)
|
||||
cd web && npm install && npm run dev
|
||||
|
||||
# Run backend tests
|
||||
go test ./...
|
||||
|
||||
# Run a single backend test
|
||||
go test ./internal/broker/... -run TestFanOut
|
||||
|
||||
# Frontend type-check
|
||||
cd web && npm run check
|
||||
|
||||
# Frontend lint
|
||||
cd web && npm run lint
|
||||
|
||||
# Full build (frontend + backend)
|
||||
make all
|
||||
|
||||
# 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`](docs/FUNCTIONAL_SPEC.md) | User-facing feature specification |
|
||||
| [`docs/TECHNICAL_SPEC.md`](docs/TECHNICAL_SPEC.md) | Technology choices, architecture, API design |
|
||||
| [`docs/TECHNICAL_SPEC.md`](docs/TECHNICAL_SPEC.md) | Architecture, WebSocket protocol, API design |
|
||||
| [`docs/WORK_PLAN.md`](docs/WORK_PLAN.md) | Phased development plan with milestones |
|
||||
|
||||
---
|
||||
|
||||
## Release
|
||||
|
||||
Releases are built with [goreleaser](https://goreleaser.com/):
|
||||
|
||||
```bash
|
||||
goreleaser release --clean
|
||||
```
|
||||
|
||||
See [`.goreleaser.yaml`](.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
|
||||
|
||||
Reference in New Issue
Block a user