From af61358351aa09ab814f4ec8ad9df41a0955c4b0 Mon Sep 17 00:00:00 2001 From: Edward Wall Date: Thu, 17 Oct 2024 17:09:49 +0200 Subject: [PATCH] SINQSW-107 draft streamdevice based counterbox driver --- Makefile.RHEL8 | 25 ++- sinqEPICSApp/Db/counterbox.db | 39 +++++ sinqEPICSApp/Db/counterbox.proto | 106 +++++++++++++ sinqEPICSApp/Db/counterbox_common.db | 211 +++++++++++++++++++++++++ sinqEPICSApp/Db/counterbox_v2.db | 64 ++++++++ sinqEPICSApp/scripts/counterbox.cmd | 13 ++ sinqEPICSApp/scripts/counterbox_v2.cmd | 13 ++ sinqEPICSApp/src/CounterBox.cpp | 60 +++++++ sinqEPICSApp/src/Makefile | 2 +- sinqEPICSApp/src/sinq.dbd | 1 + 10 files changed, 525 insertions(+), 9 deletions(-) create mode 100644 sinqEPICSApp/Db/counterbox.db create mode 100644 sinqEPICSApp/Db/counterbox.proto create mode 100644 sinqEPICSApp/Db/counterbox_common.db create mode 100644 sinqEPICSApp/Db/counterbox_v2.db create mode 100644 sinqEPICSApp/scripts/counterbox.cmd create mode 100644 sinqEPICSApp/scripts/counterbox_v2.cmd create mode 100644 sinqEPICSApp/src/CounterBox.cpp diff --git a/Makefile.RHEL8 b/Makefile.RHEL8 index e3d02c2..cbd595e 100644 --- a/Makefile.RHEL8 +++ b/Makefile.RHEL8 @@ -11,11 +11,16 @@ REQUIRED+=SynApps REQUIRED+=stream REQUIRED+=scaler REQUIRED+=asynMotor +REQUIRED+=calc # Release version -LIBVERSION=2024-v2 +LIBVERSION=2024-ed-dev # DB files to include in the release +TEMPLATES += sinqEPICSApp/Db/counterbox_common.db +TEMPLATES += sinqEPICSApp/Db/counterbox.db +TEMPLATES += sinqEPICSApp/Db/counterbox_v2.db +TEMPLATES += sinqEPICSApp/Db/counterbox.proto TEMPLATES += sinqEPICSApp/Db/dimetix.db TEMPLATES += sinqEPICSApp/Db/slsvme.db TEMPLATES += sinqEPICSApp/Db/spsamor.db @@ -24,18 +29,22 @@ TEMPLATES += sinqEPICSApp/Db/spsamor.db DBDS += sinqEPICSApp/src/sinq.dbd # Source files to build -SOURCES += sinqEPICSApp/src/devScalerEL737.c -SOURCES += sinqEPICSApp/src/SINQController.cpp -SOURCES += sinqEPICSApp/src/SINQAxis.cpp SOURCES += sinqEPICSApp/src/EL734Driver.cpp -SOURCES += sinqEPICSApp/src/NanotecDriver.cpp -SOURCES += sinqEPICSApp/src/stptok.cpp -SOURCES += sinqEPICSApp/src/PhytronDriver.cpp SOURCES += sinqEPICSApp/src/EuroMoveDriver.cpp +SOURCES += sinqEPICSApp/src/MasterMACSDriver.cpp +SOURCES += sinqEPICSApp/src/NanotecDriver.cpp +SOURCES += sinqEPICSApp/src/PhytronDriver.cpp +SOURCES += sinqEPICSApp/src/SINQAxis.cpp +SOURCES += sinqEPICSApp/src/SINQController.cpp +SOURCES += sinqEPICSApp/src/devScalerEL737.c SOURCES += sinqEPICSApp/src/pmacAsynIPPort.c SOURCES += sinqEPICSApp/src/pmacAxis.cpp SOURCES += sinqEPICSApp/src/pmacController.cpp -SOURCES += sinqEPICSApp/src/MasterMACSDriver.cpp +SOURCES += sinqEPICSApp/src/stptok.cpp +SOURCES += sinqEPICSApp/src/CounterBox.cpp + +SCRIPTS += sinqEPICSApp/scripts/counterbox.cmd +SCRIPTS += sinqEPICSApp/scripts/counterbox_v2.cmd USR_CFLAGS += -Wall -Wextra # -Werror diff --git a/sinqEPICSApp/Db/counterbox.db b/sinqEPICSApp/Db/counterbox.db new file mode 100644 index 0000000..5b661ce --- /dev/null +++ b/sinqEPICSApp/Db/counterbox.db @@ -0,0 +1,39 @@ +# EL737 EPICS Database for streamdevice support +# Macros +# P - Prefix +# NAME - just a name, e.g. EL737 +# PROTO - Stream device protocol file +# ASYN_PORT - Low level Asyn IP Port to EL737 + +################################################################################ +# Status Variables + +record(longin, "$(P):$(NAME):MONITOR-CHANNEL") +{ + field(DESC, "PRESET-COUNT Monitors this channel") + field(VAL, 1) + field(DISP, 1) +} + +record(longin, "$(P):$(NAME):MONITOR-CHANNEL_RBV") +{ + field(DESC, "PRESET-COUNT Monitors this channel") + field(VAL, 1) + field(DISP, 1) +} + +################################################################################ +# Count Commands + + +################################################################################ +# Read all monitors values + +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") +} diff --git a/sinqEPICSApp/Db/counterbox.proto b/sinqEPICSApp/Db/counterbox.proto new file mode 100644 index 0000000..5ee1adf --- /dev/null +++ b/sinqEPICSApp/Db/counterbox.proto @@ -0,0 +1,106 @@ +# +# 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";} +} + +################################################################################ +# 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";} +} diff --git a/sinqEPICSApp/Db/counterbox_common.db b/sinqEPICSApp/Db/counterbox_common.db new file mode 100644 index 0000000..ae4a176 --- /dev/null +++ b/sinqEPICSApp/Db/counterbox_common.db @@ -0,0 +1,211 @@ +# 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") +} + +record(longout, "$(P):$(NAME):FULL-RESET") +{ + field(DESC, "Reset the Counterbox") + field(OUT, "@$(PROTO) fullReset($(P):$(NAME):) $(ASYN_PORT)") + field(DTYP, "stream") +} + +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") +} + +################################################################################ +# Status Variables + +record(stringin, "$(P):$(NAME):MsgTxt") +{ + field(DESC, "Unexpected received response") + field(DTYP, "devCounterBoxStringError") + field(FLNK, "$(P):$(NAME):INVALID-CONFIG") +} + +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(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(bi, "$(P):$(NAME):COUNTING") +{ + field(DESC, "Counterbox is Counting") + field(VAL, 0) +} + +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(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(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(PINI, "YES") + field(OUT, "@$(PROTO) stopCount($(P):$(NAME):) $(ASYN_PORT)") +} + +################################################################################ +# 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") +} + +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") +} diff --git a/sinqEPICSApp/Db/counterbox_v2.db b/sinqEPICSApp/Db/counterbox_v2.db new file mode 100644 index 0000000..c708eb3 --- /dev/null +++ b/sinqEPICSApp/Db/counterbox_v2.db @@ -0,0 +1,64 @@ +# 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") + field(PINI, "YES") +} + +################################################################################ +# Count Commands + + +################################################################################ +# 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 + +record(bo, "$(P):$(NAME):TESTGEN") +{ + field(DESC, "Turn on/off Testgen Signal") + field(DTYP, "stream") + field(OUT, "@$(PROTO) switchTestgenOnOff($(P):$(NAME):) $(ASYN_PORT)") + field(VAL, 0) + field(PINI, "YES") +} diff --git a/sinqEPICSApp/scripts/counterbox.cmd b/sinqEPICSApp/scripts/counterbox.cmd new file mode 100644 index 0000000..606cb1e --- /dev/null +++ b/sinqEPICSApp/scripts/counterbox.cmd @@ -0,0 +1,13 @@ +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)") diff --git a/sinqEPICSApp/scripts/counterbox_v2.cmd b/sinqEPICSApp/scripts/counterbox_v2.cmd new file mode 100644 index 0000000..d9df1eb --- /dev/null +++ b/sinqEPICSApp/scripts/counterbox_v2.cmd @@ -0,0 +1,13 @@ +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)") diff --git a/sinqEPICSApp/src/CounterBox.cpp b/sinqEPICSApp/src/CounterBox.cpp new file mode 100644 index 0000000..591fea3 --- /dev/null +++ b/sinqEPICSApp/src/CounterBox.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#define update_val(prec, new_val) \ + { \ + static_assert(std::char_traits::length((new_val)) < MAX_SIZE, \ + "New Val Exceeds Max String Length"); \ + strncpy((prec)->val, (new_val), MAX_SIZE); \ + } + +static long map_raw_failure_message(struct stringinRecord *prec) { + const uint8_t MAX_SIZE = 40; // including null terminator + + if (strcmp(prec->val, "?OF") == 0) { + update_val(prec, "?OF: Configuration Error"); + } else if (strcmp(prec->val, "?OV") == 0) { + update_val(prec, "?OV: Overflow"); + } else if (strcmp(prec->val, "?1") == 0) { + update_val(prec, "?1: Parameter out of range"); + } else if (strcmp(prec->val, "?2") == 0) { + update_val(prec, "?2: Bad command"); + } else if (strcmp(prec->val, "?3") == 0) { + update_val(prec, "?3: Bad parameter"); + } else if (strcmp(prec->val, "?4") == 0) { + update_val(prec, "?4: Bad counter"); + } else if (strcmp(prec->val, "?5") == 0) { + update_val(prec, "?5: Parameter missing"); + } else if (strcmp(prec->val, "?6") == 0) { + update_val(prec, "?6: Too many counts"); + } else if (strcmp(prec->val, "?91") == 0) { + update_val(prec, "?91: Start Failure"); + } else if (strcmp(prec->val, "?92") == 0) { + update_val(prec, "?92: Failure while counting"); + } else if (strcmp(prec->val, "?93") == 0) { + update_val(prec, "?93: Read Failure"); + } else if (strstr(prec->val, "?") != NULL) { + char val_copy[40] = {0}; + strncpy(val_copy, prec->val, MAX_SIZE); + snprintf(prec->val, MAX_SIZE - 1, "%s: HW error", val_copy); + } else { + // Leave the message as is + } + + return 0; // returns: (-1,0)=>(failure,success) +} + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_ai; + DEVSUPFUN special_linconv; +} devCounterBoxStringError = { + 6, NULL, NULL, NULL, NULL, (DEVSUPFUN)map_raw_failure_message, NULL}; + +epicsExportAddress(dset, devCounterBoxStringError); diff --git a/sinqEPICSApp/src/Makefile b/sinqEPICSApp/src/Makefile index fbe1eca..af0bf2f 100644 --- a/sinqEPICSApp/src/Makefile +++ b/sinqEPICSApp/src/Makefile @@ -29,7 +29,7 @@ sinqEPICS_SRCS += pmacController.cpp pmacAxis.cpp sinqEPICS_SRCS += NanotecDriver.cpp stptok.cpp sinqEPICS_SRCS += PhytronDriver.cpp sinqEPICS_SRCS += EuroMoveDriver.cpp - +sinqEPICS_SRCS += CounterBox.cpp # Build the main IOC entry point on workstation OSs. sinqEPICS_SRCS_DEFAULT += sinqEPICSMain.cpp diff --git a/sinqEPICSApp/src/sinq.dbd b/sinqEPICSApp/src/sinq.dbd index 3957538..2d5e3f3 100644 --- a/sinqEPICSApp/src/sinq.dbd +++ b/sinqEPICSApp/src/sinq.dbd @@ -20,6 +20,7 @@ registrar(SINQControllerRegister) #include "/ioc/modules/scaler/koennecke/R3.14.12/dbd/scaler.dbd" device(scaler,INST_IO,devScalerEL737,"asynScalerEL737") +device(stringin,INST_IO,devCounterBoxStringError,"devCounterBoxStringError") #--------- For lakeshore and magnets #include "/ioc/modules/stream/2.7.9/R3.14.12/dbd/stream.dbd"