完善 working5 gmoccapy HAL 输入对标

This commit is contained in:
2026-06-26 22:07:41 -04:00
parent 0d79b3545b
commit 917cec15ad
20 changed files with 2853 additions and 27 deletions

View File

@@ -11,3 +11,5 @@ After every GPT/Codex execution completes, append the full execution process
log to: log to:
`/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md` `/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.

Binary file not shown.

View File

@@ -2,6 +2,15 @@ import { getSourceReferencesForProfile } from "./source-reference-map.js";
const sourceReferenceObjects = getSourceReferencesForProfile("gmoccapy-xyzab"); 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 = { export const gmoccapyXyzabPanelSchema = {
id: "gmoccapy-xyzab-hal-component", id: "gmoccapy-xyzab-hal-component",
profileId: "gmoccapy-xyzab", profileId: "gmoccapy-xyzab",
@@ -20,8 +29,8 @@ export const gmoccapyXyzabPanelSchema = {
id: "tbtn-estop", id: "tbtn-estop",
type: "button", type: "button",
text: "ESTOP", text: "ESTOP",
halpin: "gmoccapy.h-button.button-0", halpin: "gmoccapy.v-button.button-0",
halNet: "gmoccapy-h-button-0", halNet: "gmoccapy-v-button-0",
target: "gmoccapy.command.STATE_ESTOP", target: "gmoccapy.command.STATE_ESTOP",
nativeCommand: "STATE_ESTOP / STATE_ESTOP_RESET", nativeCommand: "STATE_ESTOP / STATE_ESTOP_RESET",
webAction: { type: "ESTOP" }, webAction: { type: "ESTOP" },
@@ -30,8 +39,8 @@ export const gmoccapyXyzabPanelSchema = {
id: "tbtn-on", id: "tbtn-on",
type: "button", type: "button",
text: "POWER", text: "POWER",
halpin: "gmoccapy.h-button.button-1", halpin: "gmoccapy.v-button.button-1",
halNet: "gmoccapy-h-button-1", halNet: "gmoccapy-v-button-1",
target: "gmoccapy.command.STATE_ON", target: "gmoccapy.command.STATE_ON",
nativeCommand: "STATE_ON / STATE_OFF", nativeCommand: "STATE_ON / STATE_OFF",
webAction: { type: "TOGGLE_POWER" }, webAction: { type: "TOGGLE_POWER" },
@@ -40,8 +49,8 @@ export const gmoccapyXyzabPanelSchema = {
id: "rbt-manual", id: "rbt-manual",
type: "button", type: "button",
text: "MANUAL", text: "MANUAL",
halpin: "gmoccapy.v-button.button-0", halpin: "gmoccapy.v-button.button-2",
halNet: "gmoccapy-v-button-0", halNet: "gmoccapy-v-button-2",
target: "gmoccapy.command.MODE_MANUAL", target: "gmoccapy.command.MODE_MANUAL",
nativeCommand: "MODE_MANUAL", nativeCommand: "MODE_MANUAL",
webAction: { type: "SET_MODE", mode: "manual" }, webAction: { type: "SET_MODE", mode: "manual" },
@@ -50,8 +59,8 @@ export const gmoccapyXyzabPanelSchema = {
id: "rbt-mdi", id: "rbt-mdi",
type: "button", type: "button",
text: "MDI", text: "MDI",
halpin: "gmoccapy.v-button.button-1", halpin: "gmoccapy.v-button.button-3",
halNet: "gmoccapy-v-button-1", halNet: "gmoccapy-v-button-3",
target: "gmoccapy.command.MODE_MDI", target: "gmoccapy.command.MODE_MDI",
nativeCommand: "MODE_MDI", nativeCommand: "MODE_MDI",
webAction: { type: "SET_MODE", mode: "mdi" }, webAction: { type: "SET_MODE", mode: "mdi" },
@@ -60,8 +69,8 @@ export const gmoccapyXyzabPanelSchema = {
id: "rbt-auto", id: "rbt-auto",
type: "button", type: "button",
text: "AUTO", text: "AUTO",
halpin: "gmoccapy.v-button.button-2", halpin: "gmoccapy.v-button.button-4",
halNet: "gmoccapy-v-button-2", halNet: "gmoccapy-v-button-4",
target: "gmoccapy.command.MODE_AUTO", target: "gmoccapy.command.MODE_AUTO",
nativeCommand: "MODE_AUTO", nativeCommand: "MODE_AUTO",
webAction: { type: "SET_MODE", 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-at-speed", source: "near_speed.out", target: "spindle.0.at-speed" },
{ signal: "spindle-abs", source: "abs_spindle_feedback.out", target: "gmoccapy.spindle_feedback_bar" }, { 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: { nativeRuntime: {
@@ -269,11 +298,32 @@ export const gmoccapyXyzabProfile = {
halPins: [ halPins: [
"gmoccapy.h-button.button-0", "gmoccapy.h-button.button-0",
"gmoccapy.v-button.button-0", "gmoccapy.v-button.button-0",
"gmoccapy.jog.axis.jog-x-plus", ...gmoccapyXyzabJogAxisPins,
"gmoccapy.jog.axis.jog-b-minus", ...gmoccapyXyzabJogIncrementPins,
"gmoccapy.jog.jog-increment",
"gmoccapy.jog.turtle-jog",
"gmoccapy.feed.feed-override.counts", "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.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.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-x",
"gmoccapy.tooloffset-z", "gmoccapy.tooloffset-z",
"gmoccapy.toolchange-change", "gmoccapy.toolchange-change",
@@ -284,6 +334,8 @@ export const gmoccapyXyzabProfile = {
"gmoccapy.spindle_feedback_bar", "gmoccapy.spindle_feedback_bar",
"gmoccapy.spindle_at_speed_led", "gmoccapy.spindle_at_speed_led",
"gmoccapy.error", "gmoccapy.error",
"gmoccapy.delete-message",
"gmoccapy.warning-confirm",
], ],
offsets: { offsets: {
toolOffsetX: 0, toolOffsetX: 0,

View File

@@ -20,6 +20,16 @@ export const gmoccapyCommunicationModel = {
path: ["gmoccapy.* pins", "HAL shared memory", "halui/iocontrol/motmod/spindle_sim"], path: ["gmoccapy.* pins", "HAL shared memory", "halui/iocontrol/motmod/spindle_sim"],
notes: "postgui HAL connects gmoccapy pins after halcomp.ready().", 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: { remap: {
label: "Interpreter REMAP", label: "Interpreter REMAP",
path: ["milltask", "RS274NGC", "Python remap prolog/ngc/epilog"], path: ["milltask", "RS274NGC", "Python remap prolog/ngc/epilog"],
@@ -148,6 +158,34 @@ export const gmoccapyCommunicationModel = {
nativeStatus: "iocontrol.0.coolant-flood/mist", nativeStatus: "iocontrol.0.coolant-flood/mist",
gate: "blocked in estop/off", 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"), model.startupStages.findIndex((stage) => stage.id === "gmoccapy"),
hasNativeNmlCommandPath: model.nativePaths.command.path.includes("NML emcCommand"), hasNativeNmlCommandPath: model.nativePaths.command.path.includes("NML emcCommand"),
hasNativeHalPath: model.nativePaths.hal.path.includes("HAL shared memory"), 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"), hasWebStorePath: model.webPath.path.includes("store.dispatch"),
semanticBoundary: model.semanticBoundary, semanticBoundary: model.semanticBoundary,
}; };

View File

@@ -68,7 +68,13 @@ export const gmoccapyHalModel = {
"gmoccapy.jog.axis.jog-b-plus", "gmoccapy.jog.axis.jog-b-plus",
"gmoccapy.jog.axis.jog-b-minus", "gmoccapy.jog.axis.jog-b-minus",
"gmoccapy.jog.jog-inc-0", "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.jog-increment",
"gmoccapy.jog.turtle-jog",
], ],
webSource: "JOG dispatch and machine.jogIncrement", webSource: "JOG dispatch and machine.jogIncrement",
semanticBoundary: "native_jog_pin_reference_web_task_policy_gate", semanticBoundary: "native_jog_pin_reference_web_task_policy_gate",
@@ -77,17 +83,38 @@ export const gmoccapyHalModel = {
group: "override", group: "override",
pins: [ pins: [
"gmoccapy.feed.feed-override.counts", "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.feed-override.direct-value",
"gmoccapy.feed.reset-feed-override",
"gmoccapy.spindle.spindle-override.counts", "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.spindle-override.direct-value",
"gmoccapy.spindle.reset-spindle-override",
"gmoccapy.jog.jog-velocity.counts", "gmoccapy.jog.jog-velocity.counts",
"gmoccapy.jog.jog-velocity.count-enable",
"gmoccapy.jog.jog-velocity.analog-enable",
"gmoccapy.jog.jog-velocity.direct-value", "gmoccapy.jog.jog-velocity.direct-value",
"gmoccapy.rapid.rapid-override.counts", "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.rapid-override.direct-value",
"gmoccapy.rapid.reset-rapid-override",
], ],
webSource: "feed/spindle override state", webSource: "feed/spindle override state",
semanticBoundary: "native_override_pin_reference_web_state_mapping", 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", group: "tool",
pins: [ pins: [
@@ -98,7 +125,7 @@ export const gmoccapyHalModel = {
"gmoccapy.toolchange-changed", "gmoccapy.toolchange-changed",
"gmoccapy.toolchange-number", "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", semanticBoundary: "native_tool_pin_reference_simulated_tool_loop",
}, },
{ {
@@ -257,12 +284,319 @@ export const gmoccapyHalModel = {
role: "automatic simulated tool-change completion", 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", semanticBoundary: "gmoccapy_hal_pin_postgui_reference_web_diagnostic_only",
}; };
export function createGmoccapyHalSummary(model = gmoccapyHalModel) { export function createGmoccapyHalSummary(model = gmoccapyHalModel) {
const pinCount = model.nativePins.reduce((total, group) => total + group.pins.length, 0); const pinCount = model.nativePins.reduce((total, group) => total + group.pins.length, 0);
const postguiFile = model.halFiles.find((file) => file.stage === "POSTGUI_HALFILE"); const postguiFile = model.halFiles.find((file) => file.stage === "POSTGUI_HALFILE");
const mappedHardwareButtonCount = [
...(model.hardwareButtons?.verticalMain || []),
...(model.hardwareButtons?.bottomMain || []),
].filter((button) => button.webSelector).length;
return { return {
apiName: `${model.apiName}-summary`, apiName: `${model.apiName}-summary`,
profileId: model.profileId, profileId: model.profileId,
@@ -275,6 +609,48 @@ export function createGmoccapyHalSummary(model = gmoccapyHalModel) {
postguiRequiresHalcompReady: Boolean(postguiFile?.requiresHalcompReady) && postguiRequiresHalcompReady: Boolean(postguiFile?.requiresHalcompReady) &&
model.postguiNets.every((net) => net.requiresHalcompReady), model.postguiNets.every((net) => net.requiresHalcompReady),
hasToolChangeSimulationLoop: model.postguiNets.some((net) => net.simulationLoop), 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, 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;
}

View File

@@ -20,6 +20,7 @@ import {
buildTaskHalProgramMotionPlan, buildTaskHalProgramMotionPlan,
buildTaskHalSessionFromMachineFiles, buildTaskHalSessionFromMachineFiles,
} from "../runtime/linuxcnc-task-hal-runtime.js"; } from "../runtime/linuxcnc-task-hal-runtime.js";
import { gmoccapyHalModel, resolveGmoccapyHardwareButton } from "../runtime/gmoccapy-hal-model.js";
import { import {
createLinuxCncTaskPolicyStatus, createLinuxCncTaskPolicyStatus,
gateLinuxCncTaskAction, gateLinuxCncTaskAction,
@@ -230,6 +231,35 @@ const initialState = {
flood: true, flood: true,
mist: false, 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: { preview: {
pathPoints: 64, pathPoints: 64,
selectedView: "iso", selectedView: "iso",
@@ -964,6 +994,34 @@ export function createSimulationStore(seed = {}) {
case "REFRESH_KINEMATICS_FRAME": case "REFRESH_KINEMATICS_FRAME":
return refreshAsyncKinematicsFrame({ operatorMessage: "LinuxCNC kinematics frame refreshed" }); return refreshAsyncKinematicsFrame({ operatorMessage: "LinuxCNC kinematics frame refreshed" });
break; 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": case "TOGGLE_POWER":
{ {
const gate = gateLinuxCncTaskAction(state, action); const gate = gateLinuxCncTaskAction(state, action);
@@ -1585,6 +1643,12 @@ export function createSimulationStore(seed = {}) {
200, 200,
), ),
}, },
gmoccapyGui: {
...state.gmoccapyGui,
lastHalPin: null,
lastHalPinValue: null,
lastHalPinEffect: `${action.target} override manual step`,
},
operatorMessage: `${action.target} override adjusted`, operatorMessage: `${action.target} override adjusted`,
}); });
} }
@@ -1601,10 +1665,55 @@ export function createSimulationStore(seed = {}) {
...state.spindle, ...state.spindle,
override: clampPercent(state.spindle.override + action.delta, 0, 150), 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", operatorMessage: "spindle override adjusted",
}); });
} }
break; 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": case "TOGGLE_COOLANT":
{ {
const gate = gateLinuxCncTaskAction(state, action); const gate = gateLinuxCncTaskAction(state, action);
@@ -3269,6 +3378,535 @@ function clampPercent(value, min, max) {
return Math.min(Math.max(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) { function buildDroFromFrame(frame, runtimeFeedback = null) {
const dtg = runtimeFeedback?.dtg || null; const dtg = runtimeFeedback?.dtg || null;
return { return {

View File

@@ -735,7 +735,7 @@ button[data-icon-name] span {
grid-area: override; grid-area: override;
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(4, minmax(0, 1fr));
gap: 6px; gap: 6px;
padding: 6px; padding: 6px;
border-top: 2px solid var(--border); border-top: 2px solid var(--border);
@@ -806,7 +806,7 @@ button[data-icon-name] span {
.stepper { .stepper {
display: grid; display: grid;
grid-template-columns: 30px minmax(54px, 1fr) 30px; grid-template-columns: 30px minmax(48px, 1fr) 30px 36px;
gap: 2px; gap: 2px;
align-items: center; align-items: center;
margin-top: 1px; margin-top: 1px;
@@ -827,6 +827,42 @@ button[data-icon-name] span {
min-width: 0; min-width: 0;
min-height: 26px; min-height: 26px;
padding: 2px 4px; 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 { .spindle-coolant-panel {

View File

@@ -353,13 +353,13 @@ function renderSidebar(element, state, dispatch) {
const manualGate = gateLinuxCncTaskAction(state, { type: "SET_MODE", mode: "manual" }); const manualGate = gateLinuxCncTaskAction(state, { type: "SET_MODE", mode: "manual" });
const mdiGate = gateLinuxCncTaskAction(state, { type: "SET_MODE", mode: "mdi" }); const mdiGate = gateLinuxCncTaskAction(state, { type: "SET_MODE", mode: "mdi" });
element.innerHTML = ` 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_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 })} ${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: "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_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 })} ${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 })} ${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 })} ${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) })}
<button type="button" class="sidebar-button" data-action="kins-identity" data-active="${state.kinsType === "identity"}">IDENTITY</button> <button type="button" class="sidebar-button" data-action="kins-identity" data-active="${state.kinsType === "identity"}">IDENTITY</button>
<button type="button" class="sidebar-button" data-action="kins-tcp" data-active="${state.kinsType.startsWith("tcp-")}"${tcpDisabled}>TCP</button> <button type="button" class="sidebar-button" data-action="kins-tcp" data-active="${state.kinsType.startsWith("tcp-")}"${tcpDisabled}>TCP</button>
<time>13:30:31<br />20.06.2026</time> <time>13:30:31<br />20.06.2026</time>
@@ -449,8 +449,14 @@ function renderInfoTabs(element, state) {
<dt>HAL model:</dt><dd data-gmoccapy-hal="boundary">${gmoccapyHalModel.semanticBoundary}</dd> <dt>HAL model:</dt><dd data-gmoccapy-hal="boundary">${gmoccapyHalModel.semanticBoundary}</dd>
<dt>HAL files:</dt><dd data-gmoccapy-hal="files">${halSummary.halFileCount} files / POSTGUI ${halSummary.postguiRequiresHalcompReady ? "after halcomp.ready" : "pending"}</dd> <dt>HAL files:</dt><dd data-gmoccapy-hal="files">${halSummary.halFileCount} files / POSTGUI ${halSummary.postguiRequiresHalcompReady ? "after halcomp.ready" : "pending"}</dd>
<dt>HAL pins:</dt><dd data-gmoccapy-hal="pins">${halSummary.nativePinGroupCount} groups / ${halSummary.nativePinCount} pins</dd> <dt>HAL pins:</dt><dd data-gmoccapy-hal="pins">${halSummary.nativePinGroupCount} groups / ${halSummary.nativePinCount} pins</dd>
<dt>Hard buttons:</dt><dd data-gmoccapy-hal="hard-buttons">${formatGmoccapyHardButtons(halSummary)}</dd>
<dt>HAL jog:</dt><dd data-gmoccapy-hal="jog-inputs">${formatGmoccapyJogInputs(halSummary, state)}</dd>
<dt>HAL inputs:</dt><dd data-gmoccapy-hal="operator-inputs">${formatGmoccapyHalInputs(halSummary, state)}</dd>
<dt>HAL overrides:</dt><dd data-gmoccapy-hal="override-inputs">${formatGmoccapyOverrideInputs(halSummary, state)}</dd>
<dt>HAL messages:</dt><dd data-gmoccapy-hal="message-inputs">${formatGmoccapyMessageInputs(halSummary, state)}</dd>
<dt>postgui nets:</dt><dd data-gmoccapy-hal="postgui">${gmoccapyHalModel.postguiNets.map((net) => net.signal).join(" / ")}</dd> <dt>postgui nets:</dt><dd data-gmoccapy-hal="postgui">${gmoccapyHalModel.postguiNets.map((net) => net.signal).join(" / ")}</dd>
<dt>Tool loop:</dt><dd data-gmoccapy-hal="tool-loop">${halSummary.hasToolChangeSimulationLoop ? "simulated tool-change loop" : "pending"}</dd> <dt>Tool loop:</dt><dd data-gmoccapy-hal="tool-loop">${halSummary.hasToolChangeSimulationLoop ? "simulated tool-change loop" : "pending"}</dd>
<dt>Tool change:</dt><dd data-gmoccapy-hal="tool-change">${formatGmoccapyToolChange(halSummary)}</dd>
<dt>Operator:</dt><dd data-operator-message>${escapeHtml(state.operatorMessage)}</dd> <dt>Operator:</dt><dd data-operator-message>${escapeHtml(state.operatorMessage)}</dd>
</dl> </dl>
`; `;
@@ -597,6 +603,15 @@ function renderOverride(element, state, dispatch) {
</section> </section>
${overrideControl("Rapid Override", "rapid", state.feed.rapidOverride)} ${overrideControl("Rapid Override", "rapid", state.feed.rapidOverride)}
${overrideControl("Feed Rate", "feed", state.feed.feedOverride, `F ${state.feed.feedRate}`)} ${overrideControl("Feed Rate", "feed", state.feed.feedOverride, `F ${state.feed.feedRate}`)}
<section class="override-control" data-control="gmoccapy-hal-inputs">
<h2>HAL Inputs</h2>
<div class="toggle-row">
<button type="button" data-action="ignore-limits" data-active="${state.gmoccapyGui.ignoreLimits}">Limits</button>
<button type="button" data-action="block-delete" data-active="${state.gmoccapyGui.optionalBlocks}">/ Block</button>
<button type="button" data-action="optional-stop" data-active="${state.gmoccapyGui.optionalStop}">M1</button>
</div>
<div class="status-chip" data-value="gmoccapy-hal-last">${escapeHtml(formatGmoccapyHalLastInput(state))}</div>
</section>
`; `;
for (const target of ["rapid", "feed"]) { for (const target of ["rapid", "feed"]) {
@@ -606,7 +621,22 @@ function renderOverride(element, state, dispatch) {
element.querySelector(`[data-action="${target}-override-up"]`).addEventListener("click", () => { element.querySelector(`[data-action="${target}-override-up"]`).addEventListener("click", () => {
dispatch({ type: "ADJUST_OVERRIDE", target, delta: 10 }); 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 = "") { function overrideControl(label, target, value, prefix = "") {
@@ -618,6 +648,7 @@ function overrideControl(label, target, value, prefix = "") {
<button type="button" data-action="${target}-override-down">-</button> <button type="button" data-action="${target}-override-down">-</button>
<div data-value="${target}-override">${value} %</div> <div data-value="${target}-override">${value} %</div>
<button type="button" data-action="${target}-override-up">+</button> <button type="button" data-action="${target}-override-up">+</button>
<button type="button" data-action="${target}-override-reset">100</button>
</div> </div>
</section> </section>
`; `;
@@ -647,6 +678,7 @@ function renderSpindleCoolant(element, state, dispatch) {
<button type="button" data-action="spindle-override-down">-</button> <button type="button" data-action="spindle-override-down">-</button>
<div data-value="spindle-override">${state.spindle.override} %</div> <div data-value="spindle-override">${state.spindle.override} %</div>
<button type="button" data-action="spindle-override-up">+</button> <button type="button" data-action="spindle-override-up">+</button>
<button type="button" data-action="spindle-override-reset">100</button>
</div> </div>
<div class="spindle-range"><i style="width: ${(state.spindle.rpm / 6000) * 100}%"></i></div> <div class="spindle-range"><i style="width: ${(state.spindle.rpm / 6000) * 100}%"></i></div>
</section> </section>
@@ -664,6 +696,9 @@ function renderSpindleCoolant(element, state, dispatch) {
element.querySelector('[data-action="spindle-override-up"]').addEventListener("click", () => { element.querySelector('[data-action="spindle-override-up"]').addEventListener("click", () => {
dispatch({ type: "ADJUST_SPINDLE_OVERRIDE", delta: 10 }); 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", () => { element.querySelector('[data-action="spindle-forward"]').addEventListener("click", () => {
dispatch({ type: "SET_SPINDLE_DIRECTION", direction: "forward" }); dispatch({ type: "SET_SPINDLE_DIRECTION", direction: "forward" });
}); });
@@ -716,7 +751,7 @@ function renderBottomControls(element, state, dispatch) {
title: gate.allowed ? label : gate.operatorMessage || "blocked", title: gate.allowed ? label : gate.operatorMessage || "blocked",
active: bottomControlActive(state, action), active: bottomControlActive(state, action),
disabled: Boolean(disabled), disabled: Boolean(disabled),
attrs: blockedAttrs, attrs: `${blockedAttrs}${bottomNativeButtonAttrs(action)}`,
}); });
}) })
.join("")} .join("")}
@@ -785,6 +820,17 @@ function bottomIconButtonId(action) {
}[action] || "btn_load"; }[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) { function bottomControlActive(state, action) {
if (action === "PAUSE") return state.runState === "paused"; if (action === "PAUSE") return state.runState === "paused";
if (action === "RUN" || action === "RESUME") return state.runState === "running"; if (action === "RUN" || action === "RESUME") return state.runState === "running";
@@ -855,6 +901,79 @@ function formatGmoccapyActionMapping(actionId) {
return `${mapping.nativeCommand} / ${mapping.gate}`; 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) { function formatNumber(value, digits = 3) {
return Number(value).toFixed(digits); return Number(value).toFixed(digits);
} }

View File

@@ -5103,3 +5103,582 @@ Final push and verification:
Outcome: Outcome:
- Backup-directory file `备忘/备忘3.txt` is now committed and pushed to cloud repository. - 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. - 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 R5gmoccapy 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-onlyjog 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 halPinsaxis 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。

View File

@@ -282,6 +282,25 @@
throw new Error(`missing gmoccapy icon ${selector} expected ${iconName}, got ${button?.dataset.iconName}`); 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")) { if (!doc.querySelector('[data-gmoccapy-comm="boundary"]')?.textContent.includes("native_gmoccapy_nml_hal_reference")) {
throw new Error("missing gmoccapy communication boundary diagnostic"); throw new Error("missing gmoccapy communication boundary diagnostic");
} }
@@ -294,6 +313,60 @@
if (!doc.querySelector('[data-gmoccapy-hal="postgui"]')?.textContent.includes("tool-change-loop")) { if (!doc.querySelector('[data-gmoccapy-hal="postgui"]')?.textContent.includes("tool-change-loop")) {
throw new Error("missing gmoccapy HAL postgui diagnostic"); 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")) { if (!doc.querySelector('[data-tool-preview="summary"]')?.textContent.includes("T1")) {
throw new Error("missing tool preview summary"); throw new Error("missing tool preview summary");
} }
@@ -877,6 +950,16 @@
if (win.webRtcp5AxisSimulation.getState().runState !== "idle") { if (win.webRtcp5AxisSimulation.getState().runState !== "idle") {
throw new Error("RESET did not return to 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(); doc.querySelector('[data-action="feed-override-down"]').click();
await wait(50); await wait(50);
if (win.webRtcp5AxisSimulation.getState().feed.feedOverride !== 90) { if (win.webRtcp5AxisSimulation.getState().feed.feedOverride !== 90) {
@@ -885,6 +968,11 @@
if (doc.querySelector('[data-value="feed-override"]')?.textContent.trim() !== "90 %") { if (doc.querySelector('[data-value="feed-override"]')?.textContent.trim() !== "90 %") {
throw new Error("feed override DOM did not update"); 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(); doc.querySelector('[data-action="rapid-override-up"]').click();
await wait(50); await wait(50);
if (win.webRtcp5AxisSimulation.getState().feed.rapidOverride !== 110) { if (win.webRtcp5AxisSimulation.getState().feed.rapidOverride !== 110) {
@@ -895,6 +983,121 @@
if (win.webRtcp5AxisSimulation.getState().spindle.override !== 110) { if (win.webRtcp5AxisSimulation.getState().spindle.override !== 110) {
throw new Error("spindle override button did not update state"); 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-flood"]').click();
doc.querySelector('[data-action="toggle-mist"]').click(); doc.querySelector('[data-action="toggle-mist"]').click();
await wait(50); await wait(50);

View File

@@ -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.stat()"), true);
assert.equal(model.nativePaths.status.path.includes("linuxcnc.error_channel()"), 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.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.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"]); 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", "abort",
"spindle", "spindle",
"coolant", "coolant",
"hardware-button",
"hal-pin-input",
"hal-jog-pin",
"hal-warning-confirm",
]) { ]) {
assert.equal(actionIds.includes(actionId), true, `${actionId} missing`); 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.nativeStatus.includes("spindle.0.forward"), true);
assert.equal(spindleMapping.gate.includes("restricted during interpreter"), 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); const summary = createGmoccapyCommunicationSummary(model);
assert.equal(summary.apiName, "web-rtcp-5axis-gmoccapy-communication-model-summary"); assert.equal(summary.apiName, "web-rtcp-5axis-gmoccapy-communication-model-summary");
assert.equal(summary.profileId, "gmoccapy-xyzab"); assert.equal(summary.profileId, "gmoccapy-xyzab");
assert.equal(summary.nativePathCount, 4); assert.equal(summary.nativePathCount, 6);
assert.equal(summary.startupStageCount, 9); assert.equal(summary.startupStageCount, 9);
assert.equal(summary.actionCount, 15); assert.equal(summary.actionCount, 19);
assert.equal(summary.postguiAfterHalcompReady, true); assert.equal(summary.postguiAfterHalcompReady, true);
assert.equal(summary.hasNativeNmlCommandPath, true); assert.equal(summary.hasNativeNmlCommandPath, true);
assert.equal(summary.hasNativeHalPath, true); assert.equal(summary.hasNativeHalPath, true);
assert.equal(summary.hasNativeHardwareButtonPath, true);
assert.equal(summary.hasNativeHalPinInputPath, true);
assert.equal(summary.hasWebStorePath, true); assert.equal(summary.hasWebStorePath, true);
assert.equal(summary.semanticBoundary, model.semanticBoundary); assert.equal(summary.semanticBoundary, model.semanticBoundary);

View File

@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import { import {
createGmoccapyHalSummary, createGmoccapyHalSummary,
gmoccapyHalModel, gmoccapyHalModel,
resolveGmoccapyHardwareButton,
} from "../../app/src/runtime/gmoccapy-hal-model.js"; } from "../../app/src/runtime/gmoccapy-hal-model.js";
const model = gmoccapyHalModel; const model = gmoccapyHalModel;
@@ -23,6 +24,7 @@ for (const groupName of [
"hard-buttons", "hard-buttons",
"jog", "jog",
"override", "override",
"operator-inputs",
"tool", "tool",
"program", "program",
"message", "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.h-button.button-0"), true);
assert.equal(groups.get("hard-buttons").pins.includes("gmoccapy.v-button.button-6"), 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.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.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("tool").pins.includes("gmoccapy.toolchange-changed"), true);
assert.equal(groups.get("program").pins.includes("gmoccapy.program.current-line"), true); assert.equal(groups.get("program").pins.includes("gmoccapy.program.current-line"), true);
assert.equal(groups.get("message").pins.includes("gmoccapy.error"), 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.target === "gmoccapy.tooloffset-x"), true);
assert.equal(model.postguiNets.some((net) => net.simulationLoop && net.signal === "tool-change-loop"), 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); const summary = createGmoccapyHalSummary(model);
assert.equal(summary.apiName, "web-rtcp-5axis-gmoccapy-hal-model-summary"); assert.equal(summary.apiName, "web-rtcp-5axis-gmoccapy-hal-model-summary");
assert.equal(summary.profileId, "gmoccapy-xyzab"); assert.equal(summary.profileId, "gmoccapy-xyzab");
assert.equal(summary.halFileCount, 4); assert.equal(summary.halFileCount, 4);
assert.equal(summary.nativePinGroupCount, 7); assert.equal(summary.nativePinGroupCount, 8);
assert.equal(summary.nativePinCount >= 50, true); assert.equal(summary.nativePinCount >= 50, true);
assert.equal(summary.coreNetCount, 10); assert.equal(summary.coreNetCount, 10);
assert.equal(summary.spindleNetCount, 4); assert.equal(summary.spindleNetCount, 4);
assert.equal(summary.postguiNetCount, 5); assert.equal(summary.postguiNetCount, 5);
assert.equal(summary.postguiRequiresHalcompReady, true); assert.equal(summary.postguiRequiresHalcompReady, true);
assert.equal(summary.hasToolChangeSimulationLoop, 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); assert.equal(summary.semanticBoundary, model.semanticBoundary);
console.log("gmoccapy_hal_model_smoke=ok"); console.log("gmoccapy_hal_model_smoke=ok");

View File

@@ -200,4 +200,116 @@ assert.equal(controlStore.getState().spindle.direction, "stop");
controlStore.dispatch({ type: "TOGGLE_COOLANT", kind: "flood" }); controlStore.dispatch({ type: "TOGGLE_COOLANT", kind: "flood" });
assert.equal(controlStore.getState().coolant.flood, true); 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"); console.log("gmoccapy_xyzab_gates_smoke=ok");

View File

@@ -41,13 +41,46 @@ assert.equal(profile.jointConfig[4].homeOffset, 45);
assert.equal(profile.hal.halcmd.feedbackNets.length, 5); 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.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.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.program.current-line"), true);
assert.equal(profile.halPins.includes("gmoccapy.spindle_at_speed_led"), 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.toolTable.toolCount, 17);
assert.equal(profile.macros.includes("go_to_position X-pos Y-pos Z-pos"), true); 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.id, "gmoccapy-xyzab-hal-component");
assert.equal(profile.panelSchema.boundary, "gmoccapy_hal_component_reference_only"); assert.equal(profile.panelSchema.boundary, "gmoccapy_hal_component_reference_only");
assert.equal(profile.panelSchema.groups.length, 2); 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"]); assert.deepEqual(fiveAxisProfiles.map(({ id }) => id), ["xyzac-trt", "xyzbc-trt", "gmoccapy-xyzab"]);

View File

@@ -67,6 +67,8 @@ assert.equal(gmoccapyXyzabProfile.tcpCapable, false);
assert.equal(gmoccapyXyzabProfile.rtcpProof, false); assert.equal(gmoccapyXyzabProfile.rtcpProof, false);
assert.equal(gmoccapyXyzabProfile.promotionAllowed, false); assert.equal(gmoccapyXyzabProfile.promotionAllowed, false);
assert.equal(gmoccapyXyzabProfile.hal.postguiRequiresHalcompReady, true); 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"); const gmoccapySourceSummary = createProfileSourceReferenceSummary("gmoccapy-xyzab");
assert.equal(gmoccapySourceSummary.referenceCount >= 10, true); assert.equal(gmoccapySourceSummary.referenceCount >= 10, true);
assert.ok(gmoccapySourceSummary.references.some((reference) => reference.path.endsWith("gmoccapy_XYZAB.ini"))); assert.ok(gmoccapySourceSummary.references.some((reference) => reference.path.endsWith("gmoccapy_XYZAB.ini")));

View File

@@ -57,3 +57,151 @@
下一步是什么: 下一步是什么:
- 下一轮可继续按任务矩阵深入核对 Tool/M6/M61 手动换刀对话框、限位 override、optional stop/block delete HAL pin 交叉行为等更细粒度 gmoccapy 行为。 - 下一轮可继续按任务矩阵深入核对 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 pinfalling 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 已完成。

View File

@@ -13,4 +13,8 @@
| W5-130 | 校验 G-code interpreter/runtime | 完成 | 自动运行、MDI、暂停/继续/停止、M6/M61 引用边界测试随完整 Node 冒烟通过 | | W5-130 | 校验 G-code interpreter/runtime | 完成 | 自动运行、MDI、暂停/继续/停止、M6/M61 引用边界测试随完整 Node 冒烟通过 |
| W5-140 | 校验五轴运动和单位换算 | 完成 | kinematics、real LinuxCNC 5-axis cases、linear unit conversion 测试随完整 Node 冒烟通过 | | W5-140 | 校验五轴运动和单位换算 | 完成 | kinematics、real LinuxCNC 5-axis cases、linear unit conversion 测试随完整 Node 冒烟通过 |
| W5-150 | 校验 gmoccapy UI 通信和按钮门控 | 完成 | 浏览器 smoke 验证 mode gate、NML/HAL 诊断、POSTGUI 顺序、按钮状态 | | 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 | 更新验收证据和决策记录 | 完成 | 命令、结果、修改原因和后续项已记录 | | W5-900 | 更新验收证据和决策记录 | 完成 | 命令、结果、修改原因和后续项已记录 |

View File

@@ -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-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-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-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` - `run_preconditions_machine_file_smoke=ok`
- `gmoccapy_shell_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 edgerising 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 时只同步 countscount-enable 为 true 时按 `gmoccapy_XYZAB.pref` 中 scale 调整 feed/rapid/spindle/jog velocity。
- direct-value 在 analog-enable 为 false 时忽略analog-enable 为 true 时按 0..1 比例映射滑块范围。
- reset pins 忽略 falling edgerising 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 记录 ## 页面/截图/report/job 记录
本轮未生成截图、PDF、job_id 或 report_id。浏览器验收由 headless Chromium smoke 脚本完成。 本轮未生成截图、PDF、job_id 或 report_id。浏览器验收由 headless Chromium smoke 脚本完成。

View File

@@ -40,3 +40,99 @@
- `app/src/state/linuxcnc-task-policy.js` 成为模式切换和执行动作的统一互锁入口。 - `app/src/state/linuxcnc-task-policy.js` 成为模式切换和执行动作的统一互锁入口。
- `app/src/ui/gmoccapy-shell.js` 侧边模式按钮使用同一 gate 输出 disabled、`data-command-ready``aria-disabled` - `app/src/ui/gmoccapy-shell.js` 侧边模式按钮使用同一 gate 输出 disabled、`data-command-ready``aria-disabled`
- Node 和浏览器测试增加下电、未回零、已回零三个阶段的断言。 - Node 和浏览器测试增加下电、未回零、已回零三个阶段的断言。
## DR-W5-003XYZAB 换刀按 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 edgefalling 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-005HAL 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` 给出本配置默认 scalefeed/rapid/spindle 为 1jog 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-006jog 与消息 HAL pin 按 gmoccapy 回调边界建模
日期2026-06-27
决策:
- `gmoccapy.jog.axis.jog-*-plus/minus` 作为按下/释放电平输入处理pin true 触发 jog pressedpin 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 验证这些语义。

View File

@@ -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-项目功能内容 01-项目功能内容
02-项目程序开发详细步骤 02-项目程序开发详细步骤
03-推进台账:每轮做了什么、改了哪些文件、验证了什么、下一步是什么。 03-推进台账:每轮做了什么、改了哪些文件、验证了什么、下一步是什么。
04-任务矩阵:所有任务编号、状态、验收标准,防止重复做。 04-任务矩阵:所有任务编号、状态、验收标准,防止重复做。
05-验收证据命令、页面、job_id、report_id、PDF、截图等证据。 05-验收证据命令、页面、job_id、report_id、PDF、截图等证据。
06-决策记录:为什么这么做,后续变更时不反复争论同一个问题 06-决策记录:为什么这么做,后续变更时不反复争论同一个问题
另加 README 索引,便于后续按任务矩阵继续推进。 另加 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`
代码区也有一批未提交改动