15 Commits

Author SHA1 Message Date
5b772152c9 Fix low rate deduction on HW daq
Some checks failed
Example Action / Lint (push) Successful in 8s
Example Action / BuildAndTest (push) Failing after 14s
2026-02-09 08:32:48 +01:00
00b9093b37 SPC: add example for st.cmd and nicos setup
All checks were successful
Example Action / Lint (push) Successful in 2s
Example Action / BuildAndTest (push) Successful in 38s
2026-02-06 11:05:51 +01:00
38e774cf84 Add back missing comment in db file 2026-02-06 10:29:24 +01:00
6cf24254e3 Last big things:
* Implemented support for diabling theshold channel
* Separate proton current simulation db.
* Separate startup snippet with support for simulation mode.
* Short readme entry
2026-02-06 10:29:24 +01:00
4e42eab39b Kind of working, but time based count doesn't work from nicos 2026-02-06 10:29:24 +01:00
b584085218 Counting from nicos works, but time preset is not correct 2026-02-06 10:29:24 +01:00
de012d37ea Correct meaning of IS_LOWRATE
0 means not low-rate i.e. good rate
1 means low-rate
2026-02-06 10:29:24 +01:00
94402c4592 make it run 2026-02-06 10:29:24 +01:00
45b01bf4c4 compiled soft_proton 2026-02-06 10:29:24 +01:00
025e985b75 wip 2026-02-06 10:29:24 +01:00
14f4e3eee7 Add startup snippet 2026-02-06 10:29:24 +01:00
2aa6bd4405 Continued work 2026-02-06 10:29:24 +01:00
1d47b02833 More work 2026-02-06 10:29:24 +01:00
bec218ad86 More work on soft proton counter 2026-02-06 10:29:24 +01:00
94814b2140 Start work on sinqdaq soft proton implementation 2026-02-06 10:29:24 +01:00
11 changed files with 731 additions and 7 deletions

View File

@@ -20,6 +20,8 @@ TEMPLATES += db/daq_common.db
TEMPLATES += db/daq_2nd_gen.db
TEMPLATES += db/daq_2nd_gen_test.db
TEMPLATES += db/daq.proto
TEMPLATES += db/daq_soft_proton.db
TEMPLATES += db/sim_proton_current.db
# Just for simulation
TEMPLATES += db/daq_simcontrol.db
@@ -29,13 +31,15 @@ DBDS += src/daq.dbd
# Source files to build
SOURCES += src/daq.cpp
SOURCES += src/daq_soft_proton.c
SCRIPTS += scripts/daq_4ch.cmd
SCRIPTS += scripts/daq_8ch.cmd
SCRIPTS += scripts/daq_2nd_gen.cmd
SCRIPTS += scripts/daq_soft_proton.cmd
SCRIPTS += sim/daq_sim.py
CXXFLAGS += -std=c++17
USR_CFLAGS += -Wall -Wextra #-Werror
USR_CFLAGS += -std=c11 -Wall -Wextra #-Werror
# MISCS would be the place to keep the stream device template files

View File

