Files
Mujoco_WASM/web_platform/e2e/fixtures/controlBridge.ts
T
chenlin 3ad29356c9
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
feat(lekiwi): release V0.10.1 初步集成 LeKiwi,优化碰撞模型
集成通用机器人数值接口、本机控制桥、LeRobot 插件和统一键盘遥操作。采用离线 CoACD 全臂碰撞配方 revision 4、局部装配区切分与结构自接触,限制直接关节位姿写入并保留安全看门狗。同步版本号、变更记录、来源许可证和兼容性验证。
2026-09-20 14:42:30 +08:00

83 lines
2.7 KiB
TypeScript

import { spawn, execFile, type ChildProcess } from 'node:child_process';
import { randomBytes } from 'node:crypto';
import { existsSync } from 'node:fs';
import { resolve } from 'node:path';
import { promisify } from 'node:util';
const execFileAsync = promisify(execFile);
export const bridgePython =
process.env.BRIDGE_PYTHON ??
(existsSync(resolve('.venv/bin/python')) ? resolve('.venv/bin/python') : 'python3');
export async function stopProcess(child: ChildProcess): Promise<void> {
if (child.exitCode !== null || child.signalCode !== null) return;
await new Promise<void>((resolve) => {
const timer = setTimeout(() => child.kill('SIGKILL'), 3000);
child.once('exit', () => {
clearTimeout(timer);
resolve();
});
child.kill('SIGTERM');
});
}
export async function startBridge() {
const token = randomBytes(32).toString('hex');
const child = spawn(bridgePython, ['-m', 'mujoco_control_bridge', '--port', '0'], {
env: { ...process.env, PYTHONPATH: '', PYTHONNOUSERSITE: '1', MUJOCO_CONTROL_TOKEN: token },
stdio: ['ignore', 'pipe', 'pipe'],
});
let output = '',
errors = '';
child.stderr.on('data', (chunk: Buffer) => {
errors = (errors + chunk.toString()).slice(-65536);
});
try {
const endpoint = await new Promise<string>((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error(`桥接启动超时:${errors}`)), 15000);
child.once('error', (error) => {
clearTimeout(timeout);
reject(error);
});
child.once('exit', () => {
clearTimeout(timeout);
reject(new Error(`桥接启动失败:${errors}`));
});
child.stdout.on('data', (chunk: Buffer) => {
output += chunk.toString();
const ready = output.match(/CONTROL_BRIDGE_READY (\{[^\n]+\})/);
if (ready) {
clearTimeout(timeout);
resolve((JSON.parse(ready[1]) as { endpoint: string }).endpoint);
}
});
});
return {
endpoint,
token,
stop: () => stopProcess(child),
errors: () => errors,
python: async (
script: string,
python = bridgePython,
environment: Record<string, string> = {},
) => {
const { stdout, stderr } = await execFileAsync(python, ['-c', script], {
env: {
...process.env,
PYTHONPATH: '',
PYTHONNOUSERSITE: '1',
...environment,
MUJOCO_CONTROL_TOKEN: token,
MUJOCO_CONTROL_ENDPOINT: endpoint,
},
timeout: 120000,
maxBuffer: 4 * 1024 * 1024,
});
return { stdout, stderr };
},
};
} catch (error) {
await stopProcess(child);
throw error;
}
}