Added new feature msgPrintControl from sinqMotor 0.8.0.

Correspondingly, the minimum version requirement for sinqMotor has been bumped to 0.8.0.
This commit is contained in:
2025-03-04 12:41:20 +01:00
parent cb91a8aa36
commit 4ad842c097
8 changed files with 299 additions and 408 deletions

View File

@ -13,7 +13,7 @@ REQUIRED+=sinqMotor
motorBase_VERSION=7.2.2 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.7.0 sinqMotor_VERSION=0.8.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

View File

@ -18,22 +18,42 @@ The folder "utils" contains utility scripts for working with masterMacs motor co
masterMacs exposes the following IOC shell functions (all in masterMacsController.cpp): masterMacs exposes the following IOC shell functions (all in masterMacsController.cpp):
- `masterMacsController`: Create a new controller object. - `masterMacsController`: Create a new controller object.
- `masterMacsAxis`: Create a new axis object. - `masterMacsAxis`: Create a new axis object.
These functions are parametrized as follows:
The full mcu.cmd file looks like this:
``` ```
masterMacsController( # Define the name of the controller and the corresponding port
"$(NAME)", # Name of the MCU, e.g. mcu1. This parameter should be provided by an environment variable. epicsEnvSet("NAME","mcu")
"$(ASYN_PORT)", # IP-Port of the MCU. This parameter should be provided by an environment variable. epicsEnvSet("ASYN_PORT","p$(NAME)")
8, # Maximum number of axes
0.05, # Busy poll period in seconds # Create the TCP/IP socket used to talk with the controller. The socket can be adressed from within the IOC shell via the port name
1, # Idle poll period in seconds drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025")
0.05 # Communication timeout in seconds
); # Create the controller object with the defined name and connect it to the socket via the port name.
``` # The other parameters are as follows:
``` # 8: Maximum number of axes
masterMacsAxis( # 0.05: Busy poll period in seconds
"$(NAME)", # Name of the associated MCU, e.g. mcu1. This parameter should be provided by an environment variable. # 1: Idle poll period in seconds
1, # Index of the axis. # 1: Socket communication timeout in seconds
); masterMacsController("$(NAME)", "$(ASYN_PORT)", 8, 0.05, 1, 1);
# Define some axes for the specified MCU at the given slot (1, 2 and 5). No slot may be used twice!
masterMacsAxis("$(NAME)",1);
masterMacsAxis("$(NAME)",2);
masterMacsAxis("$(NAME)",5);
# Set the number of subsequent timeouts
setMaxSubsequentTimeouts("$(NAME)", 20);
# Configure the timeout frequency watchdog:
setThresholdComTimeout("$(NAME)", 100, 1);
# Parametrize the EPICS record database with the substitution file named after the MCU.
epicsEnvSet("SINQDBPATH","$(sinqMotor_DB)/sinqMotor.db")
dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
epicsEnvSet("SINQDBPATH","$(masterMacs_DB)/masterMacs.db")
dbLoadTemplate("$(TOP)/$(NAME).substitutions", "INSTR=$(INSTR)$(NAME):,CONTROLLER=$(NAME)")
dbLoadRecords("$(sinqMotor_DB)/asynRecord.db","P=$(INSTR)$(NAME),PORT=$(ASYN_PORT)")
``` ```
### Versioning ### Versioning

View File

