├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .nvmrc ├── .tern-project ├── CHANGELOG.md ├── Example ├── .buckconfig ├── .eslintignore ├── .eslintrc.json ├── .flowconfig ├── .gitignore ├── .watchmanconfig ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── keystores │ │ ├── BUCK │ │ └── debug.keystore.properties │ └── settings.gradle ├── app │ ├── actions │ │ ├── DeviceActionTypes.js │ │ ├── DeviceActions.js │ │ ├── DeviceContextActionTypes.js │ │ ├── DeviceContextActions.js │ │ ├── DiscoveryActionTypes.js │ │ ├── DiscoveryActions.js │ │ ├── GlobalActionTypes.js │ │ ├── GlobalActions.js │ │ ├── ServiceActionTypes.js │ │ └── ServiceActions.js │ ├── components │ │ ├── Button.js │ │ ├── CharacteristicList.js │ │ ├── CharacteristicNotify.js │ │ ├── CharacteristicRead.js │ │ ├── CharacteristicWrite.js │ │ ├── DeviceList.js │ │ ├── NoBluetooth.js │ │ ├── ServiceList.js │ │ └── TopBar.js │ ├── containers │ │ ├── App.js │ │ ├── CharacteristicDetail.js │ │ ├── DeviceDetail.js │ │ ├── DeviceDiscovery.js │ │ └── ServiceDetail.js │ ├── lib │ │ ├── GlobalState.js │ │ └── Routes.js │ └── reducers │ │ ├── Device.js │ │ ├── DeviceContext.js │ │ ├── Discovery.js │ │ ├── Global.js │ │ ├── Service.js │ │ └── index.js ├── index.android.js ├── index.ios.js ├── ios │ ├── Example.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Example.xcscheme │ ├── Example │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── main.m │ └── ExampleTests │ │ ├── ExampleTests.m │ │ └── Info.plist └── package.json ├── LICENSE ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── sogilis │ └── ReactNativeBluetooth │ ├── BluetoothAction.java │ ├── BluetoothActionsLoop.java │ ├── Constants.java │ ├── ReactNativeBluetoothModule.java │ ├── ReactNativeBluetoothPackage.java │ ├── domain │ ├── BluetoothException.java │ ├── BluetoothHelpers.java │ ├── DeviceCollection.java │ └── GattCollection.java │ └── events │ ├── BluetoothEvent.java │ ├── EventBuilders.java │ ├── EventEmitter.java │ └── EventNames.java ├── index.js ├── ios ├── .swiftlint.yml ├── CoreBluetoothSwift │ ├── Aliases.swift │ ├── BluetoothActions.swift │ ├── CentralEventHandler.swift │ ├── CoreBluetoothSwift.h │ ├── DictionaryExtensions.swift │ ├── Info.plist │ ├── OutputBuilder.swift │ ├── PeripheralEventHandler.swift │ └── PeripheralStore.swift ├── ReactNativeBluetooth.xcodeproj │ └── project.pbxproj └── ReactNativeBluetooth │ ├── ReactNativeBluetooth.h │ └── ReactNativeBluetooth.m ├── package.json ├── src ├── characteristicRead.js ├── characteristicWrite.js ├── connection.js ├── discovery.js ├── lib.js └── scanStartStop.js └── test ├── .eslintrc.json ├── connectTests.js ├── disconnectTests.js ├── discoverCharacteristicsTests.js ├── discoverServicesTests.js ├── notifyTests.js ├── reactNativeMock.js ├── readCharacteristicTests.js ├── startStopScanTests.js ├── testCommon.js └── writeCharacteristicTests.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "presets": ["react-native"], 5 | }, 6 | "production": { 7 | "presets": ["react-native"], 8 | }, 9 | "test": { 10 | "presets": ["react-native"], 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | android 2 | ios 3 | node_modules 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": true, 7 | "spread": true, 8 | "binaryLiterals": true, 9 | "blockBindings": true, 10 | "templateStrings": true, 11 | "modules": true, 12 | "destructuring": true, 13 | "generators": true, 14 | "experimentalObjectRestSpread": true, 15 | "impliedStrict": true 16 | } 17 | }, 18 | "plugins": [ 19 | "react", 20 | "react-native" 21 | ], 22 | "globals": { 23 | "__DEV__": false 24 | }, 25 | "extends": [ 26 | "eslint:recommended", 27 | "plugin:react/recommended" 28 | ], 29 | "env": { 30 | "browser": true, 31 | "es6": true, 32 | "node": true 33 | }, 34 | "rules": { 35 | "semi": [ 36 | 2, 37 | "always" 38 | ], 39 | "no-console": 0, 40 | "react-native/no-unused-styles": 2, 41 | "react-native/split-platform-components": 2, 42 | "react-native/no-inline-styles": 2, 43 | "react-native/no-color-literals": 0, 44 | "react/display-name": 0 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | android/app/enlaps.keystore 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | 38 | # BUCK 39 | buck-out/ 40 | \.buckd/ 41 | android/app/libs 42 | android/keystores/debug.keystore 43 | 44 | # Enlaps 45 | /TempResources/ 46 | 47 | # Carthage 48 | # 49 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 50 | ios/Carthage/Checkouts 51 | ios/Carthage/Build 52 | 53 | # Fastlane 54 | 2R72QGPL88.cer 55 | AppStore_io.enlaps.tikee.dev.mobileprovision 56 | Enlaps.app.dSYM.zip 57 | Enlaps.ipa 58 | ios/fastlane/report.xml 59 | 60 | #React native 61 | ios/main.jsbundle 62 | 63 | #Crashlytics 64 | android/app/src/main/assets/crashlytics-build.properties 65 | android/app/src/main/res/values/com_crashlytics_export_strings.xml 66 | 67 | #Anon 68 | environment.plist 69 | environment_preprocess.h 70 | /android/build/ 71 | /android/react-native-bluetooth.iml 72 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /Example 2 | /android/build 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v6.5.0 2 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaVersion": 6, 3 | "libs": [ 4 | "ecma6", 5 | "browser", 6 | "underscore" 7 | ], 8 | "loadEagerly": [ 9 | ], 10 | "plugins": { 11 | "complete_strings": {}, 12 | "modules": {}, 13 | "es_modules": {}, 14 | "requirejs": {}, 15 | "node": {}, 16 | "jsx": {}, 17 | "doc_comment": { 18 | "fullDocs": true 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.1.1](https://github.com/sogilis/react-native-bluetooth-manager/tree/0.1.1) (2016-10-04) 4 | [Full Changelog](https://github.com/sogilis/react-native-bluetooth-manager/compare/0.1.0...0.1.1) 5 | 6 | **Closed issues:** 7 | 8 | - Id comparison is case sensitive [\#4](https://github.com/sogilis/react-native-bluetooth-manager/issues/4) 9 | 10 | ## [0.1.0](https://github.com/sogilis/react-native-bluetooth-manager/tree/0.1.0) (2016-10-03) 11 | **Closed issues:** 12 | 13 | - onServicesDiscovered An error [\#3](https://github.com/sogilis/react-native-bluetooth-manager/issues/3) 14 | 15 | **Merged pull requests:** 16 | 17 | - Device list [\#2](https://github.com/sogilis/react-native-bluetooth-manager/pull/2) ([sebn](https://github.com/sebn)) 18 | - Bluetooth state change [\#1](https://github.com/sogilis/react-native-bluetooth-manager/pull/1) ([sebn](https://github.com/sebn)) 19 | 20 | 21 | 22 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /Example/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /Example/.eslintignore: -------------------------------------------------------------------------------- 1 | android 2 | ios 3 | -------------------------------------------------------------------------------- /Example/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": true, 7 | "spread": true, 8 | "binaryLiterals": true, 9 | "blockBindings": true, 10 | "templateStrings": true, 11 | "modules": true, 12 | "destructuring": true, 13 | "generators": true, 14 | "experimentalObjectRestSpread": true, 15 | "impliedStrict": true 16 | } 17 | }, 18 | "plugins": [ 19 | "react", 20 | "react-native" 21 | ], 22 | "globals": { 23 | "__DEV__": false 24 | }, 25 | "extends": [ 26 | "eslint:recommended", 27 | "plugin:react/recommended" 28 | ], 29 | "env": { 30 | "browser": true, 31 | "es6": true, 32 | "node": true 33 | }, 34 | "rules": { 35 | "semi": [ 36 | 2, 37 | "always" 38 | ], 39 | "no-console": 0, 40 | "react-native/no-unused-styles": 2, 41 | "react-native/split-platform-components": 2, 42 | "react-native/no-inline-styles": 2, 43 | "react-native/no-color-literals": 0, 44 | "react/display-name": 0 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Example/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | # We fork some components by platform. 4 | .*/*[.]android.js 5 | 6 | # Ignore templates with `@flow` in header 7 | .*/local-cli/generator.* 8 | 9 | # Ignore malformed json 10 | .*/node_modules/y18n/test/.*\.json 11 | 12 | # Ignore the website subdir 13 | /website/.* 14 | 15 | # Ignore BUCK generated dirs 16 | /\.buckd/ 17 | 18 | # Ignore unexpected extra @providesModule 19 | .*/node_modules/commoner/test/source/widget/share.js 20 | 21 | # Ignore duplicate module providers 22 | # For RN Apps installed via npm, "Libraries" folder is inside node_modules/react-native but in the source repo it is in the root 23 | .*/Libraries/react-native/React.js 24 | .*/Libraries/react-native/ReactNative.js 25 | .*/node_modules/jest-runtime/build/__tests__/.* 26 | 27 | [include] 28 | 29 | [libs] 30 | node_modules/react-native/Libraries/react-native/react-native-interface.js 31 | node_modules/react-native/flow 32 | flow/ 33 | 34 | [options] 35 | module.system=haste 36 | 37 | esproposal.class_static_fields=enable 38 | esproposal.class_instance_fields=enable 39 | 40 | experimental.strict_type_args=true 41 | 42 | munge_underscores=true 43 | 44 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 45 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 46 | 47 | suppress_type=$FlowIssue 48 | suppress_type=$FlowFixMe 49 | suppress_type=$FixMe 50 | 51 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(30\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 52 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(30\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 53 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 54 | 55 | unsafe.enable_getters_and_setters=true 56 | 57 | [version] 58 | ^0.30.0 59 | -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | 37 | # BUCK 38 | buck-out/ 39 | \.buckd/ 40 | android/app/libs 41 | android/keystores/debug.keystore 42 | -------------------------------------------------------------------------------- /Example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /Example/android/app/BUCK: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # To learn about Buck see [Docs](https://buckbuild.com/). 4 | # To run your application with Buck: 5 | # - install Buck 6 | # - `npm start` - to start the packager 7 | # - `cd android` 8 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 9 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 10 | # - `buck install -r android/app` - compile, install and run application 11 | # 12 | 13 | lib_deps = [] 14 | for jarfile in glob(['libs/*.jar']): 15 | name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) 16 | lib_deps.append(':' + name) 17 | prebuilt_jar( 18 | name = name, 19 | binary_jar = jarfile, 20 | ) 21 | 22 | for aarfile in glob(['libs/*.aar']): 23 | name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) 24 | lib_deps.append(':' + name) 25 | android_prebuilt_aar( 26 | name = name, 27 | aar = aarfile, 28 | ) 29 | 30 | android_library( 31 | name = 'all-libs', 32 | exported_deps = lib_deps 33 | ) 34 | 35 | android_library( 36 | name = 'app-code', 37 | srcs = glob([ 38 | 'src/main/java/**/*.java', 39 | ]), 40 | deps = [ 41 | ':all-libs', 42 | ':build_config', 43 | ':res', 44 | ], 45 | ) 46 | 47 | android_build_config( 48 | name = 'build_config', 49 | package = 'com.example', 50 | ) 51 | 52 | android_resource( 53 | name = 'res', 54 | res = 'src/main/res', 55 | package = 'com.example', 56 | ) 57 | 58 | android_binary( 59 | name = 'app', 60 | package_type = 'debug', 61 | manifest = 'src/main/AndroidManifest.xml', 62 | keystore = '//android/keystores:debug', 63 | deps = [ 64 | ':app-code', 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /Example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // whether to bundle JS and assets in debug mode 22 | * bundleInDebug: false, 23 | * 24 | * // whether to bundle JS and assets in release mode 25 | * bundleInRelease: true, 26 | * 27 | * // whether to bundle JS and assets in another build variant (if configured). 28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 29 | * // The configuration property can be in the following formats 30 | * // 'bundleIn${productFlavor}${buildType}' 31 | * // 'bundleIn${buildType}' 32 | * // bundleInFreeDebug: true, 33 | * // bundleInPaidRelease: true, 34 | * // bundleInBeta: true, 35 | * 36 | * // the root of your project, i.e. where "package.json" lives 37 | * root: "../../", 38 | * 39 | * // where to put the JS bundle asset in debug mode 40 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 41 | * 42 | * // where to put the JS bundle asset in release mode 43 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 44 | * 45 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 46 | * // require('./image.png')), in debug mode 47 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 48 | * 49 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 50 | * // require('./image.png')), in release mode 51 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 52 | * 53 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 54 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 55 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 56 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 57 | * // for example, you might want to remove it from here. 58 | * inputExcludes: ["android/**", "ios/**"], 59 | * 60 | * // override which node gets called and with what additional arguments 61 | * nodeExecutableAndArgs: ["node"] 62 | * 63 | * // supply additional arguments to the packager 64 | * extraPackagerArgs: [] 65 | * ] 66 | */ 67 | 68 | apply from: "../../node_modules/react-native/react.gradle" 69 | 70 | /** 71 | * Set this to true to create two separate APKs instead of one: 72 | * - An APK that only works on ARM devices 73 | * - An APK that only works on x86 devices 74 | * The advantage is the size of the APK is reduced by about 4MB. 75 | * Upload all the APKs to the Play Store and people will download 76 | * the correct one based on the CPU architecture of their device. 77 | */ 78 | def enableSeparateBuildPerCPUArchitecture = false 79 | 80 | /** 81 | * Run Proguard to shrink the Java bytecode in release builds. 82 | */ 83 | def enableProguardInReleaseBuilds = false 84 | 85 | android { 86 | compileSdkVersion 23 87 | buildToolsVersion "23.0.1" 88 | 89 | defaultConfig { 90 | applicationId "com.example" 91 | minSdkVersion 21 92 | targetSdkVersion 22 93 | versionCode 1 94 | versionName "1.0" 95 | ndk { 96 | abiFilters "armeabi-v7a", "x86" 97 | } 98 | } 99 | splits { 100 | abi { 101 | reset() 102 | enable enableSeparateBuildPerCPUArchitecture 103 | universalApk false // If true, also generate a universal APK 104 | include "armeabi-v7a", "x86" 105 | } 106 | } 107 | buildTypes { 108 | release { 109 | minifyEnabled enableProguardInReleaseBuilds 110 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 111 | } 112 | } 113 | // applicationVariants are e.g. debug, release 114 | applicationVariants.all { variant -> 115 | variant.outputs.each { output -> 116 | // For each separate APK per architecture, set a unique version code as described here: 117 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 118 | def versionCodes = ["armeabi-v7a":1, "x86":2] 119 | def abi = output.getFilter(OutputFile.ABI) 120 | if (abi != null) { // null for the universal-debug, universal-release variants 121 | output.versionCodeOverride = 122 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 123 | } 124 | } 125 | } 126 | } 127 | 128 | dependencies { 129 | compile fileTree(dir: "libs", include: ["*.jar"]) 130 | compile "com.android.support:appcompat-v7:23.0.1" 131 | compile "com.facebook.react:react-native:+" // From node_modules 132 | compile project(':react-native-bluetooth') 133 | } 134 | 135 | // Run this once to be able to run the application with BUCK 136 | // puts all compile dependencies into folder libs for BUCK to use 137 | task copyDownloadableDepsToLibs(type: Copy) { 138 | from configurations.compile 139 | into 'libs' 140 | } 141 | -------------------------------------------------------------------------------- /Example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # okhttp 54 | 55 | -keepattributes Signature 56 | -keepattributes *Annotation* 57 | -keep class okhttp3.** { *; } 58 | -keep interface okhttp3.** { *; } 59 | -dontwarn okhttp3.** 60 | 61 | # okio 62 | 63 | -keep class sun.misc.Unsafe { *; } 64 | -dontwarn java.nio.file.* 65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 66 | -dontwarn okio.** 67 | -------------------------------------------------------------------------------- /Example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Example/android/app/src/main/java/com/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "Example"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/android/app/src/main/java/com/example/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.facebook.react.ReactApplication; 7 | import com.facebook.react.ReactInstanceManager; 8 | import com.facebook.react.ReactNativeHost; 9 | import com.facebook.react.ReactPackage; 10 | import com.facebook.react.shell.MainReactPackage; 11 | 12 | import com.sogilis.ReactNativeBluetooth.ReactNativeBluetoothPackage; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | public class MainApplication extends Application implements ReactApplication { 18 | 19 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 20 | @Override 21 | protected boolean getUseDeveloperSupport() { 22 | return BuildConfig.DEBUG; 23 | } 24 | 25 | @Override 26 | protected List getPackages() { 27 | return Arrays.asList( 28 | new MainReactPackage(), 29 | new ReactNativeBluetoothPackage() 30 | ); 31 | } 32 | }; 33 | 34 | @Override 35 | public ReactNativeHost getReactNativeHost() { 36 | return mReactNativeHost; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogilis/react-native-bluetooth-manager/5824dc20f84952b6401f125f37a1e74902dcfde1/Example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogilis/react-native-bluetooth-manager/5824dc20f84952b6401f125f37a1e74902dcfde1/Example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogilis/react-native-bluetooth-manager/5824dc20f84952b6401f125f37a1e74902dcfde1/Example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogilis/react-native-bluetooth-manager/5824dc20f84952b6401f125f37a1e74902dcfde1/Example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Example 3 | 4 | -------------------------------------------------------------------------------- /Example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.1' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$rootDir/../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | -------------------------------------------------------------------------------- /Example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogilis/react-native-bluetooth-manager/5824dc20f84952b6401f125f37a1e74902dcfde1/Example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip 6 | -------------------------------------------------------------------------------- /Example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /Example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /Example/android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | visibility = [ 6 | 'PUBLIC', 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /Example/android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /Example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Example' 2 | 3 | include ':app' 4 | 5 | include ':react-native-bluetooth' 6 | project(':react-native-bluetooth').projectDir = new File(rootProject.projectDir, '../../android') 7 | -------------------------------------------------------------------------------- /Example/app/actions/DeviceActionTypes.js: -------------------------------------------------------------------------------- 1 | export const SERVICEDISCOVERED = "SERVICEDISCOVERED"; 2 | export const DISCONNECTIONHANDLER = "DISCONNECTIONHANDLER"; 3 | export const CONNECTIONSTATUS = "CONNECTIONSTATUS"; 4 | export const CONNECTIONINPROGRESS = "CONNECTIONINPROGRESS"; 5 | export const RESETSERVICES = "RESETSERVICES"; 6 | -------------------------------------------------------------------------------- /Example/app/actions/DeviceActions.js: -------------------------------------------------------------------------------- 1 | import * as types from './DeviceActionTypes'; 2 | 3 | export function serviceDiscovered(service) { 4 | return { 5 | type: types.SERVICEDISCOVERED, 6 | service: service 7 | }; 8 | } 9 | 10 | export function storeDisconnectionHandler(handler) { 11 | return { 12 | type: types.DISCONNECTIONHANDLER, 13 | handler: handler, 14 | }; 15 | } 16 | 17 | export function setConnectionStatus(isConnected) { 18 | return { 19 | type: types.CONNECTIONSTATUS, 20 | isConnected: isConnected, 21 | }; 22 | } 23 | 24 | export function setConnectionInProgress(inProgress) { 25 | return { 26 | type: types.CONNECTIONINPROGRESS, 27 | inProgress: inProgress, 28 | }; 29 | } 30 | 31 | export function resetServices() { 32 | return { 33 | type: types.RESETSERVICES, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /Example/app/actions/DeviceContextActionTypes.js: -------------------------------------------------------------------------------- 1 | export const SETDEVICE = "SETDEVICE"; 2 | export const SETSERVICE = "SETSERVICE"; 3 | export const SETCHARACTERISTIC = "SETCHARACTERISTIC"; 4 | -------------------------------------------------------------------------------- /Example/app/actions/DeviceContextActions.js: -------------------------------------------------------------------------------- 1 | import * as types from './DeviceContextActionTypes'; 2 | 3 | export function setDevice(device) { 4 | return { 5 | type: types.SETDEVICE, 6 | device: device, 7 | }; 8 | } 9 | 10 | export function setService(service) { 11 | return { 12 | type: types.SETSERVICE, 13 | service: service, 14 | }; 15 | } 16 | 17 | export function setCharacteristic(characteristic) { 18 | return { 19 | type: types.SETCHARACTERISTIC, 20 | characteristic: characteristic, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /Example/app/actions/DiscoveryActionTypes.js: -------------------------------------------------------------------------------- 1 | export const DEVICEDISCOVERED = "DEVICEDISCOVERED"; 2 | export const DISCOVERYSTATUSCHANGE = "DISCOVERYSTATUSCHANGE"; 3 | export const RESETDEVICES = "RESETDEVICES"; 4 | -------------------------------------------------------------------------------- /Example/app/actions/DiscoveryActions.js: -------------------------------------------------------------------------------- 1 | import * as types from './DiscoveryActionTypes'; 2 | 3 | export function deviceDiscovered(device) { 4 | return { 5 | type: types.DEVICEDISCOVERED, 6 | device: device 7 | }; 8 | } 9 | 10 | export function discoveryStatusChange(status) { 11 | return { 12 | type: types.DISCOVERYSTATUSCHANGE, 13 | status: status, 14 | }; 15 | } 16 | 17 | export function resetDevices() { 18 | return { 19 | type: types.RESETDEVICES, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /Example/app/actions/GlobalActionTypes.js: -------------------------------------------------------------------------------- 1 | export const APPLICATIONERROR = "APPLICATIONERROR"; 2 | export const RESETERROR = "RESETERROR"; 3 | -------------------------------------------------------------------------------- /Example/app/actions/GlobalActions.js: -------------------------------------------------------------------------------- 1 | import * as types from './GlobalActionTypes'; 2 | 3 | export function applicationError(error) { 4 | return { 5 | type: types.APPLICATIONERROR, 6 | error: error, 7 | }; 8 | } 9 | 10 | export function resetApplicationError() { 11 | return { 12 | type: types.RESETERROR, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /Example/app/actions/ServiceActionTypes.js: -------------------------------------------------------------------------------- 1 | export const CHARACTERISTICDISCOVERED = "CHARACTERISTICDISCOVERED"; 2 | export const RESETCHARACTERISTICS = "RESETCHARACTERISTICS"; 3 | -------------------------------------------------------------------------------- /Example/app/actions/ServiceActions.js: -------------------------------------------------------------------------------- 1 | import * as types from './ServiceActionTypes'; 2 | 3 | export function characteristicDiscovered(characteristic) { 4 | return { 5 | type: types.CHARACTERISTICDISCOVERED, 6 | characteristic: characteristic 7 | }; 8 | } 9 | 10 | export function resetCharacteristics() { 11 | return { 12 | type: types.RESETCHARACTERISTICS, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /Example/app/components/Button.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | PropTypes, 3 | } from 'react'; 4 | 5 | import { 6 | StyleSheet, 7 | Text, 8 | TouchableHighlight, 9 | View, 10 | } from 'react-native'; 11 | 12 | const Button = React.createClass({ 13 | propTypes: { 14 | onPress: PropTypes.func, 15 | style: View.propTypes.style, 16 | children: PropTypes.string, 17 | }, 18 | 19 | getInitialState() { 20 | return { 21 | active: false, 22 | }; 23 | }, 24 | 25 | onHighlight() { 26 | this.setState({active: true}); 27 | }, 28 | 29 | onUnhighlight() { 30 | this.setState({active: false}); 31 | }, 32 | 33 | render() { 34 | return ( 35 | 41 | ); 42 | } 43 | }); 44 | 45 | const ButtonRender = ({onHighlight, onUnhighlight, onPress, isActive, children, style}) => { 46 | const colorStyle = { 47 | backgroundColor: isActive ? 'grey' : '#00AFEE', 48 | }; 49 | 50 | return ( 51 | 57 | {children} 58 | 59 | ); 60 | }; 61 | 62 | const styles = StyleSheet.create({ 63 | button: { 64 | borderRadius: 5, 65 | alignSelf: 'stretch', 66 | justifyContent: 'center', 67 | overflow: 'hidden', 68 | }, 69 | buttonText: { 70 | fontSize: 18, 71 | margin: 5, 72 | textAlign: 'center', 73 | color: 'white', 74 | }, 75 | }); 76 | 77 | 78 | ButtonRender.propTypes = { 79 | onHighlight: PropTypes.func, 80 | onUnhighlight: PropTypes.func, 81 | onPress: PropTypes.func, 82 | isActive: PropTypes.bool, 83 | children: PropTypes.node, 84 | style: View.propTypes.style, 85 | }; 86 | 87 | export default Button; 88 | 89 | -------------------------------------------------------------------------------- /Example/app/components/CharacteristicList.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { 3 | Text, 4 | TouchableOpacity, 5 | StyleSheet, 6 | ListView, 7 | RecyclerViewBackedScrollView, 8 | View, 9 | } from 'react-native'; 10 | 11 | const makeDataSource = characteristics => { 12 | const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); 13 | 14 | return ds.cloneWithRows(characteristics); 15 | }; 16 | 17 | const renderCharacteristicRow = selectCharacteristic => { 18 | return characteristic => { 19 | if (!characteristic) return ; 20 | 21 | return ( 22 | selectCharacteristic(characteristic)} 24 | key={characteristic.id} 25 | style={styles.textHolder}> 26 | {characteristic.id} 27 | 28 | ); 29 | }; 30 | }; 31 | 32 | const renderSeparator = (sectionID, rowID) => { 33 | return ( 34 | 38 | ); 39 | }; 40 | 41 | const scrollComponent = props => { 42 | return (); 43 | }; 44 | 45 | const CharacteristicList = ({characteristics, selectCharacteristic}) => ( 46 | 52 | 53 | ); 54 | 55 | CharacteristicList.propTypes = { 56 | characteristics: PropTypes.array.isRequired, 57 | selectCharacteristic: PropTypes.func.isRequired, 58 | }; 59 | 60 | var styles = StyleSheet.create({ 61 | characteristicText: { 62 | fontSize: 18, 63 | color: 'grey', 64 | }, 65 | seperator: { 66 | height: 2, 67 | backgroundColor: '#CCCCCC', 68 | }, 69 | textHolder: { 70 | paddingLeft: 15, 71 | paddingTop: 15, 72 | paddingBottom: 15, 73 | }, 74 | }); 75 | 76 | export default CharacteristicList; 77 | -------------------------------------------------------------------------------- /Example/app/components/CharacteristicNotify.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { StyleSheet, View, Text, ActivityIndicator, Alert } from 'react-native'; 3 | import Bluetooth from 'react-native-bluetooth-manager'; 4 | 5 | import Button from './Button'; 6 | 7 | const CharacteristicNotify = React.createClass({ 8 | propTypes: { 9 | characteristic: PropTypes.object.isRequired, 10 | }, 11 | 12 | getInitialState() { 13 | return { 14 | operationInProgress: false, 15 | characteristicStatus: 'Not subscribed', 16 | isSubscribed: false, 17 | buttonText: 'Subscribe' 18 | }; 19 | }, 20 | 21 | showNotifyAlert(detail) { 22 | Alert.alert( 23 | 'Notify Subscription Error', 24 | detail 25 | ); 26 | }, 27 | 28 | componentWillMount() { 29 | this.unsubscribe = () => {}; 30 | }, 31 | 32 | componentWillUnmount() { 33 | this.unsubscribe(); 34 | }, 35 | 36 | unsubscribeFromNotification() { 37 | this.unsubscribe(); 38 | 39 | this.setState({ 40 | isSubscribed: false, 41 | characteristicStatus: "Not subscribed", 42 | buttonText: 'Notify' 43 | }); 44 | }, 45 | 46 | onNotificationReceived(detail) { 47 | console.log("Received notification", detail); 48 | this.setState({ 49 | characteristicStatus: detail.value ? detail.value.toString('hex') : "No value passed in notification", 50 | }); 51 | }, 52 | 53 | subscribeToNotifyValue() { 54 | if (this.state.isSubscribed) { 55 | this.unsubscribeFromNotification(); 56 | return; 57 | } 58 | 59 | this.setState({ 60 | operationInProgress: true, 61 | characteristicStatus: "", 62 | }); 63 | 64 | this.unsubscribe = 65 | Bluetooth.characteristicDidNotify(this.props.characteristic, this.onNotificationReceived); 66 | 67 | this.setState({ 68 | isSubscribed: true, 69 | characteristicStatus: "Waiting for value", 70 | operationInProgress: false, 71 | buttonText: 'Unsubscribe' 72 | }); 73 | }, 74 | 75 | render() { 76 | if (!this.props.characteristic.properties.notify) return null; 77 | 78 | return ( 79 | 80 | 81 | 82 | {this.state.characteristicStatus} 83 | 84 | 85 | 86 | ); 87 | } 88 | }); 89 | 90 | const styles = StyleSheet.create({ 91 | container: { 92 | marginTop: 20, 93 | justifyContent: 'space-between', 94 | paddingLeft: 20, 95 | paddingRight: 20, 96 | }, 97 | resultHolder: { 98 | borderWidth: 2, 99 | borderColor: 'grey', 100 | borderRadius: 5, 101 | padding: 10, 102 | flexDirection: 'row', 103 | marginTop: 20, 104 | justifyContent: 'center', 105 | overflow: 'hidden', 106 | }, 107 | buttonStyle: { 108 | width: 120, 109 | marginRight: 10, 110 | } 111 | }); 112 | 113 | CharacteristicNotify.propTypes = { 114 | characteristic: PropTypes.object.isRequired, 115 | backAction: PropTypes.func, 116 | }; 117 | 118 | export default CharacteristicNotify; 119 | -------------------------------------------------------------------------------- /Example/app/components/CharacteristicRead.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { StyleSheet, View, Text, ActivityIndicator, Alert } from 'react-native'; 3 | import Bluetooth from 'react-native-bluetooth-manager'; 4 | 5 | import Button from './Button'; 6 | 7 | const CharacteristicRead = React.createClass({ 8 | propTypes: { 9 | characteristic: PropTypes.object.isRequired, 10 | }, 11 | 12 | getInitialState() { 13 | return { 14 | operationInProgress: false, 15 | characteristicValue: "No Value", 16 | }; 17 | }, 18 | 19 | showReadAlert(detail) { 20 | Alert.alert( 21 | 'Read Characteristic Error', 22 | detail 23 | ); 24 | }, 25 | 26 | readCharacteristicValue() { 27 | this.setState({ 28 | operationInProgress: true, 29 | characteristicValue: "", 30 | }); 31 | 32 | Bluetooth.readCharacteristicValue(this.props.characteristic) 33 | .then(c => { 34 | const toDisplay = c.value.toString('hex'); 35 | this.setState({characteristicValue: toDisplay}); 36 | }) 37 | .catch(e => { 38 | this.setState({ 39 | characteristicValue: "No Value", 40 | }); 41 | 42 | const message = "message" in e ? e.message : e; 43 | this.showReadAlert(message); 44 | }) 45 | .finally(() => this.setState({operationInProgress: false})); 46 | }, 47 | 48 | render() { 49 | if (!this.props.characteristic.properties.read) return null; 50 | 51 | return ( 52 | 53 | 54 | 55 | {this.state.characteristicValue} 56 | 57 | 58 | 59 | ); 60 | } 61 | }); 62 | 63 | const styles = StyleSheet.create({ 64 | container: { 65 | marginTop: 20, 66 | justifyContent: 'space-between', 67 | paddingLeft: 20, 68 | paddingRight: 20, 69 | }, 70 | resultHolder: { 71 | borderWidth: 2, 72 | borderColor: 'grey', 73 | borderRadius: 5, 74 | padding: 10, 75 | flexDirection: 'row', 76 | marginTop: 20, 77 | justifyContent: 'center', 78 | overflow: 'hidden', 79 | }, 80 | buttonStyle: { 81 | width: 120, 82 | } 83 | }); 84 | 85 | CharacteristicRead.propTypes = { 86 | characteristic: PropTypes.object.isRequired, 87 | backAction: PropTypes.func, 88 | }; 89 | 90 | export default CharacteristicRead; 91 | -------------------------------------------------------------------------------- /Example/app/components/CharacteristicWrite.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { StyleSheet, View, Text, ActivityIndicator, Alert, TextInput } from 'react-native'; 3 | import Bluetooth from 'react-native-bluetooth-manager'; 4 | import { Buffer } from 'buffer'; 5 | 6 | import Button from './Button'; 7 | 8 | const CharacteristicWrite = React.createClass({ 9 | propTypes: { 10 | characteristic: PropTypes.object.isRequired, 11 | }, 12 | 13 | getInitialState() { 14 | return { 15 | operationInProgress: false, 16 | characteristicStatus: "Waiting for write", 17 | textToSend: "", 18 | }; 19 | }, 20 | 21 | showWriteAlert(error) { 22 | Alert.alert( 23 | 'Write Characteristic Error', 24 | error.message 25 | ); 26 | }, 27 | 28 | writeCharacteristicValue() { 29 | this.setState({ 30 | operationInProgress: true, 31 | characteristicStatus: "", 32 | }); 33 | 34 | const valueToWrite = new Buffer(this.state.textToSend); 35 | 36 | Bluetooth.writeCharacteristicValue(this.props.characteristic, valueToWrite, true) 37 | .then(() => this.setState({characteristicStatus: "\u2705"})) 38 | .catch(e => { 39 | this.setState({ 40 | characteristicStatus: "Write error", 41 | }); 42 | this.showWriteAlert(e); 43 | }) 44 | .finally(() => this.setState({operationInProgress: false})); 45 | }, 46 | 47 | render() { 48 | if (!this.props.characteristic.properties.write) return null; 49 | 50 | return ( 51 | 52 | 53 | 54 | 55 | {this.state.characteristicStatus} 56 | 57 | this.setState({ textToSend: text })} 61 | value={this.state.textToSend} 62 | /> 63 | 64 | ); 65 | } 66 | }); 67 | 68 | const styles = StyleSheet.create({ 69 | container: { 70 | marginTop: 20, 71 | justifyContent: 'space-between', 72 | paddingLeft: 20, 73 | paddingRight: 20, 74 | }, 75 | statusContainer: { 76 | flexDirection: 'row', 77 | alignItems: 'center', 78 | }, 79 | textEntry: { 80 | borderWidth: 2, 81 | borderColor: 'grey', 82 | borderRadius: 5, 83 | marginTop: 10, 84 | paddingLeft: 20, 85 | paddingRight: 20, 86 | height: 40, 87 | }, 88 | buttonStyle: { 89 | width: 120, 90 | marginRight: 20, 91 | } 92 | }); 93 | 94 | CharacteristicWrite.propTypes = { 95 | characteristic: PropTypes.object.isRequired, 96 | backAction: PropTypes.func, 97 | }; 98 | 99 | export default CharacteristicWrite; 100 | -------------------------------------------------------------------------------- /Example/app/components/DeviceList.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { 3 | Text, 4 | TouchableOpacity, 5 | StyleSheet, 6 | ListView, 7 | RecyclerViewBackedScrollView, 8 | View, 9 | } from 'react-native'; 10 | 11 | const makeDataSource = devices => { 12 | var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); 13 | 14 | return ds.cloneWithRows(devices); 15 | }; 16 | 17 | const renderDeviceRow = selectDevice => { 18 | return device => { 19 | return ( 20 | selectDevice(device)} 22 | key={device.address} 23 | style={styles.textHolder}> 24 | {device.name} 25 | 26 | ); 27 | }; 28 | }; 29 | 30 | const renderSeparator = (sectionID, rowID) => { 31 | return ( 32 | 36 | ); 37 | }; 38 | 39 | const scrollComponent = props => { 40 | return (); 41 | }; 42 | 43 | const DeviceList = ({devices, selectDevice}) => ( 44 | 50 | 51 | ); 52 | 53 | DeviceList.propTypes = { 54 | devices: PropTypes.array.isRequired, 55 | selectDevice: PropTypes.func.isRequired, 56 | }; 57 | 58 | var styles = StyleSheet.create({ 59 | deviceText: { 60 | fontSize: 18, 61 | color: 'grey', 62 | }, 63 | seperator: { 64 | height: 2, 65 | backgroundColor: '#CCCCCC', 66 | }, 67 | textHolder: { 68 | paddingLeft: 15, 69 | paddingTop: 15, 70 | paddingBottom: 15, 71 | }, 72 | }); 73 | 74 | export default DeviceList; 75 | -------------------------------------------------------------------------------- /Example/app/components/NoBluetooth.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | import TopBar from '../components/TopBar'; 4 | import { StyleSheet, View, Text } from 'react-native'; 5 | 6 | const NoBluetooth = ({state}) => ( 7 | 8 | 11 | Bluetooth is not valid. Current status: {state} 12 | 13 | ); 14 | 15 | const styles = StyleSheet.create({ 16 | mainContainer: { 17 | flex: 1, 18 | paddingLeft: 20, 19 | paddingRight: 20, 20 | }, 21 | messageText: { 22 | fontSize: 18, 23 | textAlign: 'center', 24 | }, 25 | }); 26 | 27 | NoBluetooth.propTypes = { 28 | state: PropTypes.string.isRequired, 29 | }; 30 | 31 | export default NoBluetooth; 32 | -------------------------------------------------------------------------------- /Example/app/components/ServiceList.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { 3 | Text, 4 | TouchableOpacity, 5 | StyleSheet, 6 | ListView, 7 | RecyclerViewBackedScrollView, 8 | View, 9 | } from 'react-native'; 10 | 11 | const makeDataSource = services => { 12 | const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); 13 | 14 | return ds.cloneWithRows(services); 15 | }; 16 | 17 | const renderServiceRow = selectService => { 18 | return service => { 19 | if (!service) return ; 20 | 21 | return ( 22 | selectService(service)} 24 | key={service.id} 25 | style={styles.textHolder}> 26 | {service.id} 27 | 28 | ); 29 | }; 30 | }; 31 | 32 | const renderSeparator = (sectionID, rowID) => { 33 | return ( 34 | 38 | ); 39 | }; 40 | 41 | const scrollComponent = props => { 42 | return (); 43 | }; 44 | 45 | const ServiceList = ({services, selectService}) => ( 46 | 52 | 53 | ); 54 | 55 | ServiceList.propTypes = { 56 | services: PropTypes.array.isRequired, 57 | selectService: PropTypes.func.isRequired, 58 | }; 59 | 60 | var styles = StyleSheet.create({ 61 | serviceText: { 62 | fontSize: 18, 63 | color: 'grey', 64 | }, 65 | seperator: { 66 | height: 2, 67 | backgroundColor: '#CCCCCC', 68 | }, 69 | textHolder: { 70 | paddingLeft: 15, 71 | paddingTop: 15, 72 | paddingBottom: 15, 73 | }, 74 | }); 75 | 76 | export default ServiceList; 77 | -------------------------------------------------------------------------------- /Example/app/components/TopBar.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | import { StyleSheet, View, Text, TouchableOpacity } from 'react-native'; 4 | 5 | const renderBackIfNeeded = backAction => { 6 | if (!backAction) return ; 7 | 8 | return ( 9 | 10 | 11 | {"<"} 12 | 13 | ); 14 | }; 15 | 16 | const renderDummyIfNeeded = needed => { 17 | if (!needed) return null; 18 | return ; 19 | }; 20 | 21 | const TopBar = ({headerText, backAction}) => ( 22 | 23 | {renderBackIfNeeded(backAction)} 24 | {headerText} 25 | {renderDummyIfNeeded(backAction != null)} 26 | 27 | 28 | ); 29 | 30 | TopBar.propTypes = { 31 | text: PropTypes.string.isRequired, 32 | }; 33 | 34 | const styles = StyleSheet.create({ 35 | container: { 36 | flexDirection: 'row', 37 | height: 50, 38 | backgroundColor: '#00AFEE', 39 | marginBottom: 10, 40 | justifyContent: 'space-between', 41 | alignItems: 'center', 42 | }, 43 | headerText: { 44 | fontSize: 20, 45 | color: 'white', 46 | }, 47 | backStyle: { 48 | fontSize: 30, 49 | color: 'white', 50 | marginLeft: 8, 51 | }, 52 | }); 53 | 54 | TopBar.propTypes = { 55 | headerText: PropTypes.string.isRequired, 56 | backAction: PropTypes.func, 57 | }; 58 | 59 | export default TopBar; 60 | -------------------------------------------------------------------------------- /Example/app/containers/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Routes from '../lib/Routes'; 3 | import { createStore, applyMiddleware, combineReducers, compose, } from 'redux'; 4 | import { Provider } from 'react-redux'; 5 | import thunk from 'redux-thunk'; 6 | import * as reducers from '../reducers'; 7 | 8 | const reducer = combineReducers(reducers); 9 | 10 | const store = createStore( 11 | reducer, {}, 12 | compose( 13 | applyMiddleware(thunk) 14 | ) 15 | ); 16 | 17 | // if (module.hot) { 18 | // module.hot.accept('../reducers', () => { 19 | // const nextRootReducer = require('../reducers/index'); 20 | // const reducer = combineReducers(nextRootReducer); 21 | // store.replaceReducer(reducer); 22 | // }); 23 | // } 24 | 25 | const App = () => ( 26 | 27 | 28 | 29 | ); 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /Example/app/containers/CharacteristicDetail.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Text, View, StyleSheet } from 'react-native'; 3 | import { connect } from 'react-redux'; 4 | 5 | import TopBar from '../components/TopBar'; 6 | 7 | import { applicationError } from '../actions/GlobalActions'; 8 | import { setCharacteristic } from '../actions/DeviceContextActions'; 9 | 10 | import CharacteristicRead from '../components/CharacteristicRead'; 11 | import CharacteristicWrite from '../components/CharacteristicWrite'; 12 | import CharacteristicNotify from '../components/CharacteristicNotify'; 13 | 14 | const CharacteristicDetail = React.createClass({ 15 | propTypes: { 16 | navigator: PropTypes.func.isRequired, 17 | setCharacteristic: PropTypes.func.isRequired, 18 | applicationError: PropTypes.func.isRequired, 19 | characteristic: PropTypes.object.isRequired, 20 | }, 21 | 22 | goBack() { 23 | const { setCharacteristic, navigator } = this.props; 24 | 25 | setCharacteristic(null); 26 | navigator('ServiceDetail'); 27 | }, 28 | 29 | formatProperties() { 30 | const { characteristic } = this.props; 31 | 32 | if (!characteristic.properties) 33 | return ""; 34 | 35 | const writeString = characteristic.properties.write ? " WRITE " : ""; 36 | const readString = characteristic.properties.read ? " READ " : ""; 37 | const notifyString = characteristic.properties.notify ? " NOTIFY " : ""; 38 | return `${writeString}${readString}${notifyString}`; 39 | }, 40 | 41 | render() { 42 | const { characteristic } = this.props; 43 | 44 | return ( 45 | 46 | 49 | 50 | UUID: {characteristic.id} 51 | Properties: {this.formatProperties()} 52 | 53 | 54 | 55 | 56 | 57 | ); 58 | }, 59 | }); 60 | 61 | const styles = StyleSheet.create({ 62 | detailText: { 63 | fontSize: 16, 64 | color: 'grey', 65 | marginTop: 10, 66 | }, 67 | container: { 68 | flex: 1, 69 | }, 70 | detailContainer: { 71 | flex: 1, 72 | paddingLeft: 5, 73 | }, 74 | }); 75 | 76 | const mapStateToProps = state => { 77 | const { characteristic } = state.deviceContext; 78 | 79 | return { 80 | characteristic: characteristic, 81 | }; 82 | }; 83 | 84 | const mapDispatchToProps = dispatch => { 85 | return { 86 | applicationError: message => { 87 | dispatch(applicationError(message)); 88 | }, 89 | setCharacteristic: characteristic => { 90 | dispatch(setCharacteristic(characteristic)); 91 | }, 92 | }; 93 | }; 94 | 95 | export default connect(mapStateToProps, mapDispatchToProps, null)(CharacteristicDetail); 96 | -------------------------------------------------------------------------------- /Example/app/containers/DeviceDetail.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Text, View, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native'; 3 | import { connect } from 'react-redux'; 4 | import TopBar from '../components/TopBar'; 5 | import ServiceList from '../components/ServiceList'; 6 | import Bluetooth from 'react-native-bluetooth-manager'; 7 | 8 | import { applicationError } from '../actions/GlobalActions'; 9 | import { setService, setDevice } from '../actions/DeviceContextActions'; 10 | import { 11 | serviceDiscovered, 12 | storeDisconnectionHandler, 13 | setConnectionStatus, 14 | setConnectionInProgress, 15 | resetServices 16 | } from '../actions/DeviceActions'; 17 | 18 | const DeviceDetail = React.createClass({ 19 | propTypes: { 20 | navigator: PropTypes.func.isRequired, 21 | applicationError: PropTypes.func.isRequired, 22 | setConnectionStatus: PropTypes.func.isRequired, 23 | setConnectionInProgress: PropTypes.func.isRequired, 24 | storeDisconnectionHandler: PropTypes.func.isRequired, 25 | setService: PropTypes.func.isRequired, 26 | setDevice: PropTypes.func.isRequired, 27 | resetServices: PropTypes.func.isRequired, 28 | serviceDiscovered: PropTypes.func.isRequired, 29 | disconnectionHandler: PropTypes.func.isRequired, 30 | isConnected: PropTypes.bool.isRequired, 31 | connectionInProgress: PropTypes.bool.isRequired, 32 | device: PropTypes.object.isRequired, 33 | services: PropTypes.array.isRequired, 34 | }, 35 | 36 | disconnect() { 37 | const { 38 | setConnectionStatus, 39 | setConnectionInProgress, 40 | resetServices, 41 | applicationError, 42 | isConnected, 43 | device, 44 | } = this.props; 45 | 46 | if (isConnected) { 47 | setConnectionInProgress(true); 48 | this.endListeningForDisconnection(); 49 | 50 | Bluetooth.disconnect(device) 51 | .then(() => { 52 | setConnectionStatus(false); 53 | setConnectionInProgress(false); 54 | resetServices(); 55 | }).catch(e => { 56 | applicationError(e.message); 57 | }); 58 | } else { 59 | resetServices(); 60 | } 61 | }, 62 | 63 | listenForDisconnect() { 64 | const { 65 | setConnectionStatus, 66 | resetServices, 67 | } = this.props; 68 | 69 | this.endListeningForDisconnection(); 70 | 71 | setConnectionStatus(false); 72 | resetServices(); 73 | 74 | applicationError('Device connection lost'); 75 | this.props.navigator("DeviceDiscovery"); 76 | }, 77 | 78 | connect() { 79 | const { 80 | applicationError, 81 | setConnectionStatus, 82 | isConnected, 83 | setConnectionInProgress, 84 | storeDisconnectionHandler, 85 | serviceDiscovered, 86 | device 87 | } = this.props; 88 | 89 | if (isConnected) { 90 | this.disconnect(); 91 | return; 92 | } 93 | 94 | setConnectionInProgress(true); 95 | 96 | const disconnectSubscription = 97 | Bluetooth.deviceDidDisconnect(device, this.listenForDisconnect); 98 | 99 | storeDisconnectionHandler(disconnectSubscription); 100 | 101 | if ((this.props.services || []).length > 0) { 102 | return; 103 | } 104 | 105 | Bluetooth.connect(device, null) 106 | .then(() => { 107 | setConnectionStatus(true); 108 | setConnectionInProgress(false); 109 | 110 | return Bluetooth.discoverServices(device, null); 111 | }) 112 | .then(services => { 113 | services.forEach(service => serviceDiscovered(service)); 114 | }) 115 | .catch(error => applicationError(error.message)); 116 | }, 117 | 118 | serviceSelected(service) { 119 | const { setService, navigator } = this.props; 120 | setService(service); 121 | 122 | navigator('ServiceDetail'); 123 | }, 124 | 125 | endListeningForDisconnection() { 126 | const { disconnectionHandler, storeDisconnectionHandler } = this.props; 127 | disconnectionHandler(); 128 | storeDisconnectionHandler(() => {}); 129 | }, 130 | 131 | goBack() { 132 | const { 133 | setDevice, 134 | navigator, 135 | } = this.props; 136 | 137 | this.disconnect(); 138 | 139 | setDevice(null); 140 | navigator('DeviceDiscovery'); 141 | }, 142 | 143 | renderStatus() { 144 | const { connectionInProgress, isConnected } = this.props; 145 | 146 | if (connectionInProgress) { 147 | return ; 148 | } 149 | 150 | return ( 151 | 152 | 153 | {isConnected ? 'Disconnect' : 'Connect'} 154 | 155 | 156 | ); 157 | }, 158 | 159 | renderServiceLabel() { 160 | const { isConnected } = this.props; 161 | 162 | if (!isConnected) return null; 163 | 164 | return Services; 165 | }, 166 | 167 | render() { 168 | const { device, services } = this.props; 169 | 170 | return ( 171 | 172 | 175 | {this.renderStatus()} 176 | {this.renderServiceLabel()} 177 | 178 | 179 | 180 | 181 | ); 182 | }, 183 | }); 184 | 185 | const styles = StyleSheet.create({ 186 | statusText: { 187 | fontSize: 20, 188 | color: '#00AFEE', 189 | }, 190 | container: { 191 | flex: 1, 192 | }, 193 | listContainer: { 194 | flex: 1, 195 | }, 196 | labelText: { 197 | fontSize: 20, 198 | color: 'grey', 199 | marginLeft: 15, 200 | }, 201 | statusContainer: { 202 | flexDirection: 'row', 203 | justifyContent: 'center', 204 | marginTop: 5, 205 | marginBottom: 15, 206 | }, 207 | }); 208 | 209 | const mapStateToProps = state => { 210 | const { device } = state.deviceContext; 211 | 212 | return { 213 | ...state.device, 214 | device: device, 215 | }; 216 | }; 217 | 218 | const mapDispatchToProps = dispatch => { 219 | return { 220 | applicationError: message => { 221 | dispatch(applicationError(message)); 222 | }, 223 | serviceDiscovered: service => { 224 | dispatch(serviceDiscovered(service)); 225 | }, 226 | storeDisconnectionHandler: handler => { 227 | dispatch(storeDisconnectionHandler(handler)); 228 | }, 229 | setConnectionStatus: status => { 230 | dispatch(setConnectionStatus(status)); 231 | }, 232 | setConnectionInProgress: inProgress => { 233 | dispatch(setConnectionInProgress(inProgress)); 234 | }, 235 | resetServices: () => { 236 | dispatch(resetServices()); 237 | }, 238 | setService: service => { 239 | dispatch(setService(service)); 240 | }, 241 | setDevice: device => { 242 | dispatch(setDevice(device)); 243 | }, 244 | }; 245 | }; 246 | 247 | export default connect(mapStateToProps, mapDispatchToProps, null)(DeviceDetail); 248 | -------------------------------------------------------------------------------- /Example/app/containers/DeviceDiscovery.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { View, StyleSheet, ActivityIndicator } from 'react-native'; 3 | import { connect } from 'react-redux'; 4 | 5 | import DeviceList from '../components/DeviceList'; 6 | import TopBar from '../components/TopBar'; 7 | import Bluetooth from 'react-native-bluetooth-manager'; 8 | 9 | import { 10 | deviceDiscovered, 11 | discoveryStatusChange, 12 | resetDevices, 13 | } from '../actions/DiscoveryActions'; 14 | 15 | import { applicationError } from '../actions/GlobalActions'; 16 | import { setDevice } from '../actions/DeviceContextActions'; 17 | 18 | const ScanOptions = { 19 | uuids: [], 20 | }; 21 | 22 | const DeviceDiscovery = React.createClass({ 23 | propTypes: { 24 | navigator: PropTypes.func.isRequired, 25 | deviceDiscovered: PropTypes.func.isRequired, 26 | discoveryStatusChange: PropTypes.func.isRequired, 27 | resetDevices: PropTypes.func.isRequired, 28 | applicationError: PropTypes.func.isRequired, 29 | discoveryStatus: PropTypes.string.isRequired, 30 | devicesDiscovered: PropTypes.array.isRequired, 31 | setDevice: PropTypes.func.isRequired, 32 | }, 33 | 34 | componentWillMount() { 35 | const { deviceDiscovered, discoveryStatusChange, applicationError, resetDevices } = this.props; 36 | 37 | resetDevices(); 38 | discoveryStatusChange("Connecting"); 39 | 40 | this.unsubscribe = Bluetooth.didDiscoverDevice((device) => { 41 | deviceDiscovered(device); 42 | }); 43 | 44 | this.scanStoppedUnsubscribe = Bluetooth.scanDidStop(() => { 45 | discoveryStatusChange("Done"); 46 | }); 47 | 48 | Bluetooth.startScan(ScanOptions) 49 | .then(scan => scan.stopAfter(15000)) 50 | .catch(error => applicationError(error.message)); 51 | }, 52 | 53 | componentWillUnmount() { 54 | this.scanStoppedUnsubscribe(); 55 | this.unsubscribe(); 56 | 57 | Bluetooth.stopScan(); 58 | }, 59 | 60 | scanInProgress() { 61 | return this.props.discoveryStatus != "Done"; 62 | }, 63 | 64 | deviceSelected(device) { 65 | const { setDevice, navigator } = this.props; 66 | 67 | setDevice(device); 68 | 69 | navigator('DeviceDetail'); 70 | }, 71 | 72 | render() { 73 | const { devicesDiscovered } = this.props; 74 | 75 | return ( 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | ); 84 | }, 85 | }); 86 | 87 | var styles = StyleSheet.create({ 88 | container: { 89 | flex: 1, 90 | }, 91 | deviceListContainer: { 92 | flex: 1, 93 | }, 94 | }); 95 | 96 | const mapStateToProps = state => { 97 | return state.discovery; 98 | }; 99 | 100 | const mapDispatchToProps = dispatch => { 101 | return { 102 | deviceDiscovered: device => { 103 | dispatch(deviceDiscovered(device)); 104 | }, 105 | discoveryStatusChange: status => { 106 | dispatch(discoveryStatusChange(status)); 107 | }, 108 | resetDevices: () => { 109 | dispatch(resetDevices()); 110 | }, 111 | setDevice: device => { 112 | dispatch(setDevice(device)); 113 | }, 114 | applicationError: (message) => { 115 | dispatch(applicationError(message)); 116 | } 117 | }; 118 | }; 119 | 120 | export default connect(mapStateToProps, mapDispatchToProps, null)(DeviceDiscovery); 121 | -------------------------------------------------------------------------------- /Example/app/containers/ServiceDetail.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Text, View, StyleSheet } from 'react-native'; 3 | import { connect } from 'react-redux'; 4 | 5 | import TopBar from '../components/TopBar'; 6 | import CharacteristicList from '../components/CharacteristicList'; 7 | import Bluetooth from 'react-native-bluetooth-manager'; 8 | 9 | import { applicationError } from '../actions/GlobalActions'; 10 | import { setCharacteristic, setService } from '../actions/DeviceContextActions'; 11 | import { characteristicDiscovered, resetCharacteristics } from '../actions/ServiceActions'; 12 | 13 | const ServiceDetail = React.createClass({ 14 | propTypes: { 15 | navigator: PropTypes.func.isRequired, 16 | applicationError: PropTypes.func.isRequired, 17 | setService: PropTypes.func.isRequired, 18 | setCharacteristic: PropTypes.func.isRequired, 19 | resetCharacteristics: PropTypes.func.isRequired, 20 | characteristicDiscovered: PropTypes.func.isRequired, 21 | service: PropTypes.object.isRequired, 22 | characteristics: PropTypes.array.isRequired, 23 | }, 24 | 25 | componentWillMount() { 26 | const { service, characteristicDiscovered, applicationError, characteristics } = this.props; 27 | 28 | if ((characteristics || []).length > 0) { 29 | return; 30 | } 31 | 32 | Bluetooth.discoverCharacteristics(service, null) 33 | .then(discoveredItems => { 34 | discoveredItems.forEach(c => characteristicDiscovered(c)); 35 | }) 36 | .catch(error => { 37 | applicationError(error.message); 38 | }); 39 | }, 40 | 41 | characteristicSelected(characteristic) { 42 | const { setCharacteristic, navigator } = this.props; 43 | 44 | setCharacteristic(characteristic); 45 | 46 | navigator('CharacteristicDetail'); 47 | }, 48 | 49 | goBack() { 50 | const { setService, resetCharacteristics, navigator } = this.props; 51 | 52 | setService(null); 53 | resetCharacteristics(); 54 | 55 | navigator('DeviceDetail'); 56 | }, 57 | 58 | render() { 59 | const { characteristics } = this.props; 60 | 61 | return ( 62 | 63 | 66 | Characteristics 67 | 68 | 71 | 72 | 73 | ); 74 | }, 75 | }); 76 | 77 | const styles = StyleSheet.create({ 78 | labelText: { 79 | fontSize: 20, 80 | color: 'grey', 81 | marginLeft: 15, 82 | }, 83 | container: { 84 | flex: 1, 85 | }, 86 | listContainer: { 87 | flex: 1, 88 | }, 89 | }); 90 | 91 | const mapStateToProps = state => { 92 | const { service } = state.deviceContext; 93 | 94 | return { 95 | ...state.service, 96 | service: service, 97 | }; 98 | }; 99 | 100 | const mapDispatchToProps = dispatch => { 101 | return { 102 | applicationError: message => { 103 | dispatch(applicationError(message)); 104 | }, 105 | setService: service => { 106 | dispatch(setService(service)); 107 | }, 108 | setCharacteristic: characteristic => { 109 | dispatch(setCharacteristic(characteristic)); 110 | }, 111 | characteristicDiscovered: characteristic => { 112 | dispatch(characteristicDiscovered(characteristic)); 113 | }, 114 | resetCharacteristics: () => { 115 | dispatch(resetCharacteristics()); 116 | }, 117 | }; 118 | }; 119 | 120 | export default connect(mapStateToProps, mapDispatchToProps, null)(ServiceDetail); 121 | -------------------------------------------------------------------------------- /Example/app/lib/GlobalState.js: -------------------------------------------------------------------------------- 1 | var state = { }; 2 | 3 | const setAppState = newState => { 4 | state = { ...state, ...newState }; 5 | }; 6 | 7 | const getAppState = () => { 8 | return state; 9 | }; 10 | 11 | export { 12 | getAppState, 13 | setAppState, 14 | }; 15 | -------------------------------------------------------------------------------- /Example/app/lib/Routes.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Navigator, Alert } from 'react-native'; 3 | import Bluetooth from 'react-native-bluetooth-manager'; 4 | import { connect } from 'react-redux'; 5 | 6 | import DeviceDiscovery from '../containers/DeviceDiscovery'; 7 | import DeviceDetail from '../containers/DeviceDetail'; 8 | import ServiceDetail from '../containers/ServiceDetail'; 9 | import CharacteristicDetail from '../containers/CharacteristicDetail'; 10 | import NoBluetooth from '../components/NoBluetooth'; 11 | 12 | import { resetApplicationError } from '../actions/GlobalActions'; 13 | 14 | const Routes = React.createClass({ 15 | propTypes: { 16 | error: PropTypes.string, 17 | resetError: PropTypes.func.isRequired, 18 | }, 19 | 20 | getInitialState() { 21 | return { 22 | bluetoothState: "enabled", 23 | }; 24 | }, 25 | 26 | renderScene(route, navigator) { 27 | const navigate = routeName => navigator.replace( { name: routeName } ); 28 | 29 | if (this.state.bluetoothState != 'enabled') { 30 | return ; 31 | } 32 | if (route.name == 'DeviceDiscovery') { 33 | return ; 34 | } 35 | if (route.name == 'DeviceDetail') { 36 | return ; 37 | } 38 | if (route.name == 'ServiceDetail') { 39 | return ; 40 | } 41 | if (route.name == 'CharacteristicDetail') { 42 | return ; 43 | } 44 | 45 | console.error("Invalid route name requested."); 46 | }, 47 | 48 | componentWillReceiveProps(nextProps) { 49 | const { error } = nextProps; 50 | const { resetError } = this.props; 51 | 52 | if (error != null) { 53 | Alert.alert( 54 | 'Application error', 55 | error 56 | ); 57 | 58 | resetError(); 59 | } 60 | }, 61 | 62 | componentWillMount() { 63 | this.unsubscribe = Bluetooth.didChangeState(newState => { 64 | this.setState({bluetoothState: newState}); 65 | }); 66 | }, 67 | 68 | componentWillUnmount() { 69 | this.unsubscribe(); 70 | }, 71 | 72 | render() { 73 | return ( 74 | 77 | ); 78 | } 79 | }); 80 | 81 | 82 | const mapStateToProps = state => { 83 | const { error } = state.global; 84 | 85 | return { 86 | error: error, 87 | }; 88 | }; 89 | 90 | const mapDispatchToProps = dispatch => { 91 | return { 92 | resetError: () => { 93 | dispatch(resetApplicationError()); 94 | } 95 | }; 96 | }; 97 | 98 | export default connect(mapStateToProps, mapDispatchToProps, null)(Routes); 99 | 100 | -------------------------------------------------------------------------------- /Example/app/reducers/Device.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/DeviceActionTypes'; 2 | 3 | const initialState = { 4 | services: [], 5 | disconnectionHandler: () => {}, 6 | isConnected: false, 7 | connectionInProgress: false, 8 | }; 9 | 10 | export default function device(state = initialState, action) { 11 | switch (action.type) { 12 | case types.SERVICEDISCOVERED: 13 | return { 14 | ...state, 15 | services: [...state.services, action.service], 16 | }; 17 | case types.DISCONNECTIONHANDLER: 18 | return { 19 | ...state, 20 | disconnectionHandler: action.handler, 21 | }; 22 | case types.CONNECTIONSTATUS: 23 | return { 24 | ...state, 25 | isConnected: action.isConnected, 26 | }; 27 | case types.CONNECTIONINPROGRESS: 28 | return { 29 | ...state, 30 | connectionInProgress: action.inProgress, 31 | }; 32 | case types.RESETSERVICES: 33 | return { 34 | ...state, 35 | services: [], 36 | }; 37 | default: 38 | return state; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Example/app/reducers/DeviceContext.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/DeviceContextActionTypes'; 2 | 3 | const initialState = { 4 | device: null, 5 | service: null, 6 | characteristic: null, 7 | }; 8 | 9 | export default function deviceContext(state = initialState, action) { 10 | switch (action.type) { 11 | case types.SETDEVICE: 12 | return { 13 | ...state, 14 | device: action.device, 15 | }; 16 | case types.SETSERVICE: 17 | return { 18 | ...state, 19 | service: action.service, 20 | }; 21 | case types.SETCHARACTERISTIC: 22 | return { 23 | ...state, 24 | characteristic: action.characteristic, 25 | }; 26 | default: 27 | return state; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/app/reducers/Discovery.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/DiscoveryActionTypes'; 2 | import _ from 'lodash'; 3 | 4 | const initialState = { 5 | devicesDiscovered: [], 6 | discoveryStatus: "", 7 | }; 8 | 9 | export default function discovery(state = initialState, action) { 10 | switch (action.type) { 11 | case types.DEVICEDISCOVERED: 12 | return { 13 | ...state, 14 | devicesDiscovered: _.uniq([...state.devicesDiscovered, action.device]), 15 | }; 16 | case types.DISCOVERYSTATUSCHANGE: 17 | return { 18 | ...state, 19 | discoveryStatus: action.status, 20 | }; 21 | case types.RESETDEVICES: 22 | return { 23 | ...state, 24 | devicesDiscovered: [], 25 | }; 26 | default: 27 | return state; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Example/app/reducers/Global.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/GlobalActionTypes'; 2 | 3 | const initialState = { 4 | error: null 5 | }; 6 | 7 | export default function discovery(state = initialState, action) { 8 | switch (action.type) { 9 | case types.APPLICATIONERROR: 10 | return { 11 | ...state, 12 | error: action.error, 13 | }; 14 | case types.RESETERROR: 15 | return { 16 | ...state, 17 | error: null, 18 | }; 19 | default: 20 | return state; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Example/app/reducers/Service.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/ServiceActionTypes'; 2 | 3 | const initialState = { 4 | characteristics: [], 5 | }; 6 | 7 | export default function service(state = initialState, action) { 8 | switch (action.type) { 9 | case types.CHARACTERISTICDISCOVERED: 10 | return { 11 | ...state, 12 | characteristics: [...state.characteristics, action.characteristic], 13 | }; 14 | case types.RESETCHARACTERISTICS: 15 | return { 16 | ...state, 17 | characteristics: [], 18 | }; 19 | default: 20 | return state; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Example/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import discovery from './Discovery'; 2 | import global from './Global'; 3 | import deviceContext from './DeviceContext'; 4 | import device from './Device'; 5 | import service from './Service'; 6 | 7 | export { 8 | discovery, 9 | global, 10 | deviceContext, 11 | device, 12 | service, 13 | }; 14 | -------------------------------------------------------------------------------- /Example/index.android.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | 3 | import App from './app/containers/App'; 4 | 5 | AppRegistry.registerComponent('Example', () => App); 6 | -------------------------------------------------------------------------------- /Example/index.ios.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | 3 | import App from './app/containers/App'; 4 | 5 | AppRegistry.registerComponent('Example', () => App); 6 | -------------------------------------------------------------------------------- /Example/ios/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/ios/Example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Example/ios/Example/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import "RCTBundleURLProvider.h" 13 | #import "RCTRootView.h" 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | NSURL *jsCodeLocation; 20 | 21 | #if DEBUG 22 | NSString *serverIP = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"ServerIP"]; 23 | NSString *locationString = [NSString stringWithFormat:@"http://%@:8081/index.ios.bundle?platform=ios&dev=true", serverIP]; 24 | jsCodeLocation = [NSURL URLWithString:locationString]; 25 | #else 26 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 27 | #endif 28 | 29 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 30 | moduleName:@"Example" 31 | initialProperties:nil 32 | launchOptions:launchOptions]; 33 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 34 | 35 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 36 | UIViewController *rootViewController = [UIViewController new]; 37 | rootViewController.view = rootView; 38 | self.window.rootViewController = rootViewController; 39 | [self.window makeKeyAndVisible]; 40 | return YES; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Example/ios/Example/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/ios/Example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Example/ios/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ServerIP 6 | 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | NSLocationWhenInUseUsageDescription 33 | 34 | UILaunchStoryboardName 35 | LaunchScreen 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | 40 | UIStatusBarHidden 41 | 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example/ios/Example/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/ios/ExampleTests/ExampleTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | #import "RCTLog.h" 14 | #import "RCTRootView.h" 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface ExampleTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation ExampleTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Example/ios/ExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "lint": "eslint ." 8 | }, 9 | "dependencies": { 10 | "react": "15.3.1", 11 | "react-native": "0.33.0", 12 | "react-native-bluetooth-manager": "file:..", 13 | "react-redux": "^4.4.5", 14 | "redux": "^3.6.0", 15 | "redux-thunk": "^2.1.0" 16 | }, 17 | "devDependencies": { 18 | "eslint": "^3.4.0", 19 | "eslint-plugin-react": "^6.2.0", 20 | "eslint-plugin-react-native": "^2.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-bluetooth-manager 2 | 3 | [![npm version](https://badge.fury.io/js/react-native-bluetooth-manager.svg)](https://npmjs.org/package/react-native-bluetooth-manager) 4 | 5 | __Disclaimer: This is alpha software.__ 6 | 7 | Bluetooth API for [React Native](https://github.com/facebook/react-native). 8 | 9 | ## Features 10 | 11 | - Handle bluetooth availability 12 | - Scan nearby low-energy devices 13 | - Discover services and characteristics 14 | - Connect to devices 15 | - Read, write and receive characteristic notifications 16 | - Support both iOS/Android 17 | 18 | ## Install 19 | 20 | ``` 21 | npm install --save react-native-bluetooth-manager 22 | react-native link react-native-bluetooth-manager 23 | ``` 24 | 25 | ## Usage 26 | 27 | See the [Example app](https://github.com/sogilis/react-native-bluetooth-manager/tree/master/Example). 28 | 29 | ``` 30 | import Bluetooth from 'react-native-bluetooth-manager'; 31 | ``` 32 | 33 | ### Bluetooth state change 34 | 35 | ``` 36 | const unsubscribe = Bluetooth.didChangeState(bluetoothState => { 37 | // bluetoothState == 'enabled' | 'disabled' 38 | }); 39 | ``` 40 | 41 | ### Device scan 42 | 43 | ``` 44 | const discoverOptions = { 45 | uuids: [] // list of BLE service uuids to filter devices during scan 46 | }; 47 | 48 | const onDeviceFound = device => { 49 | const {id, name} = device; 50 | ... 51 | }; 52 | 53 | Bluetooth.startScanWithDiscovery(discoverOptions, onDeviceFound) 54 | .then(scan => scan.stopAfter(9000)) // automatically stop scan after 9000ms 55 | .then(stoppedOnTime => { 56 | // true if scan ran for full duration, false if stopped before 57 | }); 58 | 59 | Bluetooth.stopScan(); // manually stop scan 60 | 61 | ``` 62 | 63 | ### Device connection, services & characteristics discovery 64 | 65 | ``` 66 | Bluetooth.connect(device) 67 | .then(() => { 68 | // device is connected 69 | // proceed with services & characteristics discovery (see below) 70 | }).catch(error => { 71 | // error when connecting to device 72 | }); 73 | 74 | Bluetooth.disconnect(device) 75 | .then(() => { // disconnection ok }) 76 | .catch(() => { // disconnection error }); 77 | ``` 78 | 79 | Once device is connected, it is recommended to discover service and characteristics before calling any operation. 80 | 81 | ``` 82 | Bluetooth.discoverServices(device, serviceIds) 83 | .then(services => { 84 | // discover service characteristics 85 | services.forEach(service => { 86 | // optionally, perform characteristic discovery one after the other for better stability 87 | Bluetooth.discoverCharacteristics(service, characteristicIds) 88 | .then(characteristics => { 89 | // memoize characteristics for later use 90 | }); 91 | }); 92 | }); 93 | ``` 94 | 95 | Disconnection events can happen any time due to loss of communication: 96 | 97 | ``` 98 | const unsubscribe = Bluetooth.deviceDidDisconnect(event => { 99 | unsubscribe(); 100 | if (event.error) { 101 | // disconnection due to error 102 | } else { 103 | ... 104 | } 105 | }); 106 | ``` 107 | 108 | ### Characteristic operations 109 | 110 | Use characteristics retrieved during the discovery stage. 111 | 112 | Read a characteristic value: 113 | 114 | ``` 115 | Bluetooth.readCharacteristicValue(characteristic) 116 | .then(payload => { 117 | const { 118 | value, // value after base-64 decoding 119 | base64Value, // original base-64 encoded value 120 | } = payload; 121 | ... 122 | }); 123 | ``` 124 | 125 | 126 | Write a value to a characteristic (with or without response): 127 | 128 | ``` 129 | const payload = new Buffer(...); 130 | // see https://www.npmjs.com/package/buffer 131 | // lib will encode buffer in Base 64 before transmission 132 | 133 | Bluetooth.writeCharacteristicValue(characteristic, payload, withResponse) 134 | .then(() => { 135 | // if withResponse == true, wait for Bluetooth write response (acknowledgement) 136 | // if withResponse == false, returns immediately 137 | }); 138 | ``` 139 | 140 | Subscribe to notifications from characteristic: 141 | 142 | ``` 143 | const onNotification = payload => { 144 | const { value } = payload; // value after base-64 decoding 145 | }; 146 | 147 | const unsubscribe = Bluetooth.characteristicDidNotify(characteristic, onNotification); 148 | 149 | unsubscribe(); // stop notifications handling 150 | ``` 151 | 152 | ## License 153 | 154 | Copyright 2016 Sogilis SARL 155 | 156 | Licensed under the Apache License, Version 2.0 (the "License"); 157 | you may not use this software except in compliance with the License. 158 | You may obtain a copy of the License at 159 | 160 | http://www.apache.org/licenses/LICENSE-2.0 161 | 162 | Unless required by applicable law or agreed to in writing, software 163 | distributed under the License is distributed on an "AS IS" BASIS, 164 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 165 | See the License for the specific language governing permissions and 166 | limitations under the License. 167 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 21 9 | targetSdkVersion 22 10 | versionCode 1 11 | versionName "1.0" 12 | ndk { 13 | abiFilters "armeabi-v7a", "x86" 14 | } 15 | } 16 | } 17 | 18 | dependencies { 19 | compile 'com.facebook.react:react-native:+' // From node_modules 20 | } 21 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/BluetoothAction.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sogilis.ReactNativeBluetooth; 18 | 19 | import android.bluetooth.BluetoothAdapter; 20 | import android.util.Log; 21 | 22 | import com.sogilis.ReactNativeBluetooth.domain.BluetoothException; 23 | import com.sogilis.ReactNativeBluetooth.events.EventEmitter; 24 | 25 | import static com.sogilis.ReactNativeBluetooth.Constants.MODULE_NAME; 26 | 27 | public abstract class BluetoothAction { 28 | public final String eventName; 29 | public final String deviceId; 30 | private String id; 31 | private EventEmitter eventEmitter; 32 | 33 | protected BluetoothAdapter bluetoothAdapter; 34 | 35 | public abstract void run() throws BluetoothException; 36 | 37 | public BluetoothAction(String eventName, EventEmitter eventEmitter) { 38 | this(eventName, null, eventEmitter); 39 | } 40 | 41 | public BluetoothAction(String eventName, String deviceId, EventEmitter eventEmitter) { 42 | this(eventName, deviceId, deviceId, eventEmitter); 43 | } 44 | 45 | public BluetoothAction(String eventName, String deviceId, String id, EventEmitter eventEmitter) { 46 | this.eventName = eventName; 47 | this.deviceId = deviceId; 48 | this.id = id; 49 | this.eventEmitter = eventEmitter; 50 | this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 51 | } 52 | 53 | public boolean start() { 54 | if (this.bluetoothAdapter == null) { 55 | emitError("Bluetooth not supported"); 56 | } 57 | 58 | if (!this.bluetoothAdapter.isEnabled()) { 59 | emitError("Bluetooth disabled"); 60 | } 61 | 62 | try { 63 | this.run(); 64 | return true; 65 | } 66 | catch(BluetoothException e) { 67 | emitError(e.getMessage()); 68 | } 69 | return false; 70 | } 71 | 72 | public void cancel(String reason) { 73 | Log.d(MODULE_NAME, "BluetoothAction " + eventName + " cancelled because of [" + reason + "]"); 74 | emitError(reason); 75 | } 76 | 77 | private void emitError(String errorMessage) { 78 | eventEmitter.emitError(eventName, errorMessage, id); 79 | } 80 | 81 | public String toString() { 82 | String shortName = eventName.substring(eventName.lastIndexOf(".") + 1); 83 | return shortName + " <" + deviceId + ">"; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/BluetoothActionsLoop.java: -------------------------------------------------------------------------------- 1 | package com.sogilis.ReactNativeBluetooth; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.Queue; 6 | import java.util.concurrent.ConcurrentLinkedQueue; 7 | 8 | import static com.sogilis.ReactNativeBluetooth.Constants.MODULE_NAME; 9 | 10 | class BluetoothActionsLoop { 11 | 12 | private Queue actionsQueue ; 13 | private BluetoothAction currentAction; 14 | 15 | BluetoothActionsLoop() { 16 | this.actionsQueue = new ConcurrentLinkedQueue<>(); 17 | this.currentAction = null; 18 | } 19 | 20 | void addAction(BluetoothAction bluetoothAction) { 21 | Log.d(MODULE_NAME, "Loop - add " + bluetoothAction); 22 | actionsQueue.add(bluetoothAction); 23 | tick(); 24 | } 25 | 26 | void actionDone() { 27 | Log.d(MODULE_NAME, "Loop - done " + currentAction); 28 | currentAction = null; 29 | tick(); 30 | } 31 | 32 | public int size() { 33 | return actionsQueue.size(); 34 | } 35 | 36 | private synchronized void tick() { 37 | if (currentAction != null) { 38 | Log.d(MODULE_NAME, "Loop#tick - already pending " + currentAction); 39 | return; 40 | } 41 | 42 | if (actionsQueue.isEmpty()) { 43 | Log.d(MODULE_NAME, "Loop#tick - empty queue"); 44 | return; 45 | } 46 | 47 | currentAction = actionsQueue.poll(); 48 | Log.d(MODULE_NAME, "Loop#tick - running " + currentAction); 49 | if (!currentAction.start()) { 50 | Log.d(MODULE_NAME, "Loop - failed " + currentAction); 51 | actionDone(); 52 | } 53 | } 54 | 55 | public synchronized void cancelGattActions(String deviceId, String reason) { 56 | Log.d(MODULE_NAME, "Loop - cancel device actions for " + deviceId + " (" + size() + " action(s) in queue - currentAction = " + currentAction + ")"); 57 | if (currentAction != null && deviceId.equals(currentAction.deviceId)) { 58 | currentAction.cancel(reason); 59 | currentAction = null; 60 | } 61 | for (BluetoothAction action: actionsQueue) { 62 | if (deviceId.equals(action.deviceId)) { 63 | actionsQueue.remove(action); 64 | action.cancel(reason); 65 | } 66 | } 67 | Log.d(MODULE_NAME, "Loop - " + size() + " action(s) left in queue - currentAction = " + currentAction + ")"); 68 | tick(); 69 | } 70 | 71 | public void clear() { 72 | currentAction = null; 73 | actionsQueue.clear(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/Constants.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sogilis.ReactNativeBluetooth; 18 | 19 | public class Constants { 20 | public static final String MODULE_NAME = "ReactNativeBluetooth"; 21 | } 22 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/ReactNativeBluetoothPackage.java: -------------------------------------------------------------------------------- 1 | package com.sogilis.ReactNativeBluetooth; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.bridge.JavaScriptModule; 9 | import com.facebook.react.bridge.NativeModule; 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.uimanager.ViewManager; 12 | 13 | public class ReactNativeBluetoothPackage implements ReactPackage { 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactContext) { 16 | List modules = new ArrayList<>(); 17 | modules.add(new ReactNativeBluetoothModule(reactContext)); 18 | return modules; 19 | } 20 | 21 | @Override 22 | public List> createJSModules() { 23 | return Collections.emptyList(); 24 | } 25 | 26 | @Override 27 | public List createViewManagers(ReactApplicationContext reactContext) { 28 | return Collections.emptyList(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/domain/BluetoothException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sogilis.ReactNativeBluetooth.domain; 18 | 19 | public class BluetoothException extends Exception { 20 | public BluetoothException(String detailMessage) { 21 | super(detailMessage); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/domain/BluetoothHelpers.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sogilis.ReactNativeBluetooth.domain; 18 | 19 | import android.bluetooth.BluetoothDevice; 20 | import android.bluetooth.BluetoothGatt; 21 | import android.bluetooth.BluetoothGattCharacteristic; 22 | import android.bluetooth.BluetoothGattDescriptor; 23 | import android.bluetooth.BluetoothGattService; 24 | 25 | import java.util.UUID; 26 | import android.util.Log; 27 | import static com.sogilis.ReactNativeBluetooth.Constants.MODULE_NAME; 28 | 29 | public class BluetoothHelpers { 30 | public static String deviceId(BluetoothDevice device) { 31 | return device.getAddress(); 32 | } 33 | 34 | public static String serviceId(BluetoothGattService service) { 35 | return service.getUuid().toString(); 36 | } 37 | 38 | public static BluetoothGattService findServiceById(BluetoothGatt gatt, String serviceId) throws BluetoothException { 39 | BluetoothGattService service = gatt.getService(UUID.fromString(serviceId)); 40 | 41 | if (service == null) { 42 | throw new BluetoothException("No such service: " + serviceId + 43 | " (device: " + deviceId(gatt.getDevice()) + ")"); 44 | } 45 | 46 | return service; 47 | } 48 | 49 | public static BluetoothGattCharacteristic findCharacteristic(BluetoothGatt gatt, String serviceId, String characteristicId) throws BluetoothException { 50 | return findCharacteristic(gatt.getDevice(), findServiceById(gatt, serviceId), characteristicId); 51 | } 52 | 53 | public static BluetoothGattCharacteristic findCharacteristic(BluetoothDevice device, BluetoothGattService service, String characteristicId) throws BluetoothException { 54 | BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(characteristicId)); 55 | 56 | if (characteristic == null) { 57 | throw new BluetoothException("No such characteristic: " + characteristicId + 58 | " (service: " + serviceId(service) + 59 | ", device: " + deviceId(device) + ")"); 60 | } 61 | 62 | return characteristic; 63 | } 64 | 65 | public static BluetoothGattCharacteristic findCharacteristic(BluetoothGatt gatt, String serviceId, String characteristicId, int property) throws BluetoothException { 66 | BluetoothGattService service = findServiceById(gatt, serviceId); 67 | BluetoothGattCharacteristic characteristic = findCharacteristic(gatt.getDevice(), service, characteristicId); 68 | 69 | if (! hasProperty(characteristic, property)) { 70 | throw new BluetoothException("Characteristic " + characteristicId + 71 | " doesn't have property " + propertyName(property) + 72 | " (service: " + serviceId(service) + 73 | ", device: " + deviceId(gatt.getDevice()) + ")"); 74 | } 75 | 76 | return characteristic; 77 | } 78 | 79 | public static String propertyName(int property) throws BluetoothException { 80 | switch(property) { 81 | case BluetoothGattCharacteristic.PROPERTY_READ: return "READ"; 82 | case BluetoothGattCharacteristic.PROPERTY_WRITE: return "WRITE"; 83 | case BluetoothGattCharacteristic.PROPERTY_NOTIFY: return "NOTIFY"; 84 | default: throw new BluetoothException("Unhandled characteristic property: " + Integer.toString(property)); 85 | } 86 | } 87 | 88 | public static boolean hasProperty(BluetoothGattCharacteristic characteristic, int property) { 89 | return (characteristic.getProperties() & property) != 0; 90 | } 91 | 92 | public static final UUID CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); 93 | 94 | public static BluetoothGattDescriptor configDescriptor(BluetoothGattCharacteristic characteristic) { 95 | return characteristic.getDescriptor(CONFIG_DESCRIPTOR_UUID); 96 | } 97 | 98 | public static boolean enableNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { 99 | BluetoothGattDescriptor descriptor = configDescriptor(characteristic); 100 | descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 101 | return gatt.setCharacteristicNotification(characteristic, true) && gatt.writeDescriptor(descriptor); 102 | } 103 | 104 | public static boolean disableNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { 105 | BluetoothGattDescriptor descriptor = configDescriptor(characteristic); 106 | descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); 107 | return gatt.writeDescriptor(descriptor) && gatt.setCharacteristicNotification(characteristic, false); 108 | } 109 | 110 | public static String gattStatusString(int status) { 111 | switch(status) { 112 | case BluetoothGatt.GATT_CONNECTION_CONGESTED: return "Connection congested"; 113 | case BluetoothGatt.GATT_FAILURE: return "Failure"; 114 | case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION: return "Insufficient authentication"; 115 | case BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION: return "Insufficient encryption"; 116 | case BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH: return "Invalid attribute length"; 117 | case BluetoothGatt.GATT_INVALID_OFFSET: return "Invalid offset"; 118 | case BluetoothGatt.GATT_READ_NOT_PERMITTED: return "Read not permitted"; 119 | case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED: return "Request not supported"; 120 | case BluetoothGatt.GATT_WRITE_NOT_PERMITTED: return "Write not permitted"; 121 | default: return "GATT error with unknown status code: " + status; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/domain/DeviceCollection.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sogilis.ReactNativeBluetooth.domain; 18 | 19 | import android.bluetooth.BluetoothDevice; 20 | 21 | import static com.sogilis.ReactNativeBluetooth.domain.BluetoothHelpers.deviceId; 22 | 23 | import java.util.concurrent.ConcurrentHashMap; 24 | 25 | public class DeviceCollection { 26 | private ConcurrentHashMap devices = new ConcurrentHashMap<>(); 27 | 28 | public boolean add(BluetoothDevice device) { 29 | BluetoothDevice alreadyPresentDevice = devices.putIfAbsent(deviceId(device), device); 30 | return alreadyPresentDevice == null; 31 | } 32 | 33 | public int size() { 34 | return devices.size(); 35 | } 36 | 37 | public void clear() { 38 | devices.clear(); 39 | } 40 | 41 | public BluetoothDevice get(String id) throws BluetoothException { 42 | if (devices.containsKey(id)) { 43 | return devices.get(id); 44 | } else { 45 | throw new BluetoothException("No such device: " + id); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/domain/GattCollection.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sogilis.ReactNativeBluetooth.domain; 18 | 19 | import android.bluetooth.BluetoothDevice; 20 | import android.bluetooth.BluetoothGatt; 21 | import android.util.Log; 22 | 23 | import static com.sogilis.ReactNativeBluetooth.Constants.MODULE_NAME; 24 | import static com.sogilis.ReactNativeBluetooth.domain.BluetoothHelpers.deviceId; 25 | 26 | import java.util.concurrent.ConcurrentHashMap; 27 | 28 | public class GattCollection { 29 | private ConcurrentHashMap gatts = new ConcurrentHashMap<>(); 30 | 31 | public boolean add(BluetoothGatt gatt) { 32 | BluetoothGatt alreadyPresentGatt = gatts.putIfAbsent(deviceId(gatt.getDevice()), gatt); 33 | return alreadyPresentGatt == null; 34 | } 35 | 36 | public int size() { 37 | return gatts.size(); 38 | } 39 | 40 | public void close(String deviceId) { 41 | BluetoothGatt gatt = gatts.remove(deviceId); 42 | 43 | if (gatt != null) { 44 | BluetoothDevice device = gatt.getDevice(); 45 | Log.d(MODULE_NAME, "Closing GATT client " + device.getName() + " (" + device.getAddress() + ")"); 46 | gatt.close(); 47 | } 48 | } 49 | 50 | public void close(BluetoothGatt gatt) { 51 | close(deviceId(gatt.getDevice())); 52 | } 53 | 54 | public BluetoothGatt get(String deviceId) throws BluetoothException { 55 | if (gatts.containsKey(deviceId)) { 56 | return gatts.get(deviceId); 57 | } else { 58 | throw new BluetoothException("Unknown or disconnected device: " + deviceId); 59 | } 60 | } 61 | 62 | public BluetoothGatt get(BluetoothDevice device) throws BluetoothException { 63 | return get(deviceId(device)); 64 | } 65 | 66 | public void clear() { 67 | for (BluetoothGatt gatt: gatts.values()) { 68 | close(gatt); 69 | } 70 | gatts.clear(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/events/BluetoothEvent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sogilis.ReactNativeBluetooth.events; 18 | 19 | import com.facebook.react.bridge.ReadableMap; 20 | 21 | public class BluetoothEvent { 22 | protected String name; 23 | protected Object data; 24 | 25 | public BluetoothEvent(String name, Object data) { 26 | this.name = name; 27 | this.data = data; 28 | } 29 | 30 | public String getName() { 31 | return this.name; 32 | } 33 | 34 | public String getShortName() { 35 | return name.substring(name.lastIndexOf(".") + 1); 36 | } 37 | 38 | public Object getData() { 39 | return data; 40 | } 41 | 42 | public boolean isError() { 43 | return (data instanceof ReadableMap) && 44 | ((ReadableMap) data).hasKey("error"); 45 | } 46 | 47 | public String toString() { 48 | return "{ " + getShortName() + ": " + (data != null ? data.toString() : "null") + " }"; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/events/EventBuilders.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sogilis.ReactNativeBluetooth.events; 18 | 19 | import android.bluetooth.BluetoothDevice; 20 | import android.bluetooth.BluetoothGattCharacteristic; 21 | import android.bluetooth.BluetoothGattService; 22 | import android.util.Base64; 23 | 24 | import static android.bluetooth.BluetoothGattCharacteristic.*; 25 | 26 | import com.facebook.react.bridge.ReadableMap; 27 | import com.facebook.react.bridge.WritableArray; 28 | import com.facebook.react.bridge.WritableMap; 29 | import com.facebook.react.bridge.WritableNativeArray; 30 | import com.facebook.react.bridge.WritableNativeMap; 31 | 32 | import java.util.List; 33 | 34 | import static com.sogilis.ReactNativeBluetooth.domain.BluetoothHelpers.serviceId; 35 | import static com.sogilis.ReactNativeBluetooth.events.EventNames.*; 36 | import static com.sogilis.ReactNativeBluetooth.domain.BluetoothHelpers.deviceId; 37 | import static com.sogilis.ReactNativeBluetooth.domain.BluetoothHelpers.hasProperty; 38 | 39 | public class EventBuilders { 40 | public static BluetoothEvent stateChanged(String newState) { 41 | return new BluetoothEvent(STATE_CHANGED, newState); 42 | } 43 | 44 | public static BluetoothEvent scanStarted() { 45 | return new BluetoothEvent(SCAN_STARTED, new WritableNativeMap()); 46 | } 47 | 48 | public static BluetoothEvent scanStopped() { 49 | return new BluetoothEvent(SCAN_STOPPED, new WritableNativeMap()); 50 | } 51 | 52 | public static BluetoothEvent deviceDiscovered(BluetoothDevice device) { 53 | return new BluetoothEvent(DEVICE_DISCOVERED, deviceMap(device)); 54 | } 55 | 56 | public static BluetoothEvent deviceConnected(BluetoothDevice device) { 57 | return new BluetoothEvent(DEVICE_CONNECTED, deviceMap(device)); 58 | } 59 | 60 | public static BluetoothEvent deviceDisconnected(BluetoothDevice device) { 61 | return new BluetoothEvent(DEVICE_DISCONNECTED, deviceMap(device)); 62 | } 63 | 64 | public static BluetoothEvent serviceDiscoveryStarted(BluetoothDevice device) { 65 | return new BluetoothEvent(SERVICE_DISCOVERY_STARTED, deviceMap(device)); 66 | } 67 | 68 | public static BluetoothEvent servicesDiscovered(BluetoothDevice device, List services) { 69 | return new BluetoothEvent(SERVICES_DISCOVERED, serviceListMap(device, services)); 70 | } 71 | 72 | public static BluetoothEvent characteristicDiscoveryStarted(BluetoothDevice device, BluetoothGattService service) { 73 | return new BluetoothEvent(CHARACTERISTIC_DISCOVERY_STARTED, serviceMap(device, service)); 74 | } 75 | 76 | public static BluetoothEvent characteristicsDiscovered(BluetoothDevice device, 77 | BluetoothGattService service, 78 | List characteristics) { 79 | return new BluetoothEvent(CHARACTERISTICS_DISCOVERED, 80 | characteristicListMap(device, service, characteristics)); 81 | } 82 | 83 | public static BluetoothEvent characteristicRead(BluetoothDevice device, 84 | BluetoothGattCharacteristic characteristic) { 85 | return new BluetoothEvent(CHARACTERISTIC_READ, 86 | characteristicMap(device, characteristic)); 87 | } 88 | 89 | public static BluetoothEvent characteristicWritten(BluetoothDevice device, 90 | BluetoothGattCharacteristic characteristic) { 91 | return new BluetoothEvent(CHARACTERISTIC_WRITTEN, 92 | characteristicMap(device, characteristic)); 93 | } 94 | 95 | public static BluetoothEvent characteristicNotified(BluetoothDevice device, 96 | BluetoothGattCharacteristic characteristic) { 97 | return new BluetoothEvent(CHARACTERISTIC_NOTIFIED, 98 | characteristicMap(device, characteristic)); 99 | } 100 | 101 | public static BluetoothEvent error(String eventName, String errorMessage, String optionalId) { 102 | return new BluetoothEvent(eventName, errorMap(errorMessage, optionalId)); 103 | } 104 | 105 | public static ReadableMap deviceMap(BluetoothDevice device) { 106 | WritableMap map = new WritableNativeMap(); 107 | 108 | map.putString("id", deviceId(device)); 109 | map.putString("address", device.getAddress()); 110 | map.putString("name", device.getName()); 111 | 112 | return map; 113 | } 114 | 115 | public static WritableMap serviceMap(BluetoothDevice device, BluetoothGattService service) { 116 | WritableMap map = new WritableNativeMap(); 117 | 118 | map.putString("id", service.getUuid().toString()); 119 | map.putString("deviceId", deviceId(device)); 120 | 121 | return map; 122 | } 123 | 124 | public static WritableMap serviceListMap(BluetoothDevice device, List services) { 125 | WritableMap map = new WritableNativeMap(); 126 | 127 | map.putString("deviceId", deviceId(device)); 128 | map.putArray("services", serviceArray(device, services)); 129 | 130 | return map; 131 | } 132 | 133 | public static WritableArray serviceArray(BluetoothDevice device, List services) { 134 | WritableArray array = new WritableNativeArray(); 135 | 136 | for (BluetoothGattService service: services) { 137 | array.pushMap(serviceMap(device, service)); 138 | } 139 | 140 | return array; 141 | } 142 | 143 | public static WritableMap characteristicMap(BluetoothDevice device, BluetoothGattCharacteristic characteristic) { 144 | byte[] value = characteristic.getValue(); 145 | String encodedValue = (value != null ? Base64.encodeToString(value, Base64.DEFAULT) : null); 146 | WritableMap map = new WritableNativeMap(); 147 | 148 | map.putString("value", encodedValue); 149 | map.putString("id", characteristic.getUuid().toString()); 150 | map.putString("serviceId", characteristic.getService().getUuid().toString()); 151 | map.putString("deviceId", deviceId(device)); 152 | map.putMap("properties", propertiesMap(characteristic)); 153 | 154 | return map; 155 | } 156 | 157 | private static ReadableMap characteristicListMap(BluetoothDevice device, BluetoothGattService service, List characteristics) { 158 | WritableMap map = new WritableNativeMap(); 159 | 160 | map.putString("deviceId", deviceId(device)); 161 | map.putString("serviceId", serviceId(service)); 162 | map.putArray("characteristics", characteristicArray(device, characteristics)); 163 | 164 | return map; 165 | } 166 | 167 | private static WritableArray characteristicArray(BluetoothDevice device, List characteristics) { 168 | WritableArray array = new WritableNativeArray(); 169 | 170 | for (BluetoothGattCharacteristic characteristic: characteristics) { 171 | array.pushMap(characteristicMap(device, characteristic)); 172 | } 173 | 174 | return array; 175 | } 176 | 177 | public static WritableMap propertiesMap(BluetoothGattCharacteristic characteristic) { 178 | WritableMap map = new WritableNativeMap(); 179 | 180 | map.putBoolean("read", hasProperty(characteristic, PROPERTY_READ)); 181 | map.putBoolean("write", hasProperty(characteristic, PROPERTY_WRITE)); 182 | map.putBoolean("notify", hasProperty(characteristic, PROPERTY_NOTIFY)); 183 | 184 | return map; 185 | } 186 | 187 | public static ReadableMap errorMap(String errorMessage, String optionalId) { 188 | WritableMap map = new WritableNativeMap(); 189 | map.putString("error", errorMessage); 190 | if (optionalId != null) { 191 | map.putString("id", optionalId); 192 | } 193 | return map; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/events/EventEmitter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sogilis.ReactNativeBluetooth.events; 18 | 19 | import android.util.Log; 20 | 21 | import com.facebook.react.bridge.ReactApplicationContext; 22 | import com.facebook.react.modules.core.DeviceEventManagerModule; 23 | 24 | import static com.sogilis.ReactNativeBluetooth.Constants.MODULE_NAME; 25 | 26 | public class EventEmitter { 27 | private final ReactApplicationContext reactContext; 28 | 29 | public EventEmitter(ReactApplicationContext reactContext) { 30 | this.reactContext = reactContext; 31 | } 32 | 33 | public void emit(BluetoothEvent event) { 34 | log(event); 35 | reactContext. 36 | getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class). 37 | emit(event.getName(), event.getData()); 38 | } 39 | 40 | private void log(BluetoothEvent event) { 41 | if (event.isError()) { 42 | Log.e(MODULE_NAME, "Emit error " + event.toString()); 43 | } else { 44 | Log.d(MODULE_NAME, "Emit " + event.toString()); 45 | } 46 | } 47 | 48 | public void emitError(String eventName, String errorMessage, String optionalId) { 49 | emit(EventBuilders.error(eventName, errorMessage, optionalId)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android/src/main/java/com/sogilis/ReactNativeBluetooth/events/EventNames.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.sogilis.ReactNativeBluetooth.events; 18 | 19 | import static com.sogilis.ReactNativeBluetooth.Constants.MODULE_NAME; 20 | 21 | public class EventNames { 22 | public static final String STATE_CHANGED = name("STATE_CHANGED"); 23 | public static final String SCAN_STARTED = name("SCAN_STARTED"); 24 | public static final String SCAN_STOPPED = name("SCAN_STOPPED"); 25 | public static final String DEVICE_DISCOVERED = name("DEVICE_DISCOVERED"); 26 | public static final String DEVICE_CONNECTED = name("DEVICE_CONNECTED"); 27 | public static final String DEVICE_DISCONNECTED = name("DEVICE_DISCONNECTED"); 28 | public static final String SERVICE_DISCOVERY_STARTED = name("SERVICE_DISCOVERY_STARTED"); 29 | public static final String SERVICES_DISCOVERED = name("SERVICES_DISCOVERED"); 30 | public static final String CHARACTERISTIC_DISCOVERY_STARTED = name("CHARACTERISTIC_DISCOVERY_STARTED"); 31 | public static final String CHARACTERISTICS_DISCOVERED = name("CHARACTERISTICS_DISCOVERED"); 32 | public static final String CHARACTERISTIC_READ = name("CHARACTERISTIC_READ"); 33 | public static final String CHARACTERISTIC_WRITTEN = name("CHARACTERISTIC_WRITTEN"); 34 | public static final String CHARACTERISTIC_NOTIFIED = name("CHARACTERISTIC_NOTIFIED"); 35 | 36 | private static final String name(String customNamePart) { 37 | return MODULE_NAME + "." + customNamePart; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Buffer } from 'buffer'; 18 | import { idsAreSame, unsubscription, Configuration } from './src/lib'; 19 | import { connect, disconnect, deviceDidDisconnect, deviceDidConnect } from './src/connection'; 20 | import { discoverServices, discoverCharacteristics } from './src/discovery'; 21 | import { startScan, stopScan, scanDidStop } from './src/scanStartStop'; 22 | import { readCharacteristicValue } from './src/characteristicRead'; 23 | import { writeCharacteristicValue } from './src/characteristicWrite'; 24 | 25 | import { 26 | ReactNativeBluetooth, 27 | EventEmitter, 28 | } from './src/lib'; 29 | 30 | const didChangeState = (callback) => { 31 | const listener = EventEmitter.addListener( 32 | ReactNativeBluetooth.StateChanged, 33 | callback 34 | ); 35 | 36 | ReactNativeBluetooth.notifyCurrentState(); 37 | 38 | return unsubscription(listener); 39 | }; 40 | 41 | const characteristicDidNotify = (characteristic, callback) => { 42 | const onNotifyCaught = notified => { 43 | if (!idsAreSame(characteristic, notified)) 44 | return; 45 | 46 | const mappedNotified = { 47 | ...notified, 48 | value: notified.value ? new Buffer(notified.value, 'base64') : new Buffer(0), 49 | }; 50 | 51 | callback(mappedNotified); 52 | }; 53 | 54 | const listener = EventEmitter.addListener( 55 | ReactNativeBluetooth.CharacteristicNotified, 56 | onNotifyCaught 57 | ); 58 | 59 | ReactNativeBluetooth.subscribeToNotification(characteristic); 60 | 61 | return () => { 62 | listener.remove(); 63 | ReactNativeBluetooth.unsubscribeFromNotification(characteristic); 64 | }; 65 | }; 66 | 67 | const didDiscoverDevice = (callback) => { 68 | return unsubscription(EventEmitter.addListener( 69 | ReactNativeBluetooth.DeviceDiscovered, 70 | callback 71 | )); 72 | }; 73 | 74 | const startScanWithDiscovery = (customOptions, onDeviceFound) => { 75 | let unsubscribeFromDiscovery = didDiscoverDevice(onDeviceFound); 76 | let scanDidStopUnsubscribe = null; 77 | 78 | scanDidStopUnsubscribe = scanDidStop(() => { 79 | if (scanDidStopUnsubscribe) { 80 | scanDidStopUnsubscribe(); 81 | } 82 | 83 | unsubscribeFromDiscovery(); 84 | }); 85 | 86 | return startScan(customOptions); 87 | }; 88 | 89 | export default { 90 | didChangeState, 91 | startScan, 92 | startScanWithDiscovery, 93 | stopScan, 94 | scanDidStop, 95 | didDiscoverDevice, 96 | discoverServices, 97 | discoverCharacteristics, 98 | readCharacteristicValue, 99 | writeCharacteristicValue, 100 | characteristicDidNotify, 101 | connect, 102 | disconnect, 103 | deviceDidDisconnect, 104 | deviceDidConnect, 105 | Buffer, 106 | Configuration, 107 | }; 108 | -------------------------------------------------------------------------------- /ios/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: # rule identifiers to exclude from running 2 | # - colon 3 | # - control_statement 4 | # - file_length 5 | # - force_cast 6 | # - function_body_length 7 | # - leading_whitespace 8 | # - line_length 9 | # - nesting 10 | # - operator_whitespace 11 | # - return_arrow_whitespace 12 | # - todo 13 | # - trailing_newline 14 | # - trailing_whitespace 15 | # - type_body_length 16 | # - type_name 17 | # - variable_name 18 | - valid_docs 19 | included: # paths to include during linting. `--path` is ignored if present. takes precendence over `excluded`. 20 | - Enlaps 21 | excluded: # paths to ignore during linting. overridden by `included`. 22 | - Carthage 23 | - Pods 24 | 25 | line_length: 120 26 | -------------------------------------------------------------------------------- /ios/CoreBluetoothSwift/Aliases.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Aliases.swift 3 | // ReactNativeBluetooth 4 | // 5 | 6 | import CoreBluetooth 7 | 8 | public typealias BluetoothServiceReturn = [String: AnyObject] 9 | 10 | public typealias PeripheralInfo = (peripheral: CBPeripheral, error: Error?) 11 | public typealias ServiceInfo = (peripheral: CBPeripheral, service: CBService, error: Error?) 12 | public typealias CharacteristicInfo = (peripheral: CBPeripheral, characteristic: CBCharacteristic, error: Error?) 13 | 14 | typealias ServiceDiscoveryCallback = (CBPeripheral, _ error: Error?) -> Void 15 | typealias ServiceCallback = (CBPeripheral, CBService, Error?) -> Void 16 | 17 | typealias CharacteristicCallbackParams = (CBPeripheral, CBCharacteristic, Error?) 18 | typealias CharacteristicCallback = (CharacteristicCallbackParams) -> Void 19 | -------------------------------------------------------------------------------- /ios/CoreBluetoothSwift/CentralEventHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CentralEventHandler.swift 3 | // ReactNativeBluetooth 4 | // 5 | 6 | import Foundation 7 | import CoreBluetooth 8 | 9 | enum BluetoothState { 10 | case unknown 11 | case resetting 12 | case unsupported 13 | case unauthorized 14 | case poweredOff 15 | case poweredOn 16 | } 17 | 18 | class CentralEventHandler: NSObject, CBCentralManagerDelegate { 19 | fileprivate var onStateChange: ((BluetoothState) -> Void)? 20 | fileprivate var onDeviceDiscovered: ((CBPeripheral) -> Void)? 21 | fileprivate var onDeviceConnected: ((CBPeripheral) -> Void)? 22 | fileprivate var onDeviceConnectedOnce: ((CBPeripheral) -> Void)? 23 | fileprivate var onDeviceDisconnected: ((CBPeripheral) -> Void)? 24 | 25 | func onStateChange(_ handler: @escaping (BluetoothState) -> Void) -> Void { 26 | self.onStateChange = handler 27 | } 28 | 29 | func onDeviceDiscovered(_ handler: @escaping (CBPeripheral) -> Void) -> Void { 30 | self.onDeviceDiscovered = handler 31 | } 32 | 33 | func onDeviceConnected(_ handler: @escaping (CBPeripheral) -> Void) -> Void { 34 | self.onDeviceConnected = handler 35 | } 36 | 37 | func onDeviceDisconnected(_ handler: @escaping (CBPeripheral) -> Void) -> Void { 38 | self.onDeviceDisconnected = handler 39 | } 40 | 41 | /** 42 | * Waits to check powered on state and handles other cases. 43 | */ 44 | @objc func centralManagerDidUpdateState(_ central: CBCentralManager) { 45 | guard let callback = onStateChange else { 46 | print("State changed but no callback registered \(central.state)") 47 | return 48 | } 49 | 50 | switch central.state { 51 | case .unknown: 52 | callback(BluetoothState.unknown) 53 | break 54 | case .resetting: 55 | callback(BluetoothState.resetting) 56 | break 57 | case .unsupported: 58 | callback(BluetoothState.unsupported) 59 | break 60 | case .unauthorized: 61 | callback(BluetoothState.unauthorized) 62 | break 63 | case .poweredOff: 64 | callback(BluetoothState.poweredOff) 65 | break 66 | case .poweredOn: 67 | callback(BluetoothState.poweredOn) 68 | } 69 | } 70 | 71 | /** 72 | * Handles the case where a peripheral is discovered. 73 | */ 74 | @objc func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, 75 | advertisementData: [String : Any], rssi RSSI: NSNumber) { 76 | print("Discovered \(peripheral.name) at \(RSSI)") 77 | 78 | guard let callback = onDeviceDiscovered else { 79 | print("Device discovered but no callback registered.") 80 | return 81 | } 82 | 83 | callback(peripheral) 84 | } 85 | 86 | /** 87 | * Handles the case where a peripheral is connected. 88 | */ 89 | func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { 90 | print("Peripheral Connected", peripheral.name) 91 | 92 | guard let callback = onDeviceConnected else { 93 | print("Peripheral connected but no callback registered.", peripheral.name) 94 | return 95 | } 96 | 97 | callback(peripheral) 98 | } 99 | 100 | /** 101 | * Handles the case where a peripheral is disconnected. 102 | */ 103 | func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { 104 | print("Peripheral Disconnected") 105 | 106 | guard let callback = onDeviceDisconnected else { 107 | print("Peripheral disconnected but no callback registered.", peripheral.name) 108 | return 109 | } 110 | 111 | callback(peripheral) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /ios/CoreBluetoothSwift/CoreBluetoothSwift.h: -------------------------------------------------------------------------------- 1 | // 2 | // CoreBluetoothSwift.h 3 | // CoreBluetoothSwift 4 | // 5 | 6 | #import 7 | 8 | //! Project version number for CoreBluetoothSwift. 9 | FOUNDATION_EXPORT double CoreBluetoothSwiftVersionNumber; 10 | 11 | //! Project version string for CoreBluetoothSwift. 12 | FOUNDATION_EXPORT const unsigned char CoreBluetoothSwiftVersionString[]; 13 | 14 | // In this header, you should import all the public headers of your framework using statements like #import 15 | 16 | 17 | -------------------------------------------------------------------------------- /ios/CoreBluetoothSwift/DictionaryExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DictionaryExtensions.swift 3 | // ReactNativeBluetooth 4 | 5 | import Foundation 6 | 7 | extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject { 8 | func eitherOr(_ key1: Key, key2: Key) -> AnyObject? { 9 | let first = self[key1] 10 | return first != nil ? first : self[key2] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ios/CoreBluetoothSwift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | ServerIp 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ios/CoreBluetoothSwift/OutputBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutputBuilder.swift 3 | // ReactNativeBluetooth 4 | // 5 | 6 | import Foundation 7 | import CoreBluetooth 8 | 9 | private func getServiceName(serviceId: CBUUID) -> String { 10 | return "Custom Service" 11 | } 12 | 13 | class OutputBuilder { 14 | static func asService(service: CBService) -> BluetoothServiceReturn { 15 | return [ 16 | "id": service.uuid.uuidString as AnyObject, 17 | "deviceId": service.peripheral.identifier.uuidString as AnyObject, 18 | "name": "Unknown" as AnyObject 19 | ] 20 | } 21 | 22 | static func asServiceList(services: [CBService]) -> BluetoothServiceReturn { 23 | return [ 24 | "deviceId": services.first?.peripheral.identifier.uuidString as AnyObject, 25 | "services": services.map(asService) as AnyObject 26 | ] 27 | } 28 | 29 | static func asStateChange(state: BluetoothState) -> String { 30 | switch state { 31 | case .unknown: 32 | return "unknown" 33 | case .resetting: 34 | return "resetting" 35 | case .unsupported: 36 | return "notsupported" 37 | case .unauthorized: 38 | return "unauthorized" 39 | case .poweredOff: 40 | return "disabled" 41 | case .poweredOn: 42 | return "enabled" 43 | } 44 | } 45 | 46 | private static func makeCharacteristicProperties(characteristic: CBCharacteristic) -> [String: AnyObject] { 47 | let canRead = characteristic.properties.contains(CBCharacteristicProperties.read) 48 | let canWrite = characteristic.properties.contains(CBCharacteristicProperties.write) 49 | let canWriteNoResponse = characteristic.properties.contains(CBCharacteristicProperties.writeWithoutResponse) 50 | let canNotify = characteristic.properties.contains(CBCharacteristicProperties.notify) 51 | let canBroadcast = characteristic.properties.contains(CBCharacteristicProperties.broadcast) 52 | 53 | return [ 54 | "read": canRead as AnyObject, 55 | "write": canWrite as AnyObject, 56 | "writeNoResponse": canWriteNoResponse as AnyObject, 57 | "notify": canNotify as AnyObject, 58 | "broadcast": canBroadcast as AnyObject, 59 | ] 60 | } 61 | 62 | static func asCharacteristic(characteristic: CBCharacteristic) -> BluetoothServiceReturn { 63 | return [ 64 | "id": characteristic.uuid.uuidString as AnyObject, 65 | "deviceId": characteristic.service.peripheral.identifier.uuidString as AnyObject, 66 | "serviceId": characteristic.service.uuid.uuidString as AnyObject, 67 | "properties": makeCharacteristicProperties(characteristic: characteristic) as AnyObject, 68 | "value": (characteristic.value?.base64EncodedString(options: Data.Base64EncodingOptions()) ?? "") as AnyObject, 69 | ] 70 | } 71 | 72 | static func asCharacteristicList(characteristics: [CBCharacteristic]) -> BluetoothServiceReturn { 73 | return [ 74 | "deviceId": characteristics.first?.service.peripheral.identifier.uuidString as AnyObject, 75 | "serviceId": characteristics.first?.service.uuid.uuidString as AnyObject, 76 | "characteristics": characteristics.map(asCharacteristic) as AnyObject 77 | ] 78 | } 79 | 80 | static func asCharacteristicWriteResult(info: CharacteristicInfo) -> BluetoothServiceReturn { 81 | if let error = info.error { 82 | return [ 83 | "id": info.characteristic.uuid.uuidString as AnyObject, 84 | "deviceId": info.characteristic.service.peripheral.identifier.uuidString as AnyObject, 85 | "serviceId": info.characteristic.service.uuid.uuidString as AnyObject, 86 | "error": error.localizedDescription as AnyObject, 87 | ] 88 | } 89 | 90 | return [ 91 | "id": info.characteristic.uuid.uuidString as AnyObject, 92 | "deviceId": info.characteristic.service.peripheral.identifier.uuidString as AnyObject, 93 | "serviceId": info.characteristic.service.uuid.uuidString as AnyObject, 94 | "success": true as AnyObject, 95 | ] 96 | } 97 | 98 | static func asDevice(device: CBPeripheral) -> BluetoothServiceReturn { 99 | return [ 100 | "name" : (device.name ?? "Unknown") as AnyObject, 101 | "id" : device.identifier.uuidString as AnyObject, 102 | "address" : device.identifier.uuidString as AnyObject, 103 | ] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ios/CoreBluetoothSwift/PeripheralEventHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeripheralEventHandler.swift 3 | // ReactNativeBluetooth 4 | // 5 | 6 | import Foundation 7 | import CoreBluetooth 8 | 9 | class PeripheralEventHandler: NSObject, CBPeripheralDelegate { 10 | fileprivate var onServiceDiscovered: ServiceDiscoveryCallback? 11 | fileprivate var onCharacteristicDiscovered: ServiceCallback? 12 | fileprivate var onCharacteristicValueUpdated: CharacteristicCallback? 13 | fileprivate var onCharacteristicValueWritten: CharacteristicCallback? 14 | 15 | override init() { 16 | super.init() 17 | } 18 | 19 | func onServiceDiscovered(_ handler: @escaping ServiceDiscoveryCallback) { 20 | self.onServiceDiscovered = handler 21 | } 22 | 23 | func onCharacteristicDiscovered(_ handler: @escaping ServiceCallback) { 24 | self.onCharacteristicDiscovered = handler 25 | } 26 | 27 | func onCharacteristicValueUpdated(_ handler: @escaping CharacteristicCallback) { 28 | self.onCharacteristicValueUpdated = handler 29 | } 30 | 31 | func onCharacteristicValueWritten(_ handler: @escaping CharacteristicCallback) { 32 | self.onCharacteristicValueWritten = handler 33 | } 34 | 35 | func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { 36 | guard let handler = self.onServiceDiscovered else { 37 | print("Peripheral discovered but no handler set", peripheral.name ?? "") 38 | return 39 | } 40 | 41 | handler(peripheral, error) 42 | } 43 | 44 | func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, 45 | error: Error?) { 46 | guard let handler = self.onCharacteristicDiscovered else { 47 | return 48 | } 49 | 50 | handler(peripheral, service, error) 51 | } 52 | 53 | func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, 54 | error: Error?) { 55 | 56 | guard let handler = self.onCharacteristicValueUpdated else { 57 | return 58 | } 59 | 60 | handler(peripheral, characteristic, error) 61 | } 62 | 63 | func peripheral(_ peripheral: CBPeripheral, 64 | didWriteValueFor characteristic: CBCharacteristic, error: Error?) { 65 | guard let handler = self.onCharacteristicValueWritten else { 66 | print("Characteristic written but no handler set", peripheral.name) 67 | return 68 | } 69 | 70 | handler(peripheral, characteristic, error) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ios/CoreBluetoothSwift/PeripheralStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeripheralStore.swift 3 | // ReactNativeBluetooth 4 | // 5 | 6 | import Foundation 7 | import CoreBluetooth 8 | 9 | class PeripheralStore { 10 | fileprivate var peripherals = [UUID: CBPeripheral]() 11 | fileprivate let backgroundQueue = DispatchQueue(label: "PeripheralStore", attributes: []) 12 | 13 | subscript(peripheralId: UUID) -> CBPeripheral? { 14 | get { 15 | return peripherals[peripheralId] 16 | } 17 | set(newItem) { 18 | guard let newItem = newItem else { 19 | return 20 | } 21 | addPeripheral(newItem) 22 | } 23 | } 24 | 25 | var items: [CBPeripheral] { 26 | get { 27 | return peripherals.map { $0.1 } 28 | } 29 | } 30 | 31 | func removeAll() { 32 | backgroundQueue.sync(execute: { [unowned self] in 33 | self.peripherals.removeAll() 34 | }) 35 | } 36 | 37 | func addPeripheral(_ newItem: CBPeripheral) { 38 | backgroundQueue.sync(execute: { [unowned self] in 39 | self.peripherals[newItem.identifier] = newItem 40 | }) 41 | } 42 | 43 | func listIds() -> [UUID] { 44 | return peripherals.map { $0.0 } 45 | } 46 | 47 | func getPeripheral(_ lookup: [String: AnyObject]) -> CBPeripheral? { 48 | guard let deviceIdString = lookup.eitherOr("deviceId", key2: "id") as? String else { 49 | print("No device id found.") 50 | return nil 51 | } 52 | 53 | guard let deviceId = UUID(uuidString: deviceIdString) else { 54 | print("Invalid device id found.") 55 | return nil 56 | } 57 | 58 | guard let device = self.peripherals[deviceId] else { 59 | print("No peripheral found", deviceId, peripherals) 60 | return nil 61 | } 62 | 63 | return device 64 | } 65 | 66 | func getService(_ device: CBPeripheral, lookup: [String: AnyObject]) -> CBService? { 67 | guard let serviceIdString = lookup.eitherOr("serviceId", key2: "id") as? String else { 68 | print("No service id found.") 69 | return nil 70 | } 71 | 72 | let serviceId = CBUUID(string: serviceIdString).uuidString 73 | 74 | return device.services?.filter { $0.uuid.uuidString == serviceId }.first 75 | } 76 | 77 | func getService(_ lookup: [String: AnyObject]) -> CBService? { 78 | guard let device = getPeripheral(lookup) else { 79 | print("Peripheral not found when looking up service") 80 | return nil 81 | } 82 | 83 | return getService(device, lookup: lookup) 84 | } 85 | 86 | func getCharacteristic(_ lookup: [String: AnyObject]) -> CBCharacteristic? { 87 | guard let service = getService(lookup) else { 88 | print("Service not found when looking up characteristic", lookup) 89 | return nil 90 | } 91 | 92 | guard let charIdString = lookup["id"] as? String else { 93 | print("No characteristic id found.", lookup) 94 | return nil 95 | } 96 | 97 | let charId = CBUUID(string: charIdString).uuidString 98 | 99 | let characteristic = service.characteristics?.filter { $0.uuid.uuidString == charId }.first 100 | 101 | if characteristic == nil { 102 | print("Unable to locate characteristic in service", charId, 103 | service.characteristics?.map { $0.uuid.uuidString }) 104 | } 105 | 106 | return characteristic 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ios/ReactNativeBluetooth/ReactNativeBluetooth.h: -------------------------------------------------------------------------------- 1 | // 2 | // ReactNativeBluetooth.h 3 | // ReactNativeBluetooth 4 | // 5 | 6 | #import 7 | #import 8 | 9 | @interface ReactNativeBluetooth : RCTEventEmitter 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /ios/ReactNativeBluetooth/ReactNativeBluetooth.m: -------------------------------------------------------------------------------- 1 | // 2 | // ReactNativeBluetooth.m 3 | // ReactNativeBluetooth 4 | // 5 | 6 | #import "ReactNativeBluetooth.h" 7 | #import "CoreBluetoothSwift/CoreBluetoothSwift-Swift.h" 8 | 9 | NSString *const statusChangeEventName = @"StateChanged"; 10 | NSString *const scanStartedEventName = @"ScanStarted"; 11 | NSString *const scanStoppedEventName = @"ScanStopped"; 12 | NSString *const serviceDiscoveredEventName = @"ServiceDiscovered"; 13 | NSString *const serviceDiscoveryStartedEventName = @"ServiceDiscoveryStarted"; 14 | NSString *const characteristicDiscoveryStartedEventName = @"CharacteristicDiscoveryStarted"; 15 | NSString *const characteristicDiscoveredEventName = @"CharacteristicDiscovered"; 16 | NSString *const characteristicReadEventName = @"CharacteristicRead"; 17 | NSString *const characteristicWrittenEventName = @"CharacteristicWritten"; 18 | NSString *const characteristicNotifiedEventName = @"CharacteristicNotified"; 19 | NSString *const deviceConnectedEventName = @"DeviceConnected"; 20 | NSString *const deviceDisconnectedEventName = @"DeviceDisconnected"; 21 | NSString *const deviceDiscoveredEventName = @"DeviceDiscovered"; 22 | 23 | @implementation ReactNativeBluetooth { 24 | BluetoothActions * actions; 25 | } 26 | 27 | 28 | RCT_EXPORT_MODULE(); 29 | 30 | - (instancetype)init { 31 | actions = [[BluetoothActions alloc] init]; 32 | [self registerForNativeEvents]; 33 | 34 | return [super init]; 35 | } 36 | 37 | -(void)dealloc { 38 | [actions cleanUp]; 39 | } 40 | 41 | - (NSDictionary *)constantsToExport { 42 | return @{statusChangeEventName: statusChangeEventName, 43 | scanStartedEventName: scanStartedEventName, 44 | scanStoppedEventName: scanStoppedEventName, 45 | serviceDiscoveredEventName: serviceDiscoveredEventName, 46 | serviceDiscoveryStartedEventName: serviceDiscoveryStartedEventName, 47 | characteristicDiscoveryStartedEventName: characteristicDiscoveryStartedEventName, 48 | characteristicDiscoveredEventName: characteristicDiscoveredEventName, 49 | characteristicReadEventName: characteristicReadEventName, 50 | characteristicWrittenEventName: characteristicWrittenEventName, 51 | characteristicNotifiedEventName: characteristicNotifiedEventName, 52 | deviceConnectedEventName: deviceConnectedEventName, 53 | deviceDisconnectedEventName: deviceDisconnectedEventName, 54 | deviceDiscoveredEventName: deviceDiscoveredEventName}; 55 | } 56 | 57 | - (NSArray *)supportedEvents { 58 | return @[statusChangeEventName, 59 | scanStartedEventName, 60 | scanStoppedEventName, 61 | serviceDiscoveredEventName, 62 | serviceDiscoveryStartedEventName, 63 | characteristicDiscoveryStartedEventName, 64 | characteristicDiscoveredEventName, 65 | characteristicReadEventName, 66 | characteristicWrittenEventName, 67 | characteristicNotifiedEventName, 68 | deviceConnectedEventName, 69 | deviceDisconnectedEventName, 70 | deviceDiscoveredEventName, 71 | ]; 72 | } 73 | 74 | typedef NSDictionary * BluetoothServiceReturn; 75 | 76 | - (void)sendEventIfApplicable:(NSString * const)name body:(BluetoothServiceReturn)body { 77 | if (self.bridge == nil) { 78 | NSLog(@"Unable to send event, bridge is nil"); 79 | return; 80 | } 81 | 82 | [self sendEventWithName:name body:body]; 83 | } 84 | 85 | - (void)sendStringEventIfApplicable:(NSString * const)name body:(NSString *)body { 86 | if (self.bridge == nil) { 87 | NSLog(@"Unable to send event, bridge is nil"); 88 | return; 89 | } 90 | 91 | [self sendEventWithName:name body:body]; 92 | } 93 | 94 | - (void)registerForNativeEvents { 95 | __block ReactNativeBluetooth *myself = self; 96 | 97 | void (^onChangeState)(NSString *) = 98 | ^(NSString * result) { 99 | [myself sendStringEventIfApplicable:statusChangeEventName body:result]; 100 | }; 101 | 102 | void (^onServiceDiscovered)(BluetoothServiceReturn) = 103 | ^(BluetoothServiceReturn result) { 104 | [myself sendEventIfApplicable:serviceDiscoveredEventName body:result]; 105 | }; 106 | 107 | void (^onCharacteristicRead)(BluetoothServiceReturn) = 108 | ^(BluetoothServiceReturn result) { 109 | [myself sendEventIfApplicable:characteristicReadEventName body:result]; 110 | }; 111 | 112 | void (^onCharacteristicWritten)(BluetoothServiceReturn) = 113 | ^(BluetoothServiceReturn result) { 114 | [myself sendEventIfApplicable:characteristicWrittenEventName body:result]; 115 | }; 116 | 117 | void (^onCharacteristicNotified)(BluetoothServiceReturn) = 118 | ^(BluetoothServiceReturn result) { 119 | [myself sendEventIfApplicable:characteristicNotifiedEventName body:result]; 120 | }; 121 | 122 | void (^onCharacteristicDiscovered)(BluetoothServiceReturn) = 123 | ^(BluetoothServiceReturn result) { 124 | [myself sendEventIfApplicable:characteristicDiscoveredEventName body:result]; 125 | }; 126 | 127 | void (^onDeviceConnected)(BluetoothServiceReturn) = 128 | ^(BluetoothServiceReturn result) { 129 | [myself sendEventIfApplicable:deviceConnectedEventName body:result]; 130 | }; 131 | 132 | void (^onDeviceDisconnected)(BluetoothServiceReturn) = 133 | ^(BluetoothServiceReturn result) { 134 | [myself sendEventIfApplicable:deviceDisconnectedEventName body:result]; 135 | }; 136 | 137 | void (^onDeviceDiscovered)(BluetoothServiceReturn) = 138 | ^(BluetoothServiceReturn result) { 139 | [myself sendEventIfApplicable:deviceDiscoveredEventName body:result]; 140 | }; 141 | 142 | 143 | [actions onChangeState:onChangeState]; 144 | [actions onServiceDiscovered:onServiceDiscovered]; 145 | [actions onCharacteristicRead:onCharacteristicRead]; 146 | [actions onCharacteristicWritten:onCharacteristicWritten]; 147 | [actions onCharacteristicNotified:onCharacteristicNotified]; 148 | [actions onCharacteristicDiscovered:onCharacteristicDiscovered]; 149 | [actions onDeviceConnected:onDeviceConnected]; 150 | [actions onDeviceDisconnected:onDeviceDisconnected]; 151 | [actions onDeviceDiscovered:onDeviceDiscovered]; 152 | } 153 | 154 | 155 | - (void)onStatusChanged:(NSString * const)status { 156 | [self sendEventWithName:statusChangeEventName body:status]; 157 | } 158 | 159 | RCT_EXPORT_METHOD(startScan:(NSArray *)params) { 160 | __block ReactNativeBluetooth *myself = self; 161 | 162 | void (^onScanStarted)(BluetoothServiceReturn) = 163 | ^(BluetoothServiceReturn result) { 164 | [myself sendEventWithName:scanStartedEventName body:result]; 165 | }; 166 | 167 | [actions startScan:params onScanStarted:onScanStarted]; 168 | } 169 | 170 | RCT_EXPORT_METHOD(stopScan) { 171 | __block ReactNativeBluetooth *myself = self; 172 | 173 | void (^onScanStopped)(BluetoothServiceReturn) = 174 | ^(BluetoothServiceReturn result) { 175 | [myself sendEventWithName:scanStoppedEventName body:result]; 176 | }; 177 | 178 | [actions stopScan:onScanStopped]; 179 | } 180 | 181 | RCT_EXPORT_METHOD(discoverServices:(NSDictionary *)params services:(NSArray *)services) { 182 | __block ReactNativeBluetooth *myself = self; 183 | 184 | void (^onDiscoverStarted)(BluetoothServiceReturn) = 185 | ^(BluetoothServiceReturn result) { 186 | [myself sendEventWithName:serviceDiscoveryStartedEventName body:result]; 187 | }; 188 | 189 | NSDictionary * toSearch = [params count] == 0 ? NULL : params; 190 | 191 | [actions discoverServices:toSearch services:services onDiscoverStarted:onDiscoverStarted]; 192 | } 193 | 194 | RCT_EXPORT_METHOD(discoverCharacteristics:(NSDictionary *)params characteristics:(NSArray *)characteristics) { 195 | __block ReactNativeBluetooth *myself = self; 196 | 197 | void (^onDiscoverStarted)(BluetoothServiceReturn) = 198 | ^(BluetoothServiceReturn result) { 199 | [myself sendEventWithName:characteristicDiscoveryStartedEventName body:result]; 200 | }; 201 | 202 | NSDictionary * toSearch = [params count] == 0 ? NULL : params; 203 | 204 | [actions discoverCharacteristics:params characteristics:characteristics onDiscoverStarted:onDiscoverStarted]; 205 | } 206 | 207 | RCT_EXPORT_METHOD(writeCharacteristicValue:(NSDictionary *)params value:(NSString *)value withResponse:(BOOL)withResponse) { 208 | [actions writeCharacteristicValue:params data:value withResponse:withResponse]; 209 | } 210 | 211 | RCT_EXPORT_METHOD(readCharacteristicValue:(NSDictionary *)params) { 212 | [actions readCharacteristicValue:params]; 213 | } 214 | 215 | RCT_EXPORT_METHOD(connect:(NSDictionary *)params) { 216 | [actions connect:params]; 217 | } 218 | 219 | RCT_EXPORT_METHOD(disconnect:(NSDictionary *)params) { 220 | [actions disconnect:params]; 221 | } 222 | 223 | RCT_EXPORT_METHOD(notifyCurrentState) { 224 | [self sendEventWithName:statusChangeEventName body:actions.bluetoothState]; 225 | } 226 | 227 | RCT_EXPORT_METHOD(subscribeToNotification:(NSDictionary *)params) { 228 | [actions subscribeToNotification:params]; 229 | } 230 | 231 | RCT_EXPORT_METHOD(unsubscribeFromNotification:(NSDictionary *)params) { 232 | [actions unsubscribeFromNotification:params]; 233 | } 234 | 235 | @end 236 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-bluetooth-manager", 3 | "version": "0.5.0", 4 | "license": "Apache-2.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/sogilis/react-native-bluetooth-manager.git" 8 | }, 9 | "keywords": [ 10 | "react", 11 | "native", 12 | "bluetooth" 13 | ], 14 | "author": "Sogilis SARL", 15 | "bugs": { 16 | "url": "https://github.com/sogilis/react-native-bluetooth-manager/issues" 17 | }, 18 | "homepage": "https://github.com/sogilis/react-native-bluetooth-manager#readme", 19 | "scripts": { 20 | "test": "BABEL_ENV=test ./node_modules/babel-tape-runner/bin/babel-tape-runner ./test/*.js | tap-spec", 21 | "module-watch": "nodemon -w index.js -e js -x rsync -rtv index.js Example/node_modules/react-native-bluetooth-manager/index.js", 22 | "lint": "eslint .", 23 | "lint-example": "eslint Example/app" 24 | }, 25 | "devDependencies": { 26 | "babel-preset-es2016": "^6.11.3", 27 | "babel-preset-react-native": "^1.9.0", 28 | "babel-preset-react-native-stage-0": "^1.0.1", 29 | "babel-runtime": "^6.6.1", 30 | "babel-tape-runner": "^2.0.1", 31 | "blue-tape": "^1.0.0", 32 | "eslint": "^3.4.0", 33 | "eslint-plugin-react": "^6.2.0", 34 | "eslint-plugin-react-native": "^2.0.0", 35 | "mockery": "^1.7.0", 36 | "nodemon": "^1.10.2", 37 | "tap-spec": "^4.1.1", 38 | "tape": "^4.6.0" 39 | }, 40 | "dependencies": { 41 | "buffer": "^4.9.1", 42 | "lodash": "^4.15.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/characteristicRead.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | makeCharacteristicEventListener, 19 | ReactNativeBluetooth, 20 | } from './lib'; 21 | 22 | import { Buffer } from 'buffer'; 23 | 24 | const readCharacteristicValue = characteristic => { 25 | return new Promise((resolve, reject) => { 26 | const resultMapper = detail => { 27 | return { 28 | ...detail, 29 | base64Value: detail.value, 30 | value: new Buffer(detail.value, 'base64'), 31 | }; 32 | }; 33 | 34 | makeCharacteristicEventListener(resolve, reject, ReactNativeBluetooth.CharacteristicRead, characteristic, resultMapper); 35 | 36 | ReactNativeBluetooth.readCharacteristicValue(characteristic); 37 | }); 38 | }; 39 | 40 | export { 41 | readCharacteristicValue, 42 | }; 43 | -------------------------------------------------------------------------------- /src/characteristicWrite.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | makeCharacteristicEventListener, 19 | ReactNativeBluetooth, 20 | } from './lib'; 21 | 22 | const writeCharacteristicValue = (characteristic, buffer, withResponse) => { 23 | return new Promise((resolve, reject) => { 24 | if (!withResponse) { 25 | ReactNativeBluetooth.writeCharacteristicValue(characteristic, buffer.toString('base64'), withResponse); 26 | resolve(); 27 | return; 28 | } 29 | 30 | const resultMapper = detail => detail; 31 | 32 | makeCharacteristicEventListener(resolve, reject, ReactNativeBluetooth.CharacteristicWritten, characteristic, resultMapper); 33 | 34 | ReactNativeBluetooth.writeCharacteristicValue(characteristic, buffer.toString('base64'), withResponse); 35 | }); 36 | }; 37 | 38 | export { 39 | writeCharacteristicValue, 40 | }; 41 | -------------------------------------------------------------------------------- /src/connection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | idsAreSame, 19 | ReactNativeBluetooth, 20 | EventEmitter, 21 | unsubscription, 22 | Configuration, 23 | } from './lib'; 24 | 25 | const deviceDidDisconnect = (device, callback) => { 26 | const disconnectionCaught = detail => { 27 | if (!idsAreSame(device, detail)) 28 | return; 29 | 30 | callback(detail); 31 | }; 32 | 33 | const listener = EventEmitter.addListener( 34 | ReactNativeBluetooth.DeviceDisconnected, 35 | disconnectionCaught 36 | ); 37 | 38 | return unsubscription(listener); 39 | }; 40 | 41 | const deviceDidConnect = (device, callback) => { 42 | const connectionCaught = detail => { 43 | if (!idsAreSame(device, detail)) 44 | return; 45 | 46 | callback(detail); 47 | }; 48 | 49 | const listener = EventEmitter.addListener( 50 | ReactNativeBluetooth.DeviceConnected, 51 | connectionCaught 52 | ); 53 | 54 | return unsubscription(listener); 55 | }; 56 | 57 | 58 | const connect = (device) => { 59 | return new Promise((resolve, reject) => { 60 | let listener; 61 | let timer = null; 62 | 63 | const onConnectionCaught = connectedDetail => { 64 | if (!idsAreSame(device, connectedDetail)) 65 | return; 66 | 67 | if (timer) { 68 | clearTimeout(timer); 69 | } 70 | 71 | if ("error" in connectedDetail) { 72 | reject(new Error(connectedDetail["error"])); 73 | return; 74 | } 75 | 76 | resolve(connectedDetail); 77 | 78 | if (listener) { 79 | listener.remove(); 80 | } 81 | }; 82 | 83 | listener = EventEmitter.addListener( 84 | ReactNativeBluetooth.DeviceConnected, 85 | onConnectionCaught 86 | ); 87 | 88 | ReactNativeBluetooth.connect(device); 89 | timer = setTimeout(() => { 90 | if (listener) { 91 | listener.remove(); 92 | } 93 | reject(new Error('CONNECTION_TIMEOUT')); 94 | }, Configuration.timeout); 95 | }); 96 | }; 97 | 98 | const disconnect = (device) => { 99 | return new Promise((resolve, reject) => { 100 | let unsubscribe; 101 | 102 | let disconnectCallback = callBackInfo => { 103 | if (unsubscribe) { 104 | unsubscribe(); 105 | } 106 | 107 | if ("error" in callBackInfo) { 108 | reject(new Error(callBackInfo.error)); 109 | } 110 | 111 | resolve(callBackInfo); 112 | }; 113 | 114 | unsubscribe = deviceDidDisconnect(device, disconnectCallback); 115 | 116 | ReactNativeBluetooth.disconnect(device); 117 | }); 118 | }; 119 | 120 | export { 121 | connect, 122 | disconnect, 123 | deviceDidDisconnect, 124 | deviceDidConnect, 125 | }; 126 | -------------------------------------------------------------------------------- /src/discovery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | ReactNativeBluetooth, 19 | EventEmitter, 20 | unsubscription, 21 | Configuration, 22 | } from './lib'; 23 | 24 | import * as _ from 'lodash'; 25 | 26 | const findItemsByIds = (idsToLookFor, itemsToSearchIn) => { 27 | if (itemsToSearchIn == null) { 28 | return []; 29 | } 30 | 31 | const lowerCaseIds = (idsToLookFor || []).map(id => id.toLowerCase()); 32 | 33 | return idsToLookFor == null || idsToLookFor.length == 0 ? 34 | itemsToSearchIn : 35 | itemsToSearchIn.filter(item => _.includes(lowerCaseIds, item.id.toLowerCase())); 36 | }; 37 | 38 | const listenToStartupAndDiscoveryEvents = (onStartup, onStartupFailure, onDiscoveryAction, 39 | discoveryCallbackName, discoveryStartedCallbackName) => { 40 | 41 | const listener = EventEmitter.addListener( 42 | discoveryCallbackName, 43 | onDiscoveryAction 44 | ); 45 | 46 | let startupListener; 47 | let timer; 48 | 49 | const onStartedCaught = detail => { 50 | if (timer) { 51 | clearTimeout(timer); 52 | } 53 | 54 | if ("error" in detail) { 55 | onStartupFailure(new Error(detail["error"])); 56 | return; 57 | } 58 | 59 | if (startupListener) { 60 | startupListener.remove(); 61 | } 62 | 63 | onStartup(unsubscription(listener)); 64 | }; 65 | 66 | startupListener = EventEmitter.addListener( 67 | discoveryStartedCallbackName, 68 | onStartedCaught 69 | ); 70 | 71 | timer = setTimeout(() => { 72 | if (startupListener) { 73 | listener.remove(); 74 | onStartupFailure(new Error("Timeout discovering characteristics")); 75 | } 76 | }, Configuration.timeout); 77 | }; 78 | 79 | const createOnDiscoveryHandler = (requiredIds, onDiscoveredCallback, itemKey) => { 80 | return itemMap => { 81 | if ("error" in itemMap) { 82 | onDiscoveredCallback(itemMap); 83 | return; 84 | } 85 | 86 | const items = findItemsByIds(requiredIds, itemMap[itemKey]); 87 | 88 | onDiscoveredCallback(items); 89 | }; 90 | }; 91 | 92 | const discoverServicesAction = (device, serviceIds, callback) => { 93 | return new Promise((resolve, reject) => { 94 | const onServicesDiscovered = createOnDiscoveryHandler(serviceIds, callback, "services"); 95 | 96 | listenToStartupAndDiscoveryEvents(resolve, reject, onServicesDiscovered, 97 | ReactNativeBluetooth.ServiceDiscovered, ReactNativeBluetooth.ServiceDiscoveryStarted); 98 | 99 | ReactNativeBluetooth.discoverServices(device, serviceIds || []); 100 | }); 101 | }; 102 | 103 | const discoverCharacteristicsAction = (service, characteristicIds, callback) => { 104 | return new Promise((resolve, reject) => { 105 | const onCharacteristicsDiscovered = createOnDiscoveryHandler(characteristicIds, callback, "characteristics"); 106 | 107 | listenToStartupAndDiscoveryEvents(resolve, reject, onCharacteristicsDiscovered, 108 | ReactNativeBluetooth.CharacteristicDiscovered, ReactNativeBluetooth.CharacteristicDiscoveryStarted); 109 | 110 | ReactNativeBluetooth.discoverCharacteristics(service, characteristicIds || []); 111 | }); 112 | }; 113 | 114 | const callDiscoveryAction = (actionToCall, context, itemIds) => { 115 | return new Promise((resolve, reject) => { 116 | let unsubscribe; 117 | 118 | const onDiscovery = items => { 119 | if (unsubscribe) 120 | unsubscribe(); 121 | 122 | if ("error" in items) 123 | reject(items["error"]); 124 | else 125 | resolve(items); 126 | }; 127 | 128 | actionToCall(context, itemIds, onDiscovery) 129 | .then(release => { 130 | unsubscribe = release; 131 | }) 132 | .catch(error => { 133 | console.log(error); 134 | reject(error); 135 | }); 136 | }); 137 | }; 138 | 139 | const discoverServices = (device, serviceIds) => { 140 | return callDiscoveryAction(discoverServicesAction, device, serviceIds); 141 | }; 142 | 143 | const discoverCharacteristics = (service, characteristicIds) => { 144 | return callDiscoveryAction(discoverCharacteristicsAction, service, characteristicIds); 145 | }; 146 | 147 | export { 148 | discoverServices, 149 | discoverCharacteristics, 150 | }; 151 | -------------------------------------------------------------------------------- /src/lib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | NativeAppEventEmitter, 19 | NativeModules, 20 | NativeEventEmitter, 21 | Platform 22 | } from 'react-native'; 23 | 24 | const ReactNativeBluetooth = NativeModules.ReactNativeBluetooth; 25 | 26 | const EventEmitter = Platform.OS === 'android' ? NativeAppEventEmitter : 27 | new NativeEventEmitter(ReactNativeBluetooth); 28 | 29 | const idsAreSame = (set1, set2) => set1 && set2 && ("id" in set1) && ("id" in set2) && set1["id"].toLowerCase() == set2["id"].toLowerCase(); 30 | 31 | const Configuration = { 32 | timeout: 70000, 33 | }; 34 | 35 | const unsubscription = (listener) => { 36 | return () => { 37 | listener.remove(); 38 | }; 39 | }; 40 | 41 | const makeCharacteristicEventListener = (listenSuccess, listenFailure, listenEventName, characteristic, resultMapper) => { 42 | let timer = null; 43 | 44 | let listener = EventEmitter.addListener(listenEventName, detail => { 45 | if (!idsAreSame(characteristic, detail)) 46 | return; 47 | 48 | if (timer) { 49 | clearTimeout(timer); 50 | } 51 | 52 | if (listener) { 53 | listener.remove(); 54 | listener = null; 55 | } 56 | 57 | if ("error" in detail) { 58 | listenFailure(new Error(detail.error)); 59 | } else { 60 | listenSuccess(resultMapper(detail)); 61 | } 62 | }); 63 | 64 | timer = setTimeout(() => { 65 | if (listener) { 66 | listener.remove(); 67 | listenFailure(new Error("Timeout on characteristic operation")); 68 | } 69 | }, Configuration.timeout); 70 | }; 71 | 72 | export { 73 | idsAreSame, 74 | unsubscription, 75 | makeCharacteristicEventListener, 76 | ReactNativeBluetooth, 77 | EventEmitter, 78 | Configuration, 79 | }; 80 | -------------------------------------------------------------------------------- /src/scanStartStop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Sogilis SARL 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { 18 | NativeAppEventEmitter, 19 | NativeModules, 20 | NativeEventEmitter, 21 | Platform 22 | } from 'react-native'; 23 | 24 | import { 25 | unsubscription, 26 | } from './lib'; 27 | 28 | const ReactNativeBluetooth = NativeModules.ReactNativeBluetooth; 29 | 30 | const EventEmitter = Platform.OS === 'android' ? NativeAppEventEmitter : 31 | new NativeEventEmitter(ReactNativeBluetooth); 32 | 33 | const DefaultScanOptions = { 34 | uuids: [], 35 | }; 36 | 37 | let scanInProgress = false; 38 | 39 | const Scan = { 40 | stopAfter: (timeout) => { 41 | return new Promise((resolve, reject) => { 42 | let timeoutReached = false; 43 | let timer = null; 44 | let stopSubscription = null; 45 | 46 | stopSubscription = scanDidStop(() => { 47 | if (timer) { 48 | clearTimeout(timer); 49 | } 50 | 51 | if (stopSubscription) { 52 | stopSubscription(); 53 | } 54 | 55 | resolve(timeoutReached); 56 | }); 57 | 58 | timer = setTimeout(() => { 59 | timeoutReached = true; 60 | 61 | stopScan() 62 | .catch(error => reject(error)); 63 | }, timeout); 64 | }); 65 | }, 66 | }; 67 | 68 | const startScan = (customOptions = {}) => { 69 | return new Promise((resolve, reject) => { 70 | let options = Object.assign({}, DefaultScanOptions, customOptions); 71 | 72 | let listener; 73 | 74 | listener = EventEmitter.addListener(ReactNativeBluetooth.ScanStarted, (detail) => { 75 | if (listener) { 76 | listener.remove(); 77 | } 78 | 79 | scanInProgress = true; 80 | 81 | if ("error" in detail) { 82 | reject(new Error(detail.error)); 83 | } else { 84 | resolve(Scan); 85 | } 86 | }); 87 | 88 | ReactNativeBluetooth.startScan(options.uuids); 89 | }); 90 | }; 91 | 92 | const stopScan = () => { 93 | return new Promise((resolve, reject) => { 94 | if (!scanInProgress) { 95 | resolve(); 96 | return; 97 | } 98 | 99 | let listener; 100 | 101 | listener = EventEmitter.addListener(ReactNativeBluetooth.ScanStopped, detail => { 102 | if (listener) { 103 | listener.remove(); 104 | } 105 | scanInProgress = false; 106 | 107 | if ("error" in detail) { 108 | reject(new Error(detail.error)); 109 | } else { 110 | resolve(detail); 111 | } 112 | }); 113 | 114 | ReactNativeBluetooth.stopScan(); 115 | }); 116 | }; 117 | 118 | const scanDidStop = (callback) => { 119 | const scanStoppedCaught = detail => { 120 | callback(detail); 121 | }; 122 | 123 | const listener = EventEmitter.addListener( 124 | ReactNativeBluetooth.ScanStopped, 125 | scanStoppedCaught 126 | ); 127 | 128 | return unsubscription(listener); 129 | }; 130 | 131 | export { 132 | startScan, 133 | stopScan, 134 | scanDidStop, 135 | }; 136 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": true, 7 | "spread": true, 8 | "binaryLiterals": true, 9 | "blockBindings": true, 10 | "templateStrings": true, 11 | "modules": true, 12 | "destructuring": true, 13 | "generators": true, 14 | "experimentalObjectRestSpread": true, 15 | "impliedStrict": true 16 | } 17 | }, 18 | "plugins": [ 19 | "react", 20 | "react-native" 21 | ], 22 | "globals": { 23 | "__DEV__": false 24 | }, 25 | "extends": [ 26 | "eslint:recommended", 27 | "plugin:react/recommended" 28 | ], 29 | "env": { 30 | "browser": true, 31 | "es6": true, 32 | "node": true 33 | }, 34 | "rules": { 35 | "semi": [ 36 | 2, 37 | "always" 38 | ], 39 | "consistent-return": 2, 40 | "no-whitespace-before-property": 2, 41 | "no-unneeded-ternary": 2, 42 | "max-len": [0, 120], 43 | "max-lines": [0, 500], 44 | "consistent-this": 2, 45 | "keyword-spacing": 2, 46 | "block-spacing": 2, 47 | "max-depth": 2, 48 | "brace-style": 2, 49 | "comma-style": 2, 50 | "comma-spacing": 2, 51 | "camelcase": 2, 52 | "operator-linebreak": 2, 53 | "no-console": 0, 54 | "no-var": 2, 55 | "no-tabs": 2, 56 | "no-restricted-syntax": ["error", "WithStatement"], 57 | "no-underscore-dangle": 2, 58 | "space-before-blocks": 2, 59 | "spaced-comment": 2, 60 | "space-in-parens": 2, 61 | "space-infix-ops": 2, 62 | "no-useless-rename": 2, 63 | "prefer-const": 2, 64 | "no-const-assign": 2, 65 | "no-duplicate-imports": 2, 66 | "no-useless-computed-key": 2, 67 | "no-useless-constructor": 2, 68 | "generator-star-spacing": 2, 69 | "prefer-spread": 2, 70 | "quotes": [0, "single"], 71 | "prefer-rest-params": 2, 72 | "rest-spread-spacing": 2, 73 | "require-yield": 2, 74 | "no-extend-native": 2, 75 | "no-extra-bind": 2, 76 | "no-global-assign": 2, 77 | "no-implicit-coercion": 2, 78 | "no-implicit-globals": 2, 79 | "no-implied-eval": 2, 80 | "no-invalid-this": 2, 81 | "no-lone-blocks": 2, 82 | "space-unary-ops": 2, 83 | "guard-for-in": 2, 84 | "no-redeclare": 2, 85 | "no-self-assign": 2, 86 | "no-self-compare": 2, 87 | "no-return-assign": 2, 88 | "no-unmodified-loop-condition": 2, 89 | "no-useless-call": 2, 90 | "no-useless-concat": 2, 91 | "no-useless-escape": 2, 92 | "no-void": 2, 93 | "radix": 2, 94 | "wrap-iife": 2, 95 | "arrow-spacing": 2, 96 | "no-multi-spaces": 2, 97 | "complexity": 2, 98 | "yoda": 2, 99 | "no-unused-vars": 2, 100 | "no-undefined": 0, 101 | "no-use-before-define": 2, 102 | "no-restricted-globals": 2, 103 | "object-shorthand": [0, "methods"], 104 | "indent": [0, 2], 105 | "react-native/no-unused-styles": 2, 106 | "react-native/split-platform-components": 2, 107 | "react-native/no-inline-styles": 2, 108 | "react-native/no-color-literals": 0, 109 | "react/display-name": 0 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/connectTests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { 4 | test, 5 | api, 6 | TestConfiguration, 7 | } from './testCommon'; 8 | 9 | const { 10 | connect, 11 | } = api.default; 12 | 13 | const connectionDevice = { 14 | id: "12345", 15 | }; 16 | 17 | test('Tests connect success.', function(t) { 18 | connect(connectionDevice) 19 | .then(connected => { 20 | t.deepEqual(connectionDevice, connected); 21 | t.end(); 22 | }) 23 | .catch(() => { 24 | t.fail(); 25 | }); 26 | }); 27 | 28 | test('Tests connect failure.', function(t) { 29 | TestConfiguration.connectSucceed = false; 30 | 31 | const expectedError = new Error('Error'); 32 | 33 | connect(connectionDevice) 34 | .then(() => { 35 | t.fail(); 36 | }) 37 | .catch(error => { 38 | t.deepEqual(error, expectedError); 39 | t.end(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/disconnectTests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { 4 | TestConfiguration, 5 | test, 6 | api, 7 | } from './testCommon'; 8 | 9 | test('Tests disconnect success', function(t) { 10 | const { 11 | disconnect 12 | } = api.default; 13 | 14 | const expectedDevice = { 15 | id: "12345" 16 | }; 17 | 18 | disconnect(expectedDevice) 19 | .then(device => { 20 | t.deepEqual(device, expectedDevice); 21 | t.end(); 22 | }).catch(error => { 23 | t.error(error, "Error when trying to disconnect"); 24 | t.end(); 25 | }); 26 | }); 27 | 28 | test('Tests disconnect fail', function(t) { 29 | TestConfiguration.disconnectSucceed = false; 30 | 31 | const { 32 | disconnect 33 | } = api.default; 34 | 35 | const deviceId = "12345"; 36 | const expectedError = new Error('Error'); 37 | 38 | disconnect({ 39 | id: deviceId, 40 | }) 41 | .then(() => { 42 | t.fail("Disconnect should not succeed"); 43 | t.end(); 44 | }).catch(error => { 45 | t.deepEqual(error, expectedError); 46 | t.end(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/discoverCharacteristicsTests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { 4 | NativeEventEmitter, 5 | ReactNativeBluetooth, 6 | test, 7 | api, 8 | } from './testCommon'; 9 | 10 | const { 11 | discoverCharacteristics, 12 | } = api.default; 13 | 14 | const testService = { 15 | id: "12345", 16 | }; 17 | 18 | const runSuccessfullDiscoverTest = (t, characteristicIdsToDiscover, expectedCharacteristics) => { 19 | const discoveryCallbackParams = { 20 | characteristics: expectedCharacteristics, 21 | }; 22 | 23 | NativeEventEmitter.callBackParams[ReactNativeBluetooth.CharacteristicDiscovered] = [discoveryCallbackParams]; 24 | 25 | discoverCharacteristics(testService, characteristicIdsToDiscover) 26 | .then(characteristics => { 27 | t.deepEqual(characteristics, expectedCharacteristics); 28 | t.end(); 29 | }) 30 | .catch(error => { 31 | t.fail(`Discover characterstics error ${error.message}`); 32 | }); 33 | }; 34 | 35 | test('Tests discover characteristics, single characteristic.', function(t) { 36 | const expectedCharacteristics = [{ 37 | id: "8798798", 38 | deviceID: "3453987", 39 | }]; 40 | 41 | const characteristicIds = ["8798798"]; 42 | 43 | runSuccessfullDiscoverTest(t, characteristicIds, expectedCharacteristics); 44 | }); 45 | 46 | test('Tests discover characteristics, multiple characteristics.', function(t) { 47 | const expectedCharacteristics = [{ 48 | id: "8798798", 49 | deviceID: "3453987", 50 | }, { 51 | id: "78987987", 52 | deviceID: "3453987", 53 | }]; 54 | 55 | const characteristicIds = ["8798798", "78987987"]; 56 | 57 | runSuccessfullDiscoverTest(t, characteristicIds, expectedCharacteristics); 58 | }); 59 | 60 | test('Tests discover characteristics, casing of expected ids not important.', function(t) { 61 | const expectedCharacteristics = [{ 62 | id: "AAAAAAA", 63 | deviceID: "3453987", 64 | }, { 65 | id: "BBBBBBB", 66 | deviceID: "3453987", 67 | }]; 68 | 69 | const characteristicIds = ["aaaaaaa", "bbbbbbb"]; 70 | 71 | runSuccessfullDiscoverTest(t, characteristicIds, expectedCharacteristics); 72 | }); 73 | 74 | test('Tests discover characteristics, casing of to discover ids not important.', function(t) { 75 | const expectedCharacteristics = [{ 76 | id: "aaaaaaa", 77 | deviceID: "3453987", 78 | }, { 79 | id: "bbbbbbb", 80 | deviceID: "3453987", 81 | }]; 82 | 83 | const characteristicIds = ["AAAAAAA", "BBBBBBB"]; 84 | 85 | runSuccessfullDiscoverTest(t, characteristicIds, expectedCharacteristics); 86 | }); 87 | 88 | test('Tests discover characteristics, null to find array returns all characteristics.', function(t) { 89 | const expectedCharacteristics = [{ 90 | id: "sdlkjksjljf", 91 | deviceID: "3453987", 92 | }, { 93 | id: "jssdflkjlk", 94 | deviceID: "3453987", 95 | }]; 96 | 97 | runSuccessfullDiscoverTest(t, null, expectedCharacteristics); 98 | }); 99 | 100 | test('Tests discover characteristics, empty to find array returns all characteristics.', function(t) { 101 | const expectedCharacteristics = [{ 102 | id: "sdlkjksjljf", 103 | deviceID: "3453987", 104 | }, { 105 | id: "jssdflkjlk", 106 | deviceID: "3453987", 107 | }]; 108 | 109 | runSuccessfullDiscoverTest(t, [], expectedCharacteristics); 110 | }); 111 | 112 | test('Tests discover characteristics, correctly handles discovery error.', function(t) { 113 | const expectedError = new Error("An error occurred"); 114 | 115 | const discoveryCallbackParams = { 116 | error: expectedError, 117 | }; 118 | 119 | NativeEventEmitter.callBackParams[ReactNativeBluetooth.CharacteristicDiscovered] = [discoveryCallbackParams]; 120 | 121 | discoverCharacteristics(testService, null) 122 | .then(() => { 123 | t.fail(); 124 | }) 125 | .catch(error => { 126 | t.deepEqual(error, expectedError); 127 | t.end(); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/discoverServicesTests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { 4 | NativeEventEmitter, 5 | ReactNativeBluetooth, 6 | test, 7 | api, 8 | } from './testCommon'; 9 | 10 | const { 11 | discoverServices, 12 | } = api.default; 13 | 14 | const testDevice = { 15 | id: "12345", 16 | }; 17 | 18 | const runSuccessfullDiscoverTest = (t, serviceIdsToDiscover, expectedServices) => { 19 | const discoveryCallbackParams = { 20 | services: expectedServices, 21 | }; 22 | 23 | NativeEventEmitter.callBackParams[ReactNativeBluetooth.ServiceDiscovered] = [discoveryCallbackParams]; 24 | 25 | discoverServices(testDevice, serviceIdsToDiscover) 26 | .then(services => { 27 | t.deepEqual(services, expectedServices); 28 | t.end(); 29 | }) 30 | .catch(error => { 31 | t.fail(`Discover services error ${error.message}`); 32 | }); 33 | }; 34 | 35 | test('Tests discover services, single service.', function(t) { 36 | const expectedServices = [{ 37 | id: "8798798", 38 | deviceID: "3453987", 39 | }]; 40 | 41 | const serviceIds = ["8798798"]; 42 | 43 | runSuccessfullDiscoverTest(t, serviceIds, expectedServices); 44 | }); 45 | 46 | test('Tests discover services, multiple services.', function(t) { 47 | const expectedServices = [{ 48 | id: "8798798", 49 | deviceID: "3453987", 50 | }, { 51 | id: "78987987", 52 | deviceID: "3453987", 53 | }]; 54 | 55 | const serviceIds = ["8798798", "78987987"]; 56 | 57 | runSuccessfullDiscoverTest(t, serviceIds, expectedServices); 58 | }); 59 | 60 | test('Tests discover services, casing of expected ids not important.', function(t) { 61 | const expectedServices = [{ 62 | id: "AAAAAAA", 63 | deviceID: "3453987", 64 | }, { 65 | id: "BBBBBBB", 66 | deviceID: "3453987", 67 | }]; 68 | 69 | const serviceIds = ["aaaaaaa", "bbbbbbb"]; 70 | 71 | runSuccessfullDiscoverTest(t, serviceIds, expectedServices); 72 | }); 73 | 74 | test('Tests discover services, casing of to discover ids not important.', function(t) { 75 | const expectedServices = [{ 76 | id: "aaaaaaa", 77 | deviceID: "3453987", 78 | }, { 79 | id: "bbbbbbb", 80 | deviceID: "3453987", 81 | }]; 82 | 83 | const serviceIds = ["AAAAAAA", "BBBBBBB"]; 84 | 85 | runSuccessfullDiscoverTest(t, serviceIds, expectedServices); 86 | }); 87 | 88 | test('Tests discover services, null to find array returns all services.', function(t) { 89 | const expectedServices = [{ 90 | id: "sdlkjksjljf", 91 | deviceID: "3453987", 92 | }, { 93 | id: "jssdflkjlk", 94 | deviceID: "3453987", 95 | }]; 96 | 97 | runSuccessfullDiscoverTest(t, null, expectedServices); 98 | }); 99 | 100 | test('Tests discover services, empty to find array returns all services.', function(t) { 101 | const expectedServices = [{ 102 | id: "sdlkjksjljf", 103 | deviceID: "3453987", 104 | }, { 105 | id: "jssdflkjlk", 106 | deviceID: "3453987", 107 | }]; 108 | 109 | runSuccessfullDiscoverTest(t, [], expectedServices); 110 | }); 111 | 112 | test('Tests discover services, correctly handles discovery error.', function(t) { 113 | const expectedError = new Error("An error occurred"); 114 | 115 | const discoveryCallbackParams = { 116 | error: expectedError, 117 | }; 118 | 119 | NativeEventEmitter.callBackParams[ReactNativeBluetooth.ServiceDiscovered] = [discoveryCallbackParams]; 120 | 121 | discoverServices(testDevice, null) 122 | .then(() => { 123 | t.fail(); 124 | }) 125 | .catch(error => { 126 | t.deepEqual(error, expectedError); 127 | t.end(); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/notifyTests.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogilis/react-native-bluetooth-manager/5824dc20f84952b6401f125f37a1e74902dcfde1/test/notifyTests.js -------------------------------------------------------------------------------- /test/reactNativeMock.js: -------------------------------------------------------------------------------- 1 | const TestConfiguration = { 2 | platform: "android", 3 | connectSucceed: true, 4 | disconnectSucceed: true, 5 | discoverCharacteristicsSucceed: true, 6 | discoverServicesSucceed: true, 7 | }; 8 | 9 | const NativeAppEventEmitter = { 10 | callBackParams: {}, 11 | callbacks: {}, 12 | addListener: function(name, callback) { 13 | this.callbacks[name] = callback; 14 | 15 | return { 16 | remove: () => { 17 | this.callbacks[name] = null; 18 | } 19 | }; 20 | }, 21 | }; 22 | 23 | const Reset = () => { 24 | NativeAppEventEmitter.callBacks = {}; 25 | }; 26 | 27 | const NativeEventEmitter = NativeAppEventEmitter; 28 | 29 | const Platform = { 30 | OS: "android", 31 | }; 32 | 33 | const EventConstants = { 34 | DeviceConnected: "DeviceConnected", 35 | DeviceDisconnected: "DeviceDisconnected", 36 | CharacteristicDiscovered: "CharacteristicDiscovered", 37 | CharacteristicDiscoveryStarted: "CharacteristicDiscoveryStarted", 38 | ServiceDiscovered: "ServiceDiscovered", 39 | ServiceDiscoveryStarted: "ServiceDiscoveryStarted", 40 | }; 41 | 42 | const NativeModules = { 43 | ReactNativeBluetooth: { 44 | DeviceConnected: EventConstants.DeviceConnected, 45 | DeviceDisconnected: EventConstants.DeviceDisconnected, 46 | CharacteristicDiscovered: EventConstants.CharacteristicDiscovered, 47 | ServiceDiscovered: EventConstants.ServiceDiscovered, 48 | CharacteristicDiscovereryStarted: EventConstants.CharacteristicDiscovereryStarted, 49 | ServiceDiscovereryStarted: EventConstants.ServiceDiscovereryStarted, 50 | 51 | connect: function(device) { 52 | if (!TestConfiguration.connectSucceed) { 53 | NativeAppEventEmitter.callbacks[this.DeviceConnected]({ 54 | id: device.id, 55 | error: "Error", 56 | }); 57 | return; 58 | } 59 | 60 | NativeAppEventEmitter.callbacks[this.DeviceConnected](device); 61 | }, 62 | 63 | disconnect: function(device) { 64 | if (!TestConfiguration.disconnectSucceed) { 65 | NativeAppEventEmitter.callbacks[this.DeviceDisconnected]({ 66 | id: device.id, 67 | error: "Error", 68 | }); 69 | return; 70 | } 71 | 72 | NativeAppEventEmitter.callbacks[this.DeviceDisconnected](device); 73 | }, 74 | 75 | discoverCharacteristics: function(service) { 76 | if (!TestConfiguration.discoverCharacteristicsSucceed) { 77 | NativeAppEventEmitter.callbacks[this.CharacteristicDiscovereryStarted]({ 78 | id: service.id, 79 | error: "Error", 80 | }); 81 | return; 82 | } 83 | 84 | NativeAppEventEmitter.callbacks[this.CharacteristicDiscovereryStarted](service); 85 | 86 | const callBackParams = NativeAppEventEmitter.callBackParams[this.CharacteristicDiscovered]; 87 | const discoveredCallback = NativeAppEventEmitter.callbacks[this.CharacteristicDiscovered]; 88 | 89 | if (callBackParams && discoveredCallback) { 90 | discoveredCallback.apply(this, callBackParams); 91 | } 92 | }, 93 | 94 | discoverServices: function(service) { 95 | if (!TestConfiguration.discoverServicesSucceed) { 96 | NativeAppEventEmitter.callbacks[this.ServiceDiscovereryStarted]({ 97 | id: service.id, 98 | error: "Error", 99 | }); 100 | return; 101 | } 102 | 103 | NativeAppEventEmitter.callbacks[this.ServiceDiscovereryStarted](service); 104 | 105 | const callBackParams = NativeAppEventEmitter.callBackParams[this.ServiceDiscovered]; 106 | const discoveredCallback = NativeAppEventEmitter.callbacks[this.ServiceDiscovered]; 107 | 108 | if (callBackParams && discoveredCallback) { 109 | discoveredCallback.apply(this, callBackParams); 110 | } 111 | } 112 | } 113 | }; 114 | 115 | export { 116 | TestConfiguration, 117 | NativeAppEventEmitter, 118 | NativeEventEmitter, 119 | Platform, 120 | NativeModules, 121 | Reset, 122 | }; 123 | -------------------------------------------------------------------------------- /test/readCharacteristicTests.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogilis/react-native-bluetooth-manager/5824dc20f84952b6401f125f37a1e74902dcfde1/test/readCharacteristicTests.js -------------------------------------------------------------------------------- /test/startStopScanTests.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogilis/react-native-bluetooth-manager/5824dc20f84952b6401f125f37a1e74902dcfde1/test/startStopScanTests.js -------------------------------------------------------------------------------- /test/testCommon.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { 4 | TestConfiguration, 5 | NativeEventEmitter, 6 | NativeModules, 7 | } from './reactNativeMock'; 8 | 9 | const ReactNativeBluetooth = NativeModules.ReactNativeBluetooth; 10 | 11 | const test = require('blue-tape'); 12 | 13 | import mockery from "mockery"; 14 | 15 | mockery.enable(); 16 | mockery.warnOnUnregistered(false); 17 | 18 | mockery.registerSubstitute('react-native', '../test/reactNativeMock.js'); 19 | 20 | const api = require("../"); 21 | 22 | export { 23 | ReactNativeBluetooth, 24 | TestConfiguration, 25 | NativeEventEmitter, 26 | NativeModules, 27 | test, 28 | api, 29 | }; 30 | -------------------------------------------------------------------------------- /test/writeCharacteristicTests.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogilis/react-native-bluetooth-manager/5824dc20f84952b6401f125f37a1e74902dcfde1/test/writeCharacteristicTests.js --------------------------------------------------------------------------------