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

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

François Beaufort
François Beaufort

웹사이트가 안전하고 개인 정보를 보호하는 방식으로 주변 블루투스 기기와 통신할 수 있다고 하면 믿으시겠어요? 이렇게 하면 심박수 모니터, 노래하는 전구, 심지어 거북이까지 웹사이트와 직접 상호작용할 수 있습니다.

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

시작하기 전에

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

Web Bluetooth API 사양은 아직 최종적으로 결정되지 않았지만 사양 작성자는 이 API를 사용해 보고 사양에 관한 의견구현에 관한 의견을 제공해 줄 열정적인 개발자를 적극적으로 모집하고 있습니다.

Web Bluetooth API의 하위 집합은 ChromeOS, Android용 Chrome 6.0, Mac (Chrome 56), Windows 10 (Chrome 70)에서 사용할 수 있습니다. 즉, 근처의 저전력 블루투스 기기를 요청연결하고, 블루투스 특성을 읽기/write하고, GATT 알림을 수신하고, 블루투스 기기의 연결이 끊어진 시점을 파악하고, 블루투스 설명자를 읽고 쓰기까지 할 수 있어야 합니다. 자세한 내용은 MDN의 브라우저 호환성 표를 참고하세요.

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

출처 무료 체험판에서 사용 가능

현장에서 Web Bluetooth API를 사용하는 개발자로부터 최대한 많은 의견을 수렴하기 위해 Chrome은 이전에 ChromeOS, Android, Mac용 시작 체험판으로 이 기능을 Chrome 53에 추가했습니다.

무료 체험판은 2017년 1월에 종료되었습니다.

보안 요구사항

보안 절충점을 이해하려면 Web Bluetooth API 사양을 담당하는 Chrome팀의 소프트웨어 엔지니어인 제프리 야스킨의 Web Bluetooth 보안 모델 게시물을 참고하세요.

HTTPS 전용

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

사용자 동작 필요

보안 기능으로서 navigator.bluetooth.requestDevice를 사용하여 블루투스 기기를 검색하려면 터치나 마우스 클릭과 같은 사용자 제스처로 트리거해야 합니다. pointerup, click, touchend 이벤트를 수신 대기하는 것을 말합니다.

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

코드 살펴보기

Web Bluetooth API는 JavaScript 프로미스에 크게 의존합니다. 이 기능에 익숙하지 않다면 프로미스 튜토리얼을 참조하세요. 한 가지 더, () => {}는 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_thermometer건강 온도계 서비스, measurement_interval 측정 간격 특성, gatt.characteristic_user_description 특성 사용자 설명 설명어입니다.

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

샘플, 데모, Codelab

아래의 모든 웹 블루투스 샘플이 테스트를 완료했습니다. 이러한 샘플을 최대한 활용하려면 배터리 서비스, 심박수 서비스 또는 건강 온도계 서비스를 사용하여 BLE 주변기기를 시뮬레이션하는 [BLE 주변기기 시뮬레이터 Android 앱] 을 설치하는 것이 좋습니다.

초급

  • 기기 정보 - BLE 기기에서 기본 기기 정보를 가져옵니다.
  • 배터리 수준: 배터리 정보를 광고하는 BLE 기기에서 배터리 정보를 가져옵니다.
  • 에너지 재설정 - 심박수를 광고하는 BLE 기기에서 소비된 에너지를 재설정합니다.
  • Characteristic Properties: BLE 기기의 특정 특성의 모든 속성을 표시합니다.
  • 알림 - BLE 기기에서 특성 알림을 시작하고 중지합니다.
  • 기기 연결 해제 - BLE 기기에 연결한 후 연결을 해제하고 연결 해제 알림을 받습니다.
  • Get Characteristics - BLE 기기에서 광고된 서비스의 모든 특성을 가져옵니다.
  • Get Descriptors - BLE 기기에서 광고된 서비스의 모든 특성 설명자를 가져옵니다.
  • 제조업체 데이터 필터: BLE 기기에서 제조업체 데이터와 일치하는 기본 기기 정보를 검색합니다.
  • 제외 필터: 기본 제외 필터가 있는 BLE 기기에서 기본 기기 정보를 가져옵니다.

여러 작업 결합

  • GAP 특성: BLE 기기의 모든 GAP 특성을 가져옵니다.
  • Device Information Characteristics - BLE 기기의 모든 기기 정보 특성을 가져옵니다.
  • 링크 손실 - BLE 기기의 알림 수준 특성 (readValue 및 writeValue)을 설정합니다.
  • 서비스 및 특성 탐색 - BLE 기기에서 액세스할 수 있는 모든 기본 서비스와 해당 특성을 탐색합니다.
  • 자동 다시 연결: 지수 백오프 알고리즘을 사용하여 연결 해제된 BLE 기기에 다시 연결합니다.
  • Read Characteristic Value Changed: 배터리 잔량을 읽고 BLE 기기의 변경사항에 관한 알림을 받습니다.
  • Read Descriptors: BLE 기기에서 서비스의 모든 특성 설명어를 읽습니다.
  • Write Descriptor: BLE 기기의 설명어 'Characteristic User Description'(특성 사용자 설명)에 작성합니다.

선별된 웹 블루투스 데모공식 웹 블루투스 Codelab도 확인해 보세요.

라이브러리

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

도구

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

Chrome의 about://bluetooth-internals에서 블루투스 내부 페이지를 사용하면 주변 블루투스 기기의 상태, 서비스, 특성, 설명자 등 모든 항목을 검사할 수 있습니다.

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

또한 블루투스 디버깅이 어려울 수 있으므로 공식 웹 블루투스 버그 신고 방법 페이지를 확인하는 것이 좋습니다.

다음 단계

먼저 브라우저 및 플랫폼 구현 상태를 확인하여 현재 Web Bluetooth API의 어떤 부분이 구현되고 있는지 알아봅니다.

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

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

API 지원 표시

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

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

리소스

감사의 말씀

이 도움말을 검토해 주신 케이스 바스케스님께 감사드립니다. 미국 볼더의 SparkFun Electronics의 히어로 이미지