diff --git a/Makefile b/Makefile index 87d6dea..e63399c 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,12 @@ ARCH_FILTER=RHEL% asynMotor_VERSION=7.2.2 # Source files to build +SOURCES += src/msgPrintControl.cpp SOURCES += src/sinqAxis.cpp SOURCES += src/sinqController.cpp # Headers which allow using this library in concrete driver implementations +HEADERS += src/msgPrintControl.h HEADERS += src/sinqAxis.h HEADERS += src/sinqController.h diff --git a/README.md b/README.md index a9e7a03..7cadf04 100644 --- a/README.md +++ b/README.md @@ -42,34 +42,41 @@ epicsEnvSet("INSTR","SQ:SINQTEST:") iocInit() ``` The first line is a so-called shebang which instructs Linux to execute the file with the executable located at the given path - the IOC shell in this case. The controller script "mcu1.cmd" looks like this: +The script for controller 1 ("mcu1.cmd") for a Turbo PMAC (see https://git.psi.ch/sinq-epics-modules/turboPmac) has the following structure. The scripts for other controller types can be found in the README.md of their respective repositories. + ``` -# Define some needed parameters (they can be safely overwritten in e.g. mcu2.cmd) +# Define the name of the controller and the corresponding port epicsEnvSet("NAME","mcu1") epicsEnvSet("ASYN_PORT","p$(NAME)") -# Define the IP adress of the controller +# 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 drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025") -# Create the controller object in EPICS. The function "pmacv3Controller" is -# provided by loading the shared library turboPmac. -pmacv3Controller("$(NAME)","$(ASYN_PORT)",8,0.05,1,0.05); +# 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("$(NAME)", "$(ASYN_PORT)", 8, 0.05, 1, 1); -# Create four axes objects on slots 1, 2, 3 and 5 of the controller. -pmacv3Axis("$(NAME)",1); -pmacv3Axis("$(NAME)",2); -pmacv3Axis("$(NAME)",3); -pmacv3Axis("$(NAME)",5); +# Define some axes for the specified MCU at the given slot (1, 2 and 5). No slot may be used twice! +turboPmacAxis("$(NAME)",1); +turboPmacAxis("$(NAME)",2); +turboPmacAxis("$(NAME)",5); -# Create some general PVs of an asynRecord, substituting the macro P by concatenating INSTR and NAME and PORT by ASYN_PORT. -dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)") +# Set the number of subsequent timeouts +setMaxSubsequentTimeouts("$(NAME)", 20); -# Create PVs provided by the sinqMotor database template. This template is parametrized by the substitution file "mcu1.substitutions" (see below) +# Configure the timeout frequency watchdog: +setThresholdComTimeout("$(NAME)", 100, 1); + +# Parametrize the EPICS record database with the substitution file named after the MCU. epicsEnvSet("SINQDBPATH","$(sinqMotor_DB)/sinqMotor.db") -dbLoadTemplate("$(TOP)/mcu1.substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)") - -# Create PVs specific for pmacv3. Again, we load a database template and parametrize it with the substitution file "mcu1.substitutions" -epicsEnvSet("SINQDBPATH","$(pmacv3_DB)/pmacv3.db") -dbLoadTemplate("$(TOP)/mcu1.substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)") +dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)") +epicsEnvSet("SINQDBPATH","$(turboPmac_DB)/turboPmac.db") +dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)") +dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)") ``` ### Substitution file @@ -189,6 +196,9 @@ sinqMotor offers a variety of additional methods for children classes to standar - `setOffsetMovTimeout`: Set a linear offset for the expected movement time. This function is also available in the IOC shell. - `setScaleMovTimeout`: Set a scaling factor for the expected movement time. This function is also available in the IOC shell. +#### msgPrintControl.h +In addition to the two extension classes this library also includes a mechanism which prevents excessive repetitions of the same error message to the IOC shell via the classes `msgPrintControl` and `msgPrintControlKey`. A detailed description of the mechanism can be found in the docstring of `msgPrintControl`. The implementation of the `poll` function of `sinqAxis` also contains an example how to use it. Using this feature in derived drivers is entirely optional. + ### Versioning The versioning is done via git tags. Git tags are recognized by the PSI build system: If you tag a version as 1.0, it will be built into the directory /ioc/modules/sinqMotor/1.0. The tag is directly coupled to a commit so that it is always clear which source code was used to build which binary. diff --git a/db/sinqMotor.db b/db/sinqMotor.db index 96aa4f0..257297c 100755 --- a/db/sinqMotor.db +++ b/db/sinqMotor.db @@ -41,16 +41,6 @@ record(motor,"$(INSTR)$(M)") field(RMOD,"3") # Retry mode 3 ("In-Position"): This suppresses any retries from the motor record. } -# This record holds a copy of the motor record target position field (.VAL) and allows to access -# the value from within the driver via the index motorTargetPosition_. -# This record is coupled to the parameter library via motorTargetPosition_ -> MOTOR_TARGET_POSITION. -record(ao,"$(INSTR)$(M):TargetPosition") { - field(DOL, "$(INSTR)$(M).VAL CP MS") - field(OMSL, "closed_loop") - field(DTYP, "asynFloat64") - field(OUT, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_TARGET_POSITION") -} - # This PV allows force-stopping the motor record from within the driver by setting # the motorForceStop_ value in the parameter library to 1. It should be reset to 0 by the driver afterwards. # The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php. diff --git a/src/sinqAxis.cpp b/src/sinqAxis.cpp index fdcb28c..403f82d 100644 --- a/src/sinqAxis.cpp +++ b/src/sinqAxis.cpp @@ -10,9 +10,7 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo) : asynMotorAxis((asynMotorController *)pC, axisNo), pC_(pC) { asynStatus status = asynSuccess; - initial_poll_ = true; watchdogMovActive_ = false; - init_poll_counter_ = 0; scaleMovTimeout_ = 2.0; offsetMovTimeout_ = 30; targetPosition_ = 0.0; @@ -188,14 +186,18 @@ asynStatus sinqAxis::poll(bool *moving) { // According to the function documentation of asynMotorAxis::poll, this // function should be called at the end of a poll implementation. pl_status = callParamCallbacks(); - if (pl_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 + bool wantToPrint = pl_status != asynSuccess; + if (pC_->msgPrintControl_.shouldBePrinted( + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, wantToPrint, + pC_->pasynUserSelf)) { asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line " - "%d:\ncallParamCallbacks failed with %s.\n", + "%d:\ncallParamCallbacks failed with %s.%s\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, - pC_->stringifyAsynStatus(poll_status)); + pC_->stringifyAsynStatus(poll_status), + pC_->msgPrintControl_.getSuffix()); + } + if (wantToPrint) { poll_status = pl_status; } diff --git a/src/sinqAxis.h b/src/sinqAxis.h index debac44..3ef98ef 100644 --- a/src/sinqAxis.h +++ b/src/sinqAxis.h @@ -4,8 +4,8 @@ This class extends asynMotorAxis by some features used in SINQ. Stefan Mathis, November 2024 */ -#ifndef __SINQDRIVER -#define __SINQDRIVER +#ifndef sinqAxis_H +#define sinqAxis_H #include "asynMotorAxis.h" class epicsShareClass sinqAxis : public asynMotorAxis { @@ -294,9 +294,6 @@ class epicsShareClass sinqAxis : public asynMotorAxis { friend class sinqController; protected: - bool initial_poll_; - int init_poll_counter_; - // Internal variables used in the movement timeout watchdog time_t expectedArrivalTime_; time_t offsetMovTimeout_; diff --git a/src/sinqController.cpp b/src/sinqController.cpp index ae561e2..cd4dded 100644 --- a/src/sinqController.cpp +++ b/src/sinqController.cpp @@ -46,15 +46,11 @@ sinqController::sinqController(const char *portName, 0, // No additional interfaces beyond those in base class 0, // No additional callback interfaces beyond those in base class ASYN_CANBLOCK | ASYN_MULTIDEVICE, - 1, // autoconnect - 0, - 0) // Default priority and stack size -{ + 1, // autoconnect + 0, 0), // Default priority and stack size + msgPrintControl_(4) { - // Initialization of local variables asynStatus status = asynSuccess; - - // Initialization of all member variables lowLevelPortUser_ = nullptr; // Initial values for the average timeout mechanism, can be overwritten @@ -103,17 +99,6 @@ sinqController::sinqController(const char *portName, exit(-1); } - status = createParam("MOTOR_TARGET_POSITION", asynParamFloat64, - &motorTargetPosition_); - if (status != asynSuccess) { - asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a " - "parameter failed with %s).\nTerminating IOC", - portName, __PRETTY_FUNCTION__, __LINE__, - stringifyAsynStatus(status)); - exit(-1); - } - status = createParam("MOTOR_ENABLE", asynParamInt32, &motorEnable_); if (status != asynSuccess) { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, @@ -285,6 +270,10 @@ sinqController::~sinqController(void) { free(this->pAxes_); } +msgPrintControl &sinqController::getMsgPrintControl() { + return msgPrintControl_; +} + asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) { int function = pasynUser->reason; @@ -292,6 +281,7 @@ asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) { asynMotorAxis *asynAxis = getAxis(pasynUser); sinqAxis *axis = dynamic_cast(asynAxis); + if (axis == nullptr) { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\nAxis is not an " @@ -303,6 +293,8 @@ asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) { // Handle custom PVs if (function == motorEnable_) { return axis->enable(value != 0); + } else if (function == motorForceStop_) { + return axis->stop(0.0); } else { return asynMotorController::writeInt32(pasynUser, value); } @@ -313,10 +305,11 @@ asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) { // Casting into a sinqAxis is necessary to get access to the field axisNo_ asynMotorAxis *asynAxis = getAxis(pasynUser); sinqAxis *axis = dynamic_cast(asynAxis); + if (axis == nullptr) { asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\nAxis is not an " - "instance of sinqAxis", + "instance of sinqAxis.\n", portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__); return asynError; } @@ -334,13 +327,13 @@ asynStatus sinqController::errMsgCouldNotParseResponse(const char *command, const char *response, int axisNo, const char *functionName, - int lineNumber) { + int line) { asynStatus pl_status = asynSuccess; asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\nCould not interpret " "response \"%s\" for command \"%s\".\n", - portName, axisNo, functionName, lineNumber, response, command); + portName, axisNo, functionName, line, response, command); pl_status = setStringParam( motorMessageText_, @@ -363,16 +356,17 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status, const char *parameter, int axisNo, const char *functionName, - int lineNumber) { + int line) { if (status != asynSuccess) { - // Log the error message and try to propagate it. If propagating fails, - // there is nothing we can do here anyway. asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\n Accessing the " "parameter library failed for parameter %s with error %s.\n", - portName, axisNo, functionName, lineNumber, parameter, + portName, axisNo, functionName, line, parameter, stringifyAsynStatus(status)); + + // Log the error message and try to propagate it. If propagating fails, + // there is nothing we can do here anyway. setStringParam(motorMessageText_, "Accessing paramLib failed. Please call the support."); } @@ -402,20 +396,26 @@ asynStatus sinqController::checkComTimeoutWatchdog(int axisNo, } // Check if the maximum allowed number of events has been exceeded - if (timeoutEvents_.size() > maxNumberTimeouts_) { + bool wantToPrint = timeoutEvents_.size() > maxNumberTimeouts_; + if (msgPrintControl_.shouldBePrinted(portName, axisNo, __PRETTY_FUNCTION__, + __LINE__, wantToPrint, + pasynUserSelf)) { + asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d:\nMore than %ld " + "communication timeouts in %ld " + "seconds.%s\n", + portName, axisNo, __PRETTY_FUNCTION__, __LINE__, + maxNumberTimeouts_, comTimeoutWindow_, + msgPrintControl_.getSuffix()); + } + + if (wantToPrint) { snprintf(motorMessage, motorMessageSize, "More than %ld communication timeouts in %ld seconds. Please " "call the support.", maxNumberTimeouts_, comTimeoutWindow_); - asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line %d:\nMore than %ld " - "communication timeouts in %ld " - "seconds\n", - portName, axisNo, __PRETTY_FUNCTION__, __LINE__, - maxNumberTimeouts_, comTimeoutWindow_); - paramLibStatus = setIntegerParam(motorStatusCommsError_, 1); if (paramLibStatus != asynSuccess) { return paramLibAccessFailed(paramLibStatus, diff --git a/src/sinqController.h b/src/sinqController.h index 265996e..87bc9ba 100644 --- a/src/sinqController.h +++ b/src/sinqController.h @@ -5,11 +5,13 @@ README.md for details. Stefan Mathis, November 2024 */ -#ifndef __sinqController -#define __sinqController +#ifndef sinqController_H +#define sinqController_H #include "asynMotorController.h" +#include "msgPrintControl.h" #include #include +#include #define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER" #define motorMessageTextString "MOTOR_MESSAGE_TEXT" @@ -88,13 +90,13 @@ class epicsShareClass sinqController : public asynMotorController { error messages. * @param functionName Name of the caller function. It is recommended to use a macro, e.g. __func__ or __PRETTY_FUNCTION__. - * @param lineNumber Source code line where this function is + * @param line Source code line where this function is called. It is recommended to use a macro, e.g. __LINE__. * @return asynStatus Returns input status. */ asynStatus paramLibAccessFailed(asynStatus status, const char *parameter, int axisNo, const char *functionName, - int lineNumber); + int line); /** * @brief Error handling in case parsing a command response failed. @@ -109,14 +111,13 @@ class epicsShareClass sinqController : public asynMotorController { * @param axisNo_ Axis where the problem occurred * @param functionName Name of the caller function. It is recommended to use a macro, e.g. __func__ or __PRETTY_FUNCTION__. - * @param lineNumber Source code line where this function is + * @param line Source code line where this function is called. It is recommended to use a macro, e.g. __LINE__. * @return asynStatus Returns asynError. */ asynStatus errMsgCouldNotParseResponse(const char *command, const char *response, int axisNo, - const char *functionName, - int lineNumber); + const char *functionName, int line); /** * @brief Convert an asynStatus into a descriptive string. @@ -213,13 +214,22 @@ class epicsShareClass sinqController : public asynMotorController { return asynSuccess; } + /** + * @brief Get a reference to the map used to control the maximum number of + * message repetitions. See the documentation of printRepetitionWatchdog in + * msgPrintControl.h for details. + * + * @return std::unordered_map& + */ + msgPrintControl &getMsgPrintControl(); + friend class sinqAxis; protected: asynUser *lowLevelPortUser_; - double movingPollPeriod_; double idlePollPeriod_; + msgPrintControl msgPrintControl_; // Internal variables used in the communication timeout frequency watchdog time_t comTimeoutWindow_; // Size of the time window @@ -235,7 +245,6 @@ class epicsShareClass sinqController : public asynMotorController { #define FIRST_SINQMOTOR_PARAM motorMessageText_ int motorMessageText_; - int motorTargetPosition_; int motorEnable_; int motorEnableRBV_; int motorCanDisable_;