3ad29356c9
集成通用机器人数值接口、本机控制桥、LeRobot 插件和统一键盘遥操作。采用离线 CoACD 全臂碰撞配方 revision 4、局部装配区切分与结构自接触,限制直接关节位姿写入并保留安全看门狗。同步版本号、变更记录、来源许可证和兼容性验证。
98 lines
4.5 KiB
TypeScript
98 lines
4.5 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
||
import { resolve } from 'node:path';
|
||
import { writeFile } from 'node:fs/promises';
|
||
import type {} from '../physics/runner';
|
||
|
||
test('真实 WASM 3.11:LeKiwi 站稳、全向轮、臂夹爪与障碍阻挡', async ({ page }, info) => {
|
||
const errors: string[] = [];
|
||
page.on('pageerror', (e) => errors.push(e.message));
|
||
page.on('console', (msg) => {
|
||
if (msg.type() === 'error') console.log(msg.text());
|
||
});
|
||
await page.goto('/physics/runner.html');
|
||
await page.waitForFunction(() => Boolean(window.lekiwiPhysics));
|
||
const assetBase = `/@fs${resolve(process.cwd(), 'build/lekiwi')}`;
|
||
const initial = await page.evaluate((path) => window.lekiwiPhysics.boot(path), assetBase);
|
||
console.log('initial', initial);
|
||
expect(initial.version).toBe('3.11.0');
|
||
expect(initial.nu).toBe(9);
|
||
expect(initial.njnt).toBe(46);
|
||
await writeFile(
|
||
info.outputPath('lekiwi-profile.xml'),
|
||
await page.evaluate(() => window.lekiwiPhysics.xml()),
|
||
);
|
||
const stable = await page.evaluate(() => window.lekiwiPhysics.step(30));
|
||
console.log('stable', stable);
|
||
expect(stable.finite).toBe(true);
|
||
expect(Math.abs(stable.roll)).toBeLessThan(0.15);
|
||
expect(Math.abs(stable.pitch)).toBeLessThan(0.15);
|
||
const traces: unknown[] = [{ initial, stable }];
|
||
for (const [x, y, w] of [
|
||
[0.1, 0, 0],
|
||
[-0.1, 0, 0],
|
||
[0, 0.1, 0],
|
||
[0, -0.1, 0],
|
||
[0, 0, Math.PI / 6],
|
||
[0, 0, -Math.PI / 6],
|
||
]) {
|
||
const start = await page.evaluate(() => window.lekiwiPhysics.reset());
|
||
const end = await page.evaluate(
|
||
([x, y, w]) => window.lekiwiPhysics.drive(x, y, w, 2),
|
||
[x, y, w],
|
||
);
|
||
console.log('drive', x, y, w, 'start', start.position, start.yaw, 'end', end.position, end.yaw);
|
||
traces.push({ command: [x, y, w], start, end });
|
||
expect(end.finite).toBe(true);
|
||
if (x || y) {
|
||
expect(Math.abs(end.position[0] - start.position[0] - x * 2)).toBeLessThan(0.05);
|
||
expect(Math.abs(end.position[1] - start.position[1] - y * 2)).toBeLessThan(0.05);
|
||
} else expect(Math.abs(end.yaw - start.yaw - w * 2)).toBeLessThan((10 * Math.PI) / 180);
|
||
}
|
||
await page.evaluate(() => window.lekiwiPhysics.reset());
|
||
// Free-space tracking; negative elbow folding now has its own obstruction test.
|
||
const targets = [0.1, 0.1, 0.1, 0.1, 0.1, 0.3];
|
||
const arm = await page.evaluate((values) => window.lekiwiPhysics.arm(values, 2), targets);
|
||
console.log('arm', arm.arm);
|
||
traces.push({ targets, arm });
|
||
arm.arm.forEach((value, i) => expect(Math.abs(value - targets[i])).toBeLessThan(0.015));
|
||
await page.evaluate(() => window.lekiwiPhysics.wall());
|
||
const blocked = await page.evaluate(() => window.lekiwiPhysics.drive(0.1, 0, 0, 5));
|
||
console.log('blocked', blocked);
|
||
const contacts = await page.evaluate(() => window.lekiwiPhysics.contactPairs());
|
||
traces.push({ blocked, contacts });
|
||
expect(contacts.some((c) => c.geom1 === 'test_wall' || c.geom2 === 'test_wall')).toBe(true);
|
||
expect(blocked.maxContacts).toBeGreaterThan(0);
|
||
expect(blocked.position[0]).toBeLessThan(0.22);
|
||
// The complete arm/accessory outline reaches the wall before the old chassis proxy.
|
||
expect(blocked.position[0]).toBeGreaterThan(0.02);
|
||
expect(blocked.finite).toBe(true);
|
||
await writeFile(info.outputPath('physics-traces.json'), JSON.stringify(traces, null, 2));
|
||
expect(errors).toEqual([]);
|
||
await page.evaluate(() => window.lekiwiPhysics.dispose());
|
||
});
|
||
|
||
test('真实 Python/WASM:控制权、旧回调隔离、安全保持、reset 与导出重载', async ({ page }) => {
|
||
await page.goto('/physics/runner.html');
|
||
await page.waitForFunction(() => Boolean(window.lekiwiPhysics));
|
||
await page.evaluate(
|
||
(path) => window.lekiwiPhysics.boot(path),
|
||
`/@fs${resolve(process.cwd(), 'build/lekiwi')}`,
|
||
);
|
||
const result = await page.evaluate(() => window.lekiwiPhysics.runtimeChecks());
|
||
expect(result.pythonTarget).toBe(0.1);
|
||
expect(result.accepted.values['arm_shoulder_pan.pos']).toBe(0.3);
|
||
expect(result.afterOldDispose).toBe(0.3);
|
||
expect(result.manualBlocked).toBe(true);
|
||
expect(result.held).toBeCloseTo(result.actual, 10);
|
||
expect(result.paused?.enabled).toBe(false);
|
||
expect(result.pausedOwner).toBe('manual');
|
||
expect(result.cancel).toBe('DISCONNECTED');
|
||
expect(result.reset?.modelEpoch).toBe(1);
|
||
expect(result.reset?.paused).toBe(true);
|
||
const reloaded = await page.evaluate(() => window.lekiwiPhysics.reimport());
|
||
expect(reloaded.nu).toBe(9);
|
||
expect(reloaded.njnt).toBe(46);
|
||
expect(reloaded.robot?.profileId).toBe('lekiwi-v1');
|
||
await page.evaluate(() => window.lekiwiPhysics.dispose());
|
||
});
|