Skip to content

Commit 49b16d0

Browse files
committedMay 20, 2016
Initial import
1 parent 4b450f9 commit 49b16d0

27 files changed

+2797
-0
lines changed
 

‎.gitignore

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.idea
2+
gen/
3+
4+
.gradle
5+
gradle
6+
gradlew
7+
gradlew.bat
8+
9+
node_modules
10+
11+
# OSX
12+
#
13+
.DS_Store
14+
15+
# Xcode
16+
#
17+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
18+
19+
## Build generated
20+
build/
21+
DerivedData

‎BleManager.android.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Stub of BleManagerModule for Android.
3+
*
4+
* @providesModule BleManagerModule
5+
* @flow
6+
*/
7+
'use strict';
8+
9+
var warning = require('fbjs/lib/warning');
10+
11+
var BleManagerModule = {
12+
test: function() {
13+
warning('Not yet implemented for Android.');
14+
}
15+
};
16+
17+
module.exports = BleManagerModule;

‎BleManager.ios.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @providesModule BleManager
3+
* @flow
4+
*/
5+
'use strict';
6+
7+
var NativeBleManager = require('NativeModules').BleManager;
8+
9+
/**
10+
* High-level docs for the BleManagerModule iOS API can be written here.
11+
*/
12+
13+
var BleManager = {
14+
test: function() {
15+
NativeBleManager.test();
16+
}
17+
};
18+
19+
module.exports = BleManager;

‎android/build.gradle

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
buildscript {
2+
repositories {
3+
jcenter()
4+
}
5+
6+
dependencies {
7+
classpath 'com.android.tools.build:gradle:1.1.3'
8+
}
9+
}
10+
apply plugin: 'com.android.library'
11+
12+
android {
13+
compileSdkVersion 23
14+
buildToolsVersion "23.0.1"
15+
16+
defaultConfig {
17+
minSdkVersion 16
18+
}
19+
lintOptions {
20+
abortOnError false
21+
}
22+
}
23+
24+
repositories {
25+
mavenCentral()
26+
}
27+
28+
dependencies {
29+
compile 'com.facebook.react:react-native:0.14.+'
30+
}

‎android/settings.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rootProject.name = 'react-native-ble-manager'
2+

