웹 블루투스로 PLAYBULB 원통 제어

1. 어떤 내용인가요?

IMG_19700101_023537~2~2.jpg

이 Codelab에서는 Web Bluetooth API 덕분에 JavaScript만으로 PLAYBULB LED flameless candle을 제어하는 방법을 알아봅니다. 이 과정에서 클래스, 화살표 함수, 지도, 프로미스와 같은 JavaScript ES2015 기능도 사용해 볼 수 있습니다.

학습할 내용

  • JavaScript에서 근처 블루투스 기기와 상호작용하는 방법
  • ES2015 클래스, 화살표 함수, 맵, 프로미스 사용 방법

필요한 항목

2. 먼저 재생

이 Codelab을 실제로 시작하기 전에 https://googlecodelabs.github.io/candle-bluetooth에서 만들려는 앱의 최종 버전을 확인하고 사용 가능한 PLAYBULB Candle 블루투스 기기를 사용해 볼 수 있습니다.

https://www.youtube.com/watch?v=fBCPA9gIxlU에서 제가 색상을 바꾸는 모습을 보실 수도 있습니다.

3. 설정

샘플 코드 다운로드

이 코드의 샘플 코드를 가져오려면 여기에서 ZIP 파일을 다운로드하세요.

또는 이 git 저장소를 클론합니다.

git clone https://github.com/googlecodelabs/candle-bluetooth.git

소스를 ZIP 파일로 다운로드한 경우 압축을 풀면 루트 폴더 candle-bluetooth-master가 생성됩니다.

웹 서버 설치 및 확인

자체 웹 서버를 사용할 수 있지만 이 Codelab은 Chrome 웹 서버에서 잘 작동하도록 설계되었습니다. 아직 앱을 설치하지 않았다면 Chrome 웹 스토어에서 설치할 수 있습니다.

Chrome용 웹 서버 앱을 설치한 후 북마크 바에서 앱 바로가기를 클릭합니다.

Screen Shot 2016-11-16 at 4.10.42 PM.png

다음 창에서 웹 서버 아이콘을 클릭합니다.

9f3c21b2cf6cbfb5.png

그다음 이 대화상자가 표시되면 로컬 웹 서버를 구성할 수 있습니다.

Screen Shot 2016-11-16 at 3.40.47 PM.png

폴더 선택 버튼을 클릭하고 클론된(또는 보관 취소된) 저장소의 루트를 선택합니다. 이렇게 하면 웹 서버 대화상자(웹 서버 URL 섹션)에 강조표시된 URL을 통해 진행 중인 작업을 제공할 수 있습니다.

옵션에서 아래와 같이 'index.html 자동 표시' 옆의 체크박스를 선택합니다.

Screen Shot 2016-11-16 at 3.40.56 PM.png

이제 웹브라우저에서 강조 표시된 웹 서버 URL을 클릭하여 사이트를 방문하면 다음과 같은 페이지가 표시됩니다.

Screen Shot 2016-11-16 at 3.20.22 PM.png

Android 휴대전화에서 이 앱이 어떻게 표시되는지 확인하려면 Android에서 원격 디버깅을 사용 설정하고 포트 전달을 설정해야 합니다(포트 번호는 기본적으로 8887임). 그런 다음 Android 휴대전화에서 새 Chrome 탭을 열고 http://localhost:8887로 이동하면 됩니다.

다음 단계

현재 이 웹 앱은 많은 작업을 수행하지 않습니다. 블루투스 지원을 추가해 보겠습니다.

4. 초 발견

먼저 PLAYBULB Candle 블루투스 기기에 JavaScript ES2015 클래스를 사용하는 라이브러리를 작성합니다.

침착함을 유지하세요. 클래스 문법은 JavaScript에 새로운 객체 지향 상속 모델을 도입하는 것이 아닙니다. 아래에서 확인할 수 있듯이 객체를 만들고 상속을 처리하는 훨씬 더 명확한 문법을 제공할 뿐입니다.

