feat: refine vehicle console and self-repair workflow
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
const API_ROOT = "/api/calibration";
|
||||
const TARGET_KEY = "cognitive-agent.calibration-target";
|
||||
const pageTitles = {
|
||||
live: ["SEAT · ZED JOINT CALIBRATION", "座椅—双目联合标定", "座椅姿态、四轴 Hall、ZED 画面与双目位姿同步更新"],
|
||||
system: ["CONNECTION SETTINGS", "连接与运行设置", "CAN、MQTT 与 RTSP 接入参数"],
|
||||
live: ["SELF-CALIBRATION", "座椅与双目联合标定", "同步标定座椅姿态、四轴 Hall 与 ZED 实时画面"],
|
||||
system: ["SELF-CALIBRATION · SETTINGS", "连接与运行设置", "配置 CAN、MQTT 与 RTSP 接入参数"],
|
||||
};
|
||||
const phaseLabels = {
|
||||
0: "待命",
|
||||
@@ -25,7 +25,6 @@
|
||||
let reconnectTimer = 0;
|
||||
let toastTimer = 0;
|
||||
let settingsHydrated = false;
|
||||
let modelRequested = false;
|
||||
let activeView = "live";
|
||||
|
||||
function buildShell() {
|
||||
@@ -83,10 +82,10 @@
|
||||
root.setAttribute("aria-label", "N7 自标定工作台");
|
||||
root.innerHTML = `
|
||||
<header class="v2-calibration-heading">
|
||||
<div>
|
||||
<span class="section-kicker" id="calibrationPageKicker">SEAT · ZED JOINT CALIBRATION</span>
|
||||
<h1 id="calibrationPageTitle">座椅—双目联合标定</h1>
|
||||
<p id="calibrationPageDescription">座椅姿态、四轴 Hall、ZED 画面与双目位姿同步更新</p>
|
||||
<div class="v2-module-heading">
|
||||
<span class="section-kicker" id="calibrationPageKicker">SELF-CALIBRATION</span>
|
||||
<h1 id="calibrationPageTitle">座椅与双目联合标定</h1>
|
||||
<p id="calibrationPageDescription">同步标定座椅姿态、四轴 Hall 与 ZED 实时画面</p>
|
||||
</div>
|
||||
<div class="v2-calibration-statuses">
|
||||
<div><i id="calibrationCanDot"></i><span><small>CAN</small><strong id="calibrationCanState">未连接</strong></span></div>
|
||||
@@ -101,12 +100,12 @@
|
||||
<header><div><span class="section-kicker">DIGITAL TWIN</span><h2>实时座椅姿态</h2></div><span class="v2-calibration-badge wait" id="calibrationPhaseBadge">待命</span></header>
|
||||
<div class="v2-calibration-seat-scene">
|
||||
<div class="v2-calibration-floor-grid"></div>
|
||||
<div id="seatModelHost" class="seat-model-host">
|
||||
<canvas id="seatCanvas" aria-label="N7 原始三维座椅模型,可拖动旋转并滚轮缩放"></canvas>
|
||||
<div id="calibrationSeatViewer" class="seat-model-host calibration-cartoon-viewer">
|
||||
<canvas id="calibrationSeatCanvas" aria-label="自标定卡通风格三维座椅,可拖动旋转并滚轮缩放"></canvas>
|
||||
<div id="seatModelLoading" class="model-loading">
|
||||
<span class="model-spinner"></span>
|
||||
<strong>正在加载原始 N7 座椅</strong>
|
||||
<small>ANDROID GLB · LOCAL</small>
|
||||
<strong>正在构建座椅数字孪生</strong>
|
||||
<small>CALIBRATION · CARTOON TWIN</small>
|
||||
</div>
|
||||
<div class="orbit-hint">拖动旋转 · 滚轮缩放 · 双击复位</div>
|
||||
</div>
|
||||
@@ -127,17 +126,6 @@
|
||||
<div class="v2-calibration-actions"><button id="calibrationVideoStart" type="button">开始接收</button><button id="calibrationVideoStop" type="button">停止</button></div>
|
||||
</section>
|
||||
|
||||
<section class="v2-calibration-panel v2-calibration-mqtt-panel">
|
||||
<header><div><span class="section-kicker">ZED / SEATPOSE</span><h2>双目位姿数据</h2></div><span class="v2-calibration-badge wait" id="calibrationMqttBadge">等待数据</span></header>
|
||||
<div class="v2-calibration-metrics"><div><small>消息总数</small><strong id="calibrationMqttCount">0</strong></div><div><small>最新帧</small><strong id="calibrationMqttFrame">—</strong></div><div><small>解析错误</small><strong id="calibrationMqttErrors">0</strong></div></div>
|
||||
<p class="v2-calibration-detail" id="calibrationMqttDetail">MQTT 尚未连接</p>
|
||||
<div class="v2-calibration-pose-areas" id="calibrationPoseAreas"></div>
|
||||
<div class="v2-calibration-live-mapping">
|
||||
<div class="v2-calibration-subheading"><span>HALL ↔ 3D</span><strong>实时映射</strong></div>
|
||||
<div class="v2-calibration-mapping-rows" id="calibrationMappingRows"></div>
|
||||
</div>
|
||||
<div class="v2-calibration-actions"><button id="calibrationMqttConnect" type="button">连接 MQTT</button><button id="calibrationMqttDisconnect" type="button">断开</button><button id="calibrationMqttTest" type="button">回环测试</button></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="v2-calibration-panel v2-calibration-axis-section v2-calibration-live-axes">
|
||||
@@ -312,9 +300,7 @@
|
||||
renderWorkflow(state.operation.phase);
|
||||
renderAxes(state.axes, controller.job_active, transport.state === "online");
|
||||
renderSeat(state.axes);
|
||||
renderMqtt(mqtt);
|
||||
renderVideo(state.video);
|
||||
renderMappings(state.axes, mqtt.areas);
|
||||
$("#calibrationStartAll").disabled =
|
||||
transport.state !== "online" || controller.job_active;
|
||||
if (!settingsHydrated) hydrateSettings(state.settings, state.video.ffmpeg_available);
|
||||
@@ -408,55 +394,25 @@
|
||||
};
|
||||
window.n7SeatPose = pose;
|
||||
window.dispatchEvent(new CustomEvent("n7-seat-pose", { detail: pose }));
|
||||
window.dispatchEvent(new CustomEvent("calibration-seat-state", {
|
||||
detail: {
|
||||
axes: [
|
||||
{ motor: 0, position_pct: longitudinal.permille / 10 },
|
||||
{ motor: 1, position_pct: height.permille / 10 },
|
||||
{ motor: 2, position_pct: backrest.permille / 10 },
|
||||
{ motor: 3, position_pct: legrest.permille / 10 },
|
||||
],
|
||||
state: axes.some((axis) => axis.fault)
|
||||
? "FAILED"
|
||||
: axes.some((axis) => axis.moving || axis.calibrating) ? "WORKING" : "IDLE",
|
||||
},
|
||||
}));
|
||||
$("#calibrationTagLong").textContent = `${longitudinal.physical_value.toFixed(2)} cm`;
|
||||
$("#calibrationTagLeg").textContent = `${legrest.physical_value.toFixed(1)}°`;
|
||||
$("#calibrationTagBack").textContent = `${backrest.physical_value.toFixed(1)}°`;
|
||||
$("#calibrationTagHeight").textContent = `${height.physical_value.toFixed(2)} cm`;
|
||||
}
|
||||
|
||||
function renderMqtt(mqtt) {
|
||||
const friendly = {
|
||||
simulated: "仿真数据",
|
||||
subscribed: "已订阅",
|
||||
connecting: "连接中",
|
||||
disconnected: "未连接",
|
||||
error: "异常",
|
||||
};
|
||||
$("#calibrationMqttBadge").textContent = friendly[mqtt.state] || mqtt.state;
|
||||
$("#calibrationMqttBadge").className = `v2-calibration-badge ${
|
||||
mqtt.state === "error"
|
||||
? "error"
|
||||
: ["disconnected", "connecting"].includes(mqtt.state) ? "wait" : ""
|
||||
}`;
|
||||
$("#calibrationMqttCount").textContent = mqtt.count;
|
||||
$("#calibrationMqttFrame").textContent = mqtt.last_frame >= 0 ? mqtt.last_frame : "—";
|
||||
$("#calibrationMqttErrors").textContent = mqtt.parse_errors;
|
||||
$("#calibrationMqttDetail").textContent = mqtt.detail;
|
||||
const container = $("#calibrationPoseAreas");
|
||||
container.replaceChildren();
|
||||
const entries = Object.entries(mqtt.areas || {});
|
||||
if (!entries.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "v2-calibration-empty";
|
||||
empty.textContent = "等待 Seat_cushion / Seat_backrest / Seat_leg_rest 位姿…";
|
||||
container.append(empty);
|
||||
}
|
||||
entries.forEach(([name, pose]) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "v2-calibration-pose-row";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = name;
|
||||
const angle = document.createElement("span");
|
||||
angle.textContent = pose.angle == null ? "ANGLE —" : `${Number(pose.angle).toFixed(2)}°`;
|
||||
const translation = document.createElement("small");
|
||||
translation.textContent = pose.translation
|
||||
? `T [${pose.translation.slice(0, 3).map((value) => Number(value).toFixed(3)).join(", ")}]`
|
||||
: "T —";
|
||||
row.append(title, angle, translation);
|
||||
container.append(row);
|
||||
});
|
||||
}
|
||||
|
||||
function renderVideo(video) {
|
||||
const friendly = { idle: "未启动", connecting: "连接中", playing: "播放中", error: "异常" };
|
||||
$("#calibrationVideoBadge").textContent = friendly[video.state] || video.state;
|
||||
@@ -475,28 +431,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function renderMappings(axes, areas = {}) {
|
||||
const container = $("#calibrationMappingRows");
|
||||
container.replaceChildren();
|
||||
const areaFor = ["Seat_cushion", "Seat_leg_rest", "Seat_backrest", "Seat_cushion"];
|
||||
axes.forEach((axis) => {
|
||||
const pose = areas[areaFor[axis.id]] || {};
|
||||
const observed = axis.id === 0 || axis.id === 3
|
||||
? pose.translation?.[axis.id === 0 ? 0 : 1]
|
||||
: pose.angle;
|
||||
const row = document.createElement("div");
|
||||
row.className = "v2-calibration-mapping-row";
|
||||
row.innerHTML = "<strong></strong><span></span><div><i></i></div><b></b>";
|
||||
$("strong", row).textContent = axis.name;
|
||||
$("span", row).textContent = `${axis.hall} Hall`;
|
||||
$("i", row).style.width = `${axis.permille / 10}%`;
|
||||
$("b", row).textContent = observed == null
|
||||
? "3D —"
|
||||
: `${Number(observed).toFixed(3)}${axis.id === 1 || axis.id === 2 ? "°" : " T"}`;
|
||||
container.append(row);
|
||||
});
|
||||
}
|
||||
|
||||
function hydrateSettings(settings, ffmpegAvailable) {
|
||||
settingsHydrated = true;
|
||||
$("#calibrationCanInterface").value = settings.can.interface;
|
||||
@@ -580,19 +514,6 @@
|
||||
}, settings.can.mode === "simulation" ? "标定仿真台架已连接" : "真实台架连接完成");
|
||||
}
|
||||
|
||||
function loadSeatModel() {
|
||||
if (modelRequested || !$("#seatModelHost")) return;
|
||||
modelRequested = true;
|
||||
const script = document.createElement("script");
|
||||
script.src = "/assets/self-calibration-model.bundle.js";
|
||||
script.async = true;
|
||||
script.onerror = () => {
|
||||
modelRequested = false;
|
||||
$("#seatModelLoading")?.classList.add("error");
|
||||
};
|
||||
document.head.append(script);
|
||||
}
|
||||
|
||||
function closeEvents() {
|
||||
window.clearTimeout(reconnectTimer);
|
||||
reconnectTimer = 0;
|
||||
@@ -621,7 +542,6 @@
|
||||
closeEvents();
|
||||
return;
|
||||
}
|
||||
loadSeatModel();
|
||||
fetchState().catch((error) => toast(`自标定服务连接失败:${error.message}`, true));
|
||||
connectEvents();
|
||||
}
|
||||
@@ -641,18 +561,6 @@
|
||||
() => api("/start", { axis: "all" }),
|
||||
"四轴自动标定已启动",
|
||||
));
|
||||
$("#calibrationMqttConnect").addEventListener("click", () => run(
|
||||
() => api("/mqtt/connect"),
|
||||
"正在连接 MQTT",
|
||||
));
|
||||
$("#calibrationMqttDisconnect").addEventListener("click", () => run(
|
||||
() => api("/mqtt/disconnect"),
|
||||
"MQTT 已断开",
|
||||
));
|
||||
$("#calibrationMqttTest").addEventListener("click", () => run(
|
||||
() => api("/mqtt/test"),
|
||||
"MQTT 测试帧已发送",
|
||||
));
|
||||
$("#calibrationVideoStart").addEventListener("click", () => run(
|
||||
() => api("/video/start"),
|
||||
"正在启动 RTSP 接收",
|
||||
|
||||
Reference in New Issue
Block a user