89 lines
2.9 KiB
Python
Executable file
89 lines
2.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Toggle the USB relay via serial: ./relay 1|0|id [serial_port].
|
|
|
|
Without an explicit port the relay board is found automatically: every
|
|
USB serial port is probed with the ID command until one answers.
|
|
"""
|
|
import sys
|
|
import serial
|
|
import serial.tools.list_ports
|
|
|
|
|
|
def find_relay_port() -> str | None:
|
|
"""Find the relay board by probing CH340 ports with the ID command."""
|
|
candidates = [
|
|
p.device
|
|
for p in serial.tools.list_ports.comports()
|
|
if "1A86:7523" in (p.hwid or "")
|
|
]
|
|
for dev in candidates:
|
|
try:
|
|
s = serial.Serial()
|
|
s.port = dev
|
|
s.baudrate = 115200
|
|
s.timeout = 1
|
|
s.dtr = False
|
|
s.rts = False
|
|
s.open()
|
|
try:
|
|
s.readline() # gooi boot-banner weg
|
|
s.reset_input_buffer()
|
|
s.write(b"?")
|
|
s.flush()
|
|
if s.readline().decode(errors="replace").startswith("ID "):
|
|
return dev
|
|
finally:
|
|
s.close()
|
|
except serial.SerialException:
|
|
continue
|
|
return None
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) not in (2, 3) or sys.argv[1] not in ("0", "1", "id"):
|
|
print(f"Usage: {sys.argv[0]} 0|1|id [serial_port]", file=sys.stderr)
|
|
return 1
|
|
command = sys.argv[1]
|
|
serial_cmd = "?" if command == "id" else command
|
|
if len(sys.argv) == 3:
|
|
port = sys.argv[2]
|
|
else:
|
|
port = find_relay_port()
|
|
if port is None:
|
|
print("ERROR: no relay module found on any USB serial port",
|
|
file=sys.stderr)
|
|
return 1
|
|
print(f"Using {port}", file=sys.stderr)
|
|
try:
|
|
# dtr/rts expliciet uit vóór open: voorkomt (of beperkt) de CH340
|
|
# auto-reset (DTR->RST) bij het openen van de poort. pyserial 3.5
|
|
# accepteert geen dtr=/rts= kwargs in de constructor, maar de
|
|
# states worden wél geëerd door open().
|
|
s = serial.Serial()
|
|
s.port = port
|
|
s.baudrate = 115200
|
|
s.timeout = 2
|
|
s.dtr = False
|
|
s.rts = False
|
|
s.open()
|
|
except serial.SerialException as exc:
|
|
print(f"ERROR: cannot open {port}: {exc}", file=sys.stderr)
|
|
return 1
|
|
try:
|
|
# Als het board toch reset bij open: wacht op de boot-banner en
|
|
# gooi die weg, zodat de reply niet vervuild raakt. Geen banner
|
|
# binnen 2 s (timeout, readline geeft b"") = geen reset: door.
|
|
s.readline()
|
|
s.reset_input_buffer()
|
|
s.write(serial_cmd.encode())
|
|
s.flush()
|
|
reply = s.readline().decode(errors="replace").strip()
|
|
finally:
|
|
s.close()
|
|
if reply in ("ON", "OFF") or (command == "id" and reply.startswith("ID ")):
|
|
print(reply)
|
|
return 0
|
|
print(f"ERROR: no valid reply from board (got {reply!r})", file=sys.stderr)
|
|
return 1
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|