2026-09-20 22:58:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* desktop-bridge.js — 桌面壳(pywebview)原生桥探测与调用
|
|
|
|
|
|
*
|
|
|
|
|
|
* 形态约定:
|
|
|
|
|
|
* esp32_app.vbs → --window → WebView2:JS 可经 window.pywebview.api 调到
|
|
|
|
|
|
* GUI 进程 Python,由它直连 ecat_native(不经浏览器 fetch、不经页面服务子进程代理)。
|
|
|
|
|
|
* esp32_web.vbs / 浏览器标签:无 pywebview,本模块返回 null,调用方回落 HTTP。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 电机页在 tabs.html 的 iframe 内:pywebview 只注入顶层窗口,同源 iframe
|
|
|
|
|
|
* 必须向上找 window.parent / window.top 上的 pywebview.api。
|
2026-09-21 02:11:49 +08:00
|
|
|
|
*
|
|
|
|
|
|
* PDO 快发(pdo-fast-send):桌面壳且 native 带 fast_send_port 时,ECAT
|
|
|
|
|
|
* motor 发送优先 desktopEcatSendRaw(GUI 侧 ESF1 二进制直通入队)。
|
2026-09-20 22:58:00 +08:00
|
|
|
|
*/
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
|
|
function collectApi() {
|
|
|
|
|
|
const roots = [];
|
|
|
|
|
|
try { roots.push(window); } catch (_) {}
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (window.parent && window.parent !== window) roots.push(window.parent);
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (window.top && window.top !== window) roots.push(window.top);
|
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
for (const w of roots) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const api = w.pywebview && w.pywebview.api;
|
|
|
|
|
|
if (api && typeof api.ecat_api === "function") return api;
|
|
|
|
|
|
} catch (_) { /* 跨源 parent 会抛 */ }
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let _api = null;
|
|
|
|
|
|
|
|
|
|
|
|
/** @description 取桌面桥 API;没有则 null(不缓存失败,pywebview 可能稍后注入)。 */
|
|
|
|
|
|
export function getDesktopEcatApi() {
|
|
|
|
|
|
if (_api && typeof _api.ecat_api === "function") return _api;
|
|
|
|
|
|
_api = collectApi();
|
|
|
|
|
|
return _api;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** @description 是否跑在 pywebview 桌面壳里(有壳但桥方法未就绪时也可能 true)。 */
|
|
|
|
|
|
export function isDesktopShell() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return !!(window.pywebview ||
|
|
|
|
|
|
(window.parent && window.parent !== window && window.parent.pywebview) ||
|
|
|
|
|
|
(window.top && window.top !== window && window.top.pywebview));
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 桌面桥 POST。path 接受 "motor/send" / "/api/motor/send" / "/ecat/api/motor/send"。
|
|
|
|
|
|
* @returns {Promise<object>} 与 native 同构的 JSON
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function desktopEcatPost(path, body, timeoutMs) {
|
|
|
|
|
|
const api = getDesktopEcatApi();
|
|
|
|
|
|
if (!api) throw new Error("desktop bridge unavailable");
|
|
|
|
|
|
return api.ecat_api(path, body == null ? {} : body, timeoutMs || 4000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-21 02:11:49 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 桌面桥 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");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-20 22:58:00 +08:00
|
|
|
|
/** @description 状态栏用:当前 ECAT 控制通道形态。 */
|
|
|
|
|
|
export function desktopBridgeLabel() {
|
2026-09-21 02:11:49 +08:00
|
|
|
|
if (!getDesktopEcatApi()) return "HTTP 代理";
|
|
|
|
|
|
return hasDesktopFastSend() ? "APP 二进制快发" : "APP 原生桥";
|
2026-09-20 22:58:00 +08:00
|
|
|
|
}
|