Skip to content

Commit

Permalink
#92 Implement Ble device discovery on Android (#98)
Browse files Browse the repository at this point in the history
* #92 Implement Ble device discovery

Co-authored-by: jose.pereda <[email protected]>
  • Loading branch information
José Pereda and jperedadnr authored Apr 16, 2020
1 parent 56e1d7c commit 72dcf6c
Show file tree
Hide file tree
Showing 12 changed files with 1,010 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ static Optional<BleService> create() {
*/
ObservableList<BleDevice> startScanningDevices();

/**
* Stops scanning for BLE devices
*/
void stopScanningDevices();

/**
* Connects to a given BLE device
* @param device The BleDevice to connect to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@

/**
* Android implementation of BleService
*
*/
*
*/
public class AndroidBleService implements BleService {

private static final Logger LOG = Logger.getLogger(AndroidBleService.class.getName());
private static final ObservableList<BleDevice> devices = FXCollections.observableArrayList();
private static final List<String> deviceNames = new LinkedList<>();
private static final List<String> profileNames = new LinkedList<>();
private static boolean debug;

private static Consumer<ScanDetection> callback;
Expand All @@ -69,11 +70,17 @@ public class AndroidBleService implements BleService {
System.loadLibrary("Ble");
LOG.fine("Loaded AndroidBleService");
}

public AndroidBleService() {
LOG.fine("Created AndroidBleService instance");
if (debug) {
enableDebug();
}
}

// BLE BEACONS

@Override
public void startScanning(Configuration region, Consumer<ScanDetection> callback) {
LOG.fine("AndroidBleService will start scanning");
AndroidBleService.callback = callback;
Expand All @@ -82,57 +89,110 @@ public void startScanning(Configuration region, Consumer<ScanDetection> callback
startObserver(uuids);
}

@Override
public void stopScanning() {
LOG.fine("AndroidBleService will stop scanning");
stopObserver();
}

@Override
public void startBroadcasting(UUID beaconUUID, int major, int minor, String identifier) {
startBroadcast(beaconUUID.toString(), major, minor, identifier);
}

@Override
public void stopBroadcasting() {
stopBroadcast();
}

// BLE DEVICES

@Override
public ObservableList<BleDevice> startScanningDevices() {
LOG.fine("AndroidBleService will start scanning devices");
devices.clear();
deviceNames.clear();
startScanningPeripherals();
return devices;
}

@Override
public void stopScanningDevices() {
stopScanningPeripherals();
}

@Override
public void connect(BleDevice device) {
System.err.println("[ABLE]");
if (!checkDevice(device)) {
return;
}
profileNames.clear();
device.getProfiles().clear();
doConnect(device.getName(), device.getAddress());
}

@Override
public void disconnect(BleDevice device) {
System.err.println("[ABLE]");
if (!checkDevice(device)) {
return;
}
doDisconnect(device.getName(), device.getAddress());
}

@Override
public void readCharacteristic(BleDevice device, UUID uuidProfile, UUID uuidCharacteristic) {
System.err.println("[ABLE]");
doRead(device.getAddress(), uuidProfile.toString(), uuidCharacteristic.toString());
}

@Override
public void writeCharacteristic(BleDevice device, UUID uuidProfile, UUID uuidCharacteristic, byte[] value) {
System.err.println("[ABLE]");
doWrite(device.getAddress(), uuidProfile.toString(), uuidCharacteristic.toString(), value);
}

@Override
public void subscribeCharacteristic(BleDevice device, UUID uuidProfile, UUID uuidCharacteristic) {
System.err.println("[ABLE]");
doSubscribe(device.getAddress(), uuidProfile.toString(), uuidCharacteristic.toString(), true);
}

@Override
public void unsubscribeCharacteristic(BleDevice device, UUID uuidProfile, UUID uuidCharacteristic) {
System.err.println("[ABLE]");
doSubscribe(device.getAddress(), uuidProfile.toString(), uuidCharacteristic.toString(), false);
}


public void startBroadcasting(UUID beaconUUID, int major, int minor, String identifier) {
startBroadcast(beaconUUID.toString(), major, minor, identifier);
private static boolean checkDevice(BleDevice device) {
if (device == null) {
return false;
}
if (device.getName() == null) {
if (debug) {
LOG.log(Level.INFO, "AndroidBleService: Device with null name not allowed");
}
return false;
}
final boolean check = deviceNames.contains(device.getName());
if (debug) {
LOG.log(Level.INFO, "AndroidBleService: Device with name " + device.getName() + " in device list: " + check);
}
return check;
}

/**
* Stop advertising the current iOS device as a Bluetooth beacon
*
* @since 4.0.7
*/
public void stopBroadcasting() {
stopBroadcast();
}
// native BLE Beacons
private static native void startObserver(String[] uuids);
private static native void stopObserver();
private static native void startBroadcast(String uuid, int major, int minor, String id);
private static native void stopBroadcast();
private static native void enableDebug();

// callback
// native BLE Devices
private static native void startScanningPeripherals();
private static native void stopScanningPeripherals();
private static native void doConnect(String name, String address);
private static native void doDisconnect(String name, String address);
private static native void doRead(String address, String profile, String characteristic);
private static native void doWrite(String address, String profile, String characteristic, byte[] value);
private static native void doSubscribe(String address, String profile, String characteristic, boolean value);

// callbacks BLE Beacons
private static void setDetection(String uuid, int major, int minor, int rssi, int proximity) {
ScanDetection detection = new ScanDetection();
detection.setUuid(uuid);
Expand All @@ -143,32 +203,143 @@ private static void setDetection(String uuid, int major, int minor, int rssi, in
Platform.runLater(() -> callback.accept(detection));
}

private static void gotPeripheral(String name, String uuid) {
// callbacks BLE Devices
private static void gotPeripheral(String name, String address) {
if ((name != null && deviceNames.contains(name)) ||
(name == null && uuid != null && deviceNames.contains(uuid))) {
(name == null && address != null && deviceNames.contains(address))) {
return;
}
if (name != null && uuid != null && deviceNames.contains(uuid)) {
deviceNames.remove(uuid);
devices.removeIf(d -> uuid.equals(d.getAddress()));
if (name != null && address != null && deviceNames.contains(address)) {
deviceNames.remove(address);
devices.removeIf(d -> address.equals(d.getAddress()));
}

if (debug) {
LOG.log(Level.INFO, String.format("AndroidBleService got peripheral named %s and uuid: %s", name, uuid));
LOG.log(Level.INFO, String.format("AndroidBleService got peripheral named %s and address: %s", name, address));
}
BleDevice dev = new BleDevice();
dev.setName(name);
dev.setAddress(uuid);
dev.setAddress(address);
Platform.runLater(() -> devices.add(dev));
deviceNames.add(name != null ? name : uuid);
deviceNames.add(name != null ? name : address);
}

private static void gotState(String name, String state) {
if (debug) {
LOG.log(Level.INFO, String.format("BLE device %s changed state to %s", name, state));
}

getDeviceByName(name).ifPresent(device ->
Platform.runLater(() -> device.setState(BleDevice.State.fromName(state))));
}

private static native void startScanningPeripherals();
private static native void startObserver(String[] uuids);
private static native void stopObserver();
private static native void startBroadcast(String uuid, int major, int minor, String id);
private static native void stopBroadcast();
private static void gotProfile(String name, String uuid, String type) {
if (debug) {
LOG.log(Level.INFO, String.format("BLE device has profile: %s with type: %s", uuid, type));
}

getDeviceByName(name).ifPresent(device -> {
if (!profileNames.contains(uuid)) {
profileNames.add(uuid);

// if profile is not included yet:
BleProfile bleProfile = new BleProfile();
bleProfile.setUuid(UUID.fromString(uuid));
bleProfile.setType(type);
if (debug) {
LOG.log(Level.INFO, String.format("AndroidBleService creating profile %s", uuid));
}
Platform.runLater(() -> device.getProfiles().add(bleProfile));
}
});
}

private static void gotCharacteristic(String name, String profileUuid, String charUuid, String properties) {
if (debug) {
LOG.log(Level.INFO, String.format("BLE profile %s has characteristic: %s with properties: %s", profileUuid, charUuid, properties));
}

getDeviceByName(name).ifPresent(device ->
device.getProfiles().stream()
.filter(p -> p.getUuid().toString().equalsIgnoreCase(profileUuid))
.findAny()
.ifPresent(p -> {
if (debug) {
LOG.log(Level.INFO, String.format("AndroidBleService updating profile with characteristic %s", charUuid));
}
boolean exists = false;
for (BleCharacteristic c : p.getCharacteristics()) {
if (c.getUuid().toString().equalsIgnoreCase(charUuid)) {
c.setProperties(properties);
exists = true;
break;
}
}
if (!exists) {
BleCharacteristic bleCharacteristic = new BleCharacteristic(UUID.fromString(charUuid));
bleCharacteristic.setProperties(properties);
Platform.runLater(() -> p.getCharacteristics().add(bleCharacteristic));
}
}));
}

private static void gotDescriptor(String name, String profileUuid, String charUuid, String descUuid, byte[] value) {
if (debug) {
LOG.log(Level.INFO, String.format("BLE profile %s has characteristic: %s with descriptor: %s and value %s", profileUuid, charUuid, descUuid, Arrays.toString(value)));
}

getDeviceByName(name).ifPresent(device ->
device.getProfiles().stream()
.filter(p -> p.getUuid().toString().equalsIgnoreCase(profileUuid))
.findAny()
.ifPresent(p -> {
if (debug) {
LOG.log(Level.INFO, String.format("AndroidBleService updating profile with descriptor %s and value %s", descUuid, Arrays.toString(value)));
}
p.getCharacteristics().stream()
.filter(c -> c.getUuid().toString().equalsIgnoreCase(charUuid))
.findAny()
.ifPresent(c -> c.getDescriptors().stream()
.filter(d -> d.getUuid().toString().equalsIgnoreCase(descUuid))
.findAny()
.ifPresentOrElse(d -> d.setValue(value),
() -> {
BleDescriptor d = new BleDescriptor();
d.setUuid(UUID.fromString(descUuid));
d.setValue(value);
c.getDescriptors().add(d);
}));
}));
}

private static void gotValue(String name, String charUuid, byte[] value) {
if (debug) {
LOG.log(Level.INFO, String.format("BLE with characteristic: %s has value %s", charUuid, Arrays.toString(value)));
}

getDeviceByName(name).ifPresent(device ->
device.getProfiles().stream()
.flatMap(d -> d.getCharacteristics().stream())
.filter(c -> c.getUuid().toString().equalsIgnoreCase(charUuid))
.findFirst()
.ifPresent(c -> {
if (debug) {
LOG.log(Level.INFO, String.format("AndroidBleService DONE updating value for characteristic %s", charUuid));
}
Platform.runLater(() -> c.setValue(value));
}));
}

private static Optional<BleDevice> getDeviceByName(String name) {
if (name == null || !deviceNames.contains(name)) {
return Optional.empty();
}

for (BleDevice device : devices) {
if (name.equals(device.getName())) {
return Optional.of(device);
}
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ public ObservableList<BleDevice> startScanningDevices() {
return devices;
}

@Override
public void stopScanningDevices() {
stopScanningPeripherals();
}

@Override
public void connect(BleDevice device) {
if (!checkDevice(device)) {
Expand Down Expand Up @@ -235,6 +240,7 @@ private static boolean checkDevice(BleDevice device) {
// native

private static native void startScanningPeripherals();
private static native void stopScanningPeripherals();
private static native void doConnect(String name, String uuid);
private static native void doDisconnect(String name, String uuid);
private static native void doRead(String name, String uuidService, String uuidChar);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ public String parse(byte[] value) {
if (value.length != 2) {
return "Incorrect data length (2 bytes expected)";
}
final int intValue = BleUtils.getIntValue(value, BleUtils.FORMAT_SINT16, 0);

final Integer intValue = BleUtils.getIntValue(value, BleUtils.FORMAT_SINT16, 0);
if (intValue == null) {
return null;
}

switch (intValue) {
case 0: return "[" + intValue + "] Unknown";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public class BleUtils {
* @return Cached value of the characteristic or null of offset exceeds
* value size.
*/
public static Integer getIntValue(byte[] value, int formatType, int offset) {
static Integer getIntValue(byte[] value, int formatType, int offset) {
if ((offset + getTypeLen(formatType)) > value.length) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,19 @@ public String parse(byte[] value) {
return "Notifications and Indications disabled";
}

int intValue;
Integer intValue;
if (value.length == 1) {
intValue = BleUtils.getIntValue(value, BleUtils.FORMAT_UINT8, 0) & 0x3;
intValue = BleUtils.getIntValue(value, BleUtils.FORMAT_UINT8, 0);
} else {
if (value.length != 2) {
return Arrays.toString(value) + ": Incorrect data length (2 bytes expected)";
}
intValue = BleUtils.getIntValue(value, BleUtils.FORMAT_UINT16, 0) & 0x3;
intValue = BleUtils.getIntValue(value, BleUtils.FORMAT_UINT16, 0);
}
if (intValue == null) {
return null;
}
intValue = intValue & 0x3;
switch (intValue) {
case 0: return "Notifications and Indications disabled";
case 1: return "Notifications enabled";
Expand Down
Loading

0 comments on commit 72dcf6c

Please sign in to comment.