Skip to content

🐛 Device not showing up in scan after disconnecting #1279

@olalonde

Description

@olalonde

Prerequisites

  • I checked the documentation and FAQ without finding a solution
  • I checked to make sure that this issue has not already been filed

Expected Behavior

I hit a weird bug on Android where after disconnecting from a BLE device, it no longer shows up in the device scan. I have confirmed through "nrf connect for mobile" that the device is indeed advertising and scannable. Sometimes the device will suddenly show up during the scan again. Very strange.

Current Behavior

It stops showing the device in the scan after disconnecting from it.

Library version

3.5.0

Device

Android 15 (pixel 9 pro)

Environment info

info Fetching system and libraries information...
System:
  OS: macOS 15.3.1
  CPU: (8) arm64 Apple M1
  Memory: 131.61 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 23.9.0
    path: ~/.local/state/fnm_multishells/42435_1741810219145/bin/node
  Yarn: Not Found
  npm:
    version: 10.9.2
    path: ~/.local/state/fnm_multishells/42435_1741810219145/bin/npm
  Watchman:
    version: 2025.03.03.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /opt/homebrew/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 24.2
      - iOS 18.2
      - macOS 15.2
      - tvOS 18.2
      - visionOS 2.2
      - watchOS 11.2
  Android SDK:
    API Levels:
      - "21"
      - "26"
      - "27"
      - "31"
      - "32"
      - "34"
      - "35"
    Build Tools:
      - 30.0.2
      - 30.0.3
      - 31.0.0
      - 34.0.0
      - 35.0.0
    System Images:
      - android-28 | Google ARM64-V8a Play ARM 64 v8a
      - android-31 | Google APIs ARM 64 v8a
      - android-34 | Google Play ARM 64 v8a
    Android NDK: 26.1.10909125
IDEs:
  Android Studio: 2024.1 AI-241.18034.62.2411.12071903
  Xcode:
    version: 16.2/16C5032a
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.11
    path: /usr/bin/javac
  Ruby:
    version: 2.6.10
    path: /usr/bin/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 18.0.0
    wanted: ^18.0.0
  react:
    installed: 18.3.1
    wanted: 18.3.1
  react-native:
    installed: 0.76.7
    wanted: 0.76.7
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

info React Native v0.78.0 is now available (your project is running on v0.76.7).
info Changelog: https://github.com/facebook/react-native/releases/tag/v0.78.0
info Diff: https://react-native-community.github.io/upgrade-helper/?from=0.76.7&to=0.78.0
info For more info, check out "https://reactnative.dev/docs/upgrading?os=macos".

Steps to reproduce

  1. Connect to BLE device
  2. Disconnect from BLE device
  3. Scan

Formatted code sample or link to a repository

import { BleManager, Device, State } from 'react-native-ble-plx';
import { Platform, PermissionsAndroid } from 'react-native';
import { Buffer } from "buffer";

// Nordic UART Service UUID
const NUS_SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';
// Nordic UART TX characteristic (for receiving data from the device)
const NUS_TX_CHAR_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e';
// Nordic UART RX characteristic (for sending data to the device)
const NUS_RX_CHAR_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e';

class BleService {
  private manager: BleManager;
  private device: Device | null = null;
  private isConnecting: boolean = false;
  private isScanning: boolean = false;

  constructor() {
    this.manager = new BleManager();
  }

  async requestPermissions(): Promise<boolean> {
    if (Platform.OS === 'android') {
      try {
        const permissionsToRequest = [
          PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
          PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT
        ].filter(Boolean); // Filter out undefined permissions

        const results = await PermissionsAndroid.requestMultiple(permissionsToRequest);
        
        return Object.values(results).every(
          result => result === PermissionsAndroid.RESULTS.GRANTED
        );
      } catch (error) {
        console.error('Error requesting permissions:', error);
        return false;
      }
    }
    return true; // iOS handles permissions differently via Info.plist
  }

  async startScan(onDeviceFound: (device: Device) => void): Promise<void> {
    if (this.isScanning) return;
    
    try {
      this.isScanning = true;
      console.log('Starting BLE scan...');
      
      // Check if Bluetooth is enabled
      const state = await this.manager.state();
      if (state !== State.PoweredOn) {
        console.log('Bluetooth is not powered on');
        this.isScanning = false;
        return;
      }
      
      // Request permissions
      const hasPermission = await this.requestPermissions();
      if (!hasPermission) {
        console.log('Bluetooth permissions not granted');
        this.isScanning = false;
        return;
      }

      // Start scanning for BLE devices
      this.manager.startDeviceScan(
        null, // null means scan for all services
        { allowDuplicates: false },
        (error, device) => {
          if (error) {
            console.error('Scan error:', error);
            this.isScanning = false;
            return;
          }

          if (device && device.name) {
            console.log(`Found device: ${device.name} (${device.id})`);
            onDeviceFound(device);
          }
        }
      );
    } catch (error) {
      console.error('Error starting scan:', error);
      this.isScanning = false;
    }
  }

