diff --git a/Makefile b/Makefile index dd2e4ee..87d6eed 100644 --- a/Makefile +++ b/Makefile @@ -7,11 +7,13 @@ EPICS_VERSIONS=7.0.7 ARCH_FILTER=RHEL% # Additional module dependencies -REQUIRED+=asynMotor REQUIRED+=sinqMotor +# Specify the version of asynMotor we want to build against +motorBase_VERSION=7.2.2 + # Specify the version of sinqMotor we want to build against -sinqMotor_VERSION=0.6.3 +sinqMotor_VERSION=0.7.0 # These headers allow to depend on this library for derived drivers. HEADERS += src/masterMacsAxis.h diff --git a/src/masterMacs.dbd b/src/masterMacs.dbd index e54e329..f03fd5b 100644 --- a/src/masterMacs.dbd +++ b/src/masterMacs.dbd @@ -1,4 +1,5 @@ #--------------------------------------------- # SINQ specific DB definitions #--------------------------------------------- -registrar(masterMacsRegister) +registrar(masterMacsControllerRegister) +registrar(masterMacsAxisRegister) diff --git a/src/masterMacsAxis.cpp b/src/masterMacsAxis.cpp index 9d96f64..c32bed8 100644 --- a/src/masterMacsAxis.cpp +++ b/src/masterMacsAxis.cpp @@ -1,5 +1,7 @@ #include "masterMacsAxis.h" #include "asynOctetSyncIO.h" +#include "epicsExport.h" +#include "iocsh.h" #include "masterMacsController.h" #include #include @@ -9,6 +11,29 @@ #include #include +/* +Contains all instances of turboPmacAxis 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) { + masterMacsAxis *axis = *itA; + axis->init(); + } + } +} + masterMacsAxis::masterMacsAxis(masterMacsController *pC, int axisNo) : sinqAxis(pC, axisNo), pC_(pC) { @@ -29,28 +54,42 @@ masterMacsAxis::masterMacsAxis(masterMacsController *pC, int axisNo) */ if (axisNo >= pC->numAxes_) { asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:: FATAL ERROR: Axis index %d must be smaller " - "than the total number of axes %d. Call the support.", - __PRETTY_FUNCTION__, __LINE__, axisNo_, pC->numAxes_); + "Controller \"%s\", axis %d => %s, line %d:: FATAL ERROR: " + "Axis index %d must be smaller than the total number of axes " + "%d. Call the support.", + 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); + // Initialize all member variables - initial_poll_ = true; axisStatus_ = std::bitset<16>(0); axisError_ = std::bitset<16>(0); // Initial value for the motor speed, is overwritten in atFirstPoll. lastSetSpeed_ = 0.0; + // Flag for handshake waiting + waitForHandshake_ = false; + timeAtHandshake_ = 0; + // masterMacs motors can always be disabled status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable_, 1); if (status != asynSuccess) { asynPrint( pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nFATAL ERROR (setting a parameter value failed " - "with %s)\n. Terminating IOC", - __PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status)); + "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); } @@ -59,10 +98,10 @@ masterMacsAxis::masterMacsAxis(masterMacsController *pC, int axisNo) if (status != asynSuccess) { asynPrint( pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nFATAL ERROR (setting a parameter value failed " - "with %s)\n. Terminating IOC", - __PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status)); - exit(-1); + "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)); } } @@ -79,7 +118,7 @@ Read out the following values: - acceleration - Software limits */ -asynStatus masterMacsAxis::atFirstPoll() { +asynStatus masterMacsAxis::init() { // Local variable declaration asynStatus pl_status = asynSuccess; @@ -93,13 +132,29 @@ asynStatus masterMacsAxis::atFirstPoll() { // ========================================================================= - pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_, - &motorRecResolution); - if (pl_status == asynParamUndefined) { - return asynParamUndefined; - } else if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_", - __PRETTY_FUNCTION__, __LINE__); + // 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) { + pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_, + &motorRecResolution); + if (pl_status == asynParamUndefined) { + if (now + maxInitTime < time(NULL)) { + asynPrint(pC_->pasynUserSelf, 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 (pl_status == asynSuccess) { + break; + } else if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } } // Read out the current position @@ -158,7 +213,7 @@ asynStatus masterMacsAxis::atFirstPoll() { pl_status = pC_->setDoubleParam(axisNo_, pC_->motorPosition_, motorPosition); if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "motorPosition_", + return pC_->paramLibAccessFailed(pl_status, "motorPosition_", axisNo_, __PRETTY_FUNCTION__, __LINE__); } @@ -184,10 +239,10 @@ asynStatus masterMacsAxis::atFirstPoll() { // make sense to try and upstream this to the user -> Just log the // error asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\ncallParamCallbacks failed with %s for " - "axis %d.\n", - __PRETTY_FUNCTION__, __LINE__, - pC_->stringifyAsynStatus(pl_status), axisNo_); + "Controller \"%s\", axis %d => %s, line " + "%d:\ncallParamCallbacks failed with %s.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, + pC_->stringifyAsynStatus(pl_status)); return pl_status; } return pl_status; @@ -216,22 +271,64 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { double highLimit = 0.0; double lowLimit = 0.0; double limitsOffset = 0.0; + double handshakePerformed = 0; // ========================================================================= + // Are we currently waiting for a handshake? + if (waitForHandshake_) { + pC_->read(axisNo_, 86, response); + if (rw_status != asynSuccess) { + return rw_status; + } + + nvals = sscanf(response, "%lf", &handshakePerformed); + if (nvals != 1) { + return pC_->errMsgCouldNotParseResponse( + "R86", response, axisNo_, __PRETTY_FUNCTION__, __LINE__); + } + + if (handshakePerformed == 1.0) { + // Handshake has been performed successfully -> Continue with the + // poll + waitForHandshake_ = false; + } else { + // Still waiting for the handshake - try again in the next busy + // poll. This is already part of the movement procedure. + *moving = true; + + 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__); + } + + return asynSuccess; + } + } + // Motor resolution from parameter library pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_, &motorRecResolution); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } // Read the previous motor position pl_status = pC_->getDoubleParam(axisNo_, pC_->motorPosition_, &previousPosition); if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "motorPosition_", + return pC_->paramLibAccessFailed(pl_status, "motorPosition_", axisNo_, __PRETTY_FUNCTION__, __LINE__); } @@ -245,39 +342,11 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { return rw_status; } - if (faultConditionSet()) { - rw_status = readAxisError(); - - // TODO: Handling - - // pl_status = - // pC_->getIntegerParam(axisNo_, pC_->motorStatusMoving_, - // &wasMoving); - // if (pl_status != asynSuccess) { - // return pC_->paramLibAccessFailed(pl_status, "motorStatusMoving_", - // __PRETTY_FUNCTION__, __LINE__); - // } - // if (!isMoving() && wasMoving == 1) { - // rw_status = readAxisError(); - // if (rw_status != asynSuccess) { - // return rw_status; - // } - - // // TODO> Error handling - // } - } - - // Update the movement status - *moving = isMoving(); - - if (*moving) { - - asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nMOVMOVMOV\n", __PRETTY_FUNCTION__, __LINE__); - } - - if (enabled()) { - + // Check if we reached the target + if (targetReached()) { + *moving = false; + } else { + *moving = true; } // Read the current position @@ -291,8 +360,256 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { __PRETTY_FUNCTION__, __LINE__); } + /* + Read out the error if either a fault condition status flag has been set or + if a movement has just ended. + */ + if (faultConditionSet()) { + rw_status = readAxisError(); + + if (shortCircuit()) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nShort " + "circuit fault.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + pl_status = + setStringParam(pC_->motorMessageText_, + "Short circuit error. Please call the support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + + if (encoderError()) { + asynPrint( + pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nEncoder error.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + pl_status = + setStringParam(pC_->motorMessageText_, + "Encoder error. Please call the support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + + if (followingError()) { + asynPrint( + pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nMaximum allowed " + "following error exceeded.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + pl_status = setStringParam( + pC_->motorMessageText_, + "Maximum allowed following error exceeded.Check if movement " + "range is blocked. Otherwise please call the support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + + if (communicationError()) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line " + "%d\nCommunication error.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + pl_status = + setStringParam(pC_->motorMessageText_, + "Communication error between PC and motor " + "controller. Please call the support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + + if (feedbackError()) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line " + "%d\nFeedback error.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + pl_status = + setStringParam(pC_->motorMessageText_, + "Feedback error. Please call the support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + + /* + Either the software limits or the end switches of the controller have + been hit. Since the EPICS limits are derived from the software limits + and are a little bit smaller, these error cases can only happen if + either the axis has an incremental encoder which is not properly homed + or if a bug occured. + */ + if (positiveLimitSwitch() || negativeLimitSwitch() || + positiveSoftwareLimit() || negativeSoftwareLimit()) { + + // Distinction for developers + if (positiveLimitSwitch()) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nAxis " + "hit the positive limit end switch.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + if (negativeLimitSwitch()) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nAxis " + "hit the negative limit end switch.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + if (positiveSoftwareLimit()) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nAxis " + "hit the positive software limit.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + if (negativeSoftwareLimit()) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nAxis " + "hit the negative software limit.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + // Generic error message for user + pl_status = setStringParam( + pC_->motorMessageText_, + "Software limits or end switch hit. Try homing the motor, " + "moving in the opposite direction or check the SPS for " + "errors (if available). Otherwise please call the " + "support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + + if (overCurrent()) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nOvercurrent " + "error.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + pl_status = + setStringParam(pC_->motorMessageText_, + "Overcurrent error. Please call the support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + + if (overTemperature()) { + asynPrint( + pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nOvertemperature " + "error.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + pl_status = setStringParam( + pC_->motorMessageText_, + "Overtemperature error. Please call the support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + + if (overVoltage()) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nOvervoltage " + "error.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + pl_status = + setStringParam(pC_->motorMessageText_, + "Overvoltage error. Please call the support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + + if (underVoltage()) { + asynPrint( + pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nUndervoltage " + "error.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + pl_status = + setStringParam(pC_->motorMessageText_, + "Undervoltage error. Please call the support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + + if (stoFault()) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nSTO fault - " + "STO input is on disable state.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + pl_status = setStringParam(pC_->motorMessageText_, + "STO fault. Please call the support."); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + + poll_status = asynError; + } + } + // Read out the limits, if the motor is not moving - if (!isMoving()) { + if (!(*moving)) { rw_status = pC_->read(axisNo_, 34, response); if (rw_status != asynSuccess) { return rw_status; @@ -330,7 +647,8 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { &limitsOffset); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorLimitsOffset_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } highLimit = highLimit - limitsOffset; lowLimit = lowLimit + limitsOffset; @@ -338,23 +656,25 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { pl_status = pC_->setDoubleParam(axisNo_, pC_->motorHighLimitFromDriver_, highLimit); if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, - "motorHighLimitFromDriver_", - __PRETTY_FUNCTION__, __LINE__); + 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_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } } // Update the enable PV - pl_status = setIntegerParam(pC_->motorEnableRBV_, enabled()); + pl_status = setIntegerParam(pC_->motorEnableRBV_, + readyToBeSwitchedOn() && switchedOn()); if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_", + return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_", axisNo_, __PRETTY_FUNCTION__, __LINE__); } @@ -372,43 +692,37 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { pl_status = setIntegerParam(pC_->motorStatusProblem_, true); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } } - - if (!*moving) { - pl_status = setIntegerParam(pC_->motorMoveToHome_, 0); - if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "motorMoveToHome_", - __PRETTY_FUNCTION__, __LINE__); - } - } - pl_status = setIntegerParam(pC_->motorStatusMoving_, *moving); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorStatusMoving_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } pl_status = setIntegerParam(pC_->motorStatusDone_, !(*moving)); if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "motorStatusDone_", + 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_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } // Transform from motor to EPICS coordinates (see comment in - // masterMacsAxis::atFirstPoll()) + // masterMacsAxis::init()) currentPosition = currentPosition / motorRecResolution; pl_status = setDoubleParam(pC_->motorPosition_, currentPosition); if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "motorPosition_", + return pC_->paramLibAccessFailed(pl_status, "motorPosition_", axisNo_, __PRETTY_FUNCTION__, __LINE__); } @@ -436,7 +750,7 @@ asynStatus masterMacsAxis::doMove(double position, int relative, pl_status = pC_->getIntegerParam(axisNo_, pC_->motorEnableRBV_, &enabled); if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_", + return pC_->paramLibAccessFailed(pl_status, "enableMotorRBV_", axisNo_, __PRETTY_FUNCTION__, __LINE__); } @@ -444,13 +758,15 @@ asynStatus masterMacsAxis::doMove(double position, int relative, &motorRecResolution); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } if (enabled == 0) { asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nAxis %d is disabled.\n", __PRETTY_FUNCTION__, - __LINE__, axisNo_); + "Controller \"%s\", axis %d => %s, line %d:\nAxis is " + "disabled.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); return asynSuccess; } @@ -458,20 +774,34 @@ asynStatus masterMacsAxis::doMove(double position, int relative, motorCoordinatesPosition = position * motorRecResolution; motorVelocity = maxVelocity * motorRecResolution; - asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW, - "%s => line %d:\nStart of axis %d to position %lf.\n", - __PRETTY_FUNCTION__, __LINE__, axisNo_, position); + asynPrint( + pC_->pasynUserSelf, ASYN_TRACE_FLOW, + "Controller \"%s\", axis %d => %s, line %d:\nMove to position %lf.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, position); // Check if the speed is allowed to be changed pl_status = pC_->getIntegerParam(axisNo_, pC_->motorCanSetSpeed_, &motorCanSetSpeed); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorCanSetSpeed_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } - // Set the new motor speed, if the user is allowed to do so and if the motor - // speed changed since the last move command. + // Initialize the movement handshake + rw_status = pC_->write(axisNo_, 86, "0"); + if (rw_status != asynSuccess) { + pl_status = setIntegerParam(pC_->motorStatusProblem_, true); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + return rw_status; + } + + // Set the new motor speed, if the user is allowed to do so and if the + // motor speed changed since the last move command. if (motorCanSetSpeed != 0 && lastSetSpeed_ != motorVelocity) { lastSetSpeed_ = motorVelocity; @@ -482,15 +812,17 @@ asynStatus masterMacsAxis::doMove(double position, int relative, pl_status = setIntegerParam(pC_->motorStatusProblem_, true); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, - "motorStatusProblem_", + "motorStatusProblem_", axisNo_, __PRETTY_FUNCTION__, __LINE__); } return rw_status; } asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW, - "%s => line %d:\nSetting speed of axis %d to %lf.\n", - __PRETTY_FUNCTION__, __LINE__, axisNo_, motorVelocity); + "Controller \"%s\", axis %d => %s, line %d:\nSetting speed " + "to %lf.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, + motorVelocity); } // Set the target position @@ -500,7 +832,8 @@ asynStatus masterMacsAxis::doMove(double position, int relative, pl_status = setIntegerParam(pC_->motorStatusProblem_, true); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } return rw_status; } @@ -515,11 +848,17 @@ asynStatus masterMacsAxis::doMove(double position, int relative, pl_status = setIntegerParam(pC_->motorStatusProblem_, true); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } return rw_status; } + // In the next poll, we will check if the handshake has been performed in a + // reasonable time + waitForHandshake_ = true; + timeAtHandshake_ = time(NULL); + // Waiting for a handshake is already part of the movement procedure => // Start the watchdog if (startMovTimeoutWatchdog() != asynSuccess) { @@ -544,7 +883,8 @@ asynStatus masterMacsAxis::stop(double acceleration) { pl_status = setIntegerParam(pC_->motorStatusProblem_, true); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } } @@ -570,39 +910,38 @@ asynStatus masterMacsAxis::doHome(double min_velocity, double max_velocity, pl_status = pC_->getStringParam(axisNo_, pC_->encoderType_, sizeof(response), response); if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "encoderType_", + return pC_->paramLibAccessFailed(pl_status, "encoderType_", axisNo_, __PRETTY_FUNCTION__, __LINE__); } // Only send the home command if the axis has an incremental encoder if (strcmp(response, IncrementalEncoder) == 0) { + // Initialize the movement handshake + rw_status = pC_->write(axisNo_, 86, "0"); + if (rw_status != asynSuccess) { + pl_status = setIntegerParam(pC_->motorStatusProblem_, true); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, + "motorStatusProblem_", axisNo_, + __PRETTY_FUNCTION__, __LINE__); + } + return rw_status; + } + rw_status = pC_->write(axisNo_, 00, "9"); if (rw_status != asynSuccess) { return rw_status; } - pl_status = setIntegerParam(pC_->motorMoveToHome_, 1); - if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "motorMoveToHome_", - __PRETTY_FUNCTION__, __LINE__); - } - - pl_status = setStringParam(pC_->motorMessageText_, "Homing"); - if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", - __PRETTY_FUNCTION__, __LINE__); - } + // In the next poll, we will check if the handshake has been performed + // in a reasonable time + waitForHandshake_ = true; + timeAtHandshake_ = time(NULL); + return asynSuccess; } else { - pl_status = setStringParam(pC_->motorMessageText_, - "Can't home a motor with absolute encoder"); - if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", - __PRETTY_FUNCTION__, __LINE__); - } + return asynError; } - - return rw_status; } /* @@ -647,7 +986,7 @@ asynStatus masterMacsAxis::readEncoderType() { } if (pl_status != asynSuccess) { - return pC_->paramLibAccessFailed(pl_status, "encoderType_", + return pC_->paramLibAccessFailed(pl_status, "encoderType_", axisNo_, __PRETTY_FUNCTION__, __LINE__); } return asynSuccess; @@ -676,16 +1015,17 @@ asynStatus masterMacsAxis::enable(bool on) { // motor can actually be disabled in this state! if (moving) { asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nAxis %d is not idle and can therefore not " - "be enabled / disabled.\n", - __PRETTY_FUNCTION__, __LINE__, axisNo_); + "Controller \"%s\", axis %d => %s, line %d:\nAxis is not " + "idle and can therefore not be disabled.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); pl_status = setStringParam(pC_->motorMessageText_, "Axis cannot be disabled while it is moving."); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } return asynError; @@ -693,20 +1033,22 @@ asynStatus masterMacsAxis::enable(bool on) { // Axis is already enabled / disabled and a new enable / disable command // was sent => Do nothing - if (enabled() == on) { - asynPrint(pC_->pasynUserSelf, ASYN_TRACE_WARNING, - "%s => line %d:\nAxis %d on controller %s is already %s.\n", - __PRETTY_FUNCTION__, __LINE__, axisNo_, pC_->portName, - on ? "enabled" : "disabled"); + + if ((readyToBeSwitchedOn() && switchedOn()) == on) { + asynPrint( + pC_->pasynUserSelf, ASYN_TRACE_WARNING, + "Controller \"%s\", axis %d => %s, line %d:\nAxis is already %s.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, + on ? "enabled" : "disabled"); return asynSuccess; } // Enable / disable the axis if it is not moving snprintf(value, sizeof(value), "%d", on); asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW, - "%s => line %d:\n%s axis %d on controller %s\n", - __PRETTY_FUNCTION__, __LINE__, on ? "Enable" : "Disable", axisNo_, - pC_->portName); + "Controller \"%s\", axis %d => %s, line %d:\n%s axis.\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, + on ? "Enable" : "Disable"); if (on == 0) { pl_status = setStringParam(pC_->motorMessageText_, "Disabling ..."); } else { @@ -714,7 +1056,8 @@ asynStatus masterMacsAxis::enable(bool on) { } if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } rw_status = pC_->write(axisNo_, 04, value); if (rw_status != asynSuccess) { @@ -744,10 +1087,10 @@ asynStatus masterMacsAxis::enable(bool on) { // Failed to change axis status within timeout_enable_disable => Send a // corresponding message asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nFailed to %s axis %d on controller %s within %d " - "seconds\n", - __PRETTY_FUNCTION__, __LINE__, on ? "enable" : "disable", axisNo_, - pC_->portName, timeout_enable_disable); + "Controller \"%s\", axis %d => %s, line %d:\nFailed to %s axis " + "within %d seconds\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, + on ? "enable" : "disable", timeout_enable_disable); // Output message to user snprintf(value, sizeof(value), "Failed to %s within %d seconds", @@ -755,11 +1098,26 @@ asynStatus masterMacsAxis::enable(bool on) { pl_status = setStringParam(pC_->motorMessageText_, "Enabling ..."); if (pl_status != asynSuccess) { return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", - __PRETTY_FUNCTION__, __LINE__); + axisNo_, __PRETTY_FUNCTION__, + __LINE__); } return asynError; } +/** +Convert a float to an unint16_t bitset +*/ +std::bitset<16> toBitset(float val) { + + // Convert the 32-bit float to an uint32_t. This works independently of the + // endianess of the system. + uint32_t temp = (uint32_t)val; + + // Take the bottom half of bits to convert the 32 bit uint to a 16 bit uint + uint16_t lowerHalf = (uint16_t)(temp & 0xFFFF); + return std::bitset<16>(lowerHalf); +} + asynStatus masterMacsAxis::readAxisStatus() { char response[pC_->MAXBUF_]; @@ -768,14 +1126,16 @@ asynStatus masterMacsAxis::readAxisStatus() { asynStatus rw_status = pC_->read(axisNo_, 10, response); if (rw_status == asynSuccess) { - int axisStatus = 0; - int nvals = sscanf(response, "%d", &axisStatus); + float axisStatus = 0; + int nvals = sscanf(response, "%f", &axisStatus); if (nvals != 1) { return pC_->errMsgCouldNotParseResponse( "R10", response, axisNo_, __PRETTY_FUNCTION__, __LINE__); } - axisStatus_ = std::bitset<16>(axisStatus); + + axisStatus_ = toBitset(axisStatus); } + return rw_status; } @@ -787,13 +1147,119 @@ asynStatus masterMacsAxis::readAxisError() { asynStatus rw_status = pC_->read(axisNo_, 11, response); if (rw_status == asynSuccess) { - int axisError = 0; - int nvals = sscanf(response, "%d", &axisError); + float axisError = 0; + int nvals = sscanf(response, "%f", &axisError); if (nvals != 1) { return pC_->errMsgCouldNotParseResponse( "R11", response, axisNo_, __PRETTY_FUNCTION__, __LINE__); } - axisError_ = std::bitset<16>(axisError); + axisError_ = toBitset(axisError); } return rw_status; } + +/*************************************************************************************/ +/** 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 masterMacsAxis +constructor documentation. The controller is read from the portName. +*/ +asynStatus masterMacsCreateAxis(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 a lowLevelPortUser_ from a pointer to an asynPortDriver. + However, the given pointer is a nullptr and therefore doesn't + have a lowLevelPortUser_! 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 %s not found.", + portName, __PRETTY_FUNCTION__, __LINE__, portName); + return asynError; + } + // Unsafe cast of the pointer to an asynPortDriver + asynPortDriver *apd = (asynPortDriver *)(ptr); + + // Safe downcast + masterMacsController *pC = dynamic_cast(apd); + if (pC == nullptr) { + errlogPrintf("Controller \"%s\" => %s, line %d:\nController is not a " + "masterMacsController.", + 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" + masterMacsAxis *pAxis = new masterMacsAxis(pC, axis); + + // Allow manipulation of the controller again + pC->unlock(); + return asynSuccess; +} + +/* +This is boilerplate code which is used to make the FFI functions +CreateController and CreateAxis "known" to the IOC shell (iocsh). +*/ + +#ifdef vxWorks +#else + +/* +Same procedure as for the CreateController function, but for the axis +itself. +*/ +static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mmacs1)", + iocshArgString}; +static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt}; +static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0, + &CreateAxisArg1}; +static const iocshFuncDef configMasterMacsCreateAxis = {"masterMacsAxis", 2, + CreateAxisArgs}; +static void configMasterMacsCreateAxisCallFunc(const iocshArgBuf *args) { + masterMacsCreateAxis(args[0].sval, args[1].ival); +} + +// This function is made known to EPICS in masterMacs.dbd and is called by +// EPICS in order to register both functions in the IOC shell +static void masterMacsAxisRegister(void) { + iocshRegister(&configMasterMacsCreateAxis, + configMasterMacsCreateAxisCallFunc); +} +epicsExportRegistrar(masterMacsAxisRegister); + +#endif + +} // extern "C" diff --git a/src/masterMacsAxis.h b/src/masterMacsAxis.h index f6e2c3d..72ebae9 100644 --- a/src/masterMacsAxis.h +++ b/src/masterMacsAxis.h @@ -70,7 +70,7 @@ class masterMacsAxis : public sinqAxis { double max_velocity, double acceleration); /** - * @brief Implementation of the `atFirstPoll` function from sinqAxis. + * @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 @@ -79,7 +79,7 @@ class masterMacsAxis : public sinqAxis { * * @return asynStatus */ - asynStatus atFirstPoll(); + asynStatus init(); /** * @brief Enable / disable the axis. @@ -100,9 +100,10 @@ class masterMacsAxis : public sinqAxis { protected: masterMacsController *pC_; double lastSetSpeed_; + bool waitForHandshake_; + time_t timeAtHandshake_; asynStatus readConfig(); - bool initial_poll_; /* The axis status and axis error of MasterMACS are given as an integer from @@ -134,10 +135,7 @@ class masterMacsAxis : public sinqAxis { */ bool switchedOn() { return axisStatus_[1]; } - /** - * @brief Read the property from axisStatus_ - */ - bool enabled() { return axisStatus_[2]; } + // Bit 2 is unused /** * @brief Read the property from axisStatus_ @@ -162,12 +160,9 @@ class masterMacsAxis : public sinqAxis { /** * @brief Read the property from axisStatus_ */ - bool newMoveCommandWhileMoving() { return axisStatus_[7]; } + bool warning() { return axisStatus_[7]; } - /** - * @brief Read the property from axisStatus_ - */ - bool isMoving() { return axisStatus_[8]; } + // Bit 8 is unused /** * @brief Read the property from axisStatus_ @@ -209,6 +204,76 @@ class masterMacsAxis : public sinqAxis { boolean. */ + /** + * @brief Read the property from axisError_ + */ + bool shortCircuit() { return axisError_[1]; } + + /** + * @brief Read the property from axisError_ + */ + bool encoderError() { return axisError_[2]; } + + /** + * @brief Read the property from axisError_ + */ + bool followingError() { return axisError_[3]; } + + /** + * @brief Read the property from axisError_ + */ + bool communicationError() { return axisError_[4]; } + + /** + * @brief Read the property from axisError_ + */ + bool feedbackError() { return axisError_[5]; } + + /** + * @brief Read the property from axisError_ + */ + bool positiveLimitSwitch() { return axisError_[6]; } + + /** + * @brief Read the property from axisError_ + */ + bool negativeLimitSwitch() { return axisError_[7]; } + + /** + * @brief Read the property from axisError_ + */ + bool positiveSoftwareLimit() { return axisError_[8]; } + + /** + * @brief Read the property from axisError_ + */ + bool negativeSoftwareLimit() { return axisError_[9]; } + + /** + * @brief Read the property from axisError_ + */ + bool overCurrent() { return axisError_[10]; } + + /** + * @brief Read the property from axisError_ + */ + bool overTemperature() { return axisError_[11]; } + + /** + * @brief Read the property from axisError_ + */ + bool overVoltage() { return axisError_[12]; } + + /** + * @brief Read the property from axisError_ + */ + bool underVoltage() { return axisError_[13]; } + + /** + * @brief Read the property from axisError_ + */ + bool stoFault() { return axisError_[15]; } + private: friend class masterMacsController; }; diff --git a/src/masterMacsController.cpp b/src/masterMacsController.cpp index 66a10b1..0b88ca4 100644 --- a/src/masterMacsController.cpp +++ b/src/masterMacsController.cpp @@ -75,10 +75,9 @@ masterMacsController::masterMacsController(const char *portName, */ pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL); if (status != asynSuccess || lowLevelPortUser_ == nullptr) { - errlogPrintf( - "%s => line %d:\nFATAL ERROR (cannot connect to MCU controller).\n" - "Terminating IOC", - __PRETTY_FUNCTION__, __LINE__); + errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot " + "connect to MCU controller).\nTerminating IOC", + portName, __PRETTY_FUNCTION__, __LINE__); exit(-1); } @@ -93,20 +92,21 @@ masterMacsController::masterMacsController(const char *portName, lowLevelPortUser_, message_from_device, strlen(message_from_device)); if (status != asynSuccess) { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nFATAL ERROR (setting input EOS failed " - "with %s).\nTerminating IOC", - __PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); + "Controller \"%s\" => %s, line %d:\nFATAL ERROR (setting " + "input EOS failed with %s).\nTerminating IOC", + portName, __PRETTY_FUNCTION__, __LINE__, + stringifyAsynStatus(status)); pasynOctetSyncIO->disconnect(lowLevelPortUser_); exit(-1); } status = callParamCallbacks(); if (status != asynSuccess) { - asynPrint( - this->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nFATAL ERROR (executing ParamLib callbacks failed " - "with %s).\nTerminating IOC", - __PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); + asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\" => %s, line %d:\nFATAL ERROR (executing " + "ParamLib callbacks failed with %s).\nTerminating IOC", + portName, __PRETTY_FUNCTION__, __LINE__, + stringifyAsynStatus(status)); pasynOctetSyncIO->disconnect(lowLevelPortUser_); exit(-1); } @@ -144,10 +144,10 @@ masterMacsAxis *masterMacsController::castToAxis(asynMotorAxis *asynAxis) { // an instance of Axis masterMacsAxis *axis = dynamic_cast(asynAxis); if (axis == nullptr) { - asynPrint( - this->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nAxis %d is not an instance of masterMacsAxis", - __PRETTY_FUNCTION__, __LINE__, axis->axisNo_); + asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d:\nAxis is not " + "an instance of masterMacsAxis", + portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__); } return axis; } @@ -248,9 +248,10 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, fullCommand[2] = lenWithMetadataSep.quot; // MSB adjustForPrint(printableCommand, fullCommand, MAXBUF_); - asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER, - "%s => line %d:\nSending command %s\n", __PRETTY_FUNCTION__, - __LINE__, printableCommand); + asynPrint( + this->pasynUserSelf, ASYN_TRACEIO_DRIVER, + "Controller \"%s\", axis %d => %s, line %d:\nSending command %s\n", + portName, axisNo, __PRETTY_FUNCTION__, __LINE__, printableCommand); // Send out the command status = @@ -276,25 +277,28 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, } } else { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nError %s while reading from the " - "controller\n", - __PRETTY_FUNCTION__, __LINE__, + "Controller \"%s\", axis %d => %s, line %d:\nError " + "%s while reading from the controller\n", + portName, axisNo, __PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); break; } if (i + 1 == maxTrials && status == asynError) { - asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nFailed %d times to get the " - "correct response. Aborting read.\n", - __PRETTY_FUNCTION__, __LINE__, maxTrials); + asynPrint( + this->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d:\nFailed " + "%d times to get the correct response. Aborting read.\n", + portName, axisNo, __PRETTY_FUNCTION__, __LINE__, maxTrials); } } } else { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nError %s while writing to the controller\n", - __PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); + "Controller \"%s\", axis %d => %s, line %d:\nError %s while " + "writing to the controller\n", + portName, axisNo, __PRETTY_FUNCTION__, __LINE__, + stringifyAsynStatus(status)); } // MasterMACS needs a bit of time between messages, therefore thr program @@ -341,9 +345,10 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, // Log the overall status (communication successfull or not) if (status == asynSuccess) { adjustForPrint(printableResponse, fullResponse, MAXBUF_); - asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER, - "%s => line %d:\nReturn value: %s\n", __PRETTY_FUNCTION__, - __LINE__, printableResponse); + asynPrint( + lowLevelPortUser_, ASYN_TRACEIO_DRIVER, + "Controller \"%s\", axis %d => %s, line %d:\nReturn value: %s\n", + portName, axisNo, __PRETTY_FUNCTION__, __LINE__, printableResponse); pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0); } else { @@ -355,26 +360,29 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem); if (pl_status != asynSuccess) { return paramLibAccessFailed(pl_status, "motorStatusProblem_", - __PRETTY_FUNCTION__, __LINE__); + axisNo, __PRETTY_FUNCTION__, __LINE__); } if (motorStatusProblem == 0) { pl_status = axis->setStringParam(motorMessageText_, drvMessageText); if (pl_status != asynSuccess) { return paramLibAccessFailed(pl_status, "motorMessageText_", - __PRETTY_FUNCTION__, __LINE__); + axisNo, __PRETTY_FUNCTION__, + __LINE__); } pl_status = axis->setIntegerParam(motorStatusProblem_, 1); if (pl_status != asynSuccess) { return paramLibAccessFailed(pl_status, "motorStatusProblem", - __PRETTY_FUNCTION__, __LINE__); + axisNo, __PRETTY_FUNCTION__, + __LINE__); } pl_status = axis->setIntegerParam(motorStatusProblem_, 1); if (pl_status != asynSuccess) { return paramLibAccessFailed(pl_status, "motorStatusCommsError_", - __PRETTY_FUNCTION__, __LINE__); + axisNo, __PRETTY_FUNCTION__, + __LINE__); } } } @@ -414,9 +422,10 @@ asynStatus masterMacsController::parseResponse( } else if (fullResponse[i] == '\x15') { // NAK snprintf(drvMessageText, MAXBUF_, "Communication failed."); - asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nCommunication failed\n", - __PRETTY_FUNCTION__, __LINE__); + asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER, + "Controller \"%s\", axis %d => %s, line " + "%d:\nCommunication failed\n", + portName, axisNo, __PRETTY_FUNCTION__, __LINE__); break; } else if (fullResponse[i] == '\x18') { // CAN @@ -424,9 +433,10 @@ asynStatus masterMacsController::parseResponse( "Tried to write with a read-only command. This is a " "bug, please call the support."); asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nTried to write with the read-only " - "command %s\n", - __PRETTY_FUNCTION__, __LINE__, printableCommand); + "Controller \"%s\", axis %d => %s, line %d:\nTried to " + "write with the read-only command %s\n", + portName, axisNo, __PRETTY_FUNCTION__, __LINE__, + printableCommand); responseValid = false; break; } @@ -452,11 +462,11 @@ asynStatus masterMacsController::parseResponse( adjustForPrint(printableCommand, fullCommand, MAXBUF_); adjustForPrint(printableResponse, fullResponse, MAXBUF_); - asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "%s => line %d:\nMismatched response %s to " - "command %s\n", - __PRETTY_FUNCTION__, __LINE__, printableResponse, - printableCommand); + asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER, + "Controller \"%s\", axis %d => %s, line %d:\nMismatched " + "response %s to command %s\n", + portName, axisNo, __PRETTY_FUNCTION__, __LINE__, + printableResponse, printableCommand); snprintf(drvMessageText, MAXBUF_, "Mismatched response %s to command %s. Please call the " @@ -511,72 +521,6 @@ asynStatus masterMacsCreateController(const char *portName, return asynSuccess; } -/* -C wrapper for the axis constructor. Please refer to the masterMacsAxis -constructor documentation. The controller is read from the portName. -*/ -asynStatus masterMacsCreateAxis(const char *portName, int axis) { - masterMacsAxis *pAxis; - - /* - 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't - have a lowLevelPortUser_! printf is an EPICS alternative which - works w/o that, but doesn't offer the comfort provided - by the asynTrace-facility - */ - errlogPrintf("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__, - __LINE__, portName); - return asynError; - } - // Unsafe cast of the pointer to an asynPortDriver - asynPortDriver *apd = (asynPortDriver *)(ptr); - - // Safe downcast - masterMacsController *pC = dynamic_cast(apd); - if (pC == nullptr) { - errlogPrintf("%s => line %d:\ncontroller on port %s is not a " - "masterMacsController.", - __PRETTY_FUNCTION__, __LINE__, portName); - 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" - pAxis = new masterMacsAxis(pC, axis); - - // Allow manipulation of the controller again - pC->unlock(); - return asynSuccess; -} - /* This is boilerplate code which is used to make the FFI functions CreateController and CreateAxis "known" to the IOC shell (iocsh). @@ -613,30 +557,13 @@ static void configMasterMacsCreateControllerCallFunc(const iocshArgBuf *args) { args[3].dval, args[4].dval, args[5].dval); } -/* -Same procedure as for the CreateController function, but for the axis -itself. -*/ -static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mmacs1)", - iocshArgString}; -static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt}; -static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0, - &CreateAxisArg1}; -static const iocshFuncDef configMasterMacsCreateAxis = {"masterMacsAxis", 2, - CreateAxisArgs}; -static void configMasterMacsCreateAxisCallFunc(const iocshArgBuf *args) { - masterMacsCreateAxis(args[0].sval, args[1].ival); -} - // This function is made known to EPICS in masterMacs.dbd and is called by // EPICS in order to register both functions in the IOC shell -static void masterMacsRegister(void) { +static void masterMacsControllerRegister(void) { iocshRegister(&configMasterMacsCreateController, configMasterMacsCreateControllerCallFunc); - iocshRegister(&configMasterMacsCreateAxis, - configMasterMacsCreateAxisCallFunc); } -epicsExportRegistrar(masterMacsRegister); +epicsExportRegistrar(masterMacsControllerRegister); #endif diff --git a/utils/decodeCommon.py b/utils/decodeCommon.py new file mode 100644 index 0000000..06f655e --- /dev/null +++ b/utils/decodeCommon.py @@ -0,0 +1,120 @@ +""" +Code shared by "decodeError.py" and "decodeStatus.py" +""" + +def decode(value: int, interpretation): + + bit_list = [int(char) for char in bin(value)[2:]] + bit_list.reverse() + + interpreted = [] + for (bit, interpretations) in zip(bit_list, interpretation): + interpreted.append(interpretations[bit]) + return (bit_list, interpreted) + +def print_decoded(bit_list, interpreted): + for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)): + print(f"Bit {idx} = {bit_value}: {msg}") + +def interactive(): + + # Imported here, because curses is not available in Windows. Using the + # interactive mode therefore fails on Windows, but at least the single + # command mode can be used (which would not be possible if we would import + # curses at the top level) + import curses + + stdscr = curses.initscr() + curses.noecho() + curses.cbreak() + stdscr.keypad(True) + stdscr.scrollok(True) + + stdscr.addstr(">> ") + stdscr.refresh() + + history = [""] + ptr = len(history) - 1 + + while True: + c = stdscr.getch() + if c == curses.KEY_RIGHT: + (y, x) = stdscr.getyx() + if x < len(history[ptr]) + 3: + stdscr.move(y, x+1) + stdscr.refresh() + elif c == curses.KEY_LEFT: + (y, x) = stdscr.getyx() + if x > 3: + stdscr.move(y, x-1) + stdscr.refresh() + elif c == curses.KEY_UP: + if ptr > 0: + ptr -= 1 + stdscr.addch("\r") + stdscr.clrtoeol() + stdscr.addstr(">> " + history[ptr]) + elif c == curses.KEY_DOWN: + if ptr < len(history) - 1: + ptr += 1 + stdscr.addch("\r") + stdscr.clrtoeol() + stdscr.addstr(">> " + history[ptr]) + elif c == curses.KEY_ENTER or c == ord('\n') or c == ord('\r'): + if history[ptr] == 'quit': + break + + # because of arrow keys move back to the end of the line + (y, x) = stdscr.getyx() + stdscr.move(y, 3+len(history[ptr])) + + if history[ptr]: + (bit_list, interpreted) = decode(history[ptr]) + for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)): + stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}") + stdscr.refresh() + + if ptr == len(history) - 1 and history[ptr] != "": + history += [""] + else: + history[-1] = "" + ptr = len(history) - 1 + + stdscr.addstr("\n>> ") + stdscr.refresh() + + else: + if ptr < len(history) - 1: # Modifying previous input + if len(history[-1]) == 0: + history[-1] = history[ptr] + ptr = len(history) - 1 + + else: + history += [history[ptr]] + ptr = len(history) - 1 + + if c == curses.KEY_BACKSPACE: + if len(history[ptr]) == 0: + continue + (y, x) = stdscr.getyx() + history[ptr] = history[ptr][0:x-4] + history[ptr][x-3:] + stdscr.addch("\r") + stdscr.clrtoeol() + stdscr.addstr(">> " + history[ptr]) + stdscr.move(y, x-1) + stdscr.refresh() + + else: + (y, x) = stdscr.getyx() + history[ptr] = history[ptr][0:x-3] + chr(c) + history[ptr][x-3:] + stdscr.addch("\r") + stdscr.clrtoeol() + stdscr.addstr(">> " + history[ptr]) + stdscr.move(y, x+1) + stdscr.refresh() + + # to quit + curses.nocbreak() + stdscr.keypad(False) + curses.echo() + curses.endwin() \ No newline at end of file diff --git a/utils/decodeError.py b/utils/decodeError.py new file mode 100755 index 0000000..9628b18 --- /dev/null +++ b/utils/decodeError.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +""" +The R11 error read command returns an integer, which needs to be interpreted +bitwise for various error flags. This script prints out these status flags in +human-readable formatting. + +To read the manual, simply run this script without any arguments. + +Stefan Mathis, January 2025 +""" + +from decodeCommon import interactive, decode, print_decoded + +# List of tuples which encodes the states given in the file description. +# Index first with the bit index, then with the bit value +interpretation = [ + ("Not specified", "Not specified"), # Bit 0 + ("Ok", "Short circuit"), # Bit 1 + ("Ok", "Encoder error"), # Bit 2 + ("Ok", "Following error"), # Bit 3 + ("Ok", "Communication error"), # Bit 4 + ("Ok", "Feedback error"), # Bit 5 + ("Ok", "Positive limit switch hit"), # Bit 6 + ("Ok", "Negative limit switch hit"), # Bit 7 + ("Ok", "Positive software limit hit"), # Bit 8 + ("Ok", "Negative software limit hit"), # Bit 9 + ("Ok", "Over-current"), # Bit 10 + ("Ok", "Over-temperature drive"), # Bit 11 + ("Ok", "Over-voltage"), # Bit 12 + ("Ok", "Under-voltage"), # Bit 13 + ("Not specified", "Not specified"), # Bit 14 + ("Ok", "STO fault (STO input is on disable state)"), # Bit 15 +] + +if __name__ == "__main__": + from sys import argv + + if len(argv) == 1: + # Start interactive mode + interactive() + else: + + number = None + try: + number = int(float(argv[1])) + + except: + print(""" + Decode R11 message of MasterMACs + ------------------ + + MasterMACs returns its error message (R11) as a floating-point number. + The bits of this float encode different states. These states are stored + in the interpretation variable. + + This script can be used in two different ways: + + Option 1: Single Command + ------------------------ + + Usage: decodeError.py value + + 'value' is the return value of a R11 command. This value is interpreted + bit-wise and the result is printed out. + + Option 2: CLI Mode + ------------------ + + Usage: decodeError.py + + A prompt will be opened. Type in the return value of a R11 command, hit + enter and the interpretation will be printed in the prompt. After that, + the next value can be typed in. Type 'quit' to close the prompt. + """) + + + if number is not None: + print("Motor error") + print("============") + (bit_list, interpreted) = decode(number, interpretation) + print_decoded(bit_list, interpreted) diff --git a/utils/decodeStatus.py b/utils/decodeStatus.py old mode 100644 new mode 100755 index ee27133..4b16bbc --- a/utils/decodeStatus.py +++ b/utils/decodeStatus.py @@ -9,6 +9,7 @@ To read the manual, simply run this script without any arguments. Stefan Mathis, December 2024 """ +from decodeCommon import interactive, decode, print_decoded # List of tuples which encodes the states given in the file description. # Index first with the bit index, then with the bit value @@ -20,35 +21,17 @@ interpretation = [ ("Motor supply voltage absent ", "Motor supply voltage present"), # Bit 4 ("Motor performs quick stop", "Ok"), # Bit 5 ("Switch on enabled", "Switch on disabled"), # Bit 6 - ("Ok", "RWarning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7 - ("Motor is idle", "Motor is currently moving"), # Bit 8 + ("Ok", "Warning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7 + ("Not specified", "Not specified"), # Bit 8 ("Motor does not execute command messages (local mode)", "Motor does execute command messages (remote mode)"), # Bit 9 ("Target not reached", "Target reached"), # Bit 10 - ("Ok", "Internal limit active"), # Bit 11 + ("Ok", "Internal limit active (current, voltage, velocity or position)"), # Bit 11 ("Not specified", "Not specified"), # Bit 12 ("Not specified", "Not specified"), # Bit 13 - ("No event set or event has not occurred yet", "Set event has occurred"), # Bit 14 - ("Axis off (power disabled)", "Axis on (power enabled)"), # Bit 15 + ("Not specified", "Not specified"), # Bit 14 + ("Not specified", "Not specified"), # Bit 15 ] -def decode(value, big_endian: bool = False): - - interpreted = [] - - bit_list = [(value >> shift_ind) & 1 - for shift_ind in range(value.bit_length())] # little endian - - if big_endian: - bit_list.reverse() # big endian - - for (bit, interpretations) in zip(bit_list, interpretation): - interpreted.append(interpretations[bit]) - return (bit_list, interpreted) - -def print_decoded(bit_list, interpreted): - for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)): - print(f"Bit {idx} = {bit_value}: {msg}") - def interactive(): # Imported here, because curses is not available in Windows. Using the @@ -102,15 +85,10 @@ def interactive(): stdscr.move(y, 3+len(history[ptr])) if history[ptr]: - result = interpret_inputs(history[ptr].split()) - if result is None: - stdscr.addstr(f"\nBAD INPUT: Expected input of 'value [big_endian]', where 'value' is an int or a float and 'big_endian' is an optional boolean argument.") - else: - (arg, big_endian) = result - (bit_list, interpreted) = decode(arg, big_endian) - for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)): - stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}") - stdscr.refresh() + (bit_list, interpreted) = decode(history[ptr]) + for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)): + stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}") + stdscr.refresh() if ptr == len(history) - 1 and history[ptr] != "": history += [""] @@ -157,24 +135,6 @@ def interactive(): curses.echo() curses.endwin() -def interpret_inputs(inputs): - - number = None - big_endian = False - try: - number = int(float(inputs[0])) - if len(inputs) > 1: - second_arg = inputs[1] - if second_arg == "True" or second_arg == "true": - big_endian = True - elif second_arg == "False" or second_arg == "false": - big_endian = False - else: - big_endian = bool(int(second_arg)) - return (number, big_endian) - except: - return None - if __name__ == "__main__": from sys import argv @@ -183,9 +143,11 @@ if __name__ == "__main__": interactive() else: - result = interpret_inputs(argv[1:]) + number = None + try: + number = int(float(argv[1])) - if result is None: + except: print(""" Decode R10 message of MasterMACs ------------------ @@ -199,25 +161,24 @@ if __name__ == "__main__": Option 1: Single Command ------------------------ - Usage: decodeMasterMACStatusR10.py value [big_endian] + Usage: decodeStatus.py value 'value' is the return value of a R10 command. This value is interpreted - bit-wise and the result is printed out. The optional second argument can - be used to specify whether the input value needs to be interpreted as - little or big endian. Default is False. + bit-wise and the result is printed out. Option 2: CLI Mode ------------------ - Usage: decodeMasterMACStatusR10.py + Usage: decodeStatus.py A prompt will be opened. Type in the return value of a R10 command, hit enter and the interpretation will be printed in the prompt. After that, the next value can be typed in. Type 'quit' to close the prompt. """) - else: + + + if number is not None: print("Motor status") print("============") - (arg, big_endian) = result - (bit_list, interpreted) = decode(arg, big_endian) + (bit_list, interpreted) = decode(number, interpretation) print_decoded(bit_list, interpreted)