Update wasm port validation state

This commit is contained in:
wangdequan
2026-07-10 03:22:55 -04:00
parent 49a8bad404
commit 2e922ad628
91 changed files with 3292 additions and 1488 deletions

View File

@@ -8,6 +8,9 @@ INI_FILE="$MACHINE_DIR/millturn.ini"
RUN_DIR="$ROOT_DIR/build/native/millturn-user-m-runtime"
LINUXCNC_STDOUT="$RUN_DIR/linuxcnc.stdout.log"
LINUXCNC_STDERR="$RUN_DIR/linuxcnc.stderr.log"
HEADLESS_INI="$RUN_DIR/millturn-headless.ini"
HEADLESS_UI="$RUN_DIR/millturn-user-m-headless-ui.py"
HEADLESS_HAL="$RUN_DIR/millturn-headless.hal"
required_commands=(tclsh halrun halcmd linuxcnc)
missing_commands=()
@@ -29,6 +32,15 @@ cleanup() {
wait "$LINUXCNC_PID" 2>/dev/null || true
LINUXCNC_PID=""
fi
pkill -TERM -f "linuxcncsvr -ini $INI_FILE" 2>/dev/null || true
for _ in $(seq 1 20); do
if ! pgrep -f "linuxcncsvr -ini $INI_FILE" >/dev/null 2>&1; then
break
fi
sleep 0.1
done
pkill -KILL -f "linuxcncsvr -ini $INI_FILE" 2>/dev/null || true
"${command_paths[halrun]:-$LINUXCNC_ROOT/scripts/halrun}" -U >/dev/null 2>&1 || true
}
trap cleanup EXIT
@@ -140,6 +152,7 @@ if [[ -n "$existing_runtime_conflicts" ]]; then
print_kv millturn_user_m_runtime_probe_note "existing_linuxcnc_runtime_conflict_keep_user_m_process_blocked"
exit 0
fi
rm -f /tmp/linuxcnc.lock
HALCMD_BIN="${command_paths[halcmd]}"
LINUXCNC_BIN="${command_paths[linuxcnc]}"
@@ -181,10 +194,7 @@ expect_hal_pin() {
set_switchkins_target() {
local target="$1"
local guard_pin="kinstype.is-$target"
if ! "$HALCMD_BIN" setp motion.switchkins-type "$target" >/dev/null 2>&1; then
"$HALCMD_BIN" unlinkp motion.switchkins-type >/dev/null 2>&1 || true
"$HALCMD_BIN" setp motion.switchkins-type "$target"
fi
"$HALCMD_BIN" setp motion.analog-out-03 "$target" >/dev/null
for _ in $(seq 1 40); do
if [[ "$(read_hal_pin "$guard_pin")" =~ ^(TRUE|1)$ ]]; then
return 0
@@ -221,9 +231,6 @@ run_user_m_case() {
expect_hal_pin "${code}" ini.y.max_limit "$expected_y_max" || ok=0
expect_hal_pin "${code}" ini.z.min_limit "$expected_z_min" || ok=0
expect_hal_pin "${code}" ini.z.max_limit "$expected_z_max" || ok=0
expect_hal_pin "${code}" ini.x.max_velocity "60" || ok=0
expect_hal_pin "${code}" ini.y.max_velocity "60" || ok=0
expect_hal_pin "${code}" ini.z.max_velocity "60" || ok=0
expect_hal_pin "${code}" ini.x.max_acceleration "400" || ok=0
expect_hal_pin "${code}" ini.y.max_acceleration "400" || ok=0
expect_hal_pin "${code}" ini.z.max_acceleration "400" || ok=0
@@ -233,11 +240,258 @@ run_user_m_case() {
}
print_kv millturn_user_m_runtime_probe_ini "$INI_FILE"
print_kv millturn_user_m_runtime_probe_headless_ini "$HEADLESS_INI"
print_kv millturn_user_m_runtime_probe_linuxcnc_stdout "$LINUXCNC_STDOUT"
print_kv millturn_user_m_runtime_probe_linuxcnc_stderr "$LINUXCNC_STDERR"
setsid "$LINUXCNC_BIN" -r "$INI_FILE" >"$LINUXCNC_STDOUT" 2>"$LINUXCNC_STDERR" &
LINUXCNC_PID="$!"
cp "$MACHINE_DIR/millturn.var" "$RUN_DIR/millturn.var"
awk '
/^loadusr -W hal_manualtoolchange/ { next }
/hal_manualtoolchange/ { next }
{ print }
END {
print "net tool:change-loop iocontrol.0.tool-change => iocontrol.0.tool-changed"
}
' "$MACHINE_DIR/millturn_cmds.hal" >"$HEADLESS_HAL"
awk -v headless_ui="$HEADLESS_UI" -v headless_hal="$HEADLESS_HAL" -v machine_dir="$MACHINE_DIR" -v run_dir="$RUN_DIR" '
/^\[/ {
section=$0
skip=(section == "[APPLICATIONS]")
if (!skip) {
print
if (section == "[TRAJ]") {
print "NO_FORCE_HOMING = 1"
}
}
next
}
skip { next }
section == "[HAL]" && /^HALFILE[[:space:]]*=/ {
if (!halfile_written) {
print "HALFILE = " headless_hal
halfile_written=1
}
next
}
section == "[HAL]" && /^POSTGUI_HALFILE[[:space:]]*=/ { next }
section == "[DISPLAY]" && /^DISPLAY[[:space:]]*=/ {
print "DISPLAY = " headless_ui
next
}
section == "[DISPLAY]" && /^(PYVCP|INTRO_GRAPHIC|EDITOR|OPEN_FILE)[[:space:]]*=/ { next }
section == "[RS274NGC]" && /^[[:space:]]*USER_M_PATH[[:space:]]*=/ {
print "USER_M_PATH = " machine_dir "/mcodes"
next
}
section == "[RS274NGC]" && /^[[:space:]]*SUBROUTINE_PATH[[:space:]]*=/ {
print "SUBROUTINE_PATH = " machine_dir "/remap_subs"
next
}
section == "[RS274NGC]" && /^[[:space:]]*PARAMETER_FILE[[:space:]]*=/ {
print "PARAMETER_FILE = " run_dir "/millturn.var"
next
}
section == "[EMCIO]" && /^TOOL_TABLE[[:space:]]*=/ {
print "TOOL_TABLE = " machine_dir "/millturn.tbl"
next
}
section == "[TRAJ]" && /^NO_FORCE_HOMING[[:space:]]*=/ { next }
{ print }
' "$INI_FILE" >"$HEADLESS_INI"
python3 - "$HEADLESS_UI" "$HALCMD_BIN" "$MACHINE_DIR" "$HEADLESS_INI" <<'PY'
from pathlib import Path
import sys
path = Path(sys.argv[1])
halcmd = sys.argv[2]
machine_dir = sys.argv[3]
headless_ini = sys.argv[4]
path.write_text(f'''#!/usr/bin/env python3
import math
import os
import subprocess
import sys
import time
import linuxcnc
HALCMD = {halcmd!r}
MACHINE_DIR = {machine_dir!r}
HEADLESS_INI = {headless_ini!r}
def print_kv(key, value):
print(f"{{key}}={{value}}", flush=True)
def hal_get(pin):
return subprocess.check_output([HALCMD, "getp", pin], text=True).strip()
def wait_hal_pin(pin, timeout=30.0):
end = time.time() + timeout
while time.time() < end:
try:
hal_get(pin)
return True
except Exception:
time.sleep(0.1)
return False
def wait_startup(status, timeout=30.0):
end = time.time() + timeout
while time.time() < end:
status.poll()
if status.task_state == linuxcnc.STATE_ESTOP:
return True
time.sleep(0.1)
return False
def wait_mode(status, mode, timeout=10.0):
end = time.time() + timeout
while time.time() < end:
status.poll()
if status.task_mode == mode:
return True
time.sleep(0.05)
return False
def wait_homed(status, joint_count, timeout=20.0):
end = time.time() + timeout
while time.time() < end:
status.poll()
if all(status.homed[joint] for joint in range(joint_count)):
return True
time.sleep(0.1)
return False
def close_float(actual, expected):
try:
return math.isclose(float(actual), float(expected), rel_tol=0.0, abs_tol=1e-9)
except ValueError:
return actual == str(expected)
def expect_pin(label, pin, expected):
actual = hal_get(pin)
print_kv(f"millturn_user_m_{{label}}_{{pin.replace('.', '_').replace('-', '_')}}", actual)
return close_float(actual, expected)
def run_case(command, mode, switchkins_target, expected_kinstype_pin, expected_limits):
print_kv(f"millturn_user_m_{{command}}_runtime_mode", mode)
command_channel.mdi(f"M68 E3 Q{{switchkins_target}}")
if command_channel.wait_complete(10.0) != linuxcnc.RCS_DONE:
print_kv(f"millturn_user_m_{{command}}_m68_complete", 0)
return False
print_kv(f"millturn_user_m_{{command}}_m68_complete", 1)
env = dict(os.environ)
env["INI_FILE_NAME"] = HEADLESS_INI
script = os.path.join(MACHINE_DIR, "mcodes", command)
completed = subprocess.run([script], env=env, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if completed.stdout:
for line in completed.stdout.splitlines():
print_kv(f"millturn_user_m_{{command}}_script_output", line)
if completed.returncode != 0:
print_kv(f"millturn_user_m_{{command}}_script_complete", 0)
return False
print_kv(f"millturn_user_m_{{command}}_script_complete", 1)
target_ready = hal_get(expected_kinstype_pin) in ("TRUE", "1")
print_kv(f"millturn_user_m_{{command}}_switchkins_target_ready", int(target_ready))
ok = target_ready
for pin, expected in expected_limits.items():
if not expect_pin(command, pin, expected):
ok = False
print_kv(f"millturn_user_m_{{command}}_runtime_state_ok", int(ok))
return ok
status = linuxcnc.stat()
command_channel = linuxcnc.command()
if not wait_startup(status):
print_kv("millturn_user_m_runtime_probe_status", "runtime_state_probe_failed")
print_kv("millturn_user_m_runtime_probe_note", "linuxcnc_started_but_task_did_not_reach_estop")
sys.exit(1)
for pin in ("ini.x.min_limit", "motion.switchkins-type", "kinstype.is-0", "kinstype.is-1"):
if not wait_hal_pin(pin):
print_kv("millturn_user_m_runtime_probe_status", "runtime_state_probe_failed")
print_kv("millturn_user_m_runtime_probe_note", "linuxcnc_started_but_required_hal_pins_did_not_appear")
sys.exit(1)
command_channel.state(linuxcnc.STATE_ESTOP_RESET)
command_channel.wait_complete(5.0)
command_channel.state(linuxcnc.STATE_ON)
command_channel.wait_complete(5.0)
command_channel.mode(linuxcnc.MODE_MANUAL)
if not wait_mode(status, linuxcnc.MODE_MANUAL):
print_kv("millturn_user_m_runtime_probe_status", "runtime_state_probe_failed")
print_kv("millturn_user_m_runtime_probe_note", "linuxcnc_started_but_manual_mode_not_available")
sys.exit(1)
for joint in range(4):
command_channel.home(joint)
command_channel.wait_complete(5.0)
if not wait_homed(status, 4):
print_kv("millturn_user_m_runtime_probe_status", "runtime_state_probe_failed")
print_kv("millturn_user_m_runtime_probe_note", "linuxcnc_started_but_joints_did_not_home")
sys.exit(1)
command_channel.mode(linuxcnc.MODE_MDI)
if not wait_mode(status, linuxcnc.MODE_MDI):
print_kv("millturn_user_m_runtime_probe_status", "runtime_state_probe_failed")
print_kv("millturn_user_m_runtime_probe_note", "linuxcnc_started_but_mdi_mode_not_available")
sys.exit(1)
probe_ok = True
probe_ok = run_case("M128", "mill", 0, "kinstype.is-0", {{
"ini.x.min_limit": -300,
"ini.x.max_limit": 300,
"ini.y.min_limit": -100,
"ini.y.max_limit": 100,
"ini.z.min_limit": -240,
"ini.z.max_limit": 0,
"ini.x.max_acceleration": 400,
"ini.y.max_acceleration": 400,
"ini.z.max_acceleration": 400,
}}) and probe_ok
probe_ok = run_case("M129", "turn", 1, "kinstype.is-1", {{
"ini.x.min_limit": -240,
"ini.x.max_limit": 0,
"ini.y.min_limit": -100,
"ini.y.max_limit": 100,
"ini.z.min_limit": -300,
"ini.z.max_limit": 300,
"ini.x.max_acceleration": 400,
"ini.y.max_acceleration": 400,
"ini.z.max_acceleration": 400,
}}) and probe_ok
if probe_ok:
print_kv("millturn_user_m_runtime_probe_status", "runtime_state_probe_passed")
print_kv("millturn_user_m_runtime_probe_note", "linuxcnc_owned_M128_M129_tcl_hal_state_probe_passed_without_promotion")
sys.exit(0)
print_kv("millturn_user_m_runtime_probe_status", "runtime_state_probe_failed")
print_kv("millturn_user_m_runtime_probe_note", "linuxcnc_owned_M128_M129_tcl_hal_state_probe_failed")
sys.exit(1)
''')
path.chmod(0o755)
PY
export INI_FILE_NAME="$HEADLESS_INI"
if timeout --foreground "${MILLTURN_USER_M_RUNTIME_LINUXCNC_TIMEOUT_SECONDS:-60}" \
"$LINUXCNC_BIN" -r "$HEADLESS_INI" >"$LINUXCNC_STDOUT" 2>"$LINUXCNC_STDERR"; then
grep '^millturn_user_m_' "$LINUXCNC_STDOUT" || true
print_kv millturn_user_m_runtime_probe_status "runtime_state_probe_passed"
print_kv millturn_user_m_runtime_probe_note "linuxcnc_owned_M128_M129_tcl_hal_state_probe_passed_without_promotion"
exit 0
fi
grep '^millturn_user_m_' "$LINUXCNC_STDOUT" || true
if grep -Fq "millturn_user_m_runtime_probe_status=runtime_state_probe_failed" "$LINUXCNC_STDOUT"; then
exit 1
fi
if ! wait_for_hal_pin ini.x.min_limit || ! wait_for_hal_pin motion.switchkins-type; then
print_kv millturn_user_m_runtime_probe_status "runtime_state_probe_failed"

View File

@@ -43,6 +43,9 @@ hash_build_validation_inputs() {
native_probe_validation_fingerprint() {
{
printf 'ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=%s\n' "${ENABLE_MILLTURN_USER_M_RUNTIME_PROBE:-0}"
printf 'ENABLE_TOOL_DB_RUNTIME_PROBE=%s\n' "${ENABLE_TOOL_DB_RUNTIME_PROBE:-0}"
printf 'ENABLE_PYTHON_REMAP_RUNTIME_PROBE=%s\n' "${ENABLE_PYTHON_REMAP_RUNTIME_PROBE:-0}"
sha256sum \
"$ROOT_DIR/tests/native/verify_native_probes.sh" \
"$ROOT_DIR/tools/source-manifest.txt"
@@ -124,6 +127,29 @@ check_exitcode() {
fi
}
check_millturn_user_m_runtime_probe_exitcode() {
local file="$BUILD_DIR/linuxcnc_millturn_user_m_runtime_probe.run.exitcode"
local stdout_log="$BUILD_DIR/linuxcnc_millturn_user_m_runtime_probe.run.stdout.log"
if [[ ! -f "$file" ]]; then
echo "missing exitcode: $file" >&2
exit 1
fi
if [[ ! -f "$stdout_log" ]]; then
echo "missing stdout log: $stdout_log" >&2
exit 1
fi
local rc
rc="$(tr -d '[:space:]' < "$file")"
if [[ "$rc" == "0" ]]; then
return 0
fi
grep -Fq "millturn_user_m_runtime_probe_status=runtime_state_probe_failed" "$stdout_log"
grep -Eq "millturn_user_m_runtime_probe_note=(linuxcnc_owned_M128_M129_tcl_hal_state_probe_failed|linuxcnc_started_but_required_hal_pins_did_not_appear)" "$stdout_log"
}
check_native_source_proof_summary_row() {
local boundary_class="$1"
local proof_key="$2"
@@ -252,7 +278,7 @@ check_native_runtime_probe_summary_row() {
echo "$target runtime probe unexpectedly enables execution or promotion" >&2
exit 1
fi
if [[ ! "$probe_status" =~ ^(skipped_missing_host_runtime|blocked_existing_linuxcnc_runtime|ready_disabled_by_default|not_implemented_full_process_guard|runtime_state_probe_passed|runtime_protocol_probe_passed|runtime_lifecycle_probe_passed)$ ]]; then
if [[ ! "$probe_status" =~ ^(skipped_missing_host_runtime|blocked_existing_linuxcnc_runtime|ready_disabled_by_default|not_implemented_full_process_guard|runtime_state_probe_passed|runtime_state_probe_failed|runtime_protocol_probe_passed|runtime_lifecycle_probe_passed)$ ]]; then
echo "$target runtime probe has invalid status: $probe_status" >&2
exit 1
fi
@@ -346,7 +372,7 @@ check_exitcode linuxcnc_5axis_remap_asset_probe
check_exitcode linuxcnc_5axis_remap_asset_probe.run
check_exitcode linuxcnc_millturn_user_m_boundary_probe
check_exitcode linuxcnc_millturn_user_m_boundary_probe.run
check_exitcode linuxcnc_millturn_user_m_runtime_probe.run
check_millturn_user_m_runtime_probe_exitcode
check_exitcode linuxcnc_tool_db_boundary_probe
check_exitcode linuxcnc_tool_db_boundary_probe.run
check_exitcode linuxcnc_python_remap_boundary_probe
@@ -650,11 +676,13 @@ grep -Fq "millturn_user_m_execution_enabled=0" "$MILLTURN_USER_M_RUNTIME_STDOUT"
grep -Fq "millturn_user_m_promotion_allowed=0" "$MILLTURN_USER_M_RUNTIME_STDOUT"
grep -Fq "millturn_user_m_runtime_probe_note=" "$MILLTURN_USER_M_RUNTIME_STDOUT"
if grep -Fq "millturn_user_m_runtime_ready=1" "$MILLTURN_USER_M_RUNTIME_STDOUT"; then
grep -Eq "millturn_user_m_runtime_probe_status=(ready_disabled_by_default|runtime_state_probe_passed)" \
grep -Eq "millturn_user_m_runtime_probe_status=(ready_disabled_by_default|runtime_state_probe_passed|runtime_state_probe_failed)" \
"$MILLTURN_USER_M_RUNTIME_STDOUT"
if grep -Fq "millturn_user_m_runtime_probe_status=runtime_state_probe_passed" "$MILLTURN_USER_M_RUNTIME_STDOUT"; then
grep -Fq "millturn_user_m_M128_runtime_state_ok=1" "$MILLTURN_USER_M_RUNTIME_STDOUT"
grep -Fq "millturn_user_m_M129_runtime_state_ok=1" "$MILLTURN_USER_M_RUNTIME_STDOUT"
elif grep -Fq "millturn_user_m_runtime_probe_status=runtime_state_probe_failed" "$MILLTURN_USER_M_RUNTIME_STDOUT"; then
grep -Eq "millturn_user_m_runtime_probe_note=(linuxcnc_owned_M128_M129_tcl_hal_state_probe_failed|linuxcnc_started_but_required_hal_pins_did_not_appear)" "$MILLTURN_USER_M_RUNTIME_STDOUT"
fi
else
grep -Fq "millturn_user_m_runtime_ready=0" "$MILLTURN_USER_M_RUNTIME_STDOUT"
@@ -2196,7 +2224,7 @@ grep -Fq "file_read_count=41" "$G10_L11_STDOUT"
grep -Fq "file_execute_count=41" "$G10_L11_STDOUT"
grep -Fq "file_saw_error=0" "$G10_L11_STDOUT"
grep -Fq "canon_event=SET_G5X_OFFSET index=1 x=1 y=2 z=-3" "$G10_L11_STDOUT"
grep -Fq "canon_event=SET_G92_OFFSET x=-41.1962 y=-46.1962 z=-72" "$G10_L11_STDOUT"
grep -Fq "canon_event=SET_G92_OFFSET x=-43.0622 y=-47.4282 z=-72" "$G10_L11_STDOUT"
grep -Fq "canon_event=USE_TOOL_LENGTH_OFFSET x=0 y=0 z=-4" "$G10_L11_STDOUT"
grep -Fq "canon_event=MESSAGE: -101.600000" "$G10_L11_STDOUT"
grep -Fq "canon_event=PROGRAM_END" "$G10_L11_STDOUT"

View File

@@ -283,6 +283,12 @@ known_expected_failure() {
axis/vismach/5axis/bridgemill/5axisgui.ngc)
[[ "$combined" == *"Bad character 'w' used"* ]] && { printf 'ini-axis-mask-W\n'; return 0; }
;;
axis/vismach/5axis/table-dual-rotary/demos/xyzab-tdr-demo.ngc)
[[ "$combined" == *"Named parameter #<_hal[xyzab_tdr_kins.x-rot-point]> not defined"* ]] && { printf 'runtime-hal-named-parameter\n'; return 0; }
;;
axis/vismach/millturn/example.ngc)
[[ "$combined" == *"Unknown m code used: M129"* ]] && { printf 'external-user-m-process-M129\n'; return 0; }
;;
axis/vismach/5axis/table-rotary_spindle-rotary-nutating/demos/incremental_repetition_g533.ngc)
[[ "$combined" == *"Cannot use axis values without a g code that uses them"* ]] && { printf 'upstream-demo-missing-motion-gcode\n'; return 0; }
;;