24 Commits
0.0.1 ... 0.2.1

Author SHA1 Message Date
2770cd80da Description too long 2025-06-17 16:19:38 +02:00
422df50bbd Merge pull request 'SINQSW-107-configuring-with-nicos' (#1) from SINQSW-107-configuring-with-nicos into master
Reviewed-on: #1
2025-06-17 10:15:35 +02:00
65ad13d114 adds gating stuff, writes all thresholds simultaneously, updates readme with example 2025-06-17 10:12:52 +02:00
3dd7c5a1d9 moves the gating configuration logic to epics 2025-06-16 17:38:48 +02:00
f31e7c1e23 changes to play nicer with the nicos detector interface and with caproto 2025-05-23 11:51:57 +02:00
5066cafc07 rename from counterbox to daq 2025-04-29 17:22:31 +02:00
d81105551d use the INSTR environment variable instead of PREFIX 2025-04-29 15:58:32 +02:00
d4b111ce6d Commiting state after implementing gating etc before cleaning and refactoring 2025-02-13 16:06:31 +01:00
65d6f681b9 Adds more functionality to the simulation 2025-01-10 10:14:11 +01:00
4bd648ea5d Adds PVs for reseting the channels and timer 2024-11-18 14:43:16 +01:00
3b660ca560 SINQSW-107 makes some changes to better cooperate with Nicos 2024-11-13 12:29:24 +01:00
01839b3217 Merge branch 'customci' into 'master'
Switch to Sinq CI and run tests

See merge request sinq-epics-modules/counterbox!3
2024-11-07 16:30:50 +01:00
9066e314e8 Switch to Sinq CI 2024-11-07 16:30:50 +01:00
fba7487f88 No longer have to specify the Asyn Port Name 2024-11-05 15:00:45 +01:00
044a181ed0 Merge branch 'adding-sim' into 'master'
Adding sim and test

See merge request sinq-epics-modules/counterbox!1
2024-11-05 09:29:38 +01:00
897e54901f Adding sim and test 2024-11-05 09:29:38 +01:00
29e8aa9e85 simplify status/count update logic 2024-10-24 11:53:37 +02:00
9cbe58610e bugfix: scripts hadn't been updated to correct path 2024-10-23 11:41:57 +02:00
91c059370b adds PV to find out number of supported channels 2024-10-23 09:25:41 +02:00
80bd2973be missing new db files 2024-10-23 09:16:22 +02:00
7e020130c3 Makefile bugfix and hopefully fix readme 2024-10-23 09:12:26 +02:00
ded85da283 adds streamdevice as required module 2024-10-22 15:40:17 +02:00
15b5bf8cfe Adds support for 4 channel boxes and overview in Readme 2024-10-21 08:48:56 +02:00
f49f2bccd7 no longer manually set library version 2024-10-18 17:17:07 +02:00
29 changed files with 1751 additions and 576 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
O.*
.*ignore
.iocsh_history
**/__pycache__/

View File

@ -1,42 +1,44 @@
default: default:
image: docker.psi.ch:5000/wall_e/sinqepics:latest image: docker.psi.ch:5000/sinqdev/sinqepics:latest
stages: stages:
- test - lint
- build - build
- test
cppcheck: cppcheck:
stage: test stage: lint
script: script:
- cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 src/*.cpp - cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 src/*.cpp
artifacts: artifacts:
expire_in: 1 week expire_in: 1 week
tags: tags:
- docker - sinq
formatting: formatting:
stage: test stage: lint
script: script:
- clang-format --style=file --Werror --dry-run src/*.cpp - clang-format --style=file --Werror --dry-run src/*.cpp
artifacts: artifacts:
expire_in: 1 week expire_in: 1 week
tags: tags:
- docker - sinq
# clangtidy: # clangtidy:
# stage: test # stage: lint
# script: # script:
# - curl https://docker.psi.ch:5000/v2/_catalog # - curl https://docker.psi.ch:5000/v2/_catalog
# # - dnf update -y # # - dnf update -y
# # - dnf install -y clang-tools-extra # # - dnf install -y clang-tools-extra
# # - clang-tidy sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h -checks=cppcoreguidelines-*,cert-* # # - clang-tidy sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h -checks=cppcoreguidelines-*,cert-*
# # tags: # # tags:
# # - docker # # - sinq
build_module: build_module:
stage: build stage: build
script: script:
- sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile - sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile
- echo "LIBVERSION=${CI_COMMIT_TAG:-0.0.1}" >> Makefile
- make install - make install
- cp -rT "/ioc/modules/counterbox/$(ls -U /ioc/modules/counterbox/ | head -1)" "./counterbox-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}" - cp -rT "/ioc/modules/counterbox/$(ls -U /ioc/modules/counterbox/ | head -1)" "./counterbox-${CI_COMMIT_TAG:-$CI_COMMIT_SHORT_SHA}"
artifacts: artifacts:
@ -46,4 +48,14 @@ build_module:
expire_in: 1 week expire_in: 1 week
when: always when: always
tags: tags:
- docker - sinq
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,32 +1,39 @@
# This build the sinq extensions for the PSI EPICS setup # This build the sinq extensions for the PSI EPICS setup
include /ioc/tools/driver.makefile include /ioc/tools/driver.makefile
MODULE=counterbox MODULE=sinqDAQ
BUILDCLASSES=Linux BUILDCLASSES=Linux
EPICS_VERSIONS=7.0.7 EPICS_VERSIONS=7.0.7
ARCH_FILTER=RHEL% ARCH_FILTER=RHEL%
# additional module dependencies # additional module dependencies
REQUIRED+=stream REQUIRED+=asyn
REQUIRED+=calc REQUIRED+=calc
REQUIRED+=stream
# Release version
LIBVERSION=ed-dev
# DB files to include in the release # DB files to include in the release
TEMPLATES += db/counterbox.db TEMPLATES += db/channels.db
TEMPLATES += db/counterbox.proto TEMPLATES += db/gating_channels.db
TEMPLATES += db/counterbox_common.db TEMPLATES += db/daq_4ch.db
TEMPLATES += db/counterbox_v2.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 # DBD files to include in the release
DBDS += src/counterbox.dbd DBDS += src/daq.dbd
# Source files to build # Source files to build
SOURCES += src/counterbox.cpp SOURCES += src/daq.cpp
SCRIPTS += scripts/counterbox.cmd SCRIPTS += scripts/daq_4ch.cmd
SCRIPTS += scripts/counterbox_v2.cmd SCRIPTS += scripts/daq_8ch.cmd
SCRIPTS += scripts/daq_2nd_gen.cmd
SCRIPTS += sim/daq_sim.py
CXXFLAGS += -std=c++17 CXXFLAGS += -std=c++17
USR_CFLAGS += -Wall -Wextra #-Werror USR_CFLAGS += -Wall -Wextra #-Werror

205
README.md Normal file
View File

@ -0,0 +1,205 @@
SINQDAQ Epics Module
-----------------------
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.
## 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 DAQ interface
script.
Required Variables
| Environment Variable | Purpose |
|----------------------|-----------------------------------------|
| INSTR | Prefix of all device specific PVs |
| NAME | First field in all PVs after Prefix |
| DAQ\_IP | Network IP of device |
| DAQ\_PORT | Network Port of device |
All PVs take the form
```
$(INSTR)$(NAME):*
```
Available device startup scripts
* 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("INSTR", "SQ:INSTRUMENT:") # can also be set in runScript call
runScript "$(sinqDAQ_DIR)daq_2nd_gen.cmd" "NAME=DAQ, DAQ_IP=TestInst-DAQ1, DAQ_PORT=2000"
```
## PVs of Interest
| 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
The 2nd generation systems have two test channels that can be used to output
signals at a variable rate. These can be used to ensure the other channels are
working and to check the IOC - Nicos integration. These can be loaded at
runtime via the following
```
epicsEnvSet("LOAD_TEST_PVS","")
runScript "$(sinqDAQ_DIR)daq_2nd_gen.cmd" "NAME=DAQ, DAQ_IP=TestInst-DAQ1, DAQ_PORT=2000"
```
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 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 `DAQ_IP` is ignored, and a python program
simulating the hardware is started in the background, listening at the
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/daq\_sim.py](sim/daq_sim.py).
## Testing
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).
Both require that the module has been built and installed as is normal in the
require based module system.
You might have to change the specified version in the
[test/st.cmd](test/st.cmd) file to the version you compiled and want to test.

77
db/channels.db Normal file
View File

@ -0,0 +1,77 @@
# EPICS Database for streamdevice specific to measurement 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
################################################################################
# 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, "$(INSTR)$(NAME):M$(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,46 +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
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, "8") # Largest Threshold Channel
}
################################################################################
# Read all monitors values
record(ai, "$(P):$(NAME):READALL")
{
field(DESC, "Reads monitors and elapsed time")
field(INP, "@$(PROTO) readAll8($(P):$(NAME):) $(ASYN_PORT)")
field(SCAN, ".2 second")
field(DTYP, "stream")
field(FLNK, "$(P):$(NAME):UNSET-COUNTING")
}

View File

@ -1,128 +0,0 @@
#
# Counterbox Protocol File
#
OutTerminator = CR;
InTerminator = CR;
ReadTimeout = 100;
WriteTimeout = 100;
ReplyTimeout = 200;
LockTimeout = 450;
initialise {
out "RMT 1";
in;
out "ECHO 2";
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
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,259 +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):READALL 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")
}
# The COUNTING PV stays True until Counterbox has switched back to idle mode
# and the monitor counts have been read. Therefore, we know that the monitor
# values have been updated to represent their final values, when this switches
# back to False.
#
# This is accomplished via the explicit SET-COUNTING and UNSET-COUNTING seq
# records, that are triggered by a switch to the counting status
# (RAW-STATUS == 1 || 2) and a read of the monitors respectively.
record(bi, "$(P):$(NAME):COUNTING")
{
field(DESC, "Counterbox is Counting")
field(VAL, 0)
}
record(seq, "$(P):$(NAME):SET-COUNTING")
{
field(LNK1, "$(P):$(NAME):COUNTING PP")
field(DO1, 1)
field(SELM, "Specified")
field(SELL, "$(P):$(NAME):MAP-STATUS.VAL")
field(FLNK, "$(P):$(NAME):STATUS")
}
record(seq, "$(P):$(NAME):UNSET-COUNTING")
{
field(LNK0, "$(P):$(NAME):COUNTING PP")
field(DO0, 0)
field(SELM, "Specified")
field(SELL, "$(P):$(NAME):RAW-STATUS.VAL")
field(FLNK, "$(P):$(NAME):MAP-STATUS")
}
record(longin, "$(P):$(NAME):RAW-STATUS")
{
field(DESC, "Raw returned status value")
field(DTYP, "stream")
field(SCAN, ".2 second")
field(INP, "@$(PROTO) readStatus($(P):$(NAME):) $(ASYN_PORT)")
field(FLNK, "$(P):$(NAME):MAP-STATUS")
}
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):COUNTING NPP")
field(CALC, "B=1?4:(C=1&&A=0)||A=1||A=2?1:A=0?0:A=5||A=6?2:A=9||A=13||A=10||A=14?3:4")
field(FLNK, "$(P):$(NAME):SET-COUNTING")
}
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")
}
################################################################################
# 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(longin,"$(P):$(NAME):THRESHOLD-MONITOR_RBV")
{
field(DESC, "Channel monitored for minimum rate")
}
################################################################################
# Read all monitors values
record(ai,"$(P):$(NAME):ELAPSED-TIME")
{
field(DESC, "Counterbox Measured Time")
field(EGU, "seconds")
}
record(longin, "$(P):$(NAME):M1")
{
field(DESC, "Counterbox CH1")
}
record(longin, "$(P):$(NAME):M2")
{
field(DESC, "Counterbox CH2")
}
record(longin, "$(P):$(NAME):M3")
{
field(DESC, "Counterbox CH3")
}
record(longin, "$(P):$(NAME):M4")
{
field(DESC, "Counterbox CH4")
}
record(longin, "$(P):$(NAME):M5")
{
field(DESC, "Counterbox CH5")
}
record(longin, "$(P):$(NAME):M6")
{
field(DESC, "Counterbox CH6")
}
record(longin, "$(P):$(NAME):M7")
{
field(DESC, "Counterbox CH7")
}
record(longin, "$(P):$(NAME):M8")
{
field(DESC, "Counterbox CH8")
}
# 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,88 +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
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, "10") # Largest Threshold Channel
}
################################################################################
# Read all monitors values
record(ai, "$(P):$(NAME):READALL")
{
field(DESC, "Reads monitors and elapsed time")
field(INP, "@$(PROTO) readAll10($(P):$(NAME):) $(ASYN_PORT)")
field(SCAN, ".2 second")
field(DTYP, "stream")
field(FLNK, "$(P):$(NAME):UNSET-COUNTING")
}
record(longin, "$(P):$(NAME):M9")
{
field(DESC, "Counterbox CH9")
}
record(longin, "$(P):$(NAME):M10")
{
field(DESC, "Counterbox CH10")
}
################################################################################
# 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)
}
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,13 +0,0 @@
require asyn
# Need to be set by user
# epicsEnvSet("CNTBOX_HOST", "testinst-daq1:2000")
# epicsEnvSet("ASYN_PORT", "el737")
# epicsEnvSet("PREFIX", "SQ:SINQTEST")
# epicsEnvSet("NAME", "COUNTERBOX")
epicsEnvSet("PROTO", "$(sinq_DB)counterbox.proto")
drvAsynIPPortConfigure("$(ASYN_PORT)", "$(CNTBOX_HOST)", 0, 0, 0)
dbLoadRecords("$(sinq_DB)counterbox_common.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=$(ASYN_PORT)")
dbLoadRecords("$(sinq_DB)counterbox.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=$(ASYN_PORT)")

View File

@ -1,13 +0,0 @@
require asyn
# Need to be set by user
# epicsEnvSet("CNTBOX_HOST", "testinst-daq1:2000")
# epicsEnvSet("ASYN_PORT", "el737")
# epicsEnvSet("PREFIX", "SQ:SINQTEST")
# epicsEnvSet("NAME", "COUNTERBOX")
epicsEnvSet("PROTO", "$(sinq_DB)counterbox.proto")
drvAsynIPPortConfigure("$(ASYN_PORT)", "$(CNTBOX_HOST)", 0, 0, 0)
dbLoadRecords("$(sinq_DB)counterbox_common.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=$(ASYN_PORT)")
dbLoadRecords("$(sinq_DB)counterbox_v2.db", "P=$(PREFIX), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=$(ASYN_PORT)")

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

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 get_ioint_info;
DEVSUPFUN read_ai; DEVSUPFUN read_ai;
DEVSUPFUN special_linconv; DEVSUPFUN special_linconv;
} devCounterBoxStringError = { } devDAQStringError = {
6, NULL, NULL, NULL, NULL, (DEVSUPFUN)map_raw_failure_message, NULL}; 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")

8
test/ioc.sh Executable file
View File

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

14
test/st.cmd Executable file
View File

@ -0,0 +1,14 @@
#!/usr/local/bin/iocsh
on error break
require sinqDAQ
epicsEnvSet("STREAM_PROTOCOL_PATH","./db")
epicsEnvSet("INSTR","SQ:TEST:")
epicsEnvSet("NAME","CB_TEST")
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()

129
test/test.py Executable file
View File

@ -0,0 +1,129 @@
#!/usr/bin/env python3
# TODO might be better to use PyEpics or Caproto or something
import os
import pathlib
import sys
import time
from queue import Queue, Empty
from subprocess import Popen, PIPE, run
from threading import Thread
os.environ['EPICS_BASE'] = '/usr/local/epics/base-7.0.7'
os.environ['EPICS_HOST_ARCH'] = 'linux-x86_64'
os.environ['PARENT_PATH'] = str(pathlib.Path(__file__).parent.resolve())
def queue_output(pipe, queue):
while True:
line = pipe.readline()
if not line:
break
queue.put(line.decode('ascii').rstrip())
pipe.close()
def get_piped_output(proc):
stdqueue = Queue()
stdthread = Thread(target=queue_output, args=(proc.stdout, stdqueue))
stdthread.daemon = True
stdthread.start()
errqueue = Queue()
errthread = Thread(target=queue_output, args=(proc.stderr, errqueue))
errthread.daemon = True
errthread.start()
return stdqueue, errqueue
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(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(instr, name, time):
print(f'Starting count for {time} seconds')
run(['caput', f'{instr}{name}:PRESET-TIME', f'{time}'])
def presetCount(instr, name, count):
print(f'Starting count until channel 1 reaches {count}')
run(['caput', f'{instr}{name}:PRESET-COUNT', f'{count}'])
def testCanCount(instr, name):
# Check in Idle State
assert getState(instr, name) == 'Idle', 'Not in valid state'
# Start Time Based Count and Check that Status Changes
assert getCount(instr, name, 1) == 0, "Erroneous nonzero starting count value"
presetTime(instr, name, 5)
time.sleep(1)
assert getState(instr, name) == 'Counting', 'Didn\'t start counting'
time.sleep(5)
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(instr, name, presetAmount)
time.sleep(1)
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(instr, name, 1) == presetAmount, 'Counted events not equal to preset'
assert getCount(instr, name, 2) > 0
def test(instr, name):
# 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)
while True:
line = stdqueue.get()
print(line)
if 'iocRun: All initialization complete' in line:
break # IOC is now running
#time.sleep(20)
print("IOC Initialisation Complete")
print("Starting Tests")
testCanCount(instr, name)
print("Success")
proc.kill()
proc.wait()
return True
except Exception as e:
print(f"Failure: {e}", file=sys.stderr)
proc.kill()
proc.wait()
return False
if __name__ == '__main__':
print("Starting Test")
# Test V2
if test('SQ:TEST:', 'CB_TEST'):
exit(0)
else:
exit(1)