feat: improve mode and peripheral orchestration
This commit is contained in:
+145
-54
@@ -997,14 +997,14 @@ operations:
|
||||
def runtime_url(file_name: str, source_url: str) -> str:
|
||||
hashed = runtime_assets.get(file_name)
|
||||
return f"/{hashed}" if hashed else source_url
|
||||
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-145-n7-dispatch", timeout=3) as response:
|
||||
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-147-video-media", timeout=3) as response:
|
||||
app_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/gesture-control.js?v=android-pad-9", timeout=3
|
||||
) as response:
|
||||
gesture_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/console-v2.js?v=local-first-77-performance", timeout=3
|
||||
f"{self.base_url}/console-v2.js?v=local-first-79-cinema-status", timeout=3
|
||||
) as response:
|
||||
console_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
@@ -1024,7 +1024,7 @@ operations:
|
||||
) as response:
|
||||
speech_worklet = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/self-extension.js?v=nfc-mqtt-56", timeout=3
|
||||
f"{self.base_url}/self-extension.js?v=nfc-persistent-57", timeout=3
|
||||
) as response:
|
||||
extension_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
@@ -1032,7 +1032,7 @@ operations:
|
||||
) as response:
|
||||
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/assistant-chat.js?v=codex-assistant-35", timeout=3
|
||||
f"{self.base_url}/assistant-chat.js?v=codex-assistant-39-video-media", timeout=3
|
||||
) as response:
|
||||
assistant_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
@@ -1084,13 +1084,13 @@ operations:
|
||||
self.assertIn("CMD 0x431 · FB 0x388 · CH1", template)
|
||||
self.assertNotIn("透明车体 · 实体设备", template)
|
||||
self.assertIn(
|
||||
f'src="{runtime_url("app.js", "/app.js?v=dual-seat-145-n7-dispatch")}"',
|
||||
f'src="{runtime_url("app.js", "/app.js?v=dual-seat-147-video-media")}"',
|
||||
html,
|
||||
)
|
||||
self.assertIn('import "../styles.css";', vite_entry)
|
||||
self.assertNotIn('class="vehicle-system-hud"', template)
|
||||
self.assertIn(
|
||||
f'src="{runtime_url("console-v2.js", "/console-v2.js?v=local-first-77-performance")}"',
|
||||
f'src="{runtime_url("console-v2.js", "/console-v2.js?v=local-first-79-cinema-status")}"',
|
||||
html,
|
||||
)
|
||||
self.assertIn('import "../console-v2.css";', vite_entry)
|
||||
@@ -1105,7 +1105,7 @@ operations:
|
||||
self.assertIn('id="v2BootScreen"', template)
|
||||
self.assertIn('正在加载车机控制与实时状态', template)
|
||||
self.assertIn(
|
||||
f'src="{runtime_url("self-extension.js", "/self-extension.js?v=nfc-mqtt-56")}"',
|
||||
f'src="{runtime_url("self-extension.js", "/self-extension.js?v=nfc-persistent-57")}"',
|
||||
html,
|
||||
)
|
||||
self.assertIn('import "../self-extension.css";', vite_entry)
|
||||
@@ -1117,7 +1117,7 @@ operations:
|
||||
self.assertIn('id="assistantTranscript"', template)
|
||||
self.assertIn('import "../assistant-chat.css";', vite_entry)
|
||||
self.assertIn(
|
||||
f'src="{runtime_url("assistant-chat.js", "/assistant-chat.js?v=codex-assistant-35")}"',
|
||||
f'src="{runtime_url("assistant-chat.js", "/assistant-chat.js?v=codex-assistant-39-video-media")}"',
|
||||
html,
|
||||
)
|
||||
self.assertIn('import "../energy-monitor.css";', vite_entry)
|
||||
@@ -1433,16 +1433,16 @@ operations:
|
||||
self.assertIn(f'/{runtime_assets[asset_name]}', service_worker)
|
||||
else:
|
||||
self.assertIn(
|
||||
'const CACHE_VERSION = "cognitive-agent-pad-v329-mode-preview"',
|
||||
'const CACHE_VERSION = "cognitive-agent-pad-v334-video-media"',
|
||||
service_worker,
|
||||
)
|
||||
self.assertIn('/console-v2.css?v=delivery-state-69', service_worker)
|
||||
self.assertIn('/console-v2.js?v=local-first-77-performance', service_worker)
|
||||
self.assertIn('/console-v2.js?v=local-first-79-cinema-status', service_worker)
|
||||
self.assertIn('/assistant-chat.css?v=codex-assistant-30', service_worker)
|
||||
self.assertIn('/assistant-chat.js?v=codex-assistant-35', service_worker)
|
||||
self.assertIn('/assistant-chat.js?v=codex-assistant-39-video-media', service_worker)
|
||||
self.assertIn('/energy-monitor.css?v=energy-monitor-5-dialog-size', service_worker)
|
||||
self.assertIn('/energy-monitor.js?v=energy-monitor-5-performance', service_worker)
|
||||
self.assertIn('"/app.js?v=dual-seat-145-n7-dispatch"', service_worker)
|
||||
self.assertIn('"/app.js?v=dual-seat-147-video-media"', service_worker)
|
||||
self.assertIn('/firmware-workbench-entry.css?v=2', service_worker)
|
||||
self.assertIn('/firmware-workbench-entry.js?v=3', service_worker)
|
||||
self.assertIn('/firmware-workbench-theme.js?v=1', service_worker)
|
||||
@@ -1910,7 +1910,7 @@ operations:
|
||||
) as response:
|
||||
extension_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/app.js?v=dual-seat-145-n7-dispatch", timeout=3
|
||||
f"{self.base_url}/app.js?v=dual-seat-147-video-media", timeout=3
|
||||
) as response:
|
||||
app_script = response.read().decode("utf-8")
|
||||
|
||||
@@ -2904,47 +2904,51 @@ operations:
|
||||
).encode("utf-8"),
|
||||
)
|
||||
|
||||
def fail_can_after_validation(*_args, **kwargs):
|
||||
callback = kwargs.get("_on_commands_validated")
|
||||
self.assertTrue(callable(callback))
|
||||
callback(
|
||||
{
|
||||
"request_id": "req-can-validation-only",
|
||||
"utterance": "打开空调,温度设置为24度,风速调整为3档,打开遮阳帘",
|
||||
"status": "processing",
|
||||
"execution_mode": "hardware",
|
||||
"control_transport": "wired_can",
|
||||
"provider_used": "local_rules",
|
||||
"decision_summary": [],
|
||||
"function_configs": {
|
||||
"native_climate": {
|
||||
"spec": {
|
||||
"operations": [
|
||||
{
|
||||
"id": "vehicle_set_power_1",
|
||||
"capability": "climate.set_power",
|
||||
"target": {"zone": "vehicle"},
|
||||
"parameters": {"state": "on"},
|
||||
"parse_source": "local_rule",
|
||||
}
|
||||
]
|
||||
can_preview = {
|
||||
"request_id": "req-can-validation-only",
|
||||
"utterance": "打开空调,温度设置为24度,风速调整为3档,打开遮阳帘",
|
||||
"status": "succeeded",
|
||||
"execution_mode": "dry_run",
|
||||
"control_transport": "wired_can",
|
||||
"provider_used": "local_rules",
|
||||
"decision_summary": [],
|
||||
"function_configs": {
|
||||
"native_climate": {
|
||||
"spec": {
|
||||
"operations": [
|
||||
{
|
||||
"id": "vehicle_set_power_1",
|
||||
"capability": "climate.set_power",
|
||||
"target": {"zone": "vehicle"},
|
||||
"parameters": {"state": "on"},
|
||||
"parse_source": "local_rule",
|
||||
}
|
||||
}
|
||||
},
|
||||
"command_plans": {
|
||||
"native_climate": {"spec": {"commands": []}}
|
||||
},
|
||||
"operation_results": [],
|
||||
"execution_report": None,
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
"command_plans": {
|
||||
"native_climate": {"spec": {"commands": []}}
|
||||
},
|
||||
"operation_results": [],
|
||||
"execution_report": None,
|
||||
}
|
||||
|
||||
def fail_can_after_validation(*_args, **_kwargs):
|
||||
raise CanGatewayError("simulated CAN transport failure")
|
||||
|
||||
try:
|
||||
with patch.object(
|
||||
self.service,
|
||||
"process",
|
||||
side_effect=fail_can_after_validation,
|
||||
with (
|
||||
patch.object(
|
||||
self.service,
|
||||
"preview_native_can",
|
||||
return_value=can_preview,
|
||||
),
|
||||
patch.object(
|
||||
self.service,
|
||||
"process",
|
||||
side_effect=fail_can_after_validation,
|
||||
),
|
||||
):
|
||||
with self.opener.open(request, timeout=3) as response:
|
||||
events = [
|
||||
@@ -2963,11 +2967,8 @@ operations:
|
||||
for item in events
|
||||
if item["type"] == "validated_commands"
|
||||
]
|
||||
self.assertEqual(set(validated_payloads[0]["function_configs"]), {"self_extension"})
|
||||
validated = next(
|
||||
payload for payload in validated_payloads
|
||||
if {"native_climate", "self_extension"}.issubset(payload["function_configs"])
|
||||
)
|
||||
self.assertEqual(len(validated_payloads), 1)
|
||||
validated = validated_payloads[0]
|
||||
self.assertEqual(validated["status"], "processing")
|
||||
self.assertEqual(
|
||||
set(validated["function_configs"]),
|
||||
@@ -2977,6 +2978,10 @@ operations:
|
||||
[item["slot"] for item in validated["infrared_actions"]],
|
||||
[1, 4],
|
||||
)
|
||||
self.assertEqual(
|
||||
validated["parallel_dispatch"]["preflight_status"],
|
||||
"succeeded",
|
||||
)
|
||||
|
||||
def test_disabled_nfc_ir_command_does_not_discard_allowed_can_and_ir(
|
||||
self,
|
||||
@@ -3173,6 +3178,18 @@ operations:
|
||||
)
|
||||
extension.sync_peripheral_catalog(
|
||||
[
|
||||
{
|
||||
"id": "toy-001-light-on",
|
||||
"status": "learned",
|
||||
"enabled": True,
|
||||
"device_id": "toy-001",
|
||||
"device_name": "闪烁灯玩具",
|
||||
"object_names": ["闪烁灯玩具", "闪烁灯"],
|
||||
"card_uid": "2CC8B103",
|
||||
"action": "打开",
|
||||
"action_names": ["打开", "开启", "启动"],
|
||||
"slot": 1,
|
||||
},
|
||||
{
|
||||
"id": "toy-001-light-red",
|
||||
"status": "learned",
|
||||
@@ -3239,6 +3256,11 @@ operations:
|
||||
self.assertEqual(
|
||||
result["infrared_actions"][0]["parse_source"], "cloud_model"
|
||||
)
|
||||
self.assertEqual(
|
||||
[action["slot"] for action in result["infrared_actions"]],
|
||||
[1, 3],
|
||||
)
|
||||
self.assertTrue(result["infrared_actions"][0]["auto_inserted"])
|
||||
self.assertFalse(result["supplement_required"])
|
||||
|
||||
def test_main_request_executes_gpt_nfc_sequence_without_vehicle_can(self) -> None:
|
||||
@@ -4113,6 +4135,75 @@ operations:
|
||||
self.assertEqual(released["state"], "none")
|
||||
self.assertIsNone(self.service.hardware_access.admin_snapshot()["active"])
|
||||
|
||||
def test_legacy_hardware_client_id_can_request_access_and_execute_mode_card(
|
||||
self,
|
||||
) -> None:
|
||||
legacy_client_id = "legacy_mode_client_with_underscores"
|
||||
|
||||
def post(path: str, payload: dict, *, public: bool = False):
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if public:
|
||||
headers["X-Forwarded-For"] = "203.0.113.31"
|
||||
request = urllib.request.Request(
|
||||
f"{self.base_url}{path}",
|
||||
method="POST",
|
||||
headers=headers,
|
||||
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
|
||||
)
|
||||
return self.opener.open(request, timeout=3)
|
||||
|
||||
with post(
|
||||
"/api/hardware/access/request",
|
||||
{"client_id": legacy_client_id, "label": "旧版模式终端"},
|
||||
public=True,
|
||||
) as response:
|
||||
requested = json.loads(response.read().decode("utf-8"))
|
||||
canonical_client_id = requested["client_id"]
|
||||
self.assertRegex(canonical_client_id, r"^hwc-legacy-[0-9a-f]{32}$")
|
||||
|
||||
with post(
|
||||
"/api/local/hardware-access/approve",
|
||||
{"client_id": canonical_client_id},
|
||||
):
|
||||
pass
|
||||
|
||||
fake_result = {
|
||||
"status": "succeeded",
|
||||
"request_id": "req-legacy-mode-client",
|
||||
"execution_mode": "hardware",
|
||||
}
|
||||
with patch.object(
|
||||
self.service,
|
||||
"execute_mode_profile_card",
|
||||
return_value=fake_result,
|
||||
) as execute:
|
||||
with post(
|
||||
"/api/mode-profiles/office/execute",
|
||||
{
|
||||
"conversation_id": "conv-legacy-mode-client",
|
||||
"vehicle_id": "denza_n7",
|
||||
"execution_mode": "hardware",
|
||||
"hardware_client_id": legacy_client_id,
|
||||
"client_type": "web_console_mode_card",
|
||||
},
|
||||
public=True,
|
||||
) as response:
|
||||
result = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertEqual(
|
||||
execute.call_args.kwargs["hardware_client_id"],
|
||||
canonical_client_id,
|
||||
)
|
||||
|
||||
with post(
|
||||
"/api/hardware/access/release",
|
||||
{"client_id": legacy_client_id},
|
||||
public=True,
|
||||
) as response:
|
||||
released = json.loads(response.read().decode("utf-8"))
|
||||
self.assertEqual(released["state"], "none")
|
||||
|
||||
def test_local_hardware_decision_page_is_not_exposed_through_proxy(self) -> None:
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/hardware-control", timeout=3
|
||||
|
||||
Reference in New Issue
Block a user