13 Commits
0.1.0 ... 0.2.1

32 changed files with 1539 additions and 796 deletions

View File

@ -1,5 +1,5 @@
default:
image: docker.psi.ch:5000/wall_e/sinqepics:latest
image: docker.psi.ch:5000/sinqdev/sinqepics:latest
stages:
- lint
@ -13,7 +13,7 @@ cppcheck:
artifacts:
expire_in: 1 week
tags:
- docker
- sinq
formatting:
stage: lint
@ -22,7 +22,7 @@ formatting:
artifacts:
expire_in: 1 week
tags:
- docker
- sinq
# clangtidy:
# stage: lint
@ -32,12 +32,13 @@ formatting:
# # - dnf install -y clang-tools-extra
# # - clang-tidy sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h -checks=cppcoreguidelines-*,cert-*
# # tags:
# # - docker
# # - sinq
build_module:
stage: build
script:
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
- echo "LIBVERSION=${CI_COMMIT_TAG:-0.0.1}" >> Makefile
- make install
- cp -rT "/ioc/modules/counterbox/$(ls -U /ioc/modules/counterbox/ | head -1)" "./counterbox-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
artifacts:
@ -47,15 +48,14 @@ build_module:
expire_in: 1 week
when: always
tags:
- docker
- sinq
# TODO I don't know why this fails and gave up debugging for now
# test_module:
# stage: test
# script:
# - mkdir -p "/ioc/modules/counterbox"
# - cp -rT "./counterbox-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}" "/ioc/modules/counterbox/0.0.1" # Seems it needs a number
# - python3 test/test.py
# when: always
# tags:
# - docker
test_module:
stage: test
script:
- mkdir -p "/ioc/modules/counterbox"
- cp -rT "./counterbox-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}" "/ioc/modules/counterbox/${CI_COMMIT_TAG:-0.0.1}"
- python3 test/test.py
when: always
tags:
- sinq

View File

@ -1,7 +1,7 @@
# This build the sinq extensions for the PSI EPICS setup
include /ioc/tools/driver.makefile
MODULE=counterbox
MODULE=sinqDAQ
BUILDCLASSES=Linux
EPICS_VERSIONS=7.0.7
ARCH_FILTER=RHEL%
@ -13,23 +13,27 @@ REQUIRED+=stream
# DB files to include in the release
TEMPLATES += db/channels.db
TEMPLATES += db/counterbox_4ch.db
TEMPLATES += db/counterbox_8ch.db
TEMPLATES += db/counterbox_common.db
TEMPLATES += db/counterbox_v2.db
TEMPLATES += db/counterbox_v2_test.db
TEMPLATES += db/counterbox.proto
TEMPLATES += db/gating_channels.db
TEMPLATES += db/daq_4ch.db
TEMPLATES += db/daq_8ch.db
TEMPLATES += db/daq_common.db
TEMPLATES += db/daq_2nd_gen.db
TEMPLATES += db/daq_2nd_gen_test.db
TEMPLATES += db/daq.proto
# Just for simulation
TEMPLATES += db/daq_simcontrol.db
# DBD files to include in the release
DBDS += src/counterbox.dbd
DBDS += src/daq.dbd
# Source files to build
SOURCES += src/counterbox.cpp
SOURCES += src/daq.cpp
SCRIPTS += scripts/counterbox_4ch.cmd
SCRIPTS += scripts/counterbox_8ch.cmd
SCRIPTS += scripts/counterbox_v2.cmd
SCRIPTS += sim/counterbox_sim.py
SCRIPTS += scripts/daq_4ch.cmd
SCRIPTS += scripts/daq_8ch.cmd
SCRIPTS += scripts/daq_2nd_gen.cmd
SCRIPTS += sim/daq_sim.py
CXXFLAGS += -std=c++17
USR_CFLAGS += -Wall -Wextra #-Werror

168
README.md
View File

