Giao tiếp với thiết bị Bluetooth qua JavaScript

API Web Bluetooth cho phép các trang web giao tiếp với thiết bị Bluetooth.

François Beaufort
François Beaufort

Điều gì sẽ xảy ra nếu tôi nói với bạn rằng các trang web có thể giao tiếp với các thiết bị Bluetooth ở gần theo cách an toàn và bảo đảm quyền riêng tư? Bằng cách này, máy đo nhịp tim, bóng đèn hát và thậm chí rùa có thể tương tác trực tiếp với một trang web.

Cho đến nay, khả năng tương tác với thiết bị Bluetooth chỉ có thể thực hiện cho các ứng dụng dành riêng cho nền tảng. Mục đích của API Web Bluetooth là thay đổi điều này cũng như đưa API này sang trình duyệt web.

Trước khi chúng ta bắt đầu

Tài liệu này giả định rằng bạn có một số kiến thức cơ bản về cách hoạt động của Bluetooth năng lượng thấp (BLE) và Hồ sơ thuộc tính chung.

Mặc dù quy cách API Web Bluetooth chưa được hoàn thiện, nhưng tác giả bản đặc tả kỹ thuật vẫn đang tích cực tìm kiếm các nhà phát triển nhiệt tình dùng thử API này và đưa ra ý kiến phản hồi về quy cách cũng như ý kiến phản hồi về việc triển khai.

Một số ít API Web Bluetooth hiện có trong ChromeOS, Chrome dành cho Android 6.0, Mac (Chrome 56) và Windows 10 (Chrome 70). Tức là bạn sẽ có thể yêu cầukết nối với các thiết bị Bluetooth năng lượng thấp ở gần, đọc/ghi các đặc điểm Bluetooth, nhận thông báo GATT, biết khi nào thiết bị Bluetooth bị ngắt kết nối và thậm chí đọc và ghi vào bộ mô tả Bluetooth. Xem bảng Khả năng tương thích với trình duyệt của MDN để biết thêm thông tin.

Đối với Linux và các phiên bản Windows cũ hơn, hãy bật cờ #experimental-web-platform-features trong about://flags.

Có thể dùng bản dùng thử theo nguyên gốc

Để nhận được nhiều ý kiến phản hồi nhất có thể từ các nhà phát triển sử dụng API Web Bluetooth trong trường này, trước đây Chrome đã thêm tính năng này vào Chrome 53 làm bản dùng thử theo nguyên gốc cho ChromeOS, Android và máy Mac.

Thử nghiệm đã kết thúc thành công vào tháng 1 năm 2017.

Yêu cầu về bảo mật

Để hiểu được các đánh đổi về bảo mật, bạn nên xem bài đăng Mô hình bảo mật Bluetooth trên web của Jeffrey Yasskin, một kỹ sư phần mềm thuộc nhóm Chrome, chuyên phụ trách quy cách API Web Bluetooth.

Chỉ HTTPS

Vì API thử nghiệm này là một tính năng mới mạnh mẽ được thêm vào web nên API này chỉ được cung cấp cho các ngữ cảnh bảo mật. Tức là bạn cần tạo bản dựng có lưu ý đến TLS.

Yêu cầu cử chỉ của người dùng

Là một tính năng bảo mật, việc phát hiện thiết bị Bluetooth có navigator.bluetooth.requestDevice phải được kích hoạt bằng cử chỉ của người dùng, chẳng hạn như thao tác chạm hoặc nhấp chuột. Chúng ta đang nói về việc nghe các sự kiện pointerup, clicktouchend.

button.addEventListener('pointerup', function(event) {
  // Call navigator.bluetooth.requestDevice
});

Tìm hiểu về đoạn mã

API Web Bluetooth phụ thuộc rất nhiều vào Promise (Lời hứa) của JavaScript. Nếu bạn chưa quen với các dịch vụ đó, hãy xem hướng dẫn về Promises tuyệt vời này. Ngoài ra, () => {}các hàm mũi tên của ECMAScript 2015.

Yêu cầu thiết bị Bluetooth

Phiên bản thông số kỹ thuật của API Web Bluetooth này cho phép các trang web (chạy ở vai trò Trung tâm) kết nối với Máy chủ GATT từ xa qua kết nối BLE. API này hỗ trợ hoạt động giao tiếp giữa các thiết bị triển khai Bluetooth 4.0 trở lên.

