Implemented epics read/write

This commit is contained in:
Martino Ferrari
2026-04-27 12:42:10 +02:00
parent b76b7f0ba8
commit 1bda25454b
32 changed files with 1553 additions and 281 deletions
+224
View File
@@ -0,0 +1,224 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
uopi EPICS test panel
Covers: textview · gauge · barh · barv · led · multiled · setvalue · button ×3 modes · plot ×4 types
Signals: epics (UOPI:*) · synthetic (temp_moving_avg, temp_derivative, noise_lowpass, etc.)
Canvas: 1380 × 720
-->
<interface id="epics_test" name="EPICS Test Panel" version="1" width="1380" height="720">
<!-- ════════════════════════ SECTION LABELS ════════════════════════════ -->
<widget type="textlabel" id="lbl_monitor" x="10" y="6" w="160" h="22">
<option key="label" value="PROCESS VALUES"/>
<option key="fontSize" value="11"/>
<option key="color" value="#64748b"/>
</widget>
<widget type="textlabel" id="lbl_controls" x="10" y="302" w="120" h="22">
<option key="label" value="CONTROLS"/>
<option key="fontSize" value="11"/>
<option key="color" value="#64748b"/>
</widget>
<widget type="textlabel" id="lbl_trends" x="10" y="392" w="120" h="22">
<option key="label" value="TRENDS &amp; ANALYSIS"/>
<option key="fontSize" value="11"/>
<option key="color" value="#64748b"/>
</widget>
<!-- ════════════════════════ TEXT VIEWS ══════════════════════════════ -->
<!-- textview: quality dot, live value, unit from EPICS metadata -->
<!-- Temperature: analog float, alarm chain LOW/HIGH/LOLO/HIHI -->
<widget type="textview" id="tv_temp" x="10" y="28" w="220" h="58">
<signal ds="epics" name="UOPI:TEMP"/>
<option key="label" value="Temperature"/>
</widget>
<!-- Pressure: sawtooth, different unit (bar) -->
<widget type="textview" id="tv_pressure" x="244" y="28" w="210" h="58">
<signal ds="epics" name="UOPI:PRESSURE"/>
<option key="label" value="Pressure"/>
</widget>
<!-- Flow: noisy signal, 5 Hz update -->
<widget type="textview" id="tv_flow" x="468" y="28" w="210" h="58">
<signal ds="epics" name="UOPI:FLOW"/>
<option key="label" value="Flow Rate"/>
</widget>
<!-- Counter: monotonically increasing integer -->
<widget type="textview" id="tv_counter" x="692" y="28" w="200" h="58">
<signal ds="epics" name="UOPI:COUNTER"/>
<option key="label" value="Event Counter"/>
<option key="unit" value="none"/>
</widget>
<!-- Message: string PV — tests string display and write -->
<widget type="textview" id="tv_message" x="906" y="28" w="320" h="58">
<signal ds="epics" name="UOPI:MESSAGE"/>
<option key="label" value="Operator Msg"/>
</widget>
<!-- ════════════════════════ GAUGES ══════════════════════════════════ -->
<!-- gauge: SVG arc, displays LOPR/HOPR range, alarm colour zones -->
<!-- Temperature gauge: full alarm chain visible as colour zones -->
<widget type="gauge" id="g_temp" x="10" y="100" w="170" h="170">
<signal ds="epics" name="UOPI:TEMP"/>
<option key="label" value="Temperature"/>
<option key="min" value="0"/>
<option key="max" value="100"/>
<option key="thresholdHigh" value="78"/>
<option key="thresholdLow" value="20"/>
</widget>
<!-- Pressure gauge -->
<widget type="gauge" id="g_pressure" x="194" y="100" w="150" h="150">
<signal ds="epics" name="UOPI:PRESSURE"/>
<option key="label" value="Pressure"/>
<option key="min" value="0"/>
<option key="max" value="10"/>
<option key="thresholdHigh" value="8"/>
</widget>
<!-- ════════════════════════ BAR WIDGETS ═════════════════════════════ -->
<!-- Temperature horizontal bar: full-range with setpoint reference -->
<widget type="barh" id="bh_temp" x="360" y="100" w="300" h="52">
<signal ds="epics" name="UOPI:TEMP"/>
<option key="label" value="Temperature"/>
<option key="min" value="0"/>
<option key="max" value="100"/>
</widget>
<!-- Deviation from setpoint: symmetric range, can go negative -->
<widget type="barh" id="bh_dev" x="360" y="166" w="300" h="52">
<signal ds="epics" name="UOPI:TEMP_DEVIATION"/>
<option key="label" value="ΔT from SP"/>
<option key="min" value="-60"/>
<option key="max" value="60"/>
</widget>
<!-- Flow vertical bar -->
<widget type="barv" id="bv_flow" x="360" y="232" w="50" h="56">
<signal ds="epics" name="UOPI:FLOW"/>
<option key="label" value="Flow"/>
<option key="min" value="0"/>
<option key="max" value="100"/>
</widget>
<!-- ════════════════════════ LED INDICATORS ══════════════════════════ -->
<!-- Beam On/Off LED — binary bo PV, tests ZNAM/ONAM string display -->
<widget type="led" id="led_beam" x="676" y="100" w="90" h="90">
<signal ds="epics" name="UOPI:BEAM_ON"/>
<option key="label" value="Beam On"/>
<option key="condition" value="value>0"/>
</widget>
<!-- Interlock LED — computed calc, MAJOR alarm when fired -->
<widget type="led" id="led_ilck" x="780" y="100" w="90" h="90">
<signal ds="epics" name="UOPI:INTERLOCK"/>
<option key="label" value="Interlock"/>
<option key="condition" value="value>0"/>
</widget>
<!-- ════════════════════════ MULTI-LED ═══════════════════════════════ -->
<!-- multiled: bitset display — STATUS_BITS is an 8-bit rotating word -->
<widget type="multiled" id="ml_status" x="676" y="204" w="360" h="64">
<signal ds="epics" name="UOPI:STATUS_BITS"/>
<option key="label" value="Status Bits"/>
<option key="bits" value="8"/>
</widget>
<!-- ════════════════════════ CONTROLS ════════════════════════════════ -->
<!-- Setpoint: ao PV, numeric write, unit from EPICS metadata -->
<widget type="setvalue" id="sv_sp" x="10" y="324" w="240" h="56">
<signal ds="epics" name="UOPI:SETPOINT"/>
<option key="label" value="Temp Setpoint"/>
</widget>
<!-- Mode selector: mbbo enum, write 0=Idle 1=Running 2=Fault 3=Maint -->
<widget type="setvalue" id="sv_mode" x="264" y="324" w="220" h="56">
<signal ds="epics" name="UOPI:MODE"/>
<option key="label" value="Mode (03)"/>
<option key="unit" value="none"/>
</widget>
<!-- String write: stringout PV, freeform text -->
<widget type="setvalue" id="sv_msg" x="498" y="324" w="280" h="56">
<signal ds="epics" name="UOPI:MESSAGE"/>
<option key="label" value="Operator Message"/>
<option key="unit" value="none"/>
</widget>
<!-- Button ONE-SHOT: writes 1 to RESET_CMD; IOC auto-resets after 1 s -->
<widget type="button" id="btn_reset" x="794" y="324" w="160" h="56">
<signal ds="epics" name="UOPI:RESET_CMD"/>
<option key="label" value="Reset"/>
<option key="value" value="1"/>
<option key="mode" value="oneshot"/>
</widget>
<!-- Button SET-RESET: writes 1, then uopi reverts to 0 after 2 s -->
<widget type="button" id="btn_latch" x="968" y="324" w="200" h="56">
<signal ds="epics" name="UOPI:LATCH"/>
<option key="label" value="Latch (2 s)"/>
<option key="value" value="1"/>
<option key="resetValue" value="0"/>
<option key="resetTime" value="2"/>
<option key="mode" value="setReset"/>
</widget>
<!-- Button PERSISTENT: toggles BEAM_ON; stays pressed while value=1 -->
<widget type="button" id="btn_beam" x="1182" y="324" w="186" h="56">
<signal ds="epics" name="UOPI:BEAM_ON"/>
<option key="label" value="Beam Enable"/>
<option key="value" value="1"/>
<option key="resetValue" value="0"/>
<option key="mode" value="persistent"/>
</widget>
<!-- ════════════════════════ PLOTS ═══════════════════════════════════ -->
<!-- Timeseries: TEMP + moving average + derivative (3 signals, synthetic) -->
<widget type="plot" id="plot_temp" x="10" y="414" w="480" h="290">
<signal ds="epics" name="UOPI:TEMP"/>
<signal ds="synthetic" name="temp_moving_avg"/>
<signal ds="synthetic" name="temp_derivative"/>
<option key="plotType" value="timeseries"/>
<option key="timeWindow" value="120"/>
<option key="yMin" value="auto"/>
<option key="yMax" value="auto"/>
<option key="legend" value="bottom"/>
</widget>
<!-- Histogram: noise distribution (expect uniform 01) -->
<widget type="plot" id="plot_hist" x="504" y="414" w="290" h="290">
<signal ds="epics" name="UOPI:NOISE"/>
<option key="plotType" value="histogram"/>
<option key="legend" value="none"/>
</widget>
<!-- Timeseries: raw noise vs lowpass filtered (synthetic) -->
<widget type="plot" id="plot_noise" x="808" y="414" w="380" h="290">
<signal ds="epics" name="UOPI:NOISE"/>
<signal ds="synthetic" name="noise_lowpass"/>
<option key="plotType" value="timeseries"/>
<option key="timeWindow" value="30"/>
<option key="legend" value="bottom"/>
</widget>
<!-- Pressure sawtooth — bar chart (latest value per signal) -->
<widget type="plot" id="plot_bar" x="1202" y="414" w="166" h="290">
<signal ds="epics" name="UOPI:PRESSURE"/>
<signal ds="epics" name="UOPI:FLOW"/>
<option key="plotType" value="bar"/>
<option key="legend" value="bottom"/>
</widget>
</interface>
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# workspace/run.sh — start softIOC + uopi for EPICS workspace testing
#
# Usage (from project root):
# bash workspace/run.sh
#
# Requirements:
# • EPICS Base installed (EPICS_BASE set, or auto-detected below)
# • softIoc binary available
# • uopi built with EPICS support (script builds it if missing)
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WORKSPACE="$PROJECT_ROOT/workspace"
BINARY="$PROJECT_ROOT/dist/uopi"
# ── Locate EPICS Base ─────────────────────────────────────────────────────────
if [ -z "${EPICS_BASE:-}" ]; then
for candidate in \
/usr/lib/epics \
/usr/local/epics \
/opt/epics/base \
/opt/epics; do
if [ -f "$candidate/include/cadef.h" ]; then
export EPICS_BASE="$candidate"
break
fi
done
fi
if [ -z "${EPICS_BASE:-}" ]; then
echo "ERROR: EPICS_BASE is not set and could not be auto-detected."
echo " export EPICS_BASE=/path/to/epics/base"
exit 1
fi
# Detect architecture
EPICS_ARCH="${EPICS_ARCH:-$(ls "$EPICS_BASE/lib" | grep linux | head -1)}"
if [ -z "$EPICS_ARCH" ]; then
EPICS_ARCH="linux-x86_64"
fi
# ── Environment ───────────────────────────────────────────────────────────────
# Point CA exclusively at the local IOC — no broadcast scanning.
export EPICS_CA_ADDR_LIST="127.0.0.1"
export EPICS_CA_AUTO_ADDR_LIST="NO"
# Ensure the EPICS shared libraries are found at runtime.
export LD_LIBRARY_PATH="$EPICS_BASE/lib/$EPICS_ARCH${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
# Make EPICS tools (softIoc, caget, caput…) available.
export PATH="$EPICS_BASE/bin/$EPICS_ARCH:$PATH"
# ── Locate softIoc ────────────────────────────────────────────────────────────
if ! command -v softIoc &>/dev/null; then
echo "ERROR: softIoc not found in $EPICS_BASE/bin/$EPICS_ARCH or PATH."
exit 1
fi
# ── Build uopi ────────────────────────────────────────────────────────────────
echo "Building uopi with EPICS support..."
cd "$PROJECT_ROOT"
make backend-epics 2>&1
echo ""
# ── Cleanup handler ───────────────────────────────────────────────────────────
PIDS=()
cleanup() {
echo ""
echo "Shutting down..."
for pid in "${PIDS[@]:-}"; do
kill "$pid" 2>/dev/null || true
done
wait 2>/dev/null || true
}
trap cleanup EXIT INT TERM
# ── Kill any stale softIOC from a previous run ────────────────────────────────
pkill -x softIoc 2>/dev/null || true
# Give the OS a moment to release the CA port (5064/tcp+udp).
sleep 0.3
# ── Start softIOC ─────────────────────────────────────────────────────────────
echo "Starting softIOC..."
softIoc -S -d "$WORKSPACE/softioc/uopi_test.db" \
>"$WORKSPACE/softioc.log" 2>&1 &
IOC_PID=$!
PIDS+=("$IOC_PID")
# Wait for the IOC to accept Channel Access connections (up to 10 s).
echo -n "Waiting for IOC to be ready"
for i in $(seq 1 20); do
if caget -w 1 UOPI:TICK >/dev/null 2>&1; then
echo " OK"
break
fi
echo -n "."
sleep 0.5
if [ "$i" -eq 20 ]; then
echo ""
echo "ERROR: IOC did not become ready within 10 seconds."
echo "Check $WORKSPACE/softioc.log for details."
exit 1
fi
done
# ── Start uopi ────────────────────────────────────────────────────────────────
echo "Starting uopi (http://localhost:8080)..."
echo " Open the browser, switch to Edit mode, load workspace/data/epics_test.xml"
echo " or select it from the interface list in View mode."
echo " Press Ctrl-C to stop."
echo ""
cd "$PROJECT_ROOT"
"$BINARY" -config "$WORKSPACE/uopi.toml"
+2
View File
@@ -0,0 +1,2 @@
Starting iocInit
iocRun: All initialization complete
View File
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════════
# uopi test SoftIOC startup script
#
# Prerequisites:
# • EPICS Base 3.15+ (for RNDM calc function)
# • 'softIoc' binary in PATH (usually $EPICS_BASE/bin/$EPICS_HOST_ARCH/)
# • Optional: caput (from EPICS tools) to pre-fill UOPI:WAVEFORM
#
# Usage:
# cd workspace/softioc
# ./st.cmd
#
# Or directly (no waveform pre-fill):
# softIoc -s -d workspace/softioc/uopi_test.db
# ═══════════════════════════════════════════════════════════════════════════
set -euo pipefail
cd "$(dirname "$0")"
# ── Locate softIoc ────────────────────────────────────────────────────────────
if ! command -v softIoc &>/dev/null; then
# Try common EPICS install paths
for candidate in \
"$EPICS_BASE/bin/$EPICS_HOST_ARCH/softIoc" \
/usr/local/epics/base/bin/linux-x86_64/softIoc \
/opt/epics/bin/linux-x86_64/softIoc; do
if [ -x "$candidate" ]; then
export PATH="$(dirname "$candidate"):$PATH"
break
fi
done
fi
if ! command -v softIoc &>/dev/null; then
echo "ERROR: softIoc not found. Set EPICS_BASE or add softIoc to PATH."
exit 1
fi
echo "Starting uopi test IOC…"
echo " PV prefix : UOPI:"
echo " Database : uopi_test.db"
echo " softIoc : $(command -v softIoc)"
echo ""
# ── Start IOC in background so we can run caput for waveform ─────────────────
softIoc -s -d uopi_test.db &
IOC_PID=$!
# ── Pre-fill UOPI:WAVEFORM with a sine+DC pattern (requires caput) ───────────
if command -v caput &>/dev/null; then
sleep 2 # wait for IOC to initialise Channel Access
python3 - <<'PYEOF'
import subprocess, math
n = 64
# Sine with 3 harmonics: models a realistic ADC capture
vals = [
0.5 + 0.4 * math.sin(2 * math.pi * i / n)
+ 0.1 * math.sin(6 * math.pi * i / n)
+ 0.05 * math.sin(10 * math.pi * i / n)
for i in range(n)
]
args = ["caput", "-a", "UOPI:WAVEFORM", str(n)] + [f"{v:.6f}" for v in vals]
result = subprocess.run(args, capture_output=True, text=True)
if result.returncode == 0:
print(f" UOPI:WAVEFORM filled with {n}-point multi-harmonic sine.")
else:
print(f" caput failed: {result.stderr.strip()}")
PYEOF
else
echo " (caput not found — UOPI:WAVEFORM left empty; fill manually via caput)"
fi
echo ""
echo "IOC running (PID $IOC_PID). Connect uopi with:"
echo " UOPI_EPICS_CA_ADDR_LIST=127.0.0.1 ./dist/uopi -config workspace/uopi.toml"
echo ""
echo "Press Ctrl-C to stop."
wait "$IOC_PID"
+231
View File
@@ -0,0 +1,231 @@
# ═══════════════════════════════════════════════════════════════════════════
# uopi test IOC database — covers all widget types and EPICS features
#
# Requirements: EPICS Base 3.15+ (uses RNDM calc function for noise)
# Prefix: UOPI:
#
# Run with: softIoc -s -d uopi_test.db
# ═══════════════════════════════════════════════════════════════════════════
# ── Phase counter ────────────────────────────────────────────────────────────
# Increments 10x/s; used as phase source for trig-based simulations.
# At 0.1s scan, 1 step = 0.1 rad → period 2π/step·rate = 62.8 s.
record(calc, "UOPI:TICK") {
field(DESC, "Phase counter (0.1 s)")
field(SCAN, ".1 second")
field(CALC, "A+0.1")
field(INPA, "UOPI:TICK NPP NMS")
field(VAL, "0")
field(PREC, "2")
}
# ── Analog read-only signals ─────────────────────────────────────────────────
# Temperature: sine 1090 °C, 62.8 s period, full alarm chain
# Tests: textview, gauge (color zones), barh, plot timeseries, quality dot
record(calc, "UOPI:TEMP") {
field(DESC, "Simulated temperature")
field(SCAN, ".1 second")
field(CALC, "50+40*SIN(A)")
field(INPA, "UOPI:TICK NPP NMS")
field(EGU, "degC")
field(PREC, "2")
field(LOPR, "0")
field(HOPR, "100")
field(LOW, "20")
field(HIGH, "78")
field(LOLO, "12")
field(HIHI, "88")
field(LSV, "MINOR")
field(HSV, "MINOR")
field(LLSV, "MAJOR")
field(HHSV, "MAJOR")
}
# Pressure: sawtooth 010 bar, ~20 s period
# Tests: gauge, barh, different EGU
record(calc, "UOPI:PRESSURE") {
field(DESC, "Simulated pressure")
field(SCAN, ".5 second")
field(CALC, "10*((A%6.2832)/6.2832)")
field(INPA, "UOPI:TICK NPP NMS")
field(EGU, "bar")
field(PREC, "3")
field(LOPR, "0")
field(HOPR, "10")
field(HIGH, "8")
field(HIHI, "9.5")
field(HSV, "MINOR")
field(HHSV, "MAJOR")
}
# Flow rate: noisy sine 2080 L/min, faster period (10.5 s), 5 Hz update
# Tests: barv, different scan rate, noise in plot, textview with unit
record(calc, "UOPI:FLOW") {
field(DESC, "Simulated flow rate")
field(SCAN, ".2 second")
field(CALC, "50+30*SIN(A*6)+5*RNDM")
field(INPA, "UOPI:TICK NPP NMS")
field(EGU, "L/min")
field(PREC, "1")
field(LOPR, "0")
field(HOPR, "100")
field(LOW, "10")
field(LOLO, "5")
field(LSV, "MINOR")
field(LLSV, "MAJOR")
}
# Noise signal: pure random 01, 10 Hz
# Tests: plot histogram, plot FFT, fast timeseries, synthetic lowpass
record(calc, "UOPI:NOISE") {
field(DESC, "Random noise signal")
field(SCAN, ".1 second")
field(CALC, "RNDM")
field(PREC, "4")
field(LOPR, "0")
field(HOPR, "1")
}
# Auto-incrementing integer counter, 1 Hz
# Tests: textview integer, plot (monotonic), different numeric type
record(calc, "UOPI:COUNTER") {
field(DESC, "Event counter (1 Hz)")
field(SCAN, "1 second")
field(CALC, "A+1")
field(INPA, "UOPI:COUNTER NPP NMS")
field(VAL, "0")
field(LOPR, "0")
field(HOPR, "100000")
}
# Temperature deviation from setpoint
# Tests: barh with negative range, alarm on deviation, computed read-only
record(calc, "UOPI:TEMP_DEVIATION") {
field(DESC, "Temp deviation from setpoint")
field(SCAN, "1 second")
field(CALC, "A-B")
field(INPA, "UOPI:TEMP NPP NMS")
field(INPB, "UOPI:SETPOINT NPP NMS")
field(EGU, "degC")
field(PREC, "2")
field(LOPR, "-60")
field(HOPR, "60")
field(HIGH, "10")
field(HIHI, "25")
field(LOW, "-10")
field(LOLO, "-25")
field(HSV, "MINOR")
field(HHSV, "MAJOR")
field(LSV, "MINOR")
field(LLSV, "MAJOR")
}
# ── Binary / status signals ───────────────────────────────────────────────────
# Interlock: fires when temperature exceeds 80 °C — MAJOR alarm
# Tests: LED with condition, quality=bad on alarm, computed binary
record(calc, "UOPI:INTERLOCK") {
field(DESC, "High-temp interlock")
field(SCAN, "1 second")
field(CALC, "A>80?1:0")
field(INPA, "UOPI:TEMP NPP NMS")
field(HIHI, "1")
field(HHSV, "MAJOR")
field(LOPR, "0")
field(HOPR, "1")
}
# 8-bit status word: rotating single-bit pattern every 2 s
# Tests: Multi-LED widget (bitset display), integer signal
record(calc, "UOPI:STATUS_BITS") {
field(DESC, "8-bit status word")
field(SCAN, "2 second")
field(CALC, "A>=128?(A*2-255):(A*2)")
field(INPA, "UOPI:STATUS_BITS NPP NMS")
field(VAL, "1")
field(LOPR, "0")
field(HOPR, "255")
}
# ── Read-write signals ────────────────────────────────────────────────────────
# Analog output setpoint — writable, drives UOPI:TEMP_DEVIATION
# Tests: setvalue widget (numeric write), unit from metadata
record(ao, "UOPI:SETPOINT") {
field(DESC, "Temperature setpoint")
field(EGU, "degC")
field(PREC, "1")
field(LOPR, "0")
field(HOPR, "100")
field(DRVL, "0")
field(DRVH, "100")
field(VAL, "50")
field(PINI, "YES")
}
# Beam enable: binary On/Off toggle
# Tests: LED display, button (persistent mode — tracks signal state)
record(bo, "UOPI:BEAM_ON") {
field(DESC, "Beam enable")
field(ZNAM, "Off")
field(ONAM, "On")
field(VAL, "0")
field(PINI, "YES")
}
# Reset command: writes 1 and auto-reverts to 0 after 1 s (IOC-side pulse)
# Tests: button one-shot mode — IOC handles the reset automatically
record(bo, "UOPI:RESET_CMD") {
field(DESC, "Reset command (auto-clears after 1 s)")
field(ZNAM, "Idle")
field(ONAM, "Reset!")
field(HIGH, "1.0")
}
# Latch: plain writable binary — no auto-reset
# Tests: button set-reset mode — uopi writes 1 then resets after delay
record(bo, "UOPI:LATCH") {
field(DESC, "Latch enable")
field(ZNAM, "Released")
field(ONAM, "Latched")
field(VAL, "0")
field(PINI, "YES")
}
# Operating mode: 4-state enum (writable)
# Tests: setvalue (numeric write to enum), textview (shows numeric state)
# multi-LED style readback via setvalue, alarm state on Fault
record(mbbo, "UOPI:MODE") {
field(DESC, "System operating mode")
field(ZRST, "Idle")
field(ONST, "Running")
field(TWST, "Fault")
field(THST, "Maintenance")
field(ZRVL, "0")
field(ONVL, "1")
field(TWVL, "2")
field(THVL, "3")
field(TWSV, "MAJOR")
field(THSV, "MINOR")
field(VAL, "0")
field(PINI, "YES")
}
# String message: writable freeform text
# Tests: setvalue with string type, textview string display
record(stringout, "UOPI:MESSAGE") {
field(DESC, "Operator message (writable)")
field(VAL, "System nominal")
field(PINI, "YES")
}
# Waveform: 128-point float array
# Tests: plot FFT, plot histogram over array (populated at IOC start)
record(waveform, "UOPI:WAVEFORM") {
field(DESC, "64-point sample waveform")
field(FTVL, "FLOAT")
field(NELM, "64")
field(EGU, "counts")
}
+4
View File
@@ -0,0 +1,4 @@
time=2026-04-27T00:05:22.337+02:00 level=INFO msg="data source registered" name=synthetic
time=2026-04-27T00:05:22.337+02:00 level=INFO msg="data source registered" name=epics
time=2026-04-27T00:05:22.337+02:00 level=INFO msg=listening addr=:8080
time=2026-04-27T00:05:24.711+02:00 level=INFO msg="shutting down"