먼저 playbulbCandle.js에서 PlaybulbCandle 클래스를 정의하고 나중에 app.js 파일에서 사용할 수 있는 playbulbCandle 인스턴스를 만듭니다.

playbulbCandle.js

(function() {
  'use strict';

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

근처 블루투스 기기에 대한 액세스를 요청하려면 navigator.bluetooth.requestDevice를 호출해야 합니다. PLAYBULB Candle 기기는 (아직 페어링되지 않은 경우) 짧은 형식으로 0xFF02라고 알려진 일정한 블루투스 GATT 서비스 UUID를 지속적으로 알리므로, 상수를 정의하고 이를 PlaybulbCandle 클래스의 새로운 공개 connect 메서드에 있는 필터 서비스 매개변수에 추가하면 됩니다.

또한 나중에 필요할 때 액세스할 수 있도록 BluetoothDevice 객체를 내부적으로 추적합니다. navigator.bluetooth.requestDevice는 JavaScript ES2015 프로미스를 반환하므로 then 메서드에서 이 작업을 실행합니다.

playbulbCandle.js

(function() {
  'use strict';

  const CANDLE_SERVICE_UUID = 0xFF02;

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
    connect() {
      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
      return navigator.bluetooth.requestDevice(options)
      .then(function(device) {
        this.device = device;
      }.bind(this)); 
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

보안 기능으로, navigator.bluetooth.requestDevice를 사용하여 근처 블루투스 기기를 검색하려면 터치나 마우스 클릭과 같은 사용자 동작을 통해 호출해야 합니다. 따라서 사용자가 app.js 파일에서 '연결' 버튼을 클릭하면 connect 메서드를 호출합니다.

app.js

document.querySelector('#connect').addEventListener('click', function(event) {
  document.querySelector('#state').classList.add('connecting');
  playbulbCandle.connect()
  .then(function() {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
  })
  .catch(function(error) {
    console.error('Argh!', error);
  });
});

앱 실행

이 시점에서 웹브라우저에서 사이트를 방문하거나(웹 서버 앱에서 강조 표시된 웹 서버 URL을 클릭) 기존 페이지를 새로고침합니다. 녹색 'Connect' 버튼을 클릭하고 선택기에서 기기를 선택한 후 Ctrl + Shift + J 키보드 단축키를 사용하여 원하는 Dev Tools 콘솔을 열고 로깅된 BluetoothDevice 객체를 확인합니다.

Screen Shot 2016-11-16 at 3.27.12 PM.png

블루투스가 꺼져 있거나 PLAYBULB Candle 블루투스 기기가 꺼져 있으면 오류가 발생할 수 있습니다. 이 경우 기기를 켜고 다시 진행하세요.

필수 보너스

여러분은 어떨지 모르겠지만 저는 이 코드에서 이미 너무 많은 function() {}를 보고 있습니다. 대신 () => {} JavaScript ES2015 화살표 함수로 전환해 보겠습니다. 이들은 절대적인 구원자입니다. 익명 함수의 모든 장점을 가지고 있으면서도 바인딩의 슬픔은 전혀 없습니다.

playbulbCandle.js

(function() {
  'use strict';

  const CANDLE_SERVICE_UUID = 0xFF02;

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
    connect() {
      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
      return navigator.bluetooth.requestDevice(options)
      .then(device => {
        this.device = device;
      }); 
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

app.js

document.querySelector('#connect').addEventListener('click', event => {
  playbulbCandle.connect()
  .then(() => {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
  })
  .catch(error => {
    console.error('Argh!', error);
  });
});

다음 단계

- 알았어… 이 양초랑 실제로 대화할 수 있는 거야?

- 네, 다음 단계로 이동합니다.

자주 묻는 질문(FAQ)

5. 읽기

navigator.bluetooth.requestDevice의 프로미스에서 BluetoothDevice가 반환되면 어떻게 해야 할까요? device.gatt.connect()를 호출하여 블루투스 서비스 및 특성 정의를 보유하는 블루투스 원격 GATT 서버에 연결해 보겠습니다.

playbulbCandle.js

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
    connect() {
      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
      return navigator.bluetooth.requestDevice(options)
      .then(device => {
        this.device = device;
        return device.gatt.connect();
      });
    }
  }

기기 이름 읽기

여기서는 PLAYBULB Candle 블루투스 기기의 GATT 서버에 연결되어 있습니다. 이제 기본 GATT 서비스(이전에 0xFF02로 공지됨)를 가져와 이 서비스에 속하는 기기 이름 특성(0xFFFF)을 읽으려고 합니다. 이는 PlaybulbCandle 클래스에 새 메서드 getDeviceName를 추가하고 device.gatt.getPrimaryService 및 service.getCharacteristic를 사용하여 쉽게 수행할 수 있습니다. characteristic.readValue 메서드는 실제로 DataView를 반환하며, 이 값은 TextDecoder를 사용하여 디코딩하면 됩니다.

playbulbCandle.js

  const CANDLE_DEVICE_NAME_UUID = 0xFFFF;

  ...

    getDeviceName() {
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_DEVICE_NAME_UUID))
      .then(characteristic => characteristic.readValue())
      .then(data => {
        let decoder = new TextDecoder('utf-8');
        return decoder.decode(data);
      });
    }

연결되면 playbulbCandle.getDeviceName를 호출하여 이를 app.js에 추가하고 기기 이름을 표시해 보겠습니다.

app.js

document.querySelector('#connect').addEventListener('click', event => {
  playbulbCandle.connect()
  .then(() => {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
    return playbulbCandle.getDeviceName().then(handleDeviceName);
  })
  .catch(error => {
    console.error('Argh!', error);
  });
});

function handleDeviceName(deviceName) {
  document.querySelector('#deviceName').value = deviceName;
}

이 시점에서 웹브라우저에서 사이트를 방문하거나(웹 서버 앱에서 강조 표시된 웹 서버 URL을 클릭) 기존 페이지를 새로고침합니다. PLAYBULB Candle이 켜져 있는지 확인한 다음 페이지에서 '연결' 버튼을 클릭하면 색상 선택 도구 아래에 기기 이름이 표시됩니다.

Screen Shot 2016-11-16 at 3.29.21 PM.png

배터리 잔량 읽기

PLAYBULB Candle 블루투스 기기에는 기기의 배터리 잔량을 포함하는 표준 배터리 잔량 블루투스 특성도 있습니다. 즉, 블루투스 GATT 서비스 UUID에는 battery_service, 블루투스 GATT 특성 UUID에는 battery_level와 같은 표준 이름을 사용할 수 있습니다.

PlaybulbCandle 클래스에 새 getBatteryLevel 메서드를 추가하고 배터리 잔량을 백분율로 읽어보겠습니다.

playbulbCandle.js

    getBatteryLevel() {
      return this.device.gatt.getPrimaryService('battery_service')
      .then(service => service.getCharacteristic('battery_level'))
      .then(characteristic => characteristic.readValue())
      .then(data => data.getUint8(0));
    }

또한 PLAYBULB Candle 블루투스 기기에서 광고하지 않지만 액세스하는 데 여전히 필요한 배터리 서비스를 optionalServices 키에 포함하도록 options JavaScript 객체를 업데이트해야 합니다.

playbulbCandle.js

      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}],
                     optionalServices: ['battery_service']};
      return navigator.bluetooth.requestDevice(options)

이전과 마찬가지로 기기 이름을 가져오고 배터리 잔량을 표시한 후 playbulbCandle.getBatteryLevel을 호출하여 이를 app.js에 연결해 보겠습니다.

app.js

document.querySelector('#connect').addEventListener('click', event => {
  playbulbCandle.connect()
  .then(() => {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
    return playbulbCandle.getDeviceName().then(handleDeviceName)
    .then(() => playbulbCandle.getBatteryLevel().then(handleBatteryLevel));
  })
  .catch(error => {
    console.error('Argh!', error);
  });
});

function handleDeviceName(deviceName) {
  document.querySelector('#deviceName').value = deviceName;
}

function handleBatteryLevel(batteryLevel) {
  document.querySelector('#batteryLevel').textContent = batteryLevel + '%';
}

이 시점에서 웹브라우저에서 사이트를 방문하거나(웹 서버 앱에서 강조 표시된 웹 서버 URL을 클릭) 기존 페이지를 새로고침합니다. 페이지에서 '연결' 버튼을 클릭하면 기기 이름과 배터리 잔량이 모두 표시됩니다.

Screen Shot 2016-11-16 at 3.29.21 PM.png

다음 단계

- 이 전구 색상을 어떻게 변경할 수 있나요? 제가 여기 있는 이유입니다.

- 거의 다 왔습니다.

자주 묻는 질문(FAQ)

6. 색상 변경

색상을 변경하는 것은 0xFF02로 공지된 기본 GATT 서비스의 블루투스 특성(0xFFFC)에 특정 명령어 집합을 작성하는 것만큼 간단합니다. 예를 들어 PLAYBULB Candle을 빨간색으로 바꾸려면 [0x00, 255, 0, 0]와 같은 8비트 부호 없는 정수 배열을 작성합니다. 여기서 0x00는 흰색 채도이고 255, 0, 0는 각각 빨간색, 녹색, 파란색 값입니다.

characteristic.writeValue를 사용하여 PlaybulbCandle 클래스의 새로운 setColor 공개 메서드에서 블루투스 특성에 일부 데이터를 실제로 작성합니다. 또한 프로미스가 처리되면 실제 빨간색, 녹색, 파란색 값을 반환하여 나중에 app.js에서 사용할 수 있도록 합니다.

playbulbCandle.js

  const CANDLE_COLOR_UUID = 0xFFFC;

  ...

    setColor(r, g, b) {
      let data = new Uint8Array([0x00, r, g, b]);
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_COLOR_UUID))
      .then(characteristic => characteristic.writeValue(data))
      .then(() => [r,g,b]);
    }

