Bugfix for movTimeoutWatchdog

Update of README.md
This commit is contained in:
2025-02-14 16:17:11 +01:00
parent c06cf8e76c
commit e92a867189
9 changed files with 948 additions and 480 deletions

View File

@ -6,8 +6,8 @@ BUILDCLASSES=Linux
EPICS_VERSIONS=7.0.7
ARCH_FILTER=RHEL%
# additional module dependencies
REQUIRED+=asynMotor
# Specify the version of asynMotor we want to build against
asynMotor_VERSION=7.2.2
# Source files to build
SOURCES += src/sinqAxis.cpp
@ -21,6 +21,9 @@ HEADERS += src/sinqController.h
TEMPLATES += db/asynRecord.db
TEMPLATES += db/sinqMotor.db
# This file registers the motor-specific functions in the IOC shell.
DBDS += src/sinqMotor.dbd
USR_CFLAGS += -Wall -Wextra -Weffc++ -Wunused-result # -Werror
# MISCS would be the place to keep the stream device template files
# MISCS would be the place to keep the stream device template files

View File

@ -2,23 +2,22 @@
## Overview
This library offers base classes for EPICS motor drivers (`sinqAxis` and `sinqController`) of PSI SINQ. These classes are extensions of the classes `asynMotorAxis` and `asynMotorController` from the `asynMotor` framework (https://github.com/epics-modules/motor/tree/master/motorApp/MotorSrc) and bundle some common functionalities.
This library offers base classes for EPICS motor drivers (`sinqAxis` and `sinqController`) of PSI SINQ. These classes are extensions of the classes `asynMotorAxis` and `asynMotorController` from the `asynMotor` framework (https://github.com/epics-modules/motor/tree/master/motorApp/MotorSrc) and bundle some common functionality.
## User guide
### Architecture of EPICS motor drivers at SINQ
As mentioned before, the asyn-framework offers two base classes `asynMotorAxis` and `asynMotorController`. At SINQ, we extend those classes by two children classes `sinqAxis` and `sinqController` which are not complete drivers on their own, but serve as an additional framework for writing drivers. The concrete drivers are then created as separated libraries, an example is the pmacv3-driver: https://git.psi.ch/sinq-epics-modules/pmacv3
The asyn-framework offers two base classes `asynMotorAxis` and `asynMotorController`. At SINQ, we extend those classes by two children `sinqAxis` and `sinqController` which are not complete drivers on their own, but serve as an additional framework for writing drivers. The concrete drivers are then created as separated libraries, an example is the TurboPMAC-driver: https://git.psi.ch/sinq-epics-modules/turboPmac.
The full inheritance chain for two different motor drivers "a" and "b" looks for a like this:
The full inheritance chain for two different motor drivers "a" and "b" looks like this:
`asynController -> sinqController -> aController`
`asynAxis -> sinqAxis -> aAxis`
`asynController -> sinqController -> bController`
`asynAxis -> sinqAxis -> bAxis`
Those inheritance chains are created at runtime by loading shared libraries. Therefore, it is important to load compatible versions. At SINQ, the version management is SemVer-compatible (https://semver.org/lang/de/) in order to ensure compatibility.
For example, if driver "a" depends on version 2.1.0 of "sinqMotor", then it is safe to use version 2.5.3 since 2.5.3 is backwards compatible to 2.1.0. However, it is not allowed to use e.g. version 1.9.0 or 2.0.0 or 3.0.1 instead. For more details on SemVer, please refer to the official documentation.
Those inheritance chains are created at runtime by loading shared libraries. Therefore, it is important to load compatible versions. At SINQ, the versioning numbers follow the SemVer standard (https://semver.org/lang/de/). For example, if driver "a" depends on version 2.1.0 of "sinqMotor", then it is safe to use "sinqMotor" 2.5.3 since 2.5.3 is backwards compatible to 2.1.0. However, it is not allowed to use e.g. version 1.9.0 or 2.0.0 or 3.0.1 instead. For more details on SemVer, please refer to the official documentation.
To find out which version of sinqMotor is needed by driver "a", refer to its Makefile (line `sinqMotor_VERSION=x.x.x`, where x.x.x is the minimum required version).
@ -30,7 +29,7 @@ An EPICS IOC for motor control at SINQ is started by executing a script with the
# Load libraries needed for the IOC
require sinqMotor, 1.0.0
require pmacv3, 1.2.0
require turboPmac, 1.2.0
# Define environment variables used later to parametrize the individual controllers
epicsEnvSet("TOP","/ioc/sinq-ioc/sinqtest-ioc/")
@ -52,7 +51,7 @@ epicsEnvSet("ASYN_PORT","p$(NAME)")
drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025")
# Create the controller object in EPICS. The function "pmacv3Controller" is
# provided by loading the shared library pmacv3 earlier.
# provided by loading the shared library turboPmac.
pmacv3Controller("$(NAME)","$(ASYN_PORT)",8,0.05,1,0.05);
# Create four axes objects on slots 1, 2, 3 and 5 of the controller.
@ -155,27 +154,25 @@ transferred to (motor_record_pv_name).MRES or to
sinqMotor offers a variety of additional methods for children classes to standardize certain patterns (e.g. writing messages to the IOC shell and the motor message PV). For a detailed description, please see the respective function documentation in the .h-files. All of these functions can be overwritten manually if e.g. a completely different implementation of `poll` is required. Some functions are marked as virtual, because they are called from other functions of sinqMotor and therefore need runtime polymorphism. Functions without that marker are not called anywhere in sinqMotor.
#### sinqController
#### sinqController.h
- `errMsgCouldNotParseResponse`: Write a standardized message if parsing a device response failed.
- `paramLibAccessFailed`: Write a standardized message if accessing the parameter library failed.
- `stringifyAsynStatus`: Convert the enum `asynStatus` into a human-readable string.
- `checkComTimeoutWatchdog`: Calculates the timeout frequency (number of timeouts in a given time) and informs the user if a specified limit has been exceeded.
- `setThresholdComTimeout`: Set the maximum number of timeouts and the time window size for the timeout frequency limit.
- `checkMaxSubsequentTimeouts`: Check if the number of subsequent timeouts exceeds a specified limit.
- `setMaxSubsequentTimeouts`: Set the limit for the number of subsequent timeouts before the user is informed.
#### sinqAxis
- `atFirstPoll`: This function is executed once before the first poll. If it returns anything but `asynSuccess`, it retries.
- `startMovTimeoutWatchdog`: Starts a watchdog for the movement time. This watchdog compares the actual time spent in a movement operation with an expected time, which is calculated based on the distance of the current and the target position.
- `checkMovTimeoutWatchdog`: Check if the watchdog timed out.
- `setWatchdogEnabled`: Enables / disables the watchdog. This function is also available in the IOC shell.
- `setOffsetMovTimeout`: Set a linear offset for the expected movement time. This function is also available in the IOC shell.
- `setScaleMovTimeout`: Set a scaling factor for the expected movement time. This function is also available in the IOC shell.
#### sinqAxis.h
- `enable`: This function is called if the "Enable" PV from db/sinqMotor.db is set. This is an empty function which should be overwritten by concrete driver implementations.
- `move`: This function sets the absolute target position in the parameter library and then calls `doMove`.
- `doMove`: This is an empty function which should be overwritten by concrete driver implementations.
- `home`: This function sets the absolute target position in the parameter library and then calls `doHome`. The target position is assumed to be the high limit, if the distance of the current position to it is larger than that to the low limit, and the low limit otherwise.
- `home`: This function sets the internal status flags for the homing process and then calls doHome.
- `doHome`: This is an empty function which should be overwritten by concrete driver implementations.
- `poll`: This is a wrapper around `doPoll` which performs some bookkeeping tasks before and after calling `doPoll`:
Before calling `doPoll`:
- Try to execute `atFirstPoll` once during the lifetime of the IOC (and retry, if that failed)
- Reset the status problem flag, the communication error flag and the error message.
After calling `doPoll`:
- Call `checkMovTimeoutWatchdog`. If the movement timed out, create an error message for the user
@ -186,6 +183,11 @@ sinqMotor offers a variety of additional methods for children classes to standar
- `doPoll`: This is an empty function which should be overwritten by concrete driver implementations.
- `setVeloFields`: Populates the motor record fields VELO (actual velocity), VBAS (minimum allowed velocity) and VMAX (maximum allowed velocity) from the driver.
- `setAcclField`: Populates the motor record field ACCL from the driver.
- `startMovTimeoutWatchdog`: Starts a watchdog for the movement time. This watchdog compares the actual time spent in a movement operation with an expected time, which is calculated based on the distance of the current and the target position.
- `checkMovTimeoutWatchdog`: Check if the watchdog timed out.
- `setWatchdogEnabled`: Enables / disables the watchdog. This function is also available in the IOC shell.
- `setOffsetMovTimeout`: Set a linear offset for the expected movement time. This function is also available in the IOC shell.
- `setScaleMovTimeout`: Set a scaling factor for the expected movement time. This function is also available in the IOC shell.
### Versioning
@ -195,6 +197,6 @@ All existing tags can be listed with `git tag` in the sinqMotor directory. Detai
### How to build it
The makefile in the top directory includes all necessary steps for compiling a shared library together with the header files into `/ioc/modules` (using the PSI EPICS build system).Therefore it is sufficient to run `make install` from the terminal.
The makefile in the top directory includes all necessary steps for compiling a shared library together with the header files into `/ioc/modules` (using the PSI EPICS build system). Therefore it is sufficient to clone this repository to a suitable location (`git clone https://git.psi.ch/sinq-epics-modules/sinqmotor/-/tree/main`). Afterwards, switch to the directory (`cd sinqmotor`) and run `make install`.
To use the library when writing a concrete motor driver, include it in the makefile of your application / library the same way as other libraries such as e.g. `asynMotor` by adding `REQUIRED+=sinqMotor` to your Makefile. The version can be specified with `sinqMotor_VERSION=x.x.x.`

View File

@ -1,4 +1,4 @@
record(asyn,"$(P)$(NAME)")
record(asyn,"$(P)")
{
field(DTYP,"asynRecordDevice")
field(PORT,"$(PORT)")

View File

@ -10,6 +10,20 @@
# - MRES: Motor record resolution. See the README.md for a detailed discussion
# - EGU: Engineering units. In case of a rotary axis, this is "degree", in
# case of a linear axis this is "mm".
# - RTRY: The maximum number of times the motor record will try again to move to
# the desired position. When the retry limit is reached, the motor record will
# declare the motion finished. If the desired position was not reached, the
# field MISS will be set to 1 and NICOS will emit a warning "Did not reach
# target position". If this value is set to 0, the retry deadband is never
# applied and therefore MISS will always be 0. The error message "Did not reach
# target position" will therefore never appear.
# - RDBD: Retry deadband: When the motor has finished a complete motion,
# possibly including backlash takeout, the motor record will compare its current
# position with the desired position. If the magnitude of the difference is
# greater than RDBD, the motor will try again, as if the user had requested a
# move from the now current position to the desired position. Only a limited
# number of retries will be performed (see RTRY). If the given value is smaller
# than MRES, it is set to MRES.
record(motor,"$(INSTR)$(M)")
{
field(DESC,"$(DESC=$(M))")
@ -19,9 +33,29 @@ record(motor,"$(INSTR)$(M)")
field(MRES,"$(MRES)")
field(EGU,"$(EGU)")
field(INIT,"")
field(PINI, "NO")
field(PINI,"NO")
field(TWV,"1")
field(RTRY, "0")
field(RTRY,"0")
field(RDBD,"0")
field(BDST,"0")
field(RMOD,"3") # Retry mode 3 ("In-Position"): This suppresses any retries from the motor record.
}
# This PV allows force-stopping the motor record from within the driver by setting
# the motorForceStop_ value in the parameter library to 1. It should be reset to 0 by the driver afterwards.
# The implementation strategy is taken from https://epics.anl.gov/tech-talk/2022/msg00464.php.
# This record is coupled to the parameter library via motorForceStop_ -> MOTOR_FORCE_STOP.
record(longin, "$(INSTR)$(M):STOP_RBV")
{
field(DTYP, "asynInt32")
field(INP, "@asyn($(CONTROLLER),$(AXIS)) MOTOR_FORCE_STOP")
field(SCAN, "I/O Intr")
field(FLNK, "$(INSTR)$(M):Stop2Field")
}
record(longout, "$(INSTR)$(M):Stop2Field") {
field(DOL, "$(INSTR)$(M):STOP_RBV CP")
field(OUT, "$(INSTR)$(M).STOP")
field(OMSL, "closed_loop")
}
# This record forwards the motor record resolution MRES to the parameter library
@ -236,4 +270,4 @@ record(waveform, "$(INSTR)$(M):EncoderType") {
field(FTVL, "CHAR")
field(NELM, "80")
field(SCAN, "I/O Intr")
}
}

View File

@ -1,5 +1,8 @@
#include "sinqAxis.h"
#include "epicsExport.h"
#include "iocsh.h"
#include "sinqController.h"
#include <errlog.h>
#include <math.h>
#include <unistd.h>
@ -12,124 +15,95 @@ sinqAxis::sinqAxis(class sinqController *pC, int axisNo)
init_poll_counter_ = 0;
scaleMovTimeout_ = 2.0;
offsetMovTimeout_ = 30;
targetPosition_ = 0.0;
// Motor is assumed to be enabled
status = pC_->setIntegerParam(axisNo_, pC_->motorEnableRBV_, 1);
status = setIntegerParam(pC_->motorEnableRBV_, 1);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
"(setting a parameter value failed "
"with %s)\n. Terminating IOC",
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
exit(-1);
}
// By default, motors cannot be disabled
status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable_, 0);
status = setIntegerParam(pC_->motorCanDisable_, 0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
"(setting a parameter value failed "
"with %s)\n. Terminating IOC",
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
exit(-1);
}
// Provide a default value for the motor position.
status = pC_->setDoubleParam(axisNo_, pC_->motorPosition_, 0.0);
status = setDoubleParam(pC_->motorPosition_, 0.0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
"(setting a parameter value failed "
"with %s)\n. Terminating IOC",
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
exit(-1);
}
// We assume that the motor has no status problems initially
status = pC_->setIntegerParam(axisNo_, pC_->motorStatusProblem_, 0);
status = setIntegerParam(pC_->motorStatusProblem_, 0);
if (status != asynSuccess) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting a parameter value failed "
"with %s)\n. Terminating IOC",
__PRETTY_FUNCTION__, __LINE__, pC_->stringifyAsynStatus(status));
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
"(setting a parameter value failed "
"with %s)\n. Terminating IOC",
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
exit(-1);
}
}
asynStatus sinqAxis::atFirstPoll() {
asynStatus status = asynSuccess;
int variableSpeed = 0;
status =
pC_->getIntegerParam(axisNo_, pC_->motorCanSetSpeed_, &variableSpeed);
// Set the homing-related flags
status = setIntegerParam(pC_->motorStatusHome_, 0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorCanSetSpeed_",
__PRETTY_FUNCTION__, __LINE__);
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
"(setting a parameter value failed "
"with %s)\n. Terminating IOC",
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
exit(-1);
}
if (variableSpeed == 1) {
status = pC_->setDoubleParam(axisNo_, pC_->motorVbasFromDriver_, 0.0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVbasFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVmaxFromDriver_,
1000000000.0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVmaxFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
} else {
status = pC_->setDoubleParam(axisNo_, pC_->motorVbasFromDriver_, 0.0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVbasFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVmaxFromDriver_, 0.0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVmaxFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
}
status = setIntegerParam(pC_->motorStatusHomed_, 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
"(setting a parameter value failed "
"with %s)\n. Terminating IOC",
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
exit(-1);
}
status = setIntegerParam(pC_->motorStatusAtHome_, 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR "
"(setting a parameter value failed "
"with %s)\n. Terminating IOC",
pC->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(status));
exit(-1);
}
return status;
}
asynStatus sinqAxis::poll(bool *moving) {
// Local variable declaration
asynStatus pl_status = asynSuccess;
asynStatus poll_status = asynSuccess;
// =========================================================================
// If this poll is the initial poll, check if the parameter library has
// already been initialized. If not, force EPICS to repeat the poll until
// the initialization is complete (or until a timeout is reached). Once the
// parameter library has been initialized, read configuration data from the
// motor controller into it.
if (initial_poll_) {
poll_status = atFirstPoll();
if (poll_status == asynSuccess) {
initial_poll_ = false;
} else {
// Send a message to the IOC shell every 10 trials.
init_poll_counter_ += 1;
if (init_poll_counter_ % 10 == 0) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nRunning function 'atFirstPoll' "
"failed %d times with error %s.\n",
__PRETTY_FUNCTION__, __LINE__, init_poll_counter_,
pC_->stringifyAsynStatus(poll_status));
}
// Wait for 100 ms until trying the entire poll again
usleep(100000);
return poll_status;
}
}
int homing = 0;
int homed = 0;
/*
At the beginning of the poll, it is assumed that the axis has no status
@ -140,18 +114,19 @@ asynStatus sinqAxis::poll(bool *moving) {
*/
pl_status = setIntegerParam(pC_->motorStatusProblem_, false);
if (pl_status != asynSuccess) {
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusCommsError_, false);
if (pl_status != asynSuccess) {
pC_->paramLibAccessFailed(pl_status, "motorStatusCommsError_",
pC_->paramLibAccessFailed(pl_status, "motorStatusCommsError_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setStringParam(pC_->motorMessageText_, "");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// The poll function is just a wrapper around doPoll and
@ -165,41 +140,62 @@ asynStatus sinqAxis::poll(bool *moving) {
if (poll_status != asynSuccess) {
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
}
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorStatusHomed_, &homed);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusHomed_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorStatusHome_, &homing);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusHome_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
if (homing == 1 && !(*moving)) {
// Set the homing-related flags
pl_status = setIntegerParam(pC_->motorStatusHome_, 0);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusHome_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusHomed_, 1);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusHomed_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusAtHome_, 1);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusAtHome_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
// Check and update the watchdog
if (checkMovTimeoutWatchdog(*moving) != asynSuccess) {
return asynError;
}
if (pl_status != asynSuccess) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFunction isEnabled failed with %s.\n",
__PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(poll_status));
pl_status = setStringParam(pC_->motorMessageText_,
"Could not check whether the motor is "
"enabled or not. Please call the support");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
}
}
// According to the function documentation of asynMotorAxis::poll, this
// function should be called at the end of a poll implementation.
pl_status = callParamCallbacks();
if (pl_status != asynSuccess) {
// If we can't communicate with the parameter library, it doesn't make
// sense to try and upstream this to the user -> Just log the error
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\ncallParamCallbacks failed with %s for axis %d",
__PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(poll_status), axisNo_);
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\ncallParamCallbacks failed with %s.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(poll_status));
poll_status = pl_status;
}
@ -211,35 +207,35 @@ asynStatus sinqAxis::doPoll(bool *moving) { return asynSuccess; }
asynStatus sinqAxis::move(double position, int relative, double minVelocity,
double maxVelocity, double acceleration) {
double targetPosition = 0.0;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
double motorRecResolution = 0.0;
// =========================================================================
// Calculate the (absolute) target position
if (relative) {
double motorPosition = 0.0;
pl_status =
pC_->getDoubleParam(axisNo_, pC_->motorPosition_, &motorPosition);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorPosition_",
__PRETTY_FUNCTION__, __LINE__);
}
targetPosition = position + motorPosition;
} else {
targetPosition = position;
}
// Set the target position
pl_status = setDoubleParam(pC_->motorTargetPosition_, targetPosition);
// When a new move is done, the motor is not homed anymore
pl_status = setIntegerParam(pC_->motorStatusHomed_, 0);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorTargetPosition_",
__PRETTY_FUNCTION__, __LINE__);
return pC_->paramLibAccessFailed(pl_status, "motorStatusHomed_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusAtHome_, 0);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusAtHome_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution_,
&motorRecResolution);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Store the target position internally
targetPosition_ = position * motorRecResolution;
return doMove(position, relative, minVelocity, maxVelocity, acceleration);
}
@ -252,48 +248,58 @@ asynStatus sinqAxis::doMove(double position, int relative, double minVelocity,
asynStatus sinqAxis::home(double minVelocity, double maxVelocity,
double acceleration, int forwards) {
double targetPosition = 0.0;
double position = 0.0;
double highLimit = 0.0;
double lowLimit = 0.0;
asynStatus status = asynSuccess;
// Status of parameter library operations
asynStatus pl_status = asynSuccess;
status = doHome(minVelocity, maxVelocity, acceleration, forwards);
// =========================================================================
if (status == asynSuccess) {
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorPosition_, &position);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorPosition_",
__PRETTY_FUNCTION__, __LINE__);
}
status = setStringParam(pC_->motorMessageText_, "Homing");
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorHighLimit_, &highLimit);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorHighLimit_",
__PRETTY_FUNCTION__, __LINE__);
}
// Set the homing-related flags
status = setIntegerParam(pC_->motorStatusHome_, 1);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorStatusHome_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
status = setIntegerParam(pC_->motorStatusHomed_, 0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorStatusHomed_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
status = setIntegerParam(pC_->motorStatusAtHome_, 0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorStatusAtHome_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorLowLimit_, &lowLimit);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorLowLimit_",
__PRETTY_FUNCTION__, __LINE__);
}
// Update the motor record
return callParamCallbacks();
if (std::fabs(position - highLimit) > std::fabs(position - lowLimit)) {
targetPosition = highLimit;
} else if (status == asynError) {
// asynError means that we tried to home an absolute encoder
status = setStringParam(pC_->motorMessageText_,
"Can't home a motor with absolute encoder");
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Update the motor record
return callParamCallbacks();
} else {
targetPosition = lowLimit;
// Bubble up all other problems
return status;
}
// Set the target position
pl_status = setDoubleParam(pC_->motorTargetPosition_, targetPosition);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorTargetPosition_",
__PRETTY_FUNCTION__, __LINE__);
}
return doHome(minVelocity, maxVelocity, acceleration, forwards);
}
asynStatus sinqAxis::doHome(double minVelocity, double maxVelocity,
@ -311,7 +317,7 @@ asynStatus sinqAxis::setVeloFields(double velo, double vbas, double vmax) {
status =
pC_->getIntegerParam(axisNo_, pC_->motorCanSetSpeed_, &variableSpeed);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorCanSetSpeed_",
return pC_->paramLibAccessFailed(status, "motorCanSetSpeed_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
if (variableSpeed == 1) {
@ -319,30 +325,36 @@ asynStatus sinqAxis::setVeloFields(double velo, double vbas, double vmax) {
// Check the inputs and create corresponding error messages
if (vbas > vmax) {
asynPrint(pC_->lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nLower speed limit vbas=%lf must not be "
"smaller than upper limit vmax=%lf.\n",
__PRETTY_FUNCTION__, __LINE__, vbas, vmax);
"Controller \"%s\", axis %d => %s, line %d:\nLower speed "
"limit vbas=%lf must not be smaller than upper limit "
"vmax=%lf.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
vbas, vmax);
status = setStringParam(
pC_->motorMessageText_,
"Lower speed limit must not be smaller than upper speed limit");
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return asynError;
}
if (velo < vbas || velo > vmax) {
asynPrint(pC_->lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nActual speed velo=%lf must be between "
"lower limit vbas=%lf and upper limit vmax=%lf.\n",
__PRETTY_FUNCTION__, __LINE__, velo, vbas, vmax);
"Controller \"%s\", axis %d => %s, line %d:\nActual "
"speed velo=%lf must be between lower limit vbas=%lf and "
"upper limit vmax=%lf.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
velo, vbas, vmax);
status = setStringParam(pC_->motorMessageText_,
"Speed is not inside limits");
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return asynError;
}
@ -350,38 +362,44 @@ asynStatus sinqAxis::setVeloFields(double velo, double vbas, double vmax) {
status = pC_->setDoubleParam(axisNo_, pC_->motorVbasFromDriver_, vbas);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVbasFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVeloFromDriver_, velo);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVeloFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVmaxFromDriver_, vmax);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVmaxFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
} else {
// Set minimum and maximum speed equal to the set speed
status = pC_->setDoubleParam(axisNo_, pC_->motorVbasFromDriver_, velo);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVbasFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVeloFromDriver_, velo);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVeloFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
status = pC_->setDoubleParam(axisNo_, pC_->motorVmaxFromDriver_, velo);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorVmaxFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
return status;
@ -393,17 +411,17 @@ asynStatus sinqAxis::setAcclField(double accl) {
return asynError;
}
asynStatus status =
pC_->setDoubleParam(axisNo_, pC_->motorAcclFromDriver_, accl);
asynStatus status = setDoubleParam(pC_->motorAcclFromDriver_, accl);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorAcclFromDriver_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return status;
}
asynStatus sinqAxis::setWatchdogEnabled(bool enable) {
return pC_->setIntegerParam(axisNo_, pC_->motorEnableMovWatchdog_, enable);
return setIntegerParam(pC_->motorEnableMovWatchdog_, enable);
}
asynStatus sinqAxis::startMovTimeoutWatchdog() {
@ -414,15 +432,14 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
&enableMovWatchdog);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnableMovWatchdog_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
if (enableMovWatchdog == 1) {
// These parameters are only needed in this branch
double motorPosition = 0.0;
double motorPositionRec = 0.0;
double motorTargetPositionRec = 0.0;
double motorTargetPosition = 0.0;
double motorVelocity = 0.0;
double motorVelocityRec = 0.0;
double motorAccel = 0.0;
@ -443,14 +460,16 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
&motorRecResolution);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorRecResolution_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorPosition_,
&motorPositionRec);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorPosition",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
motorPosition = motorPositionRec * motorRecResolution;
@ -477,15 +496,10 @@ asynStatus sinqAxis::startMovTimeoutWatchdog() {
if (pl_status == asynSuccess && motorVelocityRec > 0.0) {
// Convert back to the value in the VELO field
motorVelocity = motorVelocityRec * motorRecResolution;
pl_status = pC_->getDoubleParam(axisNo_, pC_->motorTargetPosition_,
&motorTargetPositionRec);
motorTargetPosition = motorTargetPositionRec * motorRecResolution;
if (pl_status == asynSuccess) {
timeContSpeed =
std::ceil(std::fabs(motorTargetPosition - motorPosition) /
motorVelocity);
timeContSpeed = std::ceil(
std::fabs(targetPosition_ - motorPosition) / motorVelocity);
}
}
@ -519,7 +533,8 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
&enableMovWatchdog);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnableMovWatchdog_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Not moving or watchdog not active / enabled
@ -532,22 +547,23 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
if (expectedArrivalTime_ < time(NULL)) {
// Check the watchdog
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d exceeded the expected arrival time "
"%ld (current time is %ld).\n",
__PRETTY_FUNCTION__, __LINE__, axisNo_, expectedArrivalTime_,
time(NULL));
"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));
pl_status = setStringParam(
pC_->motorMessageText_,
"Exceeded expected arrival time. Check if the axis is blocked.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = setIntegerParam(pC_->motorStatusProblem_, true);
if (pl_status != asynSuccess) {
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_",
pC_->paramLibAccessFailed(pl_status, "motorStatusProblem_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
@ -555,4 +571,161 @@ asynStatus sinqAxis::checkMovTimeoutWatchdog(bool moving) {
return asynSuccess;
}
return asynSuccess;
}
}
// =============================================================================
// IOC shell functions
extern "C" {
/**
* @brief Enable / disable the watchdog (FFI implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param enable If 0, disable the watchdog, otherwise enable
* it
* @return asynStatus
*/
asynStatus setWatchdogEnabled(const char *portName, int axisNo, int enable) {
sinqController *pC;
pC = (sinqController *)findAsynPortDriver(portName);
if (pC == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nPort %s not found.",
portName, __PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf(
"Controller \"%s\", axis %d => %s, line %d:\nAxis does not "
"exist or is not an instance of sinqAxis.",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
}
return axis->setWatchdogEnabled(enable != 0);
}
static const iocshArg setWatchdogEnabledArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setWatchdogEnabledArg1 = {"Axis number", iocshArgInt};
static const iocshArg setWatchdogEnabledArg2 = {
"Enabling / disabling the watchdog", iocshArgInt};
static const iocshArg *const setWatchdogEnabledArgs[] = {
&setWatchdogEnabledArg0, &setWatchdogEnabledArg1, &setWatchdogEnabledArg2};
static const iocshFuncDef setWatchdogEnabledDef = {"setWatchdogEnabled", 3,
setWatchdogEnabledArgs};
static void setWatchdogEnabledCallFunc(const iocshArgBuf *args) {
setWatchdogEnabled(args[0].sval, args[1].ival, args[2].ival);
}
// =============================================================================
/**
* @brief Set the offsetMovTimeout (FFI implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param offsetMovTimeout Offset (in seconds)
* @return asynStatus
*/
asynStatus setOffsetMovTimeout(const char *portName, int axisNo,
double offsetMovTimeout) {
sinqController *pC;
pC = (sinqController *)findAsynPortDriver(portName);
if (pC == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nPort %s not found.",
portName, __PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nAxis %d does not "
"exist or is not an "
"instance of sinqAxis.",
portName, __PRETTY_FUNCTION__, __LINE__, axisNo);
}
return axis->setOffsetMovTimeout(offsetMovTimeout);
}
static const iocshArg setOffsetMovTimeoutArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setOffsetMovTimeoutArg1 = {"Axis number", iocshArgInt};
static const iocshArg setOffsetMovTimeoutArg2 = {"Offset timeout for movement",
iocshArgDouble};
static const iocshArg *const setOffsetMovTimeoutArgs[] = {
&setOffsetMovTimeoutArg0, &setOffsetMovTimeoutArg1,
&setOffsetMovTimeoutArg2};
static const iocshFuncDef setOffsetMovTimeoutDef = {"setOffsetMovTimeout", 3,
setOffsetMovTimeoutArgs};
static void setOffsetMovTimeoutCallFunc(const iocshArgBuf *args) {
setOffsetMovTimeout(args[0].sval, args[1].ival, args[2].dval);
}
// =============================================================================
/**
* @brief Set the setScaleMovTimeout (FFI implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param scaleMovTimeout Scaling factor (in seconds)
* @return asynStatus
*/
asynStatus setScaleMovTimeout(const char *portName, int axisNo,
double scaleMovTimeout) {
sinqController *pC;
pC = (sinqController *)findAsynPortDriver(portName);
if (pC == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nPort %s not found.",
portName, __PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nAxis %d does not "
"exist or is not an "
"instance of sinqAxis.",
portName, __PRETTY_FUNCTION__, __LINE__, axisNo);
}
return axis->setScaleMovTimeout(scaleMovTimeout);
}
static const iocshArg setScaleMovTimeoutArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setScaleMovTimeoutArg1 = {"Axis number", iocshArgInt};
static const iocshArg setScaleMovTimeoutArg2 = {
"Multiplier for calculated move time", iocshArgDouble};
static const iocshArg *const setScaleMovTimeoutArgs[] = {
&setScaleMovTimeoutArg0, &setScaleMovTimeoutArg1, &setScaleMovTimeoutArg2};
static const iocshFuncDef setScaleMovTimeoutDef = {"setScaleMovTimeout", 3,
setScaleMovTimeoutArgs};
static void setScaleMovTimeoutCallFunc(const iocshArgBuf *args) {
setScaleMovTimeout(args[0].sval, args[1].ival, args[2].dval);
}
// =============================================================================
// This function is made known to EPICS in sinqMotor.dbd and is called by EPICS
// in order to register all functions in the IOC shell
static void sinqAxisRegister(void) {
iocshRegister(&setOffsetMovTimeoutDef, setOffsetMovTimeoutCallFunc);
iocshRegister(&setScaleMovTimeoutDef, setScaleMovTimeoutCallFunc);
iocshRegister(&setWatchdogEnabledDef, setWatchdogEnabledCallFunc);
}
epicsExportRegistrar(sinqAxisRegister);
} // extern C

