자바스크립트를 통해 블루투스 기기와 통신하기

Web Bluetooth API를 사용하면 웹사이트가 블루투스 기기와 통신할 수 있습니다.

François Beaufort
François Beaufort

웹사이트가 안전하고 개인 정보 보호 방식으로 근처 블루투스 기기와 통신할 수 있다면 어떨까요? 이러한 방식으로 심박수 모니터, 노래하는 전구, 심지어 거북이도 웹사이트와 직접 상호작용할 수 있습니다.

지금까지는 플랫폼별 앱만 블루투스 기기와 상호작용할 수 있었습니다. Web Bluetooth API는 이를 변경하고 웹브라우저에도 제공하는 것을 목표로 합니다.

시작하기 전에

이 문서에서는 블루투스 저에너지 (BLE) 및 일반 속성 프로필의 작동 방식에 대한 기본적인 지식이 있다고 가정합니다.

Web Bluetooth API 사양이 아직 확정되지 않았지만 사양 작성자는 이 API를 사용해 보고 사양에 대한 의견구현에 대한 의견을 제공할 열정적인 개발자를 적극적으로 찾고 있습니다.

Web Bluetooth API의 일부는 ChromeOS, Android 6.0용 Chrome, Mac (Chrome 56), Windows 10 (Chrome 70)에서 사용할 수 있습니다. 즉, 근처의 저전력 블루투스 기기를 요청하고 연결하며, 블루투스 특성을 읽고/쓰며, GATT 알림을 수신하고, 블루투스 기기가 연결 해제되는 시점을 알며, 심지어 블루투스 설명자를 읽고 쓸 수 있습니다. 자세한 내용은 MDN의 브라우저 호환성 표를 참고하세요.

Linux 및 이전 버전의 Windows의 경우 about://flags에서 #experimental-web-platform-features 플래그를 사용 설정합니다.

오리진 트라이얼에서 사용 가능

필드에서 Web Bluetooth API를 사용하는 개발자로부터 최대한 많은 의견을 받기 위해 Chrome은 이전에 Chrome 53에 ChromeOS, Android, Mac용 오리진 트라이얼로 이 기능을 추가했습니다.

평가판은 2017년 1월에 성공적으로 종료되었습니다.

보안 요구사항

보안 트레이드 오프를 이해하려면 Web Bluetooth API 사양을 작업하는 Chrome팀의 소프트웨어 엔지니어인 Jeffrey Yasskin의 Web Bluetooth 보안 모델 게시물을 참고하세요.

HTTPS만

이 실험용 API는 웹에 추가된 강력한 새 기능이므로 보안 컨텍스트에서만 사용할 수 있습니다. 즉, TLS를 고려하여 빌드해야 합니다.

사용자 동작 필요

보안 기능으로 navigator.bluetooth.requestDevice를 사용하여 블루투스 기기를 검색하는 것은 터치나 마우스 클릭과 같은 사용자 동작으로 트리거해야 합니다. pointerup, click, touchend 이벤트를 수신 대기하는 것에 관한 내용입니다.

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

코드로 이동

Web Bluetooth API는 JavaScript 프로미스를 많이 사용합니다. 익숙하지 않다면 이 Promises 튜토리얼을 확인하세요. 한 가지 더 말씀드리면 () => {}는 ECMAScript 2015 화살표 함수입니다.

블루투스 기기 요청

이 버전의 Web Bluetooth API 사양을 사용하면 중심 역할로 실행되는 웹사이트가 BLE 연결을 통해 원격 GATT 서버에 연결할 수 있습니다. 블루투스 4.0 이상을 구현하는 기기 간 통신을 지원합니다.

웹사이트에서 navigator.bluetooth.requestDevice를 사용하여 근처 기기에 대한 액세스를 요청하면 브라우저에서 사용자가 기기를 선택하거나 요청을 취소할 수 있는 기기 선택기를 표시합니다.

블루투스 기기 사용자 프롬프트

navigator.bluetooth.requestDevice() 함수는 필터를 정의하는 필수 객체를 사용합니다. 이러한 필터는 광고된 블루투스 GATT 서비스 또는 기기 이름과 일치하는 기기만 반환하는 데 사용됩니다.

서비스 필터

예를 들어 블루투스 GATT 배터리 서비스를 광고하는 블루투스 기기를 요청하려면 다음을 실행합니다.

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

하지만 블루투스 GATT 서비스가 표준화된 블루투스 GATT 서비스 목록에 없는 경우 전체 블루투스 UUID 또는 짧은 16비트 또는 32비트 형식을 제공할 수 있습니다.

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

이름 필터

name 필터 키로 광고되는 기기 이름이나 namePrefix 필터 키로 이 이름의 접두사를 기반으로 블루투스 기기를 요청할 수도 있습니다. 이 경우 서비스 필터에 포함되지 않은 서비스에 액세스하려면 optionalServices 키도 정의해야 합니다. 그렇지 않으면 나중에 액세스하려고 할 때 오류가 발생합니다.

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

제조업체 데이터 필터