'No Effect' 라디오 버튼이 선택되면 playbulbCandle.setColor를 호출하도록 app.js의 changeColor 함수를 업데이트해 보겠습니다. 사용자가 색상 선택 도구 캔버스를 클릭하면 전역 r, g, b 색상 변수가 이미 설정되어 있습니다.

app.js

function changeColor() {
  var effect = document.querySelector('[name="effectSwitch"]:checked').id;
  if (effect === 'noEffect') {
    playbulbCandle.setColor(r, g, b).then(onColorChanged);
  }
}

이 시점에서 웹브라우저에서 사이트를 방문하거나(웹 서버 앱에서 강조 표시된 웹 서버 URL을 클릭) 기존 페이지를 새로고침합니다. 페이지에서 '연결' 버튼을 클릭하고 색상 선택 도구를 클릭하여 PLAYBULB Candle의 색상을 원하는 만큼 변경합니다.

Screen Shot 2016-11-16 at 3.31.37 PM.png

더 많은 촛불 효과

촛불을 켜 본 적이 있다면 불빛이 가만히 있지 않는다는 것을 아실 겁니다. 다행히 0xFF02로 공지된 기본 GATT 서비스에 사용자가 일부 촛불 효과를 설정할 수 있는 또 다른 블루투스 특성(0xFFFB)이 있습니다.

