요즘 사물 인터넷이 화제인데, 저와 같은 수리공과 프로그래머는 사물 인터넷에 대해 매우 기대하고 있습니다. 직접 만든 발명품을 실물로 구현하고 대화할 수 있는 것보다 더 멋진 일은 없습니다.
하지만 자주 사용하지 않는 앱을 설치하는 IoT 기기는 불편할 수 있으므로 Google에서는 물리적 웹 및 웹 블루투스와 같은 향후 웹 기술을 활용하여 IoT 기기를 더 직관적이고 방해가 되지 않도록 만들고 있습니다.

웹과 IoT, 함께할 수밖에 없는 이유
사물 인터넷이 큰 성공을 거두기 위해서는 아직 극복해야 할 장애물이 많습니다. 한 가지 장애물은 사용자가 구매한 기기마다 앱을 설치해야 하는 회사와 제품으로, 사용자가 거의 사용하지 않는 수많은 앱으로 휴대전화를 어지럽히게 됩니다.
이러한 이유로 기기가 방해가 되지 않는 방식으로 온라인 웹사이트에 URL을 브로드캐스트할 수 있는 Physical Web 프로젝트에 큰 기대를 걸고 있습니다. 사이트는 웹 블루투스, 웹 USB, 웹 NFC와 같은 신규 웹 기술과 함께 사용하면 기기에 직접 연결하거나 적어도 올바른 연결 방법을 설명할 수 있습니다.
이 도움말에서는 주로 웹 블루투스에 중점을 두지만 일부 사용 사례는 웹 NFC 또는 웹 USB에 더 적합할 수 있습니다. 예를 들어 보안상의 이유로 물리적 연결이 필요한 경우 웹 USB를 사용하는 것이 좋습니다.
웹사이트는 프로그레시브 웹 앱 (PWA)으로도 작동할 수 있습니다. PWA에 관한 Google의 설명을 확인해 보시기 바랍니다. PWA는 반응형 앱과 같은 사용자 환경을 제공하고, 오프라인에서 작동하며, 기기 홈 화면에 추가할 수 있는 사이트입니다.
개념 증명으로 Intel® Edison Arduino 브레이크아웃 보드를 사용하여 소형 기기를 빌드하고 있습니다. 이 기기에는 온도 센서 (TMP36)와 액추에이터 (색상 LED 음극)가 포함되어 있습니다. 이 기기의 회로도에 관한 내용은 이 도움말의 끝부분에서 확인할 수 있습니다.

