Skip to content

Commit 0c76a84

Browse files
committed
Added readCharacteristic ability
1 parent 72758ce commit 0c76a84

11 files changed

+611
-47
lines changed

android/src/main/java/com/pauldemarco/flutterblue/FlutterBluePlugin.java

+151-13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import android.bluetooth.BluetoothGattCallback;
88
import android.bluetooth.BluetoothGattCharacteristic;
99
import android.bluetooth.BluetoothGattDescriptor;
10+
import android.bluetooth.BluetoothGattService;
1011
import android.bluetooth.BluetoothManager;
1112
import android.bluetooth.BluetoothProfile;
1213
import android.bluetooth.le.ScanCallback;
@@ -18,12 +19,13 @@
1819
import android.os.Build;
1920
import android.util.Log;
2021

22+
import com.google.protobuf.ByteString;
2123
import com.google.protobuf.InvalidProtocolBufferException;
2224

2325
import java.util.HashMap;
24-
import java.util.Iterator;
2526
import java.util.List;
2627
import java.util.Map;
28+
import java.util.UUID;
2729

2830
import io.flutter.plugin.common.EventChannel;
2931
import io.flutter.plugin.common.EventChannel.EventSink;
@@ -45,6 +47,8 @@ public class FlutterBluePlugin implements MethodCallHandler {
4547
private final MethodChannel channel;
4648
private final EventChannel stateChannel;
4749
private final EventChannel scanResultChannel;
50+
private final EventChannel servicesDiscoveredChannel;
51+
private final EventChannel characteristicReadChannel;
4852
private final BluetoothManager mBluetoothManager;
4953
private BluetoothAdapter mBluetoothAdapter;
5054
private final Map<String, Result> mConnectionRequests = new HashMap<>();
@@ -62,11 +66,15 @@ public static void registerWith(Registrar registrar) {
6266
this.channel = new MethodChannel(registrar.messenger(), NAMESPACE+"/methods");
6367
this.stateChannel = new EventChannel(registrar.messenger(), NAMESPACE+"/state");
6468
this.scanResultChannel = new EventChannel(registrar.messenger(), NAMESPACE+"/scanResult");
69+
this.servicesDiscoveredChannel = new EventChannel(registrar.messenger(), NAMESPACE+"/servicesDiscovered");
70+
this.characteristicReadChannel = new EventChannel(registrar.messenger(), NAMESPACE+"/characteristicRead");
6571
this.mBluetoothManager = (BluetoothManager) r.activity().getSystemService(Context.BLUETOOTH_SERVICE);
6672
this.mBluetoothAdapter = mBluetoothManager.getAdapter();
6773
channel.setMethodCallHandler(this);
6874
stateChannel.setStreamHandler(stateHandler);
6975
scanResultChannel.setStreamHandler(scanResultsHandler);
76+
servicesDiscoveredChannel.setStreamHandler(servicesDiscoveredHandler);
77+
characteristicReadChannel.setStreamHandler(characteristicReadHandler);
7078
}
7179

7280
@Override
@@ -142,7 +150,6 @@ public void onMethodCall(MethodCall call, Result result) {
142150

143151
case "connect":
144152
{
145-
printConnectionRequests();
146153
byte[] data = call.arguments();
147154
Protos.ConnectOptions options;
148155
try {
@@ -188,7 +195,6 @@ public void onMethodCall(MethodCall call, Result result) {
188195

189196
case "disconnect":
190197
{
191-
printConnectionRequests();
192198
String deviceId = (String)call.arguments;
193199
BluetoothGatt gattServer = mGattServers.remove(deviceId);
194200
if(gattServer != null) {
@@ -204,20 +210,94 @@ public void onMethodCall(MethodCall call, Result result) {
204210
break;
205211
}
206212

207-
default:
213+
case "discoverServices":
208214
{
209-
result.notImplemented();
215+
String deviceId = (String)call.arguments;
216+
BluetoothGatt gattServer = mGattServers.get(deviceId);
217+
if(gattServer == null) {
218+
result.error("discover_services_error", "no instance of BluetoothGatt, have you connected first?", null);
219+
return;
220+
}
221+
if(gattServer.discoverServices()) {
222+
result.success(null);
223+
} else {
224+
result.error("discover_services_error", "unknown reason", null);
225+
}
210226
break;
211227
}
212-
}
213-
}
214228

229+
case "services":
230+
{
231+
String deviceId = (String)call.arguments;
232+
BluetoothGatt gattServer = mGattServers.get(deviceId);
233+
if(gattServer == null) {
234+
result.error("get_services_error", "no instance of BluetoothGatt, have you connected first?", null);
235+
return;
236+
}
237+
if(gattServer.getServices().isEmpty()) {
238+
result.error("get_services_error", "services are empty, have you called discoverServices() yet?", null);
239+
return;
240+
}
241+
Protos.DiscoverServicesResult.Builder p = Protos.DiscoverServicesResult.newBuilder();
242+
p.setRemoteId(deviceId);
243+
for(BluetoothGattService s : gattServer.getServices()){
244+
p.addServices(ProtoMaker.from(gattServer.getDevice(), s));
245+
}
246+
result.success(p.build().toByteArray());
247+
break;
248+
}
215249

216-
private void printConnectionRequests() {
217-
synchronized (mConnectionRequests) {
218-
for(Iterator<Map.Entry<String, Result>> it = mConnectionRequests.entrySet().iterator(); it.hasNext(); ) {
219-
Map.Entry<String, Result> entry = it.next();
220-
Log.d(TAG, "printConnectionRequests: deviceId:" + entry.getKey());
250+
case "readCharacteristic":
251+
{
252+
byte[] data = call.arguments();
253+
Protos.ReadAttributeRequest request;
254+
try {
255+
request = Protos.ReadAttributeRequest.newBuilder().mergeFrom(data).build();
256+
} catch (InvalidProtocolBufferException e) {
257+
result.error("RuntimeException", e.getMessage(), e);
258+
break;
259+
}
260+
BluetoothGatt gattServer = mGattServers.get(request.getRemoteId());
261+
if(gattServer == null) {
262+
result.error("read_characteristic_error", "no instance of BluetoothGatt, have you connected first?", null);
263+
return;
264+
}
265+
BluetoothGattService primaryService = gattServer.getService(UUID.fromString(request.getServiceUuid()));
266+
if(primaryService == null) {
267+
result.error("read_characteristic_error", "service (" + request.getServiceUuid() + ") could not be located on the device", null);
268+
return;
269+
}
270+
BluetoothGattService secondaryService = null;
271+
if(request.getSecondaryServiceUuid().length() > 0) {
272+
for(BluetoothGattService s : primaryService.getIncludedServices()){
273+
if(s.getUuid().equals(UUID.fromString(request.getSecondaryServiceUuid()))){
274+
secondaryService = s;
275+
}
276+
}
277+
if(secondaryService == null) {
278+
result.error("read_characteristic_error", "secondary service (" + request.getSecondaryServiceUuid() + ") could not be located on the device", null);
279+
return;
280+
}
281+
}
282+
BluetoothGattService service = (secondaryService != null) ? secondaryService : primaryService;
283+
BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(request.getUuid()));
284+
if(characteristic == null) {
285+
result.error("read_characteristic_error", "characteristic (" + request.getUuid() + ") could not be located in the service ("+service.getUuid().toString()+")", null);
286+
return;
287+
}
288+
289+
if(gattServer.readCharacteristic(characteristic)) {
290+
result.success(null);
291+
} else {
292+
result.error("read_characteristic_error", "unknown reason. occurs if readCharacteristic was called before last read finished.", null);
293+
}
294+
break;
295+
}
296+
297+
default:
298+
{
299+
result.notImplemented();
300+
break;
221301
}
222302
}
223303
}
@@ -373,6 +453,32 @@ public void onCancel(Object o) {
373453
}
374454
};
375455

456+
private EventSink servicesDiscoveredSink;
457+
private final StreamHandler servicesDiscoveredHandler = new StreamHandler() {
458+
@Override
459+
public void onListen(Object o, EventChannel.EventSink eventSink) {
460+
servicesDiscoveredSink = eventSink;
461+
}
462+
463+
@Override
464+
public void onCancel(Object o) {
465+
servicesDiscoveredSink = null;
466+
}
467+
};
468+
469+
private EventSink characteristicReadSink;
470+
private final StreamHandler characteristicReadHandler = new StreamHandler() {
471+
@Override
472+
public void onListen(Object o, EventChannel.EventSink eventSink) {
473+
characteristicReadSink = eventSink;
474+
}
475+
476+
@Override
477+
public void onCancel(Object o) {
478+
characteristicReadSink = null;
479+
}
480+
};
481+
376482
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
377483
@Override
378484
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
@@ -398,12 +504,44 @@ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState
398504

399505
@Override
400506
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
401-
Log.d(TAG, "onServicesDiscovered: ");
507+
Log.d(TAG, "onServicesDiscovered: " + gatt.getServices().size() + " sink:" + servicesDiscoveredSink);
508+
if(servicesDiscoveredSink != null) {
509+
Protos.DiscoverServicesResult.Builder p = Protos.DiscoverServicesResult.newBuilder();
510+
p.setRemoteId(gatt.getDevice().getAddress());
511+
for(BluetoothGattService s : gatt.getServices()) {
512+
p.addServices(ProtoMaker.from(gatt.getDevice(), s));
513+
}
514+
servicesDiscoveredSink.success(p.build().toByteArray());
515+
}
402516
}
403517

404518
@Override
405519
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
406520
Log.d(TAG, "onCharacteristicRead: ");
521+
if(characteristicReadSink != null) {
522+
// Rebuild the ReadAttributeRequest and send back along with response
523+
Protos.ReadAttributeRequest.Builder q = Protos.ReadAttributeRequest.newBuilder();
524+
q.setRemoteId(gatt.getDevice().getAddress());
525+
q.setUuid(characteristic.getUuid().toString());
526+
if(characteristic.getService().getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY) {
527+
q.setServiceUuid(characteristic.getService().getUuid().toString());
528+
} else {
529+
// Reverse search to find service
530+
for(BluetoothGattService s : gatt.getServices()) {
531+
for(BluetoothGattService ss : s.getIncludedServices()) {
532+
if(ss.getUuid().equals(characteristic.getService().getUuid())){
533+
q.setServiceUuid(s.getUuid().toString());
534+
q.setSecondaryServiceUuid(ss.getUuid().toString());
535+
break;
536+
}
537+
}
538+
}
539+
}
540+
Protos.ReadAttributeResponse.Builder p = Protos.ReadAttributeResponse.newBuilder();
541+
p.setRequest(q);
542+
p.setValue(ByteString.copyFrom(characteristic.getValue()));
543+
characteristicReadSink.success(p.build().toByteArray());
544+
}
407545
}
408546

409547
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.pauldemarco.flutterblue;
2+
3+
import android.bluetooth.BluetoothDevice;
4+
import android.bluetooth.BluetoothGattCharacteristic;
5+
import android.bluetooth.BluetoothGattDescriptor;
6+
import android.bluetooth.BluetoothGattService;
7+
8+
import com.google.protobuf.ByteString;
9+
10+
import java.util.UUID;
11+
12+
/**
13+
* Created by paul on 8/31/17.
14+
*/
15+
16+
public class ProtoMaker {
17+
18+
private static final UUID CCCD_UUID = UUID.fromString("000002902-0000-1000-8000-00805f9b34fb");
19+
20+
static Protos.BluetoothService from(BluetoothDevice device, BluetoothGattService service) {
21+
Protos.BluetoothService.Builder p = Protos.BluetoothService.newBuilder();
22+
p.setRemoteId(device.getAddress());
23+
p.setUuid(service.getUuid().toString());
24+
p.setIsPrimary(service.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY);
25+
for(BluetoothGattCharacteristic c : service.getCharacteristics()) {
26+
p.addCharacteristics(from(c));
27+
}
28+
for(BluetoothGattService s : service.getIncludedServices()) {
29+
p.addIncludedServices(from(device, s));
30+
}
31+
return p.build();
32+
}
33+
34+
static Protos.BluetoothCharacteristic from(BluetoothGattCharacteristic characteristic) {
35+
Protos.BluetoothCharacteristic.Builder p = Protos.BluetoothCharacteristic.newBuilder();
36+
p.setUuid(characteristic.getUuid().toString());
37+
p.setServiceUuid(characteristic.getService().getUuid().toString());
38+
p.setProperties(from(characteristic.getProperties()));
39+
if(characteristic.getValue() != null)
40+
p.setValue(ByteString.copyFrom(characteristic.getValue()));
41+
for(BluetoothGattDescriptor d : characteristic.getDescriptors()) {
42+
p.addDescriptors(from(d));
43+
if(d.getUuid() == CCCD_UUID) {
44+
if(d.getValue() != null && d.getValue().length > 0 && (d.getValue()[0] == 1 || d.getValue()[0] == 2)){
45+
p.setIsNotifying(true);
46+
}
47+
}
48+
}
49+
return p.build();
50+
}
51+
52+
static Protos.BluetoothDescriptor from(BluetoothGattDescriptor descriptor) {
53+
Protos.BluetoothDescriptor.Builder p = Protos.BluetoothDescriptor.newBuilder();
54+
p.setUuid(descriptor.getUuid().toString());
55+
p.setCharacteristicUuid(descriptor.getCharacteristic().getUuid().toString());
56+
if(descriptor.getValue() != null)
57+
p.setValue(ByteString.copyFrom(descriptor.getValue()));
58+
return p.build();
59+
}
60+
61+
static Protos.CharacteristicProperties from(int properties) {
62+
return Protos.CharacteristicProperties.newBuilder()
63+
.setBroadcast((properties & 1) != 0)
64+
.setRead((properties & 2) != 0)
65+
.setWriteWithoutResponse((properties & 4) != 0)
66+
.setWrite((properties & 8) != 0)
67+
.setNotify((properties & 16) != 0)
68+
.setIndicate((properties & 32) != 0)
69+
.setAuthenticatedSignedWrites((properties & 64) != 0)
70+
.setExtendedProperties((properties & 128) != 0)
71+
.setNotifyEncryptionRequired((properties & 256) != 0)
72+
.setIndicateEncryptionRequired((properties & 512) != 0)
73+
.build();
74+
}
75+
}

example/lib/scan_devices_page.dart

+28-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class _ScanDevicesPageState extends State<ScanDevicesPage> {
1919
List<ScanResult> scanResults = new List();
2020
bool isScanning = false;
2121
ScanResult activeScanResult;
22+
BluetoothDevice device;
2223

2324
@override
2425
void initState() {
@@ -78,18 +79,34 @@ class _ScanDevicesPageState extends State<ScanDevicesPage> {
7879

7980
_connect() async {
8081
print('Connect clicked!');
81-
BluetoothDevice device = await _flutterBlue.connect(new DeviceIdentifier('D4:35:2A:DD:54:C7'), autoConnect: false);
82+
device = await _flutterBlue.connect(new DeviceIdentifier('D4:35:2A:DD:54:C7'), autoConnect: false);
8283
print('Device connected: ${device.id} ${device.name} ${device.type}');
84+
List<BluetoothService> services = await device.discoverServices();
8385
}
8486

8587
_disconnect() async {
8688
print('Disconnect clicked!');
8789
await _flutterBlue.cancelConnection(new DeviceIdentifier('D4:35:2A:DD:54:C7'));
8890
}
8991

90-
_getState() async {
91-
BluetoothState state = await _flutterBlue.state;
92-
print(state);
92+
_readAll() async {
93+
var services = await device.services;
94+
for(int i=0;i<100;i++) {
95+
for (BluetoothService s in services) {
96+
print('${s.deviceId} Service discovered:: uuid=${s.uuid} isPrimary=${s
97+
.isPrimary}');
98+
for (BluetoothCharacteristic c in s.characteristics) {
99+
print('Reading characteristic ${c.uuid}');
100+
List<int> value = await device.readCharacteristic(c);
101+
print('Read: $value');
102+
}
103+
}
104+
}
105+
}
106+
107+
_getServices() async {
108+
var services = await device.services;
109+
services.forEach((s) => print('${s.deviceId} Service discovered:: uuid=${s.uuid} isPrimary=${s.isPrimary}'));
93110
}
94111

95112
_buildLinearProgressIndicator(BuildContext context) {
@@ -145,11 +162,15 @@ class _ScanDevicesPageState extends State<ScanDevicesPage> {
145162
),
146163
new RaisedButton(
147164
onPressed: () => _disconnect(),
148-
child: const Text('DISCONNECT'),
165+
child: const Text('DISC'),
166+
),
167+
new RaisedButton(
168+
onPressed: () => _readAll(),
169+
child: const Text('READ'),
149170
),
150171
new RaisedButton(
151-
onPressed: () => _getState(),
152-
child: const Text('STATE'),
172+
onPressed: () => _getServices(),
173+
child: const Text('SERVICES'),
153174
)
154175
],
155176
);

0 commit comments

Comments
 (0)