├── .gitignore ├── .npmignore ├── README.md ├── android ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── gm │ └── RCTGMBluetooth │ ├── RCTGMBluetoothModule.java │ ├── RCTGMBluetoothPackage.java │ └── RCTGMBluetoothService.java ├── doc ├── GprinterSDKForAndroid_V2.2.5.zip ├── PrinterSDKforiOS.zip ├── 佳博标签打印机编程手册 v1.0.3.pdf └── 佳博热敏票据打印机编程手册 v1.0.1.pdf ├── index.js ├── ios ├── RCTBluetoothSerial.xcodeproj │ └── project.pbxproj └── RCTBluetoothSerial │ ├── BLE.h │ ├── BLE.m │ ├── BLEDefines.h │ ├── CBPeripheral+BTSExtensions.h │ ├── CBPeripheral+BTSExtensions.m │ ├── RCTBluetoothSerial.h │ └── RCTBluetoothSerial.m ├── js ├── esc.js ├── tsc.js └── util.js └── package.json /.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 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | 37 | .vscode 38 | jsconfig.json 39 | *.sublime-workspace 40 | .idea -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /doc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## React Native GM Bluetooth 2 | 3 | fork from https://github.com/rusel1989/react-native-bluetooth-serial 4 | 5 | 请前往看其文档 6 | 7 | 增加了几个功能便于想打印机输入二进制 8 | 9 | `writeHexToDevice` 输入指令 GMBluetooth.writeHexToDevice('1B 40'); 10 | 11 | `writeTextToDevice` 输入文本 GMBluetooth.writeTextToDevice('ReactNative and Bluetooth and Print'); 12 | 13 | doc 目录是佳博的demo和ESC TSC文档 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.1.0' 8 | } 9 | } 10 | 11 | apply plugin: 'com.android.library' 12 | 13 | android { 14 | compileSdkVersion 23 15 | buildToolsVersion "23.0.1" 16 | 17 | defaultConfig { 18 | minSdkVersion 16 19 | targetSdkVersion 22 20 | versionCode 1 21 | versionName "1.0" 22 | ndk { 23 | abiFilters "armeabi-v7a", "x86" 24 | } 25 | } 26 | } 27 | 28 | repositories { 29 | mavenCentral() 30 | maven { 31 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 32 | url "$rootDir/../node_modules/react-native/android" 33 | } 34 | } 35 | 36 | dependencies { 37 | compile 'com.facebook.react:react-native:+' 38 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | ## Project-wide Gradle settings. 2 | # 3 | # For more details on how to configure your build environment visit 4 | # http://www.gradle.org/docs/current/userguide/build_environment.html 5 | # 6 | # Specifies the JVM arguments used for the daemon process. 7 | # The setting is particularly useful for tweaking memory settings. 8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | #Thu Nov 24 08:03:52 CET 2016 16 | android.useDeprecatedNdk=true 17 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmfe/react-native-gm-bluetooth/580aff2111b0f033e339ee38cded793091ae4460/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /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 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/gm/RCTGMBluetooth/RCTGMBluetoothModule.java: -------------------------------------------------------------------------------- 1 | package com.gm.RCTGMBluetooth; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Set; 5 | import javax.annotation.Nullable; 6 | 7 | import android.app.Activity; 8 | import android.bluetooth.BluetoothAdapter; 9 | import android.bluetooth.BluetoothDevice; 10 | import android.content.BroadcastReceiver; 11 | import android.content.Context; 12 | import android.content.Intent; 13 | import android.content.IntentFilter; 14 | import android.os.Build; 15 | import android.util.Log; 16 | import android.util.Base64; 17 | 18 | import com.facebook.react.bridge.ActivityEventListener; 19 | import com.facebook.react.bridge.LifecycleEventListener; 20 | import com.facebook.react.bridge.ReactApplicationContext; 21 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 22 | import com.facebook.react.bridge.ReactMethod; 23 | import com.facebook.react.bridge.Arguments; 24 | import com.facebook.react.bridge.WritableMap; 25 | import com.facebook.react.bridge.WritableArray; 26 | import com.facebook.react.bridge.Promise; 27 | import com.facebook.react.modules.core.DeviceEventManagerModule; 28 | 29 | import static com.gm.RCTGMBluetooth.RCTGMBluetoothPackage.TAG; 30 | 31 | @SuppressWarnings("unused") 32 | public class RCTGMBluetoothModule extends ReactContextBaseJavaModule implements ActivityEventListener, LifecycleEventListener { 33 | 34 | // Debugging 35 | private static final boolean D = true; 36 | 37 | // Event names 38 | private static final String BT_ENABLED = "bluetoothEnabled"; 39 | private static final String BT_DISABLED = "bluetoothDisabled"; 40 | private static final String CONN_SUCCESS = "connectionSuccess"; 41 | private static final String CONN_FAILED = "connectionFailed"; 42 | private static final String CONN_LOST = "connectionLost"; 43 | private static final String DEVICE_READ = "read"; 44 | private static final String ERROR = "error"; 45 | 46 | // Other stuff 47 | private static final int REQUEST_ENABLE_BLUETOOTH = 1; 48 | private static final int REQUEST_PAIR_DEVICE = 2; 49 | // Members 50 | private BluetoothAdapter mBluetoothAdapter; 51 | private RCTGMBluetoothService mBluetoothService; 52 | private ReactApplicationContext mReactContext; 53 | 54 | private StringBuffer mBuffer = new StringBuffer(); 55 | 56 | // Promises 57 | private Promise mEnabledPromise; 58 | private Promise mConnectedPromise; 59 | private Promise mDeviceDiscoveryPromise; 60 | private Promise mPairDevicePromise; 61 | private String delimiter = ""; 62 | 63 | public RCTGMBluetoothModule(ReactApplicationContext reactContext) { 64 | super(reactContext); 65 | 66 | if (D) Log.d(TAG, "Bluetooth module started"); 67 | 68 | mReactContext = reactContext; 69 | 70 | if (mBluetoothAdapter == null) { 71 | mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 72 | } 73 | 74 | if (mBluetoothService == null) { 75 | mBluetoothService = new RCTGMBluetoothService(this); 76 | } 77 | 78 | if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) { 79 | sendEvent(BT_ENABLED, null); 80 | } else { 81 | sendEvent(BT_DISABLED, null); 82 | } 83 | 84 | mReactContext.addActivityEventListener(this); 85 | mReactContext.addLifecycleEventListener(this); 86 | registerBluetoothStateReceiver(); 87 | } 88 | 89 | @Override 90 | public String getName() { 91 | return "RCTGMBluetooth"; 92 | } 93 | 94 | @Override 95 | public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) { 96 | if (D) Log.d(TAG, "On activity result request: " + requestCode + ", result: " + resultCode); 97 | if (requestCode == REQUEST_ENABLE_BLUETOOTH) { 98 | if (resultCode == Activity.RESULT_OK) { 99 | if (D) Log.d(TAG, "User enabled Bluetooth"); 100 | if (mEnabledPromise != null) { 101 | mEnabledPromise.resolve(true); 102 | } 103 | } else { 104 | if (D) Log.d(TAG, "User did *NOT* enable Bluetooth"); 105 | if (mEnabledPromise != null) { 106 | mEnabledPromise.reject(new Exception("User did not enable Bluetooth")); 107 | } 108 | } 109 | mEnabledPromise = null; 110 | } 111 | 112 | if (requestCode == REQUEST_PAIR_DEVICE) { 113 | if (resultCode == Activity.RESULT_OK) { 114 | if (D) Log.d(TAG, "Pairing ok"); 115 | } else { 116 | if (D) Log.d(TAG, "Pairing failed"); 117 | } 118 | } 119 | } 120 | 121 | @Override 122 | public void onNewIntent(Intent intent) { 123 | if (D) Log.d(TAG, "On new intent"); 124 | } 125 | 126 | 127 | @Override 128 | public void onHostResume() { 129 | if (D) Log.d(TAG, "Host resume"); 130 | } 131 | 132 | @Override 133 | public void onHostPause() { 134 | if (D) Log.d(TAG, "Host pause"); 135 | } 136 | 137 | @Override 138 | public void onHostDestroy() { 139 | if (D) Log.d(TAG, "Host destroy"); 140 | mBluetoothService.stop(); 141 | } 142 | 143 | @Override 144 | public void onCatalystInstanceDestroy() { 145 | if (D) Log.d(TAG, "Catalyst instance destroyed"); 146 | super.onCatalystInstanceDestroy(); 147 | mBluetoothService.stop(); 148 | } 149 | 150 | /*******************************/ 151 | /** Methods Available from JS **/ 152 | /*******************************/ 153 | 154 | /*************************************/ 155 | /** Bluetooth state related methods **/ 156 | 157 | @ReactMethod 158 | /** 159 | * Request user to enable bluetooth 160 | */ 161 | public void requestEnable(Promise promise) { 162 | // If bluetooth is already enabled resolve promise immediately 163 | if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) { 164 | promise.resolve(true); 165 | // Start new intent if bluetooth is note enabled 166 | } else { 167 | Activity activity = getCurrentActivity(); 168 | mEnabledPromise = promise; 169 | Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 170 | if (activity != null) { 171 | activity.startActivityForResult(intent, REQUEST_ENABLE_BLUETOOTH); 172 | } else { 173 | Exception e = new Exception("Cannot start activity"); 174 | Log.e(TAG, "Cannot start activity", e); 175 | mEnabledPromise.reject(e); 176 | mEnabledPromise = null; 177 | onError(e); 178 | } 179 | } 180 | } 181 | 182 | @ReactMethod 183 | /** 184 | * Enable bluetooth 185 | */ 186 | public void enable(Promise promise) { 187 | if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) { 188 | mBluetoothAdapter.enable(); 189 | } 190 | promise.resolve(true); 191 | } 192 | 193 | @ReactMethod 194 | /** 195 | * Disable bluetooth 196 | */ 197 | public void disable(Promise promise) { 198 | if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) { 199 | mBluetoothAdapter.disable(); 200 | } 201 | promise.resolve(true); 202 | } 203 | 204 | @ReactMethod 205 | /** 206 | * Check if bluetooth is enabled 207 | */ 208 | public void isEnabled(Promise promise) { 209 | if (mBluetoothAdapter != null) { 210 | promise.resolve(mBluetoothAdapter.isEnabled()); 211 | } else { 212 | promise.resolve(false); 213 | } 214 | } 215 | 216 | @ReactMethod 217 | public void withDelimiter(String delimiter, Promise promise) { 218 | this.delimiter = delimiter; 219 | promise.resolve(true); 220 | } 221 | 222 | /**************************************/ 223 | /** Bluetooth device related methods **/ 224 | 225 | @ReactMethod 226 | /** 227 | * List paired bluetooth devices 228 | */ 229 | public void list(Promise promise) { 230 | WritableArray deviceList = Arguments.createArray(); 231 | if (mBluetoothAdapter != null) { 232 | Set bondedDevices = mBluetoothAdapter.getBondedDevices(); 233 | 234 | for (BluetoothDevice rawDevice : bondedDevices) { 235 | WritableMap device = deviceToWritableMap(rawDevice); 236 | deviceList.pushMap(device); 237 | } 238 | } 239 | promise.resolve(deviceList); 240 | } 241 | 242 | @ReactMethod 243 | /** 244 | * Discover unpaired bluetooth devices 245 | */ 246 | public void discoverUnpairedDevices(final Promise promise) { 247 | if (D) Log.d(TAG, "Discover unpaired called"); 248 | 249 | mDeviceDiscoveryPromise = promise; 250 | registerBluetoothDeviceDiscoveryReceiver(); 251 | 252 | if (mBluetoothAdapter != null) { 253 | mBluetoothAdapter.startDiscovery(); 254 | } else { 255 | promise.resolve(Arguments.createArray()); 256 | } 257 | } 258 | 259 | @ReactMethod 260 | /** 261 | * Cancel discovery 262 | */ 263 | public void cancelDiscovery(final Promise promise) { 264 | if (D) Log.d(TAG, "Cancel discovery called"); 265 | 266 | if (mBluetoothAdapter != null) { 267 | if (mBluetoothAdapter.isDiscovering()) { 268 | mBluetoothAdapter.cancelDiscovery(); 269 | } 270 | } 271 | promise.resolve(true); 272 | } 273 | 274 | 275 | @ReactMethod 276 | /** 277 | * Pair device 278 | */ 279 | public void pairDevice(String id, Promise promise) { 280 | if (D) Log.d(TAG, "Pair device: " + id); 281 | 282 | if (mBluetoothAdapter != null) { 283 | mPairDevicePromise = promise; 284 | BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(id); 285 | if (device != null) { 286 | pairDevice(device); 287 | } else { 288 | mPairDevicePromise.reject(new Exception("Could not pair device " + id)); 289 | mPairDevicePromise = null; 290 | } 291 | } else { 292 | promise.resolve(false); 293 | } 294 | } 295 | 296 | @ReactMethod 297 | /** 298 | * Unpair device 299 | */ 300 | public void unpairDevice(String id, Promise promise) { 301 | if (D) Log.d(TAG, "Unpair device: " + id); 302 | 303 | if (mBluetoothAdapter != null) { 304 | mPairDevicePromise = promise; 305 | BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(id); 306 | if (device != null) { 307 | unpairDevice(device); 308 | } else { 309 | mPairDevicePromise.reject(new Exception("Could not unpair device " + id)); 310 | mPairDevicePromise = null; 311 | } 312 | } else { 313 | promise.resolve(false); 314 | } 315 | } 316 | 317 | /********************************/ 318 | /** Connection related methods **/ 319 | 320 | @ReactMethod 321 | /** 322 | * Connect to device by id 323 | */ 324 | public void connect(String id, Promise promise) { 325 | mConnectedPromise = promise; 326 | if (mBluetoothAdapter != null) { 327 | BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(id); 328 | if (device != null) { 329 | mBluetoothService.connect(device); 330 | } else { 331 | promise.reject(new Exception("Could not connect to " + id)); 332 | } 333 | } else { 334 | promise.resolve(true); 335 | } 336 | } 337 | 338 | @ReactMethod 339 | /** 340 | * Disconnect from device 341 | */ 342 | public void disconnect(Promise promise) { 343 | mBluetoothService.stop(); 344 | promise.resolve(true); 345 | } 346 | 347 | @ReactMethod 348 | /** 349 | * Check if device is connected 350 | */ 351 | public void isConnected(Promise promise) { 352 | promise.resolve(mBluetoothService.isConnected()); 353 | } 354 | 355 | /*********************/ 356 | /** Write to device **/ 357 | 358 | @ReactMethod 359 | /** 360 | * Write to device over serial port 361 | */ 362 | public void writeToDevice(String message, Promise promise) { 363 | if (D) Log.d(TAG, "Write " + message); 364 | byte[] data = Base64.decode(message, Base64.DEFAULT); 365 | mBluetoothService.write(data); 366 | promise.resolve(true); 367 | } 368 | 369 | @ReactMethod 370 | public void writeHexToDevice(String hexString, Promise promise){ 371 | mBluetoothService.write(hexStringToBytes(hexString)); 372 | promise.resolve(true); 373 | } 374 | 375 | @ReactMethod 376 | public void writeTextToDevice(String Text, Promise promise){ 377 | byte[] send; 378 | try { 379 | send = Text.getBytes("GB2312"); 380 | } catch (Exception e) { 381 | send = Text.getBytes(); 382 | } 383 | mBluetoothService.write(send); 384 | promise.resolve(true); 385 | } 386 | 387 | /**********************/ 388 | /** Read from device **/ 389 | 390 | @ReactMethod 391 | /** 392 | * Read from device over serial port 393 | */ 394 | public void readFromDevice(Promise promise) { 395 | if (D) Log.d(TAG, "Read"); 396 | int length = mBuffer.length(); 397 | String data = mBuffer.substring(0, length); 398 | mBuffer.delete(0, length); 399 | promise.resolve(data); 400 | } 401 | 402 | @ReactMethod 403 | public void readUntilDelimiter(String delimiter, Promise promise) { 404 | promise.resolve(readUntil(delimiter)); 405 | } 406 | 407 | 408 | /***********/ 409 | /** Other **/ 410 | 411 | @ReactMethod 412 | /** 413 | * Clear data in buffer 414 | */ 415 | public void clear(Promise promise) { 416 | mBuffer.setLength(0); 417 | promise.resolve(true); 418 | } 419 | 420 | @ReactMethod 421 | /** 422 | * Get length of data available to read 423 | */ 424 | public void available(Promise promise) { 425 | promise.resolve(mBuffer.length()); 426 | } 427 | 428 | 429 | @ReactMethod 430 | /** 431 | * Set bluetooth adapter name 432 | */ 433 | public void setAdapterName(String newName, Promise promise) { 434 | if (mBluetoothAdapter != null) { 435 | mBluetoothAdapter.setName(newName); 436 | } 437 | promise.resolve(true); 438 | } 439 | 440 | /****************************************/ 441 | /** Methods available to whole package **/ 442 | /****************************************/ 443 | 444 | /** 445 | * Handle connection success 446 | * @param msg Additional message 447 | */ 448 | void onConnectionSuccess(String msg) { 449 | WritableMap params = Arguments.createMap(); 450 | params.putString("message", msg); 451 | sendEvent(CONN_SUCCESS, null); 452 | if (mConnectedPromise != null) { 453 | mConnectedPromise.resolve(params); 454 | } 455 | mConnectedPromise = null; 456 | } 457 | 458 | /** 459 | * handle connection failure 460 | * @param msg Additional message 461 | */ 462 | void onConnectionFailed(String msg) { 463 | WritableMap params = Arguments.createMap(); 464 | params.putString("message", msg); 465 | sendEvent(CONN_FAILED, null); 466 | if (mConnectedPromise != null) { 467 | mConnectedPromise.reject(new Exception(msg)); 468 | } 469 | mConnectedPromise = null; 470 | } 471 | 472 | /** 473 | * Handle lost connection 474 | * @param msg Message 475 | */ 476 | void onConnectionLost (String msg) { 477 | WritableMap params = Arguments.createMap(); 478 | params.putString("message", msg); 479 | sendEvent(CONN_LOST, params); 480 | } 481 | 482 | /** 483 | * Handle error 484 | * @param e Exception 485 | */ 486 | void onError (Exception e) { 487 | WritableMap params = Arguments.createMap(); 488 | params.putString("message", e.getMessage()); 489 | sendEvent(ERROR, params); 490 | } 491 | 492 | /** 493 | * Handle read 494 | * @param data Message 495 | */ 496 | void onData (String data) { 497 | mBuffer.append(data); 498 | String completeData = readUntil(this.delimiter); 499 | if (completeData != null && completeData.length() > 0) { 500 | WritableMap params = Arguments.createMap(); 501 | params.putString("data", completeData); 502 | sendEvent(DEVICE_READ, params); 503 | } 504 | } 505 | 506 | private String readUntil(String delimiter) { 507 | String data = ""; 508 | int index = mBuffer.indexOf(delimiter, 0); 509 | if (index > -1) { 510 | data = mBuffer.substring(0, index + delimiter.length()); 511 | mBuffer.delete(0, index + delimiter.length()); 512 | } 513 | return data; 514 | } 515 | 516 | /*********************/ 517 | /** Private methods **/ 518 | /*********************/ 519 | 520 | /** 521 | * Check if is api level 19 or above 522 | * @return is above api level 19 523 | */ 524 | private boolean isKitKatOrAbove () { 525 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 526 | } 527 | 528 | /** 529 | * Send event to javascript 530 | * @param eventName Name of the event 531 | * @param params Additional params 532 | */ 533 | private void sendEvent(String eventName, @Nullable WritableMap params) { 534 | if (mReactContext.hasActiveCatalystInstance()) { 535 | if (D) Log.d(TAG, "Sending event: " + eventName); 536 | mReactContext 537 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 538 | .emit(eventName, params); 539 | } 540 | } 541 | 542 | /** 543 | * Convert BluetoothDevice into WritableMap 544 | * @param device Bluetooth device 545 | */ 546 | private WritableMap deviceToWritableMap(BluetoothDevice device) { 547 | if (D) Log.d(TAG, "device" + device.toString()); 548 | 549 | WritableMap params = Arguments.createMap(); 550 | 551 | params.putString("name", device.getName()); 552 | params.putString("address", device.getAddress()); 553 | params.putString("id", device.getAddress()); 554 | 555 | if (device.getBluetoothClass() != null) { 556 | params.putInt("class", device.getBluetoothClass().getDeviceClass()); 557 | } 558 | 559 | return params; 560 | } 561 | 562 | /** 563 | * Pair device before kitkat 564 | * @param device Device 565 | */ 566 | private void pairDevice(BluetoothDevice device) { 567 | try { 568 | if (D) Log.d(TAG, "Start Pairing..."); 569 | Method m = device.getClass().getMethod("createBond", (Class[]) null); 570 | m.invoke(device, (Object[]) null); 571 | registerDevicePairingReceiver(device.getAddress(), BluetoothDevice.BOND_BONDED); 572 | if (D) Log.d(TAG, "Pairing finished."); 573 | } catch (Exception e) { 574 | Log.e(TAG, "Cannot pair device", e); 575 | if (mPairDevicePromise != null) { 576 | mPairDevicePromise.reject(e); 577 | mPairDevicePromise = null; 578 | } 579 | onError(e); 580 | } 581 | } 582 | 583 | /** 584 | * Unpair device 585 | * @param device Device 586 | */ 587 | private void unpairDevice(BluetoothDevice device) { 588 | try { 589 | if (D) Log.d(TAG, "Start Unpairing..."); 590 | Method m = device.getClass().getMethod("removeBond", (Class[]) null); 591 | m.invoke(device, (Object[]) null); 592 | registerDevicePairingReceiver(device.getAddress(), BluetoothDevice.BOND_NONE); 593 | } catch (Exception e) { 594 | Log.e(TAG, "Cannot unpair device", e); 595 | if (mPairDevicePromise != null) { 596 | mPairDevicePromise.reject(e); 597 | mPairDevicePromise = null; 598 | } 599 | onError(e); 600 | } 601 | } 602 | 603 | /** 604 | * Register receiver for device pairing 605 | * @param deviceId Id of device 606 | * @param requiredState State that we require 607 | */ 608 | private void registerDevicePairingReceiver(final String deviceId, final int requiredState) { 609 | IntentFilter intentFilter = new IntentFilter(); 610 | 611 | intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 612 | 613 | final BroadcastReceiver devicePairingReceiver = new BroadcastReceiver() { 614 | public void onReceive(Context context, Intent intent) { 615 | String action = intent.getAction(); 616 | 617 | if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { 618 | final int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR); 619 | final int prevState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR); 620 | 621 | if (state == BluetoothDevice.BOND_BONDED && prevState == BluetoothDevice.BOND_BONDING) { 622 | if (D) Log.d(TAG, "Device paired"); 623 | if (mPairDevicePromise != null) { 624 | mPairDevicePromise.resolve(true); 625 | mPairDevicePromise = null; 626 | } 627 | try { 628 | mReactContext.unregisterReceiver(this); 629 | } catch (Exception e) { 630 | Log.e(TAG, "Cannot unregister receiver", e); 631 | onError(e); 632 | } 633 | } else if (state == BluetoothDevice.BOND_NONE && prevState == BluetoothDevice.BOND_BONDED){ 634 | if (D) Log.d(TAG, "Device unpaired"); 635 | if (mPairDevicePromise != null) { 636 | mPairDevicePromise.resolve(true); 637 | mPairDevicePromise = null; 638 | } 639 | try { 640 | mReactContext.unregisterReceiver(this); 641 | } catch (Exception e) { 642 | Log.e(TAG, "Cannot unregister receiver", e); 643 | onError(e); 644 | } 645 | } 646 | 647 | } 648 | } 649 | }; 650 | 651 | mReactContext.registerReceiver(devicePairingReceiver, intentFilter); 652 | } 653 | 654 | /** 655 | * Register receiver for bluetooth device discovery 656 | */ 657 | private void registerBluetoothDeviceDiscoveryReceiver() { 658 | IntentFilter intentFilter = new IntentFilter(); 659 | 660 | intentFilter.addAction(BluetoothDevice.ACTION_FOUND); 661 | intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 662 | 663 | final BroadcastReceiver deviceDiscoveryReceiver = new BroadcastReceiver() { 664 | private WritableArray unpairedDevices = Arguments.createArray(); 665 | public void onReceive(Context context, Intent intent) { 666 | String action = intent.getAction(); 667 | if (D) Log.d(TAG, "onReceive called"); 668 | 669 | if (BluetoothDevice.ACTION_FOUND.equals(action)) { 670 | BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 671 | WritableMap d = deviceToWritableMap(device); 672 | unpairedDevices.pushMap(d); 673 | } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { 674 | if (D) Log.d(TAG, "Discovery finished"); 675 | if (mDeviceDiscoveryPromise != null) { 676 | mDeviceDiscoveryPromise.resolve(unpairedDevices); 677 | mDeviceDiscoveryPromise = null; 678 | } 679 | 680 | try { 681 | mReactContext.unregisterReceiver(this); 682 | } catch (Exception e) { 683 | Log.e(TAG, "Unable to unregister receiver", e); 684 | onError(e); 685 | } 686 | } 687 | } 688 | }; 689 | 690 | mReactContext.registerReceiver(deviceDiscoveryReceiver, intentFilter); 691 | } 692 | 693 | /** 694 | * Register receiver for bluetooth state change 695 | */ 696 | private void registerBluetoothStateReceiver() { 697 | IntentFilter intentFilter = new IntentFilter(); 698 | 699 | intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 700 | 701 | final BroadcastReceiver bluetoothStateReceiver = new BroadcastReceiver() { 702 | @Override 703 | public void onReceive(Context context, Intent intent) { 704 | final String action = intent.getAction(); 705 | 706 | if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { 707 | final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 708 | switch (state) { 709 | case BluetoothAdapter.STATE_OFF: 710 | if (D) Log.d(TAG, "Bluetooth was disabled"); 711 | sendEvent(BT_DISABLED, null); 712 | break; 713 | case BluetoothAdapter.STATE_ON: 714 | if (D) Log.d(TAG, "Bluetooth was enabled"); 715 | sendEvent(BT_ENABLED, null); 716 | break; 717 | } 718 | } 719 | } 720 | }; 721 | 722 | mReactContext.registerReceiver(bluetoothStateReceiver, intentFilter); 723 | } 724 | 725 | public static byte[] hexStringToBytes(String hexString) { 726 | hexString = hexString.toLowerCase(); 727 | String[] hexStrings = hexString.split(" "); 728 | byte[] bytes = new byte[hexStrings.length]; 729 | for (int i = 0; i < hexStrings.length; i++) { 730 | char[] hexChars = hexStrings[i].toCharArray(); 731 | bytes[i] = (byte) (charToByte(hexChars[0]) << 4 | charToByte(hexChars[1])); 732 | } 733 | return bytes; 734 | } 735 | private static byte charToByte(char c) { 736 | return (byte) "0123456789abcdef".indexOf(c); 737 | } 738 | } 739 | -------------------------------------------------------------------------------- /android/src/main/java/com/gm/RCTGMBluetooth/RCTGMBluetoothPackage.java: -------------------------------------------------------------------------------- 1 | package com.gm.RCTGMBluetooth; 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.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.uimanager.ViewManager; 11 | import com.facebook.react.bridge.JavaScriptModule; 12 | 13 | public class RCTGMBluetoothPackage implements ReactPackage { 14 | static final String TAG = "GMBluetooth"; 15 | 16 | @Override 17 | public List createNativeModules(ReactApplicationContext reactContext) { 18 | List modules = new ArrayList<>(); 19 | modules.add(new RCTGMBluetoothModule(reactContext)); 20 | return modules; 21 | } 22 | 23 | @Override 24 | public List> createJSModules() { 25 | return Collections.emptyList(); 26 | } 27 | 28 | @Override 29 | public List createViewManagers(ReactApplicationContext reactContext) { 30 | return Collections.emptyList(); 31 | } 32 | } -------------------------------------------------------------------------------- /android/src/main/java/com/gm/RCTGMBluetooth/RCTGMBluetoothService.java: -------------------------------------------------------------------------------- 1 | package com.gm.RCTGMBluetooth; 2 | 3 | import java.io.InputStream; 4 | import java.io.OutputStream; 5 | import java.util.UUID; 6 | 7 | import android.bluetooth.BluetoothAdapter; 8 | import android.bluetooth.BluetoothDevice; 9 | import android.bluetooth.BluetoothSocket; 10 | import android.util.Log; 11 | 12 | import static com.gm.RCTGMBluetooth.RCTGMBluetoothPackage.TAG; 13 | 14 | /** 15 | * This class does all the work for setting up and managing Bluetooth 16 | * connections with other devices. It has a thread that listens for 17 | * incoming connections, a thread for connecting with a device, and a 18 | * thread for performing data transmissions when connected. 19 | * 20 | * This code was based on the Android SDK BluetoothChat Sample 21 | * $ANDROID_SDK/samples/android-17/BluetoothChat 22 | */ 23 | class RCTGMBluetoothService { 24 | // Debugging 25 | private static final boolean D = true; 26 | 27 | // UUIDs 28 | private static final UUID UUID_SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); 29 | 30 | // Member fields 31 | private BluetoothAdapter mAdapter; 32 | private ConnectThread mConnectThread; 33 | private ConnectedThread mConnectedThread; 34 | private RCTGMBluetoothModule mModule; 35 | private String mState; 36 | 37 | // Constants that indicate the current connection state 38 | private static final String STATE_NONE = "none"; // we're doing nothing 39 | private static final String STATE_CONNECTING = "connecting"; // now initiating an outgoing connection 40 | private static final String STATE_CONNECTED = "connected"; // now connected to a remote device 41 | 42 | /** 43 | * Constructor. Prepares a new RCTGMBluetoothModule session. 44 | * @param module Module which handles service events 45 | */ 46 | RCTGMBluetoothService(RCTGMBluetoothModule module) { 47 | mAdapter = BluetoothAdapter.getDefaultAdapter(); 48 | mState = STATE_NONE; 49 | mModule = module; 50 | } 51 | 52 | /********************************************/ 53 | /** Methods available within whole package **/ 54 | /********************************************/ 55 | 56 | /** 57 | * Start the ConnectThread to initiate a connection to a remote device. 58 | * @param device The BluetoothDevice to connect 59 | */ 60 | synchronized void connect(BluetoothDevice device) { 61 | if (D) Log.d(TAG, "connect to: " + device); 62 | 63 | if (mState.equals(STATE_CONNECTING)) { 64 | cancelConnectThread(); // Cancel any thread attempting to make a connection 65 | } 66 | 67 | cancelConnectedThread(); // Cancel any thread currently running a connection 68 | 69 | // Start the thread to connect with the given device 70 | mConnectThread = new ConnectThread(device); 71 | mConnectThread.start(); 72 | setState(STATE_CONNECTING); 73 | } 74 | 75 | /** 76 | * Check whether service is connected to device 77 | * @return Is connected to device 78 | */ 79 | boolean isConnected () { 80 | return getState().equals(STATE_CONNECTED); 81 | } 82 | 83 | /** 84 | * Write to the ConnectedThread in an unsynchronized manner 85 | * @param out The bytes to write 86 | * @see ConnectedThread#write(byte[]) 87 | */ 88 | void write(byte[] out) { 89 | if (D) Log.d(TAG, "Write in service, state is " + STATE_CONNECTED); 90 | ConnectedThread r; // Create temporary object 91 | 92 | // Synchronize a copy of the ConnectedThread 93 | synchronized (this) { 94 | if (!isConnected()) return; 95 | r = mConnectedThread; 96 | } 97 | 98 | r.write(out); // Perform the write unsynchronized 99 | } 100 | 101 | /** 102 | * Stop all threads 103 | */ 104 | synchronized void stop() { 105 | if (D) Log.d(TAG, "stop"); 106 | 107 | cancelConnectThread(); 108 | cancelConnectedThread(); 109 | 110 | setState(STATE_NONE); 111 | } 112 | 113 | /*********************/ 114 | /** Private methods **/ 115 | /*********************/ 116 | 117 | /** 118 | * Return the current connection state. 119 | */ 120 | private synchronized String getState() { 121 | return mState; 122 | } 123 | 124 | /** 125 | * Set the current state of connection 126 | * @param state An integer defining the current connection state 127 | */ 128 | private synchronized void setState(String state) { 129 | if (D) Log.d(TAG, "setState() " + mState + " -> " + state); 130 | mState = state; 131 | } 132 | 133 | /** 134 | * Start the ConnectedThread to begin managing a Bluetooth connection 135 | * @param socket The BluetoothSocket on which the connection was made 136 | * @param device The BluetoothDevice that has been connected 137 | */ 138 | private synchronized void connectionSuccess(BluetoothSocket socket, BluetoothDevice device) { 139 | if (D) Log.d(TAG, "connected"); 140 | 141 | cancelConnectThread(); // Cancel any thread attempting to make a connection 142 | cancelConnectedThread(); // Cancel any thread currently running a connection 143 | 144 | // Start the thread to manage the connection and perform transmissions 145 | mConnectedThread = new ConnectedThread(socket); 146 | mConnectedThread.start(); 147 | 148 | mModule.onConnectionSuccess("Connected to " + device.getName()); 149 | setState(STATE_CONNECTED); 150 | } 151 | 152 | 153 | /** 154 | * Indicate that the connection attempt failed and notify the UI Activity. 155 | */ 156 | private void connectionFailed() { 157 | mModule.onConnectionFailed("Unable to connect to device"); // Send a failure message 158 | RCTGMBluetoothService.this.stop(); // Start the service over to restart listening mode 159 | } 160 | 161 | /** 162 | * Indicate that the connection was lost and notify the UI Activity. 163 | */ 164 | private void connectionLost() { 165 | mModule.onConnectionLost("Device connection was lost"); // Send a failure message 166 | RCTGMBluetoothService.this.stop(); // Start the service over to restart listening mode 167 | } 168 | 169 | /** 170 | * Cancel connect thread 171 | */ 172 | private void cancelConnectThread () { 173 | if (mConnectThread != null) { 174 | mConnectThread.cancel(); 175 | mConnectThread = null; 176 | } 177 | } 178 | 179 | /** 180 | * Cancel connected thread 181 | */ 182 | private void cancelConnectedThread () { 183 | if (mConnectedThread != null) { 184 | mConnectedThread.cancel(); 185 | mConnectedThread = null; 186 | } 187 | } 188 | 189 | /** 190 | * This thread runs while attempting to make an outgoing connection 191 | * with a device. It runs straight through; the connection either 192 | * succeeds or fails. 193 | */ 194 | private class ConnectThread extends Thread { 195 | private BluetoothSocket mmSocket; 196 | private final BluetoothDevice mmDevice; 197 | 198 | ConnectThread(BluetoothDevice device) { 199 | mmDevice = device; 200 | BluetoothSocket tmp = null; 201 | 202 | // Get a BluetoothSocket for a connection with the given BluetoothDevice 203 | try { 204 | tmp = device.createRfcommSocketToServiceRecord(UUID_SPP); 205 | } catch (Exception e) { 206 | mModule.onError(e); 207 | Log.e(TAG, "Socket create() failed", e); 208 | } 209 | mmSocket = tmp; 210 | } 211 | 212 | public void run() { 213 | if (D) Log.d(TAG, "BEGIN mConnectThread"); 214 | setName("ConnectThread"); 215 | 216 | // Always cancel discovery because it will slow down a connection 217 | mAdapter.cancelDiscovery(); 218 | 219 | // Make a connection to the BluetoothSocket 220 | try { 221 | // This is a blocking call and will only return on a successful connection or an exception 222 | if (D) Log.d(TAG,"Connecting to socket..."); 223 | mmSocket.connect(); 224 | if (D) Log.d(TAG,"Connected"); 225 | } catch (Exception e) { 226 | Log.e(TAG, e.toString()); 227 | mModule.onError(e); 228 | 229 | // Some 4.1 devices have problems, try an alternative way to connect 230 | try { 231 | Log.i(TAG,"Trying fallback..."); 232 | mmSocket = (BluetoothSocket) mmDevice.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(mmDevice,1); 233 | mmSocket.connect(); 234 | Log.i(TAG,"Connected"); 235 | } catch (Exception e2) { 236 | Log.e(TAG, "Couldn't establish a Bluetooth connection."); 237 | mModule.onError(e2); 238 | try { 239 | mmSocket.close(); 240 | } catch (Exception e3) { 241 | Log.e(TAG, "unable to close() socket during connection failure", e3); 242 | mModule.onError(e3); 243 | } 244 | connectionFailed(); 245 | return; 246 | } 247 | } 248 | 249 | // Reset the ConnectThread because we're done 250 | synchronized (RCTGMBluetoothService.this) { 251 | mConnectThread = null; 252 | } 253 | 254 | connectionSuccess(mmSocket, mmDevice); // Start the connected thread 255 | 256 | } 257 | 258 | void cancel() { 259 | try { 260 | mmSocket.close(); 261 | } catch (Exception e) { 262 | Log.e(TAG, "close() of connect socket failed", e); 263 | mModule.onError(e); 264 | } 265 | } 266 | } 267 | 268 | /** 269 | * This thread runs during a connection with a remote device. 270 | * It handles all incoming and outgoing transmissions. 271 | */ 272 | private class ConnectedThread extends Thread { 273 | private final BluetoothSocket mmSocket; 274 | private final InputStream mmInStream; 275 | private final OutputStream mmOutStream; 276 | 277 | ConnectedThread(BluetoothSocket socket) { 278 | if (D) Log.d(TAG, "create ConnectedThread"); 279 | mmSocket = socket; 280 | InputStream tmpIn = null; 281 | OutputStream tmpOut = null; 282 | 283 | // Get the BluetoothSocket input and output streams 284 | try { 285 | tmpIn = socket.getInputStream(); 286 | tmpOut = socket.getOutputStream(); 287 | } catch (Exception e) { 288 | Log.e(TAG, "temp sockets not created", e); 289 | mModule.onError(e); 290 | } 291 | 292 | mmInStream = tmpIn; 293 | mmOutStream = tmpOut; 294 | } 295 | 296 | public void run() { 297 | Log.i(TAG, "BEGIN mConnectedThread"); 298 | byte[] buffer = new byte[1024]; 299 | int bytes; 300 | 301 | // Keep listening to the InputStream while connected 302 | while (true) { 303 | try { 304 | bytes = mmInStream.read(buffer); // Read from the InputStream 305 | String data = new String(buffer, 0, bytes, "ISO-8859-1"); 306 | 307 | mModule.onData(data); // Send the new data String to the UI Activity 308 | } catch (Exception e) { 309 | Log.e(TAG, "disconnected", e); 310 | mModule.onError(e); 311 | connectionLost(); 312 | RCTGMBluetoothService.this.stop(); // Start the service over to restart listening mode 313 | break; 314 | } 315 | } 316 | } 317 | 318 | /** 319 | * Write to the connected OutStream. 320 | * @param buffer The bytes to write 321 | */ 322 | void write(byte[] buffer) { 323 | try { 324 | String str = new String(buffer, "UTF-8"); 325 | if (D) Log.d(TAG, "Write in thread " + str); 326 | mmOutStream.write(buffer); 327 | } catch (Exception e) { 328 | Log.e(TAG, "Exception during write", e); 329 | mModule.onError(e); 330 | } 331 | } 332 | 333 | void cancel() { 334 | try { 335 | mmSocket.close(); 336 | } catch (Exception e) { 337 | Log.e(TAG, "close() of connect socket failed", e); 338 | } 339 | } 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /doc/GprinterSDKForAndroid_V2.2.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmfe/react-native-gm-bluetooth/580aff2111b0f033e339ee38cded793091ae4460/doc/GprinterSDKForAndroid_V2.2.5.zip -------------------------------------------------------------------------------- /doc/PrinterSDKforiOS.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmfe/react-native-gm-bluetooth/580aff2111b0f033e339ee38cded793091ae4460/doc/PrinterSDKforiOS.zip -------------------------------------------------------------------------------- /doc/佳博标签打印机编程手册 v1.0.3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmfe/react-native-gm-bluetooth/580aff2111b0f033e339ee38cded793091ae4460/doc/佳博标签打印机编程手册 v1.0.3.pdf -------------------------------------------------------------------------------- /doc/佳博热敏票据打印机编程手册 v1.0.1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmfe/react-native-gm-bluetooth/580aff2111b0f033e339ee38cded793091ae4460/doc/佳博热敏票据打印机编程手册 v1.0.1.pdf -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const ReactNative = require('react-native'); 2 | const {Buffer} = require('buffer'); 3 | const {NativeModules, DeviceEventEmitter, Platform} = ReactNative; 4 | const TSC = require('./js/tsc'); 5 | const ESC = require('./js/esc'); 6 | const Util = require('./js/util'); 7 | 8 | let GMBluetooth = NativeModules.GMBluetooth; 9 | if(Platform.OS === 'ios'){ 10 | GMBluetooth = NativeModules.BluetoothSerial; 11 | } 12 | 13 | /** 14 | * Listen for available events 15 | * @param {String} eventName Name of event one of connectionSuccess, connectionLost, data, rawData 16 | * @param {Function} handler Event handler 17 | */ 18 | GMBluetooth.on = (eventName, handler) => { 19 | DeviceEventEmitter.addListener(eventName, handler) 20 | }; 21 | 22 | /** 23 | * Stop listening for event 24 | * @param {String} eventName Name of event one of connectionSuccess, connectionLost, data, rawData 25 | * @param {Function} handler Event handler 26 | */ 27 | GMBluetooth.removeListener = (eventName, handler) => { 28 | DeviceEventEmitter.removeListener(eventName, handler) 29 | }; 30 | 31 | /** 32 | * Write data to device, you can pass string or buffer, 33 | * We must convert to base64 in RN there is no way to pass buffer directly 34 | * @param {Buffer|String} data 35 | * @return {Promise} 36 | */ 37 | GMBluetooth.write = (data) => { 38 | if (typeof data === 'string') { 39 | data = new Buffer(data) 40 | } 41 | return GMBluetooth.writeToDevice(data.toString('base64')) 42 | }; 43 | 44 | GMBluetooth.TSC = TSC; 45 | GMBluetooth.ESC = ESC; 46 | GMBluetooth.Util = Util; 47 | 48 | ESC._setBT(GMBluetooth); 49 | TSC._setBT(GMBluetooth); 50 | 51 | module.exports = GMBluetooth; 52 | -------------------------------------------------------------------------------- /ios/RCTBluetoothSerial.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 69CA06071CC43C2900AF6479 /* RCTBluetoothSerial.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 69CA06061CC43C2900AF6479 /* RCTBluetoothSerial.h */; }; 11 | 69CA06091CC43C2900AF6479 /* RCTBluetoothSerial.m in Sources */ = {isa = PBXBuildFile; fileRef = 69CA06081CC43C2900AF6479 /* RCTBluetoothSerial.m */; }; 12 | 69CA06161CC49D5E00AF6479 /* BLE.m in Sources */ = {isa = PBXBuildFile; fileRef = 69CA06101CC49D5E00AF6479 /* BLE.m */; }; 13 | 69CA06171CC49D5E00AF6479 /* CBPeripheral+BTSExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 69CA06131CC49D5E00AF6479 /* CBPeripheral+BTSExtensions.m */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXCopyFilesBuildPhase section */ 17 | 69CA06011CC43C2900AF6479 /* CopyFiles */ = { 18 | isa = PBXCopyFilesBuildPhase; 19 | buildActionMask = 2147483647; 20 | dstPath = "include/$(PRODUCT_NAME)"; 21 | dstSubfolderSpec = 16; 22 | files = ( 23 | 69CA06071CC43C2900AF6479 /* RCTBluetoothSerial.h in CopyFiles */, 24 | ); 25 | runOnlyForDeploymentPostprocessing = 0; 26 | }; 27 | /* End PBXCopyFilesBuildPhase section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 69CA06031CC43C2900AF6479 /* libRCTBluetoothSerial.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBluetoothSerial.a; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 69CA06061CC43C2900AF6479 /* RCTBluetoothSerial.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBluetoothSerial.h; sourceTree = ""; }; 32 | 69CA06081CC43C2900AF6479 /* RCTBluetoothSerial.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTBluetoothSerial.m; sourceTree = ""; }; 33 | 69CA060F1CC49D5E00AF6479 /* BLE.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLE.h; sourceTree = ""; }; 34 | 69CA06101CC49D5E00AF6479 /* BLE.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLE.m; sourceTree = ""; }; 35 | 69CA06111CC49D5E00AF6479 /* BLEDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLEDefines.h; sourceTree = ""; }; 36 | 69CA06121CC49D5E00AF6479 /* CBPeripheral+BTSExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CBPeripheral+BTSExtensions.h"; sourceTree = ""; }; 37 | 69CA06131CC49D5E00AF6479 /* CBPeripheral+BTSExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CBPeripheral+BTSExtensions.m"; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | 69CA06001CC43C2900AF6479 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | 69CA05FA1CC43C2900AF6479 = { 52 | isa = PBXGroup; 53 | children = ( 54 | 69CA06051CC43C2900AF6479 /* RCTBluetoothSerial */, 55 | 69CA06041CC43C2900AF6479 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | 69CA06041CC43C2900AF6479 /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 69CA06031CC43C2900AF6479 /* libRCTBluetoothSerial.a */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | 69CA06051CC43C2900AF6479 /* RCTBluetoothSerial */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 69CA060F1CC49D5E00AF6479 /* BLE.h */, 71 | 69CA06101CC49D5E00AF6479 /* BLE.m */, 72 | 69CA06111CC49D5E00AF6479 /* BLEDefines.h */, 73 | 69CA06121CC49D5E00AF6479 /* CBPeripheral+BTSExtensions.h */, 74 | 69CA06131CC49D5E00AF6479 /* CBPeripheral+BTSExtensions.m */, 75 | 69CA06061CC43C2900AF6479 /* RCTBluetoothSerial.h */, 76 | 69CA06081CC43C2900AF6479 /* RCTBluetoothSerial.m */, 77 | ); 78 | path = RCTBluetoothSerial; 79 | sourceTree = ""; 80 | }; 81 | /* End PBXGroup section */ 82 | 83 | /* Begin PBXNativeTarget section */ 84 | 69CA06021CC43C2900AF6479 /* RCTBluetoothSerial */ = { 85 | isa = PBXNativeTarget; 86 | buildConfigurationList = 69CA060C1CC43C2900AF6479 /* Build configuration list for PBXNativeTarget "RCTBluetoothSerial" */; 87 | buildPhases = ( 88 | 69CA05FF1CC43C2900AF6479 /* Sources */, 89 | 69CA06001CC43C2900AF6479 /* Frameworks */, 90 | 69CA06011CC43C2900AF6479 /* CopyFiles */, 91 | ); 92 | buildRules = ( 93 | ); 94 | dependencies = ( 95 | ); 96 | name = RCTBluetoothSerial; 97 | productName = RCTBluetoothSerial; 98 | productReference = 69CA06031CC43C2900AF6479 /* libRCTBluetoothSerial.a */; 99 | productType = "com.apple.product-type.library.static"; 100 | }; 101 | /* End PBXNativeTarget section */ 102 | 103 | /* Begin PBXProject section */ 104 | 69CA05FB1CC43C2900AF6479 /* Project object */ = { 105 | isa = PBXProject; 106 | attributes = { 107 | LastUpgradeCheck = 0730; 108 | ORGANIZATIONNAME = "Jakub Martyčák"; 109 | TargetAttributes = { 110 | 69CA06021CC43C2900AF6479 = { 111 | CreatedOnToolsVersion = 7.3; 112 | }; 113 | }; 114 | }; 115 | buildConfigurationList = 69CA05FE1CC43C2900AF6479 /* Build configuration list for PBXProject "RCTBluetoothSerial" */; 116 | compatibilityVersion = "Xcode 3.2"; 117 | developmentRegion = English; 118 | hasScannedForEncodings = 0; 119 | knownRegions = ( 120 | en, 121 | ); 122 | mainGroup = 69CA05FA1CC43C2900AF6479; 123 | productRefGroup = 69CA06041CC43C2900AF6479 /* Products */; 124 | projectDirPath = ""; 125 | projectRoot = ""; 126 | targets = ( 127 | 69CA06021CC43C2900AF6479 /* RCTBluetoothSerial */, 128 | ); 129 | }; 130 | /* End PBXProject section */ 131 | 132 | /* Begin PBXSourcesBuildPhase section */ 133 | 69CA05FF1CC43C2900AF6479 /* Sources */ = { 134 | isa = PBXSourcesBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | 69CA06091CC43C2900AF6479 /* RCTBluetoothSerial.m in Sources */, 138 | 69CA06161CC49D5E00AF6479 /* BLE.m in Sources */, 139 | 69CA06171CC49D5E00AF6479 /* CBPeripheral+BTSExtensions.m in Sources */, 140 | ); 141 | runOnlyForDeploymentPostprocessing = 0; 142 | }; 143 | /* End PBXSourcesBuildPhase section */ 144 | 145 | /* Begin XCBuildConfiguration section */ 146 | 69CA060A1CC43C2900AF6479 /* Debug */ = { 147 | isa = XCBuildConfiguration; 148 | buildSettings = { 149 | ALWAYS_SEARCH_USER_PATHS = YES; 150 | CLANG_ANALYZER_NONNULL = YES; 151 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 152 | CLANG_CXX_LIBRARY = "libc++"; 153 | CLANG_ENABLE_MODULES = YES; 154 | CLANG_ENABLE_OBJC_ARC = YES; 155 | CLANG_WARN_BOOL_CONVERSION = YES; 156 | CLANG_WARN_CONSTANT_CONVERSION = YES; 157 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 158 | CLANG_WARN_EMPTY_BODY = YES; 159 | CLANG_WARN_ENUM_CONVERSION = YES; 160 | CLANG_WARN_INT_CONVERSION = YES; 161 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 162 | CLANG_WARN_UNREACHABLE_CODE = YES; 163 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 164 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 165 | COPY_PHASE_STRIP = NO; 166 | DEBUG_INFORMATION_FORMAT = dwarf; 167 | ENABLE_STRICT_OBJC_MSGSEND = YES; 168 | ENABLE_TESTABILITY = YES; 169 | GCC_C_LANGUAGE_STANDARD = gnu99; 170 | GCC_DYNAMIC_NO_PIC = NO; 171 | GCC_NO_COMMON_BLOCKS = YES; 172 | GCC_OPTIMIZATION_LEVEL = 0; 173 | GCC_PREPROCESSOR_DEFINITIONS = ( 174 | "DEBUG=1", 175 | "$(inherited)", 176 | ); 177 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 178 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 179 | GCC_WARN_UNDECLARED_SELECTOR = YES; 180 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 181 | GCC_WARN_UNUSED_FUNCTION = YES; 182 | GCC_WARN_UNUSED_VARIABLE = YES; 183 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 184 | MTL_ENABLE_DEBUG_INFO = YES; 185 | ONLY_ACTIVE_ARCH = YES; 186 | SDKROOT = iphoneos; 187 | }; 188 | name = Debug; 189 | }; 190 | 69CA060B1CC43C2900AF6479 /* Release */ = { 191 | isa = XCBuildConfiguration; 192 | buildSettings = { 193 | ALWAYS_SEARCH_USER_PATHS = YES; 194 | CLANG_ANALYZER_NONNULL = YES; 195 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 196 | CLANG_CXX_LIBRARY = "libc++"; 197 | CLANG_ENABLE_MODULES = YES; 198 | CLANG_ENABLE_OBJC_ARC = YES; 199 | CLANG_WARN_BOOL_CONVERSION = YES; 200 | CLANG_WARN_CONSTANT_CONVERSION = YES; 201 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 202 | CLANG_WARN_EMPTY_BODY = YES; 203 | CLANG_WARN_ENUM_CONVERSION = YES; 204 | CLANG_WARN_INT_CONVERSION = YES; 205 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 206 | CLANG_WARN_UNREACHABLE_CODE = YES; 207 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 208 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 209 | COPY_PHASE_STRIP = NO; 210 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 211 | ENABLE_NS_ASSERTIONS = NO; 212 | ENABLE_STRICT_OBJC_MSGSEND = YES; 213 | GCC_C_LANGUAGE_STANDARD = gnu99; 214 | GCC_NO_COMMON_BLOCKS = YES; 215 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 216 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 217 | GCC_WARN_UNDECLARED_SELECTOR = YES; 218 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 219 | GCC_WARN_UNUSED_FUNCTION = YES; 220 | GCC_WARN_UNUSED_VARIABLE = YES; 221 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 222 | MTL_ENABLE_DEBUG_INFO = NO; 223 | SDKROOT = iphoneos; 224 | VALIDATE_PRODUCT = YES; 225 | }; 226 | name = Release; 227 | }; 228 | 69CA060D1CC43C2900AF6479 /* Debug */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | HEADER_SEARCH_PATHS = "$(SRCROOT)/../../react-native/React/**"; 232 | OTHER_LDFLAGS = "-ObjC"; 233 | PRODUCT_NAME = "$(TARGET_NAME)"; 234 | SKIP_INSTALL = YES; 235 | }; 236 | name = Debug; 237 | }; 238 | 69CA060E1CC43C2900AF6479 /* Release */ = { 239 | isa = XCBuildConfiguration; 240 | buildSettings = { 241 | HEADER_SEARCH_PATHS = "$(SRCROOT)/../../react-native/React/**"; 242 | OTHER_LDFLAGS = "-ObjC"; 243 | PRODUCT_NAME = "$(TARGET_NAME)"; 244 | SKIP_INSTALL = YES; 245 | }; 246 | name = Release; 247 | }; 248 | /* End XCBuildConfiguration section */ 249 | 250 | /* Begin XCConfigurationList section */ 251 | 69CA05FE1CC43C2900AF6479 /* Build configuration list for PBXProject "RCTBluetoothSerial" */ = { 252 | isa = XCConfigurationList; 253 | buildConfigurations = ( 254 | 69CA060A1CC43C2900AF6479 /* Debug */, 255 | 69CA060B1CC43C2900AF6479 /* Release */, 256 | ); 257 | defaultConfigurationIsVisible = 0; 258 | defaultConfigurationName = Release; 259 | }; 260 | 69CA060C1CC43C2900AF6479 /* Build configuration list for PBXNativeTarget "RCTBluetoothSerial" */ = { 261 | isa = XCConfigurationList; 262 | buildConfigurations = ( 263 | 69CA060D1CC43C2900AF6479 /* Debug */, 264 | 69CA060E1CC43C2900AF6479 /* Release */, 265 | ); 266 | defaultConfigurationIsVisible = 0; 267 | defaultConfigurationName = Release; 268 | }; 269 | /* End XCConfigurationList section */ 270 | }; 271 | rootObject = 69CA05FB1CC43C2900AF6479 /* Project object */; 272 | } 273 | -------------------------------------------------------------------------------- /ios/RCTBluetoothSerial/BLE.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | Copyright (c) 2013 RedBearLab 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | 12 | */ 13 | 14 | #import 15 | #if TARGET_OS_IPHONE 16 | #import 17 | #else 18 | #import 19 | #endif 20 | 21 | #import "CBPeripheral+BTSExtensions.h" 22 | 23 | @protocol BLEDelegate 24 | @optional 25 | -(void) bleDidConnect; 26 | -(void) bleDidDisconnect; 27 | -(void) bleDidUpdateRSSI:(NSNumber *) rssi; 28 | -(void) bleDidReceiveData:(unsigned char *) data length:(int) length; 29 | -(void) bleDidChangedState:(bool) isEnabled; 30 | 31 | @required 32 | @end 33 | 34 | @interface BLE : NSObject { 35 | 36 | } 37 | 38 | @property (nonatomic,assign) id delegate; 39 | @property (strong, nonatomic) NSMutableArray *peripherals; 40 | @property (strong, nonatomic) CBCentralManager *CM; 41 | @property (strong, nonatomic) CBPeripheral *activePeripheral; 42 | 43 | -(void) enableReadNotification:(CBPeripheral *)p; 44 | -(void) read; 45 | -(void) writeValue:(CBUUID *)serviceUUID characteristicUUID:(CBUUID *)characteristicUUID p:(CBPeripheral *)p data:(NSData *)data; 46 | 47 | -(BOOL) isConnected; 48 | -(void) write:(NSData *)d; 49 | -(void) readRSSI; 50 | 51 | -(void) controlSetup; 52 | -(int) findBLEPeripherals:(int) timeout; 53 | -(void) connectPeripheral:(CBPeripheral *)peripheral; 54 | 55 | -(UInt16) swap:(UInt16) s; 56 | -(const char *) centralManagerStateToString:(int)state; 57 | -(void) scanTimer:(NSTimer *)timer; 58 | -(void) printKnownPeripherals; 59 | -(void) printPeripheralInfo:(CBPeripheral*)peripheral; 60 | 61 | -(void) getAllServicesFromPeripheral:(CBPeripheral *)p; 62 | -(void) getAllCharacteristicsFromPeripheral:(CBPeripheral *)p; 63 | -(CBService *) findServiceFromUUID:(CBUUID *)UUID p:(CBPeripheral *)p; 64 | -(CBCharacteristic *) findCharacteristicFromUUID:(CBUUID *)UUID service:(CBService*)service; 65 | 66 | //-(NSString *) NSUUIDToString:(NSUUID *) UUID; 67 | -(NSString *) CBUUIDToString:(CBUUID *) UUID; 68 | 69 | -(int) compareCBUUID:(CBUUID *) UUID1 UUID2:(CBUUID *)UUID2; 70 | -(int) compareCBUUIDToInt:(CBUUID *) UUID1 UUID2:(UInt16)UUID2; 71 | -(UInt16) CBUUIDToInt:(CBUUID *) UUID; 72 | -(BOOL) UUIDSAreEqual:(NSUUID *)UUID1 UUID2:(NSUUID *)UUID2; 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /ios/RCTBluetoothSerial/BLE.m: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | Copyright (c) 2013 RedBearLab 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | 12 | */ 13 | 14 | #import "BLE.h" 15 | #import "BLEDefines.h" 16 | 17 | @implementation BLE 18 | 19 | @synthesize delegate; 20 | @synthesize CM; 21 | @synthesize peripherals; 22 | @synthesize activePeripheral; 23 | 24 | static bool isConnected = false; 25 | static int rssi = 0; 26 | static const int MAX_BUF_LENGTH = 100; 27 | 28 | // TODO should have a configurable list of services 29 | CBUUID *redBearLabsServiceUUID; 30 | CBUUID *adafruitServiceUUID; 31 | CBUUID *lairdServiceUUID; 32 | CBUUID *blueGigaServiceUUID; 33 | CBUUID *rongtaSerivceUUID; 34 | CBUUID *posnetSerivceUUID; 35 | CBUUID *serialServiceUUID; 36 | CBUUID *readCharacteristicUUID; 37 | CBUUID *writeCharacteristicUUID; 38 | 39 | -(void) readRSSI 40 | { 41 | [activePeripheral readRSSI]; 42 | } 43 | 44 | -(BOOL) isConnected 45 | { 46 | return isConnected; 47 | } 48 | 49 | -(void) read 50 | { 51 | // CBUUID *uuid_service = [CBUUID UUIDWithString:@RBL_SERVICE_UUID]; 52 | // CBUUID *uuid_char = [CBUUID UUIDWithString:@RBL_CHAR_TX_UUID]; 53 | 54 | // [self readValue:uuid_service characteristicUUID:uuid_char p:activePeripheral]; 55 | [self readValue:serialServiceUUID characteristicUUID:readCharacteristicUUID p:activePeripheral]; 56 | 57 | } 58 | 59 | -(void) write:(NSData *)d 60 | { 61 | NSLog(@"%@", @"write in ble.m"); 62 | NSInteger data_len = d.length; 63 | NSData *buffer; 64 | int i = 0; 65 | 66 | for(; i < data_len; i+=MAX_BUF_LENGTH) 67 | { 68 | NSInteger remainLength = data_len-i; 69 | NSInteger bufLen = ((remainLength)>MAX_BUF_LENGTH) ? MAX_BUF_LENGTH:remainLength; 70 | buffer = [d subdataWithRange:NSMakeRange(i, bufLen)]; 71 | 72 | NSLog(@"Buffer data %i %i %@", remainLength, i, [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding]); 73 | 74 | [self writeValue:serialServiceUUID characteristicUUID:writeCharacteristicUUID p:activePeripheral data:buffer]; 75 | } 76 | } 77 | 78 | -(void) enableReadNotification:(CBPeripheral *)p 79 | { 80 | // CBUUID *uuid_service = [CBUUID UUIDWithString:@RBL_SERVICE_UUID]; 81 | // CBUUID *uuid_char = [CBUUID UUIDWithString:@RBL_CHAR_TX_UUID]; 82 | // 83 | // [self notification:uuid_service characteristicUUID:uuid_char p:p on:YES]; 84 | [self notification:serialServiceUUID characteristicUUID:readCharacteristicUUID p:p on:YES]; 85 | 86 | } 87 | 88 | -(void) notification:(CBUUID *)serviceUUID characteristicUUID:(CBUUID *)characteristicUUID p:(CBPeripheral *)p on:(BOOL)on 89 | { 90 | CBService *service = [self findServiceFromUUID:serviceUUID p:p]; 91 | 92 | if (!service) 93 | { 94 | NSLog(@"Could not find service with UUID %@ on peripheral with UUID %@", 95 | [self CBUUIDToString:serviceUUID], 96 | p.identifier.UUIDString); 97 | 98 | return; 99 | } 100 | 101 | CBCharacteristic *characteristic = [self findCharacteristicFromUUID:characteristicUUID service:service]; 102 | 103 | if (!characteristic) 104 | { 105 | NSLog(@"Could not find characteristic with UUID %@ on service with UUID %@ on peripheral with UUID %@", 106 | [self CBUUIDToString:characteristicUUID], 107 | [self CBUUIDToString:serviceUUID], 108 | p.identifier.UUIDString); 109 | 110 | return; 111 | } 112 | 113 | [p setNotifyValue:on forCharacteristic:characteristic]; 114 | } 115 | 116 | -(UInt16) frameworkVersion 117 | { 118 | return RBL_BLE_FRAMEWORK_VER; 119 | } 120 | 121 | -(NSString *) CBUUIDToString:(CBUUID *) cbuuid; 122 | { 123 | NSData *data = cbuuid.data; 124 | 125 | if ([data length] == 2) 126 | { 127 | const unsigned char *tokenBytes = [data bytes]; 128 | return [NSString stringWithFormat:@"%02x%02x", tokenBytes[0], tokenBytes[1]]; 129 | } 130 | else if ([data length] == 16) 131 | { 132 | NSUUID* nsuuid = [[NSUUID alloc] initWithUUIDBytes:[data bytes]]; 133 | return [nsuuid UUIDString]; 134 | } 135 | 136 | return [cbuuid description]; 137 | } 138 | 139 | -(void) readValue: (CBUUID *)serviceUUID characteristicUUID:(CBUUID *)characteristicUUID p:(CBPeripheral *)p 140 | { 141 | CBService *service = [self findServiceFromUUID:serviceUUID p:p]; 142 | 143 | if (!service) 144 | { 145 | NSLog(@"Could not find service with UUID %@ on peripheral with UUID %@", 146 | [self CBUUIDToString:serviceUUID], 147 | p.identifier.UUIDString); 148 | 149 | return; 150 | } 151 | 152 | CBCharacteristic *characteristic = [self findCharacteristicFromUUID:characteristicUUID service:service]; 153 | 154 | if (!characteristic) 155 | { 156 | NSLog(@"Could not find characteristic with UUID %@ on service with UUID %@ on peripheral with UUID %@", 157 | [self CBUUIDToString:characteristicUUID], 158 | [self CBUUIDToString:serviceUUID], 159 | p.identifier.UUIDString); 160 | 161 | return; 162 | } 163 | 164 | [p readValueForCharacteristic:characteristic]; 165 | } 166 | 167 | -(void) writeValue:(CBUUID *)serviceUUID characteristicUUID:(CBUUID *)characteristicUUID p:(CBPeripheral *)p data:(NSData *)data 168 | { 169 | CBService *service = [self findServiceFromUUID:serviceUUID p:p]; 170 | 171 | if (!service) 172 | { 173 | NSLog(@"Could not find service with UUID %@ on peripheral with UUID %@", 174 | [self CBUUIDToString:serviceUUID], 175 | p.identifier.UUIDString); 176 | 177 | return; 178 | } 179 | 180 | CBCharacteristic *characteristic = [self findCharacteristicFromUUID:characteristicUUID service:service]; 181 | 182 | if (!characteristic) 183 | { 184 | NSLog(@"Could not find characteristic with UUID %@ on service with UUID %@ on peripheral with UUID %@", 185 | [self CBUUIDToString:characteristicUUID], 186 | [self CBUUIDToString:serviceUUID], 187 | p.identifier.UUIDString); 188 | 189 | return; 190 | } 191 | 192 | NSLog(@"%@", @"writeValue in ble.m"); 193 | 194 | NSLog(@"Buffer data %i", data.length); 195 | if ((characteristic.properties & CBCharacteristicPropertyWrite) == CBCharacteristicPropertyWrite) { 196 | [p writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]; 197 | } 198 | else if ((characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) == CBCharacteristicPropertyWriteWithoutResponse) { 199 | [p writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse]; 200 | } 201 | } 202 | 203 | -(UInt16) swap:(UInt16)s 204 | { 205 | UInt16 temp = s << 8; 206 | temp |= (s >> 8); 207 | return temp; 208 | } 209 | 210 | - (void) controlSetup 211 | { 212 | self.CM = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; 213 | } 214 | 215 | - (int) findBLEPeripherals:(int) timeout 216 | { 217 | if (self.CM.state != CBCentralManagerStatePoweredOn) 218 | { 219 | NSLog(@"CoreBluetooth not correctly initialized !"); 220 | NSLog(@"State = %ld (%s)\r\n", (long)self.CM.state, [self centralManagerStateToString:self.CM.state]); 221 | return -1; 222 | } 223 | 224 | [NSTimer scheduledTimerWithTimeInterval:(float)timeout target:self selector:@selector(scanTimer:) userInfo:nil repeats:NO]; 225 | 226 | #if TARGET_OS_IPHONE 227 | redBearLabsServiceUUID = [CBUUID UUIDWithString:@RBL_SERVICE_UUID]; 228 | adafruitServiceUUID = [CBUUID UUIDWithString:@ADAFRUIT_SERVICE_UUID]; 229 | lairdServiceUUID = [CBUUID UUIDWithString:@LAIRD_SERVICE_UUID]; 230 | blueGigaServiceUUID = [CBUUID UUIDWithString:@BLUEGIGA_SERVICE_UUID]; 231 | rongtaSerivceUUID = [CBUUID UUIDWithString:@RONGTA_SERVICE_UUID]; 232 | posnetSerivceUUID = [CBUUID UUIDWithString:@POSNET_SERVICE_UUID]; 233 | 234 | NSArray *services = @[redBearLabsServiceUUID, adafruitServiceUUID, lairdServiceUUID, blueGigaServiceUUID, rongtaSerivceUUID, posnetSerivceUUID]; 235 | [self.CM scanForPeripheralsWithServices:services options: nil]; 236 | #else 237 | [self.CM scanForPeripheralsWithServices:nil options:nil]; // Start scanning 238 | #endif 239 | 240 | NSLog(@"scanForPeripheralsWithServices"); 241 | 242 | return 0; // Started scanning OK ! 243 | } 244 | 245 | - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error; 246 | { 247 | done = false; 248 | 249 | [[self delegate] bleDidDisconnect]; 250 | 251 | isConnected = false; 252 | } 253 | 254 | - (void) connectPeripheral:(CBPeripheral *)peripheral 255 | { 256 | NSLog(@"Connecting to peripheral with UUID : %@", peripheral.identifier.UUIDString); 257 | 258 | self.activePeripheral = peripheral; 259 | self.activePeripheral.delegate = self; 260 | [self.CM connectPeripheral:self.activePeripheral 261 | options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]]; 262 | } 263 | 264 | - (const char *) centralManagerStateToString: (int)state 265 | { 266 | switch(state) 267 | { 268 | case CBCentralManagerStateUnknown: 269 | return "State unknown (CBCentralManagerStateUnknown)"; 270 | case CBCentralManagerStateResetting: 271 | return "State resetting (CBCentralManagerStateUnknown)"; 272 | case CBCentralManagerStateUnsupported: 273 | return "State BLE unsupported (CBCentralManagerStateResetting)"; 274 | case CBCentralManagerStateUnauthorized: 275 | return "State unauthorized (CBCentralManagerStateUnauthorized)"; 276 | case CBCentralManagerStatePoweredOff: 277 | return "State BLE powered off (CBCentralManagerStatePoweredOff)"; 278 | case CBCentralManagerStatePoweredOn: 279 | return "State powered up and ready (CBCentralManagerStatePoweredOn)"; 280 | default: 281 | return "State unknown"; 282 | } 283 | 284 | return "Unknown state"; 285 | } 286 | 287 | - (void) scanTimer:(NSTimer *)timer 288 | { 289 | [self.CM stopScan]; 290 | NSLog(@"Stopped Scanning"); 291 | NSLog(@"Known peripherals : %lu", (unsigned long)[self.peripherals count]); 292 | [self printKnownPeripherals]; 293 | } 294 | 295 | - (void) printKnownPeripherals 296 | { 297 | NSLog(@"List of currently known peripherals :"); 298 | 299 | for (int i = 0; i < self.peripherals.count; i++) 300 | { 301 | CBPeripheral *p = [self.peripherals objectAtIndex:i]; 302 | 303 | if (p.identifier != NULL) 304 | NSLog(@"%d | %@", i, p.identifier.UUIDString); 305 | else 306 | NSLog(@"%d | NULL", i); 307 | 308 | [self printPeripheralInfo:p]; 309 | } 310 | } 311 | 312 | - (void) printPeripheralInfo:(CBPeripheral*)peripheral 313 | { 314 | NSLog(@"------------------------------------"); 315 | NSLog(@"Peripheral Info :"); 316 | 317 | if (peripheral.identifier != NULL) 318 | NSLog(@"UUID : %@", peripheral.identifier.UUIDString); 319 | else 320 | NSLog(@"UUID : NULL"); 321 | 322 | NSLog(@"Name : %@", peripheral.name); 323 | NSLog(@"-------------------------------------"); 324 | } 325 | 326 | - (BOOL) UUIDSAreEqual:(NSUUID *)UUID1 UUID2:(NSUUID *)UUID2 327 | { 328 | if ([UUID1.UUIDString isEqualToString:UUID2.UUIDString]) 329 | return TRUE; 330 | else 331 | return FALSE; 332 | } 333 | 334 | -(void) getAllServicesFromPeripheral:(CBPeripheral *)p 335 | { 336 | [p discoverServices:nil]; // Discover all services without filter 337 | } 338 | 339 | -(void) getAllCharacteristicsFromPeripheral:(CBPeripheral *)p 340 | { 341 | for (int i=0; i < p.services.count; i++) 342 | { 343 | CBService *s = [p.services objectAtIndex:i]; 344 | // printf("Fetching characteristics for service with UUID : %s\r\n",[self CBUUIDToString:s.UUID]); 345 | [p discoverCharacteristics:nil forService:s]; 346 | } 347 | } 348 | 349 | -(int) compareCBUUID:(CBUUID *) UUID1 UUID2:(CBUUID *)UUID2 350 | { 351 | char b1[16]; 352 | char b2[16]; 353 | [UUID1.data getBytes:b1]; 354 | [UUID2.data getBytes:b2]; 355 | 356 | if (memcmp(b1, b2, UUID1.data.length) == 0) 357 | return 1; 358 | else 359 | return 0; 360 | } 361 | 362 | -(int) compareCBUUIDToInt:(CBUUID *)UUID1 UUID2:(UInt16)UUID2 363 | { 364 | char b1[16]; 365 | 366 | [UUID1.data getBytes:b1]; 367 | UInt16 b2 = [self swap:UUID2]; 368 | 369 | if (memcmp(b1, (char *)&b2, 2) == 0) 370 | return 1; 371 | else 372 | return 0; 373 | } 374 | 375 | -(UInt16) CBUUIDToInt:(CBUUID *) UUID 376 | { 377 | char b1[16]; 378 | [UUID.data getBytes:b1]; 379 | return ((b1[0] << 8) | b1[1]); 380 | } 381 | 382 | -(CBUUID *) IntToCBUUID:(UInt16)UUID 383 | { 384 | char t[16]; 385 | t[0] = ((UUID >> 8) & 0xff); t[1] = (UUID & 0xff); 386 | NSData *data = [[NSData alloc] initWithBytes:t length:16]; 387 | return [CBUUID UUIDWithData:data]; 388 | } 389 | 390 | -(CBService *) findServiceFromUUID:(CBUUID *)UUID p:(CBPeripheral *)p 391 | { 392 | for(int i = 0; i < p.services.count; i++) 393 | { 394 | CBService *s = [p.services objectAtIndex:i]; 395 | if ([self compareCBUUID:s.UUID UUID2:UUID]) 396 | return s; 397 | } 398 | 399 | return nil; //Service not found on this peripheral 400 | } 401 | 402 | -(CBCharacteristic *) findCharacteristicFromUUID:(CBUUID *)UUID service:(CBService*)service 403 | { 404 | for(int i=0; i < service.characteristics.count; i++) 405 | { 406 | CBCharacteristic *c = [service.characteristics objectAtIndex:i]; 407 | if ([self compareCBUUID:c.UUID UUID2:UUID]) return c; 408 | } 409 | 410 | return nil; //Characteristic not found on this service 411 | } 412 | 413 | #if TARGET_OS_IPHONE 414 | //-- no need for iOS 415 | #else 416 | - (BOOL) isLECapableHardware 417 | { 418 | NSString * state = nil; 419 | 420 | switch ([CM state]) 421 | { 422 | case CBCentralManagerStateUnsupported: 423 | state = @"The platform/hardware doesn't support Bluetooth Low Energy."; 424 | break; 425 | 426 | case CBCentralManagerStateUnauthorized: 427 | state = @"The app is not authorized to use Bluetooth Low Energy."; 428 | break; 429 | 430 | case CBCentralManagerStatePoweredOff: 431 | state = @"Bluetooth is currently powered off."; 432 | break; 433 | 434 | case CBCentralManagerStatePoweredOn: 435 | return TRUE; 436 | 437 | case CBCentralManagerStateUnknown: 438 | default: 439 | return FALSE; 440 | 441 | } 442 | 443 | NSLog(@"Central manager state: %@", state); 444 | 445 | NSAlert *alert = [[NSAlert alloc] init]; 446 | [alert setMessageText:state]; 447 | [alert addButtonWithTitle:@"OK"]; 448 | [alert setIcon:[[NSImage alloc] initWithContentsOfFile:@"AppIcon"]]; 449 | [alert beginSheetModalForWindow:nil modalDelegate:self didEndSelector:nil contextInfo:nil]; 450 | 451 | return FALSE; 452 | } 453 | #endif 454 | 455 | - (void)centralManagerDidUpdateState:(CBCentralManager *)central 456 | { 457 | #if TARGET_OS_IPHONE 458 | char *stateString = [self centralManagerStateToString:central.state]; 459 | 460 | NSLog(@"Status of CoreBluetooth central manager changed %ld (%s)", (long)central.state, stateString); 461 | 462 | bool isBluetoothEnabled = false; 463 | if (central.state == CBCentralManagerStatePoweredOn) { 464 | isBluetoothEnabled = true; 465 | } 466 | 467 | if (!isBluetoothEnabled && isConnected) { 468 | [[self delegate] bleDidDisconnect]; 469 | done = false; 470 | isConnected = false; 471 | } 472 | 473 | [[self delegate] bleDidChangedState:isBluetoothEnabled]; 474 | #else 475 | [self isLECapableHardware]; 476 | #endif 477 | } 478 | 479 | - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI 480 | { 481 | if (!self.peripherals) 482 | self.peripherals = [[NSMutableArray alloc] initWithObjects:peripheral,nil]; 483 | else 484 | { 485 | for(int i = 0; i < self.peripherals.count; i++) 486 | { 487 | CBPeripheral *p = [self.peripherals objectAtIndex:i]; 488 | [p bts_setAdvertisementData:advertisementData RSSI:RSSI]; 489 | 490 | if ((p.identifier == NULL) || (peripheral.identifier == NULL)) 491 | continue; 492 | 493 | if ([self UUIDSAreEqual:p.identifier UUID2:peripheral.identifier]) 494 | { 495 | [self.peripherals replaceObjectAtIndex:i withObject:peripheral]; 496 | NSLog(@"Duplicate UUID found updating..."); 497 | return; 498 | } 499 | } 500 | 501 | [self.peripherals addObject:peripheral]; 502 | 503 | NSLog(@"New UUID, adding"); 504 | } 505 | 506 | NSLog(@"didDiscoverPeripheral"); 507 | } 508 | 509 | - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral 510 | { 511 | if (peripheral.identifier != NULL) 512 | NSLog(@"Connected to %@ successful", peripheral.identifier.UUIDString); 513 | else 514 | NSLog(@"Connected to NULL successful"); 515 | 516 | self.activePeripheral = peripheral; 517 | [self.activePeripheral discoverServices:nil]; 518 | [self getAllServicesFromPeripheral:peripheral]; 519 | } 520 | 521 | static bool done = false; 522 | 523 | - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error 524 | { 525 | if (!error) 526 | { 527 | NSLog(@"Characteristics of service with UUID : %@ found\n",[self CBUUIDToString:service.UUID]); 528 | 529 | for (int i=0; i < service.characteristics.count; i++) 530 | { 531 | CBCharacteristic *c = [service.characteristics objectAtIndex:i]; 532 | NSLog(@"Found characteristic %@\n",[ self CBUUIDToString:c.UUID]); 533 | CBService *s = [peripheral.services objectAtIndex:(peripheral.services.count - 1)]; 534 | 535 | if ([service.UUID isEqual:s.UUID]) 536 | { 537 | if (!done) 538 | { 539 | [self enableReadNotification:activePeripheral]; 540 | [[self delegate] bleDidConnect]; 541 | isConnected = true; 542 | done = true; 543 | } 544 | 545 | break; 546 | } 547 | } 548 | } 549 | else 550 | { 551 | NSLog(@"Characteristic discorvery unsuccessful!"); 552 | } 553 | } 554 | 555 | - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error 556 | { 557 | if (!error) 558 | { 559 | // Determine if we're connected to Red Bear Labs, Adafruit or Laird hardware 560 | for (CBService *service in peripheral.services) { 561 | 562 | if ([service.UUID isEqual:redBearLabsServiceUUID]) { 563 | NSLog(@"RedBearLabs Bluetooth"); 564 | serialServiceUUID = redBearLabsServiceUUID; 565 | readCharacteristicUUID = [CBUUID UUIDWithString:@RBL_CHAR_TX_UUID]; 566 | writeCharacteristicUUID = [CBUUID UUIDWithString:@RBL_CHAR_RX_UUID]; 567 | break; 568 | } else if ([service.UUID isEqual:adafruitServiceUUID]) { 569 | NSLog(@"Adafruit Bluefruit LE"); 570 | serialServiceUUID = adafruitServiceUUID; 571 | readCharacteristicUUID = [CBUUID UUIDWithString:@ADAFRUIT_CHAR_TX_UUID]; 572 | writeCharacteristicUUID = [CBUUID UUIDWithString:@ADAFRUIT_CHAR_RX_UUID]; 573 | break; 574 | } else if ([service.UUID isEqual:lairdServiceUUID]) { 575 | NSLog(@"Laird BL600"); 576 | serialServiceUUID = lairdServiceUUID; 577 | readCharacteristicUUID = [CBUUID UUIDWithString:@LAIRD_CHAR_TX_UUID]; 578 | writeCharacteristicUUID = [CBUUID UUIDWithString:@LAIRD_CHAR_RX_UUID]; 579 | break; 580 | } else if ([service.UUID isEqual:blueGigaServiceUUID]) { 581 | NSLog(@"BlueGiga Bluetooth"); 582 | serialServiceUUID = blueGigaServiceUUID; 583 | readCharacteristicUUID = [CBUUID UUIDWithString:@BLUEGIGA_CHAR_TX_UUID]; 584 | writeCharacteristicUUID = [CBUUID UUIDWithString:@BLUEGIGA_CHAR_RX_UUID]; 585 | break; 586 | } else if ([service.UUID isEqual:rongtaSerivceUUID]) { 587 | serialServiceUUID = rongtaSerivceUUID; 588 | readCharacteristicUUID = [CBUUID UUIDWithString:@RONGTA_CHAR_TX_UUID]; 589 | writeCharacteristicUUID = [CBUUID UUIDWithString:@RONGTA_CHAR_RX_UUID]; 590 | } else if ([service.UUID isEqual:posnetSerivceUUID]) { 591 | NSLog(@"Posnet"); 592 | serialServiceUUID = posnetSerivceUUID; 593 | readCharacteristicUUID = [CBUUID UUIDWithString:@POSNET_CHAR_TX_UUID]; 594 | writeCharacteristicUUID = [CBUUID UUIDWithString:@POSNET_CHAR_RX_UUID]; 595 | } else { 596 | // ignore unknown services 597 | } 598 | } 599 | 600 | // TODO - future versions should just get characteristics we care about 601 | // [peripheral discoverCharacteristics:characteristics forService:service]; 602 | [self getAllCharacteristicsFromPeripheral:peripheral]; 603 | } 604 | else 605 | { 606 | NSLog(@"Service discovery was unsuccessful!"); 607 | } 608 | } 609 | 610 | - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 611 | { 612 | if (!error) 613 | { 614 | // printf("Updated notification state for characteristic with UUID %s on service with UUID %s on peripheral with UUID %s\r\n",[self CBUUIDToString:characteristic.UUID],[self CBUUIDToString:characteristic.service.UUID],[self UUIDToString:peripheral.UUID]); 615 | } 616 | else 617 | { 618 | NSLog(@"Error in setting notification state for characteristic with UUID %@ on service with UUID %@ on peripheral with UUID %@", 619 | [self CBUUIDToString:characteristic.UUID], 620 | [self CBUUIDToString:characteristic.service.UUID], 621 | peripheral.identifier.UUIDString); 622 | 623 | NSLog(@"Error code was %s", [[error description] cStringUsingEncoding:NSStringEncodingConversionAllowLossy]); 624 | } 625 | } 626 | 627 | - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 628 | { 629 | static unsigned char buf[512]; 630 | NSInteger data_len; 631 | 632 | if (!error) 633 | { 634 | if ([characteristic.UUID isEqual:readCharacteristicUUID]) 635 | { 636 | data_len = characteristic.value.length; 637 | [characteristic.value getBytes:buf length:data_len]; 638 | 639 | [[self delegate] bleDidReceiveData:buf length:data_len]; 640 | } 641 | } 642 | else 643 | { 644 | NSLog(@"updateValueForCharacteristic failed!"); 645 | } 646 | } 647 | 648 | - (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(NSError *)error 649 | { 650 | if (!isConnected) 651 | return; 652 | 653 | if (rssi != peripheral.RSSI.intValue) 654 | { 655 | rssi = peripheral.RSSI.intValue; 656 | [[self delegate] bleDidUpdateRSSI:activePeripheral.RSSI]; 657 | } 658 | } 659 | 660 | @end 661 | -------------------------------------------------------------------------------- /ios/RCTBluetoothSerial/BLEDefines.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | Copyright (c) 2013 RedBearLab 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | 12 | */ 13 | 14 | // BlueGiga Service 15 | #define BLUEGIGA_SERVICE_UUID "1D5688DE-866D-3AA4-EC46-A1BDDB37ECF6" 16 | #define BLUEGIGA_CHAR_TX_UUID "AF20fBAC-2518-4998-9AF7-AF42540731B3" 17 | #define BLUEGIGA_CHAR_RX_UUID "AF20fBAC-2518-4998-9AF7-AF42540731B3" 18 | 19 | // RBL Service 20 | #define RBL_SERVICE_UUID "713D0000-503E-4C75-BA94-3148F18D941E" 21 | #define RBL_CHAR_TX_UUID "713D0002-503E-4C75-BA94-3148F18D941E" 22 | #define RBL_CHAR_RX_UUID "713D0003-503E-4C75-BA94-3148F18D941E" 23 | 24 | // Adafruit BLE 25 | // http://learn.adafruit.com/getting-started-with-the-nrf8001-bluefruit-le-breakout/adding-app-support 26 | // Adafruit | Nordic's TX and RX are the opposite of RBL. This code uses RBL perspective for naming. 27 | #define ADAFRUIT_SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" 28 | #define ADAFRUIT_CHAR_TX_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" 29 | #define ADAFRUIT_CHAR_RX_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" 30 | 31 | // Laird Virtual Serial Port (vSP) service for BL600 http://www.lairdtech.com/DownloadAsset.aspx?id=2147489885 32 | #define LAIRD_SERVICE_UUID "569a1101-b87f-490c-92cb-11ba5ea5167c" 33 | #define LAIRD_CHAR_TX_UUID "569a2000-b87f-490c-92cb-11ba5ea5167c" 34 | #define LAIRD_CHAR_RX_UUID "569a2001-b87f-490c-92cb-11ba5ea5167c" 35 | 36 | #define RONGTA_SERVICE_UUID "E7810A71-73AE-499D-8C15-FAA9AEF0C3F2" 37 | #define RONGTA_CHAR_TX_UUID "BEF8D6C9-9C21-4C9E-B632-BD58C1009F9F" 38 | #define RONGTA_CHAR_RX_UUID "BEF8D6C9-9C21-4C9E-B632-BD58C1009F9F" 39 | 40 | #define POSNET_SERVICE_UUID "53544D54-4552-494F-5345-525631303030" 41 | #define POSNET_CHAR_TX_UUID "53544F55-4152-5449-4E20-205630303031" 42 | #define POSNET_CHAR_RX_UUID "53544F55-4152-5449-4E20-205630303031" 43 | 44 | #define RBL_BLE_FRAMEWORK_VER 0x0200 45 | -------------------------------------------------------------------------------- /ios/RCTBluetoothSerial/CBPeripheral+BTSExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // CBPeripheral+BTSExtensions.h 3 | // BluetoothSerial Cordova Plugin 4 | // 5 | // (c) 2103-2015 Don Coleman 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | #import 20 | #import 21 | #import 22 | 23 | @interface CBPeripheral(com_megster_bluetoothserial_extension) 24 | 25 | @property (nonatomic, retain) NSString *btsAdvertising; 26 | @property (nonatomic, retain) NSNumber *btsAdvertisementRSSI; 27 | 28 | -(void)bts_setAdvertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber*)rssi; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /ios/RCTBluetoothSerial/CBPeripheral+BTSExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // CBPeripheral+BTSExtensions.m 3 | // BluetoothSerial Cordova Plugin 4 | // 5 | // (c) 2103-2015 Don Coleman 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | #import "CBPeripheral+BTSExtensions.h" 20 | 21 | static char BTS_ADVERTISING_IDENTIFER; 22 | static char BTS_ADVERTISEMENT_RSSI_IDENTIFER; 23 | 24 | @implementation CBPeripheral(com_megster_bluetoothserial_extension) 25 | 26 | // AdvertisementData and RSSI are from didDiscoverPeripheral. 27 | // Save the manufacturerData so we can pass to Cordova in the peripheral 28 | -(void)bts_setAdvertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)rssi{ 29 | 30 | if (advertisementData) { 31 | id manufacturerData = [advertisementData objectForKey:CBAdvertisementDataManufacturerDataKey]; 32 | if (manufacturerData) { 33 | const uint8_t *bytes = [manufacturerData bytes]; 34 | long len = [manufacturerData length]; 35 | // skip manufacturer uuid 36 | NSData *data = [NSData dataWithBytes:bytes+2 length:len-2]; 37 | [self setBtsAdvertising: [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; 38 | } 39 | } 40 | 41 | [self setBtsAdvertisementRSSI: rssi]; 42 | } 43 | 44 | -(void)setBtsAdvertising:(NSString *)newAdvertisingValue{ 45 | objc_setAssociatedObject(self, &BTS_ADVERTISING_IDENTIFER, newAdvertisingValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 46 | } 47 | 48 | -(NSString*)btsAdvertising{ 49 | return objc_getAssociatedObject(self, &BTS_ADVERTISING_IDENTIFER); 50 | } 51 | 52 | 53 | -(void)setBtsAdvertisementRSSI:(NSNumber *)newAdvertisementRSSIValue { 54 | objc_setAssociatedObject(self, &BTS_ADVERTISEMENT_RSSI_IDENTIFER, newAdvertisementRSSIValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 55 | } 56 | 57 | -(NSString*)btsAdvertisementRSSI{ 58 | return objc_getAssociatedObject(self, &BTS_ADVERTISEMENT_RSSI_IDENTIFER); 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /ios/RCTBluetoothSerial/RCTBluetoothSerial.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTBluetoothSerial.h 3 | // RCTBluetoothSerial 4 | // 5 | // Created by Jakub Martyčák on 17.04.16. 6 | // Copyright © 2016 Jakub Martyčák. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | //#import "RCTEventEmitter.h" Wasnt working properly yet, events were fired but listeneres not called 13 | #import "BLE.h" 14 | 15 | @interface RCTBluetoothSerial : NSObject { 16 | BLE *_bleShield; 17 | BOOL _subscribed; 18 | RCTPromiseResolveBlock _connectionResolver; 19 | RCTPromiseRejectBlock _connectionRejector; 20 | NSString* _subscribeCallbackId; 21 | NSString* _subscribeBytesCallbackId; 22 | NSString* _rssiCallbackId; 23 | NSMutableString *_buffer; 24 | NSString *_delimiter; 25 | } 26 | @end 27 | -------------------------------------------------------------------------------- /ios/RCTBluetoothSerial/RCTBluetoothSerial.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTBluetoothSerial.m 3 | // RCTBluetoothSerial 4 | // 5 | // Created by Jakub Martyčák on 17.04.16. 6 | // Copyright © 2016 Jakub Martyčák. All rights reserved. 7 | // 8 | 9 | #import "RCTBluetoothSerial.h" 10 | 11 | @interface RCTBluetoothSerial() 12 | - (NSString *)readUntilDelimiter:(NSString *)delimiter; 13 | - (NSMutableArray *)getPeripheralList; 14 | - (void)sendDataToSubscriber; 15 | - (CBPeripheral *)findPeripheralByUUID:(NSString *)uuid; 16 | - (void)connectToUUID:(NSString *)uuid; 17 | - (void)listPeripheralsTimer:(NSTimer *)timer; 18 | - (void)connectFirstDeviceTimer:(NSTimer *)timer; 19 | - (void)connectUuidTimer:(NSTimer *)timer; 20 | @end 21 | 22 | @implementation RCTBluetoothSerial 23 | 24 | RCT_EXPORT_MODULE(); 25 | 26 | @synthesize bridge = _bridge; 27 | 28 | - (instancetype)init { 29 | _bleShield = [[BLE alloc] init]; 30 | [_bleShield controlSetup]; 31 | [_bleShield setDelegate:self]; 32 | 33 | _buffer = [[NSMutableString alloc] init]; 34 | return self; 35 | } 36 | 37 | - (dispatch_queue_t)methodQueue 38 | { 39 | // run all module methods in main thread 40 | // if we don't no timer callbacks got called 41 | return dispatch_get_main_queue(); 42 | } 43 | 44 | - (NSArray *)supportedEvents 45 | { 46 | return @[@"connectionSuccess",@"connectionLost",@"bluetoothEnabled",@"bluetoothDisabled",@"data"]; 47 | } 48 | 49 | 50 | #pragma mark - Methods available form Javascript 51 | 52 | RCT_EXPORT_METHOD(connect:(NSString *)uuid 53 | resolver:(RCTPromiseResolveBlock)resolve 54 | rejector:(RCTPromiseRejectBlock)reject) 55 | { 56 | 57 | NSLog(@"connect"); 58 | 59 | // if the uuid is null or blank, scan and 60 | // connect to the first available device 61 | 62 | if (uuid == (NSString *)[NSNull null]) { 63 | [self connectToFirstDevice]; 64 | } else if ([uuid isEqualToString:@""]) { 65 | [self connectToFirstDevice]; 66 | } else { 67 | [self connectToUUID:uuid]; 68 | } 69 | 70 | _connectionResolver = resolve; 71 | _connectionRejector = reject; 72 | } 73 | 74 | RCT_EXPORT_METHOD(disconnect:(RCTPromiseResolveBlock)resolve 75 | rejector:(RCTPromiseRejectBlock)reject) 76 | { 77 | 78 | NSLog(@"disconnect"); 79 | 80 | _connectionResolver = nil; 81 | _connectionRejector = nil; 82 | 83 | if (_bleShield.activePeripheral) { 84 | if(_bleShield.activePeripheral.state == CBPeripheralStateConnected) 85 | { 86 | [[_bleShield CM] cancelPeripheralConnection:[_bleShield activePeripheral]]; 87 | } 88 | } 89 | 90 | resolve((id)kCFBooleanTrue); 91 | } 92 | 93 | RCT_EXPORT_METHOD(subscribe:(NSString *)delimiter 94 | resolver:(RCTPromiseResolveBlock)resolve 95 | rejector:(RCTPromiseRejectBlock)reject) 96 | { 97 | NSLog(@"subscribe"); 98 | 99 | if (delimiter != nil) { 100 | _delimiter = [delimiter copy]; 101 | _subscribed = TRUE; 102 | resolve((id)kCFBooleanTrue); 103 | } else { 104 | NSError *err = nil; 105 | reject(@"no_delimiter", @"Delimiter was null", err); 106 | } 107 | } 108 | 109 | RCT_EXPORT_METHOD(unsubscribe:(NSString *)delimiter 110 | resolver:(RCTPromiseResolveBlock)resolve) 111 | { 112 | NSLog(@"unsubscribe"); 113 | 114 | _delimiter = nil; 115 | _subscribed = FALSE; 116 | 117 | resolve((id)kCFBooleanTrue); 118 | } 119 | 120 | RCT_EXPORT_METHOD(writeToDevice:(NSString *)message 121 | resolver:(RCTPromiseResolveBlock)resolve 122 | rejector:(RCTPromiseRejectBlock)reject) 123 | { 124 | NSLog(@"write"); 125 | if (message != nil) { 126 | NSData *data = [[NSData alloc] initWithBase64EncodedString:message options:NSDataBase64DecodingIgnoreUnknownCharacters]; 127 | [_bleShield write:data]; 128 | resolve((id)kCFBooleanTrue); 129 | } else { 130 | NSError *err = nil; 131 | reject(@"no_data", @"Data was null", err); 132 | } 133 | } 134 | 135 | RCT_EXPORT_METHOD(writeTextToDevice:(NSString *)text 136 | resolver:(RCTPromiseResolveBlock)resolve 137 | rejector:(RCTPromiseRejectBlock)reject) 138 | { 139 | NSLog(@"write"); 140 | if (text != nil) { 141 | NSData *data = nil; 142 | NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000); 143 | data = [text dataUsingEncoding:enc]; 144 | [_bleShield write:data]; 145 | resolve((id)kCFBooleanTrue); 146 | } else { 147 | NSError *err = nil; 148 | reject(@"no_data", @"Data was null", err); 149 | } 150 | } 151 | 152 | RCT_EXPORT_METHOD(writeHexToDevice:(NSString *)hexString 153 | resolver:(RCTPromiseResolveBlock)resolve 154 | rejector:(RCTPromiseRejectBlock)reject) 155 | { 156 | NSLog(@"write"); 157 | NSData *data = [self hexToBytes:hexString]; 158 | [_bleShield write:data]; 159 | if (hexString != nil) { 160 | resolve((id)kCFBooleanTrue); 161 | } else { 162 | NSError *err = nil; 163 | reject(@"no_data", @"Data was null", err); 164 | } 165 | } 166 | 167 | -(NSData*) hexToBytes:(NSString *)hexString { 168 | hexString = [hexString stringByReplacingOccurrencesOfString:@" " withString: @""]; 169 | NSMutableData* data = [NSMutableData data]; 170 | int idx; 171 | for (idx = 0; idx+2 <= hexString.length; idx+=2) { 172 | NSRange range = NSMakeRange(idx, 2); 173 | NSString* hexStr = [hexString substringWithRange:range]; 174 | NSScanner* scanner = [NSScanner scannerWithString:hexStr]; 175 | unsigned int intValue; 176 | [scanner scanHexInt:&intValue]; 177 | [data appendBytes:&intValue length:1]; 178 | } 179 | return data; 180 | } 181 | 182 | RCT_EXPORT_METHOD(list:(RCTPromiseResolveBlock)resolve 183 | rejector:(RCTPromiseRejectBlock)reject) 184 | { 185 | [self scanForBLEPeripherals:3]; 186 | [NSTimer scheduledTimerWithTimeInterval:(float)3.0 187 | target:self 188 | selector:@selector(listPeripheralsTimer:) 189 | userInfo:resolve 190 | repeats:NO]; 191 | } 192 | 193 | RCT_EXPORT_METHOD(isEnabled:(RCTPromiseResolveBlock)resolve 194 | rejector:(RCTPromiseRejectBlock)reject) 195 | { 196 | // short delay so CBCentralManger can spin up bluetooth 197 | [NSTimer scheduledTimerWithTimeInterval:(float)0.2 198 | target:self 199 | selector:@selector(bluetoothStateTimer:) 200 | userInfo:resolve 201 | repeats:NO]; 202 | 203 | } 204 | 205 | RCT_EXPORT_METHOD(isConnected:(RCTPromiseResolveBlock)resolve 206 | rejector:(RCTPromiseRejectBlock)reject) 207 | { 208 | if (_bleShield.isConnected) { 209 | resolve((id)kCFBooleanTrue); 210 | } else { 211 | resolve((id)kCFBooleanFalse); 212 | } 213 | } 214 | 215 | RCT_EXPORT_METHOD(available:(RCTPromiseResolveBlock)resolve 216 | rejector:(RCTPromiseRejectBlock)reject) 217 | { 218 | // future versions could use messageAsNSInteger, but realistically, int is fine for buffer length 219 | NSNumber *buffLen = [NSNumber numberWithInteger:[_buffer length]]; 220 | resolve(buffLen); 221 | } 222 | 223 | RCT_EXPORT_METHOD(read:(RCTPromiseResolveBlock)resolve 224 | rejector:(RCTPromiseRejectBlock)reject) 225 | { 226 | NSString *message = @""; 227 | 228 | if ([_buffer length] > 0) { 229 | long end = [_buffer length] - 1; 230 | message = [_buffer substringToIndex:end]; 231 | NSRange entireString = NSMakeRange(0, end); 232 | [_buffer deleteCharactersInRange:entireString]; 233 | } 234 | 235 | resolve(message); 236 | } 237 | 238 | RCT_EXPORT_METHOD(readUntil:(NSString *)delimiter 239 | resolver:(RCTPromiseResolveBlock)resolve 240 | rejector:(RCTPromiseRejectBlock)reject) 241 | { 242 | NSString *message = [self readUntilDelimiter:delimiter]; 243 | resolve(message); 244 | } 245 | 246 | RCT_EXPORT_METHOD(clear:(RCTPromiseResolveBlock)resolve) 247 | { 248 | long end = [_buffer length] - 1; 249 | NSRange truncate = NSMakeRange(0, end); 250 | [_buffer deleteCharactersInRange:truncate]; 251 | resolve((id)kCFBooleanTrue); 252 | } 253 | 254 | #pragma mark - BLEDelegate 255 | 256 | - (void)bleDidReceiveData:(unsigned char *)data length:(int)length 257 | { 258 | NSLog(@"bleDidReceiveData"); 259 | 260 | // Append to the buffer 261 | NSData *d = [NSData dataWithBytes:data length:length]; 262 | NSString *s = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding]; 263 | NSLog(@"Received %@", s); 264 | 265 | if (s) { 266 | [_buffer appendString:s]; 267 | 268 | if (_subscribed) { 269 | [self sendDataToSubscriber]; // only sends if a delimiter is hit 270 | } 271 | 272 | } else { 273 | NSLog(@"Error converting received data into a String."); 274 | } 275 | 276 | // Always send raw data if someone is listening 277 | //if (_subscribeBytesCallbackId) { 278 | // NSData* nsData = [NSData dataWithBytes:(const void *)data length:length]; 279 | //} 280 | 281 | } 282 | 283 | - (void)bleDidChangedState:(bool)isEnabled 284 | { 285 | NSLog(@"bleDidChangedState"); 286 | NSString *eventName; 287 | if (isEnabled) { 288 | eventName = @"bluetoothEnabled"; 289 | } else { 290 | eventName = @"bluetoothDisabled"; 291 | } 292 | [self.bridge.eventDispatcher sendDeviceEventWithName:eventName body:nil]; 293 | } 294 | 295 | - (void)bleDidConnect 296 | { 297 | NSLog(@"bleDidConnect"); 298 | [self.bridge.eventDispatcher sendDeviceEventWithName:@"connectionSuccess" body:nil]; 299 | //[self sendEventWithName:@"connectionSuccess" body:nil]; 300 | 301 | if (_connectionResolver) { 302 | _connectionResolver((id)kCFBooleanTrue); 303 | } 304 | } 305 | 306 | - (void)bleDidDisconnect 307 | { 308 | // TODO is there anyway to figure out why we disconnected? 309 | NSLog(@"bleDidDisconnect"); 310 | [self.bridge.eventDispatcher sendDeviceEventWithName:@"connectionLost" body:nil]; 311 | //[self sendEventWithName:@"connectionLost" body:nil]; 312 | 313 | _connectionResolver = nil; 314 | } 315 | 316 | #pragma mark - timers 317 | 318 | -(void)listPeripheralsTimer:(NSTimer *)timer 319 | { 320 | RCTPromiseResolveBlock resolve = [timer userInfo]; 321 | NSMutableArray *peripherals = [self getPeripheralList]; 322 | resolve(peripherals); 323 | } 324 | 325 | -(void)connectFirstDeviceTimer:(NSTimer *)timer 326 | { 327 | if(_bleShield.peripherals.count > 0) { 328 | NSLog(@"Connecting"); 329 | [_bleShield connectPeripheral:[_bleShield.peripherals objectAtIndex:0]]; 330 | } else { 331 | NSString *message = @"Did not find any BLE peripherals"; 332 | NSError *err = nil; 333 | NSLog(@"%@", message); 334 | _connectionRejector(@"no_peripherals", message, err); 335 | 336 | } 337 | } 338 | 339 | -(void)connectUuidTimer:(NSTimer *)timer 340 | { 341 | NSString *uuid = [timer userInfo]; 342 | CBPeripheral *peripheral = [self findPeripheralByUUID:uuid]; 343 | 344 | if (peripheral) { 345 | [_bleShield connectPeripheral:peripheral]; 346 | } else { 347 | NSString *message = [NSString stringWithFormat:@"Could not find peripheral %@.", uuid]; 348 | NSError *err = nil; 349 | NSLog(@"%@", message); 350 | _connectionRejector(@"wrong_uuid", message, err); 351 | 352 | } 353 | } 354 | 355 | - (void)bluetoothStateTimer:(NSTimer *)timer 356 | { 357 | RCTPromiseResolveBlock resolve = [timer userInfo]; 358 | int bluetoothState = [[_bleShield CM] state]; 359 | BOOL enabled = bluetoothState == CBCentralManagerStatePoweredOn; 360 | 361 | if (enabled) { 362 | resolve((id)kCFBooleanTrue); 363 | } else { 364 | resolve((id)kCFBooleanFalse); 365 | } 366 | } 367 | 368 | #pragma mark - internal implemetation 369 | 370 | - (NSString*)readUntilDelimiter: (NSString*) delimiter 371 | { 372 | 373 | NSRange range = [_buffer rangeOfString: delimiter]; 374 | NSString *message = @""; 375 | 376 | if (range.location != NSNotFound) { 377 | 378 | long end = range.location + range.length; 379 | message = [_buffer substringToIndex:end]; 380 | 381 | NSRange truncate = NSMakeRange(0, end); 382 | [_buffer deleteCharactersInRange:truncate]; 383 | } 384 | return message; 385 | } 386 | 387 | - (NSMutableArray*) getPeripheralList 388 | { 389 | 390 | NSMutableArray *peripherals = [NSMutableArray array]; 391 | 392 | for (int i = 0; i < _bleShield.peripherals.count; i++) { 393 | NSMutableDictionary *peripheral = [NSMutableDictionary dictionary]; 394 | CBPeripheral *p = [_bleShield.peripherals objectAtIndex:i]; 395 | 396 | NSString *uuid = p.identifier.UUIDString; 397 | [peripheral setObject: uuid forKey: @"uuid"]; 398 | [peripheral setObject: uuid forKey: @"id"]; 399 | 400 | NSString *name = [p name]; 401 | if (!name) { 402 | name = [peripheral objectForKey:@"uuid"]; 403 | } 404 | [peripheral setObject: name forKey: @"name"]; 405 | 406 | NSNumber *rssi = [p btsAdvertisementRSSI]; 407 | if (rssi) { // BLEShield doesn't provide advertised RSSI 408 | [peripheral setObject: rssi forKey:@"rssi"]; 409 | } 410 | 411 | [peripherals addObject:peripheral]; 412 | } 413 | 414 | return peripherals; 415 | } 416 | 417 | // calls the JavaScript subscriber with data if we hit the _delimiter 418 | - (void) sendDataToSubscriber { 419 | 420 | NSString *message = [self readUntilDelimiter:_delimiter]; 421 | 422 | if ([message length] > 0) { 423 | [self.bridge.eventDispatcher sendDeviceEventWithName:@"data" body:@{@"data": message}]; 424 | } 425 | 426 | } 427 | 428 | // Ideally we'd get a callback when found, maybe _bleShield can be modified 429 | // to callback on centralManager:didRetrievePeripherals. For now, use a timer. 430 | - (void)scanForBLEPeripherals:(int)timeout 431 | { 432 | 433 | NSLog(@"Scanning for BLE Peripherals"); 434 | 435 | // disconnect 436 | if (_bleShield.activePeripheral) { 437 | if(_bleShield.activePeripheral.state == CBPeripheralStateConnected) 438 | { 439 | [[_bleShield CM] cancelPeripheralConnection:[_bleShield activePeripheral]]; 440 | return; 441 | } 442 | } 443 | 444 | // remove existing peripherals 445 | if (_bleShield.peripherals) { 446 | _bleShield.peripherals = nil; 447 | } 448 | 449 | [_bleShield findBLEPeripherals:timeout]; 450 | } 451 | 452 | - (void)connectToFirstDevice 453 | { 454 | 455 | [self scanForBLEPeripherals:3]; 456 | 457 | [NSTimer scheduledTimerWithTimeInterval:(float)3.0 458 | target:self 459 | selector:@selector(connectFirstDeviceTimer:) 460 | userInfo:nil 461 | repeats:NO]; 462 | } 463 | 464 | - (void)connectToUUID:(NSString *)uuid 465 | { 466 | 467 | int interval = 0; 468 | 469 | if (_bleShield.peripherals.count < 1) { 470 | interval = 3; 471 | [self scanForBLEPeripherals:interval]; 472 | } 473 | 474 | [NSTimer scheduledTimerWithTimeInterval:interval 475 | target:self 476 | selector:@selector(connectUuidTimer:) 477 | userInfo:uuid 478 | repeats:NO]; 479 | } 480 | 481 | - (CBPeripheral*)findPeripheralByUUID:(NSString*)uuid 482 | { 483 | 484 | NSMutableArray *peripherals = [_bleShield peripherals]; 485 | CBPeripheral *peripheral = nil; 486 | 487 | for (CBPeripheral *p in peripherals) { 488 | 489 | NSString *other = p.identifier.UUIDString; 490 | 491 | if ([uuid isEqualToString:other]) { 492 | peripheral = p; 493 | break; 494 | } 495 | } 496 | return peripheral; 497 | } 498 | 499 | @end 500 | -------------------------------------------------------------------------------- /js/esc.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Util = require('./util'); 3 | 4 | const Common = { 5 | INIT: "1B 40",//初始化 6 | 7 | ALIGN_LEFT: "1B 61 00",//左对齐 8 | ALIGN_RIGHT: "1B 61 02",//居右对齐 9 | ALIGN_CENTER: "1B 61 01",//居中对齐 10 | 11 | UNDER_LINE: "1C 2D 01",//下划线 12 | 13 | PRINT_AND_NEW_LINE: "0A",//打印并换行 14 | 15 | FONT_SMALL: "1B 4D 01",//小号字体 9x17 16 | FONT_NORMAL: "1B 4D 00",//正常 12x24 17 | FONT_BOLD: "1B 45 01",//粗体 18 | 19 | FONT_HEIGHT_TIMES: '1B 21 10', 20 | FONT_WIDTH_TIMES: '1B 21 20', 21 | FONT_HEIGHT_WIDTH_TIMES: '1B 21 30', 22 | 23 | SOUND: "1B 42 02 02" // 蜂鸣 2次/100ms 24 | }; 25 | 26 | const Config = { 27 | wordNumber: 48 // 可打印的字数,对应80mm纸张 28 | }; 29 | 30 | let writeTextToDevice, writeHexToDevice; 31 | 32 | function _setBT(bt) { 33 | writeTextToDevice = bt.writeTextToDevice; 34 | writeHexToDevice = bt.writeHexToDevice; 35 | } 36 | 37 | function setConfig(config) { 38 | Object.assign(Config, config); 39 | } 40 | 41 | function leftRight(left, right, wordNumber = Config.wordNumber) { 42 | return left + Util.getSpace(wordNumber - Util.getWordsLength(left) - Util.getWordsLength(right)) + right; 43 | } 44 | 45 | function keyValue(name, value, wordNumber = Config.wordNumber) { 46 | const nameLen = Util.getWordsLength(name); 47 | let vArr = [], temp = ''; 48 | _.each(value, (v, i) => { 49 | const tvLen = Util.getWordsLength(temp + v); 50 | const diff = tvLen - (wordNumber - nameLen); 51 | if (diff <= 0) { 52 | temp += v; 53 | if (i === value.length - 1) { 54 | vArr.push(temp); 55 | } 56 | } else { 57 | if (Util.isChinese(v) && diff === 1) { 58 | temp += ' '; 59 | } 60 | vArr.push(temp); 61 | temp = v; 62 | } 63 | }); 64 | return _.map(vArr, (v, i) => { 65 | if (i === 0) { 66 | return name + v; 67 | } else { 68 | return Util.getSpace(name.length) + v; 69 | } 70 | }).join(''); 71 | } 72 | 73 | const ESC = { 74 | Common, 75 | Util: { 76 | leftRight, 77 | keyValue, 78 | }, 79 | _setBT, 80 | setConfig, 81 | 82 | init(){ 83 | writeHexToDevice(Common.INIT); 84 | }, 85 | printAndNewLine(){ 86 | writeHexToDevice(Common.PRINT_AND_NEW_LINE); 87 | }, 88 | alignLeft(){ 89 | writeHexToDevice(Common.ALIGN_LEFT); 90 | }, 91 | alignCenter(){ 92 | writeHexToDevice(Common.ALIGN_CENTER); 93 | }, 94 | alignRight(){ 95 | writeHexToDevice(Common.ALIGN_RIGHT); 96 | }, 97 | 98 | underline(){ 99 | writeHexToDevice(Common.UNDER_LINE); 100 | }, 101 | 102 | fontSmall(){ 103 | writeHexToDevice(Common.FONT_SMALL); 104 | }, 105 | fontNormal(){ 106 | writeHexToDevice(Common.FONT_NORMAL); 107 | }, 108 | fontBold(){ 109 | writeHexToDevice(Common.FONT_BOLD); 110 | }, 111 | 112 | fontHeightTimes(){ 113 | writeHexToDevice(Common.FONT_HEIGHT_TIMES); 114 | }, 115 | fontHeightTimes(){ 116 | writeHexToDevice(Common.FONT_WIDTH_TIMES); 117 | }, 118 | fontHeightTimes(){ 119 | writeHexToDevice(Common.FONT_HEIGHT_WIDTH_TIMES); 120 | }, 121 | 122 | text(str){ 123 | writeTextToDevice(str) 124 | }, 125 | 126 | sound(){ 127 | writeHexToDevice(Common.SOUND); 128 | } 129 | }; 130 | 131 | module.exports = ESC; -------------------------------------------------------------------------------- /js/tsc.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const Util = require('./util'); 3 | 4 | let writeTextToDevice; 5 | 6 | function _setBT(bt) { 7 | writeTextToDevice = bt.writeTextToDevice; 8 | } 9 | 10 | let Config = { 11 | width: 60, 12 | height: 40, 13 | gap: 2, 14 | direction: 0, 15 | referenceX: 30, 16 | referenceY: 30 17 | }; 18 | 19 | const TSC = { 20 | _setBT, 21 | config(con){ 22 | Object.assign(Config, con); 23 | }, 24 | size(width, height){ 25 | writeTextToDevice(`SIZE ${width} mm, ${height} mm\r\n`); 26 | }, 27 | gap(g){ 28 | writeTextToDevice(`GAP ${g} mm\r\n`); 29 | }, 30 | direction(dir = 0){ 31 | writeTextToDevice(`DIRECTION ${dir}\r\n`); 32 | }, 33 | reference(x = 0, y = 0){ 34 | writeTextToDevice(`REFERENCE ${x},${y}\r\n`); 35 | }, 36 | cls(){ 37 | writeTextToDevice(`CLS \r\n`); 38 | }, 39 | 40 | init(){ 41 | TSC.size(Config.width, Config.height); 42 | TSC.gap(Config.gap); 43 | TSC.direction(Config.direction); 44 | TSC.reference(Config.referenceX, Config.referenceY); 45 | TSC.cls(); 46 | }, 47 | 48 | text(x, y, text, x_times = 1, y_times = 1){ 49 | writeTextToDevice(`TEXT ${x},${y},"TSS24.BF2", 0,${x_times},${y_times},"${text}"`) 50 | }, 51 | print(times = 1){ 52 | writeTextToDevice(`PRINT ${times}\r\n`); 53 | }, 54 | sound(times = 2, time = 100){ 55 | writeTextToDevice(`SOUND ${times},${time}\r\n`); 56 | }, 57 | drawBox(x_start,y_start,x_end,y_end,line_thickness){ 58 | writeTextToDevice(`BOX ${x_start},${y_start},${x_end},${y_end},${line_thickness}\r\n`); 59 | }, 60 | drawBar(x,y,width,height){ 61 | writeTextToDevice(`BAR ${x},${y},${width},${height}\r\n`); 62 | }, 63 | drawQrcode(x,y,ecc_level,cell_width,mode,rotation,qrcode_content){ 64 | writeTextToDevice(`QRCODE ${x},${y},${ecc_level},${cell_width},${mode},${rotation},"${qrcode_content}"\r\n`) 65 | } 66 | } 67 | 68 | module.exports = TSC; -------------------------------------------------------------------------------- /js/util.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | function isChinese(word) { 4 | const charCode = word.charCodeAt(0); 5 | return !(charCode >= 0 && charCode <= 128) 6 | } 7 | 8 | function getWordLength(word) { 9 | return isChinese(word) ? 2 : 1; 10 | } 11 | 12 | function getWordsLength(words) { 13 | return _.reduce(words, (m, v) => m + getWordLength(v), 0); 14 | } 15 | 16 | function getSpace(len) { 17 | return _.times(len, () => ' ').join(''); 18 | } 19 | 20 | module.exports = { 21 | isChinese, 22 | getWordsLength, 23 | getWordLength, 24 | getSpace 25 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-gm-bluetooth", 3 | "version": "0.39.7", 4 | "description": "react-native-gm-bluetooth", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/gmfe/react-native-gm-bluetooth.git" 12 | }, 13 | "keywords": [ 14 | "bluetooth", 15 | "react-native", 16 | "weight", 17 | "print" 18 | ], 19 | "author": "", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/gmfe/react-native-gm-bluetooth/issues" 23 | }, 24 | "homepage": "https://github.com/gmfe/react-native-gm-bluetooth#readme", 25 | "dependencies": { 26 | "buffer": "^4.6.0" 27 | }, 28 | "peerDependencies": { 29 | "react-native": ">=0.40", 30 | "lodash": "^4.17.4" 31 | } 32 | } 33 | --------------------------------------------------------------------------------