diff --git a/AGENTS.md b/AGENTS.md index c940252..b14d402 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,3 +11,5 @@ After every GPT/Codex execution completes, append the full execution process log to: `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` + +The log content written to this file must be recorded in Chinese. diff --git a/linuxcnc.zip b/linuxcnc.zip deleted file mode 100644 index 947b12a..0000000 Binary files a/linuxcnc.zip and /dev/null differ diff --git a/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js b/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js index 048b333..a27d86a 100644 --- a/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js +++ b/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js @@ -2,6 +2,15 @@ import { getSourceReferencesForProfile } from "./source-reference-map.js"; const sourceReferenceObjects = getSourceReferencesForProfile("gmoccapy-xyzab"); +const gmoccapyXyzabJogAxisPins = ["x", "y", "z", "a", "b"].flatMap((axis) => [ + `gmoccapy.jog.axis.jog-${axis}-plus`, + `gmoccapy.jog.axis.jog-${axis}-minus`, +]); + +const gmoccapyXyzabJogIncrementPins = Array.from({ length: 6 }, (_, index) => ( + `gmoccapy.jog.jog-inc-${index}` +)); + export const gmoccapyXyzabPanelSchema = { id: "gmoccapy-xyzab-hal-component", profileId: "gmoccapy-xyzab", @@ -20,8 +29,8 @@ export const gmoccapyXyzabPanelSchema = { id: "tbtn-estop", type: "button", text: "ESTOP", - halpin: "gmoccapy.h-button.button-0", - halNet: "gmoccapy-h-button-0", + halpin: "gmoccapy.v-button.button-0", + halNet: "gmoccapy-v-button-0", target: "gmoccapy.command.STATE_ESTOP", nativeCommand: "STATE_ESTOP / STATE_ESTOP_RESET", webAction: { type: "ESTOP" }, @@ -30,8 +39,8 @@ export const gmoccapyXyzabPanelSchema = { id: "tbtn-on", type: "button", text: "POWER", - halpin: "gmoccapy.h-button.button-1", - halNet: "gmoccapy-h-button-1", + halpin: "gmoccapy.v-button.button-1", + halNet: "gmoccapy-v-button-1", target: "gmoccapy.command.STATE_ON", nativeCommand: "STATE_ON / STATE_OFF", webAction: { type: "TOGGLE_POWER" }, @@ -40,8 +49,8 @@ export const gmoccapyXyzabPanelSchema = { id: "rbt-manual", type: "button", text: "MANUAL", - halpin: "gmoccapy.v-button.button-0", - halNet: "gmoccapy-v-button-0", + halpin: "gmoccapy.v-button.button-2", + halNet: "gmoccapy-v-button-2", target: "gmoccapy.command.MODE_MANUAL", nativeCommand: "MODE_MANUAL", webAction: { type: "SET_MODE", mode: "manual" }, @@ -50,8 +59,8 @@ export const gmoccapyXyzabPanelSchema = { id: "rbt-mdi", type: "button", text: "MDI", - halpin: "gmoccapy.v-button.button-1", - halNet: "gmoccapy-v-button-1", + halpin: "gmoccapy.v-button.button-3", + halNet: "gmoccapy-v-button-3", target: "gmoccapy.command.MODE_MDI", nativeCommand: "MODE_MDI", webAction: { type: "SET_MODE", mode: "mdi" }, @@ -60,8 +69,8 @@ export const gmoccapyXyzabPanelSchema = { id: "rbt-auto", type: "button", text: "AUTO", - halpin: "gmoccapy.v-button.button-2", - halNet: "gmoccapy-v-button-2", + halpin: "gmoccapy.v-button.button-4", + halNet: "gmoccapy-v-button-4", target: "gmoccapy.command.MODE_AUTO", nativeCommand: "MODE_AUTO", webAction: { type: "SET_MODE", mode: "auto" }, @@ -215,6 +224,26 @@ export const gmoccapyXyzabProfile = { { signal: "spindle-at-speed", source: "near_speed.out", target: "spindle.0.at-speed" }, { signal: "spindle-abs", source: "abs_spindle_feedback.out", target: "gmoccapy.spindle_feedback_bar" }, ], + toolChange: { + strategy: "iocontrol-loopback", + manualGmoccapyPinsConnected: false, + postguiUnlinks: [ + "iocontrol.0.tool-change", + "iocontrol.0.tool-changed", + "iocontrol.0.tool-prep-number", + ], + commentedManualGuiNets: [ + "gmoccapy.toolchange-change <= iocontrol.0.tool-change", + "gmoccapy.toolchange-changed => iocontrol.0.tool-changed", + "gmoccapy.toolchange-number <= iocontrol.0.tool-prep-number", + ], + activeLoop: { + signal: "tool-change-loop", + source: "iocontrol.0.tool-change", + target: "iocontrol.0.tool-changed", + stage: "gmoccapy_postgui.hal", + }, + }, }, }, nativeRuntime: { @@ -269,11 +298,32 @@ export const gmoccapyXyzabProfile = { halPins: [ "gmoccapy.h-button.button-0", "gmoccapy.v-button.button-0", - "gmoccapy.jog.axis.jog-x-plus", - "gmoccapy.jog.axis.jog-b-minus", + ...gmoccapyXyzabJogAxisPins, + ...gmoccapyXyzabJogIncrementPins, + "gmoccapy.jog.jog-increment", + "gmoccapy.jog.turtle-jog", "gmoccapy.feed.feed-override.counts", + "gmoccapy.feed.feed-override.count-enable", + "gmoccapy.feed.feed-override.analog-enable", + "gmoccapy.feed.feed-override.direct-value", + "gmoccapy.feed.reset-feed-override", "gmoccapy.spindle.spindle-override.counts", + "gmoccapy.spindle.spindle-override.count-enable", + "gmoccapy.spindle.spindle-override.analog-enable", + "gmoccapy.spindle.spindle-override.direct-value", + "gmoccapy.spindle.reset-spindle-override", + "gmoccapy.rapid.rapid-override.counts", + "gmoccapy.rapid.rapid-override.count-enable", + "gmoccapy.rapid.rapid-override.analog-enable", "gmoccapy.rapid.rapid-override.direct-value", + "gmoccapy.rapid.reset-rapid-override", + "gmoccapy.jog.jog-velocity.counts", + "gmoccapy.jog.jog-velocity.count-enable", + "gmoccapy.jog.jog-velocity.analog-enable", + "gmoccapy.jog.jog-velocity.direct-value", + "gmoccapy.ignore-limits", + "gmoccapy.optional-stop", + "gmoccapy.blockdelete", "gmoccapy.tooloffset-x", "gmoccapy.tooloffset-z", "gmoccapy.toolchange-change", @@ -284,6 +334,8 @@ export const gmoccapyXyzabProfile = { "gmoccapy.spindle_feedback_bar", "gmoccapy.spindle_at_speed_led", "gmoccapy.error", + "gmoccapy.delete-message", + "gmoccapy.warning-confirm", ], offsets: { toolOffsetX: 0, diff --git a/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js b/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js index a183d76..7de50fa 100644 --- a/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js +++ b/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js @@ -20,6 +20,16 @@ export const gmoccapyCommunicationModel = { path: ["gmoccapy.* pins", "HAL shared memory", "halui/iocontrol/motmod/spindle_sim"], notes: "postgui HAL connects gmoccapy pins after halcomp.ready().", }, + hardwareButtons: { + label: "HAL hardware buttons", + path: ["external HAL bit", "gmoccapy.h-button/v-button pin", "_button_pin_changed", "visible sensitive GTK button", "linuxcnc.command()"], + notes: "gmoccapy maps rising-edge hard-button pins to the current visible software button; insensitive or hidden targets are ignored.", + }, + halPins: { + label: "HAL input pins", + path: ["external HAL value", "gmoccapy.* input pin", "hal_glib.GPin value_changed", "gmoccapy callback", "linuxcnc.command() or GTK widget state"], + notes: "jog pins, jog increment pins, ignore-limits, optional-stop/blockdelete, override counts/direct-value, reset and message pins enter gmoccapy through HAL callbacks.", + }, remap: { label: "Interpreter REMAP", path: ["milltask", "RS274NGC", "Python remap prolog/ngc/epilog"], @@ -148,6 +158,34 @@ export const gmoccapyCommunicationModel = { nativeStatus: "iocontrol.0.coolant-flood/mist", gate: "blocked in estop/off", }, + { + actionId: "hardware-button", + webAction: "explicit data-gmoccapy-hal-pin button mapping", + nativeCommand: "_button_pin_changed -> GtkButton emit clicked/pressed/toggled", + nativeStatus: "same status as the target software button", + gate: "rising-edge pin ignored when target button is hidden or insensitive", + }, + { + actionId: "hal-pin-input", + webAction: "GMOCAPY_HAL_PIN", + nativeCommand: "_ignore_limits/_optional_blocks/_blockdelete/_on_counts_changed/_on_analog_value_changed/_reset_override/_del_message_changed", + nativeStatus: "ignore-limits, block delete, optional stop, feed/rapid/spindle/jog override widget state and operator message state", + gate: "counts require count-enable, direct-value requires analog-enable, reset/delete-message pins are rising-edge only", + }, + { + actionId: "hal-jog-pin", + webAction: "GMOCAPY_HAL_PIN jog.axis/jog-inc", + nativeCommand: "_on_pin_jog_changed/_on_pin_incr_changed", + nativeStatus: "manual jog command, active jog increment and gmoccapy.jog.jog-increment HAL OUT", + gate: "axis pins use press/release level, task must be on and manual; increment pins are rising-edge only", + }, + { + actionId: "hal-warning-confirm", + webAction: "GMOCAPY_HAL_PIN warning-confirm", + nativeCommand: "dialogs.warning_dialog confirm_pin polling", + nativeStatus: "active warning dialog response", + gate: "level-polled while warning dialog is active", + }, ], }; @@ -163,6 +201,8 @@ export function createGmoccapyCommunicationSummary(model = gmoccapyCommunication model.startupStages.findIndex((stage) => stage.id === "gmoccapy"), hasNativeNmlCommandPath: model.nativePaths.command.path.includes("NML emcCommand"), hasNativeHalPath: model.nativePaths.hal.path.includes("HAL shared memory"), + hasNativeHardwareButtonPath: model.nativePaths.hardwareButtons.path.includes("_button_pin_changed"), + hasNativeHalPinInputPath: model.nativePaths.halPins.path.includes("hal_glib.GPin value_changed"), hasWebStorePath: model.webPath.path.includes("store.dispatch"), semanticBoundary: model.semanticBoundary, }; diff --git a/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js b/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js index 7136eef..1461fc0 100644 --- a/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js +++ b/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js @@ -68,7 +68,13 @@ export const gmoccapyHalModel = { "gmoccapy.jog.axis.jog-b-plus", "gmoccapy.jog.axis.jog-b-minus", "gmoccapy.jog.jog-inc-0", + "gmoccapy.jog.jog-inc-1", + "gmoccapy.jog.jog-inc-2", + "gmoccapy.jog.jog-inc-3", + "gmoccapy.jog.jog-inc-4", + "gmoccapy.jog.jog-inc-5", "gmoccapy.jog.jog-increment", + "gmoccapy.jog.turtle-jog", ], webSource: "JOG dispatch and machine.jogIncrement", semanticBoundary: "native_jog_pin_reference_web_task_policy_gate", @@ -77,17 +83,38 @@ export const gmoccapyHalModel = { group: "override", pins: [ "gmoccapy.feed.feed-override.counts", + "gmoccapy.feed.feed-override.count-enable", + "gmoccapy.feed.feed-override.analog-enable", "gmoccapy.feed.feed-override.direct-value", + "gmoccapy.feed.reset-feed-override", "gmoccapy.spindle.spindle-override.counts", + "gmoccapy.spindle.spindle-override.count-enable", + "gmoccapy.spindle.spindle-override.analog-enable", "gmoccapy.spindle.spindle-override.direct-value", + "gmoccapy.spindle.reset-spindle-override", "gmoccapy.jog.jog-velocity.counts", + "gmoccapy.jog.jog-velocity.count-enable", + "gmoccapy.jog.jog-velocity.analog-enable", "gmoccapy.jog.jog-velocity.direct-value", "gmoccapy.rapid.rapid-override.counts", + "gmoccapy.rapid.rapid-override.count-enable", + "gmoccapy.rapid.rapid-override.analog-enable", "gmoccapy.rapid.rapid-override.direct-value", + "gmoccapy.rapid.reset-rapid-override", ], webSource: "feed/spindle override state", semanticBoundary: "native_override_pin_reference_web_state_mapping", }, + { + group: "operator-inputs", + pins: [ + "gmoccapy.ignore-limits", + "gmoccapy.optional-stop", + "gmoccapy.blockdelete", + ], + webSource: "gmoccapyGui ignoreLimits, optionalBlocks, optionalStop", + semanticBoundary: "native_ignore_limits_optional_stop_blockdelete_pin_reference_web_state_mapping", + }, { group: "tool", pins: [ @@ -98,7 +125,7 @@ export const gmoccapyHalModel = { "gmoccapy.toolchange-changed", "gmoccapy.toolchange-number", ], - webSource: "toolPreview and tool-change diagnostics", + webSource: "toolPreview and disconnected manual tool-change diagnostics", semanticBoundary: "native_tool_pin_reference_simulated_tool_loop", }, { @@ -257,12 +284,319 @@ export const gmoccapyHalModel = { role: "automatic simulated tool-change completion", }, ], + hardwareButtons: { + source: { + path: "linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py", + methods: ["_button_pin_changed", "_get_child_button", "_make_hal_pins"], + }, + behavior: { + edge: "rising-edge-only", + hButtonContainer: "current ntb_button page visible children", + vButtonContainer: "vbtb_main visible children", + insensitiveTarget: "ignored", + hiddenLabelsSkipped: true, + semanticBoundary: "native_gmoccapy_hardware_button_pin_to_visible_sensitive_button", + }, + verticalMain: [ + { + index: 0, + pin: "gmoccapy.v-button.button-0", + nativeWidget: "tbtn_estop", + webAction: "ESTOP", + webDispatch: { type: "ESTOP" }, + webSelector: '[data-action="estop"]', + gate: "always clickable safety action", + }, + { + index: 1, + pin: "gmoccapy.v-button.button-1", + nativeWidget: "tbtn_on", + webAction: "TOGGLE_POWER", + webDispatch: { type: "TOGGLE_POWER" }, + webSelector: '[data-action="power"]', + gate: "blocked while STATE_ESTOP", + }, + { + index: 2, + pin: "gmoccapy.v-button.button-2", + nativeWidget: "rbt_manual", + webAction: "SET_MODE manual", + webDispatch: { type: "SET_MODE", mode: "manual" }, + webSelector: '[data-action="mode-manual"]', + gate: "machine on and AUTO interpreter idle", + }, + { + index: 3, + pin: "gmoccapy.v-button.button-3", + nativeWidget: "rbt_mdi", + webAction: "SET_MODE mdi", + webDispatch: { type: "SET_MODE", mode: "mdi" }, + webSelector: '[data-action="mode-mdi"]', + gate: "machine on and homed when NO_FORCE_HOMING=0", + }, + { + index: 4, + pin: "gmoccapy.v-button.button-4", + nativeWidget: "rbt_auto", + webAction: "SET_MODE auto", + webDispatch: { type: "SET_MODE", mode: "auto" }, + webSelector: '[data-action="mode-auto"]', + gate: "machine on and homed when NO_FORCE_HOMING=0", + }, + { + index: 5, + pin: "gmoccapy.v-button.button-5", + nativeWidget: "tbtn_user_tabs", + webAction: "SET_MODE manual", + webDispatch: { type: "SET_MODE", mode: "jog" }, + webSelector: '[data-action="mode-jog"]', + gate: "machine on and manual page available", + }, + { + index: 6, + pin: "gmoccapy.v-button.button-6", + nativeWidget: "tbtn_setup", + webAction: "UI_CONTROL", + webSelector: null, + gate: "native setup page is diagnostic-only in the Web shell", + }, + ], + bottomMain: [ + { + index: 0, + pin: "gmoccapy.h-button.button-0", + nativeWidget: "btn_homing", + webAction: "HOME", + webDispatch: { type: "HOME" }, + webSelector: '[data-action="HOME"]', + gate: "machine on and manual mode", + }, + { + index: 1, + pin: "gmoccapy.h-button.button-1", + nativeWidget: "btn_touch", + webAction: "UI_CONTROL", + webSelector: null, + gate: "touch-off page not implemented in Web shell", + }, + { + index: 3, + pin: "gmoccapy.h-button.button-3", + nativeWidget: "btn_tool", + webAction: "UI_CONTROL", + webSelector: null, + gate: "tool editor page not implemented in Web shell", + }, + { + index: 6, + pin: "gmoccapy.h-button.button-6", + nativeWidget: "tbtn_switch_mode", + webAction: "SET_KINS_TYPE identity", + webSelector: null, + gate: "native joint/world switch page position is diagnostic-only in the Web shell", + }, + ], + }, + halPinActions: { + source: { + path: "linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py", + methods: [ + "_ignore_limits", + "_optional_blocks", + "_blockdelete", + "_on_pin_jog_changed", + "_on_pin_incr_changed", + "_on_counts_changed", + "_on_analog_enable_changed", + "_on_analog_value_changed", + "_reset_override", + "_del_message_changed", + ], + prefPath: "linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.pref", + }, + jogPins: { + axes: [ + { axis: "x", plus: "gmoccapy.jog.axis.jog-x-plus", minus: "gmoccapy.jog.axis.jog-x-minus" }, + { axis: "y", plus: "gmoccapy.jog.axis.jog-y-plus", minus: "gmoccapy.jog.axis.jog-y-minus" }, + { axis: "z", plus: "gmoccapy.jog.axis.jog-z-plus", minus: "gmoccapy.jog.axis.jog-z-minus" }, + { axis: "a", plus: "gmoccapy.jog.axis.jog-a-plus", minus: "gmoccapy.jog.axis.jog-a-minus" }, + { axis: "b", plus: "gmoccapy.jog.axis.jog-b-plus", minus: "gmoccapy.jog.axis.jog-b-minus" }, + ], + increments: [ + { index: 0, pin: "gmoccapy.jog.jog-inc-0", label: "Continuous", distance: 0 }, + { index: 1, pin: "gmoccapy.jog.jog-inc-1", label: "1.000 mm", distance: 1 }, + { index: 2, pin: "gmoccapy.jog.jog-inc-2", label: "0.100 mm", distance: 0.1 }, + { index: 3, pin: "gmoccapy.jog.jog-inc-3", label: "0.010 mm", distance: 0.01 }, + { index: 4, pin: "gmoccapy.jog.jog-inc-4", label: "0.001 mm", distance: 0.001 }, + { index: 5, pin: "gmoccapy.jog.jog-inc-5", label: "1.2345 in", distance: 31.3563 }, + ], + outputPin: "gmoccapy.jog.jog-increment", + turtlePin: "gmoccapy.jog.turtle-jog", + nativeCallbacks: { + axis: "_on_pin_jog_changed -> _on_btn_jog_pressed/_on_btn_jog_released", + increment: "_on_pin_incr_changed -> _jog_increment_changed", + turtle: "_on_pin_turtle_jog -> tbtn_turtle_jog.set_active", + }, + }, + operatorPins: [ + { + pin: "gmoccapy.ignore-limits", + nativeCallback: "_ignore_limits -> chk_ignore_limits.set_active", + nativeCommand: "on_chk_ignore_limits_toggled -> command.override_limits() when active", + webDispatch: { type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.ignore-limits" }, + webState: "gmoccapyGui.ignoreLimits", + }, + { + pin: "gmoccapy.optional-stop", + nativeCallback: "_optional_blocks", + nativeCommand: "tbtn_optional_blocks -> command.set_block_delete(state)", + webDispatch: { type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.optional-stop" }, + webState: "gmoccapyGui.optionalBlocks", + }, + { + pin: "gmoccapy.blockdelete", + nativeCallback: "_blockdelete", + nativeCommand: "command.set_optional_stop(state)", + webDispatch: { type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.blockdelete" }, + webState: "gmoccapyGui.optionalStop", + }, + ], + overridePins: [ + { + target: "feed", + counts: "gmoccapy.feed.feed-override.counts", + countEnable: "gmoccapy.feed.feed-override.count-enable", + analogEnable: "gmoccapy.feed.feed-override.analog-enable", + directValue: "gmoccapy.feed.feed-override.direct-value", + reset: "gmoccapy.feed.reset-feed-override", + nativeWidget: "spc_feed", + scalePref: "scale_feed_override=1", + webState: "feed.feedOverride", + }, + { + target: "rapid", + counts: "gmoccapy.rapid.rapid-override.counts", + countEnable: "gmoccapy.rapid.rapid-override.count-enable", + analogEnable: "gmoccapy.rapid.rapid-override.analog-enable", + directValue: "gmoccapy.rapid.rapid-override.direct-value", + reset: "gmoccapy.rapid.reset-rapid-override", + nativeWidget: "spc_rapid", + scalePref: "scale_rapid_override=1", + webState: "feed.rapidOverride", + }, + { + target: "spindle", + counts: "gmoccapy.spindle.spindle-override.counts", + countEnable: "gmoccapy.spindle.spindle-override.count-enable", + analogEnable: "gmoccapy.spindle.spindle-override.analog-enable", + directValue: "gmoccapy.spindle.spindle-override.direct-value", + reset: "gmoccapy.spindle.reset-spindle-override", + nativeWidget: "spc_spindle", + scalePref: "scale_spindle_override=1", + webState: "spindle.override", + }, + { + target: "jogVelocity", + counts: "gmoccapy.jog.jog-velocity.counts", + countEnable: "gmoccapy.jog.jog-velocity.count-enable", + analogEnable: "gmoccapy.jog.jog-velocity.analog-enable", + directValue: "gmoccapy.jog.jog-velocity.direct-value", + reset: null, + nativeWidget: "spc_lin_jog_vel", + scalePref: "scale_jog_vel=140.4", + webState: "gmoccapyGui.jogVelocity", + }, + ], + messagePins: [ + { + pin: "gmoccapy.delete-message", + nativeCallback: "_del_message_changed", + nativeCommand: "pin true deletes first alert message when gmoccapy.error is set, otherwise del_last()", + webState: "operatorMessage", + }, + { + pin: "gmoccapy.warning-confirm", + nativeCallback: "dialogs.warning_dialog periodic confirm_pin poll", + nativeCommand: "pin true responds OK to active warning dialog", + webState: "gmoccapyGui.warningConfirm", + }, + { + pin: "gmoccapy.error", + nativeCallback: "_show_error/_on_message_deleted", + nativeCommand: "HAL OUT bit tracks alert messages", + webState: "gmoccapyGui.error", + }, + ], + behavior: { + optionalStopPinCrossesToBlockDelete: true, + blockdeletePinCrossesToOptionalStop: true, + jogAxisPinsPressRelease: true, + jogPinsUseTaskPolicyGate: true, + jogIncrementPinsRisingEdgeOnly: true, + jogIncrementZeroIsContinuous: true, + turtleJogLevelDriven: true, + countInputRequiresEnable: true, + directValueRequiresAnalogEnable: true, + resetPinsRisingEdgeOnly: true, + deleteMessageRisingEdgeOnly: true, + warningConfirmLevelPolled: true, + directValueRange: "0..1", + semanticBoundary: "native_gmoccapy_hal_pin_callbacks_to_web_store_actions", + }, + }, + toolChange: { + strategy: "iocontrol-loopback", + manualGmoccapyPinsConnected: false, + remapCodes: ["M6", "M61"], + postguiUnlinks: [ + { + pin: "iocontrol.0.tool-change", + reason: "gmoccapy_postgui.hal removes the core_sim connection before applying the final loopback", + }, + { + pin: "iocontrol.0.tool-changed", + reason: "gmoccapy_postgui.hal removes the core_sim connection before applying the final loopback", + }, + { + pin: "iocontrol.0.tool-prep-number", + reason: "manual gmoccapy tool-change number net is not active in this simulation", + }, + ], + commentedManualGuiNets: [ + { + signal: "tool-change", + source: "iocontrol.0.tool-change", + target: "gmoccapy.toolchange-change", + }, + { + signal: "tool-changed", + source: "gmoccapy.toolchange-changed", + target: "iocontrol.0.tool-changed", + }, + { + signal: "tool-prep-number", + source: "iocontrol.0.tool-prep-number", + target: "gmoccapy.toolchange-number", + }, + ], + activeLoop: { + signal: "tool-change-loop", + source: "iocontrol.0.tool-change", + target: "iocontrol.0.tool-changed", + stage: "POSTGUI_HALFILE", + }, + semanticBoundary: "gmoccapy_xyzab_iocontrol_tool_change_loop_no_manual_gui_dialog", + }, semanticBoundary: "gmoccapy_hal_pin_postgui_reference_web_diagnostic_only", }; export function createGmoccapyHalSummary(model = gmoccapyHalModel) { const pinCount = model.nativePins.reduce((total, group) => total + group.pins.length, 0); const postguiFile = model.halFiles.find((file) => file.stage === "POSTGUI_HALFILE"); + const mappedHardwareButtonCount = [ + ...(model.hardwareButtons?.verticalMain || []), + ...(model.hardwareButtons?.bottomMain || []), + ].filter((button) => button.webSelector).length; return { apiName: `${model.apiName}-summary`, profileId: model.profileId, @@ -275,6 +609,48 @@ export function createGmoccapyHalSummary(model = gmoccapyHalModel) { postguiRequiresHalcompReady: Boolean(postguiFile?.requiresHalcompReady) && model.postguiNets.every((net) => net.requiresHalcompReady), hasToolChangeSimulationLoop: model.postguiNets.some((net) => net.simulationLoop), + toolChangeStrategy: model.toolChange.strategy, + manualToolChangeGuiConnected: model.toolChange.manualGmoccapyPinsConnected, + postguiToolUnlinkCount: model.toolChange.postguiUnlinks.length, + commentedManualToolChangeNetCount: model.toolChange.commentedManualGuiNets.length, + hardwareButtonVerticalCount: model.hardwareButtons.verticalMain.length, + hardwareButtonBottomMainCount: model.hardwareButtons.bottomMain.length, + mappedHardwareButtonCount, + hardwareButtonEdge: model.hardwareButtons.behavior.edge, + hardwareButtonInsensitiveTarget: model.hardwareButtons.behavior.insensitiveTarget, + operatorInputPinCount: model.halPinActions.operatorPins.length, + overridePinTargetCount: model.halPinActions.overridePins.length, + jogAxisPinCount: (model.halPinActions.jogPins?.axes || []).length * 2, + jogIncrementPinCount: model.halPinActions.jogPins?.increments?.length || 0, + messageInputPinCount: model.halPinActions.messagePins.length, + optionalStopPinCrossesToBlockDelete: model.halPinActions.behavior.optionalStopPinCrossesToBlockDelete, + blockdeletePinCrossesToOptionalStop: model.halPinActions.behavior.blockdeletePinCrossesToOptionalStop, + jogAxisPinsPressRelease: model.halPinActions.behavior.jogAxisPinsPressRelease, + jogPinsUseTaskPolicyGate: model.halPinActions.behavior.jogPinsUseTaskPolicyGate, + jogIncrementPinsRisingEdgeOnly: model.halPinActions.behavior.jogIncrementPinsRisingEdgeOnly, + jogIncrementZeroIsContinuous: model.halPinActions.behavior.jogIncrementZeroIsContinuous, + turtleJogLevelDriven: model.halPinActions.behavior.turtleJogLevelDriven, + countInputRequiresEnable: model.halPinActions.behavior.countInputRequiresEnable, + directValueRequiresAnalogEnable: model.halPinActions.behavior.directValueRequiresAnalogEnable, + resetPinsRisingEdgeOnly: model.halPinActions.behavior.resetPinsRisingEdgeOnly, + deleteMessageRisingEdgeOnly: model.halPinActions.behavior.deleteMessageRisingEdgeOnly, + warningConfirmLevelPolled: model.halPinActions.behavior.warningConfirmLevelPolled, semanticBoundary: model.semanticBoundary, }; } + +export function resolveGmoccapyHardwareButton(input, model = gmoccapyHalModel) { + const pin = typeof input === "string" ? input : input?.pin; + const location = typeof input === "object" ? input?.location || input?.panel : null; + const index = typeof input === "object" && input?.index !== undefined ? Number(input.index) : null; + const allButtons = [ + ...(model.hardwareButtons?.verticalMain || []).map((button) => ({ ...button, panel: "right", location: "v" })), + ...(model.hardwareButtons?.bottomMain || []).map((button) => ({ ...button, panel: "bottom", location: "h" })), + ]; + if (pin) return allButtons.find((button) => button.pin === pin) || null; + if (!Number.isFinite(index)) return null; + const normalizedLocation = location === "right" || location === "v" || location === "vertical" ? "v" + : location === "bottom" || location === "h" || location === "horizontal" ? "h" + : null; + return allButtons.find((button) => button.location === normalizedLocation && button.index === index) || null; +} diff --git a/web-rtcp-5axis-sim-plan/app/src/state/store.js b/web-rtcp-5axis-sim-plan/app/src/state/store.js index 6b62ded..d968e58 100644 --- a/web-rtcp-5axis-sim-plan/app/src/state/store.js +++ b/web-rtcp-5axis-sim-plan/app/src/state/store.js @@ -20,6 +20,7 @@ import { buildTaskHalProgramMotionPlan, buildTaskHalSessionFromMachineFiles, } from "../runtime/linuxcnc-task-hal-runtime.js"; +import { gmoccapyHalModel, resolveGmoccapyHardwareButton } from "../runtime/gmoccapy-hal-model.js"; import { createLinuxCncTaskPolicyStatus, gateLinuxCncTaskAction, @@ -230,6 +231,35 @@ const initialState = { flood: true, mist: false, }, + gmoccapyGui: { + ignoreLimits: false, + optionalBlocks: false, + optionalStop: false, + feedOverrideCounts: 0, + rapidOverrideCounts: 0, + spindleOverrideCounts: 0, + jogVelocityCounts: 0, + feedOverrideCountEnabled: false, + rapidOverrideCountEnabled: false, + spindleOverrideCountEnabled: false, + jogVelocityCountEnabled: false, + feedOverrideAnalogEnabled: false, + rapidOverrideAnalogEnabled: false, + spindleOverrideAnalogEnabled: false, + jogVelocityAnalogEnabled: false, + jogVelocity: 100, + jogIncrementIndex: 0, + jogIncrementLabel: "Continuous", + jogIncrementOutput: 0, + turtleJog: false, + activeJogPin: null, + warningConfirm: false, + error: false, + deletedMessageCount: 0, + lastHalPin: null, + lastHalPinValue: null, + lastHalPinEffect: null, + }, preview: { pathPoints: 64, selectedView: "iso", @@ -964,6 +994,34 @@ export function createSimulationStore(seed = {}) { case "REFRESH_KINEMATICS_FRAME": return refreshAsyncKinematicsFrame({ operatorMessage: "LinuxCNC kinematics frame refreshed" }); break; + case "GMOCAPY_HARDWARE_BUTTON": + { + if (action.value === false || action.risingEdge === false) { + setState({ operatorMessage: "gmoccapy hardware button falling edge ignored" }); + break; + } + const button = resolveGmoccapyHardwareButton(action); + if (!button) { + setState({ operatorMessage: `gmoccapy hardware button unmapped: ${action.pin || `${action.location}:${action.index}`}` }); + break; + } + if (!button.webDispatch) { + setState({ operatorMessage: `gmoccapy hardware button diagnostic-only: ${button.pin}` }); + break; + } + dispatch(button.webDispatch); + } + break; + case "GMOCAPY_HAL_PIN": + { + const result = applyGmoccapyHalPinPatch(state, action, gmoccapyHalModel); + if (!result.applied) { + setState({ operatorMessage: result.operatorMessage }); + break; + } + setState(result.patch); + } + break; case "TOGGLE_POWER": { const gate = gateLinuxCncTaskAction(state, action); @@ -1585,6 +1643,12 @@ export function createSimulationStore(seed = {}) { 200, ), }, + gmoccapyGui: { + ...state.gmoccapyGui, + lastHalPin: null, + lastHalPinValue: null, + lastHalPinEffect: `${action.target} override manual step`, + }, operatorMessage: `${action.target} override adjusted`, }); } @@ -1601,10 +1665,55 @@ export function createSimulationStore(seed = {}) { ...state.spindle, override: clampPercent(state.spindle.override + action.delta, 0, 150), }, + gmoccapyGui: { + ...state.gmoccapyGui, + lastHalPin: null, + lastHalPinValue: null, + lastHalPinEffect: "spindle override manual step", + }, operatorMessage: "spindle override adjusted", }); } break; + case "RESET_OVERRIDE": + setState(resetOverridePatchForTarget(state, action.target)); + break; + case "SET_BLOCK_DELETE": + setState({ + gmoccapyGui: { + ...state.gmoccapyGui, + optionalBlocks: Boolean(action.enabled), + lastHalPin: null, + lastHalPinValue: null, + lastHalPinEffect: "block delete set from Web optional blocks button", + }, + operatorMessage: `block delete ${action.enabled ? "on" : "off"}`, + }); + break; + case "SET_OPTIONAL_STOP": + setState({ + gmoccapyGui: { + ...state.gmoccapyGui, + optionalStop: Boolean(action.enabled), + lastHalPin: null, + lastHalPinValue: null, + lastHalPinEffect: "optional stop set from Web control", + }, + operatorMessage: `optional stop ${action.enabled ? "on" : "off"}`, + }); + break; + case "SET_IGNORE_LIMITS": + setState({ + gmoccapyGui: { + ...state.gmoccapyGui, + ignoreLimits: Boolean(action.enabled), + lastHalPin: null, + lastHalPinValue: null, + lastHalPinEffect: "ignore limits set from Web checkbox", + }, + operatorMessage: action.enabled ? "limit override requested" : "limit override clear", + }); + break; case "TOGGLE_COOLANT": { const gate = gateLinuxCncTaskAction(state, action); @@ -3269,6 +3378,535 @@ function clampPercent(value, min, max) { return Math.min(Math.max(value, min), max); } +const GMOCAPY_OVERRIDE_TARGETS = { + feed: { + countPin: "gmoccapy.feed.feed-override.counts", + countEnablePin: "gmoccapy.feed.feed-override.count-enable", + analogEnablePin: "gmoccapy.feed.feed-override.analog-enable", + directValuePin: "gmoccapy.feed.feed-override.direct-value", + resetPin: "gmoccapy.feed.reset-feed-override", + countStateKey: "feedOverrideCounts", + countEnableStateKey: "feedOverrideCountEnabled", + analogEnableStateKey: "feedOverrideAnalogEnabled", + stateDomain: "feed", + stateKey: "feedOverride", + min: 0, + max: 200, + scale: 1, + }, + rapid: { + countPin: "gmoccapy.rapid.rapid-override.counts", + countEnablePin: "gmoccapy.rapid.rapid-override.count-enable", + analogEnablePin: "gmoccapy.rapid.rapid-override.analog-enable", + directValuePin: "gmoccapy.rapid.rapid-override.direct-value", + resetPin: "gmoccapy.rapid.reset-rapid-override", + countStateKey: "rapidOverrideCounts", + countEnableStateKey: "rapidOverrideCountEnabled", + analogEnableStateKey: "rapidOverrideAnalogEnabled", + stateDomain: "feed", + stateKey: "rapidOverride", + min: 0, + max: 200, + scale: 1, + }, + spindle: { + countPin: "gmoccapy.spindle.spindle-override.counts", + countEnablePin: "gmoccapy.spindle.spindle-override.count-enable", + analogEnablePin: "gmoccapy.spindle.spindle-override.analog-enable", + directValuePin: "gmoccapy.spindle.spindle-override.direct-value", + resetPin: "gmoccapy.spindle.reset-spindle-override", + countStateKey: "spindleOverrideCounts", + countEnableStateKey: "spindleOverrideCountEnabled", + analogEnableStateKey: "spindleOverrideAnalogEnabled", + stateDomain: "spindle", + stateKey: "override", + min: 0, + max: 150, + scale: 1, + }, + jogVelocity: { + countPin: "gmoccapy.jog.jog-velocity.counts", + countEnablePin: "gmoccapy.jog.jog-velocity.count-enable", + analogEnablePin: "gmoccapy.jog.jog-velocity.analog-enable", + directValuePin: "gmoccapy.jog.jog-velocity.direct-value", + countStateKey: "jogVelocityCounts", + countEnableStateKey: "jogVelocityCountEnabled", + analogEnableStateKey: "jogVelocityAnalogEnabled", + stateDomain: "gmoccapyGui", + stateKey: "jogVelocity", + min: 0, + max: 14040, + scale: 140.4, + }, +}; + +function applyGmoccapyHalPinPatch(state, action, model = gmoccapyHalModel) { + const pin = normalizeGmoccapyHalPin(action.pin); + const value = action.value; + if (!pin) { + return { applied: false, operatorMessage: "gmoccapy HAL pin blocked: missing pin" }; + } + if (!isKnownGmoccapyHalPin(pin, model)) { + return { applied: false, operatorMessage: `gmoccapy HAL pin unmapped: ${pin}` }; + } + + if (pin === "gmoccapy.ignore-limits") { + const enabled = Boolean(value); + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + ignoreLimits: enabled, + lastHalPinEffect: enabled + ? "chk_ignore_limits set active; command.override_limits() requested" + : "chk_ignore_limits cleared", + }), + operatorMessage: enabled ? "gmoccapy HAL ignore-limits requested" : "gmoccapy HAL ignore-limits cleared", + }, + }; + } + + if (pin === "gmoccapy.optional-stop") { + const enabled = Boolean(value); + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + optionalBlocks: enabled, + lastHalPinEffect: "optional-stop pin drives tbtn_optional_blocks -> set_block_delete", + }), + operatorMessage: `gmoccapy HAL optional-stop -> block delete ${enabled ? "on" : "off"}`, + }, + }; + } + + if (pin === "gmoccapy.blockdelete") { + const enabled = Boolean(value); + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + optionalStop: enabled, + lastHalPinEffect: "blockdelete pin drives command.set_optional_stop", + }), + operatorMessage: `gmoccapy HAL blockdelete -> optional stop ${enabled ? "on" : "off"}`, + }, + }; + } + + const jogAxisPin = resolveGmoccapyJogAxisPin(pin, model); + if (jogAxisPin) { + return applyGmoccapyJogAxisHalPatch(state, pin, value, jogAxisPin); + } + + const jogIncrement = resolveGmoccapyJogIncrementPin(pin, model); + if (jogIncrement) { + return applyGmoccapyJogIncrementHalPatch(state, pin, value, jogIncrement); + } + + if (pin === "gmoccapy.jog.turtle-jog") { + const enabled = Boolean(value); + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + turtleJog: enabled, + lastHalPinEffect: `turtle jog ${enabled ? "enabled" : "disabled"} by level-driven pin`, + }), + operatorMessage: `gmoccapy HAL turtle jog ${enabled ? "on" : "off"}`, + }, + }; + } + + if (pin === "gmoccapy.delete-message") { + if (!value) { + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + lastHalPinEffect: "delete-message falling edge ignored", + }), + operatorMessage: "gmoccapy HAL delete-message falling edge ignored", + }, + }; + } + const hadError = state.gmoccapyGui?.error === true || /error|blocked|warning/i.test(state.operatorMessage || ""); + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + error: false, + deletedMessageCount: Number(state.gmoccapyGui?.deletedMessageCount || 0) + 1, + lastHalPinEffect: hadError + ? "delete-message removed first alert and cleared gmoccapy.error" + : "delete-message deleted last notification", + }), + operatorMessage: hadError + ? "gmoccapy HAL delete-message cleared alert" + : "gmoccapy HAL delete-message deleted last message", + }, + }; + } + + if (pin === "gmoccapy.warning-confirm") { + const enabled = Boolean(value); + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + warningConfirm: enabled, + lastHalPinEffect: enabled + ? "warning-confirm level would accept active warning dialog" + : "warning-confirm level cleared", + }), + operatorMessage: enabled + ? "gmoccapy HAL warning-confirm asserted" + : "gmoccapy HAL warning-confirm cleared", + }, + }; + } + + const overrideTarget = findOverrideTargetForPin(pin); + if (overrideTarget) { + return applyGmoccapyOverrideHalPatch(state, pin, value, overrideTarget); + } + + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + lastHalPinEffect: "diagnostic-only gmoccapy HAL pin", + }), + operatorMessage: `gmoccapy HAL diagnostic-only: ${pin}`, + }, + }; +} + +function normalizeGmoccapyHalPin(pin) { + const value = String(pin || "").trim(); + if (!value) return ""; + return value.startsWith("gmoccapy.") ? value : `gmoccapy.${value}`; +} + +function isKnownGmoccapyHalPin(pin, model = gmoccapyHalModel) { + return model.nativePins.some((group) => group.pins.includes(pin)); +} + +function halGuiPatch(state, pin, value, patch = {}) { + return { + ...state.gmoccapyGui, + ...patch, + lastHalPin: pin, + lastHalPinValue: value, + }; +} + +function findOverrideTargetForPin(pin) { + return Object.entries(GMOCAPY_OVERRIDE_TARGETS) + .find(([, target]) => [ + target.countPin, + target.countEnablePin, + target.analogEnablePin, + target.directValuePin, + target.resetPin, + ].includes(pin)) || null; +} + +function resolveGmoccapyJogAxisPin(pin, model = gmoccapyHalModel) { + for (const entry of model.halPinActions?.jogPins?.axes || []) { + if (pin === entry.plus) return { axis: entry.axis, direction: 1 }; + if (pin === entry.minus) return { axis: entry.axis, direction: -1 }; + } + return null; +} + +function resolveGmoccapyJogIncrementPin(pin, model = gmoccapyHalModel) { + return (model.halPinActions?.jogPins?.increments || []).find((entry) => entry.pin === pin) || null; +} + +function applyGmoccapyJogAxisHalPatch(state, pin, value, jogAxisPin) { + const axis = String(jogAxisPin.axis || "").toLowerCase(); + const direction = Number(jogAxisPin.direction || 1); + const axisLabel = axis.toUpperCase(); + const sign = direction > 0 ? "+" : "-"; + if (!value) { + const isActivePin = state.gmoccapyGui?.activeJogPin === pin; + return { + applied: true, + patch: { + runState: isActivePin && state.runState === "jogging" ? "idle" : state.runState, + gmoccapyGui: halGuiPatch(state, pin, value, { + activeJogPin: isActivePin ? null : state.gmoccapyGui?.activeJogPin || null, + lastHalPinEffect: `jog ${axisLabel}${sign} released`, + }), + operatorMessage: `gmoccapy HAL jog ${axisLabel}${sign} released`, + }, + }; + } + + const gate = gateLinuxCncTaskAction(state, { type: "JOG", axis, direction }); + if (!gate.allowed) { + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + lastHalPinEffect: `jog ${axisLabel}${sign} ignored: ${gate.operatorMessage}`, + }), + operatorMessage: `gmoccapy HAL jog blocked: ${gate.operatorMessage}`, + }, + }; + } + + const increment = Number(state.gmoccapyGui?.jogIncrementOutput ?? state.machine?.jogIncrement ?? 0); + const continuous = Math.abs(increment) <= 0; + const nextAxisPose = continuous + ? state.axisPose + : { + ...state.axisPose, + [axis]: Number(state.axisPose?.[axis] || 0) + direction * increment, + }; + return { + applied: true, + patch: { + machine: { + ...state.machine, + mode: "manual", + jogAxis: axis, + jogIncrement: increment, + }, + axisPose: nextAxisPose, + runState: "jogging", + gmoccapyGui: halGuiPatch(state, pin, value, { + activeJogPin: pin, + lastHalPinEffect: continuous + ? `jog ${axisLabel}${sign} continuous press` + : `jog ${axisLabel}${sign} incremental ${increment}`, + }), + operatorMessage: continuous + ? `gmoccapy HAL jog ${axisLabel}${sign} continuous` + : `gmoccapy HAL jog ${axisLabel}${sign} ${increment}`, + }, + }; +} + +function applyGmoccapyJogIncrementHalPatch(state, pin, value, jogIncrement) { + if (!value) { + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + lastHalPinEffect: `jog increment ${jogIncrement.index} falling edge ignored`, + }), + operatorMessage: "gmoccapy HAL jog increment falling edge ignored", + }, + }; + } + + const distance = Number(jogIncrement.distance || 0); + const label = String(jogIncrement.label || `increment ${jogIncrement.index}`); + return { + applied: true, + patch: { + machine: { + ...state.machine, + jogIncrement: distance, + }, + runState: state.machine.mode === "manual" && state.runState === "jogging" ? "idle" : state.runState, + gmoccapyGui: halGuiPatch(state, pin, value, { + jogIncrementIndex: Number(jogIncrement.index), + jogIncrementLabel: label, + jogIncrementOutput: distance, + activeJogPin: null, + lastHalPinEffect: `jog increment selected ${label}; gmoccapy.jog.jog-increment=${distance}`, + }), + operatorMessage: `gmoccapy HAL jog increment ${label}`, + }, + }; +} + +function applyGmoccapyOverrideHalPatch(state, pin, value, [targetName, target]) { + if (pin === target.countEnablePin || pin === target.analogEnablePin) { + const enabled = Boolean(value); + const key = pin === target.countEnablePin ? target.countEnableStateKey : target.analogEnableStateKey; + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + [key]: enabled, + lastHalPinEffect: `${targetName} ${pin === target.countEnablePin ? "count" : "analog"} input ${enabled ? "enabled" : "disabled"}`, + }), + operatorMessage: `gmoccapy HAL ${targetName} ${pin === target.countEnablePin ? "counts" : "analog"} ${enabled ? "enabled" : "disabled"}`, + }, + }; + } + + if (pin === target.resetPin) { + if (!value) { + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + lastHalPinEffect: `${targetName} reset falling edge ignored`, + }), + operatorMessage: `gmoccapy HAL ${targetName} reset falling edge ignored`, + }, + }; + } + return { + applied: true, + patch: resetOverridePatchForTarget(state, targetName, { pin, value }), + }; + } + + if (pin === target.directValuePin) { + if (!state.gmoccapyGui?.[target.analogEnableStateKey]) { + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + lastHalPinEffect: `${targetName} direct-value ignored until analog-enable is true`, + }), + operatorMessage: `gmoccapy HAL ${targetName} direct-value ignored: analog disabled`, + }, + }; + } + const normalized = clampNumber(Number(value), 0, 1); + const nextValue = target.min + (target.max - target.min) * normalized; + return overrideValuePatch(state, targetName, target, nextValue, { + pin, + value, + effect: `${targetName} direct-value ${normalized}`, + operatorMessage: `gmoccapy HAL ${targetName} direct-value ${Math.round(nextValue)}`, + }); + } + + if (pin === target.countPin) { + const counts = Number(value); + if (!Number.isFinite(counts)) { + return { applied: false, operatorMessage: `gmoccapy HAL ${targetName} counts invalid: ${value}` }; + } + if (!state.gmoccapyGui?.[target.countEnableStateKey]) { + return { + applied: true, + patch: { + gmoccapyGui: halGuiPatch(state, pin, value, { + [target.countStateKey]: counts, + lastHalPinEffect: `${targetName} counts synchronized while count-enable is false`, + }), + operatorMessage: `gmoccapy HAL ${targetName} counts synchronized`, + }, + }; + } + const previousCounts = Number(state.gmoccapyGui?.[target.countStateKey] || 0); + const delta = (counts - previousCounts) * target.scale; + const currentValue = currentOverrideValue(state, target); + return overrideValuePatch(state, targetName, target, currentValue + delta, { + pin, + value, + countPatch: { [target.countStateKey]: counts }, + effect: `${targetName} counts delta ${delta}`, + operatorMessage: `gmoccapy HAL ${targetName} counts adjusted`, + }); + } + + return { applied: false, operatorMessage: `gmoccapy HAL override pin unmapped: ${pin}` }; +} + +function overrideValuePatch(state, targetName, target, rawValue, { + pin, + value, + countPatch = {}, + effect, + operatorMessage, +} = {}) { + const nextValue = clampPercent(rawValue, target.min, target.max); + const gmoccapyGui = halGuiPatch(state, pin, value, { + ...countPatch, + lastHalPinEffect: effect, + }); + if (target.stateDomain === "feed") { + return { + applied: true, + patch: { + feed: { + ...state.feed, + [target.stateKey]: Math.round(nextValue), + }, + gmoccapyGui, + operatorMessage, + }, + }; + } + if (target.stateDomain === "spindle") { + return { + applied: true, + patch: { + spindle: { + ...state.spindle, + [target.stateKey]: Math.round(nextValue), + }, + gmoccapyGui, + operatorMessage, + }, + }; + } + return { + applied: true, + patch: { + gmoccapyGui: { + ...gmoccapyGui, + [target.stateKey]: Number(nextValue.toFixed(3)), + }, + operatorMessage, + }, + }; +} + +function currentOverrideValue(state, target) { + if (target.stateDomain === "feed") return Number(state.feed?.[target.stateKey] || 0); + if (target.stateDomain === "spindle") return Number(state.spindle?.[target.stateKey] || 0); + return Number(state.gmoccapyGui?.[target.stateKey] || 0); +} + +function resetOverridePatchForTarget(state, targetName, hal = {}) { + const target = GMOCAPY_OVERRIDE_TARGETS[targetName]; + if (!target || targetName === "jogVelocity") { + return { + gmoccapyGui: halGuiPatch(state, hal.pin || null, hal.value ?? null, { + lastHalPinEffect: `reset unsupported for ${targetName}`, + }), + operatorMessage: `override reset unsupported: ${targetName}`, + }; + } + const effect = `${targetName} override reset to 100`; + const gmoccapyGui = halGuiPatch(state, hal.pin || null, hal.value ?? null, { + lastHalPinEffect: hal.pin ? `HAL ${effect}` : effect, + }); + if (target.stateDomain === "spindle") { + return { + spindle: { + ...state.spindle, + [target.stateKey]: 100, + }, + gmoccapyGui, + operatorMessage: hal.pin ? `gmoccapy HAL ${targetName} reset to 100` : `${targetName} override reset`, + }; + } + return { + feed: { + ...state.feed, + [target.stateKey]: 100, + }, + gmoccapyGui, + operatorMessage: hal.pin ? `gmoccapy HAL ${targetName} reset to 100` : `${targetName} override reset`, + }; +} + +function clampNumber(value, min, max) { + if (!Number.isFinite(value)) return min; + return Math.min(Math.max(value, min), max); +} + function buildDroFromFrame(frame, runtimeFeedback = null) { const dtg = runtimeFeedback?.dtg || null; return { diff --git a/web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css b/web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css index 902ac1c..a72e239 100644 --- a/web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css +++ b/web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css @@ -735,7 +735,7 @@ button[data-icon-name] span { grid-area: override; display: grid; grid-template-columns: 1fr; - grid-template-rows: repeat(3, minmax(0, 1fr)); + grid-template-rows: repeat(4, minmax(0, 1fr)); gap: 6px; padding: 6px; border-top: 2px solid var(--border); @@ -806,7 +806,7 @@ button[data-icon-name] span { .stepper { display: grid; - grid-template-columns: 30px minmax(54px, 1fr) 30px; + grid-template-columns: 30px minmax(48px, 1fr) 30px 36px; gap: 2px; align-items: center; margin-top: 1px; @@ -827,6 +827,42 @@ button[data-icon-name] span { min-width: 0; min-height: 26px; padding: 2px 4px; + font-size: 12px; +} + +.toggle-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 3px; + min-width: 0; +} + +.toggle-row button { + min-width: 0; + min-height: 24px; + padding: 2px 3px; + overflow: hidden; + font-size: 11px; + font-weight: 700; + text-overflow: ellipsis; + white-space: nowrap; +} + +.toggle-row button[data-active="true"] { + background: #ffd25f; + border-color: #a76e00; +} + +.status-chip { + min-width: 0; + overflow: hidden; + padding: 2px 4px; + background: #ebe6dc; + border: 1px solid #c6bfb3; + color: #4f4a42; + font: 10px/1.2 "Courier New", monospace; + text-overflow: ellipsis; + white-space: nowrap; } .spindle-coolant-panel { diff --git a/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js b/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js index 45444b4..e4879a4 100644 --- a/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js +++ b/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js @@ -353,13 +353,13 @@ function renderSidebar(element, state, dispatch) { const manualGate = gateLinuxCncTaskAction(state, { type: "SET_MODE", mode: "manual" }); const mdiGate = gateLinuxCncTaskAction(state, { type: "SET_MODE", mode: "mdi" }); element.innerHTML = ` - ${iconButtonHtml({ buttonId: "tbtn_estop", action: "estop", label: "E-STOP", title: "Estop the machine / F1", className: "sidebar-button estop", active: state.machine.estopActive })} - ${iconButtonHtml({ buttonId: "tbtn_on", action: "power", label: "POWER", title: "Turn the machine on/off / F2", className: "sidebar-button power", active: state.machine.powerOn })} + ${iconButtonHtml({ buttonId: "tbtn_estop", action: "estop", label: "E-STOP", title: "Estop the machine / F1", className: "sidebar-button estop", active: state.machine.estopActive, attrs: nativeButtonAttrs("v", 0) })} + ${iconButtonHtml({ buttonId: "tbtn_on", action: "power", label: "POWER", title: "Turn the machine on/off / F2", className: "sidebar-button power", active: state.machine.powerOn, attrs: nativeButtonAttrs("v", 1) })} ${iconButtonHtml({ buttonId: "tbtn_setup", action: "reset", label: "RESET", title: "Reset estop and return machine off", className: "sidebar-button", active: false })} - ${iconButtonHtml({ buttonId: "rbt_auto", action: "mode-auto", label: "AUTO", title: autoGate.operatorMessage || "Enter auto mode", className: "sidebar-button", active: state.machine.mode === "auto", gate: autoGate })} - ${iconButtonHtml({ buttonId: "rbt_manual", action: "mode-manual", label: "MANUAL", title: manualGate.operatorMessage || "Enter manual mode", className: "sidebar-button", active: state.machine.mode === "manual", gate: manualGate })} - ${iconButtonHtml({ buttonId: "tbtn_user_tabs", action: "mode-jog", label: "JOG", title: manualGate.operatorMessage || "Manual jog page", className: "sidebar-button", active: state.machine.mode === "manual", gate: manualGate })} - ${iconButtonHtml({ buttonId: "rbt_mdi", action: "mode-mdi", label: "MDI", title: mdiGate.operatorMessage || "Enter MDI mode", className: "sidebar-button", active: state.machine.mode === "mdi", gate: mdiGate })} + ${iconButtonHtml({ buttonId: "rbt_auto", action: "mode-auto", label: "AUTO", title: autoGate.operatorMessage || "Enter auto mode", className: "sidebar-button", active: state.machine.mode === "auto", gate: autoGate, attrs: nativeButtonAttrs("v", 4) })} + ${iconButtonHtml({ buttonId: "rbt_manual", action: "mode-manual", label: "MANUAL", title: manualGate.operatorMessage || "Enter manual mode", className: "sidebar-button", active: state.machine.mode === "manual", gate: manualGate, attrs: nativeButtonAttrs("v", 2) })} + ${iconButtonHtml({ buttonId: "tbtn_user_tabs", action: "mode-jog", label: "JOG", title: manualGate.operatorMessage || "Manual jog page", className: "sidebar-button", active: state.machine.mode === "manual", gate: manualGate, attrs: nativeButtonAttrs("v", 5) })} + ${iconButtonHtml({ buttonId: "rbt_mdi", action: "mode-mdi", label: "MDI", title: mdiGate.operatorMessage || "Enter MDI mode", className: "sidebar-button", active: state.machine.mode === "mdi", gate: mdiGate, attrs: nativeButtonAttrs("v", 3) })} @@ -449,8 +449,14 @@ function renderInfoTabs(element, state) {
HAL model:
${gmoccapyHalModel.semanticBoundary}
HAL files:
${halSummary.halFileCount} files / POSTGUI ${halSummary.postguiRequiresHalcompReady ? "after halcomp.ready" : "pending"}
HAL pins:
${halSummary.nativePinGroupCount} groups / ${halSummary.nativePinCount} pins
+
Hard buttons:
${formatGmoccapyHardButtons(halSummary)}
+
HAL jog:
${formatGmoccapyJogInputs(halSummary, state)}
+
HAL inputs:
${formatGmoccapyHalInputs(halSummary, state)}
+
HAL overrides:
${formatGmoccapyOverrideInputs(halSummary, state)}
+
HAL messages:
${formatGmoccapyMessageInputs(halSummary, state)}
postgui nets:
${gmoccapyHalModel.postguiNets.map((net) => net.signal).join(" / ")}
Tool loop:
${halSummary.hasToolChangeSimulationLoop ? "simulated tool-change loop" : "pending"}
+
Tool change:
${formatGmoccapyToolChange(halSummary)}
Operator:
${escapeHtml(state.operatorMessage)}
`; @@ -597,6 +603,15 @@ function renderOverride(element, state, dispatch) { ${overrideControl("Rapid Override", "rapid", state.feed.rapidOverride)} ${overrideControl("Feed Rate", "feed", state.feed.feedOverride, `F ${state.feed.feedRate}`)} +
+

HAL Inputs

+
+ + + +
+
${escapeHtml(formatGmoccapyHalLastInput(state))}
+
`; for (const target of ["rapid", "feed"]) { @@ -606,7 +621,22 @@ function renderOverride(element, state, dispatch) { element.querySelector(`[data-action="${target}-override-up"]`).addEventListener("click", () => { dispatch({ type: "ADJUST_OVERRIDE", target, delta: 10 }); }); + const reset = element.querySelector(`[data-action="${target}-override-reset"]`); + if (reset) { + reset.addEventListener("click", () => { + dispatch({ type: "RESET_OVERRIDE", target }); + }); + } } + element.querySelector('[data-action="ignore-limits"]').addEventListener("click", () => { + dispatch({ type: "SET_IGNORE_LIMITS", enabled: !state.gmoccapyGui.ignoreLimits }); + }); + element.querySelector('[data-action="block-delete"]').addEventListener("click", () => { + dispatch({ type: "SET_BLOCK_DELETE", enabled: !state.gmoccapyGui.optionalBlocks }); + }); + element.querySelector('[data-action="optional-stop"]').addEventListener("click", () => { + dispatch({ type: "SET_OPTIONAL_STOP", enabled: !state.gmoccapyGui.optionalStop }); + }); } function overrideControl(label, target, value, prefix = "") { @@ -618,6 +648,7 @@ function overrideControl(label, target, value, prefix = "") {
${value} %
+ `; @@ -647,6 +678,7 @@ function renderSpindleCoolant(element, state, dispatch) {
${state.spindle.override} %
+
@@ -664,6 +696,9 @@ function renderSpindleCoolant(element, state, dispatch) { element.querySelector('[data-action="spindle-override-up"]').addEventListener("click", () => { dispatch({ type: "ADJUST_SPINDLE_OVERRIDE", delta: 10 }); }); + element.querySelector('[data-action="spindle-override-reset"]').addEventListener("click", () => { + dispatch({ type: "RESET_OVERRIDE", target: "spindle" }); + }); element.querySelector('[data-action="spindle-forward"]').addEventListener("click", () => { dispatch({ type: "SET_SPINDLE_DIRECTION", direction: "forward" }); }); @@ -716,7 +751,7 @@ function renderBottomControls(element, state, dispatch) { title: gate.allowed ? label : gate.operatorMessage || "blocked", active: bottomControlActive(state, action), disabled: Boolean(disabled), - attrs: blockedAttrs, + attrs: `${blockedAttrs}${bottomNativeButtonAttrs(action)}`, }); }) .join("")} @@ -785,6 +820,17 @@ function bottomIconButtonId(action) { }[action] || "btn_load"; } +function bottomNativeButtonAttrs(action) { + if (action === "HOME") return nativeButtonAttrs("h", 0); + return ""; +} + +function nativeButtonAttrs(location, index) { + const prefix = location === "v" ? "v-button" : "h-button"; + const panel = location === "v" ? "right" : "bottom"; + return ` data-gmoccapy-hal-pin="gmoccapy.${prefix}.button-${index}" data-gmoccapy-native-panel="${panel}" data-gmoccapy-native-button-index="${index}"`; +} + function bottomControlActive(state, action) { if (action === "PAUSE") return state.runState === "paused"; if (action === "RUN" || action === "RESUME") return state.runState === "running"; @@ -855,6 +901,79 @@ function formatGmoccapyActionMapping(actionId) { return `${mapping.nativeCommand} / ${mapping.gate}`; } +function formatGmoccapyToolChange(halSummary) { + const manualPins = halSummary.manualToolChangeGuiConnected ? "manual GUI pins connected" : "manual GUI pins disconnected"; + return [ + halSummary.toolChangeStrategy, + manualPins, + `${halSummary.postguiToolUnlinkCount} postgui unlinkp`, + `${halSummary.commentedManualToolChangeNetCount} commented manual nets`, + ].join(" / "); +} + +function formatGmoccapyHalInputs(halSummary, state) { + return [ + `${halSummary.operatorInputPinCount} operator pins`, + halSummary.optionalStopPinCrossesToBlockDelete ? "optional-stop -> block delete" : "optional-stop direct", + halSummary.blockdeletePinCrossesToOptionalStop ? "blockdelete -> optional stop" : "blockdelete direct", + `ignore-limits ${state.gmoccapyGui.ignoreLimits ? "on" : "off"}`, + `block ${state.gmoccapyGui.optionalBlocks ? "on" : "off"}`, + `M1 ${state.gmoccapyGui.optionalStop ? "on" : "off"}`, + ].join(" / "); +} + +function formatGmoccapyJogInputs(halSummary, state) { + return [ + `${halSummary.jogAxisPinCount} axis jog pins`, + `${halSummary.jogIncrementPinCount} jog-inc pins`, + halSummary.jogAxisPinsPressRelease ? "press/release level" : "edge jog", + halSummary.jogIncrementPinsRisingEdgeOnly ? "jog-inc rising-edge-only" : "jog-inc level", + halSummary.jogIncrementZeroIsContinuous ? "jog-inc-0 continuous" : "jog-inc-0 incremental", + halSummary.turtleJogLevelDriven ? "turtle level-driven" : "turtle pending", + `active ${state.gmoccapyGui.activeJogPin || "none"}`, + `increment ${state.gmoccapyGui.jogIncrementLabel}=${state.gmoccapyGui.jogIncrementOutput}`, + ].join(" / "); +} + +function formatGmoccapyOverrideInputs(halSummary, state) { + return [ + `${halSummary.overridePinTargetCount} override targets`, + halSummary.countInputRequiresEnable ? "counts require enable" : "counts direct", + halSummary.directValueRequiresAnalogEnable ? "direct-value requires analog-enable" : "direct-value direct", + halSummary.resetPinsRisingEdgeOnly ? "reset rising-edge-only" : "reset level", + `last ${formatGmoccapyHalLastInput(state)}`, + ].join(" / "); +} + +function formatGmoccapyMessageInputs(halSummary, state) { + return [ + `${halSummary.messageInputPinCount} message pins`, + halSummary.deleteMessageRisingEdgeOnly ? "delete-message rising-edge-only" : "delete-message level", + halSummary.warningConfirmLevelPolled ? "warning-confirm level-polled" : "warning-confirm edge", + `error ${state.gmoccapyGui.error ? "on" : "off"}`, + `deleted ${state.gmoccapyGui.deletedMessageCount}`, + `confirm ${state.gmoccapyGui.warningConfirm ? "on" : "off"}`, + ].join(" / "); +} + +function formatGmoccapyHalLastInput(state) { + if (!state.gmoccapyGui?.lastHalPin && !state.gmoccapyGui?.lastHalPinEffect) return "none"; + return [ + state.gmoccapyGui.lastHalPin || "web-control", + state.gmoccapyGui.lastHalPinEffect || "-", + ].join(" "); +} + +function formatGmoccapyHardButtons(halSummary) { + return [ + `${halSummary.hardwareButtonVerticalCount} right v-button pins`, + `${halSummary.hardwareButtonBottomMainCount} bottom h-button main refs`, + `${halSummary.mappedHardwareButtonCount} mapped Web buttons`, + halSummary.hardwareButtonEdge, + `insensitive ${halSummary.hardwareButtonInsensitiveTarget}`, + ].join(" / "); +} + function formatNumber(value, digits = 3) { return Number(value).toFixed(digits); } diff --git a/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md b/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md index 13ab4f9..5769564 100644 --- a/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md +++ b/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md @@ -5103,3 +5103,582 @@ Final push and verification: Outcome: - Backup-directory file `备忘/备忘3.txt` is now committed and pushed to cloud repository. - This final verification log entry was created after the push and requires one final log-only sync commit. + +--- + +## 2026-06-26 18:33 EDT - working5 gmoccapy XYZAB tool-change HAL semantics + +User request: +- Start work according to `/home/meswork/cnc_wams/work/working5`. + +Initial context and status: +- Read `work/working5/README.md`, `01-项目功能内容.md`, `02-项目程序开发详细步骤.md`, `03-推进台账.md`, `04-任务矩阵.md`, `05-验收证据.md`, and `06-决策记录.md`. +- Read `work/working3/gmoccapy_XYZAB_execution_analysis.md`. +- Checked repository state: + - existing unrelated deletion remained present: `D linuxcnc.zip`. + - this deletion was not reverted, staged, or otherwise modified. + +Baseline/source inspection: +- Inspected Web project files: + - `app/src/state/linuxcnc-task-policy.js` + - `app/src/state/store.js` + - `app/src/runtime/gmoccapy-hal-model.js` + - `app/src/runtime/gmoccapy-communication-model.js` + - `app/src/profiles/gmoccapy-xyzab.js` + - `app/src/ui/gmoccapy-shell.js` + - related Node and browser smoke tests. +- Inspected native LinuxCNC reference files: + - `linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini` + - `linuxcnc/configs/sim/gmoccapy/core_sim_XYZAB.hal` + - `linuxcnc/configs/sim/gmoccapy/gmoccapy_postgui.hal` +- Key finding: + - `gmoccapy_postgui.hal` executes `unlinkp iocontrol.0.tool-change`, `unlinkp iocontrol.0.tool-changed`, and `unlinkp iocontrol.0.tool-prep-number`. + - the manual gmoccapy tool-change GUI nets for `gmoccapy.toolchange-change`, `gmoccapy.toolchange-changed`, and `gmoccapy.toolchange-number` are commented out. + - the active simulated tool-change completion path is `net tool-change-loop iocontrol.0.tool-change => iocontrol.0.tool-changed`. + +Implementation changes: +- Updated `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js`: + - added structured `toolChange` metadata. + - recorded strategy `iocontrol-loopback`. + - recorded `manualGmoccapyPinsConnected: false`. + - recorded M6/M61 remap references. + - recorded the three POSTGUI `unlinkp` pins. + - recorded commented manual GUI nets and the active postgui loopback. + - exposed tool-change summary fields through `createGmoccapyHalSummary()`. +- Updated `web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js`: + - added matching `hal.halcmd.toolChange` metadata to the profile. +- Updated `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`: + - added a `Tool change` diagnostic row under gmoccapy HAL diagnostics. + - displays strategy, manual GUI pin connection state, POSTGUI unlink count, and commented manual net count. +- Updated tests: + - `tests/node/verify_gmoccapy_hal_model.mjs` + - `tests/node/verify_gmoccapy_xyzab_profile.mjs` + - `tests/node/verify_profile_boundary.mjs` + - `tests/browser/gmoccapy_shell_smoke.html` +- Updated working5 records: + - `work/working5/03-推进台账.md` + - `work/working5/04-任务矩阵.md` + - `work/working5/05-验收证据.md` + - `work/working5/06-决策记录.md` + +Validation commands and results: +- Targeted Node tests: + - command: + - `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app` + - `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_profile_boundary.mjs` + - result: + - `gmoccapy_hal_model_smoke=ok` + - `gmoccapy_xyzab_profile_smoke=ok` + - `profile_boundary_smoke=ok` +- Full Node smoke: + - command: + - `npm run smoke:node` + - result: + - passed. + - included `linuxcnc_task_hal_runtime_smoke=ok`, `profile_boundary_smoke=ok`, `gmoccapy_xyzab_profile_smoke=ok`, `gmoccapy_hal_model_smoke=ok`, `gmoccapy_xyzab_gates_smoke=ok`, and `linear_unit_conversion_smoke=ok`. +- Build and browser smoke: + - command: + - `npm run build && npm run smoke` + - result: + - `gmoccapy_static_build=ok` + - `gmoccapy_shell_smoke=ok` + - `gmoccapy_dist_smoke=ok` +- Diff check: + - command: + - `git diff --check -- web-rtcp-5axis-sim-plan work/working5` + - result: + - passed with no output. + +Working tree after implementation: +- Modified intended files: + - `web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js` + - `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js` + - `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js` + - `web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs` + - `work/working5/03-推进台账.md` + - `work/working5/04-任务矩阵.md` + - `work/working5/05-验收证据.md` + - `work/working5/06-决策记录.md` + - `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` +- Existing unrelated item left untouched: + - `D linuxcnc.zip` + +Decision recorded: +- `DR-W5-003`: XYZAB tool change is modeled as POSTGUI `iocontrol` loopback, not as a manual gmoccapy tool-change dialog, because the native `gmoccapy_postgui.hal` disconnects the manual GUI pins and leaves those nets commented. + +Outcome: +- W5-160 added and marked complete. +- gmoccapy XYZAB profile, HAL model, UI diagnostics, Node tests, browser tests, and working5 records now explicitly capture the LinuxCNC XYZAB tool-change behavior. +- No commit or push was requested or performed. + +## 2026-06-26 20:15 EDT - ToDesk Ubuntu crash dialog handling + +User request: +- The desktop showed an Ubuntu crash report dialog saying "The application ToDesk has closed unexpectedly"; resolve it. + +Process log: +- Checked running ToDesk processes with `ps -ef | rg -i 'todesk|ToDesk'`. + - Found `/opt/todesk/bin/ToDesk_Service` running as root. + - The ToDesk foreground GUI process was not running initially. +- Checked systemd services. + - `todeskd.service` was loaded, enabled, and active/running. + - No `todesk.service` user or system unit existed. +- Checked installed package and files. + - `todesk 4.8.6.2` was installed. + - Binaries existed under `/opt/todesk/bin`. +- Checked crash files under `/var/crash`. + - Found `/var/crash/_opt_todesk_bin_ToDesk.1000.crash`. + - Other unrelated old crash reports for WeChat and LibreOffice were present and left untouched. +- Read the ToDesk crash report. + - Crash time: Fri Jun 26 20:06:38 2026. + - Executable: `/opt/todesk/bin/ToDesk`. + - Session tag included `wayland-session`. + - Signal: `SIGSEGV`. + - Crash title: `ToDesk crashed with SIGSEGV in html::element::determine_style()`. + - Stack top showed ToDesk internal UI/rendering functions: + - `html::element::determine_style(...)` + - `html::element::get_style() const` + - `html::element::attr_role() const` + - `html::behavior::window_frame_ctl::on(...)` +- Checked desktop/session state. + - Active session type was `wayland`. + - `DISPLAY=:0`, `WAYLAND_DISPLAY=wayland-0`, and user DBus was available. +- Checked ToDesk autostart entry. + - `/home/meswork/.config/autostart/todesk.desktop` launches ToDesk with: + - `LIBVA_DRIVER_NAME=iHD` + - `LIBVA_DRIVERS_PATH=/opt/todesk/bin` + - `GDK_BACKEND=x11` +- Confirmed Ubuntu crash dialog process. + - `apport-gtk` and `update-notifier-crash` were running. +- Attempted a combined cleanup/start command. + - The `pkill -f '/usr/share/apport/apport-gtk'` pattern also matched the active shell command, so that combined command exited early with code 143 before moving the crash file or relaunching ToDesk. + - Rechecked state afterward to avoid assuming success. +- Closed the Ubuntu crash dialog process. + - After the earlier `pkill`, `apport-gtk` was no longer running. +- Moved the ToDesk crash report out of `/var/crash`. + - Created `/home/meswork/.local/share/todesk/crash-backup`. + - Moved `_opt_todesk_bin_ToDesk.1000.crash` there with a timestamped filename. + - This prevents the same Ubuntu crash report dialog from reappearing for that crash. +- Relaunched ToDesk foreground GUI without restarting the daemon: + - `setsid env LIBVA_DRIVER_NAME=iHD LIBVA_DRIVERS_PATH=/opt/todesk/bin GDK_BACKEND=x11 DISPLAY=:0 XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus /opt/todesk/bin/ToDesk > /tmp/todesk-launch.log 2>&1 < /dev/null &` +- Observed relaunch. + - `/opt/todesk/bin/ToDesk` appeared as user `meswork`. + - `/opt/todesk/bin/ToDesk_Service` remained running as root. + - `/var/crash` no longer contained a ToDesk crash file. + - Launch log showed ToDesk extracting icon/image resources and then only `mkdir: cannot create directory '/tmp/fuse': File exists`; no fatal error appeared. +- Waited and rechecked stability. + - After about 20 seconds, both `ToDesk` and `ToDesk_Service` were still running. + - `todeskd.service` remained `active/running` with `NRestarts=0`. + - No new ToDesk crash file appeared in `/var/crash`. + - `apport-gtk` and `update-notifier-crash` were no longer running. + +Outcome: +- The Ubuntu crash report dialog was closed. +- The stale ToDesk crash report was moved out of `/var/crash`. +- ToDesk foreground GUI was relaunched successfully. +- ToDesk daemon was not restarted, reducing risk of disrupting the active remote session. +- Final observed state: + - `root ... /opt/todesk/bin/ToDesk_Service` + - `meswork ... /opt/todesk/bin/ToDesk` + - `/var/crash` contained no ToDesk crash report. + +## 2026-06-26 20:17 EDT - 将 GPT/Codex 过程日志语言规则写入 AGENTS.md + +用户请求: +- `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` 这个文件使用中文记录。 +- 将该要求写入 `AGENTS.md`。 + +执行过程: +- 读取 `/home/meswork/cnc_wams/AGENTS.md`,确认当前已有工作区范围和日志路径规则: + - 每次 GPT/Codex 执行完成后,将完整执行过程日志追加到 `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`。 +- 检查目标日志文件和 `AGENTS.md` 文件存在: + - `/home/meswork/cnc_wams/AGENTS.md` + - `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` +- 修改 `/home/meswork/cnc_wams/AGENTS.md`,在日志规则下追加: + - `The log content written to this file must be recorded in Chinese.` +- 按新的日志语言要求,将本次执行过程用中文追加到 `gpdlog.md`。 + +结果: +- 已将 `gpdlog.md` 使用中文记录的要求写入 `AGENTS.md`。 +- 从本次记录开始,日志内容按中文记录。 + +--- + +## 2026-06-26 R4 - working5 continuation: gmoccapy HAL hard-button pin mapping + +User request: +- Continue from `/home/meswork/cnc_wams/work/working5`. + +Context gathered: +- Read `work/working5/README.md`, `01-项目功能内容.md`, `02-项目程序开发详细步骤.md`, `03-推进台账.md`, `04-任务矩阵.md`, `05-验收证据.md`, and `06-决策记录.md`. +- Confirmed previous completed work through R3: mode gate fixes and XYZAB POSTGUI tool-change loopback semantics. +- Inspected LinuxCNC native references: + - `linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py` `_make_hal_pins()`, `_button_pin_changed()`, `_get_child_button()`. + - `linuxcnc/share/gmoccapy/gmoccapy.glade` `vbtb_main` order. + - `linuxcnc/configs/sim/gmoccapy/h_buttonlist.hal`, `v_buttonlist.hal`, and related glade button list examples. +- Inspected Web implementation and tests: + - `app/src/profiles/gmoccapy-xyzab.js` + - `app/src/runtime/gmoccapy-hal-model.js` + - `app/src/runtime/gmoccapy-communication-model.js` + - `app/src/state/store.js` + - `app/src/ui/gmoccapy-shell.js` + - `tests/node/verify_gmoccapy_hal_model.mjs` + - `tests/node/verify_gmoccapy_communication_model.mjs` + - `tests/node/verify_gmoccapy_xyzab_profile.mjs` + - `tests/node/verify_gmoccapy_xyzab_gates.mjs` + - `tests/browser/gmoccapy_shell_smoke.html` + +Findings: +- Native gmoccapy hard-button pins are created as `gmoccapy.h-button.button-0..9` and `gmoccapy.v-button.button-0..6`. +- `_button_pin_changed()` ignores falling edges and only handles `pin.get() == True`. +- `_get_child_button()` maps button numbers to current visible non-label GTK children, and ignores insensitive targets. +- Native `vbtb_main` order is `tbtn_estop`, `tbtn_on`, `rbt_manual`, `rbt_mdi`, `rbt_auto`, `tbtn_user_tabs`, `tbtn_setup`. +- Existing Web profile panel schema incorrectly listed Estop/Power as h-button pins and shifted Manual/MDI/Auto v-button indexes. + +Implementation changes: +- Updated `web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js`: + - corrected task/mode panel schema pin order to `gmoccapy.v-button.button-0..4` for Estop, Power, Manual, MDI, Auto. +- Updated `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js`: + - added structured `hardwareButtons` metadata. + - recorded native source methods `_button_pin_changed`, `_get_child_button`, `_make_hal_pins`. + - recorded rising-edge-only behavior, current visible child mapping, hidden-label skip behavior, and insensitive-target ignored behavior. + - mapped right-side v-button pins and selected bottom h-button references. + - added `resolveGmoccapyHardwareButton()`. + - extended `createGmoccapyHalSummary()` with hard-button counts and behavior fields. +- Updated `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js`: + - added hardware-button HAL path through `_button_pin_changed`. + - added `hardware-button` action mapping. + - added summary flag `hasNativeHardwareButtonPath`. +- Updated `web-rtcp-5axis-sim-plan/app/src/state/store.js`: + - added `GMOCAPY_HARDWARE_BUTTON` action. + - falling edges are ignored. + - mapped pins dispatch existing Web actions and reuse existing task policy gates. + - unmapped and diagnostic-only pins produce explicit operator messages. +- Updated `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`: + - added `data-gmoccapy-hal-pin`, `data-gmoccapy-native-panel`, and `data-gmoccapy-native-button-index` to mapped Web buttons. + - added `Hard buttons` gmoccapy HAL diagnostic row. +- Updated tests: + - `tests/node/verify_gmoccapy_hal_model.mjs` validates hard-button model and resolver. + - `tests/node/verify_gmoccapy_communication_model.mjs` validates hardware-button native path/action/summary. + - `tests/node/verify_gmoccapy_xyzab_profile.mjs` validates corrected profile panel pin order. + - `tests/node/verify_gmoccapy_xyzab_gates.mjs` validates `GMOCAPY_HARDWARE_BUTTON` falling edge, rising edge, gate, diagnostic-only, and unmapped behavior. + - `tests/browser/gmoccapy_shell_smoke.html` validates DOM pin attributes and hard-button diagnostic text. +- Updated working5 records: + - `work/working5/03-推进台账.md` + - `work/working5/04-任务矩阵.md` + - `work/working5/05-验收证据.md` + - `work/working5/06-决策记录.md` + +Validation commands and results: +- Targeted Node tests: + - command: + - `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app` + - `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs` + - result: + - `gmoccapy_hal_model_smoke=ok` + - `gmoccapy_communication_model_smoke=ok` + - `gmoccapy_xyzab_profile_smoke=ok` + - `gmoccapy_xyzab_gates_smoke=ok` +- Full Node smoke: + - command: + - `npm run smoke:node` + - result: + - passed. + - included `gmoccapy_xyzab_profile_smoke=ok`, `gmoccapy_communication_model_smoke=ok`, `gmoccapy_hal_model_smoke=ok`, `gmoccapy_xyzab_gates_smoke=ok`, and `linear_unit_conversion_smoke=ok`. +- Build and browser smoke: + - command: + - `npm run build && npm run smoke` + - result: + - `gmoccapy_static_build=ok` + - `gmoccapy_shell_smoke=ok` + - `gmoccapy_dist_smoke=ok` +- Diff check: + - command: + - `git diff --check -- web-rtcp-5axis-sim-plan work/working5` + - result: + - passed with no output. + +Working tree notes: +- Modified intended R4 files: + - `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js` + - `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js` + - `web-rtcp-5axis-sim-plan/app/src/state/store.js` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs` + - `web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html` + - `work/working5/03-推进台账.md` + - `work/working5/04-任务矩阵.md` + - `work/working5/05-验收证据.md` + - `work/working5/06-决策记录.md` + - `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` +- Also present from earlier R3 or pre-existing work: + - `web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js` includes both R3 tool-change metadata and R4 corrected v-button pin order. + - `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js` includes both R3 tool-change diagnostics and R4 hard-button DOM/diagnostics. + - `web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs` contains R3 tool-change boundary assertions. + - `D linuxcnc.zip` remains an unrelated pre-existing deletion and was not touched. + +Decision recorded: +- `DR-W5-004`: external HAL hard-button pins are represented through explicit pin-to-action mappings and `GMOCAPY_HARDWARE_BUTTON`, not by relying on Web DOM order. Native-only buttons not implemented in Web are diagnostic-only. + +Outcome: +- W5-170 added and marked complete. +- gmoccapy XYZAB external HAL hard-button pin behavior is now modeled, testable, visible in UI diagnostics, and covered by Node/build/browser smoke. +- No commit or push was requested or performed. + +## 2026-06-26 20:26 EDT - 取消旧日志中文转换 + +用户请求: +- 先要求将 `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` 以前的英文记录也转成中文。 +- 随后用户明确说明“不用转了”。 + +执行过程: +- 检查了 `gpdlog.md` 的规模和内容结构,确认文件约 5431 行,包含大量英文过程记录、命令、路径、测试输出和已存在的中文记录。 +- 原计划是保护命令、路径、代码块、URL 和测试输出,只转换叙述性日志文本。 +- 曾尝试安装本地离线翻译工具 `argostranslate`,第一次因下载超时失败;第二次安装进程因用户中断仍在后台运行。 +- 收到“不用转了”后,停止继续转换旧日志。 +- 检查后台进程,发现 `python3 -m pip install --user --timeout 120 --retries 5 --quiet argostranslate` 仍在运行。 +- 终止该后台安装进程,并确认没有剩余 `pip install` / `argostranslate` 相关进程。 + +结果: +- 未转换旧的 `gpdlog.md` 内容。 +- 未批量修改历史日志正文。 +- 已停止残留的离线翻译工具安装进程。 +- 仅按当前 `AGENTS.md` 规则,用中文追加本次取消转换的执行过程记录。 + +## 2026-06-27 - working5 R5:gmoccapy HAL 输入 pin 与倍率 pin 语义补齐 + +用户请求: +- 用户要求按 `/home/meswork/cnc_wams/work/working5` 开始工作。 + +执行过程: +- 读取 `/home/meswork/cnc_wams/AGENTS.md`,确认每轮执行结束后需要将完整过程日志用中文追加到 `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`。 +- 读取 `work/working5/README.md`、`01-项目功能内容.md`、`02-项目程序开发详细步骤.md`、`03-推进台账.md`、`04-任务矩阵.md`、`05-验收证据.md`、`06-决策记录.md`。 +- 确认 working5 已完成 R1 到 R4,下一步明确指向 `gmoccapy.ignore-limits`、`gmoccapy.optional-stop`、`gmoccapy.blockdelete`、override analog/count/reset pins 与 Web 状态的交叉行为。 +- 检查工作树状态,发现已有未提交改动和无关的 `linuxcnc.zip` 删除;按规则没有回退任何既有改动。 +- 使用 `rg` 搜索 Web 项目和 LinuxCNC 源码中与 `optional`、`blockdelete`、`ignore-limits`、`override`、`feed`、`rapid`、`spindle` 相关的实现。 +- 读取 `app/src/state/store.js`、`app/src/profiles/gmoccapy-xyzab.js`、`app/src/runtime/gmoccapy-hal-model.js`、`app/src/runtime/gmoccapy-communication-model.js`、`app/src/ui/gmoccapy-shell.js`、相关 Node/browser 测试。 +- 对照 `/home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py`,确认: + - `_ignore_limits()` 由 `gmoccapy.ignore-limits` 驱动 `chk_ignore_limits`,active 时通过 `on_chk_ignore_limits_toggled()` 调用 `command.override_limits()`。 + - `gmoccapy.optional-stop` 连接 `_optional_blocks()`,实际驱动 `tbtn_optional_blocks`,再调用 `command.set_block_delete()`。 + - `gmoccapy.blockdelete` 连接 `_blockdelete()`,实际调用 `command.set_optional_stop()`。 + - `_on_counts_changed()` 只有对应 `*.count-enable` 为 true 时才调整 feed/rapid/spindle/jog velocity 滑块,否则只同步 counts 基线。 + - `_on_analog_value_changed()` 只有对应 `*.analog-enable` 为 true 时才接受 `*.direct-value`。 + - `_reset_override()` 只在 pin 为 true 时复位 feed/rapid/spindle override。 +- 对照 `/home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.pref`,确认本配置默认 scale:`scale_feed_override = 1`、`scale_rapid_override = 1`、`scale_spindle_override = 1`、`scale_jog_vel = 140.4`。 + +实现修改: +- 修改 `app/src/state/store.js`: + - 引入 `gmoccapyHalModel`。 + - 新增 `gmoccapyGui` 状态,记录 `ignoreLimits`、`optionalBlocks`、`optionalStop`、override counts/enable/analog-enable、`jogVelocity` 和最后一次 HAL pin 效果。 + - 新增 `GMOCAPY_HAL_PIN` action。 + - 新增 HAL pin 解析、已知 pin 校验、operator input patch、override counts/direct-value/reset patch、100% reset 等 helper。 + - 实现 `ignore-limits`、`optional-stop -> block delete`、`blockdelete -> optional stop`、override count-enable、analog-enable、direct-value、reset rising-edge 语义。 + - 新增 `RESET_OVERRIDE`、`SET_BLOCK_DELETE`、`SET_OPTIONAL_STOP`、`SET_IGNORE_LIMITS` 等 Web action。 +- 修改 `app/src/profiles/gmoccapy-xyzab.js`: + - 在 `halPins` 中补充 feed/rapid/spindle/jog override counts、count-enable、analog-enable、direct-value、reset pins。 + - 补充 `gmoccapy.ignore-limits`、`gmoccapy.optional-stop`、`gmoccapy.blockdelete`。 +- 修改 `app/src/runtime/gmoccapy-hal-model.js`: + - 扩展 `override` pin 组。 + - 新增 `operator-inputs` pin 组。 + - 新增 `halPinActions`,记录 `_ignore_limits`、`_optional_blocks`、`_blockdelete`、`_on_counts_changed`、`_on_analog_value_changed`、`_reset_override` 等源码方法与 Web 映射。 + - 在 summary 中新增 operator input 数量、override target 数量、optional/blockdelete 交叉标志、count/direct/reset enable 规则。 +- 修改 `app/src/runtime/gmoccapy-communication-model.js`: + - 新增 `nativePaths.halPins`。 + - 新增 `hal-pin-input` action mapping,说明 `GMOCAPY_HAL_PIN` 与 gmoccapy HAL callback 的关系。 + - summary 新增 `hasNativeHalPinInputPath`。 +- 修改 `app/src/ui/gmoccapy-shell.js`: + - 在 info diagnostics 中新增 `data-gmoccapy-hal="operator-inputs"` 和 `data-gmoccapy-hal="override-inputs"` 行。 + - 在 override 面板新增 ignore limits、block delete、optional stop 和最后一次 HAL input 状态显示。 + - 为 feed/rapid/spindle override 增加 100% reset 控件。 + - 新增 HAL input/override 格式化函数。 +- 修改 `app/src/styles/gmoccapy.css`: + - 将 override 面板调整为 4 行。 + - 将 `.stepper` 扩展为 4 列以容纳 100% reset 按钮。 + - 新增 `.toggle-row` 和 `.status-chip` 样式,避免新增文本挤压或溢出。 +- 修改 Node 测试: + - `verify_gmoccapy_xyzab_gates.mjs` 覆盖 `GMOCAPY_HAL_PIN` 的 ignore-limits、optional-stop/blockdelete 交叉、counts/direct-value enable、reset rising-edge、jog velocity scale、unknown pin。 + - `verify_gmoccapy_hal_model.mjs` 覆盖 operator input 组、override pin 组和 summary 字段。 + - `verify_gmoccapy_communication_model.mjs` 覆盖 `halPins` path 和 `hal-pin-input` action mapping。 + - `verify_gmoccapy_xyzab_profile.mjs` 覆盖新增 profile halPins。 +- 修改浏览器测试 `tests/browser/gmoccapy_shell_smoke.html`: + - 覆盖 operator input/override diagnostic 文本。 + - 覆盖 feed/spindle 100% reset 按钮。 + - 覆盖 ignore limits、block delete、optional stop Web 控件状态。 + - 通过 `window.webRtcp5AxisSimulation.dispatch({ type: "GMOCAPY_HAL_PIN", ... })` 覆盖 optional/blockdelete 交叉 pin。 +- 更新 working5 文档: + - `03-推进台账.md` 追加 R5。 + - `04-任务矩阵.md` 新增并完成 W5-180。 + - `05-验收证据.md` 新增 EV-W5-180、EV-W5-181 和 R5 命令/断言摘要。 + - `06-决策记录.md` 新增 DR-W5-005。 + +验证命令与结果: +- 最小 Node 与浏览器回归: + - 命令: + - `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app` + - `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && bash ../tests/browser/verify_gmoccapy_shell_browser.sh` + - 结果: + - `gmoccapy_hal_model_smoke=ok` + - `gmoccapy_communication_model_smoke=ok` + - `gmoccapy_xyzab_profile_smoke=ok` + - `gmoccapy_xyzab_gates_smoke=ok` + - `gmoccapy_shell_smoke=ok` +- 完整 Node smoke: + - 命令: + - `npm run smoke:node` + - 结果: + - 通过。 + - 关键输出包含 `gmoccapy_xyzab_profile_smoke=ok`、`gmoccapy_communication_model_smoke=ok`、`gmoccapy_hal_model_smoke=ok`、`gmoccapy_xyzab_gates_smoke=ok`、`linear_unit_conversion_smoke=ok`。 +- 构建与浏览器 smoke: + - 命令: + - `npm run build && npm run smoke` + - 结果: + - `gmoccapy_static_build=ok` + - `gmoccapy_shell_smoke=ok` + - `gmoccapy_dist_smoke=ok` +- 空白检查: + - 命令: + - `git diff --check -- web-rtcp-5axis-sim-plan work/working5` + - 结果: + - 通过,无输出。 + +工作树备注: +- 本轮直接修改的实现和测试文件: + - `web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js` + - `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js` + - `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js` + - `web-rtcp-5axis-sim-plan/app/src/state/store.js` + - `web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css` + - `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js` + - `web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs` +- 本轮更新的工作记录文件: + - `work/working5/03-推进台账.md` + - `work/working5/04-任务矩阵.md` + - `work/working5/05-验收证据.md` + - `work/working5/06-决策记录.md` + - `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` +- 工作树中仍有先前存在或其他轮次留下的改动,例如 `AGENTS.md`、`linuxcnc.zip` 删除、`verify_profile_boundary.mjs` 等;本轮没有回退这些改动。 + +结果: +- W5-180 已完成。 +- Web 项目现在能按 gmoccapy 源码语义模拟 `ignore-limits`、`optional-stop`、`blockdelete` 和 override counts/direct-value/reset HAL input pins。 +- 新增行为已在 profile、HAL model、communication model、store、UI 和 Node/browser 测试中形成可追踪证据。 +- 没有执行 commit 或 push。 + +## 2026-06-27 R6 过程日志:继续完成 working5 jog 与消息 HAL pin 对标 + +用户请求: +- 接续上一轮,继续完成 `/home/meswork/cnc_wams/work/working5` 的工作。 + +现场恢复: +- 查看 `work/working5` 的 README、推进台账、任务矩阵、验收证据和决策记录。 +- 确认 R5 已完成 `ignore-limits`、`optional-stop`、`blockdelete` 与 override HAL input pin 语义。 +- 确认下一步为 `gmoccapy.jog.axis.*`、`gmoccapy.jog.jog-inc-*`、`gmoccapy.delete-message`、`gmoccapy.warning-confirm` 等剩余 HAL pin 与 Web 状态/消息系统的映射。 +- 查看工作树,注意到已有未提交改动包括 `AGENTS.md`、`linuxcnc.zip` 删除、`web-rtcp-5axis-sim-plan` 多个源码/测试文件和 `working5` 文档;本轮未回退这些已有改动。 + +源码对标: +- 读取 `linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py` 中 `_make_hal_pins()`、`_on_pin_jog_changed()`、`_on_pin_incr_changed()`、`_on_pin_turtle_jog()`、`_del_message_changed()` 等实现。 +- 读取 `linuxcnc/src/emc/usr_intf/gmoccapy/dialogs.py` 中 `warning_dialog()`,确认其每 100ms 轮询 `warning-confirm` confirm pin。 +- 读取 `linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini`,确认 `[DISPLAY] INCREMENTS = 1.000 mm, 0.100 mm, 0.010 mm, 0.001 mm ,1.2345 in`。 +- 确认 gmoccapy 会在 INI 增量前插入 continuous `rbt_0`,因此 XYZAB 的 jog increment HAL input 应覆盖 `jog-inc-0..5`,其中 `jog-inc-0` 为连续点动、距离 0,`jog-inc-5` 的 `1.2345 in` 在 mm 机床下为 31.3563 mm。 +- 确认 jog axis pin 与 hard-button pin 不同:hard-button 是 rising-edge-only,jog axis pin 是 true press、false release 的 level 行为。 + +实现修改: +- 修改 `app/src/profiles/gmoccapy-xyzab.js`: + - 新增 `gmoccapyXyzabJogAxisPins` 和 `gmoccapyXyzabJogIncrementPins`。 + - profile `halPins` 补齐 10 个 XYZAB axis jog pins、6 个 jog increment pins、`gmoccapy.jog.jog-increment`、`gmoccapy.jog.turtle-jog`、`gmoccapy.delete-message`、`gmoccapy.warning-confirm`。 +- 修改 `app/src/runtime/gmoccapy-hal-model.js`: + - jog pin group 增加 `jog-inc-1..5` 和 `jog.turtle-jog`。 + - `halPinActions.source.methods` 增加 `_on_pin_jog_changed`、`_on_pin_incr_changed`、`_del_message_changed`。 + - 新增 `jogPins` 结构,记录 5 个轴、10 个 axis jog pins、6 个增量 pin、output pin、turtle pin 和 native callback 语义。 + - 新增 `messagePins` 结构,记录 `delete-message`、`warning-confirm`、`error` 的 native callback 和 Web 状态。 + - summary 增加 `jogAxisPinCount`、`jogIncrementPinCount`、`messageInputPinCount`、`jogAxisPinsPressRelease`、`jogIncrementPinsRisingEdgeOnly`、`jogIncrementZeroIsContinuous`、`turtleJogLevelDriven`、`deleteMessageRisingEdgeOnly`、`warningConfirmLevelPolled`。 +- 修改 `app/src/runtime/gmoccapy-communication-model.js`: + - HAL input path 说明扩展到 jog、jog increment、message pins。 + - `hal-pin-input` action mapping 加入 `_del_message_changed` 和 delete-message rising-edge 规则。 + - 新增 `hal-jog-pin` action mapping,对应 `_on_pin_jog_changed/_on_pin_incr_changed`。 + - 新增 `hal-warning-confirm` action mapping,对应 `dialogs.warning_dialog` confirm pin polling。 +- 修改 `app/src/state/store.js`: + - `gmoccapyGui` 新增 `jogIncrementIndex`、`jogIncrementLabel`、`jogIncrementOutput`、`turtleJog`、`activeJogPin`、`warningConfirm`、`error`、`deletedMessageCount`。 + - 扩展 `GMOCAPY_HAL_PIN` 分发,处理 jog axis press/release、jog increment rising-edge、turtle jog level、delete-message true/falling edge、warning-confirm level。 + - 新增解析函数 `resolveGmoccapyJogAxisPin()`、`resolveGmoccapyJogIncrementPin()`。 + - 新增 `applyGmoccapyJogAxisHalPatch()` 和 `applyGmoccapyJogIncrementHalPatch()`。 + - jog axis HAL pin 使用 `gmoccapyGui.jogIncrementOutput` 作为 gmoccapy 的当前增量输出,避免把 Web 普通按钮默认 1mm 点动误用于 HAL continuous 默认状态。 +- 修改 `app/src/ui/gmoccapy-shell.js`: + - info diagnostics 新增 `data-gmoccapy-hal="jog-inputs"` 和 `data-gmoccapy-hal="message-inputs"`。 + - 新增 `formatGmoccapyJogInputs()` 和 `formatGmoccapyMessageInputs()`,显示 axis jog pin 数量、jog-inc 数量、press/release level、rising-edge-only、continuous、turtle、delete-message、warning-confirm 等语义。 + +测试修改: +- 修改 `tests/node/verify_gmoccapy_hal_model.mjs`: + - 覆盖 jog pin group、jogPins 结构、messagePins 结构和 summary 新字段。 +- 修改 `tests/node/verify_gmoccapy_xyzab_profile.mjs`: + - 覆盖新增 profile halPins:axis jog、jog-inc、jog-increment、turtle-jog、delete-message、warning-confirm。 +- 修改 `tests/node/verify_gmoccapy_communication_model.mjs`: + - 覆盖新增 `hal-jog-pin`、`hal-warning-confirm` action mappings。 + - summary action count 更新为 19。 +- 修改 `tests/node/verify_gmoccapy_xyzab_gates.mjs`: + - 覆盖 HAL jog 未上电 gate、continuous jog press/release、`jog-inc-2` falling edge 忽略和 rising edge 选择、`jog-inc-5` 英寸换算、turtle jog level。 + - 覆盖 `delete-message` falling edge 忽略、true 删除 alert/消息、`warning-confirm` true/false 状态。 +- 修改 `tests/browser/gmoccapy_shell_smoke.html`: + - 覆盖 `data-gmoccapy-hal="jog-inputs"` 和 `data-gmoccapy-hal="message-inputs"` 文本。 + - 通过公开 `window.webRtcp5AxisSimulation.dispatch({ type: "GMOCAPY_HAL_PIN", ... })` 验证浏览器内 jog-inc、axis jog press/release、turtle jog、delete-message、warning-confirm。 + - 在相关测试段显式 RESET 后重新 POWER 并切回 Manual,避免依赖前序状态。 + +工作文档更新: +- 修改 `work/working5/04-任务矩阵.md`:新增并完成 W5-190。 +- 修改 `work/working5/03-推进台账.md`:追加 2026-06-27 R6 记录。 +- 修改 `work/working5/05-验收证据.md`:新增 EV-W5-190、EV-W5-191 和 R6 命令/断言摘要。 +- 修改 `work/working5/06-决策记录.md`:新增 DR-W5-006,记录 jog/message HAL pin 按 gmoccapy 回调边界建模的原因和影响。 + +验证命令与结果: +- 最小模型/通信测试: + - 命令:`cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs` + - 结果:通过,输出 `gmoccapy_hal_model_smoke=ok`、`gmoccapy_communication_model_smoke=ok`。 +- profile/store 行为测试: + - 命令:`cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs` + - 结果:通过,输出 `gmoccapy_xyzab_profile_smoke=ok`、`gmoccapy_xyzab_gates_smoke=ok`。 +- 浏览器 shell smoke: + - 命令:`cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && bash ../tests/browser/verify_gmoccapy_shell_browser.sh` + - 结果:通过,输出 `gmoccapy_shell_smoke=ok`。 +- 完整 Node smoke: + - 命令:`cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run smoke:node` + - 结果:通过,关键输出包含 `gmoccapy_xyzab_profile_smoke=ok`、`gmoccapy_communication_model_smoke=ok`、`gmoccapy_hal_model_smoke=ok`、`gmoccapy_xyzab_gates_smoke=ok`、`linear_unit_conversion_smoke=ok`。 +- 构建和浏览器/dist smoke: + - 命令:`cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run build && npm run smoke` + - 结果:通过,输出 `gmoccapy_static_build=ok`、`gmoccapy_shell_smoke=ok`、`gmoccapy_dist_smoke=ok`。 +- 空白检查: + - 命令:`git diff --check -- web-rtcp-5axis-sim-plan work/working5` + - 结果:通过,无输出。 + +工作树备注: +- 本轮直接修改的实现/测试文件: + - `web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js` + - `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js` + - `web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js` + - `web-rtcp-5axis-sim-plan/app/src/state/store.js` + - `web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js` + - `web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs` + - `web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs` +- 本轮更新的 working5 文档: + - `work/working5/03-推进台账.md` + - `work/working5/04-任务矩阵.md` + - `work/working5/05-验收证据.md` + - `work/working5/06-决策记录.md` +- 工作树中仍有此前存在或其他轮次留下的改动,例如 `AGENTS.md`、`linuxcnc.zip` 删除、`web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css`、`verify_profile_boundary.mjs`、`备忘/备忘3.txt` 等;本轮没有回退这些改动。 + +结果: +- W5-190 已完成。 +- Web 项目现在能按 gmoccapy 源码语义模拟 jog axis、jog increment、turtle jog、delete-message 和 warning-confirm HAL input pins。 +- 新增行为已在 profile、HAL model、communication model、store、UI 和 Node/browser 测试中形成可追踪证据。 +- 没有执行 commit 或 push。 diff --git a/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html b/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html index a0ce0de..7452224 100644 --- a/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html +++ b/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html @@ -282,6 +282,25 @@ throw new Error(`missing gmoccapy icon ${selector} expected ${iconName}, got ${button?.dataset.iconName}`); } } + for (const [selector, pin, panel, index] of [ + ['[data-action="estop"]', "gmoccapy.v-button.button-0", "right", "0"], + ['[data-action="power"]', "gmoccapy.v-button.button-1", "right", "1"], + ['[data-action="mode-manual"]', "gmoccapy.v-button.button-2", "right", "2"], + ['[data-action="mode-mdi"]', "gmoccapy.v-button.button-3", "right", "3"], + ['[data-action="mode-auto"]', "gmoccapy.v-button.button-4", "right", "4"], + ['[data-action="mode-jog"]', "gmoccapy.v-button.button-5", "right", "5"], + ['[data-action="HOME"]', "gmoccapy.h-button.button-0", "bottom", "0"], + ]) { + const button = doc.querySelector(selector); + if ( + !button || + button.dataset.gmoccapyHalPin !== pin || + button.dataset.gmoccapyNativePanel !== panel || + button.dataset.gmoccapyNativeButtonIndex !== index + ) { + throw new Error(`gmoccapy hard-button mapping mismatch ${selector}: ${JSON.stringify(button?.dataset)}`); + } + } if (!doc.querySelector('[data-gmoccapy-comm="boundary"]')?.textContent.includes("native_gmoccapy_nml_hal_reference")) { throw new Error("missing gmoccapy communication boundary diagnostic"); } @@ -294,6 +313,60 @@ if (!doc.querySelector('[data-gmoccapy-hal="postgui"]')?.textContent.includes("tool-change-loop")) { throw new Error("missing gmoccapy HAL postgui diagnostic"); } + const halInputDiagnostic = doc.querySelector('[data-gmoccapy-hal="operator-inputs"]')?.textContent || ""; + if ( + !halInputDiagnostic.includes("3 operator pins") || + !halInputDiagnostic.includes("optional-stop -> block delete") || + !halInputDiagnostic.includes("blockdelete -> optional stop") || + !halInputDiagnostic.includes("ignore-limits off") + ) { + throw new Error(`missing gmoccapy HAL operator input diagnostic: ${halInputDiagnostic}`); + } + const overrideInputDiagnostic = doc.querySelector('[data-gmoccapy-hal="override-inputs"]')?.textContent || ""; + if ( + !overrideInputDiagnostic.includes("4 override targets") || + !overrideInputDiagnostic.includes("counts require enable") || + !overrideInputDiagnostic.includes("direct-value requires analog-enable") || + !overrideInputDiagnostic.includes("reset rising-edge-only") + ) { + throw new Error(`missing gmoccapy HAL override diagnostic: ${overrideInputDiagnostic}`); + } + const jogInputDiagnostic = doc.querySelector('[data-gmoccapy-hal="jog-inputs"]')?.textContent || ""; + if ( + !jogInputDiagnostic.includes("10 axis jog pins") || + !jogInputDiagnostic.includes("6 jog-inc pins") || + !jogInputDiagnostic.includes("press/release level") || + !jogInputDiagnostic.includes("jog-inc rising-edge-only") || + !jogInputDiagnostic.includes("jog-inc-0 continuous") + ) { + throw new Error(`missing gmoccapy HAL jog diagnostic: ${jogInputDiagnostic}`); + } + const messageInputDiagnostic = doc.querySelector('[data-gmoccapy-hal="message-inputs"]')?.textContent || ""; + if ( + !messageInputDiagnostic.includes("3 message pins") || + !messageInputDiagnostic.includes("delete-message rising-edge-only") || + !messageInputDiagnostic.includes("warning-confirm level-polled") + ) { + throw new Error(`missing gmoccapy HAL message diagnostic: ${messageInputDiagnostic}`); + } + const hardButtonDiagnostic = doc.querySelector('[data-gmoccapy-hal="hard-buttons"]')?.textContent || ""; + if ( + !hardButtonDiagnostic.includes("7 right v-button pins") || + !hardButtonDiagnostic.includes("4 bottom h-button main refs") || + !hardButtonDiagnostic.includes("7 mapped Web buttons") || + !hardButtonDiagnostic.includes("rising-edge-only") || + !hardButtonDiagnostic.includes("insensitive ignored") + ) { + throw new Error(`missing gmoccapy hard-button diagnostic: ${hardButtonDiagnostic}`); + } + const toolChangeDiagnostic = doc.querySelector('[data-gmoccapy-hal="tool-change"]')?.textContent || ""; + if ( + !toolChangeDiagnostic.includes("iocontrol-loopback") || + !toolChangeDiagnostic.includes("manual GUI pins disconnected") || + !toolChangeDiagnostic.includes("3 postgui unlinkp") + ) { + throw new Error(`missing gmoccapy HAL tool-change diagnostic: ${toolChangeDiagnostic}`); + } if (!doc.querySelector('[data-tool-preview="summary"]')?.textContent.includes("T1")) { throw new Error("missing tool preview summary"); } @@ -877,6 +950,16 @@ if (win.webRtcp5AxisSimulation.getState().runState !== "idle") { throw new Error("RESET did not return to idle"); } + doc.querySelector('[data-action="power"]').click(); + await wait(50); + if (win.webRtcp5AxisSimulation.getState().machine.powerOn !== true) { + throw new Error("POWER did not re-enable machine before override tests"); + } + doc.querySelector('[data-action="mode-manual"]').click(); + await wait(50); + if (win.webRtcp5AxisSimulation.getState().machine.mode !== "manual") { + throw new Error("MANUAL mode did not re-enable before HAL jog tests"); + } doc.querySelector('[data-action="feed-override-down"]').click(); await wait(50); if (win.webRtcp5AxisSimulation.getState().feed.feedOverride !== 90) { @@ -885,6 +968,11 @@ if (doc.querySelector('[data-value="feed-override"]')?.textContent.trim() !== "90 %") { throw new Error("feed override DOM did not update"); } + doc.querySelector('[data-action="feed-override-reset"]').click(); + await wait(50); + if (win.webRtcp5AxisSimulation.getState().feed.feedOverride !== 100) { + throw new Error("feed override reset button did not update state"); + } doc.querySelector('[data-action="rapid-override-up"]').click(); await wait(50); if (win.webRtcp5AxisSimulation.getState().feed.rapidOverride !== 110) { @@ -895,6 +983,121 @@ if (win.webRtcp5AxisSimulation.getState().spindle.override !== 110) { throw new Error("spindle override button did not update state"); } + doc.querySelector('[data-action="spindle-override-reset"]').click(); + await wait(50); + if (win.webRtcp5AxisSimulation.getState().spindle.override !== 100) { + throw new Error("spindle override reset button did not update state"); + } + doc.querySelector('[data-action="ignore-limits"]').click(); + doc.querySelector('[data-action="block-delete"]').click(); + doc.querySelector('[data-action="optional-stop"]').click(); + await wait(50); + const guiState = win.webRtcp5AxisSimulation.getState().gmoccapyGui; + if (guiState.ignoreLimits !== true || guiState.optionalBlocks !== true || guiState.optionalStop !== true) { + throw new Error(`gmoccapy HAL input buttons did not update state: ${JSON.stringify(guiState)}`); + } + if (!doc.querySelector('[data-value="gmoccapy-hal-last"]')?.textContent.includes("optional stop set from Web control")) { + throw new Error("gmoccapy HAL last input DOM did not update"); + } + win.webRtcp5AxisSimulation.dispatch({ + type: "GMOCAPY_HAL_PIN", + pin: "gmoccapy.optional-stop", + value: false, + }); + win.webRtcp5AxisSimulation.dispatch({ + type: "GMOCAPY_HAL_PIN", + pin: "gmoccapy.blockdelete", + value: false, + }); + await wait(50); + const crossedGuiState = win.webRtcp5AxisSimulation.getState().gmoccapyGui; + if (crossedGuiState.optionalBlocks !== false || crossedGuiState.optionalStop !== false) { + throw new Error(`gmoccapy HAL crossed optional/blockdelete pins did not update state: ${JSON.stringify(crossedGuiState)}`); + } + win.webRtcp5AxisSimulation.dispatch({ + type: "GMOCAPY_HAL_PIN", + pin: "gmoccapy.jog.jog-inc-2", + value: false, + }); + await wait(25); + if (win.webRtcp5AxisSimulation.getState().machine.jogIncrement !== 1) { + throw new Error("gmoccapy HAL jog increment falling edge should not change Web machine jog increment"); + } + win.webRtcp5AxisSimulation.dispatch({ + type: "GMOCAPY_HAL_PIN", + pin: "gmoccapy.jog.jog-inc-2", + value: true, + }); + await wait(25); + let halJogGuiState = win.webRtcp5AxisSimulation.getState().gmoccapyGui; + if (win.webRtcp5AxisSimulation.getState().machine.jogIncrement !== 0.1 || halJogGuiState.jogIncrementOutput !== 0.1) { + throw new Error(`gmoccapy HAL jog-inc pin did not select 0.100 mm: ${JSON.stringify(halJogGuiState)}`); + } + win.webRtcp5AxisSimulation.dispatch({ + type: "GMOCAPY_HAL_PIN", + pin: "gmoccapy.jog.axis.jog-x-plus", + value: true, + }); + await wait(50); + if ( + win.webRtcp5AxisSimulation.getState().runState !== "jogging" || + win.webRtcp5AxisSimulation.getState().gmoccapyGui.activeJogPin !== "gmoccapy.jog.axis.jog-x-plus" + ) { + throw new Error(`gmoccapy HAL jog axis pin did not enter jogging state: ${JSON.stringify(win.webRtcp5AxisSimulation.getState().gmoccapyGui)}`); + } + win.webRtcp5AxisSimulation.dispatch({ + type: "GMOCAPY_HAL_PIN", + pin: "gmoccapy.jog.axis.jog-x-plus", + value: false, + }); + await wait(50); + if ( + win.webRtcp5AxisSimulation.getState().runState !== "idle" || + win.webRtcp5AxisSimulation.getState().gmoccapyGui.activeJogPin !== null + ) { + throw new Error("gmoccapy HAL jog release did not clear active jog pin"); + } + win.webRtcp5AxisSimulation.dispatch({ + type: "GMOCAPY_HAL_PIN", + pin: "gmoccapy.jog.turtle-jog", + value: true, + }); + await wait(25); + if (win.webRtcp5AxisSimulation.getState().gmoccapyGui.turtleJog !== true) { + throw new Error("gmoccapy HAL turtle-jog pin did not update state"); + } + win.webRtcp5AxisSimulation.dispatch({ + type: "GMOCAPY_HAL_PIN", + pin: "gmoccapy.delete-message", + value: false, + }); + await wait(25); + if (win.webRtcp5AxisSimulation.getState().gmoccapyGui.deletedMessageCount !== 0) { + throw new Error("gmoccapy HAL delete-message falling edge should be ignored"); + } + win.webRtcp5AxisSimulation.dispatch({ + type: "GMOCAPY_HAL_PIN", + pin: "gmoccapy.delete-message", + value: true, + }); + win.webRtcp5AxisSimulation.dispatch({ + type: "GMOCAPY_HAL_PIN", + pin: "gmoccapy.warning-confirm", + value: true, + }); + await wait(50); + halJogGuiState = win.webRtcp5AxisSimulation.getState().gmoccapyGui; + if (halJogGuiState.deletedMessageCount !== 1 || halJogGuiState.warningConfirm !== true) { + throw new Error(`gmoccapy HAL message pins did not update state: ${JSON.stringify(halJogGuiState)}`); + } + const updatedJogDiagnostic = doc.querySelector('[data-gmoccapy-hal="jog-inputs"]')?.textContent || ""; + const updatedMessageDiagnostic = doc.querySelector('[data-gmoccapy-hal="message-inputs"]')?.textContent || ""; + if (!updatedJogDiagnostic.includes("turtle level-driven") || !updatedJogDiagnostic.includes("increment 0.100 mm=0.1")) { + throw new Error(`gmoccapy HAL jog diagnostic did not refresh: ${updatedJogDiagnostic}`); + } + if (!updatedMessageDiagnostic.includes("deleted 1") || !updatedMessageDiagnostic.includes("confirm on")) { + throw new Error(`gmoccapy HAL message diagnostic did not refresh: ${updatedMessageDiagnostic}`); + } doc.querySelector('[data-action="toggle-flood"]').click(); doc.querySelector('[data-action="toggle-mist"]').click(); await wait(50); diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs index a52c785..41c6030 100644 --- a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs +++ b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs @@ -17,6 +17,8 @@ assert.deepEqual(model.nativePaths.command.path, ["gmoccapy", "linuxcnc.command( assert.equal(model.nativePaths.status.path.includes("linuxcnc.stat()"), true); assert.equal(model.nativePaths.status.path.includes("linuxcnc.error_channel()"), true); assert.equal(model.nativePaths.hal.path.includes("HAL shared memory"), true); +assert.equal(model.nativePaths.hardwareButtons.path.includes("_button_pin_changed"), true); +assert.equal(model.nativePaths.halPins.path.includes("hal_glib.GPin value_changed"), true); assert.equal(model.nativePaths.remap.path.includes("Python remap prolog/ngc/epilog"), true); assert.deepEqual(model.webPath.path, ["button", "store.dispatch", "linuxcnc-task-policy", "runtime/status snapshot", "UI/Three.js"]); @@ -42,6 +44,10 @@ for (const actionId of [ "abort", "spindle", "coolant", + "hardware-button", + "hal-pin-input", + "hal-jog-pin", + "hal-warning-confirm", ]) { assert.equal(actionIds.includes(actionId), true, `${actionId} missing`); } @@ -60,15 +66,42 @@ const spindleMapping = model.actionMappings.find((mapping) => mapping.actionId = assert.equal(spindleMapping.nativeStatus.includes("spindle.0.forward"), true); assert.equal(spindleMapping.gate.includes("restricted during interpreter"), true); +const hardwareButtonMapping = model.actionMappings.find((mapping) => mapping.actionId === "hardware-button"); +assert.equal(hardwareButtonMapping.nativeCommand.includes("_button_pin_changed"), true); +assert.equal(hardwareButtonMapping.gate.includes("rising-edge"), true); +assert.equal(hardwareButtonMapping.gate.includes("insensitive"), true); + +const halPinMapping = model.actionMappings.find((mapping) => mapping.actionId === "hal-pin-input"); +assert.equal(halPinMapping.webAction, "GMOCAPY_HAL_PIN"); +assert.equal(halPinMapping.nativeCommand.includes("_optional_blocks"), true); +assert.equal(halPinMapping.nativeCommand.includes("_on_counts_changed"), true); +assert.equal(halPinMapping.nativeCommand.includes("_del_message_changed"), true); +assert.equal(halPinMapping.gate.includes("counts require count-enable"), true); +assert.equal(halPinMapping.gate.includes("direct-value requires analog-enable"), true); +assert.equal(halPinMapping.gate.includes("delete-message pins are rising-edge only"), true); + +const halJogMapping = model.actionMappings.find((mapping) => mapping.actionId === "hal-jog-pin"); +assert.equal(halJogMapping.webAction, "GMOCAPY_HAL_PIN jog.axis/jog-inc"); +assert.equal(halJogMapping.nativeCommand.includes("_on_pin_jog_changed"), true); +assert.equal(halJogMapping.nativeCommand.includes("_on_pin_incr_changed"), true); +assert.equal(halJogMapping.gate.includes("press/release level"), true); +assert.equal(halJogMapping.gate.includes("increment pins are rising-edge only"), true); + +const warningConfirmMapping = model.actionMappings.find((mapping) => mapping.actionId === "hal-warning-confirm"); +assert.equal(warningConfirmMapping.nativeCommand.includes("confirm_pin"), true); +assert.equal(warningConfirmMapping.gate, "level-polled while warning dialog is active"); + const summary = createGmoccapyCommunicationSummary(model); assert.equal(summary.apiName, "web-rtcp-5axis-gmoccapy-communication-model-summary"); assert.equal(summary.profileId, "gmoccapy-xyzab"); -assert.equal(summary.nativePathCount, 4); +assert.equal(summary.nativePathCount, 6); assert.equal(summary.startupStageCount, 9); -assert.equal(summary.actionCount, 15); +assert.equal(summary.actionCount, 19); assert.equal(summary.postguiAfterHalcompReady, true); assert.equal(summary.hasNativeNmlCommandPath, true); assert.equal(summary.hasNativeHalPath, true); +assert.equal(summary.hasNativeHardwareButtonPath, true); +assert.equal(summary.hasNativeHalPinInputPath, true); assert.equal(summary.hasWebStorePath, true); assert.equal(summary.semanticBoundary, model.semanticBoundary); diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs index 4589abf..4329110 100644 --- a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs +++ b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs @@ -3,6 +3,7 @@ import assert from "node:assert/strict"; import { createGmoccapyHalSummary, gmoccapyHalModel, + resolveGmoccapyHardwareButton, } from "../../app/src/runtime/gmoccapy-hal-model.js"; const model = gmoccapyHalModel; @@ -23,6 +24,7 @@ for (const groupName of [ "hard-buttons", "jog", "override", + "operator-inputs", "tool", "program", "message", @@ -34,7 +36,14 @@ for (const groupName of [ assert.equal(groups.get("hard-buttons").pins.includes("gmoccapy.h-button.button-0"), true); assert.equal(groups.get("hard-buttons").pins.includes("gmoccapy.v-button.button-6"), true); assert.equal(groups.get("jog").pins.includes("gmoccapy.jog.axis.jog-b-minus"), true); +assert.equal(groups.get("jog").pins.includes("gmoccapy.jog.jog-inc-5"), true); +assert.equal(groups.get("jog").pins.includes("gmoccapy.jog.turtle-jog"), true); assert.equal(groups.get("override").pins.includes("gmoccapy.rapid.rapid-override.direct-value"), true); +assert.equal(groups.get("override").pins.includes("gmoccapy.feed.feed-override.count-enable"), true); +assert.equal(groups.get("override").pins.includes("gmoccapy.spindle.reset-spindle-override"), true); +assert.equal(groups.get("operator-inputs").pins.includes("gmoccapy.ignore-limits"), true); +assert.equal(groups.get("operator-inputs").pins.includes("gmoccapy.optional-stop"), true); +assert.equal(groups.get("operator-inputs").pins.includes("gmoccapy.blockdelete"), true); assert.equal(groups.get("tool").pins.includes("gmoccapy.toolchange-changed"), true); assert.equal(groups.get("program").pins.includes("gmoccapy.program.current-line"), true); assert.equal(groups.get("message").pins.includes("gmoccapy.error"), true); @@ -56,17 +65,113 @@ assert.equal(model.postguiNets.some((net) => net.target === "gmoccapy.spindle_fe assert.equal(model.postguiNets.some((net) => net.target === "gmoccapy.tooloffset-x"), true); assert.equal(model.postguiNets.some((net) => net.simulationLoop && net.signal === "tool-change-loop"), true); +assert.equal(model.hardwareButtons.source.methods.includes("_button_pin_changed"), true); +assert.equal(model.hardwareButtons.behavior.edge, "rising-edge-only"); +assert.equal(model.hardwareButtons.behavior.insensitiveTarget, "ignored"); +assert.deepEqual(model.hardwareButtons.verticalMain.map((button) => button.nativeWidget), [ + "tbtn_estop", + "tbtn_on", + "rbt_manual", + "rbt_mdi", + "rbt_auto", + "tbtn_user_tabs", + "tbtn_setup", +]); +assert.deepEqual(model.hardwareButtons.verticalMain.map((button) => button.pin), [ + "gmoccapy.v-button.button-0", + "gmoccapy.v-button.button-1", + "gmoccapy.v-button.button-2", + "gmoccapy.v-button.button-3", + "gmoccapy.v-button.button-4", + "gmoccapy.v-button.button-5", + "gmoccapy.v-button.button-6", +]); +assert.equal(model.hardwareButtons.bottomMain.find((button) => button.index === 0).nativeWidget, "btn_homing"); +assert.equal(model.hardwareButtons.bottomMain.find((button) => button.index === 0).webDispatch.type, "HOME"); +assert.equal(resolveGmoccapyHardwareButton("gmoccapy.v-button.button-4", model).webDispatch.mode, "auto"); +assert.equal(resolveGmoccapyHardwareButton({ location: "bottom", index: 0 }, model).webDispatch.type, "HOME"); +assert.equal(resolveGmoccapyHardwareButton({ location: "right", index: 6 }, model).webDispatch, undefined); +assert.equal(resolveGmoccapyHardwareButton("gmoccapy.h-button.button-9", model), null); + +assert.equal(model.halPinActions.source.methods.includes("_optional_blocks"), true); +assert.equal(model.halPinActions.source.methods.includes("_blockdelete"), true); +assert.equal(model.halPinActions.source.methods.includes("_on_pin_jog_changed"), true); +assert.equal(model.halPinActions.source.methods.includes("_on_pin_incr_changed"), true); +assert.equal(model.halPinActions.source.methods.includes("_del_message_changed"), true); +assert.equal(model.halPinActions.jogPins.axes.length, 5); +assert.equal(model.halPinActions.jogPins.axes.find((entry) => entry.axis === "b").minus, "gmoccapy.jog.axis.jog-b-minus"); +assert.equal(model.halPinActions.jogPins.increments.length, 6); +assert.equal(model.halPinActions.jogPins.increments[0].distance, 0); +assert.equal(model.halPinActions.jogPins.increments[5].pin, "gmoccapy.jog.jog-inc-5"); +assert.equal(model.halPinActions.jogPins.increments[5].distance, 31.3563); +assert.equal(model.halPinActions.jogPins.turtlePin, "gmoccapy.jog.turtle-jog"); +assert.equal(model.halPinActions.operatorPins.find((pin) => pin.pin === "gmoccapy.optional-stop").webState, "gmoccapyGui.optionalBlocks"); +assert.equal(model.halPinActions.operatorPins.find((pin) => pin.pin === "gmoccapy.blockdelete").webState, "gmoccapyGui.optionalStop"); +assert.equal(model.halPinActions.overridePins.find((pin) => pin.target === "feed").countEnable, "gmoccapy.feed.feed-override.count-enable"); +assert.equal(model.halPinActions.overridePins.find((pin) => pin.target === "spindle").reset, "gmoccapy.spindle.reset-spindle-override"); +assert.equal(model.halPinActions.overridePins.find((pin) => pin.target === "jogVelocity").scalePref, "scale_jog_vel=140.4"); +assert.equal(model.halPinActions.behavior.optionalStopPinCrossesToBlockDelete, true); +assert.equal(model.halPinActions.behavior.blockdeletePinCrossesToOptionalStop, true); +assert.equal(model.halPinActions.behavior.jogAxisPinsPressRelease, true); +assert.equal(model.halPinActions.behavior.jogPinsUseTaskPolicyGate, true); +assert.equal(model.halPinActions.behavior.jogIncrementPinsRisingEdgeOnly, true); +assert.equal(model.halPinActions.behavior.jogIncrementZeroIsContinuous, true); +assert.equal(model.halPinActions.behavior.turtleJogLevelDriven, true); +assert.equal(model.halPinActions.behavior.countInputRequiresEnable, true); +assert.equal(model.halPinActions.behavior.directValueRequiresAnalogEnable, true); +assert.equal(model.halPinActions.behavior.resetPinsRisingEdgeOnly, true); +assert.equal(model.halPinActions.messagePins.find((pin) => pin.pin === "gmoccapy.delete-message").nativeCallback, "_del_message_changed"); +assert.equal(model.halPinActions.messagePins.find((pin) => pin.pin === "gmoccapy.warning-confirm").nativeCallback, "dialogs.warning_dialog periodic confirm_pin poll"); +assert.equal(model.halPinActions.behavior.deleteMessageRisingEdgeOnly, true); +assert.equal(model.halPinActions.behavior.warningConfirmLevelPolled, true); + +assert.equal(model.toolChange.strategy, "iocontrol-loopback"); +assert.equal(model.toolChange.manualGmoccapyPinsConnected, false); +assert.deepEqual(model.toolChange.remapCodes, ["M6", "M61"]); +assert.equal(model.toolChange.postguiUnlinks.map((entry) => entry.pin).join(","), "iocontrol.0.tool-change,iocontrol.0.tool-changed,iocontrol.0.tool-prep-number"); +assert.equal(model.toolChange.commentedManualGuiNets.some((net) => net.target === "gmoccapy.toolchange-change"), true); +assert.equal(model.toolChange.commentedManualGuiNets.some((net) => net.source === "gmoccapy.toolchange-changed"), true); +assert.equal(model.toolChange.activeLoop.signal, "tool-change-loop"); +assert.equal(model.toolChange.activeLoop.stage, "POSTGUI_HALFILE"); +assert.equal(model.toolChange.semanticBoundary, "gmoccapy_xyzab_iocontrol_tool_change_loop_no_manual_gui_dialog"); + const summary = createGmoccapyHalSummary(model); assert.equal(summary.apiName, "web-rtcp-5axis-gmoccapy-hal-model-summary"); assert.equal(summary.profileId, "gmoccapy-xyzab"); assert.equal(summary.halFileCount, 4); -assert.equal(summary.nativePinGroupCount, 7); +assert.equal(summary.nativePinGroupCount, 8); assert.equal(summary.nativePinCount >= 50, true); assert.equal(summary.coreNetCount, 10); assert.equal(summary.spindleNetCount, 4); assert.equal(summary.postguiNetCount, 5); assert.equal(summary.postguiRequiresHalcompReady, true); assert.equal(summary.hasToolChangeSimulationLoop, true); +assert.equal(summary.toolChangeStrategy, "iocontrol-loopback"); +assert.equal(summary.manualToolChangeGuiConnected, false); +assert.equal(summary.postguiToolUnlinkCount, 3); +assert.equal(summary.commentedManualToolChangeNetCount, 3); +assert.equal(summary.hardwareButtonVerticalCount, 7); +assert.equal(summary.hardwareButtonBottomMainCount, 4); +assert.equal(summary.mappedHardwareButtonCount, 7); +assert.equal(summary.hardwareButtonEdge, "rising-edge-only"); +assert.equal(summary.hardwareButtonInsensitiveTarget, "ignored"); +assert.equal(summary.operatorInputPinCount, 3); +assert.equal(summary.overridePinTargetCount, 4); +assert.equal(summary.jogAxisPinCount, 10); +assert.equal(summary.jogIncrementPinCount, 6); +assert.equal(summary.messageInputPinCount, 3); +assert.equal(summary.optionalStopPinCrossesToBlockDelete, true); +assert.equal(summary.blockdeletePinCrossesToOptionalStop, true); +assert.equal(summary.jogAxisPinsPressRelease, true); +assert.equal(summary.jogPinsUseTaskPolicyGate, true); +assert.equal(summary.jogIncrementPinsRisingEdgeOnly, true); +assert.equal(summary.jogIncrementZeroIsContinuous, true); +assert.equal(summary.turtleJogLevelDriven, true); +assert.equal(summary.countInputRequiresEnable, true); +assert.equal(summary.directValueRequiresAnalogEnable, true); +assert.equal(summary.resetPinsRisingEdgeOnly, true); +assert.equal(summary.deleteMessageRisingEdgeOnly, true); +assert.equal(summary.warningConfirmLevelPolled, true); assert.equal(summary.semanticBoundary, model.semanticBoundary); console.log("gmoccapy_hal_model_smoke=ok"); diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs index c374cf1..c909600 100644 --- a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs +++ b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs @@ -200,4 +200,116 @@ assert.equal(controlStore.getState().spindle.direction, "stop"); controlStore.dispatch({ type: "TOGGLE_COOLANT", kind: "flood" }); assert.equal(controlStore.getState().coolant.flood, true); +const hardButtonStore = createSimulationStore(); +hardButtonStore.dispatch({ type: "SET_PROFILE", profileId: "gmoccapy-xyzab" }); +hardButtonStore.dispatch({ type: "GMOCAPY_HARDWARE_BUTTON", pin: "gmoccapy.v-button.button-1", value: false }); +assert.equal(hardButtonStore.getState().machine.powerOn, false); +assert.equal(hardButtonStore.getState().operatorMessage, "gmoccapy hardware button falling edge ignored"); +hardButtonStore.dispatch({ type: "GMOCAPY_HARDWARE_BUTTON", pin: "gmoccapy.v-button.button-1", value: true }); +assert.equal(hardButtonStore.getState().machine.powerOn, true); +hardButtonStore.dispatch({ type: "GMOCAPY_HARDWARE_BUTTON", pin: "gmoccapy.v-button.button-4", value: true }); +assert.equal(hardButtonStore.getState().machine.mode, "manual"); +assert.equal(hardButtonStore.getState().operatorMessage, "mode blocked: home machine before AUTO"); +hardButtonStore.dispatch({ type: "GMOCAPY_HARDWARE_BUTTON", location: "bottom", index: 0, value: true }); +assert.equal(hardButtonStore.getState().machine.allHomed, true); +hardButtonStore.dispatch({ type: "GMOCAPY_HARDWARE_BUTTON", pin: "gmoccapy.v-button.button-4", value: true }); +assert.equal(hardButtonStore.getState().machine.mode, "auto"); +hardButtonStore.dispatch({ type: "GMOCAPY_HARDWARE_BUTTON", pin: "gmoccapy.v-button.button-6", value: true }); +assert.equal(hardButtonStore.getState().operatorMessage, "gmoccapy hardware button diagnostic-only: gmoccapy.v-button.button-6"); +hardButtonStore.dispatch({ type: "GMOCAPY_HARDWARE_BUTTON", pin: "gmoccapy.h-button.button-9", value: true }); +assert.equal(hardButtonStore.getState().operatorMessage, "gmoccapy hardware button unmapped: gmoccapy.h-button.button-9"); + +const halPinStore = createSimulationStore(); +halPinStore.dispatch({ type: "SET_PROFILE", profileId: "gmoccapy-xyzab" }); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.ignore-limits", value: true }); +assert.equal(halPinStore.getState().gmoccapyGui.ignoreLimits, true); +assert.equal(halPinStore.getState().operatorMessage, "gmoccapy HAL ignore-limits requested"); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.optional-stop", value: true }); +assert.equal(halPinStore.getState().gmoccapyGui.optionalBlocks, true); +assert.equal(halPinStore.getState().gmoccapyGui.optionalStop, false); +assert.equal(halPinStore.getState().operatorMessage, "gmoccapy HAL optional-stop -> block delete on"); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.blockdelete", value: true }); +assert.equal(halPinStore.getState().gmoccapyGui.optionalStop, true); +assert.equal(halPinStore.getState().operatorMessage, "gmoccapy HAL blockdelete -> optional stop on"); + +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.feed.feed-override.counts", value: 12 }); +assert.equal(halPinStore.getState().feed.feedOverride, 100); +assert.equal(halPinStore.getState().gmoccapyGui.feedOverrideCounts, 12); +assert.equal(halPinStore.getState().operatorMessage, "gmoccapy HAL feed counts synchronized"); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.feed.feed-override.count-enable", value: true }); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.feed.feed-override.counts", value: 17 }); +assert.equal(halPinStore.getState().feed.feedOverride, 105); +assert.equal(halPinStore.getState().operatorMessage, "gmoccapy HAL feed counts adjusted"); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.rapid.rapid-override.analog-enable", value: true }); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.rapid.rapid-override.direct-value", value: 0.25 }); +assert.equal(halPinStore.getState().feed.rapidOverride, 50); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.spindle.spindle-override.direct-value", value: 0.5 }); +assert.equal(halPinStore.getState().spindle.override, 100); +assert.equal(halPinStore.getState().operatorMessage, "gmoccapy HAL spindle direct-value ignored: analog disabled"); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.spindle.spindle-override.analog-enable", value: true }); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.spindle.spindle-override.direct-value", value: 0.5 }); +assert.equal(halPinStore.getState().spindle.override, 75); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.spindle.reset-spindle-override", value: false }); +assert.equal(halPinStore.getState().spindle.override, 75); +assert.equal(halPinStore.getState().operatorMessage, "gmoccapy HAL spindle reset falling edge ignored"); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.spindle.reset-spindle-override", value: true }); +assert.equal(halPinStore.getState().spindle.override, 100); +assert.equal(halPinStore.getState().operatorMessage, "gmoccapy HAL spindle reset to 100"); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.jog-velocity.count-enable", value: true }); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.jog-velocity.counts", value: 2 }); +assert.equal(halPinStore.getState().gmoccapyGui.jogVelocity, 380.8); +halPinStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.unknown", value: true }); +assert.equal(halPinStore.getState().operatorMessage, "gmoccapy HAL pin unmapped: gmoccapy.unknown"); + +const halJogStore = createSimulationStore(); +halJogStore.dispatch({ type: "SET_PROFILE", profileId: "gmoccapy-xyzab" }); +halJogStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.axis.jog-x-plus", value: true }); +assert.equal(halJogStore.getState().runState, "idle"); +assert.equal(halJogStore.getState().operatorMessage, "gmoccapy HAL jog blocked: jog blocked: machine must be on"); +halJogStore.dispatch({ type: "TOGGLE_POWER" }); +halJogStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.axis.jog-x-plus", value: true }); +assert.equal(halJogStore.getState().runState, "jogging"); +assert.equal(halJogStore.getState().axisPose.x, 43); +assert.equal(halJogStore.getState().machine.jogIncrement, 0); +assert.equal(halJogStore.getState().gmoccapyGui.activeJogPin, "gmoccapy.jog.axis.jog-x-plus"); +assert.equal(halJogStore.getState().operatorMessage, "gmoccapy HAL jog X+ continuous"); +halJogStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.axis.jog-x-plus", value: false }); +assert.equal(halJogStore.getState().runState, "idle"); +assert.equal(halJogStore.getState().gmoccapyGui.activeJogPin, null); +assert.equal(halJogStore.getState().operatorMessage, "gmoccapy HAL jog X+ released"); +halJogStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.jog-inc-2", value: false }); +assert.equal(halJogStore.getState().machine.jogIncrement, 0); +assert.equal(halJogStore.getState().operatorMessage, "gmoccapy HAL jog increment falling edge ignored"); +halJogStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.jog-inc-2", value: true }); +assert.equal(halJogStore.getState().machine.jogIncrement, 0.1); +assert.equal(halJogStore.getState().gmoccapyGui.jogIncrementIndex, 2); +assert.equal(halJogStore.getState().gmoccapyGui.jogIncrementOutput, 0.1); +halJogStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.axis.jog-x-plus", value: true }); +assert.equal(halJogStore.getState().axisPose.x, 43.1); +assert.equal(halJogStore.getState().operatorMessage, "gmoccapy HAL jog X+ 0.1"); +halJogStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.jog-inc-5", value: true }); +assert.equal(halJogStore.getState().machine.jogIncrement, 31.3563); +assert.equal(halJogStore.getState().gmoccapyGui.jogIncrementLabel, "1.2345 in"); +halJogStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.turtle-jog", value: true }); +assert.equal(halJogStore.getState().gmoccapyGui.turtleJog, true); +assert.equal(halJogStore.getState().operatorMessage, "gmoccapy HAL turtle jog on"); + +const halMessageStore = createSimulationStore(); +halMessageStore.dispatch({ type: "SET_PROFILE", profileId: "gmoccapy-xyzab" }); +halMessageStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.delete-message", value: false }); +assert.equal(halMessageStore.getState().gmoccapyGui.deletedMessageCount, 0); +assert.equal(halMessageStore.getState().operatorMessage, "gmoccapy HAL delete-message falling edge ignored"); +halMessageStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.jog.axis.jog-y-minus", value: true }); +assert.equal(halMessageStore.getState().operatorMessage, "gmoccapy HAL jog blocked: jog blocked: machine must be on"); +halMessageStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.delete-message", value: true }); +assert.equal(halMessageStore.getState().gmoccapyGui.error, false); +assert.equal(halMessageStore.getState().gmoccapyGui.deletedMessageCount, 1); +assert.equal(halMessageStore.getState().operatorMessage, "gmoccapy HAL delete-message cleared alert"); +halMessageStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.warning-confirm", value: true }); +assert.equal(halMessageStore.getState().gmoccapyGui.warningConfirm, true); +assert.equal(halMessageStore.getState().operatorMessage, "gmoccapy HAL warning-confirm asserted"); +halMessageStore.dispatch({ type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.warning-confirm", value: false }); +assert.equal(halMessageStore.getState().gmoccapyGui.warningConfirm, false); +assert.equal(halMessageStore.getState().operatorMessage, "gmoccapy HAL warning-confirm cleared"); + console.log("gmoccapy_xyzab_gates_smoke=ok"); diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs index 9b7c4ae..c66efe5 100644 --- a/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs +++ b/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs @@ -41,13 +41,46 @@ assert.equal(profile.jointConfig[4].homeOffset, 45); assert.equal(profile.hal.halcmd.feedbackNets.length, 5); assert.equal(profile.hal.halcmd.offsetNets.some((net) => net.target === "gmoccapy.tooloffset-z"), true); assert.equal(profile.hal.halcmd.simulationLoops.some((net) => net.signal === "tool-change-loop"), true); +assert.equal(profile.hal.halcmd.toolChange.strategy, "iocontrol-loopback"); +assert.equal(profile.hal.halcmd.toolChange.manualGmoccapyPinsConnected, false); +assert.deepEqual(profile.hal.halcmd.toolChange.postguiUnlinks, [ + "iocontrol.0.tool-change", + "iocontrol.0.tool-changed", + "iocontrol.0.tool-prep-number", +]); +assert.equal(profile.hal.halcmd.toolChange.commentedManualGuiNets.length, 3); +assert.equal(profile.hal.halcmd.toolChange.activeLoop.signal, "tool-change-loop"); assert.equal(profile.halPins.includes("gmoccapy.program.current-line"), true); assert.equal(profile.halPins.includes("gmoccapy.spindle_at_speed_led"), true); +assert.equal(profile.halPins.includes("gmoccapy.jog.axis.jog-x-plus"), true); +assert.equal(profile.halPins.includes("gmoccapy.jog.axis.jog-y-minus"), true); +assert.equal(profile.halPins.includes("gmoccapy.jog.axis.jog-b-minus"), true); +assert.equal(profile.halPins.includes("gmoccapy.jog.jog-inc-0"), true); +assert.equal(profile.halPins.includes("gmoccapy.jog.jog-inc-5"), true); +assert.equal(profile.halPins.includes("gmoccapy.jog.jog-increment"), true); +assert.equal(profile.halPins.includes("gmoccapy.jog.turtle-jog"), true); +assert.equal(profile.halPins.includes("gmoccapy.ignore-limits"), true); +assert.equal(profile.halPins.includes("gmoccapy.optional-stop"), true); +assert.equal(profile.halPins.includes("gmoccapy.blockdelete"), true); +assert.equal(profile.halPins.includes("gmoccapy.feed.feed-override.count-enable"), true); +assert.equal(profile.halPins.includes("gmoccapy.feed.reset-feed-override"), true); +assert.equal(profile.halPins.includes("gmoccapy.spindle.reset-spindle-override"), true); +assert.equal(profile.halPins.includes("gmoccapy.jog.jog-velocity.direct-value"), true); +assert.equal(profile.halPins.includes("gmoccapy.delete-message"), true); +assert.equal(profile.halPins.includes("gmoccapy.warning-confirm"), true); assert.equal(profile.toolTable.toolCount, 17); assert.equal(profile.macros.includes("go_to_position X-pos Y-pos Z-pos"), true); assert.equal(profile.panelSchema.id, "gmoccapy-xyzab-hal-component"); assert.equal(profile.panelSchema.boundary, "gmoccapy_hal_component_reference_only"); assert.equal(profile.panelSchema.groups.length, 2); +const taskModeControls = profile.panelSchema.groups.find((group) => group.id === "task-mode-actions").controls; +assert.deepEqual(taskModeControls.map((control) => [control.id, control.halpin]), [ + ["tbtn-estop", "gmoccapy.v-button.button-0"], + ["tbtn-on", "gmoccapy.v-button.button-1"], + ["rbt-manual", "gmoccapy.v-button.button-2"], + ["rbt-mdi", "gmoccapy.v-button.button-3"], + ["rbt-auto", "gmoccapy.v-button.button-4"], +]); assert.deepEqual(fiveAxisProfiles.map(({ id }) => id), ["xyzac-trt", "xyzbc-trt", "gmoccapy-xyzab"]); diff --git a/web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs b/web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs index dda20aa..5adf821 100644 --- a/web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs +++ b/web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs @@ -67,6 +67,8 @@ assert.equal(gmoccapyXyzabProfile.tcpCapable, false); assert.equal(gmoccapyXyzabProfile.rtcpProof, false); assert.equal(gmoccapyXyzabProfile.promotionAllowed, false); assert.equal(gmoccapyXyzabProfile.hal.postguiRequiresHalcompReady, true); +assert.equal(gmoccapyXyzabProfile.hal.halcmd.toolChange.strategy, "iocontrol-loopback"); +assert.equal(gmoccapyXyzabProfile.hal.halcmd.toolChange.manualGmoccapyPinsConnected, false); const gmoccapySourceSummary = createProfileSourceReferenceSummary("gmoccapy-xyzab"); assert.equal(gmoccapySourceSummary.referenceCount >= 10, true); assert.ok(gmoccapySourceSummary.references.some((reference) => reference.path.endsWith("gmoccapy_XYZAB.ini"))); diff --git a/work/working5/03-推进台账.md b/work/working5/03-推进台账.md index 08f1578..78cf332 100644 --- a/work/working5/03-推进台账.md +++ b/work/working5/03-推进台账.md @@ -57,3 +57,151 @@ 下一步是什么: - 下一轮可继续按任务矩阵深入核对 Tool/M6/M61 手动换刀对话框、限位 override、optional stop/block delete HAL pin 交叉行为等更细粒度 gmoccapy 行为。 + +## 2026-06-26 R3:细化 gmoccapy XYZAB 换刀 HAL 语义 + +做了什么: + +- 对照 `gmoccapy_postgui.hal`,确认该 XYZAB 仿真配置在 GUI ready 后执行 `unlinkp iocontrol.0.tool-change`、`unlinkp iocontrol.0.tool-changed`、`unlinkp iocontrol.0.tool-prep-number`,并将 `gmoccapy.toolchange-*` 手动换刀 GUI nets 保持注释状态。 +- 在 Web profile 和 HAL 诊断模型中显式记录换刀策略为 `iocontrol-loopback`,手动 gmoccapy 换刀 GUI pins 未连接,活动回环为 `iocontrol.0.tool-change -> iocontrol.0.tool-changed`。 +- 在 gmoccapy UI 诊断页新增 `Tool change` 行,显示换刀策略、手动 GUI pin 连接状态、POSTGUI `unlinkp` 数量和注释掉的手动换刀 nets 数量。 +- 补充 Node 与浏览器断言,防止后续误把此配置表述为需要 gmoccapy 手动换刀确认对话框。 + +改了哪些文件: + +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_profile_boundary.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html` +- `/home/meswork/cnc_wams/work/working5/03-推进台账.md` +- `/home/meswork/cnc_wams/work/working5/04-任务矩阵.md` +- `/home/meswork/cnc_wams/work/working5/05-验收证据.md` +- `/home/meswork/cnc_wams/work/working5/06-决策记录.md` + +验证了什么: + +- `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_profile_boundary.mjs` 通过。 +- `npm run smoke:node` 通过。 +- `npm run build && npm run smoke` 通过。 + +下一步是什么: + +- 可继续核对限位 override、optional stop/block delete、外部 HAL hard-button pin 与 UI action 的交叉行为。 + +## 2026-06-26 R4:细化外部 HAL hard-button pin 与 Web UI action 映射 + +做了什么: + +- 对照 `gmoccapy.py` 的 `_make_hal_pins()`、`_button_pin_changed()`、`_get_child_button()` 和 `gmoccapy.glade` 的 `vbtb_main` 顺序,确认外部硬按钮 pin 只响应 rising edge,并按当前可见且敏感的 GTK 按钮位置触发;目标隐藏或不敏感时忽略。 +- 修正 `gmoccapy-xyzab` panel schema 中右侧主按钮 HAL pin 顺序:`tbtn_estop`/`tbtn_on`/`rbt_manual`/`rbt_mdi`/`rbt_auto` 对应 `gmoccapy.v-button.button-0..4`,不再把 Estop/Power 误列为 `h-button`。 +- 在 HAL model 和 communication model 中增加 hard-button 结构化映射,记录 7 个右侧 `v-button` pin、底部主页面 `h-button` 引用、rising-edge 语义、insensitive ignored 语义和 Web selector/action 对应关系。 +- 在 store 中新增 `GMOCAPY_HARDWARE_BUTTON` action,用同一套 Web action/gate 模拟外部 HAL hard-button pin;falling edge、未映射 pin 和诊断-only pin 均给出明确 operator message。 +- 在 UI 按钮 DOM 上新增 `data-gmoccapy-hal-pin`、`data-gmoccapy-native-panel`、`data-gmoccapy-native-button-index`,并在 gmoccapy HAL 诊断中显示 hard-button 摘要。 +- 补充 Node 和浏览器断言,覆盖 hard-button 模型、通信路径、profile pin 顺序、store gate 行为和 DOM 映射。 + +改了哪些文件: + +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/state/store.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html` +- `/home/meswork/cnc_wams/work/working5/03-推进台账.md` +- `/home/meswork/cnc_wams/work/working5/04-任务矩阵.md` +- `/home/meswork/cnc_wams/work/working5/05-验收证据.md` +- `/home/meswork/cnc_wams/work/working5/06-决策记录.md` + +验证了什么: + +- `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs` 通过。 +- `npm run smoke:node` 通过。 +- `npm run build && npm run smoke` 通过。 + +下一步是什么: + +- 可继续核对 `gmoccapy.ignore-limits`、`gmoccapy.optional-stop`、`gmoccapy.blockdelete`、override analog/count/reset pins 与 Web 状态的交叉行为。 + +## 2026-06-27 R5:细化 gmoccapy HAL 输入 pin 与倍率 pin 语义 + +做了什么: + +- 对照 `gmoccapy.py` 的 `_ignore_limits()`、`_optional_blocks()`、`_blockdelete()`、`_on_counts_changed()`、`_on_analog_value_changed()`、`_reset_override()` 和 `gmoccapy_XYZAB.pref`,确认本轮应保留 `optional-stop`/`blockdelete` 的交叉语义,以及 counts/direct-value/reset 的 enable 和 rising-edge 规则。 +- 在 Web store 中新增 `GMOCAPY_HAL_PIN` action,统一模拟 `gmoccapy.ignore-limits`、`gmoccapy.optional-stop`、`gmoccapy.blockdelete`、feed/rapid/spindle/jog override counts、analog direct-value 和 reset pins。 +- 在 `gmoccapyGui` 状态中记录 ignore limits、block delete、optional stop、override enable/counts、jog velocity 和最后一次 HAL pin 效果。 +- 扩展 profile、HAL model、communication model 和 UI 诊断,明确 `optional-stop -> block delete`、`blockdelete -> optional stop`、counts 需要 count-enable、direct-value 需要 analog-enable、reset 只响应 rising edge。 +- 在 override 面板增加 ignore limits、block delete、optional stop 和 100% override reset 控件,并补充浏览器 DOM/状态断言。 + +改了哪些文件: + +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/state/store.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/styles/gmoccapy.css` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html` +- `/home/meswork/cnc_wams/work/working5/03-推进台账.md` +- `/home/meswork/cnc_wams/work/working5/04-任务矩阵.md` +- `/home/meswork/cnc_wams/work/working5/05-验收证据.md` +- `/home/meswork/cnc_wams/work/working5/06-决策记录.md` + +验证了什么: + +- `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && bash ../tests/browser/verify_gmoccapy_shell_browser.sh` 通过。 +- `npm run smoke:node` 通过。 +- `npm run build && npm run smoke` 通过。 + +下一步是什么: + +- 可继续核对 `gmoccapy.jog.axis.*`、`gmoccapy.jog.jog-inc-*`、`gmoccapy.delete-message`、`gmoccapy.warning-confirm` 等剩余 HAL pin 与 Web 状态/消息系统的映射。 + +## 2026-06-27 R6:细化 gmoccapy jog 与消息 HAL pin 语义 + +做了什么: + +- 对照 `gmoccapy.py` 的 `_on_pin_jog_changed()`、`_on_pin_incr_changed()`、`_on_pin_turtle_jog()`、`_del_message_changed()` 和 `dialogs.warning_dialog()`,确认 jog axis pin 是按下/释放电平语义,jog increment pin 只响应 true/rising edge,`jog-inc-0` 为连续点动,`delete-message` 只在 true 时删除消息,`warning-confirm` 是活动 warning dialog 的轮询确认 pin。 +- 在 `gmoccapy-xyzab` profile 中补齐 XYZAB 的 10 个 axis jog pins、`jog-inc-0..5`、`jog.jog-increment`、`jog.turtle-jog`、`delete-message` 和 `warning-confirm`。 +- 扩展 HAL model 和 communication model,记录 jog axis、jog increment、turtle jog、message pins 的源码回调、summary 数量和 Web 映射。 +- 扩展 `GMOCAPY_HAL_PIN`:模拟 jog axis press/release、jog increment rising-edge 选择、turtle jog level、delete-message falling-edge 忽略和 warning-confirm level 状态。 +- 在 gmoccapy UI 诊断中新增 `HAL jog` 和 `HAL messages` 行,并补充 Node/浏览器断言。 + +改了哪些文件: + +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/profiles/gmoccapy-xyzab.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-communication-model.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/runtime/gmoccapy-hal-model.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/state/store.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_communication_model.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_hal_model.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs` +- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_profile.mjs` +- `/home/meswork/cnc_wams/work/working5/03-推进台账.md` +- `/home/meswork/cnc_wams/work/working5/04-任务矩阵.md` +- `/home/meswork/cnc_wams/work/working5/05-验收证据.md` +- `/home/meswork/cnc_wams/work/working5/06-决策记录.md` + +验证了什么: + +- `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs` 通过。 +- `node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs` 通过。 +- `bash ../tests/browser/verify_gmoccapy_shell_browser.sh` 通过。 +- `npm run smoke:node` 通过。 +- `npm run build && npm run smoke` 通过。 + +下一步是什么: + +- 可继续核对 `gmoccapy.unlock-settings`、tool measurement/blockheight/probeheight、INI user messages 等更低频 HAL pins;本轮 W5-190 已完成。 diff --git a/work/working5/04-任务矩阵.md b/work/working5/04-任务矩阵.md index dccc79a..efa4fdd 100644 --- a/work/working5/04-任务矩阵.md +++ b/work/working5/04-任务矩阵.md @@ -13,4 +13,8 @@ | W5-130 | 校验 G-code interpreter/runtime | 完成 | 自动运行、MDI、暂停/继续/停止、M6/M61 引用边界测试随完整 Node 冒烟通过 | | W5-140 | 校验五轴运动和单位换算 | 完成 | kinematics、real LinuxCNC 5-axis cases、linear unit conversion 测试随完整 Node 冒烟通过 | | W5-150 | 校验 gmoccapy UI 通信和按钮门控 | 完成 | 浏览器 smoke 验证 mode gate、NML/HAL 诊断、POSTGUI 顺序、按钮状态 | +| W5-160 | 细化 M6/M61 换刀 HAL 语义 | 完成 | profile、HAL model 和 UI 诊断明确 `gmoccapy_postgui.hal` 断开手动换刀 GUI pins,并使用 `iocontrol.0.tool-change -> iocontrol.0.tool-changed` 回环;相关 Node、构建、浏览器 smoke 通过 | +| W5-170 | 细化外部 HAL hard-button pin 与 UI action 映射 | 完成 | profile/panel schema、HAL model、communication model、store 和浏览器 DOM 均明确 `gmoccapy.v-button.button-0..6`、`gmoccapy.h-button.button-0` 等硬件按钮 pin 的 rising-edge、可见敏感按钮映射和 Web gate 行为;相关 Node、构建、浏览器 smoke 通过 | +| W5-180 | 细化 ignore-limits、optional-stop、blockdelete 与 override HAL input pin 语义 | 完成 | profile、HAL model、communication model、store 和浏览器 UI 均明确 `gmoccapy.ignore-limits`、`gmoccapy.optional-stop`、`gmoccapy.blockdelete`、override counts/direct-value/reset pins 的 Web 映射;Node、构建、浏览器 smoke 通过 | +| W5-190 | 细化 jog、jog-increment、delete-message、warning-confirm HAL input pin 语义 | 完成 | profile、HAL model、communication model、store 和浏览器 UI 均明确 `gmoccapy.jog.axis.*`、`gmoccapy.jog.jog-inc-*`、`gmoccapy.jog.turtle-jog`、`gmoccapy.delete-message`、`gmoccapy.warning-confirm` 的 Web 映射;Node、构建、浏览器 smoke 通过 | | W5-900 | 更新验收证据和决策记录 | 完成 | 命令、结果、修改原因和后续项已记录 | diff --git a/work/working5/05-验收证据.md b/work/working5/05-验收证据.md index 3161b04..4067a67 100644 --- a/work/working5/05-验收证据.md +++ b/work/working5/05-验收证据.md @@ -9,6 +9,14 @@ | EV-W5-030 | W5-030 | 命令 | `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run build` | 通过,输出 `gmoccapy_static_build=ok` | | EV-W5-040 | W5-040/W5-150 | 命令 | `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run smoke` | 通过,输出 `gmoccapy_shell_smoke=ok`、`gmoccapy_dist_smoke=ok` | | EV-W5-110 | W5-110/W5-150 | 命令 | `node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && node ../tests/node/verify_run_preconditions.mjs && bash ../tests/browser/verify_gmoccapy_shell_browser.sh` | 通过,验证下电禁用 Manual/Jog/MDI/Auto,上电未回零只允许 Manual/Jog,回零后允许 Auto/MDI | +| EV-W5-160 | W5-120/W5-160 | 命令 | `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_profile_boundary.mjs` | 通过,验证 `gmoccapy_postgui.hal` 换刀策略为 `iocontrol-loopback`、手动 gmoccapy 换刀 GUI pins 未连接、3 个 POSTGUI `unlinkp` 被记录 | +| EV-W5-161 | W5-020/W5-030/W5-040/W5-160 | 命令 | `npm run smoke:node`、`npm run build && npm run smoke` | 通过,完整 Node、静态构建、浏览器 gmoccapy shell/dist smoke 均覆盖新增换刀诊断 | +| EV-W5-170 | W5-150/W5-170 | 命令 | `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs` | 通过,验证 hard-button 模型、通信路径、profile pin 顺序和 `GMOCAPY_HARDWARE_BUTTON` gate 行为 | +| EV-W5-171 | W5-020/W5-030/W5-040/W5-170 | 命令 | `npm run smoke:node`、`npm run build && npm run smoke` | 通过,完整 Node、静态构建、浏览器 gmoccapy shell/dist smoke 均覆盖 hard-button DOM 映射和诊断 | +| EV-W5-180 | W5-150/W5-180 | 命令 | `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && bash ../tests/browser/verify_gmoccapy_shell_browser.sh` | 通过,验证 `GMOCAPY_HAL_PIN` 对 ignore-limits、optional-stop、blockdelete、override counts/direct-value/reset pins 的映射和 UI 诊断 | +| EV-W5-181 | W5-020/W5-030/W5-040/W5-180 | 命令 | `npm run smoke:node`、`npm run build && npm run smoke` | 通过,完整 Node、静态构建、浏览器 gmoccapy shell/dist smoke 均覆盖新增 HAL input/override 诊断和 Web 控件 | +| EV-W5-190 | W5-150/W5-190 | 命令 | `node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs`、`node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs`、`bash ../tests/browser/verify_gmoccapy_shell_browser.sh` | 通过,验证 `GMOCAPY_HAL_PIN` 对 jog axis press/release、jog increment rising-edge、turtle jog、delete-message、warning-confirm pins 的映射和 UI 诊断 | +| EV-W5-191 | W5-020/W5-030/W5-040/W5-190 | 命令 | `npm run smoke:node`、`npm run build && npm run smoke` | 通过,完整 Node、静态构建、浏览器 gmoccapy shell/dist smoke 均覆盖新增 jog/message HAL pin 诊断和 Web 行为 | ## 命令记录 @@ -64,6 +72,231 @@ node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && node ../tests/node/verify_ - `run_preconditions_machine_file_smoke=ok` - `gmoccapy_shell_smoke=ok` +### 2026-06-26 R3 + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_profile_boundary.mjs +``` + +结果:通过,输出: + +- `gmoccapy_hal_model_smoke=ok` +- `gmoccapy_xyzab_profile_smoke=ok` +- `profile_boundary_smoke=ok` + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +npm run smoke:node +``` + +结果:通过。 + +关键输出摘要: + +- `linuxcnc_task_hal_runtime_smoke=ok` +- `profile_boundary_smoke=ok` +- `gmoccapy_xyzab_profile_smoke=ok` +- `gmoccapy_hal_model_smoke=ok` +- `gmoccapy_xyzab_gates_smoke=ok` +- `linear_unit_conversion_smoke=ok` + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +npm run build && npm run smoke +``` + +结果:通过,输出: + +- `gmoccapy_static_build=ok` +- `gmoccapy_shell_smoke=ok` +- `gmoccapy_dist_smoke=ok` + +本轮新增浏览器断言: + +- `data-gmoccapy-hal="tool-change"` 必须包含 `iocontrol-loopback`。 +- 同一诊断行必须包含 `manual GUI pins disconnected`。 +- 同一诊断行必须包含 `3 postgui unlinkp`。 + +### 2026-06-26 R4 + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs +``` + +结果:通过,输出: + +- `gmoccapy_hal_model_smoke=ok` +- `gmoccapy_communication_model_smoke=ok` +- `gmoccapy_xyzab_profile_smoke=ok` +- `gmoccapy_xyzab_gates_smoke=ok` + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +npm run smoke:node +``` + +结果:通过。 + +关键输出摘要: + +- `gmoccapy_xyzab_profile_smoke=ok` +- `gmoccapy_communication_model_smoke=ok` +- `gmoccapy_hal_model_smoke=ok` +- `gmoccapy_xyzab_gates_smoke=ok` +- `linear_unit_conversion_smoke=ok` + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +npm run build && npm run smoke +``` + +结果:通过,输出: + +- `gmoccapy_static_build=ok` +- `gmoccapy_shell_smoke=ok` +- `gmoccapy_dist_smoke=ok` + +本轮新增 Node 断言: + +- `gmoccapy.v-button.button-0..6` 对应 `tbtn_estop`、`tbtn_on`、`rbt_manual`、`rbt_mdi`、`rbt_auto`、`tbtn_user_tabs`、`tbtn_setup`。 +- `gmoccapy.h-button.button-0` 对应底部主页面 `btn_homing`,Web dispatch 为 `HOME`。 +- `GMOCAPY_HARDWARE_BUTTON` 忽略 falling edge;rising edge 复用现有 Web gate;未映射或诊断-only pin 给出明确 operator message。 + +本轮新增浏览器断言: + +- 侧边 `estop`、`power`、`mode-manual`、`mode-mdi`、`mode-auto`、`mode-jog` 按钮必须带有对应 `data-gmoccapy-hal-pin="gmoccapy.v-button.button-N"`。 +- 底部 `HOME` 按钮必须带有 `data-gmoccapy-hal-pin="gmoccapy.h-button.button-0"`。 +- `data-gmoccapy-hal="hard-buttons"` 必须包含 `7 right v-button pins`、`4 bottom h-button main refs`、`7 mapped Web buttons`、`rising-edge-only` 和 `insensitive ignored`。 + +### 2026-06-27 R5 + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs && node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && bash ../tests/browser/verify_gmoccapy_shell_browser.sh +``` + +结果:通过,输出: + +- `gmoccapy_hal_model_smoke=ok` +- `gmoccapy_communication_model_smoke=ok` +- `gmoccapy_xyzab_profile_smoke=ok` +- `gmoccapy_xyzab_gates_smoke=ok` +- `gmoccapy_shell_smoke=ok` + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +npm run smoke:node +``` + +结果:通过。 + +关键输出摘要: + +- `gmoccapy_xyzab_profile_smoke=ok` +- `gmoccapy_communication_model_smoke=ok` +- `gmoccapy_hal_model_smoke=ok` +- `gmoccapy_xyzab_gates_smoke=ok` +- `linear_unit_conversion_smoke=ok` + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +npm run build && npm run smoke +``` + +结果:通过,输出: + +- `gmoccapy_static_build=ok` +- `gmoccapy_shell_smoke=ok` +- `gmoccapy_dist_smoke=ok` + +本轮新增 Node 断言: + +- `gmoccapy.ignore-limits` 设置 `gmoccapyGui.ignoreLimits`,active 时记录 `command.override_limits()` 请求语义。 +- `gmoccapy.optional-stop` 按源码 `_optional_blocks()` 交叉驱动 `optionalBlocks`/block delete;`gmoccapy.blockdelete` 按 `_blockdelete()` 交叉驱动 `optionalStop`。 +- override counts 在 count-enable 为 false 时只同步 counts;count-enable 为 true 时按 `gmoccapy_XYZAB.pref` 中 scale 调整 feed/rapid/spindle/jog velocity。 +- direct-value 在 analog-enable 为 false 时忽略;analog-enable 为 true 时按 0..1 比例映射滑块范围。 +- reset pins 忽略 falling edge,rising edge 将 feed/rapid/spindle override 复位到 100。 + +本轮新增浏览器断言: + +- `data-gmoccapy-hal="operator-inputs"` 必须包含 `3 operator pins`、`optional-stop -> block delete`、`blockdelete -> optional stop`。 +- `data-gmoccapy-hal="override-inputs"` 必须包含 `4 override targets`、`counts require enable`、`direct-value requires analog-enable`、`reset rising-edge-only`。 +- Web override 面板的 feed/spindle 100% reset、ignore limits、block delete、optional stop 控件必须更新 store 状态和最后一次 HAL input DOM。 +- 通过 `window.webRtcp5AxisSimulation.dispatch({ type: "GMOCAPY_HAL_PIN", ... })` 验证浏览器内 optional/blockdelete 交叉 pin 行为。 + +### 2026-06-27 R6 + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +node ../tests/node/verify_gmoccapy_hal_model.mjs && node ../tests/node/verify_gmoccapy_communication_model.mjs +``` + +结果:通过,输出: + +- `gmoccapy_hal_model_smoke=ok` +- `gmoccapy_communication_model_smoke=ok` + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +node ../tests/node/verify_gmoccapy_xyzab_profile.mjs && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs +``` + +结果:通过,输出: + +- `gmoccapy_xyzab_profile_smoke=ok` +- `gmoccapy_xyzab_gates_smoke=ok` + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +bash ../tests/browser/verify_gmoccapy_shell_browser.sh +``` + +结果:通过,输出: + +- `gmoccapy_shell_smoke=ok` + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +npm run smoke:node +``` + +结果:通过。 + +关键输出摘要: + +- `gmoccapy_xyzab_profile_smoke=ok` +- `gmoccapy_communication_model_smoke=ok` +- `gmoccapy_hal_model_smoke=ok` +- `gmoccapy_xyzab_gates_smoke=ok` +- `linear_unit_conversion_smoke=ok` + +```bash +cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app +npm run build && npm run smoke +``` + +结果:通过,输出: + +- `gmoccapy_static_build=ok` +- `gmoccapy_shell_smoke=ok` +- `gmoccapy_dist_smoke=ok` + +本轮新增 Node 断言: + +- `gmoccapy.jog.axis.jog-x/y/z/a/b-{plus,minus}` 共 10 个 axis jog pins 按 press/release 电平进入和退出 jog 状态。 +- `gmoccapy.jog.jog-inc-0..5` 只响应 true/rising edge,`jog-inc-0` 为连续点动,`jog-inc-5` 按 `1.2345 in` 映射为 31.3563 mm。 +- `gmoccapy.jog.turtle-jog` 为 level-driven 状态。 +- `gmoccapy.delete-message` falling edge 忽略,true 时删除消息并清除 alert/error 语义。 +- `gmoccapy.warning-confirm` 记录 active warning dialog 的 confirm pin level。 + +本轮新增浏览器断言: + +- `data-gmoccapy-hal="jog-inputs"` 必须包含 `10 axis jog pins`、`6 jog-inc pins`、`press/release level`、`jog-inc rising-edge-only`、`jog-inc-0 continuous`。 +- `data-gmoccapy-hal="message-inputs"` 必须包含 `3 message pins`、`delete-message rising-edge-only`、`warning-confirm level-polled`。 +- 通过 `window.webRtcp5AxisSimulation.dispatch({ type: "GMOCAPY_HAL_PIN", ... })` 验证浏览器内 jog increment、axis jog press/release、turtle jog、delete-message 和 warning-confirm 状态。 + ## 页面/截图/report/job 记录 本轮未生成截图、PDF、job_id 或 report_id。浏览器验收由 headless Chromium smoke 脚本完成。 diff --git a/work/working5/06-决策记录.md b/work/working5/06-决策记录.md index 92bd29d..a3b7186 100644 --- a/work/working5/06-决策记录.md +++ b/work/working5/06-决策记录.md @@ -40,3 +40,99 @@ - `app/src/state/linuxcnc-task-policy.js` 成为模式切换和执行动作的统一互锁入口。 - `app/src/ui/gmoccapy-shell.js` 侧边模式按钮使用同一 gate 输出 disabled、`data-command-ready` 和 `aria-disabled`。 - Node 和浏览器测试增加下电、未回零、已回零三个阶段的断言。 + +## DR-W5-003:XYZAB 换刀按 POSTGUI iocontrol 回环建模,不实现手动换刀弹窗 + +日期:2026-06-26 + +决策: + +- `gmoccapy-xyzab` profile 和 `gmoccapyHalModel` 将 M6/M61 换刀执行路径标记为 `iocontrol-loopback`。 +- `manualGmoccapyPinsConnected = false`,用于说明 `gmoccapy.toolchange-change`、`gmoccapy.toolchange-changed`、`gmoccapy.toolchange-number` 在该配置中没有作为活动 HAL net 接入。 +- UI 只显示换刀 HAL 诊断,不弹出 gmoccapy 手动换刀确认流程。 + +原因: + +- `gmoccapy_postgui.hal` 在 GUI HAL pins 创建后执行 `unlinkp iocontrol.0.tool-change`、`unlinkp iocontrol.0.tool-changed`、`unlinkp iocontrol.0.tool-prep-number`。 +- 同一文件里连接 `gmoccapy.toolchange-*` 的手动换刀 nets 保持注释状态。 +- 活动换刀完成路径是 `net tool-change-loop iocontrol.0.tool-change => iocontrol.0.tool-changed`,与 `core_sim_XYZAB.hal` 的仿真换刀回环语义一致。 + +影响: + +- HAL/profile 诊断现在能区分“存在 gmoccapy toolchange pins”与“此配置没有把这些 pins 接入手动换刀流程”。 +- `verify_gmoccapy_hal_model.mjs`、`verify_gmoccapy_xyzab_profile.mjs`、`verify_profile_boundary.mjs` 和浏览器 smoke 均覆盖该换刀语义。 + +## DR-W5-004:外部 HAL hard-button pin 使用显式映射,不依赖 Web DOM 顺序 + +日期:2026-06-26 + +决策: + +- Web 侧将 `gmoccapy.h-button.*`、`gmoccapy.v-button.*` 建模为显式 pin 到 Web action/selector 的映射。 +- `GMOCAPY_HARDWARE_BUTTON` action 只模拟 rising edge;falling edge 忽略;可执行 pin 复用原有 Web action 和 `linuxcnc-task-policy` gate。 +- Web DOM 使用 `data-gmoccapy-hal-pin`、`data-gmoccapy-native-panel`、`data-gmoccapy-native-button-index` 标记原生 hard-button 对应关系,而不是依赖当前页面按钮的 DOM 顺序。 +- 未实现的 native 页面按钮,如 `tbtn_setup`、`btn_touch`、`btn_tool`、`tbtn_switch_mode`,只保留诊断映射,不伪装成已实现控制。 + +原因: + +- LinuxCNC `gmoccapy.py` 的 `_button_pin_changed()` 只响应 pin 为 true 的 rising edge。 +- `_get_child_button()` 按当前 notebook 页或 `vbtb_main` 的可见非 label 子控件位置查找按钮,并在目标不敏感时忽略。 +- `gmoccapy.glade` 中 `vbtb_main` 的原生顺序是 `tbtn_estop`、`tbtn_on`、`rbt_manual`、`rbt_mdi`、`rbt_auto`、`tbtn_user_tabs`、`tbtn_setup`,Web shell 右侧还包含 reset/TCP 等非原生布局控件,直接用 DOM 顺序会误表达 native 语义。 + +影响: + +- `gmoccapy-xyzab` panel schema 修正 Estop/Power/Manual/MDI/Auto 的 `v-button` pin 顺序。 +- HAL model、communication model、store、UI DOM 和浏览器 smoke 都能追踪 hard-button pin 到 Web action/gate 的行为。 +- 未完成的 optional-stop、blockdelete、ignore-limits 等 pin 仍作为后续独立任务处理。 + +## DR-W5-005:HAL input pin 按 gmoccapy 回调语义建模,不按名称直连 + +日期:2026-06-27 + +决策: + +- Web 侧新增 `GMOCAPY_HAL_PIN` action,用于模拟外部 HAL 输入 pin 对 gmoccapy GUI 状态和命令的影响。 +- `gmoccapy.ignore-limits` 设置 `chk_ignore_limits` 等效状态,active 时记录 `command.override_limits()` 请求语义。 +- `gmoccapy.optional-stop` 不直连 optional stop,而是按源码 `_optional_blocks()` 驱动 `tbtn_optional_blocks`,也就是 block delete。 +- `gmoccapy.blockdelete` 不直连 block delete,而是按源码 `_blockdelete()` 调用 `command.set_optional_stop()`。 +- override counts 只有对应 `*.count-enable` 为 true 时才调整滑块;否则只同步 counts 基线。 +- override direct-value 只有对应 `*.analog-enable` 为 true 时才接管滑块;reset pins 只响应 true/rising edge。 + +原因: + +- `gmoccapy.py` 中 `optional-stop` pin 连接 `_optional_blocks()`,该回调设置 `tbtn_optional_blocks`,随后 `on_tbtn_optional_blocks_toggled()` 调用 `command.set_block_delete()`。 +- `gmoccapy.py` 中 `blockdelete` pin 连接 `_blockdelete()`,该回调调用 `command.set_optional_stop()`。 +- `_on_counts_changed()`、`_on_analog_value_changed()` 和 `_reset_override()` 明确要求 enable/rising-edge 条件;`gmoccapy_XYZAB.pref` 给出本配置默认 scale:feed/rapid/spindle 为 1,jog velocity 为 140.4。 +- 如果按 pin 名称直接连同名 Web 状态,会掩盖 gmoccapy 原生回调的交叉行为和 enable 条件。 + +影响: + +- `app/src/state/store.js` 新增 `gmoccapyGui` 状态与 `GMOCAPY_HAL_PIN` 分发。 +- `gmoccapyHalModel` 和 `gmoccapyCommunicationModel` 现在显式记录 operator input pins、override pins、enable 规则和 reset 规则。 +- UI override 面板提供 ignore limits、block delete、optional stop 和 100% reset 控件,诊断区显示 operator input 与 override input 语义。 +- Node 和浏览器 smoke 均覆盖 HAL input/override 语义,防止后续把交叉 pin 改成同名直连。 + +## DR-W5-006:jog 与消息 HAL pin 按 gmoccapy 回调边界建模 + +日期:2026-06-27 + +决策: + +- `gmoccapy.jog.axis.jog-*-plus/minus` 作为按下/释放电平输入处理:pin true 触发 jog pressed,pin false 触发 jog released。 +- `gmoccapy.jog.jog-inc-N` 只响应 true/rising edge;`jog-inc-0` 表示 continuous jog,`jog-inc-1..5` 映射 `gmoccapy_XYZAB.ini` 的 `[DISPLAY] INCREMENTS`。 +- `gmoccapy.jog.turtle-jog` 作为 level-driven 状态记录,不伪装成 Web 普通倍率按钮。 +- `gmoccapy.delete-message` 只在 true 时执行删除消息语义,falling edge 忽略。 +- `gmoccapy.warning-confirm` 作为 warning dialog 轮询确认 pin 记录 level,不强行制造不存在的 Web modal 流程。 + +原因: + +- `gmoccapy.py` 中 `_on_pin_jog_changed()` 明确在 pin true 时调用 `_on_btn_jog_pressed()`,在 pin false 时调用 `_on_btn_jog_released()`;这与 hard-button 的 rising-edge-only 语义不同。 +- `_on_pin_incr_changed()` 对 `not pin.get()` 直接 return,因此 jog increment pin 只响应 true;`_make_jog_increments()` 会把 continuous 的 `rbt_0` 放在第一位,`get_increments()` 再把 INI 的 5 个增量值追加到后面。 +- `_del_message_changed()` 只在 pin true 时删除 alert 或最后一条 notification;`dialogs.warning_dialog()` 每 100ms 轮询 `warning-confirm` pin 来接受活动 warning dialog。 +- 如果把这些 pin 当作普通 Web click 或同名状态直连,会混淆 gmoccapy 源码中 “硬按钮”、“jog pin”、“message pin” 三类不同输入边界。 + +影响: + +- `GMOCAPY_HAL_PIN` 现在覆盖 jog axis press/release、jog increment selection、turtle jog、delete-message 和 warning-confirm。 +- HAL model 和 communication model 现在能分别呈现 hard-button、operator/override input、jog input、message input 的不同语义。 +- UI 诊断增加 `HAL jog` 和 `HAL messages`,浏览器 smoke 通过 DOM 和公开 dispatch 验证这些语义。 diff --git a/备忘/备忘3.txt b/备忘/备忘3.txt index d8b7eb7..1aad29f 100644 --- a/备忘/备忘3.txt +++ b/备忘/备忘3.txt @@ -1,8 +1,23 @@ -对标“/home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md“的五轴机床的执行流程,执行、测试和完善”/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan“这个从linuxcnc移植过来的数控系统仿真的功能。在数控系统仿真仿真功能方面,与linuxcnc的源程序实现的功能完全一直。不一致的地方,进行修改”/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan“这个项目。先在“/home/meswork/cnc_wams/work/working5”中创建如下文件,之后,按如下文件工作。 +对标“/home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md“的五轴机床的执行流程, +执行、测试和完善”/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan“这个从linuxcnc移植过来的数控系统仿真的功能。 +在数控系统仿真仿真功能方面,与linuxcnc的源程序实现的功能完全一直。 +不一致的地方,进行修改”/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan“这个项目。 +先在“/home/meswork/cnc_wams/work/working5”中创建如下文件,之后,按如下文件工作。 + 01-项目功能内容 02-项目程序开发详细步骤 03-推进台账:每轮做了什么、改了哪些文件、验证了什么、下一步是什么。 04-任务矩阵:所有任务编号、状态、验收标准,防止重复做。 05-验收证据:命令、页面、job_id、report_id、PDF、截图等证据。 06-决策记录:为什么这么做,后续变更时不反复争论同一个问题 -另加 README 索引,便于后续按任务矩阵继续推进。 \ No newline at end of file +另加 README 索引,便于后续按任务矩阵继续推进。 + +## Logging Rule + +After every GPT/Codex execution completes, append the full execution process +log to: + +`/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` + + +代码区也有一批未提交改动 \ No newline at end of file