完善云端RUN执行反馈

This commit is contained in:
2026-06-23 03:59:47 -04:00
parent bb71613051
commit 37604fa5b3
33 changed files with 3724 additions and 534 deletions

View File

@@ -172,6 +172,7 @@ const initialState = {
programExecutionSampleIndex: 0,
programRuntimeFeedback: null,
programRuntimeFeedbackHistory: [],
programLineExecution: {},
taskHalRuntime: null,
taskHalRuntimeReadiness: null,
taskHalStatus: null,
@@ -327,6 +328,25 @@ export function createSimulationStore(seed = {}) {
scheduleAsyncKinematicsRefresh();
};
const waitForStatePredicate = (predicate, timeoutMs = 10000) => {
if (predicate(state)) return Promise.resolve(state);
return new Promise((resolve, reject) => {
const startedAt = Date.now();
const listener = (nextState) => {
if (predicate(nextState)) {
listeners.delete(listener);
resolve(nextState);
return;
}
if (Date.now() - startedAt > timeoutMs) {
listeners.delete(listener);
reject(new Error("timed out waiting for store state"));
}
};
listeners.add(listener);
});
};
const dispatch = (action) => {
switch (action.type) {
case "BOOT_READY":
@@ -568,6 +588,10 @@ export function createSimulationStore(seed = {}) {
programExecutionMotionIndex: 0,
programExecutionSampleIndex: 0,
programRuntimeFeedback: firstFeedback,
programLineExecution: createProgramLineExecutionPatch(state.programLineExecution, firstFeedback, {
status: "ready",
source: execution.sourceMode,
}),
programElapsedSeconds: firstTiming.elapsedSeconds,
programRemainingSeconds: firstTiming.remainingSeconds,
interpreterExecutionPending: false,
@@ -603,6 +627,7 @@ export function createSimulationStore(seed = {}) {
programExecutionSourceMode: "fixture-line-playback",
programExecutionSampleIndex: 0,
programRuntimeFeedback: null,
programLineExecution: {},
interpreterExecutionPending: false,
operatorMessage: `LinuxCNC interpreter blocked: ${action.error}`,
});
@@ -815,6 +840,7 @@ export function createSimulationStore(seed = {}) {
axisPose: initialAxisPose,
runState: "idle",
programRuntimeFeedback: null,
programLineExecution: {},
preview: {
...state.preview,
pathPoints: Math.max(loadedProgram.programLines.length, 1),
@@ -909,6 +935,14 @@ export function createSimulationStore(seed = {}) {
case "RUN_FULL_BOUNDARY_AUDIT_REQUEST":
runFullBoundaryAudit(action.options || {}).catch(() => {});
break;
case "RUN_READY":
runReadySequence().catch((error) => {
dispatch({
type: "TASK_HAL_COMMAND_FAILED",
error: error instanceof Error ? error.message : String(error),
});
});
break;
case "SET_FRAME_SOURCE":
setState({
sourceMode: action.sourceMode,
@@ -1138,6 +1172,7 @@ export function createSimulationStore(seed = {}) {
axisPose: initialAxisPose,
runState: "idle",
programRuntimeFeedback: null,
programLineExecution: {},
preview: {
...state.preview,
pathPoints: Math.max(loadedProgram.programLines.length, 1),
@@ -1182,6 +1217,10 @@ export function createSimulationStore(seed = {}) {
programExecutionMotionIndex: playback.motionIndex,
programExecutionSampleIndex: playback.sampleIndex,
programRuntimeFeedback: playback.runtimeFeedback,
programLineExecution: createProgramLineExecutionPatch(state.programLineExecution, playback.runtimeFeedback, {
status: playback.complete ? "done" : "running",
source: playback.runtimeFeedback?.sourceMode,
}),
programElapsedSeconds: playback.timing.elapsedSeconds,
programRemainingSeconds: playback.timing.remainingSeconds,
feed: {
@@ -1375,7 +1414,13 @@ export function createSimulationStore(seed = {}) {
if (state.taskHalRuntime?.loaded) {
runTaskHalCommandSequence([
{ type: "EMC_JOINT_HOME", joint: -1 },
], { operatorMessage: "task/HAL machine homed" }).catch(() => {});
], {
operatorMessage: "task/HAL machine homed",
preserveMachine: {
...state.machine,
allHomed: true,
},
}).catch(() => {});
}
setState({
machine: {
@@ -1493,6 +1538,7 @@ export function createSimulationStore(seed = {}) {
programExecutionMotionIndex: 0,
programExecutionSampleIndex: 0,
programRuntimeFeedback: null,
programLineExecution: {},
axisPose: initialAxisPose,
preview: { ...state.preview, pathPoints: Math.max(state.programLines.length, 1) },
operatorMessage: "program reloaded",
@@ -1758,6 +1804,57 @@ export function createSimulationStore(seed = {}) {
return status;
};
const runReadySequence = async () => {
if (!state.machineFileStaging?.selectedGcodeSourceRel) {
const sourceRel = defaultLinuxCncGcodeSourceForState(state)?.sourceRel;
if (sourceRel) {
dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel });
await waitForStatePredicate((nextState) => nextState.machineFileStaging?.selectedGcodeSourceRel === sourceRel);
}
}
if (state.taskHalRuntime?.loaded) {
await initializeTaskHalSession({ openProgram: true });
await runTaskHalCommandSequence([
{ type: "EMC_TASK_SET_STATE", state: "ON" },
{ type: "EMC_JOINT_HOME", joint: -1 },
{ type: "EMC_TASK_SET_MODE", mode: "AUTO" },
], {
taskCycles: 3,
operatorMessage: "RUN ready: power on, homed, auto mode",
allowFixtureSession: false,
preserveMachine: {
powerOn: true,
estopActive: false,
taskState: "on",
mode: "auto",
allHomed: true,
},
});
const tcpKinsType = state.profile.kinematicsParameters.switchkinsTypes.find((type) => type.value === 1)?.webKinsType || "tcp-xyzac";
dispatch({ type: "SET_KINS_TYPE", kinsType: tcpKinsType });
return state;
}
const tcpKinsType = state.profile.kinematicsParameters.switchkinsTypes.find((type) => type.value === 1)?.webKinsType || "tcp-xyzac";
setState({
machine: {
...state.machine,
powerOn: true,
estopActive: false,
taskState: "on",
mode: "auto",
allHomed: true,
interpState: "idle",
interpResumeState: "idle",
taskPaused: false,
},
runState: "idle",
kinsType: tcpKinsType,
rtcpState: "on",
operatorMessage: "RUN ready: power on, homed, auto mode",
});
return state;
};
const loadTaskHalMotionPlanForSession = async (session = state.taskHalSession) => {
if (!state.taskHalRuntime?.loaded || typeof state.taskHalRuntime.loadProgramMotionPlan !== "function") {
return null;
@@ -1882,6 +1979,7 @@ export function createSimulationStore(seed = {}) {
operatorMessage = "task/HAL command complete",
pendingJogCommand = null,
allowFixtureSession = true,
preserveMachine = null,
} = {}) => {
if (!state.taskHalRuntime?.loaded) {
throw new Error("LinuxCNC task/HAL runtime not attached");
@@ -1910,7 +2008,7 @@ export function createSimulationStore(seed = {}) {
if (state.taskHalExecutionSequence !== sequence) {
return status;
}
dispatch({ type: "TASK_HAL_STATUS_APPLIED", status, operatorMessage });
dispatch({ type: "TASK_HAL_STATUS_APPLIED", status, operatorMessage, preserveMachine });
return status;
} catch (error) {
dispatch({
@@ -2277,6 +2375,11 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
&& (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);
const lineStatus = runState === "complete"
? "done"
: runState === "running" || runState === "mdi"
? "running"
: runState;
return {
taskHalStatus: status,
@@ -2315,6 +2418,10 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
},
programRuntimeFeedback: runtimeFeedback,
programRuntimeFeedbackHistory: feedbackHistory,
programLineExecution: createProgramLineExecutionPatch(state.programLineExecution, runtimeFeedback, {
status: lineStatus,
source: "linuxcnc-task-motion-hal-wasm",
}),
operatorMessage,
};
}
@@ -2451,6 +2558,41 @@ function createTaskHalRuntimeFeedback(state, status, axisPose, activeLine) {
};
}
function createProgramLineExecutionPatch(previous = {}, feedback = null, {
status = "running",
source = null,
} = {}) {
const line = Number(feedback?.line || 0);
if (!Number.isFinite(line) || line <= 0) {
return previous || {};
}
const axisPose = feedback.axisPose || {};
return {
...(previous || {}),
[line]: {
status,
source: source || feedback.sourceMode || "unknown",
line,
feed: Number(feedback.currentVelocityMmPerMin || 0),
requestedFeed: Number(feedback.requestedVelocityMmPerMin || 0),
feedMode: feedback.feedMode || null,
axisPose: pickExecutionAxes(axisPose),
taskCycle: Number(feedback.taskCycle || 0),
servoCycle: Number(feedback.cycle || 0),
sampleIndex: Number(feedback.sampleIndex || 0),
motionIndex: Number(feedback.motionIndex || 0),
updatedAt: new Date().toISOString(),
},
};
}
function pickExecutionAxes(axisPose = {}) {
return Object.fromEntries(["x", "y", "z", "a", "b", "c"].map((axis) => [
axis,
Number(axisPose[axis] || 0),
]));
}
function normalizeTaskHalTaskState(value) {
const state = String(value || "").toLowerCase().replaceAll("_", "-");
if (state === "on") return "on";