1. 어떤 내용인가요?

이 Codelab에서는 Web 블루투스 API를 사용하여 JavaScript만으로 PLAYBULB LED 무화염 촛불을 제어하는 방법을 알아봅니다. 이 과정에서 클래스, 화살표 함수, 맵, 프로미스와 같은 JavaScript ES2015 기능도 사용해 봅니다.
학습할 내용
- JavaScript에서 근처 블루투스 기기와 상호작용하는 방법
- ES2015 클래스, 화살표 함수, Map, Promise 사용 방법
필요한 항목
- 웹 개발에 관한 기본적인 이해
- 저전력 블루투스 (BLE) 및 일반 속성 프로필 (GATT)에 관한 기본 지식
- 원하는 텍스트 편집기
- Chrome 브라우저 앱이 설치된 Mac, Chromebook 또는 Android M 기기와 USB 마이크로-USB 케이블
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용 웹 서버 앱을 설치한 후 북마크 바에서 앱 바로가기를 클릭합니다.

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

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

폴더 선택 버튼을 클릭하고 클론된 (또는 압축 해제된) 저장소의 루트를 선택합니다. 이렇게 하면 웹 서버 대화상자(웹 서버 URL 섹션)에 강조 표시된 URL을 통해 진행 중인 작업을 제공할 수 있습니다.
옵션에서 아래와 같이 'index.html 자동 표시' 옆의 체크박스를 선택합니다.

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

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 Promise를 반환하므로 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을 클릭) 기존 페이지를 새로고침합니다. 녹색 '연결' 버튼을 클릭하고 선택기에서 기기를 선택한 후 Ctrl + Shift + J 단축키로 즐겨 사용하는 개발자 도구 콘솔을 열면 BluetoothDevice 객체가 로깅됩니다.

블루투스가 꺼져 있거나 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 메서드는 실제로 TextDecoder로 간단히 디코딩할 DataView를 반환합니다.
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이 켜져 있는지 확인한 다음 페이지에서 '연결' 버튼을 클릭하면 색상 선택기 아래에 기기 이름이 표시됩니다.

배터리 잔량 읽기
기기의 배터리 잔량을 포함하는 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을 클릭) 기존 페이지를 새로고침합니다. 페이지에서 '연결' 버튼을 클릭하면 기기 이름과 배터리 수준이 모두 표시됩니다.
다음 단계
- 이 전구의 색상을 변경하려면 어떻게 해야 하나요? 그래서 제가 여기 있는 거고요.
- 거의 다 왔으니 조금만 더 힘내세요.
자주 묻는 질문(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]);
}
'효과 없음' 라디오 버튼이 선택되면 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의 색상을 변경합니다.
Moar candle effects
이전에 촛불을 켜 본 적이 있다면 빛이 정적이지 않다는 것을 알 것입니다. 다행히도 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]);
}
또한 app.js의 changeColor 함수를 업데이트하여 '촛불 효과' 라디오 버튼이 선택되면 playbulbCandle.setCandleEffectColor를 호출하고 '깜박임' 라디오 버튼이 선택되면 playbulbCandle.setFlashingColor를 호출합니다. 이번에는 괜찮다면 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을 클릭) 기존 페이지를 새로고침합니다. 페이지에서 '연결' 버튼을 클릭하고 양초 및 깜박임 효과를 사용해 보세요.
다음 단계
- 이게 전부인가요? 3개의 촛불 효과가 좋지 않나요? 이것이 내가 여기 있는 이유인가요?
- 더 많은 질문이 있지만 이번에는 직접 찾아보세요.
7. 최선을 다하기
자, 이제 시작해 볼까요? 거의 끝났다고 생각할 수 있지만 앱은 아직 끝나지 않았습니다. 이 Codelab에서 복사하여 붙여넣은 내용을 실제로 이해했는지 확인해 보겠습니다. 이 앱을 돋보이게 하려면 지금 직접 해야 할 작업이 있습니다.
누락된 효과 추가
누락된 효과의 데이터는 다음과 같습니다.
- 펄스:
[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')에 'input' eventListener를 추가하고 playbulbCandle.setDeviceName를 호출하여 간단하게 유지합니다.
저는 개인적으로 'PLAY💡 CANDLE'이라고 이름을 지었습니다.
8. 작업이 끝났습니다.
학습한 내용
- JavaScript에서 근처 블루투스 기기와 상호작용하는 방법
- ES2015 클래스, 화살표 함수, Map, Promise 사용 방법
다음 단계
- Web Bluetooth API 자세히 알아보기
- 공식 Web 블루투스 샘플 및 데모를 둘러보세요.
- 날아다니는 심술궂은 고양이를 확인해 보세요.