manufacturerData 필터 키와 함께 광고되는 제조업체별 데이터를 기반으로 블루투스 기기를 요청할 수도 있습니다. 이 키는 companyIdentifier라는 필수 블루투스 회사 식별자 키가 있는 객체의 배열입니다. 데이터 접두사를 제공하여 해당 접두사로 시작하는 블루투스 기기의 제조업체 데이터를 필터링할 수도 있습니다. 서비스 필터에 포함되지 않은 서비스에 액세스하려면 optionalServices 키도 정의해야 합니다. 그렇지 않으면 나중에 액세스하려고 할 때 오류가 발생합니다.

// 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); });

마스크는 데이터 접두사와 함께 사용하여 제조업체 데이터의 일부 패턴을 일치시킬 수도 있습니다. 자세한 내용은 블루투스 데이터 필터 설명을 참고하세요.

제외 필터

navigator.bluetooth.requestDevice()exclusionFilters 옵션을 사용하면 브라우저 선택기에서 일부 기기를 제외할 수 있습니다. 더 광범위한 필터와 일치하지만 지원되지 않는 기기를 제외하는 데 사용할 수 있습니다.

// 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); });

필터 없음

마지막으로 filters 대신 acceptAllDevices 키를 사용하여 근처의 모든 블루투스 기기를 표시할 수 있습니다. 일부 서비스에 액세스하려면 optionalServices 키도 정의해야 합니다. 그렇지 않으면 나중에 액세스하려고 할 때 오류가 발생합니다.

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

블루투스 기기에 연결

이제 BluetoothDevice가 있으니 어떻게 해야 할까요? 서비스 및 특성 정의를 보유하는 블루투스 원격 GATT 서버에 연결해 보겠습니다.

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); });

블루투스 특성 읽기

여기서는 원격 블루투스 기기의 GATT 서버에 연결합니다. 이제 기본 GATT 서비스를 가져와 이 서비스에 속한 특성을 읽어보겠습니다. 예를 들어 기기 배터리의 현재 충전 수준을 읽어 보겠습니다.

앞의 예시에서 battery_level표준화된 배터리 잔량 특성입니다.

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); });

맞춤 블루투스 GATT 특성을 사용하는 경우 전체 블루투스 UUID 또는 짧은 16비트 또는 32비트 형식을 service.getCharacteristic에 제공할 수 있습니다.

특성에 characteristicvaluechanged 이벤트 리스너를 추가하여 값을 읽을 수도 있습니다. 특성 값 변경 읽기 샘플을 확인하여 예정된 GATT 알림을 선택적으로 처리하는 방법도 알아보세요.


.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);
}

블루투스 특성에 쓰기

블루투스 GATT 특성에 쓰는 것은 읽는 것만큼 쉽습니다. 이번에는 심박수 모니터 기기에서 심박수 제어점을 사용하여 에너지 소비량 필드의 값을 0으로 재설정해 보겠습니다.

여기에는 마법이 없습니다. 자세한 내용은 심박수 제어 지점 특성 페이지를 참고하세요.

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); });

GATT 알림 수신

이제 기기에서 심박수 측정 특성이 변경될 때 알림을 받는 방법을 살펴보겠습니다.

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
}

알림 샘플에서는 stopNotifications()로 알림을 중지하고 추가된 characteristicvaluechanged 이벤트 리스너를 올바르게 삭제하는 방법을 보여줍니다.

블루투스 기기에서 연결 해제

