feat: sync latest run execution updates
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
stageProfileMachineFiles,
|
||||
} from "../runtime/linuxcnc-machine-file-staging.js";
|
||||
import {
|
||||
buildTaskHalProgramMotionPlan,
|
||||
buildTaskHalSessionFromMachineFiles,
|
||||
} from "../runtime/linuxcnc-task-hal-runtime.js";
|
||||
import {
|
||||
@@ -42,6 +43,40 @@ const initialAxisPose = {
|
||||
c: 0.0,
|
||||
};
|
||||
|
||||
function createTaskHalStatusLoopState({
|
||||
active = false,
|
||||
sequence = 0,
|
||||
profileId = null,
|
||||
iniPath = null,
|
||||
kinematicsModuleId = null,
|
||||
tickCount = 0,
|
||||
batchSize = 5,
|
||||
intervalMs = 25,
|
||||
taskPeriodNs = 10000000,
|
||||
servoPeriodNs = 1000000,
|
||||
lastStatusAt = null,
|
||||
lastError = null,
|
||||
stopReason = null,
|
||||
} = {}) {
|
||||
return {
|
||||
apiName: "web-rtcp-5axis-task-hal-status-loop",
|
||||
active,
|
||||
sequence,
|
||||
profileId,
|
||||
iniPath,
|
||||
kinematicsModuleId,
|
||||
tickCount,
|
||||
batchSize,
|
||||
intervalMs,
|
||||
taskPeriodNs,
|
||||
servoPeriodNs,
|
||||
lastStatusAt,
|
||||
lastError,
|
||||
stopReason,
|
||||
semanticBoundary: "js_status_polling_loop_for_linuxcnc_task_hal_motion_status",
|
||||
};
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
machineProfile: "xyzac-trt",
|
||||
availableProfiles: fiveAxisProfiles.map(({ id, title, traj, kinematicsModuleId, kinematics }) => ({
|
||||
@@ -136,12 +171,14 @@ const initialState = {
|
||||
programExecutionMotionIndex: 0,
|
||||
programExecutionSampleIndex: 0,
|
||||
programRuntimeFeedback: null,
|
||||
programRuntimeFeedbackHistory: [],
|
||||
taskHalRuntime: null,
|
||||
taskHalRuntimeReadiness: null,
|
||||
taskHalStatus: null,
|
||||
taskHalSession: null,
|
||||
taskHalExecutionPending: false,
|
||||
taskHalExecutionSequence: 0,
|
||||
taskHalStatusLoop: createTaskHalStatusLoopState(),
|
||||
taskHalFallbackReason: null,
|
||||
pendingJogCommand: null,
|
||||
interpreterExecutionPending: false,
|
||||
@@ -249,6 +286,7 @@ export function createSimulationStore(seed = {}) {
|
||||
};
|
||||
state.fullExecutionBoundary = createFullLinuxCncExecutionBoundary(state);
|
||||
const listeners = new Set();
|
||||
let taskHalStatusLoopTimer = null;
|
||||
|
||||
const notify = () => {
|
||||
for (const listener of listeners) {
|
||||
@@ -772,7 +810,7 @@ export function createSimulationStore(seed = {}) {
|
||||
},
|
||||
machine: {
|
||||
...state.machine,
|
||||
mode: "auto",
|
||||
mode: state.machine.mode,
|
||||
},
|
||||
axisPose: initialAxisPose,
|
||||
runState: "idle",
|
||||
@@ -799,12 +837,51 @@ export function createSimulationStore(seed = {}) {
|
||||
});
|
||||
break;
|
||||
case "TASK_HAL_STATUS_APPLIED":
|
||||
setState(applyTaskHalStatusPatch(state, action.status, action.operatorMessage));
|
||||
setState(applyTaskHalStatusPatch(state, action.status, action.operatorMessage, {
|
||||
loopSequence: action.loopSequence,
|
||||
preserveMachine: action.preserveMachine,
|
||||
}));
|
||||
break;
|
||||
case "TASK_HAL_STATUS_LOOP_STARTED":
|
||||
setState({
|
||||
taskHalStatusLoop: {
|
||||
...createTaskHalStatusLoopState({
|
||||
active: true,
|
||||
sequence: action.sequence,
|
||||
profileId: action.profileId,
|
||||
iniPath: action.iniPath,
|
||||
kinematicsModuleId: action.kinematicsModuleId,
|
||||
batchSize: action.batchSize,
|
||||
intervalMs: action.intervalMs,
|
||||
taskPeriodNs: action.taskPeriodNs,
|
||||
servoPeriodNs: action.servoPeriodNs,
|
||||
}),
|
||||
},
|
||||
programRuntimeFeedbackHistory: [],
|
||||
operatorMessage: action.operatorMessage || "task/HAL status loop running",
|
||||
});
|
||||
break;
|
||||
case "TASK_HAL_STATUS_LOOP_STOPPED":
|
||||
setState({
|
||||
taskHalStatusLoop: {
|
||||
...state.taskHalStatusLoop,
|
||||
active: false,
|
||||
stopReason: action.reason || "stopped",
|
||||
lastError: action.error || null,
|
||||
},
|
||||
operatorMessage: action.operatorMessage || state.operatorMessage,
|
||||
});
|
||||
break;
|
||||
case "TASK_HAL_COMMAND_FAILED":
|
||||
setState({
|
||||
taskHalFallbackReason: action.error,
|
||||
taskHalExecutionPending: false,
|
||||
taskHalStatusLoop: {
|
||||
...state.taskHalStatusLoop,
|
||||
active: false,
|
||||
lastError: action.error,
|
||||
stopReason: "error",
|
||||
},
|
||||
pendingJogCommand: null,
|
||||
operatorMessage: `task/HAL fallback: ${action.error}`,
|
||||
});
|
||||
@@ -850,13 +927,29 @@ export function createSimulationStore(seed = {}) {
|
||||
setState({ operatorMessage: gate.operatorMessage });
|
||||
break;
|
||||
}
|
||||
const turningOff = state.machine.taskState === "on" || state.machine.powerOn;
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
setState({
|
||||
machine: {
|
||||
...state.machine,
|
||||
powerOn: !turningOff,
|
||||
estopActive: false,
|
||||
taskState: turningOff ? "estop-reset" : "on",
|
||||
interpState: "idle",
|
||||
interpResumeState: "idle",
|
||||
taskPaused: false,
|
||||
},
|
||||
runState: turningOff ? "powered-off" : "idle",
|
||||
feed: turningOff ? { ...state.feed, currentVelocity: 0 } : state.feed,
|
||||
coolant: turningOff ? { ...state.coolant, flood: false, mist: false } : state.coolant,
|
||||
spindle: turningOff ? { ...state.spindle, enabled: false } : state.spindle,
|
||||
operatorMessage: turningOff ? "task/HAL machine power off" : "task/HAL machine power on",
|
||||
});
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_SET_STATE", state: state.machine.powerOn ? "ESTOP_RESET" : "ON" },
|
||||
], { operatorMessage: state.machine.powerOn ? "task/HAL machine power off" : "task/HAL machine power on" }).catch(() => {});
|
||||
{ type: "EMC_TASK_SET_STATE", state: turningOff ? "ESTOP_RESET" : "ON" },
|
||||
], { operatorMessage: turningOff ? "task/HAL machine power off" : "task/HAL machine power on" }).catch(() => {});
|
||||
break;
|
||||
}
|
||||
const turningOff = state.machine.taskState === "on" || state.machine.powerOn;
|
||||
setState({
|
||||
machine: {
|
||||
...state.machine,
|
||||
@@ -1108,6 +1201,9 @@ export function createSimulationStore(seed = {}) {
|
||||
break;
|
||||
}
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
stopTaskHalStatusLoop(action.type === "ABORT" ? "aborted" : "stopped", {
|
||||
operatorMessage: action.type === "ABORT" ? "task/HAL abort requested" : "task/HAL stop requested",
|
||||
});
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_ABORT" },
|
||||
], { operatorMessage: action.type === "ABORT" ? "task/HAL abort complete" : "task/HAL program stopped" }).catch(() => {});
|
||||
@@ -1137,6 +1233,7 @@ export function createSimulationStore(seed = {}) {
|
||||
break;
|
||||
}
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
stopTaskHalStatusLoop("paused", { operatorMessage: "task/HAL pause requested" });
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_PLAN_PAUSE" },
|
||||
], { operatorMessage: "task/HAL program paused" }).catch(() => {});
|
||||
@@ -1166,7 +1263,15 @@ export function createSimulationStore(seed = {}) {
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_PLAN_RESUME" },
|
||||
], { operatorMessage: "task/HAL program resumed" }).catch(() => {});
|
||||
], {
|
||||
operatorMessage: "task/HAL program resumed",
|
||||
}).then(() => {
|
||||
if (state.runState === "running" || state.machine.interpState === "reading") {
|
||||
startTaskHalStatusLoop({
|
||||
operatorMessage: "task/HAL status loop resumed",
|
||||
});
|
||||
}
|
||||
}).catch(() => {});
|
||||
break;
|
||||
}
|
||||
const resumeState = state.machine.interpResumeState === "idle"
|
||||
@@ -1191,6 +1296,14 @@ export function createSimulationStore(seed = {}) {
|
||||
setState({ operatorMessage: gate.operatorMessage });
|
||||
break;
|
||||
}
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
stopTaskHalStatusLoop("step", { operatorMessage: "task/HAL step requested" });
|
||||
runTaskHalCommandSequence([], {
|
||||
taskCycles: 1,
|
||||
operatorMessage: "task/HAL stepped one cycle",
|
||||
}).catch(() => {});
|
||||
break;
|
||||
}
|
||||
const playback = nextProgramRuntimeSamplePlayback(state, 1);
|
||||
setState({
|
||||
machine: {
|
||||
@@ -1259,6 +1372,11 @@ export function createSimulationStore(seed = {}) {
|
||||
setState({ operatorMessage: gate.operatorMessage });
|
||||
break;
|
||||
}
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
runTaskHalCommandSequence([
|
||||
{ type: "EMC_JOINT_HOME", joint: -1 },
|
||||
], { operatorMessage: "task/HAL machine homed" }).catch(() => {});
|
||||
}
|
||||
setState({
|
||||
machine: {
|
||||
...state.machine,
|
||||
@@ -1541,6 +1659,13 @@ export function createSimulationStore(seed = {}) {
|
||||
if (!state.taskHalRuntime?.loaded || !state.machineFileStaging?.save?.files?.length) {
|
||||
return null;
|
||||
}
|
||||
const preserveMachine = {
|
||||
powerOn: state.machine.powerOn,
|
||||
estopActive: state.machine.estopActive,
|
||||
taskState: state.machine.taskState,
|
||||
mode: state.machine.mode,
|
||||
allHomed: state.machine.allHomed,
|
||||
};
|
||||
const selectedPlan = selectMachineFileProgramForState(state);
|
||||
const session = buildTaskHalSessionFromMachineFiles({
|
||||
profile: state.profile,
|
||||
@@ -1559,12 +1684,28 @@ export function createSimulationStore(seed = {}) {
|
||||
await state.taskHalRuntime.stageFiles(session.files);
|
||||
if (openProgram && session.programPath) {
|
||||
await state.taskHalRuntime.openProgram(session.programPath);
|
||||
await loadTaskHalMotionPlanForSession(session);
|
||||
}
|
||||
if (preserveMachine.powerOn) {
|
||||
await state.taskHalRuntime.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" });
|
||||
}
|
||||
if (preserveMachine.allHomed) {
|
||||
await state.taskHalRuntime.sendCommand({ type: "EMC_JOINT_HOME", joint: -1 });
|
||||
}
|
||||
await state.taskHalRuntime.sendCommand({
|
||||
type: "EMC_TASK_SET_MODE",
|
||||
mode: normalizeLinuxCncTaskMode(preserveMachine.mode).toUpperCase(),
|
||||
});
|
||||
await state.taskHalRuntime.runCycles({
|
||||
...deriveTaskHalCyclePeriods(state),
|
||||
taskCycles: 1,
|
||||
});
|
||||
dispatch({ type: "TASK_HAL_SESSION_READY", session });
|
||||
const status = await state.taskHalRuntime.readStatus();
|
||||
dispatch({
|
||||
type: "TASK_HAL_STATUS_APPLIED",
|
||||
status,
|
||||
preserveMachine,
|
||||
operatorMessage: `LinuxCNC task/HAL session ready ${session.programPath || "-"}`,
|
||||
});
|
||||
return session;
|
||||
@@ -1581,6 +1722,11 @@ export function createSimulationStore(seed = {}) {
|
||||
if (!state.taskHalSession || (expectedProgramPath && state.taskHalSession.programPath !== expectedProgramPath)) {
|
||||
await initializeTaskHalSession({ openProgram: true });
|
||||
}
|
||||
const loadedMotionPlan = await loadTaskHalMotionPlanForSession(state.taskHalSession);
|
||||
if (!loadedMotionPlan) {
|
||||
setState({ operatorMessage: "run blocked: task/HAL feed motion plan not loaded" });
|
||||
return null;
|
||||
}
|
||||
|
||||
const ready = validateRunPreconditions(state, { requireTaskHalSession: true });
|
||||
if (!ready.ok) {
|
||||
@@ -1588,21 +1734,151 @@ export function createSimulationStore(seed = {}) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return runTaskHalCommandSequence([
|
||||
stopTaskHalStatusLoop("restarted", {
|
||||
operatorMessage: "task/HAL status loop restarting",
|
||||
});
|
||||
|
||||
const status = await runTaskHalCommandSequence([
|
||||
{ type: "EMC_TASK_SET_STATE", state: "ON" },
|
||||
{ type: "EMC_TASK_SET_MODE", mode: "AUTO" },
|
||||
{ type: "EMC_TASK_PLAN_RUN", line: Math.max(Number(state.activeLine || 1) - Number(state.programStartLine || 1), 0) },
|
||||
{ type: "EMC_TASK_PLAN_RUN", line: 0 },
|
||||
], {
|
||||
taskCycles: 5,
|
||||
operatorMessage: `task/HAL program run ${ready.profileId} ${ready.kinematicsModuleId}`,
|
||||
allowFixtureSession: false,
|
||||
});
|
||||
if (shouldContinueTaskHalStatusLoop(state, status)) {
|
||||
startTaskHalStatusLoop({
|
||||
profileId: ready.profileId,
|
||||
iniPath: ready.iniPath,
|
||||
kinematicsModuleId: ready.kinematicsModuleId,
|
||||
operatorMessage: `task/HAL status loop running ${ready.profileId} ${ready.kinematicsModuleId}`,
|
||||
});
|
||||
}
|
||||
return status;
|
||||
};
|
||||
|
||||
const loadTaskHalMotionPlanForSession = async (session = state.taskHalSession) => {
|
||||
if (!state.taskHalRuntime?.loaded || typeof state.taskHalRuntime.loadProgramMotionPlan !== "function") {
|
||||
return null;
|
||||
}
|
||||
const motion = state.programExecution?.motion || [];
|
||||
const timing = state.programExecutionTiming || buildTimingForState(state, state.programExecution);
|
||||
if (!session?.programPath || !Array.isArray(motion) || motion.length === 0 || !Array.isArray(timing?.segments) || timing.segments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const plan = buildTaskHalProgramMotionPlan({
|
||||
programPath: session.programPath,
|
||||
motion,
|
||||
timing,
|
||||
programLines: state.programLines,
|
||||
linearUnits: timing.linearUnits || state.profile?.traj?.linearUnits || "mm",
|
||||
});
|
||||
if (plan.segmentCount <= 0) {
|
||||
return null;
|
||||
}
|
||||
await state.taskHalRuntime.loadProgramMotionPlan(plan);
|
||||
return plan;
|
||||
};
|
||||
|
||||
const startTaskHalStatusLoop = ({
|
||||
profileId = state.machineProfile,
|
||||
iniPath = state.profile?.iniPath || null,
|
||||
kinematicsModuleId = state.profile?.kinematicsModuleId || state.machineProfile,
|
||||
batchSize = 5,
|
||||
intervalMs = 25,
|
||||
taskPeriodNs = deriveTaskHalCyclePeriods(state).taskPeriodNs,
|
||||
servoPeriodNs = deriveTaskHalCyclePeriods(state).servoPeriodNs,
|
||||
operatorMessage = "task/HAL status loop running",
|
||||
} = {}) => {
|
||||
if (!state.taskHalRuntime?.loaded) return null;
|
||||
stopTaskHalStatusLoop("restarted", { notify: false });
|
||||
const sequence = Number(state.taskHalStatusLoop?.sequence || 0) + 1;
|
||||
dispatch({
|
||||
type: "TASK_HAL_STATUS_LOOP_STARTED",
|
||||
sequence,
|
||||
profileId,
|
||||
iniPath,
|
||||
kinematicsModuleId,
|
||||
batchSize,
|
||||
intervalMs,
|
||||
taskPeriodNs,
|
||||
servoPeriodNs,
|
||||
operatorMessage,
|
||||
});
|
||||
const tick = () => runTaskHalStatusLoopTick(sequence).catch((error) => {
|
||||
stopTaskHalStatusLoop("error", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
operatorMessage: `task/HAL status loop failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
});
|
||||
});
|
||||
taskHalStatusLoopTimer = setTimeout(tick, intervalMs);
|
||||
return sequence;
|
||||
};
|
||||
|
||||
const runTaskHalStatusLoopTick = async (sequence) => {
|
||||
const loop = state.taskHalStatusLoop || {};
|
||||
if (!loop.active || loop.sequence !== sequence || !state.taskHalRuntime?.loaded) {
|
||||
return null;
|
||||
}
|
||||
await state.taskHalRuntime.runCycles({
|
||||
taskPeriodNs: loop.taskPeriodNs,
|
||||
servoPeriodNs: loop.servoPeriodNs,
|
||||
taskCycles: loop.batchSize,
|
||||
});
|
||||
const status = await state.taskHalRuntime.readStatus();
|
||||
if (state.taskHalStatusLoop?.sequence !== sequence) {
|
||||
return status;
|
||||
}
|
||||
dispatch({
|
||||
type: "TASK_HAL_STATUS_APPLIED",
|
||||
status,
|
||||
loopSequence: sequence,
|
||||
operatorMessage: `task/HAL status tick ${Number(state.taskHalStatusLoop?.tickCount || 0) + 1}`,
|
||||
});
|
||||
if (shouldContinueTaskHalStatusLoop(state, status)) {
|
||||
taskHalStatusLoopTimer = setTimeout(
|
||||
() => runTaskHalStatusLoopTick(sequence).catch((error) => {
|
||||
stopTaskHalStatusLoop("error", {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
operatorMessage: `task/HAL status loop failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
});
|
||||
}),
|
||||
Number(state.taskHalStatusLoop?.intervalMs || loop.intervalMs || 25),
|
||||
);
|
||||
} else {
|
||||
stopTaskHalStatusLoop(state.runState === "complete" ? "complete" : state.runState, {
|
||||
operatorMessage: state.runState === "complete"
|
||||
? "task/HAL program complete"
|
||||
: `task/HAL status loop ${state.runState}`,
|
||||
});
|
||||
}
|
||||
return status;
|
||||
};
|
||||
|
||||
const stopTaskHalStatusLoop = (reason = "stopped", {
|
||||
error = null,
|
||||
operatorMessage = null,
|
||||
notify = true,
|
||||
} = {}) => {
|
||||
if (taskHalStatusLoopTimer) {
|
||||
clearTimeout(taskHalStatusLoopTimer);
|
||||
taskHalStatusLoopTimer = null;
|
||||
}
|
||||
if (notify && (state.taskHalStatusLoop?.active || state.taskHalStatusLoop?.stopReason !== reason || error)) {
|
||||
dispatch({
|
||||
type: "TASK_HAL_STATUS_LOOP_STOPPED",
|
||||
reason,
|
||||
error,
|
||||
operatorMessage,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const runTaskHalCommandSequence = async (commands, {
|
||||
taskCycles = 1,
|
||||
taskPeriodNs = 10000000,
|
||||
servoPeriodNs = 1000000,
|
||||
taskPeriodNs = deriveTaskHalCyclePeriods(state).taskPeriodNs,
|
||||
servoPeriodNs = deriveTaskHalCyclePeriods(state).servoPeriodNs,
|
||||
operatorMessage = "task/HAL command complete",
|
||||
pendingJogCommand = null,
|
||||
allowFixtureSession = true,
|
||||
@@ -1939,16 +2215,37 @@ function expectedTaskHalProgramPathForState(state = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function deriveTaskHalCyclePeriods(state = {}) {
|
||||
const taskCycleTimeSeconds = Number(state.linuxCncIniConfig?.task?.cycleTimeSeconds);
|
||||
const iniTaskPeriodNs = Number.isFinite(taskCycleTimeSeconds) && taskCycleTimeSeconds > 0
|
||||
? Math.round(taskCycleTimeSeconds * 1_000_000_000)
|
||||
: null;
|
||||
const iniServoPeriodNs = Number(state.linuxCncIniConfig?.emcmot?.servoPeriodNs);
|
||||
|
||||
return {
|
||||
taskPeriodNs: iniTaskPeriodNs || 10000000,
|
||||
servoPeriodNs: Number.isFinite(iniServoPeriodNs) && iniServoPeriodNs > 0
|
||||
? Math.round(iniServoPeriodNs)
|
||||
: 1000000,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCoordinates(value) {
|
||||
return String(value || "").replace(/[^A-Za-z]/g, "").toUpperCase();
|
||||
}
|
||||
|
||||
function applyTaskHalStatusPatch(state, status, operatorMessage) {
|
||||
function applyTaskHalStatusPatch(state, status, operatorMessage, {
|
||||
loopSequence = null,
|
||||
preserveMachine = null,
|
||||
} = {}) {
|
||||
const ui = status?.ui || {};
|
||||
const task = status?.task || {};
|
||||
const motion = status?.motionStatus?.motion || {};
|
||||
const taskState = normalizeTaskHalTaskState(ui.taskState || task.state);
|
||||
const taskMode = normalizeLinuxCncTaskMode(ui.taskMode || task.mode || state.machine.mode);
|
||||
const rawTaskState = normalizeTaskHalTaskState(ui.taskState || task.state);
|
||||
const taskState = preserveMachine?.powerOn && rawTaskState === "estop-reset"
|
||||
? "on"
|
||||
: rawTaskState;
|
||||
const taskMode = normalizeLinuxCncTaskMode(preserveMachine?.mode || ui.taskMode || task.mode || state.machine.mode);
|
||||
const interpState = normalizeTaskHalInterpState(ui.interpState || task.interpState);
|
||||
const activeLine = state.programStartLine + Math.max(Number(ui.activeLine || 1) - 1, 0);
|
||||
const kinsType = resolveTaskHalKinsType(state, status, activeLine);
|
||||
@@ -1958,7 +2255,8 @@ function applyTaskHalStatusPatch(state, status, operatorMessage) {
|
||||
: state.feed.currentVelocity;
|
||||
const paused = interpState === "paused" || motion.paused === true;
|
||||
const aborted = motion.aborted === true;
|
||||
const programComplete = interpState === "idle" && Number(task.nextProgramLine || 0) >= Number(task.openedLineCount || 1);
|
||||
const openedProgramLineCount = Number(task.openedSourceLineCount || task.openedLineCount || 1);
|
||||
const programComplete = interpState === "idle" && Number(task.nextProgramLine || 0) >= openedProgramLineCount;
|
||||
const runState = aborted
|
||||
? "stopped"
|
||||
: paused
|
||||
@@ -1972,6 +2270,13 @@ function applyTaskHalStatusPatch(state, status, operatorMessage) {
|
||||
: state.runState === "jogging"
|
||||
? "jogging"
|
||||
: "idle";
|
||||
const runtimeFeedback = createTaskHalRuntimeFeedback(state, status, axisPose, activeLine);
|
||||
const loopActive = state.taskHalStatusLoop?.active === true
|
||||
&& loopSequence !== null
|
||||
&& Number(state.taskHalStatusLoop.sequence) === Number(loopSequence)
|
||||
&& (runState === "running" || runState === "mdi");
|
||||
const nextTickCount = loopActive ? Number(state.taskHalStatusLoop.tickCount || 0) + 1 : Number(state.taskHalStatusLoop?.tickCount || 0);
|
||||
const feedbackHistory = [runtimeFeedback, ...(state.programRuntimeFeedbackHistory || [])].slice(0, 100);
|
||||
|
||||
return {
|
||||
taskHalStatus: status,
|
||||
@@ -1982,9 +2287,7 @@ function applyTaskHalStatusPatch(state, status, operatorMessage) {
|
||||
axisPose,
|
||||
kinsType,
|
||||
rtcpState: rtcpStateFromKinsType(kinsType),
|
||||
programExecutionSourceMode: state.programExecution
|
||||
? state.programExecutionSourceMode
|
||||
: "linuxcnc-task-motion-hal-wasm",
|
||||
programExecutionSourceMode: "linuxcnc-task-motion-hal-wasm",
|
||||
machine: {
|
||||
...state.machine,
|
||||
powerOn: taskState === "on",
|
||||
@@ -1994,17 +2297,42 @@ function applyTaskHalStatusPatch(state, status, operatorMessage) {
|
||||
interpState,
|
||||
interpResumeState: paused ? state.machine.interpResumeState || "reading" : interpState,
|
||||
taskPaused: paused,
|
||||
allHomed: Boolean(preserveMachine?.allHomed ?? state.machine.allHomed),
|
||||
},
|
||||
runState,
|
||||
taskHalStatusLoop: loopSequence === null
|
||||
? state.taskHalStatusLoop
|
||||
: {
|
||||
...state.taskHalStatusLoop,
|
||||
active: loopActive,
|
||||
tickCount: nextTickCount,
|
||||
lastStatusAt: new Date().toISOString(),
|
||||
stopReason: loopActive ? null : runState,
|
||||
},
|
||||
feed: {
|
||||
...state.feed,
|
||||
currentVelocity,
|
||||
},
|
||||
programRuntimeFeedback: createTaskHalRuntimeFeedback(state, status, axisPose, activeLine),
|
||||
programRuntimeFeedback: runtimeFeedback,
|
||||
programRuntimeFeedbackHistory: feedbackHistory,
|
||||
operatorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
function shouldContinueTaskHalStatusLoop(state = {}, status = {}) {
|
||||
const ui = status?.ui || {};
|
||||
const task = status?.task || {};
|
||||
const motion = status?.motionStatus?.motion || {};
|
||||
const interpState = normalizeTaskHalInterpState(ui.interpState || task.interpState);
|
||||
const taskMode = normalizeLinuxCncTaskMode(ui.taskMode || task.mode || state.machine?.mode);
|
||||
const aborted = motion.aborted === true;
|
||||
const paused = interpState === "paused" || motion.paused === true;
|
||||
const openedProgramLineCount = Number(task.openedSourceLineCount || task.openedLineCount || 1);
|
||||
const complete = interpState === "idle"
|
||||
&& Number(task.nextProgramLine || 0) >= openedProgramLineCount;
|
||||
return !aborted && !paused && !complete && (interpState === "reading" || taskMode === "mdi");
|
||||
}
|
||||
|
||||
function resolveTaskHalKinsType(state, status, activeLine) {
|
||||
const ui = status?.ui || {};
|
||||
const numeric = Number(ui.switchkinsType);
|
||||
@@ -2095,6 +2423,8 @@ function wouldResetNonZeroPoseToLocalZero(currentPose = {}, nextPose = {}) {
|
||||
function createTaskHalRuntimeFeedback(state, status, axisPose, activeLine) {
|
||||
const ui = status?.ui || {};
|
||||
const motion = status?.motionStatus?.motion || {};
|
||||
const halProgramLine = Number(status?.halSnapshot?.pins?.["motion.program-line"]?.value || 0);
|
||||
const motionProgramLine = Number(motion.programLine || 0);
|
||||
return {
|
||||
apiName: "web-rtcp-5axis-program-runtime-feedback",
|
||||
sourceMode: "linuxcnc-task-motion-hal-wasm",
|
||||
@@ -2102,6 +2432,10 @@ function createTaskHalRuntimeFeedback(state, status, axisPose, activeLine) {
|
||||
sampleIndex: Number(ui.servoCycle || 0),
|
||||
motionIndex: Math.max(Number(ui.activeLine || 1) - 1, 0),
|
||||
line: activeLine,
|
||||
motionProgramLine,
|
||||
halProgramLine,
|
||||
activeLineSource: ui.activeLineSource || (motionProgramLine > 0 ? "motion-status" : halProgramLine > 0 ? "hal-pin" : "fallback"),
|
||||
activeLineHalSynced: motionProgramLine > 0 && halProgramLine > 0 && motionProgramLine === halProgramLine,
|
||||
type: Number(motion.motionType || 0) === 3 ? "JOG" : "TASK_MOTION",
|
||||
timeSeconds: Number(ui.taskCycle || 0) * 0.01,
|
||||
axisPose,
|
||||
@@ -2452,6 +2786,10 @@ function nextProgramRuntimeSamplePlayback(state, step) {
|
||||
const sample = samples[sampleIndex];
|
||||
const motionIndex = clampMotionIndex(state, sample.motionIndex);
|
||||
const motion = state.programExecution?.motion?.[motionIndex] || null;
|
||||
const sampleWithUnits = {
|
||||
...sample,
|
||||
linearUnits: sample.linearUnits || motion?.linearUnits || state.profile.traj?.linearUnits,
|
||||
};
|
||||
const segment = timing?.segments?.[motionIndex] || null;
|
||||
const kinsType = kinsTypeFromProgramMotion(state, motion) || state.kinsType;
|
||||
const elapsedSeconds = Number(sample.timeSeconds) || Number(segment?.elapsedSeconds) || 0;
|
||||
@@ -2461,7 +2799,7 @@ function nextProgramRuntimeSamplePlayback(state, step) {
|
||||
|| 0;
|
||||
const runtimeFeedback = createProgramRuntimeFeedbackFromSample({
|
||||
state,
|
||||
sample,
|
||||
sample: sampleWithUnits,
|
||||
sampleIndex,
|
||||
motion,
|
||||
motionIndex,
|
||||
@@ -2473,7 +2811,7 @@ function nextProgramRuntimeSamplePlayback(state, step) {
|
||||
motionIndex,
|
||||
sampleIndex,
|
||||
activeLine: sample.line || motion?.line || state.activeLine,
|
||||
axisPose: axisPoseFromRuntimeSample(sample, motion, state.axisPose),
|
||||
axisPose: axisPoseFromRuntimeSample(sampleWithUnits, motion, state.axisPose),
|
||||
kinsType,
|
||||
rtcpState: rtcpStateFromKinsType(kinsType),
|
||||
timing: {
|
||||
@@ -2507,9 +2845,13 @@ function nextProgramRuntimeSamplePlayback(state, step) {
|
||||
function createInitialProgramRuntimeFeedback({ state, timing, motion, timingSnapshot }) {
|
||||
const firstSample = timing?.samples?.[0] || null;
|
||||
if (firstSample) {
|
||||
const sampleWithUnits = {
|
||||
...firstSample,
|
||||
linearUnits: firstSample.linearUnits || motion?.linearUnits || state.profile.traj?.linearUnits,
|
||||
};
|
||||
return createProgramRuntimeFeedbackFromSample({
|
||||
state,
|
||||
sample: firstSample,
|
||||
sample: sampleWithUnits,
|
||||
sampleIndex: 0,
|
||||
motion,
|
||||
motionIndex: clampMotionIndex(state, firstSample.motionIndex),
|
||||
@@ -2537,11 +2879,13 @@ function clampMotionIndex(state, motionIndex) {
|
||||
}
|
||||
|
||||
function buildTimingForState(state, execution) {
|
||||
if (execution?.plannerTiming?.plannerRuntimeReady === true) {
|
||||
const motion = execution?.motion || [];
|
||||
const requiresFeedModeTiming = motion.some((event) => event?.feedMode === "inverse-time");
|
||||
if (!requiresFeedModeTiming && execution?.plannerTiming?.plannerRuntimeReady === true) {
|
||||
return execution.plannerTiming;
|
||||
}
|
||||
return buildProgramExecutionTiming({
|
||||
motion: execution?.motion || [],
|
||||
motion,
|
||||
profile: state.profile,
|
||||
feedOverride: state.feed.feedOverride,
|
||||
rapidOverride: state.feed.rapidOverride,
|
||||
@@ -2713,6 +3057,7 @@ function createProgramRuntimeFeedbackFromSample({
|
||||
motionIndex,
|
||||
line: sample?.line || motion?.line || null,
|
||||
type: sample?.type || motion?.type || null,
|
||||
linearUnits: sample?.linearUnits || motion?.linearUnits || state.profile.traj?.linearUnits || "mm",
|
||||
timeSeconds: elapsedSeconds,
|
||||
axisPose,
|
||||
currentVelocityMmPerMin: currentVelocity,
|
||||
@@ -2750,6 +3095,7 @@ function createProgramRuntimeFeedbackFromMotion({
|
||||
motionIndex,
|
||||
line: motion?.line || null,
|
||||
type: motion?.type || null,
|
||||
linearUnits: motion?.linearUnits || state.profile.traj?.linearUnits || "mm",
|
||||
timeSeconds: Number(timing?.elapsedSeconds) || 0,
|
||||
axisPose,
|
||||
currentVelocityMmPerMin: Number(timing?.currentVelocity) || 0,
|
||||
|
||||
Reference in New Issue
Block a user