Intel Edison을 사용하여 웹 지원 IoT 기기 만들기

Kenneth Christiansen
Kenneth Christiansen

사물 인터넷은 요즘 모든 사람들의 입에 오르내리고 있으며, 저와 같은 사물 인터넷은 고전적인 애널리스트와 프로그래머들에게 큰 기대를 모으고 있습니다. 내 발명품을 활용해 보고 대화하는 것만큼 멋진 일이 있을까요?

하지만 거의 사용하지 않는 앱을 설치하는 IoT 기기는 성가실 수 있으므로 Google에서는 Physical Web 및 웹 블루투스와 같은 최신 웹 기술을 활용하여 IoT 기기를 더 직관적이고 방해가 덜 되게 만듭니다.

클라이언트 애플리케이션

웹과 IoT에 있어

사물 인터넷이 큰 성공을 거두려면 아직 극복해야 할 장애물이 많습니다 한 가지 장애물은 사용자가 구매하는 기기마다 앱을 설치하도록 요구하여 사용자의 휴대전화를 거의 사용하지 않는 수많은 앱으로 혼잡하게 만드는 회사와 제품입니다.

이러한 이유로 Google은 기기에서 방해가 되지 않는 방식으로 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"
    }
}

웹사이트 공지

Android용 Chrome 버전 49부터 Chrome이 주변 기기에서 브로드캐스트하는 URL을 확인할 수 있는 피지컬 웹을 지원합니다. 사이트에 공개적으로 액세스할 수 있어야 하고 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이 기기를 멋지게 인식한 것을 볼 수 있습니다.

Chrome이 근처의 피지컬 웹 비콘을 발표합니다.
웹 앱 URL이 표시됩니다.

센서/액추에이터와 통신

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 변수를 무시해도 됩니다. 블루투스와의 상호작용에 관한 후반 섹션에서 정의합니다.

온도계 객체의 인스턴스화에서 알 수 있듯이 아날로그 A0 포트를 통해 TMP36과 통신합니다. 컬러 LED 음극의 전압 레그는 디지털 핀 3, 5, 6에 연결되며, 이는 에디슨 Arduino 브레이크아웃 보드의 펄스 폭 변조 (PWM) 핀입니다.

에디슨 보드

Bluetooth에 연결하기

noble을(를) 사용하면 블루투스와 대화하기가 정말 쉬워졌습니다.

다음 예에서는 LED 및 온도 센서용으로 각각 하나씩 두 개의 저전력 블루투스 특성을 만듭니다. 전자를 사용하면 현재 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);
};

'알림'의 경우 구독 및 구독 취소를 처리하는 메서드를 추가해야 합니다. 기본적으로 콜백을 저장합니다. 전송하려는 새로운 온도 이유가 있으면 새 값 (위와 같이 인코딩됨)으로 콜백을 호출합니다.

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를 사용하는 것만큼 쉽습니다. TextEncoderTextDecoder만 사용하면 됩니다.

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은 피지컬 웹과 웹 블루투스를 사용하여 기기를 찾아 사용자가 거의 사용하지 않는 애플리케이션을 설치하지 않고도 기기에 쉽게 연결할 수 있도록 합니다. 이러한 애플리케이션은 사용자가 원치 않을 수 있습니다.

데모

클라이언트를 사용해 커스텀 사물 인터넷 기기에 연결하는 자체 웹 앱을 만드는 방법을 알아볼 수 있습니다.

소스 코드

소스 코드는 여기에서 확인할 수 있습니다. 언제든지 문제를 신고하거나 패치를 전송합니다.

스케치

정말로 모험심이 뛰어나서 제가 한 일을 재현하고 싶다면 아래 Edison과 브레드보드 스케치를 참조하세요.

스케치