24 Commits
0.1.0 ... 1.1.0

Author SHA1 Message Date
bc8561c299 Updated turboPmac version 2025-06-18 08:33:04 +02:00
cf6f836416 Updated turboPmac version 2025-06-17 16:51:08 +02:00
6e99b37ed2 Updated turboPmac version 2025-06-17 13:26:13 +02:00
f745af48fe Added destructor for detectorTowerController 2025-06-10 15:04:34 +02:00
0fd312b672 Updated dependency turboPmac to 1.1.1 2025-06-10 15:01:42 +02:00
c15e513b2e Updated turboPmac to 1.1.1 2025-06-10 15:00:33 +02:00
6f9b890374 Updated turboPmac to 1.1 2025-06-10 14:44:59 +02:00
fbbe8c4553 Updated turboPmac to 1.1 2025-06-10 14:22:15 +02:00
675819741b Updated turboPmac version 2025-06-10 13:57:57 +02:00
eb3826ec35 Stop or reset now also resets the deferred movement flags 2025-06-10 11:15:40 +02:00
5dd5a26243 Removed unnecessary code (bug in sinqMotor has been found) 2025-05-16 16:33:49 +02:00
832884179c Updated dependencies sinqMotor and turboPmac both to 0.15.2 2025-05-16 16:17:36 +02:00
94edef6cd8 New error message always results in status problem
Previously, a new error message did not automatically result in a status
problem being shown. This patch fixes that.
2025-05-16 11:29:38 +02:00
afdd66a648 Adjusted link to turboPmac repository 2025-05-15 17:18:39 +02:00
74dedd9cd3 Finished up documentation 2025-05-15 16:43:04 +02:00
4765b59ee5 Updated dependency turboPmac to 0.15.1 2025-05-15 14:56:56 +02:00
16ee667963 Overwrote reset from sinqAxis with doReset 2025-05-15 14:30:41 +02:00
8bfaf7eb6f Updated turboPmac dependency to 0.15.0 2025-05-15 13:57:47 +02:00
fd93c36639 Fixed some small errors in the function docstrings 2025-05-15 11:48:42 +02:00
926df04caf Updated README.md 2025-05-15 11:47:19 +02:00
d575d4fe76 Make sure non-persistent error messages are shown for one poll cycle 2025-05-14 16:44:30 +02:00
203bb9475f Updated turboPmac dependency to 0.15.1 2025-05-14 16:39:16 +02:00
60960dde44 Changed dependencies to static linking. 2025-05-12 17:10:04 +02:00
d837a8545b Adjusted driver with auxiliary axes offsets 2025-04-17 16:30:33 +02:00
20 changed files with 3279 additions and 1717 deletions

246
.clang-format Normal file
View File

@ -0,0 +1,246 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: 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
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
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
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
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
PenaltyBreakScopeResolution: 500
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
SkipMacroDefinitionBody: false
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
AfterPlacementOperator: true
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: 8
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "turboPmac"]
path = turboPmac
url = https://gitea.psi.ch/lin-epics-modules/turboPmac

View File

@ -9,26 +9,37 @@ ARCH_FILTER=RHEL%
# Additional module dependencies
REQUIRED+=motorBase
# Specify the version of motorBase we want to build against
# Specify the version of motorBase we want to dynamically link against
motorBase_VERSION=7.2.2
# Specify the version of turboPmac we want to build against
turboPmac_VERSION=0.10.0
# These headers allow to depend on this library for derived drivers.
HEADERS += src/auxiliaryAxis.h
HEADERS += src/detectorTowerAxis.h
HEADERS += src/detectorTowerAngleAxis.h
HEADERS += src/detectorTowerController.h
HEADERS += src/detectorTowerLiftAxis.h
HEADERS += src/detectorTowerSupportAxis.h
# Source files to build
SOURCES += src/auxiliaryAxis.cpp
SOURCES += src/detectorTowerAxis.cpp
SOURCES += turboPmac/sinqMotor/src/msgPrintControl.cpp
SOURCES += turboPmac/sinqMotor/src/sinqAxis.cpp
SOURCES += turboPmac/sinqMotor/src/sinqController.cpp
SOURCES += turboPmac/src/pmacAsynIPPort.c
SOURCES += turboPmac/src/turboPmacAxis.cpp
SOURCES += turboPmac/src/turboPmacController.cpp
SOURCES += turboPmac/src/pmacAsynIPPort.c
SOURCES += src/detectorTowerAngleAxis.cpp
SOURCES += src/detectorTowerController.cpp
SOURCES += src/detectorTowerLiftAxis.cpp
SOURCES += src/detectorTowerSupportAxis.cpp
# Store the record files
TEMPLATES += turboPmac/sinqMotor/db/asynRecord.db
TEMPLATES += turboPmac/sinqMotor/db/sinqMotor.db
TEMPLATES += turboPmac/db/turboPmac.db
TEMPLATES += db/detectorTower.db
# This file registers the motor-specific functions in the IOC shell.
DBDS += turboPmac/sinqMotor/src/sinqMotor.dbd
DBDS += turboPmac/src/turboPmac.dbd
DBDS += src/detectorTower.dbd
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Wextra -Werror # -Wpedantic // Does not work because EPICS macros trigger warnings

View File

@ -4,31 +4,50 @@
## Overview
This is a driver for the detector tower which is based on the Turbo PMAC driver (https://git.psi.ch/sinq-epics-modules/turboPmac). It consists of the following three objects:
- `detectorTowerAxis`: This is a virtual axis which controls multiple physical motors in order to provide a synchronized movement.
- `detectorTowerController`: This is an expanded variant of `turboPmacController` provided by the Turbo PMAC library linked above.It is needed to operate a `detectorTowerAxis`, but it can also be used to operate a "normal" `turboPmacAxis`.
- `auxiliaryAxis`: This is an auxiliary axis type attached to the main detector axis. Multiple instances of these axes are constructed automatically when creating a `detectorTowerAxis`.
The header files contain detailed documentation for all public functions. The headers themselves are exported when building the library to allow other drivers to depend on this one.
![Coordinate systems](images/CoordinateSystems.svg)
This is a driver for the detector tower which is based on the Turbo PMAC driver (https://gitea.psi.ch/lin-epics-modules/turboPmac). It consists of the following four objects:
- `detectorTowerController`: This is an expanded variant of `turboPmacController` provided by the Turbo PMAC library linked above.It is needed to operate a `detectorTowerAngleAxis`, but it can also be used to operate a "normal" `turboPmacAxis`.
- `detectorTowerAngleAxis`: This is a virtual axis which controls multiple physical motors ($x$ and $z$) in order to provide a combined movement. Moving it results in a rotation of the entire beam around the support axis position ($\alpha$).
- `detectorTowerLiftAxis`: This is a virtual axis which controls multiple physical motors in order to provide a combined movement. Moving it results in a vertical lift ($z_{lift}$).
- `detectorTowerSupportAxis`: This is an axis at the rotation center of the beam which is part of the combined movements. Its origin can be shifted manually for small adjustments, resulting in a corresponding movement. Other than that, it is not meant to move on its own, hence setting a new value to the `VAL` field of the motor record won't cause it to move.
## User guide
The centerpiece of this driver is the `detectorTowerAxis`, which controls the angle of the detector flight tube. Creating an instance of this axis type also creates multiple so-called `auxiliaryAxis`, which are used to perform secondary movements.
The centerpiece of this driver is the `detectorTowerAngleAxis`, which controls the angle of the detector flight tube. It is supported by two secondary axes, `detectorTowerLiftAxis` and `detectorTowerSupportAxis`. All three axes are created by a single IOC shell command `detectorTowerAxis` (see [Usage in IOC shell](#usage-in-ioc-shell)). All three axes use absolute encoders and therefore cannot perform a reference / home drive.
The utilities provided in the `utils` folder of https://git.psi.ch/sinq-epics-modules/turboPmac work with this driver as well.
The first two axes can be moved independently from each other or together as a combined movement by issuing both move orders within a certain time span. This time span can be configured via the IOC shell function `setDeferredMovementWait` and defaults to 100 ms. This allows the user to start a combined movement e.g. via caput:
```
caput $(ControllerPV):angle 2 & $(ControllerPV):caput lift 10
```
which moves the angle to 2 degree and shifts the entire beam vertically by 10 mm. When using the axis from NICOS, using the `maw` or `move` command with multiple devices has the same effect:
```
move("angle", 2, "lift", 10)
```
If one axis is already moving, no new move command can be issued until the movement is finished. The `detectorTowerSupportAxis` cannot be moved directly.
It is possible to shift the origin of all three axes (and therefore also moving the `detectorTowerSupportAxis`) for small adjustments. The current origin position in the axis unit (degree for `detectorTowerAngleAxis`, mm for `detectorTowerLiftAxis` and encoder steps for `detectorTowerSupportAxis`) can be read from the PV `$(INSTR)$(M):Origin`. The origin can be shifted by the amount of axis units written to `$(INSTR)$(M):AdjustOrigin`. Since this is a relative movement, writing "10" two times to `$(INSTR)$(M):AdjustOrigin` shifts the origin by 20 axis units. Therefore, the limits are only there to prevent too large shifts during a single write (since the origin can be shifted to any position by writing to `$(INSTR)$(M):AdjustOrigin` repeatedly).
The detector tower can be moved into a so-called "changer position" by writing "1" to the PV `$(INSTR)$(M):ChangeState`. This causes the entire tower to move vertically down. When it is in the changer position, the axes cannot be moved at all. In order to go back to the "working state" where the axes can be moved again, write "0" to `$(INSTR)$(M):ChangeState`. The current state of the axis can be read out from `$(INSTR)$(M):ChangingStateRBV`. In case starting a change movement succeeds, `$(INSTR)$(M):ChangeStateRBV` changes its value accordingly, otherwise it stays at its current value.
The utilities provided in the `utils` folder in `turboPmac/utils` work with this driver as well.
### Usage in IOC shell
detectorTower exports the following IOC shell functions:
- `detectorTowerController`: Create a new controller object.
- `detectorTowerAxis`: Create a new axis object.
- `detectorTowerAxis`: Create a `detectorTowerAngleAxis`, a `detectorTowerLiftAxis` and a `detectorTowerSupportAxis` object and link them to each other
The constructor function for `detectorTowerAxis` has the following syntax:
```
detectorTowerAxis("$(NAME)",1, 2, 3)
detectorTowerAngleAxis("$(NAME)",1, 2, 3)
```
with 1 being the axis number / index of the detector flight tube angle axis, 2 being the lift offset axis and 3 being the tilt offset axis. These axes are parametrized in the same way as any "normal" axes via a substitution file (see corresponding section below).
with 1 being the axis number / index of the `detectorTowerAngleAxis`, 2 being the `detectorTowerLiftAxis` and 3 being the `detectorTowerSupportAxis`. These axes are parametrized in the same way as any "normal" axes via a substitution file (see corresponding section below).
"Normal" `turboPmacAxis` may be used together with this controller:
@ -48,8 +67,8 @@ drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025")
# 1: Socket communication timeout in seconds
detectorTowerController("$(NAME)", "$(ASYN_PORT)", 8, 0.05, 1, 1);
# Slot 1, 2 and 3 are occupied by a detector tower axis and its attached auxiliary axes, while the slots 4 and 5 are "normal" Turbo PMAC axes.
detectorTowerAxis("$(NAME)",1, 2, 3);
# Slot 1, 2 and 3 are occupied by the three detector tower axis, while the slots 4 and 5 are "normal" Turbo PMAC axes.
detectorTowerAngleAxis("$(NAME)",1, 2, 3);
turboPmacAxis("$(NAME)",4);
turboPmacAxis("$(NAME)",5);
@ -61,9 +80,9 @@ setThresholdComTimeout("$(NAME)", 100, 1);
# Parametrize the EPICS record database with the substitution file named after the MCU.
# Since this driver is based on Turbo PMAC, we need to parametrize turboPmac.db in addition to sinqMotor.db and detectorTower.db.
epicsEnvSet("SINQDBPATH","$(sinqMotor_DB)/sinqMotor.db")
epicsEnvSet("SINQDBPATH","$(detectorTower_DB)/sinqMotor.db")
dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
epicsEnvSet("SINQDBPATH","$(turboPmac_DB)/turboPmac.db")
epicsEnvSet("SINQDBPATH","$(detectorTower_DB)/turboPmac.db")
dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
epicsEnvSet("SINQDBPATH","$(detectorTower_DB)/detectorTower.db")
dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
@ -74,7 +93,7 @@ dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_POR
From the perspective of EPICS, the main detector flight tube axis and the auxiliary axes are independent axes and therefore each axis needs its own parametrization in the substitution file. If additional axes are used in the axis, they are parametrized as usual:
```
detectorTowerAxis("$(NAME)",1, 2, 3);
detectorTowerAngleAxis("$(NAME)",1, 2, 3);
turboPmacAxis("$(NAME)",4);
turboPmacAxis("$(NAME)",5);
```
@ -83,9 +102,9 @@ file "$(SINQDBPATH)"
{
pattern
{ AXIS, M, DESC, EGU, DIR, MRES, MSGTEXTSIZE, ENABLEMOVWATCHDOG, LIMITSOFFSET, CANSETSPEED }
{ 1, "tower", "Angle of the beam guide", degree, Pos, 0.001, 200, 0, 1.0, 0 }
{ 2, "liftZeroCorr", "Detector vertical lift offset", mm, Pos, 0.001, 200, 0, 1.0, 0 }
{ 3, "tiltZeroCorr", "Detector tilt offset", degree, Pos, 0.001, 200, 0, 1.0, 0 }
{ 1, "angle", "Angle of the beam guide", degree, Pos, 0.001, 200, 0, 1.0, 0 }
{ 2, "lift", "Detector vertical lift offset", mm, Pos, 0.001, 200, 0, 1.0, 0 }
{ 3, "suppoer", "Support axis", mm, Pos, 0.001, 200, 0, 1.0, 0 }
{ 4, "other axis A", "A normal axis", degree, Pos, 0.001, 200, 1, 2.0, 1 }
{ 5, "other axis B", "Another normal axis", degree, Pos, 0.001, 200, 1, 2.0, 0 }
}
@ -97,23 +116,11 @@ Note that the speed of the detector tower axes 1, 2 and 3 cannot be set. Setting
### Code architecture
The code is designed around the `detectorTowerAxis`, which controls the angle of the beam guide and acts as a "master" axis. Other movements are realized as auxiliary axes (e.g. for the vertical lift offset and the tilt offset). The `detectorTowerAxis` has pointers to all associated auxiliary axes and (as described above) its IOC shell constructor also builds the associated auxiliary axes.
The code is designed around the `detectorTowerAngleAxis`, which controls the angle of the beam guide and acts as a "master" axis which contains pointers to its attached `detectorTowerLiftAxis` and `detectorTowerSupportAxis`. All three axes are polled at once via the function `detectorTowerController::pollDetectorAxes`, which is called from the individual `poll` functions of the axes (the `doPoll` mechanism from `sinqMotor` is not used). To avoid polling the axes multiple times during one controller cycle, the function `detectorTowerController::pollDetectorAxes` is only executed for the axis with the smallest index. Since this axis is polled first, the other two axes are therefore already up-to-date when they execute their own poll function. If one of the axes is moving, all three axes are marked as moving. The same is true for errors.
The `doPoll` implementation for `detectorTowerAxis` queries the status of both the `detectorTowerAxis` itself and all associated auxiliary axes. If any of the axes is moving, the `detectorTowerAxis` is set to "moving" as well. In turn, the `doPoll` implementation of a `auxiliaryAxis` checks if its associated `detectorTowerAxis` is moving and sets its own movement status accordingly.
In order to save on movement time, movement commands to auxiliary axes and the `detectorTowerAngleAxis` are collected and then send as a single resulting movement command to the MCU. In order to do so, a "collector" thread is running which checks if a movement request has been send to one of the axes. If that is the case, it waits for `detectorTowerAngleAxis->deferredMovementWait_` seconds and checks if commands for other axes are given as well. Then, it calls `detectorTowerAngleAxis::startCombinedMove` which combines all commands to a single request which is sent to the MCU. `detectorTowerAngleAxis->deferredMovementWait_` can be set with the IOC shell function `setDeferredMovementWait` and defaults to 100 ms.
The `detectorTowerController` is a thin wrapper around a `turboPmacController` which overwrites the `readInt32` and `writeInt32` in order to support the PVs "$(INSTR)$(M):ChangeState", "$(INSTR)$(M):PositionStateRBV" and "$(INSTR)$(M):ChangingStateRBV". Any calls to these two methods not concerning the aforementioned PVs are directly forwarded to `turboPmacController::readInt32` / `turboPmacController::writeInt32`.
In order to save on movement time, movement commands to auxiliary axes and the `detectorTowerAxis` are collected and then send as a single resulting movement command to the MCU. In order to do so, a "collector" thread is running which checks if a movement request has been send to one of the axes. If that is the case, it waits for some time and checks if commands for other axes are given as well. Then, it calls `detectorTowerAxis::startCombinedMove` which combines all commands to a single request which is sent to the MCU. This allows the user to start a combined movement e.g. via caput:
```
caput tower 2 & caput liftZeroCorr 10
```
When using the axis from NICOS, using the `maw` or `move` command with multiple devices has the same effect:
```
move("tower", 2, "liftZeroCorr", 10)
```
The `detectorTowerController` is a thin wrapper around a `turboPmacController` which overwrites the `readInt32`, `writeInt32` and `writeFloat64` methods in order to support the additional PVs defined in `db/detectorTower.db`. Any calls to these two methods not concerning the aforementioned PVs are directly forwarded to `turboPmacController::readInt32` / `turboPmacController::writeInt32`.
### Versioning
@ -121,4 +128,4 @@ Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-e
### How to build it
Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md.
This driver can be compiled and installed by running `make install` from the same directory where the Makefile is located. However, since it uses the git submodule turboPmac, make sure that the correct version of the submodule repository is checked out AND the change is commited (`git status` shows no non-committed changes). Please see the section "Usage as static dependency" in https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md for more details.

View File

@ -1,12 +1,3 @@
# Set to 0 to move the tower into working state and to 1 to move into changer position
record(longout, "$(INSTR)$(M):ChangeState") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) CHANGE_STATE")
field(PINI, "NO")
field(DRVH, "1")
field(DRVL, "0")
}
# Read the position state of the axis:
# 0 = not ready
# 1 = ready (in working position)
@ -20,19 +11,122 @@ record(longin, "$(INSTR)$(M):PositionStateRBV")
field(PINI, "NO")
}
# Set to 0 to move the tower into working state and to 1 to move into changer position.
# The PV "$(INSTR)$(M):ChangeStateRBV" can be used to check if starting the corresponding
# movement was successfull.
record(longout, "$(INSTR)$(M):ChangeState") {
field(DTYP, "asynInt32")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) CHANGE_STATE")
field(PINI, "NO")
field(DRVH, "1")
field(DRVL, "0")
}
# When a value is written to the PV "$(INSTR)$(M):ChangeState", the tower needs to be
# idle (PositionState = 0), otherwise the state will not be changed. If the value
# of this PV is equal to "$(INSTR)$(M):ChangeState", starting a state change was
# successfull, otherwise not.
record(longin, "$(INSTR)$(M):ChangeStateRBV")
{
field(DTYP, "asynInt32")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) CHANGE_STATE_RBV")
field(SCAN, "I/O Intr")
field(PINI, "NO")
}
# This PV combines the position state and the movement readback value from the
# motor record:
# - 0, if the tower is in working state or transitioning to change position
# - 1, If the tower is in change position or transitioning to working state
record(calc, "$(INSTR)$(M):ChangingStateRBV")
{
field(INPA, "$(INSTR)$(M).MOVN CP")
field(INPB, "$(INSTR)$(M):PositionStateRBV CP")
field(CALC, "(B == 1 || (B == 2 && A == 1)) ? 0 : 1")
field(INPA, "$(INSTR)$(M):PositionStateRBV CP")
field(INPB, "$(INSTR)$(M).MOVN CP")
field(CALC, "((A == 2 && B == 0) || A == 3) ? 1 : 0")
}
# Convert the double value from the calc record to an integer value
# Convert the double value from the calc record to an integer value.
record(longout, "$(INSTR)$(M):ChangingStateRBV_int") {
field(DOL, "$(INSTR)$(M):ChangingStateRBV CP")
field(OMSL, "closed_loop")
}
# Read out the origin of the corresponding axis by the given value.
# This PV does nothing for "normal" Turbo PMAC axes.
record(ai, "$(INSTR)$(M):Origin") {
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_ORIGIN")
field(SCAN, "I/O Intr")
field(PINI, "NO")
field(VAL, "0")
}
# Shift the origin of the corresponding axis by the given value. The axis will move to this
# position and the hardware controller will internally set this position as the
# new "0" value. This PV does nothing for "normal" Turbo PMAC axes.
record(ao, "$(INSTR)$(M):AdjustOrigin") {
field(DTYP, "Raw Soft Channel")
field(PINI, "NO")
field(FLNK, "$(INSTR)$(M):ResetAO")
field(VAL, "0")
}
# Reset the PV $(INSTR)$(M):AdjustOrigin to zero after it has been written to and
# forward the value to $(INSTR)$(M):WriteAO which does the actual writing.
record(seq, "$(INSTR)$(M):ResetAO") {
field(DESC, "Write value to hardware then reset to 0")
field(DOL1, "$(INSTR)$(M):AdjustOrigin")
field(LNK1, "$(INSTR)$(M):WriteAO.VAL PP") # Perform write to hardware
field(DOL2, "0.0")
field(LNK2, "$(INSTR)$(M):AdjustOrigin.VAL PP") # Reset to zero
}
# This record forwards the adjustment of the origin to the asyn driver.
record(ao, "$(INSTR)$(M):WriteAO") {
field(DTYP, "asynFloat64")
field(OUT, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ADJUST_ORIGIN")
field(PINI, "NO")
field(VAL, "0")
}
# This record pair reads the parameter library value for "AdjustOriginHighLimitFromDriver_"
# and pushes it to the high limit field of the "$(INSTR)$(M):AdjustOrigin" PV.
# This can be used to read limits from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via AdjustOriginHighLimitFromDriver_ -> MOTOR_AOHL_FROM_DRIVER.
record(ai, "$(INSTR)$(M):AOHL_RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_AOHL_FROM_DRIVER")
field(SCAN, "I/O Intr")
field(FLNK, "$(INSTR)$(M):PushAOHL2Field")
field(PINI, "NO")
}
# Abbreviated name because EPICS apparently has a maximum record name length
record(ao, "$(INSTR)$(M):PushAOHL2Field") {
field(DOL, "$(INSTR)$(M):AOHL_RBV NPP")
field(OUT, "$(INSTR)$(M):AdjustOrigin.DRVH")
field(OMSL, "closed_loop")
field(PINI, "NO")
}
# This record pair reads the parameter library value for "motorLowLimitFromDriver_"
# and pushes it to the low limit field of the "$(INSTR)$(M):AdjustOrigin" PV.
# This can be used to read limits from the hardware and correspondingly update the motor record from the driver.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via AdjustOriginLowLimitFromDriver_ -> MOTOR_AOLL_FROM_DRIVER.
record(ai, "$(INSTR)$(M):AOLL_RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_AOLL_FROM_DRIVER")
field(SCAN, "I/O Intr")
field(FLNK, "$(INSTR)$(M):PushAOLL2Field")
field(PINI, "NO")
}
# Abbreviated name because EPICS apparently has a maximum record name length
record(ao, "$(INSTR)$(M):PushAOLL2Field") {
field(DOL, "$(INSTR)$(M):AOLL_RBV NPP")
field(OUT, "$(INSTR)$(M):AdjustOrigin.DRVL")
field(OMSL, "closed_loop")
field(PINI, "NO")
}

