Fully-featured version of the masterMACS driver
This commit is contained in:
6
Makefile
6
Makefile
@ -7,11 +7,13 @@ EPICS_VERSIONS=7.0.7
|
|||||||
ARCH_FILTER=RHEL%
|
ARCH_FILTER=RHEL%
|
||||||
|
|
||||||
# Additional module dependencies
|
# Additional module dependencies
|
||||||
REQUIRED+=asynMotor
|
|
||||||
REQUIRED+=sinqMotor
|
REQUIRED+=sinqMotor
|
||||||
|
|
||||||
|
# Specify the version of asynMotor we want to build against
|
||||||
|
motorBase_VERSION=7.2.2
|
||||||
|
|
||||||
# Specify the version of sinqMotor we want to build against
|
# Specify the version of sinqMotor we want to build against
|
||||||
sinqMotor_VERSION=0.6.3
|
sinqMotor_VERSION=0.7.0
|
||||||
|
|
||||||
# These headers allow to depend on this library for derived drivers.
|
# These headers allow to depend on this library for derived drivers.
|
||||||
HEADERS += src/masterMacsAxis.h
|
HEADERS += src/masterMacsAxis.h
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#---------------------------------------------
|
#---------------------------------------------
|
||||||
# SINQ specific DB definitions
|
# SINQ specific DB definitions
|
||||||
#---------------------------------------------
|
#---------------------------------------------
|
||||||
registrar(masterMacsRegister)
|
registrar(masterMacsControllerRegister)
|
||||||
|
registrar(masterMacsAxisRegister)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -70,7 +70,7 @@ class masterMacsAxis : public sinqAxis {
|
|||||||
double max_velocity, double acceleration);
|
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:
|
* The following steps are performed:
|
||||||
* - Read out the motor status, motor position, velocity and acceleration
|
* - Read out the motor status, motor position, velocity and acceleration
|
||||||
@ -79,7 +79,7 @@ class masterMacsAxis : public sinqAxis {
|
|||||||
*
|
*
|
||||||
* @return asynStatus
|
* @return asynStatus
|
||||||
*/
|
*/
|
||||||
asynStatus atFirstPoll();
|
asynStatus init();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Enable / disable the axis.
|
* @brief Enable / disable the axis.
|
||||||
@ -100,9 +100,10 @@ class masterMacsAxis : public sinqAxis {
|
|||||||
protected:
|
protected:
|
||||||
masterMacsController *pC_;
|
masterMacsController *pC_;
|
||||||
double lastSetSpeed_;
|
double lastSetSpeed_;
|
||||||
|
bool waitForHandshake_;
|
||||||
|
time_t timeAtHandshake_;
|
||||||
|
|
||||||
asynStatus readConfig();
|
asynStatus readConfig();
|
||||||
bool initial_poll_;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The axis status and axis error of MasterMACS are given as an integer from
|
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]; }
|
bool switchedOn() { return axisStatus_[1]; }
|
||||||
|
|
||||||
/**
|
// Bit 2 is unused
|
||||||
* @brief Read the property from axisStatus_
|
|
||||||
*/
|
|
||||||
bool enabled() { return axisStatus_[2]; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read the property from axisStatus_
|
* @brief Read the property from axisStatus_
|
||||||
@ -162,12 +160,9 @@ class masterMacsAxis : public sinqAxis {
|
|||||||
/**
|
/**
|
||||||
* @brief Read the property from axisStatus_
|
* @brief Read the property from axisStatus_
|
||||||
*/
|
*/
|
||||||
bool newMoveCommandWhileMoving() { return axisStatus_[7]; }
|
bool warning() { return axisStatus_[7]; }
|
||||||
|
|
||||||
/**
|
// Bit 8 is unused
|
||||||
* @brief Read the property from axisStatus_
|
|
||||||
*/
|
|
||||||
bool isMoving() { return axisStatus_[8]; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read the property from axisStatus_
|
* @brief Read the property from axisStatus_
|
||||||
@ -209,6 +204,76 @@ class masterMacsAxis : public sinqAxis {
|
|||||||
boolean.
|
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:
|
private:
|
||||||
friend class masterMacsController;
|
friend class masterMacsController;
|
||||||
};
|
};
|
||||||
|
@ -75,10 +75,9 @@ masterMacsController::masterMacsController(const char *portName,
|
|||||||
*/
|
*/
|
||||||
pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL);
|
pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL);
|
||||||
if (status != asynSuccess || lowLevelPortUser_ == nullptr) {
|
if (status != asynSuccess || lowLevelPortUser_ == nullptr) {
|
||||||
errlogPrintf(
|
errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot "
|
||||||
"%s => line %d:\nFATAL ERROR (cannot connect to MCU controller).\n"
|
"connect to MCU controller).\nTerminating IOC",
|
||||||
"Terminating IOC",
|
portName, __PRETTY_FUNCTION__, __LINE__);
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,20 +92,21 @@ masterMacsController::masterMacsController(const char *portName,
|
|||||||
lowLevelPortUser_, message_from_device, strlen(message_from_device));
|
lowLevelPortUser_, message_from_device, strlen(message_from_device));
|
||||||
if (status != asynSuccess) {
|
if (status != asynSuccess) {
|
||||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s => line %d:\nFATAL ERROR (setting input EOS failed "
|
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (setting "
|
||||||
"with %s).\nTerminating IOC",
|
"input EOS failed with %s).\nTerminating IOC",
|
||||||
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
|
portName, __PRETTY_FUNCTION__, __LINE__,
|
||||||
|
stringifyAsynStatus(status));
|
||||||
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
|
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
status = callParamCallbacks();
|
status = callParamCallbacks();
|
||||||
if (status != asynSuccess) {
|
if (status != asynSuccess) {
|
||||||
asynPrint(
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
this->pasynUserSelf, ASYN_TRACE_ERROR,
|
"Controller \"%s\" => %s, line %d:\nFATAL ERROR (executing "
|
||||||
"%s => line %d:\nFATAL ERROR (executing ParamLib callbacks failed "
|
"ParamLib callbacks failed with %s).\nTerminating IOC",
|
||||||
"with %s).\nTerminating IOC",
|
portName, __PRETTY_FUNCTION__, __LINE__,
|
||||||
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
|
stringifyAsynStatus(status));
|
||||||
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
|
pasynOctetSyncIO->disconnect(lowLevelPortUser_);
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
@ -144,10 +144,10 @@ masterMacsAxis *masterMacsController::castToAxis(asynMotorAxis *asynAxis) {
|
|||||||
// an instance of Axis
|
// an instance of Axis
|
||||||
masterMacsAxis *axis = dynamic_cast<masterMacsAxis *>(asynAxis);
|
masterMacsAxis *axis = dynamic_cast<masterMacsAxis *>(asynAxis);
|
||||||
if (axis == nullptr) {
|
if (axis == nullptr) {
|
||||||
asynPrint(
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
this->pasynUserSelf, ASYN_TRACE_ERROR,
|
"Controller \"%s\", axis %d => %s, line %d:\nAxis is not "
|
||||||
"%s => line %d:\nAxis %d is not an instance of masterMacsAxis",
|
"an instance of masterMacsAxis",
|
||||||
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_);
|
portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
return axis;
|
return axis;
|
||||||
}
|
}
|
||||||
@ -248,9 +248,10 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
|
|||||||
fullCommand[2] = lenWithMetadataSep.quot; // MSB
|
fullCommand[2] = lenWithMetadataSep.quot; // MSB
|
||||||
|
|
||||||
adjustForPrint(printableCommand, fullCommand, MAXBUF_);
|
adjustForPrint(printableCommand, fullCommand, MAXBUF_);
|
||||||
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
|
asynPrint(
|
||||||
"%s => line %d:\nSending command %s\n", __PRETTY_FUNCTION__,
|
this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
|
||||||
__LINE__, printableCommand);
|
"Controller \"%s\", axis %d => %s, line %d:\nSending command %s\n",
|
||||||
|
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, printableCommand);
|
||||||
|
|
||||||
// Send out the command
|
// Send out the command
|
||||||
status =
|
status =
|
||||||
@ -276,25 +277,28 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s => line %d:\nError %s while reading from the "
|
"Controller \"%s\", axis %d => %s, line %d:\nError "
|
||||||
"controller\n",
|
"%s while reading from the controller\n",
|
||||||
__PRETTY_FUNCTION__, __LINE__,
|
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
|
||||||
stringifyAsynStatus(status));
|
stringifyAsynStatus(status));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i + 1 == maxTrials && status == asynError) {
|
if (i + 1 == maxTrials && status == asynError) {
|
||||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(
|
||||||
"%s => line %d:\nFailed %d times to get the "
|
this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"correct response. Aborting read.\n",
|
"Controller \"%s\", axis %d => %s, line %d:\nFailed "
|
||||||
__PRETTY_FUNCTION__, __LINE__, maxTrials);
|
"%d times to get the correct response. Aborting read.\n",
|
||||||
|
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, maxTrials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s => line %d:\nError %s while writing to the controller\n",
|
"Controller \"%s\", axis %d => %s, line %d:\nError %s while "
|
||||||
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status));
|
"writing to the controller\n",
|
||||||
|
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
|
||||||
|
stringifyAsynStatus(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
// MasterMACS needs a bit of time between messages, therefore thr program
|
// 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)
|
// Log the overall status (communication successfull or not)
|
||||||
if (status == asynSuccess) {
|
if (status == asynSuccess) {
|
||||||
adjustForPrint(printableResponse, fullResponse, MAXBUF_);
|
adjustForPrint(printableResponse, fullResponse, MAXBUF_);
|
||||||
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
|
asynPrint(
|
||||||
"%s => line %d:\nReturn value: %s\n", __PRETTY_FUNCTION__,
|
lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
|
||||||
__LINE__, printableResponse);
|
"Controller \"%s\", axis %d => %s, line %d:\nReturn value: %s\n",
|
||||||
|
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, printableResponse);
|
||||||
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
|
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@ -355,26 +360,29 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
|
|||||||
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
|
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
return paramLibAccessFailed(pl_status, "motorStatusProblem_",
|
return paramLibAccessFailed(pl_status, "motorStatusProblem_",
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
axisNo, __PRETTY_FUNCTION__, __LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (motorStatusProblem == 0) {
|
if (motorStatusProblem == 0) {
|
||||||
pl_status = axis->setStringParam(motorMessageText_, drvMessageText);
|
pl_status = axis->setStringParam(motorMessageText_, drvMessageText);
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
return paramLibAccessFailed(pl_status, "motorMessageText_",
|
return paramLibAccessFailed(pl_status, "motorMessageText_",
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
axisNo, __PRETTY_FUNCTION__,
|
||||||
|
__LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
|
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
return paramLibAccessFailed(pl_status, "motorStatusProblem",
|
return paramLibAccessFailed(pl_status, "motorStatusProblem",
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
axisNo, __PRETTY_FUNCTION__,
|
||||||
|
__LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
|
pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
|
||||||
if (pl_status != asynSuccess) {
|
if (pl_status != asynSuccess) {
|
||||||
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
|
return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
axisNo, __PRETTY_FUNCTION__,
|
||||||
|
__LINE__);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -414,9 +422,10 @@ asynStatus masterMacsController::parseResponse(
|
|||||||
} else if (fullResponse[i] == '\x15') {
|
} else if (fullResponse[i] == '\x15') {
|
||||||
// NAK
|
// NAK
|
||||||
snprintf(drvMessageText, MAXBUF_, "Communication failed.");
|
snprintf(drvMessageText, MAXBUF_, "Communication failed.");
|
||||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
|
||||||
"%s => line %d:\nCommunication failed\n",
|
"Controller \"%s\", axis %d => %s, line "
|
||||||
__PRETTY_FUNCTION__, __LINE__);
|
"%d:\nCommunication failed\n",
|
||||||
|
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
|
||||||
break;
|
break;
|
||||||
} else if (fullResponse[i] == '\x18') {
|
} else if (fullResponse[i] == '\x18') {
|
||||||
// CAN
|
// CAN
|
||||||
@ -424,9 +433,10 @@ asynStatus masterMacsController::parseResponse(
|
|||||||
"Tried to write with a read-only command. This is a "
|
"Tried to write with a read-only command. This is a "
|
||||||
"bug, please call the support.");
|
"bug, please call the support.");
|
||||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
||||||
"%s => line %d:\nTried to write with the read-only "
|
"Controller \"%s\", axis %d => %s, line %d:\nTried to "
|
||||||
"command %s\n",
|
"write with the read-only command %s\n",
|
||||||
__PRETTY_FUNCTION__, __LINE__, printableCommand);
|
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
|
||||||
|
printableCommand);
|
||||||
responseValid = false;
|
responseValid = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -452,11 +462,11 @@ asynStatus masterMacsController::parseResponse(
|
|||||||
adjustForPrint(printableCommand, fullCommand, MAXBUF_);
|
adjustForPrint(printableCommand, fullCommand, MAXBUF_);
|
||||||
adjustForPrint(printableResponse, fullResponse, MAXBUF_);
|
adjustForPrint(printableResponse, fullResponse, MAXBUF_);
|
||||||
|
|
||||||
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
|
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
|
||||||
"%s => line %d:\nMismatched response %s to "
|
"Controller \"%s\", axis %d => %s, line %d:\nMismatched "
|
||||||
"command %s\n",
|
"response %s to command %s\n",
|
||||||
__PRETTY_FUNCTION__, __LINE__, printableResponse,
|
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
|
||||||
printableCommand);
|
printableResponse, printableCommand);
|
||||||
|
|
||||||
snprintf(drvMessageText, MAXBUF_,
|
snprintf(drvMessageText, MAXBUF_,
|
||||||
"Mismatched response %s to command %s. Please call the "
|
"Mismatched response %s to command %s. Please call the "
|
||||||
@ -511,72 +521,6 @@ asynStatus masterMacsCreateController(const char *portName,
|
|||||||
return asynSuccess;
|
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
|
This is boilerplate code which is used to make the FFI functions
|
||||||
CreateController and CreateAxis "known" to the IOC shell (iocsh).
|
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);
|
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
|
// 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
|
// EPICS in order to register both functions in the IOC shell
|
||||||
static void masterMacsRegister(void) {
|
static void masterMacsControllerRegister(void) {
|
||||||
iocshRegister(&configMasterMacsCreateController,
|
iocshRegister(&configMasterMacsCreateController,
|
||||||
configMasterMacsCreateControllerCallFunc);
|
configMasterMacsCreateControllerCallFunc);
|
||||||
iocshRegister(&configMasterMacsCreateAxis,
|
|
||||||
configMasterMacsCreateAxisCallFunc);
|
|
||||||
}
|
}
|
||||||
epicsExportRegistrar(masterMacsRegister);
|
epicsExportRegistrar(masterMacsControllerRegister);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
120
utils/decodeCommon.py
Normal file
120
utils/decodeCommon.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
"""
|
||||||
|
Code shared by "decodeError.py" and "decodeStatus.py"
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decode(value: int, interpretation):
|
||||||
|
|
||||||
|
bit_list = [int(char) for char in bin(value)[2:]]
|
||||||
|
bit_list.reverse()
|
||||||
|
|
||||||
|
interpreted = []
|
||||||
|
for (bit, interpretations) in zip(bit_list, interpretation):
|
||||||
|
interpreted.append(interpretations[bit])
|
||||||
|
return (bit_list, interpreted)
|
||||||
|
|
||||||
|
def print_decoded(bit_list, interpreted):
|
||||||
|
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
|
||||||
|
print(f"Bit {idx} = {bit_value}: {msg}")
|
||||||
|
|
||||||
|
def interactive():
|
||||||
|
|
||||||
|
# Imported here, because curses is not available in Windows. Using the
|
||||||
|
# interactive mode therefore fails on Windows, but at least the single
|
||||||
|
# command mode can be used (which would not be possible if we would import
|
||||||
|
# curses at the top level)
|
||||||
|
import curses
|
||||||
|
|
||||||
|
stdscr = curses.initscr()
|
||||||
|
curses.noecho()
|
||||||
|
curses.cbreak()
|
||||||
|
stdscr.keypad(True)
|
||||||
|
stdscr.scrollok(True)
|
||||||
|
|
||||||
|
stdscr.addstr(">> ")
|
||||||
|
stdscr.refresh()
|
||||||
|
|
||||||
|
history = [""]
|
||||||
|
ptr = len(history) - 1
|
||||||
|
|
||||||
|
while True:
|
||||||
|
c = stdscr.getch()
|
||||||
|
if c == curses.KEY_RIGHT:
|
||||||
|
(y, x) = stdscr.getyx()
|
||||||
|
if x < len(history[ptr]) + 3:
|
||||||
|
stdscr.move(y, x+1)
|
||||||
|
stdscr.refresh()
|
||||||
|
elif c == curses.KEY_LEFT:
|
||||||
|
(y, x) = stdscr.getyx()
|
||||||
|
if x > 3:
|
||||||
|
stdscr.move(y, x-1)
|
||||||
|
stdscr.refresh()
|
||||||
|
elif c == curses.KEY_UP:
|
||||||
|
if ptr > 0:
|
||||||
|
ptr -= 1
|
||||||
|
stdscr.addch("\r")
|
||||||
|
stdscr.clrtoeol()
|
||||||
|
stdscr.addstr(">> " + history[ptr])
|
||||||
|
elif c == curses.KEY_DOWN:
|
||||||
|
if ptr < len(history) - 1:
|
||||||
|
ptr += 1
|
||||||
|
stdscr.addch("\r")
|
||||||
|
stdscr.clrtoeol()
|
||||||
|
stdscr.addstr(">> " + history[ptr])
|
||||||
|
elif c == curses.KEY_ENTER or c == ord('\n') or c == ord('\r'):
|
||||||
|
if history[ptr] == 'quit':
|
||||||
|
break
|
||||||
|
|
||||||
|
# because of arrow keys move back to the end of the line
|
||||||
|
(y, x) = stdscr.getyx()
|
||||||
|
stdscr.move(y, 3+len(history[ptr]))
|
||||||
|
|
||||||
|
if history[ptr]:
|
||||||
|
(bit_list, interpreted) = decode(history[ptr])
|
||||||
|
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
|
||||||
|
stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}")
|
||||||
|
stdscr.refresh()
|
||||||
|
|
||||||
|
if ptr == len(history) - 1 and history[ptr] != "":
|
||||||
|
history += [""]
|
||||||
|
else:
|
||||||
|
history[-1] = ""
|
||||||
|
ptr = len(history) - 1
|
||||||
|
|
||||||
|
stdscr.addstr("\n>> ")
|
||||||
|
stdscr.refresh()
|
||||||
|
|
||||||
|
else:
|
||||||
|
if ptr < len(history) - 1: # Modifying previous input
|
||||||
|
if len(history[-1]) == 0:
|
||||||
|
history[-1] = history[ptr]
|
||||||
|
ptr = len(history) - 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
history += [history[ptr]]
|
||||||
|
ptr = len(history) - 1
|
||||||
|
|
||||||
|
if c == curses.KEY_BACKSPACE:
|
||||||
|
if len(history[ptr]) == 0:
|
||||||
|
continue
|
||||||
|
(y, x) = stdscr.getyx()
|
||||||
|
history[ptr] = history[ptr][0:x-4] + history[ptr][x-3:]
|
||||||
|
stdscr.addch("\r")
|
||||||
|
stdscr.clrtoeol()
|
||||||
|
stdscr.addstr(">> " + history[ptr])
|
||||||
|
stdscr.move(y, x-1)
|
||||||
|
stdscr.refresh()
|
||||||
|
|
||||||
|
else:
|
||||||
|
(y, x) = stdscr.getyx()
|
||||||
|
history[ptr] = history[ptr][0:x-3] + chr(c) + history[ptr][x-3:]
|
||||||
|
stdscr.addch("\r")
|
||||||
|
stdscr.clrtoeol()
|
||||||
|
stdscr.addstr(">> " + history[ptr])
|
||||||
|
stdscr.move(y, x+1)
|
||||||
|
stdscr.refresh()
|
||||||
|
|
||||||
|
# to quit
|
||||||
|
curses.nocbreak()
|
||||||
|
stdscr.keypad(False)
|
||||||
|
curses.echo()
|
||||||
|
curses.endwin()
|
81
utils/decodeError.py
Executable file
81
utils/decodeError.py
Executable file
@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
The R11 error read command returns an integer, which needs to be interpreted
|
||||||
|
bitwise for various error flags. This script prints out these status flags in
|
||||||
|
human-readable formatting.
|
||||||
|
|
||||||
|
To read the manual, simply run this script without any arguments.
|
||||||
|
|
||||||
|
Stefan Mathis, January 2025
|
||||||
|
"""
|
||||||
|
|
||||||
|
from decodeCommon import interactive, decode, print_decoded
|
||||||
|
|
||||||
|
# List of tuples which encodes the states given in the file description.
|
||||||
|
# Index first with the bit index, then with the bit value
|
||||||
|
interpretation = [
|
||||||
|
("Not specified", "Not specified"), # Bit 0
|
||||||
|
("Ok", "Short circuit"), # Bit 1
|
||||||
|
("Ok", "Encoder error"), # Bit 2
|
||||||
|
("Ok", "Following error"), # Bit 3
|
||||||
|
("Ok", "Communication error"), # Bit 4
|
||||||
|
("Ok", "Feedback error"), # Bit 5
|
||||||
|
("Ok", "Positive limit switch hit"), # Bit 6
|
||||||
|
("Ok", "Negative limit switch hit"), # Bit 7
|
||||||
|
("Ok", "Positive software limit hit"), # Bit 8
|
||||||
|
("Ok", "Negative software limit hit"), # Bit 9
|
||||||
|
("Ok", "Over-current"), # Bit 10
|
||||||
|
("Ok", "Over-temperature drive"), # Bit 11
|
||||||
|
("Ok", "Over-voltage"), # Bit 12
|
||||||
|
("Ok", "Under-voltage"), # Bit 13
|
||||||
|
("Not specified", "Not specified"), # Bit 14
|
||||||
|
("Ok", "STO fault (STO input is on disable state)"), # Bit 15
|
||||||
|
]
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from sys import argv
|
||||||
|
|
||||||
|
if len(argv) == 1:
|
||||||
|
# Start interactive mode
|
||||||
|
interactive()
|
||||||
|
else:
|
||||||
|
|
||||||
|
number = None
|
||||||
|
try:
|
||||||
|
number = int(float(argv[1]))
|
||||||
|
|
||||||
|
except:
|
||||||
|
print("""
|
||||||
|
Decode R11 message of MasterMACs
|
||||||
|
------------------
|
||||||
|
|
||||||
|
MasterMACs returns its error message (R11) as a floating-point number.
|
||||||
|
The bits of this float encode different states. These states are stored
|
||||||
|
in the interpretation variable.
|
||||||
|
|
||||||
|
This script can be used in two different ways:
|
||||||
|
|
||||||
|
Option 1: Single Command
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Usage: decodeError.py value
|
||||||
|
|
||||||
|
'value' is the return value of a R11 command. This value is interpreted
|
||||||
|
bit-wise and the result is printed out.
|
||||||
|
|
||||||
|
Option 2: CLI Mode
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Usage: decodeError.py
|
||||||
|
|
||||||
|
A prompt will be opened. Type in the return value of a R11 command, hit
|
||||||
|
enter and the interpretation will be printed in the prompt. After that,
|
||||||
|
the next value can be typed in. Type 'quit' to close the prompt.
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
if number is not None:
|
||||||
|
print("Motor error")
|
||||||
|
print("============")
|
||||||
|
(bit_list, interpreted) = decode(number, interpretation)
|
||||||
|
print_decoded(bit_list, interpreted)
|
81
utils/decodeStatus.py
Normal file → Executable file
81
utils/decodeStatus.py
Normal file → Executable file
@ -9,6 +9,7 @@ To read the manual, simply run this script without any arguments.
|
|||||||
Stefan Mathis, December 2024
|
Stefan Mathis, December 2024
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from decodeCommon import interactive, decode, print_decoded
|
||||||
|
|
||||||
# List of tuples which encodes the states given in the file description.
|
# List of tuples which encodes the states given in the file description.
|
||||||
# Index first with the bit index, then with the bit value
|
# Index first with the bit index, then with the bit value
|
||||||
@ -20,35 +21,17 @@ interpretation = [
|
|||||||
("Motor supply voltage absent ", "Motor supply voltage present"), # Bit 4
|
("Motor supply voltage absent ", "Motor supply voltage present"), # Bit 4
|
||||||
("Motor performs quick stop", "Ok"), # Bit 5
|
("Motor performs quick stop", "Ok"), # Bit 5
|
||||||
("Switch on enabled", "Switch on disabled"), # Bit 6
|
("Switch on enabled", "Switch on disabled"), # Bit 6
|
||||||
("Ok", "RWarning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7
|
("Ok", "Warning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7
|
||||||
("Motor is idle", "Motor is currently moving"), # Bit 8
|
("Not specified", "Not specified"), # Bit 8
|
||||||
("Motor does not execute command messages (local mode)", "Motor does execute command messages (remote mode)"), # Bit 9
|
("Motor does not execute command messages (local mode)", "Motor does execute command messages (remote mode)"), # Bit 9
|
||||||
("Target not reached", "Target reached"), # Bit 10
|
("Target not reached", "Target reached"), # Bit 10
|
||||||
("Ok", "Internal limit active"), # Bit 11
|
("Ok", "Internal limit active (current, voltage, velocity or position)"), # Bit 11
|
||||||
("Not specified", "Not specified"), # Bit 12
|
("Not specified", "Not specified"), # Bit 12
|
||||||
("Not specified", "Not specified"), # Bit 13
|
("Not specified", "Not specified"), # Bit 13
|
||||||
("No event set or event has not occurred yet", "Set event has occurred"), # Bit 14
|
("Not specified", "Not specified"), # Bit 14
|
||||||
("Axis off (power disabled)", "Axis on (power enabled)"), # Bit 15
|
("Not specified", "Not specified"), # Bit 15
|
||||||
]
|
]
|
||||||
|
|
||||||
def decode(value, big_endian: bool = False):
|
|
||||||
|
|
||||||
interpreted = []
|
|
||||||
|
|
||||||
bit_list = [(value >> shift_ind) & 1
|
|
||||||
for shift_ind in range(value.bit_length())] # little endian
|
|
||||||
|
|
||||||
if big_endian:
|
|
||||||
bit_list.reverse() # big endian
|
|
||||||
|
|
||||||
for (bit, interpretations) in zip(bit_list, interpretation):
|
|
||||||
interpreted.append(interpretations[bit])
|
|
||||||
return (bit_list, interpreted)
|
|
||||||
|
|
||||||
def print_decoded(bit_list, interpreted):
|
|
||||||
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
|
|
||||||
print(f"Bit {idx} = {bit_value}: {msg}")
|
|
||||||
|
|
||||||
def interactive():
|
def interactive():
|
||||||
|
|
||||||
# Imported here, because curses is not available in Windows. Using the
|
# Imported here, because curses is not available in Windows. Using the
|
||||||
@ -102,15 +85,10 @@ def interactive():
|
|||||||
stdscr.move(y, 3+len(history[ptr]))
|
stdscr.move(y, 3+len(history[ptr]))
|
||||||
|
|
||||||
if history[ptr]:
|
if history[ptr]:
|
||||||
result = interpret_inputs(history[ptr].split())
|
(bit_list, interpreted) = decode(history[ptr])
|
||||||
if result is None:
|
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
|
||||||
stdscr.addstr(f"\nBAD INPUT: Expected input of 'value [big_endian]', where 'value' is an int or a float and 'big_endian' is an optional boolean argument.")
|
stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}")
|
||||||
else:
|
stdscr.refresh()
|
||||||
(arg, big_endian) = result
|
|
||||||
(bit_list, interpreted) = decode(arg, big_endian)
|
|
||||||
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
|
|
||||||
stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}")
|
|
||||||
stdscr.refresh()
|
|
||||||
|
|
||||||
if ptr == len(history) - 1 and history[ptr] != "":
|
if ptr == len(history) - 1 and history[ptr] != "":
|
||||||
history += [""]
|
history += [""]
|
||||||
@ -157,24 +135,6 @@ def interactive():
|
|||||||
curses.echo()
|
curses.echo()
|
||||||
curses.endwin()
|
curses.endwin()
|
||||||
|
|
||||||
def interpret_inputs(inputs):
|
|
||||||
|
|
||||||
number = None
|
|
||||||
big_endian = False
|
|
||||||
try:
|
|
||||||
number = int(float(inputs[0]))
|
|
||||||
if len(inputs) > 1:
|
|
||||||
second_arg = inputs[1]
|
|
||||||
if second_arg == "True" or second_arg == "true":
|
|
||||||
big_endian = True
|
|
||||||
elif second_arg == "False" or second_arg == "false":
|
|
||||||
big_endian = False
|
|
||||||
else:
|
|
||||||
big_endian = bool(int(second_arg))
|
|
||||||
return (number, big_endian)
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from sys import argv
|
from sys import argv
|
||||||
|
|
||||||
@ -183,9 +143,11 @@ if __name__ == "__main__":
|
|||||||
interactive()
|
interactive()
|
||||||
else:
|
else:
|
||||||
|
|
||||||
result = interpret_inputs(argv[1:])
|
number = None
|
||||||
|
try:
|
||||||
|
number = int(float(argv[1]))
|
||||||
|
|
||||||
if result is None:
|
except:
|
||||||
print("""
|
print("""
|
||||||
Decode R10 message of MasterMACs
|
Decode R10 message of MasterMACs
|
||||||
------------------
|
------------------
|
||||||
@ -199,25 +161,24 @@ if __name__ == "__main__":
|
|||||||
Option 1: Single Command
|
Option 1: Single Command
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
Usage: decodeMasterMACStatusR10.py value [big_endian]
|
Usage: decodeStatus.py value
|
||||||
|
|
||||||
'value' is the return value of a R10 command. This value is interpreted
|
'value' is the return value of a R10 command. This value is interpreted
|
||||||
bit-wise and the result is printed out. The optional second argument can
|
bit-wise and the result is printed out.
|
||||||
be used to specify whether the input value needs to be interpreted as
|
|
||||||
little or big endian. Default is False.
|
|
||||||
|
|
||||||
Option 2: CLI Mode
|
Option 2: CLI Mode
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
Usage: decodeMasterMACStatusR10.py
|
Usage: decodeStatus.py
|
||||||
|
|
||||||
A prompt will be opened. Type in the return value of a R10 command, hit
|
A prompt will be opened. Type in the return value of a R10 command, hit
|
||||||
enter and the interpretation will be printed in the prompt. After that,
|
enter and the interpretation will be printed in the prompt. After that,
|
||||||
the next value can be typed in. Type 'quit' to close the prompt.
|
the next value can be typed in. Type 'quit' to close the prompt.
|
||||||
""")
|
""")
|
||||||
else:
|
|
||||||
|
|
||||||
|
if number is not None:
|
||||||
print("Motor status")
|
print("Motor status")
|
||||||
print("============")
|
print("============")
|
||||||
(arg, big_endian) = result
|
(bit_list, interpreted) = decode(number, interpretation)
|
||||||
(bit_list, interpreted) = decode(arg, big_endian)
|
|
||||||
print_decoded(bit_list, interpreted)
|
print_decoded(bit_list, interpreted)
|
||||||
|
Reference in New Issue
Block a user