たった1日で出来たWeb + Android + Arduinoのリアルタイム連携プロトタイピング

はじめに

今回のネタは、早稲田大学で行われているAndroidアプリ開発養成講座TechInstituteで、センサー回りの講座を受け持つことになり、Androidのセンサーを使った応用例として作成しました。

動作概要

Android

動作としては某L◯NEの「ふるふる」っぽい動作をAndroidでは行います。「加速度センサー」で、ある一定の加速度を超えたら、「GPSセンサー」で位置情報をサーバに送信します。Webサーバでは送信された位置情報をGoogleMapにマッピングします。

Arduino

Arduinoには何かセンサーを接続します。なんでもいいのですが、「照度センサー」とします。照度センサーにより、周囲が暗くなったらサーバに「暗くなった」ことを通知します。

Web

Webサーバは送られてきた位置情報のマッピングを行います。またブローカサーバがArduinoからのセンサー情報を、接続している全Android端末にPush配信を行います。

構成

ざっくりこんな感じです。

f:id:tomo_watanabe:20140820234129p:plain

もうちょっと詳しく書くと

f:id:tomo_watanabe:20140820234656p:plain

Androidアプリから、位置情報をMQTTプロトコルでブローカにpublishすることで、subscribeしているWebサーバがそれを受信して、GoogleMap上にマッピングします。Arduinoから「暗くなった」とブローカにpublishすると、Androidアプリでは事前にそのTopicをsubscribeしているようにしてあるので、Pushが飛んできます。※MQTTについてはこちらを参照

使っている技術は難しくありません。

  • Androidアプリ:加速度センサー、GPSセンサー、MQTTクライアント
  • Arduino:照度センサー、MQTTクライアント
  • MQTTブローカサーバ:mosquitto
  • Webサーバ:Node.js + WebSocket(Socket.IO) + MQTTライブラリ

それぞれの画面

Androidアプリ

スクリーンネームを入れて、スタートボタンを押して「振る」単純なアプリです。加速度センサーをチェックして、振れ幅が大きくなったら位置情報を送信します。

f:id:tomo_watanabe:20140820235916p:plain

Arduino

UIは無いです。Cdsセルで周囲の照度を測定して、一定値以下になったら「暗くなった」と通知します。

f:id:tomo_watanabe:20140821101028j:plain

AndroidにはPushで通知が飛んできます。

f:id:tomo_watanabe:20140821101840p:plain

Web

Node.jsでWebSocketを使用して、送信された位置情報とスクリーンネームマッピングします。

f:id:tomo_watanabe:20140821000241p:plain

作成の流れ

  1. Androidのセンサー応用例のデモを作ろう
  2. ふるふるっぽいのなら、加速度センサーとGPSセンサーでできそうだな(ここまで前日案)
  3. 位置情報を表示するMapが欲しいな。んじゃMQTTを使ってNode.jsで受けるか
  4. MQTTブローカサーバ立てる
  5. PyhtonでMQTTのPubSubのサンプルを作成して動作を確認
  6. Node.js + Socket.IOでGoogleMap表示部作成
  7. MQTTライブラリをNode.jsに組み込んで、ブローカをSubscribeするようにする
  8. PythonでMQTTのサンプルのPublishを作成して、ダミーの位置情報をブローカに送信するとNode.js上のGoogleMap上にマッピングできることを確認
  9. Androidアプリで加速度センサーとGPSを組み合わせてアプリを作る
  10. MQTTライブラリを送信用に組み込んで、8で作成したtopicフォーマットに沿って送信するようにする
  11. Andoridアプリをふるふるすると、無事GoogleMapに表示できた\(^o^)/
  12. Arduinoも講義で扱うし、ArduinoにもMQTTクライアントがあるからこれも繋げてみよう
  13. 題材で使用する照度センサーとArduino Ethernetを組み合わせてMQTTでブローカに投げるようにする
  14. Androidアプリの方にArduinoの通知を受け取れるように、MQTTブローカへのSubscribeを追加
  15. ほぼ完成\(^o^)/

ここまででほぼ1日8時間くらいの作業という感じでした(途中買い物行ったりしてので、出来たのは夜中近く)