Binary file not shown.

View File

@ -0,0 +1,447 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.2" width="215.9mm" height="80mm" viewBox="0 0 21590 8000" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
<defs class="ClipPathGroup">
<clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
<rect x="0" y="0" width="21590" height="8000"/>
</clipPath>
<clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
<rect x="21" y="8" width="21547" height="7984"/>
</clipPath>
</defs>
<defs>
<font id="EmbeddedFont_1" horiz-adv-x="2048">
<font-face font-family="Liberation Sans embedded" units-per-em="2048" font-weight="normal" font-style="normal" ascent="1852" descent="423"/>
<missing-glyph horiz-adv-x="2048" d="M 0,0 L 2047,0 2047,2047 0,2047 0,0 Z"/>
<glyph unicode="α" horiz-adv-x="1060" d="M 843,237 C 801,146 751,81 693,41 634,0 565,-20 484,-20 347,-20 247,26 183,118 118,210 86,349 86,536 86,725 122,867 195,961 267,1055 373,1102 512,1102 596,1102 668,1080 728,1035 787,990 832,928 862,847 L 864,847 C 879,926 904,1005 937,1082 L 1125,1082 C 1092,1011 1061,925 1033,823 1005,720 988,636 983,571 986,464 998,360 1018,257 1037,154 1063,69 1094,0 L 911,0 C 895,41 881,85 870,133 858,180 850,215 847,237 L 843,237 Z M 275,542 C 275,394 294,287 332,220 370,153 433,119 521,119 601,119 669,158 724,235 779,312 815,415 832,546 813,687 778,792 727,863 676,934 611,969 532,969 441,969 376,936 336,869 295,802 275,693 275,542 Z"/>
<glyph unicode="z" horiz-adv-x="848" d="M 83,0 L 83,137 688,943 117,943 117,1082 901,1082 901,945 295,139 922,139 922,0 83,0 Z"/>
<glyph unicode="x" horiz-adv-x="1006" d="M 801,0 L 510,444 217,0 23,0 408,556 41,1082 240,1082 510,661 778,1082 979,1082 612,558 1002,0 801,0 Z"/>
<glyph unicode="u" horiz-adv-x="874" d="M 314,1082 L 314,396 C 314,325 321,269 335,230 349,191 371,162 402,145 433,128 478,119 537,119 624,119 692,149 742,208 792,267 817,350 817,455 L 817,1082 997,1082 997,231 C 997,105 999,28 1003,0 L 833,0 C 832,3 832,12 831,27 830,42 830,59 829,78 828,97 826,132 825,185 L 822,185 C 781,110 733,58 679,27 624,-4 557,-20 476,-20 357,-20 271,10 216,69 161,128 133,225 133,361 L 133,1082 314,1082 Z"/>
<glyph unicode="t" horiz-adv-x="531" d="M 554,8 C 495,-8 434,-16 372,-16 228,-16 156,66 156,229 L 156,951 31,951 31,1082 163,1082 216,1324 336,1324 336,1082 536,1082 536,951 336,951 336,268 C 336,216 345,180 362,159 379,138 408,127 450,127 474,127 509,132 554,141 L 554,8 Z"/>
<glyph unicode="s" horiz-adv-x="901" d="M 950,299 C 950,197 912,118 835,63 758,8 650,-20 511,-20 376,-20 273,2 200,47 127,91 79,160 57,254 L 216,285 C 231,227 263,185 311,158 359,131 426,117 511,117 602,117 669,131 712,159 754,187 775,229 775,285 775,328 760,362 731,389 702,416 654,438 589,455 L 460,489 C 357,516 283,542 240,568 196,593 162,624 137,661 112,698 100,743 100,796 100,895 135,970 206,1022 276,1073 378,1099 513,1099 632,1099 727,1078 798,1036 868,994 912,927 931,834 L 769,814 C 759,862 732,899 689,925 645,950 586,963 513,963 432,963 372,951 333,926 294,901 275,864 275,814 275,783 283,758 299,738 315,718 339,701 370,687 401,673 467,654 568,629 663,605 732,583 774,563 816,542 849,520 874,495 898,470 917,442 930,410 943,377 950,340 950,299 Z"/>
<glyph unicode="r" horiz-adv-x="530" d="M 142,0 L 142,830 C 142,906 140,990 136,1082 L 306,1082 C 311,959 314,886 314,861 L 318,861 C 347,954 380,1017 417,1051 454,1085 507,1102 575,1102 599,1102 623,1099 648,1092 L 648,927 C 624,934 592,937 552,937 477,937 420,905 381,841 342,776 322,684 322,564 L 322,0 142,0 Z"/>
<glyph unicode="p" horiz-adv-x="953" d="M 1053,546 C 1053,169 920,-20 655,-20 488,-20 376,43 319,168 L 314,168 C 317,163 318,106 318,-2 L 318,-425 138,-425 138,861 C 138,972 136,1046 132,1082 L 306,1082 C 307,1079 308,1070 309,1054 310,1037 312,1012 314,978 315,944 316,921 316,908 L 320,908 C 352,975 394,1024 447,1055 500,1086 569,1101 655,1101 788,1101 888,1056 954,967 1020,878 1053,737 1053,546 Z M 864,542 C 864,693 844,800 803,865 762,930 698,962 609,962 538,962 482,947 442,917 401,887 371,840 350,777 329,713 318,630 318,528 318,386 341,281 386,214 431,147 505,113 607,113 696,113 762,146 803,212 844,277 864,387 864,542 Z"/>
<glyph unicode="o" horiz-adv-x="980" d="M 1053,542 C 1053,353 1011,212 928,119 845,26 724,-20 565,-20 407,-20 288,28 207,125 126,221 86,360 86,542 86,915 248,1102 571,1102 736,1102 858,1057 936,966 1014,875 1053,733 1053,542 Z M 864,542 C 864,691 842,800 798,868 753,935 679,969 574,969 469,969 393,935 346,866 299,797 275,689 275,542 275,399 298,292 345,221 391,149 464,113 563,113 671,113 748,148 795,217 841,286 864,395 864,542 Z"/>
<glyph unicode="n" horiz-adv-x="874" d="M 825,0 L 825,686 C 825,757 818,813 804,852 790,891 768,920 737,937 706,954 661,963 602,963 515,963 447,933 397,874 347,815 322,732 322,627 L 322,0 142,0 142,851 C 142,977 140,1054 136,1082 L 306,1082 C 307,1079 307,1070 308,1055 309,1040 310,1024 311,1005 312,986 313,950 314,897 L 317,897 C 358,972 406,1025 461,1056 515,1087 582,1102 663,1102 782,1102 869,1073 924,1014 979,955 1006,857 1006,721 L 1006,0 825,0 Z"/>
<glyph unicode="l" horiz-adv-x="187" d="M 138,0 L 138,1484 318,1484 318,0 138,0 Z"/>
<glyph unicode="i" horiz-adv-x="187" d="M 137,1312 L 137,1484 317,1484 317,1312 137,1312 Z M 137,0 L 137,1082 317,1082 317,0 137,0 Z"/>
<glyph unicode="f" horiz-adv-x="557" d="M 361,951 L 361,0 181,0 181,951 29,951 29,1082 181,1082 181,1204 C 181,1303 203,1374 246,1417 289,1460 356,1482 445,1482 495,1482 537,1478 572,1470 L 572,1333 C 542,1338 515,1341 492,1341 446,1341 413,1329 392,1306 371,1283 361,1240 361,1179 L 361,1082 572,1082 572,951 361,951 Z"/>
<glyph unicode="e" horiz-adv-x="980" d="M 276,503 C 276,379 302,283 353,216 404,149 479,115 578,115 656,115 719,131 766,162 813,193 844,233 861,281 L 1019,236 C 954,65 807,-20 578,-20 418,-20 296,28 213,123 129,218 87,360 87,548 87,727 129,864 213,959 296,1054 416,1102 571,1102 889,1102 1048,910 1048,527 L 1048,503 276,503 Z M 862,641 C 852,755 823,838 775,891 727,943 658,969 568,969 481,969 412,940 361,882 310,823 282,743 278,641 L 862,641 Z"/>
<glyph unicode="a" horiz-adv-x="1060" d="M 414,-20 C 305,-20 224,9 169,66 114,123 87,202 87,302 87,414 124,500 198,560 271,620 390,652 554,656 L 797,660 797,719 C 797,807 778,870 741,908 704,946 645,965 565,965 484,965 426,951 389,924 352,897 330,853 323,793 L 135,810 C 166,1005 310,1102 569,1102 705,1102 807,1071 876,1009 945,946 979,856 979,738 L 979,272 C 979,219 986,179 1000,152 1014,125 1041,111 1080,111 1097,111 1117,113 1139,118 L 1139,6 C 1094,-5 1047,-10 1000,-10 933,-10 885,8 855,43 824,78 807,132 803,207 L 797,207 C 751,124 698,66 637,32 576,-3 501,-20 414,-20 Z M 455,115 C 521,115 580,130 631,160 682,190 723,231 753,284 782,336 797,390 797,445 L 797,534 600,530 C 515,529 451,520 408,504 364,488 330,463 307,430 284,397 272,353 272,299 272,240 288,195 320,163 351,131 396,115 455,115 Z"/>
<glyph unicode="C" horiz-adv-x="1324" d="M 792,1274 C 636,1274 515,1224 428,1124 341,1023 298,886 298,711 298,538 343,400 434,295 524,190 646,137 800,137 997,137 1146,235 1245,430 L 1401,352 C 1343,231 1262,138 1157,75 1052,12 930,-20 791,-20 649,-20 526,10 423,69 319,128 240,212 186,322 131,431 104,561 104,711 104,936 165,1112 286,1239 407,1366 575,1430 790,1430 940,1430 1065,1401 1166,1342 1267,1283 1341,1196 1388,1081 L 1207,1021 C 1174,1103 1122,1166 1050,1209 977,1252 891,1274 792,1274 Z"/>
<glyph unicode=" " horiz-adv-x="556"/>
</font>
</defs>
<defs class="TextShapeIndex">
<g ooo:slide="id1" ooo:id-list="id3 id4 id5 id6 id7 id8 id9 id10 id11 id12 id13 id14 id15 id16 id17 id18"/>
</defs>
<defs class="EmbeddedBulletChars">
<g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
</g>
<g id="bullet-char-template-57354" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
</g>
<g id="bullet-char-template-10146" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
</g>
<g id="bullet-char-template-10132" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
</g>
<g id="bullet-char-template-10007" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
</g>
<g id="bullet-char-template-10004" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
</g>
<g id="bullet-char-template-9679" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
</g>
<g id="bullet-char-template-8226" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
</g>
<g id="bullet-char-template-8211" transform="scale(0.00048828125,-0.00048828125)">
<path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
</g>
<g id="bullet-char-template-61548" transform="scale(0.00048828125,-0.00048828125)">
<path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
</g>
</defs>
<g>
<g id="id2" class="Master_Slide">
<g id="bg-id2" class="Background"/>
<g id="bo-id2" class="BackgroundObjects"/>
</g>
</g>
<g class="SlideGroup">
<g>
<g id="container-id1">
<g id="id1" class="Slide" clip-path="url(#presentation_clip_path)">
<g class="Page">
<g class="com.sun.star.drawing.CustomShape">
<g id="id3">
<rect class="BoundingBox" stroke="none" fill="none" x="2447" y="2002" width="16587" height="3309"/>
<path fill="rgb(204,204,204)" stroke="none" d="M 10705,3853 L 2448,2397 2517,2003 19032,4915 18963,5309 10705,3853 Z"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 10705,3853 L 2448,2397 2517,2003 19032,4915 18963,5309 10705,3853 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id4">
<rect class="BoundingBox" stroke="none" fill="none" x="1982" y="2082" width="17037" height="3037"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 2000,2100 L 2207,2136"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 2310,2155 L 2345,2161"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 2448,2179 L 2655,2216"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 2758,2234 L 2793,2240"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 2896,2258 L 3103,2295"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 3206,2313 L 3241,2319"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 3344,2337 L 3551,2374"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 3654,2392 L 3689,2398"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 3792,2416 L 3999,2453"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 4103,2471 L 4137,2477"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 4240,2495 L 4447,2532"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 4551,2550 L 4585,2556"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 4688,2574 L 4895,2611"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 4999,2629 L 5033,2635"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 5137,2654 L 5343,2690"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 5447,2708 L 5481,2714"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 5585,2733 L 5791,2769"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 5895,2787 L 5929,2793"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 6033,2812 L 6239,2848"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 6343,2866 L 6377,2872"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 6481,2891 L 6688,2927"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 6791,2945 L 6825,2952"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 6929,2970 L 7136,3006"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 7239,3025 L 7274,3031"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 7377,3049 L 7584,3085"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 7687,3104 L 7722,3110"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 7825,3128 L 8032,3164"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 8135,3183 L 8170,3189"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 8273,3207 L 8480,3244"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 8583,3262 L 8618,3268"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 8721,3286 L 8928,3323"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 9031,3341 L 9066,3347"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 9169,3365 L 9376,3402"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 9479,3420 L 9514,3426"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 9617,3444 L 9824,3481"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 9928,3499 L 9962,3505"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 10065,3523 L 10272,3560"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 10376,3578 L 10410,3584"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 10513,3602 L 10720,3639"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 10824,3657 L 10858,3663"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 10962,3681 L 11168,3718"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 11272,3736 L 11306,3742"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 11410,3761 L 11616,3797"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 11720,3815 L 11754,3821"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 11858,3840 L 12064,3876"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 12168,3894 L 12202,3900"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 12306,3919 L 12513,3955"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 12616,3973 L 12650,3979"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 12754,3998 L 12961,4034"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 13064,4052 L 13099,4059"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 13202,4077 L 13409,4113"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 13512,4132 L 13547,4138"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 13650,4156 L 13857,4192"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 13960,4211 L 13995,4217"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 14098,4235 L 14305,4271"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 14408,4290 L 14443,4296"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 14546,4314 L 14753,4351"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 14856,4369 L 14891,4375"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 14994,4393 L 15201,4430"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 15304,4448 L 15339,4454"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 15442,4472 L 15649,4509"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 15753,4527 L 15787,4533"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 15890,4551 L 16097,4588"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 16201,4606 L 16235,4612"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 16338,4630 L 16545,4667"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 16649,4685 L 16683,4691"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 16787,4709 L 16993,4746"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 17097,4764 L 17131,4770"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 17235,4788 L 17441,4825"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 17545,4843 L 17579,4849"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 17683,4868 L 17889,4904"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 17993,4922 L 18027,4928"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 18131,4947 L 18338,4983"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 18441,5001 L 18475,5007"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 18579,5026 L 18786,5062"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 18889,5080 L 18924,5087"/>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id5">
<rect class="BoundingBox" stroke="none" fill="none" x="2324" y="2200" width="353" height="3819"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="35" stroke-linejoin="round" d="M 2500,6000 L 2500,2705"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 2500,2200 L 2324,2728 2676,2728 2500,2200 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id6">
<rect class="BoundingBox" stroke="none" fill="none" x="1982" y="5082" width="17037" height="37"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 2000,5100 L 2210,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 2315,5100 L 2350,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 2455,5100 L 2665,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 2770,5100 L 2805,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 2910,5100 L 3120,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 3225,5100 L 3260,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 3365,5100 L 3575,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 3680,5100 L 3715,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 3820,5100 L 4030,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 4135,5100 L 4170,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 4275,5100 L 4485,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 4590,5100 L 4625,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 4730,5100 L 4940,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 5045,5100 L 5080,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 5185,5100 L 5395,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 5500,5100 L 5535,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 5640,5100 L 5850,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 5955,5100 L 5990,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 6095,5100 L 6305,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 6410,5100 L 6445,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 6550,5100 L 6760,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 6865,5100 L 6900,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 7005,5100 L 7215,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 7320,5100 L 7355,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 7460,5100 L 7670,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 7775,5100 L 7810,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 7915,5100 L 8125,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 8230,5100 L 8265,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 8370,5100 L 8580,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 8685,5100 L 8720,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 8825,5100 L 9035,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 9140,5100 L 9175,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 9280,5100 L 9490,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 9595,5100 L 9630,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 9735,5100 L 9945,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 10050,5100 L 10085,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 10190,5100 L 10400,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 10505,5100 L 10540,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 10645,5100 L 10855,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 10960,5100 L 10995,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 11100,5100 L 11310,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 11415,5100 L 11450,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 11555,5100 L 11765,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 11870,5100 L 11905,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 12010,5100 L 12220,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 12325,5100 L 12360,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 12465,5100 L 12675,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 12780,5100 L 12815,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 12920,5100 L 13130,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 13235,5100 L 13270,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 13375,5100 L 13585,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 13690,5100 L 13725,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 13830,5100 L 14040,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 14145,5100 L 14180,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 14285,5100 L 14495,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 14600,5100 L 14635,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 14740,5100 L 14950,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 15055,5100 L 15090,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 15195,5100 L 15405,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 15510,5100 L 15545,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 15650,5100 L 15860,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 15965,5100 L 16000,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 16105,5100 L 16315,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 16420,5100 L 16455,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 16560,5100 L 16770,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 16875,5100 L 16910,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 17015,5100 L 17225,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 17330,5100 L 17365,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 17470,5100 L 17680,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 17785,5100 L 17820,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 17925,5100 L 18135,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 18240,5100 L 18275,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 18380,5100 L 18590,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 18695,5100 L 18730,5100"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="35" stroke-linejoin="round" d="M 18835,5100 L 19000,5100"/>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id7">
<rect class="BoundingBox" stroke="none" fill="none" x="18824" y="5100" width="353" height="919"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="35" stroke-linejoin="round" d="M 19000,6000 L 19000,5605"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 19000,5100 L 18824,5628 19176,5628 19000,5100 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id8">
<rect class="BoundingBox" stroke="none" fill="none" x="1582" y="5824" width="919" height="353"/>
<path fill="none" stroke="rgb(52,101,164)" stroke-width="35" stroke-linejoin="round" d="M 1600,6000 L 1995,6000"/>
<path fill="rgb(52,101,164)" stroke="none" d="M 2500,6000 L 1972,5824 1972,6176 2500,6000 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.OpenBezierShape">
<g id="id9">
<rect class="BoundingBox" stroke="none" fill="none" x="3374" y="2399" width="449" height="2703"/>
<path fill="none" stroke="rgb(255,0,0)" stroke-width="35" stroke-linejoin="round" d="M 3500,5083 C 3501,3200 3594,3011 3653,2645"/>
<path fill="rgb(255,0,0)" stroke="none" d="M 3626,5100 L 3542,5100 3458,5100 3374,5100 3374,5089 3374,5078 3374,5067 3458,5067 3542,5067 3626,5067 3626,5078 3626,5089 3626,5100 Z"/>
<path fill="rgb(255,0,0)" stroke="none" d="M 3700,2400 L 3475,2644 3821,2708 3700,2400 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id10">
<rect class="BoundingBox" stroke="none" fill="none" x="3600" y="3576" width="2001" height="925"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="3850" y="4150"><tspan fill="rgb(255,0,0)" stroke="none">α</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id11">
<rect class="BoundingBox" stroke="none" fill="none" x="8924" y="5100" width="353" height="903"/>
<path fill="none" stroke="rgb(255,0,0)" stroke-width="35" stroke-linejoin="round" d="M 9100,5984 L 9100,5357"/>
<path fill="rgb(255,0,0)" stroke="none" d="M 9226,6000 L 9142,6000 9058,6000 8974,6000 8974,5989 8974,5978 8974,5967 9058,5967 9142,5967 9226,5967 9226,5978 9226,5989 9226,6000 Z"/>
<path fill="rgb(255,0,0)" stroke="none" d="M 9100,5100 L 8924,5381 9276,5381 9100,5100 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id12">
<rect class="BoundingBox" stroke="none" fill="none" x="9200" y="5076" width="1301" height="925"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="287px" font-weight="400"><tspan class="TextPosition" x="9450" y="5650"><tspan font-size="494px" fill="rgb(255,0,0)" stroke="none">z</tspan></tspan><tspan class="TextPosition" x="9700" y="5813"><tspan fill="rgb(255,0,0)" stroke="none">lift</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id13">
<rect class="BoundingBox" stroke="none" fill="none" x="1991" y="5991" width="17019" height="19"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 2000,6000 L 2108,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 2162,6000 L 2270,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 2324,6000 L 2432,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 2486,6000 L 2594,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 2648,6000 L 2756,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 2810,6000 L 2918,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 2972,6000 L 3080,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 3134,6000 L 3242,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 3296,6000 L 3404,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 3458,6000 L 3566,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 3620,6000 L 3728,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 3782,6000 L 3890,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 3944,6000 L 4052,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 4106,6000 L 4214,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 4268,6000 L 4376,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 4430,6000 L 4538,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 4592,6000 L 4700,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 4754,6000 L 4862,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 4916,6000 L 5024,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 5078,6000 L 5186,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 5240,6000 L 5348,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 5402,6000 L 5510,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 5564,6000 L 5672,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 5726,6000 L 5834,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 5888,6000 L 5996,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 6050,6000 L 6158,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 6212,6000 L 6320,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 6374,6000 L 6482,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 6536,6000 L 6644,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 6698,6000 L 6806,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 6860,6000 L 6968,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 7022,6000 L 7130,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 7184,6000 L 7292,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 7346,6000 L 7454,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 7508,6000 L 7616,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 7670,6000 L 7778,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 7832,6000 L 7940,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 7994,6000 L 8102,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 8156,6000 L 8264,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 8318,6000 L 8426,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 8480,6000 L 8588,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 8642,6000 L 8750,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 8804,6000 L 8912,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 8966,6000 L 9074,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 9128,6000 L 9236,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 9290,6000 L 9398,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 9452,6000 L 9560,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 9614,6000 L 9722,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 9776,6000 L 9884,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 9938,6000 L 10046,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 10100,6000 L 10208,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 10262,6000 L 10370,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 10424,6000 L 10532,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 10586,6000 L 10694,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 10748,6000 L 10856,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 10910,6000 L 11018,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 11072,6000 L 11180,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 11234,6000 L 11342,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 11396,6000 L 11504,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 11558,6000 L 11666,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 11720,6000 L 11828,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 11882,6000 L 11990,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 12044,6000 L 12152,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 12206,6000 L 12314,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 12368,6000 L 12476,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 12530,6000 L 12638,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 12692,6000 L 12800,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 12854,6000 L 12962,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 13016,6000 L 13124,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 13178,6000 L 13286,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 13340,6000 L 13448,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 13502,6000 L 13610,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 13664,6000 L 13772,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 13826,6000 L 13934,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 13988,6000 L 14096,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 14150,6000 L 14258,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 14312,6000 L 14420,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 14474,6000 L 14582,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 14636,6000 L 14744,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 14798,6000 L 14906,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 14960,6000 L 15068,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 15122,6000 L 15230,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 15284,6000 L 15392,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 15446,6000 L 15554,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 15608,6000 L 15716,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 15770,6000 L 15878,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 15932,6000 L 16040,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 16094,6000 L 16202,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 16256,6000 L 16364,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 16418,6000 L 16526,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 16580,6000 L 16688,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 16742,6000 L 16850,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 16904,6000 L 17012,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 17066,6000 L 17174,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 17228,6000 L 17336,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 17390,6000 L 17498,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 17552,6000 L 17660,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 17714,6000 L 17822,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 17876,6000 L 17984,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 18038,6000 L 18146,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 18200,6000 L 18308,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 18362,6000 L 18470,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 18524,6000 L 18632,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 18686,6000 L 18794,6000"/>
<path fill="none" stroke="rgb(0,0,0)" stroke-width="18" stroke-linejoin="round" d="M 18848,6000 L 18956,6000"/>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id14">
<rect class="BoundingBox" stroke="none" fill="none" x="1700" y="2100" width="1101" height="1017"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="1950" y="2674"><tspan fill="rgb(42,96,153)" stroke="none">z</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id15">
<rect class="BoundingBox" stroke="none" fill="none" x="1700" y="5094" width="701" height="807"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="494px" font-weight="400"><tspan class="TextPosition" x="1950" y="5668"><tspan fill="rgb(42,96,153)" stroke="none">x</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id16">
<rect class="BoundingBox" stroke="none" fill="none" x="17400" y="5100" width="1801" height="925"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="287px" font-weight="400"><tspan class="TextPosition" x="17650" y="5674"><tspan font-size="494px" fill="rgb(42,96,153)" stroke="none">z</tspan></tspan><tspan class="TextPosition" x="17900" y="5837"><tspan fill="rgb(42,96,153)" stroke="none">support</tspan></tspan></tspan></text>
</g>
</g>
<g class="com.sun.star.drawing.LineShape">
<g id="id17">
<rect class="BoundingBox" stroke="none" fill="none" x="18499" y="3299" width="527" height="1802"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 18500,3300 L 18579,3586"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 18615,3715 L 18695,4001"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 18731,4131 L 18810,4417"/>
<path fill="none" stroke="rgb(0,0,0)" d="M 18846,4546 L 18885,4686"/>
<path fill="rgb(0,0,0)" stroke="none" d="M 19000,5100 L 19024,4626 18735,4707 19000,5100 Z"/>
</g>
</g>
<g class="com.sun.star.drawing.TextShape">
<g id="id18">
<rect class="BoundingBox" stroke="none" fill="none" x="17000" y="2561" width="2001" height="1040"/>
<text class="TextShape"><tspan class="TextParagraph" font-family="Liberation Sans, sans-serif" font-size="353px" font-weight="400"><tspan class="TextPosition" x="17250" y="3004"><tspan fill="rgb(0,0,0)" stroke="none">Center of </tspan></tspan><tspan class="TextPosition" x="17250" y="3398"><tspan fill="rgb(0,0,0)" stroke="none">rotation</tspan></tspan></tspan></text>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,241 +0,0 @@
#include "auxiliaryAxis.h"
#include "detectorTowerAxis.h"
#include "detectorTowerController.h"
#include "turboPmacController.h"
#include <epicsExport.h>
#include <errlog.h>
#include <iocsh.h>
/*
Contains all instances of auxiliaryAxis which have been created and is used
in the initialization hook function.
*/
static std::vector<auxiliaryAxis *> axes;
/**
* @brief Hook function to perform certain actions during the IOC initialization
*
* @param iState
*/
static void epicsInithookFunction(initHookState iState) {
if (iState == initHookAfterDatabaseRunning) {
// Iterate through all axes of each and call the initialization method
// on each one of them.
for (std::vector<auxiliaryAxis *>::iterator itA = axes.begin();
itA != axes.end(); ++itA) {
auxiliaryAxis *axis = *itA;
axis->init();
}
}
}
auxiliaryAxis::auxiliaryAxis(detectorTowerController *pC, int axisNo)
: turboPmacAxis(pC, axisNo, false), pC_(pC) {
/*
The superclass constructor sinqAxis calls in turn its superclass constructor
asynMotorAxis. In the latter, a pointer to the constructed object this is
stored inside the array pAxes_:
pC->pAxes_[axisNo] = this;
Therefore, the axes are managed by the controller pC. If axisNo is out of
bounds, asynMotorAxis prints an error (see
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorAxis.cpp,
line 40). However, we want the IOC creation to stop completely, since this
is a configuration error.
*/
if (axisNo >= pC->numAxes()) {
asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d: FATAL ERROR: "
"Axis index %d must be smaller than the total number of axes "
"%d",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axisNo_, pC->numAxes());
exit(-1);
}
// Register the hook function during construction of the first axis object
if (axes.empty()) {
initHookRegister(&epicsInithookFunction);
}
// Collect all axes into this list which will be used in the hook
// function
axes.push_back(this);
}
auxiliaryAxis::~auxiliaryAxis(void) {
// Since the controller memory is managed somewhere else, we don't need to
// clean up the pointer pC here.
}
asynStatus auxiliaryAxis::init() {
// Local variable declaration
asynStatus status = asynSuccess;
double motorRecResolution = 0.0;
// The parameter library takes some time to be initialized. Therefore we
// wait until the status is not asynParamUndefined anymore.
time_t now = time(NULL);
time_t maxInitTime = 60;
while (1) {
status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution(),
&motorRecResolution);
if (status == asynParamUndefined) {
if (now + maxInitTime < time(NULL)) {
asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\nInitializing the parameter library failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__);
return asynError;
}
} else if (status == asynSuccess) {
break;
} else if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
return asynSuccess;
}
// Perform the actual poll
asynStatus auxiliaryAxis::doPoll(bool *moving) {
// Return value for the poll
asynStatus poll_status = asynSuccess;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
int isMoving = 0;
// =========================================================================
/*
The axis is moving if the detectorTowerAxis is moving -> We read the moving
status from the detectorTowerAxis.
*/
pl_status = pC_->getIntegerParam(dTA_->axisNo_, pC_->motorStatusMoving(),
&isMoving);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusMoving_",
dTA_->axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
*moving = isMoving != 0;
// Update the parameter library
if (dTA_->error_ != 0) {
pl_status = setIntegerParam(pC_->motorStatusProblem(), true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
if (*moving == false) {
pl_status = setIntegerParam(pC_->motorMoveToHome(), 0);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMoveToHome_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
pl_status = setIntegerParam(pC_->motorStatusMoving(), *moving);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusMoving_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusDone(), !(*moving));
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusDone_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// According to the function documentation of asynMotorAxis::poll, this
// function should be called at the end of a poll implementation.
pl_status = callParamCallbacks();
bool wantToPrint = pl_status != asynSuccess;
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, wantToPrint,
pC_->asynUserSelf())) {
asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\ncallParamCallbacks failed with %s.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(poll_status),
pC_->getMsgPrintControl().getSuffix());
}
if (wantToPrint) {
poll_status = pl_status;
}
// The limits are written into this class instance inside the doPoll
// function of detectorTowerAxis
return poll_status;
}
asynStatus auxiliaryAxis::doMove(double position, int relative,
double min_velocity, double max_velocity,
double acceleration) {
double motorRecResolution = 0.0;
asynStatus pl_status = pC_->getDoubleParam(
axisNo_, pC_->motorRecResolution(), &motorRecResolution);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Signal to the deferredMovementCollectorLoop (of the detectorTowerAxis)
// that a movement should be started to the defined target position.
targetPosition_ = position * motorRecResolution;
dTA_->receivedTarget_ = true;
return asynSuccess;
}
asynStatus auxiliaryAxis::stop(double acceleration) {
// Status of read-write-operations of ASCII commands to the controller
asynStatus rw_status = asynSuccess;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
char response[pC_->MAXBUF_];
// =========================================================================
rw_status = pC_->writeRead(axisNo_, "P350=8", response, 0);
if (rw_status != asynSuccess) {
asynPrint(
pC_->asynUserSelf(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStopping the movement "
"failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
pl_status = setIntegerParam(pC_->motorStatusProblem(), true);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return asynError;
}
return rw_status;
}
asynStatus auxiliaryAxis::reset() { return dTA_->reset(); };

View File

@ -1,81 +0,0 @@
#ifndef auxiliaryAxis_H
#define auxiliaryAxis_H
#include "turboPmacAxis.h"
// Forward declaration of the controller class to resolve the cyclic dependency
// between the controller and the axis .h-file. See
// https://en.cppreference.com/w/cpp/language/class.
class detectorTowerController;
class detectorTowerAxis;
class auxiliaryAxis : public turboPmacAxis {
public:
/**
* @brief Construct a new detectorTowerAxis
*
* @param pController Pointer to the associated controller
* @param axisNo Index of the axis
*/
auxiliaryAxis(detectorTowerController *pController, int axisNo);
/**
* @brief Destroy the turboPmacAxis
*
*/
virtual ~auxiliaryAxis();
/**
* @brief Readout of some values from the controller at IOC startup
*
* @return asynStatus
*/
asynStatus init();
/**
* @brief Implementation of the `stop` function from asynMotorAxis
*
* @param acceleration Acceleration ACCEL from the motor record. This
* value is currently not used.
* @return asynStatus
*/
asynStatus stop(double acceleration);
/**
* @brief Implementation of the `doPoll` function from sinqAxis. The
* parameters are described in the documentation of `sinqAxis::doPoll`.
*
* @param moving
* @return asynStatus
*/
asynStatus doPoll(bool *moving);
/**
* @brief Set the target value for the detector angle and trigger a position
* collection cycle
*
* @param position
* @param relative
* @param min_velocity
* @param max_velocity
* @param acceleration
* @return asynStatus
*/
asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration);
/**
* @brief Call the reset function of the associated `detectorTowerAxis`.
*
* @return asynStatus
*/
asynStatus reset();
protected:
detectorTowerController *pC_;
detectorTowerAxis *dTA_;
private:
friend class detectorTowerAxis;
};
#endif

View File

@ -0,0 +1,616 @@
#include "detectorTowerAngleAxis.h"
#include "detectorTowerController.h"
#include "detectorTowerLiftAxis.h"
#include "detectorTowerSupportAxis.h"
#include "turboPmacController.h"
#include <epicsExport.h>
#include <errlog.h>
#include <iocsh.h>
/*
Contains all instances of detectorTowerAngleAxis which have been created and is
used in the initialization hook function.
*/
static std::vector<detectorTowerAngleAxis *> axes;
/**
* @brief Hook function to perform certain actions during the IOC initialization
*
* @param iState
*/
static void epicsInithookFunction(initHookState iState) {
if (iState == initHookAfterDatabaseRunning) {
// Iterate through all axes of each and call the initialization method
// on each one of them.
for (std::vector<detectorTowerAngleAxis *>::iterator itA = axes.begin();
itA != axes.end(); ++itA) {
detectorTowerAngleAxis *axis = *itA;
axis->init();
}
}
}
static void deferredMovementCollectorLoop(void *drvPvt) {
detectorTowerAngleAxis *axis = (detectorTowerAngleAxis *)drvPvt;
while (1) {
if (axis->receivedTarget_) {
// Wait for 100 ms and then start the movement with the information
// available
axis->startingDeferredMovement_ = true;
epicsThreadSleep(axis->deferredMovementWait_);
axis->startCombinedMove();
// After the movement command has been send, reset the flag
axis->receivedTarget_ = false;
}
// Limit this loop to an idle frequency of 1 kHz
epicsThreadSleep(0.001);
}
}
detectorTowerAngleAxis::detectorTowerAngleAxis(detectorTowerController *pC,
int axisNo)
: turboPmacAxis(pC, axisNo, false), pC_(pC) {
/*
The superclass constructor sinqAxis calls in turn its superclass constructor
asynMotorAxis. In the latter, a pointer to the constructed object this is
stored inside the array pAxes_:
pC->pAxes_[axisNo] = this;
Therefore, the axes are managed by the controller pC. If axisNo is out of
bounds, asynMotorAxis prints an error (see
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorAxis.cpp,
line 40). However, we want the IOC creation to stop completely, since this
is a configuration error.
*/
if (axisNo >= pC->numAxes()) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d: FATAL ERROR: "
"Axis index %d must be smaller than the total number of axes "
"%d",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axisNo_, pC->numAxes());
exit(-1);
}
// Initialize all member variables
// Assumed to be not ready by default, this is overwritten in the next poll
error_ = 0;
receivedTarget_ = false;
startingDeferredMovement_ = false;
deferredMovementWait_ = 0.1; // seconds
// Will be populated in the init() method
beamRadius_ = 0.0;
// Register the hook function during construction of the first axis object
if (axes.empty()) {
initHookRegister(&epicsInithookFunction);
}
// Collect all axes into this list which will be used in the hook
// function
axes.push_back(this);
// Create a thread which collects all movement commands send to components
// of the virtual axis. After a component received a new target position,
// it forwards this information to the thread. The thread then waits a short
// time to see if other components also received new targets and starts the
// movement with all targets afterwards.
epicsThreadCreate("deferredMovement", epicsThreadPriorityLow,
epicsThreadGetStackSize(epicsThreadStackMedium),
(EPICSTHREADFUNC)deferredMovementCollectorLoop,
(void *)this);
}
detectorTowerAngleAxis::~detectorTowerAngleAxis(void) {
// Since the controller memory is managed somewhere else, we don't need to
// clean up the pointer pC here.
}
asynStatus detectorTowerAngleAxis::init() {
// Local variable declaration
asynStatus status = asynSuccess;
double motorRecResolution = 0.0;
char response[pC_->MAXBUF_] = {0};
int positionState = 0;
int nvals = 0;
// The parameter library takes some time to be initialized. Therefore we
// wait until the status is not asynParamUndefined anymore.
time_t now = time(NULL);
time_t maxInitTime = 60;
while (1) {
status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution(),
&motorRecResolution);
if (status == asynParamUndefined) {
if (now + maxInitTime < time(NULL)) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis "
"%ddeferredMovementCollectorLoop => %s, line "
"%d\nInitializing the parameter library failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__);
return asynError;
}
} else if (status == asynSuccess) {
break;
} else if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
// Read the detector radius
const char *command = "P459 P358";
status = pC_->writeRead(axisNo_, command, response, 2);
if (status != asynSuccess) {
return status;
}
nvals = sscanf(response, "%lf %d", &beamRadius_, &positionState);
if (nvals != 2) {
return pC_->couldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Initialize the motorStatusMoving flag
setAxisParamChecked(this, motorStatusMoving, false);
setAxisParamChecked(this, changeStateRBV, positionState == 2);
return callParamCallbacks();
}
// Perform the actual poll
asynStatus detectorTowerAngleAxis::poll(bool *moving) {
asynStatus status = asynSuccess;
// Is this axis the one with the smallest index?
// If not, just read out the movement status and update *moving
if (axisNo() < liftAxis()->axisNo() && axisNo() < supportAxis()->axisNo()) {
status = pC_->pollDetectorAxes(moving, this, liftAxis(), supportAxis());
} else {
getAxisParamChecked(this, motorStatusMoving, moving);
}
setWasMoving(*moving);
return status;
}
asynStatus detectorTowerAngleAxis::doPoll(bool *moving) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nThe doPoll method "
"of this axis type should not be reachable. This is a bug.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
asynStatus detectorTowerAngleAxis::doMove(double position, int relative,
double min_velocity,
double max_velocity,
double acceleration) {
double motorRecResolution = 0.0;
getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
// Signal to the deferredMovementCollectorLoop that a movement should be
// started to the defined target position.
setTargetPosition(position * motorRecResolution);
receivedTarget_ = true;
return asynSuccess;
}
asynStatus detectorTowerAngleAxis::startCombinedMove() {
// Status of read-write-operations of ASCII commands to the controller
asynStatus status = asynSuccess;
char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0};
double motorCoordinatesPosition = 0.0;
int positionState = 0;
// =========================================================================
getAxisParamChecked(this, positionStateRBV, &positionState);
// If the axis is in changer position, it must be moved into working
// position before any move can be started.
bool isInChangerPos = positionState == 2 || positionState == 3;
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
isInChangerPos, pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis cannot be "
"moved because it is moving from working to changer "
"position, is in changer position or is moving from changer "
"to working position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
if (isInChangerPos) {
setAxisParamChecked(this, motorMessageText,
"Move the axis to working state first.");
setAxisParamChecked(this, motorStatusProblem, true);
callParamCallbacks();
startingDeferredMovement_ = false;
return asynError;
}
// Set the target positions for beam tilt, detector tilt offset and lift
// offset
snprintf(command, sizeof(command), "Q451=%lf Q454=%lf P350=1",
targetPosition(), liftAxis_->targetPosition());
// Lock the access to the controller since this function runs in another
// thread than the poll method.
pC_->lock();
// We don't expect an answer
status = pC_->writeRead(axisNo_, command, response, 0);
// Free the controller again
pC_->unlock();
if (status != asynSuccess) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStarting movement to "
"target position %lf failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
motorCoordinatesPosition);
setAxisParamChecked(this, motorStatusProblem, true);
callParamCallbacks();
}
return status;
}
asynStatus detectorTowerAngleAxis::stop(double acceleration) {
// Status of read-write-operations of ASCII commands to the controller
asynStatus status = asynSuccess;
char response[pC_->MAXBUF_] = {0};
// =========================================================================
status = pC_->writeRead(axisNo_, "P350=8", response, 0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStopping the movement "
"failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
setAxisParamChecked(this, motorStatusProblem, true);
return asynError;
}
// Reset the deferred movement flags
startingDeferredMovement_ = false;
deferredMovementWait_ = false;
return status;
}
// The detector tower axis uses absolute encoders
asynStatus detectorTowerAngleAxis::readEncoderType() {
setAxisParamChecked(this, encoderType, AbsoluteEncoder);
return asynSuccess;
}
asynStatus
detectorTowerAngleAxis::toggleWorkingChangerState(bool toChangingPosition) {
char response[pC_->MAXBUF_] = {0};
// Status of read-write-operations of ASCII commands to the controller
asynStatus status = asynSuccess;
bool moving = false;
int positionState = 0;
// =========================================================================
status = poll(&moving);
if (status != asynSuccess) {
return status;
}
getAxisParamChecked(this, positionStateRBV, &positionState);
if (moving) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is not "
"idle and can therefore not be moved to %s state.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
toChangingPosition ? "changer" : "working",
pC_->getMsgPrintControl().getSuffix());
setAxisParamChecked(
this, motorMessageText,
"Axis cannot be moved to changer position while it is moving.");
setAxisParamChecked(this, changeStateRBV, !toChangingPosition);
return asynError;
}
// Axis is already in the correct position
bool isAlreadyThere = (toChangingPosition == false && positionState == 1) ||
(toChangingPosition == true && positionState == 2);
if (isAlreadyThere) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nAxis is already "
"in %s position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
toChangingPosition ? "changer" : "working",
pC_->getMsgPrintControl().getSuffix());
// Update the PV anyway, even though nothing changed.
setAxisParamChecked(this, changeStateRBV, toChangingPosition);
return asynSuccess;
}
// Move the axis into changer or working position
if (toChangingPosition) {
status = pC_->writeRead(axisNo_, "P350=2", response, 0);
} else {
status = pC_->writeRead(axisNo_, "P350=3", response, 0);
}
setAxisParamChecked(this, changeStateRBV, toChangingPosition);
return asynSuccess;
}
asynStatus detectorTowerAngleAxis::adjustOrigin(double newOrigin) {
asynStatus status = asynSuccess;
char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0};
int positionState = 0;
// =========================================================================
getAxisParamChecked(this, positionStateRBV, &positionState);
// If the axis is in changer position, it must be moved into working
// position before any move can be started.
bool isInChangerPos = positionState == 2 || positionState == 3;
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
isInChangerPos, pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis cannot be "
"moved because it is moving from working to changer "
"position, is in changer position or is moving from changer "
"to working position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
// Set the new origin for the angle axis
snprintf(command, sizeof(command), "Q556=%lf P350=4", newOrigin);
// We don't expect an answer
status = pC_->writeRead(axisNo_, command, response, 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nSetting new "
"angle origin %lf failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
newOrigin);
setAxisParamChecked(this, motorStatusProblem, true);
}
return status;
}
asynStatus detectorTowerAngleAxis::doReset() {
char response[pC_->MAXBUF_] = {0};
int positionState = 0;
getAxisParamChecked(this, positionStateRBV, &positionState);
// Reset the deferred movement flags
startingDeferredMovement_ = false;
deferredMovementWait_ = false;
/*
Check which action should be performed:
- If error_ == 10 or 11 (FTZ motor error): P352 = 3 (Recover FTZ)
- If any other error: P352 = 2 (Reset error)
- Otherwise: P352 = 1 (Set axis in closed-loop mode)
*/
if (error_ == 10 || error_ == 11) {
return pC_->writeRead(axisNo_, "P352=3", response, 0);
} else if (error_ != 0) {
return pC_->writeRead(axisNo_, "P352=2", response, 0);
} else {
return pC_->writeRead(axisNo_, "P352=1", response, 0);
}
}
/*************************************************************************************/
/** The following functions are C-wrappers, and can be called directly from
* iocsh */
extern "C" {
/*
C wrapper for the axis constructor. Please refer to the detectorTower
constructor documentation. The controller is read from the portName.
*/
asynStatus detectorTowerCreateAxis(const char *portName, int angleAxisIdx,
int liftAxisIdx, int supportAxisIdx) {
/*
findAsynPortDriver is a asyn library FFI function which uses the C ABI.
Therefore it returns a void pointer instead of e.g. a pointer to a
superclass of the controller such as asynPortDriver. Type-safe upcasting
via dynamic_cast is therefore not possible directly. However, we do know
that the void pointer is either a pointer to asynPortDriver (if a driver
with the specified name exists) or a nullptr. Therefore, we first do a
nullptr check, then a cast to asynPortDriver and lastly a (typesafe)
dynamic_upcast to Controller
https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c
*/
void *ptr = findAsynPortDriver(portName);
if (ptr == nullptr) {
/*
We can't use asynPrint here since this macro would require us
to get a lowLevelPortUser_ from a pointer to an asynPortDriver.
However, the given pointer is a nullptr and therefore doesn'taxis
works w/o that, but doesn't offer the comfort provided
by the asynTrace-facility
*/
errlogPrintf("Controller \"%s\" => %s, line %d\nPort not found.",
portName, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
// Unsafe cast of the pointer to an asynPortDriver
asynPortDriver *apd = (asynPortDriver *)(ptr);
// Safe downcast
detectorTowerController *pC = dynamic_cast<detectorTowerController *>(apd);
if (pC == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d\nController "
"is not a detectorTowerController.",
portName, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
// Assert that the three indices are different from each other
if (angleAxisIdx == liftAxisIdx) {
errlogPrintf("Controller \"%s\" => %s, line %d\nAll axis indices "
"must be unique.",
portName, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
// Prevent manipulation of the controller from other threads while we
// create the new axis.
pC->lock();
/*
We create a new instance of the axis, using the "new" keyword to
allocate it on the heap while avoiding RAII.
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
The created object is registered in EPICS in its constructor and can safely
be "leaked" here.
*/
detectorTowerAngleAxis *angleAxis =
new detectorTowerAngleAxis(pC, angleAxisIdx);
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
detectorTowerLiftAxis *liftAxis =
new detectorTowerLiftAxis(pC, liftAxisIdx, angleAxis);
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
detectorTowerSupportAxis *supportAxis =
new detectorTowerSupportAxis(pC, supportAxisIdx, angleAxis);
// Allow manipulation of the controller again
pC->unlock();
return asynSuccess;
}
static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mcu1)",
iocshArgString};
static const iocshArg CreateAxisArg1 = {"Number of the detector angle axis",
iocshArgInt};
static const iocshArg CreateAxisArg2 = {"Number of the lift axis", iocshArgInt};
static const iocshArg CreateAxisArg3 = {"Number of the support axis",
iocshArgInt};
static const iocshArg *const CreateAxisArgs[] = {
&CreateAxisArg0,
&CreateAxisArg1,
&CreateAxisArg2,
&CreateAxisArg3,
};
static const iocshFuncDef configDetectorTowerCreateAxis = {"detectorTowerAxis",
4, CreateAxisArgs};
static void configDetectorTowerCreateAxisCallFunc(const iocshArgBuf *args) {
detectorTowerCreateAxis(args[0].sval, args[1].ival, args[2].ival,
args[3].ival);
}
// =============================================================================
/**
* @brief Set the setDeferredMovementWait (FFI implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param scaleMovTimeout Scaling factor (in seconds)
* @return asynStatus
*/
asynStatus setDeferredMovementWait(const char *portName, int axisNo,
double deferredMovementWait) {
sinqController *pC;
pC = (sinqController *)findAsynPortDriver(portName);
if (pC == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nPort %s not found.",
portName, __PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
detectorTowerAngleAxis *axis =
dynamic_cast<detectorTowerAngleAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nAxis %d does not "
"exist or is not an instance of detectorTowerAngleAxis.",
portName, __PRETTY_FUNCTION__, __LINE__, axisNo);
return asynError;
}
axis->deferredMovementWait_ = deferredMovementWait;
return asynSuccess;
}
static const iocshArg setDeferredMovementWaitArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setDeferredMovementWaitArg1 = {"Axis number",
iocshArgInt};
static const iocshArg setDeferredMovementWaitArg2 = {
"Minimum time a deferred movement will be delayed in order to collect "
"commands in seconds",
iocshArgDouble};
static const iocshArg *const setDeferredMovementWaitArgs[] = {
&setDeferredMovementWaitArg0, &setDeferredMovementWaitArg1,
&setDeferredMovementWaitArg2};
static const iocshFuncDef setDeferredMovementWaitDef = {
"setDeferredMovementWait", 3, setDeferredMovementWaitArgs};
static void setDeferredMovementWaitCallFunc(const iocshArgBuf *args) {
setDeferredMovementWait(args[0].sval, args[1].ival, args[2].dval);
}
// =============================================================================
// This function is made known to EPICS in detectorTower.dbd and is
// called by EPICS in order to register both functions in the IOC shell
static void detectorTowerAxisRegister(void) {
iocshRegister(&configDetectorTowerCreateAxis,
configDetectorTowerCreateAxisCallFunc);
iocshRegister(&setDeferredMovementWaitDef, setDeferredMovementWaitCallFunc);
}
epicsExportRegistrar(detectorTowerAxisRegister);
} // extern "C"

View File

@ -0,0 +1,176 @@
#ifndef detectorTowerAngleAxis_H
#define detectorTowerAngleAxis_H
#include "detectorTowerController.h"
#include "turboPmacAxis.h"
class detectorTowerAngleAxis : public turboPmacAxis {
public:
/**
* @brief Construct a new detectorTowerAngleAxis
*
* @param pController Pointer to the associated controller
* @param axisNo Index of the axis
* @param liftAxis Pointer to the associated lift axis
*/
detectorTowerAngleAxis(detectorTowerController *pController, int axisNo);
/**
* @brief Destroy the detectorTowerAngleAxis
*
*/
virtual ~detectorTowerAngleAxis();
/**
* @brief Readout of some values from the controller at IOC startup
*
* @return asynStatus
*/
asynStatus init();
/**
* @brief Implementation of the `stop` function from asynMotorAxis
*
* @param acceleration Acceleration ACCEL from the motor record. This
* value is currently not used.
* @return asynStatus
*/
asynStatus stop(double acceleration);
/**
* @brief Poll all detector tower axes, if this axis is the one with the
* smallest index.
*
* We do not use the doPoll framework from sinqMotor here on purpose, since
* we want to e.g. reset the motorStatusProblem for all axes at once at the
* beginning of the poll.
*
* @param moving
* @return asynStatus
*/
asynStatus poll(bool *moving);
asynStatus doPoll(bool *moving);
/**
* @brief Set the target value for the detector angle and trigger a position
* collection cycle
*
* @param position
* @param relative
* @param min_velocity
* @param max_velocity
* @param acceleration
* @return asynStatus
*/
asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration);
/**
* @brief Start a movement to the target positions of this axis and the
* attached offset axes.
*
* @return asynStatus
*/
asynStatus startCombinedMove();
/**
* @brief This axis type has an absolute encoder by default
*
* @return asynStatus
*/
asynStatus readEncoderType();
/**
* @brief If the input value is true, move the axis into working position,
* otherwise into changer position.
*
* @return asynStatus
*/
asynStatus toggleWorkingChangerState(bool toChangingPosition);
/**
* @brief Overwrite the `reset` function so it doesn't do fast polls
* afterwards.
*
* The
*
* @return asynStatus
*/
asynStatus reset() { return doReset(); }
/**
* @brief Implementation of the `doReset` method of sinqAxis
*
* @return asynStatus
*/
asynStatus doReset();
/**
* @brief Move the axis to the position `newOrigin` and recalibrate
*
* When calling this function, the angle axis moves to `newOrigin` and the
* hardware sets this position as the new origin.
*
* @param origin
* @return asynStatus
*/
asynStatus adjustOrigin(double newOrigin);
/**
* @brief Read out the beam radius
*
* @return double
*/
double beamRadius() { return beamRadius_; }
/**
* @brief Return a pointer to the associated angle axis
*
* @return detectorTowerAngleAxis*
*/
detectorTowerLiftAxis *liftAxis() { return liftAxis_; }
/**
* @brief Return a pointer to the associated support axis
*
* @return detectorTowerAngleAxis*
*/
detectorTowerSupportAxis *supportAxis() { return supportAxis_; }
// If true, either this axis or one of the detectorTowerLiftAxis
// attached to it received a movement command.
bool receivedTarget_;
// If set to true, the virtual axis is about to start a deferred movement
// (but is currently still collecting movement commands from its component
// axes)
bool startingDeferredMovement_;
/*
The collector cycle waits until this time in seconds has passed before
starting a deferred movement
*/
double deferredMovementWait_;
/**
* @brief Variable holding the axis error for later use
*
*/
int error_;
/**
* @brief Return a pointer to the axis controller
*/
virtual detectorTowerController *pController() override { return pC_; };
protected:
detectorTowerController *pC_;
detectorTowerLiftAxis *liftAxis_;
detectorTowerSupportAxis *supportAxis_;
double beamRadius_;
private:
friend class detectorTowerLiftAxis;
friend class detectorTowerSupportAxis;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,128 +0,0 @@
#ifndef detectorTowerAxis_H
#define detectorTowerAxis_H
#include "auxiliaryAxis.h"
#include "turboPmacAxis.h"
// Forward declaration of the controller class to resolve the cyclic dependency
// between the controller and the axis .h-file. See
// https://en.cppreference.com/w/cpp/language/class.
class detectorTowerController;
class detectorTowerAxis : public turboPmacAxis {
public:
/**
* @brief Construct a new detectorTowerAxis
*
* @param pController Pointer to the associated controller
* @param axisNo Index of the axis
* @param liftOffsetAxis Pointer to the attached axis which controls
* the lift offset
* @param tiltOffsetAxis Pointer to the attached axis which controls
* the tilt offset
*/
detectorTowerAxis(detectorTowerController *pController, int axisNo,
auxiliaryAxis *liftOffsetAxis,
auxiliaryAxis *tiltOffsetAxis);
/**
* @brief Destroy the detectorTowerAxis
*
*/
virtual ~detectorTowerAxis();
/**
* @brief Readout of some values from the controller at IOC startup
*
* @return asynStatus
*/
asynStatus init();
/**
* @brief Implementation of the `stop` function from asynMotorAxis
*
* @param acceleration Acceleration ACCEL from the motor record. This
* value is currently not used.
* @return asynStatus
*/
asynStatus stop(double acceleration);
/**
* @brief Implementation of the `doPoll` function from sinqAxis. The
* parameters are described in the documentation of `sinqAxis::doPoll`.
*
* @param moving
* @return asynStatus
*/
asynStatus doPoll(bool *moving);
/**
* @brief Set the target value for the detector angle and trigger a position
* collection cycle
*
* @param position
* @param relative
* @param min_velocity
* @param max_velocity
* @param acceleration
* @return asynStatus
*/
asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration);
/**
* @brief Start a movement to the target positions of this axis and the
* attached offset axes.
*
* @return asynStatus
*/
asynStatus startCombinedMove();
/**
* @brief This axis type has an absolute encoder by default
*
* @return asynStatus
*/
asynStatus readEncoderType();
/**
* @brief If the input value is true, move the axis into working position,
* otherwise into changer position.
*
* @return asynStatus
*/
asynStatus toggleWorkingChangerState(bool toChangingPosition);
/**
* @brief Implementation of the `doReset` method of sinqAxis
*
* @param on
* @return asynStatus
*/
asynStatus doReset();
// If true, either this axis or one of the auxiliaryAxis attached to it
// received a movement command.
bool receivedTarget_;
// If set to true, the virtual axis is about to start a deferred movement
// (but is currently still collecting movement commands from its component
// axes)
bool startingDeferredMovement_;
/*
The collector cycle waits until this time in seconds has passed before
starting a deferred movement
*/
double deferredMovementWait_;
protected:
detectorTowerController *pC_;
int error_;
auxiliaryAxis *tiltOffsetAxis_;
auxiliaryAxis *liftOffsetAxis_;
private:
friend class auxiliaryAxis;
};
#endif

View File

@ -1,10 +1,15 @@
#include "detectorTowerController.h"
#include "detectorTowerAxis.h"
#include "turboPmacController.h"
#include "detectorTowerAngleAxis.h"
#include "detectorTowerLiftAxis.h"
#include "detectorTowerSupportAxis.h"
#include <epicsExport.h>
#include <errlog.h>
#include <iocsh.h>
// Necessary to make M_PI available
#define _USE_MATH_DEFINES
#include <math.h>
/**
* @brief Construct a new detectorTowerController object
*
@ -47,44 +52,151 @@ detectorTowerController::detectorTowerController(
stringifyAsynStatus(status));
exit(-1);
}
status = createParam("CHANGE_STATE_RBV", asynParamInt32, &changeStateRBV_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
status = createParam("MOTOR_ORIGIN", asynParamFloat64, &motorOrigin_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
status = createParam("MOTOR_ADJUST_ORIGIN", asynParamFloat64,
&motorAdjustOrigin_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
status = createParam("MOTOR_AOHL_FROM_DRIVER", asynParamFloat64,
&motorAdjustOriginHighLimitFromDriver_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
status = createParam("MOTOR_AOLL_FROM_DRIVER", asynParamFloat64,
&motorAdjustOriginLowLimitFromDriver_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
}
detectorTowerController::~detectorTowerController() {}
asynStatus detectorTowerController::readInt32(asynUser *pasynUser,
epicsInt32 *value) {
// Check if the axis is a detectorTowerAxis
detectorTowerAxis *axis = getDetectorTowerAxis(pasynUser);
if (axis == nullptr) {
// This is apparently a "normal" turboPmacAxis or an auxiliaryAxis
return turboPmacController::readInt32(pasynUser, value);
} else {
// The detector tower cannot be disabled
if (pasynUser->reason == motorCanDisable_) {
// The detector axes cannot be disabled
if (pasynUser->reason == motorCanDisable()) {
detectorTowerAngleAxis *aAxis = getDetectorTowerAngleAxis(pasynUser);
if (aAxis != nullptr) {
*value = 0;
return asynSuccess;
}
detectorTowerLiftAxis *lAxis = getDetectorTowerLiftAxis(pasynUser);
if (lAxis != nullptr) {
*value = 0;
return asynSuccess;
}
detectorTowerSupportAxis *sAxis =
getDetectorTowerSupportAxis(pasynUser);
if (sAxis != nullptr) {
*value = 0;
return asynSuccess;
} else {
return turboPmacController::readInt32(pasynUser, value);
}
}
return turboPmacController::readInt32(pasynUser, value);
}
asynStatus detectorTowerController::writeInt32(asynUser *pasynUser,
epicsInt32 value) {
if (pasynUser->reason == changeState_) {
detectorTowerAngleAxis *aAxis = getDetectorTowerAngleAxis(pasynUser);
if (aAxis != nullptr) {
return aAxis->toggleWorkingChangerState(value);
}
detectorTowerLiftAxis *lAxis = getDetectorTowerLiftAxis(pasynUser);
if (lAxis != nullptr) {
return lAxis->angleAxis()->toggleWorkingChangerState(value);
}
detectorTowerSupportAxis *sAxis =
getDetectorTowerSupportAxis(pasynUser);
if (sAxis != nullptr) {
return sAxis->angleAxis()->toggleWorkingChangerState(value);
}
}
return turboPmacController::writeInt32(pasynUser, value);
}
asynStatus detectorTowerController::writeFloat64(asynUser *pasynUser,
epicsFloat64 value) {
int function = pasynUser->reason;
asynStatus status = asynSuccess;
// =====================================================================
detectorTowerAxis *axis = getDetectorTowerAxis(pasynUser);
if (function == motorAdjustOrigin_) {
if (axis == nullptr) {
// This is apparently a "normal" turboPmacAxis or an auxiliaryAxis
return turboPmacController::writeInt32(pasynUser, value);
} else {
if (function == changeState_) {
return axis->toggleWorkingChangerState(value);
} else {
return turboPmacController::writeInt32(pasynUser, value);
// Is this function called by a detector axis?
detectorTowerAngleAxis *aAxis = getDetectorTowerAngleAxis(pasynUser);
if (aAxis != nullptr) {
status = aAxis->adjustOrigin(value);
if (status != asynSuccess) {
return status;
}
return turboPmacController::writeFloat64(pasynUser, value);
}
detectorTowerLiftAxis *lAxis = getDetectorTowerLiftAxis(pasynUser);
if (lAxis != nullptr) {
status = lAxis->adjustOrigin(value);
if (status != asynSuccess) {
return status;
}
return turboPmacController::writeFloat64(pasynUser, value);
}
detectorTowerSupportAxis *sAxis =
getDetectorTowerSupportAxis(pasynUser);
if (sAxis != nullptr) {
status = sAxis->adjustOrigin(value);
if (status != asynSuccess) {
return status;
}
return turboPmacController::writeFloat64(pasynUser, value);
}
return asynSuccess;
} else {
return turboPmacController::writeFloat64(pasynUser, value);
}
}
@ -93,19 +205,738 @@ Access one of the axes of the controller via the axis adress stored in asynUser.
If the axis does not exist or is not a Axis, a nullptr is returned and an
error is emitted.
*/
detectorTowerAxis *
detectorTowerController::getDetectorTowerAxis(asynUser *pasynUser) {
detectorTowerAngleAxis *
detectorTowerController::getDetectorTowerAngleAxis(asynUser *pasynUser) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
return dynamic_cast<detectorTowerAxis *>(asynAxis);
return dynamic_cast<detectorTowerAngleAxis *>(asynAxis);
}
/*
Access one of the axes of the controller via the axis index.
If the axis does not exist or is not a Axis, the function must return Null
*/
detectorTowerAxis *detectorTowerController::getDetectorTowerAxis(int axisNo) {
detectorTowerAngleAxis *
detectorTowerController::getDetectorTowerAngleAxis(int axisNo) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
return dynamic_cast<detectorTowerAxis *>(asynAxis);
return dynamic_cast<detectorTowerAngleAxis *>(asynAxis);
}
/*
Access one of the axes of the controller via the axis adress stored in asynUser.
If the axis does not exist or is not a Axis, a nullptr is returned and an
error is emitted.
*/
detectorTowerLiftAxis *
detectorTowerController::getDetectorTowerLiftAxis(asynUser *pasynUser) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
return dynamic_cast<detectorTowerLiftAxis *>(asynAxis);
}
/*
Access one of the axes of the controller via the axis index.
If the axis does not exist or is not a Axis, the function must return Null
*/
detectorTowerLiftAxis *
detectorTowerController::getDetectorTowerLiftAxis(int axisNo) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
return dynamic_cast<detectorTowerLiftAxis *>(asynAxis);
}
/*
Access one of the axes of the controller via the axis adress stored in asynUser.
If the axis does not exist or is not a Axis, a nullptr is returned and an
error is emitted.
*/
detectorTowerSupportAxis *
detectorTowerController::getDetectorTowerSupportAxis(asynUser *pasynUser) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
return dynamic_cast<detectorTowerSupportAxis *>(asynAxis);
}
/*
Access one of the axes of the controller via the axis index.
If the axis does not exist or is not a Axis, the function must return Null
*/
detectorTowerSupportAxis *
detectorTowerController::getDetectorTowerSupportAxis(int axisNo) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
return dynamic_cast<detectorTowerSupportAxis *>(asynAxis);
}
asynStatus detectorTowerController::pollDetectorAxes(
bool *moving, detectorTowerAngleAxis *angleAxis,
detectorTowerLiftAxis *liftAxis, detectorTowerSupportAxis *supportAxis) {
// Return value for the poll
asynStatus pollStatus = asynSuccess;
// Status of read-write-operations of ASCII commands to the controller
asynStatus rwStatus = asynSuccess;
// Status of parameter library operations
asynStatus plStatus = asynSuccess;
char errorMessage[MAXBUF_] = {0};
// User message which was created in one of the other axis methods
char waitingErrorMessage[MAXBUF_] = {0};
char response[MAXBUF_] = {0};
int nvals = 0;
int angleDir = 0;
int liftDir = 0;
int error = 0;
int positionState = 0;
int notInPosition = 0;
double angle = 0.0;
double prevAngle = 0.0;
double limitsOffset = 0.0;
double angleHighLimit = 0.0;
double angleLowLimit = 0.0;
double liftHighLimit = 0.0;
double liftLowLimit = 0.0;
double lift = 0.0;
double prevLift = 0.0;
double liftOrigin = 0.0;
double liftAdjustOriginHighLimit = 0.0;
double liftAdjustOriginLowLimit = 0.0;
double angleOrigin = 0.0;
double angleAdjustOriginHighLimit = 0.0;
double angleAdjustOriginLowLimit = 0.0;
double supportOrigin = 0.0;
int angleAxisNo = angleAxis->axisNo();
int liftAxisNo = liftAxis->axisNo();
int supportAxisNo = supportAxis->axisNo();
/*
For messages which in principle concern all axes, use the smallest index
*/
int comAxisNo = angleAxisNo;
if (comAxisNo > liftAxisNo) {
comAxisNo = liftAxisNo;
}
if (comAxisNo > supportAxisNo) {
comAxisNo = supportAxisNo;
}
// =========================================================================
// Reset stored errors
angleAxis->error_ = 0;
/*
At the beginning of the poll, it is assumed that the axes have no status
problems and therefore all error indicators are reset. This does not affect
the PVs until callParamCallbacks has been called!
The motorStatusProblem_ field changes the motor record fields SEVR and STAT.
*/
setAxisParamChecked(angleAxis, motorStatusProblem, false);
setAxisParamChecked(liftAxis, motorStatusProblem, false);
setAxisParamChecked(supportAxis, motorStatusProblem, false);
setAxisParamChecked(angleAxis, motorStatusCommsError, false);
setAxisParamChecked(liftAxis, motorStatusCommsError, false);
setAxisParamChecked(supportAxis, motorStatusCommsError, false);
// Read the previous motor positions
plStatus = angleAxis->motorPosition(&prevAngle);
if (plStatus != asynSuccess) {
return plStatus;
}
plStatus = liftAxis->motorPosition(&prevLift);
if (plStatus != asynSuccess) {
return plStatus;
}
/*
Query the following information:
- In-position flag
- Position state
- Axis error
- Beam angle position (COM at AMOR)
- Beam angle limits
- Beam angle origin
- Beam angle origin limits
- Beam lift position (COZ at AMOR)
- Beam lift limits
- Beam lift origin
- Beam lift origin limits
- Support motor origin (FTZ at AMOR)
*/
const char *command = "P354 P358 P359 Q510 Q513 Q514 Q515 Q553 Q554 Q310 "
"Q351 Q352 Q315 Q353 Q354 Q415";
rwStatus = writeRead(liftAxisNo, command, response, 16);
if (rwStatus != asynSuccess) {
return rwStatus;
}
nvals =
sscanf(response,
"%d %d %d %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
&notInPosition, &positionState, &error, &angle, &angleHighLimit,
&angleLowLimit, &angleOrigin, &angleAdjustOriginHighLimit,
&angleAdjustOriginLowLimit, &lift, &liftHighLimit, &liftLowLimit,
&liftOrigin, &liftAdjustOriginHighLimit,
&liftAdjustOriginLowLimit, &supportOrigin);
if (nvals != 16) {
return couldNotParseResponse(command, response, liftAxisNo,
__PRETTY_FUNCTION__, __LINE__);
}
/*
The axis limits are set as: ({[]})
where [] are the positive and negative limits set in EPICS/NICOS, {} are the
software limits set on the MCU and () are the hardware limit switches. In
other words, the EPICS/NICOS limits should be stricter than the software
limits on the MCU which in turn should be stricter than the hardware limit
switches. For example, if the hardware limit switches are at [-10, 10], the
software limits could be at [-9, 9] and the EPICS / NICOS limits could be at
[-8, 8]. Therefore, we cannot use the software limits read from the MCU
directly, but need to shrink them a bit. In this case, we're shrinking them
by limitsOffset on both sides.
*/
// Angle
getAxisParamChecked(angleAxis, motorLimitsOffset, &limitsOffset);
angleHighLimit = angleHighLimit - limitsOffset;
angleLowLimit = angleLowLimit + limitsOffset;
// Lift
getAxisParamChecked(liftAxis, motorLimitsOffset, &limitsOffset);
liftHighLimit = liftHighLimit - limitsOffset;
liftLowLimit = liftLowLimit + limitsOffset;
// Store the axis error
angleAxis->error_ = error;
// Check if the tower is moving
if (notInPosition == 1 || positionState > 2) {
// By now, the controller has actually started the movement
angleAxis->startingDeferredMovement_ = false;
*moving = true;
} else {
if (angleAxis->startingDeferredMovement_) {
*moving = true;
} else {
*moving = false;
}
}
// Calculate the lift position /w consideration of the angle
lift = lift + angleAxis->beamRadius() * sin(angle * M_PI / 180);
// Create the unique callsite identifier manually so it can be used later in
// the shouldBePrinted calls.
msgPrintControlKey keyPosState =
msgPrintControlKey(portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__);
// Reset the count, if the status is not an error state
bool resetCountPosState = true;
// Interpret the position state
switch (positionState) {
case 0:
if (getMsgPrintControl().shouldBePrinted(keyPosState, true,
pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nTower not "
"ready (P358 = 0). Reset the tower via the \"Reset\" PV "
"before continuing.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetCountPosState = false;
snprintf(errorMessage, sizeof(errorMessage),
"Reset one of the tower axes.");
pollStatus = asynError;
break;
case 1:
// Axis ready
break;
case 2:
// Axis is moving to or in changing position
break;
case 3:
// Axis is moving to working state
resetCountPosState = false;
break;
case 4:
// Axis is adjusting the angle origin
break;
case 5:
// Axis is adjusting the lift origin
break;
case 6:
// Axis is adjusting the lift support (FTZ) origin
break;
default:
if (getMsgPrintControl().shouldBePrinted(keyPosState, true,
pasynUser())) {
asynPrint(
pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nReached unknown "
"state P354 = %d.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
positionState, getMsgPrintControl().getSuffix());
}
resetCountPosState = false;
snprintf(errorMessage, sizeof(errorMessage),
"Unknown state P358 = %d has been reached. Please call "
"the support.",
positionState);
pollStatus = asynError;
}
if (resetCountPosState) {
getMsgPrintControl().resetCount(keyPosState, pasynUser());
}
if (*moving) {
if ((angle - prevAngle) > 0) {
angleDir = 1;
} else {
angleDir = 0;
}
if ((lift - prevLift) > 0) {
liftDir = 1;
} else {
liftDir = 0;
}
}
// Create the unique callsite identifier manually so it can be used later in
// the shouldBePrinted calls.
msgPrintControlKey keyError =
msgPrintControlKey(portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__);
bool resetError = true;
// Error handling
switch (error) {
case 0:
// No error
break;
case 1:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(
pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nBrake COZ not "
"released (P359=1).%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Brake COZ not released. Try resetting the axis.");
pollStatus = asynError;
break;
case 2:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(
pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nMove command "
"rejected because axis is already moving.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Move command rejected because axis is already moving. Try "
"resetting the axis.");
pollStatus = asynError;
break;
case 3:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nError motor "
"FTZ.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Error in motor FTZ. Try resetting the axis.");
pollStatus = asynError;
break;
case 4:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(
pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis stopped "
"unexpectedly.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Axis stopped unexpectedly. Try resetting the axis and issue "
"the move command again.");
pollStatus = asynError;
break;
case 5:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(
pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nRelease removed "
"while moving.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(
errorMessage, sizeof(errorMessage),
"Axis release was removed while moving. Try resetting the axis "
"and issue the move command again.");
pollStatus = asynError;
break;
case 6:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(
pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nEmergency stop "
"activated.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Emergency stop activated.");
pollStatus = asynError;
break;
case 7:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nError motor "
"COZ.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Error in motor COZ. Try resetting the axis.");
pollStatus = asynError;
break;
case 8:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nError motor "
"COM.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Error in motor COM. Try resetting the axis.");
pollStatus = asynError;
break;
case 9:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nError motor "
"COX.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Error in motor COX. Try resetting the axis.");
pollStatus = asynError;
break;
case 10:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nHit end "
"switch FTZ.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(
errorMessage, sizeof(errorMessage),
"Hit end switch FTZ. Try moving the axis in the other direction.");
pollStatus = asynError;
break;
case 11:
// Following error
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(
pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nMaximum allowed "
"following error FTZ exceeded.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Maximum allowed following error exceeded (P359 = %d). "
"Check if movement range is blocked.",
error);
pollStatus = asynError;
break;
case 12:
// Exceeded negative limit for angle
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nMinimum "
"allowed angle exceeded.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Minimum allowed angle exceeded (P359 = %d). Try moving the "
"axis in the other direction.",
error);
pollStatus = asynError;
break;
case 13:
// Exceeded negative limit for angle
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nMaximum "
"allowed angle exceeded.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage),
"Maximum allowed angle exceeded (P359 = %d). Try moving the "
"axis in the other direction.",
error);
pollStatus = asynError;
break;
default:
if (getMsgPrintControl().shouldBePrinted(keyError, true, pasynUser())) {
asynPrint(
pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nUnknown error "
"P359 = %d.%s\n",
portName, comAxisNo, __PRETTY_FUNCTION__, __LINE__, error,
getMsgPrintControl().getSuffix());
}
resetError = false;
snprintf(errorMessage, sizeof(errorMessage), "Unknown error P359 = %d.",
error);
pollStatus = asynError;
break;
}
if (resetError) {
getMsgPrintControl().resetCount(keyError, pasynUser());
}
/*
Does the paramLib already contain an error message? If either this is the
case or if error != 0, report a status problem for all axes.
*/
getAxisParamChecked(angleAxis, motorMessageText, &waitingErrorMessage);
if (error != 0 || errorMessage[0] != '\0' ||
waitingErrorMessage[0] != '\0') {
setAxisParamChecked(angleAxis, motorStatusProblem, true);
setAxisParamChecked(liftAxis, motorStatusProblem, true);
setAxisParamChecked(supportAxis, motorStatusProblem, true);
}
// Update the error message text with the one created in this poll (in case
// it is not empty).
if (errorMessage[0] != '\0') {
setAxisParamChecked(angleAxis, motorMessageText, errorMessage);
setAxisParamChecked(liftAxis, motorMessageText, errorMessage);
setAxisParamChecked(supportAxis, motorMessageText, errorMessage);
}
// Update the working position state PV
setAxisParamChecked(angleAxis, positionStateRBV, positionState);
setAxisParamChecked(liftAxis, positionStateRBV, positionState);
setAxisParamChecked(supportAxis, positionStateRBV, positionState);
// The axes are always enabled
setAxisParamChecked(angleAxis, motorEnableRBV, true);
setAxisParamChecked(liftAxis, motorEnableRBV, true);
setAxisParamChecked(supportAxis, motorEnableRBV, true);
// Are the axes currently moving?
setAxisParamChecked(angleAxis, motorStatusMoving, *moving);
setAxisParamChecked(liftAxis, motorStatusMoving, *moving);
setAxisParamChecked(supportAxis, motorStatusMoving, *moving);
// Is the axis movement done?
setAxisParamChecked(angleAxis, motorStatusDone, !(*moving));
setAxisParamChecked(liftAxis, motorStatusDone, !(*moving));
setAxisParamChecked(supportAxis, motorStatusDone, !(*moving));
// In which direction are the axes currently moving?
setAxisParamChecked(angleAxis, motorStatusDirection, angleDir);
setAxisParamChecked(liftAxis, motorStatusDirection, liftDir);
// Using the lift direction for the support axis is done on purpose!
setAxisParamChecked(supportAxis, motorStatusDirection, liftDir);
// High limits from hardware
setAxisParamChecked(angleAxis, motorHighLimitFromDriver, angleHighLimit);
setAxisParamChecked(liftAxis, motorHighLimitFromDriver, liftHighLimit);
// Using the lift high limit for the support axis is done on purpose!
setAxisParamChecked(supportAxis, motorHighLimitFromDriver, liftHighLimit);
// Low limits from hardware
setAxisParamChecked(angleAxis, motorLowLimitFromDriver, angleLowLimit);
setAxisParamChecked(liftAxis, motorLowLimitFromDriver, liftLowLimit);
// Using the lift low limit for the support axis is done on purpose!
setAxisParamChecked(supportAxis, motorLowLimitFromDriver, liftLowLimit);
// Write the motor origin
setAxisParamChecked(angleAxis, motorOrigin, angleOrigin);
setAxisParamChecked(liftAxis, motorOrigin, liftOrigin);
setAxisParamChecked(supportAxis, motorOrigin, supportOrigin);
// Origin adjustment high limit
setAxisParamChecked(angleAxis, motorAdjustOriginHighLimitFromDriver,
angleAdjustOriginHighLimit);
setAxisParamChecked(liftAxis, motorAdjustOriginHighLimitFromDriver,
liftAdjustOriginHighLimit);
// Using the lift high limit for the support axis is done on purpose!
setAxisParamChecked(supportAxis, motorAdjustOriginHighLimitFromDriver,
liftAdjustOriginHighLimit);
// Origin adjustment low limit
setAxisParamChecked(angleAxis, motorAdjustOriginLowLimitFromDriver,
angleAdjustOriginLowLimit);
setAxisParamChecked(liftAxis, motorAdjustOriginLowLimitFromDriver,
liftAdjustOriginLowLimit);
// Using the lift low limit for the support axis is done on purpose!
setAxisParamChecked(supportAxis, motorAdjustOriginLowLimitFromDriver,
liftAdjustOriginLowLimit);
// Axes positions
plStatus = angleAxis->setMotorPosition(angle);
if (plStatus != asynSuccess) {
return plStatus;
}
plStatus = liftAxis->setMotorPosition(lift);
if (plStatus != asynSuccess) {
return plStatus;
}
// Using the lift position for the support axis is done on purpose!
plStatus = supportAxis->setMotorPosition(lift);
if (plStatus != asynSuccess) {
return plStatus;
}
// Update the parameter library for all three axes
bool wantToPrint = false;
plStatus = angleAxis->callParamCallbacks();
wantToPrint = plStatus != asynSuccess;
if (getMsgPrintControl().shouldBePrinted(portName, angleAxisNo,
__PRETTY_FUNCTION__, __LINE__,
wantToPrint, pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\ncallParamCallbacks failed with %s.%s\n",
portName, angleAxisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(pollStatus),
getMsgPrintControl().getSuffix());
}
if (wantToPrint) {
pollStatus = plStatus;
}
plStatus = liftAxis->callParamCallbacks();
wantToPrint = plStatus != asynSuccess;
if (getMsgPrintControl().shouldBePrinted(portName, liftAxisNo,
__PRETTY_FUNCTION__, __LINE__,
wantToPrint, pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\ncallParamCallbacks failed with %s.%s\n",
portName, liftAxisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(pollStatus),
getMsgPrintControl().getSuffix());
}
if (wantToPrint) {
pollStatus = plStatus;
}
plStatus = supportAxis->callParamCallbacks();
wantToPrint = plStatus != asynSuccess;
if (getMsgPrintControl().shouldBePrinted(portName, supportAxisNo,
__PRETTY_FUNCTION__, __LINE__,
wantToPrint, pasynUser())) {
asynPrint(pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\ncallParamCallbacks failed with %s.%s\n",
portName, supportAxisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(pollStatus),
getMsgPrintControl().getSuffix());
}
if (wantToPrint) {
pollStatus = plStatus;
}
/*
Reset the message text for the next poll cycle AFTER updating the PVs.
This makes sure that non-persistent error messages are shown for at least
one poll cycle, but are cleared afterwards. Persisting error messages will
be recreated during each poll.
*/
setAxisParamChecked(angleAxis, motorMessageText, "");
setAxisParamChecked(liftAxis, motorMessageText, "");
setAxisParamChecked(supportAxis, motorMessageText, "");
return pollStatus;
}
/*************************************************************************************/