‎android/src/main/AndroidManifest.xml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
package="it.innove">
3+
<uses-permission android:name="android.permission.BLUETOOTH" />
4+
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
5+
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
6+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package it.innove;
2+
3+
import java.util.UUID;
4+
5+
class BLECommand {
6+
// Types
7+
public static int READ = 10000;
8+
public static int REGISTER_NOTIFY = 10001;
9+
public static int REMOVE_NOTIFY = 10002;
10+
// BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
11+
// BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
12+
13+
private UUID serviceUUID;
14+
private UUID characteristicUUID;
15+
private byte[] data;
16+
private int type;
17+
18+
19+
public BLECommand(UUID serviceUUID, UUID characteristicUUID, int type) {
20+
this.serviceUUID = serviceUUID;
21+
this.characteristicUUID = characteristicUUID;
22+
this.type = type;
23+
}
24+
25+
public BLECommand(UUID serviceUUID, UUID characteristicUUID, byte[] data, int type) {
26+
this.serviceUUID = serviceUUID;
27+
this.characteristicUUID = characteristicUUID;
28+
this.data = data;
29+
this.type = type;
30+
}
31+
32+
public int getType() {
33+
return type;
34+
}
35+
36+
public UUID getServiceUUID() {
37+
return serviceUUID;
38+
}
39+
40+
public UUID getCharacteristicUUID() {
41+
return characteristicUUID;
42+
}
43+
44+
public byte[] getData() {
45+
return data;
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
package it.innove;
2+
3+
import android.app.Activity;
4+
import android.bluetooth.BluetoothAdapter;
5+
import android.bluetooth.BluetoothDevice;
6+
import android.bluetooth.BluetoothGattCharacteristic;
7+
import android.bluetooth.BluetoothManager;
8+
import android.bluetooth.le.*;
9+
import android.content.BroadcastReceiver;
10+
import android.content.Context;
11+
import android.content.Intent;
12+
import android.content.IntentFilter;
13+
import android.os.Build;
14+
import android.os.Bundle;
15+
import android.support.annotation.Nullable;
16+
import android.util.Base64;
17+
import android.util.Log;
18+
import com.facebook.react.bridge.*;
19+
import com.facebook.react.modules.core.DeviceEventManagerModule;
20+
import org.json.JSONException;
21+
22+
import java.util.*;
23+
24+
import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
25+
26+
27+
public class BleManager extends ReactContextBaseJavaModule {
28+
29+
public static final String LOG_TAG = "logs";
30+
31+
32+
private static Activity activity;
33+
private BluetoothAdapter bluetoothAdapter;
34+
private BluetoothLeScanner bluetoothLeScanner;
35+
private ScanSettings settings;
36+
private Context context;
37+
private ReactContext reactContext;
38+
39+
// key is the MAC Address
40+
Map<String, Peripheral> peripherals = new LinkedHashMap<String, Peripheral>();
41+
Map<String, Callback> connectCallback = new LinkedHashMap<String, Callback>();
42+
43+
private Timer timer = new Timer();
44+
45+
public BleManager(ReactApplicationContext reactContext, Activity activity) {
46+
super(reactContext);
47+
BleManager.activity = activity;
48+
context = reactContext;
49+
this.reactContext = reactContext;
50+
51+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
52+
settings = new ScanSettings.Builder()
53+
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
54+
.build();
55+
}
56+
57+
Log.d(LOG_TAG, "Inizializzo modulo");
58+
59+
60+
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
61+
context.registerReceiver(mReceiver, filter);
62+
Log.d(LOG_TAG, "Inizializzo receiver stato ble");
63+
}
64+
65+
@Override
66+
public String getName() {
67+
return "BleManager";
68+
}
69+
70+
private BluetoothAdapter getBluetoothAdapter() {
71+
if (bluetoothAdapter == null) {
72+
BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
73+
Log.d(LOG_TAG, "Manager preso");
74+
bluetoothAdapter = manager.getAdapter();
75+
}
76+
return bluetoothAdapter;
77+
}
78+
79+
private void sendEvent(String eventName,
80+
@Nullable WritableMap params) {
81+
getReactApplicationContext()
82+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
83+
.emit(eventName, params);
84+
}
85+
86+
@ReactMethod
87+
public void scan(ReadableArray serviceUUIDs, final int scanSeconds, Callback successCallback) {
88+
Log.d(LOG_TAG, "scan");
89+
if (!getBluetoothAdapter().isEnabled())
90+
return;
91+
92+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
93+
for (Iterator<Map.Entry<String, Peripheral>> iterator = peripherals.entrySet().iterator(); iterator.hasNext(); ) {
94+
Map.Entry<String, Peripheral> entry = iterator.next();
95+
if (!entry.getValue().isConnected()) {
96+
iterator.remove();
97+
}
98+
}
99+
100+
if (serviceUUIDs.size() > 0) {
101+
UUID[] services = new UUID[serviceUUIDs.size()];
102+
for(int i = 0; i < serviceUUIDs.size(); i++){
103+
services[i] = UUIDHelper.uuidFromString(serviceUUIDs.getString(i));
104+
Log.d(LOG_TAG, "scan su service: " + serviceUUIDs.getString(i));
105+
}
106+
107+
//getBluetoothAdapter().startLeScan(services, mLeScanCallback);
108+
getBluetoothAdapter().startLeScan(mLeScanCallback);
109+
} else {
110+
getBluetoothAdapter().startLeScan(mLeScanCallback);
111+
}
112+
113+
if (scanSeconds > 0) {
114+
Thread thread = new Thread() {
115+
116+
@Override
117+
public void run() {
118+
119+
try {
120+
Thread.sleep(scanSeconds * 1000);
121+
} catch (InterruptedException e) {
122+
}
123+
124+
runOnUiThread(new Runnable() {
125+
@Override
126+
public void run() {
127+
getBluetoothAdapter().stopLeScan(mLeScanCallback);
128+
WritableMap map = Arguments.createMap();
129+
sendEvent("BluetoothManagerStopScan", map);
130+
}
131+
});
132+
133+
}
134+
135+
};
136+
thread.start();
137+
}
138+
}
139+
successCallback.invoke();
140+
}
141+
142+
@ReactMethod
143+
public void connect(String peripheralUUID, Callback successCallback, Callback failCallback) {
144+
Log.d(LOG_TAG, "connect: " + peripheralUUID );
145+
146+
Peripheral peripheral = peripherals.get(peripheralUUID);
147+
if (peripheral != null){
148+
peripheral.connect(successCallback, failCallback, activity);
149+
} else
150+
failCallback.invoke();
151+
}
152+
153+
@ReactMethod
154+
public void disconnect(String peripheralUUID, Callback successCallback, Callback failCallback) {
155+
Log.d(LOG_TAG, "disconnect: " + peripheralUUID);
156+
157+
Peripheral peripheral = peripherals.get(peripheralUUID);
158+
if (peripheral != null){
159+
peripheral.disconnect();
160+
successCallback.invoke();
161+
} else
162+
failCallback.invoke();
163+
}
164+
165+
@ReactMethod
166+
public void startNotification(String deviceUUID, String serviceUUID, String characteristicUUID, Callback successCallback, Callback failCallback) {
167+
Log.d(LOG_TAG, "startNotification");
168+
169+
Peripheral peripheral = peripherals.get(deviceUUID);
170+
if (peripheral != null){
171+
peripheral.registerNotify(UUID.fromString(serviceUUID), UUID.fromString(characteristicUUID), successCallback, failCallback);
172+
} else
173+
failCallback.invoke();
174+
}
175+
176+
177+
@ReactMethod
178+
public void write(String deviceUUID, String serviceUUID, String characteristicUUID, String message, Callback successCallback, Callback failCallback) {
179+
Log.d(LOG_TAG, "write su periferica: " + deviceUUID);
180+
181+
Peripheral peripheral = peripherals.get(deviceUUID);
182+
if (peripheral != null){
183+
byte[] decoded = Base64.decode(message.getBytes(), Base64.DEFAULT);
184+
Log.d(LOG_TAG, "Message(" + decoded.length + "): " + bytesToHex(decoded) + " su " + deviceUUID);
185+
peripheral.write(UUID.fromString(serviceUUID), UUID.fromString(characteristicUUID), decoded, successCallback, failCallback, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
186+
} else
187+
failCallback.invoke();
188+
}
189+
190+
private BluetoothAdapter.LeScanCallback mLeScanCallback =
191+
new BluetoothAdapter.LeScanCallback() {
192+
193+
194+
@Override
195+
public void onLeScan(final BluetoothDevice device, final int rssi,
196+
final byte[] scanRecord) {
197+
runOnUiThread(new Runnable() {
198+
@Override
199+
public void run() {
200+
Log.i(LOG_TAG, "DiscoverPeripheral: " + device.getName());
201+
String address = device.getAddress();
202+
203+
if (!peripherals.containsKey(address)) {
204+
205+
Peripheral peripheral = new Peripheral(device, rssi, scanRecord, reactContext);
206+
peripherals.put(device.getAddress(), peripheral);
207+
208+
BundleJSONConverter bjc = new BundleJSONConverter();
209+
try {
210+
Bundle bundle = bjc.convertToBundle(peripheral.asJSONObject());
211+
WritableMap map = Arguments.fromBundle(bundle);
212+
sendEvent("BluetoothManagerDiscoverPeripheral", map);
213+
} catch (JSONException e) {
214+
215+
}
216+
217+
218+
} else {
219+
// this isn't necessary
220+
Peripheral peripheral = peripherals.get(address);
221+
peripheral.updateRssi(rssi);
222+
}
223+
}
224+
});
225+
}
226+
227+
228+
};
229+
230+
@ReactMethod
231+
public void checkState(){
232+
Log.d(LOG_TAG, "checkState");
233+
234+
BluetoothAdapter adapter = getBluetoothAdapter();
235+
String state = "off";
236+
switch (adapter.getState()){
237+
case BluetoothAdapter.STATE_ON:
238+
state = "on";
239+
break;
240+
case BluetoothAdapter.STATE_OFF:
241+
state = "off";
242+
}
243+
244+
WritableMap map = Arguments.createMap();
245+
map.putString("state", state);
246+
Log.d(LOG_TAG, "state:" + state);
247+
sendEvent("BluetoothManagerDidUpdateState", map);
248+
}
249+
250+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
251+
@Override
252+
public void onReceive(Context context, Intent intent) {
253+
Log.d(LOG_TAG, "onReceive");
254+
final String action = intent.getAction();
255+
256+
String stringState = "";
257+
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
258+
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
259+
BluetoothAdapter.ERROR);
260+
switch (state) {
261+
case BluetoothAdapter.STATE_OFF:
262+
stringState = "off";
263+
break;
264+
case BluetoothAdapter.STATE_TURNING_OFF:
265+
stringState = "turning_off";
266+
break;
267+
case BluetoothAdapter.STATE_ON:
268+
stringState = "on";
269+
break;
270+
case BluetoothAdapter.STATE_TURNING_ON:
271+
stringState = "turning_on";
272+
break;
273+
}
274+
}
275+
276+
WritableMap map = Arguments.createMap();
277+
map.putString("state", stringState);
278+
Log.d(LOG_TAG, "state: " + stringState);
279+
sendEvent("BluetoothManagerDidUpdateState", map);
280+
}
281+
};
282+
283+
284+
285+
286+
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
287+
288+
public static String bytesToHex(byte[] bytes) {
289+
char[] hexChars = new char[bytes.length * 2];
290+
for (int j = 0; j < bytes.length; j++) {
291+
int v = bytes[j] & 0xFF;
292+
hexChars[j * 2] = hexArray[v >>> 4];
293+
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
294+
}
295+
return new String(hexChars);
296+
}
297+
298+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package it.innove;
2+
3+
import android.app.Activity;
4+
import com.facebook.react.ReactPackage;
5+
import com.facebook.react.bridge.JavaScriptModule;
6+
import com.facebook.react.bridge.NativeModule;
7+
import com.facebook.react.bridge.ReactApplicationContext;
8+
import com.facebook.react.uimanager.ViewManager;
9+
10+
import java.util.ArrayList;
11+
import java.util.Arrays;
12+
import java.util.List;
13+
14+
15+
public class BleManagerPackage implements ReactPackage {
16+
17+
private Activity mActivity;
18+
19+
public BleManagerPackage(Activity activityContext) {
20+
mActivity = activityContext;
21+
}
22+
23+
24+
@Override
25+
public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
26+
List<NativeModule> modules = new ArrayList<>();
27+
28+
modules.add(new BleManager(reactApplicationContext, mActivity));
29+
return modules;
30+
}
31+
32+
@Override
33+
public List<Class<? extends JavaScriptModule>> createJSModules() {
34+
return new ArrayList<>();
35+
}
36+
37+
@Override
38+
public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
39+
return Arrays.asList();
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package it.innove;
2+
3+
import android.os.Bundle;
4+
import org.json.JSONArray;
5+
import org.json.JSONException;
6+
import org.json.JSONObject;
7+
8+
import java.util.*;
9+
10+
/**
11+
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
12+
*
13+
* You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
14+
* copy, modify, and distribute this software in source code or binary form for use
15+
* in connection with the web services and APIs provided by Facebook.
16+
*
17+
* As with any software that integrates with the Facebook platform, your use of
18+
* this software is subject to the Facebook Developer Principles and Policies
19+
* [http://developers.facebook.com/policy/]. This copyright notice shall be
20+
* included in all copies or substantial portions of the software.
21+
*
22+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
24+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
25+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
26+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
27+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28+
*/
29+
public class BundleJSONConverter {
30+
private static final Map<Class<?>, Setter> SETTERS = new HashMap<Class<?>, Setter>();
31+
32+
static {
33+
SETTERS.put(Boolean.class, new Setter() {
34+
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
35+
bundle.putBoolean(key, (Boolean) value);
36+
}
37+
38+
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
39+
json.put(key, value);
40+
}
41+
});
42+
SETTERS.put(Integer.class, new Setter() {
43+
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
44+
bundle.putInt(key, (Integer) value);
45+
}
46+
47+
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
48+
json.put(key, value);
49+
}
50+
});
51+
SETTERS.put(Long.class, new Setter() {
52+
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
53+
bundle.putLong(key, (Long) value);
54+
}
55+
56+
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
57+
json.put(key, value);
58+
}
59+
});
60+
SETTERS.put(Double.class, new Setter() {
61+
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
62+
bundle.putDouble(key, (Double) value);
63+
}
64+
65+
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
66+
json.put(key, value);
67+
}
68+
});
69+
SETTERS.put(String.class, new Setter() {
70+
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
71+
bundle.putString(key, (String) value);
72+
}
73+
74+
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
75+
json.put(key, value);
76+
}
77+
});
78+
SETTERS.put(String[].class, new Setter() {
79+
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
80+
throw new IllegalArgumentException("Unexpected type from JSON");
81+
}
82+
83+
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
84+
JSONArray jsonArray = new JSONArray();
85+
for (String stringValue : (String[])value) {
86+
jsonArray.put(stringValue);
87+
}
88+
json.put(key, jsonArray);
89+
}
90+
});
91+
92+
SETTERS.put(JSONArray.class, new Setter() {
93+
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException {
94+
JSONArray jsonArray = (JSONArray)value;
95+
ArrayList<String> stringArrayList = new ArrayList<String>();
96+
// Empty list, can't even figure out the type, assume an ArrayList<String>
97+
if (jsonArray.length() == 0) {
98+
bundle.putStringArrayList(key, stringArrayList);
99+
return;
100+
}
101+
102+
// Only strings are supported for now
103+
for (int i = 0; i < jsonArray.length(); i++) {
104+
Object current = jsonArray.get(i);
105+
if (current instanceof String) {
106+
stringArrayList.add((String)current);
107+
} else {
108+
throw new IllegalArgumentException("Unexpected type in an array: " + current.getClass());
109+
}
110+
}
111+
bundle.putStringArrayList(key, stringArrayList);
112+
}
113+
114+
@Override
115+
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException {
116+
throw new IllegalArgumentException("JSONArray's are not supported in bundles.");
117+
}
118+
});
119+
}
120+
121+
public interface Setter {
122+
public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException;
123+
public void setOnJSON(JSONObject json, String key, Object value) throws JSONException;
124+
}
125+
126+
public static JSONObject convertToJSON(Bundle bundle) throws JSONException {
127+
JSONObject json = new JSONObject();
128+
129+
for(String key : bundle.keySet()) {
130+
Object value = bundle.get(key);
131+
if (value == null) {
132+
// Null is not supported.
133+
continue;
134+
}
135+
136+
// Special case List<String> as getClass would not work, since List is an interface
137+
if (value instanceof List<?>) {
138+
JSONArray jsonArray = new JSONArray();
139+
@SuppressWarnings("unchecked")
140+
List<String> listValue = (List<String>)value;
141+
for (String stringValue : listValue) {
142+
jsonArray.put(stringValue);
143+
}
144+
json.put(key, jsonArray);
145+
continue;
146+
}
147+
148+
// Special case Bundle as it's one way, on the return it will be JSONObject
149+
if (value instanceof Bundle) {
150+
json.put(key, convertToJSON((Bundle)value));
151+
continue;
152+
}
153+
154+
Setter setter = SETTERS.get(value.getClass());
155+
if (setter == null) {
156+
throw new IllegalArgumentException("Unsupported type: " + value.getClass());
157+
}
158+
setter.setOnJSON(json, key, value);
159+
}
160+
161+
return json;
162+
}
163+
164+
public static Bundle convertToBundle(JSONObject jsonObject) throws JSONException {
165+
Bundle bundle = new Bundle();
166+
@SuppressWarnings("unchecked")
167+
Iterator<String> jsonIterator = jsonObject.keys();
168+
while (jsonIterator.hasNext()) {
169+
String key = jsonIterator.next();
170+
Object value = jsonObject.get(key);
171+
if (value == null || value == JSONObject.NULL) {
172+
// Null is not supported.
173+
continue;
174+
}
175+
176+
// Special case JSONObject as it's one way, on the return it would be Bundle.
177+
if (value instanceof JSONObject) {
178+
bundle.putBundle(key, convertToBundle((JSONObject)value));
179+
continue;
180+
}
181+
182+
Setter setter = SETTERS.get(value.getClass());
183+
if (setter == null) {
184+
throw new IllegalArgumentException("Unsupported type: " + value.getClass());
185+
}
186+
setter.setOnBundle(bundle, key, value);
187+
}
188+
189+
return bundle;
190+
}
191+
}
+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package it.innove;
2+
3+
import android.bluetooth.BluetoothGattCharacteristic;
4+
import android.bluetooth.BluetoothGattDescriptor;
5+
import org.json.JSONArray;
6+
7+
public class Helper {
8+
9+
public static JSONArray decodeProperties(BluetoothGattCharacteristic characteristic) {
10+
11+
// NOTE: props strings need to be consistent across iOS and Android
12+
JSONArray props = new JSONArray();
13+
int properties = characteristic.getProperties();
14+
15+
if ((properties & BluetoothGattCharacteristic.PROPERTY_BROADCAST) != 0x0 ) {
16+
props.put("Broadcast");
17+
}
18+
19+
if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) != 0x0 ) {
20+
props.put("Read");
21+
}
22+
23+
if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0x0 ) {
24+
props.put("WriteWithoutResponse");
25+
}
26+
27+
if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0x0 ) {
28+
props.put("Write");
29+
}
30+
31+
if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0x0 ) {
32+
props.put("Notify");
33+
}
34+
35+
if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0x0 ) {
36+
props.put("Indicate");
37+
}
38+
39+
if ((properties & BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) != 0x0 ) {
40+
// Android calls this "write with signature", using iOS name for now
41+
props.put("AuthenticateSignedWrites");
42+
}
43+
44+
if ((properties & BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) != 0x0 ) {
45+
props.put("ExtendedProperties");
46+
}
47+
48+
// iOS only?
49+
//
50+
// if ((p & CBCharacteristicPropertyNotifyEncryptionRequired) != 0x0) { // 0x100
51+
// [props addObject:@"NotifyEncryptionRequired"];
52+
// }
53+
//
54+
// if ((p & CBCharacteristicPropertyIndicateEncryptionRequired) != 0x0) { // 0x200
55+
// [props addObject:@"IndicateEncryptionRequired"];
56+
// }
57+
58+
return props;
59+
}
60+
61+
public static JSONArray decodePermissions(BluetoothGattCharacteristic characteristic) {
62+
63+
// NOTE: props strings need to be consistent across iOS and Android
64+
JSONArray props = new JSONArray();
65+
int permissions = characteristic.getPermissions();
66+
67+
if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ) != 0x0 ) {
68+
props.put("Read");
69+
}
70+
71+
if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE) != 0x0 ) {
72+
props.put("Write");
73+
}
74+
75+
if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED) != 0x0 ) {
76+
props.put("ReadEncrypted");
77+
}
78+
79+
if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED) != 0x0 ) {
80+
props.put("WriteEncrypted");
81+
}
82+
83+
if ((permissions & BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED_MITM) != 0x0 ) {
84+
props.put("ReadEncryptedMITM");
85+
}
86+
87+
if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED_MITM) != 0x0 ) {
88+
props.put("WriteEncryptedMITM");
89+
}
90+
91+
if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED) != 0x0 ) {
92+
props.put("WriteSigned");
93+
}
94+
95+
if ((permissions & BluetoothGattCharacteristic.PERMISSION_WRITE_SIGNED_MITM) != 0x0 ) {
96+
props.put("WriteSignedMITM");
97+
}
98+
99+
return props;
100+
}
101+
102+
public static JSONArray decodePermissions(BluetoothGattDescriptor descriptor) {
103+
104+
// NOTE: props strings need to be consistent across iOS and Android
105+
JSONArray props = new JSONArray();
106+
int permissions = descriptor.getPermissions();
107+
108+
if ((permissions & BluetoothGattDescriptor.PERMISSION_READ) != 0x0 ) {
109+
props.put("Read");
110+
}
111+
112+
if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE) != 0x0 ) {
113+
props.put("Write");
114+
}
115+
116+
if ((permissions & BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED) != 0x0 ) {
117+
props.put("ReadEncrypted");
118+
}
119+
120+
if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED) != 0x0 ) {
121+
props.put("WriteEncrypted");
122+
}
123+
124+
if ((permissions & BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM) != 0x0 ) {
125+
props.put("ReadEncryptedMITM");
126+
}
127+
128+
if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM) != 0x0 ) {
129+
props.put("WriteEncryptedMITM");
130+
}
131+
132+
if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED) != 0x0 ) {
133+
props.put("WriteSigned");
134+
}
135+
136+
if ((permissions & BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM) != 0x0 ) {
137+
props.put("WriteSignedMITM");
138+
}
139+
140+
return props;
141+
}
142+
143+
}