더 나은 사용자 환경을 제공하려면 연결 해제 이벤트를 수신 대기하고 사용자에게 다시 연결하도록 요청하는 것이 좋습니다.

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.`);
}

device.gatt.disconnect()를 호출하여 웹 앱을 블루투스 기기에서 연결 해제할 수도 있습니다. 이렇게 하면 기존 gattserverdisconnected 이벤트 리스너가 트리거됩니다. 다른 앱이 이미 블루투스 기기와 통신하고 있는 경우 블루투스 기기 통신이 중지되지 않습니다. 기기 연결 해제 샘플자동 재연결 샘플을 확인하여 자세히 알아보세요.

블루투스 설명자 읽기 및 쓰기

블루투스 GATT 설명자는 특성 값을 설명하는 속성입니다. 블루투스 GATT 특성과 유사한 방식으로 읽고 쓸 수 있습니다.

예를 들어 기기의 건강 온도계의 측정 간격에 관한 사용자 설명을 읽는 방법을 살펴보겠습니다.

아래 예에서 health_thermometerHealth Thermometer service이고, measurement_intervalMeasurement Interval characteristic이며, gatt.characteristic_user_descriptionCharacteristic User Description descriptor입니다.

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); });

이제 기기의 건강 온도계의 측정 간격에 관한 사용자 설명을 읽었으므로 이를 업데이트하고 맞춤 값을 쓰는 방법을 살펴보겠습니다.

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); });

샘플, 데모, Codelabs

아래의 모든 Web Bluetooth 샘플이 성공적으로 테스트되었습니다. 이러한 샘플을 최대한 활용하려면 배터리 서비스, 심박수 서비스 또는 건강 온도계 서비스가 있는 BLE 주변기기를 시뮬레이션하는 [BLE 주변기기 시뮬레이터 Android 앱] 을 설치하는 것이 좋습니다.

초급

  • 기기 정보 - BLE 기기에서 기본 기기 정보를 가져옵니다.
  • 배터리 잔량 - 배터리 정보를 광고하는 BLE 기기에서 배터리 정보를 가져옵니다.
  • Reset Energy - BLE 기기에서 심박수를 광고할 때 소모된 에너지를 재설정합니다.
  • 특성 속성 - BLE 기기의 특정 특성의 모든 속성을 표시합니다.
  • 알림 - BLE 기기에서 특성 알림을 시작하고 중지합니다.
  • 기기 연결 해제 - BLE 기기에 연결한 후 연결 해제 시 연결을 해제하고 알림을 받습니다.
  • 특성 가져오기 - BLE 기기에서 광고된 서비스의 모든 특성을 가져옵니다.
  • 설명자 가져오기 - BLE 기기에서 광고된 서비스의 모든 특성 설명자를 가져옵니다.
  • 제조업체 데이터 필터 - 제조업체 데이터와 일치하는 BLE 기기에서 기본 기기 정보를 가져옵니다.
  • 제외 필터 - 기본 제외 필터가 적용된 BLE 기기에서 기본 기기 정보를 가져옵니다.

여러 작업 결합

  • GAP 특성 - BLE 기기의 모든 GAP 특성을 가져옵니다.
  • 기기 정보 특성 - BLE 기기의 모든 기기 정보 특성을 가져옵니다.
  • 링크 손실 - BLE 기기의 경고 수준 특성을 설정합니다 (readValue 및 writeValue).
  • 서비스 및 특성 검색 - BLE 기기에서 액세스 가능한 모든 기본 서비스와 그 특성을 검색합니다.
  • 자동 다시 연결 - 지수 백오프 알고리즘을 사용하여 연결이 해제된 BLE 기기에 다시 연결합니다.
  • 특성 값 변경 읽기 - 배터리 잔량을 읽고 BLE 기기의 변경사항에 대한 알림을 받습니다.
  • 설명자 읽기 - BLE 기기에서 서비스의 모든 특성 설명자를 읽습니다.
  • 쓰기 설명자 - BLE 기기의 '특성 사용자 설명' 설명자에 씁니다.

엄선된 Web Bluetooth 데모공식 Web Bluetooth Codelab도 확인해 보세요.

라이브러리

  • web-bluetooth-utils는 API에 편의 기능을 추가하는 npm 모듈입니다.
  • 가장 인기 있는 Node.js BLE 중앙 모듈인 noble에서 Web Bluetooth API shim을 사용할 수 있습니다. 이렇게 하면 WebSocket 서버나 기타 플러그인 없이 noble을 webpack/browserify할 수 있습니다.
  • angular-web-bluetooth는 Web Bluetooth API를 구성하는 데 필요한 모든 상용구를 추상화하는 Angular용 모듈입니다.

도구

  • Get Started with Web Bluetooth는 블루투스 기기와 상호작용을 시작하기 위한 모든 JavaScript 상용구 코드를 생성하는 간단한 웹 앱입니다. 기기 이름, 서비스, 특성을 입력하고 속성을 정의하면 됩니다.
  • 이미 블루투스 개발자인 경우 Web Bluetooth Developer Studio 플러그인은 블루투스 기기의 Web Bluetooth JavaScript 코드도 생성합니다.

블루투스 내부 페이지는 Chrome의 about://bluetooth-internals에서 사용할 수 있으므로 근처의 블루투스 기기에 관한 모든 항목(상태, 서비스, 특성, 설명자)을 검사할 수 있습니다.

Chrome에서 블루투스를 디버깅하는 내부 페이지의 스크린샷
블루투스 기기를 디버깅하기 위한 Chrome의 내부 페이지입니다.

블루투스를 디버깅하는 것이 어려울 수 있으므로 공식 Web 블루투스 버그를 신고하는 방법 페이지도 확인해 보시기 바랍니다.

다음 단계

먼저 브라우저 및 플랫폼 구현 상태를 확인하여 현재 구현 중인 Web Bluetooth API의 부분을 파악하세요.

아직 미완성이지만 가까운 미래에 제공될 기능을 미리 살펴보세요.

  • 근처 BLE 광고 검색navigator.bluetooth.requestLEScan()로 실행됩니다.
  • serviceadded 이벤트는 새로 발견된 블루투스 GATT 서비스를 추적하고 serviceremoved 이벤트는 삭제된 서비스를 추적합니다. 특성 또는 설명자가 블루투스 GATT 서비스에서 추가되거나 삭제되면 새 servicechanged 이벤트가 발생합니다.

API 지원 표시

Web Bluetooth API를 사용할 계획인가요? 공개적인 지원은 Chrome팀이 기능을 우선순위로 지정하는 데 도움이 되며 다른 브라우저 공급업체에 이러한 기능 지원이 얼마나 중요한지 보여줍니다.

#WebBluetooth 해시태그를 사용하여 @ChromiumDev에 트윗을 보내 어디에서 어떻게 사용하고 있는지 알려주세요.

리소스

감사의 말씀

검토해 주신 Kayce Basques님께 감사드립니다.