Fully-featured version of the masterMACS driver

This commit is contained in:
2025-02-14 16:42:43 +01:00
parent ea8c34ab84
commit 6321a78b78
8 changed files with 972 additions and 349 deletions

View File

@@ -1,4 +1,5 @@
#---------------------------------------------
# SINQ specific DB definitions
#---------------------------------------------
registrar(masterMacsRegister)
registrar(masterMacsControllerRegister)
registrar(masterMacsAxisRegister)

File diff suppressed because it is too large Load Diff

View File

@@ -70,7 +70,7 @@ class masterMacsAxis : public sinqAxis {
double max_velocity, double acceleration);
/**
* @brief Implementation of the `atFirstPoll` function from sinqAxis.
* @brief Readout of some values from the controller at IOC startup
*
* The following steps are performed:
* - Read out the motor status, motor position, velocity and acceleration
@@ -79,7 +79,7 @@ class masterMacsAxis : public sinqAxis {
*
* @return asynStatus
*/
asynStatus atFirstPoll();
asynStatus init();
/**
* @brief Enable / disable the axis.
@@ -100,9 +100,10 @@ class masterMacsAxis : public sinqAxis {
protected:
masterMacsController *pC_;
double lastSetSpeed_;
bool waitForHandshake_;
time_t timeAtHandshake_;
asynStatus readConfig();
bool initial_poll_;
/*
The axis status and axis error of MasterMACS are given as an integer from
@@ -134,10 +135,7 @@ class masterMacsAxis : public sinqAxis {
*/
bool switchedOn() { return axisStatus_[1]; }
/**
* @brief Read the property from axisStatus_
*/
bool enabled() { return axisStatus_[2]; }
// Bit 2 is unused
/**
* @brief Read the property from axisStatus_
@@ -162,12 +160,9 @@ class masterMacsAxis : public sinqAxis {
/**
* @brief Read the property from axisStatus_
*/
bool newMoveCommandWhileMoving() { return axisStatus_[7]; }
bool warning() { return axisStatus_[7]; }
/**
* @brief Read the property from axisStatus_
*/
bool isMoving() { return axisStatus_[8]; }
// Bit 8 is unused
/**
* @brief Read the property from axisStatus_
@@ -209,6 +204,76 @@ class masterMacsAxis : public sinqAxis {
boolean.
*/
/**
* @brief Read the property from axisError_
*/
bool shortCircuit() { return axisError_[1]; }
/**
* @brief Read the property from axisError_
*/
bool encoderError() { return axisError_[2]; }
/**
* @brief Read the property from axisError_
*/
bool followingError() { return axisError_[3]; }
/**
* @brief Read the property from axisError_
*/
bool communicationError() { return axisError_[4]; }
/**
* @brief Read the property from axisError_
*/
bool feedbackError() { return axisError_[5]; }
/**
* @brief Read the property from axisError_
*/
bool positiveLimitSwitch() { return axisError_[6]; }
/**
* @brief Read the property from axisError_
*/
bool negativeLimitSwitch() { return axisError_[7]; }
/**
* @brief Read the property from axisError_
*/
bool positiveSoftwareLimit() { return axisError_[8]; }
/**
* @brief Read the property from axisError_
*/
bool negativeSoftwareLimit() { return axisError_[9]; }
/**
* @brief Read the property from axisError_
*/
bool overCurrent() { return axisError_[10]; }
/**
* @brief Read the property from axisError_
*/
bool overTemperature() { return axisError_[11]; }
/**
* @brief Read the property from axisError_
*/
bool overVoltage() { return axisError_[12]; }
/**
* @brief Read the property from axisError_
*/
bool underVoltage() { return axisError_[13]; }
/**
* @brief Read the property from axisError_
*/
bool stoFault() { return axisError_[15]; }
private:
friend class masterMacsController;
};

View File

