feat: sync latest run execution updates

This commit is contained in:
2026-06-22 21:47:16 -04:00
parent 0b1aad39e1
commit 8d3177cb73
92 changed files with 22837 additions and 246 deletions

View File

@@ -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,