蓝牙

本文档介绍了如何使用蓝牙蓝牙套接字低蓝牙协议 Energy API,用于与蓝牙和蓝牙低功耗设备进行通信。

有关蓝牙的背景信息,请参阅官方蓝牙规范

清单要求

对于使用蓝牙的 Chrome 应用,请将 bluetooth 条目添加到清单中,并指定 您要实现的配置文件、协议或服务的 UUID 以及是否 您希望使用套接字和/或低功耗 API 实现这些 API。

例如,对于套接字实现:

"bluetooth": {
  "uuids": [ "1105", "1106" ],
  "socket": true
}

对于“低功耗”实现:

"bluetooth": {
  "uuids": [ "180D", "1809", "180F" ],
  "low_energy": true
}

如果只想访问适配器状态、发现附近的设备以及获取有关设备的基本信息, 只有条目本身是必填项:

"bluetooth": {}

适配器信息

获取适配器状态

要获取蓝牙适配器的状态,请使用 bluetooth.getAdapterState 方法:

chrome.bluetooth.getAdapterState(function(adapter) {
  console.log("Adapter " + adapter.address + ": " + adapter.name);
});

适配器通知

每当适配器状态发生变化时,系统都会发送 bluetooth.onAdapterStateChanged 事件。这可以 例如,用于确定适配器无线电何时打开或关闭。

var powered = false;
chrome.bluetooth.getAdapterState(function(adapter) {
  powered = adapter.powered;
});

chrome.bluetooth.onAdapterStateChanged.addListener(
  function(adapter) {
    if (adapter.powered != powered) {
      powered = adapter.powered;
      if (powered) {
        console.log("Adapter radio is on");
      } else {
        console.log("Adapter radio is off");
      }
    }
  });

设备信息

列出已知设备

要获取蓝牙适配器已知的设备列表,请使用 bluetooth.getDevices 方法:

chrome.bluetooth.getDevices(function(devices) {
  for (var i = 0; i < devices.length; i++) {
    console.log(devices[i].address);
  }
});

所有设备都会退回,包括已配对的设备和最近发现的设备。它不会 开始发现新设备(请参阅发现附近的设备)。

接收设备通知

您可以使用bluetooth.getDevices bluetooth.onDeviceAddedbluetooth.onDeviceChangedbluetooth.onDeviceRemoved 事件接收通知。

每当适配器发现或检测到设备时,系统都会发送 bluetooth.onDeviceAdded 事件 会连接到适配器:

chrome.bluetooth.onDeviceAdded.addListener(function(device) {
  console.log(device.address);
});

为此事件添加监听器不会开始发现设备(请参阅发现附近的设备 设备)。

如果设备发生更改(包括之前发现的设备即将变成配对设备), bluetooth.onDeviceChanged 事件。

chrome.bluetooth.onDeviceChanged.addListener(function(device) {
  console.log(device.address);
});

最后,每当从设备中移除配对设备时,系统都会发送 bluetooth.onDeviceRemoved 事件。 或最近未发现的设备:

chrome.bluetooth.onDeviceRemoved.addListener(function(device) {
  console.log(device.address);
});

正在发现附近的设备

要开始发现附近的设备,请使用 bluetooth.startDiscovery 方法。发现广告可以 会占用大量资源,因此在完成后应调用 bluetooth.stopDiscovery

每当您的应用需要发现附近的设备时,您都应该调用 bluetooth.startDiscovery。 不要在 bluetooth.AdapterStatediscovering 属性中进行条件调用。通过 调用也会成功,并且会确保适配器 在其他应用停止运行后继续执行发现操作。

使用 bluetooth.onDeviceAdded 接收每个新发现设备的相关信息 事件。对于最近发现或之前已配对的设备 则不会发送该事件。您应改为调用 bluetooth.getDevices 获取最新信息,然后使用bluetooth.onDeviceChanged事件 会因为发现而对这些信息进行更改。

示例:

var device_names = {};
var updateDeviceName = function(device) {
  device_names[device.address] = device.name;
};
var removeDeviceName = function(device) {
  delete device_names[device.address];
}

// Add listeners to receive newly found devices and updates
// to the previously known devices.
chrome.bluetooth.onDeviceAdded.addListener(updateDeviceName);
chrome.bluetooth.onDeviceChanged.addListener(updateDeviceName);
chrome.bluetooth.onDeviceRemoved.addListener(removeDeviceName);

// With the listeners in place, get the list of devices found in
// previous discovery sessions, or any currently active ones,
// along with paired devices.
chrome.bluetooth.getDevices(function(devices) {
  for (var i = 0; i < devices.length; i++) {
    updateDeviceName(devices[i]);
  }
});