View File

@ -8,10 +8,15 @@
#ifndef detectorTowerController_H
#define detectorTowerController_H
#include "auxiliaryAxis.h"
#include "detectorTowerAxis.h"
#include "turboPmacController.h"
// Forward declaration of the axis classes to resolve the cyclic dependency
// between the controller and the axis .h-file. See
// https://en.cppreference.com/w/cpp/language/class.
class detectorTowerAngleAxis;
class detectorTowerLiftAxis;
class detectorTowerSupportAxis;
class detectorTowerController : public turboPmacController {
public:
@ -19,7 +24,7 @@ class detectorTowerController : public turboPmacController {
* @brief Construct a new detectorTowerController object
*
* This controller object can handle both "normal" TurboPMAC axes created
with the turboPmacAxis constructor and the special detectorTowerAxis.
with the turboPmacAxis constructor and the special detectorTowerAngleAxis.
*
* @param portName See sinqController constructor
* @param ipPortConfigName See sinqController constructor
@ -34,6 +39,8 @@ class detectorTowerController : public turboPmacController {
int numAxes, double movingPollPeriod,
double idlePollPeriod, double comTimeout);
virtual ~detectorTowerController();
/**
* @brief Overloaded function of turboPmacController
*
@ -52,34 +59,105 @@ class detectorTowerController : public turboPmacController {
*/
asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
/**
* @brief Overloaded function of turboPmacController
*
* @param pasynUser Specify the axis via the asynUser
* @param value New value
* @return asynStatus
*/
asynStatus writeFloat64(asynUser *pasynUser, epicsFloat64 value);
/**
* @brief Get the axis object
*
* @param pasynUser Specify the axis via the asynUser
* @return detectorTowerAxis* If no axis could be found, this is a
* nullptr
* @return detectorTowerAngleAxis* If no axis could be found, this is
* a nullptr
*/
detectorTowerAxis *getDetectorTowerAxis(asynUser *pasynUser);
detectorTowerAngleAxis *getDetectorTowerAngleAxis(asynUser *pasynUser);
/**
* @brief Get the axis object
*
* @param axisNo Specify the axis via its index
* @return detectorTowerAxis* If no axis could be found, this is a
* nullptr
* @return detectorTowerAngleAxis* If no axis could be found, this is
* a nullptr
*/
detectorTowerAxis *getDetectorTowerAxis(int axisNo);
detectorTowerAngleAxis *getDetectorTowerAngleAxis(int axisNo);
/**
* @brief Get the axis object
*
* @param pasynUser Specify the axis via the asynUser
* @return detectorTowerLiftAxis* If no axis could be found,
* this is a nullptr
*/
detectorTowerLiftAxis *getDetectorTowerLiftAxis(asynUser *pasynUser);
/**
* @brief Get the axis object
*
* @param axisNo Specify the axis via its index
* @return detectorTowerLiftAxis* If no axis could be found,
* this is a nullptr
*/
detectorTowerLiftAxis *getDetectorTowerLiftAxis(int axisNo);
/**
* @brief Get the axis object
*
* @param pasynUser Specify the axis via the asynUser
* @return detectorTowerSupportAxis* If no axis could be found,
* this is a nullptr
*/
detectorTowerSupportAxis *getDetectorTowerSupportAxis(asynUser *pasynUser);
/**
* @brief Get the axis object
*
* @param axisNo Specify the axis via its index
* @return detectorTowerSupportAxis* If no axis could be found,
* this is a nullptr
*/
detectorTowerSupportAxis *getDetectorTowerSupportAxis(int axisNo);
/**
* @brief Common poll function for both lift and angle axes
*
* @param moving
* @param angleAxis
* @param liftAxis
* @return asynStatus
*/
asynStatus pollDetectorAxes(bool *moving, detectorTowerAngleAxis *angleAxis,
detectorTowerLiftAxis *liftAxis,
detectorTowerSupportAxis *supportAxis);
// Accessors for additional PVs
int positionStateRBV() { return positionStateRBV_; }
int changeState() { return changeState_; }
int changeStateRBV() { return changeStateRBV_; }
int motorOrigin() { return motorOrigin_; }
int motorAdjustOrigin() { return motorAdjustOrigin_; }
int motorAdjustOriginHighLimitFromDriver() {
return motorAdjustOriginHighLimitFromDriver_;
}
int motorAdjustOriginLowLimitFromDriver() {
return motorAdjustOriginLowLimitFromDriver_;
}
private:
// Indices of additional PVs
#define FIRST_detectorTower_PARAM positionStateRBV_
int positionStateRBV_;
int changeState_;
#define LAST_detectorTower_PARAM changeState_
int changeStateRBV_;
int motorOrigin_;
int motorAdjustOrigin_;
int motorAdjustOriginHighLimitFromDriver_;
int motorAdjustOriginLowLimitFromDriver_;
#define LAST_detectorTower_PARAM motorAdjustOriginLowLimitFromDriver_
};
#define NUM_detectorTower_DRIVER_PARAMS \
(&LAST_detectorTower_PARAM - &FIRST_detectorTower_PARAM + 1)

View File

@ -0,0 +1,225 @@
#include "detectorTowerLiftAxis.h"
#include "detectorTowerAngleAxis.h"
#include "detectorTowerController.h"
#include "detectorTowerSupportAxis.h"
#include "turboPmacController.h"
#include <epicsExport.h>
#include <errlog.h>
#include <iocsh.h>
/*
Contains all instances of detectorTowerLiftAxis which have been created and
is used in the initialization hook function.
*/
static std::vector<detectorTowerLiftAxis *> axes;
/**
* @brief Hook function to perform certain actions during the IOC initialization
*
* @param iState
*/
static void epicsInithookFunction(initHookState iState) {
if (iState == initHookAfterDatabaseRunning) {
// Iterate through all axes of each and call the initialization method
// on each one of them.
for (std::vector<detectorTowerLiftAxis *>::iterator itA = axes.begin();
itA != axes.end(); ++itA) {
detectorTowerLiftAxis *axis = *itA;
axis->init();
}
}
}
detectorTowerLiftAxis::detectorTowerLiftAxis(detectorTowerController *pC,
int axisNo,
detectorTowerAngleAxis *angleAxis)
: turboPmacAxis(pC, axisNo, false), pC_(pC) {
/*
The superclass constructor sinqAxis calls in turn its superclass
constructor asynMotorAxis. In the latter, a pointer to the constructed
object this is stored inside the array pAxes_:
pC->pAxes_[axisNo] = this;
Therefore, the axes are managed by the controller pC. If axisNo is out
of bounds, asynMotorAxis prints an error (see
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorAxis.cpp,
line 40). However, we want the IOC creation to stop completely, since
this is a configuration error.
*/
if (axisNo >= pC->numAxes()) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d: FATAL ERROR: "
"Axis index %d must be smaller than the total number of axes "
"%d",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axisNo_, pC->numAxes());
exit(-1);
}
// Register the hook function during construction of the first axis
// object
if (axes.empty()) {
initHookRegister(&epicsInithookFunction);
}
// Collect all axes into this list which will be used in the hook
// function
axes.push_back(this);
angleAxis_ = angleAxis;
angleAxis->liftAxis_ = this;
}
detectorTowerLiftAxis::~detectorTowerLiftAxis(void) {
// Since the controller memory is managed somewhere else, we don't need
// to clean up the pointer pC here.
}
asynStatus detectorTowerLiftAxis::init() {
// Local variable declaration
asynStatus status = asynSuccess;
double motorRecResolution = 0.0;
char response[pC_->MAXBUF_] = {0};
int positionState = 0;
int nvals = 0;
// The parameter library takes some time to be initialized. Therefore we
// wait until the status is not asynParamUndefined anymore.
time_t now = time(NULL);
time_t maxInitTime = 60;
while (1) {
status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution(),
&motorRecResolution);
if (status == asynParamUndefined) {
if (now + maxInitTime < time(NULL)) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\nInitializing the parameter library failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__);
return asynError;
}
} else if (status == asynSuccess) {
break;
} else if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
// Initialize the motorStatusMoving flag
setAxisParamChecked(this, motorStatusMoving, false);
// Check if we are currently in the changer position and update the PV
// accordingly
status = pC_->writeRead(axisNo_, "P358", response, 1);
nvals = sscanf(response, "%d", &positionState);
if (nvals != 1) {
return pC_->couldNotParseResponse("P358", response, axisNo(),
__PRETTY_FUNCTION__, __LINE__);
}
setAxisParamChecked(this, changeStateRBV, positionState == 2);
return callParamCallbacks();
}
// Perform the actual poll
asynStatus detectorTowerLiftAxis::poll(bool *moving) {
asynStatus status = asynSuccess;
// Is this axis the one with the smallest index?
// If not, just read out the movement status and update *moving
if (axisNo() < angleAxis()->axisNo() &&
axisNo() < angleAxis()->supportAxis()->axisNo()) {
status = pC_->pollDetectorAxes(moving, angleAxis(), this,
angleAxis()->supportAxis());
} else {
getAxisParamChecked(this, motorStatusMoving, moving);
}
setWasMoving(*moving);
return status;
}
asynStatus detectorTowerLiftAxis::doPoll(bool *moving) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nThe doPoll method "
"of this axis type should not be reachable. This is a bug.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
asynStatus detectorTowerLiftAxis::doMove(double position, int relative,
double min_velocity,
double max_velocity,
double acceleration) {
double motorRecResolution = 0.0;
getAxisParamChecked(this, motorRecResolution, &motorRecResolution);
// Signal to the deferredMovementCollectorLoop (of the
// detectorTowerAngleAxis) that a movement should be started to the
// defined target position.
setTargetPosition(position * motorRecResolution);
angleAxis_->receivedTarget_ = true;
return asynSuccess;
}
asynStatus detectorTowerLiftAxis::reset() { return angleAxis()->reset(); };
asynStatus detectorTowerLiftAxis::stop(double acceleration) {
return angleAxis()->stop(acceleration);
};
asynStatus detectorTowerLiftAxis::readEncoderType() {
return angleAxis()->readEncoderType();
}
asynStatus detectorTowerLiftAxis::adjustOrigin(double newOrigin) {
asynStatus status = asynSuccess;
char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0};
int positionState = 0;
// =========================================================================
getAxisParamChecked(this, positionStateRBV, &positionState);
// If the axis is in changer position, it must be moved into working
// position before any move can be started.
bool isInChangerPos = positionState == 2 || positionState == 3;
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
isInChangerPos, pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis cannot be "
"moved because it is moving from working to changer "
"position, is in changer position or is moving from changer "
"to working position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
// Set the new adjust for the lift axis
snprintf(command, sizeof(command), "Q356=%lf P350=5", newOrigin);
// We don't expect an answer
status = pC_->writeRead(axisNo_, command, response, 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nSetting new "
"lift origin %lf failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
newOrigin);
setAxisParamChecked(this, motorStatusProblem, true);
}
return status;
}