@ -34,6 +34,32 @@ static void epicsInithookFunction(initHookState iState) {
} }
} }
void appendErrorMessage(char *fullMessage, size_t capacityFullMessage,
const char *toBeAppended) {
size_t lenFullMessage = strlen(fullMessage);
size_t lenToBeAppended = strlen(toBeAppended);
if (lenFullMessage == 0) {
// The error message is empty -> Just copy the content of toBeAppended
// into fullMessage, if the formers capacity suffices
if (lenToBeAppended < capacityFullMessage) {
strcpy(fullMessage, toBeAppended);
}
} else {
// Append the message and add a linebreak in between, if the capacity of
// fullMessage suffices. We need capacity for one additional character
// because of the linebreak.
if (lenFullMessage + lenToBeAppended + 1 < capacityFullMessage) {
// Append the linebreak and readd the null terminator behind it
// fullMessage[lenFullMessage] = '\n';
// fullMessage[lenFullMessage + 1] = '\0';
// Append the actual message
strcat(fullMessage, toBeAppended);
}
}
}
masterMacsAxis::masterMacsAxis(masterMacsController *pC, int axisNo) masterMacsAxis::masterMacsAxis(masterMacsController *pC, int axisNo)
: sinqAxis(pC, axisNo), pC_(pC) { : sinqAxis(pC, axisNo), pC_(pC) {
@ -340,8 +366,9 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
return rw_status; return rw_status;
} }
// Check if we reached the target // If the motor is switched off or if it reached its target, it is not
if (targetReached()) { // moving.
if (targetReached() || !switchedOn()) {
*moving = false; *moving = false;
} else { } else {
*moving = true; *moving = true;
@ -362,70 +389,26 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
Read out the error if either a fault condition status flag has been set or Read out the error if either a fault condition status flag has been set or
if a movement has just ended. if a movement has just ended.
*/ */
if (faultConditionSet()) { msgPrintControlKey keyError = msgPrintControlKey(
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
if (faultConditionSet() || !(*moving)) {
rw_status = readAxisError(); rw_status = readAxisError();
if (shortCircuit()) { /*
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, A communication error is a special case. If a communication between
"Controller \"%s\", axis %d => %s, line %d\nShort " controller and axis error occurred, all subsequent errors are ignored,
"circuit fault.\n", since this information is not reliable.
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); */
pl_status =
setStringParam(pC_->motorMessageText_,
"Short circuit error. Please call the support.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
poll_status = asynError;
}
if (encoderError()) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nEncoder error.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
pl_status =
setStringParam(pC_->motorMessageText_,
"Encoder error. Please call the support.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
poll_status = asynError;
}
if (followingError()) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nMaximum allowed "
"following error exceeded.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
pl_status = setStringParam(
pC_->motorMessageText_,
"Maximum allowed following error exceeded.Check if movement "
"range is blocked. Otherwise please call the support.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
poll_status = asynError;
}
if (communicationError()) { if (communicationError()) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, if (pC_->msgPrintControl_.shouldBePrinted(keyError, true,
"Controller \"%s\", axis %d => %s, line " pC_->pasynUserSelf)) {
"%d\nCommunication error.\n", asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); "Controller \"%s\", axis %d => %s, line "
"%d\nCommunication error.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__,
pC_->msgPrintControl_.getSuffix());
}
pl_status = pl_status =
setStringParam(pC_->motorMessageText_, setStringParam(pC_->motorMessageText_,
@ -438,172 +421,164 @@ asynStatus masterMacsAxis::doPoll(bool *moving) {
} }
poll_status = asynError; poll_status = asynError;
} } else {
if (feedbackError()) { // This buffer must be initialized to zero because we build the
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, // error message by appending strings.
"Controller \"%s\", axis %d => %s, line " char userMessage[pC_->MAXBUF_] = {0};
"%d\nFeedback error.\n", char shellMessage[pC_->MAXBUF_] = {0};
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
pl_status = // Concatenate all other errors
setStringParam(pC_->motorMessageText_, if (shortCircuit()) {
"Feedback error. Please call the support."); appendErrorMessage(shellMessage, sizeof(shellMessage),
"Short circuit fault.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Short circuit error. Please call the support.");
poll_status = asynError;
}
if (encoderError()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Encoder error.");
appendErrorMessage(userMessage, sizeof(userMessage),
"Encoder error. Please call the support.");
poll_status = asynError;
}
if (followingError()) {
appendErrorMessage(
shellMessage, sizeof(shellMessage),
"Maximum callowed following error exceeded.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Maximum allowed following error exceeded.Check if "
"movement range is blocked. Otherwise please call the "
"support.");
poll_status = asynError;
}
if (feedbackError()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Feedback error.");
appendErrorMessage(userMessage, sizeof(userMessage),
"Feedback error. Please call the support.");
poll_status = asynError;
}
/*
Either the software limits or the end switches of the controller
have been hit. Since the EPICS limits are derived from the software
limits and are a little bit smaller, these error cases can only
happen if either the axis has an incremental encoder which is not
properly homed or if a bug occured.
*/
if (positiveLimitSwitch() || negativeLimitSwitch() ||
positiveSoftwareLimit() || negativeSoftwareLimit()) {
// Distinction for developers
if (positiveLimitSwitch()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Positive limit switch.");
}
if (negativeLimitSwitch()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Negative limit switch.");
}
if (positiveSoftwareLimit()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Positive software limit.");
}
if (negativeSoftwareLimit()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Negative software limit.");
}
// Generic error message for user
appendErrorMessage(
userMessage, sizeof(userMessage),
"Software limits or end switch hit. Try homing the motor, "
"moving in the opposite direction or check the SPS for "
"errors (if available). Otherwise please call the "
"support.");
poll_status = asynError;
}
if (overCurrent()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Overcurrent error.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Overcurrent error. Please call the support.");
poll_status = asynError;
}
if (overTemperature()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Overtemperature error.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Overtemperature error. Please call the support.");
poll_status = asynError;
}
if (overVoltage()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Overvoltage error.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Overvoltage error. Please call the support.");
poll_status = asynError;
}
if (underVoltage()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"Undervoltage error.");
appendErrorMessage(
userMessage, sizeof(userMessage),
"Undervoltage error. Please call the support.");
poll_status = asynError;
}
if (stoFault()) {
appendErrorMessage(shellMessage, sizeof(shellMessage),
"STO input is on disable state.");
appendErrorMessage(userMessage, sizeof(userMessage),
"STO fault. Please call the support.");
poll_status = asynError;
}
if (strlen(shellMessage) > 0) {
if (pC_->msgPrintControl_.shouldBePrinted(keyError, true,
pC_->pasynUserSelf)) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d\n%s.%s\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__, shellMessage,
pC_->msgPrintControl_.getSuffix());
}
}
pl_status = setStringParam(pC_->motorMessageText_, userMessage);
if (pl_status != asynSuccess) { if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__, axisNo_, __PRETTY_FUNCTION__,
__LINE__); __LINE__);
} }
poll_status = asynError;
}
/*
Either the software limits or the end switches of the controller have
been hit. Since the EPICS limits are derived from the software limits
and are a little bit smaller, these error cases can only happen if
either the axis has an incremental encoder which is not properly homed
or if a bug occured.
*/
if (positiveLimitSwitch() || negativeLimitSwitch() ||
positiveSoftwareLimit() || negativeSoftwareLimit()) {
// Distinction for developers
if (positiveLimitSwitch()) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis "
"hit the positive limit end switch.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
if (negativeLimitSwitch()) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis "
"hit the negative limit end switch.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
if (positiveSoftwareLimit()) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis "
"hit the positive software limit.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
if (negativeSoftwareLimit()) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nAxis "
"hit the negative software limit.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
// Generic error message for user
pl_status = setStringParam(
pC_->motorMessageText_,
"Software limits or end switch hit. Try homing the motor, "
"moving in the opposite direction or check the SPS for "
"errors (if available). Otherwise please call the "
"support.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
poll_status = asynError;
}
if (overCurrent()) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nOvercurrent "
"error.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
pl_status =
setStringParam(pC_->motorMessageText_,
"Overcurrent error. Please call the support.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
poll_status = asynError;
}
if (overTemperature()) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nOvertemperature "
"error.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
pl_status = setStringParam(
pC_->motorMessageText_,
"Overtemperature error. Please call the support.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
poll_status = asynError;
}
if (overVoltage()) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nOvervoltage "
"error.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
pl_status =
setStringParam(pC_->motorMessageText_,
"Overvoltage error. Please call the support.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
poll_status = asynError;
}
if (underVoltage()) {
asynPrint(
pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nUndervoltage "
"error.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
pl_status =
setStringParam(pC_->motorMessageText_,
"Undervoltage error. Please call the support.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
poll_status = asynError;
}
if (stoFault()) {
asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d\nSTO fault - "
"STO input is on disable state.\n",
pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__);
pl_status = setStringParam(pC_->motorMessageText_,
"STO fault. Please call the support.");
if (pl_status != asynSuccess) {
return pC_->paramLibAccessFailed(pl_status, "motorMessageText_",
axisNo_, __PRETTY_FUNCTION__,
__LINE__);
}
poll_status = asynError;
} }
} else {
pC_->msgPrintControl_.resetCount(keyError);
} }
// Read out the limits, if the motor is not moving // Read out the limits, if the motor is not moving