@@ -75,10 +75,9 @@ masterMacsController::masterMacsController(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).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__);
exit(-1);
}
@@ -93,20 +92,21 @@ masterMacsController::masterMacsController(const char *portName,
lowLevelPortUser_, message_from_device, strlen(message_from_device));
if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting input EOS failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (setting "
"input EOS failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
exit(-1);
}
status = callParamCallbacks();
if (status != asynSuccess) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (executing ParamLib callbacks failed "
"with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (executing "
"ParamLib callbacks failed with %s).\nTerminating IOC",
portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
exit(-1);
}
@@ -144,10 +144,10 @@ masterMacsAxis *masterMacsController::castToAxis(asynMotorAxis *asynAxis) {
// an instance of Axis
masterMacsAxis *axis = dynamic_cast<masterMacsAxis *>(asynAxis);
if (axis == nullptr) {
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nAxis %d is not an instance of masterMacsAxis",
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not "
"an instance of masterMacsAxis",
portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__);
}
return axis;
}
@@ -248,9 +248,10 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
fullCommand[2] = lenWithMetadataSep.quot; // MSB
adjustForPrint(printableCommand, fullCommand, MAXBUF_);
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nSending command %s\n", __PRETTY_FUNCTION__,
__LINE__, printableCommand);
asynPrint(
this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"Controller \"%s\", axis %d => %s, line %d:\nSending command %s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, printableCommand);
// Send out the command
status =
@@ -276,25 +277,28 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
}
} else {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nError %s while reading from the "
"controller\n",
__PRETTY_FUNCTION__, __LINE__,
"Controller \"%s\", axis %d => %s, line %d:\nError "
"%s while reading from the controller\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
break;
}
if (i + 1 == maxTrials && status == asynError) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFailed %d times to get the "
"correct response. Aborting read.\n",
__PRETTY_FUNCTION__, __LINE__, maxTrials);
asynPrint(
this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nFailed "
"%d times to get the correct response. Aborting read.\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, maxTrials);
}
}
} else {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nError %s while writing to the controller\n",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
"Controller \"%s\", axis %d => %s, line %d:\nError %s while "
"writing to the controller\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
}
// MasterMACS needs a bit of time between messages, therefore thr program
@@ -341,9 +345,10 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
// Log the overall status (communication successfull or not)
if (status == asynSuccess) {
adjustForPrint(printableResponse, fullResponse, MAXBUF_);
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nReturn value: %s\n", __PRETTY_FUNCTION__,
__LINE__, printableResponse);
asynPrint(
lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
"Controller \"%s\", axis %d => %s, line %d:\nReturn value: %s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, printableResponse);
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
} else {
@@ -355,26 +360,29 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__);
axisNo, __PRETTY_FUNCTION__, __LINE__);
}
if (motorStatusProblem == 0) {
pl_status = axis->setStringParam(motorMessageText_, drvMessageText);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__);
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem",
__PRETTY_FUNCTION__, __LINE__);
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
__PRETTY_FUNCTION__, __LINE__);
axisNo, __PRETTY_FUNCTION__,
__LINE__);
}
}
}
@@ -414,9 +422,10 @@ asynStatus masterMacsController::parseResponse(
} else if (fullResponse[i] == '\x15') {
// NAK
snprintf(drvMessageText, MAXBUF_, "Communication failed.");
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nCommunication failed\n",
__PRETTY_FUNCTION__, __LINE__);
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCommunication failed\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
break;
} else if (fullResponse[i] == '\x18') {
// CAN
@@ -424,9 +433,10 @@ asynStatus masterMacsController::parseResponse(
"Tried to write with a read-only command. This is a "
"bug, please call the support.");
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nTried to write with the read-only "
"command %s\n",
__PRETTY_FUNCTION__, __LINE__, printableCommand);
"Controller \"%s\", axis %d => %s, line %d:\nTried to "
"write with the read-only command %s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
printableCommand);
responseValid = false;
break;
}
@@ -452,11 +462,11 @@ asynStatus masterMacsController::parseResponse(
adjustForPrint(printableCommand, fullCommand, MAXBUF_);
adjustForPrint(printableResponse, fullResponse, MAXBUF_);
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nMismatched response %s to "
"command %s\n",
__PRETTY_FUNCTION__, __LINE__, printableResponse,
printableCommand);
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"Controller \"%s\", axis %d => %s, line %d:\nMismatched "
"response %s to command %s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
printableResponse, printableCommand);
snprintf(drvMessageText, MAXBUF_,
"Mismatched response %s to command %s. Please call the "
@@ -511,72 +521,6 @@ asynStatus masterMacsCreateController(const char *portName,
return asynSuccess;
}
/*
C wrapper for the axis constructor. Please refer to the masterMacsAxis
constructor documentation. The controller is read from the portName.
*/
asynStatus masterMacsCreateAxis(const char *portName, int axis) {
masterMacsAxis *pAxis;
/*
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'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("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__,
__LINE__, portName);
return asynError;
}
// Unsafe cast of the pointer to an asynPortDriver
asynPortDriver *apd = (asynPortDriver *)(ptr);
// Safe downcast
masterMacsController *pC = dynamic_cast<masterMacsController *>(apd);
if (pC == nullptr) {
errlogPrintf("%s => line %d:\ncontroller on port %s is not a "
"masterMacsController.",
__PRETTY_FUNCTION__, __LINE__, portName);
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.
*/
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
pAxis = new masterMacsAxis(pC, axis);
// Allow manipulation of the controller again
pC->unlock();
return asynSuccess;
}
/*
This is boilerplate code which is used to make the FFI functions
CreateController and CreateAxis "known" to the IOC shell (iocsh).
@@ -613,30 +557,13 @@ static void configMasterMacsCreateControllerCallFunc(const iocshArgBuf *args) {
args[3].dval, args[4].dval, args[5].dval);
}
/*
Same procedure as for the CreateController function, but for the axis
itself.
*/
static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mmacs1)",
iocshArgString};
static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt};
static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
&CreateAxisArg1};
static const iocshFuncDef configMasterMacsCreateAxis = {"masterMacsAxis", 2,
CreateAxisArgs};
static void configMasterMacsCreateAxisCallFunc(const iocshArgBuf *args) {
masterMacsCreateAxis(args[0].sval, args[1].ival);
}
// This function is made known to EPICS in masterMacs.dbd and is called by
// EPICS in order to register both functions in the IOC shell
static void masterMacsRegister(void) {
static void masterMacsControllerRegister(void) {
iocshRegister(&configMasterMacsCreateController,
configMasterMacsCreateControllerCallFunc);
iocshRegister(&configMasterMacsCreateAxis,
configMasterMacsCreateAxisCallFunc);
}
epicsExportRegistrar(masterMacsRegister);
epicsExportRegistrar(masterMacsControllerRegister);
#endif