121
src/detectorTowerLiftAxis.h Normal file
View File

@ -0,0 +1,121 @@
#ifndef detectorTowerLiftAxis_H
#define detectorTowerLiftAxis_H
#include "detectorTowerController.h"
#include "turboPmacAxis.h"
class detectorTowerLiftAxis : public turboPmacAxis {
public:
/**
* @brief Construct a new detectorTowerAngleAxis
*
* @param pController Pointer to the associated controller
* @param axisNo Index of the axis
*/
detectorTowerLiftAxis(detectorTowerController *pController, int axisNo,
detectorTowerAngleAxis *angleAxis);
virtual ~detectorTowerLiftAxis();
/**
* @brief Readout of some values from the controller at IOC startup
*
* @return asynStatus
*/
asynStatus init();
/**
* @brief Implementation of the `stop` function from asynMotorAxis
*
* @param acceleration Acceleration ACCEL from the motor record. This
* value is currently not used.
* @return asynStatus
*/
asynStatus stop(double acceleration);
/**
* @brief Poll all detector tower axes, if this axis is the one with the
* smallest index.
*
* We do not use the doPoll framework from sinqMotor here on purpose, since
* we want to e.g. reset the motorStatusProblem for all axes at once at the
* beginning of the poll.
*
* @param moving
* @return asynStatus
*/
asynStatus poll(bool *moving);
asynStatus doPoll(bool *moving);
/**
* @brief Set the target value for the detector lift and trigger a position
* collection cycle
*
* @param position
* @param relative
* @param min_velocity
* @param max_velocity
* @param acceleration
* @return asynStatus
*/
asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration);
/**
* @brief Calls the `reset` function of the associated angle axis.
*
* @param on
* @return asynStatus
*/
asynStatus reset();
/**
* @brief Move the axis to the position `newOrigin` and recalibrate
*
* When calling this function, the lift axis moves to `newOrigin` and the
* hardware sets this position as the new origin.
*
* @param origin
* @return asynStatus
*/
asynStatus adjustOrigin(double newOrigin);
/**
* @brief Move the support motor of the beam lift to the position
* `newOrigin` and recalibrate
*
* When calling this function, the lift axis support motor moves to
* `newOrigin` and the hardware sets this position as the new origin.
*
* @param origin
* @return asynStatus
*/
asynStatus adjustSupportOrigin(double newOrigin);
/**
* @brief Return a pointer to the associated angle axis
*
* @return detectorTowerAngleAxis*
*/
detectorTowerAngleAxis *angleAxis() { return angleAxis_; }
/**
* @brief This axis type has an absolute encoder by default
*
* @return asynStatus
*/
asynStatus readEncoderType();
/**
* @brief Return a pointer to the axis controller
*/
virtual detectorTowerController *pController() override { return pC_; };
protected:
detectorTowerController *pC_;
detectorTowerAngleAxis *angleAxis_;
private:
friend class detectorTowerAngleAxis;
};
#endif

