6.3 Read 32 temperature channels
Run the Chapter 4 32-channel temperature database through iocsh.bash without
the compiled jeonglee_Demo executable, and group representative channels into
one structured pvAccess (PVA) object with dbLoadGroup. You inspect both
individual Channel Access (CA) and PVA records and a composite QSRV2 group
process variable (PV) served by the Input/Output Controller (IOC).
Prerequisites
- Complete Run a 32-channel temperature simulator and Generate and load 32 channel records.
- Confirm that
db/TC-32.dbanddb/tc32.protoexist injeonglee_Demo/with$(P)$(R)macros intact after the Chapter 4 exercises. - Stop any IOC or simulator listening on port
9399. - Activate the matching EPICS-env 1.5.0 tree in the IOC and client terminals.
Open three Bash terminals on the same host with these roles and settings:
| Terminal | Role | Working directory | Environment |
|---|---|---|---|
| Terminal 1 | TC-32 emulator (9399) | jeonglee_Demo/simulator/ | No EPICS environment required |
| Terminal 2 | IOC (iocsh.bash) | jeonglee_Demo/ then jeonglee_Demo/loader_tc32/ | source "$TREE/setEpicsEnv.bash" |
| Terminal 3 | CA and PVA client | Any directory | source "$TREE/setEpicsEnv.bash", export EPICS_CA_AUTO_ADDR_LIST=NO EPICS_CA_ADDR_LIST=127.0.0.1, export EPICS_PVA_AUTO_ADDR_LIST=NO EPICS_PVA_ADDR_LIST=127.0.0.1 |
In each EPICS terminal, set TREE to your installation path from
lesson 1.1 (such as
"$HOME/epics/1.5.0/debian-13/7.0.10") before running
source "$TREE/setEpicsEnv.bash".
Prepare the database, group JSON, and startup
-
In terminal 2 from your
jeonglee_Demo/application directory, copy the generated database and protocol into a loader directory:mkdir -p loader_tc32 cp db/TC-32.db db/tc32.proto loader_tc32/ cd loader_tc32 -
In terminal 2 inside
loader_tc32/, save thisQSRV2group definition astc32_group.json:{ "$(P)$(R)Summary": { "+id": "training:nt/TC32Summary:1.0", "device.model": {"+type": "const", "+const": "TC-32"}, "ch01": {"+type": "scalar", "+channel": "$(P)$(R)CH01.VAL"}, "ch16": {"+type": "scalar", "+channel": "$(P)$(R)CH16.VAL"}, "ch32": {"+type": "scalar", "+channel": "$(P)$(R)CH32.VAL", "+trigger": "*"} } }Each mapping key configures how
QSRV2assemblesLOADER:TC32:Summary:"+id"assigns the top-level PVA structure identifiertraining:nt/TC32Summary:1.0."+type": "const"with"+const": "TC-32"publishes a constantdevice.modelstring field without an underlying EPICS record."+type": "scalar"embeds each selected channel’sNTScalarvalue, alarm, timestamp, and display metadata from$(P)$(R)CH*.VAL."+trigger": "*"onch32publishes an atomic subscription update across all group fields when channel 32 finishes a streaming sweep. Omitting+putorderkeeps the group read-only.
-
In terminal 2 inside
loader_tc32/, save this startup file asst.cmd:on error break module StreamDevice epicsEnvSet("STREAM_PROTOCOL_PATH", ".") drvAsynIPPortConfigure("TC32", "127.0.0.1:9399", 0, 0, 0) asynOctetSetInputEos("TC32", 0, "\n") asynOctetSetOutputEos("TC32", 0, "\n") dbLoadRecords("TC-32.db", "P=LOADER:,R=TC32:,PORT=TC32") dbLoadGroup("tc32_group.json", "P=LOADER:,R=TC32:") iocInit()softIocPVXlinkspvxswithQSRV2enabled by default, sodbLoadGroupneeds no extramoduledirective inst.cmd. -
In terminal 2 inside
loader_tc32/, preview the generated startup before launching the IOC:iocsh.bash -n st.cmd
Read scalar channels and the structured group
-
In terminal 1 inside
jeonglee_Demo/simulator/, start the TC-32 emulator on port9399:./tc32_emulator.bash --port 9399 -
In terminal 2 inside
loader_tc32/, start the IOC:iocsh.bash st.cmd -
At the
iocshprompt in terminal 2, list the registeredQSRV2group definition:pvxgl(1, "*")pvxglreports the group name, atomic delivery mode, and four mapped members (device.model,ch01,ch16, andch32):LOADER:TC32:Summary Atomic Get/Put:yes Atomic Members:4 -
In terminal 3, direct CA and PVA searches to loopback:
export EPICS_CA_AUTO_ADDR_LIST=NO EPICS_CA_ADDR_LIST=127.0.0.1 export EPICS_PVA_AUTO_ADDR_LIST=NO EPICS_PVA_ADDR_LIST=127.0.0.1 -
In terminal 3, read three scalar temperature channels and channel 01 metadata through CA:
caget LOADER:TC32:CH01 LOADER:TC32:CH16 LOADER:TC32:CH32 caget LOADER:TC32:CH01.EGU LOADER:TC32:CH01.DESC LOADER:TC32:CH01.SEVRLive temperatures fall between
10and90degreesCelsiuswithNO_ALARMseverity:LOADER:TC32:CH01 77.1052 LOADER:TC32:CH16 86.3493 LOADER:TC32:CH32 76.6859 LOADER:TC32:CH01.EGU Celsius LOADER:TC32:CH01.DESC TC temperature at Channel 01 LOADER:TC32:CH01.SEVR NO_ALARM -
In terminal 3, read channel 01 as an individual PVA
NTScalarrecord:pvxget LOADER:TC32:CH01pvxgetprints the value, alarm, timestamp, and engineering units from the sameairecord:LOADER:TC32:CH01 value double = 76.4265 alarm.severity int32_t = 0 alarm.status int32_t = 0 alarm.message string = "" timeStamp.secondsPastEpoch int64_t = 1794422277 timeStamp.nanoseconds int32_t = 691465958 timeStamp.userTag int32_t = 0 display.limitLow double = 0 display.limitHigh double = 0 display.description string = "TC temperature at Channel 01" display.units string = "Celsius" display.precision int32_t = 0 display.form.index int32_t = 0 display.form.choices string[] = {7}["Default", "String", "Binary", "Decimal", "Hex", "Exponential", "Engineering"] control.limitLow double = 0 control.limitHigh double = 0 valueAlarm.lowAlarmLimit double = nan valueAlarm.lowWarningLimit double = nan valueAlarm.highWarningLimit double = nan valueAlarm.highAlarmLimit double = nan -
In terminal 3, read the composite
LOADER:TC32:Summarygroup PV in a single PVA request:pvxget LOADER:TC32:SummaryOne atomic PVA read returns
ch01,ch16,ch32, anddevice.modeltogether from the server:LOADER:TC32:Summary record._options.atomic bool = true ch01.value double = 76.4265 ch01.alarm.severity int32_t = 0 ch01.alarm.status int32_t = 0 ch01.alarm.message string = "" ch01.timeStamp.secondsPastEpoch int64_t = 1794422277 ch01.timeStamp.nanoseconds int32_t = 691465958 ch01.timeStamp.userTag int32_t = 0 ch01.display.limitLow double = 0 ch01.display.limitHigh double = 0 ch01.display.description string = "TC temperature at Channel 01" ch01.display.units string = "Celsius" ch01.display.precision int32_t = 0 ch01.display.form.index int32_t = 0 ch01.display.form.choices string[] = {7}["Default", "String", "Binary", "Decimal", "Hex", "Exponential", "Engineering"] ch01.control.limitLow double = 0 ch01.control.limitHigh double = 0 ch01.valueAlarm.lowAlarmLimit double = nan ch01.valueAlarm.lowWarningLimit double = nan ch01.valueAlarm.highWarningLimit double = nan ch01.valueAlarm.highAlarmLimit double = nan ch16.value double = 86.5753 ch16.alarm.severity int32_t = 0 ch16.alarm.status int32_t = 0 ch16.alarm.message string = "" ch16.timeStamp.secondsPastEpoch int64_t = 1794422277 ch16.timeStamp.nanoseconds int32_t = 729144531 ch16.timeStamp.userTag int32_t = 0 # Repeated display, control, and valueAlarm fields omitted for ch16 and ch32 ch32.value double = 77.3631 ch32.alarm.severity int32_t = 0 ch32.alarm.status int32_t = 0 ch32.alarm.message string = "" ch32.timeStamp.secondsPastEpoch int64_t = 1794422277 ch32.timeStamp.nanoseconds int32_t = 688521675 ch32.timeStamp.userTag int32_t = 0 device.model string = "TC-32"
Verification
-
In terminal 3, read all 32 scalar channels once to confirm every record receives live stream updates between
10and90:for ch in {01..32}; do caget -t "LOADER:TC32:CH$ch"; done -
In terminal 3, inspect the published PVA type identifier on
LOADER:TC32:Summary:pvxinfo LOADER:TC32:Summary | head -n 3The structure header shows the custom
+idcontract:LOADER:TC32:Summary from 127.0.0.1:43259 struct "training:nt/TC32Summary:1.0" { struct { -
In terminal 3, subscribe to three scalar channels with
camonitorto verify continuous updates:camonitor LOADER:TC32:CH01 LOADER:TC32:CH16 LOADER:TC32:CH32Stop
camonitorin terminal 3 with Ctrl+C, stoptc32_emulator.bashin terminal 1 with Ctrl+C, and then enterexitin terminal 2.