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");