‎android/src/main/java/it/innove/Peripheral.java

+607
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package it.innove;
2+
3+
import java.util.UUID;
4+
import java.util.regex.Matcher;
5+
import java.util.regex.Pattern;
6+
7+
public class UUIDHelper {
8+
9+
// base UUID used to build 128 bit Bluetooth UUIDs
10+
public static final String UUID_BASE = "0000XXXX-0000-1000-8000-00805f9b34fb";
11+
12+
// handle 16 and 128 bit UUIDs
13+
public static UUID uuidFromString(String uuid) {
14+
15+
if (uuid.length() == 4) {
16+
uuid = UUID_BASE.replace("XXXX", uuid);
17+
}
18+
return UUID.fromString(uuid);
19+
}
20+
21+
// return 16 bit UUIDs where possible
22+
public static String uuidToString(UUID uuid) {
23+
String longUUID = uuid.toString();
24+
Pattern pattern = Pattern.compile("0000(.{4})-0000-1000-8000-00805f9b34fb", Pattern.CASE_INSENSITIVE);
25+
Matcher matcher = pattern.matcher(longUUID);
26+
if (matcher.matches()) {
27+
// 16 bit UUID
28+
return matcher.group(1);
29+
} else {
30+
return longUUID;
31+
}
32+
}
33+
}

