Files
Robot_Arm/webapp/js/ik-policy-glue.js
T

74 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================================
* ik-policy-glue.js —— 查看器接入 Transformer 策略(URL 加 ?policy=1 启用)
* ?policy=1 用 models/traj_policy.onnx
* ?policy=models/xxx.onnx 指定模型
* &ep=webgpu 改用 WebGPU 后端(默认 wasm-SIMD 多线程)
* 目标变化(松手求解 / 实时追踪)都交给 ArmPolicy.Runner:50 Hz 执行时钟放在 Web Worker(不受 rAF 60 Hz 与后台节流影响),
* 每 3 步推理一次,安全层认证后才执行;ik-glue 的逐帧碰撞守卫不参与(策略执行不走 animTo),由 Runner 安全层保证。
* onnxruntime-web 默认从 jsDelivr 加载;离线部署把 ort.min.js 与 *.wasm 放到 vendor/ort/ 并加 &ortLocal=1。
* 多线程 wasm 需要页面跨源隔离(COOP/COEP 头,见 ml/README.md);没有时自动退回单线程。
* ==========================================================================*/
(function () {
'use strict';
const qs = new URLSearchParams(location.search);
if (!qs.has('policy')) return;
const MODEL = qs.get('policy') && qs.get('policy') !== '1' ? qs.get('policy') : 'models/traj_policy.onnx';
const ORT_BASE = qs.get('ortLocal') === '1' ? 'vendor/ort/' : 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/';
const badge = () => document.getElementById('ikBadge');
const say = (t, cls) => { const b = badge(); if (b) { b.textContent = t; b.className = 'badge ' + (cls || 'wait'); } console.log('[policy]', t); };
let ready = false, runner = null, running = false, worker = null;
window.__POLICY = { active: () => ready, setGoal: () => false, stats: () => runner && runner.stats };
function loadScript(src) { return new Promise((res, rej) => { const s = document.createElement('script'); s.src = src; s.onload = res; s.onerror = () => rej(new Error('load ' + src)); document.head.appendChild(s); }); }
async function waitIK() { for (let i = 0; i < 200 && !(window.IK && window.IK.solver && window.ArmPolicy); i++) await new Promise(r => setTimeout(r, 50)); if (!window.IK) throw new Error('IK 未就绪'); }
async function boot() {
try {
await waitIK();
const S = window.IK.solver;
const meta = await (await fetch(MODEL.replace('.onnx', '.meta.json'))).json();
if (meta.fp !== S.modelFingerprint()) throw new Error('模型碰撞指纹 ' + meta.fp + ' ≠ 当前 ' + S.modelFingerprint() + ':collide.json 已变,须重训');
if (meta.feat_ver !== ArmPolicy.FEAT_VER) throw new Error('特征版本不符:模型 ' + meta.feat_ver + ' / 运行时 ' + ArmPolicy.FEAT_VER);
if (meta.tcp0 && meta.tcp0.some((v, i) => Math.abs(v - S.TCP0[i]) > 0.01)) throw new Error('TCP 与训练时不同:' + S.TCP0 + ' vs ' + meta.tcp0);
if (!window.ort) await loadScript(ORT_BASE + 'ort.min.js');
ort.env.wasm.wasmPaths = ORT_BASE;
ort.env.wasm.numThreads = self.crossOriginIsolated ? Math.min(4, navigator.hardwareConcurrency || 1) : 1;
const ep = qs.get('ep') === 'webgpu' ? ['webgpu', 'wasm'] : ['wasm'];
const sess = await ort.InferenceSession.create(MODEL, { executionProviders: ep, graphOptimizationLevel: 'all' });
runner = new ArmPolicy.Runner(S, ArmPolicy.ortInfer(ort, sess), {});
/* 预热:首次推理有编译开销 */
await ArmPolicy.ortInfer(ort, sess)(new Float32Array(8 * ArmPolicy.F_DIM), new Float32Array(8 * ArmPolicy.H * 6), 8);
const tick = URL.createObjectURL(new Blob(['let h=null;onmessage=e=>{clearInterval(h);if(e.data>0)h=setInterval(()=>postMessage(0),e.data)}'], { type: 'text/javascript' }));
worker = new Worker(tick);
worker.onmessage = onTick;
window.__POLICY = {
active: () => ready,
setGoal: (T, R, tool) => {
if (tool) { say('策略模式暂不支持工具偏移 ≠ 0,已交回规划器', 'badb'); return false; }
if (!running || !runner.q) runner.reset(window.IK.curQ());
runner.setGoal(T, R); start(); return true;
},
stop, stats: () => runner.stats, runner: () => runner,
latency: () => { const p = (a, x) => { const s = a.slice().sort((u, v) => u - v); return s.length ? s[Math.min(s.length - 1, Math.floor(s.length * x))] : null; };
return { inferP50: p(runner.stats.inferMs, .5), inferP99: p(runner.stats.inferMs, .99), safetyP50: p(runner.stats.safetyMs, .5), safetyP99: p(runner.stats.safetyMs, .99), threads: ort.env.wasm.numThreads, ep: ep[0] }; }
};
ready = true;
say('策略模式就绪(' + ep[0] + ',' + ort.env.wasm.numThreads + ' 线程):拖动目标即由策略执行', 'ok');
} catch (e) { say('策略模式不可用:' + e.message, 'badb'); console.error(e); }
}
function start() { if (running) return; running = true; worker.postMessage(ArmPolicy.DT * 1000); }
function stop() { running = false; if (worker) worker.postMessage(0); }
function onTick() {
if (!running) return;
if (runner.needPlan()) runner.plan().catch(e => { console.error(e); stop(); say('策略推理出错:' + e.message, 'badb'); });
const q = runner.tick();
if (q) window.IK.setJoints(q, false);
if (runner.done && !runner.queue.length) {
stop();
const { e, oe } = runner.err(runner.q);
say('策略到达 · 误差 ' + e.toFixed(2) + ' mm' + (runner.goal.R ? ' / ' + oe.toFixed(2) + '°' : '') + ' · 刹停 ' + runner.stats.brakes + ' 次', 'ok');
} else if (runner.brakeRun >= 2) say('策略:安全层连续拦截,减速重规划中…', 'wait');
}
boot();
})();