ハンダ付け不要!初心者でもできる、30分おきにセンサーの値をWeb上に保存する仕組みを安定して動かす(プログラム編)

Posted: / Tags: ESP8266



※こちらはハンダ付け不要!初心者でもできる、30分おきにセンサーの値をWeb上に保存する仕組みを安定して動かす(回路編)の後編にあたる記事です。

ハンダ付けが不要で初心者でも手が出しやすいESP-WROOM-02 Arduino互換ボードを使って、30分ごとに圧力センサーの値をWeb上に保存する仕組みを安定的に動作させる方法の後編(プログラム編)です。

前回は、マイコンボードの入出力の基礎と圧力センサーの値を取得する回路設計について説明しました。一応、以下に配線図を再掲しておきます。

blg-20160307-save-sensor-values-by-an-hour-2-circuit.png

今回は、読み取ったセンサーの値をMilkcocoaに保存するプログラムを書いていきます。この仕組みの重要ポイントである「安定動作」のコツを中心に説明していきます。

安定して動作させるためにはどうするか?

今回重要なのは、安定して動作させることです。

安定して動作させるためにはどうすれば良いでしょうか? 「絶対に落ちないモデム・ルータを用意して、絶対に電波が干渉しない環境を用意して...」なんてことは不可能ですよね。

安定して動作させるためには、「どこでも問題が起こりうる」という前提でプログラムを組みます。

具体的には「起動しっぱなしにして30分ごとにセンサーの値を保存」とするのではなく、「30分ごとに起動して、保存が出来たことをちゃんと確認してから、スリープ」するようにします。

以下のような流れになります。

  1. 起動
  2. センサーの値をWeb上に保存
  3. 保存が成功したら、30分スリープする → 30分経過したらリセット → 1. に戻る
  4. 失敗したらすぐにリセット → 1. に戻る

blg-20160301-save-sensor-values-by-an-hour-diagram.png

具体的には以下のようなプログラムを組みます。

  1. 起動
  2. 自分自身の保存命令を監視
  3. センサーの値を読む
  4. 読んだセンサーの値をMilkcocoaに保存(保存命令の実行)
  5. 監視していた保存命令が受け取れたら、30分スリープする → 30分経過したら 1. に戻る
  6. 保存命令が受け取れずにloopが一定回数以上呼ばれたら、失敗したとみなして再起動 → 1. に戻る

失敗したら再起動をして、成功したと判断するまでスリープ状態に移行しません。こうすることで、通信で障害が起こってもまたプログラムを最初からやり直してくれるので、「安定して動作する」わけです。

※なお、MilkcocoaがMQTT(Pub/Sub方式)を使っているためこういう流れになっています。HTTPであれば、レスポンスの200 OKの有無等で成功/失敗の判定をします。

スリープ状態から目覚められるように配線

安定稼働させるコツがわかったところでプログラムを書きましょう、と言いたいところですがひとつやっておくことがあります。

それは、スリープ状態から目覚められるように、RESETピンとIO16ピンをつなげることです。

RESETピンとは、ピンがLOWになったら今の状態がどうであれ初期状態から動作するピンです(これを「リセット割込み」と言って、マイコンの電源が入ったときも実はリセット割込みが発生していて、その割込みによって動作が開始しています)。

ESP-WROOM-02にはリセットボタンがついていて、それを押すとRESETピンがLOWになります。ゲーム等のリセットボタン、と考えればわかりやすいかと思います。

blg-20160307-save-sensor-values-by-an-hour-2-resetbtn.png

IO16ピンは前回触れた、デジタル入出力ピンですが、もうひとつ機能がついています。「期間付きスリープを行った際にスリープしてから指定した時間が経過したとき、HIGHからLOWになる」という機能です。

さて、この2つのピンを繋げるとどうなるでしょうか。

起動中とスリープ中は、IO16ピンはデフォルトでHIGHになっているので、RESETピンとつながっていても何も起こりません。

しかし、スリープしてから指定した時間が経過したとき、IO16ピンがLOWになります。そうすると、RESETピンがLOWになりリセットがかかります。リセットがかかったらIO16ピンはすぐにHIGHになって初期状態から動作を始めます。

つまり、スリープから起きるためにIO16ピンとRESETピンを繋げる必要があるということですね。繋げないと永久に眠ったままです。

※ちなみに、IO16ピンとRESETピンは隣同士なので、ピンヘッダをお持ちであればピンヘッダで繋げた方がスマートです。

blg-20160307-save-sensor-values-by-an-hour-2-pinheader.jpg

