feat(ecat): PDO 快发 ESF1 桌面强制直通,堵死慢发回落
桌面 APP 下 ECAT motor 发送只走 pywebview ESF1→native TCP; 删除桥内 HTTP/JSON 回落。附延迟测量与 pack 自测脚本与规格文档。
This commit is contained in:
@@ -8,6 +8,9 @@
|
||||
*
|
||||
* 电机页在 tabs.html 的 iframe 内:pywebview 只注入顶层窗口,同源 iframe
|
||||
* 必须向上找 window.parent / window.top 上的 pywebview.api。
|
||||
*
|
||||
* PDO 快发(pdo-fast-send):桌面壳且 native 带 fast_send_port 时,ECAT
|
||||
* motor 发送优先 desktopEcatSendRaw(GUI 侧 ESF1 二进制直通入队)。
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
@@ -59,7 +62,26 @@ export async function desktopEcatPost(path, body, timeoutMs) {
|
||||
return api.ecat_api(path, body == null ? {} : body, timeoutMs || 4000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 桌面桥 PDO 快发(ESF1)。frames=[{ch,id,dlc,data(hex)}]。
|
||||
* @returns {Promise<object>} {ok,queued,backlog,path,log};path=fast_tcp|http_send_raw|http_motor_send
|
||||
*/
|
||||
export async function desktopEcatSendRaw(frames, timeoutMs) {
|
||||
const api = getDesktopEcatApi();
|
||||
if (!api || typeof api.ecat_send_raw !== "function") {
|
||||
throw new Error("desktop fast-send unavailable");
|
||||
}
|
||||
return api.ecat_send_raw(frames, timeoutMs || 2000);
|
||||
}
|
||||
|
||||
/** @description 桌面桥是否暴露 ecat_send_raw(方法在,不代表 native 快发口一定在)。 */
|
||||
export function hasDesktopFastSend() {
|
||||
const api = getDesktopEcatApi();
|
||||
return !!(api && typeof api.ecat_send_raw === "function");
|
||||
}
|
||||
|
||||
/** @description 状态栏用:当前 ECAT 控制通道形态。 */
|
||||
export function desktopBridgeLabel() {
|
||||
return getDesktopEcatApi() ? "APP 原生桥" : "HTTP 代理";
|
||||
if (!getDesktopEcatApi()) return "HTTP 代理";
|
||||
return hasDesktopFastSend() ? "APP 二进制快发" : "APP 原生桥";
|
||||
}
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ import { escapeHtml, flashBusyNow } from "../common/utils.js";
|
||||
/* 传输层:WiFi(HTTP) / EtherCAT(过程数据) 二选一,业务逻辑完全共用 */
|
||||
import {
|
||||
createTransport, loadTransportId, saveTransportId, LS_TRANSPORT
|
||||
} from "./transport.js?v=20260611c";
|
||||
} from "./transport.js?v=20260923i";
|
||||
import { deviceLink } from "../common/device-link.js";
|
||||
/* 刷新速度「每链路一份」的纯逻辑(默认值/钳制/旧数据迁移),
|
||||
* 抽出去是为了能脱离 DOM 自测,见 refresh-rate.js 头注释。 */
|
||||
|
||||
@@ -44,7 +44,8 @@ import { staleLimitMs } from "./refresh-rate.js";
|
||||
import { deviceLink } from "../common/device-link.js";
|
||||
import {
|
||||
desktopEcatPost, desktopBridgeLabel, getDesktopEcatApi,
|
||||
} from "../common/desktop-bridge.js?v=20260923g";
|
||||
desktopEcatSendRaw, hasDesktopFastSend,
|
||||
} from "../common/desktop-bridge.js?v=20260923i";
|
||||
|
||||
/** 单个 PDO 周期回传的状态槽数。
|
||||
* 固件 1.6.21 起段 2 密排整轮:一拍带齐总线电机(最多 9),空槽不占位。
|
||||
@@ -242,6 +243,41 @@ export function createEcatTransport(ctx) {
|
||||
return callHttp(tag, body, timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* ECAT 发送统一入口。
|
||||
* 桌面桥且 ecat_send_raw 可用:**只走 ESF1**,失败直接返回 ok:false,
|
||||
* **禁止**回落 HTTP motor/send(2026-09-23 堵死慢发,防误走 JSON 路径)。
|
||||
* 无桌面桥(浏览器标签):仍走 api("motor/send") HTTP。
|
||||
* payload = {frames:[{ch,id,dlc,data}]}。
|
||||
*/
|
||||
let lastFastSendPath = "";
|
||||
async function sendViaFastOrJson(payload) {
|
||||
if (hasDesktopFastSend()) {
|
||||
lastFastSendPath = "fast_tcp";
|
||||
try {
|
||||
const r = await desktopEcatSendRaw(payload.frames || [], 2000);
|
||||
if (r && r.ok === true) {
|
||||
if (r.path) lastFastSendPath = r.path;
|
||||
return r;
|
||||
}
|
||||
const msg = (r && r.log && r.log.length)
|
||||
? r.log.join(" / ")
|
||||
: "二进制快发失败(已禁用 HTTP/JSON 回落)";
|
||||
recordSlow("fast-send", 0, msg);
|
||||
return { ok: false, queued: 0, backlog: 0, path: "fast_tcp", log: [msg] };
|
||||
} catch (e) {
|
||||
const err = e instanceof Error ? e : new Error(String(e));
|
||||
recordSlow("fast-send", 0, err.message);
|
||||
return {
|
||||
ok: false, queued: 0, backlog: 0, path: "fast_tcp",
|
||||
log: [err.message || "二进制快发异常"],
|
||||
};
|
||||
}
|
||||
}
|
||||
lastFastSendPath = "json";
|
||||
return api("motor/send", payload, 6000);
|
||||
}
|
||||
|
||||
/** ok:false 是后端的常规业务错误信道(HTTP 仍是 200)。 */
|
||||
function unwrap(json, what) {
|
||||
if (!json || json.ok !== true) {
|
||||
@@ -533,7 +569,10 @@ export function createEcatTransport(ctx) {
|
||||
supportsPeriodSync: false,
|
||||
|
||||
sendHint(ch) {
|
||||
return desktopBridgeLabel() + " · motor/send · CH" + ch +
|
||||
const fast = lastFastSendPath === "fast_tcp"
|
||||
? "二进制快发"
|
||||
: desktopBridgeLabel();
|
||||
return fast + " · motor/send · CH" + ch +
|
||||
(periodMs ? " · PDO " + fmtPeriod(periodMs) : "");
|
||||
},
|
||||
|
||||
@@ -706,6 +745,9 @@ export function createEcatTransport(ctx) {
|
||||
* 在**同一个 seq 沿**由从站一次性发出 —— 这是多轴同步下发的前提,
|
||||
* 逐帧调用会退化成一帧一个周期。
|
||||
* 返回值补成 WiFi 的 results[] 形状(后端只回 queued/backlog)。
|
||||
*
|
||||
* PDO 快发:桌面桥有 ecat_send_raw 时**只走** ESF1 二进制直通,
|
||||
* 失败 ok:false、不回落 JSON(堵死慢发)。无桥(浏览器)才走 motor/send。
|
||||
*/
|
||||
async sendFrames(frames, opts) {
|
||||
if (!started) await ensureStarted();
|
||||
@@ -714,7 +756,7 @@ export function createEcatTransport(ctx) {
|
||||
ch: opts.ch, id: f.id, dlc: f.dlc, data: f.data,
|
||||
})),
|
||||
};
|
||||
const r = await api("motor/send", payload, 6000);
|
||||
const r = await sendViaFastOrJson(payload);
|
||||
if (!r || r.ok !== true) {
|
||||
const msg = (r && r.log && r.log.length) ? r.log.join(" / ") : "发送失败";
|
||||
return frames.map(() => ({ status: "error", code: -2, gd32_err: 0, desc: msg }));
|
||||
@@ -733,7 +775,7 @@ export function createEcatTransport(ctx) {
|
||||
ch: f.ch,
|
||||
}));
|
||||
if (!started) await ensureStarted();
|
||||
const r = await api("motor/send", { frames: built }, 6000);
|
||||
const r = await sendViaFastOrJson({ frames: built });
|
||||
if (!r || r.ok !== true) {
|
||||
const msg = (r && r.log && r.log.length) ? r.log.join(" / ") : "发送失败";
|
||||
return built.map(() => ({ status: "error", code: -2, gd32_err: 0, desc: msg }));
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"use strict";
|
||||
|
||||
import { createWifiTransport } from "./transport-wifi.js";
|
||||
import { createEcatTransport } from "./transport-ecat.js?v=20260611c";
|
||||
import { createEcatTransport } from "./transport-ecat.js?v=20260923i";
|
||||
|
||||
/** localStorage key:记住上次选的传输,刷新页面后保持 */
|
||||
export const LS_TRANSPORT = "dcu_motor_transport";
|
||||
|
||||
Reference in New Issue
Block a user