diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index 1c02a0c..dcfc395 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -212,6 +212,7 @@ The validation fails if: | `linuxcnc_parameter_file_harness` | Validates LinuxCNC parameter file restore/save behavior and required/read-only parameter handling. | | `linuxcnc_interp_init_harness` | Validates vendored LinuxCNC `Interp::init()` emits canonical initialization boundaries, reads metric/inch machine units, and synchronizes current/selected tool slots through standalone status adapters. | | `linuxcnc_indexer_harness` | Validates vendored LinuxCNC single-axis rotary indexer dispatch emits lock/unlock and motion boundaries through the standalone event sink. | +| `linuxcnc_remap_hal_sync_harness` | Validates vendored LinuxCNC `M68`/`M66` execution can drive the standalone HAL adapter boundary used by 5-axis switchkins remap files, including `_hal[motion.switchkins-type]` readback. | | `linuxcnc_tp_api_probe` | Validates vendored LinuxCNC trajectory planner calls for linear, arc, and queued motion paths. | | `linuxcnc_kinematics_probe` | Validates vendored LinuxCNC `trivkins.c` plus `kins_util.c` initialize and perform identity forward/inverse mapping through the standalone HAL/RTAPI boundary. | | `linuxcnc_5axis_kinematics_probe` | Validates vendored LinuxCNC `5axiskins.c` through `switchkins.c`, including 5-axis forward/inverse round-trip behavior and switching to identity kinematics. | diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_hal_adapter.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_hal_adapter.cpp index 3a8d053..9c409cd 100644 --- a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_hal_adapter.cpp +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_hal_adapter.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,12 @@ std::unordered_map &values_for(standalone::HalValueKind k return pins(); } +std::unordered_map> &pin_aliases() +{ + static std::unordered_map> values; + return values; +} + int lookup_hal_value(const char *name, std::unordered_map &values, hal_type_t *type, hal_data_u **ptr, bool *connected) { @@ -81,6 +88,7 @@ void reset_hal_adapter() pins().clear(); signals().clear(); params().clear(); + pin_aliases().clear(); } void set_hal_value(HalValueKind kind, const char *name, hal_type_t type, hal_data_u value, @@ -89,6 +97,39 @@ void set_hal_value(HalValueKind kind, const char *name, hal_type_t type, hal_dat values_for(kind)[name] = HalEntry{type, value, connected}; } +void link_hal_pin_alias(const char *source, const char *alias) +{ + if (!source || !alias) { + return; + } + pin_aliases()[source].push_back(alias); +} + +void sync_motion_analog_output(int index, double value) +{ + hal_data_u analog{}; + analog.f = value; + + std::ostringstream out_name; + out_name << "motion.analog-out-" << index; + set_hal_value(HalValueKind::Pin, out_name.str().c_str(), HAL_FLOAT, analog); + + std::ostringstream padded_out_name; + padded_out_name << "motion.analog-out-0" << index; + set_hal_value(HalValueKind::Pin, padded_out_name.str().c_str(), HAL_FLOAT, analog); + + const std::string names[] = {out_name.str(), padded_out_name.str()}; + for (const auto &name : names) { + auto aliases = pin_aliases().find(name); + if (aliases == pin_aliases().end()) { + continue; + } + for (const auto &alias : aliases->second) { + set_hal_value(HalValueKind::Pin, alias.c_str(), HAL_FLOAT, analog); + } + } +} + } // namespace standalone int hal_init(const char *) diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_hal_adapter.hh b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_hal_adapter.hh index 74cb197..80460e4 100644 --- a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_hal_adapter.hh +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_hal_adapter.hh @@ -13,5 +13,7 @@ enum class HalValueKind { void reset_hal_adapter(); void set_hal_value(HalValueKind kind, const char *name, hal_type_t type, hal_data_u value, bool connected = true); +void link_hal_pin_alias(const char *source, const char *alias); +void sync_motion_analog_output(int index, double value); } // namespace standalone diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp index 8a7cdfc..af8ed23 100644 --- a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp @@ -12,6 +12,7 @@ #include "emc/rs274ngc/rs274ngc_return.hh" #include "canon_event_sink.hh" +#include "linuxcnc_hal_adapter.hh" #include "linuxcnc_tool_adapter.hh" #include "nml_intf/emc.hh" @@ -381,6 +382,7 @@ void SET_AUX_OUTPUT_VALUE(int index, double value) std::ostringstream oss; oss << "SET_AUX_OUTPUT_VALUE index=" << index << " value=" << value; standalone::push_canon_event(oss.str()); + standalone::sync_motion_analog_output(index, value); } int WAIT(int index, int input_type, int wait_type, double timeout) { diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_remap_hal_sync_harness.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_remap_hal_sync_harness.cpp new file mode 100644 index 0000000..24b0bfb --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_remap_hal_sync_harness.cpp @@ -0,0 +1,117 @@ +#define private public +#include "emc/rs274ngc/rs274ngc_interp.hh" +#undef private + +#include +#include +#include +#include + +#include "canon_event_sink.hh" +#include "linuxcnc_hal_adapter.hh" +#include "linuxcnc_tool_adapter.hh" + +namespace { + +void print_bool(const std::string &name, bool value) +{ + std::cout << name << "=" << (value ? 1 : 0) << "\n"; +} + +void seed_switchkins_hal() +{ + standalone::reset_hal_adapter(); + + hal_data_u switchkins{}; + switchkins.f = 0.0; + standalone::set_hal_value(standalone::HalValueKind::Pin, "motion.switchkins-type", + HAL_FLOAT, switchkins); + standalone::set_hal_value(standalone::HalValueKind::Pin, "motion.analog-out-03", + HAL_FLOAT, switchkins); + standalone::link_hal_pin_alias("motion.analog-out-03", "motion.switchkins-type"); +} + +void initialize_interp(Interp &interp) +{ + seed_switchkins_hal(); + standalone::reset_tool_adapter(); + standalone::reset_canon_events(); + + interp._setup.length_units = CANON_UNITS_MM; + interp._setup.distance_mode = DISTANCE_MODE::ABSOLUTE; + interp._setup.ijk_distance_mode = DISTANCE_MODE::ABSOLUTE; + interp._setup.feed_mode = FEED_MODE::UNITS_PER_MINUTE; + interp._setup.plane = CANON_PLANE::XY; + interp._setup.motion_mode = G_0; + interp._setup.percent_flag = false; + interp._setup.sequence_number = 0; + interp._setup.parameter_occurrence = 0; + interp._setup.num_spindles = 1; + interp._setup.feature_set = FEATURE_INI_VARS | FEATURE_HAL_PIN_VARS; + interp._setup.parameter_g73_peck_clearance = 1.0; + interp._setup.parameter_g83_peck_clearance = 1.0; + interp._setup.parameters[5599] = 1.0; + std::memcpy(interp._readers, Interp::default_readers, sizeof(Interp::default_readers)); + interp.init_named_parameters(); +} + +bool read_hal_named(Interp &interp, const char *name, double expected) +{ + int status = 0; + double value = 0.0; + const int rc = interp.find_named_param(name, &status, &value); + std::cout << name << ": rc=" << rc << " found=" << status << " value=" << value << "\n"; + return rc == INTERP_OK && status == 1 && std::fabs(value - expected) < 0.000001; +} + +bool has_event(const std::string &needle) +{ + for (const auto &event : standalone::canon_events()) { + if (event.find(needle) != std::string::npos) { + return true; + } + } + return false; +} + +} // namespace + +int main() +{ + Interp interp; + initialize_interp(interp); + + bool ok = true; + ok &= read_hal_named(interp, "_hal[motion.switchkins-type]", 0.0); + + const int m68_to_tcp_rc = interp.execute("M68 E3 Q1\n"); + std::cout << "m68_to_tcp=" << m68_to_tcp_rc << "\n"; + ok &= m68_to_tcp_rc == INTERP_OK; + ok &= read_hal_named(interp, "_hal[motion.switchkins-type]", 1.0); + ok &= read_hal_named(interp, "_hal[motion.analog-out-03]", 1.0); + + const int m66_sync_rc = interp.execute("M66 E0 L0\n"); + std::cout << "m66_sync=" << m66_sync_rc << "\n"; + std::cout << "m66_input_flag=" << interp._setup.input_flag << "\n"; + std::cout << "m66_input_index=" << interp._setup.input_index << "\n"; + std::cout << "m66_input_digital=" << interp._setup.input_digital << "\n"; + ok &= m66_sync_rc == INTERP_EXECUTE_FINISH; + ok &= interp._setup.input_flag; + ok &= interp._setup.input_index == 0; + ok &= !interp._setup.input_digital; + + const int m68_to_identity_rc = interp.execute("M68 E3 Q0\n"); + std::cout << "m68_to_identity=" << m68_to_identity_rc << "\n"; + ok &= m68_to_identity_rc == INTERP_OK; + ok &= read_hal_named(interp, "_hal[motion.switchkins-type]", 0.0); + + print_bool("canon_has_m68_tcp", has_event("SET_AUX_OUTPUT_VALUE index=3 value=1")); + print_bool("canon_has_m66_sync", has_event("WAIT index=0 input_type=0 wait_type=0")); + print_bool("canon_has_m68_identity", has_event("SET_AUX_OUTPUT_VALUE index=3 value=0")); + ok &= has_event("SET_AUX_OUTPUT_VALUE index=3 value=1"); + ok &= has_event("WAIT index=0 input_type=0 wait_type=0"); + ok &= has_event("SET_AUX_OUTPUT_VALUE index=3 value=0"); + + print_bool("fiveaxis_remap_hal_sync_boundary", ok); + return ok ? 0 : 1; +} diff --git a/wasm-port/tests/native/verify_native_probes.sh b/wasm-port/tests/native/verify_native_probes.sh index 11647c2..7bcc119 100755 --- a/wasm-port/tests/native/verify_native_probes.sh +++ b/wasm-port/tests/native/verify_native_probes.sh @@ -161,6 +161,8 @@ check_exitcode linuxcnc_interp_init_harness check_exitcode linuxcnc_interp_init_harness.run check_exitcode linuxcnc_remap_parse_harness check_exitcode linuxcnc_remap_parse_harness.run +check_exitcode linuxcnc_remap_hal_sync_harness +check_exitcode linuxcnc_remap_hal_sync_harness.run check_exitcode linuxcnc_indexer_harness check_exitcode linuxcnc_indexer_harness.run check_exitcode linuxcnc_rs274_compile_probe @@ -264,6 +266,21 @@ grep -Fq "xyzbc_trt_M430_ngc=1" "$REMAP_PARSE_STDOUT" grep -Fq "xyzbc_trt_linuxcnc_ngc_remaps_parsed=1" "$REMAP_PARSE_STDOUT" grep -Fq "fiveaxis_linuxcnc_remap_parse_path=1" "$REMAP_PARSE_STDOUT" +REMAP_HAL_SYNC_STDOUT="$BUILD_DIR/linuxcnc_remap_hal_sync_harness.run.stdout.log" +grep -Fq "_hal[motion.switchkins-type]: rc=0 found=1 value=0" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "m68_to_tcp=0" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "_hal[motion.switchkins-type]: rc=0 found=1 value=1" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "_hal[motion.analog-out-03]: rc=0 found=1 value=1" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "m66_sync=2" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "m66_input_flag=1" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "m66_input_index=0" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "m66_input_digital=0" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "m68_to_identity=0" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "canon_has_m68_tcp=1" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "canon_has_m66_sync=1" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "canon_has_m68_identity=1" "$REMAP_HAL_SYNC_STDOUT" +grep -Fq "fiveaxis_remap_hal_sync_boundary=1" "$REMAP_HAL_SYNC_STDOUT" + NAMEDPARAM_STDOUT="$BUILD_DIR/linuxcnc_namedparam_harness.run.stdout.log" grep -Fq "init_named_parameters=0" "$NAMEDPARAM_STDOUT" grep -Fq "global_named_count=57" "$NAMEDPARAM_STDOUT" diff --git a/wasm-port/tools/build_native_probes.sh b/wasm-port/tools/build_native_probes.sh index 50a1da4..e1ff432 100755 --- a/wasm-port/tools/build_native_probes.sh +++ b/wasm-port/tools/build_native_probes.sh @@ -697,6 +697,11 @@ REMAP_PARSE_SOURCES=( "$WRAP_DIR/linuxcnc_remap_parse_harness.cpp" ) +REMAP_HAL_SYNC_SOURCES=( + "${INTERP_CORE_SOURCES[@]}" + "$WRAP_DIR/linuxcnc_remap_hal_sync_harness.cpp" +) + build_binary_target \ linuxcnc_ini_probe \ "$BUILD_DIR/linuxcnc_ini_probe" \ @@ -1282,6 +1287,28 @@ else "$BUILD_DIR/linuxcnc_remap_parse_harness.run.stderr.log" fi +build_binary_target \ + linuxcnc_remap_hal_sync_harness \ + "$BUILD_DIR/linuxcnc_remap_hal_sync_harness" \ + REMAP_RUNTIME_FLAGS \ + REMAP_HAL_SYNC_SOURCES \ + MINIMAL_LINK_FLAGS + +if [[ "$(tr -d '[:space:]' < "$BUILD_DIR/linuxcnc_remap_hal_sync_harness.exitcode")" == "0" ]]; then + set +e + "$BUILD_DIR/linuxcnc_remap_hal_sync_harness" \ + >"$BUILD_DIR/linuxcnc_remap_hal_sync_harness.run.stdout.log" \ + 2>"$BUILD_DIR/linuxcnc_remap_hal_sync_harness.run.stderr.log" + REMAP_HAL_SYNC_RUN_RC=$? + set -e + echo "$REMAP_HAL_SYNC_RUN_RC" > "$BUILD_DIR/linuxcnc_remap_hal_sync_harness.run.exitcode" +else + rm -f \ + "$BUILD_DIR/linuxcnc_remap_hal_sync_harness.run.exitcode" \ + "$BUILD_DIR/linuxcnc_remap_hal_sync_harness.run.stdout.log" \ + "$BUILD_DIR/linuxcnc_remap_hal_sync_harness.run.stderr.log" +fi + build_binary_target \ linuxcnc_indexer_harness \ "$BUILD_DIR/linuxcnc_indexer_harness" \