diff --git a/.eslintignore b/.eslintignore
index 3ef8f41d..fddf3ca8 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,4 @@
/**/node_modules/*
node_modules/
-docs/**
\ No newline at end of file
+docs/**
+lib/**
\ No newline at end of file
diff --git a/android/src/main/java/com/bleplx/BlePlxModule.java b/android/src/main/java/com/bleplx/BlePlxModule.java
index 1464bd27..b165a1b3 100644
--- a/android/src/main/java/com/bleplx/BlePlxModule.java
+++ b/android/src/main/java/com/bleplx/BlePlxModule.java
@@ -36,22 +36,36 @@
import com.bleplx.converter.ServiceToJsObjectConverter;
import com.bleplx.utils.ReadableArrayConverter;
import com.bleplx.utils.SafePromise;
+import com.polidea.rxandroidble2.internal.RxBleLog;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.app.Activity;
+import io.reactivex.exceptions.UndeliverableException;
+import io.reactivex.plugins.RxJavaPlugins;
+
@ReactModule(name = BlePlxModule.NAME)
public class BlePlxModule extends ReactContextBaseJavaModule {
public static final String NAME = "BlePlx";
public BlePlxModule(ReactApplicationContext reactContext) {
super(reactContext);
+ RxJavaPlugins.setErrorHandler(throwable -> {
+ if (throwable instanceof UndeliverableException) {
+ RxBleLog.e("Handle all unhandled exceptions from RxJava: " + throwable.getMessage());
+ } else {
+ Thread currentThread = Thread.currentThread();
+ Thread.UncaughtExceptionHandler errorHandler = currentThread.getUncaughtExceptionHandler();
+ if (errorHandler != null) {
+ errorHandler.uncaughtException(currentThread, throwable);
+ }
+ }
+ });
}
@Override
diff --git a/example/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx b/example/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx
index 44f88be0..29366514 100644
--- a/example/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx
+++ b/example/src/components/atoms/TestStateDisplay/TestStateDisplay.tsx
@@ -1,11 +1,11 @@
import React from 'react'
-import type { TestStateType } from 'example/types'
+import type { TestStateType } from '../../../types'
import { AppText } from '../AppText/AppText'
import { Container, Header, Label } from './TestStateDisplay.styled'
export type TestStateDisplayProps = {
label?: string
- state: TestStateType
+ state?: TestStateType
value?: string
}
@@ -21,9 +21,9 @@ export function TestStateDisplay({ label, state, value }: TestStateDisplayProps)
- {marks[state]}
+ {!!state && {marks[state]}}
- {value && {value}}
+ {!!value && {value}}
)
}
diff --git a/example/src/consts/nRFDeviceConsts.ts b/example/src/consts/nRFDeviceConsts.ts
new file mode 100644
index 00000000..66395ca6
--- /dev/null
+++ b/example/src/consts/nRFDeviceConsts.ts
@@ -0,0 +1,13 @@
+import { fullUUID } from 'react-native-ble-plx'
+import base64 from 'react-native-base64'
+import { getDateAsBase64 } from '../utils/getDateAsBase64'
+
+export const deviceTimeService = fullUUID('1847')
+export const currentTimeCharacteristic = fullUUID('2A2B')
+export const deviceTimeCharacteristic = fullUUID('2B90')
+export const currentTimeCharacteristicTimeTriggerDescriptor = fullUUID('290E')
+
+export const writeWithResponseBase64Time = getDateAsBase64(new Date('2022-08-11T08:17:19Z'))
+export const writeWithoutResponseBase64Time = getDateAsBase64(new Date('2023-09-12T10:12:16Z'))
+export const monitorExpectedMessage = 'Hi, it works!'
+export const currentTimeCharacteristicTimeTriggerDescriptorValue = base64.encode('BLE-PLX')
diff --git a/example/src/navigation/navigators/MainStack.tsx b/example/src/navigation/navigators/MainStack.tsx
index adbabf63..8efa01a2 100644
--- a/example/src/navigation/navigators/MainStack.tsx
+++ b/example/src/navigation/navigators/MainStack.tsx
@@ -7,6 +7,7 @@ export type MainStackParamList = {
DASHBOARD_SCREEN: undefined
DEVICE_DETAILS_SCREEN: undefined
DEVICE_NRF_TEST_SCREEN: undefined
+ DEVICE_CONNECT_DISCONNECT_TEST_SCREEN: undefined
}
const MainStack = createNativeStackNavigator()
@@ -37,6 +38,13 @@ export function MainStackComponent() {
headerTitle: 'nRF device test'
}}
/>
+
)
}
diff --git a/example/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx b/example/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx
index d2c5e85b..0fc6c65f 100644
--- a/example/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx
+++ b/example/src/screens/MainStack/DashboardScreen/DashboardScreen.tsx
@@ -79,6 +79,11 @@ export function DashboardScreen({ navigation }: DashboardScreenProps) {
/>
navigation.navigate('DEVICE_NRF_TEST_SCREEN')} />
+ BLEService.isDeviceWithIdConnected('asd')} />
+ navigation.navigate('DEVICE_CONNECT_DISCONNECT_TEST_SCREEN')}
+ />
+const NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO = 10
+
+export function DeviceConnectDisconnectTestScreen(_props: DeviceConnectDisconnectTestScreenProps) {
+ const [expectedDeviceName, setExpectedDeviceName] = useState('')
+ const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING')
+ const [deviceId, setDeviceId] = useState('')
+ const [connectCounter, setConnectCounter] = useState(0)
+ const [characteristicDiscoverCounter, setCharacteristicDiscoverCounter] = useState(0)
+ const [connectInDisconnectTestCounter, setConnectInDisconnectTestCounter] = useState(0)
+ const [disconnectCounter, setDisconnectCounter] = useState(0)
+ const [monitorMessages, setMonitorMessages] = useState([])
+ const monitorSubscriptionRef = useRef(null)
+
+ const addMonitorMessage = (message: string) => setMonitorMessages(prevMessages => [...prevMessages, message])
+
+ const checkDeviceName = (device: Device) =>
+ device.name?.toLocaleLowerCase() === expectedDeviceName.toLocaleLowerCase()
+
+ const startConnectAndDiscover = async () => {
+ setTestScanDevicesState('IN_PROGRESS')
+ await BLEService.initializeBLE()
+ await BLEService.scanDevices(connectAndDiscoverOnDeviceFound, [deviceTimeService])
+ }
+
+ const startConnectAndDisconnect = async () => {
+ setTestScanDevicesState('IN_PROGRESS')
+ await BLEService.initializeBLE()
+ await BLEService.scanDevices(connectAndDisconnectOnDeviceFound, [deviceTimeService])
+ }
+
+ const startConnectOnly = async () => {
+ setTestScanDevicesState('IN_PROGRESS')
+ await BLEService.initializeBLE()
+ await BLEService.scanDevices(
+ async (device: Device) => {
+ if (checkDeviceName(device)) {
+ console.info(`connecting to ${device.id}`)
+ await startConnectToDevice(device)
+ setConnectCounter(prevCount => prevCount + 1)
+ setTestScanDevicesState('DONE')
+ setDeviceId(device.id)
+ }
+ },
+ [deviceTimeService]
+ )
+ }
+
+ const connectAndDiscoverOnDeviceFound = async (device: Device) => {
+ if (checkDeviceName(device)) {
+ setTestScanDevicesState('DONE')
+ setDeviceId(device.id)
+ try {
+ for (let i = 0; i < NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO; i += 1) {
+ console.info(`connecting to ${device.id}`)
+ await startConnectToDevice(device)
+ setConnectCounter(prevCount => prevCount + 1)
+ console.info(`discovering in ${device.id}`)
+ await startDiscoverServices()
+ setCharacteristicDiscoverCounter(prevCount => prevCount + 1)
+ }
+ console.info('Multiple connect success')
+ } catch (error) {
+ console.error('Multiple connect error')
+ }
+ }
+ }
+ const connectAndDisconnectOnDeviceFound = async (device: Device) => {
+ if (checkDeviceName(device)) {
+ setTestScanDevicesState('DONE')
+ setDeviceId(device.id)
+ try {
+ for (let i = 0; i < NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO; i += 1) {
+ await startConnectToDevice(device)
+ console.info(`connecting to ${device.id}`)
+ setConnectInDisconnectTestCounter(prevCount => prevCount + 1)
+ await startDisconnect(device)
+ console.info(`disconnecting from ${device.id}`)
+ setDisconnectCounter(prevCount => prevCount + 1)
+ }
+ console.info('connect/disconnect success')
+ } catch (error) {
+ console.error('Connect/disconnect error')
+ }
+ }
+ }
+
+ const discoverCharacteristicsOnly = async () => {
+ if (!deviceId) {
+ console.error('Device not ready')
+ return
+ }
+ try {
+ for (let i = 0; i < NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO; i += 1) {
+ console.info(`discovering in ${deviceId}`)
+ await startDiscoverServices()
+ setCharacteristicDiscoverCounter(prevCount => prevCount + 1)
+ }
+ console.info('Multiple discovering success')
+ } catch (error) {
+ console.error('Multiple discovering error')
+ }
+ }
+
+ const startConnectToDevice = (device: Device) => BLEService.connectToDevice(device.id)
+
+ const startDiscoverServices = () => BLEService.discoverAllServicesAndCharacteristicsForDevice()
+
+ const startDisconnect = (device: Device) => BLEService.disconnectDeviceById(device.id)
+
+ const startCharacteristicMonitor = (directDeviceId?: DeviceId) => {
+ if (!deviceId && !directDeviceId) {
+ console.error('Device not ready')
+ return
+ }
+ monitorSubscriptionRef.current = BLEService.setupCustomMonitor(
+ directDeviceId || deviceId,
+ deviceTimeService,
+ currentTimeCharacteristic,
+ characteristicListener
+ )
+ }
+
+ const characteristicListener = (error: BleError | null, characteristic: Characteristic | null) => {
+ if (error) {
+ if (error.errorCode === BleErrorCode.ServiceNotFound || error.errorCode === BleErrorCode.ServicesNotDiscovered) {
+ startDiscoverServices().then(() => startCharacteristicMonitor())
+ return
+ }
+ console.error(JSON.stringify(error))
+ }
+ if (characteristic) {
+ if (characteristic.value) {
+ const message = base64.decode(characteristic.value)
+ console.info(message)
+ addMonitorMessage(message)
+ }
+ }
+ }
+
+ const setupOnDeviceDisconnected = (directDeviceId?: DeviceId) => {
+ if (!deviceId && !directDeviceId) {
+ console.error('Device not ready')
+ return
+ }
+ BLEService.onDeviceDisconnectedCustom(directDeviceId || deviceId, disconnectedListener)
+ }
+
+ const disconnectedListener = (error: BleError | null, device: Device | null) => {
+ if (error) {
+ console.error('onDeviceDisconnected')
+ console.error(JSON.stringify(error, null, 4))
+ }
+ if (device) {
+ console.info(JSON.stringify(device, null, 4))
+ }
+ }
+
+ // https://github.com/dotintent/react-native-ble-plx/issues/1103
+ const showIssue1103Crash = async () => {
+ setTestScanDevicesState('IN_PROGRESS')
+ await BLEService.initializeBLE()
+ await BLEService.scanDevices(
+ async (device: Device) => {
+ if (checkDeviceName(device)) {
+ console.info(`connecting to ${device.id}`)
+ await startConnectToDevice(device)
+ setConnectCounter(prevCount => prevCount + 1)
+ setTestScanDevicesState('DONE')
+ setDeviceId(device.id)
+ await startDiscoverServices()
+ await wait(1000)
+ setupOnDeviceDisconnected(device.id)
+ await wait(1000)
+ startCharacteristicMonitor(device.id)
+ await wait(1000)
+ const info = 'Now disconnect device'
+ console.info(info)
+ Toast.show({
+ type: 'info',
+ text1: info
+ })
+ }
+ },
+ [deviceTimeService]
+ )
+ }
+
+ return (
+
+
+
+
+
+ setupOnDeviceDisconnected()} />
+
+
+
+
+
+
+
+
+ startCharacteristicMonitor()} />
+
+
+
+
+ )
+}
diff --git a/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx b/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx
index 1784f1a9..1b3715b3 100644
--- a/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx
+++ b/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx
@@ -1,27 +1,26 @@
import React, { useState, type Dispatch } from 'react'
-import type { TestStateType } from 'example/types'
import type { NativeStackScreenProps } from '@react-navigation/native-stack'
-import { Device, fullUUID, type Base64 } from 'react-native-ble-plx'
+import { Device, type Base64 } from 'react-native-ble-plx'
import { Platform, ScrollView } from 'react-native'
import base64 from 'react-native-base64'
-import { getDateAsBase64 } from '../../../utils/getDateAsBase64'
+import type { TestStateType } from '../../../types'
import { BLEService } from '../../../services'
import type { MainStackParamList } from '../../../navigation/navigators'
import { AppButton, AppTextInput, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms'
import { wait } from '../../../utils/wait'
+import {
+ currentTimeCharacteristic,
+ currentTimeCharacteristicTimeTriggerDescriptor,
+ currentTimeCharacteristicTimeTriggerDescriptorValue,
+ deviceTimeCharacteristic,
+ deviceTimeService,
+ monitorExpectedMessage,
+ writeWithResponseBase64Time,
+ writeWithoutResponseBase64Time
+} from '../../../consts/nRFDeviceConsts'
type DevicenRFTestScreenProps = NativeStackScreenProps
-const deviceTimeService = fullUUID('1847')
-const currentTimeCharacteristic = fullUUID('2A2B')
-const deviceTimeCharacteristic = fullUUID('2B90')
-const currentTimeCharacteristicTimeTriggerDescriptor = fullUUID('290E')
-
-const writeWithResponseBase64Time = getDateAsBase64(new Date('2022-08-11T08:17:19Z'))
-const writeWithoutResponseBase64Time = getDateAsBase64(new Date('2023-09-12T10:12:16Z'))
-const monitorExpectedMessage = 'Hi, it works!'
-const currentTimeCharacteristicTimeTriggerDescriptorValue = base64.encode('BLE-PLX')
-
export function DevicenRFTestScreen(_props: DevicenRFTestScreenProps) {
const [expectedDeviceName, setExpectedDeviceName] = useState('')
const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING')
diff --git a/example/src/screens/MainStack/index.ts b/example/src/screens/MainStack/index.ts
index afb59930..20784153 100644
--- a/example/src/screens/MainStack/index.ts
+++ b/example/src/screens/MainStack/index.ts
@@ -1,3 +1,4 @@
export * from './DashboardScreen/DashboardScreen'
export * from './DeviceDetailsScreen/DeviceDetailsScreen'
export * from './DevicenRFTestScreen/DevicenRFTestScreen'
+export * from './DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen'
diff --git a/example/src/services/BLEService/BLEService.ts b/example/src/services/BLEService/BLEService.ts
index 6775c705..2f175525 100644
--- a/example/src/services/BLEService/BLEService.ts
+++ b/example/src/services/BLEService/BLEService.ts
@@ -5,6 +5,7 @@ import {
Device,
State as BluetoothState,
LogLevel,
+ type DeviceId,
type TransactionId,
type UUID,
type Characteristic,
@@ -77,6 +78,16 @@ class BLEServiceInstance {
})
}
+ disconnectDeviceById = (id: DeviceId) =>
+ this.manager
+ .cancelDeviceConnection(id)
+ .then(() => this.showSuccessToast('Device disconnected'))
+ .catch(error => {
+ if (error?.code !== BleErrorCode.DeviceDisconnected) {
+ this.onError(error)
+ }
+ })
+
onBluetoothPowerOff = () => {
this.showErrorToast('Bluetooth is turned off')
}
@@ -95,7 +106,7 @@ class BLEServiceInstance {
})
}
- connectToDevice = (deviceId: string) =>
+ connectToDevice = (deviceId: DeviceId) =>
new Promise((resolve, reject) => {
this.manager.stopDeviceScan()
this.manager
@@ -211,6 +222,9 @@ class BLEServiceInstance {
)
}
+ setupCustomMonitor: BleManager['monitorCharacteristicForDevice'] = (...args) =>
+ this.manager.monitorCharacteristicForDevice(...args)
+
finishMonitor = () => {
this.isCharacteristicMonitorDisconnectExpected = true
this.characteristicMonitor?.remove()
@@ -255,7 +269,7 @@ class BLEServiceInstance {
})
}
- getCharacteristicsForDevice = (serviceUUID: string) => {
+ getCharacteristicsForDevice = (serviceUUID: UUID) => {
if (!this.device) {
this.showErrorToast(deviceNotConnectedErrorText)
throw new Error(deviceNotConnectedErrorText)
@@ -265,7 +279,7 @@ class BLEServiceInstance {
})
}
- getDescriptorsForDevice = (serviceUUID: string, characteristicUUID: string) => {
+ getDescriptorsForDevice = (serviceUUID: UUID, characteristicUUID: UUID) => {
if (!this.device) {
this.showErrorToast(deviceNotConnectedErrorText)
throw new Error(deviceNotConnectedErrorText)
@@ -280,11 +294,11 @@ class BLEServiceInstance {
this.showErrorToast(deviceNotConnectedErrorText)
throw new Error(deviceNotConnectedErrorText)
}
- return this.manager.isDeviceConnected(this.device.id).catch(error => {
- this.onError(error)
- })
+ return this.manager.isDeviceConnected(this.device.id)
}
+ isDeviceWithIdConnected = (id: DeviceId) => this.manager.isDeviceConnected(id).catch(console.error)
+
getConnectedDevices = (expectedServices: UUID[]) => {
if (!this.device) {
this.showErrorToast(deviceNotConnectedErrorText)
@@ -313,6 +327,9 @@ class BLEServiceInstance {
return this.manager.onDeviceDisconnected(this.device.id, listener)
}
+ onDeviceDisconnectedCustom: BleManager['onDeviceDisconnected'] = (...args) =>
+ this.manager.onDeviceDisconnected(...args)
+
readRSSIForDevice = () => {
if (!this.device) {
this.showErrorToast(deviceNotConnectedErrorText)
@@ -333,7 +350,7 @@ class BLEServiceInstance {
})
}
- cancelTransaction = (transactionId: string) => this.manager.cancelTransaction(transactionId)
+ cancelTransaction = (transactionId: TransactionId) => this.manager.cancelTransaction(transactionId)
enable = () =>
this.manager.enable().catch(error => {
diff --git a/example/types/TestStateType.ts b/example/src/types/TestStateType.ts
similarity index 100%
rename from example/types/TestStateType.ts
rename to example/src/types/TestStateType.ts
diff --git a/example/types/index.ts b/example/src/types/index.ts
similarity index 100%
rename from example/types/index.ts
rename to example/src/types/index.ts
diff --git a/src/BleManager.js b/src/BleManager.js
index cc401555..9f16a5b1 100644
--- a/src/BleManager.js
+++ b/src/BleManager.js
@@ -28,6 +28,7 @@ import type {
ConnectionOptions,
BleManagerOptions
} from './TypeDefinition'
+import { Platform } from 'react-native'
const enableDisableDeprecatedMessage =
'react-native-ble-plx: The enable and disable feature is no longer supported. In Android SDK 31+ there were major changes in permissions, which may cause problems with these functions, and in SDK 33+ they were completely removed.'
@@ -457,6 +458,9 @@ export class BleManager {
* @returns {Promise} Connected {@link Device} object if successful.
*/
async connectToDevice(deviceIdentifier: DeviceId, options: ?ConnectionOptions): Promise {
+ if (Platform.OS === 'android' && (await this.isDeviceConnected(deviceIdentifier))) {
+ await this.cancelDeviceConnection(deviceIdentifier)
+ }
const nativeDevice = await this._callPromise(BleModule.connectToDevice(deviceIdentifier, options))
return new Device(nativeDevice, this)
}