View File

@ -4,7 +4,7 @@
#include <bitset> #include <bitset>
// Forward declaration of the controller class to resolve the cyclic dependency // Forward declaration of the controller class to resolve the cyclic dependency
// between C804Controller.h and C804Axis.h. See // between the controller and the axis .h-file. See
// https://en.cppreference.com/w/cpp/language/class. // https://en.cppreference.com/w/cpp/language/class.
class masterMacsController; class masterMacsController;

View File

@ -117,39 +117,18 @@ Access one of the axes of the controller via the axis adress stored in asynUser.
If the axis does not exist or is not a Axis, a nullptr is returned and an If the axis does not exist or is not a Axis, a nullptr is returned and an
error is emitted. error is emitted.
*/ */
masterMacsAxis *masterMacsController::getAxis(asynUser *pasynUser) { masterMacsAxis *masterMacsController::getMasterMacsAxis(asynUser *pasynUser) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser); asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser);
return masterMacsController::castToAxis(asynAxis); return dynamic_cast<masterMacsAxis *>(asynAxis);
} }
/* /*
Access one of the axes of the controller via the axis index. Access one of the axes of the controller via the axis index.
If the axis does not exist or is not a Axis, the function must return Null If the axis does not exist or is not a Axis, the function must return Null
*/ */
masterMacsAxis *masterMacsController::getAxis(int axisNo) { masterMacsAxis *masterMacsController::getMasterMacsAxis(int axisNo) {
asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo); asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo);
return masterMacsController::castToAxis(asynAxis); return dynamic_cast<masterMacsAxis *>(asynAxis);
}
masterMacsAxis *masterMacsController::castToAxis(asynMotorAxis *asynAxis) {
// =========================================================================
// If the axis slot of the pAxes_ array is empty, a nullptr must be returned
if (asynAxis == nullptr) {
return nullptr;
}
// Here, an error is emitted since asyn_axis is not a nullptr but also not
// an instance of Axis
masterMacsAxis *axis = dynamic_cast<masterMacsAxis *>(asynAxis);
if (axis == nullptr) {
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;
} }
asynStatus masterMacsController::read(int axisNo, int tcpCmd, char *response) { asynStatus masterMacsController::read(int axisNo, int tcpCmd, char *response) {
@ -195,7 +174,7 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
// ========================================================================= // =========================================================================
masterMacsAxis *axis = getAxis(axisNo); masterMacsAxis *axis = getMasterMacsAxis(axisNo);
if (axis == nullptr) { if (axis == nullptr) {
// We already did the error logging directly in getAxis // We already did the error logging directly in getAxis
return asynError; return asynError;
@ -248,17 +227,17 @@ 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,
"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 =
pasynOctetSyncIO->write(lowLevelPortUser_, fullCommand, pasynOctetSyncIO->write(lowLevelPortUser_, fullCommand,
fullCommandLength, comTimeout_, &nbytesOut); fullCommandLength, comTimeout_, &nbytesOut);
msgPrintControlKey writeKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (status == asynSuccess) { if (status == asynSuccess) {
msgPrintControl_.resetCount(writeKey);
// Try to read the answer repeatedly // Try to read the answer repeatedly
int maxTrials = 2; int maxTrials = 2;
@ -294,15 +273,18 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
} }
} else { } else {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, if (msgPrintControl_.shouldBePrinted(writeKey, true, pasynUserSelf)) {
"Controller \"%s\", axis %d => %s, line %d:\nError %s while " asynPrint(
"writing to the controller\n", this->pasynUserSelf, ASYN_TRACE_ERROR,
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, "Controller \"%s\", axis %d => %s, line %d:\nError %s while "
stringifyAsynStatus(status)); "writing to the controller.%s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status), msgPrintControl_.getSuffix());
}
} }
// MasterMACS needs a bit of time between messages, therefore thr program // MasterMACS needs a bit of time between messages, therefore thr
// execution is paused after the communication happened. // program execution is paused after the communication happened.
// usleep(1500); // usleep(1500);
// Create custom error messages for different failure modes // Create custom error messages for different failure modes
@ -345,10 +327,6 @@ 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,
"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 {
@ -408,6 +386,9 @@ asynStatus masterMacsController::parseResponse(
char printableCommand[MAXBUF_] = {0}; char printableCommand[MAXBUF_] = {0};
char printableResponse[MAXBUF_] = {0}; char printableResponse[MAXBUF_] = {0};
msgPrintControlKey parseKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
// We don't use strlen here since the C string terminator 0x00 // We don't use strlen here since the C string terminator 0x00
// occurs in the middle of the char array. // occurs in the middle of the char array.
for (uint32_t i = 0; i < MAXBUF_; i++) { for (uint32_t i = 0; i < MAXBUF_; i++) {
@ -422,27 +403,39 @@ 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_TRACEIO_DRIVER,
"Controller \"%s\", axis %d => %s, line " if (msgPrintControl_.shouldBePrinted(parseKey, true,
"%d:\nCommunication failed\n", pasynUserSelf)) {
portName, axisNo, __PRETTY_FUNCTION__, __LINE__); asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line "
"%d:\nCommunication failed.%s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
msgPrintControl_.getSuffix());
}
break; break;
} else if (fullResponse[i] == '\x18') { } else if (fullResponse[i] == '\x18') {
// CAN // CAN
snprintf(drvMessageText, MAXBUF_, snprintf(drvMessageText, MAXBUF_,
"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,
"Controller \"%s\", axis %d => %s, line %d:\nTried to " if (msgPrintControl_.shouldBePrinted(parseKey, true,
"write with the read-only command %s\n", pasynUserSelf)) {
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, asynPrint(
printableCommand); this->pasynUserSelf, ASYN_TRACE_ERROR,
"Controller \"%s\", axis %d => %s, line %d:\nTried to "
"write with the read-only command %s.%s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
printableCommand, msgPrintControl_.getSuffix());
}
responseValid = false; responseValid = false;
break; break;
} }
} }
if (responseValid) { if (responseValid) {
msgPrintControl_.resetCount(parseKey);
// Check if the response matches the expectations. Each response // Check if the response matches the expectations. Each response
// contains the string "axisNo R tcpCmd" (including the spaces) // contains the string "axisNo R tcpCmd" (including the spaces)
char expectedResponseSubstring[MAXBUF_] = {0}; char expectedResponseSubstring[MAXBUF_] = {0};
@ -457,22 +450,32 @@ asynStatus masterMacsController::parseResponse(
tcpCmd); tcpCmd);
} }
msgPrintControlKey responseMatchKey =
msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
if (strstr(&fullResponse[responseStart], expectedResponseSubstring) == if (strstr(&fullResponse[responseStart], expectedResponseSubstring) ==
NULL) { NULL) {
adjustForPrint(printableCommand, fullCommand, MAXBUF_); adjustForPrint(printableCommand, fullCommand, MAXBUF_);
adjustForPrint(printableResponse, fullResponse, MAXBUF_); adjustForPrint(printableResponse, fullResponse, MAXBUF_);
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER, if (msgPrintControl_.shouldBePrinted(parseKey, true,
"Controller \"%s\", axis %d => %s, line %d:\nMismatched " pasynUserSelf)) {
"response %s to command %s\n", asynPrint(
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
printableResponse, printableCommand); "Controller \"%s\", axis %d => %s, line %d:\nMismatched "
"response %s to command %s.%s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
printableResponse, printableCommand,
msgPrintControl_.getSuffix());
}
snprintf(drvMessageText, MAXBUF_, snprintf(drvMessageText, MAXBUF_,
"Mismatched response %s to command %s. Please call the " "Mismatched response %s to command %s. Please call the "
"support.", "support.",
printableResponse, printableCommand); printableResponse, printableCommand);
return asynError; return asynError;
} else {
msgPrintControl_.resetCount(responseMatchKey);
} }
} }
return asynSuccess; return asynSuccess;