예를 들어 '촛불 효과'를 설정하려면 [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]를 작성하면 됩니다. [0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00]를 사용하여 '깜박임 효과'를 설정할 수도 있습니다.

PlaybulbCandle 클래스에 setCandleEffectColor 및 setFlashingColor 메서드를 추가해 보겠습니다.

playbulbCandle.js

  const CANDLE_EFFECT_UUID = 0xFFFB;

  ...

    setCandleEffectColor(r, g, b) {
      let data = new Uint8Array([0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]);
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_EFFECT_UUID))
      .then(characteristic => characteristic.writeValue(data))
      .then(() => [r,g,b]);
    }
    setFlashingColor(r, g, b) {
      let data = new Uint8Array([0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00]);
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_EFFECT_UUID))
      .then(characteristic => characteristic.writeValue(data))
      .then(() => [r,g,b]);
    }

그리고 'Candle Effect' 라디오 버튼이 선택되면 playbulbCandle.setCandleEffectColor를 호출하고 'Flashing' 라디오 버튼이 선택되면 playbulbCandle.setFlashingColor를 호출하도록 app.js의 changeColor 함수를 업데이트합니다. 괜찮으시다면 이번에는 switch을(를) 사용하겠습니다.

app.js

function changeColor() {
  var effect = document.querySelector('[name="effectSwitch"]:checked').id;
  switch(effect) {
    case 'noEffect':
      playbulbCandle.setColor(r, g, b).then(onColorChanged);
      break;
    case 'candleEffect':
      playbulbCandle.setCandleEffectColor(r, g, b).then(onColorChanged);
      break;
    case 'flashing':
      playbulbCandle.setFlashingColor(r, g, b).then(onColorChanged);
      break;
  }
}