// Now begin the discovery process.
chrome.bluetooth.startDiscovery(function() {
  // Stop discovery after 30 seconds.
  setTimeout(function() {
    chrome.bluetooth.stopDiscovery(function() {});
  }, 30000);
});

如果用户关闭蓝牙无线装置,所有发现会话都将结束,且不会恢复 自动同步。如果这对您的应用很重要,您应该观看 bluetooth.onAdapterStateChanged 事件。如果 discovering 属性更改为 false,则 您的应用需要再次调用 bluetooth.startDiscovery 才能继续调用。请注意 发现存在资源密集型特性。

识别设备

Google 提供了许多不同的选项 用于标识 bluetooth.getDevices 和相关事件。

如果设备支持蓝牙设备 ID 规范,则系统会将多个属性添加到 包含该规范所定义的字段的 Device 对象。示例:

chrome.bluetooth.getDevices(function(devices) {
  for (var i = 0; i < devices.length; i++) {
    if (devices[0].vendorIdSource != undefined) {
      console.log(devices[0].address + ' = ' +
                  devices[0].vendorIdSource + ':' +
                  devices[0].vendorId.toString(16) + ':' +
                  devices[0].productId.toString(16) + ':' +
                  devices[0].deviceId.toString(16));
    }
  }
});

通常,设备 ID 规范就足以识别特定的型号,甚至是修订版本 设备的配置如果没有这些信息,您必须转而参考 设备的类或类型,可视需要在 address 中与制造商前缀结合使用。

大多数蓝牙设备都提供“设备类”信息作为根据 基带分配号码文档。此位字段在 deviceClass 中提供 属性。

chrome.bluetooth.getDevices(function(devices) {
  for (var i = 0; i < devices.length; i++) {
    if (devices[0].vendorIdSource != undefined) {
      console.log(devices[0].address + ' = ' +
                  devices[0].deviceClass.toString(16));
    }
  }
});

解析字段可能比较复杂,因此对于最常见的设备类型,Chrome 会为您处理此操作, 设置 type 字段。如果无法获得或无法满足您的需求,您需要 自行解析 deviceClass

chrome.bluetooth.getDevices(function(devices) {
  for (var i = 0; i < devices.length; i++) {
    if (devices[0].vendorIdSource != undefined) {
      console.log(devices[0].address + ' = ' + devices[0].type);
    }
  }
});

使用 RFCOMM 和 L2CAP

Chrome 应用可以连接到任何支持 RFCOMM 或 L2CAP 服务的设备。这包括 市面上大多数经典蓝牙设备

连接到套接字

要连接到设备,您需要具备以下三个条件:用于建立连接的套接字 with,使用 bluetoothSocket.create 创建;你要连接到的设备的地址 以及服务本身的 UUID。

在建立连接之前,您应使用 bluetooth.getDevice 或设备发现 API。

建立基础连接所需的信息,包括 RFCOMM 还是 应使用 L2CAP 协议,以及哪个通道或 PSM 可通过 设备。

示例:

var uuid = '1105';
var onConnectedCallback = function() {
  if (chrome.runtime.lastError) {
    console.log("Connection failed: " + chrome.runtime.lastError.message);
  } else {
    // Profile implementation here.
  }
};

chrome.bluetoothSocket.create(function(createInfo) {
  chrome.bluetoothSocket.connect(createInfo.socketId,
    device.address, uuid, onConnectedCallback);
});

保留 socketId 的句柄,以便稍后向此 socketId 发送数据 (bluetoothSocket.send) 套接字。

通过套接字接收和发送到套接字

从套接字接收和向套接字发送数据使用 ArrayBuffer 对象。要了解 ArrayBuffers, 请查看概览 JavaScript 类型数组和教程如何转换 ArrayBuffer to 和 from String

如需发送 arrayBuffer 中的数据,请使用 bluetoothSocket.send

chrome.bluetoothSocket.send(socketId, arrayBuffer, function(bytes_sent) {
  if (chrome.runtime.lastError) {
    console.log("Send failed: " + chrome.runtime.lastError.message);
  } else {
    console.log("Sent " + bytes_sent + " bytes")
  }
})

