藍牙

本文件說明如何使用藍牙藍牙通訊端藍牙低功耗 API 與藍牙和藍牙低功耗裝置進行通訊。

如需藍牙的背景資訊,請參閱官方的藍牙規格

資訊清單規定

對於使用藍牙的 Chrome 應用程式,請在資訊清單中新增 bluetooth 項目,並視需要指定您要實作的設定檔、通訊協定或服務的 UUID,以及是否要透過通訊端和/或 Low Energy 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.onDeviceAddedbluetooth.onDeviceChangedbluetooth.onDeviceRemoved 事件來接收通知,而非重複呼叫 bluetooth.getDevices

每當轉接程式找到裝置或與轉接程式建立連線時,就會傳送 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 才能繼續。請注意 探索過程中需要大量資源的性質

辨識不同裝置

系統會提供許多不同的選項,用於識別 bluetooth.getDevices 以及相關事件所傳回的裝置。

如果裝置支援藍牙裝置 ID 規格,系統會在裝置物件中新增幾項屬性,其中包含該規格所定義的欄位。示例:

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 裝置類型,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 服務的裝置。這包括市面上的大多數經典藍牙裝置。

連線至插座

如要與裝置建立連線,你需要有三項條件。用於建立連線的通訊端,使用 bluetoothSocket.create 建立;要連線的裝置位址,以及服務本身的 UUID。

在建立連線前,應使用 bluetooth.getDevice 或裝置探索 API 確認轉接器已知道裝置。

建立基礎連線所需的資訊,包括是否使用 RFCOMM 或 L2CAP 通訊協定,以及要使用裝置上的 SDP 探索功能取得的通道或 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 的控制代碼,以便稍後將資料 (bluetoothSocket.send) 傳送到這個通訊端。

接收及傳送至通訊端

使用 ArrayBuffer 物件接收和傳送至通訊端的資料。如要瞭解 ArrayBuffers,請參閱「JavaScript 類型陣列」總覽,以及「如何將 ArrayBuffer 轉換為 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) 或「藍牙 Smart」是一種無線技術,旨在減少耗電量。Bluetooth Low Energy API 可讓應用程式使用與週邊裝置的 LE 連線實作核心角色。以下各節將說明如何探索、連線至藍牙低功耗週邊裝置,以及與這些裝置互動。

探索週邊裝置並建立連線

與傳統藍牙裝置一樣,只要按照「探索鄰近裝置」一文所述的方式發掘 LE 週邊裝置。LE 裝置會傳送名為「廣告資料」的資料封包,讓裝置處於廣告模式可偵測出。廣告資料可能包含裝置可用服務的 UUID。如果有的話,則可使用對應 bluetooth.Device 物件的 uuids 屬性存取這些 UUID。

找到 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.getServicesbluetoothLowEnergy.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 存取的服務、特性和描述元都會獲得指派專屬的執行個體 ID,您可以使用 instanceId 欄位取得這組 ID。這個執行個體 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.onServiceAddedbluetoothLowEnergy.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 方法,讀取及寫入描述元的值。

如要檢查特性是否支援讀取或寫入功能,應用程式可以檢查 bluetoothLowEnergy.Characteristic 物件的 properties 欄位。雖然這個欄位不包含有關存取值的安全性要求相關資訊,但會說明特性通常可支援的值運算。

處理價值通知

有些特性能讓他們透過通知或指示瞭解其價值。舉例來說,「心率測量」特性無法讀取或寫入,但會定期傳送目前值的更新。應用程式可以使用 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.startCharacteristicNotificationsbluetoothLowEnergy.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.startCharacteristicNotificationsbluetoothLowEnergy.stopCharacteristicNotifications 方法,控制通知行為。