サーバーをクラウドに移設し、連続して電流値が測定できるようになり、結果もブラウザーでリアルタイムにグラフ化して確認できるようになりました。しかし2日ぐらいは連続動作するものの、その後、Arduinoからサーバーにデーターが送られなくなってしまいます。電力の利用状況は一年ぐらいは継続して調べたいので、電力計は年単位で連続動作できるようにしておきたいものです。ということで今回はWi-Fi通信の安定化の取り組みについて書きます。安定化させるまでに1ヶ月ぐらいかかった記録なので、話が少し長くなりました。
2日程度は連続して動くので、単純なプログラムのバグではなさそうです。Wi-Fiの通信、あるいはWi-Fiを経由したWebサーバーとの通信が不安定で、Arduinoのプログラムが送信できなくなってしまうのだろうと考えました。
データー送信が止まってしまった様子です。0時30分頃を最後にデーター送信が止まってしまい、18時過ぎにArduinoのリセットボタンを押して送信を再開させています。
データー送信が止まったとき何が起こっているか調べる
Wi-Fi通信に使っているのはTI社のCC3000というWi-Fi通信用チップを搭載したSparkFun社のボード(Arduino用シールド)です。
SparkFunはこのボードを使うためのArduino用ライブラリーも提供しています。ソースコードは以下にすべて公開されています。
https://github.com/sparkfun/SFE_CC3000_Library
class SFE_CC3000の関数 init() // 初期化 startSmartConfig() // スマホのSmartConfigアプリを使いSSIDと // パスワードを指定してWi-Fiに接続 fastConnect() // SmartConfigで指定したデーターを使ってWi-Fiに接続 class SFE_CC3000_Clientの関数 connect() // サーバーにTCP接続する write() // サーバーにデーターを送信する stop() // サーバーとの接続を切断する
Arduinoの電力計プログラムは次のような構造です。
setup() { // Arduinoの初期化関数 wifi.init(); // CC3000を初期化する if (!wifi.fastConnect()) { // fastConnectでWi-Fiにつなぐ wifi.startSmartConfig(); // だめならSmartConfigでつなぐ } 30秒間隔でタイマー割り込みを設定; 30秒タイマーの割込み処理() { 30秒待ちフラグを解除; } loop() { // Arduinoプログラムの処理ループ 30秒待ちフラグが解除されるのを待つ; クランプ1の電流値を測定; クランプ2の電流値を測定; if (client.connect() == 成功) { // WebサーバーにTCP接続し、成功なら client.write(電流値データー); // 電流値データーを送信する client.stop(); // Webサーバーとの接続を切断する } }
まず、プログラムがどう動いているのかを調べました。そのためにArduinoからパソコンにデバッグメッセージを出力したかったのですが、電力モニターは玄関にある分電盤近くにあり、パソコンを置く場所がありません。そこで、Arduinoに緑と赤のLEDをつけ、それを使ってプログラムの状態を表示するようにしました。次のような感じです。
loop() { // Arduinoプログラムの処理ループ 30秒待ちフラグが解除されるのを待つ; クランプ1の電流値を測定; クランプ2の電流値を測定; 緑のLEDを点ける; if (client.connect() == 成功) { // Webサーバーに接続 緑のLEDを消す; client.write(電流値データー); // 電流値データーをHTTP POSTする client.stop(); // Webサーバーとの接続を切断する } else { 緑のLEDを消し、赤のLEDを点ける; } }
LEDを点灯、消灯する場所を変えながら調べた結果、何らかの理由でWebサーバーに接続できなくなり、その後は30秒ごとにWebサーバーへの接続(connect関数)を試みて失敗することを繰り返していることが分かりました。動作を調べる過程でライブラリーのソースコードを読んだり、Wi-Fiルーターの通信ログを調べたりしました。特に以下の三つの資料は理解の助けになりました。
「Internet of Things Wi-Fi Module API Documentation」
エラー時の状態を調べ、リカバリーするプログラムを試みる
資料を調べると、CC3000はWi-Fi接続が切れると自動的に再接続を試みるようです。それでもconnect()に失敗するので、CC3000の再接続がうまくいっていないという仮説を立てました。connect()に失敗した時の状態を調べ、その状態に応じてリカバリーするようにプログラムを改修することを考えました。CC3000を管理するオブジェクトには「初期化済み」「Wi-Fiのアクセスポイントに接続済み」「DHCP完了」の三つの状態があります。DHCPは自分のIPアドレスをDHCPサーバーにアサインしてもらう機能です。緑と赤のLEDを使い、connect()に失敗した時の状態を調べると、「初期化済み」「Wi-Fiアクセスポイント接続済み」でDHCPは完了している場合とそうでない場合がありました。Wi-Fiアクセスポイントへの接続とDHCPはfastConnect()というライブラリー関数の中でおこなわれます。そこで、connect()に失敗したときに一旦Wi-Fiの接続を切り、fastConnect()するというプログラムを試しましたが、fastConnect()に失敗し、リカバリーできませんでした。connect()関数の中でサーバーの文字列名からIPアドレスを求める処理と、IPアドレスを指定してサーバーに接続する処理の二つを行っているのですが、ライブラリー関数の中なので、そのどちらが失敗しているのかも分かりません。
調べている過程で、Adafruitという会社も同じCC3000という通信チップを搭載したArduino用のボードとそのボード用のライブラリーを開発、販売していることが分かりました。ライブラリーインタフェースを調べると、SparkFun社のライブラリーよりも単機能の関数が提供されています。Adafruit社のライブラリーソースコードも以下のサイトにすべて公開されています。
https://github.com/adafruit/Adafruit_CC3000_Library
SparkFun社(以下S社)とAdafruit社(以下A社)の主なライブラリー関数を比較したものが次です。
SparkFun社のライブラリー init() // 初期化 startSmartConfig() // SmartConfig機能 fastConnect() // 前回のデーターを使いWi-Fiに接続 connect() // サーバーにTCP接続する connected() // 接続を確認する write() // サーバーにデーターを送信する stop() // サーバーとの接続を切る Adafruit社のライブラリー startSmartConfig() // SmartConfig機能 begin() // 前回のデーターを使いWi-Fiに接続 checkDHCP() // DHCP完了を待つ getHostByName() // サーバー名からIPアドレスを得る connectTCP() // サーバーにTCP接続する connected() // 接続を確認する write() // サーバーにデーターを送信する close() // サーバーとの接続を切る
特に、今回エラーになるS社のconnect()という関数がA社のライブラリーではgetHostByName()とconnectTCP()という二つの関数に分かれており、問題の切り分けができそうです。S社製ボードにA社製ライブラリーという組み合わせは動くかどうか分かりませんし、両社とも動作保証はしてくれないと思いましたが、Wi-Fi制御チップは同じですし、S社製ボード+A社製ライブラリーで動かしたというブログ記事もあったので、試してみることにしました。
Arduinoボードとやりとりするピン番号がS社のボードとA社のボードでは異なります。例えば割込み要求はS社が2番ピンなのに対してA社のボードは3番ピンを使っています。そこでプログラムの中でピン番号を定義している部分をS社ボード用に書き直し、あとはそのままコンパイルしたところ、S社ボードとA社ライブラリーの組み合わせで動かすことができました!
この状態で連続動作させたところ、S社ライブラリーの時と同じように2、3日でデーター送信が途絶えることが分かりました。さらにこの時、getHostByName()という関数で失敗していることも分かりました。ここでライブラリーのソースコードを読んで内部処理を理解し、リカバリープログラムを書くことも考えたのですが、参考になるサンプルプログラムもなかったので、別のアプローチ、ウォッチドッグタイマーを試みることにしました。
ウォッチドッグタイマーは、設定した時間までにタイマーをクリアーしないとシステムをリセットするという時限爆弾のような機能です。Arduinoでプログラムからシステムをリセットする方法は以下のページに解説があります。
http://playground.arduino.cc/Main/ArduinoReset
Arduinoではウォッチドッグタイマーのライブラリーが提供されています。以下のページに解説があります。
http://nongnu.org/avr-libc/user-manual/group__avr__watchdog.html
ライブラリーには次の三つの関数が用意されています。
wdt_enable(value); // ウォッチドッグタイマーを有効にする wdt_reset(); // ウォッチドッグタイマーをクリアーする wdt_disable(); // ウォッチドッグタイマーを無効にする
リセットがされるまでの時間は wdt_enable() のパラメーターで指定し、15m秒から8秒までの時間が設定できます。このライブラリーは「ArduinoのCPUは確実にリセットできますが、シールドと呼ばれる拡張ボードまでリセットできるかは分からない」とArduino公式サイトに書かれていましたが、とりあえず使ってみることにしました。例えばgetHostByName()を呼ぶ前後に下記のようにウォッチドッグタイマーをセットしました。
wdt_enable(WDTO_8S); // 8秒のウォッチドッグタイマーをセット for (ip = 0; cc3000.getHostByName(domain, &ip), ip == 0; ) { // getHostByName() して、ipアドレスが0なら delay (500); // 500m秒待って、再挑戦 } wdt_reset(); // 8秒以内に処理が終わればタイマーをクリアー wdt_disable(); // タイマーを無効化
Wi-Fi通信ライブラリーを呼び出しているところの前後にこのようにウォッチドッグタイマーをセットし、仮にライブラリーの中で止まってしまってもシステムがリセットされ、プログラムが最初から再実行されるようにしました。
ウォッチドッグタイマーを入れてからはデーター送信が止まることなく、連続して動作するようになりました。サーバーにデーターが到着する間隔を調べたのが次のグラフです。
何も問題がなければ30秒ごとにデーターが到着します。グラフではデーターの到着間隔を緑の点で示しており、それが帯状に30秒(右軸)のラインに並んでいます。0時30分頃に到着間隔が3分30秒ぐらいに伸びています。ここでgetHostByName()に失敗し、ウォッチドッグタイマーがクリアーできず、システムがリセットされ、プログラムが最初から再実行され、データー送信が再開しています。
電力モニターはウォッチドッグタイマーを入れることで連続して動作させることができました。ただし、通信の障害はどのタイミングでも起こり得ます。例えばデーター送信の際、データーが途中まで送信された後に障害が発生する可能性もあります。この時、電力モニターの端末であるArduinoはウォッチドッグタイマーにより再起動されますが、サーバー側には中途半端なデーターが残る可能性があります。今回は対処していませんが、サーバー側プログラムでも不完全なデーターは無視するなり削除するなりといった処理が必要になります。
これで連続して電気の利用状況が分かるようになりました。次はグラフ表示を強化します。