View File

@ -38,7 +38,7 @@ class masterMacsController : public sinqController {
* @return masterMacsAxis* If no axis could be found, this is a * @return masterMacsAxis* If no axis could be found, this is a
* nullptr * nullptr
*/ */
masterMacsAxis *getAxis(asynUser *pasynUser); masterMacsAxis *getMasterMacsAxis(asynUser *pasynUser);
/** /**
* @brief Get the axis object * @brief Get the axis object
@ -47,7 +47,7 @@ class masterMacsController : public sinqController {
* @return masterMacsAxis* If no axis could be found, this is a * @return masterMacsAxis* If no axis could be found, this is a
* nullptr * nullptr
*/ */
masterMacsAxis *getAxis(int axisNo); masterMacsAxis *getMasterMacsAxis(int axisNo);
protected: protected:
asynUser *lowLevelPortUser_; asynUser *lowLevelPortUser_;
@ -114,15 +114,6 @@ class masterMacsController : public sinqController {
int *valueStop, int axisNo, int tcpCmd, int *valueStop, int axisNo, int tcpCmd,
bool isRead); bool isRead);
/**
* @brief Save cast of the given asynAxis pointer to a masterMacsAxis
* pointer. If the cast fails, this function returns a nullptr.
*
* @param asynAxis
* @return masterMacsAxis*
*/
masterMacsAxis *castToAxis(asynMotorAxis *asynAxis);
private: private:
// Set the maximum buffer size. This is an empirical value which must be // Set the maximum buffer size. This is an empirical value which must be
// large enough to avoid overflows for all commands to the device / // large enough to avoid overflows for all commands to the device /

View File

@ -2,9 +2,14 @@
Code shared by "decodeError.py" and "decodeStatus.py" Code shared by "decodeError.py" and "decodeStatus.py"
""" """
import struct
def decode(value: int, interpretation): def decode(value: int, interpretation):
bit_list = [int(char) for char in bin(value)[2:]] # Pack the input as a short and unpack it as an unsigned short
value_uint16 = format(value, '016b') # Format as 16-bit unsigned integer
bit_list = [int(char) for char in value_uint16]
bit_list.reverse() bit_list.reverse()
interpreted = [] interpreted = []

View File

@ -32,109 +32,6 @@ interpretation = [
("Not specified", "Not specified"), # Bit 15 ("Not specified", "Not specified"), # Bit 15
] ]
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()
if __name__ == "__main__": if __name__ == "__main__":
from sys import argv from sys import argv