Skip to content
File

Blob: scripts/monitor_device.py

python136 lines
1#!/usr/bin/env python3
2"""Stream filtered USB device logs. --check waits for a successful HTTPS heartbeat."""
3import argparse
4import json
5import termios
6import time
7 
8from config import read_env
9from project import ROOT
10from serial_device import device_port
11 
12VISIBLE_LOG_MARKERS = (
13 "HTTPS ",
14 "ON AIR:",
15 "Music validated:",
16 "Live spectrum ready:",
17 "Crypto ",
18 "Crypto:",
19 "crypto security tests passed",
20 "str0m initialized",
21)
22 
23 
24def open_port():
25 import serial
26 port = serial.Serial(port=None, baudrate=115200, timeout=0.1, exclusive=True)
27 port.dtr = True
28 port.rts = True
29 port.port = device_port()
30 port.open()
31 attrs = termios.tcgetattr(port.fileno())
32 attrs[2] &= ~termios.HUPCL
33 termios.tcsetattr(port.fileno(), termios.TCSANOW, attrs)
34 return port
35 
36 
37def main(argv=None):
38 import serial
39 parser = argparse.ArgumentParser(description=__doc__)
40 parser.add_argument(
41 "--reset", action="store_true", help="Reboot the board before monitoring"
42 )
43 parser.add_argument(
44 "--check",
45 action="store_true",
46 help="Require a successful heartbeat (and startup banner with --reset); nonzero on failure",
47 )
48 parser.add_argument(
49 "--seconds",
50 type=float,
51 help="Stop after this duration (check defaults to 90 seconds)",
52 )
53 args = parser.parse_args(argv)
54 if args.seconds is not None and args.seconds <= 0:
55 parser.error("--seconds must be positive")
56 seconds = args.seconds if args.seconds is not None else (90 if args.check else None)
57 secrets = read_env(ROOT / ".credential.env")
58 out = ROOT / "artifacts/radio"
59 out.mkdir(parents=True, exist_ok=True, mode=0o700)
60 prefix = "autonomous-check" if args.check else "autonomous-monitor"
61 path = out / (prefix + ".log")
62 path.touch(mode=0o600, exist_ok=True)
63 path.chmod(0o600)
64 port = open_port()
65 if args.reset:
66 port.write(b'{"cmd":"restart_device"}\n')
67 port.flush()
68 pending = bytearray()
69 result = {
70 "reset_requested": args.reset,
71 "usb_signaling_commands_sent": 0,
72 "on_air_banner_captured": False,
73 "https_stages": [],
74 "successful_heartbeats": 0,
75 }
76 deadline = time.monotonic() + seconds if seconds is not None else None
77 try:
78 with path.open("w") as log:
79 while deadline is None or time.monotonic() < deadline:
80 try:
81 pending.extend(port.read(8192))
82 except (serial.SerialException, OSError):
83 port.close()
84 time.sleep(0.3)
85 try:
86 port = open_port()
87 except (serial.SerialException, OSError, RuntimeError):
88 continue
89 continue
90 while b"\n" in pending:
91 raw, _, pending = pending.partition(b"\n")
92 line = raw.decode(errors="replace").strip()
93 for value in secrets.values():
94 if value:
95 line = line.replace(value, "[redacted]")
96 log.write(line + "\n")
97 log.flush()
98 # Print selected diagnostics; save all redacted lines in the private log.
99 if any(marker in line for marker in VISIBLE_LOG_MARKERS):
100 print(line[:400], flush=True)
101 if "ON AIR: autonomous" in line:
102 result["on_air_banner_captured"] = True
103 for stage in ("start", "channels", "ready"):
104 if (
105 f"HTTPS {stage}: status=200" in line
106 and stage not in result["https_stages"]
107 ):
108 result["https_stages"].append(stage)
109 if "HTTPS heartbeat: status=200" in line:
110 result["successful_heartbeats"] += 1
111 if (
112 args.check
113 and result["successful_heartbeats"]
114 and (not args.reset or result["on_air_banner_captured"])
115 ):
116 break
117 except KeyboardInterrupt:
118 pass
119 finally:
120 port.close()
121 if args.check:
122 result["passed"] = bool(result["successful_heartbeats"]) and (
123 not args.reset or result["on_air_banner_captured"]
124 )
125 summary = out / (prefix + ".json")
126 summary.touch(mode=0o600, exist_ok=True)
127 summary.chmod(0o600)
128 summary.write_text(json.dumps(result, indent=2) + "\n")
129 print(json.dumps(result, indent=2), flush=True)
130 return 0 if result["passed"] else 1
131 return 0
132 
133 
134if __name__ == "__main__":
135 raise SystemExit(main())