expo-ble模块测试demo
This commit is contained in:
222
ble/core/BleClient.ts
Normal file
222
ble/core/BleClient.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import {BleManager, Device, Characteristic, BleError as PlxError, ScanOptions} from 'react-native-ble-plx';
|
||||
import {Platform, PermissionsAndroid} from 'react-native';
|
||||
import {BleDevice, ConnectionState, BleError, ScanResult} from './types';
|
||||
|
||||
export class BleClient {
|
||||
private static instance: BleClient;
|
||||
private manager: BleManager | null = null;
|
||||
private connectedDevice: Device | null = null;
|
||||
|
||||
// Simple event system
|
||||
private listeners: Map<string, Set<Function>> = new Map();
|
||||
|
||||
private constructor() {
|
||||
if (Platform.OS !== 'web') {
|
||||
this.manager = new BleManager();
|
||||
}
|
||||
}
|
||||
|
||||
public static getInstance(): BleClient {
|
||||
if (!BleClient.instance) {
|
||||
BleClient.instance = new BleClient();
|
||||
}
|
||||
return BleClient.instance;
|
||||
}
|
||||
|
||||
public addListener(event: string, callback: Function) {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, new Set());
|
||||
}
|
||||
this.listeners.get(event)!.add(callback);
|
||||
}
|
||||
|
||||
public removeListener(event: string, callback: Function) {
|
||||
if (this.listeners.has(event)) {
|
||||
this.listeners.get(event)!.delete(callback);
|
||||
}
|
||||
}
|
||||
|
||||
private emit(event: string, ...args: any[]) {
|
||||
if (this.listeners.has(event)) {
|
||||
this.listeners.get(event)!.forEach(cb => cb(...args));
|
||||
}
|
||||
}
|
||||
|
||||
public async startScan(
|
||||
serviceUUIDs: string[] | null = null,
|
||||
options: ScanOptions = {},
|
||||
onDeviceFound: (result: ScanResult) => void
|
||||
): Promise<void> {
|
||||
if (!this.manager) {
|
||||
console.warn('BLE not supported on web');
|
||||
return;
|
||||
}
|
||||
const state = await this.manager.state();
|
||||
if (state !== 'PoweredOn') {
|
||||
throw new Error(`Bluetooth is not powered on. State: ${state}`);
|
||||
}
|
||||
|
||||
this.manager.startDeviceScan(serviceUUIDs, options, (error, device) => {
|
||||
if (error) {
|
||||
this.emit('scanError', error);
|
||||
return;
|
||||
}
|
||||
if (device) {
|
||||
onDeviceFound({
|
||||
device,
|
||||
rssi: device.rssi,
|
||||
localName: device.name
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getConnectedDevices(serviceUUIDs: string[]): Promise<BleDevice[]> {
|
||||
if (!this.manager) return [];
|
||||
try {
|
||||
const devices = await this.manager.connectedDevices(serviceUUIDs);
|
||||
return devices as BleDevice[];
|
||||
} catch (e) {
|
||||
throw this.normalizeError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public stopScan() {
|
||||
if (!this.manager) return;
|
||||
this.manager.stopDeviceScan();
|
||||
}
|
||||
|
||||
public async connect(deviceId: string): Promise<BleDevice> {
|
||||
if (!this.manager) throw new Error('BLE not supported on web');
|
||||
try {
|
||||
this.emit('connectionStateChange', {deviceId, state: ConnectionState.CONNECTING});
|
||||
const device = await this.manager.connectToDevice(deviceId);
|
||||
this.connectedDevice = await device.discoverAllServicesAndCharacteristics();
|
||||
this.emit('connectionStateChange', {deviceId, state: ConnectionState.CONNECTED});
|
||||
|
||||
// Handle disconnection monitoring
|
||||
device.onDisconnected((error, disconnectedDevice) => {
|
||||
this.connectedDevice = null;
|
||||
this.emit('connectionStateChange', {
|
||||
deviceId: disconnectedDevice.id,
|
||||
state: ConnectionState.DISCONNECTED
|
||||
});
|
||||
this.emit('disconnected', disconnectedDevice);
|
||||
});
|
||||
|
||||
return this.connectedDevice;
|
||||
} catch (error: any) {
|
||||
this.emit('connectionStateChange', {deviceId, state: ConnectionState.DISCONNECTED});
|
||||
throw this.normalizeError(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async disconnect(deviceId?: string): Promise<void> {
|
||||
const id = deviceId || this.connectedDevice?.id;
|
||||
if (!id) return;
|
||||
if (!this.manager) return;
|
||||
|
||||
try {
|
||||
this.emit('connectionStateChange', {deviceId: id, state: ConnectionState.DISCONNECTING});
|
||||
await this.manager.cancelDeviceConnection(id);
|
||||
// onDisconnected callback will handle the state update
|
||||
} catch (error: any) {
|
||||
throw this.normalizeError(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async read(
|
||||
deviceId: string,
|
||||
serviceUUID: string,
|
||||
characteristicUUID: string
|
||||
): Promise<string> {
|
||||
if (!this.manager) throw new Error('BLE not supported on web');
|
||||
try {
|
||||
const char = await this.manager.readCharacteristicForDevice(
|
||||
deviceId,
|
||||
serviceUUID,
|
||||
characteristicUUID
|
||||
);
|
||||
return char.value || ''; // Base64 string
|
||||
} catch (e) {
|
||||
throw this.normalizeError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public async write(
|
||||
deviceId: string,
|
||||
serviceUUID: string,
|
||||
characteristicUUID: string,
|
||||
dataBase64: string,
|
||||
response: boolean = true
|
||||
): Promise<Characteristic> {
|
||||
if (!this.manager) throw new Error('BLE not supported on web');
|
||||
let result: Characteristic | null;
|
||||
try {
|
||||
if (response) {
|
||||
result = await this.manager.writeCharacteristicWithResponseForDevice(
|
||||
deviceId, serviceUUID, characteristicUUID, dataBase64
|
||||
);
|
||||
} else {
|
||||
result = await this.manager.writeCharacteristicWithoutResponseForDevice(
|
||||
deviceId, serviceUUID, characteristicUUID, dataBase64
|
||||
);
|
||||
}
|
||||
return result
|
||||
} catch (e) {
|
||||
throw this.normalizeError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public async monitor(
|
||||
deviceId: string,
|
||||
serviceUUID: string,
|
||||
characteristicUUID: string,
|
||||
listener: (error: BleError | null, value: string | null) => void
|
||||
) {
|
||||
if (!this.manager) {
|
||||
listener({message: 'BLE not supported on web', errorCode: 0, reason: null}, null);
|
||||
return {
|
||||
remove: () => {
|
||||
}
|
||||
};
|
||||
}
|
||||
return this.manager.monitorCharacteristicForDevice(
|
||||
deviceId,
|
||||
serviceUUID,
|
||||
characteristicUUID,
|
||||
(error, char) => {
|
||||
if (error) {
|
||||
listener(this.normalizeError(error), null);
|
||||
} else {
|
||||
listener(null, char?.value || null);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async requestMtu(deviceId: string, mtu: number): Promise<number> {
|
||||
if (!this.manager) return 23;
|
||||
try {
|
||||
const device = await this.manager.requestMTUForDevice(deviceId, mtu);
|
||||
return device.mtu;
|
||||
} catch (e) {
|
||||
console.warn("MTU negotiation failed", e);
|
||||
// iOS doesn't allow explicit MTU request usually, so we might ignore or return default
|
||||
return 23;
|
||||
}
|
||||
}
|
||||
|
||||
public getConnectedDevice(): Device | null {
|
||||
return this.connectedDevice;
|
||||
}
|
||||
|
||||
private normalizeError(e: any): BleError {
|
||||
// wrapper to convert PlxError to our BleError
|
||||
return {
|
||||
errorCode: e.errorCode || 0,
|
||||
message: e.message || 'Unknown error',
|
||||
reason: e.reason
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user