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

Kenneth Christiansen
Kenneth Christiansen

最近はインターネットがよく話題に 変更やプログラマーの関心も高まっています自分の発明を実際に活用し、語り合うことほど素晴らしいことはありません。

しかし、めったに使用しないアプリをインストールする IoT デバイスは、ユーザーに不快な思いをさせる可能性があります。そこで Google は、フィジカル ウェブやウェブ Bluetooth などの今後リリースされるウェブ テクノロジーを利用して、IoT デバイスをより直感的で、煩わしさを軽減します。

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

ウェブと IoT は、

Internet of Things(モノのインターネット)が大きな成功を収めるには まだ克服すべきハードルがまだたくさんあります障壁の一つは、購入するデバイスごとにアプリをインストールする必要がある企業や製品です。これにより、ユーザーのスマートフォンには、ほとんど使用しないアプリが数多く混在してしまいます。

このような理由から、Google はフィジカル ウェブ プロジェクトに大きな期待を寄せています。このプロジェクトでは、デバイスからオンライン ウェブサイトに URL を煩わしさを感じさせない方法でブロードキャストできます。ウェブ Bluetoothウェブ USBウェブ NFC などの新しいウェブ テクノロジーと組み合わせることで、サイトはデバイスに直接接続するか、少なくともそのための適切な方法を説明できます。

この記事では主にウェブ Bluetooth に焦点を当てますが、ユースケースによってはウェブ NFC またはウェブ USB のほうが適している場合があります。たとえば、セキュリティ上の理由で物理接続が必要な場合は、ウェブ 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 でフィジカル ウェブがサポートされ、関連するデバイスからブロードキャストされている 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* を使用して、取締役会の強化について話し合います。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 とのインターフェースに関する後のセクションで定義します。

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

エジソンボード

Bluetooth と通信しています

noble なら、Bluetooth がもっと簡単に。

次の例では、2 つの Bluetooth Low Energy 特性(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 メソッドを追加し、「read」が機能するように値をエンコードする必要があります。

TemperatureCharacteristic.prototype.onReadRequest = function(offset, callback) {
    var data = new Buffer(8);
    data.writeDoubleLE(this._lastValue, 0);
    callback(this.RESULT_SUCCESS, data);
};

「通知」には、定期購入と登録解除を処理するメソッドを追加する必要があります。基本的には、コールバックを保存します。送信する新しい Temperature の理由がある場合、新しい値(上記のようにエンコード)を使用してそのコールバックを呼び出します。

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

クライアント ウェブアプリの作成

クライアント アプリの Bluetooth 以外の部分がどのように機能するかについてあまり多くのデメリットを述べることなく、Polymer* で作成したレスポンシブ ユーザー インターフェースを例に挙げてデモを行うことができます。完成したアプリは次のようになります。

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

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

ウェブ Bluetooth を使用すると、Bluetooth Low Energy デバイスと簡単に通信できます。ここでは、接続コードの簡略バージョンを見てみましょう。Promise の仕組みが不明の場合は、続きを読む前にこちらのリソースをご覧ください。

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

WebBluetooth API で使用される DataView / ArrayBuffer からの文字列の読み取りと書き込みは、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 はフィジカル ウェブとウェブの Bluetooth を使用してデバイスを検出します。ユーザーは、使用頻度の低いアプリ(随時更新される可能性がある)をインストールすることなく、簡単に接続できます。

デモ

クライアントを試すことで、独自のモノのインターネット デバイスに接続する独自のウェブアプリの作成方法を確認できます。

ソースコード

ソースコードはこちらから入手できます。 問題の報告やパッチの送信はご遠慮ください。

スケッチ

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

スケッチ