参考情報(Topicの扱い)

  • Android

    • subscribe "PUBLIC/location/port/all/#"
    • publish "PUBLIC/location/state/hogefuga" ← hogefugaはスクリーンネームが入る
  • Node.js

    • subscribe "PUBLIC/location/state/#"
  • Arduino

    • publish "PUBLIC/location/port/all/push"

ArduinoのあたりのTopicの扱いが変だけど、最後取ってつけた関係上...

まとめ

ここに出てくる技術はひと通り経験したことがあるので、アイデアが出れば実装はさほど難しくありません。 だいたい1日の作業でこのくらいまでプロトタイピング実装できるんだなーと、あらためて今の技術の恩恵を感じました。逆に言えば、技術よりもアイデアの方が重要なのかも(こっちのが難しい)

さて、実装してみると色々展開できることが見えてきます。

MQTTなので上手くTopicのPublish/Subscribeのルールを決めてやることで

他にも

  • ボタンでの通知を時間間隔での通知にして、ジオフェンスを使った遊びや監視?
  • Arduino側をウェアラブルデバイスとしてアイデアを膨らますこともできそうです
  • Arduino側を様々な家電に置き換えて考えると、コントローラ&通知システム作れますね

ソースコードは公開してもいいのですが、なんせ1日で作ったレベルなのでちょっと...

要望があれば整理して公開できるようにするかもしれません。

ArduinoのWiFiシールド(CC3000)でWiFiのセットアップ

ArduinoWiFiが使える、かつTELEC取得済みのCC3000 WiFiシールドを入手したので、セットアップしてみました。

まずはハンダ付け

スイッチサイエンスから購入したのですが、ブツがビニールに入ってるだけで特に説明書らしきものはありません。Arduinoシールドとはいえ、ピンソケットも付属していないので、一緒にピンソケットも購入しておいたほうがいいでしょう。

このようなWiFiシールドだとさらに重ねる可能性があるので、ピンソケットをこのようにハンダ付けしておきます。なおこのシールドはSPIを使用していますので、重ねる場合は注意して下さい。

f:id:tomo_watanabe:20140510204418p:plain

WiFiのセットアップ

WiFiのセットアップはsparkfunのページのCC3000 Hookup Guideを見ながら行えば問題ありません。

CC3000のライブラリのダウンロード

ライブラリのコードはGithubにありますが、リポジトリのmasterをココからzipファイルで落としてきて展開した方が楽です。落として展開したフォルダを「SFE_CC3000_Library-master」→「SFE_CC3000_Library」のようにちょっと修正して、ライブラリフォルダの中に放り込みます。Macだと~/Documents/Arduino/librariesになると思います。

WebClientで動作確認

ライブラリにファイルを置いたら、ArduinoIDEを起動します。動作確認はArduino Uno R3で行いました。

f:id:tomo_watanabe:20140510210119p:plain

ファイル→スケッチの例→SFE_CC3000_Library→WebClientでサンプルスケッチをオープンします(※Macの場合)

// Constants
char ap_ssid[] = "SSID";                  // SSID of network
char ap_password[] = "PASSWORD";          // Password of network
unsigned int ap_security = WLAN_SEC_WPA2; // Security of network
unsigned int timeout = 30000;             // Milliseconds
char server[] = "www.example.com";        // Remote host site

SSIDとPASSWORDの部分を、接続したいSSID名とパスワードに変更するだけです。serverは接続確認を行う先のURLです。このスケッチは起動時にクライアントのIPアドレスを設定して、"http://www.example.com/index.html"をGETで取ってくるようになっています。書き換えたらArduinoに書き込みます。書き込みが終ったらシリアルモニタを起動します。この時通信速度は115200に設定します。

正常にIPアドレスを取得し、www.example.comからデータを取得できると下記のようにGETで取得したログが流れます。

f:id:tomo_watanabe:20140510210750p:plain

このスケッチをベースにスケッチを書けば通常のEthernetと同じように使えるようになります。

SmartConfigを使ったセットアップ

このCC3000のウリは、スマートフォンSSIDとパスワードを設定できることのようなので、そちらも試してみます。

ファイル→スケッチの例→SFE_CC3000_Library→SmartConfigでサンプルスケッチをオープンします(※Macの場合)

