merge: integrate wired and wireless CAN into N7Web

This commit is contained in:
minghanchen
2026-08-19 10:21:27 +08:00
34 changed files with 4740 additions and 246 deletions
+116 -10
View File
@@ -10,6 +10,7 @@ import urllib.request
from http.cookiejar import CookieJar
from http.server import ThreadingHTTPServer
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
from cognitive_agent.auth import AuthConfig, AuthRuntime, hash_password
@@ -229,18 +230,18 @@ operations:
def test_static_console_and_runtime_endpoint(self) -> None:
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
html = response.read().decode("utf-8")
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-85", timeout=3) as response:
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-90", 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-8", timeout=3
) as response:
gesture_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/console-v2.js?v=sync-layout-26", timeout=3
f"{self.base_url}/console-v2.js?v=sync-layout-27", timeout=3
) as response:
console_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/console-v2.css?v=sync-layout-26", timeout=3
f"{self.base_url}/console-v2.css?v=sync-layout-27", timeout=3
) as response:
console_styles = response.read().decode("utf-8")
with self.opener.open(
@@ -275,12 +276,12 @@ operations:
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
self.assertNotIn("透明车体 · 实体设备", html)
self.assertIn('src="/app.js?v=dual-seat-85"', html)
self.assertIn('href="/styles.css?v=dual-seat-9"', html)
self.assertIn('src="/app.js?v=dual-seat-90"', html)
self.assertIn('href="/styles.css?v=dual-seat-10"', html)
self.assertIn('src="/gesture-control.js?v=android-pad-8"', html)
self.assertNotIn('class="vehicle-system-hud"', html)
self.assertIn('src="/console-v2.js?v=sync-layout-26"', html)
self.assertIn('href="/console-v2.css?v=sync-layout-26"', html)
self.assertIn('src="/console-v2.js?v=sync-layout-27"', html)
self.assertIn('href="/console-v2.css?v=sync-layout-27"', html)
self.assertIn('src="/self-extension.js?v=sync-layout-26"', html)
self.assertIn('href="/self-extension.css?v=sync-layout-26"', html)
self.assertNotIn("self-calibration", html)
@@ -398,7 +399,19 @@ operations:
self.assertIn('const dynamicModeOrder = ["rocking_horse", "dynamic_cradle"]', app_script)
self.assertIn('divider.className = "mode-group-divider"', app_script)
self.assertIn(
'mode === "hardware" && !state.runtime?.hardware_execution_allowed',
'mode === "hardware" && !hardwareExecutionReady',
app_script,
)
self.assertIn('data-control-transport="wired_can"', html)
self.assertIn('data-control-transport="wireless_mqtt"', html)
self.assertIn('control_transport: state.controlTransport', app_script)
self.assertIn('const transportField = $("#vehicleTransportField", originalForm)', console_script)
self.assertIn('configStack.append(transportField)', console_script)
self.assertIn('.console-v2 .v2-transport-field .vehicle-transport-options', console_styles)
self.assertIn('button.disabled = false', app_script)
self.assertIn('button.setAttribute("aria-pressed", String(selected))', app_script)
self.assertNotIn(
'availableTransports.find((item) => item.state_configured)?.id',
app_script,
)
self.assertIn("hardwareButton.disabled = !hardwareExecutionReady", app_script)
@@ -484,7 +497,7 @@ operations:
self.assertIn('client_type: "web_console"', app_script)
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
self.assertIn('"cognitive-agent:module-change"', app_script)
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v178"', service_worker)
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v179"', service_worker)
self.assertIn('"/assets/pets/self-extension-cat.png?v=3"', service_worker)
self.assertIn(".then(() => self.skipWaiting())", service_worker)
self.assertNotIn("const cached = await caches.match(request)", service_worker)
@@ -700,7 +713,7 @@ operations:
) as response:
extension_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/app.js?v=dual-seat-83", timeout=3
f"{self.base_url}/app.js?v=dual-seat-90", timeout=3
) as response:
app_script = response.read().decode("utf-8")
@@ -1033,10 +1046,103 @@ operations:
synchronize.assert_called_once_with(
"conv-e2e-physical-state",
"denza_n7",
control_transport="wired_can",
hardware_client_id=None,
hardware_access_bypass=True,
)
def test_wireless_passenger_snapshot_uses_mqtt_can0(self) -> None:
physical_state = {
"position": {
"longitudinal": {
"position_pct": 42,
"position_valid": True,
"motion_state": "stopped",
"fault": False,
}
},
"captured_at": "2026-08-18T00:00:00Z",
"complete": True,
"missing": [],
}
executor = SimpleNamespace(read_physical_state=lambda: dict(physical_state))
with (
patch.object(
self.service,
"vehicle_mqtt_settings",
SimpleNamespace(state_configured=True),
),
patch.object(
self.service._vehicle_mqtt_transport,
"bus",
return_value=object(),
) as mqtt_bus,
patch.object(
self.service,
"_new_hardware_executor",
return_value=executor,
) as new_executor,
patch.object(
self.service.hardware_execution,
"reserve",
side_effect=AssertionError(
"wireless cached state must not reserve the physical bench"
),
),
):
payload = self.service.synchronize_hardware_state(
"conv-e2e-wireless-seat-state",
"denza_n7",
control_transport="wireless_mqtt",
hardware_access_bypass=True,
)
bus_factory = new_executor.call_args.args[0]
bus_factory()
mqtt_bus.assert_called_once_with(channel=0, replay_latest=True)
self.assertTrue(new_executor.call_args.kwargs["remote_transport"])
self.assertEqual(payload["source"], "wireless_mqtt_ch0")
self.assertEqual(payload["control_transport"], "wireless_mqtt")
self.assertEqual(
payload["actuator_state"]["passenger"]["position"]["longitudinal"]["position_pct"],
42,
)
def test_wireless_vehicle_snapshot_does_not_reserve_physical_bench(self) -> None:
snapshot = SimpleNamespace(
feedback_states=(),
observed_ids=frozenset(),
captured_at="2026-08-18T00:00:00Z",
capture_sequence=17,
)
with (
patch.object(
self.service,
"vehicle_mqtt_settings",
SimpleNamespace(state_configured=True),
),
patch.object(
self.service._vehicle_mqtt_transport,
"snapshot",
return_value=snapshot,
),
patch.object(
self.service.hardware_execution,
"reserve",
side_effect=AssertionError(
"wireless cached state must not reserve the physical bench"
),
),
):
payload = self.service.synchronize_vehicle_state(
"conv-e2e-wireless-vehicle-state",
"denza_n7",
control_transport="wireless_mqtt",
hardware_access_bypass=True,
)
self.assertEqual(payload["source"], "wireless_mqtt_ch1")
self.assertEqual(payload["capture_sequence"], 17)
def test_gpt_http_failure_falls_back_without_returning_502(self) -> None:
original_api_key = self.service.openai.api_key
self.service.openai.api_key = "test-key"