‎ios/BLECommandContext.h

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#import <Foundation/Foundation.h>
2+
#import <CoreBluetooth/CoreBluetooth.h>
3+
4+
@interface BLECommandContext : NSObject
5+
6+
@property CBPeripheral *peripheral;
7+
@property CBService *service;
8+
@property CBCharacteristic *characteristic;
9+
10+
@end

‎ios/BLECommandContext.m

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#import "BLECommandContext.h"
2+
3+
@implementation BLECommandContext
4+
5+
@end

‎ios/BleManager.h

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#import "RCTBridgeModule.h"
2+
#import <CoreBluetooth/CoreBluetooth.h>
3+
4+
5+
@interface BleManager : NSObject <RCTBridgeModule, CBCentralManagerDelegate, CBPeripheralDelegate>{
6+
NSString* discoverPeripherialCallbackId;
7+
NSMutableDictionary* connectCallbacks;
8+
NSMutableDictionary *readCallbacks;
9+
NSMutableDictionary *writeCallbacks;
10+
NSMutableArray *writeQueue;
11+
NSMutableDictionary *notificationCallbacks;
12+
NSMutableDictionary *stopNotificationCallbacks;
13+
NSMutableDictionary *connectCallbackLatches;
14+
}
15+
16+
@property (strong, nonatomic) NSMutableSet *peripherals;
17+
@property (strong, nonatomic) CBCentralManager *manager;
18+
19+
20+
@end

‎ios/BleManager.m

