Use native LinuxCNC homing and consolidate web panel status

This commit is contained in:
2026-09-09 12:28:02 +08:00
parent eb5c58cdb3
commit 0be2ee8ce1
35 changed files with 881 additions and 350 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
import type { SourceStatus } from "./protocol";
export function MachineStatus({ status, commandStatus }: { status?: SourceStatus; commandStatus: string }) {
return <section className="machine-status" aria-label="LinuxCNC status">
<div className="machine-status-dimensions">
<span>Task mode <output>{status?.task.mode ?? "PENDING"}</output></span>
<span>Task state <output>{status?.task.state ?? "PENDING"}</output></span>
<span>Interpreter <output>{status?.task.interpState ?? "PENDING"}</output></span>
<span>Motion mode <output>{status?.traj.mode ?? "PENDING"}</output></span>
<span>Motion type <output>{status?.traj.motion_type ?? "-"}</output></span>
<span>Homing <output>{status ? status.homing_active ? "ACTIVE" : "IDLE" : "PENDING"}</output></span>
<span>Machine sequence <output>{status ? status.joint.length > 0 && status.joint.every((joint) => joint.homed) ? "HOMED" : "NOT HOMED" : "WASM PENDING"}</output></span>
</div>
<span className="command-status" role="status" aria-live="polite" title={commandStatus}>{commandStatus}</span>
</section>;
}

View File

@@ -18,6 +18,7 @@ export type MessageEnvelope<TPayload = unknown> = {
};
export type MotionSnapshot = {
homing?: boolean;
stepping?: boolean;
axes?: number[];
joints?: number[];
@@ -42,6 +43,18 @@ export type MotionSnapshot = {
max_velocity?: number;
};
export type HomeJointStatus = {
joint: number; homing: boolean; homed: boolean; home_state: number; home_state_name: string;
position: number; motor_position: number; motor_offset: number; velocity: number; home_sequence: number;
};
export type SourceStatus = {
task: { mode: string; state: string; interpState: string };
traj: { mode: string; motion_type: number };
homing_active: boolean;
joint: HomeJointStatus[];
homing_history: Array<{ tick: number; joint: HomeJointStatus[] }>;
};
export type TrajectorySnapshot = {
queue?: Array<{ id?: number; source_line?: number | null; start?: Record<string, number>; end?: Record<string, number> }>;
active?: { id?: number; source_line?: number | null; start?: Record<string, number>; end?: Record<string, number> } | null;
@@ -49,6 +62,7 @@ export type TrajectorySnapshot = {
};
export type SimSnapshotV1 = {
source_status?: SourceStatus;
schema?: string;
snapshot_id?: string;
base_snapshot_id?: string;
@@ -114,6 +128,7 @@ export type XyzacCapabilitySnapshot = {
};
export type PanelSnapshotV1 = {
source_status?: SourceStatus;
schema: "axis-panel-snapshot.v1";
context_id: string;
session_id: string;

View File

@@ -83,6 +83,12 @@ function projection(result: Record<string, unknown>, message: MessageEnvelope, p
} : previous.snapshot.visual;
const nextSnapshot = {
...previous.snapshot,
source_status: (result.source_status as SimSnapshotV1["source_status"]) ?? (previous.snapshot.source_status ? {
...previous.snapshot.source_status,
task: { ...previous.snapshot.source_status.task,
mode: String((result.task as Record<string, unknown>)?.mode ?? previous.snapshot.source_status.task.mode),
interpState: (result.axis_state as AxisStateView)?.source_interp_state ?? previous.snapshot.source_status.task.interpState },
} : undefined),
simulation_tick: message.simulation_tick ?? delta.tick ?? previous.snapshot.simulation_tick,
state_hash: String(result.state_hash ?? previous.snapshot.state_hash),
task: (result.task as SimSnapshotV1["task"]) ?? previous.snapshot.task,
@@ -147,6 +153,7 @@ function projection(result: Record<string, unknown>, message: MessageEnvelope, p
const axes = [pose.x, pose.y, pose.z, pose.a, pose.c].map((value) => Number.isFinite(value) ? value : 0);
const snapshot = {
schema: panel.schema,
source_status: panel.source_status,
simulation_tick: panel.simulation_tick,
state_hash: panel.state_hash,
task: panel.task as SimSnapshotV1["task"],

View File

@@ -73,7 +73,7 @@ button { color: inherit; }
.machine-sequence-heading { display: grid; gap: 2px; min-width: 148px; }
.machine-sequence-heading strong { font-size: 12px; }
.machine-sequence-heading span { color: #555; font: 10px ui-monospace, monospace; }
.machine-sequence-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; min-width: 0; }
.machine-sequence-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; min-width: 0; padding: 8px; border-bottom: 1px solid #aaa; }
.machine-sequence-actions .axis-text-button { min-height: 28px; }
.machine-sequence-actions .axis-text-button:first-child { border-color: #9b4a4a; }
@@ -245,14 +245,6 @@ output { font: 12px ui-monospace, monospace; text-align: right; }
.visually-hidden-file-input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
.axis-layer-controls { display: flex; flex-wrap: wrap; gap: 8px 12px; margin-top: 4px; color: #4d4d4d; font-size: 11px; }
.axis-layer-controls input { margin: 0; }
.axis-info-bar { display: flex; align-items: stretch; width: 100%; min-width: 0; min-height: 27px; margin-top: auto; border-top: 1px solid #777; background: #c4c4c4; }
.axis-info-bar > span { display: flex; align-items: center; min-width: 0; min-height: 25px; padding: 2px 9px; overflow: hidden; border-right: 1px solid #999; font: 12px ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
.task-state { min-width: 92px; background: #ececec; font-weight: 700 !important; }
.task-state.running { color: #145d1c; }
.task-state.blocked, .task-state.error { color: #9b111e; background: #f2d6d6; }
.tool-state { min-width: 0; flex: 1; }
.position-state { min-width: 230px; }
.footer-status { min-width: 180px; color: #505050; }
@media (max-width: 900px) {
.xyzac-control-band { grid-template-columns: 1fr; }
@@ -287,6 +279,7 @@ output { font: 12px ui-monospace, monospace; text-align: right; }
.dro-mode-row { font-size: 9px; }
.dro-row output { font-size: 13px; }
.dro-footer { flex-direction: column; gap: 3px; }
.axis-info-bar { flex-wrap: wrap; }
.position-state, .footer-status { min-width: 0; flex: 1; }
}
.machine-status { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px; padding: 6px 8px; border-bottom: 1px solid #999; min-width: 0; }
.machine-status-dimensions { display: flex; flex: 1 1 auto; flex-wrap: wrap; gap: 6px 18px; }
.machine-status-dimensions output { font-weight: bold; margin-left: 5px; }