blg-20160307-save-sensor-values-by-an-hour-2-pinheader-set.jpg

Arduino IDEでの開発準備

Arduino IDEで開発をする準備をしましょう。

前回も触れましたが、Arduino IDEでスケッチを書き込める状態にするまでは、スイッチサイエンスさんの記事(ESP-WROOM-02開発ボードをArduino IDEで開発する方法)を参考にして下さい。

Arduino IDEを1.6.5を使っていて1.6.7に変更した場合は、Board generic (platform esp8266, package esp8266) is unknownというエラーがでるかと思います。

この場合は、/ホームディレクトリ(ユーザー名)/ライブラリ/Preferences/Arduino15/packeges/esp8266/hardware/にあるesp8266フォルダを削除して、Arduino IDEを再起動した後、再度ボードマネージャーよりインストールしてください。

参考:Board generic (platform esp8266, package esp8266) is unknown #1514

※なお、「ライブラリ」フォルダは隠しフォルダなので、Finderで辿る場合はFinderの設定で隠しフォルダの表示をONにして下さい。

Milkcocoaを使った開発準備

スケッチを書き込める状態になったら、Milkcocoaを使えるようにしましょう。まずは利用登録です。

Milkcocoaに登録する(無料)

MilkcocoaのWebサイト無料登録ボタンから利用登録をします。メールアドレスとパスワードを送信したあと、送られてくるメールで認証をしたら登録完了です。

blg-20160215-contact-form-sitetop.png

Milkcocoaの管理画面にログインする

登録後、MilkcocoaのWebサイトログインボタンからログインをします。

blg-20160215-contact-form-login.png

Milkcocoaアプリを作成する

ログインをしたら以下のようなアプリリスト画面が出るので、新しいアプリを作る(アプリ名は16文字まで)を押してアプリ名を入力後、アプリを作成します。

blg-20160215-contact-form-admin.png

アプリを作成したら、作成したアプリをクリックして「アプリの管理画面」へ移動します。

blg-20160215-contact-form-app.png

「○○の概要 app_id: hogehoge」の、hogehogeの部分をプログラム内で使うのでいつでもコピペできる状態にしておきましょう。

blg-20160307-save-sensor-values-by-an-hour-2-appid.png

Milkcocoa ESP8266 SDKの準備

Arduino IDE用のMilkcocoaライブラリ、Milkcocoa ESP8266 SDKをインストールしましょう(Milkcocoa Arduino SDKではありません)。

まず、SDKを作業用パソコンにダウンロードします。以下のリンクを開き、右のほうにある「Download ZIP」ボタンをクリックして保存します。

blg-20160307-save-sensor-values-by-an-hour-2-github.png

ダウンロードが終わったら、Arduino IDEの「スケッチ」 > 「ライブラリをインクルード」 > 「.ZIP形式のライブラリをインストール...」をクリックします。

blg-20160307-save-sensor-values-by-an-hour-2-zipinstall.png

ファイル選択画面が開かれるので、先ほどダウンロードしたZIPファイルを選択して読み込みます。

blg-20160307-save-sensor-values-by-an-hour-2-selectzip.png

これでインストール完了です。「スケッチ」 > 「ライブラリをインクルード」の中に「Milkcocoa ESP8266 SDK」が追加されていれば、インストール成功です。

※2016/03/06以前にSDKをインストールされた方も、お手数ですがホームディレクトリ(ユーザー名)/Documents/Arduino/libraries/にあるMilkcocoa_ESP8266_SDK-masterをフォルダごと削除して、IDEを再起動後、上記の手順でリポジトリから再度ダウンロード&インストール頂ければと思います。

※※ Milkcocoa Arduino SDKとMilkcocoa ESP8266 SDKの両方がインストールされていると正常に動作しません。Milkcocoa Arduino SDKがインストールされている場合は必ず(ホームディレクトリ(ユーザー名)/Documents/Arduino/libraries/にあるMilkcocoa_Arduino_SDK-masterを)削除もしくは移動して下さい。

Milkcocoaの動作確認のプログラムを書く

準備ができたので、プログラムを書いていきましょう。まずはMilkcocoaの動作確認をしましょう。

「スケッチの例」 > 「Milkcoco ESP8266 SDK」 > 「milkcocoa_esp8266」を開きましょう。Github上にもコードがあるためそちらをコピペしても良いです。

blg-20160307-save-sensor-values-by-an-hour-2-testskecth.png