@ -1,7 +1,8 @@
Counterbox Epics Module
SINQDAQ Epics Module
-----------------------
A Stream and Asyn based driver for configuring the Counterboxes at SINQ.
A Stream and Asyn based driver for configuring the Data Acquisition Systems at
SINQ.
This supports the older 4 and 8 channel EL737 models and the new 10CH 2nd
generation systems.
@ -9,51 +10,53 @@ generation systems.
## How to Use
Unless a custom database is needed, a device can be configure simply by setting
the required environment variables when calling the correct counterbox script.
the required environment variables when calling the correct DAQ interface
script.
Required Variables
| Environment Variable | Purpose |
|----------------------|-----------------------------------------|
| PREFIX | Prefix of all device specific PVs |
| INSTR | Prefix of all device specific PVs |
| NAME | First field in all PVs after Prefix |
| CNTBOX\_IP | Network IP of device |
| CNTBOX\_PORT | Network Port of device |
| DAQ\_IP | Network IP of device |
| DAQ\_PORT | Network Port of device |
All PVs take the form
```
$(PREFIX):$(NAME):*
$(INSTR)$(NAME):*
```
Available device startup scripts
* scripts/counterbox\_4ch.cmd
* scripts/counterbox\_8ch.cmd
* scripts/counterbox\_v2.cmd
* scripts/daq\_4ch.cmd
* scripts/daq\_8ch.cmd
* scripts/daq\_2nd\_gen.cmd
A device can be configured using one of the startup scripts as follows
```
epicsEnvSet("PREFIX", "SQ:INSTRUMENT") # can also be set in runScript call
epicsEnvSet("INSTR", "SQ:INSTRUMENT:") # can also be set in runScript call
runScript "$(counterbox_DIR)counterbox_v2.cmd" "NAME=COUNTERBOX, CNTBOX_IP=TestInst-DAQ1, CNTBOX_PORT=2000"
runScript "$(sinqDAQ_DIR)daq_2nd_gen.cmd" "NAME=DAQ, DAQ_IP=TestInst-DAQ1, DAQ_PORT=2000"
```
## PVs of Interest
| PV | Description |
|---------------------------------------|----------------------------------------------------------------------|
| \$(PREFIX):\$(NAME):MsgTxt | Contains unexpected response to executed command |
| \$(PREFIX):\$(NAME):STATUS | 0: Idle, 1: Counting, 2: Low rate, 3: Paused, 4: Error |
| \$(PREFIX):\$(NAME):MONITOR-CHANNEL | Channel that PRESET-COUNT monitors (has RBV, only v2 can be changed) |
| \$(PREFIX):\$(NAME):PRESET-COUNT | Run count until specified pv value reached |
| \$(PREFIX):\$(NAME):PRESET-TIME | Run count until specified pv value in seconds reached |
| \$(PREFIX):\$(NAME):THRESHOLD | Minimum rate for counting to preceed. (has RBV) |
| \$(PREFIX):\$(NAME):THRESHOLD-MONITOR | Channel monitored for minimum rate (has RBV) |
| \$(PREFIX):\$(NAME):ELAPSED-TIME | Time Counterbox has been measuring for |
| \$(PREFIX):\$(NAME):M_ | Current count on channel. (1-10 depending on box) |
| \$(PREFIX):\$(NAME):CHANNELS | Number of available channels (4, 8 or 10) |
| PV | Description |
|-------------------------------------|----------------------------------------------------------------------|
| \$(INSTR)\$(NAME):MsgTxt | Contains unexpected response to executed command |
| \$(INSTR)\$(NAME):STATUS | 0: Idle, 1: Counting, 2: Low rate, 3: Paused, 4: Error |
| \$(INSTR)\$(NAME):MONITOR-CHANNEL | Channel that PRESET-COUNT monitors (has RBV, only v2 can be changed) |
| \$(INSTR)\$(NAME):PRESET-COUNT | Run count until specified pv value reached |
| \$(INSTR)\$(NAME):PRESET-TIME | Run count until specified pv value in seconds reached |
| \$(INSTR)\$(NAME):THRESHOLD | Minimum rate for counting to preceed. (has RBV) |
| \$(INSTR)\$(NAME):THRESHOLD-MONITOR | Channel monitored for minimum rate (has RBV) |
| \$(INSTR)\$(NAME):ELAPSED-TIME | Time DAQ has been measuring for |
| \$(INSTR)\$(NAME):M_ | Current count on channel. (1-10 depending on DAQ system) |
| \$(INSTR)\$(NAME):CHANNELS | Number of available channels (4, 8 or 10) |
| \$(INSTR)\$(NAME):GATE-_ | Configuration for Gating in newer hardware |
## Generating Test Signals
@ -64,34 +67,133 @@ runtime via the following
```
epicsEnvSet("LOAD_TEST_PVS","")
runScript "$(counterbox_DIR)counterbox_v2.cmd" "NAME=COUNTERBOX, CNTBOX_IP=TestInst-DAQ1, CNTBOX_PORT=2000"
runScript "$(sinqDAQ_DIR)daq_2nd_gen.cmd" "NAME=DAQ, DAQ_IP=TestInst-DAQ1, DAQ_PORT=2000"
```
See the file [counterbox\_v2\_test.db](./db/counterbox_v2_test.db)
See the file [daq\_2nd\_gen\_test.db](./db/daq_2nd_gen_test.db)
## Nicos Interface
A set of Nicos devices have been developed which allow control of the Detector
Hardware via this Epics Driver. The corresponding code can be found in
[sinqdaq.py](https://gitea.psi.ch/lin-instrument-computers/Nicos/src/branch/release-3.12/nicos_sinq/devices/epics/sinqdaq.py).
## Full Example
Include the following snippet in your IOC
```
# st.cmd at SINQTEST
epicsEnvSet("STREAM_PROTOCOL_PATH","./db")
epicsEnvSet("INSTR","SQ:SINQTEST:")
require sinqDAQ
runScript "$(sinqDAQ_DIR)daq_2nd_gen.cmd" "NAME=DAQ, DAQ_IP=TestInst-DAQ1, DAQ_PORT=2000"
```
What follows is an example Nicos setup file. The "channels" are created in the loop
at the bottom.
```
# DAQDetector.py
description = 'Devices for the detectors'
countprefix = 'SQ:SINQTEST:DAQ'
devices = dict(
ElapsedTime = device(
'nicos_sinq.devices.epics.sinqdaq.DAQTime',
daqpvprefix = countprefix,
),
DAQPreset = device(
'nicos_sinq.devices.epics.sinqdaq.DAQPreset',
description = '2nd Generation Data Acquisition',
daqpvprefix = countprefix,
channels = [],
time_channel = ['ElapsedTime'],
),
DAQV2 = device(
'nicos_sinq.devices.epics.sinqdaq.SinqDetector',
description = 'Detector Interface',
timers = ['ElapsedTime'],
counters = [],
monitors = ['DAQPreset'],
images = [],
others = [],
liveinterval = 2,
saveintervals = [2]
),
ThresholdChannel = device(
'nicos_sinq.devices.epics.sinqdaq.DAQMinThresholdChannel',
daqpvprefix = countprefix,
channels = [],
visibility = {'metadata', 'namespace'},
),
Threshold = device(
'nicos_sinq.devices.epics.sinqdaq.DAQMinThreshold',
daqpvprefix = countprefix,
min_rate_channel = 'ThresholdChannel',
visibility = {'metadata', 'namespace'},
),
Gate1 = device(
'nicos_sinq.devices.epics.sinqdaq.DAQGate',
daqpvprefix = countprefix,
channel = 1,
visibility = {'metadata', 'namespace'},
),
Gate2 = device(
'nicos_sinq.devices.epics.sinqdaq.DAQGate',
daqpvprefix = countprefix,
channel = 2,
visibility = {'metadata', 'namespace'},
),
TestGen = device('nicos_sinq.devices.epics.sinqdaq.DAQTestGen',
daqpvprefix = countprefix,
visibility = {'metadata', 'namespace'},
),
)
for i in range(10):
devices[f'monitor{i+1}'] = device(
'nicos_sinq.devices.epics.sinqdaq.DAQChannel',
description = f'Monitor {i + 1}',
daqpvprefix = countprefix,
channel = i + 1,
type = 'monitor',
)
devices['DAQPreset'][1]['channels'].append(f'monitor{i+1}')
devices['ThresholdChannel'][1]['channels'].append(f'monitor{i+1}')
devices['DAQV2'][1]['monitors'].append(f'monitor{i+1}')
startupcode = '''
SetDetectors(DAQV2)
'''
```
## Simulation
Simulation of the Hardware can be toggled on as follows:
```
epicsEnvSet("SET_SIM_MODE","") # run counterbox simulation instead of connecting to actual box
runScript "$(counterbox_DIR)counterbox_v2.cmd" "NAME=CB_TEST, CNTBOX_IP=localhost, CNTBOX_PORT=2000"
epicsEnvSet("SET_SIM_MODE","") # run DAQ simulation instead of connecting to actual system
runScript "$(sinqDAQ_DIR)daq_2nd_gen.cmd" "NAME=CB_TEST, DAQ_IP=localhost, DAQ_PORT=2000"
```
In such a case, the provided `CNTBOX_IP` is ignored, and a python program
In such a case, the provided `DAQ_IP` is ignored, and a python program
simulating the hardware is started in the background, listening at the
specified `CNTBOX_PORT`. So, if you have multiple devices listening on the same
specified `DAQ_PORT`. So, if you have multiple devices listening on the same
port, you might have to change this port value of one of the devices when
simulating hardware. You can then interact with the PVs as with the normal
hardware. Keep in mind, however, that not all functionality has been
implemented.
See [sim/counterbox\_sim.py](sim/counterbox_sim.py).
See [sim/daq\_sim.py](sim/daq_sim.py).
## Testing
An IOC with the counterbox\_v2 started in simulation mode can be started via
the [test/ioc.sh](test/ioc.sh) script.
An IOC with the 2nd generation DAQ started in simulation mode can be started
via the [test/ioc.sh](test/ioc.sh) script.
There is also a simple automated test that can be run for a simple check of
functionality and that the PVs load [test/test.py](test/test.py).

View File

@ -1,20 +1,77 @@
# EL737 EPICS Database for streamdevice support
# EPICS Database for streamdevice specific to measurement channels
#
# Macros
# P - Prefix
# NAME - just a name, e.g. EL737
# INSTR - Prefix
# NAME - the device name, e.g. EL737
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to EL737
# ASYN_PORT - Low level Asyn IP Port to DAQ
# CHANNEL - the number associated with the measurment channel
################################################################################
# Status Variables
# Trigger a change in status as clearing
record(bo, "$(INSTR)$(NAME):T$(CHANNEL)")
{
field(DESC, "Trigger Clearing Status")
field(VAL, 1)
field(OUT, "$(INSTR)$(NAME):S$(CHANNEL) PP")
}
# Trigger a change in status as value returned to 0
record(seq, "$(INSTR)$(NAME):O$(CHANNEL)")
{
field(DESC, "Trigger Returned to 0 Status")
field(LNK0, "$(INSTR)$(NAME):S$(CHANNEL) PP")
field(DO0, 0)
field(SELM, "Specified")
field(SELL, "$(INSTR)$(NAME):M$(CHANNEL).VAL")
}
# Current Status of Channel, i.e. is it ready to count?
record(bi, "$(INSTR)$(NAME):S$(CHANNEL)")
{
field(DESC, "Channel Status")
field(VAL, 0)
field(ZNAM, "OK")
field(ONAM, "CLEARING")
}
################################################################################
# Count Commands
record(longout, "$(INSTR)$(NAME):C$(CHANNEL)")
{
field(DESC, "Clear the current channel count")
field(DTYP, "stream")
field(OUT, "@$(PROTO) clearChannel($(INSTR)$(NAME):, $(CHANNEL)) $(ASYN_PORT)")
field(FLNK, "$(INSTR)$(NAME):T$(CHANNEL)")
}
record(ao,"$(INSTR)$(NAME):THRESH$(CHANNEL)")
{
field(DESC, "Sets min rate for counting to proceed")
field(OMSL, "supervisory")
field(OROC, "0")
field(OUT, "@$(PROTO) setMinRate($(INSTR)$(NAME):, $(CHANNEL)) $(ASYN_PORT)")
field(DTYP, "stream")
}
################################################################################
# Read all monitors values
record(longin, "$(P):$(NAME):M$(CHANNEL)")
record(longin, "$(INSTR)$(NAME):M$(CHANNEL)")
{
field(DESC, "Counterbox CH$(CHANNEL)")
field(DESC, "DAQ CH$(CHANNEL)")
field(EGU, "cts")
field(FLNK, "$(INSTR)$(NAME):O$(CHANNEL)")
}
record(ai, "$(INSTR)$(NAME):R$(CHANNEL)")
{
field(DESC, "Rate of DAQ CH$(CHANNEL)")
field(INP, "@$(PROTO) readRate($(INSTR)$(NAME):, $(CHANNEL)) $(ASYN_PORT)")
field(DTYP, "stream")
field(EGU, "cts/sec")
field(SCAN, "1 second")
}

View File

@ -1,134 +0,0 @@
#
# Counterbox Protocol File
#
OutTerminator = CR;
InTerminator = CR;
ReadTimeout = 100;
WriteTimeout = 100;
ReplyTimeout = 200;
LockTimeout = 450;
initialise {
out "RMT 1"; # Turn on Remote Control
in;
out "ECHO 2"; # Ask for reponses
in "%(\$1MsgTxt)s"; # Clear MsgTxt on Init
@mismatch{
exec 'echo "Failed to configure counterbox" && exit(1)';
}
}
fullReset {
out "\%";
wait 5000;
}
################################################################################
# Status Variables
readStatus {
out "RS";
in "%d";
@mismatch{in "%(\$1MsgTxt)s";}
}
readPresetMonitor {
out "PC";
in "%d";
@mismatch{in "%(\$1MsgTxt)s";}
}
writePresetMonitor {
out "PC %d";
@mismatch{in "%(\$1MsgTxt)s";}
}
################################################################################
# Count Commands
startWithCountPreset {
out "MP %d";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
startWithTimePreset {
out "TP %#.2f";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
pauseCount {
out "PS";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
continueCount {
out "CO";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
stopCount {
out "S";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
setMinRate{
out "DL %(\$1THRESHOLD-MONITOR)d %(\$1THRESHOLD)d";
in;
out "DR %(\$1THRESHOLD-MONITOR)d";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
readMinRate{
out "DR";
in "%(\$1THRESHOLD-MONITOR_RBV)d";
out "DL %(\$1THRESHOLD-MONITOR_RBV)d";
in "%(\$1THRESHOLD_RBV)d";
@mismatch{in "%(\$1MsgTxt)s";}
}
################################################################################
# Read Values From Monitors
readAll4 {
out "RA";
in "%(\$1ELAPSED-TIME)f %(\$1M1)d %(\$1M2)d %(\$1M3)d %(\$1M4)d";
@mismatch{in "%(\$1MsgTxt)s";}
}
readAll8 {
out "RA";
in "%(\$1ELAPSED-TIME)f %(\$1M1)d %(\$1M2)d %(\$1M3)d %(\$1M4)d %(\$1M5)d %(\$1M6)d %(\$1M7)d %(\$1M8)d";
@mismatch{in "%(\$1MsgTxt)s";}
}
readAll10 {
out "RA";
in "%(\$1ELAPSED-TIME)f %(\$1M1)d %(\$1M2)d %(\$1M3)d %(\$1M4)d %(\$1M5)d %(\$1M6)d %(\$1M7)d %(\$1M8)d %(\$1M9)d %(\$1M10)d";
@mismatch{in "%(\$1MsgTxt)s";}
}
readRate {
out "RR \$2";
in "%(\$1R\$2)f";
@mismatch{in "%(\$1MsgTxt)s";}
}
################################################################################
# Testing Commands
switchTestgenOnOff {
out "TG %{off|on}";
@mismatch{in "%(\$1MsgTxt)s";}
}
# Only suppporting test channel 1 at the moment. (The first argument to TG)
setTestSignal {
out "TG 1 %(\$1TESTGEN-HIGHRATE)d %(\$1TESTGEN-LOWRATE)d";
@mismatch{in "%(\$1MsgTxt)s";}
}

View File

@ -1,29 +0,0 @@
# Counterbox EPICS Database
# Macros
# P - Prefix
# NAME - just a name, e.g. EL737
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to EL737
################################################################################
# Status Variables
record(longin, "$(P):$(NAME):MONITOR-CHANNEL")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(VAL, 1)
field(DISP, 1)
}
record(longin, "$(P):$(NAME):MONITOR-CHANNEL_RBV")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(VAL, 1)
field(DISP, 1)
}
################################################################################
# Count Commands
################################################################################
# Read all monitors values

View File

@ -1,29 +0,0 @@
# EL737 EPICS Database for streamdevice support
# Macros
# P - Prefix
# NAME - just a name, e.g. EL737
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to EL737
################################################################################
# Status Variables
record(longin, "$(P):$(NAME):MONITOR-CHANNEL")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(VAL, 1)
field(DISP, 1)
}
record(longin, "$(P):$(NAME):MONITOR-CHANNEL_RBV")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(VAL, 1)
field(DISP, 1)
}
################################################################################
# Count Commands
################################################################################
# Read all monitors values

View File

@ -1,215 +0,0 @@
# EL737 EPICS Database for streamdevice support
# Macros
# P - Prefix
# NAME - just a name, e.g. EL737
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to EL737
# Send initial initialisation commands
record(bo, "$(P):$(NAME):INIT-CONF")
{
field(DESC, "Initialises the Counterbox")
field(OUT, "@$(PROTO) initialise($(P):$(NAME):) $(ASYN_PORT)")
field(PINI, "YES") # Run at init
field(DTYP, "stream")
field(FLNK, "$(P):$(NAME):INIT-BOX")
}
# As we aren't certain of the order that PINI exectutes PVs, we only set it to
# true on INIT-CONF to make sure the box is ready to receive commands, and then
# let INIT-CONF trigger the initialisation of other necessary records
record(fanout, "$(P):$(NAME):INIT-BOX")
{
field(DESC, "Rewrite PVs to Box")
field(SELM, "All")
field(LNK0, "$(P):$(NAME):MONITOR-CHANNEL_RBV PP")
field(LNK1, "$(P):$(NAME):RAW-STATUS PP")
field(LNK2, "$(P):$(NAME):THRESHOLD_RBV PP")
}
record(longout, "$(P):$(NAME):FULL-RESET")
{
field(DESC, "Reset the Counterbox")
field(OUT, "@$(PROTO) fullReset($(P):$(NAME):) $(ASYN_PORT)")
field(DTYP, "stream")
}
################################################################################
# Status Variables
record(stringin, "$(P):$(NAME):MsgTxt")
{
field(DESC, "Unexpected received response")
field(DTYP, "devCounterBoxStringError")
field(FLNK, "$(P):$(NAME):INVALID-CONFIG")
}
# We want to recognise the invalid config error message, so that we can rerun
# the init if it occurs. This should only happen after turning the box off and
# on again or running a full reset
record(scalcout, "$(P):$(NAME):INVALID-CONFIG")
{
field(DESC, "Has the counterbox been configured?")
field(CALC, "AA[0,2] == '?OF'")
field(INAA, "$(P):$(NAME):MsgTxt")
field(FLNK, "$(P):$(NAME):REINIT-CONF")
}
record(seq, "$(P):$(NAME):REINIT-CONF")
{
field(LNK1, "$(P):$(NAME):INIT-CONF PP")
field(DO1, 1)
field(SELM, "Specified")
field(SELL, "$(P):$(NAME):INVALID-CONFIG.VAL")
}
# Important! The "$(P):$(NAME):READALL" isn't configure with a SCAN. Instead,
# it must always be triggered after the value of $(P):$(NAME):RAW-STATUS is
# updated, so that it can't be the case that the status changes back from
# counting to idle, without having updated the time and count values.
record(longin, "$(P):$(NAME):RAW-STATUS")
{
field(DESC, "Raw returned status value")
field(DTYP, "stream")
field(SCAN, ".1 second")
field(INP, "@$(PROTO) readStatus($(P):$(NAME):) $(ASYN_PORT)")
field(FLNK, "$(P):$(NAME):READALL")
}
record(calc, "$(P):$(NAME):MAP-STATUS")
{
field(DESC, "Maps Raw Status to State")
field(INPA, "$(P):$(NAME):RAW-STATUS NPP")
field(INPB, "$(P):$(NAME):INVALID-CONFIG NPP")
field(INPC, "$(P):$(NAME):RAW-STATUS.UDF NPP") # should also be invalid if can't read the status
field(CALC, "(B=1||C==1)?4:A=0?0:(A=1||A=2)?1:(A=5||A=6)?2:(A=9||A=13||A=10||A=14)?3:4")
field(FLNK, "$(P):$(NAME):STATUS")
}
record(mbbi, "$(P):$(NAME):STATUS")
{
field(DESC, "Counterbox Status")
field(INP, "$(P):$(NAME):MAP-STATUS NPP")
field(ZRVL, "0")
field(ZRST, "Idle")
field(ONVL, "1")
field(ONST, "Counting")
field(TWVL, "2")
field(TWST, "Low rate")
field(THVL, "3")
field(THST, "Paused")
# 4 should never happen, if it does it means the counter box reports undocumented statusbits
field(FRVL, "4")
field(FRST, "INVALID")
}
record(longin, "$(P):$(NAME):CHANNELS")
{
field(DESC, "Total Supported Channels")
field(VAL, $(CHANNELS))
field(DISP, 1)
}
################################################################################
# Count Commands
record(ao,"$(P):$(NAME):PRESET-COUNT")
{
field(DESC, "Count until preset reached")
field(DTYP, "stream")
field(OUT, "@$(PROTO) startWithCountPreset($(P):$(NAME):) $(ASYN_PORT)")
field(VAL, 0)
field(PREC, 2)
}
record(ao,"$(P):$(NAME):PRESET-TIME")
{
field(DESC, "Count for specified time")
field(DTYP, "stream")
field(OUT, "@$(PROTO) startWithTimePreset($(P):$(NAME):) $(ASYN_PORT)")
field(VAL, 0)
field(PREC, 2)
field(EGU, "seconds")
}
record(bo,"$(P):$(NAME):PAUSE")
{
field(DESC, "Pause the current count")
field(DTYP, "stream")
field(OUT, "@$(PROTO) pauseCount($(P):$(NAME):) $(ASYN_PORT)")
field(VAL, "0")
}
record(bo,"$(P):$(NAME):CONTINUE")
{
field(DESC, "Continue with a count that was paused")
field(DTYP, "stream")
field(OUT, "@$(PROTO) continueCount($(P):$(NAME):) $(ASYN_PORT)")
field(VAL, "0")
}
record(bo, "$(P):$(NAME):STOP")
{
field(DESC, "Stop the current counting operation")
field(DTYP, "stream")
field(OUT, "@$(PROTO) stopCount($(P):$(NAME):) $(ASYN_PORT)")
}
# TODO should changing the monitor also set things?
# or only when actually setting a threshold?
record(longout,"$(P):$(NAME):THRESHOLD")
{
field(DESC, "Minimum rate for counting to proceed")
field(VAL, "0") # Rate
field(DRVL, "0") # Minimum Rate
field(DTYP, "stream")
field(OUT, "@$(PROTO) setMinRate($(P):$(NAME):) $(ASYN_PORT)")
}
record(longin,"$(P):$(NAME):THRESHOLD_RBV")
{
field(DESC, "Minimum rate for counting to proceed")
field(DTYP, "stream")
field(INP, "@$(PROTO) readMinRate($(P):$(NAME):) $(ASYN_PORT)")
field(SCAN, "2 second")
}
record(longout,"$(P):$(NAME):THRESHOLD-MONITOR")
{
field(DESC, "Channel monitored for minimum rate")
field(VAL, "1") # Monitor
field(DRVL, "1") # Smallest Threshold Channel
field(DRVL, "$(CHANNELS)") # Largest Threshold Channel
}
record(longin,"$(P):$(NAME):THRESHOLD-MONITOR_RBV")
{
field(DESC, "Channel monitored for minimum rate")
}
################################################################################
# Read all monitors values
record(ai, "$(P):$(NAME):READALL")
{
field(DESC, "Reads monitors and elapsed time")
field(INP, "@$(PROTO) readAll$(CHANNELS)($(P):$(NAME):) $(ASYN_PORT)")
field(DTYP, "stream")
field(FLNK, "$(P):$(NAME):MAP-STATUS")
}
record(ai,"$(P):$(NAME):ELAPSED-TIME")
{
field(DESC, "Counterbox Measured Time")
field(EGU, "seconds")
}
# Not yet sure whether we want to support this
# record(longin, "$(P):$(NAME):R1")
# {
# field(DESC, "Counterbox Rate CH1")
# field(INP, "@$(PROTO) readRate($(P):$(NAME):, 1) $(ASYN_PORT)")
# field(SCAN, ".2 second")
# field(DTYP, "stream")
# }

View File

@ -1,31 +0,0 @@
# EL737 EPICS Database for streamdevice support
# Macros
# P - Prefix
# NAME - just a name, e.g. DAQV2
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to Counterbox
################################################################################
# Status Variables
record(longout, "$(P):$(NAME):MONITOR-CHANNEL")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(DTYP, "stream")
field(OUT, "@$(PROTO) writePresetMonitor($(P):$(NAME):) $(ASYN_PORT)")
field(FLNK, "$(P):$(NAME):MONITOR-CHANNEL_RBV")
}
record(longin, "$(P):$(NAME):MONITOR-CHANNEL_RBV")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(DTYP, "stream")
field(INP, "@$(PROTO) readPresetMonitor($(P):$(NAME):) $(ASYN_PORT)")
field(SCAN, "5 second")
}
################################################################################
# Count Commands
################################################################################
# Read all monitors values

View File

@ -1,31 +0,0 @@
################################################################################
# Testing Commands
# These won't match the values on the machine after a full restart But I chose
# not to force their intialisation as they are only important for testing
record(bo, "$(P):$(NAME):TESTGEN")
{
field(DESC, "Turn on/off Testgen Signal")
field(DTYP, "stream")
field(OUT, "@$(PROTO) switchTestgenOnOff($(P):$(NAME):) $(ASYN_PORT)")
field(VAL, 0)
field(ZNAM, "OFF")
field(ONAM, "ON")
}
record(longout, "$(P):$(NAME):TESTGEN-LOWRATE")
{
field(DESC, "Set Minimum Testgen Rate")
field(DTYP, "stream")
field(OUT, "@$(PROTO) setTestSignal($(P):$(NAME):) $(ASYN_PORT)")
field(VAL, 1000)
}
record(longout, "$(P):$(NAME):TESTGEN-HIGHRATE")
{
field(DESC, "Set Maximum Testgen Rate")
field(DTYP, "stream")
field(OUT, "@$(PROTO) setTestSignal($(P):$(NAME):) $(ASYN_PORT)")
field(VAL, 1000)
}

268
db/daq.proto Normal file
View File

@ -0,0 +1,268 @@
#
# SinqDAQ Protocol File
#
OutTerminator = CR;
InTerminator = CR;
ReadTimeout = 100;
WriteTimeout = 100;
ReplyTimeout = 200;
LockTimeout = 450;
initialise {
out "RMT 1"; # Turn on Remote Control
in;
out "ECHO 2"; # Ask for reponses
in "%(\$1MsgTxt)s"; # Clear MsgTxt on Init
@mismatch{
exec 'echo "Failed to configure DAQ" && exit(1)';
}
}
fullReset {
out "\%";
wait 5000;
}
################################################################################
# Status Variables
readStatus {
out "RS";
in "%d";
@mismatch{in "%(\$1MsgTxt)s";}
}
readPresetMonitor {
out "PC";
in "%d";
@mismatch{in "%(\$1MsgTxt)s";}
}
writePresetMonitor {
out "PC %d";
@mismatch{in "%(\$1MsgTxt)s";}
}
################################################################################
# Count Commands
pauseCount {
out "PS";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
continueCount {
out "CO";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
stopCount {
out "S";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
clearTimer{
# We first stop the count, as otherwise the 2nd Gen Data Acquisition starts
# counting again if a time preset was set. Not a problem with the older boxes
stopCount;
out "CT";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
clearChannel{
out "CC \$2";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
clearCounter4 {
out "CC 1";
in;
out "CC 2";
in;
out "CC 3";
in;
out "CC 4";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
clearCounter8 {
out "CC 5";
in;
out "CC 6";
in;
out "CC 7";
in;
out "CC 8";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
clearCounter10 {
out "CC 9";
in;
out "CC 10";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
startWithCountPreset {
out "MP %d";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
startWithTimePreset {
out "TP %#.2f";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
setMinRate{
out "DL \$2 %.3f";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
readMinRate{
out "DL %(\$1THRESHOLD-MONITOR_RBV)d";
in "%f";
@mismatch{in "%(\$1MsgTxt)s";}
}
setRateMonitor{
out "DR %d";
in;
@mismatch{in "%(\$1MsgTxt)s";}
}
readRateMonitor{
out "DR";
in "%d";
@mismatch{in "%(\$1MsgTxt)s";}
}
################################################################################
# Read Values From Monitors
readAll4 {
out "RA";
in "%(\$1ELAPSED-TIME)f %(\$1M1)d %(\$1M2)d %(\$1M3)d %(\$1M4)d";
@mismatch{in "%(\$1MsgTxt)s";}
}
readAll8 {
out "RA";
in "%(\$1ELAPSED-TIME)f %(\$1M1)d %(\$1M2)d %(\$1M3)d %(\$1M4)d %(\$1M5)d %(\$1M6)d %(\$1M7)d %(\$1M8)d";
@mismatch{in "%(\$1MsgTxt)s";}
}
readAll10 {
out "RA";
in "%(\$1ELAPSED-TIME)f %(\$1M1)d %(\$1M2)d %(\$1M3)d %(\$1M4)d %(\$1M5)d %(\$1M6)d %(\$1M7)d %(\$1M8)d";
# At least on the sinqtest variant this is broken
# requiring channels 9 and 10 to be manually queried
out "RC 9";
in "%(\$1M9)d";
out "RC 10";
in "%(\$1M10)d";
@mismatch{in "%(\$1MsgTxt)s";}
}
readRate {
out "RR \$2";
in "%f";
@mismatch{in "%(\$1MsgTxt)s";}
}
################################################################################
# Testing Commands
switchTestgenOnOff {
out "TG %{off|on}";
@mismatch{in "%(\$1MsgTxt)s";}
}
# Only suppporting test channel 1 at the moment. (The first argument to TG)
setTestSignal {
out "TG 1 %(\$1TESTGEN-HIGHRATE)d %(\$1TESTGEN-LOWRATE)d";
@mismatch{in "%(\$1MsgTxt)s";}
}
################################################################################
# Gating Settings
getGateStatus {
out "GT \$2";
in "%d %(\$1GATE-\$2-TRIG_RBV)d";
@mismatch{in "%(\$1MsgTxt)s";}
}
setGateStatus {
extrainput = ignore;
out "GT \$2 %(\$1GATE-\$2-ENABLE)d %(\$1GATE-\$2-TRIG)d";
in "Gate \$2";
@mismatch{in "%(\$1MsgTxt)s";}
}
setGate {
out "GATE \$2 %d";
in "";
@mismatch{in "%(\$1MsgTxt)s";}
}
################################################################################
# TODO To clean
startWithCountPreset4 {
clearTimer;
clearCounter4;
readAll4;
startWithCountPreset;
}
startWithCountPreset8 {
clearTimer;
clearCounter4;
clearCounter8;
readAll8;
startWithCountPreset;
}
startWithCountPreset10 {
clearTimer;
clearCounter4;
clearCounter8;
clearCounter10;
readAll10;
startWithCountPreset;
}
startWithTimePreset4 {
clearTimer;
clearCounter4;
readAll4;
startWithTimePreset;
}
startWithTimePreset8 {
clearTimer;
clearCounter4;
clearCounter8;
readAll8;
startWithTimePreset;
}
startWithTimePreset10 {
clearTimer;
clearCounter4;
clearCounter8;
clearCounter10;
readAll10;
startWithTimePreset;
}

78
db/daq_2nd_gen.db Normal file
View File

@ -0,0 +1,78 @@
# EPICS Database for streamdevice support for functionality specific to the 2nd
# Generation Systems
#
# Macros
# INSTR - Prefix
# NAME - the device name, e.g. DAQV2
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to DAQ
################################################################################
# Status Variables
record(longout, "$(INSTR)$(NAME):MONITOR-CHANNEL")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(DRVL, "1") # Smallest Monitor Channel
field(DRVH, "$(CHANNELS)") # Largest Monitor Channel
field(DTYP, "stream")
field(OUT, "@$(PROTO) writePresetMonitor($(INSTR)$(NAME):) $(ASYN_PORT)")
field(FLNK, "$(INSTR)$(NAME):MONITOR-CHANNEL_RBV")
}
record(longin, "$(INSTR)$(NAME):MONITOR-CHANNEL_RBV")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(DTYP, "stream")
field(INP, "@$(PROTO) readPresetMonitor($(INSTR)$(NAME):) $(ASYN_PORT)")
field(SCAN, "1 second")
}
# Force back to 1 if it is 0, as 0 has no meaning...
record(seq, "$(INSTR)$(NAME):CORRECT-MONITOR-CHANNEL")
{
field(SELM, "Specified")
field(SELL, "$(INSTR)$(NAME):MONITOR-CHANNEL_RBV.VAL NPP")
field(DO0, 1)
field(LNK0, "$(INSTR)$(NAME):MONITOR-CHANNEL PP")
field(SCAN, ".5 second")
}
################################################################################
# Count Commands
# The hardware stores a separate threshold for each channel, which is somewhat
# unintuitive for the user, as only one can actually be made use of at a time.
# So, we just write the threshold value to all channels when it is changed.
record(dfanout,"$(INSTR)$(NAME):THRESHOLD-F")
{
field(OMSL, "supervisory")
field(SELM, "All")
field(OUTA, "$(INSTR)$(NAME):THRESHOLD-F1 PP")
field(OUTB, "$(INSTR)$(NAME):THRESHOLD-F2 PP")
}
record(dfanout,"$(INSTR)$(NAME):THRESHOLD-F1")
{
field(OMSL, "supervisory")
field(SELM, "All")
field(OUTA, "$(INSTR)$(NAME):THRESH1 PP")
field(OUTB, "$(INSTR)$(NAME):THRESH2 PP")
field(OUTC, "$(INSTR)$(NAME):THRESH3 PP")
field(OUTD, "$(INSTR)$(NAME):THRESH4 PP")
field(OUTE, "$(INSTR)$(NAME):THRESH5 PP")
field(OUTF, "$(INSTR)$(NAME):THRESH6 PP")
field(OUTG, "$(INSTR)$(NAME):THRESH7 PP")
field(OUTH, "$(INSTR)$(NAME):THRESH8 PP")
}
record(dfanout,"$(INSTR)$(NAME):THRESHOLD-F2")
{
field(OMSL, "supervisory")
field(SELM, "All")
field(OUTA, "$(INSTR)$(NAME):THRESH9 PP")
field(OUTB, "$(INSTR)$(NAME):THRESH10 PP")
}
################################################################################
# Read all monitors values

40
db/daq_2nd_gen_test.db Normal file
View File

@ -0,0 +1,40 @@
# EPICS Database for streamdevice support for testing functionality specific to
# the 2nd Generation Systems
#
# Macros
# INSTR - Prefix
# NAME - the device name, e.g. DAQV2
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to DAQ
################################################################################
# Testing Commands
# These won't match the values on the machine after a full restart But I chose
# not to force their intialisation as they are only important for testing
record(bo, "$(INSTR)$(NAME):TESTGEN")
{
field(DESC, "Turn on/off Testgen Signal")
field(DTYP, "stream")
field(OUT, "@$(PROTO) switchTestgenOnOff($(INSTR)$(NAME):) $(ASYN_PORT)")
field(VAL, 0)
field(ZNAM, "OFF")
field(ONAM, "ON")
}
record(longout, "$(INSTR)$(NAME):TESTGEN-LOWRATE")
{
field(DESC, "Set Minimum Testgen Rate")
field(DTYP, "stream")
field(OUT, "@$(PROTO) setTestSignal($(INSTR)$(NAME):) $(ASYN_PORT)")
field(VAL, 1000)
}
record(longout, "$(INSTR)$(NAME):TESTGEN-HIGHRATE")
{
field(DESC, "Set Maximum Testgen Rate")
field(DTYP, "stream")
field(OUT, "@$(PROTO) setTestSignal($(INSTR)$(NAME):) $(ASYN_PORT)")
field(VAL, 1000)
}

45
db/daq_4ch.db Normal file
View File

@ -0,0 +1,45 @@
# EPICS Database for streamdevice support 1st gen systems with 4 channels
#
# Macros
# INSTR - Prefix
# NAME - the device name, e.g. EL737
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to DAQ
################################################################################
# Status Variables
record(longout, "$(INSTR)$(NAME):MONITOR-CHANNEL")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(VAL, 1)
field(DRVL, "1") # Smallest Monitor Channel
field(DRVH, "1") # Largest Monitor Channel
field(DISP, 1)
}
record(longin, "$(INSTR)$(NAME):MONITOR-CHANNEL_RBV")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(VAL, 1)
field(DISP, 1)
}
################################################################################
# Count Commands
# The hardware stores a separate threshold for each channel, which is somewhat
# unintuitive for the user, as only one can actually be made use of at a time.
# So, we just write the threshold value to all channels when it is changed.
record(dfanout,"$(INSTR)$(NAME):THRESHOLD-F")
{
field(OMSL, "supervisory")
field(SELM, "All")
field(OUTA, "$(INSTR)$(NAME):THRESH1 PP")
field(OUTB, "$(INSTR)$(NAME):THRESH2 PP")
field(OUTC, "$(INSTR)$(NAME):THRESH3 PP")
field(OUTD, "$(INSTR)$(NAME):THRESH4 PP")
}
################################################################################
# Read all monitors values

49
db/daq_8ch.db Normal file
View File

@ -0,0 +1,49 @@
# EPICS Database for streamdevice support 1st gen systems with 8 channels
#
# Macros
# INSTR - Prefix
# NAME - the device name, e.g. EL737
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to DAQ
################################################################################
# Status Variables
record(longout, "$(INSTR)$(NAME):MONITOR-CHANNEL")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(VAL, 1)
field(DRVL, "1") # Smallest Monitor Channel
field(DRVH, "1") # Largest Monitor Channel
field(DISP, 1)
}
record(longin, "$(INSTR)$(NAME):MONITOR-CHANNEL_RBV")
{
field(DESC, "PRESET-COUNT Monitors this channel")
field(VAL, 1)
field(DISP, 1)
}
################################################################################
# Count Commands
# The hardware stores a separate threshold for each channel, which is somewhat
# unintuitive for the user, as only one can actually be made use of at a time.
# So, we just write the threshold value to all channels when it is changed.
record(dfanout,"$(INSTR)$(NAME):THRESHOLD-F")
{
field(OMSL, "supervisory")
field(SELM, "All")
field(OUTA, "$(INSTR)$(NAME):THRESH1 PP")
field(OUTB, "$(INSTR)$(NAME):THRESH2 PP")
field(OUTC, "$(INSTR)$(NAME):THRESH3 PP")
field(OUTD, "$(INSTR)$(NAME):THRESH4 PP")
field(OUTE, "$(INSTR)$(NAME):THRESH5 PP")
field(OUTF, "$(INSTR)$(NAME):THRESH6 PP")
field(OUTG, "$(INSTR)$(NAME):THRESH7 PP")
field(OUTH, "$(INSTR)$(NAME):THRESH8 PP")
}
################################################################################
# Read all monitors values

243
db/daq_common.db Normal file
View File

@ -0,0 +1,243 @@
# EPICS Database for streamdevice support with SinqDAQ Systems
#
# Macros
# INSTR - Prefix
# NAME - the device name, e.g. EL737
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to EL737
# Send initial initialisation commands
record(bo, "$(INSTR)$(NAME):INIT-CONF")
{
field(DESC, "Initialises the DAQ")
field(OUT, "@$(PROTO) initialise($(INSTR)$(NAME):) $(ASYN_PORT)")
field(PINI, "YES") # Run at init
field(DTYP, "stream")
}
record(longout, "$(INSTR)$(NAME):FULL-RESET")
{
field(DESC, "Reset the DAQ")
field(OUT, "@$(PROTO) fullReset($(INSTR)$(NAME):) $(ASYN_PORT)")
field(DTYP, "stream")
}
################################################################################
# Status Variables
record(stringin, "$(INSTR)$(NAME):MsgTxt")
{
field(DESC, "Unexpected received response")
field(DTYP, "devDAQStringError")
field(FLNK, "$(INSTR)$(NAME):INVALID-CONFIG")
}
# We want to recognise the invalid config error message, so that we can rerun
# the init if it occurs. This should only happen after turning the DAQ off and
# on again or running a full reset
record(scalcout, "$(INSTR)$(NAME):INVALID-CONFIG")
{
field(DESC, "Has the DAQ been configured?")
field(CALC, "AA[0,2] == '?OF'")
field(INAA, "$(INSTR)$(NAME):MsgTxt")
field(FLNK, "$(INSTR)$(NAME):REINIT-CONF")
}
record(seq, "$(INSTR)$(NAME):REINIT-CONF")
{
field(LNK1, "$(INSTR)$(NAME):INIT-CONF PP")
field(DO1, 1)
field(SELM, "Specified")
field(SELL, "$(INSTR)$(NAME):INVALID-CONFIG.VAL")
}
# Important! The "$(INSTR)$(NAME):READALL" isn't configure with a SCAN. Instead,
# it must always be triggered after the value of $(INSTR)$(NAME):RAW-STATUS is
# updated, so that it can't be the case that the status changes back from
# counting to idle, without having updated the time and count values.
record(longin, "$(INSTR)$(NAME):RAW-STATUS")
{
field(DESC, "Raw returned status value")
field(DTYP, "stream")
field(SCAN, ".5 second")
field(INP, "@$(PROTO) readStatus($(INSTR)$(NAME):) $(ASYN_PORT)")
field(FLNK, "$(INSTR)$(NAME):READALL")
}
record(calc, "$(INSTR)$(NAME):MAP-STATUS")
{
field(DESC, "Maps Raw Status to State")
field(INPA, "$(INSTR)$(NAME):RAW-STATUS NPP")
field(INPB, "$(INSTR)$(NAME):INVALID-CONFIG NPP")
field(INPC, "$(INSTR)$(NAME):RAW-STATUS.UDF NPP") # should also be invalid if can't read the status
field(CALC, "(B=1||C==1)?4:A=0?0:(A=1||A=2)?1:(A=5||A=6)?2:(A=9||A=13||A=10||A=14)?3:4")
field(FLNK, "$(INSTR)$(NAME):STATUS")
}
record(mbbi, "$(INSTR)$(NAME):STATUS")
{
field(DESC, "DAQ Status")
field(INP, "$(INSTR)$(NAME):MAP-STATUS NPP")
field(ZRVL, "0")
field(ZRST, "Idle")
field(ONVL, "1")
field(ONST, "Counting")
field(TWVL, "2")
field(TWST, "Low rate")
field(THVL, "3")
field(THST, "Paused")
# 4 should never happen, if it does it means the DAQ reports undocumented statusbits
field(FRVL, "4")
field(FRST, "INVALID")
}
record(longin, "$(INSTR)$(NAME):CHANNELS")
{
field(DESC, "Total Supported Channels")
field(VAL, $(CHANNELS))
field(DISP, 1)
}
# Trigger a change in status as clearing
record(bo, "$(INSTR)$(NAME):ETT")
{
field(DESC, "Trigger Clearing Status")
field(VAL, 1)
field(OUT, "$(INSTR)$(NAME):ETS PP")
}
# Trigger a change in status as value returned to 0
record(seq, "$(INSTR)$(NAME):ETO")
{
field(DESC, "Trigger Returned to 0 Status")
field(LNK0, "$(INSTR)$(NAME):ETS PP")
field(DO0, 0)
field(SELM, "Specified")
field(SELL, "$(INSTR)$(NAME):ELAPSED-TIME.VAL")
}
# Current Status of Channel, i.e. is it ready to count?
record(bi, "$(INSTR)$(NAME):ETS")
{
field(DESC, "Channel Status")
field(VAL, 0)
field(ZNAM, "OK")
field(ONAM, "CLEARING")
}
################################################################################
# Count Commands
record(ao,"$(INSTR)$(NAME):PRESET-COUNT")
{
field(DESC, "Count until preset reached")
field(DTYP, "stream")
field(OUT, "@$(PROTO) startWithCountPreset$(CHANNELS)($(INSTR)$(NAME):) $(ASYN_PORT)")
field(VAL, 0)
field(PREC, 2)
field(FLNK, "$(INSTR)$(NAME):RAW-STATUS")
}
record(ao,"$(INSTR)$(NAME):PRESET-TIME")
{
field(DESC, "Count for specified time")
field(DTYP, "stream")
field(OUT, "@$(PROTO) startWithTimePreset$(CHANNELS)($(INSTR)$(NAME):) $(ASYN_PORT)")
field(VAL, 0)
field(PREC, 2)
field(EGU, "seconds")
field(FLNK, "$(INSTR)$(NAME):RAW-STATUS")
}
record(bo,"$(INSTR)$(NAME):PAUSE")
{
field(DESC, "Pause the current count")
field(DTYP, "stream")
field(OUT, "@$(PROTO) pauseCount($(INSTR)$(NAME):) $(ASYN_PORT)")
field(VAL, "0")
field(FLNK, "$(INSTR)$(NAME):RAW-STATUS")
}
record(bo,"$(INSTR)$(NAME):CONTINUE")
{
field(DESC, "Continue with a count that was paused")
field(DTYP, "stream")
field(OUT, "@$(PROTO) continueCount($(INSTR)$(NAME):) $(ASYN_PORT)")
field(VAL, "0")
field(FLNK, "$(INSTR)$(NAME):RAW-STATUS")
}
record(longout, "$(INSTR)$(NAME):STOP")
{
field(DESC, "Stop the current counting operation")
field(DTYP, "stream")
field(OUT, "@$(PROTO) stopCount($(INSTR)$(NAME):) $(ASYN_PORT)")
field(FLNK, "$(INSTR)$(NAME):RAW-STATUS")
}
record(ao,"$(INSTR)$(NAME):THRESHOLD")
{
field(DESC, "Minimum rate for counting to proceed")
field(VAL, "1") # Default Rate
# Could perhaps still be improved.
# It seems to only accept whole counts?
field(DRVL, "1") # Minimum Rate
field(DRVH, "100000") # Maximum Rate
field(OMSL, "supervisory")
field(OROC, "0")
field(OUT, "$(INSTR)$(NAME):THRESHOLD-F PP")
}
record(ai,"$(INSTR)$(NAME):THRESHOLD_RBV")
{
field(DESC, "Minimum rate for counting to proceed")
field(INP, "@$(PROTO) readMinRate($(INSTR)$(NAME):) $(ASYN_PORT)")
field(DTYP, "stream")
field(SCAN, "1 second")
field(EGU, "cts/sec")
}
record(longout,"$(INSTR)$(NAME):THRESHOLD-MONITOR")
{
field(DESC, "Channel monitored for minimum rate")
field(VAL, "1") # Monitor
field(DRVL, "0") # Smallest Threshold Channel (0 is off)
field(DRVH, "$(CHANNELS)") # Largest Threshold Channel
field(OUT, "@$(PROTO) setRateMonitor($(INSTR)$(NAME):) $(ASYN_PORT)")
field(DTYP, "stream")
}
record(longin,"$(INSTR)$(NAME):THRESHOLD-MONITOR_RBV")
{
field(DESC, "Channel monitored for minimum rate")
field(INP, "@$(PROTO) readRateMonitor($(INSTR)$(NAME):) $(ASYN_PORT)")
field(DTYP, "stream")
field(SCAN, "1 second")
field(EGU, "CH")
}
record(longout, "$(INSTR)$(NAME):CT")
{
field(DESC, "Clear the timer")
field(DTYP, "stream")
field(OUT, "@$(PROTO) clearTimer($(INSTR)$(NAME):) $(ASYN_PORT)")
field(FLNK, "$(INSTR)$(NAME):ETT")
}
################################################################################
# Read all monitors values
record(ai, "$(INSTR)$(NAME):READALL")
{
field(DESC, "Reads monitors and elapsed time")
field(INP, "@$(PROTO) readAll$(CHANNELS)($(INSTR)$(NAME):) $(ASYN_PORT)")
field(DTYP, "stream")
field(FLNK, "$(INSTR)$(NAME):MAP-STATUS")
}
record(ai,"$(INSTR)$(NAME):ELAPSED-TIME")
{
field(DESC, "DAQ Measured Time")
field(EGU, "sec")
field(FLNK, "$(INSTR)$(NAME):ETO")
}

27
db/daq_simcontrol.db Normal file
View File

@ -0,0 +1,27 @@
# Sinq DAQ EPICS Database for StreamDevice Communication with Simulation
# Macros
#
# INSTR - Prefix
# NAME - just a name, e.g. EL737
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to EL737
################################################################################
record(bo, "$(INSTR)$(NAME):G1")
{
field(DESC, "Set Gate 1 Low/High")
field(ZNAM, "Low")
field(ONAM, "High")
field(DTYP, "stream")
field(OUT, "@$(PROTO) setGate($(INSTR)$(NAME):, 1) $(ASYN_PORT)")
}
record(bo, "$(INSTR)$(NAME):G2")
{
field(DESC, "Set Gate 2 Low/High")
field(ZNAM, "Low")
field(ONAM, "High")
field(DTYP, "stream")
field(OUT, "@$(PROTO) setGate($(INSTR)$(NAME):, 2) $(ASYN_PORT)")
}

102
db/gating_channels.db Normal file
View File

@ -0,0 +1,102 @@
# EPICS Database for streamdevice specific to gating channels
#
# Macros
# INSTR - Prefix
# NAME - the device name, e.g. EL737
# PROTO - Stream device protocol file
# ASYN_PORT - Low level Asyn IP Port to DAQ
# CHANNEL - the number associated with the measurment channel
################################################################################
# Records for configuring gating settings
record(mbbo, "$(INSTR)$(NAME):GATE-$(CHANNEL)")
{
field(DESC, "Sets the current gate state")
field(DTYP, "Soft Channel")
field(OUT, "$(INSTR)$(NAME):GATE-$(CHANNEL)-SET PP")
field(ZRST, "Disabled")
field(ONST, "Trigger Low")
field(TWST, "Trigger High")
}
record(dfanout, "$(INSTR)$(NAME):GATE-$(CHANNEL)-SET")
{
field(OUTA, "$(INSTR)$(NAME):GATE-$(CHANNEL)-SEL.SELN PP")
field(OUTB, "$(INSTR)$(NAME):GATE-$(CHANNEL)-SEL2.SELN PP")
}
record(seq, "$(INSTR)$(NAME):GATE-$(CHANNEL)-SEL")
{
field(SELM, "Specified")
field(DO0, 0)
field(LNK0, "$(INSTR)$(NAME):GATE-$(CHANNEL)-ENABLE PP")
field(DO1, 1)
field(LNK1, "$(INSTR)$(NAME):GATE-$(CHANNEL)-ENABLE PP")
field(DO2, 1)
field(LNK2, "$(INSTR)$(NAME):GATE-$(CHANNEL)-ENABLE PP")
}
record(bo, "$(INSTR)$(NAME):GATE-$(CHANNEL)-ENABLE")
{
field(DESC, "Enable Gating Channel")
field(ZNAM, "Disabled")
field(ONAM, "Enabled")
field(DTYP, "stream")
field(OUT, "@$(PROTO) setGateStatus($(INSTR)$(NAME):, $(CHANNEL)) $(ASYN_PORT)")
}
record(seq, "$(INSTR)$(NAME):GATE-$(CHANNEL)-SEL2")
{
field(SELM, "Specified")
field(DO1, 0)
field(LNK1, "$(INSTR)$(NAME):GATE-$(CHANNEL)-TRIG PP")
field(DO2, 1)
field(LNK2, "$(INSTR)$(NAME):GATE-$(CHANNEL)-TRIG PP")
}
record(bo, "$(INSTR)$(NAME):GATE-$(CHANNEL)-TRIG")
{
field(DESC, "Set Count when first Gate high/low")
field(ZNAM, "Low")
field(ONAM, "High")
field(DTYP, "stream")
field(OUT, "@$(PROTO) setGateStatus($(INSTR)$(NAME):, $(CHANNEL)) $(ASYN_PORT)")
}
# Records for reading configured gating settings
record(mbbi, "$(INSTR)$(NAME):GATE-$(CHANNEL)_RBV")
{
field(DESC, "The current gate state")
field(ZRST, "Disabled")
field(ONST, "Trigger Low")
field(TWST, "Trigger High")
}
record(calcout, "$(INSTR)$(NAME):GATE-$(CHANNEL)-SEL_RBV")
{
field(INPA, "$(INSTR)$(NAME):GATE-$(CHANNEL)-ENABLE_RBV NPP")
field(INPB, "$(INSTR)$(NAME):GATE-$(CHANNEL)-TRIG_RBV NPP")
field(CALC, "A=0?0:B=0?1:2")
field(OOPT, "On Change")
field(OUT, "$(INSTR)$(NAME):GATE-$(CHANNEL)_RBV PP")
field(FLNK, "$(INSTR)$(NAME):GATE-$(CHANNEL)_RBV")
}
record(bi, "$(INSTR)$(NAME):GATE-$(CHANNEL)-ENABLE_RBV")
{
field(DESC, "Is Gating Channel Enabled")
field(ZNAM, "Disabled")
field(ONAM, "Enabled")
field(DTYP, "stream")
field(INP, "@$(PROTO) getGateStatus($(INSTR)$(NAME):, $(CHANNEL)) $(ASYN_PORT)")
field(SCAN, "2 second")
field(FLNK, "$(INSTR)$(NAME):GATE-$(CHANNEL)-SEL_RBV")
}
record(bi, "$(INSTR)$(NAME):GATE-$(CHANNEL)-TRIG_RBV")
{
field(DESC, "Count when first Gate high/low")
field(ZNAM, "Low")
field(ONAM, "High")
}

View File

@ -1,21 +0,0 @@
require asyn
require stream
epicsEnvSet("$(NAME)_CNTBOX_HOST", "$(CNTBOX_IP):$(CNTBOX_PORT)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) epicsEnvSet("$(NAME)_CNTBOX_HOST", "127.0.0.1:$(CNTBOX_PORT)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) system "$(counterbox_DIR)counterbox_sim.py $(CNTBOX_PORT) 4 &"
# starting the python socket seems to take a while
# and need misc to use built in sleep command
$(SET_SIM_MODE=#) $(SET_SIM_MODE) system 'sleep 3'
epicsEnvSet("PROTO", "$(counterbox_DB)counterbox.proto")
drvAsynIPPortConfigure("ASYN_$(NAME)", "$($(NAME)_CNTBOX_HOST)", 0, 0, 0)
dbLoadRecords("$(counterbox_DB)counterbox_common.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNELS=4")
dbLoadRecords("$(counterbox_DB)counterbox_4ch.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME)")
# Could also use substitions instead.
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=1")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=2")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=3")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=4")

View File

@ -1,25 +0,0 @@
require asyn
require stream
epicsEnvSet("$(NAME)_CNTBOX_HOST", "$(CNTBOX_IP):$(CNTBOX_PORT)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) epicsEnvSet("$(NAME)_CNTBOX_HOST", "127.0.0.1:$(CNTBOX_PORT)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) system "$(counterbox_DIR)counterbox_sim.py $(CNTBOX_PORT) 10 &"
# starting the python socket seems to take a while
# and need misc to use built in sleep command
$(SET_SIM_MODE=#) $(SET_SIM_MODE) system 'sleep 3'
epicsEnvSet("PROTO", "$(counterbox_DB)counterbox.proto")
drvAsynIPPortConfigure("ASYN_$(NAME)", "$($(NAME)_CNTBOX_HOST)", 0, 0, 0)
dbLoadRecords("$(counterbox_DB)counterbox_common.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNELS=8")
dbLoadRecords("$(counterbox_DB)counterbox_8ch.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME)")
# Could also use substitions instead.
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=1")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=2")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=3")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=4")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=5")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=6")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=7")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=8")

View File

@ -1,29 +0,0 @@
require asyn
require stream
epicsEnvSet("$(NAME)_CNTBOX_HOST", "$(CNTBOX_IP):$(CNTBOX_PORT=2000)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) epicsEnvSet("$(NAME)_CNTBOX_HOST", "127.0.0.1:$(CNTBOX_PORT=2000)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) system "$(counterbox_DIR)counterbox_sim.py $(CNTBOX_PORT=2000) 10 &"
# starting the python socket seems to take a while
# and need misc to use built in sleep command
$(SET_SIM_MODE=#) $(SET_SIM_MODE) system 'sleep 5'
epicsEnvSet("PROTO", "$(counterbox_DB)counterbox.proto")
drvAsynIPPortConfigure("ASYN_$(NAME)", "$($(NAME)_CNTBOX_HOST)", 0, 0, 0)
dbLoadRecords("$(counterbox_DB)counterbox_common.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNELS=10")
dbLoadRecords("$(counterbox_DB)counterbox_v2.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME)")
# Could also use substitions instead.
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=1")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=2")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=3")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=4")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=5")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=6")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=7")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=8")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=9")
dbLoadRecords("$(counterbox_DB)channels.db", "P=$(PREFIX), NAME=$(NAME), CHANNEL=10")
$(LOAD_TEST_PVS=#) $(LOAD_TEST_PVS) dbLoadRecords("$(counterbox_DB)counterbox_v2_test.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME)")

35
scripts/daq_2nd_gen.cmd Normal file
View File

@ -0,0 +1,35 @@
require asyn
require stream
epicsEnvSet("$(NAME)_DAQ_HOST", "$(DAQ_IP):$(DAQ_PORT=2000)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) require misc
$(SET_SIM_MODE=#) $(SET_SIM_MODE) epicsEnvSet("$(NAME)_DAQ_HOST", "127.0.0.1:$(DAQ_PORT=2000)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) system "$(sinqDAQ_DIR)daq_sim.py $(DAQ_PORT=2000) 10 &"
# starting the python socket seems to take a while
# and need misc to use built in sleep command
$(SET_SIM_MODE=#) $(SET_SIM_MODE) sleep 3
epicsEnvSet("PROTO", "$(sinqDAQ_DB)daq.proto")
drvAsynIPPortConfigure("ASYN_$(NAME)", "$($(NAME)_DAQ_HOST)", 0, 0, 0)
dbLoadRecords("$(sinqDAQ_DB)daq_common.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNELS=10")
dbLoadRecords("$(sinqDAQ_DB)daq_2nd_gen.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNELS=10")
# Could also use substitions instead.
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=1")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=2")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=3")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=4")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=5")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=6")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=7")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=8")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=9")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=10")
dbLoadRecords("$(sinqDAQ_DB)gating_channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=1")
dbLoadRecords("$(sinqDAQ_DB)gating_channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=2")
$(LOAD_TEST_PVS=#) $(LOAD_TEST_PVS) dbLoadRecords("$(sinqDAQ_DB)daq_2nd_gen_test.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) dbLoadRecords("$(sinqDAQ_DB)daq_simcontrol.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME)")

22
scripts/daq_4ch.cmd Normal file
View File

@ -0,0 +1,22 @@
require asyn
require stream
epicsEnvSet("$(NAME)_DAQ_HOST", "$(DAQ_IP):$(DAQ_PORT)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) require misc
$(SET_SIM_MODE=#) $(SET_SIM_MODE) epicsEnvSet("$(NAME)_DAQ_HOST", "127.0.0.1:$(DAQ_PORT)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) system "$(sinqDAQ_DIR)daq_sim.py $(DAQ_PORT) 4 &"
# starting the python socket seems to take a while
# and need misc to use built in sleep command
$(SET_SIM_MODE=#) $(SET_SIM_MODE) sleep 3
epicsEnvSet("PROTO", "$(sinqDAQ_DB)daq.proto")
drvAsynIPPortConfigure("ASYN_$(NAME)", "$($(NAME)_DAQ_HOST)", 0, 0, 0)
dbLoadRecords("$(sinqDAQ_DB)daq_common.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNELS=4")
dbLoadRecords("$(sinqDAQ_DB)daq_4ch.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME)")
# Could also use substitions instead.
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=1")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=2")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=3")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=4")

26
scripts/daq_8ch.cmd Normal file
View File

@ -0,0 +1,26 @@
require asyn
require stream
epicsEnvSet("$(NAME)_DAQ_HOST", "$(DAQ_IP):$(DAQ_PORT)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) require misc
$(SET_SIM_MODE=#) $(SET_SIM_MODE) epicsEnvSet("$(NAME)_DAQ_HOST", "127.0.0.1:$(DAQ_PORT)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) system "$(sinqDAQ_DIR)daq_sim.py $(DAQ_PORT) 8 &"
# starting the python socket seems to take a while
# and need misc to use built in sleep command
$(SET_SIM_MODE=#) $(SET_SIM_MODE) sleep 3
epicsEnvSet("PROTO", "$(sinqDAQ_DB)daq.proto")
drvAsynIPPortConfigure("ASYN_$(NAME)", "$($(NAME)_DAQ_HOST)", 0, 0, 0)
dbLoadRecords("$(sinqDAQ_DB)daq_common.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNELS=8")
dbLoadRecords("$(sinqDAQ_DB)daq_8ch.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME)")
# Could also use substitions instead.
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=1")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=2")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=3")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=4")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=5")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=6")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=7")
dbLoadRecords("$(sinqDAQ_DB)channels.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME), CHANNEL=8")

View File

@ -1,147 +0,0 @@
#!/usr/bin/env python3
import re
import socket
import sys
import time
from random import randrange
HOST = "127.0.0.1" # Localhost
PORT = int(sys.argv[1]) # Port to listen on
TOTAL_CH = int(sys.argv[2]) # Number of Channels
class CounterBox:
def __init__(self, total_channels):
self.total_channels = total_channels
self.counts = [0] * self.total_channels
self.status = 0
self.countmode = 'time'
self.presettime = 0
self.presetcount = 0
self.starttime = 0
self.elapsed = 0
self.monitor = 0
def resetCounts(self):
self.counts = [0] * self.total_channels
self.starttime = time.time()
self.elapsed = 0
def getStatus(self):
return self.status
def getCounts(self):
return self.counts
def getMonitorCount(self):
return self.counts[self.monitor]
def updateCounts(self):
for i in range(self.total_channels):
self.counts[i] += randrange(5)
def getRunTime(self):
elapsed = round(time.time() - self.starttime, 3)
if self.countmode == 'time':
if elapsed < self.presettime:
self.updateCounts()
self.elapsed = elapsed
else:
self.elapsed = self.presettime
self.status = 0
elif self.countmode == 'count':
if self.getMonitorCount() < self.presetcount:
self.updateCounts()
self.elapsed = elapsed
if self.getMonitorCount() >= self.presetcount:
self.counts[self.monitor] = self.presetcount
self.status = 0
else:
raise Exception("Invalid State")
return self.elapsed
def startTimePreset(self, presettime):
self.countmode = 'time'
self.status = 1
self.presettime = round(presettime, 3)
self.resetCounts()
def startCountPreset(self, presetcount):
self.countmode = 'count'
self.status = 1
self.presetcount = presetcount
self.resetCounts()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
def send(data: str):
if data:
return conn.sendall(f'{data}\r'.encode())
else:
return conn.sendall(b'\r')
def receive():
data = conn.recv(1024)
if data:
# also removes terminator
return data.decode('ascii').rstrip()
else:
return ''
counterbox = CounterBox(TOTAL_CH)
while True:
data = receive()
if not data: # Empty implies client disconnected
break # Close Server
try:
if data == 'RMT 1':
send('')
elif data == 'ECHO 2':
send('Counterbox') # Sends some sort of info command
elif data == 'RA':
send(
' '.join(map(str,
[counterbox.getRunTime()] + \
counterbox.getCounts()
))
)
elif data == 'RS':
send(str(counterbox.getStatus()))
elif re.fullmatch(r'TP (\d+(\.\d+)?)', data):
presettime = float(re.fullmatch(r'TP (\d+(\.\d+)?)', data).group(1))
counterbox.startTimePreset(presettime)
send('')
elif re.fullmatch(r'MP (\d+)', data):
counts = int(re.fullmatch(r'MP (\d+)', data).group(1))
counterbox.startCountPreset(counts)
send('')
else:
send('?2') # Bad command
except:
send('?OV')

331
sim/daq_sim.py Normal file
View File

@ -0,0 +1,331 @@
#!/usr/bin/env python3
import os
import re
import socket
import sys
import time
from random import randrange
HOST = "127.0.0.1" # Localhost
PORT = int(sys.argv[1]) # Port to listen on
TOTAL_CH = int(sys.argv[2]) # Number of Channels
LOG2FILE = False if len(sys.argv) < 4 else bool(int(sys.argv[3]))
import logging
logger = logging.getLogger('daq')
if LOG2FILE:
logging.basicConfig(filename=os.path.join(os.getcwd(), 'daq_sim.log'), level=logging.INFO)
class DAQ:
def __init__(self, total_channels):
self.total_channels = total_channels
self.counts = [0] * self.total_channels
self.rates = [randrange(5) for i in range(self.total_channels)]
self.status = 0
self.countmode = 'time'
self.presettime = 0
self.presetcount = 0
self.starttime = 0
self.elapsed = 0
self.monitor = 0
self.minratechannel = 0
self.minrates = [2] * self.total_channels
self.gate_config = [
#(enabled, count high/low),
( False, True),
( False, True),
]
self.gate = [
# high/low
False,
False,
]
def clearCount(self, counter):
self.counts[counter-1] = 0
def clearCounts(self):
self.counts = [0] * self.total_channels
def clearTime(self):
self.elapsed = 0
self.starttime = time.time()
def getStatus(self):
return self.status
def getCount(self, channel):
return self.counts[channel - 1]
def getCounts(self):
# The sinqtest daq returns a maximum of 8
return self.counts[0:min(len(self.counts), 8)]
def getMonitorCount(self):
return self.counts[self.monitor]
def updateRates(self):
for i in range(self.total_channels):
self.rates[i] = randrange(5)
def updateCounts(self):
for i in range(self.total_channels):
self.counts[i] += self.rates[i]
def getRunTime(self):
elapsed = round(time.time() - self.starttime, 3)
self.updateRates()
# If gating and a low rate threshold are enabled, then the threshold
# seems to have precedence and sets the status to 5.
# If we are waiting on the gate, then the status is just 1 as normal
# after having started a count.
if self.countmode == 'time':
if elapsed < self.presettime:
if self.minratechannel >= 0 and self.rates[self.minratechannel] < self.minrates[self.minratechannel]:
# adjust the starttime, so that it is as if this polling period didn't happen
self.starttime += elapsed - self.elapsed
self.status = 5
return self.elapsed
self.status = 1
for i in range(len(self.gate)):
# If the gating is enabled and the signal is in the active position
if self.gate_config[i][0] and self.gate_config[i][1] != self.gate[i]:
self.starttime += elapsed - self.elapsed
return self.elapsed
self.updateCounts()
self.elapsed = elapsed
else:
self.elapsed = self.presettime if self.presettime > 0 else self.elapsed
self.status = 0
elif self.countmode == 'count':
if self.getMonitorCount() < self.presetcount:
if self.minratechannel >= 0 and self.rates[self.minratechannel] < self.minrates[self.minratechannel]:
# adjust the starttime, so that it is as if this polling period didn't happen
self.starttime += elapsed - self.elapsed
self.status = 5
return self.elapsed
self.status = 1
for i in range(len(self.gate)):
# If the gating is enabled and the signal is in the active position
if self.gate_config[i][0] and self.gate_config[i][1] != self.gate[i]:
self.starttime += elapsed - self.elapsed
return self.elapsed
self.updateCounts()
self.elapsed = elapsed
if self.getMonitorCount() >= self.presetcount:
self.counts[self.monitor] = self.presetcount if self.presetcount > 0 else self.counts[self.monitor]
self.status = 0
else:
raise Exception("Invalid State")
return self.elapsed
def stop(self):
self.getRunTime()
self.status = 0
self.presettime = 0
self.presetcount = 0
def startTimePreset(self, presettime):
self.countmode = 'time'
self.status = 1
self.presettime = round(presettime, 3)
self.clearTime()
self.clearCounts()
def startCountPreset(self, presetcount):
self.countmode = 'count'
self.status = 1
self.presetcount = presetcount
self.clearTime()
self.clearCounts()
def setMonitorChannel(self, channel):
self.monitor = channel - 1
def getMonitorChannel(self):
return self.monitor + 1
def getRate(self, channel):
return float(self.rates[channel - 1])
def getMinRateChannel(self):
return self.minratechannel + 1
def setMinRateChannel(self, channel):
self.minratechannel = channel - 1
def getMinRate(self, channel):
return self.minrates[channel - 1]
def setMinRate(self, channel, rate):
self.minrates[channel - 1] = rate
def getGateStatus(self, channel):
return self.gate_config[channel - 1]
def setGateStatus(self, channel, enable, highlow):
self.gate_config[channel - 1] = (enable, highlow)
def setGate(self, channel, highlow):
self.gate[channel - 1] = highlow
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
def send(data: str):
if data:
logger.info(f'SENDING: "{data}"')
return conn.sendall(f'{data}\r'.encode())
else:
logger.info(f'SENDING: ""')
return conn.sendall(b'\r')
def receive():
data = conn.recv(1024)
if data:
# also removes terminator
received = data.decode('ascii').rstrip()
else:
received = ''
logger.info(f'RECEIVED: "{received}"')
return received
daq = DAQ(TOTAL_CH)
while True:
data = receive()
if not data: # Empty implies client disconnected
break # Close Server
try:
if data == 'RMT 1':
send('')
elif data == 'ECHO 2':
send('DAQ System Version Simulation') # Sends some sort of info command
elif data == 'RA':
send(
' '.join(map(str,
[daq.getRunTime()] + \
daq.getCounts()
))
)
elif re.fullmatch(r'RC (\d+)', data):
channel = int(re.fullmatch(r'RC (\d+)', data).group(1))
count = daq.getCount(channel)
send(f'{count}')
elif data == 'RS':
send(str(daq.getStatus()))
elif data == 'S':
daq.stop()
send('')
elif data == 'CT':
daq.clearTime()
send('')
elif re.fullmatch(r'CC (\d+)', data):
counter = int(re.fullmatch(r'CC (\d+)', data).group(1))
daq.clearCount(counter)
send('')
elif re.fullmatch(r'TP (\d+(\.\d+)?)', data):
presettime = float(re.fullmatch(r'TP (\d+(\.\d+)?)', data).group(1))
daq.startTimePreset(presettime)
send('')
elif re.fullmatch(r'MP (\d+)', data):
counts = int(re.fullmatch(r'MP (\d+)', data).group(1))
daq.startCountPreset(counts)
send('')
elif data == 'PC':
send(str(daq.getMonitorChannel()))
elif re.fullmatch(r'PC (\d+)', data):
channel = int(re.fullmatch(r'PC (\d+)', data).group(1))
daq.setMonitorChannel(channel)
send('')
elif data == 'DR':
send(str(daq.getMinRateChannel()))
elif re.fullmatch(r'DR (\d+)', data):
channel = int(re.fullmatch(r'DR (\d+)', data).group(1))
daq.setMinRateChannel(channel)
send('')
elif re.fullmatch(r'DL (\d+)', data):
channel = int(re.fullmatch(r'DL (\d+)', data).group(1))
send('{:.3f}'.format(daq.getMinRate(channel)))
elif re.fullmatch(r'DL (\d+) (\d+(?:.\d+)?)', data):
channel, rate = re.fullmatch(r'DL (\d+) (\d+(?:.\d+)?)', data).groups()
daq.setMinRate(int(channel), float(rate))
send('')
elif re.fullmatch(r'RR (\d+)', data):
channel = int(re.fullmatch(r'RR (\d+)', data).group(1))
send(daq.getRate(channel))
elif re.fullmatch(r'GT (\d+)', data):
channel = int(re.fullmatch(r'GT (\d+)', data).group(1))
enabled, highlow = daq.getGateStatus(channel)
send(f'{int(enabled)} {int(highlow)}')
elif re.fullmatch(r'GT (\d+) (\d+) (\d+)', data):
channel, enable, highlow = re.fullmatch(r'GT (\d+) (\d+) (\d+)', data).groups()
channel, enable, highlow = int(channel), bool(int(enable)), bool(int(highlow))
daq.setGateStatus(channel, enable, highlow)
if enable:
send(f'Gate {channel} enabled, counting when input = {"high" if highlow else "low"}')
else:
send(f'Gate {channel} disabled')
# Only for the simulation
elif re.fullmatch(r'GATE (\d+) (\d+)', data):
channel, highlow = re.fullmatch(r'GATE (\d+) (\d+)', data).groups()
channel, highlow = int(channel), bool(int(highlow))
daq.setGate(channel, highlow)
send('')
else:
send('?2') # Bad command
except Exception as e:
logger.exception('Simulation Broke')
send('?OV')

View File

@ -1,5 +0,0 @@
#---------------------------------------------
# Counterbox specific DB definitions
#---------------------------------------------
device(stringin,INST_IO,devCounterBoxStringError,"devCounterBoxStringError")

View File

@ -54,7 +54,7 @@ struct {
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_ai;
DEVSUPFUN special_linconv;
} devCounterBoxStringError = {
} devDAQStringError = {
6, NULL, NULL, NULL, NULL, (DEVSUPFUN)map_raw_failure_message, NULL};
epicsExportAddress(dset, devCounterBoxStringError);
epicsExportAddress(dset, devDAQStringError);

5
src/daq.dbd Normal file
View File

@ -0,0 +1,5 @@
#---------------------------------------------
# DAQ specific DB definitions
#---------------------------------------------
device(stringin, INST_IO, devDAQStringError, "devDAQStringError")

View File

@ -5,4 +5,4 @@ export EPICS_BASE=/usr/local/epics/base-7.0.7
PARENT_PATH="$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )"
"${PARENT_PATH}/st.cmd"
/usr/local/bin/procServ -L - -f -i ^D^C 20001 "${PARENT_PATH}/st.cmd"

View File

@ -2,13 +2,13 @@
on error break
require counterbox
require sinqDAQ
epicsEnvSet("STREAM_PROTOCOL_PATH","./db")
epicsEnvSet("PREFIX","SQ:TEST")
epicsEnvSet("INSTR","SQ:TEST:")
epicsEnvSet("NAME","CB_TEST")
epicsEnvSet("SET_SIM_MODE","") # Run Counterbox Simulation Instead of Actual Box
runScript "$(counterbox_DIR)counterbox_v2.cmd" "CNTBOX_IP=localhost, CNTBOX_PORT=2000"
epicsEnvSet("SET_SIM_MODE","") # Run Simulation Instead of Actual Interface
runScript "$(sinqDAQ_DIR)daq_2nd_gen.cmd" "DAQ_IP=127.0.0.1, DAQ_PORT=2000"
iocInit()

View File

@ -40,54 +40,54 @@ def get_piped_output(proc):
return stdqueue, errqueue
def getState(prefix, name):
result = run(['caget', f'{prefix}:{name}:STATUS'], stdout=PIPE)
def getState(instr, name):
result = run(['caget', f'{instr}{name}:STATUS'], stdout=PIPE)
state = result.stdout.decode('ascii').rstrip().split()[1]
print(f'Currently in state {state}')
return state
def getCount(prefix, name, ch):
result = run(['caget', f'{prefix}:{name}:M{ch}'], stdout=PIPE)
def getCount(instr, name, ch):
result = run(['caget', f'{instr}{name}:M{ch}'], stdout=PIPE)
count = int(result.stdout.decode('ascii').rstrip().split()[1])
print(f'M{ch} == {count}')
return count
def presetTime(prefix, name, time):
def presetTime(instr, name, time):
print(f'Starting count for {time} seconds')
run(['caput', f'{prefix}:{name}:PRESET-TIME', f'{time}'])
run(['caput', f'{instr}{name}:PRESET-TIME', f'{time}'])
def presetCount(prefix, name, count):
def presetCount(instr, name, count):
print(f'Starting count until channel 1 reaches {count}')
run(['caput', f'{prefix}:{name}:PRESET-COUNT', f'{count}'])
run(['caput', f'{instr}{name}:PRESET-COUNT', f'{count}'])
def testCanCount(prefix, name):
def testCanCount(instr, name):
# Check in Idle State
assert getState(prefix, name) == 'Idle', 'Not in valid state'
assert getState(instr, name) == 'Idle', 'Not in valid state'
# Start Time Based Count and Check that Status Changes
assert getCount(prefix, name, 1) == 0, "Erroneous nonzero starting count value"
presetTime(prefix, name, 5)
assert getCount(instr, name, 1) == 0, "Erroneous nonzero starting count value"
presetTime(instr, name, 5)
time.sleep(1)
assert getState(prefix, name) == 'Counting', 'Didn\'t start counting'
assert getState(instr, name) == 'Counting', 'Didn\'t start counting'
time.sleep(5)
assert getState(prefix, name) == 'Idle', 'Didn\'t finish counting'
assert getCount(prefix, name, 1) > 0, 'No events were counted'
assert getState(instr, name) == 'Idle', 'Didn\'t finish counting'
assert getCount(instr, name, 1) > 0, 'No events were counted'
# Start Monitor Based Count and Check that Status Changes
presetAmount = 100
presetCount(prefix, name, presetAmount)
presetCount(instr, name, presetAmount)
time.sleep(1)
assert getState(prefix, name) == 'Counting', 'Didn\'t start counting'
assert getCount(prefix, name, 1) < presetAmount
while getState(prefix, name) != 'Idle':
assert getState(instr, name) == 'Counting', 'Didn\'t start counting'
assert getCount(instr, name, 1) < presetAmount
while getState(instr, name) != 'Idle':
time.sleep(1)
assert getCount(prefix, name, 1) == presetAmount, 'Counted events not equal to preset'
assert getCount(prefix, name, 2) > 0
assert getCount(instr, name, 1) == presetAmount, 'Counted events not equal to preset'
assert getCount(instr, name, 2) > 0
def test(prefix, name):
def test(instr, name):
# TODO pass prefix and name to script
proc = Popen([f'{os.environ["PARENT_PATH"]}/st.cmd'], stdout=PIPE, stderr=PIPE, shell=False)
# TODO pass instr and name to script
proc = Popen([f'{os.environ["PARENT_PATH"]}/ioc.sh'], stdout=PIPE, stderr=PIPE, shell=False)
try:
stdqueue, errqueue = get_piped_output(proc)
@ -99,7 +99,12 @@ def test(prefix, name):
if 'iocRun: All initialization complete' in line:
break # IOC is now running
testCanCount(prefix, name)
#time.sleep(20)
print("IOC Initialisation Complete")
print("Starting Tests")
testCanCount(instr, name)
print("Success")
proc.kill()
@ -118,7 +123,7 @@ if __name__ == '__main__':
print("Starting Test")
# Test V2
if test('SQ:TEST', 'CB_TEST'):
if test('SQ:TEST:', 'CB_TEST'):
exit(0)
else:
exit(1)