Initial commit
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**uopi** is a web-based HMI (Human-Machine Interface) for monitoring and controlling industrial/scientific systems, primarily EPICS-based. It consists of:
|
||||
|
||||
- A **backend** — a single portable binary (targeting old Linux machines) that serves a web UI and REST API, connects to data sources (EPICS, synthetic), and multiplexes live data across multiple clients via WebSocket.
|
||||
- A **frontend** — a fast, reactive web UI (no WebGPU) with two modes: **edit** (drag-and-drop interface builder) and **view** (live interaction with HMI panels).
|
||||
|
||||
## Architecture
|
||||
|
||||
### Backend
|
||||
|
||||
- Single self-contained binary with embedded frontend assets.
|
||||
- Data sources are plugin-extensible; built-in: **EPICS** and **Synthetic**.
|
||||
- Data ingestion uses subscribe/monitor patterns where available; timestamps from the source are authoritative.
|
||||
- The same channel data is shared (mutualized) across all connected clients requesting the same signal.
|
||||
- Interfaces are stored server-side and serialized as **XML**.
|
||||
- REST API for headless/desktop client access alongside the WebSocket push for the web frontend.
|
||||
|
||||
### Data Sources
|
||||
|
||||
**EPICS**: Use Channel Access or PVAccess. Retrieve all metadata from PV name (type, range, enum values, R/W mode, units). Prefer monitors over polling. Support historical data via EPICS Archive Appliance when available.
|
||||
|
||||
**Synthetic**: Compose signals from existing sources (any DS) and math/DSP functions (moving average, bandpass, gain, offset, derivative, custom formula). Support Lua scripting for custom logic.
|
||||
|
||||
### Frontend
|
||||
|
||||
Two primary modes toggled via toolbar:
|
||||
|
||||
- **Edit mode**: resizable signal tree pane (left) + free-form canvas (center) + properties pane (right). Signals are drag-and-dropped from the tree onto the canvas; on drop, compatible widget types are shown. Widget selection shows bounding box with resize handles. Multi-select via Ctrl+click or area select enables group move, delete, and align/distribute. Undo/redo (Ctrl+Z / Ctrl+Shift+Z). Save/load to server; export/import as local XML file.
|
||||
- **View mode**: collapsible interface list pane (left) + live HMI canvas. Right-click context menu per widget: signal info, copy signal name, export data to CSV. Optional historical time navigation + "live" button.
|
||||
|
||||
### Widget Types
|
||||
|
||||
- Text view, gauge, vertical/horizontal bar, set-value control, LED (with condition), multi-LED (bitset), button (command/set value).
|
||||
- Plot widget (multi-signal): timeseries, FFT, waterfall (multidimensional), histogram, bar chart, logic analyzer.
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Backend:** Go 1.22+, single binary, `//go:embed` for frontend assets
|
||||
- **Frontend:** Svelte 5 + TypeScript + Vite; plots via uPlot (time-series) and ECharts (others); edit canvas via Konva
|
||||
- **Config:** TOML file + `UOPI_*` env var overrides (`github.com/BurntSushi/toml`)
|
||||
- **WebSocket:** `nhooyr.io/websocket` (no CGo)
|
||||
- **EPICS:** CGo bindings to `libca`; `gopher-lua` for synthetic signal Lua scripts
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```
|
||||
cmd/uopi/ # main — wires config, embed, server
|
||||
internal/config/ # TOML config loading
|
||||
internal/server/ # HTTP + WebSocket handlers
|
||||
internal/broker/ # signal fan-out (Phase 1)
|
||||
internal/datasource/# DataSource interface + EPICS + synthetic (Phases 2–3)
|
||||
internal/dsp/ # DSP functions for synthetic (Phase 3)
|
||||
internal/storage/ # interface XML persistence (Phase 6)
|
||||
internal/api/ # REST handlers (Phase 1+)
|
||||
web/ # Svelte source (npm); web/embed.go provides embed.FS to Go
|
||||
web/dist/ # built frontend — generated, not committed
|
||||
dist/ # compiled binary — generated, not committed
|
||||
```
|
||||
|
||||
The embed package lives at `web/embed.go` (not in `cmd/`) because `//go:embed` paths cannot use `..`.
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Full build (frontend then backend)
|
||||
make all
|
||||
|
||||
# Backend only (frontend must already be built)
|
||||
make backend
|
||||
|
||||
# Frontend only
|
||||
make frontend
|
||||
|
||||
# Run backend (dev mode, frontend must be running separately)
|
||||
go run ./cmd/uopi
|
||||
|
||||
# Frontend dev server (proxies /api and /healthz to :8080)
|
||||
cd web && npm run dev
|
||||
|
||||
# All tests
|
||||
make test
|
||||
|
||||
# Single Go test
|
||||
go test ./internal/broker/... -run TestFanOut
|
||||
|
||||
# Go vet
|
||||
go vet ./...
|
||||
|
||||
# Svelte type check
|
||||
cd web && npm run check
|
||||
|
||||
# Svelte lint
|
||||
cd web && npm run lint
|
||||
```
|
||||
|
||||
## Key Architectural Notes
|
||||
|
||||
- `web/embed.go` declares `//go:embed dist` and exports `var FS embed.FS`; `main.go` does `fs.Sub(web.FS, "dist")` to get a clean root FS for `http.FileServerFS`.
|
||||
- The `DataSource` interface (to be defined in `internal/datasource/iface.go`) is the extension point for new data sources.
|
||||
- The signal broker (Phase 1) is the central fan-out: one upstream goroutine per signal, N client channels downstream. Reference-counted subscribe/unsubscribe.
|
||||
- WebSocket messages are JSON; protocol defined in `docs/TECHNICAL_SPEC.md §3.3`.
|
||||
Reference in New Issue
Block a user