最近、インターネット オブ シングス(IoT)が話題になっています。私のような改造家やプログラマにとっては、非常に興味深いものです。自分で作った発明品を動かして会話できるなんて、最高にクールです。
ただし、ほとんど使用しないアプリがインストールされている IoT デバイスは煩わしい場合があります。そこで Google は、Physical Web や Web Bluetooth などの今後のウェブ技術を活用して、IoT デバイスをより直感的で邪魔にならないものにしています。

ウェブと IoT の組み合わせ
モノのインターネットが大きな成功を収めるには、まだ克服すべきハードルが数多くあります。1 つの障害は、購入するデバイスごとにアプリをインストールするようユーザーに求める企業や製品です。これにより、ユーザーのスマートフォンには、ほとんど使用しないアプリが多数インストールされてしまいます。
そのため、Physical Web プロジェクトに大きな期待を寄せています。このプロジェクトでは、デバイスが URL をオンライン ウェブサイトに邪魔にならない方法でブロードキャストできます。Web Bluetooth、Web USB、Web NFC などの新しいウェブ技術と組み合わせることで、サイトはデバイスに直接接続できます。少なくとも、適切な接続方法を説明できます。
この記事では主に Web Bluetooth について説明しますが、一部のユースケースでは Web NFC や Web 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
前者は、Bluetooth Low Energy を介して通信するために使用するノード モジュールである 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 は 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 がデバイスを正常に検出していることを示しています。


センサー/アクチュエータとの通信
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
変数に保存されています。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
を使用する場合と同じくらい簡単です。必要なのは 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);
},
概要
以上です。ご覧のとおり、クライアント側で Web Bluetooth を使用し、Edison で Node.js を使用して Bluetooth Low Energy と通信するのは非常に簡単で強力です。
Chrome は Physical Web と Web Bluetooth を使用してデバイスを見つけ、ユーザーが望まない、または定期的に更新される可能性のある、あまり使用しないアプリをインストールしなくても、ユーザーがデバイスに簡単に接続できるようにします。
デモ
クライアントを試して、独自のウェブアプリを作成してカスタム IoT デバイスに接続する方法を確認できます。
ソースコード
ソースコードはこちらで入手できます。問題の報告やパッチの送信をお気軽にお寄せください。
スケッチ
冒険心を出して、私が行ったことを再現したい場合は、以下の Edison とブレッドボードのスケッチを参照してください。