commit d612f5c5652d007319a1e8bc08aad80edce2c8d1 Author: Edward Wall Date: Fri Oct 18 13:18:43 2024 +0200 Draft Stream Device Rewrite of Counterbox Driver diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..67fb9e0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,236 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..29015c8 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,49 @@ +default: + image: docker.psi.ch:5000/wall_e/sinqepics:latest + +stages: + - test + - build + +cppcheck: + stage: test + script: + - cppcheck --std=c++17 --addon=cert --addon=misc --error-exitcode=1 src/*.cpp + artifacts: + expire_in: 1 week + tags: + - docker + +formatting: + stage: test + script: + - clang-format --style=file --Werror --dry-run src/*.cpp + artifacts: + expire_in: 1 week + tags: + - docker + +# clangtidy: +# stage: test +# script: +# - curl https://docker.psi.ch:5000/v2/_catalog +# # - dnf update -y +# # - dnf install -y clang-tools-extra +# # - clang-tidy sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h -checks=cppcoreguidelines-*,cert-* +# # tags: +# # - docker + +build_module: + stage: build + script: + - sed -i 's/ARCH_FILTER=.*/ARCH_FILTER=linux%/' Makefile + - make install + - cp -rT "/ioc/modules/counterbox/$(ls -U /ioc/modules/counterbox/ | head -1)" "./counterbox-${CI_COMMIT_SHORT_SHA}" + artifacts: + name: "counterbox-${CI_COMMIT_SHORT_SHA}" + paths: + - "counterbox-${CI_COMMIT_SHORT_SHA}/*" + expire_in: 1 week + when: always + tags: + - docker diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..62ca49f --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +# This build the sinq extensions for the PSI EPICS setup +include /ioc/tools/driver.makefile + +MODULE=counterbox +BUILDCLASSES=Linux +EPICS_VERSIONS=7.0.7 +ARCH_FILTER=RHEL% + +# additional module dependencies +REQUIRED+=stream +REQUIRED+=calc + +# Release version +LIBVERSION=ed-dev + +# DB files to include in the release +TEMPLATES += db/counterbox.db +TEMPLATES += db/counterbox.proto +TEMPLATES += db/counterbox_common.db +TEMPLATES += db/counterbox_v2.db + +# DBD files to include in the release +DBDS += src/counterbox.dbd + +# Source files to build +SOURCES += src/counterbox.cpp + +SCRIPTS += scripts/counterbox.cmd +SCRIPTS += scripts/counterbox_v2.cmd + +USR_CFLAGS += -Wall -Wextra #-Werror + +# MISCS would be the place to keep the stream device template files diff --git a/db/counterbox.db b/db/counterbox.db new file mode 100644 index 0000000..06ce6c7 --- /dev/null +++ b/db/counterbox.db @@ -0,0 +1,46 @@ +# 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") +} diff --git a/db/counterbox.proto b/db/counterbox.proto new file mode 100644 index 0000000..6f7d8d3 --- /dev/null +++ b/db/counterbox.proto @@ -0,0 +1,128 @@ +# +# 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";} +} diff --git a/db/counterbox_common.db b/db/counterbox_common.db new file mode 100644 index 0000000..29d1f58 --- /dev/null +++ b/db/counterbox_common.db @@ -0,0 +1,259 @@ +# 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") +# } diff --git a/db/counterbox_v2.db b/db/counterbox_v2.db new file mode 100644 index 0000000..665eb17 --- /dev/null +++ b/db/counterbox_v2.db @@ -0,0 +1,88 @@ +# 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) +} diff --git a/scripts/counterbox.cmd b/scripts/counterbox.cmd new file mode 100644 index 0000000..606cb1e --- /dev/null +++ b/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/scripts/counterbox_v2.cmd b/scripts/counterbox_v2.cmd new file mode 100644 index 0000000..d9df1eb --- /dev/null +++ b/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/src/counterbox.cpp b/src/counterbox.cpp new file mode 100644 index 0000000..591fea3 --- /dev/null +++ b/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/src/counterbox.dbd b/src/counterbox.dbd new file mode 100644 index 0000000..4ef690d --- /dev/null +++ b/src/counterbox.dbd @@ -0,0 +1,5 @@ +#--------------------------------------------- +# Counterbox specific DB definitions +#--------------------------------------------- + +device(stringin,INST_IO,devCounterBoxStringError,"devCounterBoxStringError")