+592
Large diffs are not rendered by default.
+279
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
// !$*UTF8*$!
2+
{
3+
archiveVersion = 1;
4+
classes = {
5+
};
6+
objectVersion = 46;
7+
objects = {
8+
9+
/* Begin PBXBuildFile section */
10+
A908F1921CEEFEB500BE1488 /* NSData+Conversion.m in Sources */ = {isa = PBXBuildFile; fileRef = A908F18B1CEEFEB500BE1488 /* NSData+Conversion.m */; };
11+
A908F1931CEEFEB500BE1488 /* CBPeripheral+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = A908F18D1CEEFEB500BE1488 /* CBPeripheral+Extensions.m */; };
12+
A908F1941CEEFEB500BE1488 /* BLECommandContext.m in Sources */ = {isa = PBXBuildFile; fileRef = A908F18F1CEEFEB500BE1488 /* BLECommandContext.m */; };
13+
A908F1951CEEFEB500BE1488 /* BleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A908F1911CEEFEB500BE1488 /* BleManager.m */; };
14+
/* End PBXBuildFile section */
15+
16+
/* Begin PBXCopyFilesBuildPhase section */
17+
58B511D91A9E6C8500147676 /* CopyFiles */ = {
18+
isa = PBXCopyFilesBuildPhase;
19+
buildActionMask = 2147483647;
20+
dstPath = "include/$(PRODUCT_NAME)";
21+
dstSubfolderSpec = 16;
22+
files = (
23+
);
24+
runOnlyForDeploymentPostprocessing = 0;
25+
};
26+
/* End PBXCopyFilesBuildPhase section */
27+
28+
/* Begin PBXFileReference section */
29+
134814201AA4EA6300B7C361 /* libBleManager.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBleManager.a; sourceTree = BUILT_PRODUCTS_DIR; };
30+
A908F18A1CEEFEB500BE1488 /* NSData+Conversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Conversion.h"; sourceTree = "<group>"; };
31+
A908F18B1CEEFEB500BE1488 /* NSData+Conversion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Conversion.m"; sourceTree = "<group>"; };
32+
A908F18C1CEEFEB500BE1488 /* CBPeripheral+Extensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CBPeripheral+Extensions.h"; sourceTree = "<group>"; };
33+
A908F18D1CEEFEB500BE1488 /* CBPeripheral+Extensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CBPeripheral+Extensions.m"; sourceTree = "<group>"; };
34+
A908F18E1CEEFEB500BE1488 /* BLECommandContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BLECommandContext.h; sourceTree = "<group>"; };
35+
A908F18F1CEEFEB500BE1488 /* BLECommandContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BLECommandContext.m; sourceTree = "<group>"; };
36+
A908F1901CEEFEB500BE1488 /* BleManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BleManager.h; sourceTree = "<group>"; };
37+
A908F1911CEEFEB500BE1488 /* BleManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BleManager.m; sourceTree = "<group>"; };
38+
/* End PBXFileReference section */
39+
40+
/* Begin PBXFrameworksBuildPhase section */
41+
58B511D81A9E6C8500147676 /* Frameworks */ = {
42+
isa = PBXFrameworksBuildPhase;
43+
buildActionMask = 2147483647;
44+
files = (
45+
);
46+
runOnlyForDeploymentPostprocessing = 0;
47+
};
48+
/* End PBXFrameworksBuildPhase section */
49+
50+
/* Begin PBXGroup section */
51+
134814211AA4EA7D00B7C361 /* Products */ = {
52+
isa = PBXGroup;
53+
children = (
54+
134814201AA4EA6300B7C361 /* libBleManager.a */,
55+
);
56+
name = Products;
57+
sourceTree = "<group>";
58+
};
59+
58B511D21A9E6C8500147676 = {
60+
isa = PBXGroup;
61+
children = (
62+
A908F1891CEEFE0F00BE1488 /* BleManager */,
63+
134814211AA4EA7D00B7C361 /* Products */,
64+
);
65+
sourceTree = "<group>";
66+
};
67+
A908F1891CEEFE0F00BE1488 /* BleManager */ = {
68+
isa = PBXGroup;
69+
children = (
70+
A908F18A1CEEFEB500BE1488 /* NSData+Conversion.h */,
71+
A908F18B1CEEFEB500BE1488 /* NSData+Conversion.m */,
72+
A908F18C1CEEFEB500BE1488 /* CBPeripheral+Extensions.h */,
73+
A908F18D1CEEFEB500BE1488 /* CBPeripheral+Extensions.m */,
74+
A908F18E1CEEFEB500BE1488 /* BLECommandContext.h */,
75+
A908F18F1CEEFEB500BE1488 /* BLECommandContext.m */,
76+
A908F1901CEEFEB500BE1488 /* BleManager.h */,
77+
A908F1911CEEFEB500BE1488 /* BleManager.m */,
78+
);
79+
name = BleManager;
80+
sourceTree = "<group>";
81+
};
82+
/* End PBXGroup section */
83+
84+
/* Begin PBXNativeTarget section */
85+
58B511DA1A9E6C8500147676 /* BleManager */ = {
86+
isa = PBXNativeTarget;
87+
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "BleManager" */;
88+
buildPhases = (
89+
58B511D71A9E6C8500147676 /* Sources */,
90+
58B511D81A9E6C8500147676 /* Frameworks */,
91+
58B511D91A9E6C8500147676 /* CopyFiles */,
92+
);
93+
buildRules = (
94+
);
95+
dependencies = (
96+
);
97+
name = BleManager;
98+
productName = RCTDataManager;
99+
productReference = 134814201AA4EA6300B7C361 /* libBleManager.a */;
100+
productType = "com.apple.product-type.library.static";
101+
};
102+
/* End PBXNativeTarget section */
103+
104+
/* Begin PBXProject section */
105+
58B511D31A9E6C8500147676 /* Project object */ = {
106+
isa = PBXProject;
107+
attributes = {
108+
LastUpgradeCheck = 0610;
109+
ORGANIZATIONNAME = Facebook;
110+
TargetAttributes = {
111+
58B511DA1A9E6C8500147676 = {
112+
CreatedOnToolsVersion = 6.1.1;
113+
};
114+
};
115+
};
116+
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "BleManager" */;
117+
compatibilityVersion = "Xcode 3.2";
118+
developmentRegion = English;
119+
hasScannedForEncodings = 0;
120+
knownRegions = (
121+
en,
122+
);
123+
mainGroup = 58B511D21A9E6C8500147676;
124+
productRefGroup = 58B511D21A9E6C8500147676;
125+
projectDirPath = "";
126+
projectRoot = "";
127+
targets = (
128+
58B511DA1A9E6C8500147676 /* BleManager */,
129+
);
130+
};
131+
/* End PBXProject section */
132+
133+
/* Begin PBXSourcesBuildPhase section */
134+
58B511D71A9E6C8500147676 /* Sources */ = {
135+
isa = PBXSourcesBuildPhase;
136+
buildActionMask = 2147483647;
137+
files = (
138+
A908F1931CEEFEB500BE1488 /* CBPeripheral+Extensions.m in Sources */,
139+
A908F1951CEEFEB500BE1488 /* BleManager.m in Sources */,
140+
A908F1921CEEFEB500BE1488 /* NSData+Conversion.m in Sources */,
141+
A908F1941CEEFEB500BE1488 /* BLECommandContext.m in Sources */,
142+
);
143+
runOnlyForDeploymentPostprocessing = 0;
144+
};
145+
/* End PBXSourcesBuildPhase section */
146+
147+
/* Begin XCBuildConfiguration section */
148+
58B511ED1A9E6C8500147676 /* Debug */ = {
149+
isa = XCBuildConfiguration;
150+
buildSettings = {
151+
ALWAYS_SEARCH_USER_PATHS = NO;
152+
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
153+
CLANG_CXX_LIBRARY = "libc++";
154+
CLANG_ENABLE_MODULES = YES;
155+
CLANG_ENABLE_OBJC_ARC = YES;
156+
CLANG_WARN_BOOL_CONVERSION = YES;
157+
CLANG_WARN_CONSTANT_CONVERSION = YES;
158+
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
159+
CLANG_WARN_EMPTY_BODY = YES;
160+
CLANG_WARN_ENUM_CONVERSION = YES;
161+
CLANG_WARN_INT_CONVERSION = YES;
162+
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
163+
CLANG_WARN_UNREACHABLE_CODE = YES;
164+
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
165+
COPY_PHASE_STRIP = NO;
166+
ENABLE_STRICT_OBJC_MSGSEND = YES;
167+
GCC_C_LANGUAGE_STANDARD = gnu99;
168+
GCC_DYNAMIC_NO_PIC = NO;
169+
GCC_OPTIMIZATION_LEVEL = 0;
170+
GCC_PREPROCESSOR_DEFINITIONS = (
171+
"DEBUG=1",
172+
"$(inherited)",
173+
);
174+
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
175+
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
176+
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
177+
GCC_WARN_UNDECLARED_SELECTOR = YES;
178+
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
179+
GCC_WARN_UNUSED_FUNCTION = YES;
180+
GCC_WARN_UNUSED_VARIABLE = YES;
181+
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
182+
MTL_ENABLE_DEBUG_INFO = YES;
183+
ONLY_ACTIVE_ARCH = YES;
184+
SDKROOT = iphoneos;
185+
USER_HEADER_SEARCH_PATHS = "$(inherited) $(SRCROOT)/../react-native/React/** $(SRCROOT)/node_modules/react-native/React/**";
186+
};
187+
name = Debug;
188+
};
189+
58B511EE1A9E6C8500147676 /* Release */ = {
190+
isa = XCBuildConfiguration;
191+
buildSettings = {
192+
ALWAYS_SEARCH_USER_PATHS = NO;
193+
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
194+
CLANG_CXX_LIBRARY = "libc++";
195+
CLANG_ENABLE_MODULES = YES;
196+
CLANG_ENABLE_OBJC_ARC = YES;
197+
CLANG_WARN_BOOL_CONVERSION = YES;
198+
CLANG_WARN_CONSTANT_CONVERSION = YES;
199+
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
200+
CLANG_WARN_EMPTY_BODY = YES;
201+
CLANG_WARN_ENUM_CONVERSION = YES;
202+
CLANG_WARN_INT_CONVERSION = YES;
203+
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
204+
CLANG_WARN_UNREACHABLE_CODE = YES;
205+
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
206+
COPY_PHASE_STRIP = YES;
207+
ENABLE_NS_ASSERTIONS = NO;
208+
ENABLE_STRICT_OBJC_MSGSEND = YES;
209+
GCC_C_LANGUAGE_STANDARD = gnu99;
210+
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
211+
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
212+
GCC_WARN_UNDECLARED_SELECTOR = YES;
213+
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
214+
GCC_WARN_UNUSED_FUNCTION = YES;
215+
GCC_WARN_UNUSED_VARIABLE = YES;
216+
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
217+
MTL_ENABLE_DEBUG_INFO = NO;
218+
SDKROOT = iphoneos;
219+
USER_HEADER_SEARCH_PATHS = "$(inherited) $(SRCROOT)/../react-native/React/** $(SRCROOT)/node_modules/react-native/React/**";
220+
VALIDATE_PRODUCT = YES;
221+
};
222+
name = Release;
223+
};
224+
58B511F01A9E6C8500147676 /* Debug */ = {
225+
isa = XCBuildConfiguration;
226+
buildSettings = {
227+
HEADER_SEARCH_PATHS = (
228+
"$(inherited)",
229+
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
230+
"$(SRCROOT)/../../React/**",
231+
"$(SRCROOT)/../../node_modules/react-native/React/**",
232+
);
233+
LIBRARY_SEARCH_PATHS = "$(inherited)";
234+
OTHER_LDFLAGS = "-ObjC";
235+
PRODUCT_NAME = BleManager;
236+
SKIP_INSTALL = YES;
237+
};
238+
name = Debug;
239+
};
240+
58B511F11A9E6C8500147676 /* Release */ = {
241+
isa = XCBuildConfiguration;
242+
buildSettings = {
243+
HEADER_SEARCH_PATHS = (
244+
"$(inherited)",
245+
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
246+
"$(SRCROOT)/../../React/**",
247+
);
248+
LIBRARY_SEARCH_PATHS = "$(inherited)";
249+
OTHER_LDFLAGS = "-ObjC";
250+
PRODUCT_NAME = BleManager;
251+
SKIP_INSTALL = YES;
252+
};
253+
name = Release;
254+
};
255+
/* End XCBuildConfiguration section */
256+
257+
/* Begin XCConfigurationList section */
258+
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "BleManager" */ = {
259+
isa = XCConfigurationList;
260+
buildConfigurations = (
261+
58B511ED1A9E6C8500147676 /* Debug */,
262+
58B511EE1A9E6C8500147676 /* Release */,
263+
);
264+
defaultConfigurationIsVisible = 0;
265+
defaultConfigurationName = Release;
266+
};
267+
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "BleManager" */ = {
268+
isa = XCConfigurationList;
269+
buildConfigurations = (
270+
58B511F01A9E6C8500147676 /* Debug */,
271+
58B511F11A9E6C8500147676 /* Release */,
272+
);
273+
defaultConfigurationIsVisible = 0;
274+
defaultConfigurationName = Release;
275+
};
276+
/* End XCConfigurationList section */
277+
};
278+
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
279+
}

