Reputation:
I wish to detect Eddystone Ul and uid without using Proximity Beacon API or Nearby Messages API. I wish to use native android libraries like BluetoothAdapter or BluetoothGatt or BluetoothGap to parse the eddystone frames. Is this feasible? if so how and If its not feasible then what could be the alternative?
Upvotes: 4
Views: 6207
Reputation: 19001
The following is the simplest way to get information about Eddystone AFAIK.
// onLeScan() method of BluetoothAdapter.LeScanCallback interface.
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
{
// Parse the payload of the advertisement packet
// as a list of AD structures.
List<ADStructure> structures =
ADPayloadParser.getInstance().parse(scanRecord);
// For each AD structure contained in the advertisement packet.
for (ADStructure structure : structures)
{
// If the AD structure represents Eddystone UID.
if (structure instanceof EddystoneUID)
{
// Eddystone UID
EddystoneUID es = (EddystoneUID)structure;
Log.d(TAG, "Tx Power = " + es.getTxPower());
Log.d(TAG, "Namespace ID = " + es.getNamespaceIdAsString());
Log.d(TAG, "Instance ID = " + es.getInstanceIdAsString());
Log.d(TAG, "Beacon ID = " + es.getBeaconIdAsString());
// As byte arrays if you want.
byte[] namespaceId = es.getNamespaceId();
byte[] instanceId = es.getInstanceId();
byte[] beaconId = es.getBeaconId();
}
// If the AD structure represents Eddystone URL.
else if (structure instanceof EddystoneURL)
{
// Eddystone URL
EddystoneURL es = (EddystoneURL)structure;
Log.d(TAG, "Tx Power = " + es.getTxPower());
Log.d(TAG, "URL = " + es.getURL());
}
// If the AD structure represents Eddystone TLM.
else if (structure instanceof EddystoneTLM)
{
// Eddystone TLM
EddystoneTLM es = (EddystoneTLM)structure;
Log.d(TAG, "TLM Version = " + es.getTLMVersion());
Log.d(TAG, "Battery Voltage = " + es.getBatteryVoltage());
Log.d(TAG, "Beacon Temperature = " + es.getBeaconTemperature());
Log.d(TAG, "Advertisement Count = " + es.getAdvertisementCount());
Log.d(TAG, "Elapsed Time = " + es.getElapsedTime());
}
}
}
You don't have to know details about the Eddystone specification if you use nv-bluetooth.
dependencies {
compile 'com.neovisionaries:nv-bluetooth:1.7'
}
Beacon Temperature in Eddystone TLM is expressed in a signed fixed-point notation. The code examples in How to detect Eddystone-Compatible Beacons (Android Beacon Library) don't show how to extract the data as a floating point number as of this writing. On the other hand, EddystoneTLM
class in nv-bluetooth has a method as shown below, so you don't have to decode the fixed-point notation.
public float getBeaconTemperature();
Likewise, EddystoneURL
class has a method to get the URL as URL
.
public URL getURL();
So, you don't have to do a step like below which is required when you use Android Beacon Library.
String url = UrlBeaconUrlCompressor.uncompress(beacon.getId1().toByteArray());
nv-bluetooth implements data structures related to Eddystone as an inheritance tree as shown below. This kind of proper inheritance tree is difficult to find in other libraries.
ADStructure
|
+-- ServiceData
|
+-- Eddystone
|
+-- EddystoneUID
|
+-- EddystoneURL
|
+-- EddystoneTLM
One of the benefits of the proper inheritance tree is that methods are placed at the right positions. Like this:
ADStructure
| // AD Structure Length - 1
| int getLength();
|
| // AD Type
| int getType();
|
| // AD Data
| byte[] getData();
|
+-- ServiceData
| | // Service UUID
| | UUID getServiceUUID();
| |
| +-- Eddystone
| | // Eddystone Frame Type
| | FrameType getFrameType();
| |
| +-- EddystoneUID
| | // Tx Power
| | int getTxPower();
| |
| | // Namespace ID (byte[])
| | byte[] getNamespaceId();
| |
| | // Instance ID (byte[])
| | byte[] getInstanceId();
| |
| | // Beacon ID (byte[])
| | byte[] getBeaconId();
| |
| | // Namespace ID (String)
| | String getNamespaceIdAsString();
| |
| | // Instance ID (String)
| | String getInstanceIdAsString();
| |
| | // Beacon ID (String)
| | String getBeaconIdAsString();
| |
| +-- EddystoneURL
| | // Tx Power
| | int getTxPower();
| |
| | // URL
| | URL getURL();
| |
| +-- EddystoneTLM
| // TLM Version
| int getTLMVersion();
|
| // Battery Voltage
| int getBatteryVoltage();
|
| // Beacon Temperature
| float getBeaconTemperature();
|
| // Advertisement Count
| long getAdvertisementCount();
|
| // Elapsed Time
| long getElapsedTime();
|
+-- ADManufacturerSpecific
| | // Company ID
| | int getCompanyId();
| |
| +-- IBeacon
| | // Major Number
| | int getMajor();
| |
| | (abbrev)
| |
| +-- Ucode
| | // Ucode
| | String getUcode();
| |
| | (abbrev)
Upvotes: 4
Reputation: 64941
You can detect Eddystone-UID beacons with the native Android onLeScan
callback. I have posted example code showing how you can do this in the answer here:
https://stackoverflow.com/a/32799901/1461050
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
for (int startByte = 0; startByte < scanRecord.length; startByte++) {
if (scanRecord.length-startByte > 19) { // need at least 19 bytes for Eddystone-UID
// Check that this has the right pattern needed for this to be Eddystone-UID
if (scanRecord[startByte+0] == 0xaa && scanRecord[startByte+1] == 0xfe &&
scanRecord[startByte+2] == 0x00) {
// This is an Eddystone-UID beacon.
byte[] namespaceIdentifierBytes = Arrays.copyOfRange(scanRecord, startByte+4, startByte+13);
byte[] instanceIdentifierBytes = Arrays.copyOfRange(scanRecord, startByte+14, startByte+19);
// TODO: do something with the above identifiers here
}
}
}
}
The key to doing this is understanding and parsing the byte layout of each beacon type. This is how the open source Android Beacon Library parses multiple beacon types like Eddystone by defining layout expressions for each beacon to aid parsing. Even if you want to roll your own code, you can look at its source code and use that to understand how it works. I have posted a description of how this parsing happens in the answer linked above.
Upvotes: 0
Reputation: 2149
All of Eddystone data is contained in the Bluetooth 4.0 ("Low Energy," "BLE") advertising packet, so no need for BluetoothGatt
or BluetoothGap
. Use the BluetoothLeScanner
instead. In the onScanResult
callback, you can access the advertising data via:
// assuming `result` is the ScanResult passed to the `onScanResult` callback
byte[] rawData = result
.getScanRecord()
.getServiceData(ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"));
Then, you will need to parse the bytes according to the Eddystone specs:
UID:
https://github.com/google/eddystone/tree/master/eddystone-uid
URL:
https://github.com/google/eddystone/tree/master/eddystone-url
There's also an example project included in the Eddystone repo, so you can probably start there, maybe reuse some code:
https://github.com/google/eddystone/tree/master/tools/eddystone-validator
Upvotes: 5