添加无线模块
This commit is contained in:
+194
-42
@@ -39,10 +39,16 @@ function loadHardwareClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
function loadControlTransport() {
|
||||
const stored = localStorage.getItem("cognitive-agent.control-transport");
|
||||
return ["wired_can", "wireless_mqtt"].includes(stored) ? stored : "wired_can";
|
||||
}
|
||||
|
||||
const state = {
|
||||
provider: "openai",
|
||||
reasoningEffort: localStorage.getItem("cognitive-agent.reasoning-effort") || "medium",
|
||||
executionMode: "dry_run",
|
||||
controlTransport: loadControlTransport(),
|
||||
conversationId: loadConversationId(),
|
||||
vehicleId: null,
|
||||
runtime: null,
|
||||
@@ -61,6 +67,7 @@ const state = {
|
||||
hardwareAccessPoller: null,
|
||||
physicalStatePoller: null,
|
||||
physicalStateSyncPromise: null,
|
||||
passengerStateSyncPromise: null,
|
||||
pendingModeProfileId: null,
|
||||
modeRunTimer: null,
|
||||
modeDraftCommand: "",
|
||||
@@ -76,11 +83,18 @@ const apiRequest = (input, options) => window.CognitiveAgentNetwork.request(inpu
|
||||
// workflow so the UI does not report failure while the seat is still moving.
|
||||
const HARDWARE_REQUEST_TIMEOUT_MS = 15 * 60 * 1000;
|
||||
const REQUEST_TIMEOUT_MS = 45 * 1000;
|
||||
const DEFAULT_PHYSICAL_STATE_POLL_INTERVAL_MS = 5000;
|
||||
|
||||
function requestTimeoutForExecutionMode(mode) {
|
||||
return mode === "hardware" ? HARDWARE_REQUEST_TIMEOUT_MS : REQUEST_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
function physicalStatePollIntervalMs() {
|
||||
const configured = Number(selectedVehicleTransport()?.state_sync?.poll_interval_ms);
|
||||
if (!Number.isFinite(configured)) return DEFAULT_PHYSICAL_STATE_POLL_INTERVAL_MS;
|
||||
return Math.max(250, Math.min(DEFAULT_PHYSICAL_STATE_POLL_INTERVAL_MS, configured));
|
||||
}
|
||||
|
||||
function refreshIcons() {
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
@@ -4265,12 +4279,80 @@ function isLocalDecisionHost() {
|
||||
return ["127.0.0.1", "localhost", "::1"].includes(window.location.hostname);
|
||||
}
|
||||
|
||||
function vehicleTransportOptions() {
|
||||
return Array.isArray(state.runtime?.vehicle_transports?.options)
|
||||
? state.runtime.vehicle_transports.options
|
||||
: [];
|
||||
}
|
||||
|
||||
function selectedVehicleTransport() {
|
||||
return vehicleTransportOptions().find((item) => item.id === state.controlTransport)
|
||||
|| (state.controlTransport === "wireless_mqtt"
|
||||
? state.runtime?.vehicle_mqtt
|
||||
: state.runtime?.vehicle_can)
|
||||
|| null;
|
||||
}
|
||||
|
||||
function isDriverOnlyCh1Runtime() {
|
||||
return state.runtime?.vehicle_can?.configured === true
|
||||
&& state.runtime?.vehicle_can?.target_policy === "ch1_body_can_driver_only"
|
||||
const transport = selectedVehicleTransport();
|
||||
return transport?.configured === true
|
||||
&& transport?.target_policy === "ch1_body_can_driver_only"
|
||||
&& state.runtime?.hardware?.configured !== true;
|
||||
}
|
||||
|
||||
function renderControlTransport() {
|
||||
const options = vehicleTransportOptions();
|
||||
$$('[data-control-transport]').forEach((button) => {
|
||||
const id = button.dataset.controlTransport;
|
||||
const runtime = options.find((item) => item.id === id)
|
||||
|| (id === "wireless_mqtt" ? state.runtime?.vehicle_mqtt : state.runtime?.vehicle_can);
|
||||
const selected = id === state.controlTransport;
|
||||
button.classList.toggle("active", selected);
|
||||
button.setAttribute("aria-pressed", String(selected));
|
||||
// Transport choice is intentionally manual. An unavailable connection is
|
||||
// shown as offline, but it must never disable or silently change the
|
||||
// operator's wired/wireless selection.
|
||||
button.disabled = false;
|
||||
button.dataset.connectionState = runtime?.state_sync?.state === "online" ? "online" : "offline";
|
||||
button.title = runtime?.state_configured === true
|
||||
? runtime?.state_sync?.last_error || ""
|
||||
: `${id === "wireless_mqtt" ? "无线 MQTT" : "有线 CAN"} 当前未配置(仍可手动选择)`;
|
||||
});
|
||||
const selected = selectedVehicleTransport();
|
||||
const note = $("#vehicleTransportNote");
|
||||
if (!note) return;
|
||||
if (selected?.state_configured !== true) {
|
||||
note.textContent = state.controlTransport === "wireless_mqtt"
|
||||
? "已手动选择无线控制 · MQTT 当前未配置或不可用"
|
||||
: "已手动选择有线控制 · ZLG CAN 当前未配置或不可用";
|
||||
return;
|
||||
}
|
||||
if (state.controlTransport === "wireless_mqtt") {
|
||||
const connection = selected?.state_sync?.state === "online" ? "已连接" : "未连接";
|
||||
note.textContent = `MQTT ${connection} · 状态 ${selected?.condition_topic || "n7/n7-headunit/can/state"} · 控制 ${selected?.control_topic || "n7/can_control"}`;
|
||||
} else {
|
||||
const connection = selected?.state_sync?.state === "online" ? "已连接" : "未连接";
|
||||
note.textContent = `ZLG 有线 ${connection} · CH${selected?.channel ?? 1} · CAN FD 500k/2M`;
|
||||
}
|
||||
}
|
||||
|
||||
function selectControlTransport(transport) {
|
||||
if (!["wired_can", "wireless_mqtt"].includes(transport)) return;
|
||||
const changed = state.controlTransport !== transport;
|
||||
state.controlTransport = transport;
|
||||
localStorage.setItem("cognitive-agent.control-transport", transport);
|
||||
if (changed) {
|
||||
state.conversationId = createConversationId();
|
||||
sessionStorage.setItem("cognitive-agent.conversation-id", state.conversationId);
|
||||
state.seatFeedbackSources.passenger = "unavailable";
|
||||
state.seatFeedbackSources.driver = "unavailable";
|
||||
}
|
||||
state.vehicleStateSnapshot = null;
|
||||
state.vehicleStateTurn = -1;
|
||||
renderControlTransport();
|
||||
applyRuntimeTargetPolicy(state.runtime);
|
||||
}
|
||||
|
||||
function renderHardwareAccess(status) {
|
||||
state.hardwareAccess = status;
|
||||
}
|
||||
@@ -4301,6 +4383,8 @@ function updateSeatFeedbackModeLabel() {
|
||||
physical_can: "实车已同步",
|
||||
physical_partial: "物理反馈不完整",
|
||||
physical_can_ch1: "实车已同步",
|
||||
wireless_mqtt_ch0: "MQTT 已同步",
|
||||
wireless_mqtt_ch1: "MQTT 已同步",
|
||||
simulated_hardware: "模拟 HIL",
|
||||
simulated: "虚拟反馈",
|
||||
unavailable: "不可用",
|
||||
@@ -4309,6 +4393,8 @@ function updateSeatFeedbackModeLabel() {
|
||||
physical_can: "实车",
|
||||
physical_partial: "部分",
|
||||
physical_can_ch1: "实车",
|
||||
wireless_mqtt_ch0: "MQTT",
|
||||
wireless_mqtt_ch1: "MQTT",
|
||||
simulated_hardware: "模拟",
|
||||
simulated: "虚拟",
|
||||
unavailable: "未连接",
|
||||
@@ -4326,7 +4412,11 @@ function updateSeatFeedbackModeLabel() {
|
||||
}
|
||||
|
||||
function selectExecutionMode(mode) {
|
||||
if (mode === "hardware" && !state.runtime?.hardware_execution_allowed) return;
|
||||
const selectedTransport = selectedVehicleTransport();
|
||||
const hardwareExecutionReady = Boolean(
|
||||
state.runtime?.hardware?.configured || selectedTransport?.configured,
|
||||
);
|
||||
if (mode === "hardware" && !hardwareExecutionReady) return;
|
||||
if (mode === "simulated_hardware" && !state.runtime?.simulated_hardware?.enabled) return;
|
||||
state.executionMode = mode;
|
||||
$$('[data-execution-mode]').forEach((button) => {
|
||||
@@ -4334,7 +4424,8 @@ function selectExecutionMode(mode) {
|
||||
});
|
||||
const hardware = mode === "hardware";
|
||||
const simulatedHardware = mode === "simulated_hardware";
|
||||
const vehicleCanReady = Boolean(state.runtime?.vehicle_can?.configured);
|
||||
const vehicleCanReady = Boolean(selectedTransport?.configured);
|
||||
const mqttSelected = state.controlTransport === "wireless_mqtt";
|
||||
const ch1DriverOnly = isDriverOnlyCh1Runtime();
|
||||
$("#simulationFaultField").hidden = !simulatedHardware;
|
||||
$("#executionBadge").className = `status-badge ${hardware ? "" : simulatedHardware ? "warning" : "neutral"}`.trim();
|
||||
@@ -4348,7 +4439,7 @@ function selectExecutionMode(mode) {
|
||||
: simulatedHardware ? "模拟台架隔离边界" : "虚拟执行边界";
|
||||
$("#safetyText").textContent = hardware
|
||||
? ch1DriverOnly && vehicleCanReady
|
||||
? "仅使用 ZLG CH1 控制主驾座椅与已验证车身设备;每次动作前必须确认 P 挡和 0 km/h,并以 CH1 状态帧回读为准。CH0、副驾 T2G 与 IAR 路径均不参与。"
|
||||
? `仅使用${mqttSelected ? "本地 MQTT 无线链路" : "ZLG 有线 CH1"}控制主驾座椅与已验证车身设备;每次动作前必须确认 P 挡和 0 km/h,并以 CH1 状态帧回读为准。CH0、副驾 T2G 与 IAR 路径均不参与。`
|
||||
: vehicleCanReady
|
||||
? "同一 CAN 盒由单进程持有:CH0 控制副驾四电机并校验板端反馈;CH1 控制主驾四电机与车身设备,每次动作前校验 P 挡、零车速和对应状态帧。"
|
||||
: "当前仅通道 0 连接副驾座椅台架并校验板端反馈;车端 CAN 未启用,模式中的其他车舱领域保持虚拟执行。"
|
||||
@@ -4358,7 +4449,9 @@ function selectExecutionMode(mode) {
|
||||
$("#executeStageLabel").textContent = hardware ? "台架执行" : simulatedHardware ? "故障仿真" : "虚拟执行";
|
||||
$("#feedbackModeLabel").textContent = hardware
|
||||
? (ch1DriverOnly
|
||||
? "CH1 BODY CAN · AUTHORITATIVE PHYSICAL FEEDBACK"
|
||||
? (mqttSelected
|
||||
? "MQTT CH1 · AUTHORITATIVE CAN FEEDBACK"
|
||||
: "CH1 BODY CAN · AUTHORITATIVE PHYSICAL FEEDBACK")
|
||||
: "T2G BENCH · PHYSICAL CAN FEEDBACK")
|
||||
: simulatedHardware
|
||||
? "SIMULATED HIL · FAULT INJECTION"
|
||||
@@ -5062,13 +5155,18 @@ async function saveCurrentModeProfile() {
|
||||
}
|
||||
|
||||
function applyRuntimeTargetPolicy(runtime) {
|
||||
const passengerReady = runtime?.hardware?.configured === true;
|
||||
const driverReady = runtime?.vehicle_can?.configured === true;
|
||||
const selectedTransport = selectedVehicleTransport();
|
||||
const wirelessDualChannel = state.controlTransport === "wireless_mqtt"
|
||||
&& selectedTransport?.target_policy === "dual_channel_mqtt_can0_passenger_can1_vehicle";
|
||||
const passengerReady = wirelessDualChannel
|
||||
? selectedTransport?.configured === true
|
||||
: runtime?.hardware?.configured === true;
|
||||
const driverReady = selectedTransport?.configured === true;
|
||||
const driverOnly = driverReady && !passengerReady;
|
||||
const stage = $("#seatTwin");
|
||||
if (stage) {
|
||||
stage.dataset.passengerChannel = "CH0";
|
||||
stage.dataset.driverChannel = "CH1";
|
||||
stage.dataset.driverChannel = state.controlTransport === "wireless_mqtt" ? "MQTT" : "CH1";
|
||||
stage.dataset.passengerConfigured = String(passengerReady);
|
||||
stage.dataset.driverConfigured = String(driverReady);
|
||||
}
|
||||
@@ -5115,6 +5213,7 @@ async function loadRuntime() {
|
||||
const response = await apiRequest("/api/runtime", { cache: "no-store" });
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
state.runtime = await response.json();
|
||||
renderControlTransport();
|
||||
applyRuntimeTargetPolicy(state.runtime);
|
||||
populateVehicleSelector();
|
||||
populateModeChips();
|
||||
@@ -5123,12 +5222,17 @@ async function loadRuntime() {
|
||||
state.reasoningEffort = configuredEffort;
|
||||
}
|
||||
const hardware = state.runtime.hardware || {};
|
||||
const vehicleCan = state.runtime.vehicle_can || {};
|
||||
const linkedHardwareReady = Boolean(hardware.configured && vehicleCan.configured);
|
||||
const seatHardwareReady = Boolean(hardware.configured);
|
||||
const vehicleCan = selectedVehicleTransport() || {};
|
||||
const mqttSelected = state.controlTransport === "wireless_mqtt";
|
||||
const mqttDualChannel = mqttSelected
|
||||
&& vehicleCan.target_policy === "dual_channel_mqtt_can0_passenger_can1_vehicle";
|
||||
const seatHardwareReady = Boolean(
|
||||
mqttDualChannel ? vehicleCan.configured : hardware.configured,
|
||||
);
|
||||
const vehicleHardwareReady = Boolean(vehicleCan.configured);
|
||||
const linkedHardwareReady = seatHardwareReady && vehicleHardwareReady;
|
||||
const ch1DriverOnly = vehicleHardwareReady && !seatHardwareReady;
|
||||
const hardwareExecutionReady = Boolean(state.runtime.hardware_execution_allowed);
|
||||
const hardwareExecutionReady = Boolean(hardware.configured || vehicleCan.configured);
|
||||
const badge = $("#modelBadge");
|
||||
const gptButton = $('[data-provider="openai"]');
|
||||
if (state.runtime.openai_available) {
|
||||
@@ -5163,32 +5267,35 @@ async function loadRuntime() {
|
||||
&& hardware.access_policy === "local_approval_idle_lease"
|
||||
? ` · 本地审批 · 空闲 ${hardware.access_inactivity_timeout_seconds || 180} 秒自动停止`
|
||||
: "";
|
||||
const vehicleRouteText = mqttSelected
|
||||
? `MQTT ${vehicleCan.host || "127.0.0.1"}:${vehicleCan.port || 1883} · ${vehicleCan.condition_topic || "n7/n7-headunit/can/state"} / ${vehicleCan.control_topic || "n7/can_control"}`
|
||||
: `${vehicleCan.backend || "zlgcan"} · CH${vehicleCan.channel ?? 1} 车身 CAN FD ${Number(vehicleCan.arbitration?.bitrate || 500000) / 1000}k/${Number(vehicleCan.data?.bitrate || 2000000) / 1000000}M`;
|
||||
$("#hardwareNote").textContent = ch1DriverOnly && vehicleHardwareReady && !seatHardwareReady
|
||||
? `${vehicleCan.backend} · CH${vehicleCan.channel} 车身 CAN FD ${vehicleCan.arbitration.bitrate / 1000}k/${vehicleCan.data.bitrate / 1000000}M · 仅主驾/车端白名单 · CH0/T2G/IAR 已禁用${accessText}${concurrencyText}`
|
||||
? `${vehicleRouteText} · 仅主驾/车端白名单 · CH0/T2G/IAR 已禁用${accessText}${concurrencyText}`
|
||||
: linkedHardwareReady
|
||||
? `${hardware.backend} · CH${hardware.channel} 座椅 ${hardware.bitrate / 1000} kbit/s · CH${vehicleCan.channel} 车身 CAN FD ${vehicleCan.arbitration.bitrate / 1000}k/${vehicleCan.data.bitrate / 1000000}M · ${profileText} · ${flashText}${accessText}${concurrencyText}`
|
||||
? `${mqttDualChannel ? "MQTT CAN0 副驾座椅 · MQTT CAN1 其余车辆" : `${hardware.backend} · CH${hardware.channel} 座椅 ${hardware.bitrate / 1000} kbit/s`} · ${vehicleRouteText} · ${profileText}${mqttDualChannel ? " · 远端自动烧录禁用" : ` · ${flashText}`}${accessText}${concurrencyText}`
|
||||
: seatHardwareReady
|
||||
? `${hardware.backend} · CH${hardware.channel} 座椅 ${hardware.bitrate / 1000} kbit/s · ${profileText} · ${flashText} · 车端 CAN 未启用,其他领域保持虚拟执行${accessText}${concurrencyText}`
|
||||
: `联动真实执行已锁定:${profileText};请完成座椅固件、双通道 ZLG 适配器和车身 CAN FD 配置`;
|
||||
: `真实执行已锁定:${profileText};当前${mqttSelected ? "无线 MQTT" : "有线 CAN"}控制未通过后端审批配置`;
|
||||
$("#hardwareAliasNote").textContent = ch1DriverOnly && vehicleHardwareReady && !seatHardwareReady
|
||||
? "CH1 → 车身 CAN FD / 主驾座椅 · CH0 与副驾 T2G 路径禁止"
|
||||
? `${mqttSelected ? "MQTT" : "CH1"} → 车身 CAN / 主驾座椅 · CH0 与副驾 T2G 路径禁止`
|
||||
: linkedHardwareReady
|
||||
? "CH0 → 副驾座椅控制器 · CH1 → 车身 CAN FD"
|
||||
? `${mqttDualChannel ? "MQTT CAN0" : "CH0"} → 副驾座椅控制器 · ${mqttDualChannel ? "MQTT CAN1" : "CH1"} → 其余车辆 CAN`
|
||||
: seatHardwareReady
|
||||
? `CH${hardware.channel} → 副驾座椅控制器 · 车身控制暂用虚拟 CAN`
|
||||
: "尚未配置可执行的物理 CAN 通道";
|
||||
$("#hardwareBadge").className = `status-badge ${hardwareExecutionReady ? "" : "danger"}`.trim();
|
||||
$("#hardwareBadge").innerHTML = ch1DriverOnly && vehicleHardwareReady && !seatHardwareReady
|
||||
? '<i data-lucide="shield-check"></i>CH1 车端已配置'
|
||||
? `<i data-lucide="shield-check"></i>${mqttSelected ? "MQTT" : "CH1"} 车端已配置`
|
||||
: linkedHardwareReady
|
||||
? '<i data-lucide="shield-check"></i>双通道已配置'
|
||||
: seatHardwareReady
|
||||
? '<i data-lucide="shield-check"></i>座椅台架已配置'
|
||||
: '<i data-lucide="shield-off"></i>硬件锁定';
|
||||
$("#hardwareCoverage").textContent = ch1DriverOnly && vehicleHardwareReady && !seatHardwareReady
|
||||
? `CH1 主驾 + 车端设备 · 物理字段回读${accessText}${concurrencyText}`
|
||||
? `${mqttSelected ? "MQTT" : "CH1"} 主驾 + 车端设备 · CAN 字段回读${accessText}${concurrencyText}`
|
||||
: linkedHardwareReady
|
||||
? `CH0 座椅 + CH${vehicleCan.channel} 车身${accessText}${concurrencyText}`
|
||||
? `CH0 座椅 + ${mqttSelected ? "MQTT" : `CH${vehicleCan.channel}`} 车身${accessText}${concurrencyText}`
|
||||
: seatHardwareReady
|
||||
? `CH${hardware.channel} 座椅 · 物理反馈${accessText}${concurrencyText}`
|
||||
: "已阻断";
|
||||
@@ -5209,10 +5316,16 @@ async function loadRuntime() {
|
||||
}
|
||||
|
||||
async function synchronizePassengerSeatState() {
|
||||
if (!state.runtime?.hardware?.configured) return false;
|
||||
const requestedTransport = state.controlTransport;
|
||||
const mqttSelected = requestedTransport === "wireless_mqtt";
|
||||
const stateReady = mqttSelected
|
||||
? selectedVehicleTransport()?.state_configured
|
||||
: state.runtime?.hardware?.configured;
|
||||
if (!stateReady) return false;
|
||||
const query = new URLSearchParams({
|
||||
conversation_id: state.conversationId,
|
||||
vehicle_id: state.vehicleId || state.runtime.default_vehicle_id || "",
|
||||
control_transport: requestedTransport,
|
||||
hardware_client_id: state.hardwareClientId,
|
||||
});
|
||||
try {
|
||||
@@ -5225,15 +5338,16 @@ async function synchronizePassengerSeatState() {
|
||||
}
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const payload = await response.json();
|
||||
if (requestedTransport !== state.controlTransport) return false;
|
||||
renderSeatState({ passenger: payload.actuator_state?.passenger || {} });
|
||||
state.seatFeedbackSources.passenger = payload.complete
|
||||
? "physical_can"
|
||||
? payload.source === "wireless_mqtt_ch0" ? "wireless_mqtt_ch0" : "physical_can"
|
||||
: "physical_partial";
|
||||
state.seatFeedbackSource = state.seatFeedbackSources.passenger;
|
||||
updateSeatFeedbackModeLabel();
|
||||
return true;
|
||||
} catch (_error) {
|
||||
if (!state.seatFeedbackSources.passenger.startsWith("physical")) {
|
||||
if (!["physical_can", "wireless_mqtt_ch0"].includes(state.seatFeedbackSources.passenger)) {
|
||||
state.seatFeedbackSources.passenger = "unavailable";
|
||||
updateSeatFeedbackModeLabel();
|
||||
}
|
||||
@@ -5242,10 +5356,12 @@ async function synchronizePassengerSeatState() {
|
||||
}
|
||||
|
||||
async function synchronizeDriverSeatState() {
|
||||
if (!state.runtime?.vehicle_can?.configured) return false;
|
||||
if (!selectedVehicleTransport()?.state_configured) return false;
|
||||
const requestedTransport = state.controlTransport;
|
||||
const query = new URLSearchParams({
|
||||
conversation_id: state.conversationId,
|
||||
vehicle_id: state.vehicleId || state.runtime.default_vehicle_id || "",
|
||||
control_transport: requestedTransport,
|
||||
hardware_client_id: state.hardwareClientId,
|
||||
});
|
||||
try {
|
||||
@@ -5256,13 +5372,16 @@ async function synchronizeDriverSeatState() {
|
||||
}
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const payload = await response.json();
|
||||
if (requestedTransport !== state.controlTransport) return false;
|
||||
applyAuthoritativeVehicleState(payload);
|
||||
state.seatFeedbackSources.driver = payload.complete ? "physical_can_ch1" : "physical_partial";
|
||||
state.seatFeedbackSources.driver = payload.complete
|
||||
? payload.source === "wireless_mqtt_ch1" ? "wireless_mqtt_ch1" : "physical_can_ch1"
|
||||
: "physical_partial";
|
||||
state.seatFeedbackSource = state.seatFeedbackSources.driver;
|
||||
updateSeatFeedbackModeLabel();
|
||||
return true;
|
||||
} catch (_error) {
|
||||
if (!state.seatFeedbackSources.driver.startsWith("physical")) {
|
||||
if (!["physical_can_ch1", "wireless_mqtt_ch1"].includes(state.seatFeedbackSources.driver)) {
|
||||
state.seatFeedbackSources.driver = "unavailable";
|
||||
updateSeatFeedbackModeLabel();
|
||||
}
|
||||
@@ -5273,7 +5392,22 @@ async function synchronizeDriverSeatState() {
|
||||
async function synchronizePhysicalSeatState() {
|
||||
if (state.physicalStateSyncPromise) return state.physicalStateSyncPromise;
|
||||
state.physicalStateSyncPromise = (async () => {
|
||||
// Both channels share one adapter owner; keep passive reads serialized.
|
||||
if (state.controlTransport === "wireless_mqtt") {
|
||||
// CAN0 may legitimately be absent and wait for its bounded seat-status
|
||||
// timeout. Keep that wait single-flight in the background so cached
|
||||
// CAN1 vehicle state can still refresh at the MQTT polling cadence.
|
||||
if (!state.passengerStateSyncPromise) {
|
||||
let passengerPromise;
|
||||
passengerPromise = synchronizePassengerSeatState().finally(() => {
|
||||
if (state.passengerStateSyncPromise === passengerPromise) {
|
||||
state.passengerStateSyncPromise = null;
|
||||
}
|
||||
});
|
||||
state.passengerStateSyncPromise = passengerPromise;
|
||||
}
|
||||
return synchronizeDriverSeatState();
|
||||
}
|
||||
// The wired channels share one adapter owner; keep passive reads serialized.
|
||||
const passenger = await synchronizePassengerSeatState();
|
||||
const driver = await synchronizeDriverSeatState();
|
||||
return passenger || driver;
|
||||
@@ -5285,6 +5419,16 @@ async function synchronizePhysicalSeatState() {
|
||||
}
|
||||
}
|
||||
|
||||
function restartPhysicalStatePolling() {
|
||||
if (state.physicalStatePoller) window.clearInterval(state.physicalStatePoller);
|
||||
state.physicalStatePoller = window.setInterval(() => {
|
||||
const activeModule = document.body.dataset.module;
|
||||
if (document.visibilityState !== "visible") return;
|
||||
if (!["self-orchestration", "self-extension"].includes(activeModule)) return;
|
||||
synchronizePhysicalSeatState();
|
||||
}, physicalStatePollIntervalMs());
|
||||
}
|
||||
|
||||
async function loadAuthStatus() {
|
||||
const response = await apiRequest("/api/auth/status", { cache: "no-store" });
|
||||
if (!response.ok) return;
|
||||
@@ -5585,7 +5729,7 @@ function renderDomainState(domainState) {
|
||||
if (vehicleSystems) {
|
||||
vehicleSystems.targets.climateLevel = Math.max(0, Math.min(7, Number(climate.fan_level) || 0));
|
||||
vehicleSystems.targets.climateTemperature = Number(
|
||||
state.runtime?.vehicle_can?.target_policy === "ch1_body_can_driver_only"
|
||||
selectedVehicleTransport()?.target_policy === "ch1_body_can_driver_only"
|
||||
? driverTemperature
|
||||
: passengerTemperature,
|
||||
) || 24;
|
||||
@@ -6213,8 +6357,8 @@ function renderExecution(result) {
|
||||
: simulatedHil
|
||||
? `${executedDomains.join(" / ")} 已完成隔离 HIL 仿真,未访问物理 CAN`
|
||||
: physical
|
||||
? state.runtime?.vehicle_can?.target_policy === "ch1_body_can_driver_only"
|
||||
? `${executedDomains.join(" / ")} 已通过 CH1 下发,并以车端状态字段确认 ${operationResults.filter((item) => item.status === "succeeded").length} 个步骤`
|
||||
? selectedVehicleTransport()?.target_policy === "ch1_body_can_driver_only"
|
||||
? `${executedDomains.join(" / ")} 已通过${result.control_transport === "wireless_mqtt" ? " MQTT" : " CH1"}下发,并以车端状态字段确认 ${operationResults.filter((item) => item.status === "succeeded").length} 个步骤`
|
||||
: `${executedDomains.join(" / ")} 已下发真实 CAN,并以 IAR 固件实际反馈确认 ${operationResults.filter((item) => item.status === "succeeded").length} 个步骤`
|
||||
: `${executedDomains.join(" / ")} 共 ${report.spec.results.length} 个操作已完成 CAN 编码与虚拟执行`;
|
||||
if (partiallySucceeded) {
|
||||
@@ -6349,8 +6493,12 @@ function applyAuthoritativeVehicleState(payload) {
|
||||
domainState: domainPatch,
|
||||
};
|
||||
if (actuatorPatch?.driver) {
|
||||
state.seatFeedbackSource = "physical_can_ch1";
|
||||
state.seatFeedbackSources.driver = "physical_can_ch1";
|
||||
const source = payload.control_transport === "wireless_mqtt"
|
||||
|| payload.source === "wireless_mqtt_ch1"
|
||||
? "wireless_mqtt_ch1"
|
||||
: "physical_can_ch1";
|
||||
state.seatFeedbackSource = source;
|
||||
state.seatFeedbackSources.driver = source;
|
||||
}
|
||||
renderSeatState(actuatorPatch);
|
||||
renderDomainState(domainPatch);
|
||||
@@ -6389,8 +6537,10 @@ function renderResult(result) {
|
||||
if (result.actuator_state?.driver) state.seatFeedbackSources.driver = "simulated_hardware";
|
||||
} else if (feedbackSource === "physical_can_ch1"
|
||||
&& isAuthoritativeVehicleStatePayload(result)) {
|
||||
state.seatFeedbackSource = "physical_can_ch1";
|
||||
state.seatFeedbackSources.driver = "physical_can_ch1";
|
||||
state.seatFeedbackSource = result.control_transport === "wireless_mqtt"
|
||||
? "wireless_mqtt_ch1"
|
||||
: "physical_can_ch1";
|
||||
state.seatFeedbackSources.driver = state.seatFeedbackSource;
|
||||
} else if (reportMode === "hil") {
|
||||
state.seatFeedbackSource = result.execution_report?.spec?.physical_state?.complete
|
||||
? "physical_can"
|
||||
@@ -6436,7 +6586,7 @@ function renderResult(result) {
|
||||
renderTrace(result.trace_events || []);
|
||||
renderValidation(result.validation || []);
|
||||
const ch1HardwareResult = reportMode === "hil"
|
||||
&& state.runtime?.vehicle_can?.target_policy === "ch1_body_can_driver_only";
|
||||
&& selectedVehicleTransport()?.target_policy === "ch1_body_can_driver_only";
|
||||
if (ch1HardwareResult) {
|
||||
applyAuthoritativeVehicleState(result);
|
||||
} else {
|
||||
@@ -6499,6 +6649,7 @@ async function submitRequest(event) {
|
||||
conversation_id: state.conversationId,
|
||||
vehicle_id: state.vehicleId || state.runtime?.default_vehicle_id,
|
||||
execution_mode: state.executionMode,
|
||||
control_transport: state.controlTransport,
|
||||
...(state.provider === "openai" ? { reasoning_effort: state.reasoningEffort } : {}),
|
||||
...(state.executionMode === "hardware" && !driverOnlyCh1
|
||||
? { hardware_client_id: state.hardwareClientId }
|
||||
@@ -6747,6 +6898,12 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
$$(".segment[data-provider]").forEach((button) => button.addEventListener("click", () => selectProvider(button.dataset.provider)));
|
||||
$$('[data-reasoning-effort]').forEach((button) => button.addEventListener("click", () => selectReasoningEffort(button.dataset.reasoningEffort)));
|
||||
$$('[data-execution-mode]').forEach((button) => button.addEventListener("click", () => selectExecutionMode(button.dataset.executionMode)));
|
||||
$$('[data-control-transport]').forEach((button) => button.addEventListener("click", async () => {
|
||||
selectControlTransport(button.dataset.controlTransport);
|
||||
await loadRuntime();
|
||||
restartPhysicalStatePolling();
|
||||
await synchronizePhysicalSeatState();
|
||||
}));
|
||||
$$(".example-command").forEach((button) => button.addEventListener("click", () => {
|
||||
$("#utterance").value = button.dataset.example;
|
||||
}));
|
||||
@@ -6763,12 +6920,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
);
|
||||
}
|
||||
await synchronizePhysicalSeatState();
|
||||
state.physicalStatePoller = window.setInterval(() => {
|
||||
const activeModule = document.body.dataset.module;
|
||||
if (document.visibilityState !== "visible") return;
|
||||
if (!["self-orchestration", "self-extension"].includes(activeModule)) return;
|
||||
synchronizePhysicalSeatState();
|
||||
}, 5000);
|
||||
restartPhysicalStatePolling();
|
||||
if (new URLSearchParams(window.location.search).get("autorun") === "1") {
|
||||
$("#requestForm").requestSubmit();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user