Files
detectortower/src/detectorTowerAngleAxis.cpp

718 lines
28 KiB
C++

#include "detectorTowerAngleAxis.h"
#include "detectorTowerController.h"
#include "turboPmacController.h"
#include <epicsExport.h>
#include <errlog.h>
#include <iocsh.h>
/*
Contains all instances of detectorTowerAngleAxis which have been created and is
used in the initialization hook function.
*/
static std::vector<detectorTowerAngleAxis *> axes;
/**
* @brief Hook function to perform certain actions during the IOC initialization
*
* @param iState
*/
static void epicsInithookFunction(initHookState iState) {
if (iState == initHookAfterDatabaseRunning) {
// Iterate through all axes of each and call the initialization method
// on each one of them.
for (std::vector<detectorTowerAngleAxis *>::iterator itA = axes.begin();
itA != axes.end(); ++itA) {
detectorTowerAngleAxis *axis = *itA;
axis->init();
}
}
}
static void deferredMovementCollectorLoop(void *drvPvt) {
detectorTowerAngleAxis *axis = (detectorTowerAngleAxis *)drvPvt;
while (1) {
if (axis->receivedTarget_) {
// Wait for 100 ms and then start the movement with the information
// available
axis->startingDeferredMovement_ = true;
epicsThreadSleep(axis->deferredMovementWait_);
axis->startCombinedMove();
// After the movement command has been send, reset the flag
axis->receivedTarget_ = false;
}
// Limit this loop to an idle frequency of 1 kHz
epicsThreadSleep(0.001);
}
}
detectorTowerAngleAxis::detectorTowerAngleAxis(detectorTowerController *pC,
int axisNo)
: turboPmacAxis(pC, axisNo, false), pC_(pC) {
/*
The superclass constructor sinqAxis calls in turn its superclass constructor
asynMotorAxis. In the latter, a pointer to the constructed object this is
stored inside the array pAxes_:
pC->pAxes_[axisNo] = this;
Therefore, the axes are managed by the controller pC. If axisNo is out of
bounds, asynMotorAxis prints an error (see
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorAxis.cpp,
line 40). However, we want the IOC creation to stop completely, since this
is a configuration error.
*/
if (axisNo >= pC->numAxes()) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d: FATAL ERROR: "
"Axis index %d must be smaller than the total number of axes "
"%d",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
axisNo_, pC->numAxes());
exit(-1);
}
// Initialize all member variables
// Assumed to be not ready by default, this is overwritten in the next poll
error_ = 0;
receivedTarget_ = false;
startingDeferredMovement_ = false;
deferredMovementWait_ = 0.1; // seconds
// Will be populated in the init() method
beamRadius_ = 0.0;
// Register the hook function during construction of the first axis object
if (axes.empty()) {
initHookRegister(&epicsInithookFunction);
}
// Collect all axes into this list which will be used in the hook
// function
axes.push_back(this);
// Create a thread which collects all movement commands send to components
// of the virtual axis. After a component received a new target position,
// it forwards this information to the thread. The thread then waits a short
// time to see if other components also received new targets and starts the
// movement with all targets afterwards.
epicsThreadCreate("deferredMovement", epicsThreadPriorityLow,
epicsThreadGetStackSize(epicsThreadStackMedium),
(EPICSTHREADFUNC)deferredMovementCollectorLoop,
(void *)this);
}
detectorTowerAngleAxis::~detectorTowerAngleAxis(void) {
// Since the controller memory is managed somewhere else, we don't need to
// clean up the pointer pC here.
}
asynStatus detectorTowerAngleAxis::init() {
// Local variable declaration
asynStatus status = asynSuccess;
double motorRecResolution = 0.0;
char response[pC_->MAXBUF_] = {0};
int positionState = 0;
int nvals = 0;
// The parameter library takes some time to be initialized. Therefore we
// wait until the status is not asynParamUndefined anymore.
time_t now = time(NULL);
time_t maxInitTime = 60;
while (1) {
status = pC_->getDoubleParam(axisNo_, pC_->motorRecResolution(),
&motorRecResolution);
if (status == asynParamUndefined) {
if (now + maxInitTime < time(NULL)) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis "
"%ddeferredMovementCollectorLoop => %s, line "
"%d\nInitializing the parameter library failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__);
return asynError;
}
} else if (status == asynSuccess) {
break;
} else if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
// Read the detector radius
const char *command = "P459 P358";
status = pC_->writeRead(axisNo_, command, response, 2);
if (status != asynSuccess) {
return status;
}
nvals = sscanf(response, "%lf %d", &beamRadius_, &positionState);
if (nvals != 2) {
return pC_->couldNotParseResponse(command, response, axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Initialize the motorStatusMoving flag
status = setIntegerParam(pC_->motorStatusMoving(), 0);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorStatusMoving_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
status = setIntegerParam(pC_->changeStateRBV(), positionState == 2);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "changeStateRBV", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
return callParamCallbacks();
}
// Perform the actual poll
asynStatus detectorTowerAngleAxis::poll(bool *moving) {
asynStatus status = asynSuccess;
// Is this axis the one with the smallest index?
// If not, just read out the movement status and update *moving
if (axisNo() < liftAxis()->axisNo() && axisNo() < supportAxis()->axisNo()) {
status = pC_->pollDetectorAxes(moving, this, liftAxis(), supportAxis());
} else {
status = pC_->getIntegerParam(axisNo(), pC_->motorStatusMoving(),
(int *)moving);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorStatusMoving",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
wasMoving_ = *moving;
return status;
}
asynStatus detectorTowerAngleAxis::doPoll(bool *moving) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nThe doPoll method "
"of this axis type should not be reachable. This is a bug.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
asynStatus detectorTowerAngleAxis::doMove(double position, int relative,
double min_velocity,
double max_velocity,
double acceleration) {
double motorRecResolution = 0.0;
asynStatus plStatus = pC_->getDoubleParam(
axisNo_, pC_->motorRecResolution(), &motorRecResolution);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "motorRecResolution_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Signal to the deferredMovementCollectorLoop that a movement should be
// started to the defined target position.
targetPosition_ = position * motorRecResolution;
receivedTarget_ = true;
return asynSuccess;
}
asynStatus detectorTowerAngleAxis::startCombinedMove() {
// Status of read-write-operations of ASCII commands to the controller
asynStatus rwStatus = asynSuccess;
// Status of parameter library operations
asynStatus plStatus = asynSuccess;
char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0};
double motorCoordinatesPosition = 0.0;
int positionState = 0;
// =========================================================================
plStatus =
pC_->getIntegerParam(axisNo_, pC_->positionStateRBV(), &positionState);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "positionStateRBV_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// If the axis is in changer position, it must be moved into working
// position before any move can be started.
bool isInChangerPos = positionState == 2 || positionState == 3;
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
isInChangerPos, pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis cannot be "
"moved because it is moving from working to changer "
"position, is in changer position or is moving from changer "
"to working position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
if (isInChangerPos) {
plStatus = setStringParam(pC_->motorMessageText(),
"Move the axis to working state first.");
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "motorMessageText",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
plStatus = setIntegerParam(pC_->motorStatusProblem(), true);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
callParamCallbacks();
startingDeferredMovement_ = false;
return asynError;
}
// Set the target positions for beam tilt, detector tilt offset and lift
// offset
snprintf(command, sizeof(command), "Q451=%lf Q454=%lf P350=1",
targetPosition_, liftAxis_->targetPosition_);
// Lock the access to the controller since this function runs in another
// thread than the poll method.
pC_->lock();
// We don't expect an answer
rwStatus = pC_->writeRead(axisNo_, command, response, 0);
// Free the controller again
pC_->unlock();
if (rwStatus != asynSuccess) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStarting movement to "
"target position %lf failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
motorCoordinatesPosition);
plStatus = setIntegerParam(pC_->motorStatusProblem(), true);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
callParamCallbacks();
}
return rwStatus;
}
asynStatus detectorTowerAngleAxis::stop(double acceleration) {
// Status of read-write-operations of ASCII commands to the controller
asynStatus rwStatus = asynSuccess;
// Status of parameter library operations
asynStatus plStatus = asynSuccess;
char response[pC_->MAXBUF_] = {0};
// =========================================================================
rwStatus = pC_->writeRead(axisNo_, "P350=8", response, 0);
if (rwStatus != asynSuccess) {
asynPrint(
pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nStopping the movement "
"failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
plStatus = setIntegerParam(pC_->motorStatusProblem(), true);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return asynError;
}
return rwStatus;
}
// The detector tower axis uses absolute encoders
asynStatus detectorTowerAngleAxis::readEncoderType() {
asynStatus status = setStringParam(pC_->encoderType(), AbsoluteEncoder);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "encoderType_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
return asynSuccess;
}
asynStatus
detectorTowerAngleAxis::toggleWorkingChangerState(bool toChangingPosition) {
char response[pC_->MAXBUF_] = {0};
// Status of read-write-operations of ASCII commands to the controller
asynStatus rwStatus = asynSuccess;
// Status of parameter library operations
asynStatus plStatus = asynSuccess;
bool moving = false;
int positionState = 0;
// =========================================================================
rwStatus = poll(&moving);
if (rwStatus != asynSuccess) {
return rwStatus;
}
plStatus =
pC_->getIntegerParam(axisNo_, pC_->positionStateRBV(), &positionState);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "positionStateRBV_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
if (moving) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is not "
"idle and can therefore not be moved to %s state.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
toChangingPosition ? "changer" : "working",
pC_->getMsgPrintControl().getSuffix());
plStatus = setStringParam(
pC_->motorMessageText(),
"Axis cannot be moved to changer position while it is moving.");
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
plStatus = setIntegerParam(pC_->changeStateRBV(), !toChangingPosition);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "changeStateRBV",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return asynError;
}
// Axis is already in the correct position
bool isAlreadyThere = (toChangingPosition == false && positionState == 1) ||
(toChangingPosition == true && positionState == 2);
if (isAlreadyThere) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nAxis is already "
"in %s position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
toChangingPosition ? "changer" : "working",
pC_->getMsgPrintControl().getSuffix());
// Update the PV anyway, even though nothing changed.
plStatus = setIntegerParam(pC_->changeStateRBV(), toChangingPosition);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "changeStateRBV",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return asynSuccess;
}
// Move the axis into changer or working position
if (toChangingPosition) {
rwStatus = pC_->writeRead(axisNo_, "P350=2", response, 0);
} else {
rwStatus = pC_->writeRead(axisNo_, "P350=3", response, 0);
}
if (plStatus != asynSuccess) {
plStatus = setIntegerParam(pC_->changeStateRBV(), !toChangingPosition);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "changeStateRBV",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
plStatus = setIntegerParam(pC_->changeStateRBV(), toChangingPosition);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "changeStateRBV", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
return rwStatus;
}
asynStatus detectorTowerAngleAxis::adjustOrigin(double newOrigin) {
asynStatus status = asynSuccess;
char command[pC_->MAXBUF_] = {0};
char response[pC_->MAXBUF_] = {0};
int positionState = 0;
// =========================================================================
status =
pC_->getIntegerParam(axisNo_, pC_->positionStateRBV(), &positionState);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "positionStateRBV_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// If the axis is in changer position, it must be moved into working
// position before any move can be started.
bool isInChangerPos = positionState == 2 || positionState == 3;
if (pC_->getMsgPrintControl().shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
isInChangerPos, pC_->pasynUser())) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis cannot be "
"moved because it is moving from working to changer "
"position, is in changer position or is moving from changer "
"to working position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->getMsgPrintControl().getSuffix());
}
// Set the new origin for the angle axis
snprintf(command, sizeof(command), "Q556=%lf P350=4", newOrigin);
// We don't expect an answer
status = pC_->writeRead(axisNo_, command, response, 0);
if (status != asynSuccess) {
asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nSetting new "
"angle origin %lf failed.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
newOrigin);
status = setIntegerParam(pC_->motorStatusProblem(), true);
if (status != asynSuccess) {
return pC_->paramLibAccessFailed(status, "motorStatusProblem_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
}
return status;
}
asynStatus detectorTowerAngleAxis::doReset() {
char response[pC_->MAXBUF_] = {0};
asynStatus plStatus = asynSuccess;
int positionState = 0;
plStatus =
pC_->getIntegerParam(axisNo_, pC_->positionStateRBV(), &positionState);
if (plStatus != asynSuccess) {
return pC_->paramLibAccessFailed(plStatus, "positionStateRBV_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
/*
Check which action should be performed:
- If error_ == 10 or 11 (FTZ motor error): P352 = 3 (Recover FTZ)
- If any other error: P352 = 2 (Reset error)
- Otherwise: P352 = 1 (Set axis in closed-loop mode)
*/
if (error_ == 10 || error_ == 11) {
return pC_->writeRead(axisNo_, "P352=3", response, 0);
} else if (error_ != 0) {
return pC_->writeRead(axisNo_, "P352=2", response, 0);
} else {
return pC_->writeRead(axisNo_, "P352=1", response, 0);
}
}
/*************************************************************************************/
/** The following functions are C-wrappers, and can be called directly from
* iocsh */
extern "C" {
/*
C wrapper for the axis constructor. Please refer to the detectorTower
constructor documentation. The controller is read from the portName.
*/
asynStatus detectorTowerCreateAxis(const char *portName, int angleAxisIdx,
int liftAxisIdx, int supportAxisIdx) {
/*
findAsynPortDriver is a asyn library FFI function which uses the C ABI.
Therefore it returns a void pointer instead of e.g. a pointer to a
superclass of the controller such as asynPortDriver. Type-safe upcasting
via dynamic_cast is therefore not possible directly. However, we do know
that the void pointer is either a pointer to asynPortDriver (if a driver
with the specified name exists) or a nullptr. Therefore, we first do a
nullptr check, then a cast to asynPortDriver and lastly a (typesafe)
dynamic_upcast to Controller
https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c
*/
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'taxis
works w/o that, but doesn't offer the comfort provided
by the asynTrace-facility
*/
errlogPrintf("Controller \"%s\" => %s, line %d\nPort not found.",
portName, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
// Unsafe cast of the pointer to an asynPortDriver
asynPortDriver *apd = (asynPortDriver *)(ptr);
// Safe downcast
detectorTowerController *pC = dynamic_cast<detectorTowerController *>(apd);
if (pC == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d\nController "
"is not a detectorTowerController.",
portName, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
// Assert that the three indices are different from each other
if (angleAxisIdx == liftAxisIdx) {
errlogPrintf("Controller \"%s\" => %s, line %d\nAll axis indices "
"must be unique.",
portName, __PRETTY_FUNCTION__, __LINE__);
return asynError;
}
// Prevent manipulation of the controller from other threads while we
// create the new axis.
pC->lock();
/*
We create a new instance of the axis, using the "new" keyword to
allocate it on the heap while avoiding RAII.
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
The created object is registered in EPICS in its constructor and can safely
be "leaked" here.
*/
detectorTowerAngleAxis *angleAxis =
new detectorTowerAngleAxis(pC, angleAxisIdx);
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
detectorTowerLiftAxis *liftAxis =
new detectorTowerLiftAxis(pC, liftAxisIdx, angleAxis);
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
detectorTowerSupportAxis *supportAxis =
new detectorTowerSupportAxis(pC, supportAxisIdx, angleAxis);
// Allow manipulation of the controller again
pC->unlock();
return asynSuccess;
}
static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mcu1)",
iocshArgString};
static const iocshArg CreateAxisArg1 = {"Number of the detector angle axis",
iocshArgInt};
static const iocshArg CreateAxisArg2 = {"Number of the lift axis", iocshArgInt};
static const iocshArg CreateAxisArg3 = {"Number of the support axis",
iocshArgInt};
static const iocshArg *const CreateAxisArgs[] = {
&CreateAxisArg0,
&CreateAxisArg1,
&CreateAxisArg2,
&CreateAxisArg3,
};
static const iocshFuncDef configDetectorTowerCreateAxis = {"detectorTowerAxis",
4, CreateAxisArgs};
static void configDetectorTowerCreateAxisCallFunc(const iocshArgBuf *args) {
detectorTowerCreateAxis(args[0].sval, args[1].ival, args[2].ival,
args[3].ival);
}
// =============================================================================
/**
* @brief Set the setDeferredMovementWait (FFI implementation)
*
* @param portName Name of the controller
* @param axisNo Axis number
* @param scaleMovTimeout Scaling factor (in seconds)
* @return asynStatus
*/
asynStatus setDeferredMovementWait(const char *portName, int axisNo,
double deferredMovementWait) {
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);
detectorTowerAngleAxis *axis =
dynamic_cast<detectorTowerAngleAxis *>(asynAxis);
if (axis == nullptr) {
errlogPrintf("Controller \"%s\" => %s, line %d:\nAxis %d does not "
"exist or is not an instance of detectorTowerAngleAxis.",
portName, __PRETTY_FUNCTION__, __LINE__, axisNo);
return asynError;
}
axis->deferredMovementWait_ = deferredMovementWait;
return asynSuccess;
}
static const iocshArg setDeferredMovementWaitArg0 = {"Controller port name",
iocshArgString};
static const iocshArg setDeferredMovementWaitArg1 = {"Axis number",
iocshArgInt};
static const iocshArg setDeferredMovementWaitArg2 = {
"Minimum time a deferred movement will be delayed in order to collect "
"commands in seconds",
iocshArgDouble};
static const iocshArg *const setDeferredMovementWaitArgs[] = {
&setDeferredMovementWaitArg0, &setDeferredMovementWaitArg1,
&setDeferredMovementWaitArg2};
static const iocshFuncDef setDeferredMovementWaitDef = {
"setDeferredMovementWait", 3, setDeferredMovementWaitArgs};
static void setDeferredMovementWaitCallFunc(const iocshArgBuf *args) {
setDeferredMovementWait(args[0].sval, args[1].ival, args[2].dval);
}
// =============================================================================
// This function is made known to EPICS in detectorTower.dbd and is
// called by EPICS in order to register both functions in the IOC shell
static void detectorTowerAxisRegister(void) {
iocshRegister(&configDetectorTowerCreateAxis,
configDetectorTowerCreateAxisCallFunc);
iocshRegister(&setDeferredMovementWaitDef, setDeferredMovementWaitCallFunc);
}
epicsExportRegistrar(detectorTowerAxisRegister);
} // extern "C"