diff --git a/db/sinqMotor.db b/db/sinqMotor.db index 6860018..a314aec 100755 --- a/db/sinqMotor.db +++ b/db/sinqMotor.db @@ -109,11 +109,11 @@ record(ao,"$(INSTR)$(M):RecResolution") { # This record contains messages from the driver (usually error messages). # The macro ERRORMSGSIZE can be used to set the maximum length of the message. # if not provided, a default value of 200 is used. -# This record is coupled to the parameter library via motorErrorMessage -> MOTOR_MESSAGE_TEXT. +# This record is coupled to the parameter library via motorErrorMessage -> MOTOR_ERROR_MESSAGE. record(waveform, "$(INSTR)$(M):ErrorMessage") { alias("$(INSTR)$(M)-MsgTxt") # Old PV name, aliased for backwards compatibility field(DTYP, "asynOctetRead") - field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_MESSAGE_TEXT") + field(INP, "@asyn($(CONTROLLER),$(AXIS),1) MOTOR_ERROR_MESSAGE") field(FTVL, "CHAR") field(NELM, "$(ERRORMSGSIZE=200)") # Should be the same as MAXBUF in the driver code field(SCAN, "I/O Intr") diff --git a/src/sinqAxis.cpp b/src/sinqAxis.cpp index 88fc15a..ba0284f 100644 --- a/src/sinqAxis.cpp +++ b/src/sinqAxis.cpp @@ -35,6 +35,11 @@ struct sinqAxisImpl { Store the time since the last poll */ epicsTimeStamp lastPollTime; + + // For this time in seconds, any error message will be kept alive in the + // parameter library. + time_t errorDisplayDuration; + epicsTimeStamp errorInitialAppearance; }; sinqAxis::sinqAxis(class sinqController *pC, int axisNo) @@ -49,6 +54,8 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo) .targetPosition = 0.0, .wasMoving = false, .lastPollTime = lastPollTime, + .errorDisplayDuration = 2, + .errorInitialAppearance = lastPollTime, }); }()) { @@ -281,6 +288,14 @@ asynStatus sinqAxis::forcedPoll(bool *moving) { setAxisParamChecked(this, motorErrorMessage, static_cast(waitingMessage)); } + + bool statProblem = false; + getAxisParamChecked(this, motorStatusProblem, &statProblem); + if (!statProblem) { + // The error initially appeared at this point in time + pSinqA_->errorInitialAppearance = ts; + } + setAxisParamChecked(this, motorStatusProblem, true); } else { // No errors are waiting -> Clear everything. @@ -330,11 +345,12 @@ asynStatus sinqAxis::forcedPoll(bool *moving) { poll_status = pl_status; } - /* - Delete the error message AFTER updating the PVs so it is not there anymore - during the next poll. - */ - setAxisParamChecked(this, motorErrorMessage, ""); + // Delete the error message after the display duration has been exceeded. + epicsTimeStamp errorRemovalTime = pSinqA_->errorInitialAppearance; + epicsTimeAddSeconds(&errorRemovalTime, pSinqA_->errorDisplayDuration); + if (epicsTimeLessThanEqual(&ts, &errorRemovalTime) == 0) { + setAxisParamChecked(this, motorErrorMessage, ""); + } return poll_status; } @@ -370,6 +386,10 @@ asynStatus sinqAxis::move(double position, int relative, double minVelocity, pC_->portName, axisNo(), __PRETTY_FUNCTION__, __LINE__); setAxisParamChecked(this, motorErrorMessage, "Motor needs to be homed / referenced first."); + setAxisParamChecked(this, motorStatusProblem, true); + epicsTimeStamp ts; + epicsTimeGetCurrent(&ts); + pSinqA_->errorInitialAppearance = ts; return pC_->callParamCallbacks(); } @@ -746,6 +766,11 @@ asynStatus sinqAxis::setScaleMovTimeout(time_t scaleMovTimeout) { return asynSuccess; } +asynStatus sinqAxis::setErrorDisplayDuration(time_t errorDisplayDuration) { + pSinqA_->errorDisplayDuration = errorDisplayDuration; + return asynSuccess; +} + bool sinqAxis::wasMoving() { return pSinqA_->wasMoving; } void sinqAxis::setWasMoving(bool wasMoving) { pSinqA_->wasMoving = wasMoving; } @@ -903,12 +928,63 @@ static void setScaleMovTimeoutCallFunc(const iocshArgBuf *args) { // ============================================================================= -// This function is made known to EPICS in sinqMotor.dbd and is called by EPICS -// in order to register all functions in the IOC shell +/** + * @brief Set the (minimum) error display duration (FFI implementation) + * + * @param portName Name of the controller + * @param axisNo Axis number + * @param errorDisplayDuration Minimum display duration + * @return asynStatus + */ +asynStatus setErrorDisplayDuration(const char *portName, int axisNo, + double errorDisplayDuration) { + + sinqController *pC; + pC = (sinqController *)findAsynPortDriver(portName); + if (pC == nullptr) { + errlogPrintf("Controller \"%s\" => %s, line %d:\nPort %s not found.", + portName, __PRETTY_FUNCTION__, __LINE__, portName); + return asynError; + } + + asynMotorAxis *asynAxis = pC->getAxis(axisNo); + sinqAxis *axis = dynamic_cast(asynAxis); + if (axis == nullptr) { + errlogPrintf("Controller \"%s\" => %s, line %d:\nAxis %d does not " + "exist or is not an instance of sinqAxis.", + portName, __PRETTY_FUNCTION__, __LINE__, axisNo); + } + + return axis->setErrorDisplayDuration(errorDisplayDuration); +} + +static const iocshArg setErrorDisplayDurationArg0 = {"Controller port name", + iocshArgString}; +static const iocshArg setErrorDisplayDurationArg1 = {"Axis number", + iocshArgInt}; +static const iocshArg setErrorDisplayDurationArg2 = { + "(Minimum) error display duration", iocshArgDouble}; +static const iocshArg *const setErrorDisplayDurationArgs[] = { + &setErrorDisplayDurationArg0, &setErrorDisplayDurationArg1, + &setErrorDisplayDurationArg2}; +static const iocshFuncDef setErrorDisplayDurationDef = { + "setErrorDisplayDuration", 3, setErrorDisplayDurationArgs, + "Specify an offset (in seconds) for the movement timeout watchdog"}; + +static void settErrorDisplayDurationCallFunc(const iocshArgBuf *args) { + setErrorDisplayDuration(args[0].sval, args[1].ival, args[2].dval); +} + +// ============================================================================= + +// This function is made known to EPICS in sinqMotor.dbd and is called by +// EPICS in order to register all functions in the IOC shell static void sinqAxisRegister(void) { iocshRegister(&setOffsetMovTimeoutDef, setOffsetMovTimeoutCallFunc); iocshRegister(&setScaleMovTimeoutDef, setScaleMovTimeoutCallFunc); iocshRegister(&setWatchdogEnabledDef, setWatchdogEnabledCallFunc); + iocshRegister(&setErrorDisplayDurationDef, + settErrorDisplayDurationCallFunc); } epicsExportRegistrar(sinqAxisRegister); diff --git a/src/sinqAxis.h b/src/sinqAxis.h index 6b1986c..6ca2def 100644 --- a/src/sinqAxis.h +++ b/src/sinqAxis.h @@ -337,7 +337,7 @@ class HIDDEN sinqAxis : public asynMotorAxis { /** * @brief Enable / disable the watchdog. Also available in the IOC shell - * (see "extern C" section in sinqController.cpp). + * (see "extern C" section in sinqAxis.cpp). * * If enable is set to false and the watchdog is currently running, this * function stops it immediately. @@ -349,7 +349,7 @@ class HIDDEN sinqAxis : public asynMotorAxis { /** * @brief Set the offsetMovTimeout. Also available in the IOC shell - * (see "extern C" section in sinqController.cpp). + * (see "extern C" section in sinqAxis.cpp). * * See documentation of `checkMovTimeoutWatchdog` for details. * @@ -360,7 +360,7 @@ class HIDDEN sinqAxis : public asynMotorAxis { /** * @brief Set the scaleMovTimeout. Also available in the IOC shell - * (see "extern C" section in sinqController.cpp). + * (see "extern C" section in sinqAxis.cpp). * See documentation of `checkMovTimeoutWatchdog` for details. * @@ -369,6 +369,18 @@ class HIDDEN sinqAxis : public asynMotorAxis { */ virtual asynStatus setScaleMovTimeout(time_t scaleMovTimeout); + /** + * @brief Set the errorDisplayDuration. Also available in the IOC shell + * (see "extern C" section in sinqAxis.cpp). + * + This time in seconds define for which time an error will be displayed after + its root cause has been resolved. + * + * @param errorDisplayDuration Error display time (in seconds) + * @return asynStatus + */ + virtual asynStatus setErrorDisplayDuration(time_t errorDisplayDuration); + /** * @brief Return the axis number of this axis * diff --git a/src/sinqController.cpp b/src/sinqController.cpp index b288879..e54c08d 100644 --- a/src/sinqController.cpp +++ b/src/sinqController.cpp @@ -176,10 +176,10 @@ sinqController::sinqController(const char *portName, // =========================================================================; - // MOTOR_MESSAGE_TEXT corresponds to the PV definition inside sinqMotor.db. + // MOTOR_ERROR_MESSAGE corresponds to the PV definition inside sinqMotor.db. // This text is used to forward status messages to NICOS and in turn to the // user. - status = createParam("MOTOR_MESSAGE_TEXT", asynParamOctet, + status = createParam("MOTOR_ERROR_MESSAGE", asynParamOctet, &pSinqC_->motorErrorMessage); if (status != asynSuccess) { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, diff --git a/src/sinqController.h b/src/sinqController.h index 6148a36..166d727 100644 --- a/src/sinqController.h +++ b/src/sinqController.h @@ -24,7 +24,7 @@ Stefan Mathis, November 2024 #include #define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER" -#define motorMessageTextString "MOTOR_MESSAGE_TEXT" +#define motorMessageTextString "MOTOR_ERROR_MESSAGE" #define IncrementalEncoder "incremental" #define AbsoluteEncoder "absolute" #define NoEncoder "none"