react-native-ble-scanner-kit
v2.0.2
Published
A React Native module for Bluetooth Low Energy (BLE) device scanning and management with distance estimation and device tracking capabilities
Maintainers
Readme
react-native-ble-scanner-kit
Proprietary software — Copyright (c) 2024 Ant-Tech. All rights reserved.
Unauthorized use is prohibited. See LICENSE for details.
npm: react-native-ble-scanner-kit
A React Native module for Bluetooth Low Energy (BLE) scanning with real-time distance estimation, per-device tracking, and proximity-based vibration/sound alerts. Native scanning uses SentiDriveBleModule (CoreBluetooth on iOS, BluetoothLeScanner on Android); filtering, Kalman smoothing, session guards, and pulse logic run in TypeScript.
Version 2.x is the New Architecture–oriented implementation (Turbo Module / bridgeless-friendly native patterns). It is published as 2.0.0 and upward on npm (previous line was 1.0.x).
Requirements
| Dependency | Version |
| --- | --- |
| React Native | >= 0.73.0 |
| React | >= 18.0.0 |
| Android minSdk | 24 |
| iOS | 13.4+ |
Optional: react-native-sound-player — required only if you use scanConfig.enableSound / proximity tones.
Installation
npm install react-native-ble-scanner-kit
# or
yarn add react-native-ble-scanner-kitFor proximity sound, also install and link audio in the host app:
npm install react-native-sound-playerReact Native autolinking picks up the library. Rebuild the native app after adding the dependency.
iOS
cd ios && pod installAdd to Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to scan for nearby BLE devices.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app uses Bluetooth to scan for nearby BLE devices.</string>Android
The library’s AndroidManifest.xml is merged into your app and declares BLE-related permissions (e.g. BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_FINE_LOCATION where applicable, BLUETOOTH_SCAN, BLUETOOTH_CONNECT). Runtime BLE permissions on Android are requested from JavaScript when you call initializeBluetooth() (via PermissionsAndroid).
Migrating from 1.x (1.0.14 and earlier)
| Topic | 1.x | 2.x |
| --- | --- | --- |
| Package name | react-native-ble-scanner-kit | Same |
| react-native-permissions | Documented as required peer | Not used — Android runtime BLE is handled inside initializeBluetooth() |
| startScan | Returned emitter synchronously | Returns Promise<typeof DeviceEventEmitter> — use await startScan(...) |
| CocoaPods pod | Legacy pod name | Pod name react-native-ble-scanner-kit (see react-native-ble-scanner-kit.podspec) |
Update imports to the same package name; add await before startScan and keep calling stopScan() on unmount.
Quick start
import {
initializeBluetooth,
startScan,
stopScan,
} from 'react-native-ble-scanner-kit';
// 1. Initialize (Android runtime permissions + native Bluetooth)
await initializeBluetooth();
// 2. Start scanning — 2.x is async
const emitter = await startScan();
// 3. Listen for devices
const sub = emitter.addListener('onDeviceFound', (device) => {
console.log(device.id, device.name, device.rssi, device.estimatedDistance);
});
// 4. Stop when done
sub.remove();
stopScan();Hook example:
useEffect(() => {
let sub: { remove: () => void } | undefined;
let cancelled = false;
(async () => {
try {
await initializeBluetooth();
if (cancelled) return;
const emitter = await startScan();
sub = emitter.addListener('onDeviceFound', handleDevice);
} catch (e) {
console.error(e);
}
})();
return () => {
cancelled = true;
sub?.remove();
stopScan();
};
}, []);API reference
initializeBluetooth(): Promise<void>
Prepares BLE on the device. On Android, requests required runtime permissions. On iOS, relies on Info.plist strings and user consent. Then calls native initializeBluetooth.
Throws if permissions are denied or Bluetooth is unusable; also emits onError with structured codes where applicable.
try {
await initializeBluetooth();
} catch (error) {
console.error(error instanceof Error ? error.message : error);
}startScan(targetId?, config?, isUseDeviceID?): Promise<typeof DeviceEventEmitter>
Starts a BLE scan and resolves to an event emitter (same listener API as typical RN patterns).
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| targetId | string | undefined | If set, only matching devices are surfaced (see isUseDeviceID). |
| config | SentiDriveScanConfigs | See below | Duration, RSSI model, Kalman, pulse tiers, vibration/sound flags. |
| isUseDeviceID | boolean | false | If true, targetId is matched against hardware id (MAC / UUID, normalized). If false, matched against advertised name. |
Default configuration (merged in JS):
{
scanConfig: {
scanDuration: 30000,
enableVibration: false,
enableSound: false,
txPower: -59,
n: 2,
},
rssiFilter: {
enableKalmanFilter: false,
kalmanProcessNoise: 0.01,
kalmanMeasurementNoise: 0.25,
},
// Default pulse tiers (see src/pulse.ts)
pulseConfigs: [
{ distance: 1, intervalMs: 500 },
{ distance: 3, intervalMs: 1000 },
{ distance: 5, intervalMs: 2000 },
{ distance: 10, intervalMs: 3000 },
{ distance: 20, intervalMs: 5000 },
],
}Filter by hardware id (e.g. MAC-style id):
const emitter = await startScan(
'AA:BB:CC:DD:EE:FF',
{
scanConfig: {
scanDuration: 60000,
enableVibration: true,
txPower: -65,
n: 2.5,
},
rssiFilter: {
enableKalmanFilter: true,
kalmanProcessNoise: 0.01,
kalmanMeasurementNoise: 0.5,
},
pulseConfigs: [
{ distance: 10, intervalMs: 2000 },
{ distance: 3, intervalMs: 500 },
],
},
true, // isUseDeviceID
);Scan all devices:
const emitter = await startScan();
emitter.addListener('onDeviceFound', (device) => {
console.log(`${device.name} is ~${device.estimatedDistance.toFixed(1)}m away`);
});Behavior notes (2.x):
- Session id — Events from a previous scan are ignored after
stopScanor a newstartScan. - Android — A minimum ~500 ms delay is applied after
stopScanbefore the next nativestartScanto reduce scan throttling. - Device lost — If a device is not seen for ~5 s, it is removed from tracking and
onDeviceLostfires.
stopScan(): void
Stops scanning, clears timers, device trackers, and pulse state, and detaches internal listeners. Emits onStopped with message "Stopped". Safe to call when idle.
Always call stopScan() when leaving a screen or unmounting a component that started a scan.
estimateDistance(rssi: number, txPower: number, n: number): number
Log-distance path loss (meters):
distance = 10 ^ ((txPower - rssi) / (10 * n))
getBluetoothStatus(): Promise<BluetoothStatus>
Returns the adapter state (on, off, unauthorized, unsupported, etc.).
openBluetoothSettings(): void
Opens system settings relevant to Bluetooth on each platform.
onBluetoothStatusChange(callback): () => void
Subscribe to onBluetoothStateChanged; returns an unsubscribe function.
setDebugEnabled(enabled: boolean): void
Toggles verbose internal logging where implemented.
setSoundFileName(fileName: string): void
Default asset name for proximity sound (default alert.mp3). Requires react-native-sound-player in the host app and the file in native bundles (res/raw on Android, main bundle on iOS).
startVibration(duration?: number): void
Triggers a one-shot vibration (default 400 ms on Android path; iOS may use native haptic when available).
KalmanFilter
Exported 1D Kalman helper for custom RSSI smoothing (processNoise, measurementNoise, update, reset).
Device tracker helpers
Used internally by startScan; exported for advanced/testing use:updateDeviceTracker, clearDeviceTimer, startDeviceLostTimer, clearAllDeviceTrackers.
Pulse / sound helpers
updatePulseForDevice, clearPulseForDevice, pulseDeviceKey, playSound, stopSound.
Debug / introspection
getIsScanning, getLastStopAt, DeviceEventEmitter, NativeSentiDriveModule, teardownNativeBridge.
Events
Subscribe on the emitter returned from await startScan() (or import DeviceEventEmitter from the package).
| Event | Payload | Description |
| --- | --- | --- |
| onDeviceFound | DeviceFoundEvent | Device update with smoothed RSSI and estimatedDistance. |
| onDeviceLost | { id, name, lastSeenAt } | No advertisement for ~5 s. |
| onStopped | { message } | e.g. "Stopped" or "Timeout". |
| onError | ErrorEvent | BT_OFF, PERMISSION_DENIED, SCAN_FAILED, etc. |
| onBluetoothStateChanged | { bluetoothStatus } | Adapter state changes. |
| onScanResult | raw | Internal — prefer onDeviceFound in app code. |
emitter.addListener('onError', ({ code, message }) => {
console.error(`[${code}] ${message}`);
});Types (summary)
type BluetoothStatus =
| 'on' | 'off' | 'turningOff' | 'turningOn'
| 'unauthorized' | 'unsupported' | 'resetting' | 'unknown';
type DeviceFoundEvent = {
id: string;
name: string;
rssi: number;
estimatedDistance: number;
lastSeenAt?: number;
};
type ScanConfigs = {
scanDuration?: number | null;
enableVibration?: boolean;
enableSound?: boolean;
txPower?: number;
n?: number;
};
type RSSIFilter = {
enableKalmanFilter?: boolean;
kalmanProcessNoise?: number;
kalmanMeasurementNoise?: number;
};
type PulseConfig = { distance: number; intervalMs: number };
type SentiDriveScanConfigs = {
scanConfig?: ScanConfigs;
rssiFilter?: RSSIFilter;
pulseConfigs?: PulseConfig[];
};
type ErrorEvent = {
code:
| 'BT_OFF'
| 'PERMISSION_DENIED'
| 'DEVICE_NOT_FOUND'
| 'SCAN_FAILED'
| 'NOT_SUPPORTED'
| 'INVALID_TARGET_ID'
| 'IOS_FOREGROUND_REQUIRED';
message: string;
};Full definitions: src/types.ts / published dist/*.d.ts.
Distance estimation
txPower: RSSI at 1 m (calibrate per beacon/hardware).n: path-loss exponent (often2free space,2.5–4indoors).- Kalman: optional RSSI smoothing before distance is recomputed.
Pulse (proximity) alerts
pulseConfigs define distance bands and alert intervals. While scanning, enableVibration / enableSound turn on tiered feedback; sound uses react-native-sound-player when installed.
Development (library workspace)
npm run build # TypeScript → dist/
npm testLicense
SEE LICENSE IN LICENSE — see LICENSE.
