简体   繁体   English

如何通过 scanRecord 识别 Eddystone

[英]How to identify a Eddystone via scanRecord

I'm working on a Android App which is scanning for BLE devices.我正在开发一个正在扫描 BLE 设备的 Android 应用程序。 Everytime I found a device, I receive: byte[] scanRecord, BluetoothDevice device, int rssi from BluetoothAdapter.startLeScan()每次找到设备时,我都会收到: byte[] scanRecord, BluetoothDevice device, int rssi from BluetoothAdapter.startLeScan()

I then convert the byte array to a ScanRecord object: ScanRecord.parseFromBytes()然后我将字节数组转换为 ScanRecord 对象: ScanRecord.parseFromBytes()

I have now the following information from my Eddystone (from toString() method).我现在从我的 Eddystone 获得以下信息(来自toString()方法)。

`com.reelyactive.blesdk.support.ble.ScanRecord [mAdvertiseFlags=6, mServiceUuids=[0000feaa-0000-1000-8000-00805f9b34fb], mManufacturerSpecificData={}, mServiceData={0000feaa-0000-1000-8000-00805f9b34fb=[16, -36, 2, 107, 110, 116, 107, 46, 105, 111, 47, 101, 100, 100, 121, 115, 116, 111, 110, 101], 0000d00d-0000-1000-8000-00805f9b34fb=[67, 77, 103, 52, 50, 57, 100]}, mTxPowerLevel=-12, mDeviceName=IIS_EDDY_003] IIS_EDDY_003` 

Can someone tell me, how to identify the device as an Eddystone with this information?有人可以告诉我,如何使用此信息将设备识别为 Eddystone? Service uuids maybe?服务 uuids 也许? I don't always know the name or the adress of the device.我并不总是知道设备的名称或地址。

android.bluetooth.le.ScanRecord is one of the worst APIs in Android. android.bluetooth.le.ScanRecord是 Android 中最糟糕的 API 之一。

If you already have a scanRecord (byte array), I recommend nv-bluetooth to extract Eddystone data.如果您已经有了scanRecord (字节数组),我建议使用nv-bluetooth来提取 Eddystone 数据。 The following code snippet shows usage of nv-bluetooth.以下代码片段显示了 nv-bluetooth 的用法。

// Parse the payload of the advertising packet.
List<ADStructure> structures =
    ADPayloadParser.getInstance().parse(scanRecord);

// For each AD structure contained in the payload.
for (ADStructure structure : structures)
{
    if (structure instanceof EddystoneUID)
    {
        // Eddystone UID
        EddystoneUID es = (EddystoneUID)structure;

        // (1) Calibrated Tx power at 0 m.
        int power = es.getTxPower();

        // (2) 10-byte Namespace ID
        byte[] namespaceId = es.getNamespaceId();
        String namespaceIdAsString = es.getNamespaceIdAsString();

        // (3) 6-byte Instance ID
        byte[] instanceId = es.getInstanceId();
        String instanceIdAsString = es.getInstanceIdAsString();

        // (4) 16-byte Beacon ID
        byte[] beaconId = es.getBeaconId();
        String beaconIdAsString = es.getBeaconIdAsString();
    }
    else if (structure instanceof EddystoneURL)
    {
        // Eddystone URL
        EddystoneURL es = (EddystoneURL)structure;

        // (1) Calibrated Tx power at 0 m.
        int power = es.getTxPower();

        // (2) URL
        URL url = es.getURL();
    }
    else if (structure instanceof EddystoneTLM)
    {
        // Eddystone TLM
        EddystoneTLM es = (EddystoneTLM)structure;

        // (1) TLM Version
        int version = es.getTLMVersion();

        // (2) Battery Voltage
        int voltage = es.getBatteryVoltage();

        // (3) Beacon Temperature
        float temperature = es.getBeaconTemperature();

        // (4) Advertisement count since power-on or reboot.
        long count = es.getAdvertisementCount();

        // (5) Elapsed time in milliseconds since power-on or reboot.
        long elapsed = es.getElapsedTime();
    }
    else if (structure instanceof IBeacon)
    {
        // iBeacon
        IBeacon iBeacon = (IBeacon)structure;

        // (1) Proximity UUID
        UUID uuid = iBeacon.getUUID();

        // (2) Major number
        int major = iBeacon.getMajor();

        // (3) Minor number
        int minor = iBeacon.getMinor();

        // (4) Tx Power
        int power = iBeacon.getPower();
    }
}

The above code implies that a scan record should be parsed as a list of AD structures .上面的代码意味着应该将扫描记录解析为AD 结构列表。 However, parseFromBytes of android.bluetooth.le.ScanRecord does not parse a scan record in the right way.但是, android.bluetooth.le.ScanRecord parseFromBytes没有以正确的方式解析扫描记录。

ScanRecord has the following methods (and some others): ScanRecord有以下方法(和其他一些方法):

  1. getAdvertiseFlags()
  2. getDeviceName()
  3. getManufacturerSpecificData()
  4. getServiceData()
  5. getTxPowerLevel()

These methods correspond to some AD structures.这些方法对应于一些 AD 结构。 This API design is the same structure as AnimalRecord class shown below.此 API 设计与下图所示的AnimalRecord类结构相同。

public class AnimalRecord
{
    public Cat getCat() { ... }
    public Dog getDog() { ... }
    public Eagle getEagle() { ... }
    ...
}

Flags, Local Name, Manufacturer Specific Data, Service Data, and Tx Power Level also should be parsed as AD structures like below.标志、本地名称、制造商特定数据、服务数据和 Tx 功率级别也应解析为如下所示的 AD 结构。

// Parse the payload of the advertising packet.
List<ADStructure> structures =
    ADPayloadParser.getInstance().parse(scanRecord);

