Improved script docs and script usage description in README.md
Some checks failed
Test And Build / Lint (push) Failing after 4s
Test And Build / Build (push) Successful in 7s

Also introduced graceful error handling when trying to access
interactive mode on Windows.
This commit is contained in:
2025-09-24 15:42:21 +02:00
parent a435c3c960
commit 6f72766ae6
4 changed files with 204 additions and 152 deletions

View File

@@ -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. - 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. - 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 ## Developer guide
### Usage in IOC shell ### Usage in IOC shell

View File

@@ -9,73 +9,82 @@ To read the manual, simply run this script without any arguments.
Stefan Mathis, January 2025 Stefan Mathis, January 2025
""" """
import platform
from decodeCommon import interactive, decode, print_decoded 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
interpretation = [ interpretation = [
("Not specified", "Not specified"), # Bit 0 ("Not specified", "Not specified"), # Bit 0
("Ok", "Short circuit"), # Bit 1 ("Ok", "Short circuit"), # Bit 1
("Ok", "Encoder error"), # Bit 2 ("Ok", "Encoder error"), # Bit 2
("Ok", "Following error"), # Bit 3 ("Ok", "Following error"), # Bit 3
("Ok", "Communication error"), # Bit 4 ("Ok", "Communication error"), # Bit 4
("Ok", "Feedback error"), # Bit 5 ("Ok", "Feedback error"), # Bit 5
("Ok", "Positive limit switch hit"), # Bit 6 ("Ok", "Positive limit switch hit"), # Bit 6
("Ok", "Negative limit switch hit"), # Bit 7 ("Ok", "Negative limit switch hit"), # Bit 7
("Ok", "Positive software limit hit"), # Bit 8 ("Ok", "Positive software limit hit"), # Bit 8
("Ok", "Negative software limit hit"), # Bit 9 ("Ok", "Negative software limit hit"), # Bit 9
("Ok", "Over-current"), # Bit 10 ("Ok", "Over-current"), # Bit 10
("Ok", "Over-temperature drive"), # Bit 11 ("Ok", "Over-temperature drive"), # Bit 11
("Ok", "Over-voltage"), # Bit 12 ("Ok", "Over-voltage"), # Bit 12
("Ok", "Under-voltage"), # Bit 13 ("Ok", "Under-voltage"), # Bit 13
("Not specified", "Not specified"), # Bit 14 ("Not specified", "Not specified"), # Bit 14
("Ok", "STO fault (STO input is on disable state)"), # Bit 15 ("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__": if __name__ == "__main__":
from sys import argv from sys import argv
if "-h" or "--help" in argv:
print(help)
if len(argv) == 1: if len(argv) == 1:
# Start interactive mode # Start interactive mode
interactive() if platform.system() == "Linux":
interactive()
else:
print(help)
else: else:
number = None number = None
try: try:
number = int(float(argv[1])) number = int(float(argv[1]))
except: except:
print(""" print(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
------------------
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: if number is not None:
print("Motor error") print("Motor error")
print("============") print("===========")
(bit_list, interpreted) = decode(number, interpretation) (bit_list, interpreted) = decode(number, interpretation)
print_decoded(bit_list, interpreted) print_decoded(bit_list, interpreted)

View File

@@ -9,71 +9,81 @@ To read the manual, simply run this script without any arguments.
Stefan Mathis, December 2024 Stefan Mathis, December 2024
""" """
import platform
from decodeCommon import interactive, decode, print_decoded 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
interpretation = [ interpretation = [
("Not ready to be switched on", "Ready to be switched on"), # Bit 0 ("Not ready to be switched on", "Ready to be switched on"), # Bit 0
("Not switched on", "Switched on"), # Bit 1 ("Not switched on", "Switched on"), # Bit 1
("Disabled", "Enabled"), # Bit 2 ("Disabled", "Enabled"), # Bit 2
("Ok", "Fault condition set"), # Bit 3 ("Ok", "Fault condition set"), # Bit 3
("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", "Warning: 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
("Not specified", "Not specified"), # 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)",
("Target not reached", "Target reached"), # Bit 10 "Motor does execute command messages (remote mode)"), # Bit 9
("Ok", "Internal limit active (current, voltage, velocity or position)"), # Bit 11 ("Target not reached", "Target reached"), # Bit 10
("Not specified", "Not specified"), # Bit 12 ("Ok", "Internal limit active (current, voltage, velocity or position)"), # Bit 11
("Not specified", "Not specified"), # Bit 13 ("Not specified", "Not specified"), # Bit 12
("Not specified", "Not specified"), # Bit 14 ("Not specified", "Not specified"), # Bit 13
("Not specified", "Not specified"), # Bit 15 ("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__": if __name__ == "__main__":
from sys import argv from sys import argv
if "-h" or "--help" in argv:
print(help)
if len(argv) == 1: if len(argv) == 1:
# Start interactive mode # Start interactive mode
interactive() if platform.system() == "Linux":
interactive()
else:
print(help)
else: else:
number = None number = None
try: try:
number = int(float(argv[1])) number = int(float(argv[1]))
except: except:
print(""" print(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
------------------
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.
""")
if number is not None: if number is not None:
print("Motor status") print("Motor status")
print("============") print("============")

View File

@@ -6,51 +6,72 @@ To read the manual, simply run this script without any arguments.
Stefan Mathis, April 2025 Stefan Mathis, April 2025
""" """
import platform
import struct import struct
import socket 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): def packMasterMacsCommand(command):
# 0x0D = Carriage return # 0x0D = Carriage return
buf = struct.pack('B',0x0D) buf = struct.pack('B', 0x0D)
buf = bytes(command,'utf-8') + buf buf = bytes(command, 'utf-8') + buf
return bytes(command,'utf-8') return bytes(command, 'utf-8')
def readMasterMacsReply(input): def readMasterMacsReply(input):
msg = bytearray() msg = bytearray()
expectAck = True expectAck = True
while True: while True:
b = input.recv(1) b = input.recv(1)
bint = int.from_bytes(b,byteorder='little') bint = int.from_bytes(b, byteorder='little')
if bint == 2 or bint == 7: #STX or BELL if bint == 2 or bint == 7: # STX or BELL
expectAck = False expectAck = False
continue continue
if expectAck and bint == 6: # ACK if expectAck and bint == 6: # ACK
return bytes(msg) return bytes(msg)
else: else:
if bint == 13 and not expectAck: # CR if bint == 13 and not expectAck: # CR
return bytes(msg) return bytes(msg)
else: else:
msg.append(bint) msg.append(bint)
if __name__ == "__main__": if __name__ == "__main__":
from sys import argv from sys import argv
try: if "-h" or "--help" in argv:
print(help)
else:
addr = argv[1].split(':') addr = argv[1].split(':')
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((addr[0],int(addr[1]))) s.connect((addr[0], int(addr[1])))
if len(argv) == 3: if len(argv) == 2:
buf = packMasterMacsCommand(argv[2])
s.send(buf)
reply = readMasterMacsReply(s)
print(reply.decode('utf-8') + '\n')
else: if platform.system() == "Linux":
import curses
try:
stdscr = curses.initscr() stdscr = curses.initscr()
curses.noecho() curses.noecho()
@@ -80,13 +101,13 @@ if __name__ == "__main__":
if ptr > 0: if ptr > 0:
ptr -= 1 ptr -= 1
stdscr.addch("\r") stdscr.addch("\r")
stdscr.clrtoeol() stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr]) stdscr.addstr(">> " + history[ptr])
elif c == curses.KEY_DOWN: elif c == curses.KEY_DOWN:
if ptr < len(history) - 1: if ptr < len(history) - 1:
ptr += 1 ptr += 1
stdscr.addch("\r") stdscr.addch("\r")
stdscr.clrtoeol() stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr]) stdscr.addstr(">> " + history[ptr])
elif c == curses.KEY_ENTER or c == ord('\n') or c == ord('\r'): elif c == curses.KEY_ENTER or c == ord('\n') or c == ord('\r'):
if history[ptr] == 'quit': if history[ptr] == 'quit':
@@ -112,7 +133,7 @@ if __name__ == "__main__":
stdscr.refresh() stdscr.refresh()
else: else:
if ptr < len(history) - 1: # Modifying previous input if ptr < len(history) - 1: # Modifying previous input
if len(history[-1]) == 0: if len(history[-1]) == 0:
history[-1] = history[ptr] history[-1] = history[ptr]
ptr = len(history) - 1 ptr = len(history) - 1
@@ -125,47 +146,34 @@ if __name__ == "__main__":
if len(history[ptr]) == 0: if len(history[ptr]) == 0:
continue continue
(y, x) = stdscr.getyx() (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.addch("\r")
stdscr.clrtoeol() stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr]) stdscr.addstr(">> " + history[ptr])
stdscr.move(y, x-1) stdscr.move(y, x-1)
stdscr.refresh() stdscr.refresh()
else: else:
(y, x) = stdscr.getyx() (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.addch("\r")
stdscr.clrtoeol() stdscr.clrtoeol()
stdscr.addstr(">> " + history[ptr]) stdscr.addstr(">> " + history[ptr])
stdscr.move(y, x+1) stdscr.move(y, x+1)
stdscr.refresh() stdscr.refresh()
# to quit
finally: curses.nocbreak()
stdscr.keypad(False)
# to quit curses.echo()
curses.nocbreak() curses.endwin()
stdscr.keypad(False) else:
curses.echo() print(help)
curses.endwin() elif len(argv) == 3:
buf = packMasterMacsCommand(argv[2])
except: s.send(buf)
print(""" reply = readMasterMacsReply(s)
Invalid Arguments print(reply.decode('utf-8') + '\n')
else:
Option 1: Single Command print(help)
------------------------
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.
""")