diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..da3868c --- /dev/null +++ b/.clang-format @@ -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 +... + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0c3cbc8 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# Use the PSI build system +include /ioc/tools/driver.makefile + +MODULE=beamShift +BUILDCLASSES=Linux +EPICS_VERSIONS=7.0.7 +ARCH_FILTER=RHEL% + +# Additional module dependencies +REQUIRED+=motorBase + +# Specify the version of motorBase we want to build against +motorBase_VERSION=7.2.2 + +# These headers allow to depend on this library for derived drivers. +HEADERS += src/beamShiftAxis.h + +# Source files to build +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/beamShiftAxis.cpp + +# Store the record files +TEMPLATES += turboPmac/sinqMotor/db/asynRecord.db +TEMPLATES += turboPmac/sinqMotor/db/sinqMotor.db +TEMPLATES += turboPmac/db/turboPmac.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/beamShift.dbd + +USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result -Werror # -Wpedantic // Does not work because EPICS macros trigger warnings diff --git a/README.md b/README.md new file mode 100644 index 0000000..2dfa4f3 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# beamShift + +## Please read the documentation of sinqMotor first: https://git.psi.ch/sinq-epics-modules/sinqmotor + +## Overview + +This is a driver for a "beam shift": a combined movement where two physical axes moves synchronously and are treated as a single virtual axis. It is based on the turboPmac driver (https://gitea.psi.ch/lin-epics-modules/turboPmac) and behaves mostly like a normal turboPmac driver except for the following: +- The virtual axis cannot be disabled, but can be enabled in case its physical axes are inhibited. +- The virtual axis cannot perform a reference / home drive since it has an absolute encoder. +- The virtual axis has a custom error set. + +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. + +## User guide + +### IOC startup script + +beamShift exports the following IOC shell functions: +- `beamShiftAxis`: Create a new axis object. + +The full turboPmacX.cmd file looks like this: + +``` +# Define the name of the controller and the corresponding port +epicsEnvSet("DRIVER_PORT","turboPmacX") +epicsEnvSet("IP_PORT","p$(DRIVER_PORT)") + +# Create the TCP/IP socket used to talk with the controller. The socket can be adressed from within the IOC shell via the port name. +# We do not use the standard asyn port driver here, but a PMAC-specific one which enables the usage of StreamDevices for +# communicating with the controller directly. +pmacAsynIPPortConfigure("$(IP_PORT)","172.28.101.24:1025") + +# Create the controller object with the defined name and connect it to the socket via the port name. +# The other parameters are as follows: +# 8: Maximum number of axes +# 0.05: Busy poll period in seconds +# 1: Idle poll period in seconds +# 1: Socket communication timeout in seconds +turboPmacController("$(DRIVER_PORT)", "$(IP_PORT)", 8, 0.05, 1, 1); + +# Define some axes for the specified MCU at the given slot (1, 2 and 5). No slot may be used twice! +beamShiftAxis("$(DRIVER_PORT)",1); +turboPmacAxis("$(DRIVER_PORT)",2); +turboPmacAxis("$(DRIVER_PORT)",5); + +# Set the number of subsequent timeouts +setMaxSubsequentTimeouts("$(DRIVER_PORT)", 20); + +# Configure the timeout frequency watchdog: A maximum of 10 timeouts are allowed in 300 seconds before an alarm message is sent. +setThresholdComTimeout("$(DRIVER_PORT)", 300, 10); + +# Parametrize the EPICS record database with the substitution file named after the MCU. +epicsEnvSet("SINQDBPATH","$(turboPmac_DB)/sinqMotor.db") +dbLoadTemplate("$(TOP)/$(DRIVER_PORT).substitutions", "INSTR=$(INSTR)$(DRIVER_PORT):,CONTROLLER=$(DRIVER_PORT)") +epicsEnvSet("SINQDBPATH","$(turboPmac_DB)/turboPmac.db") +dbLoadTemplate("$(TOP)/$(DRIVER_PORT).substitutions", "INSTR=$(INSTR)$(DRIVER_PORT):,CONTROLLER=$(DRIVER_PORT)") +dbLoadRecords("$(turboPmac_DB)/asynRecord.db","P=$(INSTR)$(DRIVER_PORT),PORT=$(IP_PORT)") +``` + +### Additional records + +`beamShift` provides no additional records. + +## Developer guide + +### Versioning + +Please see the documentation for the module sinqMotor: https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md. + +### How to build it + +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 sinqMotor, 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. diff --git a/src/beamShift.dbd b/src/beamShift.dbd new file mode 100644 index 0000000..f7a0e9c --- /dev/null +++ b/src/beamShift.dbd @@ -0,0 +1,4 @@ +#--------------------------------------------- +# SINQ specific DB definitions +#--------------------------------------------- +registrar(beamShiftAxisRegister) \ No newline at end of file diff --git a/src/beamShiftAxis.cpp b/src/beamShiftAxis.cpp new file mode 100644 index 0000000..3947cdb --- /dev/null +++ b/src/beamShiftAxis.cpp @@ -0,0 +1,689 @@ +#include "beamShiftAxis.h" +#include "asynOctetSyncIO.h" +#include "epicsExport.h" +#include "iocsh.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* +Contains all instances of beamShiftAxis which have been created and is +used in the initialization hook function. + */ +static std::vector 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::iterator itA = axes.begin(); + itA != axes.end(); ++itA) { + beamShiftAxis *axis = *itA; + axis->init(); + } + } +} + +beamShiftAxis::beamShiftAxis(turboPmacController *pC, int axisNo) + : turboPmacAxis(pC, axisNo), pC_(pC) { + + asynStatus status = asynSuccess; + + // 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); + + // Initialize all member variables + axisStatus_ = 0; + + // Provide initial values for some parameter library entries + status = pC_->setIntegerParam(axisNo, pC_->rereadEncoderPosition(), 0); + if (status != asynSuccess) { + asynPrint( + pC_->pasynUser(), ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR " + "(setting a parameter value failed with %s)\n. Terminating IOC", + pC_->portName, axisNo, __PRETTY_FUNCTION__, __LINE__, + pC_->stringifyAsynStatus(status)); + exit(-1); + } + + // The girder translation cannot be disabled + status = pC_->setIntegerParam(axisNo, pC_->motorCanDisable(), 0); + if (status != asynSuccess) { + pC_->paramLibAccessFailed(status, "motorCanDisable", axisNo, + __PRETTY_FUNCTION__, __LINE__); + } + + // The girder translation speed cannot be changed + status = pC_->setIntegerParam(axisNo, pC_->motorCanSetSpeed(), 0); + if (status != asynSuccess) { + pC_->paramLibAccessFailed(status, "motorCanDisable", axisNo, + __PRETTY_FUNCTION__, __LINE__); + } +} + +beamShiftAxis::~beamShiftAxis(void) { + // Since the controller memory is managed somewhere else, we don't need to + // clean up the pointer pC here. +} + +asynStatus beamShiftAxis::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_->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__); + } + } + + // Update the parameter library immediately + status = callParamCallbacks(); + if (status != asynSuccess) { + // If we can't communicate with the parameter library, it doesn't make + // sense to try and upstream this to the user -> Just log the error + asynPrint( + pC_->pasynUser(), ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\ncallParamCallbacks " + "failed with %s.\n", + pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__, + pC_->stringifyAsynStatus(status)); + return status; + } + + return this->readEncoderType(); +} + +// Perform the actual poll +asynStatus beamShiftAxis::doPoll(bool *moving) { + + // Return value for the poll + asynStatus errorStatus = asynSuccess; + + // 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 command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; + char userMessage[pC_->MAXBUF_] = {0}; + int nvals = 0; + + int direction = 0; + int error = 0; + double currentPosition = 0.0; + double previousPosition = 0.0; + double highLimit = 0.0; + double lowLimit = 0.0; + double limitsOffset = 0.0; + + // ========================================================================= + + // Read the previous motor position + pl_status = motorPosition(&previousPosition); + if (pl_status != asynSuccess) { + return pl_status; + } + + /* + Query the following parameter: + - Moving flag + - Position + - Error + - Upper limit + - Lower limit + */ + snprintf(command, sizeof(command), "P154 Q110 P159 Q113 Q114"); + rw_status = pC_->writeRead(axisNo(), command, response, 5); + if (rw_status != asynSuccess) { + return rw_status; + }; + + nvals = sscanf(response, "%d %lf %d %lf %lf", (int *)moving, + ¤tPosition, &error, &highLimit, &lowLimit); + if (nvals != 5) { + return pC_->couldNotParseResponse(command, response, axisNo(), + __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. + */ + pl_status = + pC_->getDoubleParam(axisNo(), pC_->motorLimitsOffset(), &limitsOffset); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorLimitsOffset_", + axisNo(), __PRETTY_FUNCTION__, + __LINE__); + } + highLimit = highLimit - limitsOffset; + lowLimit = lowLimit + limitsOffset; + + // Update the enablement PV. If the error is 1, the axis is not enabled + pl_status = setIntegerParam(pC_->motorEnableRBV(), (error != 1)); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_", axisNo(), + __PRETTY_FUNCTION__, __LINE__); + } + + if (*moving) { + // If the axis is moving, evaluate the movement direction + if ((currentPosition - previousPosition) > 0) { + direction = 1; + } else { + direction = 0; + } + } + + errorStatus = handleError(error, userMessage, sizeof(userMessage)); + pl_status = setStringParam(pC_->motorMessageText(), userMessage); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText", + axisNo(), __PRETTY_FUNCTION__, + __LINE__); + } + + // Update the parameter library + if (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__); + } + + pl_status = setIntegerParam(pC_->motorStatusDirection(), direction); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorStatusDirection_", + axisNo(), __PRETTY_FUNCTION__, + __LINE__); + } + + pl_status = pC_->setDoubleParam(axisNo(), pC_->motorHighLimitFromDriver(), + highLimit); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorHighLimitFromDriver_", + axisNo(), __PRETTY_FUNCTION__, + __LINE__); + } + + pl_status = + pC_->setDoubleParam(axisNo(), pC_->motorLowLimitFromDriver(), lowLimit); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorLowLimit_", axisNo(), + __PRETTY_FUNCTION__, __LINE__); + } + + pl_status = setMotorPosition(currentPosition); + if (pl_status != asynSuccess) { + return pl_status; + } + return errorStatus; +} + +asynStatus beamShiftAxis::handleError(int error, char *userMessage, + int sizeUserMessage) { + asynStatus status = asynSuccess; + + // Create the unique callsite identifier manually so it can be used later in + // the shouldBePrinted calls. + msgPrintControlKey keyError = msgPrintControlKey( + pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__); + bool resetError = true; + + switch (error) { + case 0: + // No error + break; + case 1: + // Axis is not switched on. This is a normal state which is not + // problematic, it just requires that the user enables the axis. + break; + case 2: + // Axis received a new command while it was moving + if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, + pC_->pasynUser())) { + asynPrint( + pC_->pasynUser(), ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nAxis is " + "still moving, but received another move command. EPICS " + "should prevent this, check if *moving is set correctly.%s\n", + pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__, + pC_->getMsgPrintControl().getSuffix()); + } + resetError = false; + + snprintf(userMessage, sizeUserMessage, + "Axis received move command while it is still moving. Please " + "call the support."); + status = asynError; + break; + case 3: + // Axis received a new command while it was moving + if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, + pC_->pasynUser())) { + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nAxis 1 " + "error during motion.%s\n", + pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__, + pC_->getMsgPrintControl().getSuffix()); + } + resetError = false; + + snprintf(userMessage, sizeUserMessage, + "Axis 1 got an error while moving. Please call the support."); + status = asynError; + break; + case 4: + // Axis received a new command while it was moving + if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, + pC_->pasynUser())) { + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nAxis 2 " + "error during motion.%s\n", + pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__, + pC_->getMsgPrintControl().getSuffix()); + } + resetError = false; + + snprintf(userMessage, sizeUserMessage, + "Axis 2 got an error while moving. Please call the support."); + status = asynError; + break; + case 5: + // Axis received a new command while it was moving + if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, + pC_->pasynUser())) { + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nTarget " + "position exceeds limits.%s\n", + pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__, + pC_->getMsgPrintControl().getSuffix()); + } + resetError = false; + + snprintf(userMessage, sizeUserMessage, + "Target position exceeds limits. Please call the support."); + status = asynError; + break; + default: + + if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, + pC_->pasynUser())) { + asynPrint( + pC_->pasynUser(), ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nUnknown error " + "P%2.2d01 = %d.%s\n", + pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__, + axisNo(), error, pC_->getMsgPrintControl().getSuffix()); + } + resetError = false; + + snprintf(userMessage, sizeUserMessage, + "Unknown error P%2.2d01 = %d. Please call the support.", + axisNo(), error); + status = setStringParam(pC_->motorMessageText(), userMessage); + if (status != asynSuccess) { + return pC_->paramLibAccessFailed(status, "motorMessageText_", + axisNo(), __PRETTY_FUNCTION__, + __LINE__); + } + + status = asynError; + break; + } + + if (resetError) { + pC_->getMsgPrintControl().resetCount(keyError, pC_->pasynUser()); + } + return status; +} + +asynStatus beamShiftAxis::doMove(double position, int relative, + double minVelocity, double maxVelocity, + 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 command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; + double motorCoordinatesPosition = 0.0; + double motorRecResolution = 0.0; + int enabled = 0; + + // ========================================================================= + + pl_status = pC_->getIntegerParam(axisNo(), pC_->motorEnableRBV(), &enabled); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_", axisNo(), + __PRETTY_FUNCTION__, __LINE__); + } + + pl_status = pC_->getDoubleParam(axisNo(), pC_->motorRecResolution(), + &motorRecResolution); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_", + axisNo(), __PRETTY_FUNCTION__, + __LINE__); + } + + if (enabled == 0) { + asynPrint( + pC_->pasynUser(), ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nAxis is disabled.\n", + pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__); + return asynSuccess; + } + + // Convert from EPICS to user / motor units + motorCoordinatesPosition = position * motorRecResolution; + + asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW, + "Controller \"%s\", axis %d => %s, line %d\nStart of axis to " + "position %lf.\n", + pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__, position); + + // Set target position and start the move command + snprintf(command, sizeof(command), "Q251=%lf P150=1", + motorCoordinatesPosition); + + // We don't expect an answer + rw_status = pC_->writeRead(axisNo(), command, response, 0); + if (rw_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); + pl_status = setIntegerParam(pC_->motorStatusProblem(), true); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_", + axisNo(), __PRETTY_FUNCTION__, + __LINE__); + } + } + return rw_status; +} + +asynStatus beamShiftAxis::stop(double acceleration) { + + char response[pC_->MAXBUF_] = {0}; + + // ========================================================================= + + return pC_->writeRead(axisNo(), "P150=8", response, 0); +} + +asynStatus beamShiftAxis::doReset() { + + char response[pC_->MAXBUF_] = {0}; + + // ========================================================================= + + // Delete the error + return pC_->writeRead(axisNo(), "P152=2", response, 0); +} + +/* +This is a no-op. +*/ +asynStatus beamShiftAxis::doHome(double min_velocity, double max_velocity, + double acceleration, int forwards) { + return asynSuccess; +} + +/* +Encoder type is absolute encoder +*/ +asynStatus beamShiftAxis::readEncoderType() { + + asynStatus pl_status = setStringParam(pC_->encoderType(), AbsoluteEncoder); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "encoderType_", axisNo(), + __PRETTY_FUNCTION__, __LINE__); + } + return asynSuccess; +} + +asynStatus beamShiftAxis::enable(bool on) { + char command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; + char userMessage[pC_->MAXBUF_] = {0}; + asynStatus status = asynSuccess; + int nvals = 0; + int error = 0; + + // ========================================================================= + + // Axis can only be switched on, trying to switch it off creates an error + // message + if (on) { + // Check if the axis is currently switched off + snprintf(command, sizeof(command), "P159"); + status = pC_->writeRead(axisNo(), command, response, 1); + + nvals = sscanf(response, "%d", &error); + if (nvals != 1) { + return pC_->couldNotParseResponse(command, response, axisNo(), + __PRETTY_FUNCTION__, __LINE__); + } + + if (error == 1) { + snprintf(command, sizeof(command), "P152=1"); + return pC_->writeRead(axisNo(), command, response, 0); + } else { + // Status is ignored on purpose, we always return asynError in this + // path. + handleError(error, userMessage, sizeof(userMessage)); + + status = setIntegerParam(pC_->motorStatusProblem(), true); + if (status != asynSuccess) { + return pC_->paramLibAccessFailed(status, "motorStatusProblem_", + axisNo(), __PRETTY_FUNCTION__, + __LINE__); + } + + status = setStringParam(pC_->motorMessageText(), userMessage); + if (status != asynSuccess) { + return pC_->paramLibAccessFailed(status, "motorMessageText_", + axisNo(), __PRETTY_FUNCTION__, + __LINE__); + } + return asynError; + } + return asynSuccess; + } else { + + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nAxis cannot be " + "switched off.\n", + pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__); + status = + setStringParam(pC_->motorMessageText(), "Cannot be switched off."); + if (status != asynSuccess) { + return pC_->paramLibAccessFailed(status, "motorMessageText_", + axisNo(), __PRETTY_FUNCTION__, + __LINE__); + } + + return asynError; + } +} + +/*************************************************************************************/ +/** 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 beamShiftAxis +constructor documentation. The controller is read from the portName. +*/ +asynStatus beamShiftCreateAxis(const char *portName, int axis) { + + /* + 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 an asynUser from a pointer to an asynPortDriver. + However, the given pointer is a nullptr and therefore doesn't + have an asynUser! printf is an EPICS alternative which + 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 + turboPmacController *pC = dynamic_cast(apd); + if (pC == nullptr) { + errlogPrintf("Controller \"%s\" => %s, line %d\nController " + "is not a turboPmacController.", + 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. + */ +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#pragma GCC diagnostic ignored "-Wunused-variable" + beamShiftAxis *pAxis = new beamShiftAxis(pC, axis); + + // Allow manipulation of the controller again + pC->unlock(); + return asynSuccess; +} + +/* +Same procedure as for the CreateController function, but for the axis +itself. +*/ +static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mcu1)", + iocshArgString}; +static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt}; +static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0, + &CreateAxisArg1}; +static const iocshFuncDef configbeamShiftCreateAxis = { + "beamShiftAxis", 2, CreateAxisArgs, + "Create an instance of a beamShift axis. The first argument is the " + "controller this axis should be attached to, the second argument is the " + "axis number."}; +static void configBeamShiftCreateAxisCallFunc(const iocshArgBuf *args) { + beamShiftCreateAxis(args[0].sval, args[1].ival); +} + +// This function is made known to EPICS in beamShift.dbd and is called +// by EPICS in order to register both functions in the IOC shell +static void beamShiftAxisRegister(void) { + iocshRegister(&configbeamShiftCreateAxis, + configBeamShiftCreateAxisCallFunc); +} +epicsExportRegistrar(beamShiftAxisRegister); + +} // extern "C" diff --git a/src/beamShiftAxis.h b/src/beamShiftAxis.h new file mode 100644 index 0000000..c0ac278 --- /dev/null +++ b/src/beamShiftAxis.h @@ -0,0 +1,119 @@ +#ifndef beamShiftAXIS_H +#define beamShiftAXIS_H +#include "turboPmacAxis.h" +#include "turboPmacController.h" + +class beamShiftAxis : public turboPmacAxis { + public: + /** + * @brief Construct a new beamShiftAxis + * + * @param pController Pointer to the associated controller + * @param axisNo Index of the axis + */ + beamShiftAxis(turboPmacController *pController, int axisNo); + + /** + * @brief Destroy the turboPmacAxis + * + */ + virtual ~beamShiftAxis(); + + /** + * @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 `doHome` function from sinqAxis. The + * parameters are described in the documentation of `sinqAxis::doHome`. + * + * @param minVelocity + * @param maxVelocity + * @param acceleration + * @param forwards + * @return asynStatus + */ + asynStatus doHome(double minVelocity, double maxVelocity, + double acceleration, int forwards); + + /** + * @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 Implementation of the `doMove` function from sinqAxis. The + * parameters are described in the documentation of `sinqAxis::doMove`. + * + * @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 Readout of some values from the controller at IOC startup + * + * The following steps are performed: + * - Read out the motor status, motor position, velocity and acceleration + * from the MCU and store this information in the parameter library. + * - Set the enable PV according to the initial status of the axis. + * + * @return asynStatus + */ + asynStatus init(); + + /** + * @brief Implementation of the `doReset` function from sinqAxis. + * + * @param on + * @return asynStatus + */ + asynStatus doReset(); + + /** + * @brief Enable the axis, if it is disabled + * + * This axis type cannot be disabled by the user, but it can be enabled if + * it is switched off. + * + * @param on + * @return asynStatus + */ + asynStatus enable(bool on); + + /** + * @brief This axis type has an absolute encoder by default + * + * @return asynStatus + */ + asynStatus readEncoderType(); + + /** + * @brief Interpret the error code and populate the user message accordingly + * + * @param error + * @param userMessage + * @param sizeUserMessage + * @return asynStatus + */ + asynStatus handleError(int error, char *userMessage, int sizeUserMessage); + + protected: + turboPmacController *pC_; +}; + +#endif