‎ios/BleManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "0730"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "58B511DA1A9E6C8500147676"
18+
BuildableName = "libBleManager.a"
19+
BlueprintName = "BleManager"
20+
ReferencedContainer = "container:BleManager.xcodeproj">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES">
30+
<Testables>
31+
</Testables>
32+
<AdditionalOptions>
33+
</AdditionalOptions>
34+
</TestAction>
35+
<LaunchAction
36+
buildConfiguration = "Debug"
37+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
38+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
39+
launchStyle = "0"
40+
useCustomWorkingDirectory = "NO"
41+
ignoresPersistentStateOnLaunch = "NO"
42+
debugDocumentVersioning = "YES"
43+
debugServiceExtension = "internal"
44+
allowLocationSimulation = "YES">
45+
<MacroExpansion>
46+
<BuildableReference
47+
BuildableIdentifier = "primary"
48+
BlueprintIdentifier = "58B511DA1A9E6C8500147676"
49+
BuildableName = "libBleManager.a"
50+
BlueprintName = "BleManager"
51+
ReferencedContainer = "container:BleManager.xcodeproj">
52+
</BuildableReference>
53+
</MacroExpansion>
54+
<AdditionalOptions>
55+
</AdditionalOptions>
56+
</LaunchAction>
57+
<ProfileAction
58+
buildConfiguration = "Release"
59+
shouldUseLaunchSchemeArgsEnv = "YES"
60+
savedToolIdentifier = ""
61+
useCustomWorkingDirectory = "NO"
62+
debugDocumentVersioning = "YES">
63+
<MacroExpansion>
64+
<BuildableReference
65+
BuildableIdentifier = "primary"
66+
BlueprintIdentifier = "58B511DA1A9E6C8500147676"
67+
BuildableName = "libBleManager.a"
68+
BlueprintName = "BleManager"
69+
ReferencedContainer = "container:BleManager.xcodeproj">
70+
</BuildableReference>
71+
</MacroExpansion>
72+
</ProfileAction>
73+
<AnalyzeAction
74+
buildConfiguration = "Debug">
75+
</AnalyzeAction>
76+
<ArchiveAction
77+
buildConfiguration = "Release"
78+
revealArchiveInOrganizer = "YES">
79+
</ArchiveAction>
80+
</Scheme>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>SchemeUserState</key>
6+
<dict>
7+
<key>BleManagerModule.xcscheme</key>
8+
<dict>
9+
<key>orderHint</key>
10+
<integer>0</integer>
11+
</dict>
12+
</dict>
13+
<key>SuppressBuildableAutocreation</key>
14+
<dict>
15+
<key>58B511DA1A9E6C8500147676</key>
16+
<dict>
17+
<key>primary</key>
18+
<true/>
19+
</dict>
20+
</dict>
21+
</dict>
22+
</plist>

