STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
board_startup_sim.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""
3Integration-only simulated board: SETUP heartbeats → wait for SENSOR_CONFIG (type 5) → send SELF_TEST.
4
5Matches Diablo PacketHeader (6B) + SelfTestPacket + N×SelfTestResult (DAQv2-Comms).
6"""
7
8from __future__ import annotations
9
10import argparse
11import socket
12import struct
13import sys
14import time
15
16PACKET_BOARD_HEARTBEAT = 1
17PACKET_SENSOR_CONFIG = 5
18PACKET_SELF_TEST = 12
19BOARD_TYPE_PT = 1 # minimal type for heartbeat body
20BOARD_STATE_SETUP = 1
21ENGINE_SAFE = 0
22DIABLO_VERSION = 0
23
24
25def build_board_heartbeat(_board_type: int, board_id: int, ts_ms: int) -> bytes:
26 """DAQv2 BoardHeartbeatPacket: 32B hash + board_id + engine_state + board_state."""
27 header = struct.pack(
28 "<BBI", PACKET_BOARD_HEARTBEAT, DIABLO_VERSION, ts_ms & 0xFFFFFFFF
29 )
30 firmware_hash = bytes(32)
31 body = firmware_hash + struct.pack(
32 "<BBB", board_id & 0xFF, ENGINE_SAFE, BOARD_STATE_SETUP
33 )
34 return header + body
35
36
37def build_self_test_packet(sensor_results: list[tuple[int, int]]) -> bytes:
38 """sensor_results: list of (sensor_id, result) result 1=pass 0=fail."""
39 ts_ms = int(time.time() * 1000) & 0xFFFFFFFF
40 header = struct.pack("<BBI", PACKET_SELF_TEST, DIABLO_VERSION, ts_ms)
41 n = min(len(sensor_results), 255)
42 adc_good = 1 # SelfTestPacket.adc_good — required on wire before num_sensors
43 body = struct.pack("BB", adc_good, n)
44 for i in range(n):
45 sid, res = sensor_results[i]
46 body += struct.pack("BB", sid & 0xFF, 1 if res else 0)
47 return header + body
48
49
50def main() -> int:
51 p = argparse.ArgumentParser(
52 description="Board startup E2E helper for integration tests"
53 )
54 p.add_argument("--board-ip", default="127.0.0.60")
55 p.add_argument("--listen-port", type=int, required=True)
56 p.add_argument("--daq-host", default="127.0.0.1")
57 p.add_argument("--daq-port", type=int, required=True)
58 p.add_argument("--board-id", type=int, default=60)
59 p.add_argument(
60 "--board-type",
61 type=int,
62 default=BOARD_TYPE_PT,
63 help="Heartbeat board_type byte",
64 )
65 p.add_argument("--timeout-s", type=float, default=90.0)
66 args = p.parse_args()
67
68 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
69 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
70 try:
71 sock.bind((args.board_ip, args.listen_port))
72 except OSError as e:
73 print(
74 f"[board_startup_sim] bind {args.board_ip}:{args.listen_port} failed: {e}",
75 flush=True,
76 )
77 return 1
78
79 sock.settimeout(0.5)
80 target = (args.daq_host, args.daq_port)
81 deadline = time.time() + args.timeout_s
82 last_hb = 0.0
83
84 print(
85 f"[board_startup_sim] board_id={args.board_id} listen={args.board_ip}:{args.listen_port} "
86 f"→ DAQ {target[0]}:{target[1]}",
87 flush=True,
88 )
89
90 while time.time() < deadline:
91 now = time.time()
92 if now - last_hb >= 1.0:
93 ts_ms = int(now * 1000) & 0xFFFFFFFF
94 pkt = build_board_heartbeat(args.board_type, args.board_id, ts_ms)
95 sock.sendto(pkt, target)
96 last_hb = now
97 try:
98 data, _addr = sock.recvfrom(4096)
99 if data and len(data) >= 1 and data[0] == PACKET_SENSOR_CONFIG:
100 # enable_serial_printing (log mode byte, 0..3) is always the LAST byte of
101 # the config packet. Report it so the integration test can confirm a
102 # GUI-driven mode change actually reached this board (it does not act on it).
103 mode = data[-1]
104 print(
105 f"[board_startup_sim] SENSOR_CONFIG received enable_serial_printing={mode}, sending SELF_TEST",
106 flush=True,
107 )
108 # Single pass result for connector 2 — keeps WS path unambiguous (integration asserts sensor_2=1).
109 st = build_self_test_packet([(2, 1)])
110 # Send a small burst to avoid single-packet UDP loss flaking the E2E test.
111 for _ in range(3):
112 sock.sendto(st, target)
113 time.sleep(0.05)
114 sock.close()
115 return 0
116 except socket.timeout:
117 continue
118
119 print("[board_startup_sim] timeout waiting for SENSOR_CONFIG", flush=True)
120 sock.close()
121 return 2
122
123
124if __name__ == "__main__":
125 sys.exit(main())
#define min(a, b)
Definition Arduino.h:42
bytes build_self_test_packet(list[tuple[int, int]] sensor_results)
bytes build_board_heartbeat(int _board_type, int board_id, int ts_ms)
TOML_NODISCARD_CTOR time() noexcept=default