#はじめに
昨今の新型コロナウイルスの影響で家にいなければいけなくなったので,学生寮の友達と一緒にカブとナスを栽培することにしました.カブはそこまでではないですが,ナスは水分を多く必要とするようなので,こまめな水やりと土壌の水分量管理が重要です.せっかくなので,土壌湿度をスマホでモニタリングできるようにしました.
今回,マイコンにはESP32,スマホアプリでの表示にはBlynkというIoTプラットフォームを使います.
多少冗長かもしれませんが,今後またESP32を触るときのために,画像多めでプロトタイピングの過程を細かく残していきます.
https://github.com/ketaro-m/Soil-Moisture <- コード
#全体像
プランターの土壌水分量をセンサで拾いESP32に入力.その値を近くのWifiネットワークを利用してBlynkサーバに送信.その値をスマホで受け取りアプリ上でグラフとして表示.
【イメージ図】
画像引用元(https://www.mgo-tec.com/blog-entry-esp32-blynk-01.html)
#ESP32
EPS32とは,Arduinoなどと同じようなIoT用のマイコンです.Arduino IDEでほぼ同じ要領で開発ができる上に,一般的にArduinoよりも安価です.さらに嬉しい点は,Arduinoとは違って元々WifiやBluetooth機能が付属している点です.色々な種類があるのですが,今回は特にAdafruit社の"Huzzah32-ESP32-Feather"というものを使っています.データシートやピンの配置なども上のサイトに全てまとまっていので,これを見ながら作っていきます.
ESP32(Huzzah32)をArduino IDEから使うためには若干の準備が必要なのですが,以下のサイトが参考になると思います.
追記
上の方法は,ESP32のリポジトリをダウンロードしてローカル上でパスを通す方法です.
しかし2018年7月からはESP32のGitHubページにあるように,ArduinoのBoard Managerに直接ESP32のパッケージのURLを指定してボードを使えるようになりました.
実際こちらの方がアップデートにも対応しやすいので推奨されています.
#Blynk
スマホ側のプラットフォームとしてはBlynkというものを使います.これは既にスマホアプリ(iOS,Androidともに対応)が準備されており,ESP32やArduino,Raspberry piなどにも対応しているという優れ物です.もう少し複雑なことをやろうとしたら簡単なウェブサイトを作るなども考えられますが,今回行いたいのはデータの可視化程度なので,既に構築されたこちらのプラットフォームを使おうと思います.
登録の仕方などは以下のサイトが非常に参考になります.
グラフとして"Gauge"と"SuperChart"を追加すると,以下のようにそれっぽくなります.
アカウント登録時には,登録したメールアドレスに以下のようなメールが送られてきます.黒く塗りつぶしてあるアクセストークンはこのプロジェクトを指定する番号で,ESPのスケッチの中で使います.
ESP32のスケッチで使うBlynkのライブラリも,このメールから入手することができます.(GitHubからダウンロードしてもいいです.)
Arduino IDEにライブラリをimportするには普通は以下の公式サイトに則って行えばできるはずが,このライブラリは
Specified folder/zip does not contain a valid library
というエラーが出てimportできなかったので,zipファイルを解凍し,その中にある"Blynk"というフォルダを"User/Document/Arduino/libraries/"に直接コピーすることでimportできるようになりました(ファイルを移動した後はIDEを再起動しましょう).
##プロジェクトの共有
せっかく作ったので,自分1人だけではなくメンバー全体からアクセスしたいものです.ただ,Blynkは同時に2つのデバイスから同じアカウントにログインすることはできないようです.その代わりにプロジェクトの共有というものが存在します.アプリの画面から"Project Settings -> Shered access -> Generate Link"としてQRコードを作ることで,他のデバイスにもこのプロジェクトを共有することができます.プロジェクト実行は親スマホから操作できるのですが,それが実行されている限り(右上が▷マークではなく□マークになっている状態),親スマホの電源を切ってもアプリを閉じても他のデバイスからはいつでもアクセスしてグラフを見ることができます.この点も優れていますよね.
#実装
色々と環境を整えたところで,実際に作っていきます.
若干冗長かもしれませんが,やった手順通り丁寧に書いていこうと思います.
##Lチカ
まずはお作法通りLチカから試して,ESP32が正常に動作するかを見ていきます.以下のコードをESP32に書き込みます.
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(2000); // wait for two seconds
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
とりあえず手持ちのESP32がちゃんと作動することは確認できました.Lチカ pic.twitter.com/He8YiUGY39
— Keitaro Murakami (@keitaro_mrkm) May 30, 2020
##アナログ入力
次に,アナログ入力がちゃんと計測できるかを確認します.
###まずは簡単なところから
家にテスターがなくてセンサの出力値が直接測れないので,いきなりセンサ出力をESP32に読み取らせる前に,まずはアナログ入力がちゃんと読み取れるかだけ確認します.可変抵抗によって変化する電位をアナログ入力ピンに入力し,正しく読み取れるかを確認しましょう.
#define readPin 13
float value = 0;
void setup() {
Serial.begin(115200);
delay(1000);
}
void loop() {
value = analogRead(readPin);
Serial.println(value);
delay(500);
}
参考サイト: ESP32 ADC – Read Analog Values with Arduino IDE
アナログ入力ピン pic.twitter.com/dmJN0XuFmC
— Keitaro Murakami (@keitaro_mrkm) May 30, 2020
この出力値から分かるように,0~3.3V は 0~4095 という値に変換されていることが分かります.
###土壌湿度センサの組み込み
ESP32がちゃんとアナログ入力を計測できることが確認できたので,この部分をセンサに代えましょう.
土壌湿度センサはこちらを使います.1つあたり¥100程度です.
Soil Moisture sensa-mozyu-ruo-tomatikku Water Supply System LM393
VCCを3.3V,GNDを0V,AOをESP32につなぎます(DO(デジタル出力)は放置しておきます).
この湿度センサは静電容量変化型湿度センサと呼ばれるタイプのもので,湿度によるセンサの静電容量の変化に伴って出力電位が変化するという動作原理となっています.もちろんこの間の関係は線形ではないので,土壌水分量とセンサ出力値の関係を求めなくてはいけません.この資料によると,含水率は以下のように定義され,農業にはこの値がよく用いられるそうなので,この尺度を使ってセンサ値をcalibrationします.
$$含水率[%] = 水分量[g] \div 湿度重[g] \times 100$$
どうしてもデータシートを発見できなかったので,仕方なく自分で実験することにしました.下の動画のように,乾いた土に徐々に水を加えながらセンサ値を計測していきます.
結果としてこのような関係が得られ以下の関数でフィッティングができたので,最終的な出力にはこの逆関数をかけることにします.低い水分量のデータ点を取り損ねていますし,正直この値,そしてこの関係式はかなり精度が怪しいのですが,趣味のクオリティだということで目を瞑ってください$\dots$センサーのcalibration pic.twitter.com/dh7rp0idQS
— Keitaro Murakami (@keitaro_mrkm) May 30, 2020
##Blynkへのデータの送信
センサ値の取得ができることが確認できたので,Blynkサーバへデータを送信するパートに移ります.
大体wifiなどの通信周りのことは,デバイスのちょっとした違いなどで予期しないエラーが出まくったりして本当に嫌になるものですが,Blynkの登録メールにある Sketch generator(https://examples.blynk.cc/) を見ると,下の画像のようにサンプルコードをたくさん見ることができます.素晴らしい$\dots$
左側のタブからBoardやExampleを選択できるので,自分の使っているデバイスに変更します.
まあ多少のエラーは出たりするので,他のコードなども参考にしてエラーを改善しつつ,
#define BLYNK_PRINT Serial
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
char auth[] = "YourAuthToken";
char ssid[] = "YourNetworkName";
char pass[] = "YourPassword";
#define PIN_UPTIME V5
BLYNK_READ(PIN_UPTIME)
{
// This command writes Arduino's uptime in seconds to Virtual Pin (5)
Blynk.virtualWrite(PIN_UPTIME, millis() / 1000);
}
void setup()
{
Serial.begin(115200);
Blynk.begin(auth, ssid, pass);
}
void loop()
{
Blynk.run();
}
というスケッチを書き込むと,1秒毎にその秒数がデータとして送られ,その結果をアプリから見られることが確認できました.
ちなみに"V5"というのはバーチャルピン番号で,Blynkはサーバ上に立ててあるバーチャルピン番号を指定してデータの送受信を行うといった仕様になっているようです.
Blynkへのデータ送信 pic.twitter.com/5YWfMx3Hr1
— Keitaro Murakami (@keitaro_mrkm) May 30, 2020
##deep sleep
これで基本的な機能は完成ですが,少しだけ工夫を入れておきましょう.
カブやナスの栽培期間は数ヶ月にも及ぶので,ずっとをESP32を稼働しているとバッテリーが持ちません.センサー値の読み取りと送信は30分に一回ぐらいで十分なので,このタイミングのみESP32が稼働するようにして残りの時間はバッテリーを節約して欲しいわけです.
そこでESP32に備わっている**"deep sleep"**というモードを使います.
deep sleepモードというのは,電源こそ入ってはいるものの,主な機能は止めているので消費電流を大幅に抑えられるというモードのことです.deep sleepモードのうちは消費電流が10μA,通常の場合はおよそ200mAなので,消費電力に大きな差が出てきます.
参考サイト: ESP32 Deep Sleep with Arduino IDE and Wake Up Sources
簡単に,ちゃんと作動するかテストしてみましょう.入力値をシリアルモニタに表示したのち5秒間のdeep sleepモードに入るということを繰り返します.
#include <BlynkSimpleEsp32.h>
#define readPin 34
#define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 5 /* Time ESP32 will go to sleep (in seconds) */
float value = 0;
void setup() {
Serial.begin(115200);
Serial.println("Wake up!");
value = analogRead(readPin);
Serial.print("Analog input is ");
Serial.println(value);
Serial.println();
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
}
void loop() {
}
deep sleepモードから抜け出すと,その後から処理が再開するのではなくまた初めからプログラムが読み込まれます.ですから,loop()の中は空ですが表面上は繰り返しの処理に見えます.
シリアルモニタの様子を観察すると,5秒間のdeep sleepの後にプログラムが走り直し,センサー値を読み取っている様子を確認できました.deep sleep モード pic.twitter.com/00BIlYBLIM
— Keitaro Murakami (@keitaro_mrkm) May 30, 2020
###deep sleepしてない?
さあ色々と確認ができたので,最後に全てを統合しますが,ここで問題が$\dots$
30分に一回30秒間のデータ計測をして残りの時間はdeep sleepに入るという設定にしたものの,半日で充電が切れてしまったのです.よく見てみると,deep sleepモードに入った後もセンサのライトがついてしまっています.つまり,センサにはずっと電流が流れ続けてしまっていて,そこで電力を消費してしまっていたということです.
今まではESP32の3.3Vピンから直接的にセンサのVCCに引っ張ってきましたが,これではdeep sleepモードでも相変わらず3.3Vが供給され続けて,そこで電力を消費してしまいます.
そこで,トランジスタによるスイッチング回路を挟み込んで,deep sleepモードから起き上がった際のみに3.3Vに接続されるように変更します.
この図には書かれていないのですが,トランジスタが電流を増幅した際にセンサに過剰な電流が流れないようにベースの手前に抵抗を差し込む必要があります.その抵抗値の選定ですが,
- トランジスタの電流増幅率 $h_f = 140$
- トランジスタのベース・エミッタ間の立ち上がり電圧 $V_{BE} = 0.7V$
- センサの定格電流 $I_C = 35mA$
ということで,上のサイトにある通りにベース前抵抗 $R_B$を計算すると
$$R_B = \frac{V_B-V_{BE}}{\frac{I_C}{h_f}} = \frac{3.3V-0.7V}{\frac{35mA}{140}} = 10k\Omega$$
となるので,ベースの手前には$10k\Omega$の抵抗を挟んで,12番ピンをON,OFFにすることでセンサのVCCを3.3V/0Vと切り替えられるようにします.
試しに以下のスケッチを書いてちゃんと動作するか確かめてみます.
#define readPin 34
#define switchPin 12
float value = 0;
void setup() {
Serial.begin(115200);
pinMode(switchPin, OUTPUT);
delay(1000);
}
void loop() {
digitalWrite(switchPin, HIGH);
delay(100);
value = analogRead(readPin);
Serial.print("Switch on; ");Serial.println(value);
digitalWrite(switchPin, LOW);
delay(1000);
digitalWrite(switchPin, LOW);
delay(100);
value = analogRead(readPin);
Serial.print("Switch off; ");Serial.println(value);
delay(1000);
}
センサ上ののLEDが光ったり消えたりしていて,ちゃんとVCC入力が制御できていることが分かります.(この動画を撮影したときは$10k\Omega$の抵抗が手に入らなかったので,複数の抵抗をつないで$2k\Omega$ぐらいになっています.)
トランジスタのスイッチ pic.twitter.com/ur2OjV02oI
— Keitaro Murakami (@keitaro_mrkm) May 30, 2020
この機能は電力消費の観点からも大切でしたが,センサに電流が流れ続けていると表面がイオン化してきてしまうらしいので,電流が流れないようにしたのはその意味でも良かったかもしれません.
##いよいよ統合
さあこれで全てのバグが直ったので,全てを統合します.
30分に一回deep sleepから起き上がり,30秒間の計測を行います.deep sleepから起き上がった直後にトランジスタスイッチをオンにしてセンサをアクティブにし,deep sleepに入る直前にトランジスタスイッチをオフにします.このセンサは電圧がかかった後すぐは値が安定しなかったので,計測の30秒のうちの初めの10秒間は計測を行わずにセンサを慣らし,残りの20秒間で1秒おきに計測した値の平均値をとってその値を返すようにします.
#define BLYNK_PRINT Serial
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#define readPin1 34 //readPin for sensor1
#define readPin2 39 //readPin for sensor2
#define switchPin 12
#define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP (8) /* Time ESP32 will go to sleep (in seconds) */
// Use Virtual pin 5,6 for uptime display
#define PIN_UPTIME1 V5 //virtual pin for sensor1
#define PIN_UPTIME2 V6 //virtual pin for sensor2
char auth[] = "YourAuthToken";
char ssid[] = "YourNetworkName";
char pass[] = "YourPassword";
int v1 = 0; //value for sensor1
int v2 = 0; //value for sensor2
void setup() {
Serial.begin(115200);
pinMode(switchPin, OUTPUT);
digitalWrite(switchPin, HIGH);
delay(9000);
sensorRead();
//Blynk.begin(auth, ssid, pass); これはwifiが接続できなかったときに永遠にstopしてしまう
WiFi.begin(ssid, pass);
Blynk.config(auth);
boolean con = Blynk.connect();
if (con == 1) {
Serial.println("connected");
Serial.print("sensor1, 2 = ");Serial.print(v1);Serial.print(", ");Serial.println(v2);
Blynk.virtualWrite(PIN_UPTIME1, v1);
Blynk.virtualWrite(PIN_UPTIME2, v2);
} else {
Serial.println("connection failed...");
}
digitalWrite(switchPin, LOW);
delay(1000);
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
delay(1000);
}
void sensorRead() {
int dur = 20;
v1 = 0;v2 = 0;
Serial.print("sensor1 ");Serial.println("sensor2");
for (int i = 0; i < dur; i += 1) {
Serial.print(analogRead(readPin1));Serial.print(" ");Serial.println(analogRead(readPin2));
v1 += analogRead(readPin1);
v2 += analogRead(readPin2);
delay(1000);
}
v1 /= dur;v2 /= dur;
v1 = convert(v1);v2 = convert(v2);
}
float convert(int x) {
return log(x/3115.2)/(-0.026);
}
void loop() {
}
###デバッグ1
Blynkでは"Blynk.connect();"などとしてwifi接続を始めてしまうと"analogread()"というメソッドが機能しなくなってしまうバグがあるらしいです.よって上のコードにあるように,wifi接続をする前にセンサ値を読み取って保存しておく必要があります.(些細なことですがこのエラーに何時間悩まされたことか$\dots$)
###デバッグ2
Blynkサーバに接続する際に"Blynk.begin(auth, ssid, pass);"で接続しようとすると,wifi接続が悪かった時などに永遠にそこでフリーズしてしまいます.そして結果としてdeep sleepに入ることがなく充電を消費してしまうというエラーが発生しました.代わりに"Blynk.connect();"にはデフォルトでtimeoutが備わっているので,それを活用することでwifi接続できなくても眠らせてあげましょう.(https://docs.blynk.cc/#blynk-firmware-configuration)
###デモ
下の動画は,deep sleepと計測をそれぞれ8秒,5秒ずつにしたときのデモ動画です.
8秒ごとにセンサがアクティブになり,それがちゃんとスマホアプリに表示されている様子が見れると思います.ちなみに,ESP32は部屋のwifi回線,スマホは4G回線と,別々のネットワークでちゃんとデータが表示できることも確認できました.
完成版 pic.twitter.com/HPIxG8ePuJ
— Keitaro Murakami (@keitaro_mrkm) May 30, 2020
#設置
遂に完成したので,いざ設置しましょう!電池はリリウムイオン電池を使います.deep sleepを組み込んでいるので,理論的には3ヶ月持つはずです.
無事にナスとカブが出来上がったらその写真も追記しようと思います.お楽しみに!