From 6f72766ae61e8f9a9358230f0a528be3167bb7ef Mon Sep 17 00:00:00 2001 From: smathis Date: Wed, 24 Sep 2025 15:42:21 +0200 Subject: [PATCH] Improved script docs and script usage description in README.md Also introduced graceful error handling when trying to access interactive mode on Windows. --- README.md | 25 +++++++++ utils/decodeError.py | 105 +++++++++++++++++++----------------- utils/decodeStatus.py | 104 +++++++++++++++++++---------------- utils/writeRead.py | 122 ++++++++++++++++++++++-------------------- 4 files changed, 204 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index a7ebe18..9d95db0 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,31 @@ The folder "utils" contains utility scripts for working with masterMacs motor co - 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. +These scripts can be run from anywhere. On Linux, the shebang (#!) automatically +calls the system Python 3 executable: + +```bash +# To show the help, use either flag -h or --help (works on all scripts) +/path/to/mastermacs_repo/utils/decodeStatus.py -h +/path/to/mastermacs_repo/utils/decodeError.py --help +/path/to/mastermacs_repo/utils/writeRead.py -h + +# To run in non-interactive mode, give the value as an argument +/path/to/mastermacs_repo/utils/decodeStatus.py 1234 +/path/to/mastermacs_repo/utils/decodeError.py 5678 +/path/to/mastermacs_repo/utils/writeRead.py "R11" + +# To run in interactive mode, don't give any argument. This only works on Linux +/path/to/mastermacs_repo/utils/decodeStatus.py +/path/to/mastermacs_repo/utils/decodeError.py +/path/to/mastermacs_repo/utils/writeRead.py +``` + +To use these scripts on Windows, prefix the Python 3 executable: +```bash +C:/path/to/python3.exe C:/path/to/mastermacs_repo/utils/decodeStatus.py 1234 +``` + ## Developer guide ### Usage in IOC shell diff --git a/utils/decodeError.py b/utils/decodeError.py index 9628b18..d7ed2ee 100755 --- a/utils/decodeError.py +++ b/utils/decodeError.py @@ -9,73 +9,82 @@ To read the manual, simply run this script without any arguments. Stefan Mathis, January 2025 """ +import platform + 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 + ("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 ] +help = """ +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 (Linux-only) +------------------------------- + +Usage: decodeError.py + +ONLY AVAILABLE ON LINUX! + +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 __name__ == "__main__": from sys import argv + if "-h" or "--help" in argv: + print(help) + if len(argv) == 1: # Start interactive mode - interactive() + if platform.system() == "Linux": + interactive() + else: + print(help) 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. - """) - - + print(help) if number is not None: print("Motor error") - print("============") + print("===========") (bit_list, interpreted) = decode(number, interpretation) print_decoded(bit_list, interpreted) diff --git a/utils/decodeStatus.py b/utils/decodeStatus.py index d5a8dd9..b8f00bb 100755 --- a/utils/decodeStatus.py +++ b/utils/decodeStatus.py @@ -9,71 +9,81 @@ To read the manual, simply run this script without any arguments. Stefan Mathis, December 2024 """ +import platform + 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 ready to be switched on", "Ready to be switched on"), # Bit 0 - ("Not switched on", "Switched on"), # Bit 1 - ("Disabled", "Enabled"), # Bit 2 - ("Ok", "Fault condition set"), # Bit 3 - ("Motor supply voltage absent ", "Motor supply voltage present"), # Bit 4 - ("Motor performs quick stop", "Ok"), # Bit 5 - ("Switch on enabled", "Switch on disabled"), # Bit 6 - ("Ok", "Warning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7 - ("Not specified", "Not specified"), # Bit 8 - ("Motor does not execute command messages (local mode)", "Motor does execute command messages (remote mode)"), # Bit 9 - ("Target not reached", "Target reached"), # Bit 10 - ("Ok", "Internal limit active (current, voltage, velocity or position)"), # Bit 11 - ("Not specified", "Not specified"), # Bit 12 - ("Not specified", "Not specified"), # Bit 13 - ("Not specified", "Not specified"), # Bit 14 - ("Not specified", "Not specified"), # Bit 15 + ("Not ready to be switched on", "Ready to be switched on"), # Bit 0 + ("Not switched on", "Switched on"), # Bit 1 + ("Disabled", "Enabled"), # Bit 2 + ("Ok", "Fault condition set"), # Bit 3 + ("Motor supply voltage absent ", "Motor supply voltage present"), # Bit 4 + ("Motor performs quick stop", "Ok"), # Bit 5 + ("Switch on enabled", "Switch on disabled"), # Bit 6 + ("Ok", "Warning: Movement function was called while motor is still moving. The function call is ignored"), # Bit 7 + ("Not specified", "Not specified"), # Bit 8 + ("Motor does not execute command messages (local mode)", + "Motor does execute command messages (remote mode)"), # Bit 9 + ("Target not reached", "Target reached"), # Bit 10 + ("Ok", "Internal limit active (current, voltage, velocity or position)"), # Bit 11 + ("Not specified", "Not specified"), # Bit 12 + ("Not specified", "Not specified"), # Bit 13 + ("Not specified", "Not specified"), # Bit 14 + ("Not specified", "Not specified"), # Bit 15 ] +help = """ +Decode R10 message of MasterMACs +------------------ + +MasterMACs returns its status message (R10) 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: decodeStatus.py value + +'value' is the return value of a R10 command. This value is interpreted +bit-wise and the result is printed out. + +Option 2: CLI Mode (Linux-only) +------------------------------- + +Usage: decodeStatus.py + +ONLY AVAILABLE ON LINUX! + +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, +the next value can be typed in. Type 'quit' to close the prompt. +""" + if __name__ == "__main__": from sys import argv + if "-h" or "--help" in argv: + print(help) + if len(argv) == 1: # Start interactive mode - interactive() + if platform.system() == "Linux": + interactive() + else: + print(help) else: number = None try: number = int(float(argv[1])) - except: - print(""" - Decode R10 message of MasterMACs - ------------------ - - MasterMACs returns its status message (R10) 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: decodeStatus.py value - - 'value' is the return value of a R10 command. This value is interpreted - bit-wise and the result is printed out. - - Option 2: CLI Mode - ------------------ - - Usage: decodeStatus.py - - 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, - the next value can be typed in. Type 'quit' to close the prompt. - """) - - + print(help) if number is not None: print("Motor status") print("============") diff --git a/utils/writeRead.py b/utils/writeRead.py index 25524fb..ded14a5 100644 --- a/utils/writeRead.py +++ b/utils/writeRead.py @@ -6,51 +6,72 @@ To read the manual, simply run this script without any arguments. Stefan Mathis, April 2025 """ +import platform + import struct import socket -import curses + +help = """ +Send commands to and receive replies from MasterMACS controllers + +Option 1: Single Command +------------------------ + +Usage: writeRead.py pmachost:port command +This then returns the response for command. + +Option 2: CLI Mode (Linux-only) +------------------------------- + +Usage: writeRead.py pmachost:port + +ONLY AVAILABLE ON LINUX! + +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. +""" + def packMasterMacsCommand(command): - # 0x0D = Carriage return - buf = struct.pack('B',0x0D) - buf = bytes(command,'utf-8') + buf - return bytes(command,'utf-8') + # 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 + 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 + if expectAck and bint == 6: # ACK return bytes(msg) else: - if bint == 13 and not expectAck: # CR + if bint == 13 and not expectAck: # CR return bytes(msg) else: msg.append(bint) + if __name__ == "__main__": from sys import argv - try: - + if "-h" or "--help" in argv: + print(help) + else: addr = argv[1].split(':') - s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) - s.connect((addr[0],int(addr[1]))) + 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') + if len(argv) == 2: - else: - - try: + if platform.system() == "Linux": + import curses stdscr = curses.initscr() curses.noecho() @@ -80,13 +101,13 @@ if __name__ == "__main__": if ptr > 0: ptr -= 1 stdscr.addch("\r") - stdscr.clrtoeol() + stdscr.clrtoeol() stdscr.addstr(">> " + history[ptr]) elif c == curses.KEY_DOWN: if ptr < len(history) - 1: ptr += 1 stdscr.addch("\r") - stdscr.clrtoeol() + stdscr.clrtoeol() stdscr.addstr(">> " + history[ptr]) elif c == curses.KEY_ENTER or c == ord('\n') or c == ord('\r'): if history[ptr] == 'quit': @@ -112,7 +133,7 @@ if __name__ == "__main__": stdscr.refresh() else: - if ptr < len(history) - 1: # Modifying previous input + if ptr < len(history) - 1: # Modifying previous input if len(history[-1]) == 0: history[-1] = history[ptr] ptr = len(history) - 1 @@ -125,47 +146,34 @@ if __name__ == "__main__": if len(history[ptr]) == 0: continue (y, x) = stdscr.getyx() - history[ptr] = history[ptr][0:x-4] + history[ptr][x-3:] + history[ptr] = history[ptr][0:x-4] + \ + history[ptr][x-3:] stdscr.addch("\r") - stdscr.clrtoeol() + 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:] + history[ptr] = history[ptr][0:x-3] + \ + chr(c) + history[ptr][x-3:] stdscr.addch("\r") - stdscr.clrtoeol() + 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. - """) - + # to quit + curses.nocbreak() + stdscr.keypad(False) + curses.echo() + curses.endwin() + else: + print(help) + elif len(argv) == 3: + buf = packMasterMacsCommand(argv[2]) + s.send(buf) + reply = readMasterMacsReply(s) + print(reply.decode('utf-8') + '\n') + else: + print(help)