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

Kenneth Christiansen
Kenneth Christiansen

요즘 사물 인터넷이 화제인데, 저와 같은 수리공과 프로그래머는 사물 인터넷에 대해 매우 흥분하고 있습니다. 직접 만든 발명품을 실물로 구현하고 대화할 수 있는 것보다 더 멋진 일은 없습니다.

하지만 거의 사용하지 않는 앱을 설치하는 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이 기기를 잘 찾는 것을 볼 수 있습니다.

Chrome에서 근처의 Physical Web 비콘을 알립니다.
웹 앱 URL이 표시됩니다.

센서/액추에이터와 통신

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에 연결됩니다.

Edison Board

블루투스와 대화하기

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);
};

'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를 사용하는 것만큼 쉽습니다. 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 및 브레드보드 스케치를 참고하세요.

스케치