Compare commits

...

5 Commits

Author SHA1 Message Date
c2eca33ce8 Ready for release 1.0 2025-05-23 12:25:48 +02:00
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 334 additions and 217 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,18 +204,23 @@ 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
// check, the IOC is stopped, since this is definitely a configuration
// problem.
/*
This check is also done in asynMotorAxis, but there the IOC continues
running even though the configuration is incorrect. When failing this check,
the IOC is stopped, since this is definitely a configuration problem.
*/
if ((axisNo < 0) || (axisNo >= pC->numAxes())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
@ -313,6 +338,8 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
}
}
sinqAxis::~sinqAxis() = default;
asynStatus sinqAxis::poll(bool *moving) {
// Local variable declaration
@ -346,10 +373,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 +387,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 +438,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 +446,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 +494,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 +512,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 +534,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 +708,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 +744,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 +762,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 +777,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 +788,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 +796,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 +811,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