// For each AD structure contained in the payload.
for (ADStructure structure : structures)
{
    if (structure instanceof Flags)
    {
        // Flags
        Flags flags = (Flags)structure;
    }
    else if (structure instanceof LocalName)
    {
        // Local Name
        LocalName name = (LocalName)structure;
    }
    else if (structure instanceof ADManufacturerSpecific)
    {
        // Manufacturer Specific Data
        // Note that iBeacon is a kind of Manufacturer Specific Data
        ADManufacturerSpecific ms = (ADManufacturerSpecific)structure;
    }
    else if (structure instanceof ServiceData)
    {
        // Service Data
        // Note that Eddystone is a kind of Service Data.
        ServiceData sd = (ServiceData)structure;
    }
    else if (structure instanceof TxPowerLevel)
    {
        // TxPowerLevel
        TxPowerLevel level = (TxPowerLevel)structure;
    }
}

As commented in the code above, Eddystone is a kind of Service Data.正如上面代码中所评论的,Eddystone 是一种服务数据。 Therefore, Eddystone UID, Eddystone URL and Eddystone TLM should have an inheritance tree like below.因此,Eddystone UID、Eddystone URL 和 Eddystone TLM 应该具有如下所示的继承树。

ADStructure
  |
  +-- ServiceData
        |
        +-- Eddystone
              |
              +-- EddystoneUID
              +-- EddystoneURL
              +-- EddystoneTLM

I hope those who know the BLE specification very well and have good design skills will rewrite Android's BLE APIs from scratch.我希望那些非常了解BLE规范并具有良好设计技能的人从头开始重写Android的BLE API。

For those who like to know how it actually works:对于那些想知道它实际工作原理的人:

mServiceData={
  0000feaa-0000-1000-8000-00805f9b34fb=[
    16, -36, 2, 107, 110, 116, 107, 46, 105, 111, 47, 101, 100, 100, 121, 115, 116, 111, 110, 101
  ], 
  0000d00d-0000-1000-8000-00805f9b34fb=[
    67, 77, 103, 52, 50, 57, 100
  ]
}

The first service data package is identifiable as a EddyStone data by the top 32 bits of "0000feaa-0000-1000-8000-00805f9b34fb".第一个服务数据包可以通过“0000feaa-0000-1000-8000-00805f9b34fb”的前32位识别为EddyStone数据。 When converting 0000feAA is the 16 bit EddyStone Service UUID that can be found in the Bluetooth Data Service Specification .转换 0000feAA 时是 16 位 EddyStone 服务 UUID,可在蓝牙数据服务规范中找到

16-bit UUID for Members => 0xFEAA => Google

Services always emit "????????-0000-1000-8000-00805f9b34fb" with the top 32 bits of this UUID replaced by the service its alias.服务总是发出“????????-0000-1000-8000-00805f9b34fb”,此 UUID 的前 32 位被服务的别名替换。 And in this case 'feaa' means EddyStone Service data (created/specified by Google).在这种情况下,“feaa”表示 EddyStone 服务数据(由 Google 创建/指定)。

So because of identifying the key we now know that the value is an EddyStone DataView.因此,由于确定了键,我们现在知道该值是一个 EddyStone DataView。 Those values need to be mapped/interpreted according to the EddyStone specifications:这些值需要根据 EddyStone 规范进行映射/解释:

https://github.com/google/eddystone/blob/master/protocol-specification.md https://github.com/google/eddystone/blob/master/protocol-specification.md

To extract the Frame Type (EddyStone UID, URL, TLM or EID) you take the first value of the array:要提取帧类型(EddyStone UID、URL、TLM 或 EID),您需要取数组的第一个值:

FrameType = 16; => 0x10 => EddyStone URL

To understand the remaining values we need to look at the EddyStone URL specification:要了解剩余的值,我们需要查看 EddyStone URL 规范:

https://github.com/google/eddystone/tree/master/eddystone-url https://github.com/google/eddystone/tree/master/eddystone-url

To extract the TX Power you take the second value of the array:要提取 TX 功率,您需要取数组的第二个值:

TX Power = -36; => -36

To extract the URL Schema you take all remaining values and convert them to charcodes:要提取 URL 架构,您需要获取所有剩余值并将它们转换为字符代码:

107 => k
110 => n
116 => t
107 => k
 46 => .
105 => i
111 => o
 47 => /
101 => e
100 => d
100 => d
121 => y
115 => s
116 => t
111 => o
110 => n
101 => e

So the URL is: 'kntk.io/eddystone'

To summarize:总结一下:

The beacon advertises a EddyStone data service package recognizable by the 128bit UUID "0000feaa-0000-1000-8000-00805f9b34fb" and is using the "EddyStone URL" frame type (first value of frame) and is advertising the following url: "kntk.io/eddystone"信标通告了一个可通过 128 位 UUID“0000feaa-0000-1000-8000-00805f9b34fb”识别的 EddyStone 数据服务包,并使用“EddyStone URL”帧类型(帧的第一个值)并通告以下 URL:“kntk. io/eddystone"

I hope that by breaking down this data from the question into the actual real values will be helping people ending up here to understand how Bluetooth Advertising actually works.我希望通过将问题中的这些数据分解为实际的真实值将帮助人们最终了解蓝牙广告的实际工作原理。

You can use one of the many libraries to do all these things for you, but understanding the basics can be useful...您可以使用众多库中的一个来为您完成所有这些事情,但了解基础知识会很有用...


Note: I suspect that the second package is a native Kontakt.io Service frame type being advertised by the beacon to be used internally by the Kontakt.io tools.注意:我怀疑第二个包是由信标广告的本地 Kontakt.io 服务帧类型,供 Kontakt.io 工具在内部使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM