Current status of the detector tower.

This commit is contained in:
2025-03-10 14:27:19 +01:00
parent bb492e678b
commit 3a3519fbaa
9 changed files with 249 additions and 156 deletions

View File

@@ -1,4 +1,4 @@
#include "offsetAxis.h"
#include "auxiliaryAxis.h"
#include "detectorTowerAxis.h"
#include "detectorTowerController.h"
#include "turboPmacController.h"
@@ -7,10 +7,10 @@
#include <iocsh.h>
/*
Contains all instances of offsetAxis which have been created and is used
Contains all instances of auxiliaryAxis which have been created and is used
in the initialization hook function.
*/
static std::vector<offsetAxis *> axes;
static std::vector<auxiliaryAxis *> axes;
/**
* @brief Hook function to perform certain actions during the IOC initialization
@@ -21,15 +21,15 @@ 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<offsetAxis *>::iterator itA = axes.begin();
for (std::vector<auxiliaryAxis *>::iterator itA = axes.begin();
itA != axes.end(); ++itA) {
offsetAxis *axis = *itA;
auxiliaryAxis *axis = *itA;
axis->init();
}
}
}
offsetAxis::offsetAxis(detectorTowerController *pC, int axisNo)
auxiliaryAxis::auxiliaryAxis(detectorTowerController *pC, int axisNo)
: turboPmacAxis(pC, axisNo, false), pC_(pC) {
/*
@@ -69,12 +69,12 @@ offsetAxis::offsetAxis(detectorTowerController *pC, int axisNo)
axes.push_back(this);
}
offsetAxis::~offsetAxis(void) {
auxiliaryAxis::~auxiliaryAxis(void) {
// Since the controller memory is managed somewhere else, we don't need to
// clean up the pointer pC here.
}
asynStatus offsetAxis::init() {
asynStatus auxiliaryAxis::init() {
// Local variable declaration
asynStatus status = asynSuccess;
@@ -108,7 +108,7 @@ asynStatus offsetAxis::init() {
}
// Perform the actual poll
asynStatus offsetAxis::doPoll(bool *moving) {
asynStatus auxiliaryAxis::doPoll(bool *moving) {
// Return value for the poll
asynStatus poll_status = asynSuccess;
@@ -164,14 +164,32 @@ asynStatus offsetAxis::doPoll(bool *moving) {
__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();
bool wantToPrint = pl_status != asynSuccess;
if (pC_->msgPrintControl_.shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, wantToPrint,
pC_->pasynUserSelf)) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\ncallParamCallbacks failed with %s.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->stringifyAsynStatus(poll_status),
pC_->msgPrintControl_.getSuffix());
}
if (wantToPrint) {
poll_status = pl_status;
}
// The limits are written into this class instance inside the doPoll
// function of detectorTowerAxis
return poll_status;
}
asynStatus offsetAxis::doMove(double position, int relative,
double min_velocity, double max_velocity,
double acceleration) {
asynStatus auxiliaryAxis::doMove(double position, int relative,
double min_velocity, double max_velocity,
double acceleration) {
double motorRecResolution = 0.0;
asynStatus pl_status = pC_->getDoubleParam(
@@ -190,7 +208,7 @@ asynStatus offsetAxis::doMove(double position, int relative,
return asynSuccess;
}
asynStatus offsetAxis::stop(double acceleration) {
asynStatus auxiliaryAxis::stop(double acceleration) {
// Status of read-write-operations of ASCII commands to the controller
asynStatus rw_status = asynSuccess;

View File

@@ -1,5 +1,5 @@
#ifndef offsetAxis_H
#define offsetAxis_H
#ifndef auxiliaryAxis_H
#define auxiliaryAxis_H
#include "turboPmacAxis.h"
// Forward declaration of the controller class to resolve the cyclic dependency
@@ -8,7 +8,7 @@
class detectorTowerController;
class detectorTowerAxis;
class offsetAxis : public turboPmacAxis {
class auxiliaryAxis : public turboPmacAxis {
public:
/**
* @brief Construct a new detectorTowerAxis
@@ -16,13 +16,13 @@ class offsetAxis : public turboPmacAxis {
* @param pController Pointer to the associated controller
* @param axisNo Index of the axis
*/
offsetAxis(detectorTowerController *pController, int axisNo);
auxiliaryAxis(detectorTowerController *pController, int axisNo);
/**
* @brief Destroy the turboPmacAxis
*
*/
virtual ~offsetAxis();
virtual ~auxiliaryAxis();
/**
* @brief Readout of some values from the controller at IOC startup

View File

@@ -33,20 +33,23 @@ static void deferredMovementCollectorLoop(void *drvPvt) {
detectorTowerAxis *axis = (detectorTowerAxis *)drvPvt;
while (1) {
if (axis->receivedTarget_) {
// Wait for 50 ms and then start the movement with the information
// Wait for 100 ms and then start the movement with the information
// available
epicsThreadSleep(0.05);
axis->startingDeferredMovement_ = true;
epicsThreadSleep(0.1);
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);
}
}
detectorTowerAxis::detectorTowerAxis(detectorTowerController *pC, int axisNo,
offsetAxis *liftOffsetAxis,
offsetAxis *tiltOffsetAxis)
auxiliaryAxis *liftOffsetAxis,
auxiliaryAxis *tiltOffsetAxis)
: turboPmacAxis(pC, axisNo, false), pC_(pC) {
/*
@@ -77,6 +80,7 @@ detectorTowerAxis::detectorTowerAxis(detectorTowerController *pC, int axisNo,
error_ = 0;
targetPosition_ = 0.0;
receivedTarget_ = false;
startingDeferredMovement_ = false;
liftOffsetAxis_ = liftOffsetAxis;
tiltOffsetAxis_ = tiltOffsetAxis;
liftOffsetAxis->dTA_ = this;
@@ -248,18 +252,32 @@ asynStatus detectorTowerAxis::doPoll(bool *moving) {
// Store the axis status
error_ = error;
// Update the enablement PV
pl_status = setIntegerParam(pC_->motorEnableRBV_, (positionState != 2));
// Update the working position state PV
pl_status = setIntegerParam(pC_->positionStateRBV_, positionState);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "positionStateRBV_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Axis is always enabled
pl_status = setIntegerParam(pC_->motorEnableRBV_, 1);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorEnableRBV_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
}
// Check if the motor is moving
if (inPosition == 0) {
*moving = false;
} else {
if (inPosition == 1) {
// By now, the controller has actually started the movement
startingDeferredMovement_ = false;
*moving = true;
} else {
if (startingDeferredMovement_) {
*moving = true;
} else {
*moving = false;
}
}
// Create the unique callsite identifier manually so it can be used later in
@@ -282,17 +300,28 @@ asynStatus detectorTowerAxis::doPoll(bool *moving) {
if (pC_->msgPrintControl_.shouldBePrinted(keyPosState, true,
pC_->pasynUserSelf)) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nAxis in change "
"position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->msgPrintControl_.getSuffix());
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nAxis moving "
"to or in change "
"position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->msgPrintControl_.getSuffix());
}
resetCountPosState = false;
break;
case 3:
if (pC_->msgPrintControl_.shouldBePrinted(keyPosState, true,
pC_->pasynUserSelf)) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_FLOW,
"Controller \"%s\", axis %d => %s, line %d\nAxis moving "
"to working position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->msgPrintControl_.getSuffix());
}
resetCountPosState = false;
break;
default:
*moving = false;
if (pC_->msgPrintControl_.shouldBePrinted(keyPosState, true,
pC_->pasynUserSelf)) {
@@ -318,7 +347,7 @@ asynStatus detectorTowerAxis::doPoll(bool *moving) {
}
if (resetCountPosState) {
pC_->msgPrintControl_.resetCount(keyPosState);
pC_->msgPrintControl_.resetCount(keyPosState, pC_->pasynUserSelf);
}
if (*moving) {
@@ -637,7 +666,7 @@ asynStatus detectorTowerAxis::doPoll(bool *moving) {
}
if (resetError) {
pC_->msgPrintControl_.resetCount(keyError);
pC_->msgPrintControl_.resetCount(keyError, pC_->pasynUserSelf);
}
// Update the parameter library
@@ -723,12 +752,16 @@ asynStatus detectorTowerAxis::doPoll(bool *moving) {
}
int tiltAxisNo = tiltOffsetAxis_->axisNo_;
pl_status = pC_->setDoubleParam(tiltAxisNo, pC_->motorPosition_,
tiltOffsetAxis_->targetPosition_ /
motorRecResolution);
// There exists no readback value for tiltOffsetAxis_, hence we just set the
// current position to the target position.
pl_status = tiltOffsetAxis_->setDoubleParam(
pC_->motorPosition_,
tiltOffsetAxis_->targetPosition_ / motorRecResolution);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorPosition_",
liftAxisNo, __PRETTY_FUNCTION__,
tiltAxisNo, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = pC_->setDoubleParam(tiltAxisNo, pC_->motorHighLimitFromDriver_,
@@ -755,10 +788,11 @@ asynStatus detectorTowerAxis::doPoll(bool *moving) {
__PRETTY_FUNCTION__, __LINE__);
}
pl_status = setIntegerParam(pC_->positionState_, positionState);
pl_status = setIntegerParam(pC_->positionStateRBV_, positionState);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "positionState_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
return pC_->paramLibAccessFailed(pl_status, "positionStateRBV_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
return poll_status;
@@ -795,29 +829,9 @@ asynStatus detectorTowerAxis::startCombinedMove() {
char command[pC_->MAXBUF_], response[pC_->MAXBUF_];
double motorCoordinatesPosition = 0.0;
int positionState = 0;
int moving = 0;
// =========================================================================
pl_status = pC_->getIntegerParam(axisNo_, pC_->motorStatusMoving_, &moving);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorStatusMoving_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Check if the axis is already moving. In this case, do nothing
if (moving) {
return asynSuccess;
}
pl_status =
pC_->getIntegerParam(axisNo_, pC_->positionState_, &positionState);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "positionState_", 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;
@@ -833,6 +847,7 @@ asynStatus detectorTowerAxis::startCombinedMove() {
pC_->msgPrintControl_.getSuffix());
}
if (isInChangerPos) {
startingDeferredMovement_ = false;
return asynError;
}
@@ -842,6 +857,7 @@ asynStatus detectorTowerAxis::startCombinedMove() {
targetPosition_, liftOffsetAxis_->targetPosition_,
tiltOffsetAxis_->targetPosition_);
// DEBUG
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, "%s\n", command);
// Lock the access to the controller since this function runs in another
@@ -915,7 +931,8 @@ asynStatus detectorTowerAxis::readEncoderType() {
return asynSuccess;
}
asynStatus detectorTowerAxis::moveToWorkingPosition(bool toWorkingPosition) {
asynStatus
detectorTowerAxis::toggleWorkingChangerState(bool toChangingPosition) {
char response[pC_->MAXBUF_];
@@ -933,28 +950,21 @@ asynStatus detectorTowerAxis::moveToWorkingPosition(bool toWorkingPosition) {
doPoll(&moving);
pl_status =
pC_->getIntegerParam(axisNo_, pC_->positionState_, &positionState);
pC_->getIntegerParam(axisNo_, pC_->positionStateRBV_, &positionState);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "positionState_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
return pC_->paramLibAccessFailed(pl_status, "positionStateRBV_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// If the axis is currently moving, it cannot be disabled. Ignore the
// command and inform the user. We check the last known status of the axis
// instead of "moving", since status -6 is also moving, but the motor can
// actually be disabled in this state!
if (pC_->msgPrintControl_.shouldBePrinted(pC_->portName, axisNo_,
__PRETTY_FUNCTION__, __LINE__,
moving, pC_->pasynUserSelf)) {
if (moving) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis is not "
"idle and can therefore not be moved to %s position.%s\n",
"idle and can therefore not be moved to %s state.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
toWorkingPosition ? "working" : "changer",
toChangingPosition ? "changer" : "working",
pC_->msgPrintControl_.getSuffix());
}
if (moving) {
pl_status = setStringParam(
pC_->motorMessageText_,
"Axis cannot be moved to changer position while it is moving.");
@@ -967,8 +977,8 @@ asynStatus detectorTowerAxis::moveToWorkingPosition(bool toWorkingPosition) {
}
// Axis is already in the correct position
bool isAlreadyThere = (toWorkingPosition == true && positionState == 1) ||
(toWorkingPosition == false && positionState == 2);
bool isAlreadyThere = (toChangingPosition == false && positionState == 1) ||
(toChangingPosition == true && positionState == 2);
if (pC_->msgPrintControl_.shouldBePrinted(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
@@ -977,7 +987,7 @@ asynStatus detectorTowerAxis::moveToWorkingPosition(bool toWorkingPosition) {
"Controller \"%s\", axis %d => %s, line %d\nAxis is already "
"in %s position.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
toWorkingPosition ? "working" : "changer",
toChangingPosition ? "changer" : "working",
pC_->msgPrintControl_.getSuffix());
}
if (isAlreadyThere) {
@@ -985,15 +995,14 @@ asynStatus detectorTowerAxis::moveToWorkingPosition(bool toWorkingPosition) {
}
// Move the axis into changer or working position
if (toWorkingPosition) {
rw_status = pC_->writeRead(axisNo_, "P350=3", response, 0);
pl_status = setStringParam(pC_->motorMessageText_,
"Moving to working position ...");
} else {
if (toChangingPosition) {
rw_status = pC_->writeRead(axisNo_, "P350=2", response, 0);
pl_status = setStringParam(pC_->motorMessageText_,
"Moving to changer position ...");
} else {
rw_status = pC_->writeRead(axisNo_, "P350=3", response, 0);
pl_status = setStringParam(pC_->motorMessageText_,
"Moving to working state ...");
}
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
@@ -1010,15 +1019,16 @@ asynStatus detectorTowerAxis::reset() {
int positionState = 0;
pl_status =
pC_->getIntegerParam(axisNo_, pC_->positionState_, &positionState);
pC_->getIntegerParam(axisNo_, pC_->positionStateRBV_, &positionState);
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "positionState_", axisNo_,
__PRETTY_FUNCTION__, __LINE__);
return pC_->paramLibAccessFailed(pl_status, "positionStateRBV_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
/*
Check which action should be performed:
- If positionState_ == 0 (not ready): P352 = 1 (Set axis in closed-loop
- If positionState == 0 (not ready): P352 = 1 (Set axis in closed-loop
mode)
- If error_ == 10 or 11 (FTZ motor error): P352 = 3 (Recover FTZ)
- If any other error: P352 = 2 (Reset error)
@@ -1107,8 +1117,8 @@ asynStatus detectorTowerCreateAxis(const char *portName, int axisDetectorTower,
be "leaked" here.
*/
offsetAxis *liftOffsetAxis = new offsetAxis(pC, liftOffsetaxisNo);
offsetAxis *tiltOffsetAxis = new offsetAxis(pC, tiltOffsetaxisNo);
auxiliaryAxis *liftOffsetAxis = new auxiliaryAxis(pC, liftOffsetaxisNo);
auxiliaryAxis *tiltOffsetAxis = new auxiliaryAxis(pC, tiltOffsetaxisNo);
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"

