🐛 Device not showing up in scan after disconnecting
Prerequisites
- [x] I checked the documentation and FAQ without finding a solution
- [x] 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
- Connect to BLE device
- Disconnect from BLE device
- 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
Strangely, when I open the "Nrf Connect for Mobile" app, scan BLE devices and go back to my app, the device suddenly shows up again in my own app. Very strange. Rebooting the app also fixes the issue.
same problem
@olalonde do you trigger startDeviceScan again after disconnecting? It should return a full list of reachable devices
@aliberski me and my team also look to be seeing this issue right now
Were you able to get this working, @olalonde?
experiencing the same thing on android only as well
I face this issue when disconnecting from a device shortly after the connection had been established. My workaround for now is to make sure to keep the connection for at least 5-10 seconds before allowing to disconnect from the device. Then I wait for around 20 seconds before scanning for the device again. Adding timeouts does not feel right, and the problem still occurs from time to time, but it helps a bit.
@olalonde @claudioPrisco @animaonline @kent-williams @irenmax I may have a potential patch but need assistance validating it properly: #1296
The way discoveredDevices is handled is a little suspect.
@benletchford, thanks for the quick fix attempt!
Unfortunately, I'm not seeing improved behavior yet with the branch. I'm testing with two devices, and I've noticed that the devices eventually show up in the scans if you wait for 30 sec or more.
I'm interested to see what others are seeing.
This issue only seems to affect Android. I added scanMode: ScanMode.Balanced to the Android scan options, and it fixed the problem. Hope this helps!
const scanOptions: ScanOptions = {
callbackType: ScanCallbackType.AllMatches,
...(Platform.OS === 'android' && { scanMode: ScanMode.Balanced }),
};
manager.startDeviceScan(null, scanOptions, (error, device) => {
// …
});
Decreasing the scan latency with ScanMode pretty much fixed things for me. Thanks @yoshifumi4423 !