‎ios/CBPeripheral+Extensions.h

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#import <objc/runtime.h>
2+
#import <Foundation/Foundation.h>
3+
#import <CoreBluetooth/CoreBluetooth.h>
4+
#import "CBPeripheral+Extensions.h"
5+
6+
7+
@interface CBPeripheral(com_megster_ble_extension)
8+
9+
@property (nonatomic, retain) NSDictionary *advertising;
10+
@property (nonatomic, retain) NSNumber *advertisementRSSI;
11+
12+
-(void)setAdvertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber*)rssi;
13+
-(NSDictionary *)asDictionary;
14+
-(NSString *)uuidAsString;
15+
16+
@end

‎ios/CBPeripheral+Extensions.m

+265
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
#import "CBPeripheral+Extensions.h"
2+
3+
static char ADVERTISING_IDENTIFER;
4+
static char ADVERTISEMENT_RSSI_IDENTIFER;
5+
6+
@implementation CBPeripheral(com_megster_ble_extension)
7+
8+
-(NSString *)uuidAsString {
9+
if (self.identifier.UUIDString) {
10+
return self.identifier.UUIDString;
11+
} else {
12+
return @"";
13+
}
14+
}
15+
16+
17+
-(NSDictionary *)asDictionary {
18+
NSString *uuidString = NULL;
19+
if (self.identifier.UUIDString) {
20+
uuidString = self.identifier.UUIDString;
21+
} else {
22+
uuidString = @"";
23+
}
24+
25+
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
26+
[dictionary setObject: uuidString forKey: @"id"];
27+
28+
if ([self name]) {
29+
[dictionary setObject: [self name] forKey: @"name"];
30+
}
31+
32+
/*
33+
if ([self RSSI]) {
34+
[dictionary setObject: [self RSSI] forKey: @"rssi"];
35+
} else if ([self advertisementRSSI]) {
36+
[dictionary setObject: [self advertisementRSSI] forKey: @"rssi"];
37+
}*/
38+
39+
if ([self advertising]) {
40+
[dictionary setObject: [self advertising] forKey: @"advertising"];
41+
}
42+
43+
if([[self services] count] > 0) {
44+
[self serviceAndCharacteristicInfo: dictionary];
45+
}
46+
47+
return dictionary;
48+
49+
}
50+
51+
// AdvertisementData is from didDiscoverPeripheral. RFduino advertises a service name in the Mfg Data Field.
52+
-(void)setAdvertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)rssi{
53+
54+
[self setAdvertising:[self serializableAdvertisementData: advertisementData]];
55+
[self setAdvertisementRSSI: rssi];
56+
57+
}
58+
59+
// Translates the Advertisement Data from didDiscoverPeripheral into a structure that can be serialized as JSON
60+
//
61+
// This version keeps the iOS constants for keys, future versions could create more friendly keys
62+
//
63+
// Advertisement Data from a Peripheral could look something like
64+
//
65+
// advertising = {
66+
// kCBAdvDataChannel = 39;
67+
// kCBAdvDataIsConnectable = 1;
68+
// kCBAdvDataLocalName = foo;
69+
// kCBAdvDataManufacturerData = {
70+
// CDVType = ArrayBuffer;
71+
// data = "AABoZWxsbw==";
72+
// };
73+
// kCBAdvDataServiceData = {
74+
// FED8 = {
75+
// CDVType = ArrayBuffer;
76+
// data = "ACAAYWJjBw==";
77+
// };
78+
// };
79+
// kCBAdvDataServiceUUIDs = (
80+
// FED8
81+
// );
82+
// kCBAdvDataTxPowerLevel = 32;
83+
//};
84+
- (NSDictionary *) serializableAdvertisementData: (NSDictionary *) advertisementData {
85+
86+
NSMutableDictionary *dict = [advertisementData mutableCopy];
87+
88+
// Service Data is a dictionary of CBUUID and NSData
89+
// Convert to String keys with Array Buffer values
90+
NSMutableDictionary *serviceData = [dict objectForKey:CBAdvertisementDataServiceDataKey];
91+
if (serviceData) {
92+
NSLog(@"%@", serviceData);
93+
94+
for(CBUUID *key in serviceData) {
95+
[serviceData setObject:dataToArrayBuffer([serviceData objectForKey:key]) forKey:[key UUIDString]];
96+
[serviceData removeObjectForKey:key];
97+
}
98+
}
99+
100+
// Create a new list of Service UUIDs as Strings instead of CBUUIDs
101+
NSMutableArray *serviceUUIDs = [dict objectForKey:CBAdvertisementDataServiceUUIDsKey];
102+
NSMutableArray *serviceUUIDStrings;
103+
if (serviceUUIDs) {
104+
serviceUUIDStrings = [[NSMutableArray alloc] initWithCapacity:serviceUUIDs.count];
105+
106+
for (CBUUID *uuid in serviceUUIDs) {
107+
[serviceUUIDStrings addObject:[uuid UUIDString]];
108+
}
109+
110+
// replace the UUID list with list of strings
111+
[dict removeObjectForKey:CBAdvertisementDataServiceUUIDsKey];
112+
[dict setObject:serviceUUIDStrings forKey:CBAdvertisementDataServiceUUIDsKey];
113+
114+
}
115+
116+
// Solicited Services UUIDs is an array of CBUUIDs, convert into Strings
117+
NSMutableArray *solicitiedServiceUUIDs = [dict objectForKey:CBAdvertisementDataSolicitedServiceUUIDsKey];
118+
NSMutableArray *solicitiedServiceUUIDStrings;
119+
if (solicitiedServiceUUIDs) {
120+
// NSLog(@"%@", solicitiedServiceUUIDs);
121+
solicitiedServiceUUIDStrings = [[NSMutableArray alloc] initWithCapacity:solicitiedServiceUUIDs.count];
122+
123+
for (CBUUID *uuid in solicitiedServiceUUIDs) {
124+
[solicitiedServiceUUIDStrings addObject:[uuid UUIDString]];
125+
}
126+
127+
// replace the UUID list with list of strings
128+
[dict removeObjectForKey:CBAdvertisementDataSolicitedServiceUUIDsKey];
129+
[dict setObject:solicitiedServiceUUIDStrings forKey:CBAdvertisementDataSolicitedServiceUUIDsKey];
130+
}
131+
132+
// Convert the manufacturer data
133+
NSData *mfgData = [dict objectForKey:CBAdvertisementDataManufacturerDataKey];
134+
if (mfgData) {
135+
[dict setObject:dataToArrayBuffer([dict objectForKey:CBAdvertisementDataManufacturerDataKey]) forKey:CBAdvertisementDataManufacturerDataKey];
136+
}
137+
138+
return dict;
139+
}
140+
141+
// Put the service, characteristic, and descriptor data in a format that will serialize through JSON
142+
// sending a list of services and a list of characteristics
143+
- (void) serviceAndCharacteristicInfo: (NSMutableDictionary *) info {
144+
145+
NSMutableArray *serviceList = [NSMutableArray new];
146+
NSMutableArray *characteristicList = [NSMutableArray new];
147+
148+
// This can move into the CBPeripherial Extension
149+
for (CBService *service in [self services]) {
150+
[serviceList addObject:[[service UUID] UUIDString]];
151+
for (CBCharacteristic *characteristic in service.characteristics) {
152+
NSMutableDictionary *characteristicDictionary = [NSMutableDictionary new];
153+
[characteristicDictionary setObject:[[service UUID] UUIDString] forKey:@"service"];
154+
[characteristicDictionary setObject:[[characteristic UUID] UUIDString] forKey:@"characteristic"];
155+
156+
if ([characteristic value]) {
157+
[characteristicDictionary setObject:dataToArrayBuffer([characteristic value]) forKey:@"value"];
158+
}
159+
if ([characteristic properties]) {
160+
//[characteristicDictionary setObject:[NSNumber numberWithInt:[characteristic properties]] forKey:@"propertiesValue"];
161+
[characteristicDictionary setObject:[self decodeCharacteristicProperties:characteristic] forKey:@"properties"];
162+
}
163+
// permissions only exist on CBMutableCharacteristics
164+
[characteristicDictionary setObject:[NSNumber numberWithBool:[characteristic isNotifying]] forKey:@"isNotifying"];
165+
[characteristicList addObject:characteristicDictionary];
166+
167+
// descriptors always seem to be nil, probably a bug here
168+
NSMutableArray *descriptorList = [NSMutableArray new];
169+
for (CBDescriptor *descriptor in characteristic.descriptors) {
170+
NSMutableDictionary *descriptorDictionary = [NSMutableDictionary new];
171+
[descriptorDictionary setObject:[[descriptor UUID] UUIDString] forKey:@"descriptor"];
172+
if ([descriptor value]) { // should always have a value?
173+
[descriptorDictionary setObject:[descriptor value] forKey:@"value"];
174+
}
175+
[descriptorList addObject:descriptorDictionary];
176+
}
177+
if ([descriptorList count] > 0) {
178+
[characteristicDictionary setObject:descriptorList forKey:@"descriptors"];
179+
}
180+
181+
}
182+
}
183+
184+
[info setObject:serviceList forKey:@"services"];
185+
[info setObject:characteristicList forKey:@"characteristics"];
186+
187+
}
188+
189+
-(NSArray *) decodeCharacteristicProperties: (CBCharacteristic *) characteristic {
190+
NSMutableArray *props = [NSMutableArray new];
191+
192+
CBCharacteristicProperties p = [characteristic properties];
193+
194+
// NOTE: props strings need to be consistent across iOS and Android
195+
if ((p & CBCharacteristicPropertyBroadcast) != 0x0) {
196+
[props addObject:@"Broadcast"];
197+
}
198+
199+
if ((p & CBCharacteristicPropertyRead) != 0x0) {
200+
[props addObject:@"Read"];
201+
}
202+
203+
if ((p & CBCharacteristicPropertyWriteWithoutResponse) != 0x0) {
204+
[props addObject:@"WriteWithoutResponse"];
205+
}
206+
207+
if ((p & CBCharacteristicPropertyWrite) != 0x0) {
208+
[props addObject:@"Write"];
209+
}
210+
211+
if ((p & CBCharacteristicPropertyNotify) != 0x0) {
212+
[props addObject:@"Notify"];
213+
}
214+
215+
if ((p & CBCharacteristicPropertyIndicate) != 0x0) {
216+
[props addObject:@"Indicate"];
217+
}
218+
219+
if ((p & CBCharacteristicPropertyAuthenticatedSignedWrites) != 0x0) {
220+
[props addObject:@"AutheticateSignedWrites"];
221+
}
222+
223+
if ((p & CBCharacteristicPropertyExtendedProperties) != 0x0) {
224+
[props addObject:@"ExtendedProperties"];
225+
}
226+
227+
if ((p & CBCharacteristicPropertyNotifyEncryptionRequired) != 0x0) {
228+
[props addObject:@"NotifyEncryptionRequired"];
229+
}
230+
231+
if ((p & CBCharacteristicPropertyIndicateEncryptionRequired) != 0x0) {
232+
[props addObject:@"IndicateEncryptionRequired"];
233+
}
234+
235+
return props;
236+
}
237+
238+
// Borrowed from Cordova messageFromArrayBuffer since Cordova doesn't handle NSData in NSDictionary
239+
id dataToArrayBuffer(NSData* data)
240+
{
241+
return @{
242+
@"CDVType" : @"ArrayBuffer",
243+
@"data" :[data base64EncodedStringWithOptions:0]
244+
};
245+
}
246+
247+
248+
-(void)setAdvertising:(NSDictionary *)newAdvertisingValue{
249+
objc_setAssociatedObject(self, &ADVERTISING_IDENTIFER, newAdvertisingValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
250+
}
251+
252+
-(NSString*)advertising{
253+
return objc_getAssociatedObject(self, &ADVERTISING_IDENTIFER);
254+
}
255+
256+
257+
-(void)setAdvertisementRSSI:(NSNumber *)newAdvertisementRSSIValue {
258+
objc_setAssociatedObject(self, &ADVERTISEMENT_RSSI_IDENTIFER, newAdvertisementRSSIValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
259+
}
260+
261+
-(NSString*)advertisementRSSI{
262+
return objc_getAssociatedObject(self, &ADVERTISEMENT_RSSI_IDENTIFER);
263+
}
264+
265+
@end

‎ios/NSData+Conversion.h

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#import <Foundation/Foundation.h>
2+
3+
@interface NSData (NSData_Conversion)
4+
5+
#pragma mark - String Conversion
6+
- (NSString *)hexadecimalString;
7+
8+
@end

‎ios/NSData+Conversion.m

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#import "NSData+Conversion.h"
2+
3+
@implementation NSData (NSData_Conversion)
4+
5+
#pragma mark - String Conversion
6+
- (NSString *)hexadecimalString {
7+
/* Returns hexadecimal string of NSData. Empty string if data is empty. */
8+
9+
const unsigned char *dataBuffer = (const unsigned char *)[self bytes];
10+
11+
if (!dataBuffer)
12+
return [NSString string];
13+
14+
NSUInteger dataLength = [self length];
15+
NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
16+
17+
for (int i = 0; i < dataLength; ++i)
18+
[hexString appendString:[NSString stringWithFormat:@"%02x", (unsigned int)dataBuffer[i]]];
19+
20+
return [NSString stringWithString:hexString];
21+
}
22+
23+
@end

‎package.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "BleManager",
3+
"version": "0.0.1",
4+
"keywords": ["
5+
react-native",
6+
"android",
7+
"ios",
8+
"ble",
9+
"bluetooth"
10+
],
11+
"author": {
12+
"name": "Innove",
13+
"url": "https://github.com/innoveit"
14+
}
15+
}

0 commit comments

Comments
 (0)
Please sign in to comment.