プログラム内の各定数を書き換えます。繋げたいWiFiアクセスポイントのSSID("...SSID...")とそのパスワード("...PASS...")、さきほど作成したMilkcocoaアプリのapp_id("...YOUR_MILKCOCOA_APP_ID...")の3つです。

定数の書き換え
/************************* WiFi Access Point *********************************/

#define WLAN_SSID       "...SSID..." ←書き換える
#define WLAN_PASS       "...PASS..." ←書き換える

/************************* Your Milkcocoa Setup *********************************/

#define MILKCOCOA_APP_ID      "...YOUR_MILKCOCOA_APP_ID..." ←書き換える
#define MILKCOCOA_DATASTORE   "esp8266/tout/half-hour"

適当に名前を付けて保存をしたあと、「マイコンボードに書き込む(IDEの左上にある右矢印ボタン)」を押して書き込みます。

書き込みが終わったら「シリアルモニタ(IDEの右上にある虫眼鏡ボタン)」を立ち上げて、8秒ごとにonpush 1と表示されることを確認します。

※シリアルモニタの右下のセレクトボックスは、左から「CRおよびLF」「115200bps」に設定します。

ここで、見慣れない関数が出てきたと思うので簡単に説明をします。

まずはWiFiまわりの関数です。使うにはESP8266Wifi.hをインクルードする必要があります。

ESP8266Wifi.hの中にある関数
// SSIDとPASSを使ってWiFiに接続する
WiFi.begin(WLAN_SSID, WLAN_PASS);
ESP8266Wifi.hの中にある関数
// WiFiの状態を返す
WiFi.status();
/**
 * 状態の種類
 * WL_IDLE_STATUS      = 0
 * WL_NO_SSID_AVAIL    = 1
 * WL_SCAN_COMPLETED   = 2
 * WL_CONNECTED        = 3
 * WL_CONNECT_FAILED   = 4
 * WL_CONNECTION_LOST  = 5
 * WL_DISCONNECTED     = 6
**/

// 主に以下のように接続状態になるまでwhileを回すという使い方をします
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}

次はMilkcocoaまわりの関数です。使うにはMilkcocoa.hをインクルードする必要があります。

Milkcocoa.hの中にある関数
// milkcocoaへ接続/接続確認を行う。loop()の最初に書く。
milkcocoa.loop();
// 引数にタイムアウト(ms)を指定できる
if(!milkcocoa.loop(50000)){
  // 失敗時の処理
}
Milkcocoa.hの中にある関数
// {"v":1}というデータを"milkcocoa_datastore_name"へ保存
DataElement elem = DataElement();
elem.setValue("v", 1);
milkcocoa.push("milkcocoa_datastore_name", &elem);
Milkcocoa.hの中にある関数
// "milkcocoa_datastore_name"へのpushを監視。onpush()関数をコールバックとして登録して、pushが呼ばれたらonpushが実行される。
void onpush(DataElement *elem) {
  /* pushが呼ばれたら実行される */
  Serial.println(elem->getInt("v")); // Int型の"v"の値を取り出す
}
milkcocoa.on("milkcocoa_datastore_name", "push", onpush);

接続のタイムアウトの必要性

プログラムを書く前に、WiFiやMQTTの接続のタイムアウトを自分で設定する必要性について説明します。

今回の仕組みで、(通信に限って)障害が起きる可能性があるタイミングは以下の3つです。

  • ESP-WROOM-02がWiFiアクセスポイントに接続するタイミング
  • Milkcocoaへ接続するタイミング
  • Milkcocoaにデータを保存するタイミング

blg-20160307-save-sensor-values-by-an-hour-2-wifimqtt.png

「安定して動作させるためにはどうするか?」で述べたように、結局保存さえ出来れば良いので、最後の「Milkcocoaにデータを保存する」の成功判定さえ見ておけば良い気がします。

が、そうもいきません。WiFiやMilkcocoaへの接続が何らかの理由で失敗する場合、タイムアウトを設定していなければ無限に接続を試みてしまい、そこから先の処理に進まなくなってしまいます。

blg-20160307-save-sensor-values-by-an-hour-2-wifimqtt2.png

試みていればいつか接続が成功する場合は良いですが、初期状態から開始してあげないと成功しないような場合、リセットボタンを押しにいかない限り処理が止まったままになってしまいます。

そういうわけで、接続できずに○○秒経ったら(○○回試みたら)失敗したとみなして再起動するようにします。

プログラムを書く

いよいよプログラムを書きます。といってもプログラム自体は「スケッチの例」 > 「Milkcoco ESP8266 SDK」 > 「milkcocoa_esp8266_tout_by_half_hour」にあります。Github上にもコードがあるため、そちらをコピペしても良いです。