이 시점에서 웹브라우저에서 사이트를 방문하거나(웹 서버 앱에서 강조 표시된 웹 서버 URL을 클릭) 기존 페이지를 새로고침합니다. 페이지에서 '연결' 버튼을 클릭하고 촛불 및 깜박임 효과를 사용해 보세요.

Screen Shot 2016-11-16 at 3.33.23 PM.png

다음 단계

- 그게 다야? 촛불 효과 3개? 이것이 내가 여기 있는 이유인가?

- 더 많은 방법이 있지만 이번에는 직접 찾아보세요.

7. 한 걸음 더 다가가기

이제 준비되었습니다. 거의 끝났다고 생각할 수도 있지만 앱은 아직 끝나지 않았습니다. 이 Codelab에서 복사하여 붙여넣은 내용을 실제로 이해했는지 확인해 보겠습니다. 이제 이 앱을 멋지게 만들기 위해 직접 해야 할 일은 다음과 같습니다.

누락된 효과 추가

누락된 효과의 데이터는 다음과 같습니다.

  • Pulse: [0x00, r, g, b, 0x01, 0x00, 0x09, 0x00](r, g, b 값을 조정해야 할 수 있음)
  • 무지개: [0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00](간질 환자는 이 효과를 피하는 것이 좋습니다)
  • 무지개 페이드: [0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]

기본적으로 PlaybulbCandle 클래스에 새 setPulseColor, setRainbow, setRainbowFade 메서드를 추가하고 changeColor에서 호출하는 것을 의미합니다.

'효과 없음' 수정

눈치채셨을 수도 있지만 '효과 없음' 옵션은 진행 중인 효과를 재설정하지 않습니다. 사소한 문제이지만 여전히 존재합니다. 이 문제를 해결해 보겠습니다. setColor 메서드에서 먼저 새 클래스 변수 _isEffectSet를 통해 효과가 진행 중인지 확인하고, true인 경우 [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00] 데이터로 새 색상을 설정하기 전에 효과를 사용 중지해야 합니다.

기기 이름 입력

간단한 질문이네요. 맞춤 기기 이름을 작성하는 것은 이전 블루투스 기기 이름 특성에 작성하는 것만큼 간단합니다. TextEncoder encode 메서드를 사용하여 기기 이름이 포함된 Uint8Array를 가져오는 것이 좋습니다.

그런 다음 document.querySelector('#deviceName')에 '입력' eventListener을 추가하고 playbulbCandle.setDeviceName를 호출하여 간단하게 유지합니다.

저는 제 촛불에 PLAY💡 CANDLE이라는 이름을 붙였어요.

8. 작업이 끝났습니다.

학습한 내용

  • JavaScript에서 근처 블루투스 기기와 상호작용하는 방법
  • ES2015 클래스, 화살표 함수, 맵, 프로미스 사용 방법

다음 단계