respira/src/utils/machineStateHelpers.ts
Jan-Henrik Bruhn 7250e0e586 feature: Add stitch step control for error recovery and manual positioning
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>
2026-03-26 12:04:38 +01:00

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";
}
}