では、メインプログラムを見てみましょう。

メインの関数のsetup()(スケッチがスタートしたときに1度だけ呼び出される関数)とloop()setup()のあとに繰り返し実行される関数)だけ抜き出すと以下のようになります(見やすくするためにシリアル出力は削除しています)。

void setup() {
  delay(10);

  // WiFiアクセスポイントに接続する(自分で定義する)
  setupWiFi();

  // pushを監視
  milkcocoa.on(MILKCOCOA_DATASTORE, "push", onpush);
};

// ループ回数
int loopCounter = 0;

void loop() {
  // Milkcocoaへ接続(自分で定義する)
  connectToMilkcocoa();

  // センサーの値を取得
  int sensorValue = analogRead(A0);

  // "v"にセンサーの値をセット
  DataElement elem = DataElement();
  elem.setValue("v", sensorValue);

  // 1回目のループのときのみpush
  if(loopCounter == 0) milkcocoa.push(MILKCOCOA_DATASTORE, &elem);

  delay(100);

  // ループが50回呼ばれたら失敗したとみなす
  if(loopCounter > 50){
    delay(500);
    // 2秒後に再起動
    ESP.deepSleep(2 * 1000 * 1000);
    delay(1000);
  }
  loopCounter++;
};

ポイントはループ回数をカウントして、50回ループが呼ばれたら再起動するところです。

ESP.deepSleep()とは、スリープ用の関数で実行するとスリープモードに入ります(参考:ESP8266の真骨頂Deep-Sleepモードの使い方)。引数にスリープ時間(μ秒指定)を指定することが出来ます。指定した時間が経過するとIO16ピンがHIGHからLOWになります(0を指定すると永久スリープします)。

ESP.deepSleep(2 * 1000 * 1000)と、短い時間のスリープを再起動として使っています(あんまり短い時間だと問題が起きそうなのでゆとりを持たせる意味で2秒にしています)。

pushが成功した場合は、loop()が50回実行される前に以下のonpushが実行されて30分間のスリープが始まります。

onpush()
void onpush(DataElement *elem) {
  delay(500);
  // 1800秒(30分)スリープ
  ESP.deepSleep(1800000000);
  delay(1000);
};

WiFiの接続部分(setupWiFi())とMilkcocoaの接続部分(connectToMilkcocoa())では以下のようにタイムアウトを設定します。

WiFiの接続部分
void setupWiFi() {
  WiFi.begin(WLAN_SSID, WLAN_PASS);
  int wifiCounter = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(450);
    // 20回試行したら失敗したとみなして再起動
    if(wifiCounter > 20) {
      delay(500);
      ESP.deepSleep(2 * 1000 * 1000);
      delay(1000);
    }
    delay(50);
    wifiCounter++;
  }
};
Milkcocoaの接続部分
void connectToMilkcocoa() {
  // 50秒たっても接続できなかったら失敗したとみなして再起動
  if(!milkcocoa.loop(10*5000)){
    delay(500);
    ESP.deepSleep(2 * 1000 * 1000);
    delay(1000);
  }
};

実際に、プログラムを実行してみると以下のようなシリアル出力が表示されるかと思います。

blg-20160307-save-sensor-values-by-an-hour-2-result.png

なお、圧力は以下みたいな感じでかけました。普通に物をのせても圧が弱くて値が0になってしまうので、折った小さい紙とかを挟むと良いです。

スリープ状態になっているのでそのまま30分待てば起きて、また処理が始まります(待つのが面倒であれば30分指定の部分を15秒とかにしてみて下さい)。

おわりに

30分おきにセンサーの値をWeb上に保存する仕組みを安定して動かすためのプログラムについて説明しました。

データを保存するときにだけ起きて、成功したことをしっかり確認してから眠ることで安定動作させることが出来ました。またこのやり方だと、スリープ状態での消費電力はとても小さくエコだったり、眠っているときはWiFiに接続していないためよくある「WiFi混雑問題」も回避できたり、良いこと尽くしです。

今回は初心者向けではあるものの「安定動作」と少々テクニカルな部分を重要ポイントとして書きました。というのも、私自身、Raspberry PiでIoTを始めたものの、実際に動かしてみると途中で動かなくなってやる気がなくなって放置、という経験があったからです。

「作って終わり」ではなく、作ったものが生活に溶け込むことが出来てはじめて、「IoT」を体験できるのではないでしょうか。

気になった方は、是非試してみて下さい。