From 4ad842c0976ce2dd37b7cc0ce0caf10c96659474 Mon Sep 17 00:00:00 2001 From: smathis Date: Tue, 4 Mar 2025 12:41:20 +0100 Subject: [PATCH] Added new feature msgPrintControl from sinqMotor 0.8.0. Correspondingly, the minimum version requirement for sinqMotor has been bumped to 0.8.0. --- Makefile | 2 +- README.md | 50 +++-- src/masterMacsAxis.cpp | 417 ++++++++++++++++------------------- src/masterMacsAxis.h | 2 +- src/masterMacsController.cpp | 113 +++++----- src/masterMacsController.h | 13 +- utils/decodeCommon.py | 7 +- utils/decodeStatus.py | 103 --------- 8 files changed, 299 insertions(+), 408 deletions(-) diff --git a/Makefile b/Makefile index 87d6eed..ab64d8d 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ REQUIRED+=sinqMotor motorBase_VERSION=7.2.2 # 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. HEADERS += src/masterMacsAxis.h diff --git a/README.md b/README.md index 46bb4e4..fa011a3 100644 --- a/README.md +++ b/README.md @@ -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): - `masterMacsController`: Create a new controller object. - `masterMacsAxis`: Create a new axis object. -These functions are parametrized as follows: + +The full mcu.cmd file looks like this: + ``` -masterMacsController( - "$(NAME)", # Name of the MCU, e.g. mcu1. This parameter should be provided by an environment variable. - "$(ASYN_PORT)", # IP-Port of the MCU. This parameter should be provided by an environment variable. - 8, # Maximum number of axes - 0.05, # Busy poll period in seconds - 1, # Idle poll period in seconds - 0.05 # Communication timeout in seconds - ); -``` -``` -masterMacsAxis( - "$(NAME)", # Name of the associated MCU, e.g. mcu1. This parameter should be provided by an environment variable. - 1, # Index of the axis. - ); +# Define the name of the controller and the corresponding port +epicsEnvSet("NAME","mcu") +epicsEnvSet("ASYN_PORT","p$(NAME)") + +# 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 +drvAsynIPPortConfigure("$(ASYN_PORT)","172.28.101.24:1025") + +# 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 +# 0.05: Busy poll period in seconds +# 1: Idle poll period in seconds +# 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 diff --git a/src/masterMacsAxis.cpp b/src/masterMacsAxis.cpp index 0522412..baf22a4 100644 --- a/src/masterMacsAxis.cpp +++ b/src/masterMacsAxis.cpp @@ -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) : sinqAxis(pC, axisNo), pC_(pC) { @@ -340,8 +366,9 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { return rw_status; } - // Check if we reached the target - if (targetReached()) { + // If the motor is switched off or if it reached its target, it is not + // moving. + if (targetReached() || !switchedOn()) { *moving = false; } else { *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 if a movement has just ended. */ - if (faultConditionSet()) { + msgPrintControlKey keyError = msgPrintControlKey( + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + + if (faultConditionSet() || !(*moving)) { rw_status = readAxisError(); - if (shortCircuit()) { - asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line %d\nShort " - "circuit fault.\n", - 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; - } - + /* + A communication error is a special case. If a communication between + controller and axis error occurred, all subsequent errors are ignored, + since this information is not reliable. + */ if (communicationError()) { - asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line " - "%d\nCommunication error.\n", - pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + if (pC_->msgPrintControl_.shouldBePrinted(keyError, true, + pC_->pasynUserSelf)) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line " + "%d\nCommunication error.%s\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, + pC_->msgPrintControl_.getSuffix()); + } pl_status = setStringParam(pC_->motorMessageText_, @@ -438,172 +421,164 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { } poll_status = asynError; - } + } else { - if (feedbackError()) { - asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line " - "%d\nFeedback error.\n", - pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); + // This buffer must be initialized to zero because we build the + // error message by appending strings. + char userMessage[pC_->MAXBUF_] = {0}; + char shellMessage[pC_->MAXBUF_] = {0}; - pl_status = - setStringParam(pC_->motorMessageText_, - "Feedback error. Please call the support."); + // Concatenate all other errors + if (shortCircuit()) { + 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) { return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", axisNo_, __PRETTY_FUNCTION__, __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 diff --git a/src/masterMacsAxis.h b/src/masterMacsAxis.h index 72ebae9..36d9c80 100644 --- a/src/masterMacsAxis.h +++ b/src/masterMacsAxis.h @@ -4,7 +4,7 @@ #include // 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. class masterMacsController; diff --git a/src/masterMacsController.cpp b/src/masterMacsController.cpp index ea46664..3b027a3 100644 --- a/src/masterMacsController.cpp +++ b/src/masterMacsController.cpp @@ -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 error is emitted. */ -masterMacsAxis *masterMacsController::getAxis(asynUser *pasynUser) { +masterMacsAxis *masterMacsController::getMasterMacsAxis(asynUser *pasynUser) { asynMotorAxis *asynAxis = asynMotorController::getAxis(pasynUser); - return masterMacsController::castToAxis(asynAxis); + return dynamic_cast(asynAxis); } /* 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 */ -masterMacsAxis *masterMacsController::getAxis(int axisNo) { +masterMacsAxis *masterMacsController::getMasterMacsAxis(int axisNo) { asynMotorAxis *asynAxis = asynMotorController::getAxis(axisNo); - return masterMacsController::castToAxis(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(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; + return dynamic_cast(asynAxis); } 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) { // We already did the error logging directly in getAxis return asynError; @@ -248,17 +227,17 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, fullCommand[2] = lenWithMetadataSep.quot; // MSB 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 status = pasynOctetSyncIO->write(lowLevelPortUser_, fullCommand, fullCommandLength, comTimeout_, &nbytesOut); + msgPrintControlKey writeKey = + msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__); + if (status == asynSuccess) { + msgPrintControl_.resetCount(writeKey); // Try to read the answer repeatedly int maxTrials = 2; @@ -294,15 +273,18 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, } } else { - asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line %d:\nError %s while " - "writing to the controller\n", - portName, axisNo, __PRETTY_FUNCTION__, __LINE__, - stringifyAsynStatus(status)); + if (msgPrintControl_.shouldBePrinted(writeKey, true, pasynUserSelf)) { + asynPrint( + this->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d:\nError %s while " + "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 - // execution is paused after the communication happened. + // MasterMACS needs a bit of time between messages, therefore thr + // program execution is paused after the communication happened. // usleep(1500); // 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) if (status == asynSuccess) { 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); } else { @@ -408,6 +386,9 @@ asynStatus masterMacsController::parseResponse( char printableCommand[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 // occurs in the middle of the char array. for (uint32_t i = 0; i < MAXBUF_; i++) { @@ -422,27 +403,39 @@ asynStatus masterMacsController::parseResponse( } else if (fullResponse[i] == '\x15') { // NAK snprintf(drvMessageText, MAXBUF_, "Communication failed."); - asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER, - "Controller \"%s\", axis %d => %s, line " - "%d:\nCommunication failed\n", - portName, axisNo, __PRETTY_FUNCTION__, __LINE__); + + if (msgPrintControl_.shouldBePrinted(parseKey, true, + pasynUserSelf)) { + 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; } else if (fullResponse[i] == '\x18') { // CAN snprintf(drvMessageText, MAXBUF_, "Tried to write with a read-only command. This is a " "bug, please call the support."); - asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line %d:\nTried to " - "write with the read-only command %s\n", - portName, axisNo, __PRETTY_FUNCTION__, __LINE__, - printableCommand); + + if (msgPrintControl_.shouldBePrinted(parseKey, true, + pasynUserSelf)) { + asynPrint( + 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; break; } } if (responseValid) { + msgPrintControl_.resetCount(parseKey); + // Check if the response matches the expectations. Each response // contains the string "axisNo R tcpCmd" (including the spaces) char expectedResponseSubstring[MAXBUF_] = {0}; @@ -457,22 +450,32 @@ asynStatus masterMacsController::parseResponse( tcpCmd); } + msgPrintControlKey responseMatchKey = + msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__); + if (strstr(&fullResponse[responseStart], expectedResponseSubstring) == NULL) { adjustForPrint(printableCommand, fullCommand, MAXBUF_); adjustForPrint(printableResponse, fullResponse, MAXBUF_); - 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); + if (msgPrintControl_.shouldBePrinted(parseKey, true, + pasynUserSelf)) { + asynPrint( + this->pasynUserSelf, ASYN_TRACEIO_DRIVER, + "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_, "Mismatched response %s to command %s. Please call the " "support.", printableResponse, printableCommand); return asynError; + } else { + msgPrintControl_.resetCount(responseMatchKey); } } return asynSuccess; diff --git a/src/masterMacsController.h b/src/masterMacsController.h index 1e4e78e..91c771c 100644 --- a/src/masterMacsController.h +++ b/src/masterMacsController.h @@ -38,7 +38,7 @@ class masterMacsController : public sinqController { * @return masterMacsAxis* If no axis could be found, this is a * nullptr */ - masterMacsAxis *getAxis(asynUser *pasynUser); + masterMacsAxis *getMasterMacsAxis(asynUser *pasynUser); /** * @brief Get the axis object @@ -47,7 +47,7 @@ class masterMacsController : public sinqController { * @return masterMacsAxis* If no axis could be found, this is a * nullptr */ - masterMacsAxis *getAxis(int axisNo); + masterMacsAxis *getMasterMacsAxis(int axisNo); protected: asynUser *lowLevelPortUser_; @@ -114,15 +114,6 @@ class masterMacsController : public sinqController { int *valueStop, int axisNo, int tcpCmd, 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: // Set the maximum buffer size. This is an empirical value which must be // large enough to avoid overflows for all commands to the device / diff --git a/utils/decodeCommon.py b/utils/decodeCommon.py index 06f655e..5a4dcc5 100644 --- a/utils/decodeCommon.py +++ b/utils/decodeCommon.py @@ -2,9 +2,14 @@ Code shared by "decodeError.py" and "decodeStatus.py" """ +import struct + 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() interpreted = [] diff --git a/utils/decodeStatus.py b/utils/decodeStatus.py index 4b16bbc..d5a8dd9 100755 --- a/utils/decodeStatus.py +++ b/utils/decodeStatus.py @@ -32,109 +32,6 @@ interpretation = [ ("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__": from sys import argv