Files
sinqMotor/src/sinqAxis.h
2025-06-17 08:43:51 +02:00

735 lines
28 KiB
C++

// SPDX-License-Identifier: GPL-3.0-only
/*
This class extends asynMotorAxis by some features used in SINQ.
Stefan Mathis, November 2024
*/
#ifndef sinqAxis_H
#define sinqAxis_H
#include "asynMotorAxis.h"
#include <memory>
#include <type_traits>
struct sinqAxisImpl;
class epicsShareClass sinqAxis : public asynMotorAxis {
public:
/**
* @brief Construct a new sinqAxis object
*
* @param pC_ Pointer to the controller of the axis
* @param axis Index of the axis
*/
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.
*
* Wrapper around `doPoll` which performs the following operations:
- Call the `doPoll` method
- Reset motorStatusProblem_, motorStatusCommsError_ and motorMessageText_ if
doPoll returned asynSuccess
- If the movement timeout watchdog has been started, check it.
- The flags `motorStatusHome_`, `motorStatusHomed_` and
`motorStatusAtHome_` are set to their idle values (0, 1 and 1 respectively)
in the `poll()` method once the homing procedure is finished. See the
documentation of the `home()` method for more details.
- Run `callParamCallbacks()`
- Return the status of `doPoll`
*
* @param moving Forwarded to `doPoll`.
* @return asynStatus Forward the status of `doPoll`, unless one of
the parameter library operation fails (in that case, returns the status of
the failed operation.
*/
virtual asynStatus poll(bool *moving);
/**
* @brief Implementation of the "proper", device-specific poll method. This
method should be implemented by a child class of sinqAxis.
*
* @param moving Should be set to true, if the axis is moving,
* and false otherwise.
* @return asynStatus
*/
virtual asynStatus doPoll(bool *moving);
/**
* @brief Perform some standardized operations before and after the concrete
`doMove` implementation.
* Wrapper around `doMove` which calculates the (absolute) target position
and stores it in the member variable `targetPosition_`. This member variable
is e.g. used for the movement watchdog. Afterwards, it calls and returns
`doMove`.
*
* @param position Forwarded to `doMove`.
* @param relative Forwarded to `doMove`.
* @param minVelocity Forwarded to `doMove`.
* @param maxVelocity Forwarded to `doMove`.
* @param acceleration Forwarded to `doMove`.
* @return asynStatus Forward the status of `doMove`, unless one of
the parameter library operation fails (in that case, returns the failed
operation status).
*/
virtual asynStatus move(double position, int relative, double minVelocity,
double maxVelocity, double acceleration);
/**
* @brief Implementation of the "proper", device-specific move method. This
method should be implemented by a child class of sinqAxis.
*
* @param position Target position `VAL` from the motor record
* @param relative Specifies, whether the target position is
relative or absolute.
* @param minVelocity Minimum velocity VMIN from the motor record
* @param maxVelocity Maximum velocity VMAX from the motor record
* @param acceleration Acceleration ACCEL from the motor record
* @return asynStatus
*/
virtual asynStatus doMove(double position, int relative, double minVelocity,
double maxVelocity, double acceleration);
/**
* @brief Wrapper around doHome which handles the homing-related flags
*
* The homing procedure of the motor record is controlled by the following
* parameter library flags:
*
* - `motorMoveToHome_`: Setting this flag to `1` indicates to EPICS that a
homing procedure should start and can therefore be used to start homing from
within the driver.
* - `motorStatusHome_`: This flag should be set to `1` while the motor is
actively moving toward its home position and to `0` when the home position
is reached.
*
* - `motorStatusHomed_`: This flag should be set to `0` at the start of a
homing command and to 1 once the home position is reached.
*
* - `motorStatusAtHome_`: This flag is similar to `motorStatusHomed_`, but
in addition it should also be `1` when the motor is at its home position,
but wasn't actively homed in order to get there.
*
* This function performs the following operations in the given order:
*
* - Call `doHome()` and forward the parameters
*
* - If `doHome()` returned asynSuccess: Set `motorStatusHome_` to `1`,
`motorStatusHomed_` to `0` and `motorStatusAtHome_` to `0`.
*
* - If `doHome()` returned asynError: This means that the motor cannot be
homed because the encoder is absolute. Set a corresponding error message,
but return asynSuccess in order to avoid any automatic retries by asyn.
* - If `doHome()` returned anything else: Forward the status.
*
* The flags `motorStatusHome_`, `motorStatusHomed_` and
`motorStatusAtHome_` are set to their idle values (0, 1 and 1 respectively)
in the `poll()` method once the homing procedure is finished.
*
* @param minVelocity Forwarded to `doHome`.
* @param maxVelocity Forwarded to `doHome`.
* @param acceleration Forwarded to `doHome`.
* @param forwards Forwarded to `doHome`.
* @return asynStatus Forward the status of `doHome`, unless one of
the parameter library operation fails (in that case, returns the failed
operation status).
*/
virtual asynStatus home(double minVelocity, double maxVelocity,
double acceleration, int forwards);
/**
* @brief Implementation of the "proper", device-specific home method. This
method should be implemented by a child class of sinqAxis. If the motor
cannot be homed because it has an absolute encoder, this function should
return asynError.
*
* @param minVelocity Minimum velocity VMIN from the motor record
* @param maxVelocity Maximum velocity VMAX from the motor record
* @param acceleration Acceleration ACCEL from the motor record
* @param forwards Is 1, if the motor record field HOMF was used
to trigger the homing, and 0, if HOMR was used.
* @return asynStatus
*/
virtual asynStatus doHome(double minVelocity, double maxVelocity,
double acceleration, int forwards);
/**
* @brief This function is called when the PV "$(INSTR)$(M):Reset" is set to
* any value. It calls `doReset` (which ought to be implemented by a child
* class) and then performs da defined number of consecutive fast polls. If
* one of the polls returns asynSuccess, it returns immediately.
*
* @return asynStatus
*/
virtual asynStatus reset();
/**
* @brief Implementation of the "proper", device-specific `reset` method.
This method should be implemented by a child class of sinqAxis. If the
motor cannot be reset, this function should return asynError.
*
* @return asynStatus
*/
virtual asynStatus doReset();
/**
* @brief This function enables / disables an axis. It should be implemented
* by a child class of sinqAxis.
*
* The concrete implementation should (but doesn't need to) follow the
* convention that a value of 0 disables the axis and any other value
* enables it.
*
* @param on
* @return asynStatus
*/
virtual asynStatus enable(bool on);
/**
* @brief Populate the motor record fields VELO, VBAS and VMAX
*
* Populates the speed fields of the motor record. If the param lib
* entry motorCanSetSpeed_ (connected to the PV x:VariableSpeed) is set to
* 1, VBAS and VMAX are set to min and max respectively. Otherwise, they are
* set to val. Additionally, the speed itself is set to VELO.
*
* The units of the inputs are engineering units (EGU) per second (e.g. mm/s
* if the EGU is mm).
*
* If the given configuration is invalid (min > max, velo < min, velo > max)
* and the motor is configured as a variable speed motor (param lib entry
* motorCanSetSpeed_ is 1), this function returns an asynError.
*
* @param velo Actual velocity (EGU / s)
* @param vbas Minimum allowed velocity (EGU / s)
* @param velo Maximum allowed velocity (EGU / s)
*
* @return asynStatus
*/
virtual asynStatus setVeloFields(double velo, double vbas, double vmax);
/**
* @brief Populate the ACCL field of the motor record
*
* Populates the acceleration field of the motor record with the given
* value. If accl is not positive, this function does not set the value and
* returns an asynError.
*
* The unit of the input is engineering units (EGU) per second squared (e.g.
* mm/s^2 if the EGU is mm).
*
* @param accl Actual acceleration (EGU / s^2)
* @return asynStatus
*/
virtual asynStatus setAcclField(double accl);
/**
* @brief Start the watchdog for the movement, if the watchdog is not
disabled. See the documentation of checkMovTimeoutWatchdog for more details.
*
* @return asynStatus If one of the parameter library operations
used to get the values for the timeout calculation failed, return that
status, otherwise return asynSuccess.
*/
virtual asynStatus startMovTimeoutWatchdog();
/**
* @brief Check if the watchdog timed out
*
Manages a timeout mechanism for the movement:
If the axis is moving and the movement takes too long, create an error
message and return asynError. The watchdog is started when moving switches
from "false" to "true" and stopped when moving switches from "true" to
"false". At the watchdog start, the estimated movement time is calculated as
t = offsetMovTimeout_ + scaleMovTime_ * [timeContSpeed + 2*timeAccel]
with
timeContSpeed = abs(targetPosition - motorPosition) / motorVelBase
timeAcc = motorVelBase / motorAccel
The values motorVelBase, motorAccel and positionAtMovementStart are taken
from the parameter library. Therefore it is necessary to populate them
before using this function. If they are not given, both speed and velocity
are assumed to be infinite. This means that timeContSpeed and/or timeAcc are
set to zero. targetPosition is populated automatically when using the doMove
function.
The values offsetMovTimeout_ and scaleMovTimeout_ can be set directly from
the IOC shell with the functions setScaleMovTimeout and setOffsetMovTimeout,
if sinqMotor is loaded via the "require" mechanism.
*
* @param moving Should be the "moving" status from `poll` /
`doPoll`.
* @return asynStatus Return asynError, if the watchdog timed out,
and asynSuccess otherwise.
*/
virtual asynStatus checkMovTimeoutWatchdog(bool moving);
/**
* @brief Enable / disable the watchdog. Also available in the IOC shell
* (see "extern C" section in sinqController.cpp).
*
* If enable is set to false and the watchdog is currently running, this
* function stops it immediately.
*
* @param enabled
* @return asynStatus
*/
virtual asynStatus setWatchdogEnabled(bool enable);
/**
* @brief Set the offsetMovTimeout. Also available in the IOC shell
* (see "extern C" section in sinqController.cpp).
*
* See documentation of `checkMovTimeoutWatchdog` for details.
*
* @param offsetMovTimeout Offset (in seconds)
* @return asynStatus
*/
virtual asynStatus setOffsetMovTimeout(time_t offsetMovTimeout);
/**
* @brief Set the scaleMovTimeout. Also available in the IOC shell
* (see "extern C" section in sinqController.cpp).
*
See documentation of `checkMovTimeoutWatchdog` for details.
*
* @param scaleMovTimeout Scaling factor (in seconds)
* @return asynStatus
*/
virtual asynStatus setScaleMovTimeout(time_t scaleMovTimeout);
/**
* @brief Return the axis number of this axis
*
* @return int
*/
int axisNo() { return axisNo_; }
/**
* @brief Read the motor position from the paramLib, adjusted for the
* motorRecResolution
*
* The motorPosition value in the paramLib is the encoder position
* divided by the motorRecResolution (see README.md). This function
* fetches the paramLib value and multiplies it with motorRecResolution
* (also fetched from the paramLib).
*
* @param motorPositon
* @return asynStatus
*/
asynStatus motorPosition(double *motorPositon);
/**
* @brief Write the motor position in the paramLib, adjusted for the
* motorRecResolution
*
* The motorPosition value in the paramLib is the encoder position
* divided by the motorRecResolution (see README.md). This function takes
* the input value and divides it with motorRecResolution (fetched from
* the paramLib).
*
* @param motorPosition
* @return asynStatus
*/
asynStatus setMotorPosition(double motorPosition);
/**
* @brief Check if the axis is not connected and print a corresponding error
* message
*
* This method is meant to be used at the end of "interactive" function
* calls such as move, home, stop etc which can be manually triggered from
* the IOC shell or from the channel access protocol.
*/
asynStatus assertConnected();
/**
* @brief Return a pointer to the axis controller.
*
* This function should be overriden in derived classes using the `override`
* keyword so the macros `getAxisParamChecked` and `setAxisParamChecked`
* work correctly:
*
* ```
* class mySpecialAxis : public sinqAxis {
public:
mySpecialController* getControllerMethod() override {
return mySpecialControllerPtr;
}
};
* ```
*/
virtual sinqController *pController() { return pC_; };
/**
* @brief Returns true, if the axis was moving in the last poll cycle, and
* false otherwise.
*
* @return true
* @return false
*/
bool wasMoving();
/**
* @brief Override the wasMoving flag (normally, it is automatically updated
* during each poll).
*
*/
void setWasMoving(bool wasMoving);
/**
* @brief Read out the last received target position in engineering units.
*
* @return double
*/
double targetPosition();
/**
* @brief Override the targetPosition value (normally, it is automatically
* updated at every call of the move() method).
*
*/
void setTargetPosition(double targetPosition);
private:
std::unique_ptr<sinqAxisImpl> pSinqA_;
sinqController *pC_;
};
// =============================================================================
// Helper functions and definitions for the macro setAxisParamChecked
template <typename T> struct TypeTag {};
// Generic fallback - if the compiler tries to compile this function, it fails.
template <typename A, typename C, typename T>
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), T writeValue,
const char *callerFunctionName, int lineNumber,
TypeTag<void>) {
static_assert(sizeof(T) == 0, "Unsupported type for setAxisParamImpl");
return asynError;
}
template <typename A, typename C>
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), int writeValue,
const char *callerFunctionName, int lineNumber,
TypeTag<int>) {
int indexValue = (controller->*func)();
asynStatus status = axis->setIntegerParam(indexValue, writeValue);
if (status != asynSuccess) {
return controller->paramLibAccessFailed(
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
}
return asynSuccess;
}
template <typename A, typename C>
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), bool writeValue,
const char *callerFunctionName, int lineNumber,
TypeTag<bool>) {
return setAxisParamImpl(axis, controller, indexName, func,
static_cast<int>(writeValue), callerFunctionName,
lineNumber, TypeTag<int>{});
}
template <typename A, typename C>
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), double writeValue,
const char *callerFunctionName, int lineNumber,
TypeTag<double>) {
int indexValue = (controller->*func)();
asynStatus status = axis->setDoubleParam(indexValue, writeValue);
if (status != asynSuccess) {
return controller->paramLibAccessFailed(
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
}
return asynSuccess;
}
template <typename A, typename C>
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), char *writeValue,
const char *callerFunctionName, int lineNumber,
TypeTag<char *>) {
int indexValue = (controller->*func)();
asynStatus status = axis->setStringParam(indexValue, writeValue);
if (status != asynSuccess) {
return controller->paramLibAccessFailed(
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
}
return asynSuccess;
}
template <typename A, typename C>
asynStatus setAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), const char *writeValue,
const char *callerFunctionName, int lineNumber,
TypeTag<const char *>) {
int indexValue = (controller->*func)();
asynStatus status = axis->setStringParam(indexValue, writeValue);
if (status != asynSuccess) {
return controller->paramLibAccessFailed(
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
}
return asynSuccess;
}
/**
* @brief Helper function to set an integer / double / string parameter for an
* axis in the paramLib
*
* This function should not be used directly, but rather through its macro
* variant `setAxisParamChecked`.
*
* @tparam T
* @param axis
* @param controller
* @param indexName
* @param func
* @param writeValue
* @param callerFunctionName
* @param lineNumber
* @return asynStatus
*/
template <typename A, typename C, typename T>
asynStatus setAxisParam(A *axis, C *controller, const char *indexName,
int (C::*func)(), T writeValue,
const char *callerFunctionName, int lineNumber) {
return setAxisParamImpl(axis, controller, indexName, func, writeValue,
callerFunctionName, lineNumber, TypeTag<T>{});
}
/**
* @brief Macro to set an paramLib parameter and error checking the return value
*
* This macro is a wrapper around `setIntegerParam` / `setDoubleParam` /
* `setStringParam` which checks if the operation was successfull. If it wasn't,
* it returns by calling the paramLibAccessFailed function.
*
* For example, the following input:
* ```
* setAxisParamChecked(this, motorStatusProblem_, false)
* ```
* expands into the following code:
* ```
* {
* int indexValue = controller->motorStatusProblem_();
* asynStatus status = axis->setIntegerParam(indexValue, writeValue);
* if (status != asynSuccess) {
* return controller->paramLibAccessFailed(
* status, "motorStatusProblem_", axis->axisNo(), __PRETTY_FUNCTION__,
* __LINE__);
* }
* return asynSuccess;
* }
* ```
* =============================================================================
*/
#define setAxisParamChecked(axis, indexSetterFunction, writeValue) \
do { \
auto *ctrlPtr = (axis)->pController(); \
using ControllerType = \
typename std::remove_pointer<decltype(ctrlPtr)>::type; \
asynStatus setStatus = \
setAxisParam(axis, ctrlPtr, #indexSetterFunction, \
static_cast<int (ControllerType::*)()>( \
&ControllerType::indexSetterFunction), \
writeValue, __PRETTY_FUNCTION__, __LINE__); \
if (setStatus != asynSuccess) \
return setStatus; \
} while (0)
// =============================================================================
// Helper functions and definitions for the macro getAxisParamChecked
// Generic fallback - if the compiler tries to compile this function, it fails.
template <typename A, typename C, typename T>
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), T *readValue,
const char *callerFunctionName, int lineNumber,
TypeTag<void>) {
static_assert(
sizeof(T) == 0,
"no specialization of getAxisParam exists for the given type");
return asynError;
}
template <typename A, typename C>
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), int *readValue,
const char *callerFunctionName, int lineNumber,
TypeTag<int>) {
int indexValue = (controller->*func)();
asynStatus status =
controller->getIntegerParam(axis->axisNo(), indexValue, readValue);
if (status != asynSuccess) {
return controller->paramLibAccessFailed(
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
}
return asynSuccess;
}
template <typename A, typename C>
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), bool *readValue,
const char *callerFunctionName, int lineNumber,
TypeTag<bool>) {
return getAxisParamImpl(axis, indexName, func, (int *)readValue,
callerFunctionName, lineNumber);
}
template <typename A, typename C>
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), double *readValue,
const char *callerFunctionName, int lineNumber,
TypeTag<double>) {
int indexValue = (controller->*func)();
asynStatus status =
controller->getDoubleParam(axis->axisNo(), indexValue, readValue);
if (status != asynSuccess) {
return controller->paramLibAccessFailed(
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
}
return asynSuccess;
}
template <typename A, typename C>
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), char *readValue,
const char *callerFunctionName, int lineNumber,
TypeTag<char>) {
int indexValue = (controller->*func)();
asynStatus status = controller->getStringParam(
axis->axisNo(), indexValue, controller->msgSize(), readValue);
if (status != asynSuccess) {
return controller->paramLibAccessFailed(
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
}
return asynSuccess;
}
template <typename A, typename C, size_t N>
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), char (*readValue)[N],
const char *callerFunctionName, int lineNumber,
TypeTag<char>) {
// Decay the array to char*
return getAxisParamImpl(axis, controller, indexName, func,
static_cast<char *>(readValue), callerFunctionName,
lineNumber);
}
template <typename A, typename C>
asynStatus getAxisParamImpl(A *axis, C *controller, const char *indexName,
int (C::*func)(), std::string *readValue,
const char *callerFunctionName, int lineNumber,
TypeTag<std::string>) {
int indexValue = (controller->*func)();
// Convert the pointer to a reference, since getStringParam expects the
// latter.
std::string &rReadValue = *readValue;
asynStatus status =
controller->getStringParam(axis->axisNo(), indexValue, rReadValue);
if (status != asynSuccess) {
return controller->paramLibAccessFailed(
status, indexName, axis->axisNo(), callerFunctionName, lineNumber);
}
return asynSuccess;
}
/**
* @brief Helper function to set an integer / double / string parameter for an
* axis in the paramLib
*
* This function should not be used directly, but rather through its macro
* variant `getAxisParamChecked`.
*
* @tparam T
* @param axis
* @param controller
* @param indexName
* @param func
* @param readValue
* @param callerFunctionName
* @param lineNumber
* @param maxChars Only used when readValue is a char*. Specifies the maximum
* number of characters which can be placed into the buffer the pointer points
* to.
* @return asynStatus
*/
template <typename A, typename C, typename T>
asynStatus getAxisParam(A *axis, C *controller, const char *indexName,
int (C::*func)(), T *readValue,
const char *callerFunctionName, int lineNumber) {
return getAxisParamImpl(axis, controller, indexName, func, readValue,
callerFunctionName, lineNumber, TypeTag<T>{});
}
/**
* @brief Macro to get an paramLib parameter and error checking the return value
*
* This macro is a wrapper around `getIntegerParam` / `getDoubleParam` /
* `getStringParam` which checks if the operation was successfull. If it wasn't,
* it returns by calling the paramLibAccessFailed function. In order
*
* For example, the following input:
* ```
* getAxisParamChecked(this, motorStatusProblem_, &readValue)
* ```
* expands into the following code:
* ```
* {
* int indexValue = controller->motorStatusProblem_();
* asynStatus status = controller->getIntegerParam(axis->axisNo(),
* indexValue, readValue); if (status != asynSuccess) { return
* controller->paramLibAccessFailed( status, "motorStatusProblem_",
* axis->axisNo(), __PRETTY_FUNCTION__,
* __LINE__);
* }
* return asynSuccess;
* }
* ```
* =============================================================================
*/
#define getAxisParamChecked(axis, indexGetterFunction, readValue) \
do { \
auto *ctrlPtr = (axis)->pController(); \
using ControllerType = \
typename std::remove_pointer<decltype(ctrlPtr)>::type; \
asynStatus getStatus = \
getAxisParam(axis, ctrlPtr, #indexGetterFunction, \
static_cast<int (ControllerType::*)()>( \
&ControllerType::indexGetterFunction), \
readValue, __PRETTY_FUNCTION__, __LINE__); \
if (getStatus != asynSuccess) \
return getStatus; \
} while (0)
#endif