Intel Edison を使用したウェブ対応 IoT デバイスの作成

Kenneth Christiansen
Kenneth Christiansen

Internet of Things(IoT)は 最近誰もが話題にしており自分で作った発明品を動かして会話できるなんて、最高にクールです。

ただし、あまり使用しないアプリがインストールされている IoT デバイスは煩わしい場合があります。そこで Google は、Physical Web や Web Bluetooth などの今後のウェブ技術を活用して、IoT デバイスをより直感的で邪魔にならないものにしています。

クライアント アプリケーション

ウェブと IoT の組み合わせ

モノのインターネットが大きな成功を収めるには、まだ多くのハードルを克服する必要があります。障壁の一つは、購入するデバイスごとにアプリをインストールする必要がある企業や製品であり、使用頻度の低い多数のアプリでユーザーのスマートフォンが煩雑になるからです。

そのため Google は、デバイスが煩わしくなく、オンライン ウェブサイトに URL をブロードキャストできるようにする Physical Web プロジェクトに大きな期待を寄せています。Web BluetoothWeb USBWeb NFC などの新しいウェブ技術と組み合わせることで、サイトはデバイスに直接接続できます。少なくとも、適切な接続方法を説明できます。

この記事では主にウェブ Bluetooth について説明しますが、ユースケースによってはウェブ NFC やウェブ USB のほうが適している場合もあります。たとえば、セキュリティ上の理由で物理的な接続が必要な場合は、Web USB が適しています。

ウェブサイトはプログレッシブ ウェブアプリ(PWA)として機能することもできます。PWA について詳しくは、Google の説明をご覧ください。PWA は、アプリのようなレスポンシブなユーザー エクスペリエンスを提供し、オフラインで動作し、デバイスのホーム画面に追加できるサイトです。

概念実証として、Intel® Edison Arduino ブレークアウト ボードを使用して小型デバイスを構築しました。このデバイスには、温度センサー(TMP36)とアクチュエーター(カラー LED カソード)が含まれています。このデバイスの回路図は、この記事の最後にあります。

ブレッドボード。

Intel Edison は、完全な Linux* ディストリビューションを実行できるため、興味深い製品です。そのため、Node.js を使用して簡単にプログラムできます。インストーラを使用すると Intel* XDK を簡単にインストールできますが、手動でプログラミングしてデバイスにアップロードすることもできます。

私の Node.js アプリには、3 つのノード モジュールとその依存関係が必要でした。

  • eddystone-beacon
  • parse-color
  • johnny-five

前者は noble を自動的にインストールします。これは、Bluetooth Low Energy を介した通信に使用するノードモジュールです。

をご覧ください。

プロジェクトの 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 は Physical Web をサポートしています。これにより、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 が表示されます。

センサー/アクチュエータとの通信

Google は、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 変数は今のところ無視できます。これらは、後述の Bluetooth とのインターフェースに関するセクションで定義します。

Thermometer オブジェクトのインスタンス化でわかるように、アナログ A0 ポートを介して TMP36 と通信しています。カラー LED カソード上の電圧レッグは、デジタル ピン 3、5、6 に接続されています。これは、Edison Arduino ブレークアウト ボードのパルス幅変調(PWM)ピンです。

エジソンボード

Bluetooth との通信

Bluetooth との通信は、noble を使用するよりも簡単です。

次の例では、LED 用と温度センサー用の 2 つの Bluetooth Low Energy 特性を作成します。前者は、現在の 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 変数に保存されています。「read」が機能するには、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 は、bleno で配布されている 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
        ]
        })
    ]);
});

クライアント ウェブアプリを作成する

クライアント アプリの Bluetooth 以外の部分の動作の欠陥にあまり触れることなく、例として Polymer* で作成したレスポンシブなユーザー インターフェースをデモできます。作成されたアプリは次のようになります。

スマートフォンのクライアント アプリ。
エラー メッセージ

右側は以前のバージョンです。開発を容易にするために追加したシンプルなエラーログが表示されています。

Web Bluetooth を使用すると、Bluetooth Low Energy デバイスと簡単に通信できます。接続コードの簡素化されたバージョンを見てみましょう。プロミスの仕組みがわからない場合は、先にこちらのリソースをご覧ください。

Bluetooth デバイスに接続するには、Promise の連鎖が必要です。まず、デバイス(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);
},

概要

これで完了です。ご覧のとおり、クライアント側のウェブ Bluetooth と Edison の Node.js を使用して、Bluetooth Low Energy との通信は非常に簡単で強力です。

Chrome は Physical Web と Web Bluetooth を使用してデバイスを見つけ、ユーザーが望まない、または定期的に更新される可能性のある、あまり使用しないアプリをインストールしなくても、ユーザーがデバイスに簡単に接続できるようにします。

デモ

クライアントをお試しになり、独自のウェブアプリを作成してカスタムのモノのインターネット デバイスに接続する方法についてヒントを得ることができます。

ソースコード

ソースコードはこちらで入手できます。問題の報告やパッチの送信をお気軽にお寄せください。

スケッチ

冒険心を出して、私が行ったことを再現したい場合は、以下の Edison とブレッドボードのスケッチを参照してください。

スケッチ