Added msgPrintControl feature to control the maximum number of IOC shell

message repetitions.
This commit is contained in:
2025-03-04 09:12:11 +01:00
parent 591509bd43
commit d3307db987
7 changed files with 92 additions and 82 deletions

View File

@ -10,10 +10,12 @@ ARCH_FILTER=RHEL%
asynMotor_VERSION=7.2.2 asynMotor_VERSION=7.2.2
# Source files to build # Source files to build
SOURCES += src/msgPrintControl.cpp
SOURCES += src/sinqAxis.cpp SOURCES += src/sinqAxis.cpp
SOURCES += src/sinqController.cpp SOURCES += src/sinqController.cpp
# Headers which allow using this library in concrete driver implementations # Headers which allow using this library in concrete driver implementations
HEADERS += src/msgPrintControl.h
HEADERS += src/sinqAxis.h HEADERS += src/sinqAxis.h
HEADERS += src/sinqController.h HEADERS += src/sinqController.h

View File

@ -42,34 +42,41 @@ epicsEnvSet("INSTR","SQ:SINQTEST:")
iocInit() 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 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("NAME","mcu1")
epicsEnvSet("ASYN_PORT","p$(NAME)") 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") drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025")
# Create the controller object in EPICS. The function "pmacv3Controller" is # Create the controller object with the defined name and connect it to the socket via the port name.
# provided by loading the shared library turboPmac. # The other parameters are as follows:
pmacv3Controller("$(NAME)","$(ASYN_PORT)",8,0.05,1,0.05); # 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. # Define some axes for the specified MCU at the given slot (1, 2 and 5). No slot may be used twice!
pmacv3Axis("$(NAME)",1); turboPmacAxis("$(NAME)",1);
pmacv3Axis("$(NAME)",2); turboPmacAxis("$(NAME)",2);
pmacv3Axis("$(NAME)",3); turboPmacAxis("$(NAME)",5);
pmacv3Axis("$(NAME)",5);
# Create some general PVs of an asynRecord, substituting the macro P by concatenating INSTR and NAME and PORT by ASYN_PORT. # Set the number of subsequent timeouts
dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)") 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") epicsEnvSet("SINQDBPATH","$(sinqMotor_DB)/sinqMotor.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")
# Create PVs specific for pmacv3. Again, we load a database template and parametrize it with the substitution file "mcu1.substitutions" dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
epicsEnvSet("SINQDBPATH","$(pmacv3_DB)/pmacv3.db") dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)")
dbLoadTemplate("$(TOP)/mcu1.substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
``` ```
### Substitution file ### 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. - `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. - `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 ### 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. 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.

View File

@ -41,16 +41,6 @@ record(motor,"$(INSTR)$(M)")
field(RMOD,"3") # Retry mode 3 ("In-Position"): This suppresses any retries from the motor record. 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 # 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 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. # The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.

View File

@ -10,9 +10,7 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
: asynMotorAxis((asynMotorController *)pC, axisNo), pC_(pC) { : asynMotorAxis((asynMotorController *)pC, axisNo), pC_(pC) {
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
initial_poll_ = true;
watchdogMovActive_ = false; watchdogMovActive_ = false;
init_poll_counter_ = 0;
scaleMovTimeout_ = 2.0; scaleMovTimeout_ = 2.0;
offsetMovTimeout_ = 30; offsetMovTimeout_ = 30;
targetPosition_ = 0.0; targetPosition_ = 0.0;
@ -188,14 +186,18 @@ asynStatus sinqAxis::poll(bool *moving) {
// According to the function documentation of asynMotorAxis::poll, this // According to the function documentation of asynMotorAxis::poll, this
// function should be called at the end of a poll implementation. // function should be called at the end of a poll implementation.
pl_status = callParamCallbacks(); pl_status = callParamCallbacks();
if (pl_status != asynSuccess) { bool wantToPrint = pl_status != asynSuccess;
// If we can't communicate with the parameter library, it doesn't make if (pC_->msgPrintControl_.shouldBePrinted(
// sense to try and upstream this to the user -> Just log the error pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, wantToPrint,
pC_->pasynUserSelf)) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line " "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_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(poll_status)); pC_->stringifyAsynStatus(poll_status),
pC_->msgPrintControl_.getSuffix());
}
if (wantToPrint) {
poll_status = pl_status; poll_status = pl_status;
} }

View File

