mirror of
https://github.com/jhbruhn/respira.git
synced 2026-04-27 17:45:45 +00:00
Implements automatic stitch rollback on thread errors (upper thread: -6, lower thread: -2, sewing start: -21) and manual step controls to adjust stitch position when machine is paused/stopped/interrupted. Adds UI with step buttons (-100/-10/-1/+1/+10/+100), thread start jump, and current stitch reset. Uses existing NEEDLE_MODE_INSTRUCTIONS (0x0709) BLE command. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
264 lines
7.9 KiB
TypeScript
264 lines
7.9 KiB
TypeScript
import { MachineStatus } from "../types/machine";
|
|
|
|
/**
|
|
* Machine state categories for safety logic
|
|
*/
|
|
export const MachineStateCategory = {
|
|
IDLE: "idle",
|
|
ACTIVE: "active",
|
|
WAITING: "waiting",
|
|
COMPLETE: "complete",
|
|
INTERRUPTED: "interrupted",
|
|
ERROR: "error",
|
|
} as const;
|
|
|
|
export type MachineStateCategoryType =
|
|
(typeof MachineStateCategory)[keyof typeof MachineStateCategory];
|
|
|
|
/**
|
|
* Categorize a machine status into a semantic safety category
|
|
*/
|
|
export function getMachineStateCategory(
|
|
status: MachineStatus,
|
|
): MachineStateCategoryType {
|
|
switch (status) {
|
|
// IDLE states - safe to perform any action
|
|
case MachineStatus.IDLE:
|
|
case MachineStatus.SEWING_WAIT:
|
|
case MachineStatus.Initial:
|
|
case MachineStatus.LowerThread:
|
|
return MachineStateCategory.IDLE;
|
|
|
|
// ACTIVE states - operation in progress, dangerous to interrupt
|
|
case MachineStatus.SEWING:
|
|
case MachineStatus.MASK_TRACING:
|
|
case MachineStatus.SEWING_DATA_RECEIVE:
|
|
case MachineStatus.HOOP_AVOIDANCEING:
|
|
return MachineStateCategory.ACTIVE;
|
|
|
|
// WAITING states - waiting for user/machine action
|
|
case MachineStatus.COLOR_CHANGE_WAIT:
|
|
case MachineStatus.MASK_TRACE_LOCK_WAIT:
|
|
case MachineStatus.HOOP_AVOIDANCE:
|
|
return MachineStateCategory.WAITING;
|
|
|
|
// COMPLETE states - operation finished
|
|
case MachineStatus.SEWING_COMPLETE:
|
|
case MachineStatus.MASK_TRACE_COMPLETE:
|
|
case MachineStatus.RL_RECEIVED:
|
|
return MachineStateCategory.COMPLETE;
|
|
|
|
// INTERRUPTED states - operation paused/stopped
|
|
case MachineStatus.PAUSE:
|
|
case MachineStatus.STOP:
|
|
case MachineStatus.SEWING_INTERRUPTION:
|
|
return MachineStateCategory.INTERRUPTED;
|
|
|
|
// ERROR/UNKNOWN states
|
|
case MachineStatus.None:
|
|
case MachineStatus.TryConnecting:
|
|
case MachineStatus.RL_RECEIVING:
|
|
default:
|
|
return MachineStateCategory.ERROR;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines if the pattern can be safely deleted in the current state.
|
|
* Prevents deletion during active operations (SEWING, MASK_TRACING, etc.)
|
|
*/
|
|
export function canDeletePattern(status: MachineStatus): boolean {
|
|
const category = getMachineStateCategory(status);
|
|
// Can delete in IDLE, WAITING, or COMPLETE states, never during ACTIVE operations
|
|
return (
|
|
category === MachineStateCategory.IDLE ||
|
|
category === MachineStateCategory.WAITING ||
|
|
category === MachineStateCategory.COMPLETE
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Determines if a pattern can be safely uploaded in the current state.
|
|
* Only allow uploads when machine is idle or in a complete state.
|
|
*/
|
|
export function canUploadPattern(status: MachineStatus): boolean {
|
|
const category = getMachineStateCategory(status);
|
|
// Can upload in IDLE or COMPLETE states (includes MASK_TRACE_COMPLETE)
|
|
return (
|
|
category === MachineStateCategory.IDLE ||
|
|
category === MachineStateCategory.COMPLETE
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Determines if sewing can be started in the current state.
|
|
* Allows starting from ready state or resuming from interrupted states.
|
|
*/
|
|
export function canStartSewing(status: MachineStatus): boolean {
|
|
// Only in specific ready states
|
|
return (
|
|
status === MachineStatus.SEWING_WAIT ||
|
|
status === MachineStatus.MASK_TRACE_COMPLETE ||
|
|
status === MachineStatus.PAUSE ||
|
|
status === MachineStatus.STOP ||
|
|
status === MachineStatus.SEWING_INTERRUPTION
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Determines if mask trace can be started in the current state.
|
|
* When hasSewingProgress is true, SEWING_WAIT means the machine is paused mid-sew,
|
|
* not waiting for initial start - mask trace should not be offered.
|
|
*/
|
|
export function canStartMaskTrace(
|
|
status: MachineStatus,
|
|
hasSewingProgress = false,
|
|
): boolean {
|
|
if (status === MachineStatus.IDLE || status === MachineStatus.MASK_TRACE_COMPLETE) {
|
|
return true;
|
|
}
|
|
// Only allow mask trace in SEWING_WAIT if sewing hasn't started yet
|
|
if (status === MachineStatus.SEWING_WAIT && !hasSewingProgress) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determines if sewing can be resumed in the current state.
|
|
* Only for interrupted operations (PAUSE, STOP, SEWING_INTERRUPTION).
|
|
*/
|
|
export function canResumeSewing(status: MachineStatus): boolean {
|
|
// Only in interrupted states
|
|
const category = getMachineStateCategory(status);
|
|
return category === MachineStateCategory.INTERRUPTED;
|
|
}
|
|
|
|
/**
|
|
* Determines if the step control UI should be shown.
|
|
* Allows manual stitch position adjustment when machine is paused/stopped/interrupted,
|
|
* or in SEWING_WAIT if sewing has already started (currentStitch > 0).
|
|
*/
|
|
export function canShowStepControl(
|
|
status: MachineStatus,
|
|
hasSewingProgress: boolean,
|
|
): boolean {
|
|
if (
|
|
status === MachineStatus.PAUSE ||
|
|
status === MachineStatus.STOP ||
|
|
status === MachineStatus.SEWING_INTERRUPTION
|
|
) {
|
|
return true;
|
|
}
|
|
// SEWING_WAIT is also the paused state; show controls only if sewing already started
|
|
if (status === MachineStatus.SEWING_WAIT && hasSewingProgress) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determines if disconnect should show a confirmation dialog.
|
|
* Confirms if disconnecting during active operation or while waiting.
|
|
*/
|
|
export function shouldConfirmDisconnect(status: MachineStatus): boolean {
|
|
const category = getMachineStateCategory(status);
|
|
// Confirm if disconnecting during active operation or waiting for action
|
|
return (
|
|
category === MachineStateCategory.ACTIVE ||
|
|
category === MachineStateCategory.WAITING
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Visual information for a machine state
|
|
*/
|
|
export interface StateVisualInfo {
|
|
color: string;
|
|
iconName:
|
|
| "ready"
|
|
| "active"
|
|
| "waiting"
|
|
| "complete"
|
|
| "interrupted"
|
|
| "error";
|
|
label: string;
|
|
description: string;
|
|
}
|
|
|
|
/**
|
|
* Get visual styling information for a machine state.
|
|
* Returns color, icon, label, and description for UI display.
|
|
*/
|
|
export function getStateVisualInfo(status: MachineStatus): StateVisualInfo {
|
|
const category = getMachineStateCategory(status);
|
|
|
|
// Map state category to visual properties
|
|
const visualMap: Record<MachineStateCategoryType, StateVisualInfo> = {
|
|
[MachineStateCategory.IDLE]: {
|
|
color: "info",
|
|
iconName: "ready",
|
|
label: "Ready",
|
|
description: "Machine is idle and ready for operations",
|
|
},
|
|
[MachineStateCategory.ACTIVE]: {
|
|
color: "warning",
|
|
iconName: "active",
|
|
label: "Active",
|
|
description: "Operation in progress - do not interrupt",
|
|
},
|
|
[MachineStateCategory.WAITING]: {
|
|
color: "warning",
|
|
iconName: "waiting",
|
|
label: "Waiting",
|
|
description: "Waiting for user or machine action",
|
|
},
|
|
[MachineStateCategory.COMPLETE]: {
|
|
color: "success",
|
|
iconName: "complete",
|
|
label: "Complete",
|
|
description: "Operation completed successfully",
|
|
},
|
|
[MachineStateCategory.INTERRUPTED]: {
|
|
color: "danger",
|
|
iconName: "interrupted",
|
|
label: "Interrupted",
|
|
description: "Operation paused or stopped",
|
|
},
|
|
[MachineStateCategory.ERROR]: {
|
|
color: "danger",
|
|
iconName: "error",
|
|
label: "Error",
|
|
description: "Machine in error or unknown state",
|
|
},
|
|
};
|
|
|
|
return visualMap[category];
|
|
}
|
|
|
|
/**
|
|
* Map machine state category to status indicator state.
|
|
* Returns the appropriate state for the StatusIndicator component.
|
|
*/
|
|
export function getStatusIndicatorState(
|
|
status: MachineStatus,
|
|
): "active" | "down" | "fixing" | "idle" {
|
|
const category = getMachineStateCategory(status);
|
|
|
|
switch (category) {
|
|
case MachineStateCategory.IDLE:
|
|
return "active"; // Gray, no animation
|
|
case MachineStateCategory.ACTIVE:
|
|
return "active"; // Green with animation
|
|
case MachineStateCategory.WAITING:
|
|
return "fixing"; // Yellow with animation
|
|
case MachineStateCategory.COMPLETE:
|
|
return "active"; // Green with animation
|
|
case MachineStateCategory.INTERRUPTED:
|
|
return "down"; // Red with animation
|
|
case MachineStateCategory.ERROR:
|
|
return "down"; // Red with animation
|
|
default:
|
|
return "idle";
|
|
}
|
|
}
|