Add ABB120 demo suite and docs

This commit is contained in:
wangdequan
2026-06-28 20:58:18 +08:00
parent 84a5b54195
commit 65187499d6
820 changed files with 215410 additions and 211 deletions

View File

@@ -0,0 +1,19 @@
language grl 0.1
module W2MotionDiagnostics
const speed v = joint(30 %)
const speed vl = linear(100 mm/s)
const zone zf = fine
target too_far_joint = joint_target { joints: [220 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
target unreachable_pose = pose_target { pose: pose(2000 mm, 0 mm, 2000 mm, 0 deg, 0 deg, 0 deg) }
target arc_a = pose_target { pose: pose(500 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) }
target arc_b = pose_target { pose: pose(550 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) }
target arc_c = pose_target { pose: pose(600 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) }
path empty_path {
}
proc main()
movej too_far_joint speed v zone zf
movel unreachable_pose speed vl zone zf
movec via arc_b target arc_c speed vl zone zf
run_path empty_path
end
end

View File

@@ -0,0 +1,14 @@
language grl 0.1
module W2RuntimeTimeout
const speed v = joint(30 %)
const zone zf = fine
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
proc on_timeout_recover()
alarm TIMEOUT_RECOVER "timeout recover" severity warning
end
proc main()
wait io.di[99] == true timeout 100 ms on_timeout alarm "DI99 timeout"
wait io.di[98] == true timeout 100 ms on_timeout call on_timeout_recover()
movej home speed v zone zf
end
end

View File

@@ -0,0 +1,52 @@
{
"suite_id": "working2-abb120-spec",
"robot_id": "abb_irb120_3_58",
"spec_source": "work/doc/通用机器人编程语法规范.md",
"program_root": "kdl-wasm/web/tests/fixtures/abb120/spec-programs",
"runtime_commands": ["loadProgram", "start", "stepInto", "hold", "resume", "stepMotion", "stop", "resetFault"],
"programs": [
{ "program_id": "W2_00_MinimalModule", "file": "runtime/W2_00_MinimalModule.grl", "coverage_level": "Runtime", "spec_sections": [4, 5, 6, 9, 10, 23], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_10_DataTargetToolFrame", "file": "runtime/W2_10_DataTargetToolFrame.grl", "coverage_level": "Runtime", "spec_sections": [6, 8, 9], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_20_ExpressionMath", "file": "runtime/W2_20_ExpressionMath.grl", "coverage_level": "Runtime", "spec_sections": [7, 9, 13], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_30_MotionAllTypes", "file": "runtime/W2_30_MotionAllTypes.grl", "coverage_level": "Runtime", "spec_sections": [8, 9, 10], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_40_PathEvents", "file": "runtime/W2_40_PathEvents.grl", "coverage_level": "Runtime", "spec_sections": [11, 13, 19], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_50_OperationProcess", "file": "runtime/W2_50_OperationProcess.grl", "coverage_level": "Runtime", "spec_sections": [12, 13], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_60_IOWaitPulse", "file": "runtime/W2_60_IOWaitPulse.grl", "coverage_level": "Runtime", "spec_sections": [13], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_70_ControlFlow", "file": "runtime/W2_70_ControlFlow.grl", "coverage_level": "Runtime", "spec_sections": [14], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_80_ProcFuncCall", "file": "runtime/W2_80_ProcFuncCall.grl", "coverage_level": "Runtime", "spec_sections": [15], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_90_ExceptionAlarm", "file": "runtime/W2_90_ExceptionAlarm.grl", "coverage_level": "Runtime", "spec_sections": [16, 17], "expected_status": "pass", "expected_diagnostics": ["GRL_P1_UNIMPLEMENTED"] },
{ "program_id": "W2_99_FullSpecExample", "file": "runtime/W2_99_FullSpecExample.grl", "coverage_level": "Runtime", "spec_sections": [24], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_E10_ExpressionDiagnostics", "file": "static/W2_E10_ExpressionDiagnostics.grl", "coverage_level": "Static", "spec_sections": [7, 22], "expected_status": "diagnostic", "expected_diagnostics": ["GRL_EXPR_UNKNOWN_SYMBOL", "GRL_EXPR_UNKNOWN_FUNCTION", "GRL_EXPR_ARITY", "GRL_EXPR_DOMAIN", "GRL_EXPR_DIV_ZERO", "GRL_EXPR_UNIT_MISMATCH"] },
{ "program_id": "W2_E20_SemanticDiagnostics", "file": "static/W2_E20_SemanticDiagnostics.grl", "coverage_level": "Static", "spec_sections": [6, 15, 22], "expected_status": "diagnostic", "expected_diagnostics": ["GRL_SYMBOL_DUPLICATE", "GRL_ARGUMENT_NOT_LVALUE", "GRL_CALL_TARGET_NOT_FOUND", "GRL_FUNC_SIDE_EFFECT", "GRL_TARGET_NOT_FOUND"] },
{ "program_id": "W2_E30_MotionDiagnostics", "file": "error/W2_E30_MotionDiagnostics.grl", "coverage_level": "Static", "spec_sections": [8, 9, 10, 11, 22], "expected_status": "diagnostic", "expected_diagnostics": ["KDL_JOINT_LIMIT", "KDL_TARGET_UNREACHABLE", "GRL_PATH_EMPTY"] },
{ "program_id": "W2_E40_ControlFlowDiagnostics", "file": "static/W2_E40_ControlFlowDiagnostics.grl", "coverage_level": "Static", "spec_sections": [14, 22], "expected_status": "diagnostic", "expected_diagnostics": ["GRL_BREAK_OUTSIDE_FLOW", "GRL_CONTINUE_OUTSIDE_LOOP", "GRL_JUMP_INTO_BLOCK", "GRL_SWITCH_CASE_DUPLICATE", "GRL_SWITCH_CASE_NOT_CONSTANT", "GRL_LABEL_NOT_FOUND"] },
{ "program_id": "W2_E50_RuntimeTimeout", "file": "error/W2_E50_RuntimeTimeout.grl", "coverage_level": "Runtime", "spec_sections": [13, 16], "expected_status": "diagnostic", "expected_diagnostics": ["VC_WAIT_TIMEOUT"] },
{ "program_id": "W2_P10_BrandHints", "file": "post/W2_P10_BrandHints.grl", "coverage_level": "Post", "spec_sections": [18, 20], "expected_status": "pass", "expected_diagnostics": ["GRL_POST_HINT_IGNORED"] },
{ "program_id": "W2_P20_CrossBrandMotion", "file": "post/W2_P20_CrossBrandMotion.grl", "coverage_level": "Post", "spec_sections": [20], "expected_status": "pass", "expected_diagnostics": [] },
{ "program_id": "W2_P30_Roundtrip", "file": "post/W2_P30_Roundtrip.grl", "coverage_level": "Post", "spec_sections": [19, 20, 21], "expected_status": "pass", "expected_diagnostics": [] }
],
"coverage_matrix": [
{ "spec_section": 4, "coverage_level": "Runtime/Static", "program_ids": ["W2_00_MinimalModule"] },
{ "spec_section": 5, "coverage_level": "Runtime", "program_ids": ["W2_00_MinimalModule"] },
{ "spec_section": 6, "coverage_level": "Runtime/Static", "program_ids": ["W2_10_DataTargetToolFrame", "W2_E20_SemanticDiagnostics"] },
{ "spec_section": 7, "coverage_level": "Runtime/Static/Post", "program_ids": ["W2_20_ExpressionMath", "W2_E10_ExpressionDiagnostics", "W2_P20_CrossBrandMotion"] },
{ "spec_section": 8, "coverage_level": "Runtime/Static", "program_ids": ["W2_10_DataTargetToolFrame", "W2_E30_MotionDiagnostics"] },
{ "spec_section": 9, "coverage_level": "Runtime/Post", "program_ids": ["W2_20_ExpressionMath", "W2_30_MotionAllTypes", "W2_P20_CrossBrandMotion"] },
{ "spec_section": 10, "coverage_level": "Runtime/Static/Post", "program_ids": ["W2_30_MotionAllTypes", "W2_E30_MotionDiagnostics", "W2_P20_CrossBrandMotion"] },
{ "spec_section": 11, "coverage_level": "Runtime/Static", "program_ids": ["W2_40_PathEvents", "W2_E30_MotionDiagnostics"] },
{ "spec_section": 12, "coverage_level": "Runtime/Post", "program_ids": ["W2_50_OperationProcess", "W2_P30_Roundtrip"] },
{ "spec_section": 13, "coverage_level": "Runtime", "program_ids": ["W2_60_IOWaitPulse", "W2_E50_RuntimeTimeout"] },
{ "spec_section": 14, "coverage_level": "Runtime/Static", "program_ids": ["W2_70_ControlFlow", "W2_E40_ControlFlowDiagnostics"] },
{ "spec_section": 15, "coverage_level": "Runtime/Static", "program_ids": ["W2_80_ProcFuncCall", "W2_E20_SemanticDiagnostics"] },
{ "spec_section": 16, "coverage_level": "Runtime/Static", "program_ids": ["W2_90_ExceptionAlarm", "W2_E50_RuntimeTimeout"] },
{ "spec_section": 17, "coverage_level": "Runtime/Static", "program_ids": ["W2_90_ExceptionAlarm"] },
{ "spec_section": 18, "coverage_level": "Post", "program_ids": ["W2_P10_BrandHints"] },
{ "spec_section": 19, "coverage_level": "Post", "program_ids": ["W2_P30_Roundtrip"] },
{ "spec_section": 20, "coverage_level": "Post", "program_ids": ["W2_P20_CrossBrandMotion"] },
{ "spec_section": 21, "coverage_level": "Runtime/Static", "program_ids": ["W2_00_MinimalModule", "W2_E10_ExpressionDiagnostics"] },
{ "spec_section": 22, "coverage_level": "Runtime/Static", "program_ids": ["W2_E10_ExpressionDiagnostics", "W2_E20_SemanticDiagnostics", "W2_E30_MotionDiagnostics", "W2_E40_ControlFlowDiagnostics", "W2_E50_RuntimeTimeout"] },
{ "spec_section": 23, "coverage_level": "Runtime/Static", "program_ids": ["W2_00_MinimalModule"] },
{ "spec_section": 24, "coverage_level": "Runtime/Post", "program_ids": ["W2_99_FullSpecExample", "W2_P30_Roundtrip"] },
{ "spec_section": 25, "coverage_level": "Runtime/Static/Post", "program_ids": ["W2_00_MinimalModule", "W2_10_DataTargetToolFrame", "W2_20_ExpressionMath", "W2_30_MotionAllTypes", "W2_40_PathEvents", "W2_50_OperationProcess", "W2_60_IOWaitPulse", "W2_70_ControlFlow", "W2_80_ProcFuncCall", "W2_90_ExceptionAlarm", "W2_P20_CrossBrandMotion"] }
]
}

View File

@@ -0,0 +1,12 @@
language grl 0.1
module W2BrandHints
post_hint abb
post_hint fanuc
post_hint kuka
const speed v = joint(40 %)
const zone zf = fine
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
proc main()
movej home speed v zone zf
end
end

View File

@@ -0,0 +1,20 @@
language grl 0.1
module W2CrossBrandMotion
post_hint abb
const speed vj = joint(50 %)
const speed vl = linear(200 mm/s)
const zone z10 = z(10 mm)
const zone zcnt = cnt(50)
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
target pick = pose_target { pose: pose(500 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) }
target mid = pose_target { pose: pose(550 mm, 50 mm, 0 mm, 0 deg, 0 deg, 0 deg) }
target place = pose_target { pose: pose(600 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) }
proc main()
movej home speed vj zone fine
movel pick speed vl zone z10
movec via mid target place speed vl zone zcnt
io.do[1] = true
wait io.di[1] == true timeout 1 s
pulse io.do[2] duration 100 ms
end
end

View File

@@ -0,0 +1,24 @@
language grl 0.1
module W2Roundtrip
const speed vj = joint(40 %)
const speed vl = linear(150 mm/s)
const zone z10 = z(10 mm)
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
target pick = pose_target { pose: pose(500 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) }
path roundtrip_path {
defaults { speed: vj, zone: z10 }
point p0 movej home zone fine
point p1 movel pick speed vl zone z10
}
operation roundtrip_op {
kind: handling
path: roundtrip_path
process {
generator: "working2"
}
}
proc main()
run_operation roundtrip_op
movej home speed vj zone fine
end
end

View File

@@ -0,0 +1,9 @@
language grl 0.1
module W2MinimalModule
const speed v_joint = joint(40 %)
const zone zf = fine
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
proc main()
movej home speed v_joint zone zf
end
end

View File

@@ -0,0 +1,45 @@
language grl 0.1
module W2DataTargetToolFrame
persistent tool gripper = tool {
tcp: pose(0 mm, 0 mm, 100 mm, 0 deg, 0 deg, 0 deg),
mass: 1.25 kg,
cog: [0 mm, 0 mm, 40 mm]
}
persistent frame fixture = frame { origin: pose(800 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) }
const bool use_fixture = true
const int slot_count = 2
const real blend_scale = 1.5
const string recipe = "w2"
const time settle = 250 ms
const length lift = 80 mm
const angle face = 90 deg
const percent safe_percent = 40 %
const speed vj = joint(safe_percent)
const speed vl = linear(120 mm/s)
const zone z5 = z(5 mm)
const pose safe_pose = pose(500 mm, 0 mm, 100 mm, 0 deg, 0 deg, face)
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
target approach = joint_target {
joints: [0 deg, -22.918312 deg, 28.647890 deg, 0 deg, 11.459156 deg, 0 deg]
}
target pick = pose_target {
pose: pose(500 mm, 0 mm, 0 mm, 0 deg, 0 deg, 90 deg),
config: robot_config(0, 0, 1),
tool: gripper,
frame: fixture
}
target pick_q = pose_target {
pose: poseq(500 mm, 30 mm, 0 mm, 0, 0, 0, 1),
tool: gripper,
frame: fixture
}
proc main()
set_tool gripper
set_frame fixture
set_speed vl
set_zone z5
movej home speed vj zone fine
movej approach speed vj zone z5 tool gripper frame fixture
movel pick_q speed vl zone z5 tool gripper frame fixture
end
end

View File

@@ -0,0 +1,20 @@
language grl 0.1
module W2ExpressionMath
const real blend_base = 5 + 5
const real trig_value = sin(30 deg)
const bool math_ok = (10 mm + 5 mm) > 12 mm and not false
const speed v_pick = linear(100 + 50 mm/s)
const speed v_safe = linear(max(50 mm/s, 200 mm/s / 2))
const zone z_app = z(clamp(blend_base mm, 1 mm, 50 mm))
target home = joint_target {
joints: [0 deg, (10 + 5) deg, -30 deg, 0 deg, abs(-15) deg, round(pi) deg]
}
target pick = pose_target {
pose: pose(400 + 50 mm, 20 * 2 mm, sqrt(90000) mm, 0 deg, 0 deg, atan2(1, 1))
}
proc main()
movej home speed v_safe zone z_app
movel pick speed v_pick zone z_app
wait io.ai[2] > 0.5 timeout 1 + 1 s
end
end

View File

@@ -0,0 +1,21 @@
language grl 0.1
module W2MotionAllTypes
persistent tool gripper = tool { tcp: pose(0 mm, 0 mm, 80 mm, 0 deg, 0 deg, 0 deg), mass: 1 kg }
persistent frame fixture = frame { origin: pose(800 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg) }
const speed vj = joint(45 %)
const speed vl = linear(150 mm/s)
const zone z10 = z(10 mm)
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
target start = pose_target { pose: pose(500 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg), tool: gripper, frame: fixture }
target mid = pose_target { pose: pose(550 mm, 50 mm, 0 mm, 0 deg, 0 deg, 0 deg), tool: gripper, frame: fixture }
target finish = pose_target { pose: pose(600 mm, 0 mm, 0 mm, 0 deg, 0 deg, 0 deg), tool: gripper, frame: fixture }
proc main()
set_tool gripper
set_frame fixture
set_speed vl
set_zone z10
movej home speed vj zone fine
movel start
movec via mid target finish speed linear(100 mm/s) zone fine
end
end

View File

@@ -0,0 +1,26 @@
language grl 0.1
module W2PathEvents
const speed vj = joint(35 %)
const speed vl = linear(120 mm/s)
const zone z10 = z(10 mm)
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
target pick = joint_target { joints: [11.459156 deg, -20.053523 deg, 25.783101 deg, 5.729578 deg, -11.459156 deg, 17.188734 deg] }
target place = joint_target { joints: [-20.053523 deg, -14.323945 deg, 20.053523 deg, -14.323945 deg, 8.594367 deg, -22.918312 deg] }
path generated_path {
source {
type: "cad_curve",
id: "edge_032",
sample_distance: 5 mm
}
defaults { speed: vj, zone: z10 }
point p_home movej home zone fine
event before p_pick io.do[10] = true
point p_pick movej pick speed vj
event after p_pick wait io.di[10] == true timeout 2 s on_timeout alarm "pick sensor timeout"
point p_place movej place speed vj
event at p_place distance 10 mm pulse io.do[11] duration 100 ms
}
proc main()
run_path generated_path
end
end

View File

@@ -0,0 +1,26 @@
language grl 0.1
module W2OperationProcess
const speed v = joint(35 %)
const zone zf = fine
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
path op_path {
defaults { speed: v, zone: zf }
point p0 movej home
}
operation pick_op {
kind: handling
path: op_path
process {
gripper: "main_clamp",
dwell_ms: 50 + 50 ms,
clamp_force: max(20, 10)
}
start_action:
io.do[1] = true
end_action:
io.do[1] = false
}
proc main()
run_operation pick_op
end
end

View File

@@ -0,0 +1,23 @@
language grl 0.1
module W2IOWaitPulse
const speed v = joint(35 %)
const zone zf = fine
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
path io_path {
defaults { speed: v, zone: zf }
point p0 movej home
event at p0 distance 5 + 5 mm pulse io.do[20] duration 50 + 50 ms
}
proc main()
io.do[1] = true
io.ao[1] = 0.75
io.go[1] = 3
wait all(io.di[1] == true, io.di[2] == false) timeout 1 s on_timeout alarm "all timeout"
wait any(io.di[3] == true, io.di[4] == true) timeout 1 s
wait rising(io.di[5]) timeout 1 s
wait falling(io.di[6]) timeout 1 s
wait changed(io.di[7]) timeout 1 s
pulse io.do[2] duration 50 + 50 ms
run_path io_path
end
end

View File

@@ -0,0 +1,35 @@
language grl 0.1
module W2ControlFlow
const speed v = joint(30 %)
const zone zf = fine
var bool ready = true
var bool fault = false
var int mode = 2
var int retry = 0
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
proc main()
if ready == true
movej home speed v zone zf
elseif fault == true
alarm FAULT "fault"
else
alarm NOT_READY "not ready"
end
while retry < 1
retry = 1
continue
end
for i = 1 to 2 step 1
movej home speed v zone zf
end
switch mode
case 1
alarm MODE1 "mode 1"
break
case 2
movej home speed v zone zf
default
alarm MODE_OTHER "mode other"
end
end
end

View File

@@ -0,0 +1,26 @@
language grl 0.1
module W2ProcFuncCall
const speed v = joint(30 %)
const zone zf = fine
var int global_count = 0
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
proc read_part(out bool ok)
ok = true
end
proc increment_retry(inout int retry)
retry = 1
end
func bool can_run(in int retry, in int max_retry)
return retry < max_retry
end
proc main()
var bool ok = false
var int retry = 0
call read_part(ok)
call increment_retry(retry)
if retry < 3
movej home speed v zone zf
end
return
end
end

View File

@@ -0,0 +1,25 @@
language grl 0.1
module W2ExceptionAlarm
const speed v = joint(30 %)
const zone zf = fine
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
trap recover_trap()
raise E_STOP
end
task background cycle 20 ms
alarm TASK_MONITOR "task boundary"
end
proc main()
alarm START "start" severity info
try
movej home speed v zone zf
raise E_PICK_FAILED
catch E_PICK_FAILED
alarm RECOVER "recovering" severity warning
finally
alarm CLEANUP "cleanup"
end
enable interrupt guard
disable interrupt guard
end
end

View File

@@ -0,0 +1,80 @@
language grl 0.1
module W2FullSpecExample
persistent tool gripper = tool {
tcp: pose(0 mm, 0 mm, 120 mm, 0 deg, 0 deg, 0 deg),
mass: 1.5 kg
}
persistent frame fixture = frame {
origin: pose(800 mm, 0 mm, 200 mm, 0 deg, 0 deg, 0 deg)
}
const speed v_fast = joint(55 %)
const speed v_pick = linear(180 mm/s)
const zone z_pick = z(10 mm)
target home = joint_target {
joints: [0 deg, -20 deg, 40 deg, 0 deg, 30 deg, 0 deg]
}
target pick = pose_target {
pose: pose(500 mm, 120 mm, 300 mm, 180 deg, 0 deg, 90 deg),
config: robot_config(0, 0, 1),
tool: gripper,
frame: fixture
}
target place = pose_target {
pose: pose(650 mm, -100 mm, 320 mm, 180 deg, 0 deg, 90 deg),
config: robot_config(0, 0, 1),
tool: gripper,
frame: fixture
}
path pick_path {
source {
type: "generated_example",
id: "pick_path"
}
defaults {
tool: gripper,
frame: fixture,
speed: v_pick,
zone: z_pick
}
point approach movej home speed v_fast zone fine
point above_pick movel pick offset z 100 mm
point at_pick movel pick zone fine
event after at_pick io.do[1] = true
event after at_pick wait io.di[1] == true timeout 2 s on_timeout alarm "Clamp close timeout"
point leave_pick movel pick offset z 100 mm
}
path place_path {
defaults {
tool: gripper,
frame: fixture,
speed: v_pick,
zone: z_pick
}
point above_place movel place offset z 100 mm
point at_place movel place zone fine
event after at_place io.do[1] = false
event after at_place wait io.di[2] == true timeout 2 s on_timeout alarm "Clamp open timeout"
point leave_place movel place offset z 100 mm
}
operation pick_op {
kind: handling
path: pick_path
process {
gripper: "main_clamp"
}
}
operation place_op {
kind: handling
path: place_path
process {
gripper: "main_clamp"
}
}
proc main()
set_tool gripper
set_frame fixture
run_operation pick_op
run_operation place_op
movej home speed v_fast zone fine
end
end

View File

@@ -0,0 +1,14 @@
language grl 0.1
module W2ExpressionDiagnostics
const speed v_ok = linear(100 mm/s)
const speed v_unknown_symbol = linear(missing_symbol mm/s)
const speed v_unknown_function = linear(no_such_function(1) mm/s)
const speed v_bad_arity = linear(atan2(1) mm/s)
const speed v_domain = linear(sqrt(-1) mm/s)
const speed v_div_zero = linear(100 mm/s / 0)
const zone z_unit = z(10 mm + 1 s)
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
proc main()
movej home speed v_ok zone fine
end
end

View File

@@ -0,0 +1,19 @@
language grl 0.1
module W2SemanticDiagnostics
const speed v = joint(30 %)
const speed v = joint(40 %)
const zone zf = fine
target home = joint_target { joints: [0 deg, 0 deg, 0 deg, 0 deg, 0 deg, 0 deg] }
proc set_done(out bool done)
done = true
end
func bool bad_func()
wait io.di[1] == true
return true
end
proc main()
call set_done(true)
call missing_proc()
movej missing_target speed v zone zf
end
end

View File

@@ -0,0 +1,21 @@
language grl 0.1
module W2ControlFlowDiagnostics
var int mode = 1
proc main()
break
continue
jump inner
if mode == 1
label inner
end
switch mode
case 1
break
case 1
break
case mode + 1
break
end
jump missing
end
end

View File

@@ -0,0 +1,132 @@
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import {
runAbb120SpecSuite,
type Abb120SpecManifest,
type Abb120SpecProgramSource
} from "../../src/suites/abb120SpecSuite.js";
const WEB_ROOT = join(dirname(fileURLToPath(import.meta.url)), "../..");
const SPEC_ROOT = join(WEB_ROOT, "tests", "fixtures", "abb120", "spec-programs");
function loadManifest(): Abb120SpecManifest {
return JSON.parse(readFileSync(join(SPEC_ROOT, "manifest.json"), "utf8")) as Abb120SpecManifest;
}
function loadPrograms(manifest = loadManifest()): Abb120SpecProgramSource[] {
return manifest.programs.map((entry) => ({
program_id: entry.program_id,
file: entry.file,
text: readFileSync(join(SPEC_ROOT, entry.file), "utf8")
}));
}
describe("working2 ABB120 GRL spec suite", () => {
it("maps grammar sections 4 through 25 to concrete Runtime, Static, or Post programs", () => {
const manifest = loadManifest();
const sections = manifest.coverage_matrix.map((item) => item.spec_section);
expect(sections).toEqual(Array.from({ length: 22 }, (_value, index) => index + 4));
expect(manifest.programs).toHaveLength(19);
expect(manifest.programs.filter((program) => program.coverage_level === "Runtime")).toHaveLength(12);
expect(manifest.programs.filter((program) => program.coverage_level === "Static")).toHaveLength(4);
expect(manifest.programs.filter((program) => program.coverage_level === "Post")).toHaveLength(3);
const programById = new Map(manifest.programs.map((program) => [program.program_id, program]));
for (const item of manifest.coverage_matrix) {
expect(item.program_ids.length).toBeGreaterThan(0);
for (const programId of item.program_ids) {
const program = programById.get(programId);
expect(program, `${item.spec_section} references missing ${programId}`).toBeDefined();
expect(
item.coverage_level.split("/"),
`${item.spec_section} coverage level must include ${programId}`
).toContain(program!.coverage_level);
}
}
});
it("builds a W2 job with compile, controller, queue, trace, IO, post, and roundtrip evidence", () => {
const manifest = loadManifest();
const job = runAbb120SpecSuite(manifest, loadPrograms(manifest), {
now: "2026-06-28T01:30:00.000Z",
gitRevision: "test-revision"
});
expect(job.job_id).toMatch(/^W2-JOB-20260628013000-[a-f0-9]{8}$/);
expect(job.suite_id).toBe("working2-abb120-spec");
expect(job.robot_id).toBe("abb_irb120_3_58");
expect(job.coverage.missingSections).toEqual([]);
expect(job.programs.map((program) => program.program_id)).toEqual(manifest.programs.map((program) => program.program_id));
const runtime = job.programs.filter((program) => program.coverage_level === "Runtime");
expect(runtime.every((program) => program.compile.parsed)).toBe(true);
expect(runtime.every((program) => program.compile.sourceMapEntries > 0)).toBe(true);
expect(runtime.every((program) => program.controller?.commands.map((command) => command.command).includes("loadProgram"))).toBe(true);
expect(runtime.every((program) => program.controller?.commands.map((command) => command.command).includes("resetFault"))).toBe(true);
expect(runtime.some((program) => program.program_id === "W2_60_IOWaitPulse" && Object.keys(program.io?.image ?? {}).length > 0)).toBe(true);
expect(runtime.some((program) => (program.trace ?? []).some((event) => event.kind === "wait"))).toBe(true);
expect(runtime.some((program) => (program.motionQueue?.items.length ?? 0) > 0)).toBe(true);
expect(runtime.some((program) =>
program.program_id === "W2_99_FullSpecExample" &&
program.compile.pathCount === 2 &&
program.compile.operationCount === 2 &&
(program.motionQueue?.items.some((item) => item.source.kind === "path" && item.source.operationId === "pick_op") ?? false)
)).toBe(true);
expect(runtime.every((program) => (program.trajectory?.sampleCount ?? 0) >= 0)).toBe(true);
expect(runtime.some((program) =>
(program.trajectory?.frames ?? []).some((frame) =>
frame.joints.length === 6 &&
frame.tcp.position.length === 3 &&
frame.tcp.quaternion.length === 4
)
)).toBe(true);
for (const program of job.programs) {
const expected = manifest.programs.find((entry) => entry.program_id === program.program_id)!;
expect(program.status, program.program_id).toBe(expected.expected_status);
const codes = program.diagnostics.map((diagnostic) => diagnostic.code);
for (const code of expected.expected_diagnostics) {
expect(codes, program.program_id).toContain(code);
}
expect(codes, program.program_id).not.toContain("W2_EXPECTED_DIAGNOSTIC_MISSING");
expect(codes, program.program_id).not.toContain("W2_EXPECTED_STATUS_MISMATCH");
}
const diagnostics = Object.fromEntries(job.programs.map((program) => [
program.program_id,
program.diagnostics.map((diagnostic) => diagnostic.code)
]));
expect(diagnostics.W2_E10_ExpressionDiagnostics).toEqual(expect.arrayContaining([
"GRL_EXPR_UNKNOWN_SYMBOL",
"GRL_EXPR_UNKNOWN_FUNCTION",
"GRL_EXPR_ARITY",
"GRL_EXPR_DOMAIN",
"GRL_EXPR_DIV_ZERO",
"GRL_EXPR_UNIT_MISMATCH"
]));
expect(diagnostics.W2_E20_SemanticDiagnostics).toEqual(expect.arrayContaining([
"GRL_SYMBOL_DUPLICATE",
"GRL_ARGUMENT_NOT_LVALUE"
]));
expect(diagnostics.W2_E30_MotionDiagnostics).toEqual(expect.arrayContaining([
"GRL_PATH_EMPTY",
"KDL_JOINT_LIMIT",
"KDL_TARGET_UNREACHABLE"
]));
expect(diagnostics.W2_E40_ControlFlowDiagnostics).toEqual(expect.arrayContaining([
"GRL_BREAK_OUTSIDE_FLOW"
]));
expect(diagnostics.W2_E50_RuntimeTimeout).toEqual(expect.arrayContaining([
"VC_WAIT_TIMEOUT"
]));
const post = job.programs.filter((program) => program.coverage_level === "Post");
expect(post.every((program) => program.post?.filenames.some((filename) => filename.endsWith(".mod")))).toBe(true);
expect(post.every((program) => program.post?.filenames.some((filename) => filename.endsWith(".ls")))).toBe(true);
expect(post.every((program) => program.post?.filenames.some((filename) => filename.endsWith(".src")))).toBe(true);
expect(post.every((program) => program.roundtrip?.status.match(/pass|warn/))).toBe(true);
expect(job.artifacts.jobJson).toContain(job.job_id);
});
});

View File

@@ -69,6 +69,8 @@ describe("ABB120 suite artifacts", () => {
expect(html).toContain("ABB120 Station");
expect(html).toContain("Path: pick_place");
expect(html).toContain("W2-JOB-latest");
expect(html).toContain("working2-abb120-spec");
expect(html).toContain("Controller");
expect(html).toContain("Motion Queue");
expect(html).toContain("Reports");