From e941c9bbb45a20971802adc25e0f0a4ea14d46e2 Mon Sep 17 00:00:00 2001 From: CNC Local Date: Fri, 22 May 2026 05:33:58 +0800 Subject: [PATCH] Emit feed mode state events --- README.zh-CN.md | 2 + core/src/canon_event_sink.cpp | 7 ++++ core/src/canon_event_sink.h | 1 + core/src/linuxcnc_canon_bridge.cpp | 17 +++++++- core/src/smoke_gcode_parser.cpp | 9 ++++ .../cnc_sim_api_linuxcnc_rs274_smoke.cpp | 42 +++++++++++++++++++ core/tests/cnc_sim_api_smoke.cpp | 35 ++++++++++++++++ test-linuxcnc-rs274-native.sh | 20 +++++++++ tests/gcode/linuxcnc_feed_modes.ngc | 5 +++ 9 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 tests/gcode/linuxcnc_feed_modes.ngc diff --git a/README.zh-CN.md b/README.zh-CN.md index 322bd9a..ba9d92a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -64,6 +64,7 @@ The first useful target is: - 已有当前刀号事件行号回归语料,LinuxCNC `M61 Q...` 会输出带行号的 `tool-change` 事件。 - 已有停止类事件回归语料,`M0/M1/M60` 会通过 `program-end.reserved` 输出 `1=程序停`、`2=可选停`、`3=托盘交换`。 - 已有冷却液状态回归语料,`M7/M8/M9` 会通过 `comment.reserved` 临时输出 `10/11=mist off/on`、`20/21=flood off/on`。 +- 已有进给模式回归语料,`G93/G94/G95` 会通过 `comment.reserved` 临时输出 `93/94/95`,并通过 `set-feed` 记录 Canon feed mode 回调。 - LinuxCNC Canon bridge 会把当前解释行号补到没有自带 `lineno` 的回调事件上,方便 Web 端诊断和源码高亮。 - 下一步是继续替换源码级链接中残留的 Python、INI/HAL、文件系统和动态加载依赖,并把 RTCP 内核接入 canon 事件流和机型配置。 @@ -88,6 +89,7 @@ The first useful target is: - LinuxCNC 注释事件行号回归 - `M0` / `M1` / `M60` 停止类事件回归 - `M7` / `M8` / `M9` 冷却液状态回归 +- `G93` / `G94` / `G95` 进给模式回归 - `G10 L2` / `G10 L20` / `G92.1` 坐标系事件回归 - `G38.3` 探针事件回归 diff --git a/core/src/canon_event_sink.cpp b/core/src/canon_event_sink.cpp index 65dcdcf..e6727c4 100644 --- a/core/src/canon_event_sink.cpp +++ b/core/src/canon_event_sink.cpp @@ -111,6 +111,13 @@ void CanonEventSink::set_feed_rate(double feed, int line) { emit(event); } +void CanonEventSink::set_feed_mode(int mode, int line) { + CncSimEvent event = base_event(CNC_SIM_EVENT_SET_FEED, line); + event.feed = feed_; + event.reserved = mode; + emit(event); +} + void CanonEventSink::set_spindle_speed(double spindle, int line) { spindle_ = spindle; CncSimEvent event = base_event(CNC_SIM_EVENT_SET_SPINDLE, line); diff --git a/core/src/canon_event_sink.h b/core/src/canon_event_sink.h index 4fe39a7..63183dd 100644 --- a/core/src/canon_event_sink.h +++ b/core/src/canon_event_sink.h @@ -21,6 +21,7 @@ public: void use_length_units(double scale, int line); void select_plane(int plane, int line); void set_feed_rate(double feed, int line); + void set_feed_mode(int mode, int line); void set_spindle_speed(double spindle, int line); void start_spindle(int direction, int line); void stop_spindle(int line); diff --git a/core/src/linuxcnc_canon_bridge.cpp b/core/src/linuxcnc_canon_bridge.cpp index 1c48b67..067c7ec 100644 --- a/core/src/linuxcnc_canon_bridge.cpp +++ b/core/src/linuxcnc_canon_bridge.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -296,6 +297,9 @@ void SET_FEED_REFERENCE(CANON_FEED_REFERENCE) { } void SET_FEED_MODE(int, int) { + if (active_sink) { + active_sink->set_feed_mode(0, event_line()); + } } void SET_MOTION_CONTROL_MODE(CANON_MOTION_MODE, double) { @@ -427,8 +431,17 @@ void RELOAD_TOOLDATA(void) { void CLAMP_AXIS(CANON_AXIS) { } -void COMMENT(const char *) { - emit_state_event(CNC_SIM_EVENT_COMMENT, 0); +void COMMENT(const char *message) { + int reserved = 0; + const std::string_view text = message ? std::string_view(message) : std::string_view(); + if (text.find("feed mode set to inverse time") != std::string_view::npos) { + reserved = 93; + } else if (text.find("feed mode set to units per minute") != std::string_view::npos) { + reserved = 94; + } else if (text.find("feed mode set to units per revolution") != std::string_view::npos) { + reserved = 95; + } + emit_state_event(CNC_SIM_EVENT_COMMENT, reserved); } void DISABLE_ADAPTIVE_FEED() { diff --git a/core/src/smoke_gcode_parser.cpp b/core/src/smoke_gcode_parser.cpp index a461216..345e1cd 100644 --- a/core/src/smoke_gcode_parser.cpp +++ b/core/src/smoke_gcode_parser.cpp @@ -314,6 +314,15 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string absolute_ = true; } else if (g == 91) { absolute_ = false; + } else if (g == 93 || g == 94 || g == 95) { + emit_comment_state(sink_, line_number, g); + if (stop_if_callback_aborted(sink_, error)) { + return -1; + } + sink_.set_feed_mode(g == 95 ? 1 : 0, line_number); + if (stop_if_callback_aborted(sink_, error)) { + return -1; + } } else if (g == 0 || g == 1 || g == 2 || g == 3) { modal_motion = g; } else if (g == 4) { diff --git a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp index 42b2143..0f0bf2c 100644 --- a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp +++ b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp @@ -86,6 +86,11 @@ int main() { bool saw_flood_on = false; bool saw_mist_off = false; bool saw_flood_off = false; + bool saw_g93_mode = false; + bool saw_g94_mode = false; + bool saw_g95_mode = false; + bool saw_g94_feed_mode = false; + bool saw_g95_feed_mode = false; for (const auto &event : events) { saw_tool = saw_tool || event.type == CNC_SIM_EVENT_TOOL_CHANGE; saw_rapid = saw_rapid || event.type == CNC_SIM_EVENT_RAPID; @@ -243,6 +248,43 @@ int main() { ok &= expect(saw_mist_off, "expected LinuxCNC M9 mist off state"); ok &= expect(saw_flood_off, "expected LinuxCNC M9 flood off state"); + const char feed_mode_program[] = + "G21 G90 G17\n" + "G93\n" + "G94\n" + "G95\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, feed_mode_program, sizeof(feed_mode_program) - 1) == 0, + cnc_sim_last_error(sim)); + for (const auto &event : events) { + saw_g93_mode = saw_g93_mode || + (event.type == CNC_SIM_EVENT_COMMENT && + event.line == 2 && + event.reserved == 93); + saw_g94_mode = saw_g94_mode || + (event.type == CNC_SIM_EVENT_COMMENT && + event.line == 3 && + event.reserved == 94); + saw_g95_mode = saw_g95_mode || + (event.type == CNC_SIM_EVENT_COMMENT && + event.line == 4 && + event.reserved == 95); + saw_g94_feed_mode = saw_g94_feed_mode || + (event.type == CNC_SIM_EVENT_SET_FEED && + event.line == 3 && + event.reserved == 0); + saw_g95_feed_mode = saw_g95_feed_mode || + (event.type == CNC_SIM_EVENT_SET_FEED && + event.line == 4 && + event.reserved == 0); + } + ok &= expect(saw_g93_mode, "expected LinuxCNC G93 feed mode state"); + ok &= expect(saw_g94_mode, "expected LinuxCNC G94 feed mode state"); + ok &= expect(saw_g95_mode, "expected LinuxCNC G95 feed mode state"); + ok &= expect(saw_g94_feed_mode, "expected LinuxCNC G94 set-feed mode event"); + ok &= expect(saw_g95_feed_mode, "expected LinuxCNC G95 set-feed mode event"); + const char comment_program[] = "G21 G90 G17\n" "(operator note)\n" diff --git a/core/tests/cnc_sim_api_smoke.cpp b/core/tests/cnc_sim_api_smoke.cpp index ba0a800..73bdc09 100644 --- a/core/tests/cnc_sim_api_smoke.cpp +++ b/core/tests/cnc_sim_api_smoke.cpp @@ -77,6 +77,10 @@ int main() { bool saw_flood_on = false; bool saw_mist_off = false; bool saw_flood_off = false; + bool saw_g93_mode = false; + bool saw_g94_mode = false; + bool saw_g95_mode = false; + bool saw_g95_feed_mode = false; bool saw_incremental_move = false; bool saw_r_arc_center = false; for (const auto &event : events) { @@ -226,6 +230,37 @@ int main() { ok &= expect(saw_mist_off, "expected M9 mist off state"); ok &= expect(saw_flood_off, "expected M9 flood off state"); + const char feed_mode_program[] = + "G21\n" + "G93\n" + "G94\n" + "G95\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, feed_mode_program, sizeof(feed_mode_program) - 1) == 0, + cnc_sim_last_error(sim)); + for (const auto &event : events) { + saw_g93_mode = saw_g93_mode || + (event.type == CNC_SIM_EVENT_COMMENT && + event.line == 2 && + event.reserved == 93); + saw_g94_mode = saw_g94_mode || + (event.type == CNC_SIM_EVENT_COMMENT && + event.line == 3 && + event.reserved == 94); + saw_g95_mode = saw_g95_mode || + (event.type == CNC_SIM_EVENT_COMMENT && + event.line == 4 && + event.reserved == 95); + saw_g95_feed_mode = saw_g95_feed_mode || + (event.type == CNC_SIM_EVENT_SET_FEED && + event.line == 4 && + event.reserved == 1); + } + ok &= expect(saw_g93_mode, "expected G93 feed mode state"); + ok &= expect(saw_g94_mode, "expected G94 feed mode state"); + ok &= expect(saw_g95_mode, "expected G95 feed mode state"); + ok &= expect(saw_g95_feed_mode, "expected G95 feed-per-rev set-feed mode"); + const char linuxcnc_config[] = "{\"backend\":\"linuxcnc-rs274\"}"; config_rc = cnc_sim_load_config_json(sim, linuxcnc_config, sizeof(linuxcnc_config) - 1); rc = cnc_sim_parse_program(sim, program, sizeof(program) - 1); diff --git a/test-linuxcnc-rs274-native.sh b/test-linuxcnc-rs274-native.sh index 87eb10e..6f2bf4b 100755 --- a/test-linuxcnc-rs274-native.sh +++ b/test-linuxcnc-rs274-native.sh @@ -41,6 +41,7 @@ CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linux CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_tool_number.ngc >/tmp/cnc_sim_linuxcnc_tool_number.json CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_program_stops.ngc >/tmp/cnc_sim_linuxcnc_program_stops.json CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coolant.ngc >/tmp/cnc_sim_linuxcnc_coolant.json +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_feed_modes.ngc >/tmp/cnc_sim_linuxcnc_feed_modes.json CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_canned_cycle.ngc >/tmp/cnc_sim_linuxcnc_canned_cycle.json CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_comments.ngc >/tmp/cnc_sim_linuxcnc_comments.json CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_probe_no_error.ngc >/tmp/cnc_sim_linuxcnc_probe_no_error.json @@ -117,6 +118,24 @@ for expected in [(2, 11), (3, 21), (4, 10), (4, 20)]: if expected not in coolant_states: raise SystemExit(f"missing coolant state {expected!r}") +feed_modes = json.loads(Path("/tmp/cnc_sim_linuxcnc_feed_modes.json").read_text()) +feed_mode_comments = [ + (event["line"], event["reserved"]) + for event in feed_modes + if event["type"] == "comment" +] +for expected in [(2, 93), (3, 94), (4, 95)]: + if expected not in feed_mode_comments: + raise SystemExit(f"missing feed mode comment state {expected!r}") +feed_mode_events = [ + (event["line"], event["reserved"]) + for event in feed_modes + if event["type"] == "set-feed" +] +for expected in [(3, 0), (4, 0)]: + if expected not in feed_mode_events: + raise SystemExit(f"missing feed mode event {expected!r}") + cycle = json.loads(Path("/tmp/cnc_sim_linuxcnc_canned_cycle.json").read_text()) motions = [event for event in cycle if event["type"] in {"rapid", "linear-feed"}] expected_cycle = [ @@ -196,6 +215,7 @@ echo "dumped /tmp/cnc_sim_linuxcnc_spindle_direction.json" echo "dumped /tmp/cnc_sim_linuxcnc_tool_number.json" echo "dumped /tmp/cnc_sim_linuxcnc_program_stops.json" echo "dumped /tmp/cnc_sim_linuxcnc_coolant.json" +echo "dumped /tmp/cnc_sim_linuxcnc_feed_modes.json" echo "dumped /tmp/cnc_sim_linuxcnc_canned_cycle.json" echo "dumped /tmp/cnc_sim_linuxcnc_comments.json" echo "dumped /tmp/cnc_sim_linuxcnc_probe_no_error.json" diff --git a/tests/gcode/linuxcnc_feed_modes.ngc b/tests/gcode/linuxcnc_feed_modes.ngc new file mode 100644 index 0000000..48f22d6 --- /dev/null +++ b/tests/gcode/linuxcnc_feed_modes.ngc @@ -0,0 +1,5 @@ +G21 G90 G17 +G93 +G94 +G95 +M30