View File

@ -18,33 +18,29 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
*/
sinqAxis(class sinqController *pC_, int axisNo);
/**
* @brief This function is executed once during the very first poll.
*
* This function is executed at the very first poll after the IOC startup.
If it returns anything else than 'asynSuccess', the function is evaluated
again after 100 ms until it succeeds. Every 10 trials a warning is emitted.
The default implementation just returns asynSuccess and is meant to be
overwritten by concrete driver implementations.
*
* @return asynStatus
*/
virtual asynStatus atFirstPoll();
/**
* @brief Perform some standardized operation before and after the concrete
`doPoll` implementation.
*
* Wrapper around doPoll which performs the following operations:
Before calling doPoll:
- Try to execute atFirstPoll once (and retry, if that failed)
After calling doPoll:
- Reset motorStatusProblem_, motorStatusCommsError_ and motorMessageText_ if
doPoll returned asynSuccess
- If the movement timeout watchdog has been started, check it.
- Update the parameter library entry motorEnableRBV_ by calling isEnabled.
- Run `callParamCallbacks`
- 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`.
@ -100,23 +96,42 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
double maxVelocity, double acceleration);
/**
* @brief Perform some standardized operation before and after the concrete
`doHome` implementation.
*
* Wrapper around move which calculates the (absolute) target position and
stores it in the parameter library. The target position in a homing maneuver
is calculated as follows:
* @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.
if abs(current position - high limit) > abs(current position - low limit)
{
high limit
}
else
{
low limit
}
* - `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.
After that, it calls and returns doHome.
* - 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`.
@ -131,7 +146,9 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
/**
* @brief Implementation of the "proper", device-specific home method. This
method should be implemented by a child class of sinqAxis.
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
@ -280,11 +297,13 @@ class epicsShareClass sinqAxis : public asynMotorAxis {
bool initial_poll_;
int init_poll_counter_;
// Helper variables for movementTimeoutWatchdog
// 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_;
private:
sinqController *pC_;

View File

@ -5,6 +5,32 @@
#include "iocsh.h"
#include "sinqAxis.h"
#include <errlog.h>
#include <vector>
/*
Contains all instances of turboPmacAxis which have been created and is used in
the initialization hook function.
*/
static std::vector<sinqController *> controller;
/**
* @brief Hook function to perform certain actions during the IOC initialization
*
* @param iState
*/
void sinqController::epicsInithookFunction(initHookState iState) {
if (iState == initHookAfterIocRunning) {
// Iterate through all axes of each and call the initialization method
// on each one of them.
for (std::vector<sinqController *>::iterator itC = controller.begin();
itC != controller.end(); ++itC) {
sinqController *controller = *itC;
controller->startPoller(controller->movingPollPeriod_,
controller->idlePollPeriod_, 1);
}
}
}
sinqController::sinqController(const char *portName,
const char *ipPortConfigName, int numAxes,
@ -15,15 +41,6 @@ sinqController::sinqController(const char *portName,
// As described in the function documentation, an offset of 1 is
// added for better readability of the configuration.
numAxes + 1,
/*
4 Parameters are added in sinqController:
- MOTOR_MESSAGE_TEXT
- MOTOR_TARGET_POSITION
- ENABLE_AXIS
- ENABLE_AXIS_RBV
- ENABLE_MOV_WATCHDOG
- LIMITS_OFFSET
*/
NUM_MOTOR_DRIVER_PARAMS + NUM_SINQMOTOR_DRIVER_PARAMS +
numExtraParams,
0, // No additional interfaces beyond those in base class
@ -40,6 +57,21 @@ sinqController::sinqController(const char *portName,
// Initialization of all member variables
lowLevelPortUser_ = nullptr;
// Initial values for the average timeout mechanism, can be overwritten
// later by a FFI function
comTimeoutWindow_ = 3600;
maxNumberTimeouts_ = 60;
timeoutEvents_ = {};
// Inform the user after 10 timeouts in a row (default value)
maxSubsequentTimeouts_ = 10;
maxSubsequentTimeoutsExceeded_ = false;
// Store the poll period information. The poller itself will be started
// later (after the IOC is running in epicsInithookFunction)
movingPollPeriod_ = movingPollPeriod;
idlePollPeriod_ = idlePollPeriod;
// =========================================================================;
/*
@ -48,10 +80,10 @@ sinqController::sinqController(const char *portName,
*/
pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL);
if (status != asynSuccess || lowLevelPortUser_ == nullptr) {
errlogPrintf(
"%s => line %d:\nFATAL ERROR (cannot connect to MCU controller).\n"
"Terminating IOC",
__PRETTY_FUNCTION__, __LINE__);
errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot "
"connect to MCU controller).\n"
"Terminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__);
exit(-1);
}
@ -64,38 +96,30 @@ sinqController::sinqController(const char *portName,
createParam("MOTOR_MESSAGE_TEXT", asynParamOctet, &motorMessageText_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
exit(-1);
}
// Internal parameter library entry which stores the movement target
status = createParam("MOTOR_TARGET_POSITION", asynParamFloat64,
&motorTargetPosition_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
status = createParam("MOTOR_ENABLE", asynParamInt32, &motorEnable_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
status = createParam("MOTOR_ENABLE_RBV", asynParamInt32, &motorEnableRBV_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
@ -103,9 +127,10 @@ sinqController::sinqController(const char *portName,
createParam("MOTOR_CAN_DISABLE", asynParamInt32, &motorCanDisable_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
@ -113,9 +138,10 @@ sinqController::sinqController(const char *portName,
createParam("MOTOR_CAN_SET_SPEED", asynParamInt32, &motorCanSetSpeed_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
@ -123,9 +149,10 @@ sinqController::sinqController(const char *portName,
&motorLimitsOffset_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
@ -138,9 +165,10 @@ sinqController::sinqController(const char *portName,
&motorHighLimitFromDriver_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
@ -148,9 +176,10 @@ sinqController::sinqController(const char *portName,
&motorLowLimitFromDriver_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
@ -158,9 +187,10 @@ sinqController::sinqController(const char *portName,
&motorEnableMovWatchdog_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
@ -168,9 +198,10 @@ sinqController::sinqController(const char *portName,
&motorVeloFromDriver_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
@ -178,9 +209,10 @@ sinqController::sinqController(const char *portName,
&motorVbasFromDriver_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
@ -188,9 +220,10 @@ sinqController::sinqController(const char *portName,
&motorVmaxFromDriver_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
@ -198,31 +231,40 @@ sinqController::sinqController(const char *portName,
&motorAcclFromDriver_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
status = createParam("ENCODER_TYPE", asynParamOctet, &encoderType_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (creating a parameter failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
// Poller configuration
status = startPoller(movingPollPeriod, idlePollPeriod, 1);
status = createParam("MOTOR_FORCE_STOP", asynParamInt32, &motorForceStop_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (starting the poller failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (creating a "
"parameter failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
exit(-1);
}
// Register the hook function during construction of the first axis object
if (controller.empty()) {
initHookRegister(&epicsInithookFunction);
}
// Collect all axes into this list which will be used in the hook function
controller.push_back(this);
}
sinqController::~sinqController(void) {
@ -241,8 +283,9 @@ asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not an instance of sinqAxis",
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not an "
"instance of sinqAxis",
portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
@ -255,27 +298,21 @@ asynStatus sinqController::writeInt32(asynUser *pasynUser, epicsInt32 value) {
}
asynStatus sinqController::readInt32(asynUser *pasynUser, epicsInt32 *value) {
// Casting into a sinqAxis is necessary to get access to the field axisNo_
asynMotorAxis *asynAxis = getAxis(pasynUser);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not an "
"instance of sinqAxis",
portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
if (pasynUser->reason == motorEnableRBV_) {
// Read out the parameter library
asynMotorAxis *asynAxis = getAxis(pasynUser);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not an instance of sinqAxis",
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
return asynError;
}
return getIntegerParam(axis->axisNo_, motorEnableRBV_, value);
} else if (pasynUser->reason == motorCanDisable_) {
// Check if the motor can be disabled
asynMotorAxis *asynAxis = getAxis(pasynUser);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not an instance of sinqAxis",
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
return asynError;
}
return getIntegerParam(axis->axisNo_, motorCanDisable_, value);
} else {
return asynMotorController::readInt32(pasynUser, value);
@ -290,21 +327,21 @@ asynStatus sinqController::errMsgCouldNotParseResponse(const char *command,
asynStatus pl_status = asynSuccess;
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\nCould not interpret response '%s' for "
"command %s.\n",
functionName, lineNumber, response, command);
"Controller \"%s\", axis %d => %s, line %d:\nCould not interpret "
"response \"%s\" for command \"%s\".\n",
portName, axisNo, functionName, lineNumber, response, command);
pl_status = setStringParam(
motorMessageText_,
"Could not interpret MCU response. Please call the support");
"Could not interpret controller response. Please call the support");
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_",
return paramLibAccessFailed(pl_status, "motorMessageText_", axisNo,
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(motorStatusCommsError_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
return paramLibAccessFailed(pl_status, "motorStatusCommsError_", axisNo,
__PRETTY_FUNCTION__, __LINE__);
}
@ -313,6 +350,7 @@ asynStatus sinqController::errMsgCouldNotParseResponse(const char *command,
asynStatus sinqController::paramLibAccessFailed(asynStatus status,
const char *parameter,
int axisNo,
const char *functionName,
int lineNumber) {
@ -320,9 +358,9 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status,
// Log the error message and try to propagate it. If propagating fails,
// there is nothing we can do here anyway.
asynPrint(lowLevelPortUser_, ASYN_TRACE_ERROR,
"%s => line %d:\n Accessing the parameter library failed for "
"parameter %s with error %s.\n",
functionName, lineNumber, parameter,
"Controller \"%s\", axis %d => %s, line %d:\n Accessing the "
"parameter library failed for parameter %s with error %s.\n",
portName, axisNo, functionName, lineNumber, parameter,
stringifyAsynStatus(status));
setStringParam(motorMessageText_,
"Accessing paramLib failed. Please call the support.");
@ -331,6 +369,125 @@ asynStatus sinqController::paramLibAccessFailed(asynStatus status,
return status;
}
asynStatus sinqController::checkComTimeoutWatchdog(int axisNo,
char *motorMessage,
size_t motorMessageSize) {
asynStatus paramLibStatus = asynSuccess;
// Add a new timeout event to the queue
timeoutEvents_.push_back(time(NULL));
// Remove every event which is older than the time window from the deque
while (1) {
if (timeoutEvents_.empty()) {
break;
}
if (timeoutEvents_[0] + comTimeoutWindow_ <= time(NULL)) {
timeoutEvents_.pop_front();
} else {
break;
}
}
// Check if the maximum allowed number of events has been exceeded
if (timeoutEvents_.size() > maxNumberTimeouts_) {
snprintf(motorMessage, motorMessageSize,
"More than %ld communication timeouts in %ld seconds. Please "
"call the support.",
maxNumberTimeouts_, comTimeoutWindow_);
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nMore than %ld "
"communication timeouts in %ld "
"seconds\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
maxNumberTimeouts_, comTimeoutWindow_);
paramLibStatus = setIntegerParam(motorStatusCommsError_, 1);
if (paramLibStatus != asynSuccess) {
return paramLibAccessFailed(paramLibStatus,
"motorStatusCommsError_", axisNo,
__PRETTY_FUNCTION__, __LINE__);
}
return asynError;
} else {
return asynSuccess;
}
}
asynStatus sinqController::checkComTimeoutWatchdog(sinqAxis *axis) {
char motorMessage[200] = {0};
asynStatus status =
checkComTimeoutWatchdog(axis->axisNo_, motorMessage, 200);
if (status == asynError) {
status = axis->setStringParam(motorMessageText_, motorMessage);
if (status != asynSuccess) {
return paramLibAccessFailed(status, "motorMessageText_",
axis->axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
return status;
}
asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo, int axisNo,
char *motorMessage,
size_t motorMessageSize) {
asynStatus paramLibStatus = asynSuccess;
if (timeoutNo >= maxSubsequentTimeouts_) {
if (!maxSubsequentTimeoutsExceeded_) {
snprintf(motorMessage, motorMessageSize,
"Communication timeout between IOC and motor controller. "
"Trying to reconnect ...");
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nMore than %d "
"subsequent communication "
"timeouts\n",
this->portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
maxSubsequentTimeouts_);
paramLibStatus = setIntegerParam(motorStatusCommsError_, 1);
if (paramLibStatus != asynSuccess) {
return paramLibAccessFailed(paramLibStatus,
"motorStatusCommsError_", axisNo,
__PRETTY_FUNCTION__, __LINE__);
}
maxSubsequentTimeoutsExceeded_ = true;
}
return asynError;
} else {
maxSubsequentTimeoutsExceeded_ = false;
motorMessage[0] = '\0';
return asynSuccess;
}
}
asynStatus sinqController::checkMaxSubsequentTimeouts(int timeoutNo,
sinqAxis *axis) {
char motorMessage[200] = {0};
asynStatus status =
checkMaxSubsequentTimeouts(axis->axisNo_, timeoutNo, motorMessage, 200);
if (status == asynError) {
status = axis->setStringParam(motorMessageText_, motorMessage);
if (status != asynSuccess) {
return paramLibAccessFailed(status, "motorMessageText_",
axis->axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
return status;
}
// 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)
@ -386,161 +543,131 @@ const char *sinqController::stringifyAsynStatus(asynStatus status) {
return asynParamInvalidListStringified;
}
asynPrint(
pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nInput did not match any variant of asynStatus.\n",
__PRETTY_FUNCTION__, __LINE__);
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
"%s, line %d:\nInput did not match any "
"variant of asynStatus.\n",
__PRETTY_FUNCTION__, __LINE__);
return inputDidNotMatchAsynStatus;
}
// =============================================================================
// Provide the setters to IOC shell
// IOC shell functions
extern "C" {
// =============================================================================
/**
* @brief Enable / disable the watchdog (FFI implementation)
* @brief Set the threshold for the communication timeout frequency (FFI
* implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param enable If 0, disable the watchdog, otherwise enable
* it
* @param comTimeoutWindow Size of the time window used to calculate
* the moving average of timeout events. Set this value to 0 to deactivate
* the watchdog.
* @param maxNumberTimeouts Maximum number of timeouts which may occur
* within the time window before the watchdog is triggered.
* @return asynStatus
*/
asynStatus setWatchdogEnabled(const char *portName, int axisNo, int enable) {
asynStatus setThresholdComTimeout(const char *portName, int comTimeoutWindow,
int maxNumberTimeouts) {
sinqController *pC;
pC = (sinqController *)findAsynPortDriver(portName);
if (pC == nullptr) {
errlogPrintf("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__,
__LINE__, portName);
errlogPrintf("Controller \"%s\" => %s, line %d:\nPort %s not found.",
portName, __PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf("%s => line %d:\nAxis %d does not exist or is not an "
"instance of sinqAxis.",
__PRETTY_FUNCTION__, __LINE__, axisNo);
}
return axis->setWatchdogEnabled(enable != 0);
return pC->setThresholdComTimeout(comTimeoutWindow, maxNumberTimeouts);
}
static const iocshArg setWatchdogEnabledArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setWatchdogEnabledArg1 = {"Axis number", iocshArgInt};
static const iocshArg setWatchdogEnabledArg2 = {
"Enabling / disabling the watchdog", iocshArgInt};
static const iocshArg *const setWatchdogEnabledArgs[] = {
&setWatchdogEnabledArg0, &setWatchdogEnabledArg1, &setWatchdogEnabledArg2};
static const iocshFuncDef setWatchdogEnabledDef = {"setWatchdogEnabled", 3,
setWatchdogEnabledArgs};
static const iocshArg setThresholdComTimeoutArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setThresholdComTimeoutArg1 = {
"Time window communication timeout frequency", iocshArgInt};
static const iocshArg setThresholdComTimeoutArg2 = {
"Maximum allowed number of communication timeouts within the window",
iocshArgInt};
static const iocshArg *const setThresholdComTimeoutArgs[] = {
&setThresholdComTimeoutArg0, &setThresholdComTimeoutArg1,
&setThresholdComTimeoutArg2};
static const iocshFuncDef setThresholdComTimeoutDef = {
"setThresholdComTimeout", 3, setThresholdComTimeoutArgs};
static void setWatchdogEnabledCallFunc(const iocshArgBuf *args) {
setWatchdogEnabled(args[0].sval, args[1].ival, args[2].ival);
static void setThresholdComTimeoutCallFunc(const iocshArgBuf *args) {
setThresholdComTimeout(args[0].sval, args[1].ival, args[2].ival);
}
// =============================================================================
/**
* @brief Set the offsetMovTimeout (FFI implementation)
* @brief Set the maximum number of subsequent timeouts (FFI implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param offsetMovTimeout Offset (in seconds)
* @param comTimeoutWindow Size of the time window used to calculate
* the moving average of timeout events. Set this value to 0 to deactivate
* the watchdog.
* @param maxNumberTimeouts Maximum number of timeouts which may occur
* within the time window before the watchdog is triggered.
* @return asynStatus
*/
asynStatus setOffsetMovTimeout(const char *portName, int axisNo,
double offsetMovTimeout) {
asynStatus setMaxSubsequentTimeouts(const char *portName,
int maxSubsequentTimeouts) {
void *ptr = findAsynPortDriver(portName);
if (ptr == nullptr) {
/*
We can't use asynPrint here since this macro would require us
to get a lowLevelPortUser_ from a pointer to an asynPortDriver.
However, the given pointer is a nullptr and therefore doesn't
have a lowLevelPortUser_! printf is an EPICS alternative which
works w/o that, but doesn't offer the comfort provided
by the asynTrace-facility
*/
errlogPrintf("Controller \"%s\" => %s, line %d:\nPort %s not found.",
portName, __PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
// Unsafe cast of the pointer to an asynPortDriver
asynPortDriver *apd = (asynPortDriver *)(ptr);
sinqController *pC;
pC = (sinqController *)findAsynPortDriver(portName);
// Safe downcast
sinqController *pC = dynamic_cast<sinqController *>(apd);
if (pC == nullptr) {
errlogPrintf("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__,
__LINE__, portName);
errlogPrintf(
"Controller \"%s\" => %s, line %d:\ncontroller on port %s is not a "
"turboPmacController.",
portName, __PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf("%s => line %d:\nAxis %d does not exist or is not an "
"instance of sinqAxis.",
__PRETTY_FUNCTION__, __LINE__, axisNo);
}
// Set the new value
pC->setMaxSubsequentTimeouts(maxSubsequentTimeouts);
return axis->setOffsetMovTimeout(offsetMovTimeout);
return asynSuccess;
}
static const iocshArg setOffsetMovTimeoutArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setOffsetMovTimeoutArg1 = {"Axis number", iocshArgInt};
static const iocshArg setOffsetMovTimeoutArg2 = {"Offset timeout for movement",
iocshArgDouble};
static const iocshArg *const setOffsetMovTimeoutArgs[] = {
&setOffsetMovTimeoutArg0, &setOffsetMovTimeoutArg1,
&setOffsetMovTimeoutArg2};
static const iocshFuncDef setOffsetMovTimeoutDef = {"setOffsetMovTimeout", 3,
setOffsetMovTimeoutArgs};
static void setOffsetMovTimeoutCallFunc(const iocshArgBuf *args) {
setOffsetMovTimeout(args[0].sval, args[1].ival, args[2].dval);
}
// =============================================================================
/**
* @brief Set the setScaleMovTimeout (FFI implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param scaleMovTimeout Scaling factor (in seconds)
* @return asynStatus
*/
asynStatus setScaleMovTimeout(const char *portName, int axisNo,
double scaleMovTimeout) {
sinqController *pC;
pC = (sinqController *)findAsynPortDriver(portName);
if (pC == nullptr) {
errlogPrintf("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__,
__LINE__, portName);
return asynError;
}
asynMotorAxis *asynAxis = pC->getAxis(axisNo);
sinqAxis *axis = dynamic_cast<sinqAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf("%s => line %d:\nAxis %d does not exist or is not an "
"instance of sinqAxis.",
__PRETTY_FUNCTION__, __LINE__, axisNo);
}
return axis->setScaleMovTimeout(scaleMovTimeout);
}
static const iocshArg setScaleMovTimeoutArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setScaleMovTimeoutArg1 = {"Axis number", iocshArgInt};
static const iocshArg setScaleMovTimeoutArg2 = {
"Multiplier for calculated move time", iocshArgDouble};
static const iocshArg *const setScaleMovTimeoutArgs[] = {
&setScaleMovTimeoutArg0, &setScaleMovTimeoutArg1, &setScaleMovTimeoutArg2};
static const iocshFuncDef setScaleMovTimeoutDef = {"setScaleMovTimeout", 3,
setScaleMovTimeoutArgs};
static void setScaleMovTimeoutCallFunc(const iocshArgBuf *args) {
setScaleMovTimeout(args[0].sval, args[1].ival, args[2].dval);
static const iocshArg SetMaxSubsequentTimeoutsArg0 = {
"Controller name (e.g. mcu1)", iocshArgString};
static const iocshArg SetMaxSubsequentTimeoutsArg1 = {
"Maximum number of subsequent timeouts before the user receives an error "
"message",
iocshArgInt};
static const iocshArg *const SetMaxSubsequentTimeoutsArgs[] = {
&SetMaxSubsequentTimeoutsArg0, &SetMaxSubsequentTimeoutsArg1};
static const iocshFuncDef setMaxSubsequentTimeoutsDef = {
"setMaxSubsequentTimeouts", 2, SetMaxSubsequentTimeoutsArgs};
static void setMaxSubsequentTimeoutsCallFunc(const iocshArgBuf *args) {
setMaxSubsequentTimeouts(args[0].sval, args[1].ival);
}
// =============================================================================
// This function is made known to EPICS in sinqMotor.dbd and is called by EPICS
// in order to register all functions in the IOC shell
static void sinqControllerRegister(void) {
iocshRegister(&setOffsetMovTimeoutDef, setOffsetMovTimeoutCallFunc);
iocshRegister(&setScaleMovTimeoutDef, setScaleMovTimeoutCallFunc);
iocshRegister(&setWatchdogEnabledDef, setWatchdogEnabledCallFunc);
iocshRegister(&setThresholdComTimeoutDef, setThresholdComTimeoutCallFunc);
iocshRegister(&setMaxSubsequentTimeoutsDef,
setMaxSubsequentTimeoutsCallFunc);
}
epicsExportRegistrar(sinqControllerRegister);

View File

@ -1,5 +1,6 @@
/*
This class extends asynMotorController by some features used in SINQ.
This class extends asynMotorController by some features used in SINQ. See the
README.md for details.
Stefan Mathis, November 2024
*/
@ -7,6 +8,8 @@ Stefan Mathis, November 2024
#ifndef __sinqController
#define __sinqController
#include "asynMotorController.h"
#include <deque>
#include <initHooks.h>
#define motorMessageIsFromDriverString "MOTOR_MESSAGE_DRIVER"
#define motorMessageTextString "MOTOR_MESSAGE_TEXT"
@ -90,7 +93,8 @@ class epicsShareClass sinqController : public asynMotorController {
* @return asynStatus Returns input status.
*/
asynStatus paramLibAccessFailed(asynStatus status, const char *parameter,
const char *functionName, int lineNumber);
int axisNo, const char *functionName,
int lineNumber);
/**
* @brief Error handling in case parsing a command response failed.
@ -122,20 +126,122 @@ class epicsShareClass sinqController : public asynMotorController {
*/
const char *stringifyAsynStatus(asynStatus status);
/**
* @brief This function should be called when a communication timeout
occured. It calculates the frequency of communication timeout events and
creates an error message, if an threshold has been exceeded.
*
Occasionally, communication timeouts between the IOC and the motor
controller may happen, usually because the controller takes too long to
respond. If this happens infrequently, this is not a problem. However, if it
happens very often, this may indicate a network problem and must therefore
be forwarded to the user. This is checked by calculating the moving average
of events and comparing it to a threshhold. Both the threshold and the time
window for the moving average can be configured in the IOC via the function
setThresholdCom.
This function exists in two variants: Either the error message can be
written into a buffer provided by the caller or it written directly into the
parameter library of the provided axis.
* @param axis Axis to which the error message is sent
*
* @return asynStatus asynError, if the threshold has been
exceeded, asynSuccess otherwise
*/
virtual asynStatus checkComTimeoutWatchdog(class sinqAxis *axis);
/**
* @brief See documentation of checkComTimeoutWatchdog(sinqAxis * axis)
*
* @param userMessage Buffer for the user message
* @param userMessageSize Buffer size in chars
* @return asynStatus
*/
virtual asynStatus checkComTimeoutWatchdog(int axisNo, char *motorMessage,
size_t motorMessageSize);
/**
* @brief Set the threshold for the communication timeout mechanism
*
* @param comTimeoutWindow Size of the time window used to calculate
* the moving average of timeout events. Set this value to 0 to deactivate
* the watchdog.
* @param maxNumberTimeouts Maximum number of timeouts which may occur
* within the time window before the watchdog is triggered.
* @return asynStatus
*/
virtual asynStatus setThresholdComTimeout(time_t comTimeoutWindow,
size_t maxNumberTimeouts) {
comTimeoutWindow_ = comTimeoutWindow;
maxNumberTimeouts_ = maxNumberTimeouts;
return asynSuccess;
}
/**
* @brief Inform the user, if the number of timeouts exceeds the threshold
* specified with setMaxSubsequentTimeouts
*
* @param timeoutNo Number of subsequent timeouts which already
* happened.
* @param axis
* @return asynStatus
*/
virtual asynStatus checkMaxSubsequentTimeouts(int timeoutNo,
class sinqAxis *axis);
/**
* @brief See documentation of checkMaxSubsequentTimeouts(sinqAxis * axis)
*
* @param userMessage Buffer for the user message
* @param userMessageSize Buffer size in chars
* @return asynStatus
*/
virtual asynStatus checkMaxSubsequentTimeouts(int timeoutNo, int axisNo,
char *motorMessage,
size_t motorMessageSize);
/**
* @brief Set the maximum number of subsequent timeouts before the user is
* informed.
*
* @param maxSubsequentTimeouts
* @return asynStatus
*/
asynStatus setMaxSubsequentTimeouts(int maxSubsequentTimeouts) {
maxSubsequentTimeouts_ = maxSubsequentTimeouts;
return asynSuccess;
}
friend class sinqAxis;
protected:
asynUser *lowLevelPortUser_;
double movingPollPeriod_;
double idlePollPeriod_;
// 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_;
#define FIRST_SINQMOTOR_PARAM motorMessageText_
int motorMessageText_;
int motorTargetPosition_;
int motorEnable_;
int motorEnableRBV_;
int motorCanDisable_;
int motorEnableMovWatchdog_;
int motorCanSetSpeed_;
int motorLimitsOffset_;
int motorForceStop_;
/*
These parameters are here to write values from the hardware to the EPICS
motor record. Using motorHighLimit_ / motorLowLimit_ does not work:
@ -151,6 +257,9 @@ class epicsShareClass sinqController : public asynMotorController {
int motorLowLimitFromDriver_;
int encoderType_;
#define LAST_SINQMOTOR_PARAM encoderType_
private:
static void epicsInithookFunction(initHookState iState);
};
#define NUM_SINQMOTOR_DRIVER_PARAMS \
(&LAST_SINQMOTOR_PARAM - &FIRST_SINQMOTOR_PARAM + 1)

View File

@ -2,3 +2,4 @@
# SINQ specific DB definitions
#---------------------------------------------
registrar(sinqControllerRegister)
registrar(sinqAxisRegister)