View File

@ -0,0 +1,209 @@
#include "detectorTowerSupportAxis.h"
#include "detectorTowerAngleAxis.h"
#include "detectorTowerController.h"
#include "detectorTowerLiftAxis.h"
#include "turboPmacController.h"
#include <epicsExport.h>
#include <errlog.h>
#include <iocsh.h>
/*
Contains all instances of detectorTowerSupportAxis which have been created and
is used in the initialization hook function.
*/
static std::vector<detectorTowerSupportAxis *> axes;
/**
* @brief Hook function to perform certain actions during the IOC initialization
*
* @param iState
*/
static void epicsInithookFunction(initHookState iState) {
if (iState == initHookAfterDatabaseRunning) {
// Iterate through all axes of each and call the initialization method
// on each one of them.
for (std::vector<detectorTowerSupportAxis *>::iterator itA =
axes.begin();
itA != axes.end(); ++itA) {
detectorTowerSupportAxis *axis = *itA;
axis->init();
}
}
}
detectorTowerSupportAxis::detectorTowerSupportAxis(
detectorTowerController *pC, int axisNo, detectorTowerAngleAxis *angleAxis)
: turboPmacAxis(pC, axisNo, false), pC_(pC) {
/*
The superclass constructor sinqAxis calls in turn its superclass constructor
asynMotorAxis. In the latter, a pointer to the constructed object this is
stored inside the array pAxes_:
pC->pAxes_[axisNo] = this;
Therefore, the axes are managed by the controller pC. If axisNo is out of
bounds, asynMotorAxis prints an error (see
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorAxis.cpp,
line 40). However, we want the IOC creation to stop completely, since this
is a configuration error.
*/
if (axisNo >= pC->numAxes()) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d: FATAL ERROR: "
"Axis index %d must be smaller than the total number of axes "
"%d",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axisNo_, pC->numAxes());
exit(-1);
}
// Register the hook function during construction of the first axis object
if (axes.empty()) {
initHookRegister(&epicsInithookFunction);
}
// Collect all axes into this list which will be used in the hook
// function
axes.push_back(this);
// Link the axes to each other
angleAxis_ = angleAxis;
angleAxis_->supportAxis_ = this;
}
detectorTowerSupportAxis::~detectorTowerSupportAxis(void) {
// Since the controller memory is managed somewhere else, we don't need to
// clean up the pointer pC here.
}
asynStatus detectorTowerSupportAxis::init() {
// Local variable declaration
asynStatus status = asynSuccess;
double motorRecResolution = 0.0;
char response[pC_->MAXBUF_] = {0};
int positionState = 0;
int nvals = 0;
// The parameter library takes some time to be initialized. Therefore we
// wait until the status is not asynParamUndefined anymore.
time_t now = time(NULL);
time_t maxInitTime = 60;
while (1) {
status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution(),
&motorRecResolution);
if (status == asynParamUndefined) {
if (now + maxInitTime < time(NULL)) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\nInitializing the parameter library failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__);
return asynError;
}
} else if (status == asynSuccess) {
break;
} else if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
// Initialize the motorStatusMoving flag
setAxisParamChecked(this, motorStatusMoving, false);
// Check if we are currently in the changer position and update the PV
// accordingly
status = pC_->writeRead(axisNo_, "P358", response, 1);
nvals = sscanf(response, "%d", &positionState);
if (nvals != 1) {
return pC_->couldNotParseResponse("P358", response, axisNo(),
__PRETTY_FUNCTION__, __LINE__);
}
setAxisParamChecked(this, changeStateRBV, positionState == 2);
return callParamCallbacks();
}
// Perform the actual poll
asynStatus detectorTowerSupportAxis::poll(bool *moving) {
asynStatus status = asynSuccess;
// Is this axis the one with the smallest index?
// If not, just read out the movement status and update *moving
if (axisNo() < angleAxis()->liftAxis()->axisNo() &&
axisNo() < angleAxis()->axisNo()) {
status = pC_->pollDetectorAxes(moving, angleAxis(),
angleAxis()->liftAxis(), this);
} else {
getAxisParamChecked(this, motorStatusMoving, moving);
}
setWasMoving(*moving);
return status;
}
asynStatus detectorTowerSupportAxis::doPoll(bool *moving) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nThe doPoll method "
"of this axis type should not be reachable. This is a bug.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
asynStatus detectorTowerSupportAxis::stop(double acceleration) {
return angleAxis()->stop(acceleration);
}
asynStatus detectorTowerSupportAxis::reset() { return angleAxis()->reset(); };
asynStatus detectorTowerSupportAxis::readEncoderType() {
return angleAxis()->readEncoderType();
}
asynStatus detectorTowerSupportAxis::adjustOrigin(double newOrigin) {
asynStatus status = asynSuccess;
char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0};
int positionState = 0;
// =========================================================================
getAxisParamChecked(this, positionStateRBV, &positionState);
// If the axis is in changer position, it must be moved into working
// position before any move can be started.
bool isInChangerPos = positionState == 2 || positionState == 3;
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
isInChangerPos, pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis cannot be "
"moved because it is moving from working to changer "
"position, is in changer position or is moving from changer "
"to working position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
// Set the new adjust for the lift axis
snprintf(command, sizeof(command), "Q656=%lf P350=6", newOrigin);
// We don't expect an answer
status = pC_->writeRead(axisNo_, command, response, 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nSetting new "
"lift origin %lf failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
newOrigin);
setAxisParamChecked(this, motorStatusProblem, true);
}
return status;
}

