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
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")
}