from __future__ import annotations import os import sys import unittest from collections import deque from dataclasses import replace from pathlib import Path from unittest.mock import patch ROOT = Path(__file__).resolve().parents[4] SEAT_SOURCE = ROOT / "domains" / "seat" / "src" if str(SEAT_SOURCE) not in sys.path: sys.path.insert(0, str(SEAT_SOURCE)) from cognitive_agent.infrastructure.can.gateway import CanFrame, CanGatewayError # noqa: E402 from cognitive_agent.application.seat_service import SeatDemoService # noqa: E402 from cognitive_agent.runtime.contracts import ContractValidator # noqa: E402 from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402 HardwareSafetyError, SeatHardwareSettings, ) from cognitive_agent.infrastructure.firmware import ( # noqa: E402 FirmwareProgrammingError, T2GFirmwareGuard, T2GSeatProgrammer, ) from seat_domain import HardwareSeatExecutor, SeatCanMatrix, T2GSeatCompiler # noqa: E402 def function_config(*operations: dict[str, object]) -> dict[str, object]: return { "apiVersion": "cognitive-agent/v1alpha1", "kind": "FunctionConfig", "metadata": { "id": "function-test-hardware", "name": "T2G hardware test", "version": "1.0.0", "request_id": "req-hardware-test", }, "spec": {"domain": "seat", "operations": list(operations)}, } def operation( capability: str, parameters: dict[str, object], *, operation_id: str = "op-hardware-test", instance: str = "passenger", zone: str = "front_right", motion: dict[str, object] | None = None, ) -> dict[str, object]: result = { "id": operation_id, "capability": capability, "target": {"instance": instance, "zone": zone}, "parameters": parameters, "timeout_ms": 20, } if motion is not None: result["motion"] = motion return result class ReactiveFakeBus: def __init__( self, matrix: SeatCanMatrix, *, complete_motion: bool = True, position_valid: bool = True, absolute_stall_motors: set[int] | None = None, absolute_timeout_motors: set[int] | None = None, absolute_stall_hall_after: dict[int, int] | None = None, absolute_failure_targets: dict[int, int] | None = None, timed_failure_result: int | None = None, timed_failure_event: int = 4, timed_endpoint: str | None = None, ): self.matrix = matrix self.complete_motion = complete_motion self.position_valid = position_valid self.absolute_stall_motors = set(absolute_stall_motors or set()) self.absolute_timeout_motors = set(absolute_timeout_motors or set()) self.absolute_stall_hall_after = dict(absolute_stall_hall_after or {}) self.absolute_failure_targets = dict(absolute_failure_targets or {}) self.timed_failure_result = timed_failure_result self.timed_failure_event = timed_failure_event self.timed_endpoint = timed_endpoint self.hall = [0, 0, 0, 0] self.position = [0, 0, 0, 0] self.max_hall = [480, 374, 917, 647] self.sent: list[CanFrame] = [] self.received: deque[CanFrame] = deque() self.closed = False def _frame(self, message: str, values: dict[str, int]) -> CanFrame: encoded = self.matrix.encode(message, values) return CanFrame(int(encoded["frame_id"], 16), bytes.fromhex(encoded["data_hex"])) def send(self, arbitration_id: int, data: bytes) -> CanFrame: frame = CanFrame(arbitration_id, bytes(data)) self.sent.append(frame) if arbitration_id == 0x300: command = self.matrix.decode("T2G_Seat_Move_Command_300", data) sequence = command["Sequence"] opcode = command["Opcode"] stop_all_selector_valid = ( opcode != 9 or command["Motor_Selector"] == 0 ) self.received.append( self._frame( "T2G_Seat_Move_System_Status_301", { "Ack": ( 4 if opcode == 9 and stop_all_selector_valid else 2 if opcode == 9 else 1 ), "Reject_Reason": 0 if stop_all_selector_valid else 4, "Last_Opcode": opcode, "Last_Sequence": sequence, "Safety_Inhibit": 0, "Fault_Summary": 0, }, ) ) if opcode == 9 and stop_all_selector_valid: for motor in range(4): self.received.append( self._frame( "T2G_Seat_Move_Motor_Status_302", { "Motor_Sub_ID": motor, "Task_State": 0, "Result": 0, "Position_Valid": int(self.position_valid), "Motor_Sequence": sequence, "Value_X100": 0, "Duty_Percent": 0, }, ) ) for motor in range(4): self.received.append( self._frame( "T2G_Seat_Move_Diagnostic_303", { "Motor_Sub_ID": motor, "Hall_Count": self.hall[motor], "Max_Hall_Count": self.max_hall[motor], "Hall_Speed_Half_Pps": 180, }, ) ) if opcode == 1 and self.complete_motion: motor = command["Motor_Selector"] if ( motor in self.absolute_stall_motors | self.absolute_timeout_motors and command["Param_A"] == self.absolute_failure_targets.get(motor, 0) ): if motor in self.absolute_stall_hall_after: self.hall[motor] = self.absolute_stall_hall_after[motor] self.received.append( self._frame( "T2G_Seat_Move_Current_Diagnostic_304", { "Motor_Sub_ID": motor, "Current_Sample_Valid": 1, "Current_Baseline_Valid": 1, "High_Current": int( motor in self.absolute_stall_motors ), "Hall_Activity_Low": 1, "Current_Raw": 800, "Current_Baseline_Raw": 100, "Stall_Current_Delta_Threshold_Raw": 500, }, ) ) self.received.append( self._frame( "T2G_Seat_Move_Motor_Status_302", { "Motor_Sub_ID": motor, "Task_State": 7, "Result": ( 6 if motor in self.absolute_timeout_motors else 5 ), "Position_Valid": 1, "Motor_Sequence": sequence, "Value_X100": self.position[motor], "Duty_Percent": 0, }, ) ) self.received.append( self._frame( "T2G_Seat_Move_Diagnostic_303", { "Motor_Sub_ID": motor, "Last_Motor_Event": ( 5 if motor in self.absolute_timeout_motors else 4 ), "Hall_Count": self.hall[motor], "Max_Hall_Count": self.max_hall[motor], }, ) ) else: self.position[motor] = command["Param_A"] self.received.append( self._frame( "T2G_Seat_Move_Motor_Status_302", { "Motor_Sub_ID": motor, "Task_State": 6, "Result": 2, "Position_Valid": 1, "Motor_Sequence": sequence, "Value_X100": command["Param_A"], "Duty_Percent": 90, }, ) ) # Absolute commands in a multi-axis request each perform # a fresh Position_Valid preflight. Mirror cyclic 0x302. for cyclic_motor in range(4): self.received.append( self._frame( "T2G_Seat_Move_Motor_Status_302", { "Motor_Sub_ID": cyclic_motor, "Task_State": 0, "Result": 0, "Position_Valid": 1, "Motor_Sequence": sequence, "Value_X100": self.position[cyclic_motor], "Duty_Percent": 0, }, ) ) if opcode == 14: self.received.append( self._frame( "T2G_Seat_Dynamic_Mode_Status_306", { "Dynamic_Mode_ID": command["Param_A"], "Dynamic_State": 2, "Dynamic_Phase": 2 if command["Param_A"] == 1 else 5, "Dynamic_Active_Mask": 0x0F, "Dynamic_Cycle_Index": 0, "Dynamic_Phase_Elapsed_Ms": 100, }, ) ) if opcode == 15: self.received.append( self._frame( "T2G_Seat_Dynamic_Mode_Status_306", { "Dynamic_Mode_ID": 0, "Dynamic_State": 0, "Dynamic_Phase": 0, "Dynamic_Active_Mask": 0, "Dynamic_Cycle_Index": 0, "Dynamic_Phase_Elapsed_Ms": 0, }, ) ) if opcode == 0x0B and self.complete_motion: motor = command["Motor_Selector"] self.position_valid = True self.hall[motor] = 0 self.position[motor] = 0 home_status = { "Motor_Sub_ID": motor, "Task_State": 6, "Result": 2, "Position_Valid": 1, "Near_Endpoint": 1, "Motor_Sequence": sequence, "Value_X100": 0, "Duty_Percent": 0, } self.received.append( self._frame("T2G_Seat_Move_Motor_Status_302", home_status) ) self.received.append( self._frame( "T2G_Seat_Move_Diagnostic_303", { "Motor_Sub_ID": motor, "Hall_Count": 0, "Max_Hall_Count": self.max_hall[motor], }, ) ) # The real controller publishes all four cyclic motor status # samples after a home completes. Keep a complete cycle queued # for the next axis preflight in a four-axis zero request. for cyclic_motor in range(4): self.received.append( self._frame( "T2G_Seat_Move_Motor_Status_302", { "Motor_Sub_ID": cyclic_motor, "Task_State": 0, "Result": 0, "Position_Valid": 1, "Motor_Sequence": sequence, "Value_X100": self.position[cyclic_motor], "Duty_Percent": 0, }, ) ) for cyclic_motor in range(4): self.received.append( self._frame( "T2G_Seat_Move_Diagnostic_303", { "Motor_Sub_ID": cyclic_motor, "Hall_Count": self.hall[cyclic_motor], "Max_Hall_Count": self.max_hall[cyclic_motor], "Hall_Speed_Half_Pps": 180, }, ) ) if opcode == 3 and self.timed_endpoint is not None: motor = command["Motor_Selector"] at_far_endpoint = self.timed_endpoint == "far" self.hall[motor] = self.max_hall[motor] if at_far_endpoint else 0 self.position[motor] = 10_000 if at_far_endpoint else 0 self.received.append( self._frame( "T2G_Seat_Move_Motor_Status_302", { "Motor_Sub_ID": motor, "Task_State": 7, "Result": 4, "Direction": command["Direction"], "Position_Valid": 1, "Near_Endpoint": int(not at_far_endpoint), "Far_Endpoint": int(at_far_endpoint), "Motor_Sequence": sequence, "Value_X100": self.position[motor], "Duty_Percent": 0, }, ) ) self.received.append( self._frame( "T2G_Seat_Move_Diagnostic_303", { "Motor_Sub_ID": motor, "Last_Motor_Event": 3 if at_far_endpoint else 2, "Hall_Count": self.hall[motor], "Max_Hall_Count": self.max_hall[motor], }, ) ) elif opcode == 3 and self.timed_failure_result is not None: motor = command["Motor_Selector"] self.received.append( self._frame( "T2G_Seat_Move_Motor_Status_302", { "Motor_Sub_ID": motor, "Task_State": 7, "Result": self.timed_failure_result, "Direction": command["Direction"], "Position_Valid": int(self.position_valid), "Motor_Sequence": sequence, "Value_X100": 0, "Duty_Percent": command["Param_B"] // 10, }, ) ) self.received.append( self._frame( "T2G_Seat_Move_Diagnostic_303", { "Motor_Sub_ID": motor, "Last_Motor_Event": self.timed_failure_event, "Hall_Count": self.hall[motor], "Max_Hall_Count": self.max_hall[motor], "Hall_Speed_Half_Pps": 180, }, ) ) elif opcode == 3 and self.complete_motion: motor = command["Motor_Selector"] delta = max(1, command["Param_A"] // 35) if command["Direction"] == 1: self.hall[motor] = min( self.max_hall[motor], self.hall[motor] + delta ) else: self.hall[motor] = max(0, self.hall[motor] - delta) self.received.append( self._frame( "T2G_Seat_Move_Motor_Status_302", { "Motor_Sub_ID": motor, "Task_State": 6, "Result": 2, "Direction": command["Direction"], "Position_Valid": int(self.position_valid), "Motor_Sequence": sequence, "Value_X100": 0, "Duty_Percent": command["Param_B"] // 10, }, ) ) self.received.append( self._frame( "T2G_Seat_Move_Diagnostic_303", { "Motor_Sub_ID": motor, "Hall_Count": self.hall[motor], "Max_Hall_Count": self.max_hall[motor], "Hall_Speed_Half_Pps": 180, }, ) ) # Keep a cyclic sample for the endpoint re-home preflight # that follows a successful opposite-direction probe. self.received.append( self._frame( "T2G_Seat_Move_Diagnostic_303", { "Motor_Sub_ID": motor, "Hall_Count": self.hall[motor], "Max_Hall_Count": self.max_hall[motor], "Hall_Speed_Half_Pps": 180, }, ) ) elif arbitration_id == 0x29E: control = self.matrix.decode("T2G_Seat_Thermal_Control_29E", data) heating = control["Driver_Seat_Heating_On"] ventilation = control["Driver_Seat_Ventilation_On"] self.received.append( self._frame( "T2G_Seat_Thermal_Status_4FA", { "Driver_Seat_Heating_Status": ( min(control["Driver_Seat_Heating_Gear"], 3) + 1 if heating == 1 else 1 ), "Driver_Seat_Ventilation_Status": ( min(control["Driver_Seat_Ventilation_Gear"], 3) + 1 if ventilation == 1 else 1 ), }, ) ) return frame def receive(self, timeout_ms: int) -> CanFrame | None: del timeout_ms return self.received.popleft() if self.received else None def close(self) -> None: self.closed = True class ProtocolSignatureFakeBus: def __init__(self, signature: bool): self.sent: list[CanFrame] = [] self.received: deque[CanFrame] = deque() self.closed = False if signature: self.received.append(CanFrame(0x301, bytes.fromhex("10 00 00 00 00 00 F0 F0"))) for motor in range(4): self.received.append( CanFrame(0x302, bytes([motor, 0, 0x08, 0, 0, 0, 0, 0])) ) for motor, maximum in enumerate((480, 374, 917, 647)): self.received.append( CanFrame( 0x303, bytes([motor, 0, 0, 0, maximum & 0xFF, maximum >> 8, 0, 0]), ) ) for motor in range(4): self.received.append( CanFrame(0x304, bytes([motor, 3, 0, 0, 0, 0, 0xF4, 0x01])) ) self.received.append(CanFrame(0x305, bytes(8))) self.received.append(CanFrame(0x306, bytes(8))) self.received.append( CanFrame(0x307, bytes.fromhex("53 4D 56 38 0F 10 04 01")) ) self.received.append(CanFrame(0x4FA, bytes.fromhex("09 00 00 00 00 00 00 00"))) def send(self, arbitration_id: int, data: bytes) -> CanFrame: frame = CanFrame(arbitration_id, bytes(data)) self.sent.append(frame) return frame def receive(self, timeout_ms: int) -> CanFrame | None: del timeout_ms return self.received.popleft() if self.received else None def close(self) -> None: self.closed = True class FailingProtocolSignatureBus(ProtocolSignatureFakeBus): def receive(self, timeout_ms: int) -> CanFrame | None: del timeout_ms raise RuntimeError("CAN receive failed during passive protocol probe") class LegacyProtocolSignatureFakeBus(ProtocolSignatureFakeBus): def __init__(self): super().__init__(False) self.received.append(CanFrame(0x301, bytes.fromhex("10 00 00 00 00 00 F0 00"))) for motor in range(4): self.received.append( CanFrame(0x302, bytes([motor, 0, 0x08, 0, 0, 0, 0, 0])) ) for motor, maximum in enumerate((480, 374, 917, 647)): self.received.append( CanFrame( 0x303, bytes([motor, 0, 0, 0, maximum & 0xFF, maximum >> 8, 0, 0]), ) ) for motor in range(4): self.received.append( CanFrame(0x304, bytes([motor, 3, 0, 0, 0, 0, 0xF4, 0x01])) ) self.received.append(CanFrame(0x305, bytes(8))) self.received.append(CanFrame(0x4FA, bytes(8))) class FakeProgrammer: def __init__(self): self.calls = 0 def program(self) -> dict[str, object]: self.calls += 1 return { "backend": "fake", "programmed": True, "image": "Seat_Controller_demo.hex", "image_sha256": "0" * 64, } class T2GHardwareTests(unittest.TestCase): def setUp(self) -> None: self.matrix = SeatCanMatrix( [ ROOT / "domains" / "seat" / "can" / "seat-thermal-hardware-matrix.yaml", ROOT / "domains" / "seat" / "can" / "seat-motion-hardware-matrix.yaml", ] ) self.compiler = T2GSeatCompiler( ROOT / "domains" / "seat" / "can" / "signal-mapping.hardware.yaml", ROOT / "platforms" / "t2g_seat_bench" / "calibration" / "seat.yaml", ) self.contracts = ContractValidator(ROOT) def test_compiler_emits_verified_heating_payload(self) -> None: config = function_config( operation("seat.set_heating", {"state": "on", "level": 3}) ) plan = self.compiler.compile(config, {}) frames = [ self.matrix.encode(item["message"], item["signals"]) for item in plan["spec"]["commands"] ] self.assertEqual(frames[0]["frame_id"], "0x3CE") self.assertEqual(frames[0]["data_hex"], "10 00 00 00 00 00 00 00") self.assertEqual(frames[1]["frame_id"], "0x29E") self.assertEqual(frames[1]["data_hex"], "16 00 30 00 00 00 00 00") def test_compiler_emits_verified_backrest_payload(self) -> None: config = function_config( operation("seat.set_backrest_recline", {"position_pct": 50}) ) plan = self.compiler.compile(config, {}) command = plan["spec"]["commands"][0] frame = self.matrix.encode(command["message"], command["signals"]) self.assertEqual(frame["frame_id"], "0x300") self.assertEqual(frame["data_hex"], "01 12 00 00 88 13 30 75") self.assertEqual(command["signals"]["Param_B"], 30_000) self.assertEqual(command["completion"]["timeout_ms"], 33_000) def test_compiler_prefers_absolute_relative_target_with_timed_fallback(self) -> None: config = function_config( operation( "seat.set_backrest_recline", {"position_pct": 31, "angle_deg": 109}, motion={ "mode": "relative", "direction": "increase", "start_position_pct": 20, "delta_position_pct": 11, "start_angle_deg": 104, "delta_angle_deg": 5, }, ) ) command = self.compiler.compile(config, {})["spec"]["commands"][0] self.assertEqual(command["signals"]["Opcode"], 1) self.assertEqual(command["signals"]["Direction"], 0) self.assertEqual(command["signals"]["Param_A"], 3100) self.assertEqual(command["completion"]["signal"], "Value_X100") fallback = command["relative_fallback"] self.assertEqual(fallback["signals"]["Opcode"], 3) self.assertEqual(fallback["signals"]["Direction"], 1) self.assertEqual(fallback["signals"]["Param_A"], 1320) self.assertEqual(fallback["signals"]["Param_B"], 900) self.assertEqual(fallback["completion"]["signal"], "Task_State") def test_compiler_emits_native_dynamic_mode_command(self) -> None: config = function_config( operation("seat.start_dynamic_mode", {"mode_id": 1}) ) plan = self.compiler.compile(config, {}) command = plan["spec"]["commands"][0] frame = self.matrix.encode(command["message"], command["signals"]) self.assertEqual(frame["frame_id"], "0x300") self.assertEqual(frame["data_hex"], "0E 10 00 00 01 00 00 00") self.assertEqual(command["completion"]["message"], "T2G_Seat_Dynamic_Mode_Status_306") self.assertEqual(command["completion"]["accepted_states"], [1, 2]) self.assertEqual(self.contracts.validate(plan), []) def test_compiler_emits_native_dynamic_mode_stop_command(self) -> None: config = function_config(operation("seat.stop_dynamic_mode", {})) plan = self.compiler.compile(config, {}) command = plan["spec"]["commands"][0] frame = self.matrix.encode(command["message"], command["signals"]) self.assertEqual(frame["frame_id"], "0x300") self.assertEqual(frame["data_hex"], "0F 10 00 00 00 00 00 00") self.assertEqual(command["completion"]["signal"], "Dynamic_State") self.assertEqual(command["completion"]["accepted_states"], [0]) self.assertEqual(self.contracts.validate(plan), []) def test_compiler_maps_large_relative_backrest_request_to_absolute_target(self) -> None: config = function_config( operation( "seat.set_backrest_recline", {"position_pct": 64, "angle_deg": 124}, motion={ "mode": "relative", "direction": "increase", "start_position_pct": 20, "delta_position_pct": 44, "start_angle_deg": 104, "delta_angle_deg": 20, }, ) ) command = self.compiler.compile(config, {})["spec"]["commands"][0] self.assertEqual(command["signals"]["Opcode"], 1) self.assertEqual(command["signals"]["Direction"], 0) self.assertEqual(command["signals"]["Param_A"], 6400) self.assertEqual(command["completion"]["signal"], "Value_X100") def test_compiler_emits_bounded_user_timed_motion(self) -> None: config = function_config( operation( "seat.set_backrest_recline", {"position_pct": 100, "angle_deg": 163}, motion={ "mode": "timed", "direction": "increase", "start_position_pct": 0, "delta_position_pct": 100, "requested_duration_ms": 70_000, "applied_duration_ms": 30_000, "max_duration_ms": 30_000, "min_duration_ms": 300, "duration_clamped": True, "endpoint_expected": "far", }, ) ) command = self.compiler.compile(config, {})["spec"]["commands"][0] self.assertEqual(command["signals"]["Opcode"], 3) self.assertEqual(command["signals"]["Direction"], 1) self.assertEqual(command["signals"]["Param_A"], 30_000) self.assertEqual(command["signals"]["Param_B"], 900) self.assertEqual(command["completion"]["timeout_ms"], 35_000) def test_compiler_uses_bench_safe_legrest_timed_duty(self) -> None: config = function_config( operation( "seat.set_legrest_height", {"position_pct": 30, "angle_deg": 117}, motion={ "mode": "timed", "direction": "increase", "start_position_pct": 0, "delta_position_pct": 30, "requested_duration_ms": 3_000, "applied_duration_ms": 3_000, "max_duration_ms": 25_000, "min_duration_ms": 300, "duration_clamped": False, }, ) ) command = self.compiler.compile(config, {})["spec"]["commands"][0] self.assertEqual(command["signals"]["Motor_Selector"], 3) self.assertEqual(command["signals"]["Opcode"], 3) self.assertEqual(command["signals"]["Param_A"], 3_000) self.assertEqual(command["signals"]["Param_B"], 500) def test_driver_target_is_rejected_by_hardware_compiler(self) -> None: driver = function_config( operation( "seat.set_heating", {"state": "on", "level": 2}, instance="driver", zone="front_left", ) ) with self.assertRaisesRegex(ValueError, "passenger/front_right"): self.compiler.compile(driver, {}) def test_compiler_spaces_sequential_axes_around_work_flash_persistence(self) -> None: config = function_config( operation("seat.set_longitudinal_position", {"position_pct": 51}), operation("seat.set_cushion_tilt", {"position_pct": 30}), ) commands = self.compiler.compile(config, {})["spec"]["commands"] self.assertEqual( [command["send_offset_ms"] for command in commands], [0, 2_000], ) def test_duplicate_passenger_motion_is_sent_once(self) -> None: config = function_config( operation( "seat.set_backrest_recline", {"position_pct": 60}, operation_id="op-passenger-a", ), operation( "seat.set_backrest_recline", {"position_pct": 60}, operation_id="op-passenger-b", ), ) plan = self.compiler.compile(config, {}) self.assertEqual(len(plan["spec"]["commands"]), 1) self.assertEqual( plan["spec"]["commands"][0]["operation_ref"], "op-passenger-a,op-passenger-b", ) bus = ReactiveFakeBus(self.matrix) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20 ).execute(plan, config) self.assertEqual([item.data[0] for item in bus.sent], [9, 1]) operation_results = [ item for item in report["spec"]["results"] if item["ref"].startswith("op-") ] self.assertEqual( {item["ref"] for item in operation_results}, {"op-passenger-a", "op-passenger-b"}, ) def test_executor_waits_for_ack_and_physical_motion_feedback(self) -> None: config = function_config( operation("seat.set_backrest_recline", {"position_pct": 50}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20 ).execute(plan, config) self.assertEqual(report["spec"]["mode"], "hil") self.assertEqual(report["spec"]["status"], "succeeded") self.assertFalse(report["spec"]["results"][0]["telemetry"]["simulated"]) self.assertTrue(report["spec"]["results"][0]["telemetry"]["hardware_feedback"]) self.assertEqual([item.data[0] for item in bus.sent], [9, 1]) self.assertTrue(bus.closed) def test_executor_waits_for_native_dynamic_mode_feedback(self) -> None: config = function_config( operation("seat.start_dynamic_mode", {"mode_id": 2}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20 ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") telemetry = report["spec"]["results"][0]["telemetry"] self.assertEqual(telemetry["dynamic_mode_id"], 2) self.assertEqual(telemetry["dynamic_state"], 2) self.assertEqual(telemetry["dynamic_phase"], 5) self.assertEqual([item.data[0] for item in bus.sent], [9, 14]) self.assertTrue(bus.closed) def test_executor_stops_dynamic_mode_without_homing_or_pre_stop(self) -> None: config = function_config(operation("seat.stop_dynamic_mode", {})) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, synchronize_position_before_first_motion=True, position_sync_state={"synchronized": False}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") telemetry = report["spec"]["results"][0]["telemetry"] self.assertEqual(telemetry["dynamic_mode_id"], 0) self.assertEqual(telemetry["dynamic_state"], 0) self.assertEqual(telemetry["repeat"], "none") self.assertEqual([item.data[0] for item in bus.sent], [15]) self.assertTrue(bus.closed) def test_firmware_replacement_forces_four_axis_position_memory_sync(self) -> None: config = function_config( operation("seat.set_backrest_recline", {"position_pct": 50}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix) bus.hall = [220, 180, 600, 310] bus.position = [4500, 4800, 6500, 4700] position_sync_state = {"synchronized": True} class ReprogrammedGuard: def prepare_bus(self) -> tuple[ReactiveFakeBus, dict[str, object]]: return bus, { "firmware_verified": True, "programmed_this_request": True, } with patch("seat_domain.hardware_executor.time.sleep"): report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, firmware_guard=ReprogrammedGuard(), # type: ignore[arg-type] synchronize_position_before_first_motion=True, position_sync_state=position_sync_state, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") sent_commands = [ self.matrix.decode("T2G_Seat_Move_Command_300", frame.data) for frame in bus.sent if frame.arbitration_id == 0x300 ] self.assertEqual( [(item["Opcode"], item["Motor_Selector"]) for item in sent_commands], [(9, 0), (0x0B, 0), (0x0B, 1), (0x0B, 2), (0x0B, 3), (1, 2)], ) sync = next( item for item in report["spec"]["results"] if item["ref"] == "seat-position-memory-sync" ) self.assertEqual( sync["telemetry"]["trigger"], "repository_firmware_reprogrammed" ) self.assertEqual(len(sync["telemetry"]["axes"]), 4) self.assertTrue(position_sync_state["synchronized"]) def test_first_process_motion_syncs_once_and_reuses_the_reference(self) -> None: config = function_config( operation("seat.set_longitudinal_position", {"position_pct": 40}) ) plan = self.compiler.compile(config, {}) state = {"synchronized": False} first_bus = ReactiveFakeBus(self.matrix) with patch("seat_domain.hardware_executor.time.sleep"): first = HardwareSeatExecutor( self.matrix, lambda: first_bus, ack_timeout_ms=20, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, synchronize_position_before_first_motion=True, position_sync_state=state, ).execute(plan, config) second_bus = ReactiveFakeBus(self.matrix) second = HardwareSeatExecutor( self.matrix, lambda: second_bus, ack_timeout_ms=20, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, synchronize_position_before_first_motion=True, position_sync_state=state, ).execute(plan, config) self.assertEqual(first["spec"]["status"], "succeeded") self.assertEqual(second["spec"]["status"], "succeeded") self.assertEqual([item.data[0] for item in first_bus.sent], [9, 0x0B, 0x0B, 0x0B, 0x0B, 1]) self.assertEqual([item.data[0] for item in second_bus.sent], [9, 1]) def test_passive_snapshot_reads_all_passenger_axes_and_thermal_state(self) -> None: bus = ReactiveFakeBus(self.matrix) bus.received.extend( [ bus._frame( "T2G_Seat_Move_System_Status_301", { "Ack": 1, "System_State": 2, "Last_Sequence": 9, "Last_Opcode": 1, "Position_Valid_Mask": 0x0F, "Firmware_Revision": 1, }, ), bus._frame( "T2G_Seat_Move_Diagnostic_303", { "Motor_Sub_ID": 0, "Hall_Count": 120, "Max_Hall_Count": 480, "Hall_Speed_Half_Pps": 4, }, ), bus._frame( "T2G_Seat_Move_Current_Diagnostic_304", { "Motor_Sub_ID": 0, "Current_Sample_Valid": 1, "Current_Baseline_Valid": 1, "Current_Raw": 240, "Current_Baseline_Raw": 100, "Stall_Current_Delta_Threshold_Raw": 500, }, ), bus._frame( "T2G_Seat_Thermal_Current_Diagnostic_305", { "Fan_Current_Sample_Valid": 1, "Heater_Current_Sample_Valid": 1, "Fan_Running": 1, "Heater_Level": 2, "Fan_Current_Raw": 300, "Heater_Current_Raw": 450, }, ), ] ) for motor, value_x100 in enumerate((1200, 3400, 5600, 7800)): bus.received.append( bus._frame( "T2G_Seat_Move_Motor_Status_302", { "Motor_Sub_ID": motor, "Task_State": 6, "Result": 2, "Position_Valid": 1, "Motor_Sequence": 7, "Value_X100": value_x100, "Duty_Percent": 0, }, ) ) bus.received.append( bus._frame( "T2G_Seat_Thermal_Status_4FA", { "Driver_Seat_Ventilation_Status": 1, "Driver_Seat_Heating_Status": 3, }, ) ) executor = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, thermal_timeout_ms=20, axis_maximum_x100={ capability: 10_000 for capability in self.compiler.axes }, ) physical = executor.read_physical_state() self.assertTrue(physical["complete"]) self.assertEqual( { axis: value["position_pct"] for axis, value in physical["position"].items() }, { "longitudinal": 12, "cushion_tilt": 34, "backrest_recline": 56, "legrest_height": 78, }, ) self.assertEqual(physical["ventilation"]["state"], "off") self.assertEqual(physical["heating"]["state"], "on") self.assertEqual(physical["heating"]["level"], 2) self.assertEqual(physical["channel"], 0) self.assertEqual( physical["protocol_profile"], "passenger-seat-native-can0-v1" ) self.assertEqual(physical["system"]["position_valid_mask"], 0x0F) self.assertEqual( physical["diagnostics"]["motors"]["longitudinal"]["hall_count"], 120, ) self.assertEqual( physical["diagnostics"]["motors"]["longitudinal"]["current"]["raw"], 240, ) self.assertEqual( physical["diagnostics"]["thermal"]["heater_current_raw"], 450 ) self.assertEqual( physical["observed_frame_ids"], ["0x301", "0x302", "0x303", "0x304", "0x305", "0x4FA"], ) self.assertTrue(bus.closed) def test_executor_sends_firmware_valid_stop_all_before_four_axis_zero(self) -> None: config = function_config( operation( "seat.set_longitudinal_position", {"position_pct": 0}, operation_id="zero-horizontal", ), operation( "seat.set_cushion_tilt", {"position_pct": 0}, operation_id="zero-cushion", ), operation( "seat.set_backrest_recline", {"position_pct": 0}, operation_id="zero-backrest", ), operation( "seat.set_legrest_height", {"position_pct": 0}, operation_id="zero-legrest", ), ) plan = self.compiler.compile(config, {}) self.assertEqual(self.compiler.protocol["stop_all_motor_selector"], 0) self.assertEqual( [ command["signals"]["Motor_Selector"] for command in plan["spec"]["commands"] ], [0, 1, 2, 3], ) self.assertTrue( all( command["signals"]["Opcode"] == 0x0B and command["signals"]["Param_A"] == 0x4B52 and command["signals"]["Param_B"] == 120 for command in plan["spec"]["commands"] ) ) bus = ReactiveFakeBus(self.matrix) position_sync_state = {"synchronized": False} report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, stop_all_motor_selector=int( self.compiler.protocol["stop_all_motor_selector"] ), synchronize_position_before_first_motion=True, position_sync_state=position_sync_state, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") sent_commands = [ self.matrix.decode("T2G_Seat_Move_Command_300", frame.data) for frame in bus.sent if frame.arbitration_id == 0x300 ] self.assertEqual( [(item["Opcode"], item["Motor_Selector"]) for item in sent_commands], [(9, 0), (0x0B, 0), (0x0B, 1), (0x0B, 2), (0x0B, 3)], ) self.assertTrue(position_sync_state["synchronized"]) self.assertEqual( sum( item["ref"] == "seat-position-memory-sync" for item in report["spec"]["results"] ), 1, ) def test_zero_recovers_verified_stale_near_endpoint_offset(self) -> None: config = function_config( operation("seat.set_longitudinal_position", {"position_pct": 0}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix, absolute_stall_motors={0}) bus.hall[0] = 6 bus.position[0] = 125 report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") sent_commands = [ self.matrix.decode("T2G_Seat_Move_Command_300", frame.data) for frame in bus.sent if frame.arbitration_id == 0x300 ] self.assertEqual([item["Opcode"] for item in sent_commands], [9, 0x0B]) telemetry = report["spec"]["results"][0]["telemetry"] recovery = telemetry["position_recovery"] self.assertEqual(recovery["mode"], "explicit_known_range_home") self.assertEqual(recovery["hall_before"], 6) self.assertEqual(recovery["hall_after"], 0) self.assertEqual(telemetry["effective_position_pct"], 0) def test_zero_recovers_after_full_travel_reaches_near_endpoint_window(self) -> None: config = function_config( operation("seat.set_cushion_tilt", {"position_pct": 0}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus( self.matrix, absolute_stall_motors={1}, absolute_stall_hall_after={1: 4}, ) bus.hall[1] = 374 bus.position[1] = 10000 report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") sent_commands = [ self.matrix.decode("T2G_Seat_Move_Command_300", frame.data) for frame in bus.sent if frame.arbitration_id == 0x300 ] self.assertEqual([item["Opcode"] for item in sent_commands], [9, 0x0B]) recovery = report["spec"]["results"][0]["telemetry"]["position_recovery"] self.assertEqual(recovery["hall_before"], 374) self.assertEqual(recovery["hall_after"], 0) def test_zero_recovers_real_m0_stall_at_calibrated_near_endpoint(self) -> None: config = function_config( operation("seat.set_longitudinal_position", {"position_pct": 0}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus( self.matrix, absolute_stall_motors={0}, absolute_stall_hall_after={0: 52}, ) bus.hall[0] = 480 bus.position[0] = 10_000 report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") sent_commands = [ self.matrix.decode("T2G_Seat_Move_Command_300", frame.data) for frame in bus.sent if frame.arbitration_id == 0x300 ] self.assertEqual([item["Opcode"] for item in sent_commands], [9, 0x0B]) recovery = report["spec"]["results"][0]["telemetry"]["position_recovery"] self.assertEqual(recovery["hall_before"], 480) self.assertEqual(recovery["hall_after"], 0) self.assertEqual( report["spec"]["results"][0]["telemetry"]["effective_position_pct"], 0, ) def test_explicit_zero_homes_even_when_the_stored_position_is_midrange(self) -> None: config = function_config( operation("seat.set_longitudinal_position", {"position_pct": 0}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix, absolute_stall_motors={0}) bus.hall[0] = 116 bus.position[0] = 2417 report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") sent_commands = [ self.matrix.decode("T2G_Seat_Move_Command_300", frame.data) for frame in bus.sent if frame.arbitration_id == 0x300 ] self.assertEqual([item["Opcode"] for item in sent_commands], [9, 0x0B]) recovery = report["spec"]["results"][0]["telemetry"]["position_recovery"] self.assertEqual(recovery["hall_before"], 116) self.assertEqual(recovery["hall_after"], 0) def test_zero_recovers_near_endpoint_timeout_after_full_travel(self) -> None: config = function_config( operation("seat.set_backrest_recline", {"position_pct": 0}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus( self.matrix, absolute_timeout_motors={2}, absolute_stall_hall_after={2: 3}, ) bus.hall[2] = 605 bus.position[2] = 4485 report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") recovery = report["spec"]["results"][0]["telemetry"]["position_recovery"] self.assertEqual(recovery["hall_before"], 605) self.assertEqual(recovery["hall_after"], 0) def test_far_endpoint_timeout_within_position_tolerance_is_accepted(self) -> None: config = function_config( operation("seat.set_legrest_height", {"position_pct": 100}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus( self.matrix, absolute_timeout_motors={3}, absolute_stall_hall_after={3: 646}, absolute_failure_targets={3: 10_000}, ) bus.position[3] = 9_990 report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, axis_maximum_x100={"seat.set_legrest_height": 10_000}, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") telemetry = report["spec"]["results"][0]["telemetry"] self.assertEqual( telemetry["terminal_tolerance_acceptance"]["mode"], "verified_far_endpoint_within_target_tolerance", ) self.assertEqual(telemetry["effective_position_pct"], 100) def test_midrange_timeout_within_tolerance_still_fails(self) -> None: config = function_config( operation("seat.set_legrest_height", {"position_pct": 50}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus( self.matrix, absolute_timeout_motors={3}, absolute_stall_hall_after={3: 323}, absolute_failure_targets={3: 5_000}, ) bus.position[3] = 4_990 report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, axis_maximum_x100={"seat.set_legrest_height": 10_000}, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "failed") self.assertIn("TIMEOUT", report["spec"]["results"][-1]["message"]) def test_executor_blocks_absolute_motion_when_board_position_is_invalid(self) -> None: config = function_config( operation("seat.set_backrest_recline", {"position_pct": 50}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix, position_valid=False) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20 ).execute(plan, config) self.assertEqual(report["spec"]["status"], "failed") self.assertEqual([item.data[0] for item in bus.sent], [9, 9]) failure = report["spec"]["results"][-1] self.assertIn("Position_Valid=0", failure["message"]) self.assertTrue(failure["telemetry"]["known_range_home_required"]) def test_large_relative_request_is_blocked_without_sending_absolute_opcode(self) -> None: config = function_config( operation( "seat.set_backrest_recline", {"position_pct": 64, "angle_deg": 124}, motion={ "mode": "relative", "direction": "increase", "start_position_pct": 20, "delta_position_pct": 44, "start_angle_deg": 104, "delta_angle_deg": 20, }, ) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix, position_valid=False) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20 ).execute(plan, config) self.assertEqual(report["spec"]["status"], "failed") self.assertEqual([item.data[0] for item in bus.sent], [9, 9]) self.assertNotIn(1, [item.data[0] for item in bus.sent]) self.assertTrue( report["spec"]["results"][-1]["telemetry"]["known_range_home_required"] ) def test_executor_can_home_invalid_axis_when_separately_enabled(self) -> None: config = function_config( operation("seat.set_backrest_recline", {"position_pct": 50}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix, position_valid=False) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, motor_max_hall={0: 480, 1: 374, 2: 917, 3: 647}, auto_home=True, home_timeout_s=10, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") self.assertEqual([item.data[0] for item in bus.sent], [9, 0x0B, 1]) telemetry = report["spec"]["results"][0]["telemetry"] self.assertEqual(telemetry["position_recovery"]["mode"], "home_known_range") self.assertEqual(telemetry["position_recovery"]["expected_max_hall"], 917) def test_executor_accepts_hall_confirmed_timed_motion_without_position(self) -> None: config = function_config( operation( "seat.set_backrest_recline", {"position_pct": 31, "angle_deg": 109}, motion={ "mode": "relative", "direction": "increase", "start_position_pct": 20, "delta_position_pct": 11, "start_angle_deg": 104, "delta_angle_deg": 5, }, ) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix, position_valid=False) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20 ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") self.assertEqual([item.data[0] for item in bus.sent], [9, 3]) telemetry = report["spec"]["results"][0]["telemetry"] self.assertEqual(telemetry["motion_mode"], "relative_timed_duty") self.assertFalse(telemetry["position_valid"]) self.assertGreater(telemetry["hall_delta"], 0) self.assertTrue(telemetry["hall_direction_verified"]) def test_executor_accepts_user_timed_motion_that_reaches_endpoint_early(self) -> None: config = function_config( operation( "seat.set_backrest_recline", {"position_pct": 100, "angle_deg": 163}, motion={ "mode": "timed", "direction": "increase", "start_position_pct": 0, "delta_position_pct": 100, "requested_duration_ms": 70_000, "applied_duration_ms": 30_000, "max_duration_ms": 30_000, "min_duration_ms": 300, "duration_clamped": True, "endpoint_expected": "far", }, ) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix, timed_endpoint="far") report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, axis_maximum_x100={"seat.set_backrest_recline": 10_000}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") self.assertEqual([item.data[0] for item in bus.sent], [9, 3]) telemetry = report["spec"]["results"][0]["telemetry"] self.assertEqual(telemetry["motion_mode"], "timed_user_request") self.assertEqual(telemetry["endpoint_reached"], "far") self.assertTrue(telemetry["endpoint_terminal_acceptance"]) self.assertEqual(telemetry["effective_position_pct"], 100) self.assertIn("最大允许时长为30秒", telemetry["user_notice"]) self.assertIn("到达最后方", telemetry["user_notice"]) def test_executor_does_not_hide_non_endpoint_user_timed_stall(self) -> None: config = function_config( operation( "seat.set_backrest_recline", {"position_pct": 100, "angle_deg": 163}, motion={ "mode": "timed", "direction": "increase", "start_position_pct": 0, "delta_position_pct": 100, "requested_duration_ms": 2_000, "applied_duration_ms": 2_000, "max_duration_ms": 30_000, "min_duration_ms": 300, "duration_clamped": False, }, ) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus( self.matrix, timed_failure_result=5, timed_failure_event=4, ) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, axis_maximum_x100={"seat.set_backrest_recline": 10_000}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "failed") self.assertEqual( report["spec"]["results"][-1]["telemetry"]["failure_category"], "stall_no_hall_activity", ) def test_executor_uses_closed_loop_target_for_calibrated_relative_motion(self) -> None: config = function_config( operation( "seat.set_backrest_recline", {"position_pct": 31, "angle_deg": 109}, motion={ "mode": "relative", "direction": "increase", "start_position_pct": 20, "delta_position_pct": 11, "start_angle_deg": 104, "delta_angle_deg": 5, }, ) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix, position_valid=True) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20, axis_maximum_x100={"seat.set_backrest_recline": 10_000}, ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") self.assertEqual([item.data[0] for item in bus.sent], [9, 1]) telemetry = report["spec"]["results"][0]["telemetry"] self.assertEqual(telemetry["motion_mode"], "relative_absolute_target") self.assertEqual(telemetry["effective_position_pct"], 31) def test_executor_reports_stall_with_post_failure_hall_diagnostics(self) -> None: config = function_config( operation( "seat.set_backrest_recline", {"position_pct": 31, "angle_deg": 109}, motion={ "mode": "relative", "direction": "increase", "start_position_pct": 20, "delta_position_pct": 11, "start_angle_deg": 104, "delta_angle_deg": 5, }, ) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus( self.matrix, position_valid=False, timed_failure_result=5, timed_failure_event=4, ) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20 ).execute(plan, config) self.assertEqual(report["spec"]["status"], "failed") self.assertEqual([item.data[0] for item in bus.sent], [9, 3, 9]) failure = report["spec"]["results"][-1] self.assertIn("STALL", failure["message"]) self.assertEqual(failure["telemetry"]["motor_result_name"], "STALL") self.assertEqual(failure["telemetry"]["last_motor_event_name"], "STALL") self.assertEqual( failure["telemetry"]["failure_category"], "stall_no_hall_activity" ) self.assertEqual(failure["telemetry"]["hall_before"], 0) self.assertEqual(failure["telemetry"]["hall_after"], 0) self.assertEqual( failure["telemetry"]["diagnostic_frame"]["frame_id"], "0x303" ) def test_executor_sends_stop_all_after_motion_feedback_timeout(self) -> None: config = function_config( operation("seat.set_cushion_tilt", {"position_pct": 50}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix, complete_motion=False) report = HardwareSeatExecutor( self.matrix, lambda: bus, ack_timeout_ms=20 ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") self.assertEqual( report["spec"]["verification_status"], "submitted_unverified", ) self.assertEqual([item.data[0] for item in bus.sent], [9, 1, 9]) telemetry = report["spec"]["results"][-1]["telemetry"] self.assertTrue(telemetry["stop_all_attempted"]) self.assertEqual(telemetry["delivery_status"], "submitted") self.assertEqual(telemetry["command_frame"]["frame_id"], "0x300") self.assertEqual(telemetry["verification_status"], "submitted_unverified") self.assertFalse(telemetry["physical_effect_confirmed"]) def test_executor_waits_for_real_thermal_status(self) -> None: config = function_config( operation("seat.set_ventilation", {"state": "on", "level": 2}) ) plan = self.compiler.compile(config, {}) bus = ReactiveFakeBus(self.matrix) report = HardwareSeatExecutor( self.matrix, lambda: bus, thermal_timeout_ms=20 ).execute(plan, config) self.assertEqual(report["spec"]["status"], "succeeded") telemetry = report["spec"]["results"][0]["telemetry"] self.assertEqual(telemetry["feedback_frame"]["frame_id"], "0x4FA") self.assertEqual(telemetry["feedback_value"], 3) self.assertEqual(telemetry["verification_status"], "verified_success") def test_executor_preserves_can_adapter_open_failure(self) -> None: config = function_config( operation("seat.set_heating", {"state": "on", "level": 1}) ) plan = self.compiler.compile(config, {}) def unavailable_bus(): raise CanGatewayError( "ZCAN_OpenDevice failed; connect the adapter and close ZCANPRO first" ) report = HardwareSeatExecutor(self.matrix, unavailable_bus).execute(plan, config) self.assertEqual(report["spec"]["status"], "failed") failure = report["spec"]["results"][-1] self.assertEqual(failure["telemetry"]["error_type"], "CanGatewayError") self.assertEqual( failure["telemetry"]["verification_status"], "failed_or_blocked" ) self.assertIn("ZCAN_OpenDevice failed", failure["message"]) self.assertFalse(failure["telemetry"]["stop_all_attempted"]) def test_settings_allow_only_front_passenger_target(self) -> None: environment = { "COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "true", "COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN_REQUIRED": "true", "COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN": "bench-secret", "COGNITIVE_AGENT_SEAT_FIRMWARE_PROFILES": "combined", "COGNITIVE_AGENT_SEAT_CAN_BACKEND": "python-can", "COGNITIVE_AGENT_SEAT_CAN_BITRATE": "500000", "COGNITIVE_AGENT_SEAT_CAN_INTERFACE": "socketcan", "COGNITIVE_AGENT_SEAT_CAN_CHANNEL": "0", "COGNITIVE_AGENT_SEAT_AUTO_FLASH_ENABLED": "false", } with patch.dict(os.environ, environment, clear=False): settings = SeatHardwareSettings.from_env(ROOT) settings = replace( settings, firmware_images={"combined": ROOT / "pyproject.toml"} ) settings.authorize("bench-secret") settings.validate_function_config( function_config( operation( "seat.set_heating", {"state": "on", "level": 1}, instance="passenger", zone="front_right", ) ) ) with self.assertRaises(HardwareSafetyError): settings.validate_function_config( function_config( operation( "seat.set_heating", {"state": "on", "level": 1}, instance="rear_left", zone="rear_left", ) ) ) settings.validate_function_config( function_config( operation("seat.set_longitudinal_position", {"position_pct": 40}) ) ) settings.validate_function_config( function_config(operation("seat.set_cushion_tilt", {"position_pct": 40})) ) settings.validate_function_config( function_config(operation("seat.stop_dynamic_mode", {})) ) with self.assertRaises(HardwareSafetyError): settings.validate_function_config( function_config( operation("seat.set_legrest_extension", {"position_pct": 40}) ) ) with self.assertRaises(HardwareSafetyError): settings.validate_function_config( function_config( operation( "seat.set_heating", {"state": "on", "level": 1}, instance="driver", zone="front_left", ) ) ) with self.assertRaises(HardwareSafetyError): settings.authorize("wrong-secret") def test_settings_enable_auto_flash_by_default_only_for_hardware_mode(self) -> None: hardware_environment = { "COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "true", "COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN": "bench-secret", "COGNITIVE_AGENT_SEAT_FIRMWARE_PROFILES": "combined", "COGNITIVE_AGENT_SEAT_CAN_BACKEND": "python-can", "COGNITIVE_AGENT_SEAT_CAN_INTERFACE": "socketcan", "COGNITIVE_AGENT_SEAT_CAN_CHANNEL": "0", } with patch.dict(os.environ, hardware_environment, clear=True): automatic = SeatHardwareSettings.from_env(ROOT) self.assertTrue(automatic.enabled) self.assertTrue(automatic.auto_flash_enabled) self.assertTrue(automatic.position_sync_enabled) with patch.dict( os.environ, { **hardware_environment, "COGNITIVE_AGENT_SEAT_AUTO_FLASH_ENABLED": "false", }, clear=True, ): opted_out = SeatHardwareSettings.from_env(ROOT) self.assertFalse(opted_out.auto_flash_enabled) with patch.dict(os.environ, {}, clear=True): dry_run = SeatHardwareSettings.from_env(ROOT) self.assertFalse(dry_run.enabled) self.assertFalse(dry_run.auto_flash_enabled) self.assertFalse(dry_run.position_sync_enabled) def test_settings_allow_explicit_local_debug_mode_without_token(self) -> None: environment = { "COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "true", "COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN_REQUIRED": "false", "COGNITIVE_AGENT_SEAT_FIRMWARE_PROFILES": "combined", "COGNITIVE_AGENT_SEAT_CAN_BACKEND": "python-can", "COGNITIVE_AGENT_SEAT_CAN_INTERFACE": "socketcan", "COGNITIVE_AGENT_SEAT_CAN_CHANNEL": "0", "COGNITIVE_AGENT_SEAT_AUTO_FLASH_ENABLED": "false", } with patch.dict(os.environ, environment, clear=True): settings = SeatHardwareSettings.from_env(ROOT) self.assertFalse(settings.approval_required) self.assertTrue(settings.configured) settings.authorize(None) public = settings.public_info() self.assertFalse(public["approval_required"]) self.assertFalse(public["approval_configured"]) def test_settings_reject_driver_target(self) -> None: environment = { "COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "true", "COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN": "bench-secret", "COGNITIVE_AGENT_SEAT_FIRMWARE_PROFILES": "combined", } with patch.dict(os.environ, environment, clear=False): settings = SeatHardwareSettings.from_env(ROOT) with self.assertRaises(HardwareSafetyError): settings.validate_function_config( function_config( operation( "seat.set_heating", {"state": "on", "level": 1}, operation_id="op-driver", instance="driver", zone="front_left", ), ) ) def test_firmware_guard_reuses_bus_when_protocol_signature_matches(self) -> None: bus = ProtocolSignatureFakeBus(True) guard = T2GFirmwareGuard( lambda: bus, None, auto_flash=False, probe_timeout_ms=20, ) prepared, telemetry = guard.prepare_bus() self.assertIs(prepared, bus) self.assertFalse(telemetry["programmed_this_request"]) self.assertEqual(telemetry["firmware_revision"], 15) self.assertEqual(telemetry["firmware_build_id"], 16) self.assertEqual(bus.sent, []) self.assertEqual(telemetry["max_hall_counts"], [480, 374, 917, 647]) self.assertFalse(bus.closed) def test_firmware_guard_programs_then_reverifies(self) -> None: before = ProtocolSignatureFakeBus(False) after = ProtocolSignatureFakeBus(True) buses = deque([before, after]) programmer = FakeProgrammer() guard = T2GFirmwareGuard( buses.popleft, programmer, # type: ignore[arg-type] auto_flash=True, probe_timeout_ms=20, boot_wait_ms=0, ) prepared, telemetry = guard.prepare_bus() self.assertIs(prepared, after) self.assertEqual(programmer.calls, 1) self.assertTrue(before.closed) self.assertTrue(telemetry["programmed_this_request"]) def test_firmware_guard_programs_when_unflashed_target_does_not_ack(self) -> None: before = FailingProtocolSignatureBus(False) after = ProtocolSignatureFakeBus(True) buses = deque([before, after]) programmer = FakeProgrammer() guard = T2GFirmwareGuard( buses.popleft, programmer, # type: ignore[arg-type] auto_flash=True, probe_timeout_ms=20, boot_wait_ms=0, ) prepared, telemetry = guard.prepare_bus() self.assertIs(prepared, after) self.assertEqual(programmer.calls, 1) self.assertTrue(before.closed) self.assertTrue(telemetry["programmed_this_request"]) def test_firmware_guard_fails_closed_when_auto_flash_is_disabled(self) -> None: bus = ProtocolSignatureFakeBus(False) guard = T2GFirmwareGuard( lambda: bus, None, auto_flash=False, probe_timeout_ms=20, ) with self.assertRaises(FirmwareProgrammingError): guard.prepare_bus() self.assertTrue(bus.closed) def test_firmware_guard_reports_legacy_revision_and_missing_dynamic_status(self) -> None: bus = LegacyProtocolSignatureFakeBus() guard = T2GFirmwareGuard( lambda: bus, None, auto_flash=False, probe_timeout_ms=20, ) with self.assertRaises(FirmwareProgrammingError) as raised: guard.prepare_bus() message = str(raised.exception) self.assertIn("firmware_revision=0 (expected 15)", message) self.assertIn("missing_frame_ids=0x306,0x307", message) self.assertIn("max_hall_counts=480,374,917,647", message) self.assertTrue(bus.closed) def test_programmer_command_downloads_and_hands_off_to_running_target(self) -> None: firmware_root = ROOT / "firmware" / "t2g" / "Seat_Controller_demo" output_image = ( firmware_root / "IAR" / "rev_d" / "Exe" / "Seat_Controller_demo.out" ) program_macro = firmware_root / "IAR" / "Seat_Controller_program.mac" programmer = T2GSeatProgrammer( cspybat=ROOT / "fake-iar" / "common" / "bin" / "cspybat.exe", debug_file=output_image, hex_image=( firmware_root / "IAR" / "rev_d" / "Exe" / "Seat_Controller_demo.hex" ), macro_file=program_macro, ) # Dependency discovery is covered by settings/configuration tests. This # test only validates the command and never starts C-SPY or a probe. with patch.object(T2GSeatProgrammer, "required_files", return_value=()): command = programmer.command() self.assertEqual(command[3], str(output_image)) self.assertIn(f"--macro={program_macro}", command) self.assertNotIn("--download_only", command) self.assertIn("--leave_target_running", command) timeout_index = command.index("--timeout") self.assertEqual(command[timeout_index + 1], "3000") self.assertIn("--jet_probe=cmsisdap", command) self.assertNotIn("Seat_Controller_demo.mac", " ".join(command)) def test_hardware_validation_replaces_reference_dry_run_warnings(self) -> None: validation = [ {"severity": "warning", "code": code, "message": "dry-run"} for code in ( "LEVEL_COMMAND_SIMULATED", "SYNTHETIC_MOTION_CAN", "DRIVER_MOTION_INTERLOCK_NOT_IMPLEMENTED", "COMBINED_THERMAL_SIMULATED", "VIRTUAL_EXECUTION_ONLY", ) ] SeatDemoService._adapt_validation_for_hardware(validation) codes = {issue["code"] for issue in validation} self.assertEqual( codes, { "LEVEL_COMMAND_T2G_MAPPED", "T2G_MOTION_PROTOCOL_MAPPED", "BENCH_ONLY_NO_VEHICLE_INTERLOCKS", "COMBINED_THERMAL_T2G_MAPPED", "REFERENCE_MATRIX_OVERRIDDEN_BY_T2G_BENCH", }, ) messages = " ".join(issue["message"] for issue in validation) self.assertNotIn("禁止实车下发", messages) self.assertNotIn("只能虚拟执行", messages) def test_physical_feedback_updates_only_front_passenger(self) -> None: actions = [ { "target": "passenger", "function": "ventilation", "state": "on", "requested_level": 2, } ] config = function_config( operation( "seat.set_ventilation", {"state": "on", "level": 2}, instance="passenger", zone="front_right", ) ) report = { "spec": { "results": [ { "ref": "op-hardware-test", "status": "succeeded", "telemetry": {"feedback_value": 3}, } ] } } projected = SeatDemoService._hardware_feedback_actions( actions, config, report ) self.assertEqual({item["target"] for item in projected}, {"passenger"}) self.assertTrue( all(item["requested_level"] == 2 for item in projected) ) def test_complete_physical_snapshot_projects_unrequested_passenger_axes(self) -> None: actions = [ { "domain": "seat", "target": "passenger", "action_type": "position", "axis": "longitudinal", "position_pct": 25, } ] config = function_config( operation( "seat.set_longitudinal_position", {"position_pct": 25}, ) ) report = { "spec": { "results": [ { "ref": "op-hardware-test", "status": "succeeded", "telemetry": {"effective_position_pct": 25}, } ], "physical_state": { "position": { axis: { "position_pct": 0, "position_valid": True, } for axis in ( "longitudinal", "cushion_tilt", "backrest_recline", "legrest_height", ) }, "ventilation": {"state": "off", "level": 0}, "heating": {"state": "off", "level": 0}, }, } } projected = SeatDemoService._hardware_feedback_actions( actions, config, report ) positions = { item["axis"]: item["position_pct"] for item in projected if item.get("action_type") == "position" } self.assertEqual( positions, { "longitudinal": 0, "cushion_tilt": 0, "backrest_recline": 0, "legrest_height": 0, }, ) self.assertEqual({item["target"] for item in projected}, {"passenger"}) if __name__ == "__main__": unittest.main()