View File

@ -0,0 +1,123 @@
#ifndef detectorTowerSupportAxis_H
#define detectorTowerSupportAxis_H
#include "detectorTowerController.h"
#include "turboPmacAxis.h"
/**
* @brief Passive axis which is mostly controlled indirectly by the hardware
*
*/
class detectorTowerSupportAxis : public turboPmacAxis {
public:
/**
* @brief Construct a new detectorTowerSupportAxis
*
* @param pController Pointer to the associated controller
* @param axisNo Index of the axis
* @param liftAxis Pointer to the associated lift axis
*/
detectorTowerSupportAxis(detectorTowerController *pController, int axisNo,
detectorTowerAngleAxis *angleAxis);
virtual ~detectorTowerSupportAxis();
/**
* @brief Readout of some values from the controller at IOC startup
*
* @return asynStatus
*/
asynStatus init();
/**
* @brief Implementation of the `stop` function from asynMotorAxis
*
* @param acceleration Acceleration ACCEL from the motor record. This
* value is currently not used.
* @return asynStatus
*/
asynStatus stop(double acceleration);
/**
* @brief Poll all detector tower axes, if this axis is the one with the
* smallest index.
*
* We do not use the doPoll framework from sinqMotor here on purpose, since
* we want to e.g. reset the motorStatusProblem for all axes at once at the
* beginning of the poll.
*
* @param moving
* @return asynStatus
*/
asynStatus poll(bool *moving);
asynStatus doPoll(bool *moving);
/**
* @brief This axis cannot be moved directly
*
* @param position
* @param relative
* @param min_velocity
* @param max_velocity
* @param acceleration
* @return asynStatus
*/
asynStatus doMove(double position, int relative, double min_velocity,
double max_velocity, double acceleration) {
return asynSuccess;
}
/**
* @brief If the input value is true, move the axis into working position,
* otherwise into changer position.
*
* @return asynStatus
*/
asynStatus toggleWorkingChangerState(bool toChangingPosition);
/**
* @brief Calls the `reset` function of the associated lift axis.
*
* @param on
* @return asynStatus
*/
asynStatus reset();
/**
* @brief Move the axis to the position `newOrigin` and recalibrate
*
* When calling this function, the angle axis moves to `newOrigin` and the
* hardware sets this position as the new origin.
*
* @param origin
* @return asynStatus
*/
asynStatus adjustOrigin(double newOrigin);
/**
* @brief Return a pointer to the associated angle axis
*
* @return detectorTowerAngleAxis*
*/
detectorTowerAngleAxis *angleAxis() { return angleAxis_; }
/**
* @brief This axis type has an absolute encoder by default
*
* @return asynStatus
*/
asynStatus readEncoderType();
/**
* @brief Return a pointer to the axis controller
*/
virtual detectorTowerController *pController() override { return pC_; };
protected:
detectorTowerController *pC_;
detectorTowerAngleAxis *angleAxis_;
private:
friend class detectorTowerLiftAxis;
};
#endif

1
turboPmac Submodule

Submodule turboPmac added at f423002d23