  stopScan(): void {
    if (this.isScanning) {
      this.manager.stopDeviceScan();
      this.isScanning = false;
      console.log('BLE scan stopped');
    }
  }

  async connectToDevice(deviceId: string): Promise<boolean> {
    if (this.isConnecting) return false;
    
    try {
      this.isConnecting = true;
      console.log(`Connecting to device: ${deviceId}`);
      
      // Stop scanning before connecting
      this.stopScan();
      
      // Connect to the device
      const device = await this.manager.connectToDevice(deviceId);
      console.log('Connected, discovering services and characteristics...');
      
      // Discover services and characteristics
      await device.discoverAllServicesAndCharacteristics();
      
      // Check if device has the Nordic UART Service
      const services = await device.services();
      const hasNusService = services.some(service => 
        service.uuid.toLowerCase() === NUS_SERVICE_UUID
      );
      
      if (!hasNusService) {
        console.log('Device does not have the Nordic UART Service');
        await device.cancelConnection();
        this.isConnecting = false;
        return false;
      }
      
      // Setup notification for receiving data
      await this.setupNotifications(device);
      
      this.device = device;
      this.isConnecting = false;
      console.log('Connected successfully');
      return true;
    } catch (error) {
      console.error('Connection error:', error);
      this.isConnecting = false;
      return false;
    }
  }

  async setupNotifications(device: Device): Promise<void> {
    try {
      // Monitor for incoming data on the TX characteristic
      device.monitorCharacteristicForService(
        NUS_SERVICE_UUID,
        NUS_TX_CHAR_UUID,
        (error, characteristic) => {
          if (error) {
            console.error('Notification error:', error);
            return;
          }
          
          if (characteristic?.value) {
            const decodedValue = this.decodeBase64(characteristic.value);
            console.log('Received:', decodedValue);
          }
        }
      );
    } catch (error) {
      console.error('Error setting up notifications:', error);
    }
  }

  decodeBase64(data: string): string {
    try {
      return Buffer.from(data, 'base64').toString('utf8');
    } catch (error) {
      console.error('Error decoding base64:', error);
      return '';
    }
  }

  async sendCommand(command: string): Promise<boolean> {
    if (!this.device) {
      console.log('No device connected');
      return false;
    }
    
    try {
      // Convert string to base64 encoded data
      const data = Buffer.from(command).toString('base64');
      
      // Write to the RX characteristic
      await this.device.writeCharacteristicWithResponseForService(
        NUS_SERVICE_UUID,
        NUS_RX_CHAR_UUID,
        data
      );
      console.log(`Command sent: ${command}`);
      return true;
    } catch (error) {
      console.error('Error sending command:', error);
      return false;
    }
  }

  async startRelay(): Promise<boolean> {
    return this.sendCommand('start');
  }

  async stopRelay(): Promise<boolean> {
    return this.sendCommand('stop');
  }

  async disconnect(): Promise<void> {
    if (this.device) {
      try {
        await this.device.cancelConnection();
        console.log('Disconnected from device');
      } catch (error) {
        console.error('Error disconnecting:', error);
      } finally {
        this.device = null;
      }
    }
  }

  cleanup(): void {
    this.stopScan();
    this.disconnect();
    this.manager.destroy();
  }
}

// Create singleton instance
const bleService = new BleService();
export default bleService;

Relevant log output

(NOBRIDGE) LOG  Starting BLE scan...
 (NOBRIDGE) LOG  Found device: BLE_Relay (E7:15:B1:4E:A4:59)
 (NOBRIDGE) LOG  Found device: BLE_Relay (E7:15:B1:4E:A4:59)
 (NOBRIDGE) LOG  Found device: BLE_Relay (E7:15:B1:4E:A4:59)
 (NOBRIDGE) LOG  Found device: BLE_Relay (E7:15:B1:4E:A4:59)
 (NOBRIDGE) LOG  Connecting to device: E7:15:B1:4E:A4:59
 (NOBRIDGE) LOG  BLE scan stopped
 (NOBRIDGE) LOG  Connected, discovering services and characteristics...
 (NOBRIDGE) LOG  Connected successfully
 (NOBRIDGE) LOG  Disconnected from device
 (NOBRIDGE) ERROR  Notification error: [BleError: Device E7:15:B1:4E:A4:59 was disconnected]
 (NOBRIDGE) LOG  Starting BLE scan...
 (NOBRIDGE) LOG  Found device: M10K3QE (78:9C:85:1B:FF:AF)
 (NOBRIDGE) LOG  Found device: M10K3QE (78:9C:85:1B:FF:AF)
 (NOBRIDGE) LOG  Found device: M10K3QE (78:9C:85:1B:FF:AF)
etc.

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions