添加NFC绑定红外设备方式,融合车端设备和扩展设备统一控制
This commit is contained in:
@@ -44,7 +44,9 @@
|
||||
if (validatedCommandContext.requestId && requestId
|
||||
&& validatedCommandContext.requestId !== requestId) return false;
|
||||
if (validatedCommandContext.utterance && utterance
|
||||
&& validatedCommandContext.utterance !== utterance) return false;
|
||||
&& validatedCommandContext.utterance !== utterance
|
||||
&& !validatedCommandContext.utterance.includes(utterance)
|
||||
&& !utterance.includes(validatedCommandContext.utterance)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -105,6 +107,22 @@
|
||||
return `0x300 · ${hexByte(opcode)} ${hexByte(control)} <SEQ_LO> <SEQ_HI> ${littleEndian16(paramA)} ${littleEndian16(paramB)}`;
|
||||
};
|
||||
|
||||
if (capability === "infrared.emit") {
|
||||
const slot = boundedInteger(parameters.slot, 1, 248);
|
||||
const action = String(parameters.action || "红外动作").trim();
|
||||
const deviceName = String(operation.target?.name || operation.target?.device || "红外外设").trim();
|
||||
if (slot !== null) {
|
||||
return {
|
||||
channel: "IR",
|
||||
category: `${deviceName} · ${action}`,
|
||||
command: `infrared emit ${slot}`,
|
||||
frame: JSON.stringify({ action: "emit", data: { no: slot }, type: "infrared" }),
|
||||
frameLabel: "GSCU1B TCP",
|
||||
...(parseSource ? { parseSource } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const isPassengerSeat = new Set(["passenger", "front-right"]).has(target);
|
||||
if (isPassengerSeat) {
|
||||
const passengerAxes = {
|
||||
@@ -324,23 +342,74 @@
|
||||
|
||||
function parsedCanCommands(result = {}) {
|
||||
const configurations = result.function_configs || {};
|
||||
const plans = result.command_plans || {};
|
||||
const planKeys = new Set(Object.keys(plans));
|
||||
const hasCompiledPlan = planKeys.size > 0 || Boolean(result.command_plan);
|
||||
if (!hasCompiledPlan) return [];
|
||||
const configurationEntries = Object.entries(configurations);
|
||||
if (!configurationEntries.length && result.function_config) {
|
||||
configurationEntries.push(["default", result.function_config]);
|
||||
}
|
||||
const executionResults = [
|
||||
...(result.execution_report?.spec?.results || []),
|
||||
...(result.operation_results || []),
|
||||
...Object.values(result.phase_execution_reports || {}).flatMap(
|
||||
(report) => report?.spec?.results || [],
|
||||
),
|
||||
];
|
||||
const executionByRef = new Map();
|
||||
executionResults.forEach((execution) => {
|
||||
const ref = String(execution?.ref || "");
|
||||
if (!ref) return;
|
||||
const previous = executionByRef.get(ref);
|
||||
if (!previous || (!previous.telemetry && execution?.telemetry)) {
|
||||
executionByRef.set(ref, execution);
|
||||
}
|
||||
});
|
||||
const submittedStates = new Set(["submitted", "queued", "published", "sent", "delivered"]);
|
||||
const deliveryForOperation = (operation) => {
|
||||
const capability = String(operation?.capability || "");
|
||||
const operationId = String(operation?.id || "");
|
||||
const execution = executionByRef.get(operationId) || executionResults.find((candidate) => {
|
||||
const ref = String(candidate?.ref || "");
|
||||
return ref && operationId && (
|
||||
operationId.startsWith(`${ref}-`) || ref.startsWith(`${operationId}-`)
|
||||
);
|
||||
});
|
||||
const telemetry = execution?.telemetry || {};
|
||||
const mqttMessages = Array.isArray(telemetry.mqtt_messages) ? telemetry.mqtt_messages : [];
|
||||
const deliveryStates = [
|
||||
telemetry.delivery_status,
|
||||
...mqttMessages.map((message) => message?.delivery_status),
|
||||
].map((value) => String(value || "").toLowerCase());
|
||||
const submitted = result.execution_mode === "hardware"
|
||||
&& telemetry.simulated !== true
|
||||
&& (
|
||||
deliveryStates.some((value) => submittedStates.has(value))
|
||||
|| (Array.isArray(telemetry.sent_frames) && telemetry.sent_frames.length > 0)
|
||||
|| telemetry.gateway_acknowledged === true
|
||||
|| telemetry.feedback_observed === true
|
||||
|| telemetry.physical_effect_confirmed === true
|
||||
);
|
||||
const transportLabel = capability === "infrared.emit"
|
||||
? "红外 TCP"
|
||||
: result.control_transport === "wireless_mqtt" ? "无线 CAN" : "有线 CAN";
|
||||
return {
|
||||
deliveryState: submitted ? "submitted" : "not_submitted",
|
||||
deliveryLabel: submitted ? "下发" : "未下发",
|
||||
deliveryTransport: transportLabel,
|
||||
};
|
||||
};
|
||||
const commands = [];
|
||||
Object.entries(configurations).forEach(([configKey, configuration]) => {
|
||||
if (planKeys.size > 0 && !planKeys.has(configKey)) return;
|
||||
configurationEntries.forEach(([_configKey, configuration]) => {
|
||||
(configuration?.spec?.operations || []).forEach((operation) => {
|
||||
const item = parsedCanCommandForOperation(operation);
|
||||
if (item) commands.push(item);
|
||||
if (item) commands.push({ ...item, ...deliveryForOperation(operation) });
|
||||
});
|
||||
});
|
||||
const unique = new Map();
|
||||
commands.forEach((item) => {
|
||||
const key = `${item.channel}:${item.command}`;
|
||||
const previous = unique.get(key);
|
||||
if (!previous || (previous.parseSource !== "local_rule" && item.parseSource === "local_rule")) {
|
||||
if (!previous
|
||||
|| (previous.deliveryState !== "submitted" && item.deliveryState === "submitted")
|
||||
|| (previous.parseSource !== "local_rule" && item.parseSource === "local_rule")) {
|
||||
unique.set(key, item);
|
||||
}
|
||||
});
|
||||
@@ -357,17 +426,18 @@
|
||||
panel.dataset.state = commands.length ? "ready" : message ? "pending" : "empty";
|
||||
if (commands.length) {
|
||||
list.innerHTML = `<ol>${commands.map((item) => `
|
||||
<li data-can-channel="${escapeHtml(item.channel)}">
|
||||
<li data-can-channel="${escapeHtml(item.channel)}" data-delivery-state="${escapeHtml(item.deliveryState)}">
|
||||
<span>
|
||||
<b class="v2-can-command-channel">${escapeHtml(item.channel)}</b>
|
||||
${item.parseSource ? `<b class="v2-can-command-source" data-parse-source="${escapeHtml(item.parseSource)}">${item.parseSource === "direct_control" ? "设备控件" : item.parseSource === "local_rule" ? "本地规则" : "云端模型"}</b>` : ""}
|
||||
${escapeHtml(item.category)}
|
||||
<b class="v2-can-command-delivery" data-delivery-state="${escapeHtml(item.deliveryState)}" title="${escapeHtml(`${item.deliveryTransport} · ${item.deliveryLabel}`)}">${escapeHtml(item.deliveryLabel)}</b>
|
||||
<i class="v2-can-command-category">${escapeHtml(item.category)}</i>
|
||||
</span>
|
||||
<code>${escapeHtml(item.command)}</code>
|
||||
${item.frame ? `<small class="v2-can-command-frame">固件原生 CAN0 · ${escapeHtml(item.frame)}</small>` : ""}
|
||||
${item.frame ? `<small class="v2-can-command-frame">${escapeHtml(item.frameLabel || "固件原生 CAN0")} · ${escapeHtml(item.frame)}</small>` : ""}
|
||||
</li>`).join("")}</ol>`;
|
||||
} else {
|
||||
list.innerHTML = `<p>${escapeHtml(message || "本次请求未生成已验证的 CAN 控制指令")}</p>`;
|
||||
list.innerHTML = `<p>${escapeHtml(message || "本次请求未解析出控制指令")}</p>`;
|
||||
}
|
||||
requestAnimationFrame(syncSidebarLogBaseline);
|
||||
}
|
||||
@@ -380,15 +450,18 @@
|
||||
const items = commands.map((command) => ({
|
||||
channel: command.startsWith("seat passenger ") ? "CAN0" : "CAN1",
|
||||
command,
|
||||
deliveryState: "not_submitted",
|
||||
deliveryLabel: "未下发",
|
||||
}));
|
||||
count.textContent = String(items.length);
|
||||
panel.dataset.state = "ready";
|
||||
list.innerHTML = `<ol>${items.map((item) => `
|
||||
<li data-can-channel="${item.channel}">
|
||||
<li data-can-channel="${item.channel}" data-delivery-state="${item.deliveryState}">
|
||||
<span>
|
||||
<b class="v2-can-command-channel">${item.channel}</b>
|
||||
<b class="v2-can-command-source" data-parse-source="direct_control">设备控件</b>
|
||||
${escapeHtml(deviceName)}
|
||||
<b class="v2-can-command-delivery" data-delivery-state="${item.deliveryState}" title="尚未进入 CAN 传输链路">${item.deliveryLabel}</b>
|
||||
<i class="v2-can-command-category">${escapeHtml(deviceName)}</i>
|
||||
</span>
|
||||
<code>${escapeHtml(item.command)}</code>
|
||||
</li>`).join("")}</ol>`;
|
||||
@@ -1205,7 +1278,7 @@
|
||||
: trace.length
|
||||
? trace.slice(-4).map((item) => item.label || item.stage || item.event || "执行步骤")
|
||||
: domains.length
|
||||
? ["接收编排指令", `编排 ${domains.length} 个设备域`, "下发执行计划", "同步执行结果"]
|
||||
? ["接收编排指令", `编排 ${domains.length} 个设备域`, "生成执行计划", "同步执行结果"]
|
||||
: ["等待车机或网页端下发编排任务"];
|
||||
timeline.innerHTML = stages.map((stage, index) => `
|
||||
<span class="${task?.status === "running" && index === stages.length - 1 ? "active" : "done"}">
|
||||
@@ -1258,7 +1331,7 @@
|
||||
// `running` snapshot for the same task must not erase those commands.
|
||||
if (!validatedCommandsBelongToTask(task)) {
|
||||
resetValidatedCommandContext();
|
||||
renderParsedCanCommands(null, { message: "正在解析自然语言并生成 CAN 控制指令…" });
|
||||
renderParsedCanCommands(null, { message: "正在解析自然语言并生成控制指令…" });
|
||||
}
|
||||
} else if (result) {
|
||||
const commands = parsedCanCommands(result);
|
||||
@@ -1451,7 +1524,7 @@
|
||||
insertVisibleControl(simulationFaultField, "v2-sync-simulation-fault-field");
|
||||
const canCommandPanel = make("details", "v2-can-command-panel", `
|
||||
<summary>
|
||||
<span><i data-lucide="binary"></i><b>已解析 CAN 控制指令</b></span>
|
||||
<span><i data-lucide="binary"></i><b>已解析控制指令</b></span>
|
||||
<strong id="v2CanCommandCount">0</strong>
|
||||
<i data-lucide="chevron-down"></i>
|
||||
</summary>
|
||||
@@ -1550,7 +1623,7 @@
|
||||
$("#v2LogFilter")?.addEventListener("change", renderSyncLogs);
|
||||
$("#requestForm")?.addEventListener("submit", () => {
|
||||
resetValidatedCommandContext();
|
||||
renderParsedCanCommands(null, { message: "正在解析自然语言并生成 CAN 控制指令…" });
|
||||
renderParsedCanCommands(null, { message: "正在解析自然语言并生成控制指令…" });
|
||||
});
|
||||
window.addEventListener("cognitive-agent:validated-can-commands", (event) => {
|
||||
const result = event.detail || {};
|
||||
@@ -1558,7 +1631,7 @@
|
||||
if (validatedCommandsVisible) rememberValidatedCommandContext(result);
|
||||
else resetValidatedCommandContext();
|
||||
renderParsedCanCommands(result, {
|
||||
message: "已完成白名单校验,正在下发控制指令…",
|
||||
message: "控制指令已解析,正在等待传输结果…",
|
||||
});
|
||||
});
|
||||
window.addEventListener("cognitive-agent:result", (event) => {
|
||||
@@ -1591,7 +1664,7 @@
|
||||
});
|
||||
window.addEventListener("cognitive-agent:request-complete", (event) => {
|
||||
if (event.detail?.error && !validatedCommandsVisible) {
|
||||
renderParsedCanCommands(null, { message: "本次请求未生成 CAN 控制指令" });
|
||||
renderParsedCanCommands(null, { message: "本次请求未生成控制指令" });
|
||||
}
|
||||
if (event.detail?.error) {
|
||||
const error = event.detail.error;
|
||||
|
||||
Reference in New Issue
Block a user