expo-ble模块测试demo

This commit is contained in:
Yudi Xiao
2025-12-10 10:29:50 +08:00
parent 709c466792
commit 41f4080264
23 changed files with 3749 additions and 141 deletions

323
hooks/useBlePeripheral.ts Normal file
View File

@@ -0,0 +1,323 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import { Platform, PermissionsAndroid } from 'react-native';
import { BLE_UUIDS as PROTOCOL_UUIDS } from '../ble/index';
import { BlePeripheralManager, PeripheralEventCallback, DeviceInfo } from '@/ble/manager/BlePeripheralManager';
import { Buffer } from 'buffer';
interface PeripheralState {
isAdvertising: boolean;
connectedCentralCount: number;
logs: string[];
deviceInfo: DeviceInfo;
loading: {
advertising: boolean;
};
error: string | null;
}
const MAX_LOGS = 100;
const DEFAULT_DEVICE_INFO: DeviceInfo = {
devname: 'BLE Test Device',
version: '2.1.1',
activated: true,
brand: 0,
size: 1,
allspace: 1024,
freespace: 512,
};
export const useBlePeripheral = () => {
const [state, setState] = useState<PeripheralState>({
isAdvertising: false,
connectedCentralCount: 0,
logs: [],
deviceInfo: DEFAULT_DEVICE_INFO,
loading: {
advertising: false,
},
error: null,
});
// Ref to track peripheral manager
const peripheralManagerRef = useRef<BlePeripheralManager>(BlePeripheralManager.getInstance());
// Ref to track advertising state for cleanup
const isAdvertisingRef = useRef(false);
// Ref to track component mount state
const isMountedRef = useRef(true);
// Add log message with limit
const addLog = useCallback((message: string) => {
const timestamp = new Date().toLocaleTimeString();
setState(prev => {
const newLogs = [...prev.logs, `[${timestamp}] ${message}`];
const trimmedLogs = newLogs.length > MAX_LOGS
? newLogs.slice(-MAX_LOGS)
: newLogs;
return { ...prev, logs: trimmedLogs };
});
}, []);
// Set error with user visibility
const setError = useCallback((error: string | null) => {
setState(prev => ({ ...prev, error }));
if (error) {
addLog(`ERROR: ${error}`);
}
}, [addLog]);
// Initialize peripheral mode
useEffect(() => {
if (Platform.OS === 'web') {
addLog('BLE peripheral mode not supported on web platform');
return;
}
addLog('BLE Peripheral mode initialized (Real BLE)');
addLog('Service UUID: ' + PROTOCOL_UUIDS.SERVICE);
addLog('Write Characteristic UUID: ' + PROTOCOL_UUIDS.WRITE_CHARACTERISTIC);
addLog('Read Characteristic UUID: ' + PROTOCOL_UUIDS.READ_CHARACTERISTIC);
// Set up event callbacks
const callbacks: PeripheralEventCallback = {
onAdvertisingStateChange: (isAdvertising, error) => {
if (isMountedRef.current) {
setState(prev => ({
...prev,
isAdvertising,
loading: { ...prev.loading, advertising: false },
error: error || null
}));
addLog(`Advertising ${isAdvertising ? 'started' : 'stopped'}${error ? ` with error: ${error}` : ''}`);
}
},
onCentralConnected: (centralId) => {
if (isMountedRef.current) {
setState(prev => ({
...prev,
connectedCentralCount: peripheralManagerRef.current.getConnectedCentralsCount()
}));
addLog(`Central device connected: ${centralId}`);
}
},
onCentralDisconnected: (centralId) => {
if (isMountedRef.current) {
setState(prev => ({
...prev,
connectedCentralCount: peripheralManagerRef.current.getConnectedCentralsCount()
}));
addLog(`Central device disconnected: ${centralId}`);
}
},
onCharacteristicRead: (centralId, characteristicUuid, data) => {
if (isMountedRef.current) {
addLog(`Characteristic read by ${centralId}: ${characteristicUuid}`);
}
},
onCharacteristicWrite: (centralId, characteristicUuid, data) => {
if (isMountedRef.current) {
const jsonString = Buffer.from(data).toString('utf-8');
addLog(`Characteristic write by ${centralId}: ${characteristicUuid}`);
addLog(`Data: ${jsonString}`);
}
}
};
peripheralManagerRef.current.addCallback(callbacks);
return () => {
isMountedRef.current = false;
if (isAdvertisingRef.current) {
stopAdvertising();
}
peripheralManagerRef.current.removeCallback(callbacks);
};
}, [addLog]);
// Check and request necessary permissions for Android 12+
const checkPermissions = useCallback(async (): Promise<boolean> => {
if (Platform.OS !== 'android') {
return true; // iOS handles permissions differently
}
try {
const apiLevel = Platform.Version;
if (typeof apiLevel === 'number' && apiLevel >= 31) {
// Android 12+ (API 31+) requires multiple Bluetooth permissions for peripheral mode
const requiredPermissions = [
PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE,
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN
];
for (const permission of requiredPermissions) {
const granted = await PermissionsAndroid.check(permission);
if (!granted) {
const result = await PermissionsAndroid.request(permission, {
title: 'Bluetooth Permission Required',
message: 'This app needs Bluetooth permissions to act as a BLE peripheral device.',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
});
if (result !== PermissionsAndroid.RESULTS.GRANTED) {
setError(`${permission} is required for peripheral mode`);
return false;
}
}
}
}
return true;
} catch (error) {
console.error('Permission check failed:', error);
setError('Failed to check Bluetooth permissions');
return false;
}
}, [setError]);
// Start advertising as peripheral (real BLE)
const startAdvertising = useCallback(async () => {
if (Platform.OS === 'web') {
setError('BLE peripheral not supported on web');
return;
}
if (isAdvertisingRef.current) {
addLog('Already advertising as peripheral');
return;
}
try {
setError(null);
setState(prev => ({
...prev,
loading: { ...prev.loading, advertising: true }
}));
// Check permissions first
const hasPermissions = await checkPermissions();
if (!hasPermissions) {
if (isMountedRef.current) {
setState(prev => ({
...prev,
loading: { ...prev.loading, advertising: false }
}));
}
return;
}
const currentDeviceInfo = peripheralManagerRef.current.getDeviceInfo();
addLog('Starting BLE advertising...');
addLog('Device Name: ' + currentDeviceInfo.devname);
addLog('Advertising with service UUID: ' + PROTOCOL_UUIDS.SERVICE);
// iOS platform limitation warning
if (Platform.OS === 'ios') {
addLog('⚠️ iOS: Advertising will stop when app goes to background');
}
peripheralManagerRef
await peripheralManagerRef.current.startAdvertising(currentDeviceInfo.devname);
isAdvertisingRef.current = true;
} catch (error) {
if (isMountedRef.current) {
const errorMsg = `Failed to start advertising: ${error}`;
addLog(errorMsg);
setError(errorMsg);
setState(prev => ({
...prev,
loading: { ...prev.loading, advertising: false }
}));
}
}
}, [addLog, setError, checkPermissions]);
// Stop advertising
const stopAdvertising = useCallback(async () => {
if (!isAdvertisingRef.current) {
addLog('Not currently advertising');
return;
}
try {
addLog('Stopping BLE advertising...');
await peripheralManagerRef.current.stopAdvertising();
isAdvertisingRef.current = false;
setState(prev => ({
...prev,
isAdvertising: false,
connectedCentralCount: 0
}));
addLog('BLE advertising stopped');
} catch (error) {
addLog(`Failed to stop advertising: ${error}`);
}
}, [addLog]);
// Get characteristic read value (for testing/debugging: Shows what response will be sent when central reads this characteristic)
const getCharacteristicReadValue = useCallback((characteristicUuid: string) => {
if (!isAdvertisingRef.current) {
addLog('Cannot get read value - not advertising');
return null;
}
addLog(`Getting characteristic read value: ${characteristicUuid}`);
if (characteristicUuid === PROTOCOL_UUIDS.READ_CHARACTERISTIC) {
// Return device info as JSON - use manager to get latest data instead of stale state
const response = {
type: 0x02, // Device info response type
data: peripheralManagerRef.current.getDeviceInfo()
};
addLog('Returning device info response');
return JSON.stringify(response);
}
addLog('Unknown characteristic for read');
return null;
}, [addLog]);
// Update device info
const updateDeviceInfo = useCallback((updates: Partial<DeviceInfo>) => {
peripheralManagerRef.current.updateDeviceInfo(updates);
// Sync state with manager to avoid stale state issues
setState(prev => ({
...prev,
deviceInfo: peripheralManagerRef.current.getDeviceInfo()
}));
addLog('Device info updated');
}, [addLog]);
// Reset device info
const resetDeviceInfo = useCallback(() => {
peripheralManagerRef.current.resetDeviceInfo();
// Sync state with manager to avoid stale state issues
setState(prev => ({
...prev,
deviceInfo: peripheralManagerRef.current.getDeviceInfo()
}));
addLog('Device info reset to defaults');
}, [addLog]);
// Clear logs
const clearLogs = useCallback(() => {
setState(prev => ({ ...prev, logs: [] }));
}, []);
return {
...state,
startAdvertising,
stopAdvertising,
getCharacteristicReadValue,
updateDeviceInfo,
resetDeviceInfo,
clearLogs,
};
};