接入 task HAL Web 仿真运行时

This commit is contained in:
2026-06-22 06:11:55 +08:00
parent 3771b9eafe
commit bd11a5f8d6
42 changed files with 5574 additions and 50 deletions

View File

@@ -0,0 +1,213 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import assert from "node:assert/strict";
import createLinuxCncTaskHalModule from "../../../build/wasm/task-hal/linuxcnc_task_hal.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const rootDir = resolve(__dirname, "../../..");
const wasmPath = resolve(rootDir, "build/wasm/task-hal/linuxcnc_task_hal.wasm");
const hal = await createLinuxCncTaskHalModule({
wasmBinary: readFileSync(wasmPath),
print() {},
printErr(message) {
console.error(message);
},
});
function allocCString(value) {
const bytes = hal.lengthBytesUTF8(value) + 1;
const ptr = hal._malloc(bytes);
hal.stringToUTF8(value, ptr, bytes);
return ptr;
}
function withCString(value, fn) {
const ptr = allocCString(value);
try {
return fn(ptr);
} finally {
hal._free(ptr);
}
}
function readJsonWithBuffer(call) {
const bytes = 32768;
const ptr = hal._malloc(bytes);
try {
const rc = call(ptr, bytes);
assert.equal(rc, 0);
return JSON.parse(hal.UTF8ToString(ptr));
} finally {
hal._free(ptr);
}
}
function createPin(name, type, dir = 32) {
const ptrAddr = hal._malloc(4);
try {
const rc = withCString(name, (namePtr) => {
switch (type) {
case "bit":
return hal._hal_pin_bit_new(namePtr, dir, ptrAddr, 1);
case "float":
return hal._hal_pin_float_new(namePtr, dir, ptrAddr, 1);
case "s32":
return hal._hal_pin_s32_new(namePtr, dir, ptrAddr, 1);
case "u32":
return hal._hal_pin_u32_new(namePtr, dir, ptrAddr, 1);
default:
throw new Error(`unsupported pin type: ${type}`);
}
});
assert.equal(rc, 0, `${name} create`);
return hal.getValue(ptrAddr, "i32");
} finally {
hal._free(ptrAddr);
}
}
function setPinFloat(name, value) {
withCString(name, (namePtr) => assert.equal(hal._lchal_set_pin_float(namePtr, value), 0));
}
function setPinBit(name, value) {
withCString(name, (namePtr) => assert.equal(hal._lchal_set_pin_bit(namePtr, value ? 1 : 0), 0));
}
function getPin(name) {
return withCString(name, (namePtr) =>
readJsonWithBuffer((outPtr, outLen) => hal._lchal_get_pin_json(namePtr, outPtr, outLen)),
);
}
function getP(name) {
return withCString(name, (namePtr) => {
const bytes = 128;
const outPtr = hal._malloc(bytes);
try {
assert.equal(hal._hal_get_p(namePtr, outPtr, bytes), 0);
return hal.UTF8ToString(outPtr);
} finally {
hal._free(outPtr);
}
});
}
function loadHalFile(path, text) {
return withCString(path, (pathPtr) =>
withCString(text, (textPtr) => hal._lchal_load_hal_file(pathPtr, textPtr)),
);
}
assert.equal(hal._lchal_init_runtime(), 0);
assert.notEqual(createPin("motion.analog-out-03", "float"), 0);
assert.notEqual(createPin("probe.out", "bit"), 0);
assert.notEqual(createPin("probe.in", "bit", 16), 0);
assert.notEqual(createPin("motion.program-line", "s32"), 0);
assert.notEqual(hal._hal_malloc(16), 0);
setPinFloat("motion.analog-out-03", 2.5);
assert.equal(getPin("motion.analog-out-03").value, 2.5);
assert.equal(getP("motion.analog-out-03"), "2.5");
const paramFloatAddr = hal._malloc(8);
const paramS32Addr = hal._malloc(4);
try {
hal.setValue(paramFloatAddr, 7.25, "double");
hal.setValue(paramS32Addr, -12, "i32");
withCString("standalone.param-float", (namePtr) =>
assert.equal(hal._hal_param_float_new(namePtr, 192, paramFloatAddr, 1), 0),
);
withCString("standalone.param-s32", (namePtr) =>
assert.equal(hal._hal_param_s32_new(namePtr, 192, paramS32Addr, 1), 0),
);
assert.equal(getP("standalone.param-float"), "7.25");
assert.equal(getP("standalone.param-s32"), "-12");
} finally {
hal._free(paramFloatAddr);
hal._free(paramS32Addr);
}
withCString("motion.program-line", (namePtr) => {
const typePtr = hal._malloc(4);
const valuePtrPtr = hal._malloc(4);
const connectedPtr = hal._malloc(1);
try {
assert.equal(hal._hal_get_pin_value_by_name(namePtr, typePtr, valuePtrPtr, connectedPtr), 0);
assert.equal(hal.getValue(typePtr, "i32"), 3);
assert.notEqual(hal.getValue(valuePtrPtr, "i32"), 0);
} finally {
hal._free(typePtr);
hal._free(valuePtrPtr);
hal._free(connectedPtr);
}
});
assert.equal(loadHalFile("probe.hal", "net probe-signal probe.out probe.in\n"), 0);
setPinBit("probe.out", true);
assert.equal(getPin("probe.in").value, true);
withCString("probe-signal", (namePtr) => {
const typePtr = hal._malloc(4);
const valuePtrPtr = hal._malloc(4);
const connectedPtr = hal._malloc(1);
try {
assert.equal(hal._hal_get_signal_value_by_name(namePtr, typePtr, valuePtrPtr, connectedPtr), 0);
assert.equal(hal.getValue(typePtr, "i32"), 1);
assert.notEqual(hal.getValue(valuePtrPtr, "i32"), 0);
} finally {
hal._free(typePtr);
hal._free(valuePtrPtr);
hal._free(connectedPtr);
}
});
assert.equal(
loadHalFile(
"threads.hal",
[
"loadrt trivkins",
"addf motion-command-handler servo-thread",
"addf motion-controller servo-thread",
"",
].join("\n"),
),
0,
);
assert.equal(hal._lchal_step_threads(1000000, 2), 0);
withCString("motion-command-handler", (functPtr) =>
withCString("servo-thread", (threadPtr) =>
assert.equal(hal._hal_del_funct_from_thread(functPtr, threadPtr), 0),
),
);
assert.equal(hal._hal_stop_threads(), 0);
assert.equal(hal._hal_start_threads(), 0);
assert.equal(loadHalFile("blocked.hal", "loadusr external-user-m\n"), 2);
const snapshot = readJsonWithBuffer((outPtr, outLen) => hal._lchal_get_snapshot_json(outPtr, outLen));
assert.equal(snapshot.semanticBoundary, "linuxcnc_hal_runtime_phase2_minimal");
assert.equal(snapshot.halRuntimeReady, true);
assert.equal(snapshot.halSyncReady, false);
assert.equal(snapshot.nativeHalSyncReady, false);
assert.equal(snapshot.cycle, 2);
assert.equal(snapshot.pins["probe.in"].value, true);
assert.equal(snapshot.signals["probe-signal"].value, true);
assert.equal(snapshot.params["standalone.param-float"].value, 7.25);
assert.equal(snapshot.params["standalone.param-s32"].value, -12);
assert.equal(snapshot.threads["servo-thread"].functions.length >= 1, true);
assert.equal(snapshot.events.includes("blocked:loadusr"), true);
assert.equal(
snapshot.events.includes("call:servo-thread:motion-controller"),
true,
);
assert.equal(snapshot.events.includes("delf:motion-command-handler:servo-thread"), true);
assert.equal(snapshot.events.includes("threads_stop"), true);
console.log("hal_runtime_registry=ok");
console.log("hal_net_signal_propagation=ok");
console.log("hal_thread_scheduler=ok");
console.log("loadusr_blocked_evidence=ok");
console.log("hal_runtime_wasm_node_smoke=ok");

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
BUILD_DIR="$ROOT_DIR/build/wasm/task-hal"
NATIVE_BUILD_DIR="$ROOT_DIR/build/native/hal-runtime"
if ! command -v emcc >/dev/null 2>&1 && [[ -f "$HOME/emsdk/emsdk_env.sh" ]]; then
# shellcheck source=/dev/null
source "$HOME/emsdk/emsdk_env.sh" >/dev/null
fi
if command -v emcc >/dev/null 2>&1; then
if [[ "${SKIP_TASK_HAL_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_task_hal_wasm.sh"
fi
node "$ROOT_DIR/tests/wasm/node/verify_hal_runtime.mjs"
exit 0
fi
mkdir -p "$NATIVE_BUILD_DIR" "$BUILD_DIR"
g++ -std=c++20 \
-I"$ROOT_DIR/runtime/core/shims" \
-I"$ROOT_DIR/runtime/core/linuxcnc_wrap" \
"$ROOT_DIR/runtime/core/linuxcnc_wrap/linuxcnc_hal_runtime.cpp" \
"$ROOT_DIR/runtime/core/linuxcnc_wrap/linuxcnc_hal_runtime_probe.cpp" \
-o "$NATIVE_BUILD_DIR/linuxcnc_hal_runtime_probe"
"$NATIVE_BUILD_DIR/linuxcnc_hal_runtime_probe"
echo "hal_runtime_wasm_node_smoke=skipped_missing_emcc"

View File

@@ -0,0 +1,111 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import assert from "node:assert/strict";
import createLinuxCncTaskHalModule from "../../../build/wasm/task-hal/linuxcnc_task_hal.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const rootDir = resolve(__dirname, "../../..");
const wasmPath = resolve(rootDir, "build/wasm/task-hal/linuxcnc_task_hal.wasm");
const runtime = await createLinuxCncTaskHalModule({
wasmBinary: readFileSync(wasmPath),
print() {},
printErr(message) {
console.error(message);
},
});
function allocCString(value) {
const bytes = runtime.lengthBytesUTF8(value) + 1;
const ptr = runtime._malloc(bytes);
runtime.stringToUTF8(value, ptr, bytes);
return ptr;
}
function withCString(value, fn) {
const ptr = allocCString(value);
try {
return fn(ptr);
} finally {
runtime._free(ptr);
}
}
function readJson(call) {
const bytes = 65536;
const ptr = runtime._malloc(bytes);
try {
const rc = call(ptr, bytes);
assert.equal(rc, 0);
return JSON.parse(runtime.UTF8ToString(ptr));
} finally {
runtime._free(ptr);
}
}
function send(command) {
return withCString(JSON.stringify(command), (commandPtr) =>
assert.equal(runtime._lcmot_write_command_json(commandPtr), 0),
);
}
assert.equal(runtime._lchal_init_runtime(), 0);
assert.equal(
withCString("xyzac-trt.ini", (pathPtr) =>
withCString("[TRAJ]\nCOORDINATES = X Y Z A C\n", (iniPtr) =>
runtime._lcmot_init_from_ini(pathPtr, iniPtr),
),
),
0,
);
send({ type: "EMC_TRAJ_LINEAR_MOVE", line: 42, x: 1, y: 2, z: -3, a: 10, c: 20, velocity: 120 });
assert.equal(runtime._lcmot_step_servo(1000000, 1), 0);
let status = readJson((outPtr, outLen) => runtime._lcmot_read_status_json(outPtr, outLen));
let hal = readJson((outPtr, outLen) => runtime._lcmot_read_hal_snapshot_json(outPtr, outLen));
assert.equal(status.semanticBoundary, "linuxcnc_motion_runtime_phase3_minimal");
assert.equal(status.motionRuntimeReady, true);
assert.equal(status.nativeHalSyncReady, false);
assert.equal(status.motion.programLine, 42);
assert.equal(status.motion.inPosition, true);
assert.equal(status.axis.x, 1);
assert.equal(status.axis.z, -3);
assert.equal(hal.pins["motion.program-line"].value, 42);
assert.equal(hal.pins["joint.0.motor-pos-cmd"].value, 1);
assert.equal(hal.pins["joint.0.motor-pos-fb"].value, 1);
send({ type: "EMCMOT_SET_AOUT", value: 1 });
assert.equal(runtime._lcmot_step_servo(1000000, 1), 0);
status = readJson((outPtr, outLen) => runtime._lcmot_read_status_json(outPtr, outLen));
hal = readJson((outPtr, outLen) => runtime._lcmot_read_hal_snapshot_json(outPtr, outLen));
assert.equal(status.motion.switchkinsType, 1);
assert.equal(hal.pins["motion.switchkins-type"].value, 1);
assert.equal(hal.pins["motion.analog-out-03"].value, 1);
send({ type: "EMC_JOG_INCR", axis: "X", distance: 0.5, velocity: 60 });
assert.equal(runtime._lcmot_step_servo(1000000, 1), 0);
status = readJson((outPtr, outLen) => runtime._lcmot_read_status_json(outPtr, outLen));
hal = readJson((outPtr, outLen) => runtime._lcmot_read_hal_snapshot_json(outPtr, outLen));
assert.equal(status.axis.x, 1.5);
assert.equal(status.motion.teleopMode, 1);
assert.equal(hal.pins["axis.0.pos-fb"].value, 1.5);
send({ type: "EMC_TRAJ_PAUSE" });
assert.equal(runtime._lcmot_step_servo(1000000, 1), 0);
status = readJson((outPtr, outLen) => runtime._lcmot_read_status_json(outPtr, outLen));
assert.equal(status.motion.paused, true);
send({ type: "EMC_TRAJ_RESUME" });
assert.equal(runtime._lcmot_step_servo(1000000, 1), 0);
status = readJson((outPtr, outLen) => runtime._lcmot_read_status_json(outPtr, outLen));
assert.equal(status.motion.paused, false);
console.log("motion_hal_servo_cycle=ok");
console.log("motion_program_line_hal_sync=ok");
console.log("switchkins_type_hal_sync=ok");
console.log("jog_motion_status_sync=ok");
console.log("motion_hal_sync_smoke=ok");

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
if ! command -v emcc >/dev/null 2>&1 && [[ -f "$HOME/emsdk/emsdk_env.sh" ]]; then
# shellcheck source=/dev/null
source "$HOME/emsdk/emsdk_env.sh" >/dev/null
fi
if ! command -v emcc >/dev/null 2>&1; then
echo "motion_hal_sync_smoke=skipped_missing_emcc"
echo "nativeHalSyncReady=false"
exit 0
fi
if [[ "${SKIP_TASK_HAL_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_task_hal_wasm.sh"
fi
node "$ROOT_DIR/tests/wasm/node/verify_motion_hal_sync.mjs"

View File

@@ -0,0 +1,56 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import assert from "node:assert/strict";
import { createLinuxCncTaskHalSdk } from "../../../runtime/sdk/src/linuxcnc-task-hal.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const rootDir = resolve(__dirname, "../../..");
const wasmPath = resolve(rootDir, "build/wasm/task-hal/linuxcnc_task_hal.wasm");
const sdk = await createLinuxCncTaskHalSdk({
wasmBinary: readFileSync(wasmPath),
print() {},
printErr(message) {
console.error(message);
},
});
const readiness = sdk.readiness();
assert.equal(readiness.taskRuntimeReady, true);
assert.equal(readiness.motionRuntimeReady, true);
assert.equal(readiness.halRuntimeReady, true);
assert.equal(readiness.nativeTaskReady, false);
assert.equal(readiness.nativeHalSyncReady, false);
sdk.initSession({
iniPath: "xyzac-trt.ini",
iniText: "[TRAJ]\nCOORDINATES = X Y Z A C\n",
});
assert.equal(sdk.stageFile("programs/sdk-task-hal.ngc", "G0 X3 Y0 Z-1\n"), 0);
assert.equal(sdk.openProgram("programs/sdk-task-hal.ngc"), 0);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
assert.equal(sdk.runCycles({ taskCycles: 1 }), 0);
let status = sdk.readStatus();
assert.equal(status.semanticBoundary, "linuxcnc_task_motion_hal_wasm_phase4_minimal");
assert.equal(status.taskRuntimeReady, true);
assert.equal(status.motionStatus.motion.programLine, 1);
assert.equal(status.motionStatus.axis.x, 3);
assert.equal(status.halSnapshot.pins["motion.program-line"].value, 1);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_PLAN_EXECUTE", mdi: "M429" }), 0);
assert.equal(sdk.runCycles({ taskCycles: 1 }), 0);
status = sdk.readStatus();
assert.equal(status.motionStatus.motion.switchkinsType, 0);
const events = sdk.readEvents().events;
assert.equal(events.includes("task_plan_run"), true);
assert.equal(events.includes("task_mdi_switchkins:M429"), true);
console.log("linuxcnc_task_hal_sdk=ok");
console.log("task_hal_sdk_status_snapshot=ok");

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
if ! command -v emcc >/dev/null 2>&1 && [[ -f "$HOME/emsdk/emsdk_env.sh" ]]; then
# shellcheck source=/dev/null
source "$HOME/emsdk/emsdk_env.sh" >/dev/null
fi
if ! command -v emcc >/dev/null 2>&1; then
echo "linuxcnc_task_hal_sdk=skipped_missing_emcc"
echo "nativeTaskReady=false"
echo "nativeHalSyncReady=false"
exit 0
fi
if [[ "${SKIP_TASK_HAL_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_task_hal_wasm.sh"
fi
node "$ROOT_DIR/tests/wasm/node/verify_task_hal_sdk.mjs"

View File

@@ -0,0 +1,148 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import assert from "node:assert/strict";
import createLinuxCncTaskHalModule from "../../../build/wasm/task-hal/linuxcnc_task_hal.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const rootDir = resolve(__dirname, "../../..");
const wasmPath = resolve(rootDir, "build/wasm/task-hal/linuxcnc_task_hal.wasm");
const runtime = await createLinuxCncTaskHalModule({
wasmBinary: readFileSync(wasmPath),
print() {},
printErr(message) {
console.error(message);
},
});
function allocCString(value) {
const bytes = runtime.lengthBytesUTF8(value) + 1;
const ptr = runtime._malloc(bytes);
runtime.stringToUTF8(value, ptr, bytes);
return ptr;
}
function withCString(value, fn) {
const ptr = allocCString(value);
try {
return fn(ptr);
} finally {
runtime._free(ptr);
}
}
function readJson(call) {
const bytes = 131072;
const ptr = runtime._malloc(bytes);
try {
const rc = call(ptr, bytes);
assert.equal(rc, 0);
return JSON.parse(runtime.UTF8ToString(ptr));
} finally {
runtime._free(ptr);
}
}
function send(command) {
return withCString(JSON.stringify(command), (commandPtr) =>
assert.equal(runtime._lctask_send_command_json(commandPtr), 0, command.type),
);
}
function status() {
return readJson((outPtr, outLen) => runtime._lctask_read_status_json(outPtr, outLen));
}
function events() {
return readJson((outPtr, outLen) => runtime._lctask_read_events_json(outPtr, outLen)).events;
}
const session = {
iniPath: "xyzac-trt.ini",
iniText: "[TRAJ]\nCOORDINATES = X Y Z A C\n",
};
assert.equal(
withCString(JSON.stringify(session), (sessionPtr) => runtime._lctask_init_session(sessionPtr)),
0,
);
const program = ["G0 X1 Y2 Z-3 A10 C20", "G1 X2 Y3 Z-4 A11 C21"].join("\n");
assert.equal(
withCString("programs/task-hal-smoke.ngc", (pathPtr) =>
withCString(program, (textPtr) => runtime._lctask_stage_file(pathPtr, textPtr)),
),
0,
);
assert.equal(
withCString("programs/task-hal-smoke.ngc", (pathPtr) => runtime._lctask_open_program(pathPtr)),
0,
);
send({ type: "EMC_TASK_SET_STATE", state: "ON" });
send({ type: "EMC_TASK_SET_MODE", mode: "AUTO" });
send({ type: "EMC_TASK_PLAN_RUN", line: 0 });
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 2), 0);
let snapshot = status();
assert.equal(snapshot.semanticBoundary, "linuxcnc_task_motion_hal_wasm_phase4_minimal");
assert.equal(snapshot.taskRuntimeReady, true);
assert.equal(snapshot.taskStatusFromLinuxCncRuntime, true);
assert.equal(snapshot.taskCommandsDriveMotionRuntime, true);
assert.equal(snapshot.nativeTaskReady, false);
assert.equal(snapshot.nativeHalSyncReady, false);
assert.equal(snapshot.task.state, "ON");
assert.equal(snapshot.task.mode, "AUTO");
assert.equal(snapshot.task.openedLineCount, 2);
assert.equal(snapshot.task.nextProgramLine, 2);
assert.equal(snapshot.motionStatus.motion.programLine, 2);
assert.equal(snapshot.motionStatus.axis.x, 2);
assert.equal(snapshot.motionStatus.axis.z, -4);
assert.equal(snapshot.halSnapshot.pins["motion.program-line"].value, 2);
assert.equal(snapshot.halSnapshot.pins["joint.0.motor-pos-cmd"].value, 2);
send({ type: "EMC_TASK_PLAN_PAUSE" });
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
snapshot = status();
assert.equal(snapshot.task.interpState, "PAUSED");
assert.equal(snapshot.motionStatus.motion.paused, true);
send({ type: "EMC_TASK_PLAN_RESUME" });
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
snapshot = status();
assert.equal(snapshot.motionStatus.motion.paused, false);
send({ type: "EMC_TASK_PLAN_EXECUTE", mdi: "M428" });
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
snapshot = status();
assert.equal(snapshot.motionStatus.motion.switchkinsType, 1);
assert.equal(snapshot.halSnapshot.pins["motion.switchkins-type"].value, 1);
send({ type: "EMC_JOG_INCR", axis: "X", distance: 0.25, velocity: 60 });
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
snapshot = status();
assert.equal(snapshot.motionStatus.axis.x, 2.25);
assert.equal(snapshot.motionStatus.motion.teleopMode, 1);
send({ type: "EMC_TASK_ABORT" });
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
snapshot = status();
assert.equal(snapshot.task.execState, "DONE");
assert.equal(snapshot.motionStatus.motion.aborted, true);
const eventLog = events();
assert.equal(eventLog.includes("task_session_motion_init"), true);
assert.equal(eventLog.includes("task_plan_run"), true);
assert.equal(eventLog.includes("task_plan_pause"), true);
assert.equal(eventLog.includes("task_plan_resume"), true);
assert.equal(eventLog.includes("task_mdi_switchkins:M428"), true);
assert.equal(eventLog.includes("task_jog_incr"), true);
assert.equal(eventLog.includes("task_abort"), true);
console.log("linuxcnc_task_runtime_smoke=ok");
console.log("task_status_from_linuxcnc_runtime=ok");
console.log("task_commands_drive_motion_runtime=ok");
console.log("mdi_jog_task_motion_hal_sync=ok");

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
if ! command -v emcc >/dev/null 2>&1 && [[ -f "$HOME/emsdk/emsdk_env.sh" ]]; then
# shellcheck source=/dev/null
source "$HOME/emsdk/emsdk_env.sh" >/dev/null
fi
if ! command -v emcc >/dev/null 2>&1; then
echo "linuxcnc_task_runtime_smoke=skipped_missing_emcc"
echo "nativeTaskReady=false"
echo "nativeHalSyncReady=false"
exit 0
fi
if [[ "${SKIP_TASK_HAL_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_task_hal_wasm.sh"
fi
node "$ROOT_DIR/tests/wasm/node/verify_task_hal_wasm.mjs"