WellNuo/contexts/BLEContext.tsx
Sergei 86e73f004d Add BLE infrastructure for sensor connectivity
Core BLE system:
- BLEManager: Real BLE device scanning and connection (iOS/Android)
- MockBLEManager: Simulator-safe mock for development
- BLEContext: React context for BLE state management
- BLEProvider: Added to app/_layout.tsx

Bluetooth permissions:
- iOS: NSBluetoothAlwaysUsageDescription, NSBluetoothPeripheralUsageDescription
- Android: BLUETOOTH, BLUETOOTH_ADMIN, BLUETOOTH_CONNECT, BLUETOOTH_SCAN, ACCESS_FINE_LOCATION

Dependencies:
- react-native-ble-plx@3.5.0
- expo-device@8.0.10
- react-native-base64@0.2.2

Simulator support:
- Auto-detects iOS simulator via expo-device
- Falls back to MockBLEManager with fake devices
- No crashes or permission errors in development
2026-01-14 19:07:44 -08:00

172 lines
5.0 KiB
TypeScript

// BLE Context - Global state for Bluetooth management
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
import { bleManager, WPDevice, WiFiNetwork, WiFiStatus, isBLEAvailable } from '@/services/ble';
interface BLEContextType {
// State
foundDevices: WPDevice[];
isScanning: boolean;
connectedDevices: Set<string>;
isBLEAvailable: boolean;
error: string | null;
// Actions
scanDevices: () => Promise<void>;
stopScan: () => void;
connectDevice: (deviceId: string) => Promise<boolean>;
disconnectDevice: (deviceId: string) => Promise<void>;
getWiFiList: (deviceId: string) => Promise<WiFiNetwork[]>;
setWiFi: (deviceId: string, ssid: string, password: string) => Promise<boolean>;
getCurrentWiFi: (deviceId: string) => Promise<WiFiStatus | null>;
rebootDevice: (deviceId: string) => Promise<void>;
clearError: () => void;
}
const BLEContext = createContext<BLEContextType | undefined>(undefined);
export function BLEProvider({ children }: { children: ReactNode }) {
const [foundDevices, setFoundDevices] = useState<WPDevice[]>([]);
const [isScanning, setIsScanning] = useState(false);
const [connectedDevices, setConnectedDevices] = useState<Set<string>>(new Set());
const [error, setError] = useState<string | null>(null);
const scanDevices = useCallback(async () => {
try {
setError(null);
setIsScanning(true);
const devices = await bleManager.scanDevices();
// Sort by RSSI (strongest first)
const sorted = devices.sort((a, b) => b.rssi - a.rssi);
setFoundDevices(sorted);
} catch (err: any) {
console.error('[BLEContext] Scan error:', err);
setError(err.message || 'Failed to scan for devices');
throw err;
} finally {
setIsScanning(false);
}
}, []);
const stopScan = useCallback(() => {
bleManager.stopScan();
setIsScanning(false);
}, []);
const connectDevice = useCallback(async (deviceId: string): Promise<boolean> => {
try {
setError(null);
const success = await bleManager.connectDevice(deviceId);
if (success) {
setConnectedDevices(prev => new Set(prev).add(deviceId));
}
return success;
} catch (err: any) {
console.error('[BLEContext] Connect error:', err);
setError(err.message || 'Failed to connect to device');
return false;
}
}, []);
const disconnectDevice = useCallback(async (deviceId: string): Promise<void> => {
try {
await bleManager.disconnectDevice(deviceId);
setConnectedDevices(prev => {
const next = new Set(prev);
next.delete(deviceId);
return next;
});
} catch (err: any) {
console.error('[BLEContext] Disconnect error:', err);
setError(err.message || 'Failed to disconnect device');
}
}, []);
const getWiFiList = useCallback(async (deviceId: string): Promise<WiFiNetwork[]> => {
try {
setError(null);
return await bleManager.getWiFiList(deviceId);
} catch (err: any) {
console.error('[BLEContext] Get WiFi list error:', err);
setError(err.message || 'Failed to get WiFi networks');
throw err;
}
}, []);
const setWiFi = useCallback(
async (deviceId: string, ssid: string, password: string): Promise<boolean> => {
try {
setError(null);
return await bleManager.setWiFi(deviceId, ssid, password);
} catch (err: any) {
console.error('[BLEContext] Set WiFi error:', err);
setError(err.message || 'Failed to configure WiFi');
throw err;
}
},
[]
);
const getCurrentWiFi = useCallback(
async (deviceId: string): Promise<WiFiStatus | null> => {
try {
setError(null);
return await bleManager.getCurrentWiFi(deviceId);
} catch (err: any) {
console.error('[BLEContext] Get current WiFi error:', err);
setError(err.message || 'Failed to get current WiFi');
throw err;
}
},
[]
);
const rebootDevice = useCallback(async (deviceId: string): Promise<void> => {
try {
setError(null);
await bleManager.rebootDevice(deviceId);
// Remove from connected devices
setConnectedDevices(prev => {
const next = new Set(prev);
next.delete(deviceId);
return next;
});
} catch (err: any) {
console.error('[BLEContext] Reboot error:', err);
setError(err.message || 'Failed to reboot device');
throw err;
}
}, []);
const clearError = useCallback(() => {
setError(null);
}, []);
const value: BLEContextType = {
foundDevices,
isScanning,
connectedDevices,
isBLEAvailable,
error,
scanDevices,
stopScan,
connectDevice,
disconnectDevice,
getWiFiList,
setWiFi,
getCurrentWiFi,
rebootDevice,
clearError,
};
return <BLEContext.Provider value={value}>{children}</BLEContext.Provider>;
}
export function useBLE() {
const context = useContext(BLEContext);
if (context === undefined) {
throw new Error('useBLE must be used within a BLEProvider');
}
return context;
}