优化了副驾座椅控制
This commit is contained in:
@@ -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"):
|
||||
|
||||
Reference in New Issue
Block a user