View File

@@ -1,6 +1,6 @@
#ifndef detectorTowerAxis_H
#define detectorTowerAxis_H
#include "offsetAxis.h"
#include "auxiliaryAxis.h"
#include "turboPmacAxis.h"
// Forward declaration of the controller class to resolve the cyclic dependency
@@ -8,6 +8,10 @@
// https://en.cppreference.com/w/cpp/language/class.
class detectorTowerController;
/**
* @brief
*
*/
class detectorTowerAxis : public turboPmacAxis {
public:
/**
@@ -15,12 +19,17 @@ class detectorTowerAxis : public turboPmacAxis {
*
* @param pController Pointer to the associated controller
* @param axisNo Index of the axis
* @param liftOffsetAxis Pointer to the attached axis which controls
* the lift offset
* @param tiltOffsetAxis Pointer to the attached axis which controls
* the tilt offset
*/
detectorTowerAxis(detectorTowerController *pController, int axisNo,
offsetAxis *liftOffsetAxis, offsetAxis *tiltOffsetAxis);
auxiliaryAxis *liftOffsetAxis,
auxiliaryAxis *tiltOffsetAxis);
/**
* @brief Destroy the turboPmacAxis
* @brief Destroy the detectorTowerAxis
*
*/
virtual ~detectorTowerAxis();
@@ -87,7 +96,7 @@ class detectorTowerAxis : public turboPmacAxis {
*
* @return asynStatus
*/
asynStatus moveToWorkingPosition(bool toWorkingPosition);
asynStatus toggleWorkingChangerState(bool toChangingPosition);
/**
* @brief Reset the axis error
@@ -97,20 +106,25 @@ class detectorTowerAxis : public turboPmacAxis {
*/
asynStatus reset();
// If true, either this axis or one of the offsetAxis attached to it
// If true, either this axis or one of the auxiliaryAxis attached to it
// received a movement command.
bool receivedTarget_;
// If set to true, the virtual axis is about to start a deferred movement
// (but is currently still collecting movement commands from its component
// axes)
bool startingDeferredMovement_;
protected:
detectorTowerController *pC_;
double targetPosition_;
int error_;
offsetAxis *tiltOffsetAxis_;
offsetAxis *liftOffsetAxis_;
auxiliaryAxis *tiltOffsetAxis_;
auxiliaryAxis *liftOffsetAxis_;
private:
friend class detectorTowerController;
friend class offsetAxis;
friend class auxiliaryAxis;
};
#endif

