Real-time high-voltage monitoring and control for the PRad-II HyCal calorimeter (~1700 channels). A daemon (prad2hvd) connects to CAEN SY1527 mainframes and TDK-Lambda GEN booster supplies, polls continuously, logs faults, and serves live data to any number of browser or Qt clients via WebSocket.
┌──────────────┐ ┌──────────────┐
│ CAEN SY1527 │ │ TDK-Lambda │
│ HV Crates │ │ Boosters │
└──────┬───────┘ └──────┬───────┘
│ TCP/IP │ SCPI/TCP
▼ ▼
┌─────────────────────────────────────┐
│ prad2hvd (daemon) │
│ │
│ HVPoller thread ── poll + classify │
│ BoosterPoller thread ── poll │
│ FaultTracker ── daily log files │
│ OpLogger ── user operation log │
│ WebSocket + HTTP server (deflate) │
└──────────────┬──────────────────────┘
│ http://host:8765/
│ ws://host:8765/
┌───────┼───────┐
▼ ▼ ▼
Browser Qt GUI Scripts
client client (wscat)
The daemon owns all hardware connections and makes all status decisions (fault, warning, ΔV threshold). Clients are pure displays — they read level, dv_warn, and other fields from the daemon's JSON and render them. No classification logic runs on the client. Access control (Guest/User/Expert) is enforced server-side per WebSocket connection.
# Build (daemon only, no Qt needed)
mkdir build && cd build
cmake ..
make -j$(nproc)
# Start daemon (serves both WebSocket and dashboard HTTP on port 8765)
./bin/prad2hvd
# With access control (optional — omit for full access)
./bin/prad2hvd -U operator -E expert
# Open in any browser
# http://localhost:8765/mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/opt/prad2hvmon
make -j$(nproc)
make install
# Add the install tree to PATH / LD_LIBRARY_PATH and export PRAD2HV_*_DIR
source /opt/prad2hvmon/bin/prad2hv_setup.sh # bash / zsh
source /opt/prad2hvmon/bin/prad2hv_setup.csh # csh / tcsh
# Run — point -d at a writable state directory so the install tree itself
# can stay read-only (fault_log/, op_log/, settings_log/, hv_settings/,
# vmon_data/ live under $d). Without -d the daemon writes into the
# database directory, which is fine for a dev checkout but won't work for
# a root-owned install.
prad2hvd -d /var/lib/prad2hvmonLayout after make install:
/opt/prad2hvmon/
bin/ prad2hvd, prad2hvmon,
prad2hv_setup.sh, prad2hv_setup.csh,
faultgrep, merge_settings, json2table, …
lib/ libcaenhvwrapper.so
share/prad2hvmon/
database/ crates.json, hycal_modules.json, …
resources/ monitor.html + css/js
scripts/ Python tool sources
prad2hv_setup.sh exports PRAD2HV_DATABASE_DIR and PRAD2HV_RESOURCE_DIR;
the daemon reads them at runtime so the install tree can be relocated
without recompiling. Each daemon option still accepts an explicit path to
override. The prad2hv_ prefix keeps these files from colliding with
prad2evviewer's setup.sh / setup.csh when both are installed under
the same <prefix>.
cmake .. -DBUILD_GUI=ON -DCMAKE_PREFIX_PATH=/path/to/Qt5/lib/cmake
make -j$(nproc)
# Run Qt client (loads dashboard from daemon's HTTP server)
./bin/prad2hvmon -H localhost -p 8765
# Save/restore settings via daemon (no GUI needed)
./bin/prad2hvmon read -s snapshot.json
./bin/prad2hvmon write -f snapshot.json -P <expert_password>Headless process that polls hardware, classifies channel status, logs faults, and broadcasts JSON snapshots over WebSocket.
| Option | Description |
|---|---|
-c <file> |
Crates JSON (default: database/crates.json) |
-m <file> |
Module geometry JSON (default: database/hycal_modules.json) |
-g <file> |
GUI config JSON (default: database/gui_config.json) |
-i <file> |
Error-ignore JSON (default: database/error_ignore.json) |
-l <file> |
Voltage limits JSON (default: database/voltage_limits.json) |
-w <file> |
ΔV warning rules JSON (default: database/dv_warn.json) |
-d <dir> |
Data directory root (default: database dir). Contains fault_log/, op_log/, settings_log/, hv_settings/, vmon_data/. |
-R <dir> |
Override VMon recorder directory (default: $d/vmon_data) |
-r <dir> |
Resources directory for HTTP serving (default: auto-discover) |
-p <port> |
WebSocket + HTTP port (default: 8765) |
-t <ms> |
Poll interval in ms (default: 2000) |
-n <count> |
Fault log buffer size for live display (default: 200) |
-v <level> |
Console verbosity: 0=silent, 1=faults only, 2=warn+fault (default: 2) |
-U <pass> |
User-level password for access control (default: none) |
-E <pass> |
Expert-level password for access control (default: none) |
Stop with Ctrl+C.
The daemon supports three access levels, enforced server-side. Clients authenticate via a login dialog in the dashboard header.
| Capability | Guest | User | Expert |
|---|---|---|---|
| Live monitoring (all tabs) | ✓ | ✓ | ✓ |
| Alarm mute, Save settings | ✓ | ✓ | ✓ |
| Channel ON/OFF, bulk ON/OFF | — | ✓ | ✓ |
| Booster ON/OFF | — | ✓ | ✓ |
| VSet/ISet/SVMax/Name editing | — | — | ✓ |
| Booster VSet/ISet editing | — | — | ✓ |
| Load settings, bulk Set V | — | — | ✓ |
No passwords configured (default): all clients get full Expert access. This is backward-compatible — the daemon behaves exactly as before.
With passwords: every new connection starts as Guest (watch-only). Click the access pill in the header to open the login dialog, select a level, and enter the password.
# Single password for both levels
./bin/prad2hvd -U mypassword
# Separate passwords (expert password also grants User access)
./bin/prad2hvd -U operator -E supervisor
# Only expert password set — User level is open, Expert is protected
./bin/prad2hvd -E expert123The daemon logs authentication attempts to the console. All command gating is enforced server-side — client-side UI disabling is cosmetic only.
Fault logs are written to $d/fault_log/YYYY-MM-DD.log continuously, whether or not any client is connected. User operations (voltage changes, power toggles, settings load, authentication) are logged separately to $d/op_log/YYYY-MM-DD.log. $d is the data directory root — by default the same as the database directory; override with -d <dir> (or pass a separate PRAD2HV_DATABASE_DIR for the database and -d for writable state so the install tree can stay read-only).
The daily log file is tab-separated:
2026-03-14 10:23:45.123 FAULT APPEAR channel W232 ON OVC|Over Current 1523.40 1525.00
2026-03-14 10:23:45.456 WARN APPEAR channel G235 ON DVW|dV warning 1402.10 1410.00
2026-03-14 10:23:48.789 FAULT DISAPPEAR channel W232 ON OVC|Over Current 1524.90 1525.00
Columns: timestamp, level (FAULT/WARN), direction (APPEAR/DISAPPEAR), type, name, status, VMon, VSet. The last two columns are empty for board/booster entries. The file always records both levels regardless of -v.
User operations are logged to $d/op_log/YYYY-MM-DD.log (tab-separated, daily rotation):
2026-03-21 14:05:12.345 Expert set_voltage crate=PRadHV slot=0 ch=32 value=1525.00
2026-03-21 14:05:13.678 User set_power crate=PRadHV slot=0 ch=32 on=true
2026-03-21 14:05:15.901 Expert auth granted
2026-03-21 14:06:00.123 Expert load_settings channels=1728
Columns: timestamp, access level, command type, detail. All accepted commands and authentication events are recorded. Rejected commands (access denied) are not logged.
Use tmux to keep the daemon running after logout:
# Start in a named tmux session
tmux new-session -d -s hvd './bin/prad2hvd -v 1 -U operator -E expert'
# Detach and log out — daemon keeps running
# Reattach later to check output
tmux attach -t hvd
# Detach from session (keep daemon running): Ctrl+A then D
# (clonpc19 uses Ctrl+A as the tmux prefix instead of the default Ctrl+B)
# List sessions
tmux ls
# Stop the daemon
tmux send-keys -t hvd C-c
# Force kill if unresponsive
tmux kill-session -t hvdOptional thin client — a QWebEngineView window that loads monitor.html and connects to the daemon via WebSocket. No direct hardware access — all commands go through the daemon.
| Option | Description |
|---|---|
-H <host> |
Daemon hostname (default: localhost) |
-p <port> |
Daemon WebSocket port (default: 8765) |
-r <dir> |
Resources directory (default: auto-discover) |
Ctrl+S saves a timestamped PNG screenshot to $d/screenshots/ (defaults to the database dir; override with the GUI's -d <dir>).
Save and restore all writable channel parameters (VSet, ISet, SVMax, etc.) via the daemon — same code path as the GUI Save/Load buttons.
# Save current settings to JSON
./bin/prad2hvmon read -s snapshot.json
# Restore settings from JSON (requires Expert password if auth is enabled)
./bin/prad2hvmon write -f snapshot.json -P <expert_password>
# Remote daemon
./bin/prad2hvmon read -H clonpc19 -s snapshot.json
./bin/prad2hvmon write -H clonpc19 -f snapshot.json -P mypass| Option | Description |
|---|---|
-s <file> |
Save output path (read mode; prints to stdout if omitted) |
-f <file> |
Settings file to load (write mode; required) |
-P <pass> |
Expert password (write mode; required if daemon has auth) |
-H <host> |
Daemon hostname (default: localhost) |
-p <port> |
Daemon port (default: 8765) |
-t <sec> |
Timeout in seconds (default: 10) |
The daemon must be running. Unchanged parameters are skipped (no unnecessary hardware writes). The write command reports restored / unchanged / skipped / errors. CLI read works at any access level; CLI write requires Expert — use -P to authenticate.
The daemon serves the dashboard directly — open http://<daemon-host>:8765/ in any browser. No separate file server needed. The same port handles both HTTP (for HTML/JS/CSS) and WebSocket (for live data).
Multiple clients can connect simultaneously. All receive the same live data.
The daemon runs on clonpc19 behind the JLab gateway. To access it from outside:
# SSH tunnel — forward port 8765 through the gateway
ssh -L 8765:clonpc19:8765 -J your_username@hallgw.jlab.org clasrun@clonpc19Then open in your local browser:
http://localhost:8765/
For the Qt GUI client:
./bin/prad2hvmon -H localhost -p 8765The tunnel must stay open while you use the dashboard. WebSocket data is compressed with permessage-deflate (~10× reduction), so the dashboard stays responsive even over slower tunnels.
- Channel Table — Sortable, filterable, live-updating. Inline VSet/ISet/SVMax/Name editing in Expert mode (click ✏ to edit, Enter/✓ to apply, Escape/✕ to cancel). Bulk ON/OFF (User+) and bulk Set V (Expert) on filtered channels. Summary strip with fault/warning counts.
- Access Control — Three-tier login (Guest / User / Expert) via header pill. Guest is watch-only, User enables power control, Expert unlocks all editing. Server-enforced.
- Save / Load — Save all writable parameters to JSON (all levels), or restore from a previously saved file (Expert only). Available from the tab bar (GUI) or command line (
prad2hvmon read/write). Both paths go through the daemon — same logic, same format. - Board Status — Per-board temperature, HVMax, firmware, status.
- HyCal Geometry Map — 2D canvas at physical positions. Color by VMon, VSet, |ΔV|, or Status. Click for draggable live popups with controls.
- Booster HV Panel — TDK-Lambda GEN supply cards with live readback, VSet/ISet controls, ON/OFF. Connection managed by daemon.
- Fault Log — Live fault transition log (configurable ring buffer, default 200 entries). Colour-coded by level (FAULT/WARN) and direction (APPEAR/DISAPPEAR). Includes VMon/VSet at the moment of transition for HV channels. Unread-indicator dot on the tab.
- Alarm — Audible two-tone beep every 2s on faults. Mute toggle, auto-re-arm when faults clear.
All in database/. The daemon reads them at startup and serves relevant ones to clients.
| File | Used by | Purpose |
|---|---|---|
crates.json |
Daemon | CAEN crate addresses [{"name":"...","ip":"..."}] |
hycal_modules.json |
Daemon + Client | Module geometry and booster definitions |
gui_config.json |
Client | Display thresholds, color ranges, render interval |
voltage_limits.json |
Daemon | Per-pattern voltage limits (OVL fault) |
error_ignore.json |
Daemon | Per-channel error suppression |
dv_warn.json |
Daemon | Per-pattern ΔV warning thresholds |
{
"default": 2.0,
"rules": [
{ "pattern": "W*", "max_dv": 2.0 },
{ "pattern": "G*", "max_dv": 3.0 },
{ "pattern": "PRIMARY*", "max_dv": 5.0 },
{ "pattern": "*", "max_dv": 2.0 }
]
}First matching pattern wins. Same wildcard scheme as voltage_limits.json and error_ignore.json.
Standalone Python 3 utilities in scripts/. No daemon required.
| Tool | Purpose |
|---|---|
faultgrep.py |
Filter and analyse fault logs (persistent faults, date ranges, wildcards) |
merge_settings.py |
Merge two settings snapshots (by address or name) |
json2table.py |
Pretty-print JSON config/settings as text tables |
caenhv_reset.py |
Remote reboot of CAEN mainframes via tsconnect serial (JLab CLON env required) |
vmon_reader.py |
Decode / plot VMDF v2 recorder files produced by prad2hvd |
See scripts/README.md for full usage. After make install, each script also gets a thin bin/ wrapper (faultgrep, merge_settings, json2table, caenhv_reset, vmon_reader) that works without sourcing prad2hv_setup.sh.
Daemon (pure C++17, no Qt):
- CMake 3.14+, C++17 compiler
libcaenhvwrapper.so(incaen_lib/)- zlib (
zlib-devel/zlib1g-dev) - Auto-fetched: nlohmann/json, fmt, websocketpp, standalone Asio
Qt GUI client (optional):
- Qt 5 (Widgets, WebEngineWidgets, WebSockets)
Chao Peng — Argonne National Laboratory