#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); // 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) { 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 speed cannot be changed status = pC_->setIntegerParam(axisNo, pC_->motorCanSetSpeed(), 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); } } 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 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; int enabled = 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 P158 P159 Q113 Q114"); status = pC_->writeRead(axisNo(), command, response, 6); if (status != asynSuccess) { return status; }; nvals = sscanf(response, "%d %lf %d %d %lf %lf", (int *)moving, ¤tPosition, &enabled, &error, &highLimit, &lowLimit); if (nvals != 6) { 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. */ getAxisParamChecked(this, motorLimitsOffset, &limitsOffset); highLimit = highLimit - limitsOffset; lowLimit = lowLimit + limitsOffset; // Update the enablement PV. setAxisParamChecked(this, motorEnableRBV, enabled); 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)); setAxisParamChecked(this, motorMessageText, userMessage); // Update the parameter library if (error != 0) { setAxisParamChecked(this, motorStatusProblem, true); } if (*moving == false) { setAxisParamChecked(this, motorMoveToHome, false); } setAxisParamChecked(this, motorStatusMoving, *moving); setAxisParamChecked(this, motorStatusMoving, !(*moving)); setAxisParamChecked(this, motorStatusDirection, direction); setAxisParamChecked(this, motorHighLimitFromDriver, highLimit); setAxisParamChecked(this, motorLowLimitFromDriver, lowLimit); 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. if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, pC_->pasynUser())) { asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d\nAxis must " "be enabled first.%s\n", pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__, pC_->getMsgPrintControl().getSuffix()); } resetError = false; snprintf(userMessage, sizeUserMessage, "Axis must be enabled first."); status = asynError; 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, but 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; case 6: // 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 not " "ready, delete error first.%s\n", pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__, pC_->getMsgPrintControl().getSuffix()); } resetError = false; snprintf(userMessage, sizeUserMessage, "Delete error first."); 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); setAxisParamChecked(this, motorMessageText, userMessage); 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 status = asynSuccess; char command[pC_->MAXBUF_] = {0}; char response[pC_->MAXBUF_] = {0}; double motorCoordinatesPosition = 0.0; double motorRecResolution = 0.0; int enabled = 0; // ========================================================================= getAxisParamChecked(this, motorEnableRBV, &enabled); getAxisParamChecked(this, motorRecResolution, &motorRecResolution); 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 status = pC_->writeRead(axisNo(), command, response, 0); 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); } return 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() { setAxisParamChecked(this, encoderType, AbsoluteEncoder); return asynSuccess; } asynStatus beamShiftAxis::enable(bool on) { char command[pC_->MAXBUF_] = {0}; char response[pC_->MAXBUF_] = {0}; char userMessage[pC_->MAXBUF_] = {0}; int nvals = 0; int error = 0; int enabled = 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), "P158 P159"); asynStatus status = pC_->writeRead(axisNo(), command, response, 2); if (status != asynSuccess) { return status; } nvals = sscanf(response, "%d %d", &enabled, &error); if (nvals != 2) { return pC_->couldNotParseResponse(command, response, axisNo(), __PRETTY_FUNCTION__, __LINE__); } if (enabled == 0) { 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)); setAxisParamChecked(this, motorStatusProblem, true); setAxisParamChecked(this, motorMessageText, userMessage); 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__); setAxisParamChecked(this, motorMessageText, "Cannot be switched off."); 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"