Implemented epics read/write
This commit is contained in:
@@ -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 & 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 (0–3)"/>
|
||||
<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 0–1) -->
|
||||
<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>
|
||||
Executable
+124
@@ -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"
|
||||
@@ -0,0 +1,2 @@
|
||||
Starting iocInit
|
||||
iocRun: All initialization complete
|
||||
Executable
+81
@@ -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"
|
||||
@@ -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 10–90 °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 0–10 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 20–80 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 0–1, 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")
|
||||
}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user