优化了副驾座椅控制

This commit is contained in:
minghanchen
2026-08-25 11:39:41 +08:00
parent 33c92d1352
commit 9ac8fdface
34 changed files with 31714 additions and 289 deletions
+242 -3
View File
@@ -203,7 +203,7 @@ class MqttCanPayloadTests(unittest.TestCase):
self.assertEqual(frame.arbitration_id, 0x42E)
self.assertTrue(frame.is_fd)
def test_ignores_legacy_can0_state_outside_seat_ota_wire_protocol(self) -> None:
def test_accepts_native_passenger_seat_firmware_status(self) -> None:
frames = parse_mqtt_can_payload(
json.dumps(
{
@@ -220,7 +220,9 @@ class MqttCanPayloadTests(unittest.TestCase):
}
)
)
self.assertEqual(frames, [])
self.assertEqual(len(frames), 1)
self.assertEqual(frames[0].channel, 0)
self.assertEqual(frames[0].arbitration_id, 0x302)
def test_translates_seat_ota_info_responses_into_four_position_states(self) -> None:
values = {
@@ -317,7 +319,7 @@ class MqttCanPayloadTests(unittest.TestCase):
)
self.assertEqual(
[(frame.channel, frame.arbitration_id) for frame in frames],
[(1, 0x342)],
[(0, 0x302), (1, 0x342)],
)
def test_rejects_decoded_state_invalid_channels_and_channel_conflicts(self) -> None:
@@ -435,6 +437,76 @@ class MqttCanTransportTests(unittest.TestCase):
self.assertLess(time.perf_counter() - started, 0.05)
self.assertEqual(snapshot.observed_ids, frozenset({0x388}))
def test_native_can0_status_maps_to_passenger_frontend_state(self) -> None:
self.client.emit(
"n7/n7-headunit/can/state",
{
"schema": "n7-can-signals/v1",
"signals": [
{
"channel": "can0",
"canId": "0x302",
"frameType": "classic",
"bytes": [axis, 0x26, 0x08, 0, 0, value & 0xFF,
(value >> 8) & 0xFF, 0],
}
for axis, value in enumerate((1000, 2500, 5000, 9000))
] + [
{
"channel": "can0",
"canId": "0x4FA",
"frameType": "classic",
"bytes": [0x22, 0, 0, 0, 0, 0, 0, 0],
},
{
"channel": "can0",
"canId": "0x306",
"frameType": "classic",
"bytes": [1, 2, 3, 0x0F, 2, 0, 100, 0],
},
],
},
)
service = SeatDemoService(REPOSITORY_ROOT)
service._vehicle_mqtt_transport = self.transport
service.vehicle_mqtt_settings = SimpleNamespace(state_configured=True)
payload = service._synchronize_hardware_state_once(
"conv-native-can0-state",
control_transport="wireless_mqtt",
)
passenger = payload["actuator_state"]["passenger"]
self.assertEqual(
{
axis: value["position_pct"]
for axis, value in passenger["position"].items()
if axis in {
"longitudinal",
"cushion_tilt",
"backrest_recline",
"legrest_height",
}
},
{
"longitudinal": 10,
"cushion_tilt": 25,
"backrest_recline": 50,
"legrest_height": 90,
},
)
self.assertEqual(passenger["ventilation"]["level"], 1)
self.assertEqual(passenger["heating"]["level"], 3)
self.assertEqual(passenger["dynamic_mode"]["mode_id"], 1)
self.assertEqual(passenger["dynamic_mode"]["state"], "running")
self.assertTrue(
all(
item["feedback_source"] == "wireless_mqtt_ch0"
for item in passenger["position"].values()
if item.get("position_valid") is not None
)
)
def test_vehicle_envelope_four_frames_decode_from_raw_can1_bytes(self) -> None:
self.client.emit(
"n7/n7-headunit/can/state",
@@ -758,6 +830,158 @@ class MqttCanTransportTests(unittest.TestCase):
)
)
def test_wireless_hardware_can1_service_skips_local_hardware_connection(self) -> None:
service = SeatDemoService(REPOSITORY_ROOT)
service._vehicle_mqtt_transport = self.transport
service.vehicle_mqtt_settings = SimpleNamespace(
enabled=True,
configured=True,
feedback_timeout_ms=1,
authorize=lambda _token: None,
)
validated_events: list[dict[str, object]] = []
with tempfile.TemporaryDirectory() as temporary_directory:
service.artifact_root = Path(temporary_directory)
with (
patch.object(
service.hardware_execution,
"reserve",
side_effect=AssertionError(
"wireless CAN1 must not reserve the local physical bench"
),
),
patch("cognitive_agent.application.native_can_runtime.time.sleep"),
):
result = service.process(
"空调温度设置为24度",
provider="local",
conversation_id="conv-wireless-can1-no-local-hardware",
execution_mode="hardware",
control_transport="wireless_mqtt",
_on_commands_validated=validated_events.append,
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(list(result["command_plans"]), ["native_climate"])
self.assertEqual(len(validated_events), 1)
self.assertEqual(
list(validated_events[0]["command_plans"]),
["native_climate"],
)
payloads = [json.loads(item[1]) for item in self.client.published]
self.assertEqual(len(payloads), 3)
self.assertTrue(all(item["channel"] == "can1" for item in payloads))
self.assertTrue(all(item["command"] == "ac temp driver 24" for item in payloads))
def test_wireless_hardware_combines_can0_and_can1_without_local_profile(self) -> None:
service = SeatDemoService(REPOSITORY_ROOT)
service._vehicle_mqtt_transport = self.transport
service.vehicle_mqtt_settings = SimpleNamespace(
enabled=True,
configured=True,
feedback_timeout_ms=1,
authorize=lambda _token: None,
)
validated_events: list[dict[str, object]] = []
seat_execution: dict[str, object] = {}
def execute_seat(
plan: dict[str, object],
config: dict[str, object],
_state: dict[str, object],
) -> dict[str, object]:
seat_execution["plan"] = plan
seat_execution["config"] = config
operation = config["spec"]["operations"][0] # type: ignore[index]
request_id = config["metadata"]["request_id"] # type: ignore[index]
return {
"apiVersion": "cognitive-agent/v1alpha1",
"kind": "ExecutionReport",
"metadata": {
"id": f"report-{request_id}",
"name": "wireless CAN0 test report",
"version": "1.0.0",
"request_id": request_id,
"labels": {
"simulation": "false",
"seat_feedback": "wireless_mqtt_ch0",
},
},
"spec": {
"mode": "hil",
"status": "succeeded",
"started_at": "2026-08-25T00:00:00Z",
"finished_at": "2026-08-25T00:00:00Z",
"results": [
{
"ref": operation["id"],
"domain": "seat",
"status": "succeeded",
"message": "wireless CAN0 command published",
"telemetry": {
"effective_position_pct": 35,
"position_valid": True,
"simulated": False,
},
}
],
},
}
with tempfile.TemporaryDirectory() as temporary_directory:
service.artifact_root = Path(temporary_directory)
with (
patch.object(
type(service.hardware_settings),
"validate_function_config",
side_effect=AssertionError(
"wireless mixed CAN0/CAN1 must not check combined profile"
),
),
patch.object(
service.hardware_execution,
"reserve",
side_effect=AssertionError(
"wireless mixed CAN0/CAN1 must not reserve local hardware"
),
),
patch.object(
service,
"_new_hardware_executor",
return_value=SimpleNamespace(execute=execute_seat),
) as executor_factory,
patch("cognitive_agent.application.native_can_runtime.time.sleep"),
):
result = service.process(
"把副驾座椅水平位置调到35%,空调温度设置为24度",
provider="local",
conversation_id="conv-wireless-can0-can1-no-local-hardware",
execution_mode="hardware",
control_transport="wireless_mqtt",
_on_commands_validated=validated_events.append,
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(
set(result["command_plans"]),
{"seat", "native_climate"},
)
self.assertEqual(len(validated_events), 1)
self.assertEqual(
set(validated_events[0]["command_plans"]),
{"seat", "native_climate"},
)
self.assertEqual(
seat_execution["plan"]["spec"]["commands"][0]["message"], # type: ignore[index]
"T2G_Seat_Move_Command_300",
)
self.assertTrue(executor_factory.call_args.kwargs["remote_transport"])
payloads = [json.loads(item[1]) for item in self.client.published]
self.assertEqual(len(payloads), 3)
self.assertTrue(all(item["channel"] == "can1" for item in payloads))
self.assertTrue(all(item["command"] == "ac temp driver 24" for item in payloads))
def test_wireless_fixed_controls_and_shade_preset_never_wait_for_state(
self,
) -> None:
@@ -935,6 +1159,21 @@ class MqttCanTransportTests(unittest.TestCase):
self.assertEqual(payload["command"], "")
self.assertEqual(payload["frameType"], "classic")
self.assertFalse(payload["brs"])
bus.set_debug_command("seat passenger heat low")
bus.send(0x3CE, bytes.fromhex("10 00 00 00 00 00 00 00"))
bus.send(0x29E, bytes.fromhex("16 00 10 00 00 00 00 00"))
thermal_payloads = [
json.loads(item[1]) for item in self.client.published[-2:]
]
self.assertEqual(
[item["canId"] for item in thermal_payloads],
["0x3CE", "0x29E"],
)
self.assertTrue(
all(item["command"] == "seat passenger heat low" for item in thermal_payloads)
)
with self.assertRaisesRegex(Exception, "0x29E thermal control payload"):
bus.send(0x29E, bytes(8))
with self.assertRaisesRegex(Exception, "approved CAN0 control set"):
bus.send(0x431, bytes(8))
with self.assertRaisesRegex(Exception, "requires classic CAN"):