@ -4,8 +4,8 @@ This class extends asynMotorAxis by some features used in SINQ.
Stefan Mathis, November 2024 Stefan Mathis, November 2024
*/ */
#ifndef __SINQDRIVER #ifndef sinqAxis_H
#define __SINQDRIVER #define sinqAxis_H
#include "asynMotorAxis.h" #include "asynMotorAxis.h"
class epicsShareClass sinqAxis : public asynMotorAxis { class epicsShareClass sinqAxis : public asynMotorAxis {
@ -294,9 +294,6 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
friend class sinqController; friend class sinqController;
protected: protected:
bool initial_poll_;
int init_poll_counter_;
// Internal variables used in the movement timeout watchdog // Internal variables used in the movement timeout watchdog
time_t expectedArrivalTime_; time_t expectedArrivalTime_;
time_t offsetMovTimeout_; time_t offsetMovTimeout_;

View File

@ -46,15 +46,11 @@ sinqController::sinqController(const char *portName,
0, // No additional interfaces beyond those in base class 0, // No additional interfaces beyond those in base class
0, // No additional callback interfaces beyond those in base class 0, // No additional callback interfaces beyond those in base class
ASYN_CANBLOCK | ASYN_MULTIDEVICE, ASYN_CANBLOCK | ASYN_MULTIDEVICE,
1, // autoconnect 1, // autoconnect
0, 0, 0), // Default priority and stack size
0) // Default priority and stack size msgPrintControl_(4) {
{
// Initialization of local variables
asynStatus status = asynSuccess; asynStatus status = asynSuccess;
// Initialization of all member variables
lowLevelPortUser_ = nullptr; lowLevelPortUser_ = nullptr;
// Initial values for the average timeout mechanism, can be overwritten // Initial values for the average timeout mechanism, can be overwritten
@ -103,17 +99,6 @@ sinqController::sinqController(const char *portName,
exit(-1); 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_); status = createParam("MOTOR_ENABLE", asynParamInt32, &motorEnable_);
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
@ -285,6 +270,10 @@ sinqController::~sinqController(void) {
free(this->pAxes_); free(this->pAxes_);
} }
msgPrintControl &sinqController::getMsgPrintControl() {
return msgPrintControl_;
}
asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) { asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
int function = pasynUser->reason; int function = pasynUser->reason;
@ -292,6 +281,7 @@ asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
asynMotorAxis *asynAxis = getAxis(pasynUser); asynMotorAxis *asynAxis = getAxis(pasynUser);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis); sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) { if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not an " "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 // Handle custom PVs
if (function == motorEnable_) { if (function == motorEnable_) {
return axis->enable(value != 0); return axis->enable(value != 0);
} else if (function == motorForceStop_) {
return axis->stop(0.0);
} else { } else {
return asynMotorController::writeInt32(pasynUser, value); 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_ // Casting into a sinqAxis is necessary to get access to the field axisNo_
asynMotorAxis *asynAxis = getAxis(pasynUser); asynMotorAxis *asynAxis = getAxis(pasynUser);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis); sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) { if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not an " "Controller \"%s\", axis %d => %s, line %d:\nAxis is not an "
"instance of sinqAxis", "instance of sinqAxis.\n",
portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__); portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynError; return asynError;
} }
@ -334,13 +327,13 @@ asynStatus sinqController::errMsgCouldNotParseResponse(const char *command,
const char *response, const char *response,
int axisNo, int axisNo,
const char *functionName, const char *functionName,
int lineNumber) { int line) {
asynStatus pl_status = asynSuccess; asynStatus pl_status = asynSuccess;
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR, asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nCould not interpret " "Controller \"%s\", axis %d => %s, line %d:\nCould not interpret "
"response \"%s\" for command \"%s\".\n", "response \"%s\" for command \"%s\".\n",
portName, axisNo, functionName, lineNumber, response, command); portName, axisNo, functionName, line, response, command);
pl_status = setStringParam( pl_status = setStringParam(
motorMessageText_, motorMessageText_,
@ -363,16 +356,17 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status,
const char *parameter, const char *parameter,
int axisNo, int axisNo,
const char *functionName, const char *functionName,
int lineNumber) { int line) {
if (status != asynSuccess) { 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, asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\n Accessing the " "Controller \"%s\", axis %d => %s, line %d:\n Accessing the "
"parameter library failed for parameter %s with error %s.\n", "parameter library failed for parameter %s with error %s.\n",
portName, axisNo, functionName, lineNumber, parameter, portName, axisNo, functionName, line, parameter,
stringifyAsynStatus(status)); stringifyAsynStatus(status));
// Log the error message and try to propagate it. If propagating fails,
// there is nothing we can do here anyway.
setStringParam(motorMessageText_, setStringParam(motorMessageText_,
"Accessing paramLib failed. Please call the support."); "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 // 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, snprintf(motorMessage, motorMessageSize,
"More than %ld communication timeouts in %ld seconds. Please " "More than %ld communication timeouts in %ld seconds. Please "
"call the support.", "call the support.",
maxNumberTimeouts_, comTimeoutWindow_); 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); paramLibStatus = setIntegerParam(motorStatusCommsError_, 1);
if (paramLibStatus != asynSuccess) { if (paramLibStatus != asynSuccess) {
return paramLibAccessFailed(paramLibStatus, return paramLibAccessFailed(paramLibStatus,

View File

@ -5,11 +5,13 @@ README.md for details.
Stefan Mathis, November 2024 Stefan Mathis, November 2024
*/ */
#ifndef __sinqController #ifndef sinqController_H
#define __sinqController #define sinqController_H
#include "asynMotorController.h" #include "asynMotorController.h"
#include "msgPrintControl.h"
#include <deque> #include <deque>
#include <initHooks.h> #include <initHooks.h>
#include <unordered_map>
#define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER" #define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER"
#define motorMessageTextString "MOTOR_MESSAGE_TEXT" #define motorMessageTextString "MOTOR_MESSAGE_TEXT"
@ -88,13 +90,13 @@ class epicsShareClass sinqController : public asynMotorController {
error messages. error messages.
* @param functionName Name of the caller function. It is recommended * @param functionName Name of the caller function. It is recommended
to use a macro, e.g. __func__ or __PRETTY_FUNCTION__. 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__. called. It is recommended to use a macro, e.g. __LINE__.
* @return asynStatus Returns input status. * @return asynStatus Returns input status.
*/ */
asynStatus paramLibAccessFailed(asynStatus status, const char *parameter, asynStatus paramLibAccessFailed(asynStatus status, const char *parameter,
int axisNo, const char *functionName, int axisNo, const char *functionName,
int lineNumber); int line);
/** /**
* @brief Error handling in case parsing a command response failed. * @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 axisNo_ Axis where the problem occurred
* @param functionName Name of the caller function. It is recommended * @param functionName Name of the caller function. It is recommended
to use a macro, e.g. __func__ or __PRETTY_FUNCTION__. 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__. called. It is recommended to use a macro, e.g. __LINE__.
* @return asynStatus Returns asynError. * @return asynStatus Returns asynError.
*/ */
asynStatus errMsgCouldNotParseResponse(const char *command, asynStatus errMsgCouldNotParseResponse(const char *command,
const char *response, int axisNo, const char *response, int axisNo,
const char *functionName, const char *functionName, int line);
int lineNumber);
/** /**
* @brief Convert an asynStatus into a descriptive string. * @brief Convert an asynStatus into a descriptive string.
@ -213,13 +214,22 @@ class epicsShareClass sinqController : public asynMotorController {
return asynSuccess; 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<msgPrintControlKey, size_t>&
*/
msgPrintControl &getMsgPrintControl();
friend class sinqAxis; friend class sinqAxis;
protected: protected:
asynUser *lowLevelPortUser_; asynUser *lowLevelPortUser_;
double movingPollPeriod_; double movingPollPeriod_;
double idlePollPeriod_; double idlePollPeriod_;
msgPrintControl msgPrintControl_;
// Internal variables used in the communication timeout frequency watchdog // Internal variables used in the communication timeout frequency watchdog
time_t comTimeoutWindow_; // Size of the time window time_t comTimeoutWindow_; // Size of the time window
@ -235,7 +245,6 @@ class epicsShareClass sinqController : public asynMotorController {
#define FIRST_SINQMOTOR_PARAM motorMessageText_ #define FIRST_SINQMOTOR_PARAM motorMessageText_
int motorMessageText_; int motorMessageText_;
int motorTargetPosition_;
int motorEnable_; int motorEnable_;
int motorEnableRBV_; int motorEnableRBV_;
int motorCanDisable_; int motorCanDisable_;