Fully-featured version of the masterMACS driver

This commit is contained in:
2025-02-14 16:42:43 +01:00
parent ea8c34ab84
commit 6321a78b78
8 changed files with 972 additions and 349 deletions

View File

@ -7,11 +7,13 @@ EPICS_VERSIONS=7.0.7
ARCH_FILTER=RHEL% ARCH_FILTER=RHEL%
# Additional module dependencies # Additional module dependencies
REQUIRED+=asynMotor
REQUIRED+=sinqMotor REQUIRED+=sinqMotor
# Specify the version of asynMotor we want to build against
motorBase_VERSION=7.2.2
# Specify the version of sinqMotor we want to build against # Specify the version of sinqMotor we want to build against
sinqMotor_VERSION=0.6.3 sinqMotor_VERSION=0.7.0
# These headers allow to depend on this library for derived drivers. # These headers allow to depend on this library for derived drivers.
HEADERS += src/masterMacsAxis.h HEADERS += src/masterMacsAxis.h

View File

@ -1,4 +1,5 @@
#--------------------------------------------- #---------------------------------------------
# SINQ specific DB definitions # SINQ specific DB definitions
#--------------------------------------------- #---------------------------------------------
registrar(masterMacsRegister) registrar(masterMacsControllerRegister)
registrar(masterMacsAxisRegister)

File diff suppressed because it is too large Load Diff

View File

@ -70,7 +70,7 @@ class masterMacsAxis : public sinqAxis {
double max_velocity, double acceleration); double max_velocity, double acceleration);
/** /**
* @brief Implementation of the `atFirstPoll` function from sinqAxis. * @brief Readout of some values from the controller at IOC startup
* *
* The following steps are performed: * The following steps are performed:
* - Read out the motor status, motor position, velocity and acceleration * - Read out the motor status, motor position, velocity and acceleration
@ -79,7 +79,7 @@ class masterMacsAxis : public sinqAxis {
* *
* @return asynStatus * @return asynStatus
*/ */
asynStatus atFirstPoll(); asynStatus init();
/** /**
* @brief Enable / disable the axis. * @brief Enable / disable the axis.
@ -100,9 +100,10 @@ class masterMacsAxis : public sinqAxis {
protected: protected:
masterMacsController *pC_; masterMacsController *pC_;
double lastSetSpeed_; double lastSetSpeed_;
bool waitForHandshake_;
time_t timeAtHandshake_;
asynStatus readConfig(); asynStatus readConfig();
bool initial_poll_;
/* /*
The axis status and axis error of MasterMACS are given as an integer from The axis status and axis error of MasterMACS are given as an integer from
@ -134,10 +135,7 @@ class masterMacsAxis : public sinqAxis {
*/ */
bool switchedOn() { return axisStatus_[1]; } bool switchedOn() { return axisStatus_[1]; }
/** // Bit 2 is unused
* @brief Read the property from axisStatus_
*/
bool enabled() { return axisStatus_[2]; }
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
@ -162,12 +160,9 @@ class masterMacsAxis : public sinqAxis {
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
*/ */
bool newMoveCommandWhileMoving() { return axisStatus_[7]; } bool warning() { return axisStatus_[7]; }
/** // Bit 8 is unused
* @brief Read the property from axisStatus_
*/
bool isMoving() { return axisStatus_[8]; }
/** /**
* @brief Read the property from axisStatus_ * @brief Read the property from axisStatus_
@ -209,6 +204,76 @@ class masterMacsAxis : public sinqAxis {
boolean. boolean.
*/ */
/**
* @brief Read the property from axisError_
*/
bool shortCircuit() { return axisError_[1]; }
/**
* @brief Read the property from axisError_
*/
bool encoderError() { return axisError_[2]; }
/**
* @brief Read the property from axisError_
*/
bool followingError() { return axisError_[3]; }
/**
* @brief Read the property from axisError_
*/
bool communicationError() { return axisError_[4]; }
/**
* @brief Read the property from axisError_
*/
bool feedbackError() { return axisError_[5]; }
/**
* @brief Read the property from axisError_
*/
bool positiveLimitSwitch() { return axisError_[6]; }
/**
* @brief Read the property from axisError_
*/
bool negativeLimitSwitch() { return axisError_[7]; }
/**
* @brief Read the property from axisError_
*/
bool positiveSoftwareLimit() { return axisError_[8]; }
/**
* @brief Read the property from axisError_
*/
bool negativeSoftwareLimit() { return axisError_[9]; }
/**
* @brief Read the property from axisError_
*/
bool overCurrent() { return axisError_[10]; }
/**
* @brief Read the property from axisError_
*/
bool overTemperature() { return axisError_[11]; }
/**
* @brief Read the property from axisError_
*/
bool overVoltage() { return axisError_[12]; }
/**
* @brief Read the property from axisError_
*/
bool underVoltage() { return axisError_[13]; }
/**
* @brief Read the property from axisError_
*/
bool stoFault() { return axisError_[15]; }
private: private:
friend class masterMacsController; friend class masterMacsController;
}; };

