# 07-emctaskmain 周期对标蓝图 ## 对标目标 本蓝图把 LinuxCNC `emctaskmain.cc` 的 task 主循环拆成 `wasm-port` 可逐步实现的周期阶段。后续实现不得只在 JSON status 层拼接 motion,而必须让 task 周期本身读取并消费 motion snapshot。 ## 上游周期源代码锚点 | 上游位置 | 行为 | WASM 对标要求 | | --- | --- | --- | | `emctask_startup()` motion 初始化 | `emcMotionInit()` 后立即 `emcMotionUpdate(&emcStatus->motion)`,失败则启动失败 | `lctask_init_session()` 必须初始化 motion runtime,并建立第一份 task 持有的 motion snapshot | | 主循环 `task_beat++` | task heartbeat 每周期递增 | `lctask_run_cycles()` 每个 task cycle 增加 task heartbeat/cycle | | `check_ini_hal_items()` | 周期检查 INI/HAL 项 | standalone 可先记录为 shim 阶段,后续接 HAL/INI 检查 | | `emcCommandBuffer->read()` | 读取新 task command,成功后清 `taskPlanError/taskExecuteError` | JSON C ABI command 应先进入 command buffer/slot,再由 task cycle 消费 | | `emcTaskPlan()` | 处理 task state/mode/interpreter/read-ahead/MDI 队列 | 当前 `lctask_send_command_json()` 的状态变化要迁到 plan 阶段 | | `emcTaskExecute()` | 根据 `execState` issue command 或等待 motion/io | 当前直接 forward motion command 的逻辑要迁到 execute 阶段 | | `emcMotionUpdate(&emcStatus->motion)` | 每周期读取 motion 状态 | 每个 `lctask_run_cycles()` 周期必须执行结构化 `lcmot_read_snapshot()` 等价调用 | | volatile home sync | motion enabled -> disabled 时 unhome volatile joints | standalone 先保留字段/事件,后续接 joints/homing | | estop subordinate sync | io estop 时 disable traj、abort task/io/spindle、synch plan | 需要 task 周期内根据 `emcStatus.io` 和 `emcStatus.motion` 统一处理 | | motion/io error sync | soft-limit、motion error、io hard fault 驱动 abort cleanup | 必须新增 motion error 注入测试 | | `emcTaskUpdate(&emcStatus->task)` | 刷新 task status | WASM status snapshot 要从 `emcStatus.task` 等价结构导出 | | top-level status aggregation | 根据 task/motion/io 状态设置 `emcStatus.status` 和 `task.status` | JSON status 中的 task top-level status 必须由聚合结果生成 | | `emcStatusBuffer->write(emcStatus)` | 写出完整状态 | WASM 中写入 last status snapshot,SDK read 只读该 snapshot | ## WASM 周期骨架 目标伪代码: ```cpp int lctask_run_cycles(long task_period_ns, long servo_period_ns, int task_cycles) { for each task cycle: task_heartbeat++; task_cycle_begin(); check_ini_hal_items_shim(); command_read_result = wasm_task_command_buffer_read(); if (command_read_result.new_command) { taskPlanError = false; taskExecuteError = false; } if (wasm_emcTaskPlan() != 0) { taskPlanError = true; } if (wasm_emcTaskExecute() != 0) { taskExecuteError = true; } wasm_emcMotionUpdate(&emcStatus.motion); wasm_sync_subordinate_states(); wasm_emcTaskUpdate(&emcStatus.task); wasm_update_top_level_status(); wasm_status_buffer_write(emcStatus); lcmot_step_servo(servo_period_ns, servo_per_task); } ``` 说明: - 上游 motion 是独立 realtime 子系统,task 读的是 motion 已产生的状态。 - WASM standalone 需要 deterministic servo step。若先 step servo 再 motion update 才能模拟“读取最新子系统状态”,必须在 `06-决策记录.md` 明确差异,并用测试固定语义。 - 无论 servo step 放在周期前还是周期后,`emcTaskExecute()` 的判断必须读取 task 持有的上一份/本份 motion snapshot,不允许临时解析 status JSON。 ## `EMC_STAT` 字段映射优先级 第一优先级字段用于让 task 主循环能按 LinuxCNC 结构运行: | LinuxCNC 字段 | 上游定义 | 当前/目标 WASM 来源 | 用途 | | --- | --- | --- | --- | | `EMC_STAT.task.state` | `EMC_TASK_STAT.state` | 当前 `TaskRuntime.state` -> `emcStatus.task.state` | ESTOP/OFF/ON gate | | `EMC_STAT.task.mode` | `EMC_TASK_STAT.mode` | 当前 `TaskRuntime.mode` -> `emcStatus.task.mode` | MANUAL/MDI/AUTO gate | | `EMC_STAT.task.execState` | `EMC_TASK_STAT.execState` | 当前 `exec_state` -> enum | execute 状态机 | | `EMC_STAT.task.interpState` | `EMC_TASK_STAT.interpState` | 当前 `interp_state` -> enum | read-ahead/pause/run | | `EMC_STAT.task.currentLine` | `EMC_TASK_STAT.currentLine` | motion plan/interp list line | motion id、UI line | | `EMC_STAT.task.readLine` | `EMC_TASK_STAT.readLine` | interpreter read line | read-ahead 对标 | | `EMC_STAT.task.motionLine` | `EMC_TASK_STAT.motionLine` | motion snapshot `programLine/id` | 当前 motion line | | `EMC_STAT.task.task_paused` | `EMC_TASK_STAT.task_paused` | pause/step state | pause/resume/step | | `EMC_STAT.motion.status` | base `RCS_STAT_MSG.status` | `lcmot` snapshot status | wait/error/status aggregation | | `EMC_STAT.motion.traj.enabled` | `EMC_TRAJ_STAT.enabled` | state ON/off + motion runtime | estop/disable edge | | `EMC_STAT.motion.traj.inpos` | `EMC_TRAJ_STAT.inpos` | `lcmot.in_position` | wait-for-motion | | `EMC_STAT.motion.traj.queue` | `EMC_TRAJ_STAT.queue` | `lcmot.queue_count` | queue/status aggregation | | `EMC_STAT.motion.traj.activeQueue` | `EMC_TRAJ_STAT.activeQueue` | active motion depth | queue/status aggregation | | `EMC_STAT.motion.traj.queueFull` | `EMC_TRAJ_STAT.queueFull` | queue capacity check | `WAITING_FOR_MOTION_QUEUE` | | `EMC_STAT.motion.traj.id` | `EMC_TRAJ_STAT.id` | `lcmot.motion_id` | stepping/current line | | `EMC_STAT.motion.traj.paused` | `EMC_TRAJ_STAT.paused` | `lcmot.paused` | pause/step | | `EMC_STAT.motion.traj.single_stepping` | `EMC_TRAJ_STAT.single_stepping` | `lcmot.stepping` | step | | `EMC_STAT.motion.traj.actualPosition` | `EMC_TRAJ_STAT.actualPosition` | `lcmot.axis_fb` mapped pose | interpreter sync/start line | | `EMC_STAT.motion.traj.position` | `EMC_TRAJ_STAT.position` | `lcmot.axis_cmd` mapped pose | UI/canon endpoint | | `EMC_STAT.motion.on_soft_limit` | `EMC_MOTION_STAT.on_soft_limit` | future injection/runtime flag | soft-limit path | | `EMC_STAT.io.status` | `EMC_IO_STAT.status` | initial shim `DONE`/future IO runtime | wait/error aggregation | | `EMC_STAT.io.aux.estop` | `EMC_IO_STAT.aux.estop` | task state/IO shim | estop subordinate sync | 第二优先级字段在 program execution 更深入时补齐: - `activeGCodes`、`activeMCodes`、`activeSettings` - `g5x_offset`、`g92_offset`、`rotation_xy`、`toolOffset` - `synch_di`、`synch_do`、`analog_input`、`analog_output` - spindle orient state/fault - toolchanger fault/reason ## command read 对标 当前 `lctask_send_command_json()` 直接修改 task state 或 forward motion。目标结构: 1. `lctask_send_command_json()` 只把 JSON 解析成 task command envelope,写入 WASM command buffer。 2. `lctask_run_cycles()` 内的 command read 阶段消费一条 command。 3. command read 成功后清 `taskPlanError` 和 `taskExecuteError`。 4. command 的语义处理转入 `wasm_emcTaskPlan()` 或 `wasm_emcTaskIssueCommand()`。 验收: - 调用 `sendCommand()` 后不调用 `runCycles()`,task state 不发生 LinuxCNC 语义变化。 - 调用一次 `runCycles()` 后,command 被计划/执行,状态变化出现。 ## plan 对标 第一阶段 plan 只迁移现有已支持命令: - `EMC_TASK_SET_STATE` - `EMC_TASK_SET_MODE` - `EMC_TASK_PLAN_RUN` - `EMC_TASK_PLAN_PAUSE` - `EMC_TASK_PLAN_RESUME` - `EMC_TASK_PLAN_STEP` - `EMC_TASK_ABORT` - `EMC_TASK_PLAN_EXECUTE` - `EMC_JOINT_HOME` - `EMC_JOG_INCR` 要求: - plan 只决定 task/interp/exec 状态和待执行命令,不直接 step motion。 - AUTO run 必须通过 state/mode/home/program/motion-plan gates。 - MDI/JOG/HOME 只生成后续 execute 要 issue 的 command。 ## execute 对标 第一阶段 execute 对标 `emcTaskExecute()` 的 motion 相关分支: - `DONE`:从 interp/MDI/jog/home pending command 取命令,检查 `motion.traj.queueFull`,issue command 或进入 wait。 - `WAITING_FOR_MOTION_QUEUE`:直到 `motion.traj.queueFull == false` 才继续。 - `WAITING_FOR_MOTION`:`motion.status == ERROR` 进入 `ERROR`,`motion.status == DONE` 进入 `DONE`。 - `WAITING_FOR_MOTION_AND_IO`:motion/io 都 DONE 才完成,任一 ERROR 则 ERROR。 - `ERROR`:执行 abort cleanup,清 pending command/interp list,重置 interp/exec state。 暂缓: - `WAITING_FOR_SYSTEM_CMD`:外部进程 runtime boundary。 - spindle orient 深度行为:先保留字段和测试入口,后续接 spindle snapshot。 - toolchanger fault 深度行为:先由 IO shim 固定 DONE。 ## motion update 对标 必须新增结构化边界,建议名称: ```c typedef struct LcmotStatusSnapshot { ... } LcmotStatusSnapshot; int lcmot_read_status_snapshot(LcmotStatusSnapshot *out); ``` 要求: - `lcmot_read_status_snapshot()` 不推进 servo,不消费队列,只复制当前 motion runtime 状态。 - `wasm_emcMotionUpdate(&emcStatus.motion)` 只把 `LcmotStatusSnapshot` 映射到 `EMC_MOTION_STAT` 等价结构。 - `lctask_read_status_json()` 只导出最后一次 `wasm_status_buffer_write()` 的结果。 验收: - 先写入 motion command,不跑 task cycle,status 不应被 task semantic 更新。 - 跑 task cycle 后,task-held `motion.traj.queue/queueFull/id/inpos/position` 更新。 - 直接调用 low-level `lcmot_step_servo()` 后,除非再跑 task cycle,否则 task status snapshot 仍保持上一周期。 ## subordinate sync 对标 第一阶段需要覆盖: 1. `motion.traj.enabled` 从 true 到 false 时,清 homed 或记录 volatile-home TODO。 2. `io.aux.estop == true` 且 motion enabled 时,执行 traj disable、task abort、io abort、spindle abort shim、plan synch。 3. `motion.status == ERROR && motion.on_soft_limit` 时,进入 soft-limit reporting path。 4. `motion.status == ERROR` 或 hard IO error 时,执行 abort cleanup。 5. 根据 task/motion/io 三方状态聚合 top-level `RCS_STATUS::ERROR/DONE/EXEC`。 ## status write 对标 WASM 需要引入 task status buffer 等价物: - `TaskRuntime.last_status_snapshot` - 或 `StandaloneEmcStatus emcStatus` - `lctask_read_status_json()` 从该 snapshot 序列化 要求: - status read 不改变 task/motion/io 状态。 - status read 不直接调用 `lcmot_read_status_json()` 改变观察口径。 - debug/event log 可以单独存在,但不能替代 status buffer。 status JSON 只是 status buffer 的 host/browser 序列化格式。后续新增 LinuxCNC 对标字段时,字段应先进入 `StandaloneEmcStatus` 或后续 `EMC_STAT` 等价容器,再导出到 `emcStatus` JSON 对象;旧 `task`、`motionStatus` 等顶层状态字段不再作为兼容视图输出。具体字段映射和 SJ 批次见 `12-status-json-LinuxCNC对标方案.md`。 ## 替换上游源码路线 | 阶段 | 引入源 | 目标 | | --- | --- | --- | | R1 | `emc_nml.hh` 必需 enum/status 子集或 vendored include | 用 LinuxCNC 状态类型替换字符串状态 | | R2 | `emctaskmain.cc` 周期骨架等价函数 | 建立 task main loop 结构 | | R3 | `taskintf.cc` motion issuing 可编译子集 | 用上游 task interface 发 motion command | | R4 | `emctask.cc` state/mode/task update 可编译子集 | 减少自有 task state logic | | R5 | `emccanon.cc` canonical motion command 子集 | interpreter/canon 到 motion 命令更接近上游 | 每一阶段必须满足: - source reuse map 更新。 - build 脚本增量编译。 - Node WASM smoke 不回退。 - 新增至少一个对标断言。