与发送数据的方法相反,数据是在事件发生后接收的 (bluetoothSocket.onReceive.创建套接字时会取消暂停(请参阅 bluetoothSocket.setPaused) 因此该事件的监听器通常添加在bluetoothSocket.createbluetoothSocket.connect

chrome.bluetoothSocket.onRecieve.addListener(function(receiveInfo) {
  if (receiveInfo.socketId != socketId)
    return;
  // receiveInfo.data is an ArrayBuffer.
});

接收套接字错误并断开连接

要收到套接字错误(包括断开连接)通知,请将监听器添加到 bluetoothSocket.onReceiveError 事件。

chrome.bluetoothSocket.onReceiveError.addListener(function(errorInfo) {
  // Cause is in errorInfo.error.
  console.log(errorInfo.errorMessage);
});

断开与插座的连接

要挂断连接并断开套接字,请使用 bluetoothSocket.disconnect

chrome.bluetoothSocket.disconnect(socketId);

出版服务

除了与设备的出站连接外,Chrome 应用可能还会发布 支持 RFCOMM 或 L2CAP。

监听套接字

支持两种类型的已发布服务。RFCOMM 是最常用的类型,涵盖了 大多数设备和配置文件:

var uuid = '1105';
chrome.bluetoothSocket.create(function(createInfo) {
  chrome.bluetoothSocket.listenUsingRfcomm(createInfo.socketId,
    uuid, onListenCallback);
});

另一种是 L2CAP,涵盖其他设备类型和供应商专用用途,例如固件 上传。

var uuid = '0b87367c-f188-47cd-bc20-a5f4f70973c6';
chrome.bluetoothSocket.create(function(createInfo) {
  chrome.bluetoothSocket.listenUsingL2cap(createInfo.socketId,
    uuid, onListenCallback);
});

在这两种情况下,都可以传递可选的 bluetoothSocket.ListenOptions,以分配特定的 信道或 PSM。回调通过 chrome.runtime.lastError 指示错误,并且指示成功 否则。保留 socketId 的句柄,以便稍后接受连接 (bluetoothSocket.onAccept)。

接受客户端连接

客户端连接被接受并通过 bluetoothSocket.onAccept 事件。

chrome.bluetoothSocket.onAccept.addListener(function(acceptInfo) {
  if (info.socketId != serverSocketId)
    return;

  // Say hello...
  chrome.bluetoothSocket.send(acceptInfo.clientSocketId,
    data, onSendCallback);

  // Accepted sockets are initially paused,
  // set the onReceive listener first.
  chrome.bluetoothSocket.onReceive.addListener(onReceive);
  chrome.bluetoothSocket.setPaused(false);
});

停止接受客户端连接

要停止接受客户端连接并取消发布服务,请使用 bluetoothSocket.disconnect

chrome.bluetoothSocket.disconnect(serverSocketId);

与低功耗设备交互

蓝牙低功耗(Bluetooth Smart)是一种旨在降低功耗的无线技术 。蓝牙低功耗 API 允许应用在 外设的 LE 连接。以下部分介绍了如何发现、连接和 可与蓝牙低功耗外围设备交互。

发现并连接外围设备

与传统蓝牙设备一样,LE 外围设备可通过上述方法发现: (请参阅发现附近的设备)。LE 设备通过发送数据包,使自身可被检测到 称为“广告数据”并且据称设备处于通告模式。广告数据 可能包含设备上可用服务的 UUID。如果存在,这些 UUID 将 可使用相应 bluetooth.Device 对象的 uuids 属性进行访问。

发现 LE 设备后,可通过调用 bluetoothLowEnergy.connect 来连接 LE 设备, 以便应用能够与其服务交互:

chrome.bluetooth.onDeviceAdded.addListener(function(device) {
  var uuid = '0000180d-0000-1000-8000-00805f9b34fb';
  if (!device.uuids || device.uuids.indexOf(uuid) < 0)
    return;

  // The device has a service with the desired UUID.
  chrome.bluetoothLowEnergy.connect(device.address, function () {
    if (chrome.runtime.lastError) {
      console.log('Failed to connect: ' + chrome.runtime.lastError.message);
      return;
    }

    // Connected! Do stuff...
    ...
  });
});

连接后,相应 bluetooth.Device 对象的 connected 属性将会 其值为 true。调用 bluetoothLowEnergy.connect 会建立 与设备物理连接上的应用。可以存在与设备的物理连接 而无需调用 bluetoothLowEnergy.connect(例如,由于其他应用)。在 在这种情况下,虽然您的应用仍然可以与设备的服务交互, 始终调用 bluetoothLowEnergy.connect 以防止其他应用断开与 实体链接。

一旦不再需要连接您的应用,它就可以通过 调用 bluetoothLowEnergy.disconnect

chrome.bluetoothLowEnergy.disconnect(deviceAddress);

请注意,这不一定会销毁与设备的物理链接, 与设备有活跃连接的应用。有时,设备可能会 已因超出应用控制范围的原因(例如, 消失或被用户通过操作系统的实用程序明确断开连接)。 您的应用应观察 bluetooth.onDeviceChanged 事件以获取有关更改的通知 连接到该连接,并在必要时重新连接。

建立连接后,运行 Chrome 的设备将具有核心角色,而 据说远程设备发挥外围设备角色。此时,您的应用可以 与设备上的服务共享内容。注意: API 目前不支持充当 LE 外围设备;应用只能实现核心角色。

服务、特征和描述符

蓝牙低功耗协议基于一个名为属性协议的简单请求-响应协议 (ATT)。中央设备使用 ATT 与外围设备上的所谓“属性”交互 称为通用属性配置文件 (GATT) 的特殊蓝牙配置文件。GATT 定义了以下高级概念:

  • 服务:GATT 服务表示为实现 特定功能例如,心率监测器通常至少具有 一项“心率服务”。GATT 服务的相关信息包含在 bluetoothLowEnergy.Service 对象的支持。
  • 特征:GATT 特征是用于构建 GATT 服务的基本数据元素, 包含 值以及定义如何访问该值的属性。例如: “心率服务”提供“心率测量”特征,用于获取 用户的心率值。GATT 特征的相关信息包含在 bluetoothLowEnergy.Characteristic 对象的属性。
  • 描述符:GATT 特征描述符,其中包含有关特征的更多信息。 GATT 特征描述符的相关信息包含在 bluetoothLowEnergy.Descriptor 对象的操作。

借助 Bluetooth Low Energy API,应用可以查找与设备的 通过调用 bluetoothLowEnergy.getServices 来对服务、特征和描述符进行标记, bluetoothLowEnergy.getCharacteristicsbluetoothLowEnergy.getDescriptors。应用可以 通过将服务、特征和描述符的 uuid 字段与 所需的 GATT UUID:

chrome.bluetoothLowEnergy.getServices(deviceAddress, function(services) {
  ...
  for (var i = 0; i < services.length; i++) {
    if (services[i].uuid == HEART_RATE_SERVICE_UUID) {
      heartRateService = services[i];
      break;
    }
  }
  ...
});

可通过该 API 访问的每项服务、特征和描述符都分配有一个唯一的 实例标识符,可使用 instanceId 字段获取。此实例 ID 可以是 来标识 GATT 对象并对其执行特定操作:

chrome.bluetoothLowEnergy.getCharacteristics(heartRateService.instanceId,
                                             function(chracteristics) {
  ...
  for (var i = 0; i < characteristics.length; i++) {
    if (characteristics[i].uuid == HEART_RATE_MEASUREMENT_UUID) {
      measurementChar = characteristics[i];
      break;
    }
  }
  ...
  chrome.bluetoothLowEnergy.getDescriptors(measurementChar.instanceId,
                                           function(descriptors) {
    ...
  });
});

服务事件

一旦设备成功连接,Chrome 便会发现其服务。随着每项服务的 那么应用将收到 bluetoothLowEnergy.onServiceAdded 以及 bluetoothLowEnergy.onServiceRemoved 事件:

  var initializeService = function(service) {
    if (!service) {
      console.log('No service selected!');
      // Reset UI, etc.
      ...
      return;
    }

    myService = service;

    // Get all the characteristics and descriptors and bootstrap the app.
    ...
  };

  chrome.bluetoothLowEnergy.onServiceAdded.addListener(function(service) {
    if (service.uuid == MY_SERVICE_UUID)
      initializeService(service);
  });

  chrome.bluetoothLowEnergy.onServiceRemoved.addListener(function(service) {
    if (service.instanceId == myService.instanceId)
      initializeService(null);
  });

Chrome 以异步方式发现服务的所有特性和描述符, 一旦发现完成,便会触发 bluetoothLowEnergy.onServiceAdded 事件。如果连接到 外围设备终止,Chrome 会移除所有相关服务,并将 该事件对象是 bluetoothLowEnergy.onServiceRemoved 事件。

某些外围设备可能会修改其服务,例如服务的特性可能会发生变化或 服务可能会彻底添加和移除。Chrome 会使用 bluetoothLowEnergy.onServiceChangedbluetoothLowEnergy.onServiceAddedbluetoothLowEnergy.onServiceRemoved 事件。

  chrome.bluetoothLowEnergy.onServiceChanged.addListener(function(service) {
    if (service.instanceId != myService.instanceId)
      return;

    updateMyService(service);
  });

读取和写入特征的值

GATT 特征对其服务的一个方面进行编码。中央应用会读取内容、执行操作和修改 通过计算特征值来获取外围设备的服务状态。特征 值是一个字节序列,其含义由定义 特定特征。例如,心率测量特性的值 会对用户的心率和消耗的卡路里总数进行编码,而身体传感器 位置特征编码了心率传感器应佩戴在身体部位的什么位置。

Chrome 提供 bluetoothLowEnergy.readCharacteristicValue 方法来读取 特征:

chrome.bluetoothLowEnergy.readCharacteristicValue(chrc.instanceId,
                                                  function(result) {
  if (chrome.runtime.lastError) {
    console.log('Failed to read value: ' + chrome.runtime.lastError.message);
    return;
  }

  var bytes = new Uint8Array(result.value);

  // Do stuff with the bytes.
  ...
});

某些特征是可写的,特别是那些充当“控制点”的特征, 该值会产生副作用。例如,心率控制点特性用于 指示心率传感器重置其消耗的总卡路里数,并且仅支持写入。接收者 因此,Chrome 提供了 bluetoothLowEnergy.writeCharacteristicValue 方法:

var myBytes = new Uint8Array([ ... ]);
chrome.bluetoothLowEnergy.writeCharacteristicValue(chrc.instanceId,
                                                   myBytes.buffer,
                                                   function() {
  if (chrome.runtime.lastError) {
    console.log('Failed to write value: ' +
                chrome.runtime.lastError.message);
    return;
  }

  // Value is written now.
});

特征描述符的行为方式相同,并且具有可读和/或可写性。Chrome 提供 bluetoothLowEnergy.readDescriptorValuebluetoothLowEnergy.writeDescriptorValue 方法来读取和写入描述符的值。

如需检查某个特性是否支持读取或写入,应用可以检查 properties bluetoothLowEnergy.Characteristic 对象的字段中。虽然此字段不包含 访问某个值的安全性要求的相关信息, 操作。

处理价值通知

某些特性通过通知或指示来告知其值。例如, 心率测量特性既不可读也不写入,但会在其 定期生成当前值。应用程序可以使用 该事件为 bluetoothLowEnergy.onCharacteristicValueChanged 事件。

  chrome.bluetoothLowEnergy.onCharacteristicValueChanged.addListener(
      function(chrc) {
    if (chrc.instanceId != myCharId)
      return;

    var bytes = new Uint8Array(chrc.value);

    // Do stuff with the bytes.
    ...
  });

即使某个特性支持通知/指示,它们在默认情况下也不会启用。一个 应用应调用 bluetoothLowEnergy.startCharacteristicNotifications 并且 使用 bluetoothLowEnergy.stopCharacteristicNotifications 方法来开始或停止接收 该事件对象是 bluetoothLowEnergy.onCharacteristicValueChanged 事件。

  // Start receiving characteristic value notifications.
  var notifying = false;
  chrome.bluetoothLowEnergy.startCharacteristicNotifications(chrc.instanceId,
                                                             function() {
    if (chrome.runtime.lastError) {
      console.log('Failed to enable notifications: ' +
                  chrome.runtime.lastError.message);
      return;
    }

    notifying = true;
  });

  ...

  // No longer interested in notifications from this characteristic.
  if (notifying) {
    chrome.bluetoothLowEnergy.stopCharacteristicNotifications(
        chrc.instanceId);
  }

启动通知后,该应用将收到 每当通知或指示出现时,调用 bluetoothLowEnergy.onCharacteristicValueChanged 从该特征接收的值。如果该特性支持读取,则此事件也将 在成功调用 bluetoothLowEnergy.readCharacteristicValue 后发送。此权限可让应用 以统一通过读取请求和通知触发的值更新的控制流:

  chrome.bluetoothLowEnergy.onCharacteristicValueChanged.addListener(
      function(chrc) {
    // Process the value.
    ...
  });

  chrome.bluetoothLowEnergy.startCharacteristicNotifications(chrc.instanceId,
                                                             function() {
    // Notifications started. Read the initial value.
    chrome.bluetoothLowEnergy.readCharacteristicValue(chrc.instanceId,
                                                      function(result) {
      ...
      // No need to do anything here since onCharacteristicValueChanged
      // will handle it.
    });
  });

如果某个特征支持通知,其 properties 字段将包含 "notify""indicate" 属性。

注意:如果某个特征支持通知/指示,其将具有“客户端 特征配置”描述符,用于启用/停用通知。Chrome 不允许 写入此描述符的应用。应用应改用 bluetoothLowEnergy.startCharacteristicNotifications 和 用于控制通知行为的 bluetoothLowEnergy.stopCharacteristicNotifications 方法。