soederqvist_a 5b772152c9
Some checks failed
Example Action / Lint (push) Successful in 8s
Example Action / BuildAndTest (push) Failing after 14s
Fix low rate deduction on HW daq
2026-02-09 08:32:48 +01:00
2025-07-04 15:41:01 +02:00
2026-02-09 08:32:48 +01:00
2026-02-06 10:29:24 +01:00
2026-02-06 10:29:24 +01:00
2025-07-04 15:04:28 +02:00
2024-11-05 09:29:38 +01:00
2026-02-06 10:29:24 +01:00

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.

Note: the epics side of this interface is also implemented/shared by the StreamGenerator module.

Functional Differences Between Models

The 2nd Generation DAQ offers some additional features that aren't available on the older EL737 Counterboxes. Specifically,

  • the possibility to change the channel monitored by the count-based preset (on the older EL737 boxes, only the 1st channel can be used)
  • two gating inputs, that enable counting to be halted via configurable high/low electrical inputs.
  • a test signal generator

Furthermore, the 2nd Generation DAQ's have 10 input channels, in place of the 8 or 4 channels on the older EL737 Counterboxes.

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 DAQ's 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

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.

Full Example of 8 Channel EL737 Counterbox

Include the following snippet in your IOC

# st.cmd at TASP

epicsEnvSet("STREAM_PROTOCOL_PATH","./db")
epicsEnvSet("INSTR","SQ:SINQTEST:")

require sinqDAQ
runScript "$(sinqDAQ_DIR)daq_8ch.cmd" "NAME=counter, DAQ_IP=tasp-ts0, DAQ_PORT=3004"

What follows is an example Nicos setup file.

# simplified tasp.py

countprefix = 'SQ:TASP:counter'

configured_channels = ['detector', 'protoncount']

devices = dict(
    elapsedtime = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQTime',
        daqpvprefix = countprefix,
    ),
    detector = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQChannel',
        description = 'Actual neutron detector',
        daqpvprefix = countprefix,
        channel = 1,
        type = 'counter',
    ),
    protoncount = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQChannel',
        description = 'Monitor for proton current',
        daqpvprefix = countprefix,
        channel = 2,
        type = 'monitor',
    ),
    DAQPreset = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQPreset',
        description = '8 Channel EL737 Counterbox',
        daqpvprefix = countprefix,
        channels = configured_channels,
        time_channel = ['elapsedtime'],
    ),
    ThresholdChannel = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQMinThresholdChannel',
        daqpvprefix = countprefix,
        channels = configured_channels,
    ),
    Threshold = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQMinThreshold',
        daqpvprefix = countprefix,
        min_rate_channel = 'ThresholdChannel',
    ),
    taspdet = device(
        'nicos_sinq.devices.epics.sinqdaq.SinqDetector',
        description = 'Detector Interface',
        timers = ['elapsedtime'],
        monitors = ['DAQPreset'] + configured_channels,
        others = [],
        liveinterval = 1,
    ),
)

startupcode = '''
SetDetectors(taspdet)
ThresholdChannel.move('protoncount')
'''

Full Example of 2nd Generation DAQ

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 = [],
    ),
    Threshold = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQMinThreshold',
        daqpvprefix = countprefix,
        min_rate_channel = 'ThresholdChannel',
    ),
    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'},
    ),
    # Only necessary if you want to use the signal generator in the
    # 2nd Generation DAQ for testing.
    TestGen = device('nicos_sinq.devices.epics.sinqdaq.DAQTestGen',
        daqpvprefix = countprefix,
        visibility = {'metadata', 'namespace'},
    ),
)

# On an actual instrument, it might be better if instead of just calling
# your channels 'Monitor <num>', you describe what is actually plugged
# into the DAQ on each channel.
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.

Testing

An IOC with the 2nd generation DAQ started in simulation mode can be started via the 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.

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 file to the version you compiled and want to test.

Software based proton current DAQ

This repository also contain a software based DAQ implementation that just requires a channel access link (remote Process Variable) to a proton rate. From that it implements the same interface as the counter boxes, and is compatible with the sinqdaq device in Nicos, albeit with only one channel.

Simulated software based proton current DAQ

You can start a proton current DAQ based on a simulation with the following command on any PC having the SINQ epics environment installed.

iocsh -r sinqDAQ,0.4.0 -c 'epicsEnvSet(SET_SIM_MODE,"" )' -c 'epicsEnvSet(INSTR,SQ:TEST:)' -c 'epicsEnvSet(NAME, SPC)' -c 'runScript $(sinqDAQ_DIR)daq_soft_proton.cmd'

Example st.cmd declaration

To include it in an existing st.cmd, add the following lines:

require sinqDAQ, 0.4.0
# This is the proton current PV from HIPA, however it's not available in the shutdown
epicsEnvSet(REMOTE_RATE_PV,"MHC6:IST:2")
# During shutdown you can specify this empty env var to have a simulated proton current.
epicsEnvSet(SET_SIM_MODE,"" ) # During operation remove this.
runScript $(sinqDAQ_DIR)daq_soft_proton.cmd "INSTR=SQ:TEST:, NAME=SPC"

Example NICOS setup file declaration

description = 'Setup for software based proton counter'

pvprefix = 'SQ:TEST:SPC'

channels = ['protoncount']

devices = dict(
    elapsedtime = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQTime',
        daqpvprefix = pvprefix,
    ),
    protoncount = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQChannel',
        description = 'Proton counter channel',
        daqpvprefix = pvprefix,
        channel = 1,
        type = 'monitor',
    ),
    preset = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQPreset',
        description = 'Time/Count Preset',
        daqpvprefix = pvprefix,
        channels = channels,
        time_channel = ['elapsedtime'],
    ),
    ThresholdChannel = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQMinThresholdChannel',
        daqpvprefix = pvprefix,
        channels = channels,
    ),
    Threshold = device(
        'nicos_sinq.devices.epics.sinqdaq.DAQMinThreshold',
        daqpvprefix = pvprefix,
        min_rate_channel = 'ThresholdChannel',
    ),
    spcdet = device(
        'nicos_sinq.devices.epics.sinqdaq.SinqDetector',
        description = 'Detector device that estimates proton counts in software',
        timers = ['elapsedtime'],
        counters = [],
        monitors = ['preset'] + channels,
        images = [],
        others = [],
        liveinterval = 7,
        saveintervals = [60]
    ),
)
startupcode = '''
SetDetectors(spcdet)
'''
Description
Epics Interface to the Neutron Detector Boxes used at SINQ
Readme 236 KiB
Languages
Python 61%
Batchfile 25%
C++ 9.3%
Makefile 3.8%
Shell 0.9%