+
+
-
-
Error: {machine.error}
+
+
{machine.isPairingError ? 'Pairing Required' : 'Error'}
+
{machine.error}
diff --git a/src/hooks/useBrotherMachine.ts b/src/hooks/useBrotherMachine.ts
index 845e5d5..bd7c295 100644
--- a/src/hooks/useBrotherMachine.ts
+++ b/src/hooks/useBrotherMachine.ts
@@ -1,5 +1,5 @@
import { useState, useCallback, useEffect } from "react";
-import { BrotherPP1Service } from "../services/BrotherPP1Service";
+import { BrotherPP1Service, BluetoothPairingError } from "../services/BrotherPP1Service";
import type {
MachineInfo,
PatternInfo,
@@ -27,6 +27,7 @@ export function useBrotherMachine() {
);
const [uploadProgress, setUploadProgress] = useState
(0);
const [error, setError] = useState(null);
+ const [isPairingError, setIsPairingError] = useState(false);
const [isCommunicating, setIsCommunicating] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
@@ -42,6 +43,22 @@ export function useBrotherMachine() {
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 => {
try {
@@ -101,6 +118,7 @@ export function useBrotherMachine() {
const connect = useCallback(async () => {
try {
setError(null);
+ setIsPairingError(false);
await service.connect();
setIsConnected(true);
@@ -116,6 +134,8 @@ export function useBrotherMachine() {
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);
}
@@ -399,6 +419,7 @@ export function useBrotherMachine() {
sewingProgress,
uploadProgress,
error,
+ isPairingError,
isPolling: isCommunicating,
isUploading,
isDeleting,
diff --git a/src/services/BrotherPP1Service.ts b/src/services/BrotherPP1Service.ts
index d19618c..0999f0d 100644
--- a/src/services/BrotherPP1Service.ts
+++ b/src/services/BrotherPP1Service.ts
@@ -5,6 +5,14 @@ import type {
} from "../types/machine";
import { MachineStatus } from "../types/machine";
+// Custom error for pairing issues
+export class BluetoothPairingError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = 'BluetoothPairingError';
+ }
+}
+
// BLE Service and Characteristic UUIDs
const SERVICE_UUID = "a76eb9e0-f3ac-4990-84cf-3a94d2426b2b";
const WRITE_CHAR_UUID = "a76eb9e2-f3ac-4990-84cf-3a94d2426b2b";
@@ -48,7 +56,9 @@ export class BrotherPP1Service {
private commandQueue: Array<() => Promise> = [];
private isProcessingQueue = false;
private isCommunicating = false;
+ private isInitialConnection = false;
private communicationCallbacks: Set<(isCommunicating: boolean) => void> = new Set();
+ private disconnectCallbacks: Set<() => void> = new Set();
/**
* Subscribe to communication state changes
@@ -64,6 +74,18 @@ export class BrotherPP1Service {
};
}
+ /**
+ * Subscribe to disconnect events
+ * @param callback Function called when device disconnects
+ * @returns Unsubscribe function
+ */
+ onDisconnect(callback: () => void): () => void {
+ this.disconnectCallbacks.add(callback);
+ return () => {
+ this.disconnectCallbacks.delete(callback);
+ };
+ }
+
private setCommunicating(value: boolean) {
if (this.isCommunicating !== value) {
this.isCommunicating = value;
@@ -71,31 +93,54 @@ export class BrotherPP1Service {
}
}
+ private handleDisconnect() {
+ console.log('[BrotherPP1Service] Device disconnected');
+ this.server = null;
+ this.writeCharacteristic = null;
+ this.readCharacteristic = null;
+ this.commandQueue = [];
+ this.isProcessingQueue = false;
+ this.setCommunicating(false);
+ this.disconnectCallbacks.forEach(callback => callback());
+ }
+
async connect(): Promise {
- this.device = await navigator.bluetooth.requestDevice({
- filters: [{ services: [SERVICE_UUID] }],
- });
-
- if (!this.device.gatt) {
- throw new Error("GATT not available");
- }
- console.log("Connecting");
- this.server = await this.device.gatt.connect();
- console.log("Connected");
- const service = await this.server.getPrimaryService(SERVICE_UUID);
- console.log("Got primary service");
-
- this.writeCharacteristic = await service.getCharacteristic(WRITE_CHAR_UUID);
- this.readCharacteristic = await service.getCharacteristic(READ_CHAR_UUID);
-
- console.log("Connected to Brother PP1 machine");
-
- console.log("Send dummy command");
+ this.isInitialConnection = true;
try {
- await this.getMachineInfo();
- console.log("Dummy command success");
- } catch (e) {
- console.log(e);
+ this.device = await navigator.bluetooth.requestDevice({
+ filters: [{ services: [SERVICE_UUID] }],
+ });
+
+ if (!this.device.gatt) {
+ throw new Error("GATT not available");
+ }
+
+ // Listen for disconnection events
+ this.device.addEventListener('gattserverdisconnected', () => {
+ this.handleDisconnect();
+ });
+
+ console.log("Connecting");
+ this.server = await this.device.gatt.connect();
+ console.log("Connected");
+ const service = await this.server.getPrimaryService(SERVICE_UUID);
+ console.log("Got primary service");
+
+ this.writeCharacteristic = await service.getCharacteristic(WRITE_CHAR_UUID);
+ this.readCharacteristic = await service.getCharacteristic(READ_CHAR_UUID);
+
+ console.log("Connected to Brother PP1 machine");
+
+ console.log("Send dummy command");
+ try {
+ await this.getMachineInfo();
+ console.log("Dummy command success");
+ } catch (e) {
+ console.log(e);
+ throw e;
+ }
+ } finally {
+ this.isInitialConnection = false;
}
}
@@ -220,34 +265,50 @@ export class BrotherPP1Service {
hexData,
);
- // Write command
- await this.writeCharacteristic.writeValueWithResponse(command);
+ try {
+ // Write command
+ await this.writeCharacteristic.writeValueWithResponse(command);
- // Longer delay to allow machine to prepare response
- await new Promise((resolve) => setTimeout(resolve, 50));
+ // Longer delay to allow machine to prepare response
+ await new Promise((resolve) => setTimeout(resolve, 50));
- // Read response
- const responseData = await this.readCharacteristic.readValue();
- const response = new Uint8Array(responseData.buffer);
+ // Read response
+ const responseData = await this.readCharacteristic.readValue();
+ const response = new Uint8Array(responseData.buffer);
- const hexResponse = Array.from(response)
- .map((b) => b.toString(16).padStart(2, "0"))
- .join(" ");
+ const hexResponse = Array.from(response)
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join(" ");
- // Parse response
- let parsed = "";
- if (response.length >= 3) {
- const respCmdId = (response[0] << 8) | response[1];
- const status = response[2];
- parsed = ` | Status: 0x${status.toString(16).padStart(2, "0")}`;
- if (respCmdId !== cmdId) {
- parsed += ` | WARNING: Response cmd 0x${respCmdId.toString(16).padStart(4, "0")} != request cmd`;
+ // Parse response
+ let parsed = "";
+ if (response.length >= 3) {
+ const respCmdId = (response[0] << 8) | response[1];
+ const status = response[2];
+ parsed = ` | Status: 0x${status.toString(16).padStart(2, "0")}`;
+ if (respCmdId !== cmdId) {
+ parsed += ` | WARNING: Response cmd 0x${respCmdId.toString(16).padStart(4, "0")} != request cmd`;
+ }
}
+
+ console.log(`[RX] ${this.getCommandName(cmdId)}:`, hexResponse, parsed);
+
+ return response;
+ } catch (error) {
+ // Detect pairing issues - only during initial connection
+ if (this.isInitialConnection && error instanceof Error) {
+ const errorMsg = error.message.toLowerCase();
+ if (
+ errorMsg.includes('gatt server is disconnected') ||
+ (errorMsg.includes('writevaluewithresponse') && errorMsg.includes('gatt server is disconnected'))
+ ) {
+ throw new BluetoothPairingError(
+ 'Device not paired. To pair: long-press the Bluetooth button on the machine, then pair it using your operating system\'s Bluetooth settings. After pairing, try connecting again.'
+ );
+ }
+ }
+ throw error;
}
-
- console.log(`[RX] ${this.getCommandName(cmdId)}:`, hexResponse, parsed);
-
- return response;
});
}