# 3axis Tutorial 状态机契约 本契约以 LinuxCNC `axis.py`、`linuxcnc` Python API 和 `emctaskmain/taskclass` 的源语义为 oracle。`sim_task.v1` 保持原有 schema;`axis_state` 是 Worker 响应中的附加 AXIS 视图。 ## 源常量 | LinuxCNC 源常量 | 数值 | AXIS 视图 | |---|---:|---| | `STATE_ESTOP` | 1 | `machine_state=ESTOP` | | `STATE_ESTOP_RESET` | 2 | `machine_state=ESTOP_RESET` | | `STATE_OFF` | 3 | `machine_state=OFF` | | `STATE_ON` | 4 | `machine_state=ON` | | `MODE_MANUAL` | 1 | `source_mode=MANUAL` | | `MODE_AUTO` | 2 | `source_mode=AUTO` | | `MODE_MDI` | 3 | `source_mode=MDI` | | `INTERP_IDLE` | 1 | `operation_state=IDLE` | | `INTERP_READING` | 2 | `operation_state=RUNNING` | | `INTERP_PAUSED` | 3 | `operation_state=PAUSED` | | `INTERP_WAITING` | 4 | `operation_state=RUNNING`,`source_interp_state=INTERP_WAITING` | ## 八个 AXIS 核心状态 ```text ESTOP, ESTOP_RESET, OFF, ON, HOME, IDLE, RUNNING, PAUSED ``` `ESTOP/ESTOP_RESET/OFF/ON` 是 machine/task 层;`HOME` 是 home command 的 transient operation;`IDLE/RUNNING/PAUSED` 是 interpreter/task activity 层。响应字段: ```json { "axis_state": { "state": "RUNNING", "machine_state": "ON", "operation_state": "RUNNING", "source_task_state": "STATE_ON", "source_interp_state": "INTERP_READING", "source_mode": "AUTO" } } ``` ## 源对齐迁移 | 当前 | 允许迁移 | 触发源行为 | |---|---|---| | `ESTOP` | `ESTOP_RESET`、`ESTOP` | AXIS `estop_clicked`: `command.state(STATE_ESTOP_RESET)` 或保持急停 | | `ESTOP_RESET` | `ON`、`OFF`、`ESTOP` | AXIS `onoff_clicked`: `STATE_ON`;再次急停回 `ESTOP` | | `OFF` | `ESTOP_RESET`、`ESTOP`、`OFF` | 上电必须先经过源定义的 reset 流程 | | `ON` | `HOME`、`IDLE`、`RUNNING`、`PAUSED`、`OFF`、`ESTOP` | `manual_ok`、home、auto/mdi、off/estop | | `HOME` | `ON`、`IDLE`、`ESTOP` | `axis.py::go_home`;本案例按 `Z -> X -> Y` individual home | | `IDLE` | `HOME`、`RUNNING`、`PAUSED`、`OFF`、`ESTOP` | `manual_ok`、program run/pause、power/estop | | `RUNNING` | `PAUSED`、`IDLE`、`ESTOP` | `INTERP_READING/WAITING`、pause/abort/complete/estop | | `PAUSED` | `RUNNING`、`IDLE`、`ESTOP` | `INTERP_PAUSED`、resume/abort/estop | `RUNNING -> HOME`、`PAUSED -> HOME`、`ESTOP -> HOME` 等迁移必须拒绝。Home 不是普通程序运行状态,必须先满足源 `manual_ok`:task `STATE_ON` 且不在 `MODE_AUTO/MDI` 非 idle 执行中。 ## 按钮门控 - 急停按钮始终可用;`ESTOP` 时触发 reset,其他机器状态触发 estop。 - 上电按钮只有源状态允许时可用;`ESTOP_RESET` 才发送 `STATE_ON`,否则发送 `STATE_OFF`。 - Open/Load/MDI/Jog/Home 需要 `STATE_ON`,且 interpreter idle 或符合 MDI queue 条件。 - Run 需要 `STATE_ON + MODE_AUTO + INTERP_IDLE + task file`。 - Pause 只允许 `MODE_AUTO` 且 `INTERP_READING/INTERP_WAITING`。 - Resume 只允许 `INTERP_PAUSED`。 - Step 需要 machine on 和 task file,不能在 ESTOP/OFF 执行。 - Home 需要 machine on、manual mode、interpreter idle;本案例 UI 逐轴执行。 - Tool/spindle/coolant/probe 操作必须同时受 ESTOP、fault、interlock、spindle busy 和 tool lifecycle gate 约束。 ## Web AXIS 操作映射 - 页面上的“取消急停”先发送 `ESTOP(active=false)`,等待 `ESTOP_RESET`,再发送 `IO_RECOVER` 进入 `ON/IDLE`;急停置位始终只发送 `ESTOP(active=true)`,其错误响应仍携带完整 `task/motion/axis_state` 快照。 - “上电”使用 `IO_RECOVER`;“复位机床”先停止 AUTO 解释器、清除急停(如有),再执行 `IO_RECOVER`。协议层 `RESET` 仍是生命周期复位,不能作为机床复位按钮,否则会按源协议清零序号。 - Manual 页签切换会先发 `SET_MODE(MANUAL)`。因此 `HOME`、逐轴 `JOG_*`、主轴和冷却只在 `STATE_ON + MODE_MANUAL + INTERP_IDLE` 开放;MDI/AUTO、fault、限位和急停均 fail-closed。 - Home All 遵循本案例未定义统一 `HOME_SEQUENCE` 时的源 AXIS 行为,固定按 `Z -> X -> Y` 逐轴发送并等待每个 `COMMAND_RESULT`。 - G-code 预览可以在加载前显示确定性解析轨迹;执行轨迹只使用 Worker authoritative planner 快照,已完成段和 active 段分层显示,DRO 从 Worker 的 motion/interpreter 快照更新。 ## 验证 - 单元状态契约:`node web/worker/test_axis_state.mjs` - 源序列:`node web/worker/test_axis3_tutorial_sequence.mjs` - native recorder:`python3 scripts/run_axis3_tutorial_native_sequence.py` - native/Worker sequence report:`coverage/axis3-tutorial-sequence.report.json` - 现有 gate contract:`web/app/test_axis_contract.mjs`、`web/app/test_axis_operation_controller.mjs` 禁止修改锁定 ABI、protocol、`sim_task.v1` 字段来承载这组视图;状态契约新增字段只能作为 additive `axis_state`,并必须有 hash/迁移测试。