-
-
Notifications
You must be signed in to change notification settings - Fork 317
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Re-design BLE beacons logic. Add support for Eddystone beacons.
Signed-off-by: Chengzhi Hu <tony.hu1213@gmail.com>
- Loading branch information
Showing
2 changed files
with
168 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
162 changes: 162 additions & 0 deletions
162
mobile/src/main/java/org/openhab/habdroid/util/BleBeaconConnector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
package org.openhab.habdroid.util; | ||
|
||
import android.annotation.TargetApi; | ||
import android.bluetooth.BluetoothAdapter; | ||
import android.bluetooth.BluetoothManager; | ||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.os.Handler; | ||
import android.os.ParcelUuid; | ||
import android.support.v7.app.AppCompatActivity; | ||
import android.util.Log; | ||
|
||
import java.lang.annotation.Target; | ||
import java.nio.ByteBuffer; | ||
import java.nio.ByteOrder; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
@TargetApi(18) | ||
public class BleBeaconConnector { | ||
private static final String TAG = BleBeaconConnector.class.getSimpleName(); | ||
|
||
private static final int REQUEST_ENABLE_BT = 0; | ||
private static final int SCAN_PERIOD = 10000; | ||
public static final ParcelUuid BASE_UUID = | ||
ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); | ||
private static final ParcelUuid EDDYSTONE_URL_SERVICE_UUID = | ||
ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"); | ||
private static final int UUID_BYTES_16_BIT = 2; | ||
private static final int UUID_BYTES_32_BIT = 4; | ||
private static final int UUID_BYTES_128_BIT = 16; | ||
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; | ||
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; | ||
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; | ||
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; | ||
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; | ||
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; | ||
|
||
private static BleBeaconConnector INSTANCE; | ||
|
||
private BluetoothAdapter bluetoothAdapter; | ||
private Handler handler; | ||
private BluetoothAdapter.LeScanCallback leScanCallback = (bluetoothDevice, i, bytes) -> { | ||
if (!isEddyStoneBeacon(bytes)){ | ||
return; | ||
} | ||
Log.d(TAG, "Device:" + bluetoothDevice.getName() + " address: " + bluetoothDevice.getAddress()); | ||
}; | ||
|
||
//Enforce singleton by using private constructors | ||
private BleBeaconConnector(){} | ||
|
||
private BleBeaconConnector(AppCompatActivity activity){ | ||
handler = new Handler(); | ||
final BluetoothManager manager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE); | ||
bluetoothAdapter = manager.getAdapter(); | ||
|
||
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()){ | ||
Intent enableBluetoothIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); | ||
activity.startActivityForResult(enableBluetoothIntent, REQUEST_ENABLE_BT); | ||
} | ||
|
||
scanLeServiceCompact(); | ||
} | ||
|
||
@SuppressWarnings("deprecation") | ||
private void scanLeServiceCompact(){ | ||
handler.postDelayed(() -> bluetoothAdapter.stopLeScan(leScanCallback), SCAN_PERIOD); | ||
bluetoothAdapter.startLeScan(leScanCallback); | ||
} | ||
|
||
public static BleBeaconConnector getInstance(AppCompatActivity activity) { | ||
if (INSTANCE == null){ | ||
INSTANCE = new BleBeaconConnector(activity); | ||
} | ||
return INSTANCE; | ||
} | ||
|
||
private boolean isEddyStoneBeacon(byte[] bytes){ | ||
List<ParcelUuid> serviceUuids = parseFromBytes(bytes); | ||
return serviceUuids.contains(EDDYSTONE_URL_SERVICE_UUID); | ||
} | ||
|
||
private List<ParcelUuid> parseFromBytes(byte[] bytes){ | ||
int length; | ||
int fieldType; | ||
int currentPos = 0; | ||
List<ParcelUuid> serviceUuids = new ArrayList<>(); | ||
while (currentPos < bytes.length) { | ||
length = bytes[currentPos++] & 0xFF; | ||
if (length == 0){ | ||
break; | ||
} | ||
fieldType = bytes[currentPos++] & 0xFF; | ||
switch (fieldType) { | ||
case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: | ||
case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: | ||
parseServiceUuid(bytes, currentPos, length, UUID_BYTES_16_BIT, serviceUuids); | ||
case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: | ||
case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: | ||
parseServiceUuid(bytes, currentPos, length, UUID_BYTES_32_BIT, serviceUuids); | ||
case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: | ||
case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: | ||
parseServiceUuid(bytes, currentPos, length, UUID_BYTES_128_BIT, serviceUuids); | ||
} | ||
currentPos += length - 1; | ||
} | ||
return serviceUuids; | ||
} | ||
|
||
private void parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, | ||
int uuidLength, List<ParcelUuid> serviceUuids) { | ||
while (dataLength > 0) { | ||
byte[] uuidBytes = new byte[uuidLength]; | ||
System.arraycopy(scanRecord, currentPos, uuidBytes, 0, uuidLength); | ||
serviceUuids.add(parseUuidFrom(uuidBytes)); | ||
dataLength -= uuidLength; | ||
currentPos += uuidLength; | ||
} | ||
} | ||
|
||
private ParcelUuid parseUuidFrom(byte[] uuidBytes) { | ||
if (uuidBytes == null) { | ||
throw new IllegalArgumentException("uuidBytes cannot be null"); | ||
} | ||
int length = uuidBytes.length; | ||
if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT && | ||
length != UUID_BYTES_128_BIT) { | ||
throw new IllegalArgumentException("uuidBytes length invalid - " + length); | ||
} | ||
// Construct a 128 bit UUID. | ||
if (length == UUID_BYTES_128_BIT) { | ||
ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN); | ||
long msb = buf.getLong(8); | ||
long lsb = buf.getLong(0); | ||
return new ParcelUuid(new UUID(msb, lsb)); | ||
} | ||
// For 16 bit and 32 bit UUID we need to convert them to 128 bit value. | ||
// 128_bit_value = uuid * 2^96 + BASE_UUID | ||
long shortUuid; | ||
if (length == UUID_BYTES_16_BIT) { | ||
shortUuid = uuidBytes[0] & 0xFF; | ||
shortUuid += (uuidBytes[1] & 0xFF) << 8; | ||
} else { | ||
shortUuid = uuidBytes[0] & 0xFF; | ||
shortUuid += (uuidBytes[1] & 0xFF) << 8; | ||
shortUuid += (uuidBytes[2] & 0xFF) << 16; | ||
shortUuid += (uuidBytes[3] & 0xFF) << 24; | ||
} | ||
long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32); | ||
long lsb = BASE_UUID.getUuid().getLeastSignificantBits(); | ||
StringBuilder sb = new StringBuilder(); | ||
sb.append("Unconverted: size: ").append(uuidBytes.length).append("\n"); | ||
for (byte uuid : uuidBytes){ | ||
sb.append(uuid).append(" "); | ||
} | ||
sb.append("\n"); | ||
Log.d(TAG, sb.toString() + "Convtered: " + new UUID(msb, lsb)); | ||
return new ParcelUuid(new UUID(msb, lsb)); | ||
} | ||
} |