2017.11.16
今週はIoTの演習です。
目次
- I. What does IoT mean?
- II. Connect Your Mbed to The Internet
- III. Let’s Try Serial Communication
- IV. Let’s Try Http Communication
- V. Examples
- VI. Practical Work
I. What Does IoT Mean?
最近は、普通の新聞等でもIoTという言葉をよく聞きますね。これはInternet Of Thingsの略で、モノとモノがインターネットを通じて通信することで構築されるシステムのことです。ここでいうモノとは、コンピュータと通信機能が搭載された物であればなんでもよくて、家電製品や監視カメラ、センサーを搭載したデバイスなどのことです。少し前までは、インターネットといえば電子メールやWeb、SNS、電子商取引であり、人と人、あるいは人とモノの間のネットワークというイメージが強かったのですが、モノとモノもインターネットにつなげると色々な可能性があるよ、ということです。概念としては全く新しいものではないのですが、マイコンや無線通信のデバイスが安価で小型になったこと、大量のデータを処理することがクラウドコンピューティング技術の進歩によって容易になったこと、AIの進歩などにより様々な応用分野が現れたこと等によって広く認知されたというべきでしょう。語呂が良かったことも流行った一因だと思います。
所詮は流行り言葉なので、IoTという言葉自体はすぐに消えていく運命にありますが、技術それ自体とそれがどのように応用可能であるかはきちんと理解しておく必要があります。この演習では特に、IoTがどのように応用可能であるかに焦点を当てて学修していきます。
What Can We Do with IoT?
IoTを活用すると、どんなことができるのでしょうか。製品をベルトコンベア式に生産する工場を考えてみましょう。工場には生産目標があり、そのための生産計画があります。順調に生産が行われていれば利益が出るのですが、ベルトコンベアや生産設備に故障が起こると生産がストップして損が出ます。最適解は故障が起こる直前で生産設備の部品を交換することですが、これはなかなか難しいです。また、問題が発生した時に、どこが壊れたのかが即座にわかると嬉しいです。そのために、生産設備にセンサーをつけて、機器の状態をサーバに逐一送ることが考えられます。例えば、機器の温度を測ったり、機器から発生する音を録音したりして、そのデータを蓄積するわけです。蓄積されたデータを解析することによって、温度が何度になったら故障の前兆であるとか、機器からどんな音がし始めたら故障の前兆であるなどのことが分かるかもしれません。
少し前に話題となったAmazonのDash ButtonもIoTの応用の一例と考えて良いでしょう。Amazonのサイトで、Dashと検索してみましょう。妙なボタンがたくさん出てくるはずです。例えば、コカコーラのボタンとか、じゃがりこのボタンなどがあります。これは、ボタンを押すだけでその商品の注文がAmazonで完了してしまうというものです。
要するにIoTは、監視作業や計測作業の自動化、少し面倒な作業の単純化などをやる仕組みということですね。監視作業や計測作業の自動化をすれば、当然膨大なデータが蓄積されます。これを世の中ではビックデータなどと呼んでいて、そのデータを解析し問題の最適化を行うためにAIを使ったりするというわけです。そいういう文脈でIoTとビックデータ、AIはセットで語られることが多くなっています。
この演習では、データの分析・解析及び最適化については扱いません。モノとモノとが通信するというところだけを取り出して演習をしてみたいと思います。Dash Buttonの簡易版のようなものですね。
II. Connect Your Mbed to The Internet
それでは、この演習で使う基本的な仕組みについて説明しましょう。以下の図を見てください。
上記の図のIoTデバイスと書かれた水色の点線で囲まれた範囲が、今回演習で作る部分です。この部分がIoTの”モノ”に対応します。一部、MacBook Airにかかっていますね。この部分は学内無線LANに接続する機能で、本来であればMacBook Airに依存せずに独立のWiFiモジュールを使いたいところなのですが、そこはかなり面倒なことになりますので、今回はこのような形で妥協しています。しかし、本質的にはなんら変わりありませんから基本はしっかりと学修できるものと思います。
Node.js
この仕組みでは、Node.jsを使ってF303K8とMacBook Airの間の通信を行い、データをインターネットへ送ります。Node.jsとは、WebブラウザではなくサーバでJavaScriptを動かすためのプログラムです。
まずはNode.jsをMacBook Airにインストールしましょう。
1. nvmをインストールする
nvmはNode.jsのバージョン管理をしてくれるプログラムです。まずはnvmをインストールし、その後nvmからNode.jsをインストールします。Terminalを起動して、以下の1行をコピペしてエンターキーを押します。
1 2 |
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash |
2. Terminalを再起動する
Terminalを一度終了して立ち上げなおします
3. Node.jsをインストールする
以下の1行をコピペしてエンターキーを押します。
1 2 |
nvm install v6.11.5 |
以下のような出力結果になるはずです。
これでNode.jsが使えるようになったはずです。確認するには以下のようにします。先頭の$はプロンプトを表していますから入力しません。以降、頭に$がついているのはTerminalでの入力だと思ってください。
1 2 |
$ node -v |
バージョン番号である6.11.5を出力されれば成功です。
Example
以下の1行をファイルに保存して、ex01.jsという名前をつけます。テキストエディタを使いますよ。
1 2 |
console.log("Hello world"); |
Terminalでこのファイルを保存したディレクトリに移動して、以下のように実行します。ディレクトリの移動にはcdコマンドを使いますよ。
1 2 |
$ node ex01.js |
Hello worldが出力されるはずです。このように、Node.jsのプログラムはJavaScriptのソースコードをnodeコマンドに渡すことによって実行されます。
Install Node.js Packages
Node.jsの良いところの1つは、npmというパッケージマネージャを使って色々なパッケージを簡単に入れられることです。npmはNode.jsと一緒にインストールされています。npmの公式Webページを見て見ましょう。
一番上に検索する場所がありますね。ここでtwitterを検索して見ます。すると、twitterに関係あるNode.jsのパッケージが1550も見つかります。このように、npmには膨大な量のパッケージが登録されています。Node.jsで何かやりたいと思ったら、とりあえずnpmで検索してみると良いでしょう。
この演習では、以下の2つのパッケージを使います。
- serialport
- request
この2つをインストールします。
1 2 3 |
$ npm install serialport $ npm install request |
この2つがちゃんとインストールされたかどうかは以下のようにして調べられます。
1 2 |
$ npm list |
serialportというパッケージは、Node.jsとF303K8の間の通信に使います。requestはNode.jsとサーバの間の通信に使います。
III. Let’s Try Serial Communication
それでは、Node.jsとF303K8の通信テストをしてみましょう。Node.jsのプログラムは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
const SerialPort = require('serialport'); const port = new SerialPort('/dev/cu.usbmodem1423', { baudRate: 57600 }); console.log("start"); const Readline = SerialPort.parsers.Readline; const parser = new Readline(); port.pipe(parser); parser.on("data", recData); function recData(data) { if(data == "cmd1") { console.log("command1 received"); } else if(data == "cmd2") { console.log("command2 received"); } else { console.log("error"); } } |
とりあえず、今の段階では意味がわからなくてOKです。F303K8のプログラムは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include "mbed.h" Serial pc(USBTX, USBRX); int main() { pc.baud(57600); while(1) { pc.printf("cmd1\n"); wait(1); pc.printf("cmd2\n"); wait(1); } } |
上記のプログラムをコンパイルしF303K8に送った後で、Node.jsの方のプログラムを起動すると、以下のような出力が得られるはずです。cmd1とcmd2が交互に受信されていることがわかります。終了するにはcontrolとcを同時に押します。
それでは、少しプログラムの説明をしましょう。
1 2 3 4 5 |
const SerialPort = require('serialport'); const port = new SerialPort('/dev/cu.usbmodem1423', { baudRate: 57600 }); |
最初の1行は、npmを使ってインストールしたserialportのパッケージを、SerialPortという名前で使えるようにしています。次の行では、このSerialPortのインスタンスを作成しています。最初の引数はMacのシリアルポートの名前ですね。これはls /dev/cu.*
で調べられます。baudRateというのは通信スピードの設定です。ここでは57600bpsにしています。bpsはBit Per Secondの略で、1秒間に何ビット送るか、ということです。シリアル通信では通信スピードをボーレート(baud rate)と呼びます。なぜかボードレートとは言いませんね。
次の3行は、受信したデータを改行までを区切りとして取り出すためのものです。
1 2 3 4 |
const Readline = SerialPort.parsers.Readline; const parser = new Readline(); port.pipe(parser); |
これで、cmd1\n
のように送られてきたデータからcmd1
を切り出すことができました。さらに、以下の行で改行まで受け取った際に実行するコールバック関数を指定しています。
1 2 |
parser.on("data", recData); |
recData
がコールバック関数の名前です。"data"
は、「データを受信したら」という指定です。データを受信したらrecData()
という関数を呼び出します。受信したデータに対応する処理はrecData()
に書くわけです。
F303K8に送るプログラムは、新しいことはありません。通信速度の設定をしていることくらいですね。
1 2 |
pc.baud(57600); |
この通信速度は、F303K8の設定とNode.jsの設定が同じボーレートでなければいけません。また、送信する文字列の最後には必ず\n
が必要です。
これがシリアル通信を行う簡単な枠組みです。今後はこの枠組みを改造して行きます。
IV. Let’s Try Http Communication
今度は、Node.jsとサーバとの通信を考えます。
IFTTT
サーバを作るのは面倒なので、ここではIFTTTを使うことにします。IFTTT(イフト)とは、インターネットに存在する様々なサービスを簡単に組み合わせて使うことを可能にするサービスです。アメリカの会社が運営するサービスですが無料で使えます。IFTTTとは、IF This Then Thatの略だそうです。
これから作るのは、https://maker.ifttt.com/trigger/clicked/with/key/brVFのようなURLにアクセスがあったら決められた電子メールアドレス宛にメールを飛ばすというものです(brVFの部分はIFTTTのキーなので人によって変わります)。
以下のURLにアクセスして、サインアップしてください。
サインインしたら、上の方のメニューからSearchをクリックします。
Webと入力してエンターキーを押すと、Webhooksという項目が出てくると思います。
Webhooksを選びます。Webhooksとは、ある決まったURLにアクセスがあった時にそれをきっかけとして何かをする際に使います。以下の画面でConnectを押します。
一番上のメニューからMy Appletsを選びます。
New Appletをクリックします。
上記のような画面になるので、”+this”の部分をクリックします。これは、「何が起こったら」に対応します。先ほど作ったWebhookを検索します。
イベント名には”clicked”と入れておきましょう。
Create triggerをクリックします。
今度は”+that”をクリックします。これは「何をする」に対応します。ここでは、自分にメールを送ることにします。
Emailを選択してください。
Send me an emailを選びます。
どんな内容のメールにするかを編集する画面になりますが、とりあえずこのままでテストしてみたいのでCreate actionをクリックします。
それでは、IFTTTのトップページに戻って、Webhooksを検索します。
Webhooksを選んだら、以下のような画面になるはずです。
左上の方にある、Documentationをクリックします。
これが、URLの情報です。{event}と書かれている部分をclickedに直したものが、今回のURLになります。WebブラウザでこのURLにアクセスして見てください。自分宛にメールが届くはずです。
Http Communication in Node.js
先ほどは、WebブラウザからWebhooksのURLにアクセスしましたが、今度はNode.jsからアクセスして見ましょう。npmでrequestというパッケージをインストールしましたね。それを使います。
以下のサンプルコードは、requestパッケージのドキュメントにある最初のサンプルです。
1 2 3 4 5 6 7 |
const request = require('request'); request('ここにWebhooksのURL', function (error, response, body) { console.log('error:', error); // Print the error if one occurred console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received console.log('body:', body); // Print the HTML for the Google homepage. }); |
このコードの’ここにWebhooksのURL’と書いてある部分を、各自で作ったWebhooksのURLに書き換えましょう。
これを実行すると、以下のような出力が得られるはずです。
statusCodeが200ということはアクセスが正常終了したことを意味し、bodyの内容はURLにアクセスした際にWebブラウザに表示されるものです。これでメールが届くはずです。
V. Examples
Our First IoT Example
ここまでの説明で必要な材料は全て揃いました。いよいよ、マイコンとインターネットの世界をつなげてみましょう。まずは、デバイスを作ります。プルダウン回路を使ってタクトスイッチをD12に繋いでください。プロダウンですから、タクトスイッチが押されたら1が、押されていなければ0が得られるはずですね。プログラムは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include "mbed.h" #define MIN_TIME_DIFF 30 Serial pc(USBTX, USBRX); DigitalIn button(D12); Timer mytimer; int main() { int previous = 0; int current = 0; int timediff = 0; pc.baud(57600); mytimer.start(); while (1) { previous = current; current = button.read(); if (previous == 0 && current == 1) { timediff = mytimer.read_ms(); if (timediff > MIN_TIME_DIFF) { pc.printf("clicked\n"); } mytimer.reset(); } if (previous == 1 && current == 0) { mytimer.reset(); } } } |
シリアル通信の速度は57600bpsにしてあります。ボタンがクリックされたら、clickedと送信します。
Node.js側では、F303K8からシリアル通信でclickedが送られてきたら、WebhooksのURLにアクセスすれば良いですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
const SerialPort = require('serialport'); const request = require('request'); const webhooks = 'ここにWebhooksのURL'; const port = new SerialPort('/dev/cu.usbmodem1423', { baudRate: 57600 }); console.log("start"); const Readline = SerialPort.parsers.Readline; const parser = new Readline(); port.pipe(parser); parser.on("data", recData); function recData(data) { if(data == "clicked") { request(webhooks, function (error, response, body) { console.log('error:', error); console.log('statusCode:', response && response.statusCode); console.log('body:', body); }); } else { console.log("error"); } } |
どうですか?ボタンを押したらメールが受信できたでしょうか。
Sending Data
今度は、ボタンを2つ使います。ボタンが複数になるとコードが煩雑になって面倒ですね。ボタンの処理をクラスにまとめてみましょう。
以下のフィアルはButton.hです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include "mbed.h" class Button { private: DigitalIn button; Timer t; int previous; int current; int min_time_diff; public: Button(PinName pin); void start(); void update(); bool pressed(); }; |
以下のフィアルは、Button.cppです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include "Button.h" Button::Button(PinName pin) : button(pin) { previous = 0; current = 0; min_time_diff = 50; }; void Button::start() { t.start(); } void Button::update() { previous = current; current = button.read(); } bool Button::pressed() { if(previous == 0 && current == 1) { if(t.read_ms() > min_time_diff) { t.reset(); return true; } t.reset(); } else if(previous == 1 && current == 0) { t.reset(); } return false; } |
このButtonクラスを使って、2つのボタンの処理を書いてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include "mbed.h" #include "Button.h" Serial pc(USBTX, USBRX); Button button1(D12); Button button2(D11); int main() { pc.baud(57600); button1.start(); button2.start(); while (1) { button1.update(); button2.update(); if(button1.pressed()) { pc.printf("cmd1\n"); } if(button2.pressed()) { pc.printf("cmd2\n"); } } } |
これで、ボタンが増えてもコードが長ったらしくならなくて良いです。
さて、IFTTTのthisには先ほどと同じくWebhooksを使うのですが、イベント名を”log”にします。
thatはgoogle spreadsheetにしてみましょう。
Node.jsのプログラムは以下のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const SerialPort = require('serialport'); const request = require('request'); const url = 'ここにWebhooksのURL'; const port = new SerialPort('/dev/cu.usbmodem1423', { baudRate: 57600 }); console.log("start"); const Readline = SerialPort.parsers.Readline; const parser = new Readline(); port.pipe(parser); parser.on("data", recData); function recData(data) { if(data == "cmd1" || data == "cmd2") { var webhooks = url + '?value1=' + data; request(webhooks, function (error, response, body) { console.log('error:', error); console.log('statusCode:', response && response.statusCode); console.log('body:', body); }); } else { console.log("error"); } } |
Webhooksは、3つの値を受け取ることができます。Webhooksに値を受け渡す際には、以下のようなurlを使います。
https://maker.ifttt.com/trigger/log/with/key/xxxxxxxxx?value1=v1&value2=v2&value3=v3
value1、value2、value3がキーで、v1、v2、v3が値です。上記のプログラムではvalue1だけを使って、2つあるボタンのどちらが押されたかを記録するようにしています。
VI .Practical Work
実験1. 3つのボタンを記録する
Exampleのコードを改造して、3つのボタンを識別できるようにしましょう。google spreadsheetに記録します。ボタンを1つ配ります。
実験2. SNSに投稿してみる
Twitterにアカウントを作って、ボタンが押されたら以下の情報をツイートするようにしてみましょう。
- ボタンが押された日時
- これまでボタンが押された累積回数 ← これ大事です
実験3. 入力装置
ボタンを2つ使います。この2つのボタンをそれぞれ、数決めボタン、送信ボタンと呼ぶことにしましょう。数決めボタンは、押されるごとに変数sendDataの値をカウントアップします。送信ボタンは、押されるとsendDataの値をgoogle spreadsheetに記録し、sendDataの値を0に戻します。
— by 綿貫 理明、石井 健太郎、飯田 周作 専修大学ネットワーク情報学部