196 lines
6.6 KiB
TypeScript
196 lines
6.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { SemanticProgramIr } from "../../src/grl/ir/index.js";
|
|
import type { PathPlanResult, TrajectoryPoint } from "../../src/kdl/types.js";
|
|
import { IrExecutionRuntime } from "../../src/controller/index.js";
|
|
import { IoImageRuntime, MotionQueue, type MotionPlanner } from "../../src/runtime/index.js";
|
|
import { DebugFacade, WorkbenchFacade } from "../../src/workbench/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[]): TrajectoryPoint {
|
|
return {
|
|
index,
|
|
time,
|
|
dt: time,
|
|
s: time,
|
|
sd: 1,
|
|
sdd: 0,
|
|
joints,
|
|
jointVelocity: [0],
|
|
jointAcceleration: [0],
|
|
flange: POSE,
|
|
tcp: POSE,
|
|
motion: "MOVEJ",
|
|
diagnostics: []
|
|
};
|
|
}
|
|
|
|
function program(): SemanticProgramIr {
|
|
return {
|
|
moduleName: "Main",
|
|
symbols: [{ kind: "path", name: "path1" }, { kind: "operation", name: "op1" }],
|
|
semanticChecks: [],
|
|
diagnostics: [],
|
|
sourceMap: [
|
|
{ kind: "RUN_PATH", id: "run_path", procedureId: "main", pathId: "path1", sourceMap: { file: "main.grl", line: 5 } },
|
|
{ kind: "path_point", id: "p1", pathId: "path1", pointId: "p1", sourceMap: { file: "main.grl", line: 10 } },
|
|
{ kind: "operation_action", id: "op1_start_action_0", operationId: "op1", sourceMap: { file: "main.grl", line: 20 } }
|
|
],
|
|
procedures: [
|
|
{
|
|
name: "main",
|
|
sourceMap: { file: "main.grl", line: 1 },
|
|
instructions: [
|
|
{ kind: "RAW_STATEMENT", text: "part_ready = true", sourceMap: { file: "main.grl", line: 4 } },
|
|
{ kind: "RUN_PATH", pathId: "path1", sourceMap: { file: "main.grl", line: 5 } }
|
|
]
|
|
}
|
|
],
|
|
paths: [
|
|
{
|
|
pathId: "path1",
|
|
request: {
|
|
pathId: "path1",
|
|
startJoints: [0],
|
|
sampleTime: 0.1,
|
|
segments: [
|
|
{
|
|
id: "p1",
|
|
motion: "MOVEJ",
|
|
target: { joints: [1] },
|
|
speed: { kind: "joint_abs", velocity: 1 },
|
|
zone: { kind: "fine" },
|
|
sourceMap: { file: "main.grl", line: 10 }
|
|
}
|
|
]
|
|
},
|
|
motions: [
|
|
{
|
|
kind: "MOVEJ",
|
|
id: "p1",
|
|
pathId: "path1",
|
|
pointId: "p1",
|
|
target: { joints: [1] },
|
|
speed: { kind: "joint_abs", velocity: 1 },
|
|
zone: { kind: "fine" },
|
|
sourceMap: { file: "main.grl", line: 10 },
|
|
source: { brand: { vendor: "KUKA", file: "src.src", line: 99 } }
|
|
}
|
|
],
|
|
events: []
|
|
}
|
|
],
|
|
operations: [
|
|
{
|
|
operationId: "op1",
|
|
kind: "handling",
|
|
pathId: "path1",
|
|
process: {},
|
|
startActions: [],
|
|
endActions: []
|
|
}
|
|
],
|
|
kdlBridge: { motionRequests: [], pathRequests: [] }
|
|
};
|
|
}
|
|
|
|
describe("debug and workbench facades", () => {
|
|
it("models breakpoints, motion breakpoints, watches, replay, and cross-source lookup", async () => {
|
|
const ir = program();
|
|
const runtime = new IrExecutionRuntime();
|
|
runtime.load(ir);
|
|
runtime.step();
|
|
|
|
const planner: MotionPlanner = {
|
|
planPath: (_handle, request): PathPlanResult => ({
|
|
ok: true,
|
|
duration: 1,
|
|
segments: [],
|
|
points: [point(0, 0, request.startJoints), point(1, 1, [1])],
|
|
diagnostics: []
|
|
})
|
|
};
|
|
const queue = new MotionQueue({ startJoints: [0], sampleTime: 0.1, planner });
|
|
const item = queue.enqueueRunPath({ kind: "RUN_PATH", pathId: "path1", sourceMap: { file: "main.grl", line: 5 } }, ir.paths[0]!);
|
|
await queue.planAll();
|
|
queue.advance(1);
|
|
|
|
const io = new IoImageRuntime();
|
|
const debug = new DebugFacade({ runtime, motionQueue: queue, io, program: ir });
|
|
debug.addBreakpoint({ id: "bp-run-path", procedure: "main", source: { line: 5 }, enabled: true });
|
|
debug.addMotionBreakpoint({ id: "mb-path", pathId: "path1", enabled: true });
|
|
const watch = debug.addWatch("watch-ready", "part_ready");
|
|
|
|
expect(watch).toMatchObject({ value: true, source: { sourceMap: { line: 5 } } });
|
|
expect(debug.checkMotionBreakpoint(item)).toMatchObject({ id: "mb-path" });
|
|
expect(debug.locatePathPoint("path1", "p1")).toMatchObject({ sourceMap: { line: 10 } });
|
|
expect(debug.locateOperation("op1")).toMatchObject([{ sourceMap: { line: 20 } }]);
|
|
expect(debug.playbackAt(1)).toMatchObject({ point: { joints: [1] } });
|
|
expect(debug.replayTrace()).toEqual(expect.arrayContaining([expect.objectContaining({ kind: "load" })]));
|
|
expect(debug.snapshot()).toMatchObject({
|
|
breakpoints: [expect.objectContaining({ id: "bp-run-path" })],
|
|
motionBreakpoints: [expect.objectContaining({ id: "mb-path" })],
|
|
watches: [expect.objectContaining({ id: "watch-ready", value: true })]
|
|
});
|
|
});
|
|
|
|
it("builds object tree, editor, teach pendant, IO panel, and report entries", async () => {
|
|
const ir = program();
|
|
const runtime = new IrExecutionRuntime();
|
|
runtime.load(ir);
|
|
runtime.step();
|
|
|
|
const queue = new MotionQueue({
|
|
startJoints: [0],
|
|
sampleTime: 0.1,
|
|
planner: {
|
|
planPath: (_handle, request): PathPlanResult => ({
|
|
ok: true,
|
|
duration: 1,
|
|
segments: [],
|
|
points: [point(0, 0, request.startJoints), point(1, 1, [1])],
|
|
diagnostics: []
|
|
})
|
|
}
|
|
});
|
|
queue.enqueueRunPath({ kind: "RUN_PATH", pathId: "path1" }, ir.paths[0]!);
|
|
await queue.planAll();
|
|
queue.advance(1);
|
|
|
|
const io = new IoImageRuntime();
|
|
io.write({ domain: "do", index: 1, raw: "io.do[1]" }, true);
|
|
|
|
const workbench = new WorkbenchFacade();
|
|
const snapshot = workbench.snapshot({
|
|
program: ir,
|
|
runtime: runtime.snapshot(),
|
|
io: io.snapshot(),
|
|
motion: queue.snapshot(),
|
|
controllerState: "paused"
|
|
});
|
|
|
|
expect(snapshot.objectTree[0]).toMatchObject({
|
|
kind: "station",
|
|
children: [
|
|
expect.objectContaining({ id: "programs" }),
|
|
expect.objectContaining({ id: "paths" }),
|
|
expect.objectContaining({ id: "operations" }),
|
|
expect.objectContaining({ id: "reports" })
|
|
]
|
|
});
|
|
expect(snapshot.editor).toMatchObject({ activeFile: "main.grl", cursor: { line: 5 } });
|
|
expect(snapshot.teachPendant).toMatchObject({ state: "paused", procedure: "main", joints: [1] });
|
|
expect(snapshot.ioPanel.image).toMatchObject({ "io.do[1]": true });
|
|
expect(snapshot.reports.map((report) => report.kind)).toEqual([
|
|
"reachability",
|
|
"cycle_time",
|
|
"io_wait",
|
|
"post",
|
|
"import"
|
|
]);
|
|
});
|
|
});
|