From e0ea8a8d3d9568fe8f2965682f72c8022ff5d777 Mon Sep 17 00:00:00 2001 From: Hayden Ball Date: Thu, 16 Jan 2025 11:18:13 +0000 Subject: [PATCH 1/3] feat: Add ability to reset device --- .../ReactNativeMcuManagerModule.kt | 32 ++++++++++++++++++- .../ios/ReactNativeMcuManagerModule.swift | 27 ++++++++++++++++ react-native-mcu-manager/src/index.ts | 4 +++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/react-native-mcu-manager/android/src/main/java/uk/co/playerdata/reactnativemcumanager/ReactNativeMcuManagerModule.kt b/react-native-mcu-manager/android/src/main/java/uk/co/playerdata/reactnativemcumanager/ReactNativeMcuManagerModule.kt index 38304ef0..ecb4d79f 100644 --- a/react-native-mcu-manager/android/src/main/java/uk/co/playerdata/reactnativemcumanager/ReactNativeMcuManagerModule.kt +++ b/react-native-mcu-manager/android/src/main/java/uk/co/playerdata/reactnativemcumanager/ReactNativeMcuManagerModule.kt @@ -11,9 +11,12 @@ import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition import expo.modules.kotlin.records.Field import expo.modules.kotlin.records.Record +import io.runtime.mcumgr.McuMgrCallback import io.runtime.mcumgr.ble.McuMgrBleTransport import io.runtime.mcumgr.exception.McuMgrException +import io.runtime.mcumgr.managers.DefaultManager import io.runtime.mcumgr.managers.ImageManager +import io.runtime.mcumgr.response.dflt.McuMgrOsResponse private const val MODULE_NAME = "ReactNativeMcuManager" private val TAG = "McuManagerModule" @@ -41,7 +44,7 @@ class ReactNativeMcuManagerModule : Module() { try { val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(macAddress) - var transport = McuMgrBleTransport(context, device) + val transport = McuMgrBleTransport(context, device) transport.connect(device).timeout(60000).await() val imageManager = ImageManager(transport) @@ -123,5 +126,32 @@ class ReactNativeMcuManagerModule : Module() { upgrade.cancel() upgrades.remove(id) } + + AsyncFunction("resetDevice") { macAddress: String, promise: Promise -> + if (this@ReactNativeMcuManagerModule.bluetoothAdapter == null) { + throw Exception("No bluetooth adapter") + } + + val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(macAddress) + + val transport = McuMgrBleTransport(context, device) + transport.connect(device).timeout(60000).await() + + val manager = DefaultManager(transport) + + val callback = object: McuMgrCallback { + override fun onResponse(response: McuMgrOsResponse) { + transport.release() + promise.resolve() + } + + override fun onError(error: McuMgrException) { + transport.release() + promise.reject(CodedException("RESET_DEVICE_FAILED", "Failed to reset device", error)) + } + } + + manager.reset(callback) + } } } diff --git a/react-native-mcu-manager/ios/ReactNativeMcuManagerModule.swift b/react-native-mcu-manager/ios/ReactNativeMcuManagerModule.swift index 1f66b77f..7fe8beff 100644 --- a/react-native-mcu-manager/ios/ReactNativeMcuManagerModule.swift +++ b/react-native-mcu-manager/ios/ReactNativeMcuManagerModule.swift @@ -94,5 +94,32 @@ public class ReactNativeMcuManagerModule: Module { upgrade.cancel() self.upgrades[id] = nil } + + AsyncFunction("resetDevice") { (bleId: String, promise: Promise) in + guard let bleUuid = UUID(uuidString: bleId) else { + promise.reject(Exception(name: "UUIDParseError", description: "Failed to parse UUID")) + return + } + + let bleTransport = McuMgrBleTransport(bleUuid) + let manager = DefaultManager(transport: bleTransport) + + manager.reset { (response: McuMgrResponse?, err: Error?) in + bleTransport.close() + + if err != nil { + promise.reject(Exception(name: "ResetError", description: err!.localizedDescription)) + return + } + + let smpErr = response?.getError() + if (smpErr != nil) { + promise.reject(Exception(name: "ResetError", description: smpErr!.localizedDescription)) + return + } + + promise.resolve() + } + } } } diff --git a/react-native-mcu-manager/src/index.ts b/react-native-mcu-manager/src/index.ts index 2d6bb654..11ada09e 100644 --- a/react-native-mcu-manager/src/index.ts +++ b/react-native-mcu-manager/src/index.ts @@ -6,5 +6,9 @@ export const eraseImage = McuManagerModule?.eraseImage as ( bleId: string ) => Promise; +export const resetDevice = McuManagerModule?.resetDevice as ( + bleId: string +) => Promise; + export { Upgrade, UpgradeMode, UpgradeFileType }; export type { FirmwareUpgradeState, UpgradeOptions }; From 0b5f13ab7a5d6728fe7b05edc1f86be5cc157862 Mon Sep 17 00:00:00 2001 From: Hayden Ball Date: Thu, 16 Jan 2025 12:02:52 +0000 Subject: [PATCH 2/3] chore: Restructure example app to disable scanning once selected --- example/app.config.ts | 7 +- example/index.ts | 8 - example/package.json | 11 +- example/src/app/(home)/_layout.tsx | 15 + example/src/app/(home)/index.tsx | 62 ++ example/src/app/(home)/select_device.tsx | 42 + example/src/app/_layout.tsx | 28 + example/src/{App.tsx => app/update.tsx} | 61 +- example/src/context/selectedDevice.ts | 28 + .../src/{ => hooks}/useBluetoothDevices.ts | 4 +- example/src/{ => hooks}/useFilePicker.ts | 0 example/src/{ => hooks}/useFirmwareUpdate.ts | 0 pnpm-lock.yaml | 885 +++++++++++++++--- 13 files changed, 977 insertions(+), 174 deletions(-) delete mode 100644 example/index.ts create mode 100644 example/src/app/(home)/_layout.tsx create mode 100644 example/src/app/(home)/index.tsx create mode 100644 example/src/app/(home)/select_device.tsx create mode 100644 example/src/app/_layout.tsx rename example/src/{App.tsx => app/update.tsx} (66%) create mode 100644 example/src/context/selectedDevice.ts rename example/src/{ => hooks}/useBluetoothDevices.ts (91%) rename example/src/{ => hooks}/useFilePicker.ts (100%) rename example/src/{ => hooks}/useFirmwareUpdate.ts (100%) diff --git a/example/app.config.ts b/example/app.config.ts index 23f9c8ad..4661c24d 100644 --- a/example/app.config.ts +++ b/example/app.config.ts @@ -4,9 +4,11 @@ import 'ts-node/register'; const config: ExpoConfig = { name: "react-native-mcu-manager-example", slug: "react-native-mcu-manager-example", - version: "1.0.0", - orientation: "portrait", assetBundlePatterns: ["**/*"], + orientation: "portrait", + platforms: ["ios", "android"], + scheme: "rnmcumgr", + version: "1.0.0", splash: { image: ".assets/images/pd.png", backgroundColor: "#FFFFFF", @@ -27,6 +29,7 @@ const config: ExpoConfig = { }, plugins: [ ["expo-document-picker"], + ["expo-router"], ["./gradlePlugin.ts"] ] }; diff --git a/example/index.ts b/example/index.ts deleted file mode 100644 index 018d06f9..00000000 --- a/example/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { registerRootComponent } from 'expo'; - -import App from './src/App'; - -// registerRootComponent calls AppRegistry.registerComponent('main', () => App); -// It also ensures that whether you load the app in Expo Go or in a native build, -// the environment is set up appropriately -registerRootComponent(App); diff --git a/example/package.json b/example/package.json index 90416df4..e27849a8 100644 --- a/example/package.json +++ b/example/package.json @@ -1,8 +1,9 @@ { "name": "react-native-mcu-manager-example", "description": "Example app for react-native-mcu-manager", + "main": "expo-router/entry", "version": "0.0.1", - "private": true, + "license": "MIT", "scripts": { "android": "expo run:android", "ios": "expo run:ios", @@ -13,12 +14,19 @@ "dependencies": { "@playerdata/react-native-mcu-manager": "workspace:*", "expo": "52.0.25", + "expo-constants": "~17.0.4", "expo-document-picker": "13.0.2", + "expo-linking": "~7.0.4", + "expo-router": "~4.0.16", "expo-splash-screen": "0.29.20", + "expo-status-bar": "~2.0.1", "lodash": "4.17.21", "react": "18.3.1", "react-native": "0.76.3", "react-native-ble-plx": "3.4.0", + "react-native-reanimated": "~3.16.1", + "react-native-safe-area-context": "4.12.0", + "react-native-screens": "~4.4.0", "react-native-toast-message": "2.2.1" }, "devDependencies": { @@ -27,7 +35,6 @@ "@types/lodash": "4.17.14", "@types/react": "19.0.7", "metro-react-native-babel-preset": "0.77.0", - "pod-install": "0.3.4", "ts-node": "^10.9.2", "typescript": "5.7.3" } diff --git a/example/src/app/(home)/_layout.tsx b/example/src/app/(home)/_layout.tsx new file mode 100644 index 00000000..f61245a7 --- /dev/null +++ b/example/src/app/(home)/_layout.tsx @@ -0,0 +1,15 @@ +import { Stack } from 'expo-router'; + +const HomeLayout = () => { + return ( + + + + + ); +}; + +export default HomeLayout; diff --git a/example/src/app/(home)/index.tsx b/example/src/app/(home)/index.tsx new file mode 100644 index 00000000..94a04ab5 --- /dev/null +++ b/example/src/app/(home)/index.tsx @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { Button, StyleSheet, Text, View } from 'react-native'; + +import { Link } from 'expo-router'; + +import { resetDevice } from '@playerdata/react-native-mcu-manager'; + +import { useSelectedDevice } from '../../context/selectedDevice'; + +const styles = StyleSheet.create({ + root: { + padding: 16, + }, + + block: { + marginBottom: 16, + }, +}); + +const Home = () => { + const { selectedDevice } = useSelectedDevice(); + const [resetState, setResetState] = useState(''); + + return ( + + + + Select a device, then use the tabs below to choose which function to + test + + + + +