1. 무엇에 관한 것인가요?
이 Codelab에서는 Web Bluetooth API를 활용하여 자바스크립트만으로 PLAYBULB LED 불꽃 없는 원통을 제어하는 방법을 알아봅니다. 또한 클래스, 화살표 함수, 맵, 프로미스와 같은 JavaScript ES2015 기능도 사용합니다.
학습할 내용
- JavaScript로 근처 블루투스 기기와 상호작용하는 방법
- ES2015 클래스, 화살표 함수, 맵, 프로미스 사용 방법
필요한 항목
- 웹 개발에 관한 기본 이해
- 저전력 블루투스 (BLE) 및 일반 속성 프로필 (GATT)에 관한 기본 지식
- 원하는 텍스트 편집기
- Chrome 브라우저 앱과 USB 마이크로-USB 케이블이 있는 Mac, Chromebook 또는 Android M 기기
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 웹 스토어에서 설치할 수 있습니다.
Web Server for Chrome 앱을 설치한 후 북마크바에서 앱 바로가기를 클릭하세요.
그 다음 창에서 Web Server 아이콘을 클릭합니다.
그러면 로컬 웹 서버를 구성할 수 있는 대화상자가 표시됩니다.
폴더 선택 버튼을 클릭하고 클론된 (또는 보관 취소된) 저장소의 루트를 선택합니다. 이렇게 하면 웹 서버 대화상자에 강조표시된 URL(웹 서버 URL 섹션)을 통해 진행 중인 작업을 제공할 수 있습니다.
아래와 같이 Options 아래에서 'Automatically show index.html' 옆에 있는 체크박스를 선택합니다.
이제 웹브라우저에서 사이트를 방문하면 (강조표시된 웹 서버 URL을 클릭) 다음과 같은 페이지가 표시됩니다.
Android 휴대전화에서 이 앱이 어떻게 표시되는지 확인하려면 Android에서 원격 디버깅을 사용 설정하고 포트 전달을 설정해야 합니다 (포트 번호는 기본적으로 8887임). 그런 다음 Android 휴대전화에서 새 Chrome 탭을 열어 http://localhost:8887로 이동하면 됩니다.
다음 단계
이 시점에서는 웹 앱이 별다른 작업을 하지 않습니다. 블루투스 지원을 추가해 보겠습니다.
4. 촛불 알아보기
먼저 PLAYBULB Candle Bluetooth 기기에 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 원통 기기는 짧은 형식으로 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
로 근처 블루투스 기기를 찾는 기능은 터치나 마우스 클릭과 같은 사용자 동작을 통해 호출해야 합니다. 따라서 사용자가 '연결' 버튼을 클릭할 때 connect
메서드를 호출합니다. app.js
파일의 다음 버튼을 사용하세요.
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 Bluetooth 기기의 GATT 서버에 연결됩니다. 이제 기본 GATT 서비스 (이전에는 0xFF02
로 공지됨)를 가져와서 이 서비스에 속한 기기 이름 특성 (0xFFFF
)을 읽으려고 합니다. 이렇게 하려면 새 메서드 getDeviceName
를 PlaybulbCandle
클래스에 추가하고 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 원통이 켜져 있는지 확인한 다음 '연결'을 클릭합니다. 색상 선택 도구 아래에 기기 이름이 표시됩니다.
배터리 잔량 읽기
PLAYBULB 원통 블루투스 기기에는 기기의 배터리 잔량이 포함된 표준 배터리 수준 블루투스 특성도 있습니다. 즉, 블루투스 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));
}
또한 optionalServices
키에 배터리 서비스를 포함하도록 options
JavaScript 객체를 업데이트해야 합니다. 이 객체는 PLAYBULB Candle 블루투스 기기에서 광고하지 않지만 이 객체에 액세스하려면 반드시 필요합니다.
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 원통의 색상을 원하는 횟수만큼 변경합니다.
모어 캔들 효과
이전에 이미 촛불을 켜본 적이 있다면 불이 정전기가 아님을 알 수 있습니다. 다행히 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]);
}
'원통 효과'가 생성될 때 playbulbCandle.setCandleEffectColor
를 호출하도록 app.js
의 changeColor
함수를 업데이트하겠습니다. '플래시' 버튼이 선택되어 있으면 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]
이는 기본적으로 새 setPulseColor
, setRainbow
, setRainbowFade
메서드를 PlaybulbCandle
클래스에 추가하고 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 클래스, 화살표 함수, 맵, 프로미스 사용 방법
다음 단계
- Web Bluetooth API 자세히 알아보기
- 공식 웹 블루투스 샘플 및 데모 둘러보기
- 날아다니는 심술 난 고양이를 확인해 보세요.