ここでスマートフォンアプリを用意しておきます。iOS版とAndroid版があるのですが、iOS版はiTunesからダウンロードできるのに対し、Android版はなぜかTIにアカウントを登録してapkを自分でインストールする必要があります。今回はapkインストールするのが面倒だったので、iOS版でセットアップしましたが基本的には同じようです。

iPhone

  • SSID : 接続するアクセスポイント
  • Password : パスワード
  • Key : オプションなので特に設定する必要はありません

ここまで設定を入力したら、Aeduinoにスケッチを書き込んでシリアルモニタを起動します。Arduinoが起動すると"Starting SmartConfig"とメッセージが出たら、スマートフォンの"start"ボタンをタップします。成功すると、下記のようにsparkfunのサイトにpingを打って動作確認してくれます。

f:id:tomo_watanabe:20140510212737p:plain

うまく設定ができない(Arduinoタイムアウトする)と下記のようになります。スマートフォンのアプリの"start"のタイミングに気をつけてください

f:id:tomo_watanabe:20140510212928p:plain

FastConnect

SmartConfigでの設定は、毎回起動時にSSIDの設定しなければならず面倒ですが、実は一度SmartConfigでSSIDとパスワードを設定しておけば、CC3000内部に記憶されるので、このFastConnetが利用できるようになります。(電源を外しても記憶しているようなので、内部Flashに記憶しているのかな)

ファイル→スケッチの例→SFE_CC3000_Library→FastConnetでサンプルスケッチをオープンします(※Macの場合)

SmartConfigで設定済みなら、何の問題もなく下記のように動作するはずです。

f:id:tomo_watanabe:20140510213758p:plain

このSmartConfigは結構便利ですね。

Intel Galileoはじめの一歩

やっとIntel Galileoが来たので、とりあえず最初にやっておくことひと通り

f:id:tomo_watanabe:20140119182300j:plain

ファームウェアのアップデート

まずはファームウェアのアップデートをやっておかないとならないのですが、いきなりここでハマりました。3回くらいファームのアップロードをやってやっと成功。やり方がまずかったかもしれませんが、シリアルポートがハングアップすることがありました。

アップデートはなぜかGalileo用のArduino IDEから行います。まずはここからマシン環境にあったIntel Galileo Arduinoを落としてインストールします。いわゆる通常のArduino IDEは使用できないので注意です。Macの場合はドライバは必要ないようです。

起動したら設定を行う

Macの場合、どうやらここで「tty.XXX」ではなく、「cu.XXX」を選択しないとダメなようです。僕は最初「tty.XXX」を指定してやったらハングアップしました(^_^;)

f:id:tomo_watanabe:20140119181238p:plain

f:id:tomo_watanabe:20140119181248p:plain

さてファームウェアのアップデートを行います。ヘルプから「Firmware Update」を選択します。

f:id:tomo_watanabe:20140119181506p:plain

こんな感じで警告がでます。ACアダプタをちゃんと挿しておきましょう。

f:id:tomo_watanabe:20140119181610p:plain

OKを押すとファームウェアのアップデートが開始されます。

f:id:tomo_watanabe:20140119181712p:plain

途中5分くらい掛かるよ。というメッセージに変わり、終了すると終了メッセージが出ます(スクショ忘れ)ちなみに実際には5分以上掛かっていました。かつ、アクセスランプの点滅等が無いので、ほぼ進行状況が掴めないという優しくない仕様・・・

(僕は最初tty.XXXを選択していたためか、ここで10分ほど放置した結果、ハングアップしてると判断して電源引っこ抜きました・・・)

Lチカで動作確認

ファームウェアのアップデートが完了したら、サンプルのLチカを実行してみます。Arduinoのメニューから「Blink」を選択して、ビルド・ダウンロードするだけです。

f:id:tomo_watanabe:20140119182605p:plain

手前のグリーンのLEDがチカチカと点滅すればOKです

f:id:tomo_watanabe:20140119182451j:plain

まとめ

