├── .eslintrc.js ├── .github ├── pull_request_template.md └── workflows │ ├── build_and_release.yml │ └── lint.yml ├── .gitignore ├── .husky ├── .gitignore └── commit-msg ├── .npmignore ├── .npmrc ├── .prettierignore ├── LICENSE ├── README.md ├── commitlint.config.js ├── example ├── .assets │ └── images │ │ └── pd.png ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── README.md ├── app.config.ts ├── babel.config.js ├── gradlePlugin.ts ├── metro.config.js ├── package.json ├── src │ ├── BLEService.ts │ ├── app │ │ ├── (home) │ │ │ ├── _layout.tsx │ │ │ ├── index.tsx │ │ │ └── select_device.tsx │ │ ├── _layout.tsx │ │ ├── bootloader_info.tsx │ │ └── update.tsx │ ├── context │ │ └── selectedDevice.ts │ └── hooks │ │ ├── useBluetoothDevices.ts │ │ ├── useFilePicker.ts │ │ └── useFirmwareUpdate.ts └── tsconfig.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── react-native-mcu-manager ├── .eslintrc.js ├── README.md ├── android │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── uk │ │ └── co │ │ └── playerdata │ │ └── reactnativemcumanager │ │ ├── DeviceUpgrade.kt │ │ ├── ReactNativeMcuManagerModule.kt │ │ ├── ReactNativeMcuMgrException.kt │ │ └── ZipPackage.java ├── expo-module.config.json ├── ios │ ├── BootloaderInfo.swift │ ├── DeviceUpgrade.swift │ ├── ReactNativeMcuManager.podspec │ ├── ReactNativeMcuManagerModule.swift │ └── UpdateOptions.swift ├── package.json ├── release.config.js ├── src │ ├── ReactNativeMcuManagerModule.ts │ ├── Upgrade.ts │ ├── bootloaderInfo.ts │ └── index.ts └── tsconfig.json └── renovate.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 4 | ignorePatterns: ['build/**'], 5 | }; 6 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | --------------------- 6 | 7 | Self Review: 8 | 9 | * [ ] Appropriate test coverage 10 | * [ ] Relevant Documentation updated 11 | 12 | Smoke Tests: 13 | 14 | * [ ] ... 15 | -------------------------------------------------------------------------------- /.github/workflows/build_and_release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | types: [opened, synchronize] 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | 22 | - name: Setup PNPM 23 | uses: pnpm/action-setup@v4 24 | with: 25 | version: 9 26 | run_install: true 27 | 28 | - name: Build 29 | working-directory: ./react-native-mcu-manager 30 | run: pnpm build 31 | 32 | android-example: 33 | name: Android example app 34 | runs-on: ubuntu-latest 35 | needs: build 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v4 39 | 40 | - uses: actions/setup-java@v4 41 | with: 42 | distribution: 'temurin' 43 | java-version: '18' 44 | 45 | - name: Setup Node.js 46 | uses: actions/setup-node@v4 47 | with: 48 | node-version: 20 49 | 50 | - name: Setup PNPM 51 | uses: pnpm/action-setup@v4 52 | with: 53 | version: 9 54 | run_install: true 55 | 56 | - name: Making sure the android example app builds 57 | run: | 58 | cd example 59 | npx expo prebuild --platform android 60 | 61 | cd android 62 | ./gradlew build 63 | 64 | ios-example: 65 | name: iOS example app 66 | runs-on: macos-14 67 | needs: build 68 | steps: 69 | - name: Checkout 70 | uses: actions/checkout@v4 71 | 72 | - name: Setup Node.js 73 | uses: actions/setup-node@v4 74 | with: 75 | node-version: 20 76 | 77 | - name: Setup PNPM 78 | uses: pnpm/action-setup@v4 79 | with: 80 | version: 9 81 | run_install: true 82 | 83 | - name: Making sure the ios example app builds 84 | run: | 85 | cd example 86 | npx expo prebuild --platform ios 87 | 88 | cd ios 89 | xcodebuild build -workspace reactnativemcumanagerexample.xcworkspace -scheme reactnativemcumanagerexample CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO 90 | 91 | release: 92 | name: Release 93 | needs: [android-example, ios-example] 94 | runs-on: ubuntu-latest 95 | 96 | permissions: 97 | contents: write 98 | pull-requests: write 99 | issues: write 100 | 101 | steps: 102 | - name: Checkout 103 | uses: actions/checkout@v4 104 | 105 | - name: Setup Node.js 106 | uses: actions/setup-node@v4 107 | with: 108 | node-version: 20 109 | 110 | - name: Setup PNPM 111 | uses: pnpm/action-setup@v4 112 | with: 113 | version: 9 114 | run_install: true 115 | 116 | - name: Release 117 | env: 118 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 119 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 120 | working-directory: react-native-mcu-manager 121 | run: npx semantic-release 122 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | 7 | jobs: 8 | lint: 9 | name: Lint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20 19 | 20 | - name: Setup PNPM 21 | uses: pnpm/action-setup@v4 22 | with: 23 | version: 9 24 | run_install: true 25 | 26 | - name: Lint 27 | run: pnpm lint 28 | 29 | - name: Typecheck MCU Manager 30 | run: pnpm typecheck 31 | working-directory: ./react-native-mcu-manager 32 | 33 | - name: Typecheck Example 34 | run: pnpm typecheck 35 | working-directory: ./example 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # VSCode 6 | .vscode/ 7 | jsconfig.json 8 | 9 | # Xcode 10 | # 11 | build/ 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata 21 | *.xccheckout 22 | *.moved-aside 23 | DerivedData 24 | *.hmap 25 | *.ipa 26 | *.xcuserstate 27 | project.xcworkspace 28 | 29 | # Android/IJ 30 | # 31 | .classpath 32 | .cxx 33 | .gradle 34 | .idea 35 | .project 36 | .settings 37 | local.properties 38 | android.iml 39 | android/app/libs 40 | android/keystores/debug.keystore 41 | 42 | # Cocoapods 43 | # 44 | example/ios/Pods 45 | 46 | # Ruby 47 | example/vendor/ 48 | 49 | # node.js 50 | # 51 | node_modules/ 52 | npm-debug.log 53 | yarn-debug.log 54 | yarn-error.log 55 | 56 | # Expo 57 | .expo/* 58 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Exclude all top-level hidden directories by convention 2 | /.*/ 3 | 4 | __mocks__ 5 | __tests__ 6 | 7 | /babel.config.js 8 | /android/src/androidTest/ 9 | /android/src/test/ 10 | /android/build/ 11 | /example/ 12 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 PlayerData Holdings Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @playerdata/react-native-mcu-manager 2 | 3 | React Native Wrappers for MCUMgr's Android / iOS client libraries 4 | 5 | # Getting started 6 | 7 | ## Installation in managed Expo projects 8 | 9 | For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, 10 | we hope this will Just Work :tm:. 11 | 12 | The example app uses Expo Prebuild, so we've some confidence, but let us know 13 | how you get on. 14 | 15 | ## Installation in bare React Native projects 16 | 17 | For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing. 18 | 19 | ### Add the package to your pnpm dependencies 20 | 21 | ``` 22 | pnpm install @playerdata/react-native-mcu-manager 23 | ``` 24 | 25 | ### Configure for iOS 26 | 27 | Run `npx pod-install` after installing the pnpm package. 28 | 29 | ### Configure for Android 30 | 31 | ## Usage 32 | 33 | ```ts 34 | import McuManager, { 35 | UpgradeOptions, 36 | } from '@playerdata/react-native-mcu-manager'; 37 | 38 | const upgradeOptions: UpgradeOptions = { 39 | estimatedSwapTime: 30, 40 | }; 41 | 42 | const onUploadProgress = (progress: number) => { 43 | console.log('Upload progress: ', progress); 44 | }; 45 | 46 | const onUploadStateChanged = (state: string) => { 47 | console.log('Upload state change: ', state); 48 | }; 49 | 50 | // bluetoothId is a MAC address on Android, and a UUID on iOS 51 | McuManager.updateDevice( 52 | bluetoothId, 53 | fileUri, 54 | upgradeOptions, 55 | onUploadProgress, 56 | onUploadStateChanged 57 | ); 58 | ``` 59 | 60 | # Contributing 61 | 62 | Contributions are very welcome! 63 | 64 | There are many examples of expo modules in the expo repo packages like 65 | https://github.com/expo/expo/blob/main/packages/expo-camera/README.md 66 | 67 | ## Development Workflow 68 | 69 | Install dependencies: 70 | 71 | ``` 72 | npm install 73 | ``` 74 | 75 | You should use the example app to test your changes: 76 | 77 | ``` 78 | cd example 79 | 80 | npx expo prebuild 81 | ``` 82 | 83 | From the top level of the repo, you can use `npm run open:(ios|android)` to open 84 | the appropriate IDE. 85 | 86 | For Swift files, you'll find the source files at `Pods > Development Pods > ReactNativeMcuManager` 87 | in XCode. 88 | 89 | For Kotlin, you'll find the source files at `reactnativemcumanager` under `Android` in Android Studio. 90 | 91 | Make sure your code passes TypeScript and ESLint. Run the following to verify: 92 | 93 | ```sh 94 | pnpm run typecheck 95 | pnpm run lint 96 | ``` 97 | 98 | To fix formatting errors, run the following: 99 | 100 | ```sh 101 | pnpm run typecheck run lint --fix 102 | ``` 103 | 104 | Remember to add unit tests for your change if possible. Run the unit tests by: 105 | 106 | ```sh 107 | pnpm run typecheck run test 108 | ``` 109 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'body-max-line-length': [0], 5 | 'subject-case': [0], 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /example/.assets/images/pd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlayerData/react-native-mcu-manager/01acf7938214b0ace3362b13e9e696f2ef694db6/example/.assets/images/pd.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Expo 3 | .expo 4 | dist/ 5 | web-build/ 6 | ios/ 7 | android/ -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby '3.4.4' 5 | 6 | gem 'cocoapods', '~> 1.11', '>= 1.11.2' 7 | -------------------------------------------------------------------------------- /example/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.7) 5 | base64 6 | nkf 7 | rexml 8 | activesupport (7.2.2.1) 9 | base64 10 | benchmark (>= 0.3) 11 | bigdecimal 12 | concurrent-ruby (~> 1.0, >= 1.3.1) 13 | connection_pool (>= 2.2.5) 14 | drb 15 | i18n (>= 1.6, < 2) 16 | logger (>= 1.4.2) 17 | minitest (>= 5.1) 18 | securerandom (>= 0.3) 19 | tzinfo (~> 2.0, >= 2.0.5) 20 | addressable (2.8.7) 21 | public_suffix (>= 2.0.2, < 7.0) 22 | algoliasearch (1.27.5) 23 | httpclient (~> 2.8, >= 2.8.3) 24 | json (>= 1.5.1) 25 | atomos (0.1.3) 26 | base64 (0.2.0) 27 | benchmark (0.4.1) 28 | bigdecimal (3.1.9) 29 | claide (1.1.0) 30 | cocoapods (1.16.1) 31 | addressable (~> 2.8) 32 | claide (>= 1.0.2, < 2.0) 33 | cocoapods-core (= 1.16.1) 34 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 35 | cocoapods-downloader (>= 2.1, < 3.0) 36 | cocoapods-plugins (>= 1.0.0, < 2.0) 37 | cocoapods-search (>= 1.0.0, < 2.0) 38 | cocoapods-trunk (>= 1.6.0, < 2.0) 39 | cocoapods-try (>= 1.1.0, < 2.0) 40 | colored2 (~> 3.1) 41 | escape (~> 0.0.4) 42 | fourflusher (>= 2.3.0, < 3.0) 43 | gh_inspector (~> 1.0) 44 | molinillo (~> 0.8.0) 45 | nap (~> 1.0) 46 | ruby-macho (>= 2.3.0, < 3.0) 47 | xcodeproj (>= 1.26.0, < 2.0) 48 | cocoapods-core (1.16.1) 49 | activesupport (>= 5.0, < 8) 50 | addressable (~> 2.8) 51 | algoliasearch (~> 1.0) 52 | concurrent-ruby (~> 1.1) 53 | fuzzy_match (~> 2.0.4) 54 | nap (~> 1.0) 55 | netrc (~> 0.11) 56 | public_suffix (~> 4.0) 57 | typhoeus (~> 1.0) 58 | cocoapods-deintegrate (1.0.5) 59 | cocoapods-downloader (2.1) 60 | cocoapods-plugins (1.0.0) 61 | nap 62 | cocoapods-search (1.0.1) 63 | cocoapods-trunk (1.6.0) 64 | nap (>= 0.8, < 2.0) 65 | netrc (~> 0.11) 66 | cocoapods-try (1.2.0) 67 | colored2 (3.1.2) 68 | concurrent-ruby (1.3.5) 69 | connection_pool (2.4.1) 70 | drb (2.2.3) 71 | escape (0.0.4) 72 | ethon (0.16.0) 73 | ffi (>= 1.15.0) 74 | ffi (1.17.2) 75 | fourflusher (2.3.1) 76 | fuzzy_match (2.0.4) 77 | gh_inspector (1.1.3) 78 | httpclient (2.8.3) 79 | i18n (1.14.7) 80 | concurrent-ruby (~> 1.0) 81 | json (2.7.6) 82 | logger (1.6.6) 83 | minitest (5.25.5) 84 | molinillo (0.8.0) 85 | nanaimo (0.4.0) 86 | nap (1.1.0) 87 | netrc (0.11.0) 88 | nkf (0.2.0) 89 | public_suffix (4.0.7) 90 | rexml (3.3.9) 91 | ruby-macho (2.5.1) 92 | securerandom (0.3.2) 93 | typhoeus (1.4.1) 94 | ethon (>= 0.9.0) 95 | tzinfo (2.0.6) 96 | concurrent-ruby (~> 1.0) 97 | xcodeproj (1.26.0) 98 | CFPropertyList (>= 2.3.3, < 4.0) 99 | atomos (~> 0.1.3) 100 | claide (>= 1.0.2, < 2.0) 101 | colored2 (~> 3.1) 102 | nanaimo (~> 0.4.0) 103 | rexml (>= 3.3.6, < 4.0) 104 | 105 | PLATFORMS 106 | ruby 107 | 108 | DEPENDENCIES 109 | cocoapods (~> 1.11, >= 1.11.2) 110 | 111 | RUBY VERSION 112 | ruby 3.4.4p34 113 | 114 | BUNDLED WITH 115 | 2.5.9 116 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | Example app for this library. 2 | To build the android and iOS folder use `npx expo prebuild`, then you can run them using `npx expo run:(android|ios)`. 3 | More documentation on this process https://docs.expo.dev/workflow/prebuild/ -------------------------------------------------------------------------------- /example/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ExpoConfig } from '@expo/config-types'; 2 | import 'ts-node/register'; 3 | 4 | const config: ExpoConfig = { 5 | name: "react-native-mcu-manager-example", 6 | slug: "react-native-mcu-manager-example", 7 | assetBundlePatterns: ["**/*"], 8 | orientation: "portrait", 9 | platforms: ["ios", "android"], 10 | scheme: "rnmcumgr", 11 | version: "1.0.0", 12 | splash: { 13 | image: ".assets/images/pd.png", 14 | backgroundColor: "#FFFFFF", 15 | }, 16 | ios: { 17 | supportsTablet: true, 18 | bundleIdentifier: "uk.co.playerdata.reactnativemcumanager.example", 19 | infoPlist: { 20 | NSBluetoothAlwaysUsageDescription: "Requires Bluetooth to perform firmware updates.", 21 | }, 22 | }, 23 | android: { 24 | package: "uk.co.playerdata.reactnativemcumanager.example", 25 | permissions: [ 26 | "ACCESS_FINE_LOCATION", 27 | "ACCESS_COARSE_LOCATION", 28 | ], 29 | }, 30 | plugins: [ 31 | ["expo-document-picker"], 32 | ["expo-router"], 33 | ["./gradlePlugin.ts"] 34 | ] 35 | }; 36 | 37 | export default config; 38 | -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = function (api) { 3 | api.cache(true); 4 | 5 | return { 6 | presets: ['babel-preset-expo'], 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /example/gradlePlugin.ts: -------------------------------------------------------------------------------- 1 | import { withGradleProperties } from '@expo/config-plugins'; 2 | import { ConfigPlugin } from '@expo/config-plugins'; 3 | import { ExpoConfig } from '@expo/config-types'; 4 | 5 | /** 6 | * A Config Plugin to modify android/gradle.properties. 7 | */ 8 | const withCustomGradleProps: ConfigPlugin = (config) => { 9 | return withGradleProperties(config, (config) => { 10 | const gradleProperties = config.modResults; 11 | 12 | gradleProperties.push( 13 | { type: 'property', key: "org.gradle.parallel", value: "true" }, 14 | { type: 'property', key: "org.gradle.daemon", value: "true" }, 15 | { type: 'property', key: "org.gradle.jvmargs", value: "-Xmx4g -Dfile.encoding=UTF-8" }, 16 | { type: 'property', key: "org.gradle.configureondemand", value: "true" } 17 | ); 18 | 19 | return config; 20 | }); 21 | }; 22 | 23 | export default withCustomGradleProps; 24 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports */ 2 | /* eslint-disable no-undef */ 3 | 4 | const { getDefaultConfig } = require('expo/metro-config'); 5 | const path = require('path'); 6 | 7 | const projectRoot = __dirname; 8 | const monorepoRoot = path.resolve(projectRoot, '..'); 9 | 10 | const config = getDefaultConfig(projectRoot); 11 | 12 | config.watchFolders = [monorepoRoot]; 13 | config.resolver.nodeModulesPaths = [ 14 | path.resolve(projectRoot, 'node_modules'), 15 | path.resolve(monorepoRoot, 'node_modules'), 16 | ]; 17 | 18 | module.exports = config; 19 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-mcu-manager-example", 3 | "description": "Example app for react-native-mcu-manager", 4 | "main": "expo-router/entry", 5 | "version": "0.0.1", 6 | "license": "MIT", 7 | "scripts": { 8 | "android": "expo run:android", 9 | "ios": "expo run:ios", 10 | "start": "expo start", 11 | "pods": "pod-install --quiet", 12 | "typecheck": "pnpm tsc --noEmit" 13 | }, 14 | "dependencies": { 15 | "@playerdata/react-native-mcu-manager": "workspace:*", 16 | "expo": "53.0.10", 17 | "expo-constants": "~17.0.4", 18 | "expo-document-picker": "13.0.3", 19 | "expo-linking": "~7.0.4", 20 | "expo-router": "~5.0.0", 21 | "expo-splash-screen": "0.29.24", 22 | "expo-status-bar": "~2.0.1", 23 | "lodash": "4.17.21", 24 | "react": "18.3.1", 25 | "react-native": "0.76.9", 26 | "react-native-ble-plx": "3.5.0", 27 | "react-native-reanimated": "~3.18.0", 28 | "react-native-safe-area-context": "5.4.1", 29 | "react-native-screens": "~4.11.0", 30 | "react-native-toast-message": "2.3.0" 31 | }, 32 | "devDependencies": { 33 | "@babel/core": "7.27.4", 34 | "@babel/runtime": "7.27.6", 35 | "@types/lodash": "4.17.17", 36 | "@types/react": "19.1.6", 37 | "metro-react-native-babel-preset": "0.77.0", 38 | "ts-node": "^10.9.2", 39 | "typescript": "5.8.3" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/src/BLEService.ts: -------------------------------------------------------------------------------- 1 | import { PermissionsAndroid, Platform } from 'react-native'; 2 | import { 3 | BleError, 4 | BleErrorCode, 5 | BleManager, 6 | State as BluetoothState, 7 | LogLevel, 8 | } from 'react-native-ble-plx'; 9 | import Toast from 'react-native-toast-message'; 10 | 11 | class BLEServiceInstance { 12 | manager: BleManager; 13 | 14 | constructor() { 15 | this.manager = new BleManager(); 16 | this.manager.setLogLevel(LogLevel.Verbose); 17 | } 18 | 19 | initializeBLE = () => 20 | new Promise((resolve) => { 21 | const subscription = this.manager.onStateChange((state) => { 22 | switch (state) { 23 | case BluetoothState.Unsupported: 24 | this.showErrorToast(''); 25 | break; 26 | case BluetoothState.PoweredOff: 27 | this.onBluetoothPowerOff(); 28 | this.manager.enable().catch((error: BleError) => { 29 | if (error.errorCode === BleErrorCode.BluetoothUnauthorized) { 30 | this.requestBluetoothPermission(); 31 | } 32 | }); 33 | break; 34 | case BluetoothState.Unauthorized: 35 | this.requestBluetoothPermission(); 36 | break; 37 | case BluetoothState.PoweredOn: 38 | resolve(); 39 | subscription.remove(); 40 | break; 41 | default: 42 | console.error('Unsupported state: ', state); 43 | } 44 | }, true); 45 | }); 46 | 47 | private onBluetoothPowerOff = () => { 48 | this.showErrorToast('Bluetooth is turned off'); 49 | }; 50 | 51 | private requestBluetoothPermission = async () => { 52 | if (Platform.OS === 'ios') { 53 | return true; 54 | } 55 | if ( 56 | Platform.OS === 'android' && 57 | PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION 58 | ) { 59 | const apiLevel = parseInt(Platform.Version.toString(), 10); 60 | 61 | if (apiLevel < 31) { 62 | const granted = await PermissionsAndroid.request( 63 | PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION 64 | ); 65 | return granted === PermissionsAndroid.RESULTS.GRANTED; 66 | } 67 | if ( 68 | PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN && 69 | PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT 70 | ) { 71 | const result = await PermissionsAndroid.requestMultiple([ 72 | PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN, 73 | PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT, 74 | ]); 75 | 76 | return ( 77 | result['android.permission.BLUETOOTH_CONNECT'] === 78 | PermissionsAndroid.RESULTS.GRANTED && 79 | result['android.permission.BLUETOOTH_SCAN'] === 80 | PermissionsAndroid.RESULTS.GRANTED 81 | ); 82 | } 83 | } 84 | 85 | this.showErrorToast('Permission have not been granted'); 86 | 87 | return false; 88 | }; 89 | 90 | private showErrorToast = (error: string) => { 91 | Toast.show({ 92 | type: 'error', 93 | text1: 'Error', 94 | text2: error, 95 | }); 96 | console.error(error); 97 | }; 98 | } 99 | 100 | export const BLEService = new BLEServiceInstance(); 101 | -------------------------------------------------------------------------------- /example/src/app/(home)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from 'expo-router'; 2 | 3 | const HomeLayout = () => { 4 | return ( 5 | 6 | 7 | 11 | 12 | ); 13 | }; 14 | 15 | export default HomeLayout; 16 | -------------------------------------------------------------------------------- /example/src/app/(home)/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, StyleSheet, Text, View } from 'react-native'; 3 | 4 | import { Link } from 'expo-router'; 5 | 6 | import { resetDevice } from '@playerdata/react-native-mcu-manager'; 7 | 8 | import { useSelectedDevice } from '../../context/selectedDevice'; 9 | 10 | const styles = StyleSheet.create({ 11 | root: { 12 | padding: 16, 13 | }, 14 | 15 | block: { 16 | marginBottom: 16, 17 | }, 18 | }); 19 | 20 | const Home = () => { 21 | const { selectedDevice } = useSelectedDevice(); 22 | const [resetState, setResetState] = useState(''); 23 | 24 | return ( 25 | 26 | 27 | 28 | Select a device, then use the tabs below to choose which function to 29 | test 30 | 31 | 32 | 33 | 34 |