fix: make Pause button pause running program

This commit is contained in:
wangdequan
2026-07-05 21:55:17 -04:00
parent 220c15295c
commit 58854f9ecf
12 changed files with 1774 additions and 126 deletions

View File

@@ -282,6 +282,13 @@ const initialState = {
override: 100,
enabled: false,
direction: "stop",
halPins: {
on: 0,
forward: 0,
reverse: 0,
speedOut: 0,
atSpeed: 0,
},
},
coolant: {
flood: false,
@@ -1123,6 +1130,7 @@ export function createSimulationStore(seed = {}) {
taskHalStatusLoop: {
...state.taskHalStatusLoop,
active: false,
sequence: Number(state.taskHalStatusLoop?.sequence || 0) + 1,
stopReason: action.reason || "stopped",
lastError: action.error || null,
},
@@ -1279,7 +1287,7 @@ export function createSimulationStore(seed = {}) {
rtcpState: turningOff ? "off" : state.rtcpState,
feed: turningOff ? { ...state.feed, currentVelocity: 0 } : state.feed,
coolant: turningOff ? { ...state.coolant, flood: false, mist: false } : state.coolant,
spindle: turningOff ? { ...state.spindle, enabled: false, direction: "stop" } : state.spindle,
spindle: turningOff ? stoppedSpindleState(state.spindle) : state.spindle,
operatorMessage: turningOff ? "task/HAL machine power off" : "task/HAL machine power on",
});
runTaskHalCommandSequence([
@@ -1303,7 +1311,7 @@ export function createSimulationStore(seed = {}) {
rtcpState: turningOff ? "off" : state.rtcpState,
feed: turningOff ? { ...state.feed, currentVelocity: 0 } : state.feed,
coolant: turningOff ? { ...state.coolant, flood: false, mist: false } : state.coolant,
spindle: turningOff ? { ...state.spindle, enabled: false, direction: "stop" } : state.spindle,
spindle: turningOff ? stoppedSpindleState(state.spindle) : state.spindle,
operatorMessage: turningOff ? "machine power off" : "machine power on",
});
}
@@ -1333,9 +1341,7 @@ export function createSimulationStore(seed = {}) {
mist: false,
},
spindle: {
...state.spindle,
enabled: false,
direction: "stop",
...stoppedSpindleState(state.spindle),
},
operatorMessage: "emergency stop active",
});
@@ -1362,9 +1368,7 @@ export function createSimulationStore(seed = {}) {
mist: false,
},
spindle: {
...state.spindle,
enabled: false,
direction: "stop",
...stoppedSpindleState(state.spindle),
},
operatorMessage: "estop reset; machine off",
});
@@ -1619,6 +1623,18 @@ export function createSimulationStore(seed = {}) {
setState({ operatorMessage: preconditions.operatorMessage });
break;
}
setState({
machine: {
...state.machine,
mode: "auto",
manualPanel: null,
interpState: "reading",
interpResumeState: "reading",
taskPaused: false,
},
runState: "running",
operatorMessage: "task/HAL program run requested",
});
runValidatedTaskHalProgramRun().catch(() => {});
break;
}
@@ -1718,11 +1734,22 @@ export function createSimulationStore(seed = {}) {
break;
}
const direction = normalizeSpindleDirection(action.direction);
const spindleEnabled = direction !== "stop";
const spindleActualRpm = spindleEnabled
? Number(state.spindle.rpm || 0) * (Number(state.spindle.override || 100) / 100)
: 0;
setState({
spindle: {
...state.spindle,
enabled: direction !== "stop",
enabled: spindleEnabled,
direction,
halPins: {
on: spindleEnabled ? 1 : 0,
forward: direction === "forward" ? 1 : 0,
reverse: direction === "reverse" ? 1 : 0,
speedOut: spindleActualRpm,
atSpeed: spindleEnabled ? 1 : 0,
},
},
operatorMessage: direction === "stop" ? "spindle stopped" : `spindle ${direction}`,
});
@@ -1737,9 +1764,29 @@ export function createSimulationStore(seed = {}) {
}
if (state.taskHalRuntime?.loaded) {
stopTaskHalStatusLoop("paused", { operatorMessage: "task/HAL pause requested" });
const pausedMachine = {
...state.machine,
interpResumeState: state.machine.interpState === "paused"
? state.machine.interpResumeState
: state.machine.interpState || "reading",
interpState: "paused",
taskPaused: true,
};
setState({
machine: pausedMachine,
runState: "paused",
feed: {
...state.feed,
currentVelocity: 0,
},
operatorMessage: "task/HAL pause requested",
});
runTaskHalCommandSequence([
{ type: "EMC_TASK_PLAN_PAUSE" },
], { operatorMessage: "task/HAL program paused" }).catch(() => {});
], {
operatorMessage: "task/HAL program paused",
preserveMachine: pausedMachine,
}).catch(() => {});
break;
}
setState({
@@ -1756,6 +1803,26 @@ export function createSimulationStore(seed = {}) {
});
}
break;
case "PAUSE_RESUME":
{
const interpState = state.machine?.interpState || state.linuxCncTaskPolicy?.interpState || "idle";
const taskMode = state.machine?.mode || state.linuxCncTaskPolicy?.taskMode || "manual";
const taskIsRunning = state.runState === "running" || state.runState === "stepping";
if (state.machine?.taskPaused === true || interpState === "paused" || state.runState === "paused") {
if (taskMode === "auto" || taskMode === "mdi") {
dispatch({ type: "RESUME" });
break;
}
setState({ operatorMessage: "resume blocked: task mode must be auto or MDI" });
break;
}
if ((taskMode === "auto" || taskMode === "mdi") && (interpState !== "idle" || taskIsRunning)) {
dispatch({ type: "PAUSE", source: "pauseresume" });
break;
}
setState({ operatorMessage: "pause ignored: interpreter is idle" });
}
break;
case "RESUME":
{
const gate = gateLinuxCncTaskAction(state, action);
@@ -1764,10 +1831,25 @@ export function createSimulationStore(seed = {}) {
break;
}
if (state.taskHalRuntime?.loaded) {
const resumeState = state.machine.interpResumeState === "idle" || state.machine.interpResumeState === "paused"
? "reading"
: state.machine.interpResumeState || "reading";
const resumedMachine = {
...state.machine,
interpState: resumeState,
interpResumeState: resumeState,
taskPaused: false,
};
setState({
machine: resumedMachine,
runState: resumeState === "reading" ? "running" : "idle",
operatorMessage: "task/HAL resume requested",
});
runTaskHalCommandSequence([
{ type: "EMC_TASK_PLAN_RESUME" },
], {
operatorMessage: "task/HAL program resumed",
preserveMachine: resumedMachine,
}).then((status) => {
if (shouldContinueTaskHalStatusLoop(state, status)) {
startTaskHalStatusLoop({
@@ -1801,11 +1883,44 @@ export function createSimulationStore(seed = {}) {
}
if (state.taskHalRuntime?.loaded) {
stopTaskHalStatusLoop("step", { operatorMessage: "task/HAL step requested" });
const playback = nextProgramRuntimeSamplePlayback(state, 1);
const playbackPatch = applyProgramPlaybackUiPatch(state, {
activeLine: playback.activeLine,
axisPose: playback.axisPose,
kinsType: playback.kinsType,
rtcpState: playback.rtcpState,
motionIndex: playback.motionIndex,
sampleIndex: playback.sampleIndex,
runtimeFeedback: playback.runtimeFeedback,
});
const steppedMachine = {
...state.machine,
mode: "auto",
manualPanel: null,
interpResumeState: state.machine.interpState === "paused"
? state.machine.interpResumeState || "reading"
: state.machine.interpState || "reading",
interpState: "paused",
taskPaused: true,
};
setState({
machine: steppedMachine,
runState: "stepping",
...playbackPatch,
programElapsedSeconds: playback.timing.elapsedSeconds,
programRemainingSeconds: playback.timing.remainingSeconds,
feed: {
...state.feed,
currentVelocity: playback.timing.currentVelocity,
},
operatorMessage: `task/HAL step requested line ${playback.activeLine}`,
});
runTaskHalCommandSequence([
{ type: "EMC_TASK_PLAN_STEP" },
], {
taskCycles: 1,
operatorMessage: "task/HAL stepped one cycle",
preserveMachine: steppedMachine,
}).catch(() => {});
break;
}
@@ -2047,6 +2162,10 @@ export function createSimulationStore(seed = {}) {
spindle: {
...state.spindle,
override: clampPercent(state.spindle.override + action.delta, 0, 150),
halPins: spindleHalPinsForState({
...state.spindle,
override: clampPercent(state.spindle.override + action.delta, 0, 150),
}),
},
gmoccapyGui: {
...state.gmoccapyGui,
@@ -2399,10 +2518,13 @@ export function createSimulationStore(seed = {}) {
}
dispatch({ type: "TASK_HAL_SESSION_READY", session });
const status = await state.taskHalRuntime.readStatus();
const statusPreserveMachine = isTaskHalRunPausedByOperator(state)
? { ...state.machine }
: preserveMachine;
dispatch({
type: "TASK_HAL_STATUS_APPLIED",
status,
preserveMachine,
preserveMachine: statusPreserveMachine,
operatorMessage: `LinuxCNC task/HAL session ready ${session.programPath || "-"}`,
});
return session;
@@ -2418,8 +2540,14 @@ export function createSimulationStore(seed = {}) {
const expectedProgramPath = expectedTaskHalProgramPathForState(state);
if (!state.taskHalSession || (expectedProgramPath && state.taskHalSession.programPath !== expectedProgramPath)) {
await initializeTaskHalSession({ openProgram: true });
if (isTaskHalRunPausedByOperator(state)) {
return null;
}
}
const loadedMotionPlan = await loadTaskHalMotionPlanForSession(state.taskHalSession);
if (isTaskHalRunPausedByOperator(state)) {
return null;
}
if (!loadedMotionPlan) {
setState({ operatorMessage: "run blocked: task/HAL feed motion plan not loaded" });
return null;
@@ -2430,10 +2558,16 @@ export function createSimulationStore(seed = {}) {
setState({ operatorMessage: ready.operatorMessage });
return null;
}
if (isTaskHalRunPausedByOperator(state)) {
return null;
}
stopTaskHalStatusLoop("restarted", {
operatorMessage: "task/HAL status loop restarting",
});
if (isTaskHalRunPausedByOperator(state)) {
return null;
}
const status = await runTaskHalCommandSequence([
{ type: "EMC_TASK_SET_STATE", state: "ON" },
@@ -2444,6 +2578,9 @@ export function createSimulationStore(seed = {}) {
operatorMessage: `task/HAL program run ${ready.profileId} ${ready.kinematicsModuleId}`,
allowFixtureSession: false,
});
if (isTaskHalRunPausedByOperator(state)) {
return status;
}
if (shouldContinueTaskHalStatusLoop(state, status)) {
startTaskHalStatusLoop({
profileId: ready.profileId,
@@ -2548,20 +2685,26 @@ export function createSimulationStore(seed = {}) {
mode: "auto",
manualPanel: null,
allHomed: true,
interpState: "idle",
interpResumeState: "idle",
interpState: "reading",
interpResumeState: "reading",
taskPaused: false,
},
runState: "idle",
runState: "running",
operatorMessage: "RUN ready: motion plan source ready; preparing task/HAL session",
});
const expectedProgramPath = expectedTaskHalProgramPathForState(state);
if (!state.taskHalSession || (expectedProgramPath && state.taskHalSession.programPath !== expectedProgramPath)) {
setState({ operatorMessage: "RUN preparing: initializing task/HAL session" });
await initializeTaskHalSession({ openProgram: true });
if (isTaskHalRunPausedByOperator(state)) {
return state;
}
}
setState({ operatorMessage: "RUN preparing: loading task/HAL motion plan" });
const loadedMotionPlan = await loadTaskHalMotionPlanWithSessionRetry();
if (isTaskHalRunPausedByOperator(state)) {
return state;
}
if (!loadedMotionPlan) {
setState({ operatorMessage: "run blocked: task/HAL feed motion plan not loaded" });
return state;
@@ -2571,9 +2714,15 @@ export function createSimulationStore(seed = {}) {
setState({ operatorMessage: ready.operatorMessage });
return state;
}
if (isTaskHalRunPausedByOperator(state)) {
return state;
}
stopTaskHalStatusLoop("restarted", {
operatorMessage: "task/HAL status loop restarting",
});
if (isTaskHalRunPausedByOperator(state)) {
return state;
}
setState({
activeLine: state.programStartLine || 1,
programExecutionMotionIndex: 0,
@@ -2592,6 +2741,9 @@ export function createSimulationStore(seed = {}) {
operatorMessage: `task/HAL program run ${ready.profileId} ${ready.kinematicsModuleId}`,
allowFixtureSession: false,
});
if (isTaskHalRunPausedByOperator(state)) {
return state;
}
if (shouldContinueTaskHalStatusLoop(state, status)) {
startTaskHalStatusLoop({
profileId: ready.profileId,
@@ -2703,7 +2855,7 @@ export function createSimulationStore(seed = {}) {
taskCycles: loop.batchSize,
});
const status = await state.taskHalRuntime.readStatus();
if (state.taskHalStatusLoop?.sequence !== sequence) {
if (state.taskHalStatusLoop?.sequence !== sequence || state.taskHalStatusLoop?.active !== true) {
return status;
}
dispatch({
@@ -3187,6 +3339,7 @@ function createLinuxCncProcessMonitor(state = {}) {
const spindleActualRpm = state.spindle?.enabled
? spindleCommandRpm * (Number(state.spindle?.override || 100) / 100)
: 0;
const spindleHalPins = state.spindle?.halPins || {};
const toolChange = {
toolInSpindle: Number(toolRuntime.toolInSpindle || toolDb.toolInSpindle || 0),
toolFromPocket: Number(toolRuntime.toolFromPocket || toolDb.toolFromPocket || 0),
@@ -3280,10 +3433,11 @@ function createLinuxCncProcessMonitor(state = {}) {
actualRpm: spindleActualRpm,
overridePercent: Number(state.spindle?.override || 0),
halPins: {
on: Number(halPins["spindle.0.on"]?.value ?? (state.spindle?.enabled ? 1 : 0)),
forward: Number(halPins["spindle.0.forward"]?.value ?? (state.spindle?.direction === "forward" ? 1 : 0)),
reverse: Number(halPins["spindle.0.reverse"]?.value ?? (state.spindle?.direction === "reverse" ? 1 : 0)),
speedOut: Number(halPins["spindle.0.speed-out"]?.value ?? spindleActualRpm),
on: Number(spindleHalPins.on ?? halPins["spindle.0.on"]?.value ?? (state.spindle?.enabled ? 1 : 0)),
forward: Number(spindleHalPins.forward ?? halPins["spindle.0.forward"]?.value ?? (state.spindle?.direction === "forward" ? 1 : 0)),
reverse: Number(spindleHalPins.reverse ?? halPins["spindle.0.reverse"]?.value ?? (state.spindle?.direction === "reverse" ? 1 : 0)),
speedOut: Number(spindleHalPins.speedOut ?? halPins["spindle.0.speed-out"]?.value ?? spindleActualRpm),
atSpeed: Number(spindleHalPins.atSpeed ?? halPins["spindle.0.at-speed"]?.value ?? (state.spindle?.enabled ? 1 : 0)),
},
},
feed: {
@@ -3779,6 +3933,12 @@ function shouldContinueTaskHalStatusLoop(state = {}, status = {}) {
return !aborted && !paused && !complete && (interpState === "reading" || taskMode === "mdi");
}
function isTaskHalRunPausedByOperator(state = {}) {
return state.runState === "paused"
|| state.machine?.interpState === "paused"
|| state.machine?.taskPaused === true;
}
function createStoppedProgramStatePatch(state, {
reason = "stopped",
operatorMessage = "program stopped",
@@ -4404,9 +4564,9 @@ function homeAxisPoseForState(state = {}) {
if (state.profile?.id === "xyzac-trt" || state.profile?.id === "xyzbc-trt") {
return clampAxisPoseToProfile({
...initialAxisPose,
x: 43,
y: -32.15,
z: -11.306,
x: 0,
y: 0,
z: 10,
}, state.profile);
}
const pose = { ...initialAxisPose };
@@ -4489,6 +4649,12 @@ function executeMdiCommand(state, rawCommand) {
rpm: parsed.spindleRpm ?? state.spindle.rpm,
enabled: parsed.spindleEnabled ?? state.spindle.enabled,
direction: parsed.spindleDirection ?? state.spindle.direction,
halPins: spindleHalPinsForState({
...state.spindle,
rpm: parsed.spindleRpm ?? state.spindle.rpm,
enabled: parsed.spindleEnabled ?? state.spindle.enabled,
direction: parsed.spindleDirection ?? state.spindle.direction,
}),
}
: state.spindle,
coolant: parsed.coolantPatch
@@ -4592,6 +4758,33 @@ function applyMdiMCode(parsed, value) {
}
}
function spindleHalPinsForState(spindle = {}) {
const enabled = Boolean(spindle.enabled) && spindle.direction !== "stop";
const speedOut = enabled
? Number(spindle.rpm || 0) * (Number(spindle.override || 100) / 100)
: 0;
return {
on: enabled ? 1 : 0,
forward: spindle.direction === "forward" ? 1 : 0,
reverse: spindle.direction === "reverse" ? 1 : 0,
speedOut,
atSpeed: enabled ? 1 : 0,
};
}
function stoppedSpindleState(spindle = {}) {
return {
...spindle,
enabled: false,
direction: "stop",
halPins: spindleHalPinsForState({
...spindle,
enabled: false,
direction: "stop",
}),
};
}
function normalizeSpindleDirection(direction) {
const value = String(direction || "stop").toLowerCase();
return value === "forward" || value === "reverse" ? value : "stop";