View File

@@ -6,7 +6,7 @@
#include <iocsh.h>
/**
* @brief Construct a new turboPmacController::turboPmacController object
* @brief Construct a new detectorTowerController object
*
* @param portName See documentation of sinqController
* @param ipPortConfigName See documentation of sinqController
@@ -27,7 +27,8 @@ detectorTowerController::detectorTowerController(
asynStatus status = asynSuccess;
status = createParam("POSITION_STATE", asynParamInt32, &positionState_);
status =
createParam("POSITION_STATE_RBV", asynParamInt32, &positionStateRBV_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
@@ -37,18 +38,7 @@ detectorTowerController::detectorTowerController(
exit(-1);
}
status = createParam("MOVE_TO_WORKING_POSITION", asynParamInt32,
&moveToWorkingPosition_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"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("RESET_ERROR", asynParamInt32, &resetError_);
status = createParam("CHANGE_STATE", asynParamInt32, &changeState_);
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d\nFATAL ERROR (creating a "
@@ -65,7 +55,7 @@ asynStatus detectorTowerController::readInt32(asynUser *pasynUser,
// Check if the axis is a detectorTowerAxis
detectorTowerAxis *axis = getDetectorTowerAxis(pasynUser);
if (axis == nullptr) {
// This is apparently a "normal" turboPmacAxis or an offsetAxis
// This is apparently a "normal" turboPmacAxis or an auxiliaryAxis
return turboPmacController::readInt32(pasynUser, value);
} else {
// The detector tower cannot be disabled
@@ -87,13 +77,11 @@ asynStatus detectorTowerController::writeInt32(asynUser *pasynUser,
detectorTowerAxis *axis = getDetectorTowerAxis(pasynUser);
if (axis == nullptr) {
// This is apparently a "normal" turboPmacAxis or an offsetAxis
// This is apparently a "normal" turboPmacAxis or an auxiliaryAxis
return turboPmacController::writeInt32(pasynUser, value);
} else {
if (function == moveToWorkingPosition_) {
return axis->moveToWorkingPosition(value != 0);
} else if (function == resetError_) {
return axis->reset();
if (function == changeState_) {
return axis->toggleWorkingChangerState(value);
} else {
return turboPmacController::writeInt32(pasynUser, value);
}

View File

@@ -8,8 +8,8 @@
#ifndef detectorTowerController_H
#define detectorTowerController_H
#include "auxiliaryAxis.h"
#include "detectorTowerAxis.h"
#include "offsetAxis.h"
#include "turboPmacController.h"
class detectorTowerController : public turboPmacController {
@@ -76,14 +76,13 @@ class detectorTowerController : public turboPmacController {
private:
// Indices of additional PVs
#define FIRST_detectorTower_PARAM positionState_
int positionState_;
int moveToWorkingPosition_;
int resetError_;
#define LAST_detectorTower_PARAM resetError_
#define FIRST_detectorTower_PARAM positionStateRBV_
int positionStateRBV_;
int changeState_;
#define LAST_detectorTower_PARAM changeState_
friend class detectorTowerAxis;
friend class offsetAxis;
friend class auxiliaryAxis;
};
#define NUM_detectorTower_DRIVER_PARAMS \
(&LAST_detectorTower_PARAM - &FIRST_detectorTower_PARAM + 1)