Draft Stream Device Rewrite of Counterbox Driver
This commit is contained in:
236
.clang-format
Normal file
236
.clang-format
Normal file
@ -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
|
||||||
|
...
|
||||||
|
|
49
.gitlab-ci.yml
Normal file
49
.gitlab-ci.yml
Normal file
@ -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
|
33
Makefile
Normal file
33
Makefile
Normal file
@ -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
|
46
db/counterbox.db
Normal file
46
db/counterbox.db
Normal file
@ -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")
|
||||||
|
}
|
128
db/counterbox.proto
Normal file
128
db/counterbox.proto
Normal file
@ -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";}
|
||||||
|
}
|
259
db/counterbox_common.db
Normal file
259
db/counterbox_common.db
Normal file
@ -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")
|
||||||
|
# }
|
88
db/counterbox_v2.db
Normal file
88
db/counterbox_v2.db
Normal file
@ -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)
|
||||||
|
}
|
13
scripts/counterbox.cmd
Normal file
13
scripts/counterbox.cmd
Normal file
@ -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)")
|
13
scripts/counterbox_v2.cmd
Normal file
13
scripts/counterbox_v2.cmd
Normal file
@ -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)")
|
60
src/counterbox.cpp
Normal file
60
src/counterbox.cpp
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#include <epicsExport.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <stringinRecord.h>
|
||||||
|
|
||||||
|
#define update_val(prec, new_val) \
|
||||||
|
{ \
|
||||||
|
static_assert(std::char_traits<char>::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);
|
5
src/counterbox.dbd
Normal file
5
src/counterbox.dbd
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#---------------------------------------------
|
||||||
|
# Counterbox specific DB definitions
|
||||||
|
#---------------------------------------------
|
||||||
|
|
||||||
|
device(stringin,INST_IO,devCounterBoxStringError,"devCounterBoxStringError")
|
Reference in New Issue
Block a user