diff --git a/Makefile b/Makefile index ab64d8d..7dd9222 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.8.0 +sinqMotor_VERSION=mathis_s # These headers allow to depend on this library for derived drivers. HEADERS += src/masterMacsAxis.h diff --git a/README.md b/README.md index fa011a3..d6ad25a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ This is a driver for the masterMacs motion controller with the SINQ communication protocol. It is based on the sinqMotor shared library (https://git.psi.ch/sinq-epics-modules/sinqmotor). The header files contain detailed documentation for all public functions. The headers themselves are exported when building the library to allow other drivers to depend on this one. +Compatible to MasterMACS software 2.0.0. + ## User guide This driver is a standard sinqMotor-derived driver and does not need any specific configuration. For the general configuration, please see https://git.psi.ch/sinq-epics-modules/sinqmotor/-/blob/main/README.md. diff --git a/src/masterMacsAxis.cpp b/src/masterMacsAxis.cpp index 6edd0b3..6769273 100644 --- a/src/masterMacsAxis.cpp +++ b/src/masterMacsAxis.cpp @@ -303,6 +303,34 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { // Are we currently waiting for a handshake? if (waitForHandshake_) { + + // Check if the handshake takes too long and communicate an error in + // this case. A handshake should not take more than 5 seconds. + time_t currentTime = time(NULL); + bool timedOut = (currentTime > timeAtHandshake_ + 5); + + if (pC_->msgPrintControl_.shouldBePrinted( + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, timedOut, + pC_->pasynUserSelf)) { + asynPrint(pC_->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d\nAsked for a " + "handshake at %ld s and didn't get a positive reply yet " + "(current time is %ld s).\n", + pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, + timeAtHandshake_, currentTime); + } + + if (timedOut) { + pl_status = + setStringParam(pC_->motorMessageText_, + "Timed out while waiting for a handshake"); + if (pl_status != asynSuccess) { + return pC_->paramLibAccessFailed(pl_status, "motorMessageText_", + axisNo_, __PRETTY_FUNCTION__, + __LINE__); + } + } + pC_->read(axisNo_, 86, response); if (rw_status != asynSuccess) { return rw_status; @@ -580,7 +608,7 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { } } } else { - pC_->msgPrintControl_.resetCount(keyError); + pC_->msgPrintControl_.resetCount(keyError, pC_->pasynUserSelf); } // Read out the limits, if the motor is not moving diff --git a/src/masterMacsController.cpp b/src/masterMacsController.cpp index 3b027a3..57aa1fd 100644 --- a/src/masterMacsController.cpp +++ b/src/masterMacsController.cpp @@ -84,10 +84,9 @@ masterMacsController::masterMacsController(const char *portName, /* Define the end-of-string of a message coming from the device to EPICS. It is not necessary to append a terminator to outgoing messages, since - the message length is encoded in the message header in the getSetResponse - method. + the message length is encoded in the message header. */ - const char *message_from_device = "\x03"; // Hex-code for ETX + const char *message_from_device = "\x0D"; // Hex-code for CR status = pasynOctetSyncIO->setInputEos( lowLevelPortUser_, message_from_device, strlen(message_from_device)); if (status != asynSuccess) { @@ -180,52 +179,17 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, return asynError; } - /* - PSI SINQ uses a custom protocol which is described in - PSI_TCP_Interface_V1-8.pdf (p. // 4-17). - A special case is the message length, which is specified by two bytes - LSB and MSB: MSB = message length / 256 LSB = message length % 256. For - example, a message length of 47 chars would result in MSB = 0, LSB = 47, - whereas a message length of 356 would result in MSB = 1, LSB = 100. - - The full protocol looks as follows: - 0x05 -> Start of protocol frame ENQ - [LSB] - [MSB] - 0x19 -> Data type PDO1 - value [Actual message] It is not necessary to append a terminator, since - this protocol encodes the message length in LSB and MSB. - 0x0D -> Carriage return (ASCII alias \r) - 0x03 -> End of text ETX - */ - - fullCommand[0] = '\x05'; // ENQ - fullCommand[1] = 1; // Placeholder value, can be anything other than 0 - fullCommand[2] = 1; // Placeholder value, can be anything other than 0 - fullCommand[3] = '\x19'; // PD01 - - // Create the command and add CR and ETX at the end + // TODO: CR at the end if (isRead) { - snprintf(&fullCommand[4], MAXBUF_ - 4, "%dR%02d\x0D\x03", axisNo, - tcpCmd); + snprintf(fullCommand, MAXBUF_ - 1, "%dR%02d\x0D", axisNo, tcpCmd); } else { - snprintf(&fullCommand[4], MAXBUF_ - 4, "%dS%02d=%s\x0D\x03", axisNo, - tcpCmd, payload); + snprintf(fullCommand, MAXBUF_ - 1, "%dS%02d=%s\x0D", axisNo, tcpCmd, + payload); } // Calculate the command length const size_t fullCommandLength = strlen(fullCommand); - // Length of the command without ENQ and ETX - const size_t lenWithMetadata = fullCommandLength - 2; - - // Perform both division and modulo operation at once. - div_t lenWithMetadataSep = std::div(lenWithMetadata, 256); - - // Now set the actual command length - fullCommand[1] = lenWithMetadataSep.rem; // LSB - fullCommand[2] = lenWithMetadataSep.quot; // MSB - adjustForPrint(printableCommand, fullCommand, MAXBUF_); // Send out the command @@ -237,14 +201,30 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__); if (status == asynSuccess) { - msgPrintControl_.resetCount(writeKey); + msgPrintControl_.resetCount(writeKey, pasynUserSelf); // Try to read the answer repeatedly int maxTrials = 2; for (int i = 0; i < maxTrials; i++) { + + /* + A typical response of the MasterMacs controller looks like this: + (.. TCP Header ...) 31 20 52 20 31 31 3d 35 31 32 2e 30 30 30 30 06 + 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 + The message terminator is the carriage return (0d), which is + specified in the controller constructor as the end-of-string (EOS) + character. However, we also need to remove the buffer zeros at the + end, because they will otherwise confuse the + pasynOctetSyncIO->read() during the next call. The + pasynOctetSyncIO->flush() method does exactly that: it takes all + bytes it can find in the socket and throws them away. We don't check + the return value of flush(), because it is always asynSuccess (see + https://www.slac.stanford.edu/grp/lcls/controls/global/doc/epics-modules/R3-14-12/asyn/asyn-R4-18-lcls2/asyn/interfaces/asynOctetBase.c) + */ status = pasynOctetSyncIO->read(lowLevelPortUser_, fullResponse, MAXBUF_, comTimeout_, &nbytesIn, &eomReason); + pasynOctetSyncIO->flush(lowLevelPortUser_); if (status == asynSuccess) { status = parseResponse(fullCommand, fullResponse, @@ -283,10 +263,6 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, } } - // 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 switch (status) { case asynSuccess: @@ -434,7 +410,7 @@ asynStatus masterMacsController::parseResponse( } if (responseValid) { - msgPrintControl_.resetCount(parseKey); + msgPrintControl_.resetCount(parseKey, pasynUserSelf); // Check if the response matches the expectations. Each response // contains the string "axisNo R tcpCmd" (including the spaces) @@ -475,7 +451,7 @@ asynStatus masterMacsController::parseResponse( printableResponse, printableCommand); return asynError; } else { - msgPrintControl_.resetCount(responseMatchKey); + msgPrintControl_.resetCount(responseMatchKey, pasynUserSelf); } } return asynSuccess;