diff --git a/Makefile b/Makefile index 28223e1..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.12.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 1a14a37..6234ac9 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ This driver is a standard sinqMotor-derived driver and does not need any specifi The folder "utils" contains utility scripts for working with masterMacs motor controllers: - decodeStatus.py: Take the return message of a R10 (read status) command and print it in human-readable form. +- decodeError.py: Take the return message of a R11 (read error) command and print it in human-readable form. +- writeRead.py: Send messages to the controller and receive answers. ## Developer guide diff --git a/src/masterMacsAxis.cpp b/src/masterMacsAxis.cpp index 7fe800b..65f1d9d 100644 --- a/src/masterMacsAxis.cpp +++ b/src/masterMacsAxis.cpp @@ -11,6 +11,14 @@ #include #include +/* +A special communication timeout is used in the following two cases: +1) Enable command +2) First move command after enabling the motor +This is due to MasterMACS running a powerup cycle, which can delay the answer. + */ +#define PowerCycleTimeout 10.0 // Value in seconds + /* Contains all instances of turboPmacAxis which have been created and is used in the initialization hook function. @@ -79,7 +87,7 @@ masterMacsAxis::masterMacsAxis(masterMacsController *pC, int axisNo) stop completely, since this is a configuration error. */ if (axisNo >= pC->numAxes()) { - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR, + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:: FATAL ERROR: " "Axis index %d must be smaller than the total number of axes " "%d. Call the support.", @@ -106,12 +114,13 @@ masterMacsAxis::masterMacsAxis(masterMacsController *pC, int axisNo) // Flag for handshake waiting waitForHandshake_ = false; timeAtHandshake_ = 0; + targetReachedUninitialized_ = true; // masterMacs motors can always be disabled status = pC_->setIntegerParam(axisNo_, pC_->motorCanDisable(), 1); if (status != asynSuccess) { asynPrint( - pC_->asynUserSelf(), ASYN_TRACE_ERROR, + pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR " "(setting a parameter value failed with %s)\n. Terminating IOC", pC_->portName, axisNo, __PRETTY_FUNCTION__, __LINE__, @@ -123,7 +132,7 @@ masterMacsAxis::masterMacsAxis(masterMacsController *pC, int axisNo) status = pC_->setIntegerParam(axisNo_, pC_->motorStatusMoving(), false); if (status != asynSuccess) { asynPrint( - pC_->asynUserSelf(), ASYN_TRACE_ERROR, + pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\nFATAL ERROR " "(setting a parameter value failed with %s)\n. Terminating IOC", pC_->portName, axisNo, __PRETTY_FUNCTION__, __LINE__, @@ -148,7 +157,7 @@ asynStatus masterMacsAxis::init() { // Local variable declaration asynStatus pl_status = asynSuccess; - char response[pC_->MAXBUF_]; + char response[pC_->MAXBUF_] = {0}; int nvals = 0; double motorRecResolution = 0.0; double motorPosition = 0.0; @@ -167,7 +176,7 @@ asynStatus masterMacsAxis::init() { &motorRecResolution); if (pl_status == asynParamUndefined) { if (now + maxInitTime < time(NULL)) { - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR, + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line " "%d\nInitializing the parameter library failed.\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, @@ -259,7 +268,7 @@ asynStatus masterMacsAxis::init() { // If we can't communicate with the parameter library, it doesn't // make sense to try and upstream this to the user -> Just log the // error - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR, + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line " "%d:\ncallParamCallbacks failed with %s.\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, @@ -281,7 +290,7 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { // Status of parameter library operations asynStatus pl_status = asynSuccess; - char response[pC_->MAXBUF_]; + char response[pC_->MAXBUF_] = {0}; int nvals = 0; int direction = 0; @@ -306,8 +315,8 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { if (pC_->getMsgPrintControl().shouldBePrinted( pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, timedOut, - pC_->asynUserSelf())) { - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR, + pC_->pasynUser())) { + asynPrint(pC_->pasynUser(), 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", @@ -341,6 +350,7 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { // Handshake has been performed successfully -> Continue with the // poll waitForHandshake_ = false; + targetReachedUninitialized_ = false; } else { // Still waiting for the handshake - try again in the next busy // poll. This is already part of the movement procedure. @@ -385,12 +395,18 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { return rw_status; } - // If the motor is switched off or if it reached its target, it is not - // moving. - if (targetReached() || !switchedOn()) { + if (targetReachedUninitialized_) { *moving = false; } else { - *moving = true; + if (targetReached() || !switchedOn()) { + *moving = false; + } else { + *moving = true; + } + } + + if (targetReached()) { + targetReachedUninitialized_ = false; } // Read the current position @@ -420,9 +436,9 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { since this information is not reliable. */ if (communicationError()) { - if (pC_->getMsgPrintControl().shouldBePrinted( - keyError, true, pC_->asynUserSelf())) { - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR, + if (pC_->getMsgPrintControl().shouldBePrinted(keyError, true, + pC_->pasynUser())) { + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line " "%d\nCommunication error.%s\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, @@ -579,8 +595,8 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { if (strlen(shellMessage) > 0) { if (pC_->getMsgPrintControl().shouldBePrinted( - keyError, true, pC_->asynUserSelf())) { - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR, + keyError, true, pC_->pasynUser())) { + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line " "%d\n%s%s\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, @@ -597,7 +613,7 @@ asynStatus masterMacsAxis::doPoll(bool *moving) { } } } else { - pC_->getMsgPrintControl().resetCount(keyError, pC_->asynUserSelf()); + pC_->getMsgPrintControl().resetCount(keyError, pC_->pasynUser()); } // Read out the limits, if the motor is not moving @@ -750,7 +766,7 @@ asynStatus masterMacsAxis::doMove(double position, int relative, } if (enabled == 0) { - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR, + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\nAxis is " "disabled.\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); @@ -762,7 +778,7 @@ asynStatus masterMacsAxis::doMove(double position, int relative, motorVelocity = maxVelocity * motorRecResolution; asynPrint( - pC_->asynUserSelf(), ASYN_TRACE_FLOW, + pC_->pasynUser(), ASYN_TRACE_FLOW, "Controller \"%s\", axis %d => %s, line %d:\nMove to position %lf.\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, position); @@ -805,7 +821,7 @@ asynStatus masterMacsAxis::doMove(double position, int relative, return rw_status; } - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_FLOW, + asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW, "Controller \"%s\", axis %d => %s, line %d:\nSetting speed " "to %lf.\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, @@ -825,11 +841,17 @@ asynStatus masterMacsAxis::doMove(double position, int relative, return rw_status; } + // If the motor has just been enabled, use Enable + double timeout = pC_->comTimeout(); + if (targetReachedUninitialized_ && timeout < PowerCycleTimeout) { + timeout = PowerCycleTimeout; + } + // Start the move if (relative) { - rw_status = pC_->write(axisNo_, 00, "2"); + rw_status = pC_->write(axisNo_, 00, "2", timeout); } else { - rw_status = pC_->write(axisNo_, 00, "1"); + rw_status = pC_->write(axisNo_, 00, "1", timeout); } if (rw_status != asynSuccess) { pl_status = setIntegerParam(pC_->motorStatusProblem(), true); @@ -912,7 +934,7 @@ asynStatus masterMacsAxis::doHome(double min_velocity, double max_velocity, // Status of parameter library operations asynStatus pl_status = asynSuccess; - char response[pC_->MAXBUF_]; + char response[pC_->MAXBUF_] = {0}; // ========================================================================= @@ -964,7 +986,8 @@ asynStatus masterMacsAxis::readEncoderType() { // Status of parameter library operations asynStatus pl_status = asynSuccess; - char command[pC_->MAXBUF_], response[pC_->MAXBUF_]; + char command[pC_->MAXBUF_] = {0}; + char response[pC_->MAXBUF_] = {0}; int nvals = 0; int encoder_id = 0; @@ -1016,6 +1039,13 @@ asynStatus masterMacsAxis::enable(bool on) { // ========================================================================= + /* + When the motor is changing its enable state, its targetReached bit is set to + 0. In order to prevent the poll method in interpreting the motor state as + "moving", this flag is used. It is reset in the handshake. + */ + targetReachedUninitialized_ = true; + doPoll(&moving); // If the axis is currently moving, it cannot be disabled. Ignore the @@ -1023,7 +1053,7 @@ asynStatus masterMacsAxis::enable(bool on) { // axis instead of "moving", since status -6 is also moving, but the // motor can actually be disabled in this state! if (moving) { - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR, + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\nAxis is not " "idle and can therefore not be disabled.\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__); @@ -1045,7 +1075,7 @@ asynStatus masterMacsAxis::enable(bool on) { if ((readyToBeSwitchedOn() && switchedOn()) == on) { asynPrint( - pC_->asynUserSelf(), ASYN_TRACE_WARNING, + pC_->pasynUser(), ASYN_TRACE_WARNING, "Controller \"%s\", axis %d => %s, line %d:\nAxis is already %s.\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, on ? "enabled" : "disabled"); @@ -1054,7 +1084,7 @@ asynStatus masterMacsAxis::enable(bool on) { // Enable / disable the axis if it is not moving snprintf(value, sizeof(value), "%d", on); - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_FLOW, + asynPrint(pC_->pasynUser(), ASYN_TRACE_FLOW, "Controller \"%s\", axis %d => %s, line %d:\n%s axis.\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, on ? "Enable" : "Disable"); @@ -1072,7 +1102,12 @@ asynStatus masterMacsAxis::enable(bool on) { // The answer to the enable command on MasterMACS might take some time, // hence we wait for a custom timespan in seconds instead of // pC_->comTimeout_ - rw_status = pC_->write(axisNo_, 04, value, 1.0); + double timeout = pC_->comTimeout(); + if (targetReachedUninitialized_ && timeout < PowerCycleTimeout) { + timeout = PowerCycleTimeout; + } + + rw_status = pC_->write(axisNo_, 04, value, timeout); if (rw_status != asynSuccess) { return rw_status; } @@ -1099,7 +1134,7 @@ asynStatus masterMacsAxis::enable(bool on) { // Failed to change axis status within timeout_enable_disable => Send a // corresponding message - asynPrint(pC_->asynUserSelf(), ASYN_TRACE_ERROR, + asynPrint(pC_->pasynUser(), ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\nFailed to %s axis " "within %d seconds\n", pC_->portName, axisNo_, __PRETTY_FUNCTION__, __LINE__, @@ -1132,7 +1167,7 @@ std::bitset<16> toBitset(float val) { } asynStatus masterMacsAxis::readAxisStatus() { - char response[pC_->MAXBUF_]; + char response[pC_->MAXBUF_] = {0}; // ========================================================================= @@ -1153,7 +1188,7 @@ asynStatus masterMacsAxis::readAxisStatus() { } asynStatus masterMacsAxis::readAxisError() { - char response[pC_->MAXBUF_]; + char response[pC_->MAXBUF_] = {0}; // ========================================================================= diff --git a/src/masterMacsAxis.h b/src/masterMacsAxis.h index 4eb2b83..77c9d74 100644 --- a/src/masterMacsAxis.h +++ b/src/masterMacsAxis.h @@ -111,6 +111,8 @@ class masterMacsAxis : public sinqAxis { bool waitForHandshake_; time_t timeAtHandshake_; + bool targetReachedUninitialized_; + asynStatus readConfig(); /* diff --git a/src/masterMacsController.cpp b/src/masterMacsController.cpp index c08e8d8..ba49126 100644 --- a/src/masterMacsController.cpp +++ b/src/masterMacsController.cpp @@ -70,7 +70,7 @@ masterMacsController::masterMacsController(const char *portName, the message length is encoded in the message header. */ const char *message_from_device = "\x0D"; // Hex-code for CR - status = pasynOctetSyncIO->setInputEos(ipPortAsynOctetSyncIO_, + status = pasynOctetSyncIO->setInputEos(pasynOctetSyncIOipPort(), message_from_device, strlen(message_from_device)); if (status != asynSuccess) { @@ -79,7 +79,7 @@ masterMacsController::masterMacsController(const char *portName, "input EOS failed with %s).\nTerminating IOC", portName, __PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); - pasynOctetSyncIO->disconnect(ipPortAsynOctetSyncIO_); + pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort()); exit(-1); } @@ -90,7 +90,7 @@ masterMacsController::masterMacsController(const char *portName, "ParamLib callbacks failed with %s).\nTerminating IOC", portName, __PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); - pasynOctetSyncIO->disconnect(ipPortAsynOctetSyncIO_); + pasynOctetSyncIO->disconnect(pasynOctetSyncIOipPort()); exit(-1); } } @@ -133,8 +133,6 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, asynStatus pl_status = asynSuccess; char fullCommand[MAXBUF_] = {0}; char fullResponse[MAXBUF_] = {0}; - char printableCommand[MAXBUF_] = {0}; - char printableResponse[MAXBUF_] = {0}; char drvMessageText[MAXBUF_] = {0}; int motorStatusProblem = 0; @@ -158,7 +156,7 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, // ========================================================================= - // Check if a timeout has been given + // Check if a custom timeout has been given if (comTimeout < 0.0) { comTimeout = comTimeout_; } @@ -169,6 +167,7 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, return asynError; } + // Build the full command depending on the inputs to this function if (isRead) { snprintf(fullCommand, MAXBUF_ - 1, "%dR%02d\x0D", axisNo, tcpCmd); } else { @@ -183,95 +182,39 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, // Calculate the command length const size_t fullCommandLength = strlen(fullCommand); - adjustForPrint(printableCommand, fullCommand, MAXBUF_); + // Flush the IOC-side socket, then write the command and wait for the + // response. + status = pasynOctetSyncIO->writeRead( + pasynOctetSyncIOipPort(), fullCommand, fullCommandLength, fullResponse, + MAXBUF_, comTimeout, &nbytesOut, &nbytesIn, &eomReason); - // Send out the command - status = pasynOctetSyncIO->write(ipPortAsynOctetSyncIO_, fullCommand, - fullCommandLength, comTimeout, &nbytesOut); - - if (status != asynSuccess) { - 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)); - } - - msgPrintControlKey writeKey = + // If a communication error occured, print this message to the + msgPrintControlKey comKey = msgPrintControlKey(portName, axisNo, __PRETTY_FUNCTION__, __LINE__); - - if (status == asynSuccess) { - 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(ipPortAsynOctetSyncIO_, - fullResponse, MAXBUF_, comTimeout, - &nbytesIn, &eomReason); - pasynOctetSyncIO->flush(ipPortAsynOctetSyncIO_); - - if (status == asynSuccess) { - status = parseResponse(fullCommand, fullResponse, - drvMessageText, &valueStart, &valueStop, - axisNo, tcpCmd, isRead); - - if (status == asynSuccess) { - // Received the correct message - break; - } - } else { - if (status != asynTimeout) { - asynPrint( - this->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line %d:\nError " - "%s while reading from the controller\n", - portName, axisNo, __PRETTY_FUNCTION__, __LINE__, - stringifyAsynStatus(status)); - break; - } - } - - if (i + 1 == maxTrials && status == asynError) { - asynPrint( - this->pasynUserSelf, ASYN_TRACE_ERROR, - "Controller \"%s\", axis %d => %s, line %d:\nFailed " - "%d times to get the correct response. Aborting read.\n", - portName, axisNo, __PRETTY_FUNCTION__, __LINE__, maxTrials); - } + if (status != asynSuccess) { + if (msgPrintControl_.shouldBePrinted(comKey, true, pasynUserSelf)) { + char printableCommand[MAXBUF_] = {0}; + adjustForPrint(printableCommand, fullCommand, MAXBUF_); + asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, + "Controller \"%s\", axis %d => %s, line %d:\nError " + "%s while sending command %s to the controller\n", + portName, axisNo, __PRETTY_FUNCTION__, __LINE__, + stringifyAsynStatus(status), printableCommand); } - } else { - 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()); - } + msgPrintControl_.resetCount(comKey, pasynUserSelf); } // Create custom error messages for different failure modes switch (status) { case asynSuccess: + // We did get a response, but does it make sense and is it designated as + // OK from the controller? This is checked here. + status = parseResponse(fullCommand, fullResponse, drvMessageText, + &valueStart, &valueStop, axisNo, tcpCmd, isRead); - if (isRead) { + // Read out the important information from the response + if (status == asynSuccess && isRead) { /* If a property has been read, we need just the part between the "=" (0x3D) and the [ACK] (0x06). Therefore, we remove all @@ -282,31 +225,30 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd, response[i] = fullResponse[i + valueStart]; } } - break; case asynTimeout: snprintf(drvMessageText, sizeof(drvMessageText), - "connection timeout for axis %d", axisNo); + "Connection timeout. Please call the support."); break; case asynDisconnected: snprintf(drvMessageText, sizeof(drvMessageText), - "axis is not connected"); + "Axis is not connected."); break; case asynDisabled: - snprintf(drvMessageText, sizeof(drvMessageText), "axis is disabled"); + snprintf(drvMessageText, sizeof(drvMessageText), "Axis is disabled."); break; case asynError: // Do nothing - error message drvMessageText has already been set. break; default: snprintf(drvMessageText, sizeof(drvMessageText), - "Communication failed (%s)", stringifyAsynStatus(status)); + "Communication failed (%s). Please call the support.", + stringifyAsynStatus(status)); break; } // Log the overall status (communication successfull or not) if (status == asynSuccess) { - adjustForPrint(printableResponse, fullResponse, MAXBUF_); pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0); } else { @@ -401,6 +343,7 @@ asynStatus masterMacsController::parseResponse( if (msgPrintControl_.shouldBePrinted(parseKey, true, pasynUserSelf)) { + adjustForPrint(printableCommand, fullCommand, MAXBUF_); asynPrint( this->pasynUserSelf, ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\nTried to " @@ -440,13 +383,13 @@ asynStatus masterMacsController::parseResponse( 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()); + 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_, diff --git a/src/masterMacsController.h b/src/masterMacsController.h index d2cc06a..a3c34a1 100644 --- a/src/masterMacsController.h +++ b/src/masterMacsController.h @@ -118,6 +118,13 @@ class masterMacsController : public sinqController { // responses from it. static const uint32_t MAXBUF_ = 200; + /** + * @brief Get the communication timeout used in the writeRead command + * + * @return double Timeout in seconds + */ + double comTimeout() { return comTimeout_; } + private: /* Stores the constructor input comTimeout diff --git a/utils/writeRead.py b/utils/writeRead.py new file mode 100644 index 0000000..25524fb --- /dev/null +++ b/utils/writeRead.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +""" +This script allows direct interaction with a MasterMACS-Controller over an ethernet connection. +To read the manual, simply run this script without any arguments. + +Stefan Mathis, April 2025 +""" + +import struct +import socket +import curses + +def packMasterMacsCommand(command): + # 0x0D = Carriage return + buf = struct.pack('B',0x0D) + buf = bytes(command,'utf-8') + buf + return bytes(command,'utf-8') + +def readMasterMacsReply(input): + msg = bytearray() + expectAck = True + while True: + b = input.recv(1) + bint = int.from_bytes(b,byteorder='little') + if bint == 2 or bint == 7: #STX or BELL + expectAck = False + continue + if expectAck and bint == 6: # ACK + return bytes(msg) + else: + if bint == 13 and not expectAck: # CR + return bytes(msg) + else: + msg.append(bint) + +if __name__ == "__main__": + from sys import argv + + try: + + addr = argv[1].split(':') + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + s.connect((addr[0],int(addr[1]))) + + if len(argv) == 3: + buf = packMasterMacsCommand(argv[2]) + s.send(buf) + reply = readMasterMacsReply(s) + print(reply.decode('utf-8') + '\n') + + else: + + try: + + 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]: + buf = packMasterMacsCommand(history[ptr]) + s.send(buf) + reply = readMasterMacsReply(s) + stdscr.addstr("\n" + reply.decode('utf-8')[0:-1]) + + 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() + + finally: + + # to quit + curses.nocbreak() + stdscr.keypad(False) + curses.echo() + curses.endwin() + + except: + print(""" + Invalid Arguments + + Option 1: Single Command + ------------------------ + + Usage: writeRead.py pmachost:port command + This then returns the response for command. + + Option 2: CLI Mode + ------------------ + + Usage: writeRead.py pmachost:port + + You can then type in a command, hit enter, and the response will see + the reponse, before being prompted to again enter a command. Type + 'quit' to close prompt. + """) +