View File

@ -75,10 +75,9 @@ masterMacsController::masterMacsController(const char *portName,
*/ */
pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL); pasynOctetSyncIO->connect(ipPortConfigName, 0, &lowLevelPortUser_, NULL);
if (status != asynSuccess || lowLevelPortUser_ == nullptr) { if (status != asynSuccess || lowLevelPortUser_ == nullptr) {
errlogPrintf( errlogPrintf("Controller \"%s\" => %s, line %d:\nFATAL ERROR (cannot "
"%s => line %d:\nFATAL ERROR (cannot connect to MCU controller).\n" "connect to MCU controller).\nTerminating IOC",
"Terminating IOC", portName, __PRETTY_FUNCTION__, __LINE__);
__PRETTY_FUNCTION__, __LINE__);
exit(-1); exit(-1);
} }
@ -93,20 +92,21 @@ masterMacsController::masterMacsController(const char *portName,
lowLevelPortUser_, message_from_device, strlen(message_from_device)); lowLevelPortUser_, message_from_device, strlen(message_from_device));
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nFATAL ERROR (setting input EOS failed " "Controller \"%s\" => %s, line %d:\nFATAL ERROR (setting "
"with %s).\nTerminating IOC", "input EOS failed with %s).\nTerminating IOC",
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); portName, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_); pasynOctetSyncIO->disconnect(lowLevelPortUser_);
exit(-1); exit(-1);
} }
status = callParamCallbacks(); status = callParamCallbacks();
if (status != asynSuccess) { if (status != asynSuccess) {
asynPrint( asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
this->pasynUserSelf, ASYN_TRACE_ERROR, "Controller \"%s\" => %s, line %d:\nFATAL ERROR (executing "
"%s => line %d:\nFATAL ERROR (executing ParamLib callbacks failed " "ParamLib callbacks failed with %s).\nTerminating IOC",
"with %s).\nTerminating IOC", portName, __PRETTY_FUNCTION__, __LINE__,
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); stringifyAsynStatus(status));
pasynOctetSyncIO->disconnect(lowLevelPortUser_); pasynOctetSyncIO->disconnect(lowLevelPortUser_);
exit(-1); exit(-1);
} }
@ -144,10 +144,10 @@ masterMacsAxis *masterMacsController::castToAxis(asynMotorAxis *asynAxis) {
// an instance of Axis // an instance of Axis
masterMacsAxis *axis = dynamic_cast<masterMacsAxis *>(asynAxis); masterMacsAxis *axis = dynamic_cast<masterMacsAxis *>(asynAxis);
if (axis == nullptr) { if (axis == nullptr) {
asynPrint( asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
this->pasynUserSelf, ASYN_TRACE_ERROR, "Controller \"%s\", axis %d => %s, line %d:\nAxis is not "
"%s => line %d:\nAxis %d is not an instance of masterMacsAxis", "an instance of masterMacsAxis",
__PRETTY_FUNCTION__, __LINE__, axis->axisNo_); portName, axis->axisNo_, __PRETTY_FUNCTION__, __LINE__);
} }
return axis; return axis;
} }
@ -248,9 +248,10 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
fullCommand[2] = lenWithMetadataSep.quot; // MSB fullCommand[2] = lenWithMetadataSep.quot; // MSB
adjustForPrint(printableCommand, fullCommand, MAXBUF_); adjustForPrint(printableCommand, fullCommand, MAXBUF_);
asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER, asynPrint(
"%s => line %d:\nSending command %s\n", __PRETTY_FUNCTION__, this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
__LINE__, printableCommand); "Controller \"%s\", axis %d => %s, line %d:\nSending command %s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, printableCommand);
// Send out the command // Send out the command
status = status =
@ -276,25 +277,28 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
} }
} else { } else {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nError %s while reading from the " "Controller \"%s\", axis %d => %s, line %d:\nError "
"controller\n", "%s while reading from the controller\n",
__PRETTY_FUNCTION__, __LINE__, portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status)); stringifyAsynStatus(status));
break; break;
} }
if (i + 1 == maxTrials && status == asynError) { if (i + 1 == maxTrials && status == asynError) {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(
"%s => line %d:\nFailed %d times to get the " this->pasynUserSelf, ASYN_TRACE_ERROR,
"correct response. Aborting read.\n", "Controller \"%s\", axis %d => %s, line %d:\nFailed "
__PRETTY_FUNCTION__, __LINE__, maxTrials); "%d times to get the correct response. Aborting read.\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, maxTrials);
} }
} }
} else { } else {
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nError %s while writing to the controller\n", "Controller \"%s\", axis %d => %s, line %d:\nError %s while "
__PRETTY_FUNCTION__, __LINE__, stringifyAsynStatus(status)); "writing to the controller\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
stringifyAsynStatus(status));
} }
// MasterMACS needs a bit of time between messages, therefore thr program // MasterMACS needs a bit of time between messages, therefore thr program
@ -341,9 +345,10 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
// Log the overall status (communication successfull or not) // Log the overall status (communication successfull or not)
if (status == asynSuccess) { if (status == asynSuccess) {
adjustForPrint(printableResponse, fullResponse, MAXBUF_); adjustForPrint(printableResponse, fullResponse, MAXBUF_);
asynPrint(lowLevelPortUser_, ASYN_TRACEIO_DRIVER, asynPrint(
"%s => line %d:\nReturn value: %s\n", __PRETTY_FUNCTION__, lowLevelPortUser_, ASYN_TRACEIO_DRIVER,
__LINE__, printableResponse); "Controller \"%s\", axis %d => %s, line %d:\nReturn value: %s\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__, printableResponse);
pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0); pl_status = axis->setIntegerParam(this->motorStatusCommsError_, 0);
} else { } else {
@ -355,26 +360,29 @@ asynStatus masterMacsController::writeRead(int axisNo, int tcpCmd,
getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem); getIntegerParam(axisNo, motorStatusProblem_, &motorStatusProblem);
if (pl_status != asynSuccess) { if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem_", return paramLibAccessFailed(pl_status, "motorStatusProblem_",
__PRETTY_FUNCTION__, __LINE__); axisNo, __PRETTY_FUNCTION__, __LINE__);
} }
if (motorStatusProblem == 0) { if (motorStatusProblem == 0) {
pl_status = axis->setStringParam(motorMessageText_, drvMessageText); pl_status = axis->setStringParam(motorMessageText_, drvMessageText);
if (pl_status != asynSuccess) { if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorMessageText_", return paramLibAccessFailed(pl_status, "motorMessageText_",
__PRETTY_FUNCTION__, __LINE__); axisNo, __PRETTY_FUNCTION__,
__LINE__);
} }
pl_status = axis->setIntegerParam(motorStatusProblem_, 1); pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) { if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusProblem", return paramLibAccessFailed(pl_status, "motorStatusProblem",
__PRETTY_FUNCTION__, __LINE__); axisNo, __PRETTY_FUNCTION__,
__LINE__);
} }
pl_status = axis->setIntegerParam(motorStatusProblem_, 1); pl_status = axis->setIntegerParam(motorStatusProblem_, 1);
if (pl_status != asynSuccess) { if (pl_status != asynSuccess) {
return paramLibAccessFailed(pl_status, "motorStatusCommsError_", return paramLibAccessFailed(pl_status, "motorStatusCommsError_",
__PRETTY_FUNCTION__, __LINE__); axisNo, __PRETTY_FUNCTION__,
__LINE__);
} }
} }
} }
@ -414,9 +422,10 @@ asynStatus masterMacsController::parseResponse(
} else if (fullResponse[i] == '\x15') { } else if (fullResponse[i] == '\x15') {
// NAK // NAK
snprintf(drvMessageText, MAXBUF_, "Communication failed."); snprintf(drvMessageText, MAXBUF_, "Communication failed.");
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nCommunication failed\n", "Controller \"%s\", axis %d => %s, line "
__PRETTY_FUNCTION__, __LINE__); "%d:\nCommunication failed\n",
portName, axisNo, __PRETTY_FUNCTION__, __LINE__);
break; break;
} else if (fullResponse[i] == '\x18') { } else if (fullResponse[i] == '\x18') {
// CAN // CAN
@ -424,9 +433,10 @@ asynStatus masterMacsController::parseResponse(
"Tried to write with a read-only command. This is a " "Tried to write with a read-only command. This is a "
"bug, please call the support."); "bug, please call the support.");
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR,
"%s => line %d:\nTried to write with the read-only " "Controller \"%s\", axis %d => %s, line %d:\nTried to "
"command %s\n", "write with the read-only command %s\n",
__PRETTY_FUNCTION__, __LINE__, printableCommand); portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
printableCommand);
responseValid = false; responseValid = false;
break; break;
} }
@ -452,11 +462,11 @@ asynStatus masterMacsController::parseResponse(
adjustForPrint(printableCommand, fullCommand, MAXBUF_); adjustForPrint(printableCommand, fullCommand, MAXBUF_);
adjustForPrint(printableResponse, fullResponse, MAXBUF_); adjustForPrint(printableResponse, fullResponse, MAXBUF_);
asynPrint(this->pasynUserSelf, ASYN_TRACE_ERROR, asynPrint(this->pasynUserSelf, ASYN_TRACEIO_DRIVER,
"%s => line %d:\nMismatched response %s to " "Controller \"%s\", axis %d => %s, line %d:\nMismatched "
"command %s\n", "response %s to command %s\n",
__PRETTY_FUNCTION__, __LINE__, printableResponse, portName, axisNo, __PRETTY_FUNCTION__, __LINE__,
printableCommand); printableResponse, printableCommand);
snprintf(drvMessageText, MAXBUF_, snprintf(drvMessageText, MAXBUF_,
"Mismatched response %s to command %s. Please call the " "Mismatched response %s to command %s. Please call the "
@ -511,72 +521,6 @@ asynStatus masterMacsCreateController(const char *portName,
return asynSuccess; return asynSuccess;
} }
/*
C wrapper for the axis constructor. Please refer to the masterMacsAxis
constructor documentation. The controller is read from the portName.
*/
asynStatus masterMacsCreateAxis(const char *portName, int axis) {
masterMacsAxis *pAxis;
/*
findAsynPortDriver is a asyn library FFI function which uses the C ABI.
Therefore it returns a void pointer instead of e.g. a pointer to a
superclass of the controller such as asynPortDriver. Type-safe upcasting
via dynamic_cast is therefore not possible directly. However, we do know
that the void pointer is either a pointer to asynPortDriver (if a driver
with the specified name exists) or a nullptr. Therefore, we first do a
nullptr check, then a cast to asynPortDriver and lastly a (typesafe)
dynamic_upcast to Controller
https://stackoverflow.com/questions/70906749/is-there-a-safe-way-to-cast-void-to-class-pointer-in-c
*/
void *ptr = findAsynPortDriver(portName);
if (ptr == nullptr) {
/*
We can't use asynPrint here since this macro would require us
to get a lowLevelPortUser_ from a pointer to an asynPortDriver.
However, the given pointer is a nullptr and therefore doesn't
have a lowLevelPortUser_! printf is an EPICS alternative which
works w/o that, but doesn't offer the comfort provided
by the asynTrace-facility
*/
errlogPrintf("%s => line %d:\nPort %s not found.", __PRETTY_FUNCTION__,
__LINE__, portName);
return asynError;
}
// Unsafe cast of the pointer to an asynPortDriver
asynPortDriver *apd = (asynPortDriver *)(ptr);
// Safe downcast
masterMacsController *pC = dynamic_cast<masterMacsController *>(apd);
if (pC == nullptr) {
errlogPrintf("%s => line %d:\ncontroller on port %s is not a "
"masterMacsController.",
__PRETTY_FUNCTION__, __LINE__, portName);
return asynError;
}
// Prevent manipulation of the controller from other threads while we
// create the new axis.
pC->lock();
/*
We create a new instance of the axis, using the "new" keyword to
allocate it on the heap while avoiding RAII.
https://github.com/epics-modules/motor/blob/master/motorApp/MotorSrc/asynMotorController.cpp
https://github.com/epics-modules/asyn/blob/master/asyn/asynPortDriver/asynPortDriver.cpp
The created object is registered in EPICS in its constructor and can
safely be "leaked" here.
*/
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-variable"
pAxis = new masterMacsAxis(pC, axis);
// Allow manipulation of the controller again
pC->unlock();
return asynSuccess;
}
/* /*
This is boilerplate code which is used to make the FFI functions This is boilerplate code which is used to make the FFI functions
CreateController and CreateAxis "known" to the IOC shell (iocsh). CreateController and CreateAxis "known" to the IOC shell (iocsh).
@ -613,30 +557,13 @@ static void configMasterMacsCreateControllerCallFunc(const iocshArgBuf *args) {
args[3].dval, args[4].dval, args[5].dval); args[3].dval, args[4].dval, args[5].dval);
} }
/*
Same procedure as for the CreateController function, but for the axis
itself.
*/
static const iocshArg CreateAxisArg0 = {"Controller name (e.g. mmacs1)",
iocshArgString};
static const iocshArg CreateAxisArg1 = {"Axis number", iocshArgInt};
static const iocshArg *const CreateAxisArgs[] = {&CreateAxisArg0,
&CreateAxisArg1};
static const iocshFuncDef configMasterMacsCreateAxis = {"masterMacsAxis", 2,
CreateAxisArgs};
static void configMasterMacsCreateAxisCallFunc(const iocshArgBuf *args) {
masterMacsCreateAxis(args[0].sval, args[1].ival);
}
// This function is made known to EPICS in masterMacs.dbd and is called by // This function is made known to EPICS in masterMacs.dbd and is called by
// EPICS in order to register both functions in the IOC shell // EPICS in order to register both functions in the IOC shell
static void masterMacsRegister(void) { static void masterMacsControllerRegister(void) {
iocshRegister(&configMasterMacsCreateController, iocshRegister(&configMasterMacsCreateController,
configMasterMacsCreateControllerCallFunc); configMasterMacsCreateControllerCallFunc);
iocshRegister(&configMasterMacsCreateAxis,
configMasterMacsCreateAxisCallFunc);
} }
epicsExportRegistrar(masterMacsRegister); epicsExportRegistrar(masterMacsControllerRegister);
#endif #endif

120
utils/decodeCommon.py Normal file
View File

@ -0,0 +1,120 @@
"""
Code shared by "decodeError.py" and "decodeStatus.py"
"""
def decode(value: int, interpretation):
bit_list = [int(char) for char in bin(value)[2:]]
bit_list.reverse()
interpreted = []
for (bit, interpretations) in zip(bit_list, interpretation):
interpreted.append(interpretations[bit])
return (bit_list, interpreted)
def print_decoded(bit_list, interpreted):
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
print(f"Bit {idx} = {bit_value}: {msg}")
def interactive():
# Imported here, because curses is not available in Windows. Using the
# interactive mode therefore fails on Windows, but at least the single
# command mode can be used (which would not be possible if we would import
# curses at the top level)
import curses
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.keypad(True)
stdscr.scrollok(True)
stdscr.addstr(">> ")
stdscr.refresh()
history = [""]
ptr = len(history) - 1
while True:
c = stdscr.getch()
if c == curses.KEY_RIGHT:
(y, x) = stdscr.getyx()
if x < len(history[ptr]) + 3:
stdscr.move(y, x+1)
stdscr.refresh()
elif c == curses.KEY_LEFT:
(y, x) = stdscr.getyx()
if x > 3:
stdscr.move(y, x-1)
stdscr.refresh()
elif c == curses.KEY_UP:
if ptr > 0:
ptr -= 1
stdscr.addch("\r")
stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr])
elif c == curses.KEY_DOWN:
if ptr < len(history) - 1:
ptr += 1
stdscr.addch("\r")
stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr])
elif c == curses.KEY_ENTER or c == ord('\n') or c == ord('\r'):
if history[ptr] == 'quit':
break
# because of arrow keys move back to the end of the line
(y, x) = stdscr.getyx()
stdscr.move(y, 3+len(history[ptr]))
if history[ptr]:
(bit_list, interpreted) = decode(history[ptr])
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}")
stdscr.refresh()
if ptr == len(history) - 1 and history[ptr] != "":
history += [""]
else:
history[-1] = ""
ptr = len(history) - 1
stdscr.addstr("\n>> ")
stdscr.refresh()
else:
if ptr < len(history) - 1: # Modifying previous input
if len(history[-1]) == 0:
history[-1] = history[ptr]
ptr = len(history) - 1
else:
history += [history[ptr]]
ptr = len(history) - 1
if c == curses.KEY_BACKSPACE:
if len(history[ptr]) == 0:
continue
(y, x) = stdscr.getyx()
history[ptr] = history[ptr][0:x-4] + history[ptr][x-3:]
stdscr.addch("\r")
stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr])
stdscr.move(y, x-1)
stdscr.refresh()
else:
(y, x) = stdscr.getyx()
history[ptr] = history[ptr][0:x-3] + chr(c) + history[ptr][x-3:]
stdscr.addch("\r")
stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr])
stdscr.move(y, x+1)
stdscr.refresh()
# to quit
curses.nocbreak()
stdscr.keypad(False)
curses.echo()
curses.endwin()