@@ -299,3 +299,82 @@ 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.
## 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)
'''
```

View File

@@ -42,7 +42,7 @@ record(calc, "$(INSTR)$(NAME):RATE_MAP")
{
field(DESC, "Want a consistent lowrate pv")
field(INPA, "$(INSTR)$(NAME):RAW-STATUS.B2 NPP")
field(CALC, "(A=1)?1:0")
field(CALC, "(A=1)?0:1")
}
################################################################################

View File

@@ -29,7 +29,7 @@ record(calc, "$(INSTR)$(NAME):RATE_MAP")
{
field(DESC, "Want a consistent lowrate pv")
field(INPA, "$(INSTR)$(NAME):RAW-STATUS.B2 NPP")
field(CALC, "(A=0)?1:0")
field(CALC, "(A=0)?0:1")
}
################################################################################

View File

@@ -29,7 +29,7 @@ record(calc, "$(INSTR)$(NAME):RATE_MAP")
{
field(DESC, "Want a consistent lowrate pv")
field(INPA, "$(INSTR)$(NAME):RAW-STATUS.B2 NPP")
field(CALC, "(A=0)?1:0")
field(CALC, "(A=0)?0:1")
}
################################################################################

View File

@@ -89,8 +89,8 @@ record(bi, "$(INSTR)$(NAME):COUNTING_PRESET")
record(bi, "$(INSTR)$(NAME):IS_LOWRATE")
{
field(INP, "$(INSTR)$(NAME):RATE_MAP PP")
field(ZNAM, "LOW RATE")
field(ONAM, "GOOD RATE")
field(ONAM, "LOW RATE")
field(ZNAM, "GOOD RATE")
}
record(bi, "$(INSTR)$(NAME):IS_PAUSED")
@@ -109,7 +109,7 @@ record(calc, "$(INSTR)$(NAME):MAP-STATUS")
field(INPD, "$(INSTR)$(NAME):COUNTING_PRESET PP")
field(INPE, "$(INSTR)$(NAME):IS_LOWRATE PP")
field(INPF, "$(INSTR)$(NAME):IS_PAUSED PP")
field(CALC, "(A=1||B=1)?4:(F=1)?3:(C=0&&D=0)?0:(E=0)?2:1")
field(CALC, "(A=1||B=1)?4:(F=1)?3:(C=0&&D=0)?0:(E=1)?2:1")
field(FLNK, "$(INSTR)$(NAME):STATUS")
}

329
db/daq_soft_proton.db Normal file
View File

@@ -0,0 +1,329 @@
# EPICS database for counterbox-like interface using proton current,
# as retrieved via network from HIPA.
#
# This is created to be able reuse the SinqDAQ interface
# in Nicos, to avoid having more code to maintain there.
#
# Commands that has no actual relevant value that needs to be written to them:
# * Pause
# * Continue
# * Stop
# * Full reset
# * Reset elapsed time
# * Reset the counter/monitor value
# Are all implemented as seq records. Due to how the existing
# interface worked, they were being written 1 from nicos.
# It just so turns out that seq record's VAL field triggers processing,
# but a calcout records VAL field doesn't. seq record was the only option
# to emulate these features with only 1 record per command.
#
# This requires following macros to specified:
# INSTR: Instrument prefix, e.g. "SQAMOR:"
# NAME: Name of the DAQ, e.g. "PROTONDAQ"
# SHUTTER1_PV: PV of a shutter state will be taken as a gate signal for the protoon current
# SHUTTER1_CLOSED_VAL: Value of the shutter PV when the shutter is closed
# SHUTTER2_PV: PV of a second shutter state will be taken as a gate signal for the protoon current
# SHUTTER2_CLOSED_VAL: Value of the shutter PV when the shutter is closed
record(mbbi, "$(INSTR)$(NAME):STATUS")
{
field(DESC, "DAQ Status")
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")
# We start in idle
field(VAL, 0)
}
record(longin, "$(INSTR)$(NAME):CHANNELS")
{
field(DESC, "Total Supported Channels")
field(VAL, 1)
field(DISP, 1)
}
record(stringin, "$(INSTR)$(NAME):MsgTxt")
{
field(DESC, "Miscellanous messages")
}
record(bi, "$(INSTR)$(NAME):IS_LOWRATE")
{
field(ZNAM, "LOW RATE")
field(ONAM, "GOOD RATE")
}
################################################################################
# Commands
################################################################################
record(ao, "$(INSTR)$(NAME):PRESET-COUNT")
{
field(DESC, "Count until preset reached")
field(VAL, 0)
field(PREC, 2)
field(FLNK, "$(INSTR)$(NAME):PRESET-COUNT-TRIG")
}
record(longout, "$(INSTR)$(NAME):PRESET-COUNT-TRIG")
{
field(DESC, "Count until preset reached")
# Signal count preset as command
field(VAL, 1)
field(OUT, "$(INSTR)$(NAME):COMMAND-TRIG PP")
field(FLNK, "$(INSTR)$(NAME):COUNT-TYPE")
}
record(ao, "$(INSTR)$(NAME):PRESET-TIME")
{
field(DESC, "Count for specified time")
field(VAL, 0)
field(PREC, 2)
field(EGU, "seconds")
field(FLNK, "$(INSTR)$(NAME):PRESET-TIME-TRIG")
}
record(longout, "$(INSTR)$(NAME):PRESET-TIME-TRIG")
{
field(DESC, "Count until preset time reached")
# Signal count preset as command
field(VAL, 2)
field(OUT, "$(INSTR)$(NAME):COMMAND-TRIG PP")
field(FLNK, "$(INSTR)$(NAME):COUNT-TYPE")
}
record(seq, "$(INSTR)$(NAME):PAUSE")
{
field(DESC, "Pause the current count")
field(SELM, "All")
field(DO0, 3)
field(LNK0, "$(INSTR)$(NAME):COMMAND-TRIG PP")
}
record(seq, "$(INSTR)$(NAME):CONTINUE")
{
field(DESC, "Continue with a count that was paused")
field(SELM, "All")
field(DO0, 4)
field(LNK0, "$(INSTR)$(NAME):COMMAND-TRIG PP")
}
record(seq, "$(INSTR)$(NAME):STOP")
{
field(DESC, "Stop the current counting operation")
field(SELM, "All")
field(DO0, 5)
field(LNK0, "$(INSTR)$(NAME):COMMAND-TRIG PP")
}
record(seq, "$(INSTR)$(NAME):FULL-RESET")
{
field(DESC, "Perform full reset")
field(SELM, "All")
field(DO0, 6)
field(LNK0, "$(INSTR)$(NAME):COMMAND-TRIG PP")
}
# Emulate Reset elapsed time
# 0. set status to busy
# 1. set elapsed time to 0
# 2. set status to OK
record(seq, "$(INSTR)$(NAME):CT")
{
field(SELM, "All")
field(LNK0, "$(INSTR)$(NAME):ETS PP")
field(DO0, 1)
field(LNK1, "$(INSTR)$(NAME):ELAPSED-TIME PP")
field(DO1, 0)
field(LNK2, "$(INSTR)$(NAME):ETS PP")
field(DO2, 0)
}
# Record is to signal command given by the client to the emulated counter.
# It's set back to no command from the emulated counter subroutine record
record(mbbi, "$(INSTR)$(NAME):COMMAND-TRIG")
{
field(DESC, "Command type")
field(VAL, 0)
field(ZRST, "No command")
field(ZRVL, 0)
field(ONST, "Count preset command")
field(ONVL, 1)
field(TWST, "Time preset command")
field(TWVL, 2)
field(THST, "Pause command")
field(THVL, 3)
field(FRST, "Continue command")
field(FRVL, 4)
field(FVST, "Stop command")
field(FVVL, 5)
field(SXST, "Full reset command")
field(SXVL, 6)
}
# Copy COMMAND-TRIG to memorize what type of count is on-going
record(longin, "$(INSTR)$(NAME):COUNT-TYPE") {
field(INP, "$(INSTR)$(NAME):COMMAND-TRIG.VAL NPP")
field(VAL, 0)
}
record(longout,"$(INSTR)$(NAME):THRESHOLD-MONITOR")
{
# Alias to RBV to be compatible with higher level interface
alias("$(INSTR)$(NAME):THRESHOLD-MONITOR_RBV")
field(DESC, "Channel monitored for minimum rate")
field(VAL, "1") # Monitor
field(DRVL, "0") # Smallest Threshold Channel (0 is off)
field(DRVH, "1") # Largest Threshold Channel
}
record(ao,"$(INSTR)$(NAME):THRESHOLD")
{
# Alias to RBV to be compatible with higher level interface
alias("$(INSTR)$(NAME):THRESHOLD_RBV")
field(DESC, "Minimum rate for counting to proceed")
field(VAL, "1") # Default Rate
field(DRVL, "1") # Minimum Rate
field(DRVH, "100000") # Maximum Rate
field(OMSL, "supervisory")
}
record(ai,"$(INSTR)$(NAME):ELAPSED-TIME")
{
field(DESC, "DAQ Measured Time")
field(EGU, "sec")
}
# Current Status of elapsed time
record(bi, "$(INSTR)$(NAME):ETS")
{
field(DESC, "Channel Status")
field(VAL, 0)
field(ZNAM, "OK")
field(ONAM, "CLEARING")
}
record(longout, "$(INSTR)$(NAME):MONITOR-CHANNEL")
{
alias("$(INSTR)$(NAME):MONITOR-CHANNEL_RBV")
field(DESC, "PRESET-COUNT Monitors this channel")
field(VAL, 1)
}
# Array Subroutine record which emulates the counterbox functionality
# Use an aSub because it allows specifying the type.
record(aSub, "$(INSTR)$(NAME):EMULATION")
{
# Scan rate determines how often we sample the rate
# and how often the counter value updates.
field(SCAN, ".1 second")
field(SNAM, "processEmulatedCounter")
# The first 4 inputs are also mapped as the first 4 outputs
field(INPA, "$(INSTR)$(NAME):STATUS")
field(FTA, "ULONG")
field(INPB, "$(INSTR)$(NAME):M1")
field(FTB, "INT64")
field(INPC, "$(INSTR)$(NAME):ELAPSED-TIME")
field(FTC, "DOUBLE")
field(INPD, "$(INSTR)$(NAME):COMMAND-TRIG")
field(FTD, "ULONG")
# Address the PV which are mapped as input backwards
field(INPE, "$(INSTR)$(NAME):THRESHOLD-MONITOR")
field(FTE, "LONG")
field(INPF, "$(INSTR)$(NAME):THRESHOLD")
field(FTF, "DOUBLE")
field(INPG, "$(INSTR)$(NAME):COUNT-TYPE")
field(FTG, "ULONG")
field(INPH, "$(INSTR)$(NAME):PRESET-COUNT")
field(FTH, "DOUBLE")
field(INPI, "$(INSTR)$(NAME):PRESET-TIME")
field(FTI, "DOUBLE")
# L is last input before EPICS 7.0.10
field(INPJ, "$(INSTR)$(NAME):R1-PREV")
field(FTJ, "DOUBLE")
field(INPL, "$(INSTR)$(NAME):R1 PP")
field(FTL, "DOUBLE")
# The first 4 outputs are also mapped as the first 4 inputs
field(OUTA, "$(INSTR)$(NAME):STATUS PP")
field(FTVA, "ULONG")
field(OUTB, "$(INSTR)$(NAME):M1 PP")
field(FTVB, "INT64")
field(OUTC, "$(INSTR)$(NAME):ELAPSED-TIME PP")
field(FTVC, "DOUBLE")
field(OUTD, "$(INSTR)$(NAME):COMMAND-TRIG PP")
field(FTVD, "ULONG")
field(OUTE, "$(INSTR)$(NAME):R1-PREV PP")
field(FTVE, "DOUBLE")
field(OUTF, "$(INSTR)$(NAME):IS_LOWRATE PP")
field(FTVF, "ULONG")
field(OUTF, "$(INSTR)$(NAME):MsgTxt PP")
field(FTVF, "CHAR")
field(NEVF, 40)
}
#######################
# Channel interface
#######################
record(int64in, "$(INSTR)$(NAME):M1")
{
field(DESC, "DAQ CH0, proton current")
field(EGU, "cts")
}
# The proton rate take by a PV over the network, PV named indicated by $(REMOTE_RATE_PV) macro
# It emulates Zero rate if either shutter is closed.
record(calc, "$(INSTR)$(NAME):R1")
{
field(DESC, "Rate of DAQ CH0 proton current")
field(INPA, "$(REMOTE_RATE_PV) CA")
field(INPB, "$(SHUTTER1_PV=0)")
field(INPC, "$(SHUTTER1_CLOSED_VAL=1)")
field(INPD, "$(SHUTTER2_PV=0)")
field(INPE, "$(SHUTTER2_CLOSED_VAL=1)")
# If either shutter is closed we have no rate
field(CALC, "B != C && D != E ? A : 0")
field(EGU, "cts/sec")
}
# Store previous rate value, so we can average over the period
record(ai, "$(INSTR)$(NAME):R1-PREV") {
field(DESC, "Previous rate of DAQ CH0 proton current")
}
# Emulate clearing channel
# 0. set status to busy
# 1. set elapsed time to 0
# 2. set status to OK
record(seq, "$(INSTR)$(NAME):C1")
{
field(SELM, "All")
field(LNK0, "$(INSTR)$(NAME):S1 PP")
field(DO0, 1)
field(LNK1, "$(INSTR)$(NAME):M1 PP")
field(DO1, 0)
field(LNK2, "$(INSTR)$(NAME):S1 PP")
field(DO2, 0)
}
# Current Status of Channel
# This is only to satify the interface.
record(bi, "$(INSTR)$(NAME):S1")
{
field(DESC, "Channel Status")
field(VAL, 0)
field(ZNAM, "OK")
field(ONAM, "CLEARING")
}

12
db/sim_proton_current.db Normal file
View File

@@ -0,0 +1,12 @@
# This is database that provides a simulated proton current
# for testing purposes.
#
record(calc, "$(INSTR)$(NAME):SIM_PROTON_CURR") {
field(SCAN, ".1 second")
field(CALC, "1500 + 101 * SIN(A)")
field(INPA, "$(INSTR)$(NAME):PROTON_CURR_VAR PP")
}
record(calc, "$(INSTR)$(NAME):PROTON_CURR_VAR") {
field(CALC, "VAL + 0.001")
}

View File

@@ -0,0 +1,8 @@
#var softProtonDebug 1
$(SET_SIM_MODE=#)dbLoadRecords("$(sinqDAQ_DB)sim_proton_current.db", "INSTR=$(INSTR), NAME=$(NAME), REMOTE_RATE_PV=NULL")
$(SET_SIM_MODE=#)epicsEnvSet("REMOTE_RATE_PV", "$(INSTR)$(NAME):SIM_PROTON_CURR")
dbLoadRecords("$(sinqDAQ_DB)daq_soft_proton.db", "INSTR=$(INSTR), NAME=$(NAME), REMOTE_RATE_PV=$(REMOTE_RATE_PV)")
iocInit

View File

@@ -3,3 +3,5 @@
#---------------------------------------------
device(stringin, INST_IO, devDAQStringError, "devDAQStringError")
function(processEmulatedCounter)
variable("softProtonDebug", int)

290
src/daq_soft_proton.c Normal file
View File

@@ -0,0 +1,290 @@
/* This is a software emulated counterbox with 1 channel.
* It's stateless implementation with only a process function.
* All states that are need between the periods,
* are saved in the EPICS databases.
* Typically it is sampling the proton rate from HIPA via
* channel access.
*/
#include <string.h>
#include <dbDefs.h>
#include <errlog.h>
#include <aSubRecord.h>
#include <epicsTypes.h>
#include <epicsString.h>
#include <registryFunction.h>
#include <epicsExport.h>
/* Sample rate */
#define SOFT_PROTON_SAMPLE_RATE 0.1
/* To allow setting debug pring from iocsh */
static int softProtonDebug=0;
epicsExportAddress(int, softProtonDebug);
struct spc_internal {
epicsUInt32 status;
epicsInt64 monitor_count;
epicsFloat64 elapsed_time;
epicsUInt32 command_trig;
epicsInt32 threshold_ch;
epicsFloat64 threshold;
epicsFloat64 proton_rate;
epicsFloat64 prev_proton_rate;
epicsUInt32 count_type;
epicsFloat64 preset_count;
epicsFloat64 preset_time;
epicsFloat64 average_rate;
};
/* Enum with values for all commands
* this has to match the what's in the mbbi in the database*/
enum commands {
NONE = 0,
COUNT_PRESET = 1,
TIME_PRESET = 2,
PAUSE = 3,
CONTINUE = 4,
STOP = 5,
FULL_RESET = 6
};
/* Enum with the possible statuses/states
* this has to match the what's in the mbbi in the database*/
enum status {
IDLE = 0,
COUNTING = 1,
LOW_RATE = 2,
PAUSED = 3,
INVALID = 4
};
int handleNoop(struct spc_internal* spc, epicsOldString* msg_text) {
const char* funcstr = "handleNoop";
//if (softProtonDebug) printf("%s was called\n", funcstr);
/* This shouldn't happen, but let's handle it just in case */
if (spc->status >= INVALID || spc->command_trig > FULL_RESET) {
strcpy(*msg_text, "INVALID STATE!");
errlogPrintf("%s: Status and/or command triggers are invalid \n"
"Status has value %d, \n Command trigger has value %d\n",
funcstr, spc->status, spc->command_trig);
return 1;
}
/* Determine if we are idle and have received a noop command */
if (spc->status == IDLE) {
switch (spc->command_trig) {
case PAUSE:
case CONTINUE:
case STOP:
strcpy(*msg_text, "Can not PAUSE/CONTINUE/STOP during IDLE.");
printf("%s: %s\n"
"Status has value %d, \n"
"Command trigger has value %d\n", *msg_text,
funcstr, spc->status, spc->command_trig);
return 1;
}
}
/* Determine if we are counting, low_rate or paused;
* and have received a noop command */
if (spc->status == COUNTING || spc->status == LOW_RATE ||
spc->status == PAUSED) {
switch (spc->command_trig) {
case COUNT_PRESET:
case TIME_PRESET:
strcpy(*msg_text, "Already counting");
printf("%s: Already counting can not start a new count\n"
"Status has value %d, \n"
"Command trigger has value %d\n",
funcstr, spc->status, spc->command_trig);
/* This case could be seen as OK.
* Nothing is keeping us from continuing, so let's do that. */
return 0;
}
}
/* Determine if we are paused and a pause command */
if (spc->status == PAUSED && spc->command_trig == PAUSE) {
errlogPrintf("%s: Already paused\n"
"Status has value %d, \n"
"Command trigger has value %d\n",
funcstr, spc->status, spc->command_trig);
return 1;
}
/* None of the noop cases detected */
return 0;
}
/*
* This function is called everytime the record processes.
* Even though this record can process arrays, we only use it with scalars.
*/
static long processEmulatedCounter(struct aSubRecord *psub)
{
const char* funcstr = "processEmulatedCounter";
//if (softProtonDebug) printf("%s was called\n", funcstr);
/* Declare internal variable */
struct spc_internal spc_int;
struct spc_internal* spc = &spc_int;
/* Copy input values to a struct on the stack
* to simplify creation of functions */
spc->status = *(epicsUInt32*)psub->a;
spc->monitor_count = *(epicsInt64*)psub->b;
spc->elapsed_time = *(epicsFloat64*)psub->c;
spc->command_trig = *(epicsUInt32*)psub->d;
spc->threshold_ch = *(epicsInt32*)psub->e;
spc->threshold = *(epicsFloat64*)psub->f;
spc->count_type = *(epicsUInt32*)psub->g;
spc->preset_count = *(epicsFloat64*)psub->h;
spc->preset_time = *(epicsFloat64*)psub->i;
spc->prev_proton_rate = *(epicsFloat64*)psub->j;
spc->proton_rate = *(epicsFloat64*)psub->l;
/* Get the pointer to output values only to increase readability. */
epicsUInt32* status_out = (epicsUInt32*)psub->vala;
epicsInt64* monitor_count_out = (epicsInt64*)psub->valb;
epicsFloat64* elapsed_time_out = (epicsFloat64*)psub->valc;
epicsUInt32* command_trig_out = (epicsUInt32*)psub->vald;
epicsFloat64* prev_proton_rate_out = (epicsFloat64*)psub->vale;
epicsUInt32* is_low_rate_out = (epicsUInt32*)psub->valf;
epicsOldString* msg_txt_out = (epicsOldString*)psub->valg;
/* Always no matter what, handle the rate */
/* - Calculate average rate */
spc->average_rate = (spc->proton_rate + spc->prev_proton_rate) / 2;
/* - Store current rate as previous rate */
*prev_proton_rate_out = spc->proton_rate;
if (spc->average_rate < spc->threshold &&
spc->threshold_ch >= 1) { /* Channel 0 is interpreted as disabled */
*is_low_rate_out = 1;
} else {
*is_low_rate_out = 0;
}
if (softProtonDebug) {
printf("%s: DEBUG:\n"
"Status has value %d, \n"
"Command trigger has value %d\n",
funcstr, spc->status, spc->command_trig);
}
/* Handle noop situations both valid and invalid */
if (handleNoop(spc, msg_txt_out))
/* We have state that prohibits further processing */
return 0;
/* Commands with priority always yielding IDLE status first */
if (spc->command_trig == FULL_RESET) {
strcpy(*msg_txt_out, "Full reset!");
/* Reset everything, done! */
*status_out = IDLE;
*monitor_count_out = 0;
*elapsed_time_out = 0.0;
*command_trig_out = NONE;
if (softProtonDebug) printf("%s: Full reset done!\n", funcstr);
return 0;
} else if (spc->command_trig == STOP ||
(spc->status == IDLE && spc->command_trig == NONE)) {
/* Stop is always valid, retains everything except status goes to idle.
* This happens to be the same case as when IDLE and no command received */
*status_out = IDLE;
*command_trig_out = NONE;
*monitor_count_out = spc->monitor_count;
*elapsed_time_out = spc->elapsed_time;
if (softProtonDebug) printf("%s: Case STOP or IDLE!\n", funcstr);
return 0;
} else if (((spc->status == COUNTING || spc->status == LOW_RATE)
&& spc->command_trig == PAUSE) ||
(spc->status == PAUSED && spc->command_trig == NONE)) {
strcpy(*msg_txt_out, "Stopping!");
/* We are counting or at low rate and received pause.
* Also we are paused but have received no command, this
* is probably slightly inaccurate but simplifies things.*/
*status_out = PAUSED;
*command_trig_out = NONE;
*monitor_count_out = spc->monitor_count;
*elapsed_time_out = spc->elapsed_time;
if (softProtonDebug) printf("%s: Case PAUSE!\n", funcstr);
return 0;
} else if (spc->status == IDLE &&
(spc->command_trig == COUNT_PRESET || spc->command_trig == TIME_PRESET)) {
/* Determine if we are idle but received a count command */
if (softProtonDebug) printf("%s: Starting Count!\n", funcstr);
/* Sanity check that count type is properly stored */
if (spc->command_trig != spc->count_type) {
if (softProtonDebug) printf("%s: Count type not stored!\n", funcstr);
return -1;
}
/* Starting a new count
* Reset counter and time */
*monitor_count_out = 0;
*elapsed_time_out = 0;
*command_trig_out = NONE;
if (*is_low_rate_out == 1) {
*status_out = LOW_RATE;
} else {
*status_out = COUNTING;
}
if (softProtonDebug) printf("%s: Case received COUNT command!\n", funcstr);
return 0;
} else if ((((spc->status == LOW_RATE && spc->command_trig == NONE) ||
(spc->status == LOW_RATE && spc->command_trig == CONTINUE)))
&& *is_low_rate_out == 1) {
*status_out = LOW_RATE;
/* LOW_RATE or resuming from PAUSE */
*command_trig_out = NONE;
/* Maintain same counter and time*/
*monitor_count_out = spc->monitor_count;
*elapsed_time_out = spc->elapsed_time;
if (softProtonDebug) printf("%s: Case LOW_RATE!\n", funcstr);
return 0;
}
if (softProtonDebug) printf("%s: Case COUNT incremental cycle!\n", funcstr);
/* Ending up here means:
* 1. status == COUNTING && command_trig == NONE
* 2. status == COUNTING && command_trig == CONTINUE
* 3. status == PAUSED && command_trig == CONTINUE
* 4. status == LOW_RATE && command_trig in [ NONE, CONTINUE ] && high_rate
*/
/* We may have had a command */
*command_trig_out = NONE;
/* Normal incremental count */
*status_out = COUNTING;
/* Increament counter and time */
*monitor_count_out = spc->monitor_count +
spc->average_rate * SOFT_PROTON_SAMPLE_RATE;
*elapsed_time_out = spc->elapsed_time + SOFT_PROTON_SAMPLE_RATE;
/* Check if we are below threshold */
if (*is_low_rate_out == 1) {
*status_out = LOW_RATE;
} else {
*status_out = COUNTING;
}
/* Check if count is finished normally.
* Higher priority than low rate */
if (/* Time based count finished */
(*elapsed_time_out >= spc->preset_time &&
spc->count_type == TIME_PRESET) ||
/* Monitor based count finished */
(*monitor_count_out >= spc->preset_count &&
spc->count_type == COUNT_PRESET)) {
*status_out = IDLE;
}
return 0;
}
/* Register these symbols for use by IOC code: */
epicsRegisterFunction(processEmulatedCounter);