4"""Write one bounded runtime-provisioning packet to an already owned UART."""
6from __future__
import annotations
12from pathlib
import Path
14MAX_PACKET_BYTES = 1225
16PACKET_PREFIX = b
"RA8NET1:"
18HEX_DIGITS = frozenset(b
"0123456789abcdef")
21def validate_packet(packet: bytes) ->
None:
22 """Reject malformed or oversized input before opening the serial device."""
23 if not packet
or len(packet) > MAX_PACKET_BYTES:
24 message =
"runtime packet is empty or exceeds its protocol bound"
25 raise ValueError(message)
26 if not packet.startswith(PACKET_PREFIX)
or not packet.endswith(b
"\n"):
27 message =
"runtime packet has an invalid frame"
28 raise ValueError(message)
29 body = packet[len(PACKET_PREFIX) : -1]
30 fields = body.split(b
":")
31 invalid_hex = any(any(byte
not in HEX_DIGITS
for byte
in field)
for field
in fields)
32 if len(fields) != PACKET_FIELD_COUNT
or invalid_hex:
33 message =
"runtime packet fields must be lowercase hexadecimal"
34 raise ValueError(message)
37def termios_baud(baud: int) -> int:
38 """Return the platform termios constant for one supported positive baud."""
40 message =
"baud must be a positive supported integer"
41 raise ValueError(message)
42 constant = getattr(termios, f
"B{baud}",
None)
44 message = f
"unsupported baud rate: {baud}"
45 raise ValueError(message)
49def argparse_baud(raw: str) -> int:
50 """Parse and validate a baud value before any serial device is opened."""
54 except ValueError
as exc:
55 raise argparse.ArgumentTypeError(str(exc))
from exc
59def write_packet(port: Path, packet: bytes, baud: int) ->
None:
60 """Configure the requested baud at 8N1 raw mode and write the packet."""
61 validate_packet(packet)
62 baud_flag = termios_baud(baud)
63 fd = os.open(port, os.O_WRONLY | os.O_NOCTTY)
65 attrs = termios.tcgetattr(fd)
68 attrs[2] = (attrs[2] & ~termios.CSIZE) | termios.CS8 | termios.CLOCAL | termios.CREAD
69 attrs[2] &= ~(termios.PARENB | termios.CSTOPB)
73 termios.tcsetattr(fd, termios.TCSANOW, attrs)
75 while offset < len(packet):
76 written = os.write(fd, packet[offset:])
78 message =
"serial write made no progress"
79 raise OSError(message)
86def _packet_is_valid(packet: bytes) -> bool:
87 """Return whether a packet passes validation."""
89 validate_packet(packet)
95def run_selftest() -> int:
96 """Prove valid framing stays quiet and malformed frames fire."""
97 exact_maximum = PACKET_PREFIX + (b
"a" * 64) + b
":" + (b
"b" * 128) + b
":" + (b
"c" * 1022) + b
"\n"
99 b
"RA8NET1:61:6262626262626262:\n",
100 b
"RA8NET1:61:6262626262626262:6874747073\n",
108 exact_maximum[:-1] + b
"cc\n",
111 failures.extend(
"valid packet was rejected" for packet
in valid
if not _packet_is_valid(packet))
112 failures.extend(
"invalid packet was accepted" for packet
in invalid
if _packet_is_valid(packet))
113 if len(exact_maximum) != MAX_PACKET_BYTES:
114 failures.append(
"maximum packet fixture does not match the C protocol bound")
116 if argparse_baud(str(DEFAULT_BAUD)) != DEFAULT_BAUD:
117 failures.append(
"default baud changed during validation")
118 except argparse.ArgumentTypeError:
119 failures.append(
"default baud is unsupported on this platform")
120 for invalid_baud
in (
"0",
"-1",
"115201",
"1.5",
"invalid"):
122 argparse_baud(invalid_baud)
123 except argparse.ArgumentTypeError:
125 failures.append(f
"invalid or unsupported baud was accepted: {invalid_baud}")
127 for failure
in failures:
128 print(f
"uart_write.py --selftest: FAIL: {failure}", file=sys.stderr)
130 print(
"uart_write.py --selftest: PASS (3 frames accepted, 5 frames + 5 bauds refused)")
135 """Parse arguments, read one packet from stdin, and write it to the UART."""
136 parser = argparse.ArgumentParser(description=__doc__)
137 parser.add_argument(
"port", nargs=
"?", type=Path)
138 parser.add_argument(
"--baud", type=argparse_baud, default=DEFAULT_BAUD)
139 parser.add_argument(
"--selftest", action=
"store_true")
140 args = parser.parse_args()
142 if args.port
is not None:
143 parser.error(
"--selftest accepts no port")
144 return run_selftest()
145 if args.port
is None:
146 parser.error(
"a serial port is required")
147 packet = sys.stdin.buffer.read(MAX_PACKET_BYTES + 1)
149 write_packet(args.port, packet, args.baud)
150 except (OSError, ValueError)
as exc:
151 print(f
"uart_write.py: {exc}", file=sys.stderr)
156if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.