Upload project files
This commit is contained in:
88
kdl-wasm/web/tests/runtime/ioRuntime.test.ts
Normal file
88
kdl-wasm/web/tests/runtime/ioRuntime.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { IoImageRuntime, ioKey } from "../../src/runtime/index.js";
|
||||
import type { IoReference, PulseInstruction, WaitInstruction } from "../../src/grl/ir/index.js";
|
||||
|
||||
const DO1: IoReference = { domain: "do", index: 1, raw: "io.do[1]" };
|
||||
const DI1: IoReference = { domain: "di", index: 1, raw: "io.di[1]" };
|
||||
const DI2: IoReference = { domain: "di", index: 2, raw: "io.di[2]" };
|
||||
const AI1: IoReference = { domain: "ai", index: 1, raw: "io.ai[1]" };
|
||||
|
||||
describe("virtual IO image, waits, pulses, edges, and scripts", () => {
|
||||
it("enforces write permissions and records IO events", () => {
|
||||
const io = new IoImageRuntime({
|
||||
permissions: [
|
||||
{ writer: "program", domains: ["do"], access: "write" },
|
||||
{ writer: "script", domains: ["di", "ai"], access: "write" }
|
||||
]
|
||||
});
|
||||
|
||||
expect(io.write(DO1, true, "program")).toMatchObject({ kind: "write", value: true });
|
||||
expect(io.write(DI1, true, "program")).toBeUndefined();
|
||||
expect(io.snapshot()).toMatchObject({
|
||||
image: { [ioKey(DO1)]: true },
|
||||
diagnostics: [expect.objectContaining({ code: "VC_IO_PERMISSION_DENIED" })]
|
||||
});
|
||||
});
|
||||
|
||||
it("evaluates waits, timeouts, on_timeout hold-stop, and edge conditions", () => {
|
||||
const io = new IoImageRuntime({
|
||||
permissions: [{ writer: "script", domains: ["di", "ai"], access: "write" }]
|
||||
});
|
||||
const wait: WaitInstruction = {
|
||||
kind: "WAIT",
|
||||
condition: "all ( io . di [ 1 ] == true , io . di [ 2 ] == false )",
|
||||
timeout: 1,
|
||||
onTimeout: { kind: "call", value: "recover" }
|
||||
};
|
||||
|
||||
expect(io.evaluateWait(wait, 0)).toMatchObject({ status: "waiting" });
|
||||
expect(io.evaluateWait(wait, 1)).toMatchObject({
|
||||
status: "hold-stop",
|
||||
onTimeout: { kind: "call", value: "recover" },
|
||||
diagnostic: { code: "VC_WAIT_TIMEOUT" }
|
||||
});
|
||||
|
||||
io.write(DI1, true, "script");
|
||||
io.write(DI2, false, "script");
|
||||
expect(io.evaluateWait(wait, 0)).toMatchObject({ status: "satisfied" });
|
||||
expect(io.matchesCondition("rising ( io . di [ 1 ] )")).toBe(true);
|
||||
io.write(DI1, false, "script");
|
||||
expect(io.matchesCondition("falling ( io . di [ 1 ] )")).toBe(true);
|
||||
io.write(AI1, 5, "script");
|
||||
expect(io.matchesCondition("changed ( io . ai [ 1 ] )")).toBe(true);
|
||||
});
|
||||
|
||||
it("auto-resets pulses and executes delayed IO scripts", () => {
|
||||
const io = new IoImageRuntime({
|
||||
permissions: [
|
||||
{ writer: "program", domains: ["do"], access: "write" },
|
||||
{ writer: "script", domains: ["di"], access: "write" }
|
||||
]
|
||||
});
|
||||
const pulse: PulseInstruction = {
|
||||
kind: "PULSE",
|
||||
target: DO1,
|
||||
duration: 0.2,
|
||||
trace: [
|
||||
{ time: 0, action: "set", target: DO1, value: true },
|
||||
{ time: 0.2, action: "reset", target: DO1, value: false }
|
||||
]
|
||||
};
|
||||
|
||||
io.executePulse(pulse);
|
||||
expect(io.read(DO1)).toBe(true);
|
||||
io.advance(0.2);
|
||||
expect(io.read(DO1)).toBe(false);
|
||||
|
||||
io.addScript("part-arrival", [{ delay: 0.5, target: DI1, value: true }]);
|
||||
io.advance(0.49);
|
||||
expect(io.read(DI1)).toBeUndefined();
|
||||
io.advance(0.01);
|
||||
expect(io.read(DI1)).toBe(true);
|
||||
expect(io.snapshot().events.map((event) => event.kind)).toEqual([
|
||||
"pulse_set",
|
||||
"pulse_reset",
|
||||
"script"
|
||||
]);
|
||||
});
|
||||
});
|
||||
157
kdl-wasm/web/tests/runtime/motionQueue.test.ts
Normal file
157
kdl-wasm/web/tests/runtime/motionQueue.test.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SemanticProgramIr } from "../../src/grl/ir/index.js";
|
||||
import type { PathPlanRequest, PathPlanResult, TrajectoryPoint } from "../../src/kdl/types.js";
|
||||
import { KdlRuntimeBridge, MotionQueue, type MotionPlanner } from "../../src/runtime/index.js";
|
||||
|
||||
const POSE = {
|
||||
position: [0, 0, 0] as [number, number, number],
|
||||
quaternion: [0, 0, 0, 1] as [number, number, number, number]
|
||||
};
|
||||
|
||||
function point(index: number, time: number, joints: number[], segmentId: string): TrajectoryPoint {
|
||||
return {
|
||||
index,
|
||||
time,
|
||||
dt: index === 0 ? 0 : time,
|
||||
s: time,
|
||||
sd: 1,
|
||||
sdd: 0,
|
||||
joints,
|
||||
jointVelocity: joints.map(() => 0),
|
||||
jointAcceleration: joints.map(() => 0),
|
||||
flange: POSE,
|
||||
tcp: POSE,
|
||||
motion: "MOVEJ",
|
||||
segmentId,
|
||||
diagnostics: []
|
||||
};
|
||||
}
|
||||
|
||||
function pathResult(request: PathPlanRequest): PathPlanResult {
|
||||
return {
|
||||
ok: true,
|
||||
duration: 1,
|
||||
segments: [],
|
||||
points: [point(0, 0, request.startJoints, request.segments[0]?.id ?? "p0"), point(1, 1, [1], request.segments[0]?.id ?? "p0")],
|
||||
diagnostics: []
|
||||
};
|
||||
}
|
||||
|
||||
function program(): SemanticProgramIr {
|
||||
return {
|
||||
moduleName: "Main",
|
||||
symbols: [],
|
||||
semanticChecks: [],
|
||||
diagnostics: [],
|
||||
sourceMap: [
|
||||
{ kind: "path_point", id: "p0", pathId: "pick_path", pointId: "p0", sourceMap: { line: 10 } },
|
||||
{ kind: "operation_action", id: "pick_op_start_action_0", operationId: "pick_op", sourceMap: { line: 20 } }
|
||||
],
|
||||
procedures: [
|
||||
{
|
||||
name: "main",
|
||||
instructions: [
|
||||
{ kind: "RUN_OPERATION", operationId: "pick_op", sourceMap: { line: 30 } }
|
||||
]
|
||||
}
|
||||
],
|
||||
paths: [
|
||||
{
|
||||
pathId: "pick_path",
|
||||
request: {
|
||||
pathId: "pick_path",
|
||||
startJoints: [0],
|
||||
sampleTime: 0.1,
|
||||
segments: [
|
||||
{
|
||||
id: "p0",
|
||||
motion: "MOVEJ",
|
||||
target: { joints: [1] },
|
||||
speed: { kind: "joint_abs", velocity: 1 },
|
||||
zone: { kind: "fine" },
|
||||
sourceMap: { line: 10 }
|
||||
}
|
||||
]
|
||||
},
|
||||
motions: [
|
||||
{
|
||||
kind: "MOVEJ",
|
||||
id: "p0",
|
||||
pathId: "pick_path",
|
||||
pointId: "p0",
|
||||
target: { joints: [1] },
|
||||
speed: { kind: "joint_abs", velocity: 1 },
|
||||
zone: { kind: "fine" },
|
||||
sourceMap: { line: 10 },
|
||||
source: { brand: { vendor: "ABB", file: "cell.mod", line: 42 } }
|
||||
}
|
||||
],
|
||||
events: []
|
||||
}
|
||||
],
|
||||
operations: [
|
||||
{
|
||||
operationId: "pick_op",
|
||||
kind: "handling",
|
||||
pathId: "pick_path",
|
||||
process: {},
|
||||
startActions: [
|
||||
{
|
||||
kind: "ACTION",
|
||||
actionKind: "start_action",
|
||||
operationId: "pick_op",
|
||||
statement: "io.do[1] = true",
|
||||
sourceMap: { line: 20 }
|
||||
}
|
||||
],
|
||||
endActions: [
|
||||
{
|
||||
kind: "ACTION",
|
||||
actionKind: "end_action",
|
||||
operationId: "pick_op",
|
||||
statement: "io.do[1] = false",
|
||||
sourceMap: { line: 22 }
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
kdlBridge: {
|
||||
motionRequests: [],
|
||||
pathRequests: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe("motion queue and KDL runtime bridge", () => {
|
||||
it("preserves operation/path/action sources and samples planned paths by virtual time", async () => {
|
||||
const plannerCalls: PathPlanRequest[] = [];
|
||||
const planner: MotionPlanner = {
|
||||
planPath: (_handle, request) => {
|
||||
plannerCalls.push(request);
|
||||
return pathResult(request);
|
||||
}
|
||||
};
|
||||
const queue = new MotionQueue({ startJoints: [0], sampleTime: 0.1, planner, robotHandle: 7 });
|
||||
const ir = program();
|
||||
const bridge = new KdlRuntimeBridge(ir, queue);
|
||||
|
||||
const items = bridge.enqueueInstruction(ir.procedures[0]!.instructions[0]!);
|
||||
expect(items.map((item) => item.kind)).toEqual(["action", "path", "action"]);
|
||||
expect(items[0]).toMatchObject({ source: { operationId: "pick_op", sourceMap: { line: 20 } } });
|
||||
expect(items[1]).toMatchObject({ source: { pathId: "pick_path", operationId: "pick_op" } });
|
||||
|
||||
await queue.planAll();
|
||||
expect(plannerCalls).toHaveLength(1);
|
||||
expect(plannerCalls[0]).toMatchObject({ pathId: "pick_path", startJoints: [0] });
|
||||
|
||||
expect(queue.advance(0.5)).toMatchObject({
|
||||
itemId: items[1]!.id,
|
||||
localTime: 0.5,
|
||||
point: { joints: [1] },
|
||||
source: { pathId: "pick_path", operationId: "pick_op" }
|
||||
});
|
||||
expect(queue.snapshot().items[1]).toMatchObject({
|
||||
planned: { ok: true, duration: 1 }
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user