Implement AXIS parity UI and execution baseline
This commit is contained in:
88
web/react/src/store.ts
Normal file
88
web/react/src/store.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { Capabilities, MessageEnvelope, SimSnapshotV1 } from "./protocol";
|
||||
|
||||
export type SessionStatus = "disconnected" | "handshaking" | "attaching" | "loading" | "ready" | "running" | "paused" | "recovering" | "blocked" | "error";
|
||||
|
||||
export type FrontendState = {
|
||||
status: SessionStatus;
|
||||
contextId: string;
|
||||
sessionId: string;
|
||||
tick: number;
|
||||
lastSeq: number;
|
||||
capabilities: Capabilities | null;
|
||||
snapshot: SimSnapshotV1 | null;
|
||||
pending: string[];
|
||||
error: { code: string; message?: string } | null;
|
||||
alarms: Array<Record<string, unknown>>;
|
||||
task: Record<string, unknown> | null;
|
||||
program: Record<string, unknown> | null;
|
||||
devices: Record<string, unknown> | null;
|
||||
deviceIo: Record<string, unknown> | null;
|
||||
hal: Record<string, unknown> | null;
|
||||
plc: Record<string, unknown> | null;
|
||||
python: Record<string, unknown> | null;
|
||||
ioAck: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
export const INITIAL_STATE: FrontendState = {
|
||||
status: "disconnected",
|
||||
contextId: "linuxcnc-2.10.0-pre0-wasm-m1-py-c1",
|
||||
sessionId: "",
|
||||
tick: 0,
|
||||
lastSeq: 0,
|
||||
capabilities: null,
|
||||
snapshot: null,
|
||||
pending: [],
|
||||
error: null,
|
||||
alarms: [],
|
||||
task: null,
|
||||
program: null,
|
||||
devices: null,
|
||||
deviceIo: null,
|
||||
hal: null,
|
||||
plc: null,
|
||||
python: null,
|
||||
ioAck: null,
|
||||
};
|
||||
|
||||
function payload<T>(message: MessageEnvelope): T {
|
||||
return message.payload as T;
|
||||
}
|
||||
|
||||
function projection(result: Record<string, unknown>, message: MessageEnvelope, previous: FrontendState) {
|
||||
const rawMotion = (result.motion ?? previous.snapshot?.motion ?? {}) as Record<string, unknown>;
|
||||
const motionState = (rawMotion.state ?? rawMotion) as Record<string, unknown>;
|
||||
const axesObject = motionState.axes as Record<string, unknown> | undefined;
|
||||
const axes = Array.isArray(motionState.axes)
|
||||
? motionState.axes as number[]
|
||||
: [axesObject?.x, axesObject?.y, axesObject?.z].map((value) => typeof value === "number" ? value : 0);
|
||||
const joints = Array.isArray(motionState.joints) ? motionState.joints as number[] : [];
|
||||
const snapshot = { simulation_tick: message.simulation_tick ?? previous.tick, state_hash: String(result.state_hash ?? previous.snapshot?.state_hash ?? ""), task: result.task as SimSnapshotV1["task"], motion: { axes, joints, velocity: Array.isArray(motionState.velocity) ? motionState.velocity as number[] : [] } } satisfies SimSnapshotV1;
|
||||
return { snapshot, task: (result.task as Record<string, unknown>) ?? previous.task, program: (result.program as Record<string, unknown>) ?? previous.program, devices: (result.devices as Record<string, unknown>) ?? previous.devices, deviceIo: (result.device_io as Record<string, unknown>) ?? previous.deviceIo, hal: (result.hal_runtime as Record<string, unknown>) ?? previous.hal, plc: (result.plc as Record<string, unknown>) ?? previous.plc, python: (result.python as Record<string, unknown>) ?? previous.python, ioAck: (result.io_ack as Record<string, unknown>) ?? previous.ioAck };
|
||||
}
|
||||
|
||||
export function reduceFrontendState(state: FrontendState, message: MessageEnvelope): FrontendState {
|
||||
const tick = message.simulation_tick ?? state.tick;
|
||||
const base = { ...state, contextId: message.context_id, sessionId: message.session_id, lastSeq: Math.max(state.lastSeq, message.seq), tick, error: null };
|
||||
switch (message.kind) {
|
||||
case "CAPABILITIES": return { ...base, status: "handshaking", capabilities: payload<Capabilities>(message) };
|
||||
case "SESSION_READY": return { ...base, status: "ready" };
|
||||
case "RECOVERY_STATUS": return { ...base, status: payload<{ status?: string }>(message).status === "verified" ? "ready" : "recovering" };
|
||||
case "STATE_SNAPSHOT": {
|
||||
const value = payload<Record<string, unknown>>(message);
|
||||
const projected = projection(value, message, state);
|
||||
return { ...base, status: "ready", ...projected };
|
||||
}
|
||||
case "COMMAND_RESULT": {
|
||||
const result = payload<Record<string, unknown>>(message);
|
||||
const task = result.task as { state?: string } | undefined;
|
||||
const status = result.status === "blocked" ? "blocked" : task?.state === "RUNNING" ? "running" : state.status === "running" ? "running" : "ready";
|
||||
return { ...base, status, ...projection(result, message, state) };
|
||||
}
|
||||
case "ALARM": return { ...base, status: "blocked", alarms: [...state.alarms, payload<Record<string, unknown>>(message)] };
|
||||
case "ERROR": {
|
||||
const value = payload<{ code?: string; message_key?: string }>(message);
|
||||
return { ...base, status: "error", error: { code: value.code ?? "worker_error", message: JSON.stringify(value) } };
|
||||
}
|
||||
default: return base;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user