3ad29356c9
集成通用机器人数值接口、本机控制桥、LeRobot 插件和统一键盘遥操作。采用离线 CoACD 全臂碰撞配方 revision 4、局部装配区切分与结构自接触,限制直接关节位姿写入并保留安全看门狗。同步版本号、变更记录、来源许可证和兼容性验证。
83 lines
2.7 KiB
TypeScript
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;
|
|
}
|
|
}
|