Compare commits

...

4 Commits

Author SHA1 Message Date
87980e403c Fixed bug regarding the poll period member variables 2025-05-23 11:17:34 +02:00
b95e782ea8 Added accessor for maxSubsequentTimeoutsExceeded flag 2025-05-23 10:10:54 +02:00
cd7cc75eb7 Added destructors for the classes in order to use the PIMPL idiom with
uniqe_ptr
2025-05-23 09:38:41 +02:00
83aa437b6b Applied PIMPL by moving members to internal struct sinqAxisImpl
This change makes sinqMotor ready for 1.x releases where ABI stability
and backwards compatibility is guaranteed.
2025-05-22 13:56:44 +02:00
6 changed files with 329 additions and 213 deletions

View File

@ -20,6 +20,8 @@ void msgPrintControlKey::format(char *buffer, size_t bufferSize) {
// =============================================================================
msgPrintControl::~msgPrintControl() = default;
bool msgPrintControl::shouldBePrinted(msgPrintControlKey &key, bool wantToPrint,
asynUser *pasynUser) {

View File

@ -84,6 +84,12 @@ template <> struct hash<msgPrintControlKey> {
*/
class msgPrintControl {
public:
/**
* @brief Destroy the msgPrintControl object
*
*/
~msgPrintControl();
/**
* @brief Checks if the error message associated with "key" has been printed
* more than `this->maxRepetitions_` times in a row. If yes, returns false,

View File

@ -3,11 +3,31 @@
#include "sinqAxis.h"
#include "epicsExport.h"
#include "iocsh.h"
#include "msgPrintControl.h"
#include "sinqController.h"
#include <epicsTime.h>
#include <errlog.h>
#include <math.h>
#include <unistd.h>
struct sinqAxisImpl {
// Internal variables used in the movement timeout watchdog
time_t expectedArrivalTime;
time_t offsetMovTimeout;
double scaleMovTimeout;
bool watchdogMovActive;
// Store the motor target position for the movement time calculation
double targetPosition;
bool wasMoving;
/*
Store the time since the last poll
*/
epicsTimeStamp lastPollTime;
};
// Generic fallback - if the compiler tries to compile this function, it fails.
template <typename T>
asynStatus setAxisParam(sinqAxis *axis, const char *indexName,
@ -184,13 +204,17 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
: asynMotorAxis((asynMotorController *)pC, axisNo), pC_(pC) {
asynStatus status = asynSuccess;
watchdogMovActive_ = false;
scaleMovTimeout_ = 2.0;
offsetMovTimeout_ = 30;
targetPosition_ = 0.0;
wasMoving_ = false;
epicsTimeStamp lastPollTime;
epicsTimeGetCurrent(&lastPollTime);
epicsTimeGetCurrent(&lastPollTime_);
pSinqA_ = std::make_unique<sinqAxisImpl>(
(sinqAxisImpl){.expectedArrivalTime = 0,
.offsetMovTimeout = 30,
.scaleMovTimeout = 2.0,
.watchdogMovActive = false,
.targetPosition = 0.0,
.wasMoving = false,
.lastPollTime = lastPollTime});
// This check is also done in asynMotorAxis, but there the IOC continues
// running even though the configuration is incorrect. When failing this
@ -313,6 +337,8 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
}
}
sinqAxis::~sinqAxis() = default;
asynStatus sinqAxis::poll(bool *moving) {
// Local variable declaration
@ -346,10 +372,10 @@ asynStatus sinqAxis::poll(bool *moving) {
*/
if (adaptivePolling != 0 && pC_->outstandingForcedFastPolls() == 0) {
// Motor wasn't moving during the last poll
if (!wasMoving_) {
if (!pSinqA_->wasMoving) {
// Add the idle poll period
epicsTimeStamp earliestTimeNextPoll = lastPollTime_;
epicsTimeStamp earliestTimeNextPoll = pSinqA_->lastPollTime;
epicsTimeAddSeconds(&earliestTimeNextPoll, pC_->idlePollPeriod());
if (epicsTimeLessThanEqual(&earliestTimeNextPoll, &ts) == 0) {
@ -360,7 +386,7 @@ asynStatus sinqAxis::poll(bool *moving) {
}
// Update the start time of the last poll
lastPollTime_ = ts;
pSinqA_->lastPollTime = ts;
/*
If the "motorMessageText" record currently contains an error message, it
@ -411,7 +437,7 @@ asynStatus sinqAxis::poll(bool *moving) {
assumed that the homing procedure is done.
*/
getAxisParamChecked(this, motorStatusHome, &homing);
if (homing == 1 && !(*moving) && wasMoving_) {
if (homing == 1 && !(*moving) && pSinqA_->wasMoving) {
setAxisParamChecked(this, motorStatusHome, false);
setAxisParamChecked(this, motorStatusHomed, true);
setAxisParamChecked(this, motorStatusAtHome, true);
@ -419,7 +445,7 @@ asynStatus sinqAxis::poll(bool *moving) {
// Update the wasMoving status
if (pC_->outstandingForcedFastPolls() == 0) {
wasMoving_ = *moving;
pSinqA_->wasMoving = *moving;
}
// Check and update the watchdog
@ -467,7 +493,7 @@ asynStatus sinqAxis::move(double position, int relative, double minVelocity,
// Store the target position internally
getAxisParamChecked(this, motorRecResolution, &motorRecRes);
targetPosition_ = position * motorRecRes;
pSinqA_->targetPosition = position * motorRecRes;
status = doMove(position, relative, minVelocity, maxVelocity, acceleration);
if (status != asynSuccess) {
@ -485,7 +511,7 @@ asynStatus sinqAxis::move(double position, int relative, double minVelocity,
setAxisParamChecked(this, motorStatusAtHome, false);
// Needed for adaptive polling
wasMoving_ = true;
pSinqA_->wasMoving = true;
return pC_->callParamCallbacks();
}
@ -507,7 +533,7 @@ asynStatus sinqAxis::home(double minVelocity, double maxVelocity,
setAxisParamChecked(this, motorStatusHome, true);
setAxisParamChecked(this, motorStatusHomed, false);
setAxisParamChecked(this, motorStatusAtHome, false);
wasMoving_ = true;
pSinqA_->wasMoving = true;
status = assertConnected();
if (status != asynSuccess) {
@ -681,7 +707,7 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
time_t timeAccel = 0;
// Activate the watchdog
watchdogMovActive_ = true;
pSinqA_->watchdogMovActive = true;
/*
NOTE: This function must not be called in the constructor (e.g. in order
@ -717,8 +743,9 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
motorVelocity = motorVelocityRec * motorRecRes;
if (pl_status == asynSuccess) {
timeContSpeed = std::ceil(
std::fabs(targetPosition_ - motorPos) / motorVelocity);
timeContSpeed =
std::ceil(std::fabs(pSinqA_->targetPosition - motorPos) /
motorVelocity);
}
}
@ -734,11 +761,11 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
}
// Calculate the expected arrival time
expectedArrivalTime_ =
time(NULL) + offsetMovTimeout_ +
scaleMovTimeout_ * (timeContSpeed + 2 * timeAccel);
pSinqA_->expectedArrivalTime =
time(NULL) + pSinqA_->offsetMovTimeout +
pSinqA_->scaleMovTimeout * (timeContSpeed + 2 * timeAccel);
} else {
watchdogMovActive_ = false;
pSinqA_->watchdogMovActive = false;
}
return asynSuccess;
}
@ -749,8 +776,8 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
getAxisParamChecked(this, motorEnableMovWatchdog, &enableMovWatchdog);
// Not moving or watchdog not active / enabled
if (enableMovWatchdog == 0 || !moving || !watchdogMovActive_) {
watchdogMovActive_ = false;
if (enableMovWatchdog == 0 || !moving || !pSinqA_->watchdogMovActive) {
pSinqA_->watchdogMovActive = false;
return asynSuccess;
}
@ -760,7 +787,7 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
__PRETTY_FUNCTION__, __LINE__);
// Check if the expected time of arrival has been exceeded.
if (expectedArrivalTime_ < time(NULL)) {
if (pSinqA_->expectedArrivalTime < time(NULL)) {
// Check the watchdog
if (pC_->getMsgPrintControl().shouldBePrinted(key, true,
pC_->pasynUser())) {
@ -768,7 +795,7 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
"Controller \"%s\", axis %d => %s, line %d:\nExceeded "
"expected arrival time %ld (current time is %ld).\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
expectedArrivalTime_, time(NULL));
pSinqA_->expectedArrivalTime, time(NULL));
}
setAxisParamChecked(
@ -783,6 +810,16 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
return asynSuccess;
}
asynStatus sinqAxis::setOffsetMovTimeout(time_t offsetMovTimeout) {
pSinqA_->offsetMovTimeout = offsetMovTimeout;
return asynSuccess;
}
asynStatus sinqAxis::setScaleMovTimeout(time_t scaleMovTimeout) {
pSinqA_->scaleMovTimeout = scaleMovTimeout;
return asynSuccess;
}
// =============================================================================
// IOC shell functions
extern "C" {

View File

@ -9,9 +9,11 @@ Stefan Mathis, November 2024
#ifndef sinqAxis_H
#define sinqAxis_H
#include "asynMotorAxis.h"
#include <epicsTime.h>
#include <memory>
#include <type_traits>
struct sinqAxisImpl;
class epicsShareClass sinqAxis : public asynMotorAxis {
public:
/**
@ -22,6 +24,13 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
*/
sinqAxis(class sinqController *pC_, int axisNo);
/**
* @brief Destroy the sinqAxis object
*
* This destructor is necessary in order to use the PIMPL idiom.
*/
~sinqAxis();
/**
* @brief Perform some standardized operations before and after the concrete
`doPoll` implementation.
@ -297,10 +306,7 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
* @param offsetMovTimeout Offset (in seconds)
* @return asynStatus
*/
virtual asynStatus setOffsetMovTimeout(time_t offsetMovTimeout) {
offsetMovTimeout_ = offsetMovTimeout;
return asynSuccess;
}
virtual asynStatus setOffsetMovTimeout(time_t offsetMovTimeout);
/**
* @brief Set the scaleMovTimeout. Also available in the IOC shell
@ -311,10 +317,7 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
* @param scaleMovTimeout Scaling factor (in seconds)
* @return asynStatus
*/
virtual asynStatus setScaleMovTimeout(time_t scaleMovTimeout) {
scaleMovTimeout_ = scaleMovTimeout;
return asynSuccess;
}
virtual asynStatus setScaleMovTimeout(time_t scaleMovTimeout);
/**
* @brief Return the axis number of this axis
@ -366,24 +369,8 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
*/
sinqController *pController() { return pC_; };
protected:
// Internal variables used in the movement timeout watchdog
time_t expectedArrivalTime_;
time_t offsetMovTimeout_;
double scaleMovTimeout_;
bool watchdogMovActive_;
// Store the motor target position for the movement time calculation
double targetPosition_;
bool wasMoving_;
/*
Store the time since the last poll
*/
epicsTimeStamp lastPollTime_;
private:
std::unique_ptr<sinqAxisImpl> pSinqA_;
sinqController *pC_;
};

View File

@ -5,8 +5,12 @@
#include "asynOctetSyncIO.h"
#include "epicsExport.h"
#include "iocsh.h"
#include "msgPrintControl.h"
#include "sinqAxis.h"
#include <deque>
#include <errlog.h>
#include <initHooks.h>
#include <unordered_map>
#include <vector>
/*
@ -34,6 +38,65 @@ void sinqController::epicsInithookFunction(initHookState iState) {
}
}
struct sinqControllerImpl {
// Number of fast polls which still need to be performed before adaptive
// polling is active again.
int outstandingForcedFastPolls;
// Number of polls forced by wakeupPoller which are still
// Pointer to the port user which is specified by the char array
// `ipPortConfigName` in the constructor
asynUser *pasynOctetSyncIOipPort;
// Message print control
msgPrintControl msgPrintC;
// Internal variables used in the communication timeout frequency watchdog
time_t comTimeoutWindow; // Size of the time window
size_t maxNumberTimeouts; // Maximum acceptable number of events within the
// time window
// Deque holding the timestamps of the individual events
std::deque<time_t> timeoutEvents;
// Communicate a timeout to the user after it has happened this many times
// in a row
int maxSubsequentTimeouts;
bool maxSubsequentTimeoutsExceeded;
/*
These integers are indices to paramLib entries and are populated when the
parameters are created. See the documentation in db/sinqMotor.db.
*/
int motorMessageText;
int motorReset;
int motorEnable;
int motorEnableRBV;
int motorCanDisable;
int motorEnableMovWatchdog;
int motorCanSetSpeed;
int motorLimitsOffset;
int motorForceStop;
int motorConnected;
/*
These parameters are here to write values from the hardware to the EPICS
motor record. Using motorHighLimit_ / motorLowLimit_ does not work:
https://epics.anl.gov/tech-talk/2023/msg00576.php. Therefore, some
additional records are introduced which read from these parameters and write
into the motor record.
*/
int motorVeloFromDriver;
int motorVbasFromDriver;
int motorVmaxFromDriver;
int motorAcclFromDriver;
int motorHighLimitFromDriver;
int motorLowLimitFromDriver;
int adaptivePolling;
int encoderType;
};
#define NUM_SINQMOTOR_DRIVER_PARAMS 18
sinqController::sinqController(const char *portName,
const char *ipPortConfigName, int numAxes,
double movingPollPeriod, double idlePollPeriod,
@ -48,29 +111,22 @@ 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
msgPrintControl_() {
1, // autoconnect
0, 0) // Default priority and stack size
{
asynStatus status = asynSuccess;
// Handle to the asynUser of the IP port asyn driver
pasynOctetSyncIOipPort_ = nullptr;
// Initial values for the average timeout mechanism, can be overwritten
// later by a FFI function
comTimeoutWindow_ = 3600; // seconds
// Number of timeouts which may occur before an error is forwarded to the
// user
maxNumberTimeouts_ = 60;
// Queue holding the timeout event timestamps
timeoutEvents_ = {};
// Inform the user after 10 timeouts in a row (default value)
maxSubsequentTimeouts_ = 10;
maxSubsequentTimeoutsExceeded_ = false;
// The paramLib indices are populated with the calls to createParam
pSinqC_ = std::make_unique<sinqControllerImpl>(
(sinqControllerImpl){.outstandingForcedFastPolls = 0,
.pasynOctetSyncIOipPort = nullptr,
.msgPrintC = msgPrintControl(),
.comTimeoutWindow = 3600,
.maxNumberTimeouts = 60,
.timeoutEvents = {},
.maxSubsequentTimeouts = 10,
.maxSubsequentTimeoutsExceeded = false});
// Store the poll period information. The poller itself will be started
// later (after the IOC is running in epicsInithookFunction)
@ -83,9 +139,9 @@ sinqController::sinqController(const char *portName,
We try to connect to the port via the port name provided by the constructor.
If this fails, the function is terminated via exit.
*/
pasynOctetSyncIO->connect(ipPortConfigName, 0, &pasynOctetSyncIOipPort_,
NULL);
if (status != asynSuccess || pasynOctetSyncIOipPort_ == nullptr) {
pasynOctetSyncIO->connect(ipPortConfigName, 0,
&pSinqC_->pasynOctetSyncIOipPort, NULL);
if (status != asynSuccess || pSinqC_->pasynOctetSyncIOipPort == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot "
"connect to MCU controller).\n"
"Terminating IOC",
@ -98,8 +154,8 @@ sinqController::sinqController(const char *portName,
// MOTOR_MESSAGE_TEXT 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, &motorMessageText_);
status = createParam("MOTOR_MESSAGE_TEXT", asynParamOctet,
&pSinqC_->motorMessageText);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -109,7 +165,7 @@ sinqController::sinqController(const char *portName,
exit(-1);
}
status = createParam("MOTOR_ENABLE", asynParamInt32, &motorEnable_);
status = createParam("MOTOR_ENABLE", asynParamInt32, &pSinqC_->motorEnable);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -119,7 +175,7 @@ sinqController::sinqController(const char *portName,
exit(-1);
}
status = createParam("MOTOR_RESET", asynParamInt32, &motorReset_);
status = createParam("MOTOR_RESET", asynParamInt32, &pSinqC_->motorReset);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -129,7 +185,8 @@ sinqController::sinqController(const char *portName,
exit(-1);
}
status = createParam("MOTOR_ENABLE_RBV", asynParamInt32, &motorEnableRBV_);
status = createParam("MOTOR_ENABLE_RBV", asynParamInt32,
&pSinqC_->motorEnableRBV);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -139,8 +196,8 @@ sinqController::sinqController(const char *portName,
exit(-1);
}
status =
createParam("MOTOR_CAN_DISABLE", asynParamInt32, &motorCanDisable_);
status = createParam("MOTOR_CAN_DISABLE", asynParamInt32,
&pSinqC_->motorCanDisable);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -150,8 +207,8 @@ sinqController::sinqController(const char *portName,
exit(-1);
}
status =
createParam("MOTOR_CAN_SET_SPEED", asynParamInt32, &motorCanSetSpeed_);
status = createParam("MOTOR_CAN_SET_SPEED", asynParamInt32,
&pSinqC_->motorCanSetSpeed);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -162,7 +219,7 @@ sinqController::sinqController(const char *portName,
}
status = createParam("MOTOR_LIMITS_OFFSET", asynParamFloat64,
&motorLimitsOffset_);
&pSinqC_->motorLimitsOffset);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -172,7 +229,8 @@ sinqController::sinqController(const char *portName,
exit(-1);
}
status = createParam("MOTOR_CONNECTED", asynParamInt32, &motorConnected_);
status = createParam("MOTOR_CONNECTED", asynParamInt32,
&pSinqC_->motorConnected);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -188,7 +246,7 @@ sinqController::sinqController(const char *portName,
the declaration of motorHighLimitFromDriver_.
*/
status = createParam("MOTOR_HIGH_LIMIT_FROM_DRIVER", asynParamFloat64,
&motorHighLimitFromDriver_);
&pSinqC_->motorHighLimitFromDriver);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -199,7 +257,7 @@ sinqController::sinqController(const char *portName,
}
status = createParam("MOTOR_LOW_LIMIT_FROM_DRIVER", asynParamFloat64,
&motorLowLimitFromDriver_);
&pSinqC_->motorLowLimitFromDriver);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -210,7 +268,7 @@ sinqController::sinqController(const char *portName,
}
status = createParam("MOTOR_ENABLE_MOV_WATCHDOG", asynParamInt32,
&motorEnableMovWatchdog_);
&pSinqC_->motorEnableMovWatchdog);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -221,7 +279,7 @@ sinqController::sinqController(const char *portName,
}
status = createParam("MOTOR_VELO_FROM_DRIVER", asynParamFloat64,
&motorVeloFromDriver_);
&pSinqC_->motorVeloFromDriver);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -232,7 +290,7 @@ sinqController::sinqController(const char *portName,
}
status = createParam("MOTOR_VBAS_FROM_DRIVER", asynParamFloat64,
&motorVbasFromDriver_);
&pSinqC_->motorVbasFromDriver);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -243,7 +301,7 @@ sinqController::sinqController(const char *portName,
}
status = createParam("MOTOR_VMAX_FROM_DRIVER", asynParamFloat64,
&motorVmaxFromDriver_);
&pSinqC_->motorVmaxFromDriver);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -254,7 +312,7 @@ sinqController::sinqController(const char *portName,
}
status = createParam("MOTOR_ACCL_FROM_DRIVER", asynParamFloat64,
&motorAcclFromDriver_);
&pSinqC_->motorAcclFromDriver);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -264,7 +322,8 @@ sinqController::sinqController(const char *portName,
exit(-1);
}
status = createParam("ADAPTIVE_POLLING", asynParamInt32, &adaptivePolling_);
status = createParam("ADAPTIVE_POLLING", asynParamInt32,
&pSinqC_->adaptivePolling);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -274,7 +333,7 @@ sinqController::sinqController(const char *portName,
exit(-1);
}
status = createParam("ENCODER_TYPE", asynParamOctet, &encoderType_);
status = createParam("ENCODER_TYPE", asynParamOctet, &pSinqC_->encoderType);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -284,7 +343,8 @@ sinqController::sinqController(const char *portName,
exit(-1);
}
status = createParam("MOTOR_FORCE_STOP", asynParamInt32, &motorForceStop_);
status = createParam("MOTOR_FORCE_STOP", asynParamInt32,
&pSinqC_->motorForceStop);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
@ -351,11 +411,11 @@ asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
}
// Handle custom PVs
if (function == motorEnable_) {
if (function == motorEnable()) {
return axis->enable(value != 0);
} else if (function == motorReset_) {
} else if (function == motorReset()) {
return axis->reset();
} else if (function == motorForceStop_) {
} else if (function == motorForceStop()) {
return axis->stop(0.0);
} else {
return asynMotorController::writeInt32(pasynUser, value);
@ -373,10 +433,10 @@ asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
return asynError;
}
if (pasynUser->reason == motorEnableRBV_) {
if (pasynUser->reason == motorEnableRBV()) {
getAxisParamChecked(axis, motorEnableRBV, value);
return asynSuccess;
} else if (pasynUser->reason == motorCanDisable_) {
} else if (pasynUser->reason == motorCanDisable()) {
getAxisParamChecked(axis, motorCanDisable, value);
return asynSuccess;
} else {
@ -389,7 +449,7 @@ asynStatus sinqController::couldNotParseResponse(const char *command,
int axisNo,
const char *functionName,
int line) {
asynPrint(pasynOctetSyncIOipPort_, ASYN_TRACE_ERROR,
asynPrint(pasynOctetSyncIOipPort(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nCould not interpret "
"response \"%s\" for command \"%s\".\n",
portName, axisNo, functionName, line, response, command);
@ -418,7 +478,7 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status,
int line) {
if (status != asynSuccess) {
asynPrint(pasynOctetSyncIOipPort_, ASYN_TRACE_ERROR,
asynPrint(pasynOctetSyncIOipPort(), 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, line, parameter,
@ -440,40 +500,42 @@ asynStatus sinqController::checkComTimeoutWatchdog(int axisNo,
asynStatus paramLibStatus = asynSuccess;
// Add a new timeout event to the queue
timeoutEvents_.push_back(time(NULL));
pSinqC_->timeoutEvents.push_back(time(NULL));
// Remove every event which is older than the time window from the deque
while (1) {
if (timeoutEvents_.empty()) {
if (pSinqC_->timeoutEvents.empty()) {
break;
}
if (timeoutEvents_[0] + comTimeoutWindow_ <= time(NULL)) {
timeoutEvents_.pop_front();
if (pSinqC_->timeoutEvents[0] + pSinqC_->comTimeoutWindow <=
time(NULL)) {
pSinqC_->timeoutEvents.pop_front();
} else {
break;
}
}
// Check if the maximum allowed number of events has been exceeded
bool wantToPrint = timeoutEvents_.size() > maxNumberTimeouts_;
bool wantToPrint =
pSinqC_->timeoutEvents.size() > pSinqC_->maxNumberTimeouts;
if (msgPrintControl_.shouldBePrinted(portName, axisNo, __PRETTY_FUNCTION__,
__LINE__, wantToPrint,
pasynUserSelf)) {
if (pSinqC_->msgPrintC.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());
pSinqC_->maxNumberTimeouts, pSinqC_->comTimeoutWindow,
pSinqC_->msgPrintC.getSuffix());
}
if (wantToPrint) {
snprintf(motorMessage, motorMessageSize,
"More than %ld communication timeouts in %ld seconds. Please "
"call the support.",
maxNumberTimeouts_, comTimeoutWindow_);
pSinqC_->maxNumberTimeouts, pSinqC_->comTimeoutWindow);
paramLibStatus = setIntegerParam(motorStatusCommsError_, 1);
if (paramLibStatus != asynSuccess) {
@ -505,8 +567,8 @@ asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo, int axisNo,
size_t motorMessageSize) {
asynStatus paramLibStatus = asynSuccess;
if (timeoutNo >= maxSubsequentTimeouts_) {
if (!maxSubsequentTimeoutsExceeded_) {
if (timeoutNo >= pSinqC_->maxSubsequentTimeouts) {
if (!pSinqC_->maxSubsequentTimeoutsExceeded) {
snprintf(motorMessage, motorMessageSize,
"Communication timeout between IOC and motor controller. "
"Trying to reconnect ...");
@ -516,7 +578,7 @@ asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo, int axisNo,
"subsequent communication timeouts. Check whether the "
"controller is still running and connected to the network.\n",
this->portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
maxSubsequentTimeouts_);
pSinqC_->maxSubsequentTimeouts);
paramLibStatus = setIntegerParam(motorStatusCommsError_, 1);
if (paramLibStatus != asynSuccess) {
@ -524,12 +586,12 @@ asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo, int axisNo,
"motorStatusCommsError_", axisNo,
__PRETTY_FUNCTION__, __LINE__);
}
maxSubsequentTimeoutsExceeded_ = true;
pSinqC_->maxSubsequentTimeoutsExceeded = true;
}
return asynError;
} else {
maxSubsequentTimeoutsExceeded_ = false;
pSinqC_->maxSubsequentTimeoutsExceeded = false;
motorMessage[0] = '\0';
return asynSuccess;
}
@ -551,8 +613,8 @@ asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo,
asynStatus sinqController::poll() {
// Decrement the number of outstanding forced fast polls, if they are not
// zero
if (outstandingForcedFastPolls_ > 0) {
outstandingForcedFastPolls_--;
if (pSinqC_->outstandingForcedFastPolls > 0) {
pSinqC_->outstandingForcedFastPolls--;
}
return asynMotorController::poll();
}
@ -560,10 +622,86 @@ asynStatus sinqController::poll() {
asynStatus sinqController::wakeupPoller() {
// + 1 since outstandingForcedFastPolls_ is reduced once at the start of
// a poll cycle
outstandingForcedFastPolls_ = forcedFastPolls_ + 1;
pSinqC_->outstandingForcedFastPolls = forcedFastPolls_ + 1;
return asynMotorController::wakeupPoller();
}
asynStatus sinqController::setMaxSubsequentTimeouts(int maxSubsequentTimeouts) {
pSinqC_->maxSubsequentTimeouts = maxSubsequentTimeouts;
return asynSuccess;
}
bool sinqController::maxSubsequentTimeoutsExceeded() {
return pSinqC_->maxSubsequentTimeoutsExceeded;
}
/**
* @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.
*/
msgPrintControl &sinqController::getMsgPrintControl() {
return pSinqC_->msgPrintC;
}
/**
* @brief Read the number of outstanding forced fast polls currently
* specified
*
*/
int sinqController::outstandingForcedFastPolls() {
return pSinqC_->outstandingForcedFastPolls;
}
/**
* @brief Return a pointer to the low-level octet (string) IP Port
*
* @return asynUser*
*/
asynUser *sinqController::pasynOctetSyncIOipPort() {
return pSinqC_->pasynOctetSyncIOipPort;
}
asynStatus sinqController::setThresholdComTimeout(time_t comTimeoutWindow,
size_t maxNumberTimeouts) {
pSinqC_->comTimeoutWindow = comTimeoutWindow;
pSinqC_->maxNumberTimeouts = maxNumberTimeouts;
return asynSuccess;
}
int sinqController::motorMessageText() { return pSinqC_->motorMessageText; }
int sinqController::motorReset() { return pSinqC_->motorReset; }
int sinqController::motorEnable() { return pSinqC_->motorEnable; }
int sinqController::motorEnableRBV() { return pSinqC_->motorEnableRBV; }
int sinqController::motorCanDisable() { return pSinqC_->motorCanDisable; }
int sinqController::motorEnableMovWatchdog() {
return pSinqC_->motorEnableMovWatchdog;
}
int sinqController::motorCanSetSpeed() { return pSinqC_->motorCanSetSpeed; }
int sinqController::motorLimitsOffset() { return pSinqC_->motorLimitsOffset; }
int sinqController::motorForceStop() { return pSinqC_->motorForceStop; }
int sinqController::motorConnected() { return pSinqC_->motorConnected; }
int sinqController::motorVeloFromDriver() {
return pSinqC_->motorVeloFromDriver;
}
int sinqController::motorVbasFromDriver() {
return pSinqC_->motorVbasFromDriver;
}
int sinqController::motorVmaxFromDriver() {
return pSinqC_->motorVmaxFromDriver;
}
int sinqController::motorAcclFromDriver() {
return pSinqC_->motorAcclFromDriver;
}
int sinqController::motorHighLimitFromDriver() {
return pSinqC_->motorHighLimitFromDriver;
}
int sinqController::motorLowLimitFromDriver() {
return pSinqC_->motorLowLimitFromDriver;
}
int sinqController::adaptivePolling() { return pSinqC_->adaptivePolling; }
int sinqController::encoderType() { return pSinqC_->encoderType; }
// Static pointers (valid for the entire lifetime of the IOC). The number behind
// the strings gives the integer number of each variant (see also method
// stringifyAsynStatus)

View File

@ -11,9 +11,8 @@ Stefan Mathis, November 2024
#define sinqController_H
#include "asynMotorController.h"
#include "msgPrintControl.h"
#include <deque>
#include <initHooks.h>
#include <unordered_map>
#include <memory>
#define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER"
#define motorMessageTextString "MOTOR_MESSAGE_TEXT"
@ -21,6 +20,8 @@ Stefan Mathis, November 2024
#define AbsoluteEncoder "absolute"
#define NoEncoder "none"
struct sinqControllerImpl;
class epicsShareClass sinqController : public asynMotorController {
public:
/**
@ -175,11 +176,7 @@ class epicsShareClass sinqController : public asynMotorController {
* @return asynStatus
*/
virtual asynStatus setThresholdComTimeout(time_t comTimeoutWindow,
size_t maxNumberTimeouts) {
comTimeoutWindow_ = comTimeoutWindow;
maxNumberTimeouts_ = maxNumberTimeouts;
return asynSuccess;
}
size_t maxNumberTimeouts);
/**
* @brief Inform the user, if the number of timeouts exceeds the threshold
@ -211,17 +208,23 @@ class epicsShareClass sinqController : public asynMotorController {
* @param maxSubsequentTimeouts
* @return asynStatus
*/
asynStatus setMaxSubsequentTimeouts(int maxSubsequentTimeouts) {
maxSubsequentTimeouts_ = maxSubsequentTimeouts;
return asynSuccess;
}
asynStatus setMaxSubsequentTimeouts(int maxSubsequentTimeouts);
/**
* @brief If true, the maximum number of subsequent communication timeouts
* set in `setMaxSubsequentTimeouts` has been exceeded
*
* @return true
* @return false
*/
bool maxSubsequentTimeoutsExceeded();
/**
* @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.
*/
msgPrintControl &getMsgPrintControl() { return msgPrintControl_; }
msgPrintControl &getMsgPrintControl();
/**
* @brief Get the axis object
@ -293,25 +296,26 @@ class epicsShareClass sinqController : public asynMotorController {
int motorRecDirection() { return motorRecDirection_; }
int motorRecOffset() { return motorRecOffset_; }
// Accessors for additional PVs defined in sinqController
int motorMessageText() { return motorMessageText_; }
int motorReset() { return motorReset_; }
int motorEnable() { return motorEnable_; }
int motorEnableRBV() { return motorEnableRBV_; }
int motorCanDisable() { return motorCanDisable_; }
int motorEnableMovWatchdog() { return motorEnableMovWatchdog_; }
int motorCanSetSpeed() { return motorCanSetSpeed_; }
int motorLimitsOffset() { return motorLimitsOffset_; }
int motorForceStop() { return motorForceStop_; }
int motorConnected() { return motorConnected_; }
int motorVeloFromDriver() { return motorVeloFromDriver_; }
int motorVbasFromDriver() { return motorVbasFromDriver_; }
int motorVmaxFromDriver() { return motorVmaxFromDriver_; }
int motorAcclFromDriver() { return motorAcclFromDriver_; }
int motorHighLimitFromDriver() { return motorHighLimitFromDriver_; }
int motorLowLimitFromDriver() { return motorLowLimitFromDriver_; }
int adaptivePolling() { return adaptivePolling_; }
int encoderType() { return encoderType_; }
// Accessors for additional PVs defined in sinqController (which are hidden
// in pSinqC_)
int motorMessageText();
int motorReset();
int motorEnable();
int motorEnableRBV();
int motorCanDisable();
int motorEnableMovWatchdog();
int motorCanSetSpeed();
int motorLimitsOffset();
int motorForceStop();
int motorConnected();
int motorVeloFromDriver();
int motorVbasFromDriver();
int motorVmaxFromDriver();
int motorAcclFromDriver();
int motorHighLimitFromDriver();
int motorLowLimitFromDriver();
int adaptivePolling();
int encoderType();
// Additional members
int numAxes() { return numAxes_; }
@ -330,7 +334,7 @@ class epicsShareClass sinqController : public asynMotorController {
*
* @return asynUser*
*/
asynUser *pasynOctetSyncIOipPort() { return pasynOctetSyncIOipPort_; }
asynUser *pasynOctetSyncIOipPort();
/**
* @brief Overloaded version of `asynController::poll` which decreases
@ -374,7 +378,7 @@ class epicsShareClass sinqController : public asynMotorController {
* specified
*
*/
int outstandingForcedFastPolls() { return outstandingForcedFastPolls_; }
int outstandingForcedFastPolls();
// Maximum error message buffer size. This is an empirical value which must
// be large enough to avoid overflows for all commands to the device /
@ -383,67 +387,9 @@ class epicsShareClass sinqController : public asynMotorController {
// =========================================================================
protected:
// Number of fast polls which still need to be performed before adaptive
// polling is active again.
int outstandingForcedFastPolls_;
// Number of polls forced by wakeupPoller which are still
// Pointer to the port user which is specified by the char array
// `ipPortConfigName` in the constructor
asynUser *pasynOctetSyncIOipPort_;
double movingPollPeriod_;
double idlePollPeriod_;
msgPrintControl msgPrintControl_;
// Internal variables used in the communication timeout frequency watchdog
time_t comTimeoutWindow_; // Size of the time window
size_t maxNumberTimeouts_; // Maximum acceptable number of events within the
// time window
std::deque<time_t>
timeoutEvents_; // Deque holding the timestamps of the individual events
// Communicate a timeout to the user after it has happened this many times
// in a row
int maxSubsequentTimeouts_;
bool maxSubsequentTimeoutsExceeded_;
/*
See the documentation in db/sinqMotor.db for the following integers
*/
#define FIRST_SINQMOTOR_PARAM motorMessageText_
int motorMessageText_;
int motorReset_;
int motorEnable_;
int motorEnableRBV_;
int motorCanDisable_;
int motorEnableMovWatchdog_;
int motorCanSetSpeed_;
int motorLimitsOffset_;
int motorForceStop_;
int motorConnected_;
/*
These parameters are here to write values from the hardware to the EPICS
motor record. Using motorHighLimit_ / motorLowLimit_ does not work:
https://epics.anl.gov/tech-talk/2023/msg00576.php. Therefore, some
additional records are introduced which read from these parameters and write
into the motor record.
*/
int motorVeloFromDriver_;
int motorVbasFromDriver_;
int motorVmaxFromDriver_;
int motorAcclFromDriver_;
int motorHighLimitFromDriver_;
int motorLowLimitFromDriver_;
int adaptivePolling_;
int encoderType_;
#define LAST_SINQMOTOR_PARAM encoderType_
private:
std::unique_ptr<sinqControllerImpl> pSinqC_;
static void epicsInithookFunction(initHookState iState);
};
#define NUM_SINQMOTOR_DRIVER_PARAMS \
(&LAST_SINQMOTOR_PARAM - &FIRST_SINQMOTOR_PARAM + 1)
#endif