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
# 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

View File

@ -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.

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.
}
# 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.

View File

@ -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;
}

View File

@ -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_;

View File

@ -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<sinqAxis *>(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<sinqAxis *>(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,

View File

@ -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 <deque>
#include <initHooks.h>
#include <unordered_map>
#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<msgPrintControlKey, size_t>&
*/
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_;