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