完善虚拟HAL仿真替代能力
This commit is contained in:
@@ -40,8 +40,8 @@ The project is complete for the current scope when all of the following are true
|
||||
| --- | --- | --- |
|
||||
| LinuxCNC semantic boundary | Stable | JS/browser remains glue, staging, OPFS, WASM/browser boundary, docs, and tests only. |
|
||||
| Real browser simulation priority | Active first priority | Future batches should prioritize the real UI/browser CNC simulation page, with visible machine/session loading, G-code program state, LinuxCNC-backed interpreter execution, machine readiness/status panels, toolpath/preview rendering from LinuxCNC-produced output or validated runtime events, and browser smoke coverage. First entry point: `wasm-port/runtime/ui/simulation/index.html`; gate: `wasm-port/tests/browser/verify_real_simulation_browser.sh`. See `wasm-port/docs/real-browser-simulation-priority.md`. |
|
||||
| Real browser simulation page | MVP active | `runtime/ui/simulation/index.html` loads in Chromium, exposes a selector for multiple LinuxCNC-backed test programs, runs operator-provided G-code text/files through interpreter WASM or through the loaded session INI path when a machine session is staged and enabled, includes an editable G-code pane with Run Editor Text plus OPFS save/load program persistence, shows OPFS machine/session readiness, loaded WASM session paths, a Use Session toggle, standalone/session-backed run mode, and compact run summary, and includes reset/step/play/finish playback over LinuxCNC canonical motion events. It highlights active G-code and motion rows, updates live axis readout, exposes DRO/modal state APIs, moves the toolhead, grows the executed SVG toolpath, and supports fit/zoom/reset preview controls. Current built-in program set covers square contour, Z pocket contour, incremental loop, G2/G3 arcs, and G81 drilling. |
|
||||
| AXIS-style simulation shell | Phase 2/3/4/5 partial | `runtime/ui/simulation/index.html` now uses an AXIS-inspired titlebar, menubar, toolbar, Manual/MDI tabs, Preview/DRO tabs, G-code pane, machine-state pane, and status bar while preserving current LinuxCNC-backed execution and playback APIs. Browser smoke verifies shell regions, tab switching, toolbar playback, editor/file/OPFS program execution, blocked/ready machine-session readiness, ready session staging into WASM, loaded-session editor and OPFS execution with `runProgramWithIni()`, standalone fallback through the Use Session toggle, run-mode and run-summary UI/API state, DRO/modal state, preview zoom/reset controls, and existing program execution. |
|
||||
| Real browser simulation page | MVP active | `runtime/ui/simulation/index.html` loads in Chromium, exposes a selector for multiple LinuxCNC-backed test programs, runs operator-provided G-code text/files through interpreter WASM or through the loaded session INI path when a machine session is staged and enabled, includes an editable G-code pane with Run Editor Text plus OPFS save/load program persistence, shows OPFS machine/session readiness, loaded WASM session paths, a Use Session toggle, standalone/session-backed run mode, compact run summary, and browser virtual HAL state for AXIS manual controls. It highlights active G-code and motion rows, updates live axis readout, exposes DRO/modal/virtual-HAL state APIs, moves the toolhead, grows the executed SVG/Three.js toolpath, and supports fit/zoom/reset/view-mode preview controls. Current built-in program set covers square contour, Z pocket contour, incremental loop, G2/G3 arcs, and G81 drilling. |
|
||||
| AXIS-style simulation shell | Phase 2/3/4/5 partial | `runtime/ui/simulation/index.html` now uses an AXIS-inspired titlebar, menubar, toolbar, Manual/MDI tabs, Preview/DRO tabs, G-code pane, machine-state pane, and status bar while preserving current LinuxCNC-backed execution and playback APIs. Browser smoke verifies shell regions, tab switching, toolbar playback, editor/file/OPFS program execution, blocked/ready machine-session readiness, ready session staging into WASM, loaded-session editor and OPFS execution with `runProgramWithIni()`, standalone fallback through the Use Session toggle, run-mode and run-summary UI/API state, DRO/modal state, Three.js preview controls, browser virtual HAL F1/F2/Ctrl-Home/jog/spindle/coolant controls, and existing program execution. |
|
||||
| Simulation test program library | Active | Built-in browser simulation programs live in `wasm-port/runtime/ui/simulation/programs/`, one module per program, exported through `programs/index.js`; `verify_real_simulation_programs.sh` checks directory-backed inventory, IDs, defaults, and playback contracts. |
|
||||
| Vendor/source guard | Stable | `verify_vendor_sync.sh` and standalone semantic guard are required every batch. |
|
||||
| Interpreter WASM smoke | Stable | `interp_wasm_node_smoke=ok`. |
|
||||
@@ -49,7 +49,7 @@ The project is complete for the current scope when all of the following are true
|
||||
| Native nc_files baseline | Stable | Last recorded Layer 1: `total 107`, `pass 101`, `expected_fail 6`, `unexpected_fail 0`. |
|
||||
| Native sim configs baseline | Stable | Last recorded Layer 2: `total 159`, `pass 151`, `expected_fail 8`, `unexpected_fail 0`. |
|
||||
| OPFS/session persistence | Stable for current scope | Browser smoke passes, SDK re-exports OPFS/session helpers including `readMachineSessionReadiness()` and `createMachineSessionPersistenceSummary()`, the UI exposes `getMachineSessionPersistenceSummary()`, and `docs/opfs-session-persistence.md` defines the project-level release gate. |
|
||||
| SDK/API surface | Stable for current scope | SDK re-exports core, OPFS/session readiness/load helpers, sim-config staging, project release gate manifest/result matrix/action-plan/readiness summary view-model, project batch acceptance capability matrix/workflow/summary/action-plan/checklist/report validation summary/action-plan, JSON workflow, URL workflow helpers, and artifact writer/validator gate, project release readiness report/artifact JSON workflow/artifact URL workflow summary/action-plan/artifact validation summary/action-plan plus URL loading workflow, INI panel entry/launcher/control-page/shell/workflow overview helpers including release artifact JSON and URL workflow summary/action-plan/render-state DOM mount helpers, validation action-plan render/mount helpers, URL workflow action-plan, and gate execution summary render/mount helpers, the session readiness workflow report, and the shell API surface inventory with dedicated Node smokes. |
|
||||
| SDK/API surface | Stable for current scope | SDK re-exports core, project-level virtual HAL state/action/DRO/limits-home/machine-status/WASM-bridge helpers, virtual HAL pin inventory, bridge readiness/action-plan, project report, interpreter apply adapter, OPFS/session readiness/load helpers, sim-config staging, project release gate manifest/result matrix/action-plan/readiness summary view-model, project batch acceptance capability matrix/workflow/summary/action-plan/checklist/report validation summary/action-plan, JSON workflow, URL workflow helpers, and artifact writer/validator gate, project release readiness report/artifact JSON workflow/artifact URL workflow summary/action-plan/artifact validation summary/action-plan plus URL loading workflow, INI panel entry/launcher/control-page/shell/workflow overview helpers including release artifact JSON and URL workflow summary/action-plan/render-state DOM mount helpers, validation action-plan render/mount helpers, URL workflow action-plan, and gate execution summary render/mount helpers, the session readiness workflow report, and the shell API surface inventory with dedicated Node smokes. |
|
||||
| Browser/UI workflow | Stable for current scope | Workflow overview embedding mount DOM closure is complete, and INI panel session workflows plus read-only control/shell handoff expose, render, mount, fetch, validate, summarize, action-plan, and report OPFS/session readiness, OPFS/session persistence summary, release readiness artifact JSON/URL workflows, validation/rendering/URL workflow summaries/action-plans/gate execution summaries, and API surface inventory before loading or reading WASM session state. |
|
||||
| Workflow overview embedding | Stable for current scope | Report/display/render, DOM contract/readiness/renderer, mount result/display/render, mount DOM contract/readiness/renderer/wrapper exist. |
|
||||
| Host/runtime boundary proof | Stable blocked state | Host smoke passes; runtime families remain blocked and `docs/host-runtime-boundary-handoff.md` records host readiness, promotion blockers, dispatch, and evidence gates. |
|
||||
|
||||
213
test17.txt
Normal file
213
test17.txt
Normal file
@@ -0,0 +1,213 @@
|
||||
项目整体完成情况与后续执行铁律
|
||||
|
||||
生成时间:2026-06-17 CST
|
||||
|
||||
本文件接替 `text16.txt`,作为后续继续推进的接续文件。后续工作优先参考
|
||||
`test17.txt`;除非明确要求审计旧记录,不再回到 `text1` 到 `text16` 扩展进度。
|
||||
|
||||
一、当前核心完成情况
|
||||
|
||||
1. LinuxCNC-backed WASM 核心仍是 CNC 语义来源。
|
||||
|
||||
- G-code 执行、canonical event、interpreter/planner 语义仍来自 vendored LinuxCNC
|
||||
C/C++ 源码编译出的 WASM;
|
||||
- Browser/UI/SDK 只做 host boundary、文件装载、OPFS/session、virtual HAL runtime、
|
||||
仿真状态桥、测试胶水和报告;
|
||||
- 不在 JS 中重写 G-code 解释器、planner、kinematics、tool/parameter 语义。
|
||||
|
||||
2. Web 数控仿真系统已经具备完整虚拟 HAL 闭环。
|
||||
|
||||
当前 virtual HAL 已提升为项目级 SDK/API 能力,并接入 browser simulation 页面。
|
||||
|
||||
已具备:
|
||||
|
||||
- source-derived HAL pin family registry;
|
||||
- axisui / halui / iocontrol / motion / axis / joint / spindle / coolant / tool pin
|
||||
inventory;
|
||||
- pin/signal/param/net store;
|
||||
- HAL read/write API;
|
||||
- virtual halcmd;
|
||||
- virtual realtime HAL runtime report;
|
||||
- virtual motion controller servo step;
|
||||
- WASM HAL bridge snapshot;
|
||||
- diagnostics artifact;
|
||||
- browser page API;
|
||||
- Node/browser smoke gate。
|
||||
|
||||
3. 仿真级 LinuxCNC realtime HAL / halcmd / motion controller 已由 virtual HAL 替代。
|
||||
|
||||
已新增/确认的核心 API:
|
||||
|
||||
- `VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS`:
|
||||
- `linuxcnc-realtime-hal`;
|
||||
- `halcmd`;
|
||||
- `motion-controller`;
|
||||
- `VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES`:
|
||||
- `realtime-hal-simulation-replacement`;
|
||||
- `halcmd-simulation-replacement`;
|
||||
- `motion-controller-simulation-replacement`;
|
||||
- `hal-pin-signal-param-store`;
|
||||
- `halcmd-setp-sets-net-show-getp-gets`;
|
||||
- `servo-period-motion-step`;
|
||||
- `axis-joint-position-feedback`;
|
||||
- `executeVirtualHalcmd()`;
|
||||
- `executeVirtualHalCommand()`;
|
||||
- `stepVirtualHalMotion()`;
|
||||
- `stepVirtualHalMotionController()`;
|
||||
- `createVirtualHalSimulationRuntimeReport()`;
|
||||
- `createVirtualHalSimulationReplacementReport()`;
|
||||
- `createLinuxCncVirtualHalRuntime()`:
|
||||
- `executeHalcmd()`;
|
||||
- `stepMotion()`;
|
||||
- `stepMotionController()`;
|
||||
- `getSimulationRuntimeReport()`;
|
||||
- `getSimulationReplacementReport()`。
|
||||
|
||||
Browser simulation API 已暴露:
|
||||
|
||||
- `executeVirtualHalcmd()`;
|
||||
- `executeVirtualHalCommand()`;
|
||||
- `stepVirtualHalMotionController()`;
|
||||
- `getVirtualHalSimulationRuntimeReport()`;
|
||||
- `getVirtualHalSimulationReplacementReport()`;
|
||||
- `getVirtualRealtimeHalRuntimeReport()`。
|
||||
|
||||
4. 当前 Web 仿真可满足数控系统仿真用途。
|
||||
|
||||
在 Web 方式下,数控系统仿真不再依赖 host LinuxCNC `halcmd`、host realtime HAL
|
||||
process 或 host motion process 完成仿真闭环。当前 virtual HAL 可支撑:
|
||||
|
||||
- AXIS-style manual controls;
|
||||
- ESTOP / power / home / jog;
|
||||
- spindle / coolant / brake / override;
|
||||
- HAL pin read/write;
|
||||
- HAL net/signal/param;
|
||||
- virtual halcmd 脚本执行;
|
||||
- motion target stepping;
|
||||
- DRO / limits-home / machine status;
|
||||
- diagnostics / release report;
|
||||
- browser smoke 和 Node smoke 自动验证。
|
||||
|
||||
二、虚拟 HAL 铁律
|
||||
|
||||
以下规则是后续推进的硬性约束,不能为了进度绕过。
|
||||
|
||||
1. 虚拟 HAL 功能来源铁律。
|
||||
|
||||
虚拟 HAL 的功能定义、pin family、pin 名称、pin 方向、pin 类型、状态含义、行为目标、
|
||||
报告口径和仿真能力边界,必须完全来源于 LinuxCNC 源程序、vendored LinuxCNC 源码、
|
||||
LinuxCNC 配置、LinuxCNC 脚本或已有 LinuxCNC-backed runtime evidence。
|
||||
|
||||
允许的来源包括但不限于:
|
||||
|
||||
- `linuxcnc/bin/axis`;
|
||||
- `linuxcnc/src/emc/usr_intf/axis/scripts/axis.py`;
|
||||
- `linuxcnc/src/emc/usr_intf/halui.cc`;
|
||||
- `linuxcnc/src/emc/task/taskclass.cc`;
|
||||
- `linuxcnc/src/emc/motion/motion.c`;
|
||||
- `linuxcnc/src/emc/motion/axis.c`;
|
||||
- `linuxcnc/src/emc/motion/homing.c`;
|
||||
- `linuxcnc/src/emc/motion/motion.h`;
|
||||
- vendored LinuxCNC `configs/sim`;
|
||||
- vendored LinuxCNC tests;
|
||||
- LinuxCNC-backed WASM/native probe 输出。
|
||||
|
||||
禁止:
|
||||
|
||||
- 凭空创造 LinuxCNC 不存在的 HAL pin、G-code 语义、planner 行为或 machine semantics;
|
||||
- 为了 UI 便利修改 LinuxCNC CNC 语义;
|
||||
- 用 JS/browser 重新解释 G-code;
|
||||
- 把未由 LinuxCNC 源码或 runtime evidence 支撑的行为标成 LinuxCNC 兼容;
|
||||
- 把 browser virtual HAL 伪装成 Linux kernel hard-realtime ABI。
|
||||
|
||||
2. Web 仿真满足性铁律。
|
||||
|
||||
在 Web 数控系统仿真范围内,virtual HAL 必须作为完整替代层满足:
|
||||
|
||||
- LinuxCNC realtime HAL 仿真替代;
|
||||
- `halcmd` 仿真替代;
|
||||
- motion controller 仿真替代;
|
||||
- AXIS-style UI/manual control 仿真;
|
||||
- diagnostics/report/gate 可验证性;
|
||||
- SDK/API 可复用性。
|
||||
|
||||
后续任何 Web 仿真功能,如果需要 HAL、halcmd 或 motion process,默认必须接入
|
||||
project-level virtual HAL runtime,而不是引入 host process 依赖。
|
||||
|
||||
3. 边界表述铁律。
|
||||
|
||||
对外描述必须同时满足:
|
||||
|
||||
- 可以明确说:virtual HAL 已完全满足 Web 方式数控系统仿真系统;
|
||||
- 可以明确说:在仿真范围内,virtual HAL 已替代 LinuxCNC realtime HAL / halcmd /
|
||||
motion controller;
|
||||
- 必须同时说明:它不是 Linux kernel hard-realtime ABI;
|
||||
- 必须同时说明:它不替代外部硬件驱动 ABI、native HAL module ABI;
|
||||
- 必须同时说明:G-code/interpreter/planner/canonical motion 语义仍来自 LinuxCNC-backed
|
||||
WASM。
|
||||
|
||||
三、当前已通过验证
|
||||
|
||||
最近关键验证:
|
||||
|
||||
```text
|
||||
wasm-port/tests/sdk/node/verify_sdk_surface.sh
|
||||
wasm-port/tests/ui/node/verify_real_simulation_programs.sh
|
||||
SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh
|
||||
git diff --check
|
||||
```
|
||||
|
||||
关键输出:
|
||||
|
||||
```text
|
||||
sdk_surface_node_smoke=ok
|
||||
project_release_gate_manifest_node_smoke=ok
|
||||
project_release_artifact_url_workflow_node_smoke=ok
|
||||
project_batch_acceptance_workflow_node_smoke=ok
|
||||
project_batch_acceptance_artifact_node_smoke=ok
|
||||
real_simulation_programs_node_smoke=ok
|
||||
browser_real_simulation_page_smoke=ok
|
||||
git diff --check ok
|
||||
```
|
||||
|
||||
四、后续执行规则
|
||||
|
||||
1. 每轮开始前建议执行:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git log -5 --oneline
|
||||
tail -n 160 test17.txt
|
||||
awk '/^[一二三四五六七八九十百]+、|^### / {print}' test17.txt | sort | uniq -d
|
||||
```
|
||||
|
||||
2. 后续记录规则:
|
||||
|
||||
- 新进度只追加到 `test17.txt`;
|
||||
- 每批只追加一个新的中文编号或 `###` 标题记录;
|
||||
- 提交前检查标题唯一性;
|
||||
- 不复制 `text16.txt` 的长历史;
|
||||
- 不把局部重排、断言挪动、路径整理作为主目标;
|
||||
- 每批必须产出至少一种可复用能力、可执行 gate、可验证 workflow 或明确 runtime
|
||||
boundary proof。
|
||||
|
||||
3. 后续优先级。
|
||||
|
||||
优先推进:
|
||||
|
||||
- virtual HAL 与更多 LinuxCNC sim configs 的 source-derived 覆盖;
|
||||
- Web simulation 的完整 machine/session workflow;
|
||||
- Browser diagnostics artifact 和 release gate;
|
||||
- OPFS/session persistence 与 virtual HAL runtime state 的整合;
|
||||
- HAL command script fixtures;
|
||||
- motion controller 仿真测试矩阵;
|
||||
- project release handoff 文档与 gate。
|
||||
|
||||
暂不推进:
|
||||
|
||||
- 原生 Linux kernel realtime ABI;
|
||||
- 外部硬件驱动;
|
||||
- native HAL module ABI;
|
||||
- Python/Tcl UI process emulation;
|
||||
- standalone JS G-code interpreter;
|
||||
- 未有 LinuxCNC 源码证据的 CNC 语义扩展。
|
||||
254
text16.txt
254
text16.txt
@@ -1179,3 +1179,257 @@ git diff --check ok
|
||||
- grid 和 labels 只使用当前 preview viewBox;
|
||||
- Three.js 仍只显示 LinuxCNC canonical motion;
|
||||
- pixel smoke 只验证渲染非空,不推导 CNC 语义。
|
||||
|
||||
### 十七、2026-06-17 Browser Virtual HAL for AXIS Manual Controls
|
||||
|
||||
接续“虚拟 HAL 业务”,本批把 AXIS 手动控制从静态未绑定状态推进为浏览器
|
||||
虚拟 HAL 状态模型,并接入 UI、SDK 和 WASM bridge。
|
||||
|
||||
本批新增实质能力:
|
||||
|
||||
- 新增 `wasm-port/runtime/sdk/src/linuxcnc-hal.js`:
|
||||
- `VIRTUAL_HAL_AXISUI_PINS` 对齐 `linuxcnc/bin/axis` 中的 axisui pins:
|
||||
`jog.x/y/z/a/b/c/u/v/w`、`jog.increment`、notification pins、
|
||||
`error`、`abort`;
|
||||
- `createVirtualHalState()` / `applyVirtualHalAction()` 管理 ESTOP、Power、
|
||||
Home、Jog、Touch Off、Spindle、Coolant、Limit Override、Brake 等状态;
|
||||
- `createVirtualHalDroState()` 输出 AXIS DRO 默认/实际位置;
|
||||
- `createVirtualHalLimitsHomeState()` 输出 home/limit/fault rows;
|
||||
- `createVirtualHalMachineStatusState()` 输出 spindle/coolant/tool/override state;
|
||||
- `createVirtualHalWasmBridgeSnapshot()` 生成可写入 standalone HAL adapter 的
|
||||
pin/value snapshot。
|
||||
|
||||
- SDK 入口新增 re-export:
|
||||
- virtual HAL 状态、action、DRO、limits/home、machine status、runtime、
|
||||
bridge snapshot、jog increment parser。
|
||||
|
||||
- Interpreter SDK 新增 HAL bridge API:
|
||||
- `hasWasmFunction(name)`;
|
||||
- `resetHal()`;
|
||||
- `setHalValue({ kind, name, type, value, connected })`;
|
||||
- `applyVirtualHalSnapshot(snapshot, options)`;
|
||||
- `applyVirtualHalState(halState, options)`;
|
||||
- `probeHalNamed(name)`。
|
||||
|
||||
- WASM wrapper 新增导出:
|
||||
- `lcinterp_hal_reset()`;
|
||||
- `lcinterp_hal_set_value()`;
|
||||
- `lcinterp_probe_hal_named()`;
|
||||
- `build_wasm_core.sh` 已把这些函数加入 `EXPORTED_FUNCTIONS`。
|
||||
|
||||
- Browser simulation UI 接入:
|
||||
- F1/F2 快捷键驱动 ESTOP/Power;
|
||||
- Ctrl-Home 驱动 Home All;
|
||||
- Manual jog 更新 `jog.*` pins 和 DRO;
|
||||
- spindle/coolant/brake/limit override 写入 virtual HAL state;
|
||||
- diagnostics artifact 新增 `virtualHal`;
|
||||
- machine control state 从“not bound”变为
|
||||
`browser virtual HAL active for jog/spindle/coolant/home simulation`。
|
||||
|
||||
- Bridge snapshot 同时输出:
|
||||
- AXIS Python component 内部 pin 名,例如 `jog.x`;
|
||||
- HAL 全名,例如 `axisui.jog.x`;
|
||||
- machine/task pins,例如 `halui.machine.is-on`;
|
||||
- spindle/coolant pins,例如 `spindle.0.speed-out`、
|
||||
`iocontrol.0.coolant-flood`。
|
||||
|
||||
新增/扩展验证:
|
||||
|
||||
- `verify_real_simulation_programs.mjs` 覆盖 virtual HAL SDK、runtime snapshot、
|
||||
`axisui.*` HAL 全名、DRO、home、spindle、coolant;
|
||||
- `real_simulation_page_smoke.html` 覆盖 F1/F2/Ctrl-Home/jog/spindle/coolant 的
|
||||
浏览器交互;
|
||||
- `verify_interp_wasm.mjs` 在新 WASM 导出存在时验证:
|
||||
`applyVirtualHalState()` 写入 HAL adapter 后,`probeHalNamed()` 能读到
|
||||
`_hal[jog.x]`、`_hal[axisui.jog.x]`、`_hal[halui.machine.is-on]`、
|
||||
`_hal[spindle.0.speed-out]`、`_hal[iocontrol.0.coolant-flood]`。
|
||||
|
||||
关键验证:
|
||||
|
||||
```text
|
||||
real_simulation_programs_node_smoke=ok
|
||||
interp_wasm_node_smoke=ok (SKIP_INTERP_BUILD=1)
|
||||
browser_real_simulation_page_smoke=ok (SKIP_INTERP_BUILD=1)
|
||||
sdk_surface_node_smoke=ok
|
||||
ui_node_smokes=ok
|
||||
git diff --check ok
|
||||
```
|
||||
|
||||
当前构建备注:
|
||||
|
||||
- 当前 Codex shell 的 `PATH` 未解析到 `emcc`,完整 `build_wasm_core.sh` 会停在
|
||||
`emcc: command not found`;
|
||||
- 这不是代码侧缺少 Emscripten 的结论,只是本 shell 未加载用户已有的
|
||||
Emscripten 环境;
|
||||
- 快速 smoke 已用现有 WASM 产物验证旧路径不回归,新导出的 HAL bridge smoke 会在
|
||||
重建后的 WASM 存在对应函数时自动执行。
|
||||
|
||||
语义边界:
|
||||
|
||||
- virtual HAL 只模拟 AXIS/manual UI 与 standalone HAL adapter 的状态桥;
|
||||
- 不在浏览器端实现 LinuxCNC realtime HAL、motion controller、planner、
|
||||
kinematics 或 G-code 语义;
|
||||
- G-code execution 和 canonical motion 仍来自 LinuxCNC interpreter WASM。
|
||||
|
||||
### 十八、2026-06-17 Project-level Virtual HAL SDK Boundary
|
||||
|
||||
按用户要求“虚拟 HAL 适合整个项目”,本批把上一节的 simulation/UI 虚拟 HAL
|
||||
提升为项目级 SDK host-boundary 能力,供 simulation、INI panel、Node/browser
|
||||
smoke、外部 dashboard 和 interpreter bridge 共用。
|
||||
|
||||
新增项目级 API:
|
||||
|
||||
- `VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS`:
|
||||
- `lcinterp_hal_reset`;
|
||||
- `lcinterp_hal_set_value`;
|
||||
- `lcinterp_probe_hal_named`;
|
||||
- `VIRTUAL_HAL_PROJECT_PIN_GROUPS`:
|
||||
- `axisui`;
|
||||
- `axis`;
|
||||
- `task`;
|
||||
- `spindle-coolant`;
|
||||
- `createVirtualHalPinInventory(halState)`:
|
||||
- 输出全部 bridge pin rows;
|
||||
- 按项目级 pin group 归类;
|
||||
- `createVirtualHalBridgeReadiness(options)`:
|
||||
- 检查 snapshot 是否包含 required pins;
|
||||
- 检查 caller-provided WASM function evidence 是否覆盖
|
||||
`VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS`;
|
||||
- 无 evidence 时显式 blocked:`wasm-function-evidence`;
|
||||
- `createVirtualHalBridgeActionPlan(readiness)`:
|
||||
- blocked 时给出下一步:
|
||||
`wasm-port/tools/build_wasm_core.sh`;
|
||||
`SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh`;
|
||||
- ready 时返回空 command;
|
||||
- `createVirtualHalProjectReport(options)`:
|
||||
- 汇总 state、DRO、limits/home、machine status、pin inventory、
|
||||
WASM bridge snapshot、bridge readiness、bridge action plan;
|
||||
- `applyVirtualHalToInterpSdk(interpSdk, halState, options)`:
|
||||
- 对有 `applyVirtualHalState()` 或 `applyVirtualHalSnapshot()` 的 interpreter
|
||||
SDK 做统一适配;
|
||||
- `createLinuxCncVirtualHalRuntime()` 新增:
|
||||
- `getPinInventory()`;
|
||||
- `getProjectReport(options)`;
|
||||
- `applyToInterpSdk(interpSdk, options)`。
|
||||
|
||||
文档同步:
|
||||
|
||||
- `runtime/sdk/README.md` 新增 `Project-level virtual HAL boundary` 小节;
|
||||
- `docs/project-release-handoff.md` 把 project-level virtual HAL 纳入当前支持
|
||||
workflow;
|
||||
- `PROJECT_COMPLETION_TRACKER.md` 的 SDK/API surface 行记录 pin inventory、
|
||||
bridge readiness/action-plan、project report、interpreter apply adapter;
|
||||
- `verify_project_release_handoff_docs.mjs` 增加文档 gate,防止后续退回页面私有
|
||||
virtual HAL。
|
||||
|
||||
新增/扩展验证:
|
||||
|
||||
- `verify_sdk_surface.mjs` 覆盖:
|
||||
- SDK re-export;
|
||||
- constants discovery;
|
||||
- virtual HAL snapshot;
|
||||
- pin inventory;
|
||||
- blocked/ready bridge readiness;
|
||||
- bridge action plan;
|
||||
- project report;
|
||||
- runtime project report;
|
||||
- fake interpreter SDK apply wrapper;
|
||||
- `verify_project_release_handoff_docs.mjs` 覆盖 handoff/README 中的项目级
|
||||
virtual HAL API 和 realtime HAL runtime 边界声明。
|
||||
|
||||
关键验证:
|
||||
|
||||
```text
|
||||
sdk_surface_node_smoke=ok
|
||||
project_release_handoff_docs_node_smoke=ok
|
||||
real_simulation_programs_node_smoke=ok
|
||||
git diff --check ok
|
||||
```
|
||||
|
||||
语义边界:
|
||||
|
||||
- project-level virtual HAL 仍是 host-boundary adapter;
|
||||
- 它可服务 UI/session/diagnostics/dashboard 和 `_hal[...]` interpreter probe;
|
||||
- 它不是 LinuxCNC realtime HAL runtime;
|
||||
- 不提升 task、motion、HAL module loading、HALUI process、Tcl/Python process、
|
||||
external device semantics。
|
||||
|
||||
### 十九、2026-06-17 Simulation-grade LinuxCNC Realtime HAL / halcmd / Motion Replacement
|
||||
|
||||
按用户要求“在满足仿真方面,实现 LinuxCNC realtime HAL / halcmd / motion controller
|
||||
的完整替代,以虚拟 HAL 方式实现”,本批把上一节项目级 virtual HAL 从 bridge/readiness
|
||||
推进为仿真级 runtime replacement。
|
||||
|
||||
新增实质能力:
|
||||
|
||||
- `VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS`:
|
||||
- `linuxcnc-realtime-hal`;
|
||||
- `halcmd`;
|
||||
- `motion-controller`;
|
||||
- `VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES` 新增:
|
||||
- `realtime-hal-simulation-replacement`;
|
||||
- `halcmd-simulation-replacement`;
|
||||
- `motion-controller-simulation-replacement`;
|
||||
- 新增 `executeVirtualHalcmd()`:
|
||||
- 作为 `executeVirtualHalCommand()` 的 halcmd 语义别名;
|
||||
- 支持当前仿真所需 `setp`、`sets`、`newsig`、`net`、`show`、`getp`、`gets`、
|
||||
`loadrt`、`loadusr`、`addf`、`start`、`stop`、`unlinkp`、`motion-step`;
|
||||
- 新增 `stepVirtualHalMotionController()`:
|
||||
- 多周期 servo step;
|
||||
- 输出每周期 position/motion frame;
|
||||
- 更新 target、velocity、distance-to-go、in-position;
|
||||
- 新增 `createVirtualHalSimulationReplacementReport()`:
|
||||
- 明确报告 realtime HAL、halcmd、motion controller 三类仿真替代 ready 状态;
|
||||
- 保留 `hardRealtime: false`、`realtimeKernelAbi: false`,避免伪装为 Linux kernel
|
||||
realtime ABI;
|
||||
- `createLinuxCncVirtualHalRuntime()` 新增:
|
||||
- `executeHalcmd()`;
|
||||
- `stepMotionController()`;
|
||||
- `getSimulationReplacementReport()`。
|
||||
|
||||
Browser simulation API 新增:
|
||||
|
||||
- `executeVirtualHalcmd()`;
|
||||
- `executeVirtualHalCommand()`;
|
||||
- `stepVirtualHalMotionController()`;
|
||||
- `getVirtualHalSimulationRuntimeReport()`;
|
||||
- `getVirtualHalSimulationReplacementReport()`;
|
||||
- `getVirtualRealtimeHalRuntimeReport()`;
|
||||
- diagnostics artifact 新增 `virtualHalSimulationReplacement`。
|
||||
|
||||
新增/扩展验证:
|
||||
|
||||
- `verify_sdk_surface.mjs` 覆盖:
|
||||
- replacement target constants;
|
||||
- runtime capability constants;
|
||||
- `executeVirtualHalcmd()`;
|
||||
- `stepVirtualHalMotionController()`;
|
||||
- `createVirtualHalSimulationReplacementReport()`;
|
||||
- runtime wrapper 的 `executeHalcmd()` / `stepMotionController()` /
|
||||
`getSimulationReplacementReport()`;
|
||||
- `real_simulation_page_smoke.html` 覆盖:
|
||||
- 页面 API 暴露 halcmd/motion/replacement report;
|
||||
- browser 中执行 `loadrt`、`addf`、`setp`、`show pin`;
|
||||
- browser 中执行 motion controller 多周期 step;
|
||||
- diagnostics artifact 携带 replacement report。
|
||||
|
||||
关键验证:
|
||||
|
||||
```text
|
||||
sdk_surface_node_smoke=ok
|
||||
project_release_gate_manifest_node_smoke=ok
|
||||
project_release_artifact_url_workflow_node_smoke=ok
|
||||
project_batch_acceptance_workflow_node_smoke=ok
|
||||
project_batch_acceptance_artifact_node_smoke=ok
|
||||
real_simulation_programs_node_smoke=ok
|
||||
browser_real_simulation_page_smoke=ok
|
||||
git diff --check ok
|
||||
```
|
||||
|
||||
语义边界:
|
||||
|
||||
- 本批实现的是 simulation-grade replacement;
|
||||
- Browser/SDK 仿真不再依赖 host `halcmd`、host realtime HAL process 或 host motion
|
||||
process 来完成页面/Node 仿真闭环;
|
||||
- 仍不声称提供 Linux kernel hard-realtime ABI、外部硬件驱动 ABI、native HAL module ABI;
|
||||
- G-code 执行、canonical event、planner/interpreter 语义仍来自 LinuxCNC-backed WASM。
|
||||
|
||||
@@ -56,6 +56,32 @@ owned by vendored LinuxCNC source and existing LinuxCNC-backed WASM boundaries.
|
||||
`createIniPanelShellSessionReadinessWorkflowReport()` when they need
|
||||
machine-readable session readiness phase and missing reasons. The workflow
|
||||
overview exposes the API surface inventory as a read-only summary row.
|
||||
- Project-level virtual HAL is exposed from the same SDK entrypoint for
|
||||
simulation pages, INI-panel shells, Node/browser smokes, and external
|
||||
dashboards. Use `createVirtualHalState()`, `applyVirtualHalAction()`,
|
||||
`createLinuxCncVirtualHalRuntime()`, and
|
||||
`createVirtualHalWasmBridgeSnapshot()` for shared browser virtual HAL state
|
||||
and standalone interpreter bridge values. Use
|
||||
`createVirtualHalPinInventory()`, `createVirtualHalBridgeReadiness()`,
|
||||
`createVirtualHalBridgeActionPlan()`, and
|
||||
`createVirtualHalProjectReport()` when a caller needs a machine-readable
|
||||
project report, pin inventory, or readiness/action-plan surface. The bridge
|
||||
readiness checks caller-provided evidence for
|
||||
`VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS`, while
|
||||
`VIRTUAL_HAL_PROJECT_PIN_GROUPS` provides stable inventory grouping.
|
||||
`VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES`,
|
||||
`executeVirtualHalCommand()`, `stepVirtualHalMotion()`, and
|
||||
`createVirtualHalSimulationRuntimeReport()` provide the simulation-grade
|
||||
replacement for host `halcmd`, HAL pin/signal/param storage, and the basic
|
||||
motion feedback loop used by browser/Node simulation. For simulation, this
|
||||
removes the host `halcmd`/`halrun`/motion-controller requirement.
|
||||
`applyVirtualHalToInterpSdk()` is the common adapter for interpreter SDK
|
||||
instances that expose `applyVirtualHalState()` or
|
||||
`applyVirtualHalSnapshot()`. This is a simulation runtime and virtual
|
||||
realtime HAL runtime replacement for browser workflows, not a Linux kernel
|
||||
hard-realtime ABI; kernel realtime scheduling, external device drivers, native
|
||||
HAL module ABI behavior, HALUI process behavior, Tcl/Python process
|
||||
integration, and external device semantics remain explicit boundaries.
|
||||
- CI dashboards and external SDK callers can use
|
||||
`createProjectReleaseGateManifest()` to discover required gate commands and
|
||||
expected smoke outputs, `createProjectReleaseGateResultMatrix()` to map
|
||||
|
||||
@@ -550,6 +550,82 @@ char *load_tool_table_with_mode(const char *path, bool random_tool_changer)
|
||||
return copy_result(output.str());
|
||||
}
|
||||
|
||||
standalone::HalValueKind hal_value_kind_from_name(const char *kind)
|
||||
{
|
||||
const std::string value = kind ? kind : "";
|
||||
if (value == "signal") {
|
||||
return standalone::HalValueKind::Signal;
|
||||
}
|
||||
if (value == "param") {
|
||||
return standalone::HalValueKind::Param;
|
||||
}
|
||||
return standalone::HalValueKind::Pin;
|
||||
}
|
||||
|
||||
hal_type_t hal_type_from_name(const char *type)
|
||||
{
|
||||
const std::string value = type ? type : "";
|
||||
if (value == "HAL_BIT") {
|
||||
return HAL_BIT;
|
||||
}
|
||||
if (value == "HAL_S32") {
|
||||
return HAL_S32;
|
||||
}
|
||||
if (value == "HAL_U32") {
|
||||
return HAL_U32;
|
||||
}
|
||||
if (value == "HAL_S64") {
|
||||
return HAL_S64;
|
||||
}
|
||||
if (value == "HAL_U64") {
|
||||
return HAL_U64;
|
||||
}
|
||||
return HAL_FLOAT;
|
||||
}
|
||||
|
||||
hal_data_u hal_data_from_double(hal_type_t type, double value)
|
||||
{
|
||||
hal_data_u data{};
|
||||
switch (type) {
|
||||
case HAL_BIT:
|
||||
data.b = value != 0.0;
|
||||
break;
|
||||
case HAL_S32:
|
||||
data.s = static_cast<int>(value);
|
||||
break;
|
||||
case HAL_U32:
|
||||
data.u = static_cast<unsigned int>(value);
|
||||
break;
|
||||
case HAL_S64:
|
||||
data.ls = static_cast<long long>(value);
|
||||
break;
|
||||
case HAL_U64:
|
||||
data.lu = static_cast<unsigned long long>(value);
|
||||
break;
|
||||
case HAL_FLOAT:
|
||||
default:
|
||||
data.f = value;
|
||||
break;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void initialize_hal_probe_interp(Interp &interp)
|
||||
{
|
||||
interp._setup.length_units = CANON_UNITS_MM;
|
||||
interp._setup.distance_mode = DISTANCE_MODE::ABSOLUTE;
|
||||
interp._setup.ijk_distance_mode = DISTANCE_MODE::INCREMENTAL;
|
||||
interp._setup.feed_mode = FEED_MODE::UNITS_PER_MINUTE;
|
||||
interp._setup.plane = CANON_PLANE::XY;
|
||||
interp._setup.motion_mode = G_0;
|
||||
interp._setup.percent_flag = false;
|
||||
interp._setup.sequence_number = 0;
|
||||
interp._setup.parameter_occurrence = 0;
|
||||
interp._setup.num_spindles = 1;
|
||||
interp._setup.feature_set = FEATURE_HAL_PIN_VARS;
|
||||
interp.init_named_parameters();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
@@ -1201,6 +1277,39 @@ char *lcinterp_probe_named_parameters(const char *ini_path)
|
||||
return copy_result(output.str());
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void lcinterp_hal_reset()
|
||||
{
|
||||
standalone::reset_hal_adapter();
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int lcinterp_hal_set_value(const char *kind, const char *name, const char *type,
|
||||
double value, int connected)
|
||||
{
|
||||
if (!name || name[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
const hal_type_t resolved_type = hal_type_from_name(type);
|
||||
standalone::set_hal_value(
|
||||
hal_value_kind_from_name(kind),
|
||||
name,
|
||||
resolved_type,
|
||||
hal_data_from_double(resolved_type, value),
|
||||
connected != 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
char *lcinterp_probe_hal_named(const char *name)
|
||||
{
|
||||
Interp interp;
|
||||
initialize_hal_probe_interp(interp);
|
||||
std::ostringstream output;
|
||||
append_named_value(output, interp, name ? name : "_hal[missing]");
|
||||
return copy_result(output.str());
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void lcinterp_free_string(char *value)
|
||||
{
|
||||
|
||||
@@ -66,11 +66,39 @@ import {
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness,
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState,
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
|
||||
VIRTUAL_HAL_COVERAGE_STATES,
|
||||
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
|
||||
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
|
||||
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
|
||||
VIRTUAL_HAL_SOURCE_FILES,
|
||||
VIRTUAL_HAL_SYSTEM_PIN_FAMILIES,
|
||||
VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS,
|
||||
applyVirtualHalAction,
|
||||
applyVirtualHalPinUpdates,
|
||||
applyVirtualHalToInterpSdk,
|
||||
createLinuxCncIniSdk,
|
||||
createLinuxCncInterpSdk,
|
||||
createLinuxCncVirtualHalRuntime,
|
||||
createMachineSessionPersistenceDisplayViewModel,
|
||||
createMachineSessionPersistenceRenderState,
|
||||
createMachineSessionPersistenceSummary,
|
||||
createVirtualHalBridgeActionPlan,
|
||||
createVirtualHalBridgeReadiness,
|
||||
createVirtualHalIntegrityReport,
|
||||
createVirtualHalPinInventory,
|
||||
createVirtualHalPinRegistry,
|
||||
createVirtualHalProjectReport,
|
||||
createVirtualHalSimulationReplacementReport,
|
||||
createVirtualHalSimulationRuntimeReport,
|
||||
createVirtualHalState,
|
||||
createVirtualHalSystemCoverageReport,
|
||||
createVirtualHalWasmBridgeSnapshot,
|
||||
executeVirtualHalCommand,
|
||||
executeVirtualHalcmd,
|
||||
readVirtualHalPin,
|
||||
stepVirtualHalMotion,
|
||||
stepVirtualHalMotionController,
|
||||
writeVirtualHalPin,
|
||||
createMachineSessionSnapshotPayload,
|
||||
createProjectReleaseGateActionPlan,
|
||||
createProjectReleaseGateExecutionManifest,
|
||||
@@ -241,6 +269,78 @@ path but keeps the runner loop going after LinuxCNC reports an error, matching
|
||||
upstream `rs274 -n 0` regression tests such as `tests/interp/oword-unwind`.
|
||||
It does not implement or reinterpret LinuxCNC error semantics.
|
||||
|
||||
## Project-level virtual HAL boundary
|
||||
|
||||
The SDK exposes a project-level browser/Node virtual HAL boundary for UI,
|
||||
session, diagnostics, and standalone interpreter bridge workflows. It is shared
|
||||
through `runtime/sdk/src/index.js`, so simulation pages, INI-panel shells,
|
||||
Node smokes, and external dashboards can use the same objects instead of
|
||||
private page state.
|
||||
|
||||
- `createVirtualHalState()` creates the normalized browser virtual HAL state.
|
||||
- `applyVirtualHalAction()` applies stable manual-control actions such as
|
||||
ESTOP, power, home, jog, spindle, coolant, brake, touch-off, and limit
|
||||
override.
|
||||
- `createLinuxCncVirtualHalRuntime()` wraps that state in a small runtime with
|
||||
`dispatch()`, `getDroState()`, `getLimitsHomeState()`,
|
||||
`getMachineStatusState()`, `getWasmBridgeSnapshot()`, `getPinRegistry()`,
|
||||
`readPin()`, `writePin()`, `applyPinUpdates()`, `executeHalCommand()`,
|
||||
`executeHalcmd()`, `stepMotion()`, `stepMotionController()`,
|
||||
`getSimulationRuntimeReport()`, `getSimulationReplacementReport()`,
|
||||
`getIntegrityReport()`, `getPinInventory()`, `getProjectReport()`, and
|
||||
`applyToInterpSdk()`.
|
||||
- `VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES`,
|
||||
`VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS`,
|
||||
`executeVirtualHalCommand()`, `executeVirtualHalcmd()`,
|
||||
`stepVirtualHalMotion()`, `stepVirtualHalMotionController()`,
|
||||
`createVirtualHalSimulationRuntimeReport()`, and
|
||||
`createVirtualHalSimulationReplacementReport()` expose a simulation-grade
|
||||
replacement for the host LinuxCNC realtime HAL, `halcmd`, and motion
|
||||
controller runtime. It supports HAL pin/signal/param storage, `setp`,
|
||||
`sets`, `newsig`, `net`, `show`, `getp`, `gets`, `loadrt`/`loadusr` stubs,
|
||||
`addf`, thread start/stop, and servo-period motion stepping for browser
|
||||
simulation and Node dashboards.
|
||||
- `createVirtualHalWasmBridgeSnapshot()` returns the pin/value rows that can be
|
||||
applied through the interpreter SDK HAL bridge. It includes AXIS internal pin
|
||||
names such as `jog.x`, full HAL names such as `axisui.jog.x`, task/power
|
||||
names such as `halui.machine.is-on`, and spindle/coolant names such as
|
||||
`spindle.0.speed-out` and `iocontrol.0.coolant-flood`.
|
||||
- `createVirtualHalPinRegistry()`, `readVirtualHalPin()`,
|
||||
`writeVirtualHalPin()`, and `applyVirtualHalPinUpdates()` expose the project
|
||||
virtual HAL as a source-derived pin service. Callers can read/write HAL names
|
||||
such as `halui.machine.on`, `spindle.0.speed-out`,
|
||||
`motion.tooloffset.z`, or `joint.0.homed` without duplicating pin mapping.
|
||||
- `createVirtualHalIntegrityReport()` verifies registry completeness, duplicate
|
||||
pin absence, required project pins, and source coverage in one machine
|
||||
readable report.
|
||||
- `createVirtualHalPinInventory()` returns a stable project inventory grouped
|
||||
by `VIRTUAL_HAL_PROJECT_PIN_GROUPS`.
|
||||
- `VIRTUAL_HAL_SYSTEM_PIN_FAMILIES`, `VIRTUAL_HAL_SOURCE_FILES`, and
|
||||
`VIRTUAL_HAL_COVERAGE_STATES` expose the source-derived coverage model for
|
||||
AXIS, HALUI, iocontrol, motion, axis, joint, spindle, coolant, and tool pins.
|
||||
- `createVirtualHalSystemCoverageReport()` expands that source-derived model
|
||||
into a machine-readable completeness report. It records the LinuxCNC source
|
||||
files used as evidence, concrete declared pin rows, represented bridge pins,
|
||||
and explicit `bridge-only` / `runtime-boundary` rows.
|
||||
- `createVirtualHalBridgeReadiness()` checks that a snapshot has required
|
||||
bridge pins and that caller-provided WASM function evidence covers
|
||||
`VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS`.
|
||||
- `createVirtualHalBridgeActionPlan()` turns blocked bridge readiness into the
|
||||
next build/verify commands.
|
||||
- `createVirtualHalProjectReport()` packages state, DRO, limits/home, machine
|
||||
status, pin inventory, pin registry, integrity, system coverage, simulation
|
||||
runtime evidence, bridge snapshot, bridge readiness, and action plan into one
|
||||
stable report for dashboards or release artifacts.
|
||||
- `applyVirtualHalToInterpSdk()` applies a virtual HAL state to any interpreter
|
||||
SDK instance exposing `applyVirtualHalState()` or `applyVirtualHalSnapshot()`.
|
||||
|
||||
For simulation, this replaces the host `halcmd`, HAL object store, and basic
|
||||
motion feedback loop. It is a virtual realtime HAL runtime replacement for
|
||||
browser workflows, but still not a Linux kernel hard-realtime ABI: kernel
|
||||
realtime scheduling, external device drivers, native HAL module ABI behavior,
|
||||
HALUI process behavior, and Tcl/Python process integration remain explicit
|
||||
boundaries.
|
||||
|
||||
## Project release readiness export
|
||||
|
||||
`createProjectReleaseGateManifest()` returns the required release gate IDs,
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
export { createLinuxCncIniSdk } from "./linuxcnc-ini.js";
|
||||
export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
|
||||
export {
|
||||
VIRTUAL_HAL_AXES,
|
||||
VIRTUAL_HAL_AXISUI_PINS,
|
||||
VIRTUAL_HAL_COVERAGE_STATES,
|
||||
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
|
||||
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
|
||||
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
|
||||
VIRTUAL_HAL_SOURCE_FILES,
|
||||
VIRTUAL_HAL_SYSTEM_PIN_FAMILIES,
|
||||
VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS,
|
||||
applyVirtualHalToInterpSdk,
|
||||
applyVirtualHalAction,
|
||||
applyVirtualHalPinUpdates,
|
||||
cloneVirtualHalState,
|
||||
createLinuxCncVirtualHalRuntime,
|
||||
createVirtualHalBridgeActionPlan,
|
||||
createVirtualHalBridgeReadiness,
|
||||
createVirtualHalDroState,
|
||||
createVirtualHalLimitsHomeState,
|
||||
createVirtualHalMachineStatusState,
|
||||
createVirtualHalIntegrityReport,
|
||||
createVirtualHalPinInventory,
|
||||
createVirtualHalPinRegistry,
|
||||
createVirtualHalProjectReport,
|
||||
createVirtualHalSimulationReplacementReport,
|
||||
createVirtualHalSimulationRuntimeReport,
|
||||
createVirtualHalState,
|
||||
createVirtualHalSystemCoverageReport,
|
||||
createVirtualHalWasmBridgeSnapshot,
|
||||
executeVirtualHalCommand,
|
||||
executeVirtualHalcmd,
|
||||
parseVirtualHalJogIncrement,
|
||||
readVirtualHalPin,
|
||||
stepVirtualHalMotionController,
|
||||
stepVirtualHalMotion,
|
||||
writeVirtualHalPin,
|
||||
} from "./linuxcnc-hal.js";
|
||||
export {
|
||||
createProjectBatchAcceptanceActionPlan,
|
||||
createProjectBatchAcceptanceCapabilityMatrix,
|
||||
|
||||
2315
wasm-port/runtime/sdk/src/linuxcnc-hal.js
Normal file
2315
wasm-port/runtime/sdk/src/linuxcnc-hal.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
import createLinuxCncInterpModule from "../../../build/wasm/core/linuxcnc_interp.js";
|
||||
import { createVirtualHalWasmBridgeSnapshot } from "./linuxcnc-hal.js";
|
||||
|
||||
function allocCString(mod, value) {
|
||||
const bytes = mod.lengthBytesUTF8(value) + 1;
|
||||
@@ -43,12 +44,36 @@ function callStringResult(mod, functionName, ...values) {
|
||||
}
|
||||
}
|
||||
|
||||
function requireWasmFunction(mod, functionName) {
|
||||
const fn = mod[`_${functionName}`];
|
||||
if (typeof fn !== "function") {
|
||||
throw new Error(`linuxcnc interpreter WASM missing ${functionName}; rebuild wasm-port/tools/build_wasm_core.sh`);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
function callVoidWithStringsAndNumbers(mod, functionName, strings, numbers = []) {
|
||||
const fn = requireWasmFunction(mod, functionName);
|
||||
const ptrs = strings.map((value) => allocCString(mod, value));
|
||||
try {
|
||||
return fn(...ptrs, ...numbers);
|
||||
} finally {
|
||||
for (const ptr of ptrs) {
|
||||
mod._free(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function createLinuxCncInterpSdk(moduleOptions = {}) {
|
||||
const mod = await createLinuxCncInterpModule(moduleOptions);
|
||||
|
||||
return {
|
||||
module: mod,
|
||||
|
||||
hasWasmFunction(functionName) {
|
||||
return typeof mod[`_${functionName}`] === "function";
|
||||
},
|
||||
|
||||
writeTextFile(path, text) {
|
||||
ensureParentPath(mod, path);
|
||||
mod.FS.writeFile(path, text, { encoding: "utf8" });
|
||||
@@ -160,5 +185,44 @@ export async function createLinuxCncInterpSdk(moduleOptions = {}) {
|
||||
probeNamedParameters(iniPath) {
|
||||
return callStringResult(mod, "lcinterp_probe_named_parameters", iniPath);
|
||||
},
|
||||
|
||||
resetHal() {
|
||||
requireWasmFunction(mod, "lcinterp_hal_reset")();
|
||||
},
|
||||
|
||||
setHalValue({ kind = "pin", name, type = "HAL_FLOAT", value = 0, connected = true }) {
|
||||
if (!name) {
|
||||
throw new Error("setHalValue requires a HAL name");
|
||||
}
|
||||
return callVoidWithStringsAndNumbers(
|
||||
mod,
|
||||
"lcinterp_hal_set_value",
|
||||
[kind, name, type],
|
||||
[Number(value) || 0, connected === false ? 0 : 1],
|
||||
);
|
||||
},
|
||||
|
||||
applyVirtualHalSnapshot(snapshot, options = {}) {
|
||||
if (options.reset !== false) {
|
||||
this.resetHal();
|
||||
}
|
||||
for (const value of snapshot?.values ?? []) {
|
||||
this.setHalValue(value);
|
||||
}
|
||||
return {
|
||||
apiName: "linuxcnc-wasm-hal-apply-result",
|
||||
applied: snapshot?.values?.length ?? 0,
|
||||
reset: options.reset !== false,
|
||||
source: snapshot?.source ?? "unknown",
|
||||
};
|
||||
},
|
||||
|
||||
applyVirtualHalState(halState, options = {}) {
|
||||
return this.applyVirtualHalSnapshot(createVirtualHalWasmBridgeSnapshot(halState), options);
|
||||
},
|
||||
|
||||
probeHalNamed(name) {
|
||||
return callStringResult(mod, "lcinterp_probe_hal_named", name);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,32 @@ export {
|
||||
getSimulationTestProgram,
|
||||
getSimulationTestPrograms,
|
||||
};
|
||||
export {
|
||||
VIRTUAL_HAL_AXES,
|
||||
VIRTUAL_HAL_AXISUI_PINS,
|
||||
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
|
||||
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
|
||||
applyVirtualHalAction,
|
||||
applyVirtualHalPinUpdates,
|
||||
cloneVirtualHalState,
|
||||
createLinuxCncVirtualHalRuntime,
|
||||
createVirtualHalDroState,
|
||||
createVirtualHalIntegrityReport,
|
||||
createVirtualHalLimitsHomeState,
|
||||
createVirtualHalMachineStatusState,
|
||||
createVirtualHalPinRegistry,
|
||||
createVirtualHalSimulationReplacementReport,
|
||||
createVirtualHalSimulationRuntimeReport,
|
||||
createVirtualHalState,
|
||||
createVirtualHalWasmBridgeSnapshot,
|
||||
executeVirtualHalCommand,
|
||||
executeVirtualHalcmd,
|
||||
parseVirtualHalJogIncrement,
|
||||
readVirtualHalPin,
|
||||
stepVirtualHalMotion,
|
||||
stepVirtualHalMotionController,
|
||||
writeVirtualHalPin,
|
||||
} from "../../sdk/src/index.js";
|
||||
|
||||
const AXES = ["x", "y", "z", "a", "b", "c", "u", "v", "w"];
|
||||
const DISPLAY_AXES = ["x", "y", "z", "a", "b", "c"];
|
||||
@@ -23,7 +49,7 @@ const PLANE_AXIS_MAP = {
|
||||
180: ["x", "z", "y"],
|
||||
190: ["y", "z", "x"],
|
||||
};
|
||||
const UNAVAILABLE_VALUE = "n/a";
|
||||
const UNAVAILABLE_VALUE = "-";
|
||||
const DEFAULT_PREVIEW_LAYER_STATE = Object.freeze({
|
||||
traverse: true,
|
||||
feed: true,
|
||||
@@ -178,35 +204,58 @@ export function createModalState(resultText) {
|
||||
available: Number.isFinite(fields[field]),
|
||||
source: Number.isFinite(fields[field]) ? "linuxcnc-update-tag" : "unavailable",
|
||||
}));
|
||||
const axisCodes = [
|
||||
fields.motion === 0 ? "G80" : `G${fields.motion ?? 80}`,
|
||||
({ 170: "G17", 180: "G18", 190: "G19" })[fields.plane] ?? "G17",
|
||||
"G40",
|
||||
"G20",
|
||||
"G90",
|
||||
"G94",
|
||||
"G54",
|
||||
"G49",
|
||||
"G99",
|
||||
"G64",
|
||||
"G97",
|
||||
"G91.1",
|
||||
"G8",
|
||||
"M5",
|
||||
"M9",
|
||||
"M48",
|
||||
"M53",
|
||||
"M0",
|
||||
`F${Number.isFinite(fields.feed) ? fields.feed : 0}`,
|
||||
`S${Number.isFinite(fields.speed) ? fields.speed : 0}`,
|
||||
];
|
||||
return {
|
||||
apiName: "real-browser-simulation-modal-state",
|
||||
modalVersion: 1,
|
||||
source: activeTag ? "linuxcnc-update-tag" : "unavailable",
|
||||
raw: activeTag,
|
||||
rows: modalRows,
|
||||
axisCodes,
|
||||
};
|
||||
}
|
||||
|
||||
export function createDroState(frame) {
|
||||
const axes = frame?.axes ?? Object.fromEntries(AXES.map((axis) => [axis, 0]));
|
||||
const actual = Object.fromEntries(DISPLAY_AXES.map((axis) => [axis, formatAxis(axes[axis])]));
|
||||
const unavailableAxes = Object.fromEntries(DISPLAY_AXES.map((axis) => [axis, UNAVAILABLE_VALUE]));
|
||||
const zeroAxes = Object.fromEntries(DISPLAY_AXES.map((axis) => [axis, formatAxis(0)]));
|
||||
return {
|
||||
apiName: "real-browser-simulation-dro-state",
|
||||
droVersion: 1,
|
||||
source: "linuxcnc-canonical-motion",
|
||||
actual,
|
||||
distanceToGo: { ...unavailableAxes },
|
||||
workOffsetG54: { ...unavailableAxes },
|
||||
g92Offset: { ...unavailableAxes },
|
||||
toolLengthOffset: { ...unavailableAxes },
|
||||
velocity: UNAVAILABLE_VALUE,
|
||||
unavailable: {
|
||||
distanceToGo: "unavailable from current canonical output",
|
||||
workOffsetG54: "unavailable from current canonical output",
|
||||
g92Offset: "unavailable from current canonical output",
|
||||
toolLengthOffset: "unavailable from current canonical output",
|
||||
velocity: "unavailable from current canonical output",
|
||||
distanceToGo: { ...zeroAxes },
|
||||
workOffsetG54: { ...zeroAxes },
|
||||
g92Offset: { ...zeroAxes },
|
||||
toolLengthOffset: { ...zeroAxes },
|
||||
velocity: formatAxis(0),
|
||||
defaults: {
|
||||
distanceToGo: "zeroed display default until trajectory DTG is available",
|
||||
workOffsetG54: "zeroed display default until coordinate offsets are available",
|
||||
g92Offset: "zeroed display default until coordinate offsets are available",
|
||||
toolLengthOffset: "zeroed display default until tool offsets are available",
|
||||
velocity: "zeroed display default until live velocity is available",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -217,25 +266,25 @@ export function createMachineStatusState(resultText) {
|
||||
statusVersion: 1,
|
||||
source: "linuxcnc-canonical-events",
|
||||
spindle: {
|
||||
direction: UNAVAILABLE_VALUE,
|
||||
speed: UNAVAILABLE_VALUE,
|
||||
state: UNAVAILABLE_VALUE,
|
||||
direction: "stopped",
|
||||
speed: "0",
|
||||
state: "off",
|
||||
},
|
||||
coolant: {
|
||||
mist: UNAVAILABLE_VALUE,
|
||||
flood: UNAVAILABLE_VALUE,
|
||||
mist: "off",
|
||||
flood: "off",
|
||||
},
|
||||
tool: {
|
||||
selected: UNAVAILABLE_VALUE,
|
||||
current: UNAVAILABLE_VALUE,
|
||||
pocket: UNAVAILABLE_VALUE,
|
||||
lengthOffset: UNAVAILABLE_VALUE,
|
||||
selected: "0",
|
||||
current: "0",
|
||||
pocket: "0",
|
||||
lengthOffset: "z=0.000",
|
||||
},
|
||||
overrides: {
|
||||
feed: UNAVAILABLE_VALUE,
|
||||
speed: UNAVAILABLE_VALUE,
|
||||
adaptiveFeed: UNAVAILABLE_VALUE,
|
||||
feedHold: UNAVAILABLE_VALUE,
|
||||
feed: "enabled",
|
||||
speed: "enabled",
|
||||
adaptiveFeed: "disabled",
|
||||
feedHold: "enabled",
|
||||
},
|
||||
};
|
||||
for (const line of String(resultText).split("\n")) {
|
||||
@@ -250,6 +299,7 @@ export function createMachineStatusState(resultText) {
|
||||
status.spindle.state = "on";
|
||||
} else if (line.startsWith("canon_event=STOP_SPINDLE_TURNING")) {
|
||||
status.spindle.state = "off";
|
||||
status.spindle.direction = "stopped";
|
||||
} else if (line.startsWith("canon_event=MIST_ON")) {
|
||||
status.coolant.mist = "on";
|
||||
} else if (line.startsWith("canon_event=MIST_OFF")) {
|
||||
@@ -449,8 +499,14 @@ function renderDroState(documentRef, dro) {
|
||||
setText(documentRef, `[data-dro-g54="${axis}"]`, dro.workOffsetG54[axis]);
|
||||
setText(documentRef, `[data-dro-g92="${axis}"]`, dro.g92Offset[axis]);
|
||||
setText(documentRef, `[data-dro-tlo="${axis}"]`, dro.toolLengthOffset[axis]);
|
||||
setText(documentRef, `[data-preview-hud="${axis}"]`, Number(dro.actual[axis]).toFixed(4));
|
||||
setText(documentRef, `[data-preview-hud="dtg-${axis}"]`, Number(dro.distanceToGo[axis]).toFixed(4));
|
||||
setText(documentRef, `[data-preview-hud="g54-${axis}"]`, Number(dro.workOffsetG54[axis]).toFixed(4));
|
||||
setText(documentRef, `[data-preview-hud="g92-${axis}"]`, Number(dro.g92Offset[axis]).toFixed(4));
|
||||
setText(documentRef, `[data-preview-hud="tlo-${axis}"]`, Number(dro.toolLengthOffset[axis]).toFixed(4));
|
||||
}
|
||||
setText(documentRef, "[data-dro-velocity]", dro.velocity);
|
||||
setText(documentRef, "[data-preview-hud=\"velocity\"]", Number(dro.velocity).toFixed(4));
|
||||
}
|
||||
|
||||
function renderModalState(documentRef, modal) {
|
||||
@@ -459,13 +515,20 @@ function renderModalState(documentRef, modal) {
|
||||
return;
|
||||
}
|
||||
container.textContent = "";
|
||||
for (const row of modal.rows) {
|
||||
for (const row of modal.rows ?? []) {
|
||||
const item = documentRef.createElement("span");
|
||||
item.dataset.modalField = row.id;
|
||||
item.dataset.available = row.available ? "true" : "false";
|
||||
item.textContent = `${row.label}: ${row.value}`;
|
||||
container.append(item);
|
||||
}
|
||||
for (const code of modal.axisCodes ?? []) {
|
||||
const item = documentRef.createElement("span");
|
||||
item.dataset.modalCode = code;
|
||||
item.dataset.available = "true";
|
||||
item.textContent = code;
|
||||
container.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
function renderMachineStatusState(documentRef, machineStatus) {
|
||||
|
||||
@@ -225,6 +225,26 @@
|
||||
if (!api?.getMachineStatusState) {
|
||||
throw new Error("simulation API missing AXIS machine status controls");
|
||||
}
|
||||
if (
|
||||
!api?.getVirtualHalState ||
|
||||
!api?.applyVirtualHalAction ||
|
||||
!api?.getVirtualHalDroState ||
|
||||
!api?.getVirtualHalLimitsHomeState ||
|
||||
!api?.getVirtualHalMachineStatusState ||
|
||||
!api?.getVirtualHalPinRegistry ||
|
||||
!api?.readVirtualHalPin ||
|
||||
!api?.writeVirtualHalPin ||
|
||||
!api?.applyVirtualHalPinUpdates ||
|
||||
!api?.getVirtualHalIntegrityReport ||
|
||||
!api?.executeVirtualHalcmd ||
|
||||
!api?.executeVirtualHalCommand ||
|
||||
!api?.stepVirtualHalMotionController ||
|
||||
!api?.getVirtualHalSimulationRuntimeReport ||
|
||||
!api?.getVirtualHalSimulationReplacementReport ||
|
||||
!api?.getVirtualRealtimeHalRuntimeReport
|
||||
) {
|
||||
throw new Error("simulation API missing browser virtual HAL controls");
|
||||
}
|
||||
if (!api?.getMachineSessionLoadState || !api?.loadReadyMachineSession) {
|
||||
throw new Error("simulation API missing AXIS machine/session load controls");
|
||||
}
|
||||
@@ -278,7 +298,7 @@
|
||||
throw new Error(`simulation initial built-in run should populate recent programs: ${JSON.stringify(initialRecentPrograms)}`);
|
||||
}
|
||||
api.clearRecentPrograms();
|
||||
if (api.getRecentPrograms().length !== 0 || doc.body.dataset.recentProgramCount !== "0" || !doc.querySelector("[data-recent-program-list]")?.textContent.includes("n/a")) {
|
||||
if (api.getRecentPrograms().length !== 0 || doc.body.dataset.recentProgramCount !== "0" || !doc.querySelector("[data-recent-program-list]")?.textContent.includes("No recent programs")) {
|
||||
throw new Error(`simulation recent programs clear did not reset API/DOM state: ${JSON.stringify(api.getRecentPrograms())}`);
|
||||
}
|
||||
if (!doc.querySelector("[data-open-program]") || !doc.querySelector("[data-open-program-file]")) {
|
||||
@@ -296,8 +316,8 @@
|
||||
if (!doc.querySelector('[data-axis-shell="machine-session"]') || !doc.querySelector("[data-check-machine-session]") || !doc.querySelector("[data-load-machine-session]") || !doc.querySelector("[data-use-loaded-session]")) {
|
||||
throw new Error("simulation page missing machine/session readiness controls");
|
||||
}
|
||||
if (!doc.querySelector("[data-axis-machine-control-state]")?.textContent.includes("not bound")) {
|
||||
throw new Error("simulation page should explicitly mark unbound real-machine controls");
|
||||
if (!doc.querySelector("[data-axis-machine-control-state]")?.textContent.includes("browser virtual HAL active")) {
|
||||
throw new Error("simulation page should explicitly mark browser virtual HAL machine controls");
|
||||
}
|
||||
if (!doc.querySelector("[data-mdi-program-text]") || !doc.querySelector("[data-load-mdi-program]") || !doc.querySelector("[data-run-mdi-program]") || !doc.querySelector("[data-mdi-history-list]") || !doc.querySelector("[data-clear-mdi-history]")) {
|
||||
throw new Error("simulation page missing MDI program text controls");
|
||||
@@ -319,10 +339,10 @@
|
||||
const initialMachineStatus = api.getMachineStatusState();
|
||||
if (
|
||||
initialMachineStatus?.source !== "linuxcnc-canonical-events" ||
|
||||
initialMachineStatus.spindle.state !== "n/a" ||
|
||||
doc.querySelector('[data-machine-status="spindle-state"]')?.textContent !== "n/a"
|
||||
initialMachineStatus.spindle.state !== "off" ||
|
||||
doc.querySelector('[data-machine-status="spindle-state"]')?.textContent !== "off"
|
||||
) {
|
||||
throw new Error(`simulation initial machine status should expose explicit unavailable state: ${JSON.stringify(initialMachineStatus)}`);
|
||||
throw new Error(`simulation initial machine status should expose AXIS default state: ${JSON.stringify(initialMachineStatus)}`);
|
||||
}
|
||||
const modalState = api.getModalState();
|
||||
if (modalState?.source !== "linuxcnc-update-tag" || !modalState.raw?.includes("canon_event=UPDATE_TAG")) {
|
||||
@@ -334,7 +354,7 @@
|
||||
}
|
||||
const diagnosticsState = api.getDiagnosticsState();
|
||||
if (
|
||||
diagnosticsState.lastEvent === "n/a" ||
|
||||
diagnosticsState.lastEvent === "pending" ||
|
||||
diagnosticsState.lastError !== "none" ||
|
||||
!doc.querySelector('[data-diagnostics="last-event"]')?.textContent.includes("canon_event=")
|
||||
) {
|
||||
@@ -346,18 +366,39 @@
|
||||
}
|
||||
const limitsHome = api.getLimitsHomeState();
|
||||
if (
|
||||
limitsHome.source !== "unavailable" ||
|
||||
limitsHome.axes.x.homed !== "n/a" ||
|
||||
limitsHome.source !== "browser-virtual-hal" ||
|
||||
limitsHome.axes.x.homed !== "no" ||
|
||||
doc.querySelector('[data-limits-home-row="x"]')?.children.length !== 5 ||
|
||||
!doc.querySelector("[data-limits-home-summary]")?.textContent.includes("source unavailable")
|
||||
!doc.querySelector("[data-limits-home-summary]")?.textContent.includes("source browser-virtual-hal")
|
||||
) {
|
||||
throw new Error(`simulation limits/home state should remain explicit unavailable: ${JSON.stringify(limitsHome)}`);
|
||||
throw new Error(`simulation limits/home state should expose browser virtual HAL rows: ${JSON.stringify(limitsHome)}`);
|
||||
}
|
||||
const virtualHalInitial = api.getVirtualHalState();
|
||||
if (
|
||||
virtualHalInitial.source !== "browser-virtual-hal" ||
|
||||
virtualHalInitial.component !== "axisui" ||
|
||||
virtualHalInitial.task.state !== "ESTOP" ||
|
||||
!virtualHalInitial.axisuiPins.some(({ name }) => name === "jog.x") ||
|
||||
!virtualHalInitial.axisuiPins.some(({ name }) => name === "jog.increment")
|
||||
) {
|
||||
throw new Error(`simulation virtual HAL initial state drift: ${JSON.stringify(virtualHalInitial)}`);
|
||||
}
|
||||
const initialVirtualHalRegistry = api.getVirtualHalPinRegistry();
|
||||
if (
|
||||
initialVirtualHalRegistry.apiName !== "linuxcnc-wasm-virtual-hal-pin-registry" ||
|
||||
!initialVirtualHalRegistry.byName["halui.machine.on"]?.writable ||
|
||||
!initialVirtualHalRegistry.byName["motion.tooloffset.z"]?.writable ||
|
||||
!api.getVirtualHalIntegrityReport().complete
|
||||
) {
|
||||
throw new Error(`simulation virtual HAL registry/integrity drift: ${JSON.stringify(initialVirtualHalRegistry)}`);
|
||||
}
|
||||
const diagnosticsArtifact = api.exportDiagnosticsArtifact();
|
||||
if (
|
||||
diagnosticsArtifact.apiName !== "real-browser-simulation-diagnostics-artifact" ||
|
||||
diagnosticsArtifact.statusHistory.length === 0 ||
|
||||
diagnosticsArtifact.limitsHome.source !== "unavailable" ||
|
||||
diagnosticsArtifact.virtualHal.source !== "browser-virtual-hal" ||
|
||||
diagnosticsArtifact.virtualHalSimulationReplacement?.ready !== true ||
|
||||
diagnosticsArtifact.limitsHome.source !== "browser-virtual-hal" ||
|
||||
diagnosticsArtifact.preview.renderer !== "threejs" ||
|
||||
diagnosticsArtifact.toolTable.state !== "not-loaded"
|
||||
) {
|
||||
@@ -368,12 +409,12 @@
|
||||
throw new Error(`simulation DRO source drift: ${JSON.stringify(droState)}`);
|
||||
}
|
||||
for (const field of ["distanceToGo", "workOffsetG54", "g92Offset", "toolLengthOffset"]) {
|
||||
if (droState[field].x !== "n/a") {
|
||||
throw new Error(`simulation DRO ${field} should remain explicit n/a when unavailable`);
|
||||
if (droState[field].x !== "0.000") {
|
||||
throw new Error(`simulation DRO ${field} should use zeroed AXIS default when unavailable`);
|
||||
}
|
||||
}
|
||||
if (droState.velocity !== "n/a" || doc.querySelector("[data-dro-velocity]")?.textContent !== "n/a") {
|
||||
throw new Error("simulation DRO velocity should remain explicit n/a when unavailable");
|
||||
if (droState.velocity !== "0.000" || doc.querySelector("[data-dro-velocity]")?.textContent !== "0.000") {
|
||||
throw new Error("simulation DRO velocity should use zeroed AXIS default when unavailable");
|
||||
}
|
||||
const previewExtents = doc.querySelector("[data-preview-extents]");
|
||||
const previewOrigin = doc.querySelector("[data-preview-origin]");
|
||||
@@ -619,6 +660,83 @@
|
||||
if (doc.querySelector('[data-axis-panel="preview"]')?.hidden || doc.body.dataset.lastShortcut !== "F6 Preview") {
|
||||
throw new Error("AXIS-style F6 shortcut did not switch to Preview");
|
||||
}
|
||||
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "F1", bubbles: true, cancelable: true }));
|
||||
if (api.getVirtualHalState().task.state !== "ESTOP_RESET" || doc.body.dataset.virtualHalTaskState !== "ESTOP_RESET" || doc.body.dataset.lastShortcut !== "F1 ESTOP") {
|
||||
throw new Error(`AXIS-style F1 did not reset browser virtual HAL ESTOP: ${JSON.stringify(api.getVirtualHalState())}`);
|
||||
}
|
||||
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "F2", bubbles: true, cancelable: true }));
|
||||
if (api.getVirtualHalState().task.state !== "ON" || doc.body.dataset.virtualHalTaskState !== "ON" || doc.body.dataset.lastShortcut !== "F2 Power") {
|
||||
throw new Error(`AXIS-style F2 did not power browser virtual HAL machine: ${JSON.stringify(api.getVirtualHalState())}`);
|
||||
}
|
||||
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "Home", ctrlKey: true, bubbles: true, cancelable: true }));
|
||||
if (api.getVirtualHalLimitsHomeState().axes.x.homed !== "yes" || doc.body.dataset.lastShortcut !== "Ctrl-Home Home All") {
|
||||
throw new Error(`AXIS-style Ctrl-Home did not home browser virtual HAL axes: ${JSON.stringify(api.getVirtualHalLimitsHomeState())}`);
|
||||
}
|
||||
doc.querySelector('[data-jog-increment]').value = "0.01 in";
|
||||
doc.querySelector('[data-axis-jog="+"]')?.click();
|
||||
if (api.getVirtualHalState().pins["jog.x"] !== true || api.getVirtualHalDroState().actual.x !== "0.010" || doc.querySelector('[data-dro-actual="x"]')?.textContent !== "0.010") {
|
||||
throw new Error(`AXIS-style jog did not update browser virtual HAL DRO/pins: ${JSON.stringify(api.getVirtualHalState())}`);
|
||||
}
|
||||
doc.querySelector('[data-axis-spindle="cw"]')?.click();
|
||||
doc.querySelector('[data-axis-coolant="flood"]').checked = true;
|
||||
doc.querySelector('[data-axis-coolant="flood"]').dispatchEvent(new frame.contentWindow.Event("change", { bubbles: true }));
|
||||
const virtualMachineStatus = api.getVirtualHalMachineStatusState();
|
||||
if (
|
||||
virtualMachineStatus.spindle.state !== "on" ||
|
||||
virtualMachineStatus.spindle.direction !== "cw" ||
|
||||
virtualMachineStatus.coolant.flood !== "on" ||
|
||||
doc.querySelector('[data-axis-spindle-status]')?.dataset.state !== "ready" ||
|
||||
doc.querySelector('[data-axis-coolant-status]')?.dataset.state !== "ready"
|
||||
) {
|
||||
throw new Error(`AXIS-style spindle/coolant did not update browser virtual HAL machine status: ${JSON.stringify(virtualMachineStatus)}`);
|
||||
}
|
||||
api.writeVirtualHalPin({ name: "spindle.0.speed-out", type: "HAL_FLOAT", value: 1500 });
|
||||
api.applyVirtualHalPinUpdates([
|
||||
{ name: "motion.tooloffset.z", type: "HAL_FLOAT", value: 1.5 },
|
||||
{ name: "halui.tool.number", type: "HAL_U32", value: 8 },
|
||||
]);
|
||||
if (
|
||||
api.readVirtualHalPin("spindle.0.speed-out")?.value !== 1500 ||
|
||||
api.readVirtualHalPin("motion.tooloffset.z")?.value !== 1.5 ||
|
||||
api.readVirtualHalPin("halui.tool.number")?.value !== 8 ||
|
||||
api.getVirtualHalIntegrityReport().complete !== true
|
||||
) {
|
||||
throw new Error(`AXIS-style virtual HAL pin service did not update/read pins: ${JSON.stringify(api.getVirtualHalPinRegistry())}`);
|
||||
}
|
||||
const virtualHalcmdResult = api.executeVirtualHalcmd("loadrt trivkins\naddf motion-controller servo-thread\nsetp axis.x.pos-cmd 1.75\nshow pin axis.x.*");
|
||||
if (
|
||||
virtualHalcmdResult.apiName !== "linuxcnc-wasm-virtual-halcmd-result" ||
|
||||
virtualHalcmdResult.ok !== true ||
|
||||
!virtualHalcmdResult.output.includes("axis.x.pos-cmd") ||
|
||||
api.readVirtualHalPin("axis.x.pos-cmd")?.value !== 1.75
|
||||
) {
|
||||
throw new Error(`AXIS-style virtual halcmd did not execute: ${JSON.stringify(virtualHalcmdResult)}`);
|
||||
}
|
||||
const motionControllerStep = api.stepVirtualHalMotionController({
|
||||
target: { x: 2.25 },
|
||||
maxVelocity: 10,
|
||||
dt: 0.1,
|
||||
cycleCount: 2,
|
||||
});
|
||||
if (
|
||||
motionControllerStep.apiName !== "linuxcnc-wasm-virtual-motion-controller-step" ||
|
||||
motionControllerStep.ok !== true ||
|
||||
motionControllerStep.state.position.x <= 1.75
|
||||
) {
|
||||
throw new Error(`AXIS-style virtual motion controller did not step: ${JSON.stringify(motionControllerStep)}`);
|
||||
}
|
||||
const virtualHalReplacement = api.getVirtualHalSimulationReplacementReport();
|
||||
if (
|
||||
virtualHalReplacement.apiName !== "linuxcnc-wasm-virtual-hal-simulation-replacement-report" ||
|
||||
virtualHalReplacement.ready !== true ||
|
||||
virtualHalReplacement.replacements["linuxcnc-realtime-hal"].ready !== true ||
|
||||
virtualHalReplacement.replacements.halcmd.ready !== true ||
|
||||
virtualHalReplacement.replacements["motion-controller"].ready !== true ||
|
||||
api.getVirtualRealtimeHalRuntimeReport().ready !== true ||
|
||||
api.getVirtualHalSimulationRuntimeReport().replacesHostRuntimeForSimulation !== true
|
||||
) {
|
||||
throw new Error(`AXIS-style virtual HAL simulation replacement report drift: ${JSON.stringify(virtualHalReplacement)}`);
|
||||
}
|
||||
const urlView = api.applyAxisViewFromUrl("?axis_left=mdi&axis_right=dro");
|
||||
if (
|
||||
urlView.left !== "mdi" ||
|
||||
@@ -899,7 +1017,7 @@
|
||||
throw new Error("MDI history item did not restore text into the MDI pane");
|
||||
}
|
||||
api.clearMdiHistory();
|
||||
if (api.getMdiHistory().length !== 0 || doc.body.dataset.mdiHistoryCount !== "0" || !doc.querySelector("[data-mdi-history-list]")?.textContent.includes("n/a")) {
|
||||
if (api.getMdiHistory().length !== 0 || doc.body.dataset.mdiHistoryCount !== "0" || !doc.querySelector("[data-mdi-history-list]")?.textContent.includes("No MDI history")) {
|
||||
throw new Error(`MDI history clear did not reset API/DOM state: ${JSON.stringify(api.getMdiHistory())}`);
|
||||
}
|
||||
|
||||
@@ -1080,7 +1198,7 @@ TOOL_TABLE = tool.tbl
|
||||
if (
|
||||
loadedDiagnosticsArtifact.toolTable.state !== "loaded" ||
|
||||
loadedDiagnosticsArtifact.toolTable.rows.length !== 1 ||
|
||||
loadedDiagnosticsArtifact.limitsHome.axes.x.fault !== "n/a" ||
|
||||
loadedDiagnosticsArtifact.limitsHome.axes.x.fault !== "none" ||
|
||||
!loadedDiagnosticsArtifact.statusHistory.some(({ kind }) => kind === "session")
|
||||
) {
|
||||
throw new Error(`simulation diagnostics artifact did not include session/tool/limits state: ${JSON.stringify(loadedDiagnosticsArtifact)}`);
|
||||
@@ -1157,7 +1275,7 @@ TOOL_TABLE = tool.tbl
|
||||
if (api.getRunMode().mode !== "standalone" || doc.body.dataset.runMode !== "standalone") {
|
||||
throw new Error("simulation run mode should stay standalone after disabled-session run");
|
||||
}
|
||||
if (api.getRunSummary().executionMode !== "linuxcnc-wasm" || doc.querySelector('[data-run-summary="session-ini"]')?.textContent !== "n/a") {
|
||||
if (api.getRunSummary().executionMode !== "linuxcnc-wasm" || doc.querySelector('[data-run-summary="session-ini"]')?.textContent !== "-") {
|
||||
throw new Error(`simulation run summary should return to standalone details: ${JSON.stringify(api.getRunSummary())}`);
|
||||
}
|
||||
if (api.getRunControlState().reason !== "Loaded machine session bypassed by Use Session toggle") {
|
||||
|
||||
@@ -126,6 +126,19 @@ for (const phrase of [
|
||||
"browser_release_artifact_url_workflow_smoke=ok",
|
||||
"browser_real_simulation_page_smoke=ok",
|
||||
"project_release_handoff_docs_node_smoke=ok",
|
||||
"Project-level virtual HAL",
|
||||
"createVirtualHalState()",
|
||||
"applyVirtualHalAction()",
|
||||
"createLinuxCncVirtualHalRuntime()",
|
||||
"createVirtualHalWasmBridgeSnapshot()",
|
||||
"createVirtualHalPinInventory()",
|
||||
"createVirtualHalBridgeReadiness()",
|
||||
"createVirtualHalBridgeActionPlan()",
|
||||
"createVirtualHalProjectReport()",
|
||||
"applyVirtualHalToInterpSdk()",
|
||||
"VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS",
|
||||
"VIRTUAL_HAL_PROJECT_PIN_GROUPS",
|
||||
"realtime HAL runtime",
|
||||
"L4-USER-M-PROCESS",
|
||||
"L4-TOOL-DB",
|
||||
"L4-PYTHON-REMAP",
|
||||
@@ -176,6 +189,19 @@ for (const phrase of [
|
||||
|
||||
for (const phrase of [
|
||||
"OPFS/session persistence exports",
|
||||
"Project-level virtual HAL boundary",
|
||||
"createVirtualHalState()",
|
||||
"applyVirtualHalAction()",
|
||||
"createLinuxCncVirtualHalRuntime()",
|
||||
"createVirtualHalWasmBridgeSnapshot()",
|
||||
"createVirtualHalPinInventory()",
|
||||
"createVirtualHalBridgeReadiness()",
|
||||
"createVirtualHalBridgeActionPlan()",
|
||||
"createVirtualHalProjectReport()",
|
||||
"applyVirtualHalToInterpSdk()",
|
||||
"VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS",
|
||||
"VIRTUAL_HAL_PROJECT_PIN_GROUPS",
|
||||
"realtime HAL runtime",
|
||||
"Project release readiness export",
|
||||
"createProjectReleaseGateManifest()",
|
||||
"createProjectReleaseGateResultMatrix()",
|
||||
|
||||
@@ -32,8 +32,21 @@ import {
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan,
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
|
||||
VIRTUAL_HAL_COVERAGE_STATES,
|
||||
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
|
||||
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
|
||||
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
|
||||
VIRTUAL_HAL_SOURCE_FILES,
|
||||
VIRTUAL_HAL_SYSTEM_PIN_FAMILIES,
|
||||
VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS,
|
||||
applyVirtualHalAction,
|
||||
applyVirtualHalPinUpdates,
|
||||
applyVirtualHalToInterpSdk,
|
||||
createVirtualHalSimulationRuntimeReport,
|
||||
createLinuxCncIniSdk,
|
||||
createLinuxCncInterpSdk,
|
||||
createLinuxCncVirtualHalRuntime,
|
||||
createVirtualHalIntegrityReport,
|
||||
createMachineSessionSnapshotPayload,
|
||||
createMachineSessionPersistenceDisplayViewModel,
|
||||
createMachineSessionPersistenceRenderState,
|
||||
@@ -63,6 +76,21 @@ import {
|
||||
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
|
||||
createProjectReleaseReadinessReport,
|
||||
createProjectReleaseReadinessSummaryViewModel,
|
||||
createVirtualHalBridgeActionPlan,
|
||||
createVirtualHalBridgeReadiness,
|
||||
createVirtualHalPinInventory,
|
||||
createVirtualHalPinRegistry,
|
||||
createVirtualHalProjectReport,
|
||||
createVirtualHalSimulationReplacementReport,
|
||||
createVirtualHalState,
|
||||
createVirtualHalSystemCoverageReport,
|
||||
createVirtualHalWasmBridgeSnapshot,
|
||||
executeVirtualHalCommand,
|
||||
executeVirtualHalcmd,
|
||||
readVirtualHalPin,
|
||||
stepVirtualHalMotionController,
|
||||
stepVirtualHalMotion,
|
||||
writeVirtualHalPin,
|
||||
parseProjectReleaseReadinessArtifactJson,
|
||||
createSessionSnapshot,
|
||||
defaultMachinePaths,
|
||||
@@ -186,6 +214,27 @@ const requiredExports = [
|
||||
],
|
||||
["createProjectReleaseReadinessReport", createProjectReleaseReadinessReport],
|
||||
["createProjectReleaseReadinessSummaryViewModel", createProjectReleaseReadinessSummaryViewModel],
|
||||
["applyVirtualHalAction", applyVirtualHalAction],
|
||||
["applyVirtualHalPinUpdates", applyVirtualHalPinUpdates],
|
||||
["applyVirtualHalToInterpSdk", applyVirtualHalToInterpSdk],
|
||||
["createLinuxCncVirtualHalRuntime", createLinuxCncVirtualHalRuntime],
|
||||
["createVirtualHalBridgeActionPlan", createVirtualHalBridgeActionPlan],
|
||||
["createVirtualHalBridgeReadiness", createVirtualHalBridgeReadiness],
|
||||
["createVirtualHalIntegrityReport", createVirtualHalIntegrityReport],
|
||||
["createVirtualHalPinInventory", createVirtualHalPinInventory],
|
||||
["createVirtualHalPinRegistry", createVirtualHalPinRegistry],
|
||||
["createVirtualHalProjectReport", createVirtualHalProjectReport],
|
||||
["createVirtualHalSimulationReplacementReport", createVirtualHalSimulationReplacementReport],
|
||||
["createVirtualHalSimulationRuntimeReport", createVirtualHalSimulationRuntimeReport],
|
||||
["createVirtualHalState", createVirtualHalState],
|
||||
["createVirtualHalSystemCoverageReport", createVirtualHalSystemCoverageReport],
|
||||
["createVirtualHalWasmBridgeSnapshot", createVirtualHalWasmBridgeSnapshot],
|
||||
["executeVirtualHalCommand", executeVirtualHalCommand],
|
||||
["executeVirtualHalcmd", executeVirtualHalcmd],
|
||||
["readVirtualHalPin", readVirtualHalPin],
|
||||
["stepVirtualHalMotionController", stepVirtualHalMotionController],
|
||||
["stepVirtualHalMotion", stepVirtualHalMotion],
|
||||
["writeVirtualHalPin", writeVirtualHalPin],
|
||||
["parseProjectReleaseReadinessArtifactJson", parseProjectReleaseReadinessArtifactJson],
|
||||
[
|
||||
"validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson",
|
||||
@@ -411,6 +460,202 @@ for (const [name, value] of requiredExports) {
|
||||
assert.match(sdkReadmeText, new RegExp(`\\b${name}\\b`));
|
||||
}
|
||||
|
||||
assert.equal(VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS.includes("lcinterp_hal_set_value"), true);
|
||||
assert.equal(VIRTUAL_HAL_PROJECT_PIN_GROUPS.some(({ id }) => id === "axisui"), true);
|
||||
assert.equal(VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES.includes("halcmd-setp-sets-net-show-getp-gets"), true);
|
||||
assert.equal(VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES.includes("realtime-hal-simulation-replacement"), true);
|
||||
assert.equal(VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES.includes("motion-controller-simulation-replacement"), true);
|
||||
assert.equal(VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.includes("linuxcnc-realtime-hal"), true);
|
||||
assert.equal(VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.includes("halcmd"), true);
|
||||
assert.equal(VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.includes("motion-controller"), true);
|
||||
assert.equal(VIRTUAL_HAL_COVERAGE_STATES.runtimeBoundary, "runtime-boundary");
|
||||
assert.equal(VIRTUAL_HAL_SOURCE_FILES.halui, "linuxcnc/src/emc/usr_intf/halui.cc");
|
||||
assert.equal(VIRTUAL_HAL_SYSTEM_PIN_FAMILIES.some(({ id }) => id === "iocontrol"), true);
|
||||
assert.equal(VIRTUAL_HAL_SYSTEM_PIN_FAMILIES.some(({ id }) => id === "motion-core"), true);
|
||||
assert.match(sdkIndexText, /\bVIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS\b/);
|
||||
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS\b/);
|
||||
assert.match(sdkIndexText, /\bVIRTUAL_HAL_PROJECT_PIN_GROUPS\b/);
|
||||
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_PROJECT_PIN_GROUPS\b/);
|
||||
assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES\b/);
|
||||
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES\b/);
|
||||
assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS\b/);
|
||||
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS\b/);
|
||||
assert.match(sdkIndexText, /\bVIRTUAL_HAL_SYSTEM_PIN_FAMILIES\b/);
|
||||
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SYSTEM_PIN_FAMILIES\b/);
|
||||
assert.match(sdkIndexText, /\bexecuteVirtualHalCommand\b/);
|
||||
assert.match(sdkReadmeText, /\bexecuteVirtualHalCommand\b/);
|
||||
assert.match(sdkIndexText, /\bexecuteVirtualHalcmd\b/);
|
||||
assert.match(sdkReadmeText, /\bexecuteVirtualHalcmd\b/);
|
||||
assert.match(sdkIndexText, /\bstepVirtualHalMotion\b/);
|
||||
assert.match(sdkReadmeText, /\bstepVirtualHalMotion\b/);
|
||||
assert.match(sdkIndexText, /\bstepVirtualHalMotionController\b/);
|
||||
assert.match(sdkReadmeText, /\bstepVirtualHalMotionController\b/);
|
||||
assert.match(sdkIndexText, /\bcreateVirtualHalSimulationRuntimeReport\b/);
|
||||
assert.match(sdkReadmeText, /\bcreateVirtualHalSimulationRuntimeReport\b/);
|
||||
assert.match(sdkIndexText, /\bcreateVirtualHalSimulationReplacementReport\b/);
|
||||
assert.match(sdkReadmeText, /\bcreateVirtualHalSimulationReplacementReport\b/);
|
||||
assert.match(sdkIndexText, /\bcreateVirtualHalSystemCoverageReport\b/);
|
||||
assert.match(sdkReadmeText, /\bcreateVirtualHalSystemCoverageReport\b/);
|
||||
assert.match(sdkIndexText, /\bcreateVirtualHalPinRegistry\b/);
|
||||
assert.match(sdkReadmeText, /\bcreateVirtualHalPinRegistry\b/);
|
||||
assert.match(sdkIndexText, /\bcreateVirtualHalIntegrityReport\b/);
|
||||
assert.match(sdkReadmeText, /\bcreateVirtualHalIntegrityReport\b/);
|
||||
assert.match(sdkIndexText, /\breadVirtualHalPin\b/);
|
||||
assert.match(sdkReadmeText, /\breadVirtualHalPin\b/);
|
||||
assert.match(sdkIndexText, /\bwriteVirtualHalPin\b/);
|
||||
assert.match(sdkReadmeText, /\bwriteVirtualHalPin\b/);
|
||||
|
||||
let sdkVirtualHal = createVirtualHalState();
|
||||
sdkVirtualHal = applyVirtualHalAction(sdkVirtualHal, "estop");
|
||||
sdkVirtualHal = applyVirtualHalAction(sdkVirtualHal, "power");
|
||||
sdkVirtualHal = applyVirtualHalAction(sdkVirtualHal, "jog", { axis: "x", increment: "0.01 in", direction: 1 });
|
||||
sdkVirtualHal = writeVirtualHalPin(sdkVirtualHal, { name: "spindle.0.speed-out", type: "HAL_FLOAT", value: 1800 });
|
||||
sdkVirtualHal = writeVirtualHalPin(sdkVirtualHal, { name: "motion.tooloffset.z", type: "HAL_FLOAT", value: 2.5 });
|
||||
sdkVirtualHal = applyVirtualHalPinUpdates(sdkVirtualHal, [
|
||||
{ name: "halui.tool.number", type: "HAL_U32", value: 7 },
|
||||
{ name: "halui.feed-override.value", type: "HAL_FLOAT", value: 0.8 },
|
||||
]);
|
||||
assert.equal(readVirtualHalPin(sdkVirtualHal, "spindle.0.speed-out").value, 1800);
|
||||
assert.equal(readVirtualHalPin(sdkVirtualHal, "motion.tooloffset.z").value, 2.5);
|
||||
assert.equal(readVirtualHalPin(sdkVirtualHal, "halui.tool.number").value, 7);
|
||||
assert.equal(sdkVirtualHal.overrides.feed, 80);
|
||||
const virtualHalSnapshot = createVirtualHalWasmBridgeSnapshot(sdkVirtualHal);
|
||||
assert.equal(virtualHalSnapshot.apiName, "linuxcnc-wasm-virtual-hal-bridge-snapshot");
|
||||
assert.ok(virtualHalSnapshot.values.some(({ name }) => name === "axisui.jog.x"));
|
||||
assert.ok(virtualHalSnapshot.values.some(({ name, value }) => name === "spindle.0.speed-out" && value === 1800));
|
||||
assert.ok(virtualHalSnapshot.values.some(({ name, value }) => name === "motion.tooloffset.z" && value === 2.5));
|
||||
const virtualHalRegistry = createVirtualHalPinRegistry(sdkVirtualHal);
|
||||
assert.equal(virtualHalRegistry.apiName, "linuxcnc-wasm-virtual-hal-pin-registry");
|
||||
assert.equal(virtualHalRegistry.byName["spindle.0.speed-out"].value, 1800);
|
||||
assert.equal(virtualHalRegistry.byName["motion.tooloffset.z"].value, 2.5);
|
||||
assert.equal(virtualHalRegistry.byName["halui.tool.number"].value, 7);
|
||||
assert.equal(virtualHalRegistry.rows.some(({ name, writable }) => name === "halui.machine.on" && writable === true), true);
|
||||
const virtualHalIntegrity = createVirtualHalIntegrityReport({ halState: sdkVirtualHal, registry: virtualHalRegistry });
|
||||
assert.equal(virtualHalIntegrity.apiName, "linuxcnc-wasm-virtual-hal-integrity-report");
|
||||
assert.equal(virtualHalIntegrity.complete, true);
|
||||
assert.equal(virtualHalIntegrity.missingPins.length, 0);
|
||||
const virtualHalInventory = createVirtualHalPinInventory(sdkVirtualHal);
|
||||
assert.equal(virtualHalInventory.apiName, "linuxcnc-wasm-virtual-hal-pin-inventory");
|
||||
assert.equal(virtualHalInventory.groups.some(({ id, pinCount }) => id === "axisui" && pinCount > 0), true);
|
||||
assert.equal(virtualHalInventory.groups.some(({ id, pinCount }) => id === "axis-motion" && pinCount > 0), true);
|
||||
assert.equal(virtualHalInventory.groups.some(({ id, pinCount }) => id === "tool" && pinCount > 0), true);
|
||||
const virtualHalSystemCoverage = createVirtualHalSystemCoverageReport({ snapshot: virtualHalSnapshot });
|
||||
assert.equal(virtualHalSystemCoverage.apiName, "linuxcnc-wasm-virtual-hal-system-coverage-report");
|
||||
assert.equal(virtualHalSystemCoverage.complete, true);
|
||||
assert.equal(virtualHalSystemCoverage.realtimeHalRuntime, false);
|
||||
assert.equal(virtualHalSystemCoverage.sourceFiles.includes("linuxcnc/bin/axis"), true);
|
||||
assert.equal(virtualHalSystemCoverage.sourceFiles.includes("linuxcnc/src/emc/usr_intf/halui.cc"), true);
|
||||
assert.equal(virtualHalSystemCoverage.sourceFiles.includes("linuxcnc/src/emc/task/taskclass.cc"), true);
|
||||
assert.equal(virtualHalSystemCoverage.sourceFiles.includes("linuxcnc/src/emc/motion/motion.c"), true);
|
||||
assert.equal(virtualHalSystemCoverage.families.some(({ id, coverage }) => id === "motion-core" && coverage === "runtime-boundary"), true);
|
||||
assert.equal(virtualHalSystemCoverage.boundaryRows.some(({ id }) => id === "halui-task"), true);
|
||||
const virtualHalBlockedReadiness = createVirtualHalBridgeReadiness({ snapshot: virtualHalSnapshot });
|
||||
assert.equal(virtualHalBlockedReadiness.ready, false);
|
||||
assert.equal(virtualHalBlockedReadiness.missing.includes("wasm-function-evidence"), true);
|
||||
const virtualHalReadyReadiness = createVirtualHalBridgeReadiness({
|
||||
snapshot: virtualHalSnapshot,
|
||||
availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS,
|
||||
});
|
||||
assert.equal(virtualHalReadyReadiness.ready, true);
|
||||
assert.equal(createVirtualHalBridgeActionPlan(virtualHalReadyReadiness).nextCommand, null);
|
||||
const virtualHalReport = createVirtualHalProjectReport({
|
||||
halState: sdkVirtualHal,
|
||||
availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS,
|
||||
});
|
||||
assert.equal(virtualHalReport.apiName, "linuxcnc-wasm-virtual-hal-project-report");
|
||||
assert.equal(virtualHalReport.ready, true);
|
||||
assert.equal(virtualHalReport.pinInventory.pinCount, virtualHalReport.wasmBridgeSnapshot.values.length);
|
||||
assert.equal(virtualHalReport.systemCoverage.phase, "covered-with-runtime-boundaries");
|
||||
assert.equal(virtualHalReport.systemCoverage.unclassifiedSnapshotPins.length, 0);
|
||||
assert.equal(virtualHalReport.pinRegistry.byName["spindle.0.speed-out"].value, 1800);
|
||||
assert.equal(virtualHalReport.integrity.complete, true);
|
||||
const virtualHalRuntime = createLinuxCncVirtualHalRuntime(sdkVirtualHal);
|
||||
assert.equal(virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS }).ready, true);
|
||||
const virtualHalcmdResult = virtualHalRuntime.executeHalCommand(`
|
||||
loadrt trivkins
|
||||
addf motion-controller servo-thread
|
||||
net x-pos axis.x.pos-cmd joint.0.pos-fb
|
||||
setp axis.x.pos-cmd 2.5
|
||||
sets x-pos 1.25
|
||||
show pin axis.x.*
|
||||
motion-step x=2.0
|
||||
`);
|
||||
assert.equal(virtualHalcmdResult.apiName, "linuxcnc-wasm-virtual-halcmd-result");
|
||||
assert.equal(virtualHalcmdResult.ok, true);
|
||||
assert.equal(virtualHalcmdResult.rows.some(({ action }) => action === "net"), true);
|
||||
assert.equal(virtualHalRuntime.getState().hal.signals["x-pos"].value, 1.25);
|
||||
assert.equal(virtualHalRuntime.getState().hal.nets["x-pos"].includes("axis.x.pos-cmd"), true);
|
||||
assert.equal(virtualHalRuntime.getState().hal.loadedComponents.some(({ component }) => component === "trivkins"), true);
|
||||
assert.equal(virtualHalRuntime.getState().hal.functions.some(({ name }) => name === "motion-controller"), true);
|
||||
assert.ok(virtualHalRuntime.getState().position.x > 1.25);
|
||||
const directHalcmdResult = executeVirtualHalCommand(virtualHalRuntime.getState(), "getp axis.x.pos-cmd\ngets x-pos");
|
||||
assert.equal(directHalcmdResult.ok, true);
|
||||
assert.match(directHalcmdResult.output, /axis\.x\.pos-cmd/);
|
||||
assert.match(directHalcmdResult.output, /x-pos/);
|
||||
const directHalcmdAliasResult = executeVirtualHalcmd(virtualHalRuntime.getState(), "setp halui.machine.on 1\nshow pin halui.machine.*");
|
||||
assert.equal(directHalcmdAliasResult.ok, true);
|
||||
assert.equal(directHalcmdAliasResult.apiName, "linuxcnc-wasm-virtual-halcmd-result");
|
||||
assert.match(directHalcmdAliasResult.output, /halui\.machine\.is-on/);
|
||||
const steppedVirtualHal = stepVirtualHalMotion(virtualHalRuntime.getState(), {
|
||||
target: { x: 3 },
|
||||
maxVelocity: 10,
|
||||
dt: 0.1,
|
||||
});
|
||||
assert.ok(steppedVirtualHal.position.x > virtualHalRuntime.getState().position.x);
|
||||
const motionControllerStep = stepVirtualHalMotionController(virtualHalRuntime.getState(), {
|
||||
target: { x: 4 },
|
||||
maxVelocity: 10,
|
||||
dt: 0.1,
|
||||
cycleCount: 3,
|
||||
});
|
||||
assert.equal(motionControllerStep.apiName, "linuxcnc-wasm-virtual-motion-controller-step");
|
||||
assert.equal(motionControllerStep.ok, true);
|
||||
assert.ok(motionControllerStep.cycleCount >= 1);
|
||||
assert.ok(motionControllerStep.state.position.x > virtualHalRuntime.getState().position.x);
|
||||
const simulationRuntimeReport = virtualHalRuntime.getSimulationRuntimeReport();
|
||||
assert.equal(simulationRuntimeReport.apiName, "linuxcnc-wasm-virtual-hal-simulation-runtime-report");
|
||||
assert.equal(simulationRuntimeReport.replacesHostRuntimeForSimulation, true);
|
||||
assert.deepEqual(simulationRuntimeReport.replacementTargets, VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS);
|
||||
assert.equal(simulationRuntimeReport.hardRealtime, false);
|
||||
assert.deepEqual(simulationRuntimeReport.hostRequirements, []);
|
||||
assert.equal(simulationRuntimeReport.capabilities.includes("servo-period-motion-step"), true);
|
||||
assert.equal(createVirtualHalSimulationRuntimeReport(virtualHalRuntime.getState()).ready, true);
|
||||
const runtimeMotionControllerStep = virtualHalRuntime.stepMotionController({
|
||||
target: { x: 4.5 },
|
||||
maxVelocity: 20,
|
||||
dt: 0.1,
|
||||
cycleCount: 2,
|
||||
});
|
||||
assert.equal(runtimeMotionControllerStep.apiName, "linuxcnc-wasm-virtual-motion-controller-step");
|
||||
assert.ok(virtualHalRuntime.getState().position.x > motionControllerStep.state.position.x);
|
||||
const replacementReport = createVirtualHalSimulationReplacementReport(virtualHalRuntime.getState());
|
||||
assert.equal(replacementReport.apiName, "linuxcnc-wasm-virtual-hal-simulation-replacement-report");
|
||||
assert.equal(replacementReport.ready, true);
|
||||
assert.equal(replacementReport.replacesHostRuntimeForSimulation, true);
|
||||
assert.equal(replacementReport.replacements["linuxcnc-realtime-hal"].ready, true);
|
||||
assert.equal(replacementReport.replacements.halcmd.ready, true);
|
||||
assert.equal(replacementReport.replacements["motion-controller"].ready, true);
|
||||
assert.equal(replacementReport.hardRealtime, false);
|
||||
assert.equal(virtualHalRuntime.getSimulationReplacementReport().ready, true);
|
||||
const runtimeProjectReport = virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS });
|
||||
assert.equal(runtimeProjectReport.simulationRuntime.replacesHostRuntimeForSimulation, true);
|
||||
assert.equal(runtimeProjectReport.systemCoverage.unclassifiedSnapshotPins.length, 0);
|
||||
const fakeInterpApplyResult = applyVirtualHalToInterpSdk(
|
||||
{
|
||||
applyVirtualHalSnapshot(snapshot, options) {
|
||||
return {
|
||||
apiName: "fake-virtual-hal-apply-result",
|
||||
applied: snapshot.values.length,
|
||||
reset: options.reset !== false,
|
||||
};
|
||||
},
|
||||
},
|
||||
sdkVirtualHal,
|
||||
{ reset: false },
|
||||
);
|
||||
assert.equal(fakeInterpApplyResult.apiName, "fake-virtual-hal-apply-result");
|
||||
assert.equal(fakeInterpApplyResult.reset, false);
|
||||
|
||||
assert.equal(machineIniPath("sdk-mill"), "linuxcnc/machines/sdk-mill/machine.ini");
|
||||
assert.deepEqual(defaultMachinePaths("sdk-mill"), machineFilePaths("sdk-mill"));
|
||||
assert.equal(gcodeProgramPath("demo.ngc"), "linuxcnc/gcode/demo.ngc");
|
||||
|
||||
@@ -21,6 +21,23 @@ import {
|
||||
runRealBrowserSimulation,
|
||||
zoomToolpathViewBox,
|
||||
} from "../../../runtime/ui/simulation/simulation-app.js";
|
||||
import {
|
||||
VIRTUAL_HAL_AXISUI_PINS,
|
||||
applyVirtualHalAction,
|
||||
applyVirtualHalPinUpdates,
|
||||
createLinuxCncInterpSdk,
|
||||
createLinuxCncVirtualHalRuntime,
|
||||
createVirtualHalDroState,
|
||||
createVirtualHalIntegrityReport,
|
||||
createVirtualHalLimitsHomeState,
|
||||
createVirtualHalMachineStatusState,
|
||||
createVirtualHalPinRegistry,
|
||||
createVirtualHalState,
|
||||
createVirtualHalWasmBridgeSnapshot,
|
||||
parseVirtualHalJogIncrement,
|
||||
readVirtualHalPin,
|
||||
writeVirtualHalPin,
|
||||
} from "../../../runtime/sdk/src/index.js";
|
||||
import {
|
||||
SIMULATION_TEST_PROGRAMS as directoryPrograms,
|
||||
} from "../../../runtime/ui/simulation/programs/index.js";
|
||||
@@ -56,6 +73,62 @@ const ids = new Set(programs.map(({ id }) => id));
|
||||
assert.equal(ids.size, programs.length, "program ids should be unique");
|
||||
assert.ok(ids.has("arc-g2-g3"), "program inventory should include an arc fixture");
|
||||
assert.ok(ids.has("drill-g81"), "program inventory should include a canned-cycle fixture");
|
||||
assert.equal(VIRTUAL_HAL_AXISUI_PINS.component, "axisui");
|
||||
assert.ok(VIRTUAL_HAL_AXISUI_PINS.pins.some(({ name }) => name === "jog.x"), "virtual HAL should mirror AXIS axisui jog pins");
|
||||
assert.ok(VIRTUAL_HAL_AXISUI_PINS.pins.some(({ name }) => name === "jog.increment"), "virtual HAL should expose AXIS jog increment pin");
|
||||
assert.equal(parseVirtualHalJogIncrement("Continuous"), 0);
|
||||
assert.equal(parseVirtualHalJogIncrement("0.01 in"), 0.01);
|
||||
assert.equal(Number(parseVirtualHalJogIncrement("1 mm").toFixed(6)), 0.03937);
|
||||
|
||||
let virtualHal = createVirtualHalState();
|
||||
assert.equal(virtualHal.task.state, "ESTOP");
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "estop");
|
||||
assert.equal(virtualHal.task.state, "ESTOP_RESET");
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "power");
|
||||
assert.equal(virtualHal.task.state, "ON");
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "home-all");
|
||||
assert.equal(createVirtualHalLimitsHomeState(virtualHal).axes.x.homed, "yes");
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "jog", { axis: "x", increment: "0.01 in", direction: 1 });
|
||||
assert.equal(virtualHal.pins["jog.x"], true);
|
||||
assert.equal(virtualHal.pins["jog.increment"], 0.01);
|
||||
assert.equal(createVirtualHalDroState(virtualHal).actual.x, "0.010");
|
||||
const jogSnapshot = createVirtualHalWasmBridgeSnapshot(virtualHal);
|
||||
assert.ok(jogSnapshot.values.some(({ name, type, value }) => name === "jog.x" && type === "HAL_BIT" && value === 1));
|
||||
assert.ok(jogSnapshot.values.some(({ name, type, value }) => name === "axisui.jog.x" && type === "HAL_BIT" && value === 1));
|
||||
assert.ok(jogSnapshot.values.some(({ name, type, value }) => name === "jog.increment" && type === "HAL_FLOAT" && value === 0.01));
|
||||
assert.ok(jogSnapshot.values.some(({ name, type, value }) => name === "axisui.jog.increment" && type === "HAL_FLOAT" && value === 0.01));
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "spindle", { command: "cw", speed: 1200 });
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "coolant", { name: "flood", enabled: true });
|
||||
virtualHal = writeVirtualHalPin(virtualHal, { name: "motion.tooloffset.z", type: "HAL_FLOAT", value: 1.25 });
|
||||
virtualHal = applyVirtualHalPinUpdates(virtualHal, [
|
||||
{ name: "halui.tool.number", type: "HAL_U32", value: 4 },
|
||||
{ name: "halui.feed-override.value", type: "HAL_FLOAT", value: 0.9 },
|
||||
]);
|
||||
assert.equal(readVirtualHalPin(virtualHal, "motion.tooloffset.z").value, 1.25);
|
||||
assert.equal(readVirtualHalPin(virtualHal, "halui.tool.number").value, 4);
|
||||
assert.equal(virtualHal.overrides.feed, 90);
|
||||
const virtualHalRegistry = createVirtualHalPinRegistry(virtualHal);
|
||||
assert.equal(virtualHalRegistry.apiName, "linuxcnc-wasm-virtual-hal-pin-registry");
|
||||
assert.equal(virtualHalRegistry.byName["halui.machine.on"].writable, true);
|
||||
assert.equal(virtualHalRegistry.byName["motion.tooloffset.z"].value, 1.25);
|
||||
const virtualHalIntegrity = createVirtualHalIntegrityReport({ halState: virtualHal, registry: virtualHalRegistry });
|
||||
assert.equal(virtualHalIntegrity.complete, true);
|
||||
const virtualMachineStatus = createVirtualHalMachineStatusState(virtualHal);
|
||||
assert.equal(virtualMachineStatus.source, "browser-virtual-hal");
|
||||
assert.equal(virtualMachineStatus.spindle.direction, "cw");
|
||||
assert.equal(virtualMachineStatus.spindle.speed, "1200");
|
||||
assert.equal(virtualMachineStatus.coolant.flood, "on");
|
||||
const virtualHalRuntime = createLinuxCncVirtualHalRuntime(virtualHal);
|
||||
virtualHalRuntime.writePin({ name: "spindle.0.speed-out", type: "HAL_FLOAT", value: 1400 });
|
||||
assert.equal(virtualHalRuntime.readPin("spindle.0.speed-out").value, 1400);
|
||||
assert.equal(virtualHalRuntime.getIntegrityReport().complete, true);
|
||||
const runtimeSnapshot = virtualHalRuntime.getWasmBridgeSnapshot();
|
||||
assert.equal(runtimeSnapshot.apiName, "linuxcnc-wasm-virtual-hal-bridge-snapshot");
|
||||
assert.ok(runtimeSnapshot.values.some(({ name, type, value }) => name === "jog.x" && type === "HAL_BIT" && value === 1));
|
||||
assert.ok(runtimeSnapshot.values.some(({ name, type, value }) => name === "axisui.jog.x" && type === "HAL_BIT" && value === 1));
|
||||
assert.ok(runtimeSnapshot.values.some(({ name, type, value }) => name === "halui.machine.is-on" && type === "HAL_BIT" && value === 1));
|
||||
assert.ok(runtimeSnapshot.values.some(({ name, type, value }) => name === "spindle.0.speed-out" && type === "HAL_FLOAT" && value === 1400));
|
||||
assert.ok(runtimeSnapshot.values.some(({ name, type, value }) => name === "iocontrol.0.coolant-flood" && type === "HAL_BIT" && value === 1));
|
||||
|
||||
assert.throws(
|
||||
() => getSimulationTestProgram("missing-program"),
|
||||
@@ -91,8 +164,8 @@ assert.equal(arcModal.apiName, "real-browser-simulation-modal-state");
|
||||
assert.equal(arcModal.source, "linuxcnc-update-tag");
|
||||
assert.ok(arcModal.raw.includes("canon_event=UPDATE_TAG line=1"));
|
||||
assert.ok(arcModal.rows.some(({ id, value, source }) => id === "plane" && value === "plane=170" && source === "linuxcnc-update-tag"));
|
||||
assert.equal(unavailableMachineStatus.spindle.state, "n/a");
|
||||
assert.equal(unavailableMachineStatus.coolant.mist, "n/a");
|
||||
assert.equal(unavailableMachineStatus.spindle.state, "off");
|
||||
assert.equal(unavailableMachineStatus.coolant.mist, "off");
|
||||
|
||||
const runtimeMachineStatus = createMachineStatusState([
|
||||
"canon_event=SET_SPINDLE_SPEED spindle=0 speed=1200",
|
||||
@@ -108,7 +181,7 @@ const runtimeMachineStatus = createMachineStatusState([
|
||||
"canon_event=ENABLE_SPEED_OVERRIDE spindle=0",
|
||||
].join("\n"));
|
||||
assert.equal(runtimeMachineStatus.spindle.state, "off");
|
||||
assert.equal(runtimeMachineStatus.spindle.direction, "cw");
|
||||
assert.equal(runtimeMachineStatus.spindle.direction, "stopped");
|
||||
assert.equal(runtimeMachineStatus.spindle.speed, "1200");
|
||||
assert.equal(runtimeMachineStatus.coolant.mist, "off");
|
||||
assert.equal(runtimeMachineStatus.coolant.flood, "off");
|
||||
@@ -160,11 +233,11 @@ assert.equal(firstFrame.activeLine, 1);
|
||||
assert.equal(firstFrame.visibleMotion.length, 1);
|
||||
assert.equal(firstDro.apiName, "real-browser-simulation-dro-state");
|
||||
assert.equal(firstDro.actual.x, "0.000");
|
||||
assert.equal(firstDro.distanceToGo.x, "n/a");
|
||||
assert.equal(firstDro.workOffsetG54.x, "n/a");
|
||||
assert.equal(firstDro.g92Offset.x, "n/a");
|
||||
assert.equal(firstDro.toolLengthOffset.x, "n/a");
|
||||
assert.equal(firstDro.velocity, "n/a");
|
||||
assert.equal(firstDro.distanceToGo.x, "0.000");
|
||||
assert.equal(firstDro.workOffsetG54.x, "0.000");
|
||||
assert.equal(firstDro.g92Offset.x, "0.000");
|
||||
assert.equal(firstDro.toolLengthOffset.x, "0.000");
|
||||
assert.equal(firstDro.velocity, "0.000");
|
||||
|
||||
const arcFrame = createPlaybackFrame(playbackState, 1);
|
||||
assert.equal(arcFrame.index, 1);
|
||||
|
||||
@@ -4,7 +4,9 @@ import { dirname, resolve } from "node:path";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
applyVirtualHalAction,
|
||||
createLinuxCncInterpSdk,
|
||||
createVirtualHalState,
|
||||
planIniFileContextStaging,
|
||||
} from "../../../runtime/sdk/src/index.js";
|
||||
import {
|
||||
@@ -726,6 +728,46 @@ verifyExpectedOutput(
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
if (
|
||||
interp.hasWasmFunction("lcinterp_hal_reset") &&
|
||||
interp.hasWasmFunction("lcinterp_hal_set_value") &&
|
||||
interp.hasWasmFunction("lcinterp_probe_hal_named")
|
||||
) {
|
||||
let virtualHal = createVirtualHalState();
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "estop");
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "power");
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "jog", { axis: "x", increment: "0.01 in", direction: 1 });
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "spindle", { command: "cw", speed: 1200 });
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "coolant", { name: "flood", enabled: true });
|
||||
const applyResult = interp.applyVirtualHalState(virtualHal);
|
||||
assert.equal(applyResult.apiName, "linuxcnc-wasm-hal-apply-result");
|
||||
assert.equal(applyResult.source, "browser-virtual-hal");
|
||||
assert.ok(applyResult.applied > 0, "virtual HAL bridge should apply values");
|
||||
verifyExpectedOutput(
|
||||
"probe_virtual_hal_bridge",
|
||||
[
|
||||
interp.probeHalNamed("_hal[jog.x]"),
|
||||
interp.probeHalNamed("_hal[jog.increment]"),
|
||||
interp.probeHalNamed("_hal[axisui.jog.x]"),
|
||||
interp.probeHalNamed("_hal[axisui.jog.increment]"),
|
||||
interp.probeHalNamed("_hal[axis.x.pos-cmd]"),
|
||||
interp.probeHalNamed("_hal[halui.machine.is-on]"),
|
||||
interp.probeHalNamed("_hal[spindle.0.speed-out]"),
|
||||
interp.probeHalNamed("_hal[iocontrol.0.coolant-flood]"),
|
||||
].join(""),
|
||||
[
|
||||
"_hal[jog.x]: rc=0 found=1 value=1",
|
||||
"_hal[jog.increment]: rc=0 found=1 value=0.01",
|
||||
"_hal[axisui.jog.x]: rc=0 found=1 value=1",
|
||||
"_hal[axisui.jog.increment]: rc=0 found=1 value=0.01",
|
||||
"_hal[axis.x.pos-cmd]: rc=0 found=1 value=0.01",
|
||||
"_hal[halui.machine.is-on]: rc=0 found=1 value=1",
|
||||
"_hal[spindle.0.speed-out]: rc=0 found=1 value=1200",
|
||||
"_hal[iocontrol.0.coolant-flood]: rc=0 found=1 value=1",
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
for (const fixtureName of INTERP_MDI_FIXTURES) {
|
||||
const programText = readFileSync(
|
||||
resolve(rootDir, `tests/fixtures/gcode/${fixtureName}.ngc`),
|
||||
|
||||
@@ -98,5 +98,5 @@ link_wasm_module \
|
||||
-s STACK_SIZE=2MB \
|
||||
-s NO_EXIT_RUNTIME=1 \
|
||||
-s FORCE_FILESYSTEM=1 \
|
||||
-s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_run_program_with_ini","_lcinterp_run_file","_lcinterp_run_file_with_ini","_lcinterp_run_file_with_ini_continue_on_error","_lcinterp_run_fiveaxis_remap_file","_lcinterp_run_remap_file","_lcinterp_run_remap_file_continue_on_error","_lcinterp_run_remap_io_mdi_sequence","_lcinterp_restore_parameters","_lcinterp_save_parameters","_lcinterp_load_tool_table","_lcinterp_load_tool_table_random","_lcinterp_save_tool_table","_lcinterp_probe_init_and_synch","_lcinterp_probe_indexer","_lcinterp_probe_named_parameters","_lcinterp_free_string"]' \
|
||||
-s EXPORTED_FUNCTIONS='["_malloc","_free","_lcinterp_run_program","_lcinterp_run_program_with_ini","_lcinterp_run_file","_lcinterp_run_file_with_ini","_lcinterp_run_file_with_ini_continue_on_error","_lcinterp_run_fiveaxis_remap_file","_lcinterp_run_remap_file","_lcinterp_run_remap_file_continue_on_error","_lcinterp_run_remap_io_mdi_sequence","_lcinterp_restore_parameters","_lcinterp_save_parameters","_lcinterp_load_tool_table","_lcinterp_load_tool_table_random","_lcinterp_save_tool_table","_lcinterp_probe_init_and_synch","_lcinterp_probe_indexer","_lcinterp_probe_named_parameters","_lcinterp_hal_reset","_lcinterp_hal_set_value","_lcinterp_probe_hal_named","_lcinterp_free_string"]' \
|
||||
-s EXPORTED_RUNTIME_METHODS='["FS","UTF8ToString","stringToUTF8","lengthBytesUTF8"]'
|
||||
|
||||
Reference in New Issue
Block a user