Intel Edison은 전체 Linux* 배포를 실행할 수 있기 때문에 흥미로운 제품입니다. 따라서 Node.js를 사용하여 쉽게 프로그래밍할 수 있습니다. 설치 프로그램을 사용하면 Intel* XDK를 설치하여 쉽게 시작할 수 있습니다. 물론 수동으로 프로그래밍하고 기기에 업로드할 수도 있습니다.
Node.js 앱에는 세 개의 노드 모듈과 종속 항목이 필요했습니다.
eddystone-beacon
parse-color
johnny-five
전자는 저전력 블루투스를 통해 통신하는 데 사용하는 노드 모듈인 noble
를 자동으로 설치합니다.
프로젝트의 package.json
파일은 다음과 같습니다.
{
"name": "edison-webbluetooth-demo-server",
"version": "1.0.0",
"main": "main.js",
"engines": {
"node": ">=0.10.0"
},
"dependencies": {
"eddystone-beacon": "^1.0.5",
"johnny-five": "^0.9.30",
"parse-color": "^1.0.0"
}
}
웹사이트 공지
버전 49부터 Android의 Chrome에서는 주변 기기에서 브로드캐스트하는 URL을 Chrome에서 볼 수 있는 Physical Web을 지원합니다. 사이트에 공개적으로 액세스할 수 있어야 하고 HTTPS를 사용해야 하는 것과 같이 개발자가 알아야 하는 몇 가지 요구사항이 있습니다.
Eddystone 프로토콜에는 URL에 18바이트 크기 제한이 있습니다. 따라서 데모 앱의 URL (https://webbt-sensor-hub.appspot.com/)이 작동하도록 하려면 URL 단축기를 사용해야 합니다.
URL을 브로드캐스트하는 것은 매우 간단합니다. 필요한 라이브러리를 가져오고 몇 가지 함수를 호출하기만 하면 됩니다. 이렇게 하는 한 가지 방법은 BLE 칩이 켜질 때 advertiseUrl
를 호출하는 것입니다.
var beacon = require("eddystone-beacon");
var bleno = require('eddystone-beacon/node_modules/bleno');
bleno.on('stateChange', function(state) {
if (state === 'poweredOn') {
beacon.advertiseUrl("https://goo.gl/9FomQC", {name: 'Edison'});
}
}
정말 간단합니다. 아래 이미지에서 Chrome이 기기를 잘 찾는 것을 볼 수 있습니다.


센서/액추에이터와 통신
Johnny-Five* 를 사용하여 보드 개선사항과 통신합니다. Johnny-Five에는 TMP36 센서와 통신하기 위한 멋진 추상화가 있습니다.
아래에서 온도 변화에 관한 알림을 받음과 동시에 초기 LED 색상을 설정하는 간단한 코드를 확인할 수 있습니다.
var five = require("johnny-five");
var Edison = require("edison-io");
var board = new five.Board({
io: new Edison()
});
board.on("ready", function() {
// Johnny-Five's Led.RGB class can be initialized with
// an array of pin numbers in R, G, B order.
// Reference: http://johnny-five.io/api/led.rgb/#parameters
var led = new five.Led.RGB([ 3, 5, 6 ]);
// Johnny-Five's Thermometer class provides a built-in
// controller definition for the TMP36 sensor. The controller
// handles computing a Celsius (also Fahrenheit & Kelvin) from
// a raw analog input value.
// Reference: http://johnny-five.io/api/thermometer/
var temp = new five.Thermometer({
controller: "TMP36",
pin: "A0",
});
temp.on("change", function() {
temperatureCharacteristic.valueChange(this.celsius);
});
colorCharacteristic._led = led;
led.color(colorCharacteristic._value);
led.intensity(30);
});
지금은 위의 *Characteristic
변수를 무시해도 됩니다. 이 변수는 블루투스와의 인터페이스에 관한 뒷부분 섹션에서 정의됩니다.
Thermometer 객체의 인스턴스화에서 알 수 있듯이 아날로그 A0
포트를 통해 TMP36과 통신합니다. 컬러 LED 음극의 전압 레그는 Edison Arduino 브레이크아웃 보드의 펄스 폭 변조 (PWM) 핀인 디지털 핀 3, 5, 6에 연결됩니다.

블루투스와 대화하기
noble
를 사용하면 블루투스와 통신하는 것이 훨씬 쉬워집니다.
다음 예에서는 LED용 블루투스 저전력 특성 1개와 온도 센서용 블루투스 저전력 특성 1개를 만듭니다. 전자를 사용하면 현재 LED 색상을 읽고 새 색상을 설정할 수 있습니다. 후자를 사용하면 온도 변화 이벤트를 구독할 수 있습니다.
noble
를 사용하면 특성을 쉽게 만들 수 있습니다. 특성이 통신하는 방식을 정의하고 UUID를 정의하기만 하면 됩니다. 커뮤니케이션 옵션은 읽기, 쓰기, 알림 또는 이들의 조합입니다.
가장 쉬운 방법은 새 객체를 만들고 bleno.Characteristic
에서 상속하는 것입니다.
결과 특성 객체는 다음과 같습니다.
var TemperatureCharacteristic = function() {
bleno.Characteristic.call(this, {
uuid: 'fc0a',
properties: ['read', 'notify'],
value: null
});
this._lastValue = 0;
this._total = 0;
this._samples = 0;
this._onChange = null;
};
util.inherits(TemperatureCharacteristic, bleno.Characteristic);
현재 온도 값을 this._lastValue
변수에 저장합니다. onReadRequest
메서드를 추가하고 '읽기'가 작동하도록 값을 인코딩해야 합니다.
TemperatureCharacteristic.prototype.onReadRequest = function(offset, callback) {
var data = new Buffer(8);
data.writeDoubleLE(this._lastValue, 0);
callback(this.RESULT_SUCCESS, data);
};
'notify'의 경우 정기 결제 및 정기 결제 취소를 처리하는 메서드를 추가해야 합니다. 기본적으로 콜백을 저장하기만 하면 됩니다. 전송할 새 온도 이유가 있으면 새 값 (위와 같이 인코딩됨)을 사용하여 해당 콜백을 호출합니다.
TemperatureCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) {
console.log("Subscribed to temperature change.");
this._onChange = updateValueCallback;
this._lastValue = undefined;
};
TemperatureCharacteristic.prototype.onUnsubscribe = function() {
console.log("Unsubscribed to temperature change.");
this._onChange = null;
};
값이 약간 변동될 수 있으므로 TMP36 센서에서 가져온 값을 평활화해야 합니다. 100개 샘플의 평균을 구하고 온도가 1도 이상 변할 때만 업데이트를 전송하기로 했습니다.
TemperatureCharacteristic.prototype.valueChange = function(value) {
this._total += value;
this._samples++;
if (this._samples < NO_SAMPLES) {
return;
}
var newValue = Math.round(this._total / NO_SAMPLES);
this._total = 0;
this._samples = 0;
if (this._lastValue && Math.abs(this._lastValue - newValue) < 1) {
return;
}
this._lastValue = newValue;
console.log(newValue);
var data = new Buffer(8);
data.writeDoubleLE(newValue, 0);
if (this._onChange) {
this._onChange(data);
}
};
온도 센서였습니다. 색상 LED는 더 간단합니다. 객체와 'read' 메서드는 아래와 같습니다. 이 특성은 '읽기' 및 '쓰기' 작업을 허용하도록 구성되며 온도 특성과 다른 UUID를 갖습니다.
var ColorCharacteristic = function() {
bleno.Characteristic.call(this, {
uuid: 'fc0b',
properties: ['read', 'write'],
value: null
});
this._value = 'ffffff';
this._led = null;
};
util.inherits(ColorCharacteristic, bleno.Characteristic);
ColorCharacteristic.prototype.onReadRequest = function(offset, callback) {
var data = new Buffer(this._value);
callback(this.RESULT_SUCCESS, data);
};
객체에서 LED를 제어하기 위해 Johnny-Five LED 객체를 저장하는 데 사용하는 this._led
구성원을 추가합니다. LED의 색상도 기본값 (흰색, 즉 #ffffff
)으로 설정했습니다.
board.on("ready", function() {
...
colorCharacteristic._led = led;
led.color(colorCharacteristic._value);
led.intensity(30);
...
}
'write' 메서드는 'read'가 문자열을 전송하는 것처럼 문자열을 수신하며, 이 문자열은 CSS 색상 코드(예: rebeccapurple
와 같은 CSS 이름 또는 #ff00bb
와 같은 16진수 코드)로 구성될 수 있습니다. 저는 parse-color라는 노드 모듈을 사용하여 항상 Johnny-Five가 예상하는 16진수 값을 가져옵니다.
ColorCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) {
var value = parse(data.toString('utf8')).hex;
if (!value) {
callback(this.RESULT_SUCCESS);
return;
}
this._value = value;
console.log(value);
if (this._led) {
this._led.color(this._value);
}
callback(this.RESULT_SUCCESS);
};
bleno 모듈을 포함하지 않으면 위의 모든 작업이 작동하지 않습니다.
eddystone-beacon
는 함께 배포된 noble
버전을 사용하지 않는 한 bleno와 함께 작동하지 않습니다. 다행히 이 작업은 매우 간단합니다.
var bleno = require('eddystone-beacon/node_modules/bleno');
var util = require('util');
이제 기기 (UUID)와 기기의 특성 (기타 UUID)을 광고하기만 하면 됩니다.
bleno.on('advertisingStart', function(error) {
...
bleno.setServices([
new bleno.PrimaryService({
uuid: 'fc00',
characteristics: [
temperatureCharacteristic, colorCharacteristic
]
})
]);
});
클라이언트 웹 앱 만들기
클라이언트 앱의 블루투스 이외 부분이 작동하는 방식에 관해 너무 자세히 설명하지 않고 Polymer*로 만든 반응형 사용자 인터페이스를 예로 들 수 있습니다. 결과 앱은 다음과 같습니다.


오른쪽에는 개발을 용이하게 하기 위해 추가한 간단한 오류 로그가 표시된 이전 버전이 표시되어 있습니다.
웹 블루투스를 사용하면 저전력 블루투스 기기와 쉽게 통신할 수 있습니다. 간소화된 버전의 연결 코드를 살펴보겠습니다. 약속의 작동 방식을 모르는 경우 계속 읽기 전에 이 리소스를 확인하세요.
블루투스 기기에 연결하려면 일련의 약속이 필요합니다.
먼저 기기 (UUID: FC00
, 이름: Edison
)를 필터링합니다. 그러면 사용자가 필터에 따라 기기를 선택할 수 있는 대화상자가 표시됩니다. 그런 다음 GATT 서비스에 연결하고 기본 서비스와 연결된 특성을 가져온 후 값을 읽고 알림 콜백을 설정합니다.
아래의 간소화된 버전 코드는 최신 Web Bluetooth API에서만 작동하므로 Android에서 Chrome Dev (M49)가 필요합니다.
navigator.bluetooth.requestDevice({
filters: [{ name: 'Edison' }],
optionalServices: [0xFC00]
})
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService(0xFC00))
.then(service => {
let p1 = () => service.getCharacteristic(0xFC0B)
.then(characteristic => {
this.colorLedCharacteristic = characteristic;
return this.readLedColor();
});
let p2 = () => service.getCharacteristic(0xFC0A)
.then(characteristic => {
characteristic.addEventListener(
'characteristicvaluechanged', this.onTemperatureChange);
return characteristic.startNotifications();
});
return p1().then(p2);
})
.catch(err => {
// Catch any error.
})
.then(() => {
// Connection fully established, unless there was an error above.
});
DataView
/ ArrayBuffer
(WebBluetooth API에서 사용하는 항목)에서 문자열을 읽고 쓰는 것은 Node.js 측에서 Buffer
를 사용하는 것만큼 쉽습니다. TextEncoder
와 TextDecoder
만 사용하면 됩니다.
readLedColor: function() {
return this.colorLedCharacteristic.readValue()
.then(data => {
// In Chrome 50+, a DataView is returned instead of an ArrayBuffer.
data = data.buffer ? data : new DataView(data);
let decoder = new TextDecoder("utf-8");
let decodedString = decoder.decode(data);
document.querySelector('#color').value = decodedString;
});
},
writeLedColor: function() {
let encoder = new TextEncoder("utf-8");
let value = document.querySelector('#color').value;
let encodedString = encoder.encode(value.toLowerCase());
return this.colorLedCharacteristic.writeValue(encodedString);
},
온도 센서의 characteristicvaluechanged
이벤트를 처리하는 것도 매우 쉽습니다.
onTemperatureChange: function(event) {
let data = event.target.value;
// In Chrome 50+, a DataView is returned instead of an ArrayBuffer.
data = data.buffer ? data : new DataView(data);
let temperature = data.getFloat64(0, /*littleEndian=*/ true);
document.querySelector('#temp').innerHTML = temperature.toFixed(0);
},
요약
이상입니다. 보시다시피 클라이언트 측의 웹 블루투스와 Edison의 Node.js를 사용하여 블루투스 저전력과 통신하는 것은 매우 쉽고 강력합니다.
Chrome은 Physical Web 및 웹 블루투스를 사용하여 기기를 찾고 사용자가 원치 않을 수 있고 때때로 업데이트될 수 있는 드물게 사용되는 애플리케이션을 설치하지 않고도 사용자가 기기에 쉽게 연결할 수 있도록 합니다.
데모
클라이언트를 사용해 맞춤 IoT 기기에 연결할 자체 웹 앱을 만드는 방법을 알아볼 수 있습니다.
소스 코드
소스 코드는 여기에서 확인할 수 있습니다. 언제든지 문제를 신고하거나 패치를 보내주세요.
스케치
모험심이 있고 제가 한 작업을 재현하려면 아래의 Edison 및 브레드보드 스케치를 참고하세요.