【问题标题】:How to identify a Eddystone via scanRecord如何通过 scanRecord 识别 Eddystone
【发布时间】:2015-09-23 08:51:57
【问题描述】:

我正在开发一个正在扫描 BLE 设备的 Android 应用。每次我找到一个设备时,我都会收到: byte[] scanRecord, BluetoothDevice device, int rssi 来自 BluetoothAdapter.startLeScan()

然后我将字节数组转换为 ScanRecord 对象: ScanRecord.parseFromBytes()

我现在从我的 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` 

谁能告诉我,如何使用此信息将设备识别为 Eddystone?服务 uuid 可能吗?我并不总是知道设备的名称或地址。

【问题讨论】:

    标签: android bluetooth gatt


    【解决方案1】:

    android.bluetooth.le.ScanRecord 是 Android 中最糟糕的 API 之一。

    如果你已经有一个scanRecord(字节数组),我推荐nv-bluetooth来提取Eddystone数据。以下代码 sn-p 显示了 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();
        }
    }
    

    上面的代码暗示扫描记录应该被解析为AD结构的列表。但是,android.bluetooth.le.ScanRecord 中的parseFromBytes 并没有以正确的方式解析扫描记录。

    ScanRecord 有以下方法(以及其他一些方法):

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

    这些方法对应于一些 AD 结构。此 API 设计与下面所示的AnimalRecord 类的结构相同。

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

    标志、本地名称、制造商特定数据、服务数据和 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;
        }
    }
    

    正如上面代码中所说,Eddystone 是一种服务数据。因此,Eddystone UID、Eddystone URL 和 Eddystone TLM 应该具有如下所示的继承树。

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

    希望对BLE规范非常了解,有良好设计能力的人,从零开始重写Android的BLE API。

    【讨论】:

    • 我可以从传输的 BLE 广告包中获取 EddystoneTLM 吗?抱歉,我将 bluez 用于 raspberry,而不是 Android。
    • 所有 Eddystone 数据都可以在 BLE 广告包中找到。您不必连接到 BLE 设备。只扫描广告包就足够了。
    • 我读到:github.com/google/eddystone/blob/master/…。但我不明白如何从中获取 EddystoneTLM。使用其他信标,我可以获得uuid,mac地址,major,minor,tx power ...
    【解决方案2】:

    对于那些想知道它是如何工作的人:

    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
      ]
    }
    

    第一个服务数据包可以通过“0000feaa-0000-1000-8000-00805f9b34fb”的前32位识别为EddyStone数据。转换时 0000feAA 是 16 位 EddyStone 服务 UUID,可在 the Bluetooth Data Service Specification 中找到。

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

    服务总是发出“????????-0000-1000-8000-00805f9b34fb”,此 UUID 的前 32 位被服务的别名替换。在这种情况下,“feaa”是指 EddyStone 服务数据(由 Google 创建/指定)。

    因此,由于识别了键,我们现在知道该值是 EddyStone DataView。这些值需要根据 EddyStone 规范进行映射/解释:

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

    要提取帧类型(EddyStone UID、URL、TLM 或 EID),您需要获取数组的第一个值:

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

    要了解剩余的值,我们需要查看 EddyStone URL 规范:

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

    要提取 TX Power,您需要获取数组的第二个值:

    TX Power = -36; => -36
    

    要提取 URL Schema,您需要获取所有剩余的值并将它们转换为字符代码:

    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'
    

    总结一下:

    信标发布了一个 EddyStone 数据服务包,该数据服务包可通过 128 位 UUID“0000feaa-0000-1000-8000-00805f9b34fb”识别,并使用“EddyStone URL”帧类型(帧的第一个值),并正在发布以下 URL: “kntk.io/eddystone”

    我希望通过将问题中的这些数据分解为实际的真实值,这将有助于最终来到这里的人们了解蓝牙广告的实际工作原理。

    您可以使用众多库之一为您完成所有这些事情,但了解基础知识可能会很有用...


    注意: 我怀疑第二个包是本机 Kontakt.io 服务帧类型,由信标广告以供 Kontakt.io 工具在内部使用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-23
      • 2021-08-10
      • 2011-11-09
      • 1970-01-01
      相关资源
      最近更新 更多