mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
Merge pull request #19 from jhbruhn/fix/fallback-to-pen-stitch-count
Some checks are pending
Build, Test, and Lint / Build, Test, and Lint (push) Waiting to run
Draft Release / Draft Release (push) Waiting to run
Draft Release / Build Web App (push) Blocked by required conditions
Draft Release / Build Release - macos-latest (push) Blocked by required conditions
Draft Release / Build Release - ubuntu-latest (push) Blocked by required conditions
Draft Release / Build Release - windows-latest (push) Blocked by required conditions
Draft Release / Upload to GitHub Release (push) Blocked by required conditions
Some checks are pending
Build, Test, and Lint / Build, Test, and Lint (push) Waiting to run
Draft Release / Draft Release (push) Waiting to run
Draft Release / Build Web App (push) Blocked by required conditions
Draft Release / Build Release - macos-latest (push) Blocked by required conditions
Draft Release / Build Release - ubuntu-latest (push) Blocked by required conditions
Draft Release / Build Release - windows-latest (push) Blocked by required conditions
Draft Release / Upload to GitHub Release (push) Blocked by required conditions
fix: Fall back to PEN stitch count when machine reports 0 total stitches, fix remaining time calculation
This commit is contained in:
commit
a275f72311
4 changed files with 211 additions and 526 deletions
|
|
@ -21,6 +21,7 @@ import {
|
||||||
canResumeSewing,
|
canResumeSewing,
|
||||||
getStateVisualInfo,
|
getStateVisualInfo,
|
||||||
} from "../utils/machineStateHelpers";
|
} from "../utils/machineStateHelpers";
|
||||||
|
import { calculatePatternTime } from "../utils/timeCalculation";
|
||||||
|
|
||||||
export function ProgressMonitor() {
|
export function ProgressMonitor() {
|
||||||
// Machine store
|
// Machine store
|
||||||
|
|
@ -56,8 +57,15 @@ export function ProgressMonitor() {
|
||||||
|
|
||||||
const stateVisual = getStateVisualInfo(machineStatus);
|
const stateVisual = getStateVisualInfo(machineStatus);
|
||||||
|
|
||||||
const progressPercent = patternInfo
|
// Use PEN stitch count as fallback when machine reports 0 total stitches
|
||||||
? ((sewingProgress?.currentStitch || 0) / patternInfo.totalStitches) * 100
|
const totalStitches = patternInfo
|
||||||
|
? (patternInfo.totalStitches === 0 && pesData?.penStitches
|
||||||
|
? pesData.penStitches.stitches.length
|
||||||
|
: patternInfo.totalStitches)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const progressPercent = totalStitches > 0
|
||||||
|
? ((sewingProgress?.currentStitch || 0) / totalStitches) * 100
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
// Calculate color block information from decoded penStitches
|
// Calculate color block information from decoded penStitches
|
||||||
|
|
@ -102,6 +110,15 @@ export function ProgressMonitor() {
|
||||||
currentStitch >= block.startStitch && currentStitch < block.endStitch,
|
currentStitch >= block.startStitch && currentStitch < block.endStitch,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Calculate time based on color blocks (matches Brother app calculation)
|
||||||
|
const { totalMinutes, elapsedMinutes } = useMemo(() => {
|
||||||
|
if (colorBlocks.length === 0) {
|
||||||
|
return { totalMinutes: 0, elapsedMinutes: 0 };
|
||||||
|
}
|
||||||
|
const result = calculatePatternTime(colorBlocks, currentStitch);
|
||||||
|
return { totalMinutes: result.totalMinutes, elapsedMinutes: result.elapsedMinutes };
|
||||||
|
}, [colorBlocks, currentStitch]);
|
||||||
|
|
||||||
// Auto-scroll to current block
|
// Auto-scroll to current block
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentBlockRef.current) {
|
if (currentBlockRef.current) {
|
||||||
|
|
@ -173,16 +190,15 @@ export function ProgressMonitor() {
|
||||||
Total Stitches
|
Total Stitches
|
||||||
</span>
|
</span>
|
||||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
{patternInfo.totalStitches.toLocaleString()}
|
{totalStitches.toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||||
<span className="text-gray-600 dark:text-gray-400 block">
|
<span className="text-gray-600 dark:text-gray-400 block">
|
||||||
Est. Time
|
Total Time
|
||||||
</span>
|
</span>
|
||||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
{Math.floor(patternInfo.totalTime / 60)}:
|
{totalMinutes} min
|
||||||
{String(patternInfo.totalTime % 60).padStart(2, "0")}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||||
|
|
@ -213,16 +229,15 @@ export function ProgressMonitor() {
|
||||||
</span>
|
</span>
|
||||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
{sewingProgress.currentStitch.toLocaleString()} /{" "}
|
{sewingProgress.currentStitch.toLocaleString()} /{" "}
|
||||||
{patternInfo?.totalStitches.toLocaleString() || 0}
|
{totalStitches.toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
<div className="bg-gray-50 dark:bg-gray-700/50 p-2 rounded">
|
||||||
<span className="text-gray-600 dark:text-gray-400 block">
|
<span className="text-gray-600 dark:text-gray-400 block">
|
||||||
Time Elapsed
|
Time
|
||||||
</span>
|
</span>
|
||||||
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
{Math.floor(sewingProgress.currentTime / 60)}:
|
{elapsedMinutes} / {totalMinutes} min
|
||||||
{String(sewingProgress.currentTime % 60).padStart(2, "0")}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,457 +0,0 @@
|
||||||
import { useState, useCallback, useEffect } from "react";
|
|
||||||
import { BrotherPP1Service, BluetoothPairingError } from "../services/BrotherPP1Service";
|
|
||||||
import type {
|
|
||||||
MachineInfo,
|
|
||||||
PatternInfo,
|
|
||||||
SewingProgress,
|
|
||||||
} from "../types/machine";
|
|
||||||
import { MachineStatus, MachineStatusNames } from "../types/machine";
|
|
||||||
import {
|
|
||||||
uuidToString,
|
|
||||||
} from "../services/PatternCacheService";
|
|
||||||
import type { IStorageService } from "../platform/interfaces/IStorageService";
|
|
||||||
import { createStorageService } from "../platform";
|
|
||||||
import type { PesPatternData } from "../formats/import/pesImporter";
|
|
||||||
import { SewingMachineError } from "../utils/errorCodeHelpers";
|
|
||||||
|
|
||||||
export function useBrotherMachine() {
|
|
||||||
const [service] = useState(() => new BrotherPP1Service());
|
|
||||||
const [storageService] = useState<IStorageService>(() => createStorageService());
|
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
|
||||||
const [machineInfo, setMachineInfo] = useState<MachineInfo | null>(null);
|
|
||||||
const [machineStatus, setMachineStatus] = useState<MachineStatus>(
|
|
||||||
MachineStatus.None,
|
|
||||||
);
|
|
||||||
const [machineError, setMachineError] = useState<number>(SewingMachineError.None);
|
|
||||||
const [patternInfo, setPatternInfo] = useState<PatternInfo | null>(null);
|
|
||||||
const [sewingProgress, setSewingProgress] = useState<SewingProgress | null>(
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
const [uploadProgress, setUploadProgress] = useState<number>(0);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [isPairingError, setIsPairingError] = useState(false);
|
|
||||||
const [isCommunicating, setIsCommunicating] = useState(false);
|
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
|
||||||
const [resumeAvailable, setResumeAvailable] = useState(false);
|
|
||||||
const [resumeFileName, setResumeFileName] = useState<string | null>(null);
|
|
||||||
const [resumedPattern, setResumedPattern] = useState<{ pesData: PesPatternData; patternOffset?: { x: number; y: number } } | null>(
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Subscribe to service communication state
|
|
||||||
useEffect(() => {
|
|
||||||
const unsubscribe = service.onCommunicationChange(setIsCommunicating);
|
|
||||||
return unsubscribe;
|
|
||||||
}, [service]);
|
|
||||||
|
|
||||||
// Subscribe to disconnect events
|
|
||||||
useEffect(() => {
|
|
||||||
const unsubscribe = service.onDisconnect(() => {
|
|
||||||
console.log('[useBrotherMachine] Device disconnected');
|
|
||||||
setIsConnected(false);
|
|
||||||
setMachineInfo(null);
|
|
||||||
setMachineStatus(MachineStatus.None);
|
|
||||||
setMachineError(SewingMachineError.None);
|
|
||||||
setPatternInfo(null);
|
|
||||||
setSewingProgress(null);
|
|
||||||
setError('Device disconnected');
|
|
||||||
setIsPairingError(false);
|
|
||||||
});
|
|
||||||
return unsubscribe;
|
|
||||||
}, [service]);
|
|
||||||
|
|
||||||
// Define checkResume first (before connect uses it)
|
|
||||||
const checkResume = useCallback(async (): Promise<PesPatternData | null> => {
|
|
||||||
try {
|
|
||||||
console.log("[Resume] Checking for cached pattern...");
|
|
||||||
|
|
||||||
// Get UUID from machine
|
|
||||||
const machineUuid = await service.getPatternUUID();
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[Resume] Machine UUID:",
|
|
||||||
machineUuid ? uuidToString(machineUuid) : "none",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!machineUuid) {
|
|
||||||
console.log("[Resume] No pattern loaded on machine");
|
|
||||||
setResumeAvailable(false);
|
|
||||||
setResumeFileName(null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we have this pattern cached
|
|
||||||
const uuidStr = uuidToString(machineUuid);
|
|
||||||
const cached = await storageService.getPatternByUUID(uuidStr);
|
|
||||||
|
|
||||||
if (cached) {
|
|
||||||
console.log("[Resume] Pattern found in cache:", cached.fileName, "Offset:", cached.patternOffset);
|
|
||||||
console.log("[Resume] Auto-loading cached pattern...");
|
|
||||||
setResumeAvailable(true);
|
|
||||||
setResumeFileName(cached.fileName);
|
|
||||||
setResumedPattern({ pesData: cached.pesData, patternOffset: cached.patternOffset });
|
|
||||||
|
|
||||||
// Fetch pattern info from machine
|
|
||||||
try {
|
|
||||||
const info = await service.getPatternInfo();
|
|
||||||
setPatternInfo(info);
|
|
||||||
console.log("[Resume] Pattern info loaded from machine");
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[Resume] Failed to load pattern info:", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the cached pattern data to be loaded
|
|
||||||
return cached.pesData;
|
|
||||||
} else {
|
|
||||||
console.log("[Resume] Pattern on machine not found in cache");
|
|
||||||
setResumeAvailable(false);
|
|
||||||
setResumeFileName(null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[Resume] Failed to check resume:", err);
|
|
||||||
setResumeAvailable(false);
|
|
||||||
setResumeFileName(null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}, [service, storageService]);
|
|
||||||
|
|
||||||
const connect = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
setError(null);
|
|
||||||
setIsPairingError(false);
|
|
||||||
await service.connect();
|
|
||||||
setIsConnected(true);
|
|
||||||
|
|
||||||
// Fetch initial machine info and status
|
|
||||||
const info = await service.getMachineInfo();
|
|
||||||
setMachineInfo(info);
|
|
||||||
|
|
||||||
const state = await service.getMachineState();
|
|
||||||
setMachineStatus(state.status);
|
|
||||||
setMachineError(state.error);
|
|
||||||
|
|
||||||
// Check for resume possibility
|
|
||||||
await checkResume();
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
const isPairing = err instanceof BluetoothPairingError;
|
|
||||||
setIsPairingError(isPairing);
|
|
||||||
setError(err instanceof Error ? err.message : "Failed to connect");
|
|
||||||
setIsConnected(false);
|
|
||||||
}
|
|
||||||
}, [service, checkResume]);
|
|
||||||
|
|
||||||
const disconnect = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
await service.disconnect();
|
|
||||||
setIsConnected(false);
|
|
||||||
setMachineInfo(null);
|
|
||||||
setMachineStatus(MachineStatus.None);
|
|
||||||
setPatternInfo(null);
|
|
||||||
setSewingProgress(null);
|
|
||||||
setError(null);
|
|
||||||
setMachineError(SewingMachineError.None);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : "Failed to disconnect");
|
|
||||||
}
|
|
||||||
}, [service]);
|
|
||||||
|
|
||||||
const refreshStatus = useCallback(async () => {
|
|
||||||
if (!isConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const state = await service.getMachineState();
|
|
||||||
setMachineStatus(state.status);
|
|
||||||
setMachineError(state.error);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : "Failed to get status");
|
|
||||||
}
|
|
||||||
}, [service, isConnected]);
|
|
||||||
|
|
||||||
const refreshPatternInfo = useCallback(async () => {
|
|
||||||
if (!isConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const info = await service.getPatternInfo();
|
|
||||||
setPatternInfo(info);
|
|
||||||
} catch (err) {
|
|
||||||
setError(
|
|
||||||
err instanceof Error ? err.message : "Failed to get pattern info",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [service, isConnected]);
|
|
||||||
|
|
||||||
const refreshProgress = useCallback(async () => {
|
|
||||||
if (!isConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const progress = await service.getSewingProgress();
|
|
||||||
setSewingProgress(progress);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : "Failed to get progress");
|
|
||||||
}
|
|
||||||
}, [service, isConnected]);
|
|
||||||
|
|
||||||
const refreshServiceCount = useCallback(async () => {
|
|
||||||
if (!isConnected || !machineInfo) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const counts = await service.getServiceCount();
|
|
||||||
setMachineInfo({
|
|
||||||
...machineInfo,
|
|
||||||
serviceCount: counts.serviceCount,
|
|
||||||
totalCount: counts.totalCount,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
// Don't set error for service count failures - it's not critical
|
|
||||||
console.warn("Failed to get service count:", err);
|
|
||||||
}
|
|
||||||
}, [service, isConnected, machineInfo]);
|
|
||||||
|
|
||||||
const loadCachedPattern =
|
|
||||||
useCallback(async (): Promise<{ pesData: PesPatternData; patternOffset?: { x: number; y: number } } | null> => {
|
|
||||||
if (!resumeAvailable) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const machineUuid = await service.getPatternUUID();
|
|
||||||
if (!machineUuid) return null;
|
|
||||||
|
|
||||||
const uuidStr = uuidToString(machineUuid);
|
|
||||||
const cached = await storageService.getPatternByUUID(uuidStr);
|
|
||||||
|
|
||||||
if (cached) {
|
|
||||||
console.log("[Resume] Loading cached pattern:", cached.fileName, "Offset:", cached.patternOffset);
|
|
||||||
// Refresh pattern info from machine
|
|
||||||
await refreshPatternInfo();
|
|
||||||
return { pesData: cached.pesData, patternOffset: cached.patternOffset };
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} catch (err) {
|
|
||||||
setError(
|
|
||||||
err instanceof Error ? err.message : "Failed to load cached pattern",
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}, [service, storageService, resumeAvailable, refreshPatternInfo]);
|
|
||||||
|
|
||||||
const uploadPattern = useCallback(
|
|
||||||
async (penData: Uint8Array, pesData: PesPatternData, fileName: string, patternOffset?: { x: number; y: number }) => {
|
|
||||||
if (!isConnected) {
|
|
||||||
setError("Not connected to machine");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setError(null);
|
|
||||||
setUploadProgress(0);
|
|
||||||
setIsUploading(true); // Set loading state immediately
|
|
||||||
const uuid = await service.uploadPattern(
|
|
||||||
penData,
|
|
||||||
(progress) => {
|
|
||||||
setUploadProgress(progress);
|
|
||||||
},
|
|
||||||
pesData.bounds,
|
|
||||||
patternOffset,
|
|
||||||
);
|
|
||||||
setUploadProgress(100);
|
|
||||||
|
|
||||||
// Cache the pattern with its UUID and offset
|
|
||||||
const uuidStr = uuidToString(uuid);
|
|
||||||
storageService.savePattern(uuidStr, pesData, fileName, patternOffset);
|
|
||||||
console.log("[Cache] Saved pattern:", fileName, "with UUID:", uuidStr, "Offset:", patternOffset);
|
|
||||||
|
|
||||||
// Clear resume state since we just uploaded
|
|
||||||
setResumeAvailable(false);
|
|
||||||
setResumeFileName(null);
|
|
||||||
|
|
||||||
// Refresh status after upload
|
|
||||||
// NOTE: We don't call refreshPatternInfo() here because the machine hasn't
|
|
||||||
// finished processing the pattern yet. Pattern info (stitch count, time estimate)
|
|
||||||
// is only available AFTER startMaskTrace() is called.
|
|
||||||
await refreshStatus();
|
|
||||||
} catch (err) {
|
|
||||||
setError(
|
|
||||||
err instanceof Error ? err.message : "Failed to upload pattern",
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setIsUploading(false); // Clear loading state
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[service, storageService, isConnected, refreshStatus],
|
|
||||||
);
|
|
||||||
|
|
||||||
const startMaskTrace = useCallback(async () => {
|
|
||||||
if (!isConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setError(null);
|
|
||||||
await service.startMaskTrace();
|
|
||||||
|
|
||||||
// After mask trace, poll machine status a few times to ensure it's ready
|
|
||||||
// The machine needs time to process the pattern before pattern info is accurate
|
|
||||||
console.log('[MaskTrace] Polling machine status...');
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
|
||||||
await refreshStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now the machine should have accurate pattern info
|
|
||||||
console.log('[MaskTrace] Refreshing pattern info...');
|
|
||||||
await refreshPatternInfo();
|
|
||||||
} catch (err) {
|
|
||||||
setError(
|
|
||||||
err instanceof Error ? err.message : "Failed to start mask trace",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [service, isConnected, refreshStatus, refreshPatternInfo]);
|
|
||||||
|
|
||||||
const startSewing = useCallback(async () => {
|
|
||||||
if (!isConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setError(null);
|
|
||||||
await service.startSewing();
|
|
||||||
await refreshStatus();
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : "Failed to start sewing");
|
|
||||||
}
|
|
||||||
}, [service, isConnected, refreshStatus]);
|
|
||||||
|
|
||||||
const resumeSewing = useCallback(async () => {
|
|
||||||
if (!isConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setError(null);
|
|
||||||
await service.resumeSewing();
|
|
||||||
await refreshStatus();
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : "Failed to resume sewing");
|
|
||||||
}
|
|
||||||
}, [service, isConnected, refreshStatus]);
|
|
||||||
|
|
||||||
const deletePattern = useCallback(async () => {
|
|
||||||
if (!isConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setError(null);
|
|
||||||
setIsDeleting(true); // Set loading state immediately
|
|
||||||
|
|
||||||
// Delete pattern from cache to prevent auto-resume
|
|
||||||
try {
|
|
||||||
const machineUuid = await service.getPatternUUID();
|
|
||||||
if (machineUuid) {
|
|
||||||
const uuidStr = uuidToString(machineUuid);
|
|
||||||
await storageService.deletePattern(uuidStr);
|
|
||||||
console.log("[Cache] Deleted pattern with UUID:", uuidStr);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn("[Cache] Failed to get UUID for cache deletion:", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
await service.deletePattern();
|
|
||||||
|
|
||||||
// Clear machine-related state but keep pattern data in UI for re-editing
|
|
||||||
setPatternInfo(null);
|
|
||||||
setSewingProgress(null);
|
|
||||||
setUploadProgress(0); // Reset upload progress to allow new uploads
|
|
||||||
setResumeAvailable(false);
|
|
||||||
setResumeFileName(null);
|
|
||||||
// NOTE: We intentionally DON'T clear setResumedPattern(null)
|
|
||||||
// so the pattern remains visible in the canvas for re-editing
|
|
||||||
// However, we DO need to preserve pesData in App.tsx for re-upload
|
|
||||||
|
|
||||||
await refreshStatus();
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : "Failed to delete pattern");
|
|
||||||
} finally {
|
|
||||||
setIsDeleting(false); // Clear loading state
|
|
||||||
}
|
|
||||||
}, [service, storageService, isConnected, refreshStatus]);
|
|
||||||
|
|
||||||
// Periodic status monitoring when connected
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isConnected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine polling interval based on machine status
|
|
||||||
let pollInterval = 2000; // Default: 2 seconds for idle states
|
|
||||||
|
|
||||||
// Fast polling for active states
|
|
||||||
if (
|
|
||||||
machineStatus === MachineStatus.SEWING ||
|
|
||||||
machineStatus === MachineStatus.MASK_TRACING ||
|
|
||||||
machineStatus === MachineStatus.SEWING_DATA_RECEIVE
|
|
||||||
) {
|
|
||||||
pollInterval = 500; // 500ms for active operations
|
|
||||||
} else if (
|
|
||||||
machineStatus === MachineStatus.COLOR_CHANGE_WAIT ||
|
|
||||||
machineStatus === MachineStatus.MASK_TRACE_LOCK_WAIT ||
|
|
||||||
machineStatus === MachineStatus.SEWING_WAIT
|
|
||||||
) {
|
|
||||||
pollInterval = 1000; // 1 second for waiting states
|
|
||||||
}
|
|
||||||
|
|
||||||
const interval = setInterval(async () => {
|
|
||||||
await refreshStatus();
|
|
||||||
|
|
||||||
// Refresh progress during sewing
|
|
||||||
if (machineStatus === MachineStatus.SEWING) {
|
|
||||||
await refreshProgress();
|
|
||||||
}
|
|
||||||
}, pollInterval);
|
|
||||||
|
|
||||||
// Separate interval for service count (slower update rate - every 10 seconds)
|
|
||||||
const serviceCountInterval = setInterval(async () => {
|
|
||||||
await refreshServiceCount();
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearInterval(interval);
|
|
||||||
clearInterval(serviceCountInterval);
|
|
||||||
};
|
|
||||||
}, [isConnected, machineStatus, refreshStatus, refreshProgress, refreshServiceCount]);
|
|
||||||
|
|
||||||
// Refresh pattern info when status changes to SEWING_WAIT
|
|
||||||
// (indicates pattern was just uploaded or is ready)
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isConnected) return;
|
|
||||||
|
|
||||||
if (machineStatus === MachineStatus.SEWING_WAIT && !patternInfo) {
|
|
||||||
refreshPatternInfo();
|
|
||||||
}
|
|
||||||
}, [isConnected, machineStatus, patternInfo, refreshPatternInfo]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isConnected,
|
|
||||||
machineInfo,
|
|
||||||
machineStatus,
|
|
||||||
machineStatusName: MachineStatusNames[machineStatus] || "Unknown",
|
|
||||||
machineError,
|
|
||||||
patternInfo,
|
|
||||||
sewingProgress,
|
|
||||||
uploadProgress,
|
|
||||||
error,
|
|
||||||
isPairingError,
|
|
||||||
isPolling: isCommunicating,
|
|
||||||
isUploading,
|
|
||||||
isDeleting,
|
|
||||||
resumeAvailable,
|
|
||||||
resumeFileName,
|
|
||||||
resumedPattern,
|
|
||||||
connect,
|
|
||||||
disconnect,
|
|
||||||
refreshStatus,
|
|
||||||
refreshPatternInfo,
|
|
||||||
uploadPattern,
|
|
||||||
startMaskTrace,
|
|
||||||
startSewing,
|
|
||||||
resumeSewing,
|
|
||||||
deletePattern,
|
|
||||||
checkResume,
|
|
||||||
loadCachedPattern,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import { create } from 'zustand';
|
import { create } from "zustand";
|
||||||
import { BrotherPP1Service, BluetoothPairingError } from '../services/BrotherPP1Service';
|
import {
|
||||||
|
BrotherPP1Service,
|
||||||
|
BluetoothPairingError,
|
||||||
|
} from "../services/BrotherPP1Service";
|
||||||
import type {
|
import type {
|
||||||
MachineInfo,
|
MachineInfo,
|
||||||
PatternInfo,
|
PatternInfo,
|
||||||
SewingProgress,
|
SewingProgress,
|
||||||
} from '../types/machine';
|
} from "../types/machine";
|
||||||
import { MachineStatus, MachineStatusNames } from '../types/machine';
|
import { MachineStatus, MachineStatusNames } from "../types/machine";
|
||||||
import { SewingMachineError } from '../utils/errorCodeHelpers';
|
import { SewingMachineError } from "../utils/errorCodeHelpers";
|
||||||
import { uuidToString } from '../services/PatternCacheService';
|
import { uuidToString } from "../services/PatternCacheService";
|
||||||
import { createStorageService } from '../platform';
|
import { createStorageService } from "../platform";
|
||||||
import type { IStorageService } from '../platform/interfaces/IStorageService';
|
import type { IStorageService } from "../platform/interfaces/IStorageService";
|
||||||
import type { PesPatternData } from '../formats/import/pesImporter';
|
import type { PesPatternData } from "../formats/import/pesImporter";
|
||||||
|
|
||||||
interface MachineState {
|
interface MachineState {
|
||||||
// Service instances
|
// Service instances
|
||||||
|
|
@ -37,7 +40,10 @@ interface MachineState {
|
||||||
// Resume state
|
// Resume state
|
||||||
resumeAvailable: boolean;
|
resumeAvailable: boolean;
|
||||||
resumeFileName: string | null;
|
resumeFileName: string | null;
|
||||||
resumedPattern: { pesData: PesPatternData; patternOffset?: { x: number; y: number } } | null;
|
resumedPattern: {
|
||||||
|
pesData: PesPatternData;
|
||||||
|
patternOffset?: { x: number; y: number };
|
||||||
|
} | null;
|
||||||
|
|
||||||
// Error state
|
// Error state
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
|
@ -62,14 +68,17 @@ interface MachineState {
|
||||||
penData: Uint8Array,
|
penData: Uint8Array,
|
||||||
pesData: PesPatternData,
|
pesData: PesPatternData,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
patternOffset?: { x: number; y: number }
|
patternOffset?: { x: number; y: number },
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
startMaskTrace: () => Promise<void>;
|
startMaskTrace: () => Promise<void>;
|
||||||
startSewing: () => Promise<void>;
|
startSewing: () => Promise<void>;
|
||||||
resumeSewing: () => Promise<void>;
|
resumeSewing: () => Promise<void>;
|
||||||
deletePattern: () => Promise<void>;
|
deletePattern: () => Promise<void>;
|
||||||
checkResume: () => Promise<PesPatternData | null>;
|
checkResume: () => Promise<PesPatternData | null>;
|
||||||
loadCachedPattern: () => Promise<{ pesData: PesPatternData; patternOffset?: { x: number; y: number } } | null>;
|
loadCachedPattern: () => Promise<{
|
||||||
|
pesData: PesPatternData;
|
||||||
|
patternOffset?: { x: number; y: number };
|
||||||
|
} | null>;
|
||||||
|
|
||||||
// Internal methods
|
// Internal methods
|
||||||
_setupSubscriptions: () => void;
|
_setupSubscriptions: () => void;
|
||||||
|
|
@ -84,7 +93,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
machineInfo: null,
|
machineInfo: null,
|
||||||
machineStatus: MachineStatus.None,
|
machineStatus: MachineStatus.None,
|
||||||
machineStatusName: MachineStatusNames[MachineStatus.None] || 'Unknown',
|
machineStatusName: MachineStatusNames[MachineStatus.None] || "Unknown",
|
||||||
machineError: SewingMachineError.None,
|
machineError: SewingMachineError.None,
|
||||||
patternInfo: null,
|
patternInfo: null,
|
||||||
sewingProgress: null,
|
sewingProgress: null,
|
||||||
|
|
@ -104,16 +113,16 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
checkResume: async (): Promise<PesPatternData | null> => {
|
checkResume: async (): Promise<PesPatternData | null> => {
|
||||||
try {
|
try {
|
||||||
const { service, storageService } = get();
|
const { service, storageService } = get();
|
||||||
console.log('[Resume] Checking for cached pattern...');
|
console.log("[Resume] Checking for cached pattern...");
|
||||||
|
|
||||||
const machineUuid = await service.getPatternUUID();
|
const machineUuid = await service.getPatternUUID();
|
||||||
console.log(
|
console.log(
|
||||||
'[Resume] Machine UUID:',
|
"[Resume] Machine UUID:",
|
||||||
machineUuid ? uuidToString(machineUuid) : 'none',
|
machineUuid ? uuidToString(machineUuid) : "none",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!machineUuid) {
|
if (!machineUuid) {
|
||||||
console.log('[Resume] No pattern loaded on machine');
|
console.log("[Resume] No pattern loaded on machine");
|
||||||
set({ resumeAvailable: false, resumeFileName: null });
|
set({ resumeAvailable: false, resumeFileName: null });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -122,31 +131,39 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
const cached = await storageService.getPatternByUUID(uuidStr);
|
const cached = await storageService.getPatternByUUID(uuidStr);
|
||||||
|
|
||||||
if (cached) {
|
if (cached) {
|
||||||
console.log('[Resume] Pattern found in cache:', cached.fileName, 'Offset:', cached.patternOffset);
|
console.log(
|
||||||
console.log('[Resume] Auto-loading cached pattern...');
|
"[Resume] Pattern found in cache:",
|
||||||
|
cached.fileName,
|
||||||
|
"Offset:",
|
||||||
|
cached.patternOffset,
|
||||||
|
);
|
||||||
|
console.log("[Resume] Auto-loading cached pattern...");
|
||||||
set({
|
set({
|
||||||
resumeAvailable: true,
|
resumeAvailable: true,
|
||||||
resumeFileName: cached.fileName,
|
resumeFileName: cached.fileName,
|
||||||
resumedPattern: { pesData: cached.pesData, patternOffset: cached.patternOffset },
|
resumedPattern: {
|
||||||
|
pesData: cached.pesData,
|
||||||
|
patternOffset: cached.patternOffset,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch pattern info from machine
|
// Fetch pattern info from machine
|
||||||
try {
|
try {
|
||||||
const info = await service.getPatternInfo();
|
const info = await service.getPatternInfo();
|
||||||
set({ patternInfo: info });
|
set({ patternInfo: info });
|
||||||
console.log('[Resume] Pattern info loaded from machine');
|
console.log("[Resume] Pattern info loaded from machine");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Resume] Failed to load pattern info:', err);
|
console.error("[Resume] Failed to load pattern info:", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cached.pesData;
|
return cached.pesData;
|
||||||
} else {
|
} else {
|
||||||
console.log('[Resume] Pattern on machine not found in cache');
|
console.log("[Resume] Pattern on machine not found in cache");
|
||||||
set({ resumeAvailable: false, resumeFileName: null });
|
set({ resumeAvailable: false, resumeFileName: null });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Resume] Failed to check resume:', err);
|
console.error("[Resume] Failed to check resume:", err);
|
||||||
set({ resumeAvailable: false, resumeFileName: null });
|
set({ resumeAvailable: false, resumeFileName: null });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +185,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
set({
|
set({
|
||||||
machineInfo: info,
|
machineInfo: info,
|
||||||
machineStatus: state.status,
|
machineStatus: state.status,
|
||||||
machineStatusName: MachineStatusNames[state.status] || 'Unknown',
|
machineStatusName: MachineStatusNames[state.status] || "Unknown",
|
||||||
machineError: state.error,
|
machineError: state.error,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -182,7 +199,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
const isPairing = err instanceof BluetoothPairingError;
|
const isPairing = err instanceof BluetoothPairingError;
|
||||||
set({
|
set({
|
||||||
isPairingError: isPairing,
|
isPairingError: isPairing,
|
||||||
error: err instanceof Error ? err.message : 'Failed to connect',
|
error: err instanceof Error ? err.message : "Failed to connect",
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +216,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
machineInfo: null,
|
machineInfo: null,
|
||||||
machineStatus: MachineStatus.None,
|
machineStatus: MachineStatus.None,
|
||||||
machineStatusName: MachineStatusNames[MachineStatus.None] || 'Unknown',
|
machineStatusName: MachineStatusNames[MachineStatus.None] || "Unknown",
|
||||||
patternInfo: null,
|
patternInfo: null,
|
||||||
sewingProgress: null,
|
sewingProgress: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
@ -207,7 +224,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
set({
|
set({
|
||||||
error: err instanceof Error ? err.message : 'Failed to disconnect',
|
error: err instanceof Error ? err.message : "Failed to disconnect",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -221,12 +238,12 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
const state = await service.getMachineState();
|
const state = await service.getMachineState();
|
||||||
set({
|
set({
|
||||||
machineStatus: state.status,
|
machineStatus: state.status,
|
||||||
machineStatusName: MachineStatusNames[state.status] || 'Unknown',
|
machineStatusName: MachineStatusNames[state.status] || "Unknown",
|
||||||
machineError: state.error,
|
machineError: state.error,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
set({
|
set({
|
||||||
error: err instanceof Error ? err.message : 'Failed to get status',
|
error: err instanceof Error ? err.message : "Failed to get status",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -241,7 +258,8 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
set({ patternInfo: info });
|
set({ patternInfo: info });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
set({
|
set({
|
||||||
error: err instanceof Error ? err.message : 'Failed to get pattern info',
|
error:
|
||||||
|
err instanceof Error ? err.message : "Failed to get pattern info",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -256,7 +274,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
set({ sewingProgress: progress });
|
set({ sewingProgress: progress });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
set({
|
set({
|
||||||
error: err instanceof Error ? err.message : 'Failed to get progress',
|
error: err instanceof Error ? err.message : "Failed to get progress",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -276,7 +294,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to get service count:', err);
|
console.warn("Failed to get service count:", err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -285,11 +303,17 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
penData: Uint8Array,
|
penData: Uint8Array,
|
||||||
pesData: PesPatternData,
|
pesData: PesPatternData,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
patternOffset?: { x: number; y: number }
|
patternOffset?: { x: number; y: number },
|
||||||
) => {
|
) => {
|
||||||
const { isConnected, service, storageService, refreshStatus, refreshPatternInfo } = get();
|
const {
|
||||||
|
isConnected,
|
||||||
|
service,
|
||||||
|
storageService,
|
||||||
|
refreshStatus,
|
||||||
|
refreshPatternInfo,
|
||||||
|
} = get();
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
set({ error: 'Not connected to machine' });
|
set({ error: "Not connected to machine" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,7 +334,14 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
// Cache the pattern with its UUID and offset
|
// Cache the pattern with its UUID and offset
|
||||||
const uuidStr = uuidToString(uuid);
|
const uuidStr = uuidToString(uuid);
|
||||||
storageService.savePattern(uuidStr, pesData, fileName, patternOffset);
|
storageService.savePattern(uuidStr, pesData, fileName, patternOffset);
|
||||||
console.log('[Cache] Saved pattern:', fileName, 'with UUID:', uuidStr, 'Offset:', patternOffset);
|
console.log(
|
||||||
|
"[Cache] Saved pattern:",
|
||||||
|
fileName,
|
||||||
|
"with UUID:",
|
||||||
|
uuidStr,
|
||||||
|
"Offset:",
|
||||||
|
patternOffset,
|
||||||
|
);
|
||||||
|
|
||||||
// Clear resume state since we just uploaded
|
// Clear resume state since we just uploaded
|
||||||
set({
|
set({
|
||||||
|
|
@ -323,7 +354,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
await refreshPatternInfo();
|
await refreshPatternInfo();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
set({
|
set({
|
||||||
error: err instanceof Error ? err.message : 'Failed to upload pattern',
|
error: err instanceof Error ? err.message : "Failed to upload pattern",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
set({ isUploading: false });
|
set({ isUploading: false });
|
||||||
|
|
@ -341,7 +372,8 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
await refreshStatus();
|
await refreshStatus();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
set({
|
set({
|
||||||
error: err instanceof Error ? err.message : 'Failed to start mask trace',
|
error:
|
||||||
|
err instanceof Error ? err.message : "Failed to start mask trace",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -357,7 +389,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
await refreshStatus();
|
await refreshStatus();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
set({
|
set({
|
||||||
error: err instanceof Error ? err.message : 'Failed to start sewing',
|
error: err instanceof Error ? err.message : "Failed to start sewing",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -373,7 +405,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
await refreshStatus();
|
await refreshStatus();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
set({
|
set({
|
||||||
error: err instanceof Error ? err.message : 'Failed to resume sewing',
|
error: err instanceof Error ? err.message : "Failed to resume sewing",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -392,10 +424,10 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
if (machineUuid) {
|
if (machineUuid) {
|
||||||
const uuidStr = uuidToString(machineUuid);
|
const uuidStr = uuidToString(machineUuid);
|
||||||
await storageService.deletePattern(uuidStr);
|
await storageService.deletePattern(uuidStr);
|
||||||
console.log('[Cache] Deleted pattern with UUID:', uuidStr);
|
console.log("[Cache] Deleted pattern with UUID:", uuidStr);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[Cache] Failed to get UUID for cache deletion:', err);
|
console.warn("[Cache] Failed to get UUID for cache deletion:", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
await service.deletePattern();
|
await service.deletePattern();
|
||||||
|
|
@ -412,7 +444,7 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
await refreshStatus();
|
await refreshStatus();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
set({
|
set({
|
||||||
error: err instanceof Error ? err.message : 'Failed to delete pattern',
|
error: err instanceof Error ? err.message : "Failed to delete pattern",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
set({ isDeleting: false });
|
set({ isDeleting: false });
|
||||||
|
|
@ -420,8 +452,12 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
// Load cached pattern
|
// Load cached pattern
|
||||||
loadCachedPattern: async (): Promise<{ pesData: PesPatternData; patternOffset?: { x: number; y: number } } | null> => {
|
loadCachedPattern: async (): Promise<{
|
||||||
const { resumeAvailable, service, storageService, refreshPatternInfo } = get();
|
pesData: PesPatternData;
|
||||||
|
patternOffset?: { x: number; y: number };
|
||||||
|
} | null> => {
|
||||||
|
const { resumeAvailable, service, storageService, refreshPatternInfo } =
|
||||||
|
get();
|
||||||
if (!resumeAvailable) return null;
|
if (!resumeAvailable) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -432,7 +468,12 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
const cached = await storageService.getPatternByUUID(uuidStr);
|
const cached = await storageService.getPatternByUUID(uuidStr);
|
||||||
|
|
||||||
if (cached) {
|
if (cached) {
|
||||||
console.log('[Resume] Loading cached pattern:', cached.fileName, 'Offset:', cached.patternOffset);
|
console.log(
|
||||||
|
"[Resume] Loading cached pattern:",
|
||||||
|
cached.fileName,
|
||||||
|
"Offset:",
|
||||||
|
cached.patternOffset,
|
||||||
|
);
|
||||||
await refreshPatternInfo();
|
await refreshPatternInfo();
|
||||||
return { pesData: cached.pesData, patternOffset: cached.patternOffset };
|
return { pesData: cached.pesData, patternOffset: cached.patternOffset };
|
||||||
}
|
}
|
||||||
|
|
@ -440,7 +481,8 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
return null;
|
return null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
set({
|
set({
|
||||||
error: err instanceof Error ? err.message : 'Failed to load cached pattern',
|
error:
|
||||||
|
err instanceof Error ? err.message : "Failed to load cached pattern",
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -457,17 +499,17 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
|
|
||||||
// Subscribe to disconnect events
|
// Subscribe to disconnect events
|
||||||
service.onDisconnect(() => {
|
service.onDisconnect(() => {
|
||||||
console.log('[useMachineStore] Device disconnected');
|
console.log("[useMachineStore] Device disconnected");
|
||||||
get()._stopPolling();
|
get()._stopPolling();
|
||||||
set({
|
set({
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
machineInfo: null,
|
machineInfo: null,
|
||||||
machineStatus: MachineStatus.None,
|
machineStatus: MachineStatus.None,
|
||||||
machineStatusName: MachineStatusNames[MachineStatus.None] || 'Unknown',
|
machineStatusName: MachineStatusNames[MachineStatus.None] || "Unknown",
|
||||||
machineError: SewingMachineError.None,
|
machineError: SewingMachineError.None,
|
||||||
patternInfo: null,
|
patternInfo: null,
|
||||||
sewingProgress: null,
|
sewingProgress: null,
|
||||||
error: 'Device disconnected',
|
error: "Device disconnected",
|
||||||
isPairingError: false,
|
isPairingError: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -475,7 +517,13 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
|
|
||||||
// Start polling for status updates
|
// Start polling for status updates
|
||||||
_startPolling: () => {
|
_startPolling: () => {
|
||||||
const { _stopPolling, refreshStatus, refreshProgress, refreshServiceCount } = get();
|
const {
|
||||||
|
_stopPolling,
|
||||||
|
refreshStatus,
|
||||||
|
refreshProgress,
|
||||||
|
refreshServiceCount,
|
||||||
|
refreshPatternInfo,
|
||||||
|
} = get();
|
||||||
|
|
||||||
// Stop any existing polling
|
// Stop any existing polling
|
||||||
_stopPolling();
|
_stopPolling();
|
||||||
|
|
@ -510,6 +558,11 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
await refreshProgress();
|
await refreshProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// follows the apps logic:
|
||||||
|
if (get().resumeAvailable && get().patternInfo?.totalStitches == 0) {
|
||||||
|
await refreshPatternInfo();
|
||||||
|
}
|
||||||
|
|
||||||
// Schedule next poll with updated interval
|
// Schedule next poll with updated interval
|
||||||
const newInterval = getPollInterval();
|
const newInterval = getPollInterval();
|
||||||
const pollIntervalId = setTimeout(poll, newInterval);
|
const pollIntervalId = setTimeout(poll, newInterval);
|
||||||
|
|
@ -546,11 +599,18 @@ export const useMachineStore = create<MachineState>((set, get) => ({
|
||||||
useMachineStore.getState()._setupSubscriptions();
|
useMachineStore.getState()._setupSubscriptions();
|
||||||
|
|
||||||
// Selector hooks for common use cases
|
// Selector hooks for common use cases
|
||||||
export const useIsConnected = () => useMachineStore((state) => state.isConnected);
|
export const useIsConnected = () =>
|
||||||
export const useMachineInfo = () => useMachineStore((state) => state.machineInfo);
|
useMachineStore((state) => state.isConnected);
|
||||||
export const useMachineStatus = () => useMachineStore((state) => state.machineStatus);
|
export const useMachineInfo = () =>
|
||||||
export const useMachineError = () => useMachineStore((state) => state.machineError);
|
useMachineStore((state) => state.machineInfo);
|
||||||
export const usePatternInfo = () => useMachineStore((state) => state.patternInfo);
|
export const useMachineStatus = () =>
|
||||||
export const useSewingProgress = () => useMachineStore((state) => state.sewingProgress);
|
useMachineStore((state) => state.machineStatus);
|
||||||
|
export const useMachineError = () =>
|
||||||
|
useMachineStore((state) => state.machineError);
|
||||||
|
export const usePatternInfo = () =>
|
||||||
|
useMachineStore((state) => state.patternInfo);
|
||||||
|
export const useSewingProgress = () =>
|
||||||
|
useMachineStore((state) => state.sewingProgress);
|
||||||
// Derived state: pattern is uploaded if machine has pattern info
|
// Derived state: pattern is uploaded if machine has pattern info
|
||||||
export const usePatternUploaded = () => useMachineStore((state) => state.patternInfo !== null);
|
export const usePatternUploaded = () =>
|
||||||
|
useMachineStore((state) => state.patternInfo !== null);
|
||||||
|
|
|
||||||
67
src/utils/timeCalculation.ts
Normal file
67
src/utils/timeCalculation.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* Convert stitch count to minutes using Brother PP1 timing formula
|
||||||
|
* Formula: ((pointCount - 1) * 150 + 3000) / 60000
|
||||||
|
* - 150ms per stitch
|
||||||
|
* - 3000ms startup time
|
||||||
|
* - Result in minutes (rounded up)
|
||||||
|
*/
|
||||||
|
export function convertStitchesToMinutes(stitchCount: number): number {
|
||||||
|
if (stitchCount <= 1) return 0;
|
||||||
|
|
||||||
|
const timeMs = (stitchCount - 1) * 150 + 3000;
|
||||||
|
const timeMin = Math.ceil(timeMs / 60000);
|
||||||
|
|
||||||
|
return timeMin < 1 ? 1 : timeMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate total and elapsed time for a pattern based on color blocks
|
||||||
|
* This matches the Brother app's calculation method
|
||||||
|
*/
|
||||||
|
export function calculatePatternTime(
|
||||||
|
colorBlocks: Array<{ stitchCount: number }>,
|
||||||
|
currentStitch: number
|
||||||
|
): {
|
||||||
|
totalMinutes: number;
|
||||||
|
elapsedMinutes: number;
|
||||||
|
remainingMinutes: number;
|
||||||
|
} {
|
||||||
|
let totalMinutes = 0;
|
||||||
|
let elapsedMinutes = 0;
|
||||||
|
let cumulativeStitches = 0;
|
||||||
|
|
||||||
|
// Calculate time per color block
|
||||||
|
for (const block of colorBlocks) {
|
||||||
|
totalMinutes += convertStitchesToMinutes(block.stitchCount);
|
||||||
|
cumulativeStitches += block.stitchCount;
|
||||||
|
|
||||||
|
if (cumulativeStitches < currentStitch) {
|
||||||
|
// This entire block is completed
|
||||||
|
elapsedMinutes += convertStitchesToMinutes(block.stitchCount);
|
||||||
|
} else if (cumulativeStitches === currentStitch) {
|
||||||
|
// We just completed this block
|
||||||
|
elapsedMinutes += convertStitchesToMinutes(block.stitchCount);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// We're partway through this block
|
||||||
|
const stitchesInBlock = currentStitch - (cumulativeStitches - block.stitchCount);
|
||||||
|
elapsedMinutes += convertStitchesToMinutes(stitchesInBlock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalMinutes,
|
||||||
|
elapsedMinutes,
|
||||||
|
remainingMinutes: Math.max(0, totalMinutes - elapsedMinutes),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format minutes as MM:SS
|
||||||
|
*/
|
||||||
|
export function formatMinutes(minutes: number): string {
|
||||||
|
const mins = Math.floor(minutes);
|
||||||
|
const secs = Math.round((minutes - mins) * 60);
|
||||||
|
return `${mins}:${String(secs).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue