Compare commits

...

5 Commits
0.7.0 ... 0.8.0

9 changed files with 334 additions and 73 deletions

View File

@ -1,6 +1,6 @@
default: default:
image: docker.psi.ch:5000/sinqdev/sinqepics:latest image: docker.psi.ch:5000/sinqdev/sinqepics:latest
stages: stages:
- lint - lint
- build - build
@ -24,16 +24,6 @@ formatting:
tags: tags:
- sinq - sinq
# clangtidy:
# stage: lint
# script:
# - curl https://docker.psi.ch:5000/v2/_catalog
# # - dnf update -y
# # - dnf install -y clang-tools-extra
# # - clang-tidy sinqEPICSApp/src/*.cpp sinqEPICSApp/src/*.c sinqEPICSApp/src/*.h -checks=cppcoreguidelines-*,cert-*
# # tags:
# # - sinq
build_module: build_module:
stage: build stage: build
script: script:
@ -48,4 +38,4 @@ build_module:
expire_in: 1 week expire_in: 1 week
when: always when: always
tags: tags:
- sinq - sinq

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.
@ -197,6 +207,12 @@ All existing tags can be listed with `git tag` in the sinqMotor directory. Detai
### How to build it ### How to build it
The makefile in the top directory includes all necessary steps for compiling a shared library together with the header files into `/ioc/modules` (using the PSI EPICS build system). Therefore it is sufficient to clone this repository to a suitable location (`git clone https://git.psi.ch/sinq-epics-modules/sinqmotor/-/tree/main`). Afterwards, switch to the directory (`cd sinqmotor`) and run `make install`. This library is based on the PSI version of the EPICS motor record, which can be found here: `https://git.psi.ch/epics_driver_modules/motorBase`. We use a branch with a bugfix which is currently not merged into master due to resistance of the PSI userbase: `https://git.psi.ch/epics_driver_modules/motorBase/-/tree/pick_fix-lockup-VAL-HOMF-VAL`. This library can be build with the following steps, assuming GCC and make are available:
- `git clone https://git.psi.ch/epics_driver_modules/motorBase/-/tree/pick_fix-lockup-VAL-HOMF-VAL`
- `cd motorBase`
- `git tag 7.2.2`. The latest version on master is currently 7.2.1, hence we increment the bugfix version counter by one
- `make install`
To build sinqMotor itself, the makefile in the top directory includes all necessary steps for compiling a shared library together with the header files into `/ioc/modules` (using the PSI EPICS build system). Therefore it is sufficient to clone this repository to a suitable location (`git clone https://git.psi.ch/sinq-epics-modules/sinqmotor/-/tree/main`). Afterwards, switch to the directory (`cd sinqmotor`) and run `make install`.
To use the library when writing a concrete motor driver, include it in the makefile of your application / library the same way as other libraries such as e.g. `asynMotor` by adding `REQUIRED+=sinqMotor` to your Makefile. The version can be specified with `sinqMotor_VERSION=x.x.x.` To use the library when writing a concrete motor driver, include it in the makefile of your application / library the same way as other libraries such as e.g. `asynMotor` by adding `REQUIRED+=sinqMotor` to your Makefile. The version can be specified with `sinqMotor_VERSION=x.x.x.`

100
src/msgPrintControl.cpp Normal file
View File

@ -0,0 +1,100 @@
#include "msgPrintControl.h"
#include <unordered_map>
msgPrintControlKey::msgPrintControlKey(char *controller, int axisNo,
const char *functionName, int line) {
controller_ = controller;
axisNo_ = axisNo;
line_ = line;
functionName_ = functionName;
}
void msgPrintControlKey::format(char *buffer, size_t bufferSize) {
snprintf(buffer, bufferSize, "controller %s, axis %d, function %s, line %d",
controller_.c_str(), axisNo_, functionName_, line_);
}
// =============================================================================
msgPrintControl::msgPrintControl(size_t maxRepetitions) {
maxRepetitions_ = maxRepetitions;
}
bool msgPrintControl::shouldBePrinted(msgPrintControlKey &key, bool wantToPrint,
asynUser *pasynUser) {
// Reset the suffix
suffix_[0] = 0;
if (wantToPrint) {
/*
We want to print the message associated with key -> Check if the number
of allowed repetitions is exceeded. If true, inform the user that
further output is suppressed.
*/
if (map_.find(key) != map_.end()) {
size_t repetitions = map_[key];
if (repetitions < maxRepetitions_) {
// Number of allowed repetitions not exceeded -> Printing the
// message is ok.
map_[key] = repetitions + 1;
return true;
} else if (repetitions == maxRepetitions_) {
// Reached number of allowed repetitions -> Printing the message
// is ok, but further trys are rejected.
char formattedKey[100] = {0};
key.format(formattedKey, sizeof(formattedKey));
snprintf(suffix_, sizeof(suffix_),
" Further repetition of this error message (key "
"\"%s\") is suppressed.",
formattedKey);
map_[key] = repetitions + 1;
return true;
} else {
// Exceeded number of allowed repetitions -> Do not print the
// message
return false;
}
} else {
// Message is not yet in map -> create an entry so it is watched in
// the future.
map_[key] = 1;
return true;
}
} else {
/*
We do not want to print the message associated with key -> If the key is
part of the map, set the counter back to zero.
*/
if (map_.find(key) != map_.end()) {
if (map_[key] != 0) {
char formattedKey[100] = {0};
key.format(formattedKey, sizeof(formattedKey));
asynPrint(pasynUser, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nError "
"associated with key \"%s\" has been resolved.\n",
key.controller_.c_str(), key.axisNo_,
key.functionName_, key.line_, formattedKey);
map_[key] = 0;
}
}
return false;
}
}
bool msgPrintControl::shouldBePrinted(char *portName, int axisNo,
const char *functionName, int line,
bool wantToPrint, asynUser *pasynUser) {
msgPrintControlKey key =
msgPrintControlKey(portName, axisNo, functionName, __LINE__);
return shouldBePrinted(key, wantToPrint, pasynUser);
}
void msgPrintControl::resetCount(msgPrintControlKey &key) {
if (map_.find(key) != map_.end()) {
map_[key] = 0;
}
}
char *msgPrintControl::getSuffix() { return suffix_; }