まずはここまでやっておけば、あとはArduinoで何かすれば普通にできそうです。とはいえ、ただのArduino互換で使うには勿体無いというか、これ一応Quarkの開発ボードなので、メインはLinux動かしてなにかやる方だと思いますが。しかしこのQuarkチップは熱持ちすぎですね。触ると数秒で手を離すレベルです。これではCES2014で発表したIntel Edisonは熱とか大丈夫なんでしょうか?この発熱だととてもIoT用のチップとしては使えない気が・・・(´ε`;)ウーン…

とりあえず次は手元にあるArduinoシールド動かしてみるかな...

インターネットとハードウェア

組込みプログラミングの必要性

プログラミングというとPC上でのプログラムが普通ですが、最近感じているのは組込み系のプログラミングの重要性です。単に組込みプログラミングというだけではなく、デバイスとネットワークを扱うためのプログラミングと言ったほうがいいかもしれません。

理由は下記の通り

  • 身近なものがインターネットに繋がる、モノのインターネットではデバイスを扱うことが必須
  • 子供の頃から見たり触れたりするのは実体のあるデバイスであり、プログラミングは実体のあるデバイスを動作させることで興味を持つことができる
  • 組込み系のデバイスのプログラムが書けるのは、おっさんばかり。若い人はこれが出来ると需要がありそう
  • ハンダ付けや簡単な電子回路の知識もついでに身につく

とはいえ、最近のデバイスはそこまでの知識が必要なくてもハードウェアを叩けるようになってきているので、まずはそこから始めるのがいいでしょう。

何から始めるか?

Arduino

Arduino Uno

言わずと知れたプロトタイピング用デバイス。IDEもありC言語ライクに書ける。情報も圧倒的に多くとりあえず始めるならコレ。ただしネットワーク周りはやや貧弱で、イーサネット対応しようとするとコストが高く、別の選択もあり


Raspberry Pi

Raspberry Pi

最近流行りの小型PCボード。拡張性は低いけれどもGPIO周りは多くの情報があり、Linux扱える人ならなら敷居が低い。ネットワークも普通に使える(実際には組み込みボードとは言い難い)


Espruino

Espruino

JavaScriptで書けるデバイス。Web系の人が入るには良さそう。KICKSTARTERで募集中(2013/9/1現在調達達成)。ポチった


TESSEL

TESSEL

こちらもJavaScriptで書ける。WiFIを搭載し、Node.jsも動く。こちらも購入予定


「モノのインターネット」を考えると、今流行のArduinoでは限界が見えている気がします。もう少しリッチなデバイスで、かつArduinoの手軽さでネットワークが扱える。というのが求められているのでしょう。後者の2つはそこを狙っているように思います。ルネサスからSAKURAボードというArduino互換に近いボードが出ているのですが、僕が使った感じではTCPライブラリに問題があるようで(TCPをクローズしてくれない)、ネットワークを扱うのは避けたほうがよさそうです。

ARMマイコンを使えるように

もっと簡単にレゴロボットでプログラムを学習しよう。とかいうのも導入的にはありだと思いますが、やはりゴールはCPUアーキテクチャやメモリマップ、レジスタ制御などをきちんと理解してプログラムが書けるようになっておきたいものです。というわけで、個人的に最近使っているArduinoに限界を感じたためARMマイコンの勉強を始めました。ゴールはCortex-Mシリーズを使えるようになること。昔はSHマイコンでμITRONとかやっていたのですが、すっかり錆びついているため思い出しながら始めます。

おまけ

ThinkITさんで、「Raspberry PiとNode.jsで作る独立稼働モバイルサーバ」という記事を書きました。Raspberry Piを使ってNode.js + ハードウェアでサーバ型カーナビを作る。というものです。

モバイルサーバ

AndroidiPhoneがもたらしたのはスマートフォンだけではなく、32bit組込みマイコンのコストが下がり、LinuxなどのリッチOSが載るようになりました。Arduino, Raspberry Piそしてこれから出るであろう様々なデバイス。インターネットとハードウェアの融合はこれからが楽しい時になりそうです\(^o^)/

ZigBeeモジュール Wireless Engine TWE-Lite DIPを使ってみた

なんか最近発売されて、かなり売れているらしいTWE-Lite DIPを使ってみました。ハマったポイントなどを書いておきます。

TWE-Lite DIPとは

東京コスモス電機が発売しているZigBeeの無線モジュールです。販売は「秋月電子」とか「千石電商」とかで売ってます。

基本的には2つ以上を組み合わせて使います。最小構成は「親機」と「子機」となります。TWE-Lite DIPはデジタルピンやアナログピンを持っており、それぞれが双方向無線通信できるようになっています。

到達距離の確認について

公式ページには見通し1km送受信可能!と書いてあるので、おおっ!と思いますが、実際にどれだけ飛ぶのかテストしますよね。サンプルとして公式にもある「通信距離の測定」をやると思います。

親機のボタンをポチポチすると、子機のLEDが点滅します。わかりやすいです。

しかし、これには1つ罠があります。送信したデータ(この場合HIGHもしくはLOW)は送信先で保持されるため、送信元が変更しない限り変化しません。つまり親機のスイッチを押しっぱなしで、子機をずーっと離していって、たとえ通信が途絶えたとしても、LEDは点灯しっぱなしになりますので、どこまで届いたかはわかりません。到達距離を測るには、親機のボタンをON/OFFし続けて、子機が同時にON/OFFすることを確認しなければなりませんので注意して下さい。これだと送信距離のテストがしづらいので、疎通テスト用に別途作成することにしました(後述)。

Arduinoと組み合わせてみる

TWE-Lite DIP単体では無線通信ができるだけなので、何かしらマイコンなどを接続してデータをやり取りすることになります。上図のようにシリアル(RX, TX)も使えるので、PCにUSBシリアルで接続すればそれでもいいのですが、今回はArduinoを接続して子機側のArduinoにセンサを載せて、親機に送信することを考えます。

Arduinoの選択

TWE-Lite DIPは3.3v駆動です。Arduinoも3.3vのpro miniなどありますが、何かしらのセンサを載せようとすると結局5vと3.3vは混在しそうなので、今回は親機にはArduino Uno、子機にはArduino pro mini 5vを選択しました。子機側がpro miniなのは、ユニバーサル基板上にセンサを配置する関係で広いスペースが欲しかったからです。親機にはセンサは搭載しないので、Arduinoユニバーサル基板を使います。

3.3v <-- --> 5v 双方向レベル変換バッファ

親機・子機ともに必要になるのは、ArduinoとTWE-Lite DIPを接続するための双方向レベル変換バッファです。今回2種類購入しておきました。1つは秋月電子8bit双方向レベル変換モジュールもう1つはサンハヤト2電源8bit双方向レベル変換モジュール。価格は前者の方が安いのですが、I2Cの変換が出来ません。そのために後者も買っておきました。

で・・・最初にハマったのが、前者の秋月電子の双方向レベル変換バッファが使えなかった...orz 後述しますがシリアル通信で速度を落としても文字化けするので、結局使うのを諦めました。←使えるよという人が居たら教えて下さい。

作成した回路図の概略はこんな感じです

f:id:tomo_watanabe:20130804011518p:plain

f:id:tomo_watanabe:20130804011527p:plain

シリアル通信をレベル変換してArduinoと接続する

当初ArduinoとTWE-Lite DIPをシリアルRX, TXで接続してやれば、親機と子機で好きなデータ送受信できるよ、わっほい\(^o^)/とか考えていたのですが、これが甘かったようです。

TWE-Lite DIPは、例えば親機のデジタルイン→子機のデジタルアウトと通信するのですが、これらの変化が実はシリアルのRX,TXを通して双方向でバンバンやり取りされていました。

公式ページから抜粋

:7881150175810000380026C9000C04220000FFFFFFFFFFA7 
:7881150175810000380026FF000C02220000FFFFFFFFFF73 <約1秒おきに受信される
:788115017581000038002743000C03220000FFFFFFFFFF2D
:788115017581000038002785000C05220000FFFFFFFFFFE9
:7881150178810000380027D3000C05230000FFFFFFFFFF97
:788115017881000038002813000C02230000FFFFFFFFFF59
:78811501758100003800284F000C02230000FFFFFFFFFF20
:788115017581000038002899000C04230000FFFFFFFFFFD4
 ^1^2^3^4^5^^^^^^^6^7^^^8^9^^^a^b^c^de1e2e3e4ef^g
 
 1: 送信元の論理ID (0x78 は子機からの通知)
 2: コマンド(0x81: IO状態の通知)
 3: パケット識別子 (アプリケーションIDより生成される)
 4: プロトコルバージョン (0x01 固定)
 5: LQI値、電波強度に応じた値で 0xFF が最大、0x00 が最小
 6: 送信元の個体識別番号
 7: 宛先の論理ID
 8: タイムスタンプ (秒64カウント)
 9: 中継フラグ(中継済みなら1)
 a: 電圧[mV]
 b: 未使用
 c: DI の状態ビット。DI1(0x1) DI2(0x2) DI3(0x4) DI4(0x8)。1がLo。
 d: DI の変更状態ビット。DI1(0x1) DI2(0x2) DI3(0x4) DI4(0x8)。1が変更対象。
 e1~e4: AD1~AD4の変換値。0~2000[mV]のAD値を16で割った値を格納。
 ef: AD1~AD4の補正値 (LSBから順に2ビットずつ補正値、LSB側が AD1, MSB側が AD4)
 g: チェックサム

このように親機と子機、双方が約1秒おきに送信しあう仕様になっています。したがって、シリアル通信を自分の好きなように使うことができません...orz 公式ページにありますが、制約されたプロトコルにしたがってシリアルに載せないとデータのやり取りができません。使う時はここに注意が必要です。

TWE-Lite DIPのシリアル通信

上記のように、好きなようにシリアル通信を行うことができないことがわかりましたが、このシリアル通信を見ていてわかったことを書いておきます。設定は親機:連続モード、子機:連続モードです。TWE-Lite DIPが独自のシリアル送信を行う条件は

  • アナログ・デジタルのどれかのピンに変化が起きた時
  • タイムスタンプが変化したとき(内部タイマ割り込みだと思う)

のいずれかです。そのため未使用のアナログの入力ピンはHIGHにしておく(VCC)必要があります。オープンにしておくと、アナログ入力が変化してしまうので、バンバン送信してしまいます。解説書にもHIGHにしておけ。と書いてあります(ちなみに単に動作させるという意味では必須ではありません)そうすると、内部タイマ割り込みにより、約1秒毎に送信を行うようになります。

シリアル通信の設定

TWE-Lite DIPは外部マイコンとのシリアル通信の設定にデフォルトでは115200, もしくは38400が選択可能となっています。ArduinoデバッグシリアルとしてPCとのシリアル通信も必要になるので、Arduinoとは2pin, 3pinを使ってソフトウェアシリアルでTWE-Lite DIPと接続します。Arduinoのシリアル通信の上限が115200なので、データの取りこぼしを考えてTWE-Lite DIPとの通信は38400としました。

そこで前述の双方向レベル変換バッファを使用するのですが、秋月電子の8bit双方向レベル変換バッファは、ArduinoからPCへのデバッグシリアルで文字化けしてしまいました。TWE-Lite DIPの通信速度を設定変更モードを使って9600まで落としてもダメでした。

というわけで、サンハヤトの双方向レベル変換バッファに変更してみたら、1発で通信データが読めるようになりました。そのためこちらを採用しました。

秋月電子の8bit双方レベル変換バッファが使えないのは理由がわかりません。本当は使えるのかも。誰か教えてください(^^;

疎通テスト

ここまで出来ると、やっと疎通テストができるようになります。親機・子機のハードウェアシリアルをPCに接続してデバッグウィンドウを眺めると、通信データが見られるようになります。今回、どれだけの距離を送受信できるか確認したかったので、DIPスイッチで起動時切り替えを行い、特定のIDの場合、親機から子機へDO → DIのデータ送信を行い、1秒起きに子機の接続したLEDを点滅させます。同時に親機のArduino Unoに接続したLEDも点滅させます。子機をモバイルバッテリかなにかでUSB給電し状態で徐々に距離を離していって、送受信できなくなると子機のLEDが点滅せず、消灯、もしくは点灯に固定されるので、送受信が途絶えたことが判定できます。(左が親機、右が子機)

f:id:tomo_watanabe:20130803224636j:plain

自宅で試してみたところ(鉄筋コンクリートマンション)壁などの障害物があっても端から端までは問題なく届いたので、家屋内でも10mくらいは大丈夫そうです。アンテナは上下の感度は殆ど無いとのことなので、2階と1階とかは難しいかもしれません(未検証)

実際に有用なプログラムを組む場合はこのデータを順次読みだして「捨てる」もしくは「処理する」判断ルーチンが必須です。(特に変化無い場合でも送受信が行われるので...)その他にも送信データは文字列ですが、センサー値などの取得は数値なので、送信時にsprintfなどを使って文字列化する必要もあります。その前にチェックサムの作成も必要ですね。

というわけで、思いの外面倒な感じになりそうです...シリアル通信が自由に使えれば楽だったのですが(^^;

今回作成したプログラムは公開しようと思いますが、一旦整理してからにしたいと思います。

JenkinsのビルドステータスをArduinoで表現する

いわゆるパトランプというやつですね。最初USB対応の製品を探したのですが、なんせ3万円とかする…これは買えない…orz というわけで、別件で試作したArduinoのシールドにフルカラーLEDを載せたのを思い出し、これを活用してしまおうということにしました。

用意する物とか

  • Arduino (今回は手元にあったUno)
  • USBケーブル(ArduinoとサーバPCを接続します)
  • Jenkinsが動いてるサーバ(Ubuntu 12.04)
    • Jenkinsはサーバ上でlocalhost:9090/jenkins/で動いてます
  • Node.js + node-serialport
    • Node.jsはサーバ上でlocalhost:3000/で動かします

構成

今回悩んだのはシリアルのコントロールでした。Jenkinsのジョブでシェルスクリプトを使って、Arduinoにシリアル経由でコマンドを投げたかったのですが、うまい方法を思いつかず、手っ取り早くNode.js + node-serialportを使って実装することにしました。

f:id:tomo_watanabe:20130716233556p:plain

Jenkinsにプラグインを導入

Jenkinsのビルドのステータス通知を取得すること。ビルド実行後にシェルスクリプトをジョブから実行する必要があります。 このサイトにズバリ書いてありました。

ステータス通知は

RESULT=`curl http://localhost:9090/jenkins/job/[HogeProject]/lastBuild/api/xml | perl -le '$_=<>;print [/<result>(.+?)</]->[0]'`
(HogeProjectの場合)

で取れます。そしてビルド実行後の処理にシェルスクリプトを使うためにPost build taskプラグインを導入しておきます。

Node.jsサーバを立てる

Node.jsサーバをポート3000番で立てます。Expressを使って作成し、node-serialportを組込みます。この辺はRaspberry PiでNode.jsを動かす(※Raspberry Piですが、Ubuntuでも基本は変わりません)とかSerialportを使ってNode.jsでArduinoと通信するを参考にしてください。ちなみに今回はSocket.IOは必要ありません。Expressでサーバだけ作って、シリアルにデータ出力さえできればいいので。さて、UbuntuではArduinoは"/dev/ttyACM0"と認識されるようなので、app.jsにこんな感じでシリアルを設定します。

// Serial Port
var portName = '/dev/ttyACM0'; // Ubuntu環境
var sp = new serialport.SerialPort(portName, {
    baudRate: 9600,
    dataBits: 8,
    parity: 'none',
    stopBits: 1,
    flowControl: false,
    parser: serialport.parsers.readline("\n")
});

そして、JenkinsのジョブのスクリプトからステータスをNode.jsで受け取るためのWebAPIを作成します。これでhttp ://localhost:3000/status?status=hogeで受け取った、パラメータhogeをspのシリアルに送信することができます。

// Web APIエントリポイント
app.get('/status', function(req, res) {
    res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
    res.end('OK');

    var msg = new Object();
    msg.status = req.query.status;
    console.log('status ' + msg.status);

    sp.write(msg.status, function(err, bytesWritten) {
        console.log('bytes written: ', bytesWritten);
    });
});

ここまで出来ると、とりあえずArduinoをPCとUSBケーブルで接続してNode.jsのサーバを起動すると、シリアルとしてArduinoをオープンできるようになります。ただ、デバイスドライバをオープンするにはデフォルトでsu権限が必要になっていますので、/dev/ttyACM0をchmodで777とか設定して、フルアクセス可能にしてしまいました(^^; とにかくこれで、Node.jsとArduinoがシリアル通信できるようになりました。

Arduinoの仕様

Arduinoの仕様も決めておきましょう。基本的にはフルカラーLEDを使い、以下のようなステータスとコマンドインターフェースとしました。statusが1文字なのは、Arduino側で実装を簡単にするためです。

  • 初期化(消灯) : /status?status=r
  • ビルド中(青) : /status?status=g
  • 成功(緑) : /status?status=s
  • 失敗(赤) : /status?status=e

Arduino側のプログラムは簡単で、シリアルの入力を待って比較してLEDを制御するだけです。ちなみにこのプログラムでは点灯しっぱなしになります。実運用では適当な時間が経ったら初期化するとか入れた方がいいでしょう。以下抜粋です(使用したLEDがアノードコモンなので、点灯が0になっています)

void loop() {
  if (Serial.available() > 0) { // シリアル通信でデータが送られてきたら
    char c = Serial.read(); // 一文字分データを取り出す。
    
    if (c == 'g') { // Jenkinsビルド開始
      Serial.println("START");
      analogWrite(LED_RED, 255);
      analogWrite(LED_BLUE, 0); // 青LEDを点灯
      analogWrite(LED_GREEN, 255);
    } else if (c == 'e') { // エラー
      Serial.println("ERROR");
      analogWrite(LED_RED, 0);  // 赤LEDを点灯
      analogWrite(LED_BLUE, 255);
      analogWrite(LED_GREEN, 255);
    } else if ( c == 's') { // 成功
      Serial.println("SUCCESS");
      analogWrite(LED_RED, 255);
      analogWrite(LED_BLUE, 255);
      analogWrite(LED_GREEN, 0);  // 緑LEDを点灯
    } else if (c =='r') { // 初期化
      Serial.println("RESET");
      analogWrite(LED_RED, 255);
      analogWrite(LED_BLUE, 255);
      analogWrite(LED_GREEN, 255);
    } else {
      Serial.println("etc...");
      analogWrite(LED_RED, 255);
      analogWrite(LED_BLUE, 255);
      analogWrite(LED_GREEN, 255);
    }
  }
  delay(1000);
}

ここまで出来たら動作確認ができます。サーバのコンソールで

$ curl http://localhost:3000/status?status=g

とか打ってみて、LEDが希望通りに点灯すればOKです。あとはこのcurlのコマンドをJenkinsのジョブの中からシェルスクリプトとして実行できるように設定してやります。

Jenkinsのジョブの設定

最後にJenkinsのジョブ設定を行います。

  • ビルド開始時に「実行中」
  • ビルド終了時に「成功」「失敗」

というようにします。

ビルド開始時

 curl http://localhost:3000/status?status=g

実際にはこんな感じ

f:id:tomo_watanabe:20130717213030p:plain

ビルド終了時スクリプトに追加

RESULT=`curl http://localhost:9090/jenkins/job/[HogeProject]/lastBuild/api/xml | perl -le '$_=<>;print [/<result>(.+?)</]->[0]'`

echo $RESULT

if [ $RESULT = SUCCESS ]
then
    echo "success"
    curl http://localhost:3000/status?status=s
else
    echo "failure"
    curl http://localhost:3000/status?status=e
fi

実際にはこんな感じ

f:id:tomo_watanabe:20130717213048p:plain

運用中のビルド中表示はこんな感じで青く点灯します。 上の2つのデバイスは、別の試作で載せた温度センサーなので今回は関係ありません。

f:id:tomo_watanabe:20130717213433j:plain

まとめ

何はともあれJenkinsとArduinoを繋げることができました。これでLEDだけではなく、音を慣らしたりもできますね。赤外線リモコンを使って、エラーになるとエアコンを止めてしまって、何が何でも直さないと帰れない・・・とか(むしろ帰りたい気が)なんかテストとかビルドとかが楽しくなる仕掛けを考えると面白いかもしれません。

今回のサンプルコードですが、Githubに置いておきます。環境にも依存するので参考程度に...

※ちなみに、これは某アプリのビルドが40分も掛かるのでステータスが知りたいと思い、休日に構想1時間 + 仮実装1時間、本実装1時間ってとこで作りました\(^o^)/