81
utils/decodeError.py Executable file
View File

@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""
The R11 error read command returns an integer, which needs to be interpreted
bitwise for various error flags. This script prints out these status flags in
human-readable formatting.
To read the manual, simply run this script without any arguments.
Stefan Mathis, January 2025
"""
from decodeCommon import interactive, decode, print_decoded
# List of tuples which encodes the states given in the file description.
# Index first with the bit index, then with the bit value
interpretation = [
("Not specified", "Not specified"), # Bit 0
("Ok", "Short circuit"), # Bit 1
("Ok", "Encoder error"), # Bit 2
("Ok", "Following error"), # Bit 3
("Ok", "Communication error"), # Bit 4
("Ok", "Feedback error"), # Bit 5
("Ok", "Positive limit switch hit"), # Bit 6
("Ok", "Negative limit switch hit"), # Bit 7
("Ok", "Positive software limit hit"), # Bit 8
("Ok", "Negative software limit hit"), # Bit 9
("Ok", "Over-current"), # Bit 10
("Ok", "Over-temperature drive"), # Bit 11
("Ok", "Over-voltage"), # Bit 12
("Ok", "Under-voltage"), # Bit 13
("Not specified", "Not specified"), # Bit 14
("Ok", "STO fault (STO input is on disable state)"), # Bit 15
]
if __name__ == "__main__":
from sys import argv
if len(argv) == 1:
# Start interactive mode
interactive()
else:
number = None
try:
number = int(float(argv[1]))
except:
print("""
Decode R11 message of MasterMACs
------------------
MasterMACs returns its error message (R11) as a floating-point number.
The bits of this float encode different states. These states are stored
in the interpretation variable.
This script can be used in two different ways:
Option 1: Single Command
------------------------
Usage: decodeError.py value
'value' is the return value of a R11 command. This value is interpreted
bit-wise and the result is printed out.
Option 2: CLI Mode
------------------
Usage: decodeError.py
A prompt will be opened. Type in the return value of a R11 command, hit
enter and the interpretation will be printed in the prompt. After that,
the next value can be typed in. Type 'quit' to close the prompt.
""")
if number is not None:
print("Motor error")
print("============")
(bit_list, interpreted) = decode(number, interpretation)
print_decoded(bit_list, interpreted)

81
utils/decodeStatus.py Normal file → Executable file
View File

@ -9,6 +9,7 @@ To read the manual, simply run this script without any arguments.
Stefan Mathis, December 2024 Stefan Mathis, December 2024
""" """
from decodeCommon import interactive, decode, print_decoded
# List of tuples which encodes the states given in the file description. # List of tuples which encodes the states given in the file description.
# Index first with the bit index, then with the bit value # Index first with the bit index, then with the bit value
@ -20,35 +21,17 @@ interpretation = [
("Motor supply voltage absent ", "Motor supply voltage present"), # Bit 4 ("Motor supply voltage absent ", "Motor supply voltage present"), # Bit 4
("Motor performs quick stop", "Ok"), # Bit 5 ("Motor performs quick stop", "Ok"), # Bit 5
("Switch on enabled", "Switch on disabled"), # Bit 6 ("Switch on enabled", "Switch on disabled"), # Bit 6
("Ok", "RWarning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7 ("Ok", "Warning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7
("Motor is idle", "Motor is currently moving"), # Bit 8 ("Not specified", "Not specified"), # Bit 8
("Motor does not execute command messages (local mode)", "Motor does execute command messages (remote mode)"), # Bit 9 ("Motor does not execute command messages (local mode)", "Motor does execute command messages (remote mode)"), # Bit 9
("Target not reached", "Target reached"), # Bit 10 ("Target not reached", "Target reached"), # Bit 10
("Ok", "Internal limit active"), # Bit 11 ("Ok", "Internal limit active (current, voltage, velocity or position)"), # Bit 11
("Not specified", "Not specified"), # Bit 12 ("Not specified", "Not specified"), # Bit 12
("Not specified", "Not specified"), # Bit 13 ("Not specified", "Not specified"), # Bit 13
("No event set or event has not occurred yet", "Set event has occurred"), # Bit 14 ("Not specified", "Not specified"), # Bit 14
("Axis off (power disabled)", "Axis on (power enabled)"), # Bit 15 ("Not specified", "Not specified"), # Bit 15
] ]
def decode(value, big_endian: bool = False):
interpreted = []
bit_list = [(value >> shift_ind) & 1
for shift_ind in range(value.bit_length())] # little endian
if big_endian:
bit_list.reverse() # big endian
for (bit, interpretations) in zip(bit_list, interpretation):
interpreted.append(interpretations[bit])
return (bit_list, interpreted)
def print_decoded(bit_list, interpreted):
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
print(f"Bit {idx} = {bit_value}: {msg}")
def interactive(): def interactive():
# Imported here, because curses is not available in Windows. Using the # Imported here, because curses is not available in Windows. Using the
@ -102,15 +85,10 @@ def interactive():
stdscr.move(y, 3+len(history[ptr])) stdscr.move(y, 3+len(history[ptr]))
if history[ptr]: if history[ptr]:
result = interpret_inputs(history[ptr].split()) (bit_list, interpreted) = decode(history[ptr])
if result is None: for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
stdscr.addstr(f"\nBAD INPUT: Expected input of 'value [big_endian]', where 'value' is an int or a float and 'big_endian' is an optional boolean argument.") stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}")
else: stdscr.refresh()
(arg, big_endian) = result
(bit_list, interpreted) = decode(arg, big_endian)
for (idx, (bit_value, msg)) in enumerate(zip(bit_list, interpreted)):
stdscr.addstr(f"\nBit {idx} = {bit_value}: {msg}")
stdscr.refresh()
if ptr == len(history) - 1 and history[ptr] != "": if ptr == len(history) - 1 and history[ptr] != "":
history += [""] history += [""]
@ -157,24 +135,6 @@ def interactive():
curses.echo() curses.echo()
curses.endwin() curses.endwin()
def interpret_inputs(inputs):
number = None
big_endian = False
try:
number = int(float(inputs[0]))
if len(inputs) > 1:
second_arg = inputs[1]
if second_arg == "True" or second_arg == "true":
big_endian = True
elif second_arg == "False" or second_arg == "false":
big_endian = False
else:
big_endian = bool(int(second_arg))
return (number, big_endian)
except:
return None
if __name__ == "__main__": if __name__ == "__main__":
from sys import argv from sys import argv
@ -183,9 +143,11 @@ if __name__ == "__main__":
interactive() interactive()
else: else:
result = interpret_inputs(argv[1:]) number = None
try:
number = int(float(argv[1]))
if result is None: except:
print(""" print("""
Decode R10 message of MasterMACs Decode R10 message of MasterMACs
------------------ ------------------
@ -199,25 +161,24 @@ if __name__ == "__main__":
Option 1: Single Command Option 1: Single Command
------------------------ ------------------------
Usage: decodeMasterMACStatusR10.py value [big_endian] Usage: decodeStatus.py value
'value' is the return value of a R10 command. This value is interpreted 'value' is the return value of a R10 command. This value is interpreted
bit-wise and the result is printed out. The optional second argument can bit-wise and the result is printed out.
be used to specify whether the input value needs to be interpreted as
little or big endian. Default is False.
Option 2: CLI Mode Option 2: CLI Mode
------------------ ------------------
Usage: decodeMasterMACStatusR10.py Usage: decodeStatus.py
A prompt will be opened. Type in the return value of a R10 command, hit A prompt will be opened. Type in the return value of a R10 command, hit
enter and the interpretation will be printed in the prompt. After that, enter and the interpretation will be printed in the prompt. After that,
the next value can be typed in. Type 'quit' to close the prompt. the next value can be typed in. Type 'quit' to close the prompt.
""") """)
else:
if number is not None:
print("Motor status") print("Motor status")
print("============") print("============")
(arg, big_endian) = result (bit_list, interpreted) = decode(number, interpretation)
(bit_list, interpreted) = decode(arg, big_endian)
print_decoded(bit_list, interpreted) print_decoded(bit_list, interpreted)