133
src/msgPrintControl.h Normal file
View File

@ -0,0 +1,133 @@
#ifndef msgPrintControl_H
#define msgPrintControl_H
#include <asynDriver.h>
#include <string.h>
#include <string>
#include <unordered_map>
/**
* @brief Class to identify a message print location. See the docstring of
* `msgPrintControl` on how to use this key.
*
*/
class msgPrintControlKey {
public:
std::string controller_;
// -1 is a non-axis specific message
int axisNo_;
const char *functionName_;
int line_;
msgPrintControlKey(char *controller_, int axisNo, const char *fileName,
int line);
bool operator==(const msgPrintControlKey &other) const {
return axisNo_ == other.axisNo_ && line_ == other.line_ &&
strcmp(functionName_, other.functionName_) == 0 &&
controller_ == other.controller_;
}
void format(char *buffer, size_t bufferSize);
};
/**
* @brief Implementation of the hash functionality for msgPrintControlKey
*
*/
namespace std {
template <> struct hash<msgPrintControlKey> {
size_t operator()(const msgPrintControlKey &obj) const {
// Combine the hashes of the members (x and y)
size_t h1 = std::hash<std::string>{}(obj.controller_);
size_t h2 = hash<int>{}(obj.axisNo_);
size_t h3 = std::hash<const char *>{}(obj.functionName_);
size_t h4 = hash<int>{}(obj.line_);
// Combine the hashes (simple XOR and shifting technique)
return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3);
}
};
} // namespace std
/**
* @brief Class to control the number of repetitions of error messages
*
* This class is used to prevent excessive repetitions of identical error
* messages. For example, if the communication between a controller and an
* axis fails, a corresponding error message is created in each poll. This
* could "flood" the IOC shell with noise. To prevent this, this class keeps
* track of the number of subsequent error message repetition. Each message is
* uniquely identified by "msgPrintControlKey". The function shouldBePrinted
* can be used in order to see if a message should be printed or not:
*
* ```
* const char* controller = "MCU" // Name of the controller
* int axisNo = 0; // Number of the axis
* bool wantToPrint = evaluateConditions(...); *
* if (msgPrintControl.shouldBePrinted(controller, axisNo, __PRETTY_FUNCTION__,
* __LINE__, wantToPrint)) { asynPrint(...)
* }
*
* ```
*/
class msgPrintControl {
public:
msgPrintControl(size_t maxRepetitions);
/**
* @brief Checks if the error message associated with "key" has been printed
* more than "maxRepetitions_" times in a row. If yes, returns false,
* otherwise true. Counter is reset if wantToPrint is false.
*
* If the conditions for printing a message are met, "wantToPrint" must be
* set to true. The function then checks if "maxRepetitions_" has been
* exceeded. If yes, the function returns no, indicating that the message
* should not be printed. If no, the number of repetitions stored in the map
* is incremented and the function returns true, indicating that the message
* should be printed.
*
* If the conditions for printing a message are not met, "wantToPrint" must
* be set to false. This resets the map entry.
*
* @param key Key associated with the message, used to
* identify individual messages
* @param wantToPrint If the message associated with key should be
* printed, this value should be true, otherwise false.
* @return bool If true, the message should be printed, if
* false, it should not.
*/
bool shouldBePrinted(msgPrintControlKey &key, bool wantToPrint,
asynUser *pasynUser);
/**
* @brief Like `shouldBePrinted(msgPrintControlKey key, bool wantToPrint)`,
* but constructs the key from the first four arguments.
*
* @param controller_
* @param axisNo
* @param fileName
* @param line
* @param wantToPrint
* @return true
* @return false
*/
bool shouldBePrinted(char *controller, int axisNo, const char *functionName,
int line, bool wantToPrint, asynUser *pasynUser);
void resetCount(msgPrintControlKey &key);
/**
* @brief Maximum number of times a message is printed before it is
* suppressed.
*
*/
size_t maxRepetitions_;
char *getSuffix();
private:
std::unordered_map<msgPrintControlKey, size_t> map_;
char suffix_[200] = {0};
};
#endif

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
@ -274,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;
@ -281,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 "
@ -292,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);
} }
@ -302,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;
} }
@ -323,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_,
@ -352,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.");
} }
@ -391,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