From cbd0bc10039a8a181be62cbf943eda66a05b3a78 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Fri, 28 Aug 2026 21:33:11 +0800 Subject: [PATCH] Align Web XYZAC command gates with LinuxCNC --- coverage/xyzac-trt-gate-matrix.report.json | 10 +- coverage/xyzac-trt-hal-tool-alarm.report.json | 6 +- coverage/xyzac-trt-manual-control.report.json | 6 +- .../xyzac-trt-program-preview.report.json | 6 +- coverage/xyzac-trt-protocol-react.report.json | 16 +- coverage/xyzac-trt-recovery.report.json | 12 +- coverage/xyzac-trt-tool-preview.report.json | 16 +- coverage/xyzac-trt-trace-parity.report.json | 4 +- ...xyzac-trt-wasm-authority-audit.report.json | 66 ++++---- ...zac-trt-wasm-only-release-gate.report.json | 4 +- .../xyzac-trt-web-wasm-parity.report.json | 10 +- coverage/xyzac-trt-workflow.report.json | 6 +- wasm-core/CMakeLists.txt | 1 + wasm-core/include/sim_task.h | 46 ++++++ wasm-core/src/sim_io.c | 2 +- wasm-core/src/sim_task.c | 84 ++++++++++ wasm-core/tests/sim_task_test.c | 11 ++ web/react/src/App.tsx | 8 +- web/worker/sim-session.mjs | 152 +++++++++++------- web/worker/sim-task.mjs | 6 + web/worker/test_xyzac_button_constraints.mjs | 6 +- web/worker/test_xyzac_gate_matrix.mjs | 1 + web/worker/test_xyzac_hal_tool_alarm.mjs | 1 + web/worker/test_xyzac_manual_control.mjs | 1 + web/worker/test_xyzac_protocol_react.mjs | 1 + web/worker/test_xyzac_recovery.mjs | 1 + web/worker/test_xyzac_rtcp.mjs | 1 + web/worker/test_xyzac_trace_parity.mjs | 1 + web/worker/test_xyzac_trt_case.mjs | 1 + web/worker/test_xyzac_workflow.mjs | 1 + 30 files changed, 337 insertions(+), 150 deletions(-) diff --git a/coverage/xyzac-trt-gate-matrix.report.json b/coverage/xyzac-trt-gate-matrix.report.json index 00bcd01..50d2c83 100644 --- a/coverage/xyzac-trt-gate-matrix.report.json +++ b/coverage/xyzac-trt-gate-matrix.report.json @@ -22,7 +22,7 @@ "ABORT", "ESTOP" ], - "state_hash": "fnv1a64:7c18597c9e7091d7", + "state_hash": "fnv1a64:71e86a767aa1a074", "status": "verified" }, "candidate_eligible": true, @@ -97,10 +97,10 @@ } }, "release_status": "STOP_RELEASE", - "report_sha256": "fd24448f6f46d484a2e27f711383db6d0695feafb73c18365082975ed33c8f97", + "report_sha256": "7fc97babb800be213cb6fc8be5bde213dec2d16905198bfaa5fbb9b1a5f2d24a", "schema": "xyzac-gate-matrix-rtcp-03.v1", "scope": "xyzac_only", - "state_hash": "fnv1a64:7b5f8d0ed1c175dd", + "state_hash": "fnv1a64:7014ca3ef3a4a3dd", "status": "verified", "task_id": "RTCP-03", "tests": [ @@ -109,7 +109,7 @@ "node", "web/worker/test_xyzac_gate_matrix.mjs" ], - "output_sha256": "c0b5ad1449a1bc463f2c282a1b783092ffcadef9bf20b93a56eff285033d971c", + "output_sha256": "0b798dd40940ed1f4482b21d07b15bbffd9bd66bb3f198bb8cf0da207da55636", "returncode": 0, "status": "verified" }, @@ -118,7 +118,7 @@ "node", "web/worker/test_xyzac_button_constraints.mjs" ], - "output_sha256": "2e105b80c6548ef53a2ebc656d6da39fe3bd7a5905f906188a3655e57e345b8e", + "output_sha256": "b9e6345ebba8dd794f8d407c68e208565a2b849f9fbfb184b3cc00b38927834e", "returncode": 0, "status": "verified" } diff --git a/coverage/xyzac-trt-hal-tool-alarm.report.json b/coverage/xyzac-trt-hal-tool-alarm.report.json index 2d0b3c8..07f2d82 100644 --- a/coverage/xyzac-trt-hal-tool-alarm.report.json +++ b/coverage/xyzac-trt-hal-tool-alarm.report.json @@ -54,11 +54,11 @@ "vismach-clear" ], "release_status": "STOP_RELEASE", - "report_sha256": "37026591b5875615e8a68374672fe7a7b433ef53504a3d52bc922a735f6fd140", + "report_sha256": "bb76f472e65cec8226cd07e00fcf00921ac44350873df4f65a2a00032925e915", "schema": "xyzac-hal-tool-alarm-rtcp-07.v1", "scope": "xyzac_only", "spindle_rpm": 1200, - "state_hash": "fnv1a64:198fc8da045d3e0f", + "state_hash": "fnv1a64:7ffad38ea9175625", "status": "verified", "task_id": "RTCP-07", "tests": [ @@ -67,7 +67,7 @@ "node", "web/worker/test_xyzac_hal_tool_alarm.mjs" ], - "output_sha256": "831953b1e6712c0187d8029facbc07cacf0a1290e17165019801a2000b8b4e40", + "output_sha256": "e77d1995a53250530bb5f3a5e4b9babc147a83cf89f989ac5e78f41c22d3aa2f", "returncode": 0, "status": "verified" } diff --git a/coverage/xyzac-trt-manual-control.report.json b/coverage/xyzac-trt-manual-control.report.json index 52641c8..0ec9e4e 100644 --- a/coverage/xyzac-trt-manual-control.report.json +++ b/coverage/xyzac-trt-manual-control.report.json @@ -61,10 +61,10 @@ }, "mdi": "verified", "release_status": "STOP_RELEASE", - "report_sha256": "a4ca2d5637e68c387e2ca5be460a2c9340d6b7198715cb1aa95d90055c623058", + "report_sha256": "4d4f011724dcbcfacb03e082116d978f1aeba9dcfe8e55402b37a627b082d6cc", "schema": "xyzac-manual-control-rtcp-06.v1", "scope": "xyzac_only", - "state_hash": "fnv1a64:8e9acc7cf9c8cec1", + "state_hash": "fnv1a64:4332c2ad8a89898d", "status": "verified", "task_id": "RTCP-06", "tests": [ @@ -73,7 +73,7 @@ "node", "web/worker/test_xyzac_manual_control.mjs" ], - "output_sha256": "c484daae09f135544f0a55376c79377e251e416651ab7d338e8d4d20f0d20768", + "output_sha256": "957df6509e77a68a20be63dd82dc06154d7e86c4b0c30b99574227d4b50b91de", "returncode": 0, "status": "verified" } diff --git a/coverage/xyzac-trt-program-preview.report.json b/coverage/xyzac-trt-program-preview.report.json index ef90933..abaaeaf 100644 --- a/coverage/xyzac-trt-program-preview.report.json +++ b/coverage/xyzac-trt-program-preview.report.json @@ -37,12 +37,12 @@ "replay_same_operation": "verified" }, "release_status": "STOP_RELEASE", - "report_sha256": "f44c782f09851b87a90e90583657a95c7ea30bffc5fd254fa8e3e16f87ed985e", + "report_sha256": "dced60b3901a793dad7d33882f08985febfa11ac38d863020b0137350dfac4e3", "schema": "xyzac-program-preview-rtcp-04.v1", "scope": "xyzac_only", "segment_count": 2, "source_hash": "fnv1a64:b7f4678312207b31", - "state_hash": "fnv1a64:dadc751fbbb70835", + "state_hash": "fnv1a64:dd298967c8e59be1", "status": "verified", "task_id": "RTCP-04", "tests": [ @@ -51,7 +51,7 @@ "node", "web/worker/test_xyzac_program_preview.mjs" ], - "output_sha256": "96e5c2c6203d26162a49206d5e9416b532bf880065a4a3418c863ea0c32bdb28", + "output_sha256": "71682d480c4e9c65128aa87f60c308878dbfe1787f42694def040dd9233fa835", "returncode": 0, "status": "verified" } diff --git a/coverage/xyzac-trt-protocol-react.report.json b/coverage/xyzac-trt-protocol-react.report.json index d8bd5d6..1cfcc9a 100644 --- a/coverage/xyzac-trt-protocol-react.report.json +++ b/coverage/xyzac-trt-protocol-react.report.json @@ -82,32 +82,32 @@ }, { "path": "web/worker/test_xyzac_protocol_react.mjs", - "sha256": "daa74b5091de4579b7ce5eb283b802e226e275bf51209af339e4cf6334fd6b5b" + "sha256": "b4c50561dbe90ea67bd6153098c9f8dce8480ccafd51d26bf167de3b18a0971d" } ], "release_status": "STOP_RELEASE", - "report_sha256": "f25c81c8aa94762e563bdfacceef0bf8b211ca632b2eed5556c88fd408b1c5b5", + "report_sha256": "2a68bfc0f2620b1d4abae70b18ca261802e5450125d37aa7c1e512adfd20ba99", "schema": "xyzac-trt-protocol-react-report.v1", - "state_hash": "fnv1a64:4cb77e59ba75a666", + "state_hash": "fnv1a64:30f3ddcdf9544695", "status": "verified", "switchkins": [ { - "operation_id": "select-0-4", + "operation_id": "select-0-5", "phase": "committed", "type": 0 }, { - "operation_id": "select-1-5", + "operation_id": "select-1-6", "phase": "committed", "type": 1 }, { - "operation_id": "select-2-6", + "operation_id": "select-2-7", "phase": "committed", "type": 2 }, { - "operation_id": "select-1-7", + "operation_id": "select-1-8", "phase": "committed", "type": 1 } @@ -119,7 +119,7 @@ "node", "web/worker/test_xyzac_protocol_react.mjs" ], - "output_sha256": "862f3c71bccb137c1551a5bfb4ed85014a7cbb2a2506f265193bbe29944c36a1", + "output_sha256": "b2d01a0bdee9b31f6680d0265c4537efd0a1fe12b650383089ad52d1148382bc", "returncode": 0 }, { diff --git a/coverage/xyzac-trt-recovery.report.json b/coverage/xyzac-trt-recovery.report.json index 838c6fd..91b7b70 100644 --- a/coverage/xyzac-trt-recovery.report.json +++ b/coverage/xyzac-trt-recovery.report.json @@ -24,8 +24,8 @@ "context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1", "endpoint": { "at_checkpoint_equal": true, - "continuous_hash": "fnv1a64:b105b6fc1bcc8a70", - "restored_hash": "fnv1a64:857caa278d03c259" + "continuous_hash": "fnv1a64:dc228a5abee74af6", + "restored_hash": "fnv1a64:6c4a6b85b192d794" }, "exclusions": [ "xyzbc", @@ -61,11 +61,11 @@ "outputs": [ { "path": "web/worker/test_xyzac_recovery.mjs", - "sha256": "d25dc8f5de748b37ca231ee7e5a0df049878511f4c0fec98061208730f57b145" + "sha256": "d875ef92ef58e591110fdd842e33a081143623b60b92450476b288ce2c72e3e0" }, { "path": "web/worker/sim-session.mjs", - "sha256": "adf00f12f4408bcbe00e296ce3d8003f764e4cdd0849c86039586fecdc822af1" + "sha256": "4f674200bfcca9af6bc2c83017329355b88307d0749ebb5c9d0fc8eaecaa2349" }, { "path": "web/worker/wasm-program-runtime.mjs", @@ -73,7 +73,7 @@ } ], "release_status": "STOP_RELEASE", - "report_sha256": "be2d59e86990e26858d11e3220cfa63d4b8f5d84f8b81f1067d89b645f78a9ae", + "report_sha256": "51beb4f92ebaa633816bdbdd1bea3bc0bd918488bb7173c4082683f06f3ed88d", "required_checkpoint_fields": [ "context_id", "simulation_tick", @@ -95,7 +95,7 @@ "node", "web/worker/test_xyzac_recovery.mjs" ], - "output_sha256": "3649f6971bb8ed8857dd5cab886834485a3d82c8dec71365853481c0f12a46c9", + "output_sha256": "43068de07ef8448d4bff5afb300400fd0358a697396d7616c1737dbb733f058d", "returncode": 0 } ] diff --git a/coverage/xyzac-trt-tool-preview.report.json b/coverage/xyzac-trt-tool-preview.report.json index 8a08a4a..9c6a476 100644 --- a/coverage/xyzac-trt-tool-preview.report.json +++ b/coverage/xyzac-trt-tool-preview.report.json @@ -74,7 +74,7 @@ }, { "path": "web/worker/sim-session.mjs", - "sha256": "adf00f12f4408bcbe00e296ce3d8003f764e4cdd0849c86039586fecdc822af1" + "sha256": "4f674200bfcca9af6bc2c83017329355b88307d0749ebb5c9d0fc8eaecaa2349" }, { "path": "web/worker/test_xyzac_exact_preview.mjs", @@ -102,7 +102,7 @@ }, { "path": "web/react/src/App.tsx", - "sha256": "31692dd2df5d9a1a26503e78627c5d10524426c975d8ffb0dfbd3f7cf5c0f8af" + "sha256": "2c9aaa252c734a3885d41364cf87f969bf9b58780137e8240906f5be0745b22c" }, { "path": "web/react/src/styles.css", @@ -110,7 +110,7 @@ }, { "path": "wasm-core/src/sim_task.c", - "sha256": "06aa4ba015d863e1749cfe11877a82507fd6aff2c6922a114f30f6c8871b0b5b" + "sha256": "b6f532a725d34a14a0f804c99fd6fd993c59a0626e203d0a3f3af9e8dc68d64b" }, { "path": "wasm-core/src/sim_program.c", @@ -289,7 +289,7 @@ "schema": "xyzac-exact-tool-preview.v1", "segment_count": 29, "source_hash": "fnv1a64:522e219683194ad2", - "state_hash": "fnv1a64:1b46405fa36a5abe", + "state_hash": "fnv1a64:300c582b009b292a", "status": "verified", "visual_segments": 29 }, @@ -350,7 +350,7 @@ "PROGRAM_RUN", "SIM_STEP" ], - "state_hash": "fnv1a64:3fe83d3f4768868c", + "state_hash": "fnv1a64:48bf3ba1b27a3272", "status": "verified" }, "static": { @@ -383,7 +383,7 @@ } }, "release_status": "STOP_RELEASE", - "report_sha256": "0127822355f55c04898dcb0f9333d291d5fa8fbb116d7199cbdf02694b97fe87", + "report_sha256": "d7447aab0398ac572599ecd7b50f24bf29a1751c0f1723f89eac9dadd1308302", "schema": "xyzac-trt-tool-preview-report.v1", "status": "verified", "task_id": "RTCP-10-TOOL-PREVIEW", @@ -402,7 +402,7 @@ "node", "web/worker/test_xyzac_exact_preview.mjs" ], - "output_sha256": "625aaa1f95578666d70390a782745ff1e74862f6ef11bbf2911b1870a2b864dd", + "output_sha256": "f4764d5350353402e40de503e040b882a66666888e11a8cf17ca696431d89efd", "returncode": 0 }, { @@ -410,7 +410,7 @@ "node", "web/worker/test_xyzac_machine_sequence.mjs" ], - "output_sha256": "a361d9e4f695d048093e7ff4b7b5e6688534071c5b3811a40bbe69e00d114133", + "output_sha256": "57599a2a43b0e5af610955e1c54d0f154999175cca25fa0b827d0cff661119ad", "returncode": 0 } ], diff --git a/coverage/xyzac-trt-trace-parity.report.json b/coverage/xyzac-trt-trace-parity.report.json index 47d18b9..3ade560 100644 --- a/coverage/xyzac-trt-trace-parity.report.json +++ b/coverage/xyzac-trt-trace-parity.report.json @@ -46,7 +46,7 @@ }, { "path": "web/worker/test_xyzac_trace_parity.mjs", - "sha256": "209254396d3ec828936ec5c2ad34de9044706f7d0309fd5ad4cacae347f15247" + "sha256": "564414a776ae502901edeac717b2ae06bcfb1fbdfb06a570c6f0001ac6282328" }, { "path": "coverage/xyzac-trt-rtcp.report.json", @@ -130,7 +130,7 @@ "mutation": "state[1].active_type=99" }, "release_status": "STOP_RELEASE", - "report_sha256": "234a362308c9caae71955a606d1779f45ce4fe170f476e49a6c5a482dd4351d5", + "report_sha256": "4bba1c060681cd39dbaaa1eb3ff3bfe42843581150780d1c5ddd7a896f19118d", "schema": "xyzac-trt-trace-parity-report.v1", "status": "verified", "task_id": "PARITY-00", diff --git a/coverage/xyzac-trt-wasm-authority-audit.report.json b/coverage/xyzac-trt-wasm-authority-audit.report.json index 19caecf..b2d92b8 100644 --- a/coverage/xyzac-trt-wasm-authority-audit.report.json +++ b/coverage/xyzac-trt-wasm-authority-audit.report.json @@ -128,10 +128,10 @@ "source_slice": "AXIS-XYZAC-PROGRAM-MDI", "sources": [ { - "lines": "107-833", + "lines": "122-846", "path": "web/worker/sim-session.mjs", "role": "session-authority-routing", - "sha256": "adf00f12f4408bcbe00e296ce3d8003f764e4cdd0849c86039586fecdc822af1", + "sha256": "4f674200bfcca9af6bc2c83017329355b88307d0749ebb5c9d0fc8eaecaa2349", "status": "resolved", "symbol": "new ProgramRunner" }, @@ -161,10 +161,10 @@ "symbol": "export class MotionState" }, { - "lines": "107-833", + "lines": "122-846", "path": "web/worker/sim-session.mjs", "role": "session-authority-routing", - "sha256": "adf00f12f4408bcbe00e296ce3d8003f764e4cdd0849c86039586fecdc822af1", + "sha256": "4f674200bfcca9af6bc2c83017329355b88307d0749ebb5c9d0fc8eaecaa2349", "status": "resolved", "symbol": "new ProgramRunner" } @@ -186,10 +186,10 @@ "symbol": "export class AuthoritativeRuntime" }, { - "lines": "107-833", + "lines": "122-846", "path": "web/worker/sim-session.mjs", "role": "session-authority-routing", - "sha256": "adf00f12f4408bcbe00e296ce3d8003f764e4cdd0849c86039586fecdc822af1", + "sha256": "4f674200bfcca9af6bc2c83017329355b88307d0749ebb5c9d0fc8eaecaa2349", "status": "resolved", "symbol": "new ProgramRunner" } @@ -205,7 +205,7 @@ "recovery": "Chromium SIGKILL/profile-reopen, corruption detection and Firefox OPFS round-trip are verified" }, "release_status": "STOP_RELEASE", - "report_sha256": "6bf29284946b658ca58c76c91b2dbad3c33f8af7afb74b91aecb5a2842f3b650", + "report_sha256": "2ca53b1aa5e691f44280473301772ac7dcace0f492fc309ebacb1f66677bfdff", "scans": { "js_gcode_matches": [ { @@ -274,12 +274,12 @@ "text": "const result = call(this.core, \"sim_interpreter_execute\", \"number\", [\"number\", \"number\", \"number\"], [this.interpreter, this.blockBuffer, bytes.length]);" }, { - "line": 1526, + "line": 1553, "path": "web/worker/sim-session.mjs", "text": "const result = this.xyzacProgram.executeBlock(block);" }, { - "line": 1545, + "line": 1572, "path": "web/worker/sim-session.mjs", "text": "const result = this.program.executeBlock(block);" }, @@ -292,7 +292,7 @@ "opfs_writer_matches": [ { "line": 1, - "path": "web/react/dist/assets/sim-worker-entry-BiqKYM9j.js", + "path": "web/react/dist/assets/sim-worker-entry-DUl-hrwT.js", "text": "(function(){var e=(()=>{var e=self.location.href;return(function(t){t||={};var t=t===void 0?{}:t,n,r;t.ready=new Promise(function(e,t){n=e,r=t});var i=Object.assign({},t),a=[],o=typeof window==`object`,s=typeof importScripts==`function`;typ" }, { @@ -1496,7 +1496,7 @@ "files": [ { "path": "web/react/src/App.tsx", - "sha256": "31692dd2df5d9a1a26503e78627c5d10524426c975d8ffb0dfbd3f7cf5c0f8af" + "sha256": "2c9aaa252c734a3885d41364cf87f969bf9b58780137e8240906f5be0745b22c" }, { "path": "web/react/src/ThreeMachineView.tsx", @@ -1652,11 +1652,11 @@ }, { "path": "web/worker/sim-session.mjs", - "sha256": "adf00f12f4408bcbe00e296ce3d8003f764e4cdd0849c86039586fecdc822af1" + "sha256": "4f674200bfcca9af6bc2c83017329355b88307d0749ebb5c9d0fc8eaecaa2349" }, { "path": "web/worker/sim-task.mjs", - "sha256": "4b4b3aa4b86aba08e5afb8534d98d6ab11c845eeb891cf4793b5906a01de1599" + "sha256": "2e055b365b4b3ce2b47de56c987e326183c8f16a4746f5fbe324268a5c681a24" }, { "path": "web/worker/sim-worker.mjs", @@ -1968,7 +1968,7 @@ }, { "path": "web/worker/test_xyzac_button_constraints.mjs", - "sha256": "e5206c37f959d0214f83892dcd2b57abf6d16335ac1f6eedb36e9bbad5a45d5f" + "sha256": "faf7aa191e66b4c7a2f4642db19ee7b1712e7781ab474502ab1b01585554c1d0" }, { "path": "web/worker/test_xyzac_exact_preview.mjs", @@ -1976,11 +1976,11 @@ }, { "path": "web/worker/test_xyzac_gate_matrix.mjs", - "sha256": "176da9a86c17cef2fb1f17bb65c5854af107524ac26871fa830e17e62fc022cf" + "sha256": "7113f7fed0c7f8a66b1b6fc73960d5e84427fce932edb527bb721838aedbfdfc" }, { "path": "web/worker/test_xyzac_hal_tool_alarm.mjs", - "sha256": "8da3024cf0a8fc29f77c481204bce0ed3672cf411a5b52e553cb60c92213b0aa" + "sha256": "95bfec604348b3d9a41faa457ff9a60df4ade47e46cabf38be391be2352b9c68" }, { "path": "web/worker/test_xyzac_machine_sequence.mjs", @@ -1988,7 +1988,7 @@ }, { "path": "web/worker/test_xyzac_manual_control.mjs", - "sha256": "6219a55c4c1470193f984d94fea528e697479903e2623abf57a4d5b5274fd797" + "sha256": "113710cb0d89651dfed8d7806ed98ad88b8817bea70aa674f8b039ace45f5187" }, { "path": "web/worker/test_xyzac_program_preview.mjs", @@ -1996,15 +1996,15 @@ }, { "path": "web/worker/test_xyzac_protocol_react.mjs", - "sha256": "daa74b5091de4579b7ce5eb283b802e226e275bf51209af339e4cf6334fd6b5b" + "sha256": "b4c50561dbe90ea67bd6153098c9f8dce8480ccafd51d26bf167de3b18a0971d" }, { "path": "web/worker/test_xyzac_recovery.mjs", - "sha256": "d25dc8f5de748b37ca231ee7e5a0df049878511f4c0fec98061208730f57b145" + "sha256": "d875ef92ef58e591110fdd842e33a081143623b60b92450476b288ce2c72e3e0" }, { "path": "web/worker/test_xyzac_rtcp.mjs", - "sha256": "7e8be744ba11def666f3cca70252d9ba64f6801d6436f624e4b8c81738a46ec5" + "sha256": "bb62b28ea9ecae7328c16ed65ba013a68cabd421bea58929d30fe9271c4e6d77" }, { "path": "web/worker/test_xyzac_session.mjs", @@ -2016,11 +2016,11 @@ }, { "path": "web/worker/test_xyzac_trace_parity.mjs", - "sha256": "209254396d3ec828936ec5c2ad34de9044706f7d0309fd5ad4cacae347f15247" + "sha256": "564414a776ae502901edeac717b2ae06bcfb1fbdfb06a570c6f0001ac6282328" }, { "path": "web/worker/test_xyzac_trt_case.mjs", - "sha256": "e1ab517d11a70e0682453c7fa82f9d1bcda8de0c7fd8547e6db24ef03fe4ad16" + "sha256": "0294493d482a6b476be0c941e2cd96eef36537500ea400d06e0f151ecb4c7c47" }, { "path": "web/worker/test_xyzac_trt_io_authority.mjs", @@ -2032,7 +2032,7 @@ }, { "path": "web/worker/test_xyzac_workflow.mjs", - "sha256": "4dc92c0185bb6f1c4c4f925bc6e5e6800acd0c1ee5c3c4c194776fda03d656d9" + "sha256": "200554ccae0ca29ec4841ec58cea90895c93f91b64eb9c1dffeb01145cef7465" }, { "path": "web/worker/trace-export.mjs", @@ -2086,10 +2086,10 @@ "scope": "selected_xyzac_simulation_only", "sources": [ { - "lines": "107-833", + "lines": "122-846", "path": "web/worker/sim-session.mjs", "role": "session-authority-routing", - "sha256": "adf00f12f4408bcbe00e296ce3d8003f764e4cdd0849c86039586fecdc822af1", + "sha256": "4f674200bfcca9af6bc2c83017329355b88307d0749ebb5c9d0fc8eaecaa2349", "status": "resolved", "symbol": "new ProgramRunner" }, @@ -2129,7 +2129,7 @@ "lines": "31-31", "path": "web/worker/sim-task.mjs", "role": "task-authority-wrapper", - "sha256": "4b4b3aa4b86aba08e5afb8534d98d6ab11c845eeb891cf4793b5906a01de1599", + "sha256": "2e055b365b4b3ce2b47de56c987e326183c8f16a4746f5fbe324268a5c681a24", "status": "resolved", "symbol": "export class SimTask" }, @@ -2207,9 +2207,9 @@ } ], "state_hashes": [ - "fnv1a64:8bb613bfa975ae2e", - "fnv1a64:051e2337103b3b9b", - "fnv1a64:7aaf47cbc3c37b54" + "fnv1a64:209531d9fc283248", + "fnv1a64:ca3f86c22f00a1b8", + "fnv1a64:08d380ff6ff129bd" ], "status": "verified", "task_id": "WASM-00", @@ -2227,7 +2227,7 @@ "control_count": 110, "first_divergent_tick": null, "motion_authority": "wasm", - "output_sha256": "de69f1a6f2f608f9d156713e50addd1677e17957b760261de7e0092a4e031a82", + "output_sha256": "e9d4aceca4c2c88882593ead1339f12d0ef81af51f257403c22f8c17e15e2f18", "returncode": 0, "state_hash": "fnv1a64:e862c9e59d9ffd8c", "status": "verified", @@ -2235,19 +2235,19 @@ { "active_type": 0, "code": "M429", - "state_hash": "fnv1a64:8bb613bfa975ae2e", + "state_hash": "fnv1a64:209531d9fc283248", "tick": 0 }, { "active_type": 1, "code": "M428", - "state_hash": "fnv1a64:051e2337103b3b9b", + "state_hash": "fnv1a64:ca3f86c22f00a1b8", "tick": 0 }, { "active_type": 2, "code": "M430", - "state_hash": "fnv1a64:7aaf47cbc3c37b54", + "state_hash": "fnv1a64:08d380ff6ff129bd", "tick": 0 } ], diff --git a/coverage/xyzac-trt-wasm-only-release-gate.report.json b/coverage/xyzac-trt-wasm-only-release-gate.report.json index 51e38c4..60140ad 100644 --- a/coverage/xyzac-trt-wasm-only-release-gate.report.json +++ b/coverage/xyzac-trt-wasm-only-release-gate.report.json @@ -96,7 +96,7 @@ "failure_samples": [], "input": { "path": "coverage/xyzac-trt-web-wasm-parity.report.json", - "sha256": "7f88767d4b0b3b70bde27e8d5165a901618dfa2fb5076eceeb117f443bd305df" + "sha256": "6e4935ebe55b771bb9a7f831b50cadda9ba7b10e09cb4b495d29faee6cf86f80" }, "known_blockers": [ "STOP_RELEASE is intentional until an owner authorizes scope expansion" @@ -104,7 +104,7 @@ "outputs": [], "release_decision": "selected_case_verified_but_release_stopped_by_scope_exclusions", "release_status": "STOP_RELEASE", - "report_sha256": "712fa17edbff4c78aa58700637b54b4974b3a2c0104f4c91de566d4ff27ac5c4", + "report_sha256": "971e5b6ec19213d5bbdf414e65868fd440391e9e27579d3d3bf5b3186c741816", "schema": "xyzac-trt-wasm-only-release-gate.v1", "status": "verified", "task_id": "RELEASE-00" diff --git a/coverage/xyzac-trt-web-wasm-parity.report.json b/coverage/xyzac-trt-web-wasm-parity.report.json index 4e7c4cc..0d0337d 100644 --- a/coverage/xyzac-trt-web-wasm-parity.report.json +++ b/coverage/xyzac-trt-web-wasm-parity.report.json @@ -91,7 +91,7 @@ }, "release_status": "STOP_RELEASE", "report_hashes": { - "coverage/xyzac-trt-gate-matrix.report.json": "c3a5bc3eabd6631a8645a28df997ef605f911230c55527dbda016edf5a37ee6f", + "coverage/xyzac-trt-gate-matrix.report.json": "b2a74bc5e8440728dbbbfb917ae1949e6acae9c4c1f973649e4b19edd8755034", "coverage/xyzac-trt-hal-tool-alarm.report.json": "99ac60c9a2d58f08063ffc07f0c2eea62d8ec97212d6d2b74d6c139f176b592e", "coverage/xyzac-trt-manual-control.report.json": "9ebb8ef2c97a57d77666c92e210ad1415a63852730b37993f995ca95cbc275b5", "coverage/xyzac-trt-program-preview.report.json": "b95a59986c0bd0cb098c49fbc8c9ced80bbe024888dcbcc14c15dd6e67432814", @@ -101,12 +101,12 @@ "coverage/xyzac-trt-source-slice-inventory.report.json": "bf5f3fd36e1943e0dc42bafb2ed195fad147bcb3e56a8d208d7f01a3641829de", "coverage/xyzac-trt-startup.report.json": "56e040b645bcefc3d6c57244cb2d3aba16f421ff5137089d9c3336970dfd968b", "coverage/xyzac-trt-threejs.report.json": "5172d7118c555fca5f92bcb443b9c259ea0225346482558d4c33ef8a4f8da5cb", - "coverage/xyzac-trt-tool-preview.report.json": "bc39dec83f71f9e164faed098d281ca1b2390b34b225fb4fa81ebf10bc860ee8", - "coverage/xyzac-trt-trace-parity.report.json": "73b2fd46c66d0c5910c746db9d8892e0ea713714576cf793f510bee35e9a7c00", - "coverage/xyzac-trt-wasm-authority-audit.report.json": "a970d9980b99d8b64315088e3f182d9d99aad3787831e28feb6127c9e854c6e8", + "coverage/xyzac-trt-tool-preview.report.json": "e7093b1039a26fd62c3352f9260989a120902e3a88b4c68b751f47f05f495b05", + "coverage/xyzac-trt-trace-parity.report.json": "6c6b3500bc1189ff68513b74f0e5144879c3af7641349cb61737191303b76950", + "coverage/xyzac-trt-wasm-authority-audit.report.json": "19fd46cca7043ed633f55cae0d7cdd3bf8bda96567fdc93cc047da39e766f9f9", "coverage/xyzac-trt-workflow.report.json": "acbb6dcfdff385dc839ddb94724f9fdcebc391ccde995881ba6be14dd5a6b5c4" }, - "report_sha256": "09fa8fc47cd34237f97b7b74707cd57721874960809f627d1d49fdc0319c627a", + "report_sha256": "888838e4d4242ff562530eb547fa3ed6dafc940502e822b182d8abf4a9ddb4d2", "schema": "xyzac-trt-web-wasm-parity-report.v1", "source_coverage": { "axis_keyboard": 10, diff --git a/coverage/xyzac-trt-workflow.report.json b/coverage/xyzac-trt-workflow.report.json index c414296..64995df 100644 --- a/coverage/xyzac-trt-workflow.report.json +++ b/coverage/xyzac-trt-workflow.report.json @@ -28,12 +28,12 @@ "pause_cursor_stable": true, "recovery_status": "recovered", "release_status": "STOP_RELEASE", - "report_sha256": "beaae5ac83afda17ceae23a7b999ae79154035ddf29e20b8c96707d7ccf4d808", + "report_sha256": "a0ccd6430435fc27c7cab05fa4f76c0b442bb3cef3295d92ec2d99ef626c5eab", "restart_source_hash_preserved": true, "run_from_line": 4, "schema": "xyzac-workflow-rtcp-05.v1", "scope": "xyzac_only", - "state_hash": "fnv1a64:cb67e9edef5a4bc8", + "state_hash": "fnv1a64:ad99d63136807bb3", "status": "verified", "task_id": "RTCP-05", "tests": [ @@ -42,7 +42,7 @@ "node", "web/worker/test_xyzac_workflow.mjs" ], - "output_sha256": "13e39016ef9db1817514bb70f6c26ca5116f3fd84c30e78887fd93e5fcad2cc6", + "output_sha256": "d850873f16ced32a4dfa5bf2e36379b22c68649afafb2a5164693edd76b4ccae", "returncode": 0, "status": "verified" } diff --git a/wasm-core/CMakeLists.txt b/wasm-core/CMakeLists.txt index d0c19da..382069d 100644 --- a/wasm-core/CMakeLists.txt +++ b/wasm-core/CMakeLists.txt @@ -246,6 +246,7 @@ if(EMSCRIPTEN) LINK_FLAGS "-sMODULARIZE=1 -sEXPORT_ES6=1 -sENVIRONMENT=web,worker -sEXPORTED_FUNCTIONS=['_malloc','_free','_sim_create','_sim_load_workspace','_sim_command','_sim_step','_sim_read_snapshot','_sim_read_event','_sim_export_state','_sim_import_state','_sim_destroy','_sim_protocol_reset','_sim_protocol_accept','_sim_protocol_pause','_sim_protocol_resume','_sim_protocol_state_value','_sim_protocol_last_seq','_sim_protocol_tick','_sim_protocol_set_tick','_sim_event_reset','_sim_event_append','_sim_event_count','_sim_event_cursor','_sim_event_read','_sim_event_export','_sim_event_import','_sim_task_size','_sim_task_init','_sim_task_reset','_sim_task_attach','_sim_task_set_command','_sim_task_set_tick','_sim_task_set_mode','_sim_task_set_program','_sim_task_transition','_sim_task_fault','_sim_task_estop','_sim_task_recover','_sim_task_restore','_sim_task_read_state','_sim_task_canonical','_sim_task_state_hash','_sim_scheduler_size','_sim_scheduler_init','_sim_scheduler_reset','_sim_scheduler_step','_sim_scheduler_restore','_sim_scheduler_read','_sim_scheduler_tick','_sim_scheduler_state_hash','_sim_scheduler_canonical','_sim_hal_reset','_sim_hal_register_pin','_sim_hal_set_pin','_sim_hal_get_pin','_sim_hal_set_param','_sim_hal_get_param','_sim_hal_connect','_sim_hal_register_thread','_sim_hal_register_function','_sim_hal_register_component','_sim_hal_set_function_failure','_sim_hal_step','_sim_hal_tick','_sim_hal_function_calls','_sim_hal_pin_count','_sim_hal_thread_count','_sim_hal_function_count','_sim_hal_component_count','_sim_hal_param_count','_sim_device_init','_sim_device_reset','_sim_device_encoder_set_speed','_sim_device_encoder_set_config','_sim_device_encoder_set_index_enable','_sim_device_coolant_command','_sim_device_coolant_set_fault','_sim_device_door_set','_sim_device_interlock_set','_sim_device_interlock_reset','_sim_device_estop_set','_sim_device_step','_sim_device_ack_issue','_sim_device_ack_ack','_sim_device_ack_abort','_sim_device_ack_step','_sim_device_state_hash','_sim_device_canonical','_sim_posemath_normalize','_sim_posemath_dot','_sim_posemath_line_point','_sim_posemath_is_finite','_sim_posemath_pose_is_finite','_sim_posemath_pose_line_point','_sim_posemath_segment_validate','_sim_posemath_segment_point','_sim_posemath_hash_state','_sim_tp_init','_sim_tp_reset','_sim_tp_enqueue','_sim_tp_step','_sim_tp_read_state','_sim_tp_state_hash','_sim_interpreter_size','_sim_interpreter_init','_sim_interpreter_reset','_sim_interpreter_execute','_sim_interpreter_read_state','_sim_interpreter_read_motion','_sim_interpreter_motion_size','_sim_interpreter_canonical','_sim_interpreter_state_hash','_sim_interpreter_block_hash','_sim_interpreter_modal_hash','_sim_interpreter_error_column','_sim_interpreter_error_code','_sim_interpreter_parameter_set','_sim_interpreter_parameter_get','_sim_motion_init','_sim_motion_size','_sim_motion_command_size','_sim_motion_state_size','_sim_motion_reset','_sim_motion_enqueue','_sim_motion_step','_sim_motion_read_state','_sim_motion_queue_depth','_sim_motion_state_hash','_sim_motion_canonical','_sim_kinematics_init','_sim_kinematics_forward','_sim_kinematics_forward_ex','_sim_kinematics_inverse','_sim_kinematics_inverse_ex','_sim_kinematics_home','_sim_kinematics_validate_pose','_sim_kinematics_state_hash','_sim_kinematics_switch_init','_sim_kinematics_switch_register','_sim_kinematics_switch_begin','_sim_kinematics_switch_abort','_sim_kinematics_switch_commit','_sim_kinematics_switch_select','_sim_kinematics_switch_forward','_sim_kinematics_switch_inverse','_sim_kinematics_switch_active','_sim_kinematics_switch_state_hash','_sim_kinematics_user_init','_sim_kinematics_user_bind_switch','_sim_kinematics_user_forward','_sim_kinematics_user_inverse','_sim_kinematics_user_reject_dynamic','_sim_kinematics_user_state_hash','_sim_kinematics_registry_count','_sim_kinematics_registry_get','_sim_kinematics_registry_find','_sim_kinematics_registry_require','_sim_kinematics_registry_validate_config','_sim_kinematics_registry_state_hash','_sim_kinematics_motion_init','_sim_kinematics_motion_reset','_sim_kinematics_motion_enqueue','_sim_kinematics_motion_pause','_sim_kinematics_motion_step','_sim_kinematics_motion_peek','_sim_kinematics_motion_pop','_sim_kinematics_motion_depth','_sim_kinematics_motion_state_hash','_sim_kinematics_feedback_init','_sim_kinematics_feedback_reset','_sim_kinematics_feedback_update','_sim_kinematics_feedback_read','_sim_kinematics_feedback_state_hash','_sim_kinematics_home_limit_init','_sim_kinematics_home_limit_reset','_sim_kinematics_home_limit_home','_sim_kinematics_home_limit_check','_sim_kinematics_home_limit_read','_sim_kinematics_home_limit_state_hash','_sim_io_init','_sim_io_reset','_sim_io_register_pins','_sim_io_spindle_command','_sim_io_spindle_set_inhibit','_sim_io_spindle_set_fault','_sim_io_spindle_set_index_enable','_sim_io_spindle_set_feedback','_sim_io_spindle_orient','_sim_io_spindle_set_oriented','_sim_io_tool_prepare','_sim_io_tool_set_prepared','_sim_io_tool_change','_sim_io_tool_set_changed','_sim_io_tool_abort','_sim_io_tool_set_fault','_sim_io_tool_clear_fault','_sim_io_probe_arm','_sim_io_probe_update','_sim_io_probe_disarm','_sim_io_set_estop','_sim_io_recover','_sim_io_step','_sim_io_read_state','_sim_io_state_hash','_sim_io_canonical'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap']" ) get_target_property(_sim_module_link_flags sim_core_module LINK_FLAGS) + string(REPLACE "_sim_task_recover','_sim_task_restore" "_sim_task_recover','_sim_task_command_gate','_sim_task_restore" _sim_module_link_flags "${_sim_module_link_flags}") string(REPLACE "_sim_interpreter_size" "_sim_program_size','_sim_program_requires_subroutines','_sim_program_init','_sim_program_reset','_sim_program_load','_sim_program_add_subroutine','_sim_program_set_rapid_velocity','_sim_program_start','_sim_program_pause','_sim_program_resume','_sim_program_abort','_sim_program_step','_sim_program_execute_block','_sim_program_seek','_sim_program_read_motion','_sim_program_clear_motion','_sim_program_canonical','_sim_program_state_hash','_sim_interpreter_size" _sim_module_link_flags "${_sim_module_link_flags}") string(REPLACE "_sim_hal_set_pin" "_sim_hal_set_pin_scalar','_sim_hal_set_pin" _sim_module_link_flags "${_sim_module_link_flags}") string(REPLACE "_sim_hal_set_param" "_sim_hal_set_param_scalar','_sim_hal_set_param" _sim_module_link_flags "${_sim_module_link_flags}") diff --git a/wasm-core/include/sim_task.h b/wasm-core/include/sim_task.h index 5b68706..1c4d113 100644 --- a/wasm-core/include/sim_task.h +++ b/wasm-core/include/sim_task.h @@ -53,6 +53,47 @@ enum sim_task_program_value { SIM_TASK_PROGRAM_FAULT = 6, }; +/* LinuxCNC emctaskmain command classes used by the Web XYZAC surface. */ +enum sim_task_gate_command { + SIM_TASK_GATE_ESTOP = 1, + SIM_TASK_GATE_RESET = 2, + SIM_TASK_GATE_POWER = 3, + SIM_TASK_GATE_HOME = 4, + SIM_TASK_GATE_JOG = 5, + SIM_TASK_GATE_MDI = 6, + SIM_TASK_GATE_RUN = 7, + SIM_TASK_GATE_PAUSE = 8, + SIM_TASK_GATE_RESUME = 9, + SIM_TASK_GATE_ABORT = 10, + SIM_TASK_GATE_RESTART = 11, + SIM_TASK_GATE_KINEMATICS = 12, + SIM_TASK_GATE_SET_MODE = 13, + SIM_TASK_GATE_PROGRAM_LOAD = 14, + SIM_TASK_GATE_STEP = 15, +}; + +enum sim_task_gate_result { + SIM_TASK_GATE_ALLOWED = 0, + SIM_TASK_GATE_ESTOP_ACTIVE = 1, + SIM_TASK_GATE_MACHINE_OFF = 2, + SIM_TASK_GATE_MACHINE_ON = 3, + SIM_TASK_GATE_INTERP_ACTIVE = 4, + SIM_TASK_GATE_TASKFILE_MISSING = 5, + SIM_TASK_GATE_ESTOPOFF_REQUIRED = 6, + SIM_TASK_GATE_PAUSE_STATE = 7, + SIM_TASK_GATE_RESUME_STATE = 8, + SIM_TASK_GATE_ABORT_STATE = 9, + SIM_TASK_GATE_RESTART_STATE = 10, + SIM_TASK_GATE_KINEMATICS_INVALID = 11, + SIM_TASK_GATE_MODE_STATE = 12, + SIM_TASK_GATE_MODE_AUTO_ACTIVE = 13, + SIM_TASK_GATE_JOG_STATE = 14, + SIM_TASK_GATE_MDI_STATE = 15, + SIM_TASK_GATE_RESET_STATE = 16, + SIM_TASK_GATE_UNKNOWN_COMMAND = 17, + SIM_TASK_GATE_UNHOMED = 18, +}; + typedef struct sim_task_state { char context_id[SIM_TASK_CONTEXT_CAPACITY]; char session_id[SIM_TASK_SESSION_CAPACITY]; @@ -81,6 +122,11 @@ int32_t sim_task_transition(sim_task *task, uint32_t state); int32_t sim_task_fault(sim_task *task, int32_t error_code); int32_t sim_task_estop(sim_task *task, int32_t error_code); int32_t sim_task_recover(sim_task *task); +int32_t sim_task_command_gate(const sim_task *task, uint32_t command, + uint32_t requested, uint32_t io_ready, + uint32_t io_estop, uint32_t homed, + uint32_t queue_depth, + uint32_t queue_limit); int32_t sim_task_restore(sim_task *task, uint32_t state, uint32_t mode, uint32_t program_status, uint32_t seq, uint32_t simulation_tick, uint32_t current_line, diff --git a/wasm-core/src/sim_io.c b/wasm-core/src/sim_io.c index af90f62..63572f7 100644 --- a/wasm-core/src/sim_io.c +++ b/wasm-core/src/sim_io.c @@ -456,7 +456,6 @@ int sim_io_set_estop(sim_io *io, uint32_t estop) { int sim_io_set_power(sim_io *io, uint32_t active) { if (!valid_io(io) || active > 1U) return SIM_IO_ERR_ARGUMENT; - if (io->state.estop) { set_last(io, SIM_IO_ERR_ESTOP); return SIM_IO_ERR_ESTOP; } if (!active) { io->state.ready = 0; io->state.tool_prepare = 0; @@ -470,6 +469,7 @@ int sim_io_set_power(sim_io *io, uint32_t active) { io->state.spindles[index].brake = 1; } } else { + if (io->state.estop) { set_last(io, SIM_IO_ERR_ESTOP); return SIM_IO_ERR_ESTOP; } if (io->state.fault || io->state.toolchanger_fault) { set_last(io, SIM_IO_ERR_FAULT); return SIM_IO_ERR_FAULT; } if (!spindle_stopped(io)) { set_last(io, SIM_IO_ERR_BUSY); return SIM_IO_ERR_BUSY; } io->state.ready = 1; diff --git a/wasm-core/src/sim_task.c b/wasm-core/src/sim_task.c index b510c6a..aa1d1d2 100644 --- a/wasm-core/src/sim_task.c +++ b/wasm-core/src/sim_task.c @@ -165,6 +165,90 @@ int32_t sim_task_recover(sim_task *task) { return result; } +int32_t sim_task_command_gate(const sim_task *task, uint32_t command, + uint32_t requested, uint32_t io_ready, + uint32_t io_estop, uint32_t homed, + uint32_t queue_depth, + uint32_t queue_limit) { + int machine_on; + int active; + int idle; + if (!valid_task(task) || io_ready > 1U || io_estop > 1U || homed > 1U) + return SIM_TASK_GATE_UNKNOWN_COMMAND; + machine_on = io_ready != 0U && io_estop == 0U; + active = task->state.state == SIM_TASK_RUNNING || + task->state.state == SIM_TASK_PAUSED || + task->state.program_status == SIM_TASK_PROGRAM_RUNNING || + task->state.program_status == SIM_TASK_PROGRAM_PAUSED; + idle = !active; + switch (command) { + case SIM_TASK_GATE_ESTOP: + /* LinuxCNC accepts ESTOP from every task state; reset is only the + inverse edge while the auxiliary estop input is asserted. */ + return requested == 1U || io_estop ? SIM_TASK_GATE_ALLOWED : SIM_TASK_GATE_ESTOPOFF_REQUIRED; + case SIM_TASK_GATE_POWER: + if (requested == 1U) return io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : machine_on ? SIM_TASK_GATE_MACHINE_ON : SIM_TASK_GATE_ALLOWED; + /* emcTaskSetState(OFF) aborts motion and is accepted while running. */ + return machine_on ? SIM_TASK_GATE_ALLOWED : SIM_TASK_GATE_MACHINE_OFF; + case SIM_TASK_GATE_PROGRAM_LOAD: + /* PLAN_OPEN is an immediate task command and is accepted in the + OFF/ESTOP/ESTOP_RESET branches when the interpreter is idle. */ + return idle ? SIM_TASK_GATE_ALLOWED : SIM_TASK_GATE_INTERP_ACTIVE; + case SIM_TASK_GATE_RUN: + if (io_estop || !machine_on) return io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : SIM_TASK_GATE_MACHINE_OFF; + if (!idle) return SIM_TASK_GATE_INTERP_ACTIVE; + if (!homed) return SIM_TASK_GATE_UNHOMED; + return task->state.program_status == SIM_TASK_PROGRAM_IDLE ? SIM_TASK_GATE_TASKFILE_MISSING : SIM_TASK_GATE_ALLOWED; + case SIM_TASK_GATE_STEP: + if (io_estop || !machine_on) return io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : SIM_TASK_GATE_MACHINE_OFF; + if (!homed) return SIM_TASK_GATE_UNHOMED; + return task->state.program_status != SIM_TASK_PROGRAM_IDLE && + (idle || task->state.mode == SIM_TASK_MODE_AUTO) + ? SIM_TASK_GATE_ALLOWED : SIM_TASK_GATE_RESET_STATE; + case SIM_TASK_GATE_PAUSE: + return machine_on && task->state.mode == SIM_TASK_MODE_AUTO && task->state.program_status == SIM_TASK_PROGRAM_RUNNING + ? SIM_TASK_GATE_ALLOWED : SIM_TASK_GATE_PAUSE_STATE; + case SIM_TASK_GATE_RESUME: + return machine_on && (task->state.mode == SIM_TASK_MODE_AUTO || task->state.mode == SIM_TASK_MODE_MDI) && task->state.program_status == SIM_TASK_PROGRAM_PAUSED + ? SIM_TASK_GATE_ALLOWED : SIM_TASK_GATE_RESUME_STATE; + case SIM_TASK_GATE_ABORT: + /* EMC_TASK_ABORT_TYPE is an immediate command in every emctaskmain + state branch, including OFF/ESTOP and an idle interpreter. */ + return SIM_TASK_GATE_ALLOWED; + case SIM_TASK_GATE_RESTART: + if (!idle) return SIM_TASK_GATE_INTERP_ACTIVE; + return task->state.program_status == SIM_TASK_PROGRAM_IDLE ? SIM_TASK_GATE_TASKFILE_MISSING : SIM_TASK_GATE_ALLOWED; + case SIM_TASK_GATE_HOME: + return machine_on && idle ? SIM_TASK_GATE_ALLOWED : machine_on ? SIM_TASK_GATE_INTERP_ACTIVE : io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : SIM_TASK_GATE_MACHINE_OFF; + case SIM_TASK_GATE_JOG: + /* AXIS allows jog commands while idle in all three task modes; the + manual tab changes mode before sending the command when required. */ + return machine_on && idle ? SIM_TASK_GATE_ALLOWED : io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : machine_on ? SIM_TASK_GATE_INTERP_ACTIVE : SIM_TASK_GATE_MACHINE_OFF; + case SIM_TASK_GATE_MDI: + if (io_estop || !machine_on) return io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : SIM_TASK_GATE_MACHINE_OFF; + if (!idle) return SIM_TASK_GATE_INTERP_ACTIVE; + if (!homed) return SIM_TASK_GATE_UNHOMED; + if (queue_limit != 0U && queue_depth >= queue_limit) return SIM_TASK_GATE_MDI_STATE; + return SIM_TASK_GATE_ALLOWED; + case SIM_TASK_GATE_KINEMATICS: + if (requested > 2U) return SIM_TASK_GATE_KINEMATICS_INVALID; + if (io_estop || !machine_on) return io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : SIM_TASK_GATE_MACHINE_OFF; + if (!homed) return SIM_TASK_GATE_UNHOMED; + return idle ? SIM_TASK_GATE_ALLOWED : SIM_TASK_GATE_INTERP_ACTIVE; + case SIM_TASK_GATE_SET_MODE: + if (requested > SIM_TASK_MODE_AUTO || io_estop || !machine_on) return io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : io_ready ? SIM_TASK_GATE_MODE_STATE : SIM_TASK_GATE_MACHINE_OFF; + /* emcTaskSetMode explicitly refuses AUTO -> MANUAL/MDI while the + AUTO interpreter is non-idle. */ + if (active && task->state.mode == SIM_TASK_MODE_AUTO && requested != SIM_TASK_MODE_AUTO) + return SIM_TASK_GATE_MODE_AUTO_ACTIVE; + return SIM_TASK_GATE_ALLOWED; + case SIM_TASK_GATE_RESET: + return machine_on && idle ? SIM_TASK_GATE_ALLOWED : io_estop ? SIM_TASK_GATE_ESTOP_ACTIVE : !machine_on ? SIM_TASK_GATE_MACHINE_OFF : SIM_TASK_GATE_RESET_STATE; + default: + return SIM_TASK_GATE_UNKNOWN_COMMAND; + } +} + int32_t sim_task_restore(sim_task *task, uint32_t state, uint32_t mode, uint32_t program_status, uint32_t seq, uint32_t simulation_tick, uint32_t current_line, diff --git a/wasm-core/tests/sim_task_test.c b/wasm-core/tests/sim_task_test.c index a39d760..ae23a9f 100644 --- a/wasm-core/tests/sim_task_test.c +++ b/wasm-core/tests/sim_task_test.c @@ -17,9 +17,20 @@ int main(void) { assert(sim_task_attach(&task) == SIM_TASK_OK); assert(sim_task_set_command(&task, 3, 0) == SIM_TASK_ERR_SEQUENCE); assert(sim_task_set_command(&task, 2, 0) == SIM_TASK_OK); + assert(sim_task_command_gate(&task, SIM_TASK_GATE_PROGRAM_LOAD, 0, 0, 0, 0, 0, 10) == SIM_TASK_GATE_ALLOWED); + assert(sim_task_command_gate(&task, SIM_TASK_GATE_RUN, 0, 0, 0, 0, 0, 10) == SIM_TASK_GATE_MACHINE_OFF); + assert(sim_task_command_gate(&task, SIM_TASK_GATE_POWER, 1, 0, 0, 0, 0, 10) == SIM_TASK_GATE_ALLOWED); + assert(sim_task_command_gate(&task, SIM_TASK_GATE_POWER, 0, 0, 0, 0, 0, 10) == SIM_TASK_GATE_MACHINE_OFF); assert(sim_task_set_program(&task, SIM_TASK_PROGRAM_LOADED, 0) == SIM_TASK_OK); + assert(sim_task_command_gate(&task, SIM_TASK_GATE_RUN, 0, 1, 0, 1, 1, 10) == SIM_TASK_GATE_ALLOWED); assert(sim_task_transition(&task, SIM_TASK_RUNNING) == SIM_TASK_OK); assert(sim_task_set_program(&task, SIM_TASK_PROGRAM_RUNNING, 1) == SIM_TASK_OK); + assert(sim_task_command_gate(&task, SIM_TASK_GATE_PAUSE, 0, 1, 0, 1, 0, 10) == SIM_TASK_GATE_PAUSE_STATE); + assert(sim_task_set_mode(&task, SIM_TASK_MODE_AUTO) == SIM_TASK_OK); + assert(sim_task_command_gate(&task, SIM_TASK_GATE_PAUSE, 0, 1, 0, 1, 0, 10) == SIM_TASK_GATE_ALLOWED); + assert(sim_task_command_gate(&task, SIM_TASK_GATE_POWER, 0, 1, 0, 1, 0, 10) == SIM_TASK_GATE_ALLOWED); + assert(sim_task_command_gate(&task, SIM_TASK_GATE_SET_MODE, SIM_TASK_MODE_MANUAL, 1, 0, 1, 0, 10) == SIM_TASK_GATE_MODE_AUTO_ACTIVE); + assert(sim_task_command_gate(&task, SIM_TASK_GATE_ESTOP, 1, 1, 0, 1, 0, 10) == SIM_TASK_GATE_ALLOWED); assert(sim_task_set_command(&task, 3, 4) == SIM_TASK_OK); assert(sim_task_transition(&task, SIM_TASK_PAUSED) == SIM_TASK_OK); assert(sim_task_set_command(&task, 4, 3) == SIM_TASK_ERR_TICK); diff --git a/web/react/src/App.tsx b/web/react/src/App.tsx index c8d6f3f..87f3b34 100644 --- a/web/react/src/App.tsx +++ b/web/react/src/App.tsx @@ -45,7 +45,7 @@ function MachineSequence({ isEstop, machineOn, allHomed, programLoaded, programR
Emergency Stop Release E-stop - {machineOn ? "Power Off" : "Power On"} + {machineOn ? "Power Off" : "Power On"} HOME ALL Run Program Run Startup Sequence @@ -172,7 +172,7 @@ export function App() { const emergencyStop = () => { stopRunTimer(); stopJogTimer(); void dispatch("ESTOP", { active: true }, false).catch(() => undefined); }; const releaseEstop = () => dispatch("ESTOP", { active: false }, false); const toggleEstop = () => { if (!isEstop) { emergencyStop(); return; } void releaseEstop().catch(() => undefined); }; - const powerOn = () => { if (isEstop || programRunning || !gates.power.enabled) return; void dispatch("POWER", { active: !machineOn }, false).catch(() => undefined); }; + const powerOn = () => { if (!gates.power.enabled) return; void dispatch("POWER", { active: isEstop ? false : !machineOn }, false).catch(() => undefined); }; const resetMachine = () => { stopRunTimer(); stopJogTimer(); if (isEstop || programRunning || !machineOn) return; void dispatch("RESET").catch(() => undefined); }; const homeAll = () => { void dispatch("HOME", { all: true }).catch(() => undefined); }; const runStartupSequence = () => { @@ -225,8 +225,8 @@ export function App() { const position = `X${Number(xyzacView.pose?.x ?? axes[0] ?? 0).toFixed(3)} Y${Number(xyzacView.pose?.y ?? axes[1] ?? 0).toFixed(3)} Z${Number(xyzacView.pose?.z ?? axes[2] ?? 0).toFixed(3)} A${Number(xyzacView.pose?.a ?? 0).toFixed(3)} C${Number(xyzacView.pose?.c ?? 0).toFixed(3)}`; return
AXIS AXISLinuxCNC Web/WASM
{state.status.toUpperCase()}{taskState}
- -
{ setProgramText(DEFAULT_PROGRAM); setPlotCleared(false); }} /> execute("PROGRAM_STEP")} /> execute(programPaused ? "PROGRAM_RESUME" : "PROGRAM_PAUSE")} /> setBlockDelete((value) => !value)} /> setOptionalStop((value) => !value)} /> setZoom((value) => Math.min(2, value + .2))} /> setZoom((value) => Math.max(.5, value - .2))} /> { setView("z"); setRightTab("preview"); }} /> { setView("z2"); setRightTab("preview"); }} /> { setView("x"); setRightTab("preview"); }} /> { setView("y"); setRightTab("preview"); }} /> { setView("y2"); setRightTab("preview"); }} /> { setView("p"); setRightTab("preview"); }} /> setRotateMode((value) => !value)} /> xyzacCommand("VISUAL_CLEAR", {}, "vismachClear")} />
+ +
{ setProgramText(DEFAULT_PROGRAM); setPlotCleared(false); }} /> execute("PROGRAM_STEP")} /> execute(programPaused ? "PROGRAM_RESUME" : "PROGRAM_PAUSE")} /> setBlockDelete((value) => !value)} /> setOptionalStop((value) => !value)} /> setZoom((value) => Math.min(2, value + .2))} /> setZoom((value) => Math.max(.5, value - .2))} /> { setView("z"); setRightTab("preview"); }} /> { setView("z2"); setRightTab("preview"); }} /> { setView("x"); setRightTab("preview"); }} /> { setView("y"); setRightTab("preview"); }} /> { setView("y2"); setRightTab("preview"); }} /> { setView("p"); setRightTab("preview"); }} /> setRotateMode((value) => !value)} /> xyzacCommand("VISUAL_CLEAR", {}, "vismachClear")} />
Load case{caseStatus}{commandStatus}
{ void releaseEstop().catch(() => undefined); }} onPower={powerOn} onHomeAll={homeAll} onRun={runProgram} onSequence={runStartupSequence} /> diff --git a/web/worker/sim-session.mjs b/web/worker/sim-session.mjs index 47a06af..23a60e5 100644 --- a/web/worker/sim-session.mjs +++ b/web/worker/sim-session.mjs @@ -56,6 +56,21 @@ const PANEL_GATE_ACTIONS = Object.freeze({ POWER: "power", }); +const WASM_GATE_COMMANDS = Object.freeze({ + ESTOP: 1, RESET: 2, POWER: 3, HOME: 4, JOG: 5, MDI: 6, RUN: 7, + PAUSE: 8, RESUME: 9, ABORT: 10, RESTART: 11, KINEMATICS: 12, + SET_MODE: 13, PROGRAM_LOAD: 14, STEP: 15, +}); + +const WASM_GATE_REASONS = Object.freeze({ + 1: "xyzac_estop_active", 2: "xyzac_machine_off", 3: "xyzac_power_already_on", + 4: "xyzac_interp_running", 5: "axis_taskfile_missing", 6: "linuxcnc_wasm_estop_inactive", + 7: "xyzac_pause_state_gate", 8: "xyzac_resume_state_gate", 9: "xyzac_abort_state_gate", + 10: "xyzac_restart_state_gate", 11: "xyzac_kinematics_type_invalid", 12: "linuxcnc_wasm_mode_unavailable", + 13: "linuxcnc_wasm_auto_interp_active", 14: "xyzac_jog_state_gate", 15: "xyzac_mdi_queue_full", + 16: "xyzac_reset_state_gate", 17: "linuxcnc_wasm_gate_unknown_command", 18: "xyzac_not_all_homed", +}); + function text(value) { return typeof value === "string" && value.length > 0 ? value : null; } @@ -216,6 +231,21 @@ export class SimSession { return { status: "verified", count: rows.length }; } + resetXyzacProgramToLoaded() { + if (!this.xyzacProgram?.source) return { status: "verified" }; + const source = this.xyzacProgram.source; + const path = this.xyzacProgram.path; + const loaded = this.xyzacProgram.load(source, path); + if (loaded.status !== "verified") return loaded; + this.xyzacProgram.setRapidVelocity(this.xyzacRapidVelocity); + const subroutines = this.loadXyzacSubroutines(path); + if (subroutines.status !== "verified") return subroutines; + const preview = this.xyzacProgram.preview(); + if (preview.status !== "verified") return preview; + this.previewState = preview; + return { status: "verified" }; + } + panelSnapshot({ task = this.task.export(), program = this.xyzacProgram ? this.xyzacProgram.snapshot() : this.program.loaded ? this.program.snapshot() : null, input = {}, tick = this.tick, motionTrace = null, includeToolpath = input.kind !== "SIM_STEP" || !this.xyzacProgram, includeConfiguration = input.kind !== "SIM_STEP" || !this.xyzacProgram, includeStableRuntime = input.kind !== "SIM_STEP" || !this.xyzacProgram } = {}) { const motion = this.xyzacProgram ? this.wasmMotionProjection() : this.motion.export(); const wasmMotion = this.wasmMotion.read(); @@ -247,7 +277,7 @@ export class SimSession { home: this.xyzacCommandGate({ kind: "HOME", payload: {} }), estop: this.xyzacCommandGate({ kind: "ESTOP", payload: { active: true } }), recover: this.xyzacCommandGate({ kind: "IO_RECOVER", payload: {} }), - power: this.xyzacCommandGate({ kind: "POWER", payload: { active: !Boolean(ioState.ready) } }), + power: this.xyzacCommandGate({ kind: "POWER", payload: { active: ioState.estop ? false : !Boolean(ioState.ready) } }), powerOn: this.xyzacCommandGate({ kind: "POWER", payload: { active: true } }), powerOff: this.xyzacCommandGate({ kind: "POWER", payload: { active: false } }), reset: this.xyzacCommandGate({ kind: "RESET", payload: {} }), @@ -360,64 +390,47 @@ export class SimSession { const program = this.xyzacProgram.snapshot(); const io = this.xyzacIo?.canonical?.() ?? {}; const wasmMotion = this.wasmMotion.read(); - const active = task.state === "RUNNING" || task.state === "PAUSED" || program.status === 2 || program.status === 3; + const homeLimit = this.wasmHomeLimit.read(); const estop = Boolean(io.estop); const ready = Boolean(io.ready) && !estop; const loaded = program.status !== 0; const action = PANEL_GATE_ACTIONS[input.kind] ?? null; - let enabled = true; - let reason = "linuxcnc_wasm_authority"; - if (input.kind === "KINEMATICS_SELECT" && ![0, 1, 2].includes(Number(input.payload?.type))) { - enabled = false; - reason = "xyzac_kinematics_type_invalid"; - } else if (input.kind === "ESTOP") { - enabled = input.payload?.active === true || estop; - reason = enabled ? "linuxcnc_wasm_estop_transition" : "linuxcnc_wasm_estop_inactive"; - } else if (input.kind === "POWER") { - const requested = input.payload?.active === true; - enabled = requested ? !estop && !ready && !active : !estop && ready && !active; - reason = estop ? "xyzac_estop_active" : active ? "xyzac_interp_running" : enabled ? "linuxcnc_wasm_power_transition" : requested ? "xyzac_power_already_on" : "xyzac_power_already_off"; - } else if (input.kind === "IO_RECOVER") { - enabled = !estop && ready && !active; - reason = enabled ? "linuxcnc_wasm_io_recover" : estop ? "xyzac_estop_active" : !ready ? "xyzac_power_required" : "xyzac_interp_running"; - } else if (input.kind === "LOAD_WORKSPACE") { - enabled = !estop && !active; - reason = enabled ? "linuxcnc_wasm_workspace_available" : estop ? "xyzac_estop_active" : "xyzac_interp_running"; - } else if (["PROGRAM_LOAD", "PROGRAM_RESTART"].includes(input.kind)) { - enabled = !estop && !active && (input.kind !== "PROGRAM_RESTART" || loaded); - reason = estop ? "xyzac_estop_active" : active ? "xyzac_interp_running" : enabled ? "linuxcnc_wasm_program_idle" : "xyzac_taskfile_missing"; - } else if (input.kind === "PROGRAM_RUN") { - enabled = ready && !active && loaded; - reason = !ready ? (estop ? "xyzac_estop_active" : "xyzac_machine_off") : active ? "xyzac_interp_running" : loaded ? "linuxcnc_wasm_run_available" : "axis_taskfile_missing"; - } else if (input.kind === "PROGRAM_PAUSE") { - enabled = ready && program.status === 2; - reason = enabled ? "linuxcnc_wasm_pause_available" : "xyzac_pause_state_gate"; - } else if (input.kind === "PROGRAM_RESUME") { - enabled = ready && program.status === 3; - reason = enabled ? "linuxcnc_wasm_resume_available" : "xyzac_resume_state_gate"; - } else if (input.kind === "PROGRAM_ABORT") { - enabled = active; - reason = enabled ? "linuxcnc_wasm_abort_available" : "xyzac_abort_state_gate"; - } else if (input.kind === "PROGRAM_STEP") { - enabled = ready && loaded && !active; - reason = !ready ? "xyzac_machine_off" : loaded ? "linuxcnc_wasm_step_available" : "axis_taskfile_missing"; - } else if (input.kind === "SET_MODE") { - enabled = ready && !active; - reason = enabled ? "linuxcnc_wasm_mode_available" : estop ? "xyzac_estop_active" : active ? "xyzac_interp_running" : "xyzac_machine_off"; - } else if (input.kind === "RESET") { - enabled = ready && !active; - reason = enabled ? "linuxcnc_wasm_reset_available" : estop ? "xyzac_estop_active" : !ready ? "xyzac_machine_off" : "xyzac_interp_running"; - } else if (input.kind === "HOME" || ["JOG_START", "JOG_STOP", "SET_OVERRIDE"].includes(input.kind)) { - enabled = ready && !active; - reason = enabled ? "linuxcnc_wasm_manual_available" : estop ? "xyzac_estop_active" : active ? "xyzac_interp_running" : "xyzac_machine_off"; - } else if (input.kind === "MDI_EXECUTE") { - enabled = ready && !active; - reason = enabled ? "linuxcnc_wasm_mdi_available" : estop ? "xyzac_estop_active" : active ? "mdi_while_program_active" : "xyzac_machine_off"; - } else if (["KINEMATICS_SELECT", "SPINDLE_COMMAND", "SPINDLE_FAULT", "SPINDLE_INHIBIT", "SPINDLE_ORIENT", "SPINDLE_ORIENTED", "TOOL_PREPARE", "TOOL_PREPARED", "TOOL_CHANGE", "TOOL_CHANGED", "TOOL_ABORT", "TOOL_FAULT", "TOOL_CLEAR_FAULT", "PROBE_CONFIG"].includes(input.kind)) { - enabled = ready && !active; - reason = enabled ? "linuxcnc_wasm_io_available" : estop ? "xyzac_estop_active" : active ? "xyzac_interp_running" : "xyzac_machine_off"; - } - return { action, enabled, reason, taskState: task.state, interpState: active ? (program.status === 3 ? "PAUSED" : "READING") : "IDLE", wasm: { io_ready: ready, io_estop: estop, program_status: program.program_status, queue_depth: wasmMotion.state.queue_depth } }; + const command = input.kind === "JOG_STOP" || input.kind === "JOG_START" || input.kind === "SET_OVERRIDE" ? WASM_GATE_COMMANDS.JOG + : input.kind === "PROGRAM_LOAD" ? WASM_GATE_COMMANDS.PROGRAM_LOAD + : input.kind === "PROGRAM_RESTART" ? WASM_GATE_COMMANDS.RESTART + : input.kind === "PROGRAM_RUN" ? WASM_GATE_COMMANDS.RUN + : input.kind === "PROGRAM_PAUSE" ? WASM_GATE_COMMANDS.PAUSE + : input.kind === "PROGRAM_RESUME" ? WASM_GATE_COMMANDS.RESUME + : input.kind === "PROGRAM_ABORT" ? WASM_GATE_COMMANDS.ABORT + : input.kind === "PROGRAM_STEP" ? WASM_GATE_COMMANDS.STEP + : input.kind === "MDI_EXECUTE" ? WASM_GATE_COMMANDS.MDI + : input.kind === "KINEMATICS_SELECT" ? WASM_GATE_COMMANDS.KINEMATICS + : input.kind === "SET_MODE" ? WASM_GATE_COMMANDS.SET_MODE + : input.kind === "RESET" ? WASM_GATE_COMMANDS.RESET + : input.kind === "HOME" ? WASM_GATE_COMMANDS.HOME + : input.kind === "ESTOP" ? WASM_GATE_COMMANDS.ESTOP + : input.kind === "POWER" ? WASM_GATE_COMMANDS.POWER + : input.kind === "IO_RECOVER" ? WASM_GATE_COMMANDS.POWER : null; + if (!command) return { action, enabled: true, reason: "linuxcnc_wasm_authority" }; + const requested = input.kind === "POWER" || input.kind === "ESTOP" ? (input.payload?.active === true ? 1 : 0) + : input.kind === "KINEMATICS_SELECT" ? Number(input.payload?.type) + : input.kind === "SET_MODE" ? ({ MANUAL: 0, MDI: 1, AUTO: 2 }[String(input.payload?.mode ?? "").toUpperCase()] ?? 3) : 0; + const result = this.task.commandGate(command, { + requested, + ioReady: ready, + ioEstop: estop, + homed: Boolean(homeLimit.state?.homed), + queueDepth: Number(task.queued_mdi_commands ?? 0), + queueLimit: Number(task.max_queued_mdi_commands ?? 10), + }); + if (result === null) return { action, enabled: false, reason: "linuxcnc_wasm_gate_unavailable", taskState: task.state, interpState: "UNKNOWN" }; + let reason = result === 0 ? "linuxcnc_wasm_authority" : WASM_GATE_REASONS[result] ?? "linuxcnc_wasm_gate_unknown_command"; + if (result === 2 && input.kind === "POWER" && input.payload?.active !== true) reason = "xyzac_power_already_off"; + if (result === 3 && input.kind === "POWER") reason = "xyzac_power_already_on"; + if (result === 4 && input.kind === "MDI_EXECUTE") reason = "mdi_while_program_active"; + if (result === 4 && input.kind === "PROGRAM_STEP") reason = "xyzac_interp_running"; + if (result === 5 && input.kind === "PROGRAM_RESTART") reason = "xyzac_taskfile_missing"; + return { action, enabled: result === 0, reason, taskState: task.state, interpState: task.state === "PAUSED" || program.status === 3 ? "PAUSED" : task.state === "RUNNING" || program.status === 2 ? "READING" : "IDLE", wasm: { io_ready: ready, io_estop: estop, program_status: program.program_status, queue_depth: wasmMotion.state.queue_depth, gate_result: result } }; } stateForCheckpoint() { @@ -971,13 +984,15 @@ export class SimSession { const deviceResponse = (kind, payload) => { const result = this.xyzacIo ? this.xyzacIo.command(kind, payload ?? {}) : this.devices.command(kind, payload ?? {}); if (result.status === "verified" && this.xyzacProgram && kind === "ESTOP" && payload?.active === true) { - // The WASM IO edge owns emergency stop. Stop the WASM trajectory and - // pause the canonical program so no compatibility state can continue - // feeding motion while the machine is stopped. - const programState = this.xyzacProgram.snapshot(); - if (programState.status === 2) this.xyzacProgram.pause(); + // LinuxCNC emcTaskSetState(ESTOP) calls emcTaskAbort: clear the + // interpreter command and trajectory, then leave the taskfile loaded + // for a later AUTO run after ESTOP_RESET and POWER ON. + this.xyzacProgram.abort(); this.wasmMotion.abort(); this.wasmMotion.setPaused(true); + const resetProgram = this.resetXyzacProgramToLoaded(); + if (resetProgram.status !== "verified") return finish(this.error(input, "program_restart", resetProgram.code ?? "program_reload_failed"), "failed"); + this.task.setProgram("loaded", 0); } try { if (kind === "ESTOP" && payload?.active === true) this.task.estop(-904); @@ -1053,10 +1068,20 @@ export class SimSession { if (this.xyzacProgram) { const result = this.xyzacIo.command("POWER", input.payload ?? {}); if (result.status === "verified") { + if (input.payload?.active === false) { + /* LinuxCNC emcTaskSetState(OFF) aborts motion, clears the + interpreter command and leaves the loaded taskfile intact. */ + this.xyzacProgram.abort(); + this.wasmMotion.abort(); + this.wasmMotion.setPaused(true); + const resetProgram = this.resetXyzacProgramToLoaded(); + if (resetProgram.status !== "verified") return finish(this.error(input, "program_restart", resetProgram.code ?? "program_reload_failed"), "failed"); + this.task.setProgram("loaded", 0); + } try { if (input.payload?.active === true) this.task.recover(); - else if (["READY", "STOPPED"].includes(this.task.state.state)) { - if (this.task.state.state === "READY") this.task.transition("STOPPED"); + else if (["READY", "RUNNING", "PAUSED", "STOPPED"].includes(this.task.state.state)) { + if (["READY", "RUNNING", "PAUSED"].includes(this.task.state.state)) this.task.transition("STOPPED"); } } catch { /* the WASM IO state remains authoritative */ } if (this.xyzacDeviceIo) this.xyzacDeviceIo.command("INTERLOCK_RESET", input.payload ?? {}, input.operation_id); @@ -1468,6 +1493,7 @@ export class SimSession { case "PROGRAM_STEP": { if (this.xyzacProgram) { const current = this.xyzacProgram.snapshot(); + if (current.status === 1 && this.task.state.mode !== "AUTO") this.task.setMode("AUTO"); if (current.status === 1) this.xyzacProgram.start(); const result = this.xyzacProgram.step(); if (result.status === "verified") { if (result.motion) this.wasmMotion.enqueue(result.motion); this.wasmMotion.setPaused(true); this.task.setProgram(result.program_status === 4 ? "completed" : "paused", result.line); this.task.transition(result.program_status === 4 ? "STOPPED" : "PAUSED"); } @@ -1523,6 +1549,7 @@ export class SimSession { if (this.task.state.state === "STOPPED") this.task.recover(); const block = input.payload?.block ?? input.payload?.command; if (this.xyzacProgram) { + this.task.setMode("MDI"); const result = this.xyzacProgram.executeBlock(block); if (result.status === "verified") { const controlCode = Number(result.program?.control_code ?? 0); @@ -1555,6 +1582,7 @@ export class SimSession { if (!this.xyzacKinematics) return finish(this.error(input, "kinematics", "xyzac_kinematics_unavailable"), "failed"); const mode = Number(input.payload?.type); if (![XYZAC_MODES.identity, XYZAC_MODES.xyzac, XYZAC_MODES.userk].includes(mode)) return finish(this.error(input, "kinematics", "xyzac_kinematics_type_invalid"), "failed"); + this.task.setMode("MDI"); const result = this.xyzacKinematics.select(mode, this.xyzacKinematics.snapshot().joints); const code = mode === XYZAC_MODES.xyzac ? "M428" : mode === XYZAC_MODES.identity ? "M429" : "M430"; return finish(this.envelope(result.status === "verified" ? "COMMAND_RESULT" : "ERROR", input, { ...result, command: input.kind, remap: { code, hal_pin: "motion.switchkins-type", hal_pin_vars: true, sync: "M66 E0 L0" }, kinematics: this.xyzacKinematics.snapshot(), task: this.task.export(), operation_id: input.operation_id ?? null }), result.status === "verified" ? "verified" : "failed"); diff --git a/web/worker/sim-task.mjs b/web/worker/sim-task.mjs index c9510f6..8159eb0 100644 --- a/web/worker/sim-task.mjs +++ b/web/worker/sim-task.mjs @@ -170,6 +170,12 @@ export class SimTask { this.lastError = 0; } + commandGate(command, { requested = 0, ioReady = false, ioEstop = false, homed = false, queueDepth = 0, queueLimit = 10 } = {}) { + this.ensureCore(); + if (!this.coreTask || (typeof this.core?._sim_task_command_gate !== "function" && typeof this.core?.sim_task_command_gate !== "function")) return null; + return coreCall(this.core, "sim_task_command_gate", "number", ["number", "number", "number", "number", "number", "number", "number", "number"], [this.coreTask, command, requested, ioReady ? 1 : 0, ioEstop ? 1 : 0, homed ? 1 : 0, queueDepth, queueLimit]); + } + export() { this.syncFromCore(); const unsigned = clone(this.state); diff --git a/web/worker/test_xyzac_button_constraints.mjs b/web/worker/test_xyzac_button_constraints.mjs index 3b040da..c6d1a51 100644 --- a/web/worker/test_xyzac_button_constraints.mjs +++ b/web/worker/test_xyzac_button_constraints.mjs @@ -30,20 +30,22 @@ for (const [kind, payload, reason] of [["PROGRAM_RUN", {}, "xyzac_machine_off"], const powered = send("POWER", { active: true }); assert.equal(powered.payload.panel_snapshot.axis_state.machine_state, "ON"); -assert.equal(send("KINEMATICS_SELECT", { type: 1 }).payload.kinematics.active_type, 1); assert.equal(send("HOME", { all: true }).payload.wasm_home_limit.state.homed, true); +assert.equal(send("KINEMATICS_SELECT", { type: 1 }).payload.kinematics.active_type, 1); assert.equal(send("POWER", { active: false }).payload.panel_snapshot.axis_state.machine_state, "OFF"); assert.equal(send("PROGRAM_RUN").payload.gate_reason, "xyzac_machine_off"); assert.equal(send("POWER", { active: true }).payload.panel_snapshot.axis_state.machine_state, "ON"); assert.equal(send("PROGRAM_RUN").payload.task.state, "RUNNING"); -for (const [kind, payload, reason] of [["HOME", { all: true }, "xyzac_interp_running"], ["MDI_EXECUTE", { block: "G0 X1" }, "mdi_while_program_active"], ["KINEMATICS_SELECT", { type: 0 }, "xyzac_interp_running"], ["POWER", { active: false }, "xyzac_interp_running"]]) { +for (const [kind, payload, reason] of [["HOME", { all: true }, "xyzac_interp_running"], ["MDI_EXECUTE", { block: "G0 X1" }, "mdi_while_program_active"], ["KINEMATICS_SELECT", { type: 0 }, "xyzac_interp_running"]]) { const blocked = send(kind, payload); assert.equal(blocked.kind, "ERROR", kind); assert.equal(blocked.payload.gate_reason, reason, kind); } assert.equal(send("PROGRAM_PAUSE").payload.task.state, "PAUSED"); assert.equal(send("PROGRAM_RESUME").payload.task.state, "RUNNING"); +assert.equal(send("POWER", { active: false }).payload.task.state, "STOPPED"); +assert.equal(send("POWER", { active: true }).payload.panel_snapshot.axis_state.machine_state, "ON"); assert.equal(send("PROGRAM_ABORT").payload.task.state, "STOPPED"); assert.equal(send("ESTOP", { active: true }).payload.panel_snapshot.axis_state.machine_state, "ESTOP"); diff --git a/web/worker/test_xyzac_gate_matrix.mjs b/web/worker/test_xyzac_gate_matrix.mjs index 4018e73..3c5d2d1 100644 --- a/web/worker/test_xyzac_gate_matrix.mjs +++ b/web/worker/test_xyzac_gate_matrix.mjs @@ -28,6 +28,7 @@ assert.equal(initial.gates.switchkinsXyzac.enabled, false); assert.equal(initial.gates.switchkinsUserk.enabled, false); assert.equal(initial.gates.vismachClear.visual_only, true); assert.equal(send("POWER", "power-on", { active: true }).payload.panel_snapshot.axis_state.machine_state, "ON"); +assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true); const powered = send("SNAPSHOT_REQUEST", "powered-snapshot").payload.panel_snapshot; assert.equal(powered.gates.switchkinsIdentity.enabled, true); assert.equal(powered.gates.switchkinsXyzac.enabled, true); diff --git a/web/worker/test_xyzac_hal_tool_alarm.mjs b/web/worker/test_xyzac_hal_tool_alarm.mjs index b8ae679..358e846 100644 --- a/web/worker/test_xyzac_hal_tool_alarm.mjs +++ b/web/worker/test_xyzac_hal_tool_alarm.mjs @@ -15,6 +15,7 @@ const send = (kind, operation_id, payload = {}) => session.accept({ ...base, kin send("HELLO", "hello"); send("SESSION_ATTACH", "attach"); assert.equal(send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }).payload.status, "verified"); assert.equal(send("POWER", "power-on", { active: true }).payload.status, "verified"); +assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true); const xml = await readFile(new URL("../../linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.xml", import.meta.url), "utf8"); const postgui = await readFile(new URL("../../linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/switchkins_postgui.hal", import.meta.url), "utf8"); diff --git a/web/worker/test_xyzac_manual_control.mjs b/web/worker/test_xyzac_manual_control.mjs index 1c4f4f6..b751623 100644 --- a/web/worker/test_xyzac_manual_control.mjs +++ b/web/worker/test_xyzac_manual_control.mjs @@ -17,6 +17,7 @@ function setup(id) { send("HELLO", "hello"); send("SESSION_ATTACH", "attach"); assert.equal(send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }).payload.status, "verified"); assert.equal(send("POWER", "power-on", { active: true }).payload.status, "verified"); + assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true); return { session, send }; } diff --git a/web/worker/test_xyzac_protocol_react.mjs b/web/worker/test_xyzac_protocol_react.mjs index 8b1cfcc..ebecd23 100644 --- a/web/worker/test_xyzac_protocol_react.mjs +++ b/web/worker/test_xyzac_protocol_react.mjs @@ -30,6 +30,7 @@ assert.equal(send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace", conf const power = send("POWER", "power-on", { active: true }); assert.equal(power.kind, "COMMAND_RESULT"); assert.equal(power.payload.command, "POWER"); +assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true); const switches = []; for (const type of [0, 1, 2, 1]) { const response = send("KINEMATICS_SELECT", `select-${type}-${seq}`, { type }); diff --git a/web/worker/test_xyzac_recovery.mjs b/web/worker/test_xyzac_recovery.mjs index 9dcadea..3697a74 100644 --- a/web/worker/test_xyzac_recovery.mjs +++ b/web/worker/test_xyzac_recovery.mjs @@ -28,6 +28,7 @@ async function create(id) { const loaded = send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }); assert.equal(loaded.payload.status, "verified"); assert.equal(send("POWER", "power-on", { active: true }).payload.status, "verified"); + assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true); return { core, session, send }; } diff --git a/web/worker/test_xyzac_rtcp.mjs b/web/worker/test_xyzac_rtcp.mjs index 0a03244..ce08f8f 100644 --- a/web/worker/test_xyzac_rtcp.mjs +++ b/web/worker/test_xyzac_rtcp.mjs @@ -96,6 +96,7 @@ send("HELLO", "hello"); send("SESSION_ATTACH", "attach"); assert.equal(send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }).payload.status, "verified"); assert.equal(send("POWER", "power-on", { active: true }).payload.status, "verified"); +assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true); const switchRows = []; for (const [code, expected] of [["M429", 0], ["M428", 1], ["M430", 2], ["M428", 1]]) { diff --git a/web/worker/test_xyzac_trace_parity.mjs b/web/worker/test_xyzac_trace_parity.mjs index a8300fb..d6d4ced 100644 --- a/web/worker/test_xyzac_trace_parity.mjs +++ b/web/worker/test_xyzac_trace_parity.mjs @@ -29,6 +29,7 @@ assert.equal(send("HELLO", "hello").kind, "CAPABILITIES"); assert.equal(send("SESSION_ATTACH", "attach").kind, "SESSION_READY"); assert.equal(send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }).payload.status, "verified"); assert.equal(send("POWER", "power-on", { active: true }).payload.status, "verified"); +assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true); assert.equal(session.xyzacKinematics.forward([12, -5, 20, 30, 45]).status, "verified"); const levels = { protocol: [], control: [], state: [], canonical: [], recovery: [] }; diff --git a/web/worker/test_xyzac_trt_case.mjs b/web/worker/test_xyzac_trt_case.mjs index 919a43b..59d8cf7 100644 --- a/web/worker/test_xyzac_trt_case.mjs +++ b/web/worker/test_xyzac_trt_case.mjs @@ -35,6 +35,7 @@ assert.equal(loaded.payload.motion_authority, "wasm"); assert.equal(loaded.payload.configuration.state, "TASK_READY"); assert.equal(loaded.payload.panel_snapshot.kinematics.joint_count, 5); assert.equal(send("POWER", "power-on", { active: true }).payload.status, "verified"); +assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true); const switchRows = []; for (const [code, expected] of [["M429", 0], ["M428", 1], ["M430", 2]]) { diff --git a/web/worker/test_xyzac_workflow.mjs b/web/worker/test_xyzac_workflow.mjs index 7e0dd8d..11c69c0 100644 --- a/web/worker/test_xyzac_workflow.mjs +++ b/web/worker/test_xyzac_workflow.mjs @@ -15,6 +15,7 @@ const send = (kind, operation_id, payload = {}) => session.accept({ ...base, kin send("HELLO", "hello"); send("SESSION_ATTACH", "attach"); assert.equal(send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }).payload.status, "verified"); assert.equal(send("POWER", "power-on", { active: true }).payload.status, "verified"); +assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true); const source = "G21 G90\nG0 X10 Y5 Z200 A30 C45\nG1 X20 Y10 Z180 A35 C50 F100\nG1 X30 Y15 Z160 A40 C55 F100\nM2\n"; const loaded = send("PROGRAM_LOAD", "program-load", { source, path: "workflow.ngc" }); assert.equal(loaded.kind, "COMMAND_RESULT");