Files
MeshCore/tools/hil/ethernet_bench_serial.py
T

104 lines
4.0 KiB
Python

"""Identity-recorded USB CLI for the explicitly erasable Ethernet RF bench.
No reset/erase/flash is performed. WiFi credentials are read only from a local
JSON file and never included in the printed command/response log.
"""
import argparse
import json
import os
from pathlib import Path
import time
import serial
from serial.tools import list_ports
from serial_session import (SerialCommandTimeout, assert_port_idle,
open_configured_session, read_command_reply)
def read_quiet(port, timeout=3):
until = time.monotonic() + timeout
# Wait the full response deadline for the first byte; silence at attach is
# not proof that a cooperative USB console has finished opening its session.
quiet = until
data = bytearray()
while time.monotonic() < min(until, quiet):
chunk = port.read(min(port.in_waiting or 1, 4096))
if chunk:
data.extend(chunk)
quiet = time.monotonic() + 0.35
return data.decode("utf-8", errors="replace")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--port", required=True)
parser.add_argument("--command", action="append", default=[])
parser.add_argument("--wifi-json", type=Path)
parser.add_argument("--wait", type=float, default=10)
args = parser.parse_args()
matches = [p for p in list_ports.comports()
if os.path.realpath(p.device) == os.path.realpath(args.port)]
if len(matches) != 1:
raise SystemExit("Selected USB identity is absent or ambiguous")
info = matches[0]
secrets = []
commands = list(args.command)
if args.wifi_json:
credentials = json.loads(args.wifi_json.read_text())
ssid, password = credentials["ssid"], credentials["password"]
if not isinstance(ssid, str) or not isinstance(password, str) or any(
c in ssid + password for c in "\r\n\0"):
raise SystemExit("Invalid credential file")
secrets = [password] if password else []
commands.extend(["set wifi.ssid " + ssid, "set wifi.pwd " + password, "set wifi on"])
if args.wait <= 0 or any(not command or any(value in command for value in "\r\n\0") for command in commands):
raise SystemExit("Use single-line nonempty commands and a positive --wait")
def redact(text):
for secret in secrets:
text = text.replace(secret, "[redacted]")
return text
port = serial.Serial(port=None, baudrate=115200, timeout=0.04, exclusive=True)
port.port = info.device
port.rts = False
port.dtr = info.vid in (0x303A, 0x2886, 0x239A)
output = {"port": info.device, "usb_serial": info.serial_number,
"vid": info.vid, "pid": info.pid, "commands": []}
try:
assert_port_idle(info.device)
open_configured_session(port)
read_quiet(port, 0.5)
port.write(b"+++MESHCORE-TERM-START\n")
port.flush()
output["hello"] = redact(read_quiet(port))
for command in commands:
port.write((command + "\n").encode())
port.flush()
try:
reply = read_command_reply(port, command, args.wait)
except SerialCommandTimeout as error:
output["commands"].append({"command": redact(command),
"reply": redact(error.partial_reply), "completed": False})
output["error"] = str(error)
break # Never misattribute this pending reply to another command.
output["commands"].append({"command": redact(command), "reply": redact(reply), "completed": True})
finally:
if port.is_open:
try:
port.write(b"+++MESHCORE-TERM-STOP\n")
port.flush()
except Exception:
pass
try:
port.close()
except Exception:
pass
print(json.dumps(output), flush=True)
return 1 if "error" in output else 0
if __name__ == "__main__":
raise SystemExit(main())