feat: sync axis task state parity work

This commit is contained in:
wangdequan
2026-07-07 18:45:26 -04:00
parent 16484afce6
commit 8ef67f94c2
562 changed files with 175971 additions and 248 deletions

View File

@@ -254,11 +254,24 @@ export function normalizeTaskHalStatus(status = {}) {
taskPaused: status.task?.taskPaused === true,
singleStepping: status.task?.singleStepping === true,
taskCycle: Number(status.task?.taskCycle ?? status.taskCycle ?? 0),
programOpen: status.task?.programOpen === true,
programFile: status.task?.file || status.task?.openProgram || null,
programStartLine: Number(status.task?.programStartLine || 0),
planId: Number(status.task?.planId || 0),
motionPlanLoaded: status.task?.motionPlanLoaded === true,
errorText: status.task?.errorText || null,
allHomed: status.task?.allHomed === true,
homed: normalizeTaskHalHomed(status.task?.homed, status.task?.allHomed === true),
noForceHoming: status.task?.noForceHoming === true,
homing: status.task?.homing === true,
homeState: String(status.task?.homeState || "UNHOMED").toLowerCase(),
motionEnabled: String(status.task?.state || "").toUpperCase() === "ON" || motion.enabled === true,
resumeInhibit: status.task?.resumeInhibit === true,
servoCycle: Number(status.motionStatus?.cycle ?? status.task?.servoCycle ?? 0),
motionQueueDepth,
motionPaused: motion.paused === true,
motionStepping: motion.stepping === true,
motionId: Number(motion.motionId || 0),
motionId: Number(motion.motionId ?? motion.id ?? 0),
idForStep: Number(motion.idForStep || 0),
halChangedPinCount: Array.isArray(status.halSnapshot?.changedPins)
? status.halSnapshot.changedPins.length
@@ -285,6 +298,13 @@ export function normalizeTaskHalStatus(status = {}) {
};
}
function normalizeTaskHalHomed(value, allHomed = false) {
if (Array.isArray(value)) {
return value.map(Boolean);
}
return allHomed ? [true, true, true, true, true] : [false, false, false, false, false];
}
function shouldUseSourceLineSegments(planSegments, lineSegments) {
if (lineSegments.length < 3) return false;
const plannedLines = new Set(planSegments.map((segment) => Number(segment.line)).filter(Number.isFinite));

View File

@@ -36,13 +36,16 @@ export function normalizeLinuxCncTaskMode(mode) {
return LINUXCNC_TASK_MODES.has(mode) ? mode : "manual";
}
export function normalizeLinuxCncTaskState(machine = {}) {
if (LINUXCNC_TASK_STATES.has(machine.taskState)) return machine.taskState;
if (machine.estopActive) return "estop";
if (machine.powerOn) return "on";
export function deriveLinuxCncTaskState(machine = {}) {
if (machine.estopActive === true || machine.taskState === "estop") return "estop";
if (machine.motionEnabled === true || machine.powerOn === true || machine.taskState === "on") return "on";
return "estop-reset";
}
export function normalizeLinuxCncTaskState(machine = {}) {
return deriveLinuxCncTaskState(machine);
}
export function normalizeLinuxCncInterpState(machine = {}, runState = "idle") {
if (LINUXCNC_INTERP_STATES.has(machine.interpState)) return machine.interpState;
if (runState === "running") return "reading";
@@ -54,8 +57,15 @@ export function createLinuxCncTaskPolicyStatus(state) {
const taskState = normalizeLinuxCncTaskState(state.machine);
const taskMode = normalizeLinuxCncTaskMode(state.machine.mode);
const interpState = normalizeLinuxCncInterpState(state.machine, state.runState);
const allHomed = Boolean(state.machine.allHomed);
const statusHomed = state.taskHalStatus?.ui?.homed || state.taskHalStatus?.task?.homed;
const homed = normalizeHomedArray(state.machine.homed, statusHomed, state.profile?.jointConfig?.length);
const allHomed = Boolean(state.machine.allHomed || homed.length > 0 && homed.every(Boolean));
const noForceHoming = Boolean(state.machine.noForceHoming);
const homing = Boolean(
state.machine.homing ||
state.taskHalStatus?.ui?.homing ||
state.taskHalStatus?.task?.homing,
);
const machineFileStaging = state.machineFileStaging || {};
const profileId = state.profile?.id || state.machineProfile || null;
const taskHalReadiness = state.taskHalRuntimeReadiness || {};
@@ -70,6 +80,8 @@ export function createLinuxCncTaskPolicyStatus(state) {
const machineFileOpened = profileId === "gmoccapy-xyzab"
? Boolean(machineFileStaging.selectedGcodeSourceRel)
: Boolean(machineFileStaging.selectedGcodeSourceRel || state.activeProgram);
const taskProgramOpen = Boolean(state.taskHalStatus?.ui?.programOpen || state.taskHalStatus?.task?.programOpen);
const taskMotionPlanLoaded = Boolean(state.taskHalStatus?.ui?.motionPlanLoaded || state.taskHalStatus?.task?.motionPlanLoaded);
const interpIdle = interpState === "idle";
const motionPaused = state.machine?.motionPaused === true ||
state.taskHalStatus?.ui?.motionPaused === true ||
@@ -77,6 +89,19 @@ export function createLinuxCncTaskPolicyStatus(state) {
const taskPaused = state.machine?.taskPaused === true ||
state.taskHalStatus?.ui?.taskPaused === true ||
state.taskHalStatus?.task?.taskPaused === true;
const motionEnabled = taskState === "on" ||
state.machine?.motionEnabled === true ||
state.taskHalStatus?.ui?.motionEnabled === true ||
state.taskHalStatus?.motionStatus?.motion?.enabled === true;
const singleStepping = state.machine?.singleStepping === true ||
state.taskHalStatus?.ui?.singleStepping === true ||
state.taskHalStatus?.task?.singleStepping === true;
const motionStepping = state.machine?.motionStepping === true ||
state.taskHalStatus?.ui?.motionStepping === true ||
state.taskHalStatus?.motionStatus?.motion?.stepping === true;
const resumeInhibit = state.machine?.resumeInhibit === true ||
state.taskHalStatus?.ui?.resumeInhibit === true ||
state.taskHalStatus?.task?.resumeInhibit === true;
const feedbackPaused = state.programRuntimeFeedback?.paused === true;
const paused = motionPaused || taskPaused || feedbackPaused || interpState === "paused" || state.runState === "paused";
const running = taskMode === "auto" &&
@@ -91,28 +116,40 @@ export function createLinuxCncTaskPolicyStatus(state) {
runState: state.runState || "idle",
motionPaused,
taskPaused,
motionEnabled,
singleStepping,
motionStepping,
resumeInhibit,
feedbackPaused,
paused,
homed,
allHomed,
noForceHoming,
homing,
iniLoaded,
machineFileStaged,
machineFileOpened,
taskProgramOpen,
taskMotionPlanLoaded,
taskHalRuntimeReady,
interpIdle,
referenceOnly,
profileId,
powerOn: taskState === "on",
estopActive: taskState === "estop",
canPowerToggle: taskState !== "estop",
powerAction: taskState === "estop-reset" ? "on" : taskState === "on" ? "off" : "disabled",
canMove: taskState === "on",
canJog: taskState === "on" && taskMode === "manual" && !running,
canHome: taskState === "on" && taskMode === "manual" && !running,
canRunAuto: taskState === "on" && taskMode === "auto" && (allHomed || noForceHoming),
canJog: taskState === "on" && taskMode === "manual" && interpIdle && !homing && !running,
canHome: taskState === "on" && taskMode === "manual" && interpIdle && !homing && !running,
canRunAuto: taskState === "on" && taskMode === "auto" && !homing && (allHomed || noForceHoming),
canRunAutoStrict: taskState === "on" && taskMode === "auto" && (allHomed || noForceHoming) &&
interpIdle && iniLoaded && machineFileStaged && machineFileOpened && taskHalRuntimeReady,
!homing && interpIdle && iniLoaded && machineFileStaged && machineFileOpened && taskHalRuntimeReady,
canStepStrict: taskState === "on" && taskMode === "auto" && !homing &&
(allHomed || noForceHoming) && machineFileOpened && !resumeInhibit,
canExecuteMdi: taskState === "on" && taskMode === "mdi" && (allHomed || noForceHoming),
canPause: taskState === "on" && taskMode === "auto" && (interpState === "reading" || interpState === "waiting"),
canResume: taskState === "on" && (taskMode === "auto" || taskMode === "mdi") && paused,
canResume: taskState === "on" && (taskMode === "auto" || taskMode === "mdi") && paused && !resumeInhibit,
canAbort: true,
canSpindle: taskState === "on",
canCoolant: taskState === "on",
@@ -128,24 +165,22 @@ export function gateLinuxCncTaskAction(state, action) {
switch (type) {
case "TOGGLE_POWER":
if (status.taskState === "estop") return block(status, "power blocked: reset ESTOP first");
if (status.powerAction === "disabled") return block(status, "power blocked: invalid task state");
return allow(status);
case "SET_MODE":
return gateMode(status, requestedMode);
case "JOG":
if (status.taskState !== "on") return block(status, "jog blocked: machine must be on");
if (status.taskMode !== "manual") return block(status, "jog blocked: switch to manual mode first");
if (status.taskMode === "auto" &&
(status.runState === "running" || status.interpState === "reading" || status.interpState === "waiting")) {
return block(status, "jog blocked: interpreter must be idle");
}
if (status.homing) return block(status, "jog blocked: homing is active");
if (status.interpState !== "idle") return block(status, "jog blocked: interpreter must be idle");
return allow(status);
case "HOME":
if (status.taskState !== "on") return block(status, "home blocked: machine must be on");
if (status.taskMode !== "manual") return block(status, "home blocked: switch to manual mode first");
if (status.taskMode === "auto" &&
(status.runState === "running" || status.interpState === "reading" || status.interpState === "waiting")) {
return block(status, "home blocked: interpreter must be idle");
}
if (status.homing) return block(status, "home blocked: homing is active");
if (status.interpState !== "idle") return block(status, "home blocked: interpreter must be idle");
return allow(status);
case "RUN_MDI":
{
@@ -163,8 +198,15 @@ export function gateLinuxCncTaskAction(state, action) {
case "RUN_FRAME":
if (status.taskState !== "on") return block(status, `${type.toLowerCase()} blocked: machine must be on`);
if (status.taskMode !== "auto") return block(status, `${type.toLowerCase()} blocked: switch to auto mode first`);
if (status.homing) return block(status, `${type.toLowerCase()} blocked: homing is active`);
if (!status.allHomed && !status.noForceHoming) return block(status, `${type.toLowerCase()} blocked: home machine first`);
if (type === "RUN" && status.interpState === "paused") return block(status, "run blocked: resume paused program first");
if (type === "RUN" && status.interpState !== "idle") return block(status, "run blocked: interpreter must be idle");
if (type === "RUN" && status.taskHalRuntimeReady && !status.machineFileOpened) {
return block(status, "run blocked: no G-code program opened");
}
if (type === "STEP" && !status.machineFileOpened) return block(status, "step blocked: no G-code program opened");
if (type === "STEP" && status.resumeInhibit) return block(status, "step blocked: resume is inhibited");
if (type === "RUN" && status.profileId === "gmoccapy-xyzab") {
const strictGate = gateGmoccapyXyzabRun(status);
if (!strictGate.allowed) return strictGate;
@@ -193,6 +235,7 @@ export function gateLinuxCncTaskAction(state, action) {
}
case "RESUME":
if (status.taskState !== "on") return block(status, "resume blocked: machine must be on");
if (status.resumeInhibit) return block(status, "resume blocked: resume is inhibited");
if (status.taskMode !== "auto" && status.taskMode !== "mdi" && status.runState !== "paused") {
return block(status, "resume blocked: task mode must be auto or MDI");
}
@@ -259,12 +302,26 @@ function gateMode(status, requestedMode) {
if (status.taskState !== "on") {
return block(status, `mode blocked: machine must be on before ${targetMode.toUpperCase()}`);
}
if ((targetMode === "mdi" || targetMode === "auto") && !status.allHomed && !status.noForceHoming) {
if ((targetMode === "mdi" || targetMode === "auto") &&
!status.homing &&
!status.allHomed &&
!status.noForceHoming) {
return block(status, `mode blocked: home machine before ${targetMode.toUpperCase()}`);
}
return allow(status);
}
function normalizeHomedArray(machineHomed, statusHomed, jointCount = 0) {
const source = Array.isArray(statusHomed)
? statusHomed
: Array.isArray(machineHomed)
? machineHomed
: [];
const count = Math.max(Number(jointCount || 0), source.length);
if (count <= 0) return source.map(Boolean);
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
}
function allow(status) {
return {
allowed: true,

View File

@@ -43,6 +43,7 @@ import { createLinuxCncParityMatrix } from "../runtime/linuxcnc-parity-matrix.js
import { gmoccapyHalModel, resolveGmoccapyHardwareButton } from "../runtime/gmoccapy-hal-model.js";
import {
createLinuxCncTaskPolicyStatus,
deriveLinuxCncTaskState,
gateLinuxCncTaskAction,
normalizeLinuxCncTaskMode,
} from "./linuxcnc-task-policy.js";
@@ -166,15 +167,22 @@ const initialState = {
machine: {
powerOn: false,
estopActive: false,
motionEnabled: false,
taskState: "estop-reset",
mode: "manual",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
resumeInhibit: false,
manualPanel: "manual",
allHomed: false,
homed: [false, false, false, false, false],
noForceHoming: false,
homing: false,
homeState: "unhomed",
selectedJoint: 0,
jogAxis: "x",
jogIncrement: 1,
@@ -410,6 +418,7 @@ export function createSimulationStore(seed = {}) {
: seed.programAxisPreviewPath;
state = {
...state,
machine: normalizeMachineForLinuxCncTask(state.machine, state.runState),
programAxisPreviewPath: initialAxisPreviewPath,
preview: {
...state.preview,
@@ -1309,13 +1318,18 @@ export function createSimulationStore(seed = {}) {
...state.machine,
powerOn: false,
estopActive: true,
motionEnabled: false,
taskState: "estop",
manualPanel: "manual",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "estopped",
taskHalPauseLock: null,
kinsType: "identity",
rtcpState: "off",
feed: {
@@ -1330,8 +1344,29 @@ export function createSimulationStore(seed = {}) {
spindle: {
...stoppedSpindleState(state.spindle),
},
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
operatorMessage: "emergency stop active",
});
if (state.taskHalRuntime?.loaded) {
runTaskHalCommandSequence([
{ type: "EMC_TASK_SET_STATE", state: "ESTOP" },
], {
operatorMessage: "task/HAL emergency stop active",
preserveMachine: {
...state.machine,
powerOn: false,
estopActive: true,
motionEnabled: false,
taskState: "estop",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
}).catch(() => {});
}
break;
case "RESET":
setState({
@@ -1339,14 +1374,19 @@ export function createSimulationStore(seed = {}) {
...state.machine,
powerOn: false,
estopActive: false,
motionEnabled: false,
taskState: "estop-reset",
manualPanel: "manual",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
resetCount: state.machine.resetCount + 1,
},
runState: "idle",
taskHalPauseLock: null,
kinsType: "identity",
rtcpState: "off",
coolant: {
@@ -1357,8 +1397,29 @@ export function createSimulationStore(seed = {}) {
spindle: {
...stoppedSpindleState(state.spindle),
},
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
operatorMessage: "estop reset; machine off",
});
if (state.taskHalRuntime?.loaded) {
runTaskHalCommandSequence([
{ type: "EMC_TASK_SET_STATE", state: "ESTOP_RESET" },
], {
operatorMessage: "task/HAL estop reset; machine off",
preserveMachine: {
...state.machine,
powerOn: false,
estopActive: false,
motionEnabled: false,
taskState: "estop-reset",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
}).catch(() => {});
}
break;
case "SET_MODE":
{
@@ -1375,27 +1436,46 @@ export function createSimulationStore(seed = {}) {
? "manual"
: null;
if (state.taskHalRuntime?.loaded) {
const manualModePatch = mode === "manual"
? {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
}
: {};
const requestedMachine = {
...state.machine,
mode,
manualPanel,
...manualModePatch,
};
setState({
machine: requestedMachine,
operatorMessage: `task/HAL mode ${mode} requested`,
});
runTaskHalCommandSequence([
{ type: "EMC_TASK_SET_MODE", mode: mode.toUpperCase() },
], {
operatorMessage: `task/HAL mode ${mode}`,
preserveMachine: {
...state.machine,
mode,
manualPanel,
},
preserveMachine: requestedMachine,
}).catch(() => {});
break;
}
setState({
machine: {
...state.machine,
mode,
manualPanel,
interpState: mode === "manual" ? "idle" : state.machine.interpState,
interpResumeState: mode === "manual" ? "idle" : state.machine.interpResumeState,
taskPaused: mode === "manual" ? false : state.machine.taskPaused,
},
machine: {
...state.machine,
mode,
manualPanel,
interpState: mode === "manual" ? "idle" : state.machine.interpState,
interpResumeState: mode === "manual" ? "idle" : state.machine.interpResumeState,
taskPaused: mode === "manual" ? false : state.machine.taskPaused,
motionPaused: mode === "manual" ? false : state.machine.motionPaused,
singleStepping: mode === "manual" ? false : state.machine.singleStepping,
motionStepping: mode === "manual" ? false : state.machine.motionStepping,
},
runState: mode === "manual" && state.runState === "running" ? "stopped" : state.runState,
operatorMessage: `mode ${mode}`,
});
@@ -1473,6 +1553,9 @@ export function createSimulationStore(seed = {}) {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
axisPose: nextAxisPose,
runState: "jogging",
@@ -1496,6 +1579,9 @@ export function createSimulationStore(seed = {}) {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
operatorMessage: `task/HAL jog ${axis.toUpperCase()} ${direction > 0 ? "+" : "-"}${increment}`,
}).catch(() => {});
@@ -1580,6 +1666,9 @@ export function createSimulationStore(seed = {}) {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
axisPose: initialAxisPose,
runState: "idle",
@@ -1615,13 +1704,15 @@ export function createSimulationStore(seed = {}) {
...state.machine,
mode: "auto",
manualPanel: null,
interpState: "reading",
interpResumeState: "reading",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "running",
taskHalPauseLock: null,
operatorMessage: "task/HAL program run requested",
operatorMessage: "task/HAL program run requested; waiting for status",
});
runValidatedTaskHalProgramRun().catch(() => {});
break;
@@ -1644,6 +1735,9 @@ export function createSimulationStore(seed = {}) {
interpState: playback.complete ? "idle" : "reading",
interpResumeState: playback.complete ? "idle" : "reading",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: playback.complete ? "complete" : "running",
taskHalPauseLock: null,
@@ -1687,6 +1781,9 @@ export function createSimulationStore(seed = {}) {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
})
.then(() => {
@@ -1705,6 +1802,9 @@ export function createSimulationStore(seed = {}) {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "stopped",
taskHalPauseLock: null,
@@ -1764,6 +1864,8 @@ export function createSimulationStore(seed = {}) {
interpState: "paused",
taskPaused: true,
motionPaused: true,
singleStepping: false,
motionStepping: false,
};
const pauseLock = createTaskHalPauseLock(state, "task-hal-plan-pause");
setState({
@@ -1803,6 +1905,8 @@ export function createSimulationStore(seed = {}) {
interpState: "paused",
taskPaused: true,
motionPaused: true,
singleStepping: false,
motionStepping: false,
},
runState: "paused",
taskHalPauseLock: pauseLock,
@@ -1853,10 +1957,12 @@ export function createSimulationStore(seed = {}) {
interpResumeState: resumeState,
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
};
setState({
machine: resumedMachine,
runState: resumeState === "reading" ? "running" : "idle",
runState: resumeState === "reading" || resumeState === "waiting" ? "running" : "idle",
taskHalPauseLock: null,
operatorMessage: "task/HAL resume requested",
});
@@ -1885,8 +1991,10 @@ export function createSimulationStore(seed = {}) {
interpResumeState: resumeState,
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: resumeState === "reading" ? "running" : "idle",
runState: resumeState === "reading" || resumeState === "waiting" ? "running" : "idle",
taskHalPauseLock: null,
operatorMessage: "program resumed",
});
@@ -1921,6 +2029,8 @@ export function createSimulationStore(seed = {}) {
interpState: "paused",
taskPaused: true,
motionPaused: true,
singleStepping: true,
motionStepping: true,
};
setState({
machine: steppedMachine,
@@ -1965,6 +2075,8 @@ export function createSimulationStore(seed = {}) {
interpState: "paused",
taskPaused: true,
motionPaused: true,
singleStepping: true,
motionStepping: true,
},
runState: "stepping",
taskHalPauseLock: null,
@@ -2002,6 +2114,9 @@ export function createSimulationStore(seed = {}) {
interpState: playback.complete ? "idle" : "reading",
interpResumeState: playback.complete ? "idle" : "reading",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "running",
...playbackPatch,
@@ -2022,16 +2137,24 @@ export function createSimulationStore(seed = {}) {
break;
}
const homeAxisPose = homeAxisPoseForState(state);
const homedFalse = createHomedArrayForState(state, false);
const homedTrue = createHomedArrayForState(state, true);
if (state.taskHalRuntime?.loaded) {
setState({
machine: {
...state.machine,
mode: "manual",
manualPanel: "manual",
allHomed: true,
allHomed: false,
homed: homedFalse,
homing: true,
homeState: "homing",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "idle",
axisPose: homeAxisPose,
@@ -2044,9 +2167,11 @@ export function createSimulationStore(seed = {}) {
operatorMessage: "task/HAL machine homed",
preserveAxisPose: homeAxisPose,
preserveMachine: {
...state.machine,
manualPanel: "manual",
allHomed: true,
homed: homedTrue,
homing: false,
homeState: "homed",
},
}).catch(() => {});
break;
@@ -2057,9 +2182,15 @@ export function createSimulationStore(seed = {}) {
mode: "manual",
manualPanel: "manual",
allHomed: true,
homed: homedTrue,
homing: false,
homeState: "homed",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "idle",
axisPose: homeAxisPose,
@@ -2073,6 +2204,9 @@ export function createSimulationStore(seed = {}) {
machine: {
...state.machine,
allHomed: false,
homed: createHomedArrayForState(state, false),
homing: false,
homeState: "unhomed",
},
operatorMessage: "machine unhomed",
});
@@ -2695,11 +2829,13 @@ export function createSimulationStore(seed = {}) {
...state.machine,
mode: "auto",
manualPanel: null,
interpState: "reading",
interpResumeState: "reading",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "running",
taskHalPauseLock: null,
operatorMessage: "RUN ready: motion plan source ready; preparing task/HAL session",
});
@@ -3806,7 +3942,13 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
: rawTaskState;
const taskMode = statusPaused
? programControlModeForMachine(state.machine)
: normalizeLinuxCncTaskMode(preserveMachine?.mode || ui.taskMode || task.mode || state.machine.mode);
: normalizeLinuxCncTaskMode(
preserveMachine?.mode ||
(preserveMachine?.allHomed && state.machine.mode !== "manual" ? state.machine.mode : null) ||
ui.taskMode ||
task.mode ||
state.machine.mode,
);
const manualPanel = taskMode === "manual"
? (preserveMachine?.manualPanel || state.machine.manualPanel || "manual")
: null;
@@ -3815,12 +3957,40 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
: Object.hasOwn(preserveMachine || {}, "interpState")
? normalizeTaskHalInterpState(preserveMachine.interpState)
: statusInterpState;
const allHomed = Boolean(preserveMachine?.allHomed ?? state.machine.allHomed);
const allHomed = Boolean(
task.allHomed === true ||
ui.allHomed === true ||
preserveMachine?.allHomed ||
state.machine.allHomed,
);
const homed = normalizeHomedArrayForState(
state,
ui.homed || task.homed || preserveMachine?.homed || state.machine.homed,
allHomed,
);
const homing = Object.hasOwn(task, "homing")
? task.homing === true
: Object.hasOwn(ui, "homing")
? ui.homing === true
: Boolean(preserveMachine?.homing);
const homeState = normalizeTaskHalHomeState(
ui.homeState ||
task.homeState ||
preserveMachine?.homeState ||
state.machine.homeState ||
(allHomed ? "homed" : "unhomed"),
);
const activeLine = state.programStartLine + Math.max(Number(ui.activeLine || 1) - 1, 0);
const kinsType = resolveTaskHalKinsType(state, status, activeLine);
const motionPaused = statusMotionPaused || preserveMachine?.motionPaused === true;
const taskPaused = statusTaskPaused || preserveMachine?.taskPaused === true;
const paused = interpState === "paused" || motionPaused || taskPaused;
const singleStepping = ui.singleStepping === true ||
task.singleStepping === true ||
preserveMachine?.singleStepping === true;
const motionStepping = ui.motionStepping === true ||
motion.stepping === true ||
preserveMachine?.motionStepping === true;
const interpResumeState = normalizeTaskHalInterpState(
ui.interpResumeState ||
task.interpResumeState ||
@@ -3955,6 +4125,7 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
...state.machine,
powerOn: taskState === "on",
estopActive: taskState === "estop",
motionEnabled: taskState === "on",
taskState,
mode: taskMode,
manualPanel,
@@ -3962,7 +4133,12 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
interpResumeState,
taskPaused: paused,
motionPaused,
singleStepping,
motionStepping,
allHomed,
homed,
homing,
homeState,
noForceHoming: Boolean(state.machine.noForceHoming),
},
runState,
@@ -4035,17 +4211,26 @@ function programControlModeForMachine(machine = {}) {
}
function createPowerToggleMachinePatch(machine = {}, turningOn = false) {
const poweredOffHomed = Array.isArray(machine.homed)
? machine.homed.map(() => false)
: [false, false, false, false, false];
return {
...machine,
powerOn: turningOn,
estopActive: false,
taskState: turningOn ? "on" : "off",
motionEnabled: turningOn,
taskState: turningOn ? "on" : "estop-reset",
manualPanel: "manual",
allHomed: turningOn ? Boolean(machine.allHomed) : false,
homed: turningOn ? normalizeHomedArrayForMachine(machine, Boolean(machine.allHomed)) : poweredOffHomed,
homing: false,
homeState: turningOn ? (machine.allHomed ? "homed" : machine.homeState || "unhomed") : "unhomed",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
};
}
@@ -4094,6 +4279,9 @@ function createStoppedProgramStatePatch(state, {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: reason === "aborted" ? "stopped" : reason,
taskHalPauseLock: null,
@@ -4108,6 +4296,7 @@ function createStoppedProgramStatePatch(state, {
...state.feed,
currentVelocity: 0,
},
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
operatorMessage,
};
}
@@ -4615,7 +4804,6 @@ function normalizeTaskHalTaskState(value) {
const state = String(value || "").toLowerCase().replaceAll("_", "-");
if (state === "on") return "on";
if (state === "estop") return "estop";
if (state === "off") return "off";
return "estop-reset";
}
@@ -4627,6 +4815,41 @@ function normalizeTaskHalInterpState(value) {
return "idle";
}
function normalizeTaskHalHomeState(value) {
const state = String(value || "").toLowerCase().replaceAll("_", "-");
if (state === "homing") return "homing";
if (state === "homed") return "homed";
if (state === "no-force-homing") return "no-force-homing";
return "unhomed";
}
function jointCountForState(state = {}) {
return Math.max(
Number(state.profile?.jointConfig?.length || 0),
Number(state.profile?.joints?.length || 0),
Array.isArray(state.machine?.homed) ? state.machine.homed.length : 0,
5,
);
}
function createHomedArrayForState(state = {}, value = false) {
return Array.from({ length: jointCountForState(state) }, () => Boolean(value));
}
function normalizeHomedArrayForState(state = {}, source = [], allHomed = false) {
const count = jointCountForState(state);
if (allHomed) return Array.from({ length: count }, () => true);
if (!Array.isArray(source)) return Array.from({ length: count }, () => false);
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
}
function normalizeHomedArrayForMachine(machine = {}, allHomed = false, fallbackCount = 5) {
const source = Array.isArray(machine.homed) ? machine.homed : [];
const count = Math.max(source.length, fallbackCount);
if (allHomed) return Array.from({ length: count }, () => true);
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
}
function kinsTypeFromSwitchkinsTypeValue(state, value) {
const numeric = Number(value);
if (!Number.isFinite(numeric) || numeric === 0) return "identity";
@@ -4639,33 +4862,37 @@ function canMoveMachine(state) {
}
function normalizeMachineForLinuxCncTask(machine = {}, runState = "idle") {
const taskState = machine.estopActive
? "estop"
: machine.taskState === "on" || machine.powerOn
? "on"
: machine.taskState === "off"
? "off"
: "estop-reset";
const mode = normalizeLinuxCncTaskMode(machine.mode);
const interpState = machine.interpState
const baseMachine = { ...initialState.machine, ...machine };
const taskState = deriveLinuxCncTaskState(baseMachine);
const mode = normalizeLinuxCncTaskMode(baseMachine.mode);
const interpState = baseMachine.interpState
|| (runState === "running" ? "reading" : runState === "paused" || runState === "stepping" ? "paused" : "idle");
const manualPanel = mode === "manual"
? (machine.manualPanel === "jog" ? "jog" : "manual")
? (baseMachine.manualPanel === "jog" ? "jog" : "manual")
: null;
const homed = normalizeHomedArrayForMachine(baseMachine, Boolean(baseMachine.allHomed));
const allHomed = Boolean(baseMachine.allHomed || homed.every(Boolean));
return {
...machine,
...baseMachine,
taskState,
mode,
manualPanel,
interpState,
interpResumeState: machine.interpResumeState || (interpState === "paused" ? "reading" : interpState),
taskPaused: Boolean(machine.taskPaused || interpState === "paused"),
motionPaused: Boolean(machine.motionPaused || machine.taskPaused || interpState === "paused"),
interpResumeState: baseMachine.interpResumeState || (interpState === "paused" ? "reading" : interpState),
taskPaused: Boolean(baseMachine.taskPaused || interpState === "paused"),
motionPaused: Boolean(baseMachine.motionPaused || baseMachine.taskPaused || interpState === "paused"),
singleStepping: Boolean(baseMachine.singleStepping),
motionStepping: Boolean(baseMachine.motionStepping),
resumeInhibit: Boolean(baseMachine.resumeInhibit),
motionEnabled: taskState === "on",
powerOn: taskState === "on",
estopActive: taskState === "estop",
allHomed: Boolean(machine.allHomed),
noForceHoming: Boolean(machine.noForceHoming),
allHomed,
homed,
noForceHoming: Boolean(baseMachine.noForceHoming),
homing: Boolean(baseMachine.homing),
homeState: allHomed ? "homed" : baseMachine.homeState || "unhomed",
};
}

View File

@@ -254,11 +254,24 @@ export function normalizeTaskHalStatus(status = {}) {
taskPaused: status.task?.taskPaused === true,
singleStepping: status.task?.singleStepping === true,
taskCycle: Number(status.task?.taskCycle ?? status.taskCycle ?? 0),
programOpen: status.task?.programOpen === true,
programFile: status.task?.file || status.task?.openProgram || null,
programStartLine: Number(status.task?.programStartLine || 0),
planId: Number(status.task?.planId || 0),
motionPlanLoaded: status.task?.motionPlanLoaded === true,
errorText: status.task?.errorText || null,
allHomed: status.task?.allHomed === true,
homed: normalizeTaskHalHomed(status.task?.homed, status.task?.allHomed === true),
noForceHoming: status.task?.noForceHoming === true,
homing: status.task?.homing === true,
homeState: String(status.task?.homeState || "UNHOMED").toLowerCase(),
motionEnabled: String(status.task?.state || "").toUpperCase() === "ON" || motion.enabled === true,
resumeInhibit: status.task?.resumeInhibit === true,
servoCycle: Number(status.motionStatus?.cycle ?? status.task?.servoCycle ?? 0),
motionQueueDepth,
motionPaused: motion.paused === true,
motionStepping: motion.stepping === true,
motionId: Number(motion.motionId || 0),
motionId: Number(motion.motionId ?? motion.id ?? 0),
idForStep: Number(motion.idForStep || 0),
halChangedPinCount: Array.isArray(status.halSnapshot?.changedPins)
? status.halSnapshot.changedPins.length
@@ -285,6 +298,13 @@ export function normalizeTaskHalStatus(status = {}) {
};
}
function normalizeTaskHalHomed(value, allHomed = false) {
if (Array.isArray(value)) {
return value.map(Boolean);
}
return allHomed ? [true, true, true, true, true] : [false, false, false, false, false];
}
function shouldUseSourceLineSegments(planSegments, lineSegments) {
if (lineSegments.length < 3) return false;
const plannedLines = new Set(planSegments.map((segment) => Number(segment.line)).filter(Number.isFinite));

View File

@@ -36,13 +36,16 @@ export function normalizeLinuxCncTaskMode(mode) {
return LINUXCNC_TASK_MODES.has(mode) ? mode : "manual";
}
export function normalizeLinuxCncTaskState(machine = {}) {
if (LINUXCNC_TASK_STATES.has(machine.taskState)) return machine.taskState;
if (machine.estopActive) return "estop";
if (machine.powerOn) return "on";
export function deriveLinuxCncTaskState(machine = {}) {
if (machine.estopActive === true || machine.taskState === "estop") return "estop";
if (machine.motionEnabled === true || machine.powerOn === true || machine.taskState === "on") return "on";
return "estop-reset";
}
export function normalizeLinuxCncTaskState(machine = {}) {
return deriveLinuxCncTaskState(machine);
}
export function normalizeLinuxCncInterpState(machine = {}, runState = "idle") {
if (LINUXCNC_INTERP_STATES.has(machine.interpState)) return machine.interpState;
if (runState === "running") return "reading";
@@ -54,8 +57,15 @@ export function createLinuxCncTaskPolicyStatus(state) {
const taskState = normalizeLinuxCncTaskState(state.machine);
const taskMode = normalizeLinuxCncTaskMode(state.machine.mode);
const interpState = normalizeLinuxCncInterpState(state.machine, state.runState);
const allHomed = Boolean(state.machine.allHomed);
const statusHomed = state.taskHalStatus?.ui?.homed || state.taskHalStatus?.task?.homed;
const homed = normalizeHomedArray(state.machine.homed, statusHomed, state.profile?.jointConfig?.length);
const allHomed = Boolean(state.machine.allHomed || homed.length > 0 && homed.every(Boolean));
const noForceHoming = Boolean(state.machine.noForceHoming);
const homing = Boolean(
state.machine.homing ||
state.taskHalStatus?.ui?.homing ||
state.taskHalStatus?.task?.homing,
);
const machineFileStaging = state.machineFileStaging || {};
const profileId = state.profile?.id || state.machineProfile || null;
const taskHalReadiness = state.taskHalRuntimeReadiness || {};
@@ -70,6 +80,8 @@ export function createLinuxCncTaskPolicyStatus(state) {
const machineFileOpened = profileId === "gmoccapy-xyzab"
? Boolean(machineFileStaging.selectedGcodeSourceRel)
: Boolean(machineFileStaging.selectedGcodeSourceRel || state.activeProgram);
const taskProgramOpen = Boolean(state.taskHalStatus?.ui?.programOpen || state.taskHalStatus?.task?.programOpen);
const taskMotionPlanLoaded = Boolean(state.taskHalStatus?.ui?.motionPlanLoaded || state.taskHalStatus?.task?.motionPlanLoaded);
const interpIdle = interpState === "idle";
const motionPaused = state.machine?.motionPaused === true ||
state.taskHalStatus?.ui?.motionPaused === true ||
@@ -77,6 +89,19 @@ export function createLinuxCncTaskPolicyStatus(state) {
const taskPaused = state.machine?.taskPaused === true ||
state.taskHalStatus?.ui?.taskPaused === true ||
state.taskHalStatus?.task?.taskPaused === true;
const motionEnabled = taskState === "on" ||
state.machine?.motionEnabled === true ||
state.taskHalStatus?.ui?.motionEnabled === true ||
state.taskHalStatus?.motionStatus?.motion?.enabled === true;
const singleStepping = state.machine?.singleStepping === true ||
state.taskHalStatus?.ui?.singleStepping === true ||
state.taskHalStatus?.task?.singleStepping === true;
const motionStepping = state.machine?.motionStepping === true ||
state.taskHalStatus?.ui?.motionStepping === true ||
state.taskHalStatus?.motionStatus?.motion?.stepping === true;
const resumeInhibit = state.machine?.resumeInhibit === true ||
state.taskHalStatus?.ui?.resumeInhibit === true ||
state.taskHalStatus?.task?.resumeInhibit === true;
const feedbackPaused = state.programRuntimeFeedback?.paused === true;
const paused = motionPaused || taskPaused || feedbackPaused || interpState === "paused" || state.runState === "paused";
const running = taskMode === "auto" &&
@@ -91,28 +116,40 @@ export function createLinuxCncTaskPolicyStatus(state) {
runState: state.runState || "idle",
motionPaused,
taskPaused,
motionEnabled,
singleStepping,
motionStepping,
resumeInhibit,
feedbackPaused,
paused,
homed,
allHomed,
noForceHoming,
homing,
iniLoaded,
machineFileStaged,
machineFileOpened,
taskProgramOpen,
taskMotionPlanLoaded,
taskHalRuntimeReady,
interpIdle,
referenceOnly,
profileId,
powerOn: taskState === "on",
estopActive: taskState === "estop",
canPowerToggle: taskState !== "estop",
powerAction: taskState === "estop-reset" ? "on" : taskState === "on" ? "off" : "disabled",
canMove: taskState === "on",
canJog: taskState === "on" && taskMode === "manual" && !running,
canHome: taskState === "on" && taskMode === "manual" && !running,
canRunAuto: taskState === "on" && taskMode === "auto" && (allHomed || noForceHoming),
canJog: taskState === "on" && taskMode === "manual" && interpIdle && !homing && !running,
canHome: taskState === "on" && taskMode === "manual" && interpIdle && !homing && !running,
canRunAuto: taskState === "on" && taskMode === "auto" && !homing && (allHomed || noForceHoming),
canRunAutoStrict: taskState === "on" && taskMode === "auto" && (allHomed || noForceHoming) &&
interpIdle && iniLoaded && machineFileStaged && machineFileOpened && taskHalRuntimeReady,
!homing && interpIdle && iniLoaded && machineFileStaged && machineFileOpened && taskHalRuntimeReady,
canStepStrict: taskState === "on" && taskMode === "auto" && !homing &&
(allHomed || noForceHoming) && machineFileOpened && !resumeInhibit,
canExecuteMdi: taskState === "on" && taskMode === "mdi" && (allHomed || noForceHoming),
canPause: taskState === "on" && taskMode === "auto" && (interpState === "reading" || interpState === "waiting"),
canResume: taskState === "on" && (taskMode === "auto" || taskMode === "mdi") && paused,
canResume: taskState === "on" && (taskMode === "auto" || taskMode === "mdi") && paused && !resumeInhibit,
canAbort: true,
canSpindle: taskState === "on",
canCoolant: taskState === "on",
@@ -128,24 +165,22 @@ export function gateLinuxCncTaskAction(state, action) {
switch (type) {
case "TOGGLE_POWER":
if (status.taskState === "estop") return block(status, "power blocked: reset ESTOP first");
if (status.powerAction === "disabled") return block(status, "power blocked: invalid task state");
return allow(status);
case "SET_MODE":
return gateMode(status, requestedMode);
case "JOG":
if (status.taskState !== "on") return block(status, "jog blocked: machine must be on");
if (status.taskMode !== "manual") return block(status, "jog blocked: switch to manual mode first");
if (status.taskMode === "auto" &&
(status.runState === "running" || status.interpState === "reading" || status.interpState === "waiting")) {
return block(status, "jog blocked: interpreter must be idle");
}
if (status.homing) return block(status, "jog blocked: homing is active");
if (status.interpState !== "idle") return block(status, "jog blocked: interpreter must be idle");
return allow(status);
case "HOME":
if (status.taskState !== "on") return block(status, "home blocked: machine must be on");
if (status.taskMode !== "manual") return block(status, "home blocked: switch to manual mode first");
if (status.taskMode === "auto" &&
(status.runState === "running" || status.interpState === "reading" || status.interpState === "waiting")) {
return block(status, "home blocked: interpreter must be idle");
}
if (status.homing) return block(status, "home blocked: homing is active");
if (status.interpState !== "idle") return block(status, "home blocked: interpreter must be idle");
return allow(status);
case "RUN_MDI":
{
@@ -163,8 +198,15 @@ export function gateLinuxCncTaskAction(state, action) {
case "RUN_FRAME":
if (status.taskState !== "on") return block(status, `${type.toLowerCase()} blocked: machine must be on`);
if (status.taskMode !== "auto") return block(status, `${type.toLowerCase()} blocked: switch to auto mode first`);
if (status.homing) return block(status, `${type.toLowerCase()} blocked: homing is active`);
if (!status.allHomed && !status.noForceHoming) return block(status, `${type.toLowerCase()} blocked: home machine first`);
if (type === "RUN" && status.interpState === "paused") return block(status, "run blocked: resume paused program first");
if (type === "RUN" && status.interpState !== "idle") return block(status, "run blocked: interpreter must be idle");
if (type === "RUN" && status.taskHalRuntimeReady && !status.machineFileOpened) {
return block(status, "run blocked: no G-code program opened");
}
if (type === "STEP" && !status.machineFileOpened) return block(status, "step blocked: no G-code program opened");
if (type === "STEP" && status.resumeInhibit) return block(status, "step blocked: resume is inhibited");
if (type === "RUN" && status.profileId === "gmoccapy-xyzab") {
const strictGate = gateGmoccapyXyzabRun(status);
if (!strictGate.allowed) return strictGate;
@@ -193,6 +235,7 @@ export function gateLinuxCncTaskAction(state, action) {
}
case "RESUME":
if (status.taskState !== "on") return block(status, "resume blocked: machine must be on");
if (status.resumeInhibit) return block(status, "resume blocked: resume is inhibited");
if (status.taskMode !== "auto" && status.taskMode !== "mdi" && status.runState !== "paused") {
return block(status, "resume blocked: task mode must be auto or MDI");
}
@@ -259,12 +302,26 @@ function gateMode(status, requestedMode) {
if (status.taskState !== "on") {
return block(status, `mode blocked: machine must be on before ${targetMode.toUpperCase()}`);
}
if ((targetMode === "mdi" || targetMode === "auto") && !status.allHomed && !status.noForceHoming) {
if ((targetMode === "mdi" || targetMode === "auto") &&
!status.homing &&
!status.allHomed &&
!status.noForceHoming) {
return block(status, `mode blocked: home machine before ${targetMode.toUpperCase()}`);
}
return allow(status);
}
function normalizeHomedArray(machineHomed, statusHomed, jointCount = 0) {
const source = Array.isArray(statusHomed)
? statusHomed
: Array.isArray(machineHomed)
? machineHomed
: [];
const count = Math.max(Number(jointCount || 0), source.length);
if (count <= 0) return source.map(Boolean);
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
}
function allow(status) {
return {
allowed: true,

View File

@@ -43,6 +43,7 @@ import { createLinuxCncParityMatrix } from "../runtime/linuxcnc-parity-matrix.js
import { gmoccapyHalModel, resolveGmoccapyHardwareButton } from "../runtime/gmoccapy-hal-model.js";
import {
createLinuxCncTaskPolicyStatus,
deriveLinuxCncTaskState,
gateLinuxCncTaskAction,
normalizeLinuxCncTaskMode,
} from "./linuxcnc-task-policy.js";
@@ -166,15 +167,22 @@ const initialState = {
machine: {
powerOn: false,
estopActive: false,
motionEnabled: false,
taskState: "estop-reset",
mode: "manual",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
resumeInhibit: false,
manualPanel: "manual",
allHomed: false,
homed: [false, false, false, false, false],
noForceHoming: false,
homing: false,
homeState: "unhomed",
selectedJoint: 0,
jogAxis: "x",
jogIncrement: 1,
@@ -410,6 +418,7 @@ export function createSimulationStore(seed = {}) {
: seed.programAxisPreviewPath;
state = {
...state,
machine: normalizeMachineForLinuxCncTask(state.machine, state.runState),
programAxisPreviewPath: initialAxisPreviewPath,
preview: {
...state.preview,
@@ -1309,13 +1318,18 @@ export function createSimulationStore(seed = {}) {
...state.machine,
powerOn: false,
estopActive: true,
motionEnabled: false,
taskState: "estop",
manualPanel: "manual",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "estopped",
taskHalPauseLock: null,
kinsType: "identity",
rtcpState: "off",
feed: {
@@ -1330,8 +1344,29 @@ export function createSimulationStore(seed = {}) {
spindle: {
...stoppedSpindleState(state.spindle),
},
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
operatorMessage: "emergency stop active",
});
if (state.taskHalRuntime?.loaded) {
runTaskHalCommandSequence([
{ type: "EMC_TASK_SET_STATE", state: "ESTOP" },
], {
operatorMessage: "task/HAL emergency stop active",
preserveMachine: {
...state.machine,
powerOn: false,
estopActive: true,
motionEnabled: false,
taskState: "estop",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
}).catch(() => {});
}
break;
case "RESET":
setState({
@@ -1339,14 +1374,19 @@ export function createSimulationStore(seed = {}) {
...state.machine,
powerOn: false,
estopActive: false,
motionEnabled: false,
taskState: "estop-reset",
manualPanel: "manual",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
resetCount: state.machine.resetCount + 1,
},
runState: "idle",
taskHalPauseLock: null,
kinsType: "identity",
rtcpState: "off",
coolant: {
@@ -1357,8 +1397,29 @@ export function createSimulationStore(seed = {}) {
spindle: {
...stoppedSpindleState(state.spindle),
},
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
operatorMessage: "estop reset; machine off",
});
if (state.taskHalRuntime?.loaded) {
runTaskHalCommandSequence([
{ type: "EMC_TASK_SET_STATE", state: "ESTOP_RESET" },
], {
operatorMessage: "task/HAL estop reset; machine off",
preserveMachine: {
...state.machine,
powerOn: false,
estopActive: false,
motionEnabled: false,
taskState: "estop-reset",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
}).catch(() => {});
}
break;
case "SET_MODE":
{
@@ -1375,27 +1436,46 @@ export function createSimulationStore(seed = {}) {
? "manual"
: null;
if (state.taskHalRuntime?.loaded) {
const manualModePatch = mode === "manual"
? {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
}
: {};
const requestedMachine = {
...state.machine,
mode,
manualPanel,
...manualModePatch,
};
setState({
machine: requestedMachine,
operatorMessage: `task/HAL mode ${mode} requested`,
});
runTaskHalCommandSequence([
{ type: "EMC_TASK_SET_MODE", mode: mode.toUpperCase() },
], {
operatorMessage: `task/HAL mode ${mode}`,
preserveMachine: {
...state.machine,
mode,
manualPanel,
},
preserveMachine: requestedMachine,
}).catch(() => {});
break;
}
setState({
machine: {
...state.machine,
mode,
manualPanel,
interpState: mode === "manual" ? "idle" : state.machine.interpState,
interpResumeState: mode === "manual" ? "idle" : state.machine.interpResumeState,
taskPaused: mode === "manual" ? false : state.machine.taskPaused,
},
machine: {
...state.machine,
mode,
manualPanel,
interpState: mode === "manual" ? "idle" : state.machine.interpState,
interpResumeState: mode === "manual" ? "idle" : state.machine.interpResumeState,
taskPaused: mode === "manual" ? false : state.machine.taskPaused,
motionPaused: mode === "manual" ? false : state.machine.motionPaused,
singleStepping: mode === "manual" ? false : state.machine.singleStepping,
motionStepping: mode === "manual" ? false : state.machine.motionStepping,
},
runState: mode === "manual" && state.runState === "running" ? "stopped" : state.runState,
operatorMessage: `mode ${mode}`,
});
@@ -1473,6 +1553,9 @@ export function createSimulationStore(seed = {}) {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
axisPose: nextAxisPose,
runState: "jogging",
@@ -1496,6 +1579,9 @@ export function createSimulationStore(seed = {}) {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
operatorMessage: `task/HAL jog ${axis.toUpperCase()} ${direction > 0 ? "+" : "-"}${increment}`,
}).catch(() => {});
@@ -1580,6 +1666,9 @@ export function createSimulationStore(seed = {}) {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
axisPose: initialAxisPose,
runState: "idle",
@@ -1615,13 +1704,15 @@ export function createSimulationStore(seed = {}) {
...state.machine,
mode: "auto",
manualPanel: null,
interpState: "reading",
interpResumeState: "reading",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "running",
taskHalPauseLock: null,
operatorMessage: "task/HAL program run requested",
operatorMessage: "task/HAL program run requested; waiting for status",
});
runValidatedTaskHalProgramRun().catch(() => {});
break;
@@ -1644,6 +1735,9 @@ export function createSimulationStore(seed = {}) {
interpState: playback.complete ? "idle" : "reading",
interpResumeState: playback.complete ? "idle" : "reading",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: playback.complete ? "complete" : "running",
taskHalPauseLock: null,
@@ -1687,6 +1781,9 @@ export function createSimulationStore(seed = {}) {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
})
.then(() => {
@@ -1705,6 +1802,9 @@ export function createSimulationStore(seed = {}) {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "stopped",
taskHalPauseLock: null,
@@ -1764,6 +1864,8 @@ export function createSimulationStore(seed = {}) {
interpState: "paused",
taskPaused: true,
motionPaused: true,
singleStepping: false,
motionStepping: false,
};
const pauseLock = createTaskHalPauseLock(state, "task-hal-plan-pause");
setState({
@@ -1803,6 +1905,8 @@ export function createSimulationStore(seed = {}) {
interpState: "paused",
taskPaused: true,
motionPaused: true,
singleStepping: false,
motionStepping: false,
},
runState: "paused",
taskHalPauseLock: pauseLock,
@@ -1853,10 +1957,12 @@ export function createSimulationStore(seed = {}) {
interpResumeState: resumeState,
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
};
setState({
machine: resumedMachine,
runState: resumeState === "reading" ? "running" : "idle",
runState: resumeState === "reading" || resumeState === "waiting" ? "running" : "idle",
taskHalPauseLock: null,
operatorMessage: "task/HAL resume requested",
});
@@ -1885,8 +1991,10 @@ export function createSimulationStore(seed = {}) {
interpResumeState: resumeState,
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: resumeState === "reading" ? "running" : "idle",
runState: resumeState === "reading" || resumeState === "waiting" ? "running" : "idle",
taskHalPauseLock: null,
operatorMessage: "program resumed",
});
@@ -1921,6 +2029,8 @@ export function createSimulationStore(seed = {}) {
interpState: "paused",
taskPaused: true,
motionPaused: true,
singleStepping: true,
motionStepping: true,
};
setState({
machine: steppedMachine,
@@ -1965,6 +2075,8 @@ export function createSimulationStore(seed = {}) {
interpState: "paused",
taskPaused: true,
motionPaused: true,
singleStepping: true,
motionStepping: true,
},
runState: "stepping",
taskHalPauseLock: null,
@@ -2002,6 +2114,9 @@ export function createSimulationStore(seed = {}) {
interpState: playback.complete ? "idle" : "reading",
interpResumeState: playback.complete ? "idle" : "reading",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "running",
...playbackPatch,
@@ -2022,16 +2137,24 @@ export function createSimulationStore(seed = {}) {
break;
}
const homeAxisPose = homeAxisPoseForState(state);
const homedFalse = createHomedArrayForState(state, false);
const homedTrue = createHomedArrayForState(state, true);
if (state.taskHalRuntime?.loaded) {
setState({
machine: {
...state.machine,
mode: "manual",
manualPanel: "manual",
allHomed: true,
allHomed: false,
homed: homedFalse,
homing: true,
homeState: "homing",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "idle",
axisPose: homeAxisPose,
@@ -2044,9 +2167,11 @@ export function createSimulationStore(seed = {}) {
operatorMessage: "task/HAL machine homed",
preserveAxisPose: homeAxisPose,
preserveMachine: {
...state.machine,
manualPanel: "manual",
allHomed: true,
homed: homedTrue,
homing: false,
homeState: "homed",
},
}).catch(() => {});
break;
@@ -2057,9 +2182,15 @@ export function createSimulationStore(seed = {}) {
mode: "manual",
manualPanel: "manual",
allHomed: true,
homed: homedTrue,
homing: false,
homeState: "homed",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "idle",
axisPose: homeAxisPose,
@@ -2073,6 +2204,9 @@ export function createSimulationStore(seed = {}) {
machine: {
...state.machine,
allHomed: false,
homed: createHomedArrayForState(state, false),
homing: false,
homeState: "unhomed",
},
operatorMessage: "machine unhomed",
});
@@ -2695,11 +2829,13 @@ export function createSimulationStore(seed = {}) {
...state.machine,
mode: "auto",
manualPanel: null,
interpState: "reading",
interpResumeState: "reading",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: "running",
taskHalPauseLock: null,
operatorMessage: "RUN ready: motion plan source ready; preparing task/HAL session",
});
@@ -3806,7 +3942,13 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
: rawTaskState;
const taskMode = statusPaused
? programControlModeForMachine(state.machine)
: normalizeLinuxCncTaskMode(preserveMachine?.mode || ui.taskMode || task.mode || state.machine.mode);
: normalizeLinuxCncTaskMode(
preserveMachine?.mode ||
(preserveMachine?.allHomed && state.machine.mode !== "manual" ? state.machine.mode : null) ||
ui.taskMode ||
task.mode ||
state.machine.mode,
);
const manualPanel = taskMode === "manual"
? (preserveMachine?.manualPanel || state.machine.manualPanel || "manual")
: null;
@@ -3815,12 +3957,40 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
: Object.hasOwn(preserveMachine || {}, "interpState")
? normalizeTaskHalInterpState(preserveMachine.interpState)
: statusInterpState;
const allHomed = Boolean(preserveMachine?.allHomed ?? state.machine.allHomed);
const allHomed = Boolean(
task.allHomed === true ||
ui.allHomed === true ||
preserveMachine?.allHomed ||
state.machine.allHomed,
);
const homed = normalizeHomedArrayForState(
state,
ui.homed || task.homed || preserveMachine?.homed || state.machine.homed,
allHomed,
);
const homing = Object.hasOwn(task, "homing")
? task.homing === true
: Object.hasOwn(ui, "homing")
? ui.homing === true
: Boolean(preserveMachine?.homing);
const homeState = normalizeTaskHalHomeState(
ui.homeState ||
task.homeState ||
preserveMachine?.homeState ||
state.machine.homeState ||
(allHomed ? "homed" : "unhomed"),
);
const activeLine = state.programStartLine + Math.max(Number(ui.activeLine || 1) - 1, 0);
const kinsType = resolveTaskHalKinsType(state, status, activeLine);
const motionPaused = statusMotionPaused || preserveMachine?.motionPaused === true;
const taskPaused = statusTaskPaused || preserveMachine?.taskPaused === true;
const paused = interpState === "paused" || motionPaused || taskPaused;
const singleStepping = ui.singleStepping === true ||
task.singleStepping === true ||
preserveMachine?.singleStepping === true;
const motionStepping = ui.motionStepping === true ||
motion.stepping === true ||
preserveMachine?.motionStepping === true;
const interpResumeState = normalizeTaskHalInterpState(
ui.interpResumeState ||
task.interpResumeState ||
@@ -3955,6 +4125,7 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
...state.machine,
powerOn: taskState === "on",
estopActive: taskState === "estop",
motionEnabled: taskState === "on",
taskState,
mode: taskMode,
manualPanel,
@@ -3962,7 +4133,12 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
interpResumeState,
taskPaused: paused,
motionPaused,
singleStepping,
motionStepping,
allHomed,
homed,
homing,
homeState,
noForceHoming: Boolean(state.machine.noForceHoming),
},
runState,
@@ -4035,17 +4211,26 @@ function programControlModeForMachine(machine = {}) {
}
function createPowerToggleMachinePatch(machine = {}, turningOn = false) {
const poweredOffHomed = Array.isArray(machine.homed)
? machine.homed.map(() => false)
: [false, false, false, false, false];
return {
...machine,
powerOn: turningOn,
estopActive: false,
taskState: turningOn ? "on" : "off",
motionEnabled: turningOn,
taskState: turningOn ? "on" : "estop-reset",
manualPanel: "manual",
allHomed: turningOn ? Boolean(machine.allHomed) : false,
homed: turningOn ? normalizeHomedArrayForMachine(machine, Boolean(machine.allHomed)) : poweredOffHomed,
homing: false,
homeState: turningOn ? (machine.allHomed ? "homed" : machine.homeState || "unhomed") : "unhomed",
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
};
}
@@ -4094,6 +4279,9 @@ function createStoppedProgramStatePatch(state, {
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
motionPaused: false,
singleStepping: false,
motionStepping: false,
},
runState: reason === "aborted" ? "stopped" : reason,
taskHalPauseLock: null,
@@ -4108,6 +4296,7 @@ function createStoppedProgramStatePatch(state, {
...state.feed,
currentVelocity: 0,
},
programRuntimeFeedback: zeroProgramRuntimeVelocity(state.programRuntimeFeedback),
operatorMessage,
};
}
@@ -4615,7 +4804,6 @@ function normalizeTaskHalTaskState(value) {
const state = String(value || "").toLowerCase().replaceAll("_", "-");
if (state === "on") return "on";
if (state === "estop") return "estop";
if (state === "off") return "off";
return "estop-reset";
}
@@ -4627,6 +4815,41 @@ function normalizeTaskHalInterpState(value) {
return "idle";
}
function normalizeTaskHalHomeState(value) {
const state = String(value || "").toLowerCase().replaceAll("_", "-");
if (state === "homing") return "homing";
if (state === "homed") return "homed";
if (state === "no-force-homing") return "no-force-homing";
return "unhomed";
}
function jointCountForState(state = {}) {
return Math.max(
Number(state.profile?.jointConfig?.length || 0),
Number(state.profile?.joints?.length || 0),
Array.isArray(state.machine?.homed) ? state.machine.homed.length : 0,
5,
);
}
function createHomedArrayForState(state = {}, value = false) {
return Array.from({ length: jointCountForState(state) }, () => Boolean(value));
}
function normalizeHomedArrayForState(state = {}, source = [], allHomed = false) {
const count = jointCountForState(state);
if (allHomed) return Array.from({ length: count }, () => true);
if (!Array.isArray(source)) return Array.from({ length: count }, () => false);
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
}
function normalizeHomedArrayForMachine(machine = {}, allHomed = false, fallbackCount = 5) {
const source = Array.isArray(machine.homed) ? machine.homed : [];
const count = Math.max(source.length, fallbackCount);
if (allHomed) return Array.from({ length: count }, () => true);
return Array.from({ length: count }, (_, index) => Boolean(source[index]));
}
function kinsTypeFromSwitchkinsTypeValue(state, value) {
const numeric = Number(value);
if (!Number.isFinite(numeric) || numeric === 0) return "identity";
@@ -4639,33 +4862,37 @@ function canMoveMachine(state) {
}
function normalizeMachineForLinuxCncTask(machine = {}, runState = "idle") {
const taskState = machine.estopActive
? "estop"
: machine.taskState === "on" || machine.powerOn
? "on"
: machine.taskState === "off"
? "off"
: "estop-reset";
const mode = normalizeLinuxCncTaskMode(machine.mode);
const interpState = machine.interpState
const baseMachine = { ...initialState.machine, ...machine };
const taskState = deriveLinuxCncTaskState(baseMachine);
const mode = normalizeLinuxCncTaskMode(baseMachine.mode);
const interpState = baseMachine.interpState
|| (runState === "running" ? "reading" : runState === "paused" || runState === "stepping" ? "paused" : "idle");
const manualPanel = mode === "manual"
? (machine.manualPanel === "jog" ? "jog" : "manual")
? (baseMachine.manualPanel === "jog" ? "jog" : "manual")
: null;
const homed = normalizeHomedArrayForMachine(baseMachine, Boolean(baseMachine.allHomed));
const allHomed = Boolean(baseMachine.allHomed || homed.every(Boolean));
return {
...machine,
...baseMachine,
taskState,
mode,
manualPanel,
interpState,
interpResumeState: machine.interpResumeState || (interpState === "paused" ? "reading" : interpState),
taskPaused: Boolean(machine.taskPaused || interpState === "paused"),
motionPaused: Boolean(machine.motionPaused || machine.taskPaused || interpState === "paused"),
interpResumeState: baseMachine.interpResumeState || (interpState === "paused" ? "reading" : interpState),
taskPaused: Boolean(baseMachine.taskPaused || interpState === "paused"),
motionPaused: Boolean(baseMachine.motionPaused || baseMachine.taskPaused || interpState === "paused"),
singleStepping: Boolean(baseMachine.singleStepping),
motionStepping: Boolean(baseMachine.motionStepping),
resumeInhibit: Boolean(baseMachine.resumeInhibit),
motionEnabled: taskState === "on",
powerOn: taskState === "on",
estopActive: taskState === "estop",
allHomed: Boolean(machine.allHomed),
noForceHoming: Boolean(machine.noForceHoming),
allHomed,
homed,
noForceHoming: Boolean(baseMachine.noForceHoming),
homing: Boolean(baseMachine.homing),
homeState: allHomed ? "homed" : baseMachine.homeState || "unhomed",
};
}