全ての記事

web bluetooth scanning API紹介

2018-04-09 @sunderls

js bluetooth scanning BLE

上一篇文章「web bluetooth 基本用法」整理了一下基本的API用法。

可以发现其API还是基于 查找->链接->操作/监听的模式,和搜索查找网页的感觉类似。上篇文章中发现有个问题是在于notification, characteristic.startNotifications()之后需要监听characteristicvaluechanged事件才行,但是监听的着所有的一切都建立在“连接”之后的。

BLE通信种类

1对1模式

中心设备(Central, initiator)发起对 BLE周边设备(Peripheral, Advertiser)的请求。搜的就是上篇文章说到的GATT。

用的是Data Channel(数据信道?不太懂)

  1. 发送的信息是一系列Advertising Event。
  2. initiator收到Advertiser的event,然后发送请求给broadcaster说要连接。
  3. Advertiser收到请求,结束advertising,开始connection event,这里走的data channel。
  4. initiator连接完毕,advertiser变成slave。
  5. 以后的相互间通信就走data channel。 7, initiator终止连接。

N对1模式

从广播设备(BLE设备, Broadcaster)发起对多个 接收器(Observer,Scanner)的广播。

走的是Advertising Channel(广播信道?)

这里广播模式下,可以不用连接接受信息。这种模式对于只不断发射特定信息的设备非常好用啊,比如beacon!

  1. 发送的信息是一系列Advertising Event。
  2. Advertising Event的开头是Type值。
  3. scanner收到broadcaster的event,然后发送请求给broadcaster说要数据
  4. broadcaster就发给scanner数据,走相同的Advertising Channel

N对1模式的web bluetooth scanning API

官方spec在这里: https://webbluetoothcg.github.io/web-bluetooth/scanning.html#idl-index ,不过还未被选择为标准。

上面文档中的例子:

function recordNearbyBeacon(major, minor, pathLossVs1m) { ... }

// 开启扫描
navigator.bluetooth.requestLEScan({
  // 设置filter
  filters: [{manufacturerData: {0x004C: {dataPrefix: new Uint8Array([
    0x02, 0x15, // iBeacon identifier.
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15  // My beacon UUID.
  ])}}}],
  options: {
    keepRepeatedDevices: true,
  }
}).then(() => {
  // 这样就可以收到`advertisementreceived`事件。
  navigator.bluetooth.addEventListener('advertisementreceived', event => {
    let appleData = event.manufacturerData.get(0x004C);
    if (appleData.byteLength != 23) {
      // Isn’t an iBeacon.
      return;
    }
    let major = appleData.getUint16(18, false);
    let minor = appleData.getUint16(20, false);
    let txPowerAt1m = -appleData.getInt8(22);
    let pathLossVs1m = txPowerAt1m - event.rssi;

    recordNearbyBeacon(major, minor, pathLossVs1m);
  });
})

因为广播中可能包含隐私信息,所以开启scan需要得到用户的授权。

navigator.bluetooth.requestLEScan(options) 开启扫描

interface Bluetooth {
    requestLEScan(options?: BluetoothLEScanOptions) : Promise<BluetoothLEScan>;
}

type BluetoothLEScanOptions  = {
    filters: BluetoothLEScanFilter[];
    keepRepeatedDevices?: boolean;
    acceptAllAdvertisements?: boolean;
}

Scan的时候一般从一个device只会收到一个event,用keepRepeatedDevices可以控制为多个event。 如果你想收到所有的event就设置acceptAllAdvertisements。

  1. 设置acceptAllAdvertisements的时候不能设置filter
  2. 如果没有acceptAllAdvertisements,filter必须

该方法返回一个promise,经过一系列合法检查,这个方法会resolve为BluetoothLEScan。

设置OPTION的时候参考以下spec

type BluetoothServiceUUID = number | string;

interface BluetoothLEScanFilter {
    name?: DOMString,
    namePrefix?: DOMString,
    services: BluetoothServiceUUID[],
    manufacturerData: BluetoothManufacturerDataFilter,
    serviceData: BluetoothServiceDataFilter
};

type BluetoothServiceUUID = number | string;
interface BluetoothServiceDataFilter {
    [index: BluetoothServiceUUID]: BluetoothDataFilter
};

interface BluetoothDataFilter {
    dataPrefix: ArrayBuffer;
    mask: ArrayBuffer;
};

interface BluetoothManufacturerDataFilter {
    [index: Uint8Array]: BluetoothDataFilter
};

BluetoothLEScan.stop() 结束scan

interface BluetoothLEScan {
    filters: BluetoothLEScanFilter[];
    keepRepeatedDevices: boolean;
    acceptAllAdvertisements: boolean;
    active: boolean;
    stop(): void;
};

advertisementreceived 事件中获取信息

interface BluetoothAdvertisingEvent : Event {
  [SameObject]
  readonly attribute BluetoothDevice device;
  readonly attribute FrozenArray<UUID> uuids;
  readonly attribute DOMString? name;
  readonly attribute unsigned short? appearance;
  readonly attribute byte? txPower;
  readonly attribute byte? rssi;
  [SameObject]
  readonly attribute BluetoothManufacturerDataMap manufacturerData;
  [SameObject]
  readonly attribute BluetoothServiceDataMap serviceData;
};

manufacturerDataMap中是一个公司code到DataView的映射 serviceDataMap是UUID到DataView的映射

总结

没了,这就是scanning API,比较简单。