diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index b08ea407..10095e6c 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -7,6 +7,9 @@ PODS: - hermes-engine (0.74.1): - hermes-engine/Pre-built (= 0.74.1) - hermes-engine/Pre-built (0.74.1) + - MMKV (1.3.7): + - MMKVCore (~> 1.3.7) + - MMKVCore (1.3.7) - MultiplatformBleAdapter (0.2.0) - RCT-Folly (2024.01.01.00): - boost @@ -958,6 +961,28 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-mmkv (2.12.2): + - DoubleConversion + - glog + - hermes-engine + - MMKV (>= 1.3.3) + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - react-native-safe-area-context (4.10.1): - React-Core - React-nativeconfig (0.74.1) @@ -1248,6 +1273,7 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - react-native-ble-plx (from `../..`) + - react-native-mmkv (from `../node_modules/react-native-mmkv`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) @@ -1277,6 +1303,8 @@ DEPENDENCIES: SPEC REPOS: trunk: + - MMKV + - MMKVCore - MultiplatformBleAdapter - SocketRocket @@ -1344,6 +1372,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" react-native-ble-plx: :path: "../.." + react-native-mmkv: + :path: "../node_modules/react-native-mmkv" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" React-nativeconfig: @@ -1404,6 +1434,8 @@ SPEC CHECKSUMS: fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: 16b8530de1b383cdada1476cf52d1b52f0692cbc + MMKV: 36a22a9ec84c9bb960613a089ddf6f48be9312b0 + MMKVCore: 158e61c8516401a9fac730288acb29e6fc19bbf9 MultiplatformBleAdapter: ea8bac405ec200d0ca9de0f89afef6f06fb2abbc RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47 RCTDeprecation: efb313d8126259e9294dc4ee0002f44a6f676aba @@ -1430,6 +1462,7 @@ SPEC CHECKSUMS: React-logger: 7e7403a2b14c97f847d90763af76b84b152b6fce React-Mapbuffer: 11029dcd47c5c9e057a4092ab9c2a8d10a496a33 react-native-ble-plx: 08539040709361221aa9f8cada60dc730b9168c5 + react-native-mmkv: 8c9a677e64a1ac89b0c6cf240feea528318b3074 react-native-safe-area-context: dcab599c527c2d7de2d76507a523d20a0b83823d React-nativeconfig: b0073a590774e8b35192fead188a36d1dca23dec React-NativeModulesApple: df46ff3e3de5b842b30b4ca8a6caae6d7c8ab09f diff --git a/example/package.json b/example/package.json index ab4192dc..fe4a001d 100644 --- a/example/package.json +++ b/example/package.json @@ -14,6 +14,7 @@ "react": "18.2.0", "react-native": "0.74.1", "react-native-base64": "^0.2.1", + "react-native-mmkv": "^2.12.2", "react-native-safe-area-context": "^4.10.1", "react-native-screens": "^3.31.1", "react-native-toast-message": "^2.1.6", @@ -23,13 +24,13 @@ "@babel/core": "^7.20.0", "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", + "@react-native/babel-preset": "0.74.83", "@react-native/eslint-config": "^0.72.2", - "@types/react-native-base64": "^0.2.0", "@react-native/metro-config": "0.74.83", + "@react-native/typescript-config": "0.74.83", + "@types/react-native-base64": "^0.2.0", "babel-plugin-module-resolver": "^5.0.0", - "metro-react-native-babel-preset": "0.76.8", - "@react-native/babel-preset": "0.74.83", - "@react-native/typescript-config": "0.74.83" + "metro-react-native-babel-preset": "0.76.8" }, "engines": { "node": ">=18" diff --git a/example/src/App.tsx b/example/src/App.tsx index ea87f883..346dc3bb 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -4,6 +4,7 @@ import { ThemeProvider } from 'styled-components' import Toast from 'react-native-toast-message' import { commonTheme } from './theme/theme' import { Navigation } from './navigation' +import './services/storage/storage' export function App() { return ( diff --git a/example/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx b/example/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx index d7af5e41..2224e261 100644 --- a/example/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx +++ b/example/src/screens/MainStack/DeviceConnectDisconnectTestScreen/DeviceConnectDisconnectTestScreen.tsx @@ -1,11 +1,11 @@ import React, { useRef, useState } from 'react' import type { NativeStackScreenProps } from '@react-navigation/native-stack' import { BleError, Characteristic, Device, type Subscription, type DeviceId, BleErrorCode } from 'react-native-ble-plx' -import { ScrollView } from 'react-native' +import { Alert, ScrollView } from 'react-native' import base64 from 'react-native-base64' import Toast from 'react-native-toast-message' import type { TestStateType } from '../../../types' -import { BLEService } from '../../../services' +import { BLEService, usePersistentDeviceName } from '../../../services' import type { MainStackParamList } from '../../../navigation/navigators' import { AppButton, AppTextInput, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' import { currentTimeCharacteristic, deviceTimeService } from '../../../consts/nRFDeviceConsts' @@ -16,9 +16,10 @@ type DeviceConnectDisconnectTestScreenProps = NativeStackScreenProps< 'DEVICE_CONNECT_DISCONNECT_TEST_SCREEN' > const NUMBER_OF_CALLS_IN_THE_TEST_SCENARIO = 10 +const CONNECTION_TIMEOUT = 5000 export function DeviceConnectDisconnectTestScreen(_props: DeviceConnectDisconnectTestScreenProps) { - const [expectedDeviceName, setExpectedDeviceName] = useState('') + const [expectedDeviceName, setExpectedDeviceName] = usePersistentDeviceName() const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING') const [deviceId, setDeviceId] = useState('') const [connectCounter, setConnectCounter] = useState(0) @@ -27,11 +28,59 @@ export function DeviceConnectDisconnectTestScreen(_props: DeviceConnectDisconnec const [disconnectCounter, setDisconnectCounter] = useState(0) const [monitorMessages, setMonitorMessages] = useState([]) const monitorSubscriptionRef = useRef(null) + const [timeoutTestState, setTimeoutTestState] = useState('WAITING') + const [timeoutTestLabel, setTimeoutTestLabel] = useState('Timeout Test') + + const startTimeoutTest = async () => { + await BLEService.initializeBLE() + setTimeoutTestLabel('Running') + await BLEService.scanDevices( + async (device: Device) => { + if (checkDeviceName(device)) { + BLEService.stopDeviceScan() + Alert.alert( + 'Prepare for Timeout Test', + 'Please turn off Bluetooth on the device you want to connect to. Press OK when ready.', + [ + { + text: 'OK', + onPress: () => runTimeoutTest(device) + } + ] + ) + } + }, + [deviceTimeService] + ) + } + + const runTimeoutTest = async (device: Device) => { + setTimeoutTestState('IN_PROGRESS') + + try { + await BLEService.connectToDevice(device.id, CONNECTION_TIMEOUT) + setTimeoutTestState('ERROR') + setTimeoutTestLabel('Device was able to connect') + } catch (error) { + if ( + error instanceof Error && + (error.message === 'Operation was cancelled' || error.message === 'Operation timed out') + ) { + console.info('Timeout test successful: Connection timed out as expected') + setTimeoutTestState('DONE') + setTimeoutTestLabel('Success') + } else { + console.error('Unexpected error during timeout test:', error) + setTimeoutTestState('ERROR') + setTimeoutTestLabel('Error occurred') + } + } + } const addMonitorMessage = (message: string) => setMonitorMessages(prevMessages => [...prevMessages, message]) const checkDeviceName = (device: Device) => - device.name?.toLocaleLowerCase() === expectedDeviceName.toLocaleLowerCase() + device.name?.toLocaleLowerCase() === expectedDeviceName?.toLocaleLowerCase() const startConnectAndDiscover = async () => { setTestScanDevicesState('IN_PROGRESS') @@ -224,6 +273,8 @@ export function DeviceConnectDisconnectTestScreen(_props: DeviceConnectDisconnec value={connectInDisconnectTestCounter.toString()} /> + + startCharacteristicMonitor()} /> diff --git a/example/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx b/example/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx index 01847a81..42440b4a 100644 --- a/example/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx +++ b/example/src/screens/MainStack/DeviceOnDisconnectTestScreen/DeviceOnDisconnectTestScreen.tsx @@ -5,7 +5,7 @@ import { ScrollView } from 'react-native' import Toast from 'react-native-toast-message' import { wait } from '../../../utils/wait' import type { TestStateType } from '../../../types' -import { BLEService } from '../../../services' +import { BLEService, usePersistentDeviceName } from '../../../services' import type { MainStackParamList } from '../../../navigation/navigators' import { AppButton, AppTextInput, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' import { deviceTimeService } from '../../../consts/nRFDeviceConsts' @@ -13,14 +13,14 @@ import { deviceTimeService } from '../../../consts/nRFDeviceConsts' type DeviceOnDisconnectTestScreenProps = NativeStackScreenProps export function DeviceOnDisconnectTestScreen(_props: DeviceOnDisconnectTestScreenProps) { - const [expectedDeviceName, setExpectedDeviceName] = useState('') + const [expectedDeviceName, setExpectedDeviceName] = usePersistentDeviceName() const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING') const [deviceId, setDeviceId] = useState('') const [currentTest, setCurrentTest] = useState(null) const onDisconnectRef = useRef(null) const checkDeviceName = (device: Device) => - device.name?.toLocaleLowerCase() === expectedDeviceName.toLocaleLowerCase() + device.name?.toLocaleLowerCase() === expectedDeviceName?.toLocaleLowerCase() const connectAndDiscover = async () => { setTestScanDevicesState('IN_PROGRESS') diff --git a/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx b/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx index 1b3715b3..57938256 100644 --- a/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx +++ b/example/src/screens/MainStack/DevicenRFTestScreen/DevicenRFTestScreen.tsx @@ -4,7 +4,7 @@ import { Device, type Base64 } from 'react-native-ble-plx' import { Platform, ScrollView } from 'react-native' import base64 from 'react-native-base64' import type { TestStateType } from '../../../types' -import { BLEService } from '../../../services' +import { BLEService, usePersistentDeviceName } from '../../../services' import type { MainStackParamList } from '../../../navigation/navigators' import { AppButton, AppTextInput, ScreenDefaultContainer, TestStateDisplay } from '../../../components/atoms' import { wait } from '../../../utils/wait' @@ -22,7 +22,7 @@ import { type DevicenRFTestScreenProps = NativeStackScreenProps export function DevicenRFTestScreen(_props: DevicenRFTestScreenProps) { - const [expectedDeviceName, setExpectedDeviceName] = useState('') + const [expectedDeviceName, setExpectedDeviceName] = usePersistentDeviceName() const [testScanDevicesState, setTestScanDevicesState] = useState('WAITING') const [testDeviceConnectedState, setTestDeviceConnectedState] = useState('WAITING') const [testDiscoverServicesAndCharacteristicsFoundState, setTestDiscoverServicesAndCharacteristicsFoundState] = @@ -115,7 +115,7 @@ export function DevicenRFTestScreen(_props: DevicenRFTestScreenProps) { } const onDeviceFound = (device: Device) => { - if (device.name?.toLocaleLowerCase() === expectedDeviceName.toLocaleLowerCase()) { + if (device.name?.toLocaleLowerCase() === expectedDeviceName?.toLocaleLowerCase()) { setTestScanDevicesState('DONE') startConnectToDevice(device) .then(onDeviceDisconnected) @@ -593,7 +593,7 @@ export function DevicenRFTestScreen(_props: DevicenRFTestScreenProps) { new Promise((resolve, reject) => { BLEService.scanDevices( (device: Device) => { - if (device.name?.toLocaleLowerCase() === expectedDeviceName.toLocaleLowerCase()) { + if (device.name?.toLocaleLowerCase() === expectedDeviceName?.toLocaleLowerCase()) { BLEService.connectToDevice(device.id) .then(() => BLEService.cancelDeviceConnection()) .then(() => resolve()) diff --git a/example/src/services/BLEService/BLEService.ts b/example/src/services/BLEService/BLEService.ts index 8b6e2a13..d09e3a62 100644 --- a/example/src/services/BLEService/BLEService.ts +++ b/example/src/services/BLEService/BLEService.ts @@ -116,11 +116,15 @@ class BLEServiceInstance { .catch(console.error) } - connectToDevice = (deviceId: DeviceId) => + stopDeviceScan = () => { + this.manager.stopDeviceScan() + } + + connectToDevice = (deviceId: DeviceId, timeout?: number) => new Promise((resolve, reject) => { this.manager.stopDeviceScan() this.manager - .connectToDevice(deviceId) + .connectToDevice(deviceId, { timeout }) .then(device => { this.device = device resolve(device) diff --git a/example/src/services/index.ts b/example/src/services/index.ts index a4a07417..c927d78d 100644 --- a/example/src/services/index.ts +++ b/example/src/services/index.ts @@ -1 +1,3 @@ export * from './BLEService/BLEService' +export * from './storage/persistentDeviceName' +export * from './storage/storage' diff --git a/example/src/services/storage/persistentDeviceName.ts b/example/src/services/storage/persistentDeviceName.ts new file mode 100644 index 00000000..dc6e64f9 --- /dev/null +++ b/example/src/services/storage/persistentDeviceName.ts @@ -0,0 +1,5 @@ +import { useMMKVString } from 'react-native-mmkv' + +export const PERSISTENT_DEVICE_NAME_KEY = 'PERSISTENT_DEVICE_NAME' + +export const usePersistentDeviceName = () => useMMKVString(PERSISTENT_DEVICE_NAME_KEY) diff --git a/example/src/services/storage/storage.ts b/example/src/services/storage/storage.ts new file mode 100644 index 00000000..05466c92 --- /dev/null +++ b/example/src/services/storage/storage.ts @@ -0,0 +1,3 @@ +import { MMKV } from 'react-native-mmkv' + +export const storage = new MMKV() diff --git a/example/yarn.lock b/example/yarn.lock index b78d593d..1b2481d6 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -4764,6 +4764,11 @@ react-native-base64@^0.2.1: resolved "https://registry.yarnpkg.com/react-native-base64/-/react-native-base64-0.2.1.tgz#3d0e73a649c4c0129f7b7695d3912456aebae847" integrity sha512-eHgt/MA8y5ZF0aHfZ1aTPcIkDWxza9AaEk4GcpIX+ZYfZ04RcaNahO+527KR7J44/mD3efYfM23O2C1N44ByWA== +react-native-mmkv@^2.12.2: + version "2.12.2" + resolved "https://registry.yarnpkg.com/react-native-mmkv/-/react-native-mmkv-2.12.2.tgz#4bba0f5f04e2cf222494cce3a9794ba6a4894dee" + integrity sha512-6058Aq0p57chPrUutLGe9fYoiDVDNMU2PKV+lLFUJ3GhoHvUrLdsS1PDSCLr00yqzL4WJQ7TTzH+V8cpyrNcfg== + react-native-safe-area-context@^4.10.1: version "4.10.1" resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.10.1.tgz#29fb27395ff7dfa2fa38788a27226330d73a81cc"