// Real BLE Manager для физических устройств import { BleManager, Device, State } from 'react-native-ble-plx'; import { PermissionsAndroid, Platform } from 'react-native'; import { IBLEManager, WPDevice, WiFiNetwork, WiFiStatus, BLE_CONFIG, BLE_COMMANDS } from './types'; import base64 from 'react-native-base64'; export class RealBLEManager implements IBLEManager { private manager: BleManager; private connectedDevices = new Map(); private scanning = false; constructor() { this.manager = new BleManager(); } // Check and request permissions private async requestPermissions(): Promise { if (Platform.OS === 'ios') { // iOS handles permissions automatically via Info.plist return true; } if (Platform.OS === 'android') { if (Platform.Version >= 31) { // Android 12+ const granted = await PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN!, PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT!, PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION!, ]); return Object.values(granted).every( status => status === PermissionsAndroid.RESULTS.GRANTED ); } else { // Android < 12 const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION! ); return granted === PermissionsAndroid.RESULTS.GRANTED; } } return false; } // Check if Bluetooth is enabled private async isBluetoothEnabled(): Promise { const state = await this.manager.state(); return state === State.PoweredOn; } async scanDevices(): Promise { const hasPermission = await this.requestPermissions(); if (!hasPermission) { throw new Error('Bluetooth permissions not granted'); } const isEnabled = await this.isBluetoothEnabled(); if (!isEnabled) { throw new Error('Bluetooth is disabled. Please enable it in settings.'); } const foundDevices = new Map(); return new Promise((resolve, reject) => { this.scanning = true; this.manager.startDeviceScan( null, { allowDuplicates: false }, (error, device) => { if (error) { this.scanning = false; reject(error); return; } if (device && device.name?.startsWith(BLE_CONFIG.DEVICE_NAME_PREFIX)) { // Parse well_id from name (WP_497_81a14c -> 497) const wellIdMatch = device.name.match(/WP_(\d+)_/); const wellId = wellIdMatch ? parseInt(wellIdMatch[1], 10) : undefined; // Extract MAC from device name (last part after underscore) const macMatch = device.name.match(/_([a-fA-F0-9]{6})$/); const mac = macMatch ? macMatch[1].toUpperCase() : ''; foundDevices.set(device.id, { id: device.id, name: device.name, mac: mac, rssi: device.rssi || -100, wellId, }); } } ); // Stop scan after timeout setTimeout(() => { this.stopScan(); resolve(Array.from(foundDevices.values())); }, BLE_CONFIG.SCAN_TIMEOUT); }); } stopScan(): void { if (this.scanning) { this.manager.stopDeviceScan(); this.scanning = false; } } async connectDevice(deviceId: string): Promise { try { const device = await this.manager.connectToDevice(deviceId); await device.discoverAllServicesAndCharacteristics(); this.connectedDevices.set(deviceId, device); return true; } catch (error) { console.error('[BLE] Connection failed:', error); return false; } } async disconnectDevice(deviceId: string): Promise { const device = this.connectedDevices.get(deviceId); if (device) { await device.cancelConnection(); this.connectedDevices.delete(deviceId); } } isDeviceConnected(deviceId: string): boolean { return this.connectedDevices.has(deviceId); } async sendCommand(deviceId: string, command: string): Promise { const device = this.connectedDevices.get(deviceId); if (!device) { throw new Error('Device not connected'); } return new Promise(async (resolve, reject) => { let responseReceived = false; let response = ''; try { // Subscribe to notifications device.monitorCharacteristicForService( BLE_CONFIG.SERVICE_UUID, BLE_CONFIG.CHAR_UUID, (error, characteristic) => { if (error) { if (!responseReceived) { responseReceived = true; reject(error); } return; } if (characteristic?.value) { const decoded = base64.decode(characteristic.value); response = decoded; responseReceived = true; resolve(decoded); } } ); // Send command const encoded = base64.encode(command); await device.writeCharacteristicWithResponseForService( BLE_CONFIG.SERVICE_UUID, BLE_CONFIG.CHAR_UUID, encoded ); // Timeout setTimeout(() => { if (!responseReceived) { responseReceived = true; reject(new Error('Command timeout')); } }, BLE_CONFIG.COMMAND_TIMEOUT); } catch (error) { reject(error); } }); } async getWiFiList(deviceId: string): Promise { // Step 1: Unlock device const unlockResponse = await this.sendCommand(deviceId, BLE_COMMANDS.PIN_UNLOCK); if (!unlockResponse.includes('ok')) { throw new Error('Failed to unlock device'); } // Step 2: Get WiFi list const listResponse = await this.sendCommand(deviceId, BLE_COMMANDS.GET_WIFI_LIST); // Parse response: "mac,XXXXXX|w|COUNT|SSID1,RSSI1|SSID2,RSSI2|..." const parts = listResponse.split('|'); if (parts.length < 3) { throw new Error('Invalid WiFi list response'); } const count = parseInt(parts[2], 10); if (count < 0) { if (count === -1) { throw new Error('WiFi scan in progress, please wait'); } if (count === -2) { return []; // No networks found } } const networks: WiFiNetwork[] = []; for (let i = 3; i < parts.length; i++) { const [ssid, rssiStr] = parts[i].split(','); if (ssid && rssiStr) { networks.push({ ssid: ssid.trim(), rssi: parseInt(rssiStr, 10), }); } } // Sort by signal strength (strongest first) return networks.sort((a, b) => b.rssi - a.rssi); } async setWiFi(deviceId: string, ssid: string, password: string): Promise { // Step 1: Unlock device const unlockResponse = await this.sendCommand(deviceId, BLE_COMMANDS.PIN_UNLOCK); if (!unlockResponse.includes('ok')) { throw new Error('Failed to unlock device'); } // Step 2: Set WiFi credentials const command = `${BLE_COMMANDS.SET_WIFI}|${ssid},${password}`; const setResponse = await this.sendCommand(deviceId, command); // Parse response: "mac,XXXXXX|W|ok" or "mac,XXXXXX|W|fail" return setResponse.includes('|W|ok'); } async getCurrentWiFi(deviceId: string): Promise { // Step 1: Unlock device const unlockResponse = await this.sendCommand(deviceId, BLE_COMMANDS.PIN_UNLOCK); if (!unlockResponse.includes('ok')) { throw new Error('Failed to unlock device'); } // Step 2: Get current WiFi status const statusResponse = await this.sendCommand(deviceId, BLE_COMMANDS.GET_WIFI_STATUS); // Parse response: "mac,XXXXXX|a|SSID,RSSI" or "mac,XXXXXX|a|,0" (not connected) const parts = statusResponse.split('|'); if (parts.length < 3) { return null; } const [ssid, rssiStr] = parts[2].split(','); if (!ssid || ssid.trim() === '') { return null; // Not connected } return { ssid: ssid.trim(), rssi: parseInt(rssiStr, 10), connected: true, }; } async rebootDevice(deviceId: string): Promise { // Step 1: Unlock device await this.sendCommand(deviceId, BLE_COMMANDS.PIN_UNLOCK); // Step 2: Reboot (device will disconnect) await this.sendCommand(deviceId, BLE_COMMANDS.REBOOT); // Remove from connected devices this.connectedDevices.delete(deviceId); } }