From 2aa6bd4405569219dda697d43ca0e1969797d1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derqvist?= Date: Fri, 16 Jan 2026 08:30:41 +0100 Subject: [PATCH] Continued work --- db/daq_soft_proton.db | 39 ++++--- src/daq_soft_proton.c | 230 ++++++++++++++++++++++++------------------ 2 files changed, 160 insertions(+), 109 deletions(-) diff --git a/db/daq_soft_proton.db b/db/daq_soft_proton.db index 4cdad2e..ca9a6ba 100644 --- a/db/daq_soft_proton.db +++ b/db/daq_soft_proton.db @@ -33,7 +33,7 @@ record(mbbi, "$(INSTR)$(NAME):STATUS") record(longin, "$(INSTR)$(NAME):CHANNELS") { field(DESC, "Total Supported Channels") - field(VAL, $(CHANNELS=1)) + field(VAL, 1) field(DISP, 1) } @@ -41,6 +41,13 @@ record(stringin, "$(INSTR)$(NAME):MsgTxt") { field(DESC, "Miscellanous messages") } + +record(bi, "$(INSTR)$(NAME):IS_LOWRATE") +{ + field(ZNAM, "LOW RATE") + field(ONAM, "GOOD RATE") +} + ################################################################################ # Commands ################################################################################ @@ -150,8 +157,7 @@ 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=1)") # Largest Threshold Channel - field(DTYP, "stream") + field(DRVH, "1") # Largest Threshold Channel } record(ao,"$(INSTR)$(NAME):THRESHOLD") @@ -160,19 +166,15 @@ record(ao,"$(INSTR)$(NAME):THRESHOLD") alias("$(INSTR)$(NAME):THRESHOLD_RBV") 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") } record(ai,"$(INSTR)$(NAME):ELAPSED-TIME") { field(DESC, "DAQ Measured Time") field(EGU, "sec") - field(FLNK, "$(INSTR)$(NAME):ETO") } # Array Subroutine record which emulates the counterbox functionality @@ -182,7 +184,6 @@ record(aSub, "$(INSTR)$(NAME):EMULATION") # Scan rate determines how often we sample the rate # and how often the counter value updates. field(SCAN, "0.1 seconds" - field(INAM, "initEmulatedCounter") field(SNAM, "processEmulatedCounter" # The first 4 inputs are also mapped as the first 4 outputs @@ -219,8 +220,12 @@ record(aSub, "$(INSTR)$(NAME):EMULATION") field(FTVC, "DOUBLE") field(OUTD, "$(INSTR)$(NAME):COMMAND-TRIG PP") field(FTVD, "ULONG") - field(INPE, "$(INSTR)$(NAME):R0-PREV PP") - field(FTE, "DOUBLE") + field(OUTE, "$(INSTR)$(NAME):R0-PREV PP") + field(FTVE, "DOUBLE") + field(OUTF, "$(INSTR)$(NAME):IS_LOWRATE PP") + field(FTVF, "ULONG") + field(OUTF, "$(INSTR)$(NAME):MSG_TXT PP") + field(FTVF, "CHAR") } ####################### @@ -237,9 +242,10 @@ record(int64in, "$(INSTR)$(NAME):M0") record(calc, "$(INSTR)$(NAME):R0") { field(DESC, "Rate of DAQ CH0 proton current") - field(INPA, "$(REMOTE_RATE_PV) CA" + #field(INPA, "$(REMOTE_RATE_PV) CA") + field(INPA, "$(INSTR)$(NAME):PROTON_CURR)" field(INPB, "$(SHUTTER1_PV=0)") - field(INPC, "$(SHUTTER1_CLOSED_VAL=1)" + 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 @@ -273,3 +279,12 @@ record(bi, "$(INSTR)$(NAME):S0") field(ONAM, "CLEARING") } +record(calc, "$(INSTR)$(NAME):PROTON_CURR") { + field(SCAN, "0.05 seconds") + 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") +} diff --git a/src/daq_soft_proton.c b/src/daq_soft_proton.c index bffe38f..c8dc940 100644 --- a/src/daq_soft_proton.c +++ b/src/daq_soft_proton.c @@ -1,3 +1,10 @@ +/* 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 #include #include @@ -8,7 +15,7 @@ #include /* Sample rate */ -static const epicsFloat64 soft_proton_sample_rate = 0.1; +#define SOFT_PROTON_SAMPLE_RATE 0.1 /* To allow setting debug pring from iocsh */ static int softProtonDebug=0; @@ -27,7 +34,8 @@ struct spc_internal { epicsFloat64 preset_time; }; -/* Enum with values for all commands */ +/* 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, @@ -38,7 +46,8 @@ enum commands { FULL_RESET = 6 }; -/* Enum with the possible statuses/states */ +/* 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, @@ -48,36 +57,29 @@ enum status { }; -int handleNoop(struct spc_internal* spc_int, int* exit_status) { +int handleNoop(struct spc_internal* spc_int, epicsChar** msg_text) { const char[] funcstr = "handleNoop"; /* This shouldn't happen, but let's handle it just in case */ if (spc_int->status >= INVALID || spc->command_trig > FULL_RESET) { + *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_int->status, spc_int->command_trig); - *exit_status = -1; return 1; } - /* Determine if we are idle or paused and have not received a command */ - if ((spc_int->status == IDLE || spc_int->status == PAUSED) && - spc_int->command_trig == NONE) { - /* Just return as quickly as possible if there is nothing going on */ - *exit_status = 0; - return 1; - } - /* Determine if we are idle and have received a noop command */ if (spc_int->status == IDLE) { switch (spc_int->command_trig) { case PAUSE: case CONTINUE: case STOP: - errlogPrintf("%s: Can not PAUSE/CONTINUE/STOP during IDLE\n" - "Status has value %d, \n" - "Command trigger has value %d\n", - funcstr, spc_int->status, spc_int->command_trig); - *exit_status = -1; + *msg_text = "Can not PAUSE/CONTINUE/STOP during IDLE."; + printf("%s: %s\n" + "Status has value %d, \n" + "Command trigger has value %d\n", + funcstr, spc_int->status, spc_int->command_trig); + *exit_status = 1; /* Positive value: No alarm and no output processing */ return 1; } } @@ -89,22 +91,24 @@ int handleNoop(struct spc_internal* spc_int, int* exit_status) { switch (spc_int->command_trig) { case COUNT_PRESET: case TIME_PRESET: - errlogPrintf("%s: Already counting can not start a new count\n" - "Status has value %d, \n" - "Command trigger has value %d\n", - funcstr, spc_int->status, spc_int->command_trig); - *exit_status = -1; - return 1; + printf("%s: Already counting can not start a new count\n" + "Status has value %d, \n" + "Command trigger has value %d\n", + funcstr, spc_int->status, spc_int->command_trig); + *exit_status = 0; + /* 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 received a command */ + /* Determine if we are paused and a pause command */ if (spc_int->status == PAUSED && spc_int->command_trig == PAUSE) { errlogPrintf("%s: Already paused\n" "Status has value %d, \n" "Command trigger has value %d\n", funcstr, spc_int->status, spc_int->command_trig); - *exit_status = -1; + *exit_status = 1; /* Positive value: No alarm and no output processing */ return 1; } /* None of the noop cases detected */ @@ -112,16 +116,7 @@ int handleNoop(struct spc_internal* spc_int, int* exit_status) { } /* - * This function is called a IOC init - */ -static long initEmulatedCounter(struct subRecord *psub) -{ - if (softProtonDebug) printf("initEmulatedCounter was called\n"); - return 0; -} - -/* - * This functioni is called everytime the record processes. + * This function is called everytime the record processes. * Even though this record can process arrays, we only use it with scalars, * hence all the [0]'s. */ @@ -132,90 +127,139 @@ static long processEmulatedCounter(struct aSubRecord *psub) /* Declare internal variable */ struct spc_internal spc_int; + struct spc_internal spc = &spc_int; int exit_status = 0; /* Copy input values to a struct on the stack * to simplify creation of functions */ - spc_int.status = psub->a[0]; - monitor_count = psub->b[0]; - elapsed_time = psub->c[0]; - command_trig = psub->d[0]; - threshold = psub->e[0]; - count_type = psub->f[0]; - preset_count = psub->g[0]; - preset_time = psub->h[0]; - prev_proton_rate = psub->j[0]; - proton_rate = psub->l[0]; + spc->status = psub->a[0]; + spc->monitor_count = psub->b[0]; + spc->elapsed_time = psub->c[0]; + spc->command_trig = psub->d[0]; + spc->threshold = psub->e[0]; + spc->count_type = psub->f[0]; + spc->preset_count = psub->g[0]; + spc->preset_time = psub->h[0]; + spc->prev_proton_rate = psub->j[0]; + spc->proton_rate = psub->l[0]; - /* Get the pointer to output values. - This to increase readability. */ + /* Get the pointer to output values only to increase readability. */ epicsUInt32* status_out = psub->vala; epicsInt64* monitor_count_out = psub->valb; epicsFloat64* elaped_time_out = psub->valc; epicsUInt32* command_trig_out = psub->vald; epicsFloat64* prev_proton_rate_out = psub->vale; + epicsUInt32* is_low_rate_out = psub->valf; + epicsChar* msg_txt_out = &psub->valg; + + /* Always no matter what, handle the rate */ + /* - Calculate average rate */ + average_rate = (spc->proton_rate + spc->prev_proton_rate) / 2; + /* - Store current rate as previous rate */ + *prev_proton_rate_out = spc_int->proton_rate; + + if (average_rate < spc->threshold) { + *is_low_rate_out = 0; /* Illogical but 0 is low_rate, 1 high rate */ + } else { + *is_low_rate_out = 1; /* Illogical but 0 is low_rate, 1 high rate */ + } /* Handle noop situations both valid and invalid */ - if (handle_noop(&spc_int, &exit_status)) - return exit_status; + if (handle_noop(spc, &msg_txt_out)) + /* We have state that prohibits further processing */ + return 0; - if (spc_int->command_trig == FULL_RESET) { + + /* Commands with priority always yielding IDLE status first */ + if (spc->command_trig == FULL_RESET) { + 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; - } - - if ((spc_int->status == COUNTING || spc_int->status == LOW_RATE) && - spc_int->command_trig == PAUSE)) { - /* We are counting or at low rate and will pause */ + } 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; + return 0; + } else if (((spc->status == COUNTING || spc->status == LOW_RATE) + && spc->command_trig == PAUSE) || + (spc->status == PAUSED && spc->command_trig == NONE)) { + 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; - - } - - /* Determine if we are idle but received a count command */ - if (status == IDLE && - (command_trig == COUNT_PRESET || command_trig == TIME_PRESET)) { + *command_trig_out = NONE; + *monitor_count_out = spc->monitor_count; + *elapsed_time_out = spc->elapsed_time; + 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 ( command_trig != count_type ) { + if (command_trig != count_type) { if (softProtonDebug) printf("%s: Count type not stored!\n", funcstr); return -1; } /* Starting a new count - /* Reset counter and time */ + * 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; + } + return 0; + } else if ((((spc_int->status == LOW_RATE && spc_int->command_trig == NONE) || + (spc_int->status == LOW_RATE && spc_int->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_int->monitor_count; + *elapsed_time_out = spc_int->elapsed_time; + return 0 + } + + /* 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_int->monitor_count + + average_rate * SOFT_PROTON_SAMPLE_RATE; + *elapsed_time_out = spc_int->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; - *command_trig_out = NONE; - *prev_proton_rate_out = spc_int->proton_rate; - goto FINISH_ONGOING_COUNT; } - /* Resume from paused */ - if (spc_int->status == PAUSED && spc_int->command_trig == CONTINUE) { - *prev_proton_rate_out = spc_int->proton_rate; - *status_out = COUNTING; - *command_trig_out = NONE; - /* continue cycle doesn't increment the counter */ - goto FINISH_ONGOING_COUNT; - } - - /* Counting */ - if (spc_int->status == COUNTING && spc_int->command_trig == NONE) { - /* Take the average proton rate of the sampling period */ - *monitor_count_out = - (spc_int->proton_rate + spc_int->prev_proton_rate) / 2; - *elapsed_time_out += soft_proton_sample_rate; - *command_trig_out = NONE; - *prev_proton_rate_out = spc_int->proton_rate; - } - - /* Special cases */ - - /* Count finished normally */ + /* Check if count is finished normally. + * Higher priority than low rate */ if (/* Time based count finished */ (*elapsed_time_out >= spc_int->preset_time && spc_int->count_type = TIME_PRESET) || @@ -223,17 +267,9 @@ static long processEmulatedCounter(struct aSubRecord *psub) (*monitor_count_out >= spc_int->preset_time && spc_int->count_type = COUNT_PRESET)) { *status_out = IDLE; - return 0; } - FINISH_ONGOING_COUNT: - if (spc_int->proton_rate < spc_int->threshold) - *status_out = LOW_RATE; - else - *status_out = COUNTING; - return 0; } -epicsRegisterFunction(initEmulatedCounter); epicsRegisterFunction(processEmulatedCounter);