Improve polling indicator with service-level communication state

Previously, isPolling was manually set in individual refresh functions,
which didn't cover all BLE communication and could have race conditions.

Now the communication indicator is driven by the service layer:
- Added isCommunicating state to BrotherPP1Service
- setCommunicating() called at queue processing boundaries
- onCommunicationChange() callback subscription pattern
- Hook subscribes to service communication state automatically

Benefits:
- Indicator shows for ALL BLE commands (status, progress, service count, etc)
- Prevents race conditions from overlapping command queue processing
- More accurate representation of actual BLE communication
- Cleaner code - no manual setIsPolling in each function

The indicator now properly shows whenever the command queue is being
processed, regardless of which specific command triggered it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Henrik 2025-12-07 00:02:22 +01:00
parent 37f80051e0
commit b7ea024df3
2 changed files with 59 additions and 7 deletions

View file

@ -27,7 +27,7 @@ export function useBrotherMachine() {
);
const [uploadProgress, setUploadProgress] = useState<number>(0);
const [error, setError] = useState<string | null>(null);
const [isPolling, setIsPolling] = useState(false);
const [isCommunicating, setIsCommunicating] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [resumeAvailable, setResumeAvailable] = useState(false);
@ -36,6 +36,12 @@ export function useBrotherMachine() {
null,
);
// Subscribe to service communication state
useEffect(() => {
const unsubscribe = service.onCommunicationChange(setIsCommunicating);
return unsubscribe;
}, [service]);
// Define checkResume first (before connect uses it)
const checkResume = useCallback(async (): Promise<PesPatternData | null> => {
try {
@ -133,14 +139,11 @@ export function useBrotherMachine() {
if (!isConnected) return;
try {
setIsPolling(true);
const state = await service.getMachineState();
setMachineStatus(state.status);
setMachineError(state.error);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to get status");
} finally {
setIsPolling(false);
}
}, [service, isConnected]);
@ -168,6 +171,22 @@ export function useBrotherMachine() {
}
}, [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;
@ -349,8 +368,16 @@ export function useBrotherMachine() {
}
}, pollInterval);
return () => clearInterval(interval);
}, [isConnected, machineStatus, refreshStatus, refreshProgress]);
// 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)
@ -372,7 +399,7 @@ export function useBrotherMachine() {
sewingProgress,
uploadProgress,
error,
isPolling,
isPolling: isCommunicating,
isUploading,
isDeleting,
resumeAvailable,

View file

@ -47,6 +47,29 @@ export class BrotherPP1Service {
private readCharacteristic: BluetoothRemoteGATTCharacteristic | null = null;
private commandQueue: Array<() => Promise<void>> = [];
private isProcessingQueue = false;
private isCommunicating = false;
private communicationCallbacks: Set<(isCommunicating: boolean) => void> = new Set();
/**
* Subscribe to communication state changes
* @param callback Function called when communication state changes
* @returns Unsubscribe function
*/
onCommunicationChange(callback: (isCommunicating: boolean) => void): () => void {
this.communicationCallbacks.add(callback);
// Immediately call with current state
callback(this.isCommunicating);
return () => {
this.communicationCallbacks.delete(callback);
};
}
private setCommunicating(value: boolean) {
if (this.isCommunicating !== value) {
this.isCommunicating = value;
this.communicationCallbacks.forEach(callback => callback(value));
}
}
async connect(): Promise<void> {
this.device = await navigator.bluetooth.requestDevice({
@ -103,6 +126,7 @@ export class BrotherPP1Service {
}
this.isProcessingQueue = true;
this.setCommunicating(true);
while (this.commandQueue.length > 0) {
const command = this.commandQueue.shift();
@ -117,6 +141,7 @@ export class BrotherPP1Service {
}
this.isProcessingQueue = false;
this.setCommunicating(false);
}
/**