Khi một trang web yêu cầu quyền truy cập vào các thiết bị ở gần bằng navigator.bluetooth.requestDevice, trình duyệt sẽ nhắc người dùng bằng trình chọn thiết bị để họ có thể chọn một thiết bị hoặc huỷ yêu cầu.

Lời nhắc dành cho người dùng thiết bị Bluetooth.

Hàm navigator.bluetooth.requestDevice() lấy một đối tượng bắt buộc xác định các bộ lọc. Các bộ lọc này chỉ dùng để trả về các thiết bị khớp với một số dịch vụ Bluetooth GATT đã quảng cáo và/hoặc tên thiết bị.

Bộ lọc dịch vụ

Ví dụ: để yêu cầu các thiết bị Bluetooth quảng cáo Dịch vụ pin GATT của Bluetooth:

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Tuy nhiên, nếu Dịch vụ Bluetooth GATT không có trong danh sách dịch vụ GATT đã chuẩn hoá, thì bạn có thể cung cấp UUID Bluetooth đầy đủ hoặc dạng ngắn 16 hoặc 32 bit.

navigator.bluetooth.requestDevice({
  filters: [{
    services: [0x1234, 0x12345678, '99999999-0000-1000-8000-00805f9b34fb']
  }]
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Bộ lọc tên

Bạn cũng có thể yêu cầu thiết bị Bluetooth dựa trên tên thiết bị đang được quảng cáo bằng khoá bộ lọc name hoặc thậm chí là tiền tố của tên này bằng khoá bộ lọc namePrefix. Xin lưu ý rằng trong trường hợp này, bạn cũng cần xác định khoá optionalServices để có thể truy cập vào mọi dịch vụ không có trong bộ lọc dịch vụ. Nếu không, bạn sẽ gặp lỗi sau này khi cố gắng truy cập vào các lớp đó.

navigator.bluetooth.requestDevice({
  filters: [{
    name: 'Francois robot'
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Bộ lọc dữ liệu của nhà sản xuất

Bạn cũng có thể yêu cầu thiết bị Bluetooth dựa trên dữ liệu cụ thể của nhà sản xuất đang được quảng cáo bằng khoá bộ lọc manufacturerData. Khoá này là một mảng các đối tượng có khoá mã nhận dạng công ty Bluetooth bắt buộc có tên là companyIdentifier. Bạn cũng có thể cung cấp một tiền tố dữ liệu để lọc dữ liệu của nhà sản xuất khỏi các thiết bị Bluetooth bắt đầu bằng tiền tố đó. Xin lưu ý rằng bạn cũng cần xác định khoá optionalServices để có thể truy cập vào bất kỳ dịch vụ nào không có trong bộ lọc dịch vụ. Nếu không, bạn sẽ gặp lỗi sau này khi cố gắng truy cập vào các tệp đó.

// Filter Bluetooth devices from Google company with manufacturer data bytes
// that start with [0x01, 0x02].
navigator.bluetooth.requestDevice({
  filters: [{
    manufacturerData: [{
      companyIdentifier: 0x00e0,
      dataPrefix: new Uint8Array([0x01, 0x02])
    }]
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Bạn cũng có thể sử dụng mặt nạ với một tiền tố dữ liệu để khớp với một số mẫu trong dữ liệu của nhà sản xuất. Hãy xem bài viết Giải thích về bộ lọc dữ liệu Bluetooth để tìm hiểu thêm.

Bộ lọc loại trừ

Tuỳ chọn exclusionFilters trong navigator.bluetooth.requestDevice() cho phép bạn loại trừ một số thiết bị khỏi bộ chọn của trình duyệt. Bạn có thể dùng thuộc tính này để loại trừ các thiết bị khớp với bộ lọc rộng hơn nhưng không được hỗ trợ.

// Request access to a bluetooth device whose name starts with "Created by".
// The device named "Created by Francois" has been reported as unsupported.
navigator.bluetooth.requestDevice({
  filters: [{
    namePrefix: "Created by"
  }],
  exclusionFilters: [{
    name: "Created by Francois"
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Không dùng bộ lọc

Cuối cùng, thay vì filters, bạn có thể sử dụng khoá acceptAllDevices để hiển thị tất cả các thiết bị Bluetooth ở gần. Bạn cũng sẽ cần xác định khoá optionalServices để có thể truy cập vào một số dịch vụ. Nếu không, bạn sẽ gặp lỗi sau này khi cố gắng truy cập vào các tính năng đó.

navigator.bluetooth.requestDevice({
  acceptAllDevices: true,
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

Kết nối với thiết bị Bluetooth

Vậy bây giờ bạn sẽ làm gì khi có BluetoothDevice? Hãy kết nối với Máy chủ GATT từ xa qua Bluetooth lưu trữ dịch vụ và các định nghĩa đặc điểm.

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => {
  // Human-readable name of the device.
  console.log(device.name);

  // Attempts to connect to remote GATT Server.
  return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });

Đọc đặc điểm Bluetooth

Ở đây, chúng ta kết nối với Máy chủ GATT của thiết bị Bluetooth từ xa. Bây giờ, chúng ta muốn tải Dịch vụ GATT chính và đọc một đặc điểm thuộc dịch vụ này. Chẳng hạn, hãy thử đọc mức pin hiện tại của pin thiết bị.

Trong ví dụ trước, battery_levelĐặc điểm mức pin được chuẩn hoá.

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => device.gatt.connect())
.then(server => {
  // Getting Battery Service…
  return server.getPrimaryService('battery_service');
})
.then(service => {
  // Getting Battery Level Characteristic…
  return service.getCharacteristic('battery_level');
})
.then(characteristic => {
  // Reading Battery Level…
  return characteristic.readValue();
})
.then(value => {
  console.log(`Battery percentage is ${value.getUint8(0)}`);
})
.catch(error => { console.error(error); });

Nếu sử dụng đặc tính Bluetooth GATT tuỳ chỉnh, bạn có thể cung cấp mã nhận dạng duy nhất (UUID) Bluetooth đầy đủ hoặc dạng 16 hoặc 32 bit ngắn cho service.getCharacteristic.

Xin lưu ý rằng bạn cũng có thể thêm trình nghe sự kiện characteristicvaluechanged dựa trên một đặc điểm để xử lý việc đọc giá trị của sự kiện đó. Hãy xem Mẫu đọc giá trị đặc điểm đã thay đổi để xem cách xử lý các thông báo GATT sắp tới (không bắt buộc).

…
.then(characteristic => {
  // Set up event listener for when characteristic value changes.
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleBatteryLevelChanged);
  // Reading Battery Level…
  return characteristic.readValue();
})
.catch(error => { console.error(error); });

function handleBatteryLevelChanged(event) {
  const batteryLevel = event.target.value.getUint8(0);
  console.log('Battery percentage is ' + batteryLevel);
}

Ghi dữ liệu cho một đặc điểm Bluetooth

Việc ghi vào Đặc tính GATT Bluetooth cũng dễ dàng như đọc. Lần này, hãy sử dụng Điểm kiểm soát nhịp tim để đặt lại giá trị của trường Mức tiêu thụ năng lượng về 0 trên thiết bị theo dõi nhịp tim.

Tôi hứa không có phép thuật nào ở đây. Tất cả thông tin này có trên trang Đặc tính điểm kiểm soát nhịp tim.

navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_control_point'))
.then(characteristic => {
  // Writing 1 is the signal to reset energy expended.
  const resetEnergyExpended = Uint8Array.of(1);
  return characteristic.writeValue(resetEnergyExpended);
})
.then(_ => {
  console.log('Energy expended has been reset.');
})
.catch(error => { console.error(error); });

Nhận thông báo GATT

Bây giờ, hãy xem cách nhận thông báo khi tính năng Đo lường nhịp tim thay đổi trên thiết bị:

navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_measurement'))
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleCharacteristicValueChanged);
  console.log('Notifications have been started.');
})
.catch(error => { console.error(error); });

function handleCharacteristicValueChanged(event) {
  const value = event.target.value;
  console.log('Received ' + value);
  // TODO: Parse Heart Rate Measurement value.
  // See https://github.com/WebBluetoothCG/demos/blob/gh-pages/heart-rate-sensor/heartRateSensor.js
}

Mẫu thông báo hướng dẫn bạn cách dừng thông báo bằng stopNotifications() và xoá đúng cách trình nghe sự kiện characteristicvaluechanged đã thêm.

Ngắt kết nối khỏi thiết bị Bluetooth

Để mang lại trải nghiệm người dùng tốt hơn, bạn nên theo dõi các sự kiện ngắt kết nối và mời người dùng kết nối lại:

navigator.bluetooth.requestDevice({ filters: [{ name: 'Francois robot' }] })
.then(device => {
  // Set up event listener for when device gets disconnected.
  device.addEventListener('gattserverdisconnected', onDisconnected);

  // Attempts to connect to remote GATT Server.
  return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });

function onDisconnected(event) {
  const device = event.target;
  console.log(`Device ${device.name} is disconnected.`);
}

Bạn cũng có thể gọi device.gatt.disconnect() để ngắt kết nối ứng dụng web của mình khỏi thiết bị Bluetooth. Thao tác này sẽ kích hoạt các trình nghe sự kiện gattserverdisconnected hiện có. Lưu ý rằng thao tác này sẽ KHÔNG dừng hoạt động giao tiếp của thiết bị Bluetooth nếu một ứng dụng khác đang giao tiếp với thiết bị Bluetooth. Hãy xem Mẫu ngắt kết nối thiết bịMẫu kết nối lại tự động để tìm hiểu kỹ hơn.

Đọc và ghi vào bộ mô tả Bluetooth

Mã mô tả Bluetooth GATT là những thuộc tính mô tả một giá trị đặc điểm. Bạn có thể đọc và ghi chúng vào theo cách tương tự như các đặc điểm của Bluetooth GATT.

Ví dụ: hãy xem cách đọc nội dung mô tả cho người dùng về khoảng thời gian đo lường của nhiệt kế sức khoẻ của thiết bị.

Trong ví dụ bên dưới, health_thermometerdịch vụ Nhiệt kế sức khoẻ, measurement_interval đặc điểm Khoảng thời gian đo lườnggatt.characteristic_user_descriptionMã mô tả đặc điểm về người dùng.

navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => descriptor.readValue())
.then(value => {
  const decoder = new TextDecoder('utf-8');
  console.log(`User Description: ${decoder.decode(value)}`);
})
.catch(error => { console.error(error); });

Bây giờ, chúng ta đã đọc nội dung mô tả cho người dùng về khoảng thời gian đo của nhiệt kế sức khoẻ của thiết bị, hãy xem cách cập nhật và viết một giá trị tuỳ chỉnh.

navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => {
  const encoder = new TextEncoder('utf-8');
  const userDescription = encoder.encode('Defines the time between measurements.');
  return descriptor.writeValue(userDescription);
})
.catch(error => { console.error(error); });

Mẫu, bản minh hoạ và lớp học lập trình

Tất cả mẫu Bluetooth cho web bên dưới đã được kiểm thử thành công. Để tận hưởng tối đa các mẫu này, bạn nên cài đặt [Ứng dụng Android Trình mô phỏng thiết bị ngoại vi BLE]. Ứng dụng này mô phỏng thiết bị ngoại vi BLE thông qua Dịch vụ pin, Dịch vụ Nhịp tim hoặc Dịch vụ Nhiệt kế sức khoẻ.

Cơ bản

  • Thông tin thiết bị – truy xuất thông tin cơ bản về thiết bị từ Thiết bị BLE.
  • Mức pin – truy xuất thông tin về pin từ thông tin về Pin quảng cáo Thiết bị BLE.
  • Đặt lại năng lượng – đặt lại mức năng lượng tiêu thụ qua Thiết bị BLE quảng cáo Nhịp tim.
  • Thuộc tính đặc điểm – hiển thị tất cả các thuộc tính của một đặc điểm cụ thể trên Thiết bị BLE.
  • Thông báo – bắt đầu và dừng thông báo đặc trưng từ một Thiết bị BLE.
  • Ngắt kết nối thiết bị – ngắt kết nối và nhận thông báo khi Thiết bị BLE bị ngắt kết nối sau khi kết nối với thiết bị đó.
  • Nhận đặc điểm – xem tất cả các đặc điểm của dịch vụ được quảng cáo từ Thiết bị BLE.
  • Tải nội dung mô tả – xem tất cả nội dung mô tả đặc điểm của một dịch vụ được quảng cáo trên một Thiết bị BLE.
  • Bộ lọc dữ liệu của nhà sản xuất – lấy thông tin cơ bản về thiết bị từ Thiết bị BLE khớp với dữ liệu của nhà sản xuất.
  • Bộ lọc loại trừ – truy xuất thông tin cơ bản về thiết bị từ Thiết bị BLE có các bộ lọc loại trừ cơ bản.

Kết hợp nhiều toán tử

Bạn cũng có thể tham khảo các bản minh hoạ Bluetooth về web được tuyển chọncác lớp học lập trình chính thức về Bluetooth cho web.

Thư viện

  • web-bluetooth-utils là một mô-đun npm thêm một số hàm tiện lợi vào API.
  • Nút API Web Bluetooth có trong noble, mô-đun trung tâm Node.js BLE phổ biến nhất. Điều này cho phép bạn đóng gói web/trình duyệt cho mọi thứ mà không cần đến máy chủ WebSocket hoặc các trình bổ trợ khác.
  • angular-web-bluetooth là một mô-đun cho Angular, có thể tóm tắt mọi mã nguyên mẫu cần thiết để định cấu hình API Web Bluetooth.

Công cụ

  • Bắt đầu sử dụng Bluetooth cho web là một Ứng dụng web đơn giản giúp tạo tất cả mã nguyên mẫu JavaScript để bắt đầu tương tác với thiết bị Bluetooth. Nhập tên thiết bị, dịch vụ, đặc điểm, xác định các thuộc tính của thiết bị và thế là xong.
  • Nếu bạn đã là nhà phát triển Bluetooth, thì Trình bổ trợ Studio Bluetooth trên web cũng sẽ tạo mã JavaScript Web Bluetooth cho thiết bị Bluetooth của bạn.

Mẹo

Bạn có thể tìm thấy trang Nội bộ Bluetooth trong Chrome tại about://bluetooth-internals để có thể kiểm tra mọi thứ về các thiết bị Bluetooth ở gần: trạng thái, dịch vụ, đặc điểm và thông tin mô tả.

Ảnh chụp màn hình trang nội bộ để gỡ lỗi Bluetooth trong Chrome
Trang nội bộ trong Chrome để gỡ lỗi thiết bị Bluetooth.

Bạn cũng nên xem trang Cách gửi lỗi Bluetooth trên web chính thức vì đôi khi, việc gỡ lỗi Bluetooth có thể gây nhiều khó khăn.

Các bước tiếp theo

Trước tiên, hãy kiểm tra trạng thái triển khai trình duyệt và nền tảng để biết phần nào của API Web Bluetooth hiện đang được triển khai.

Mặc dù vẫn chưa hoàn thiện, nhưng sau đây là vài thông tin sơ lược về những điều sẽ xảy ra trong tương lai gần:

  • Quá trình quét tìm quảng cáo BLE ở gần sẽ diễn ra với navigator.bluetooth.requestLEScan().
  • Một sự kiện serviceadded mới sẽ theo dõi các Dịch vụ GATT Bluetooth mới phát hiện, trong khi sự kiện serviceremoved sẽ theo dõi các Dịch vụ đã bị xoá. Một sự kiện servicechanged mới sẽ kích hoạt khi bất kỳ đặc điểm và/hoặc phần mô tả nào được thêm vào hoặc bị xoá khỏi Dịch vụ Bluetooth GATT.

Hỗ trợ API

Bạn có định sử dụng API Web Bluetooth không? Sự hỗ trợ công khai của bạn giúp nhóm Chrome ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác biết tầm quan trọng của việc hỗ trợ các tính năng đó.

Hãy gửi một dòng tweet đến @ChromiumDev kèm theo hashtag #WebBluetooth và cho chúng tôi biết vị trí cũng như cách bạn đang sử dụng bài đăng này.

Tài nguyên

Xác nhận

Cảm ơn Kayce Basques đã đánh giá bài viết này. Hình ảnh chính của SparkFun Electronics từ Boulder, Hoa Kỳ.