こんにちは!エンジニアの岩間です。
前回のブログでは、初の電子工作にチャレンジし、植物の自動水やり機を自作しました。
土に差し込んだ土壌センサーで計測した数値が一定の値を上回ると、自動で水やりを開始しLINEに通知する、という装置です。
しかし、ここで一つ気になることが…。センサーの値がどのように変化しているのか、リアルタイムで見てみたくなりませんか?
そこで思いついたのが、AWS IoT Coreとの連携です。
この連携により、センサーのデータをクラウドに送信して、どこにいてもデータの変化を確認することができます。
今回のブログでは、植物の自動水やり機からAWS IoT Coreにデータを送るまでの手順や、プログラムを詳しくご紹介します。
さらに、実際にデータをAWS IoT Coreに送信してみた結果も公開しますので、お楽しみに!
目次
構成図
土壌センサーとESP8266(NodeMCU)を接続した状態で、土壌センサーのデータを毎分、MQTTプロトコルでAWS IoT Coreに送信します。
そしてAWS IoT Coreで受け取ったデータは、CloudWatch Logsに送信し、グラフ化して可視化する、といった構成です。
物理回路部分は下の写真の通りです。組み立て方の詳細は前回のブログで紹介しています。
手順
モノの作成
まずはデバイスとクラウドを接続するために、AWSコンソールにログインし、IoT Coreでモノを作成します。
1. AWSコンソールにログインした状態で、検索窓に「IoT Core」と入力→検索します。
2. IoT Coreの画面に遷移したら、左のサイドメニューから「モノ」をクリックします。
3. 「モノを作成」をクリックします。
4. 作成画面に遷移したら、「1つのモノを作成」を選択し、次へをクリックします。
5. モノの名前を入力し、それ以外はデフォルト値で次へをクリックします。
6. 新しい証明書を自動生成(推奨)を選択し、次へをクリックします。
7-1. 次にポリシー選択画面に遷移します。まだポリシーがないため新規作成します。
7-2. ポリシー作成画面が表示されるので、以下のJSONドキュメントを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "iot:Connect", "Resource": "arn:aws:iot:[リージョン名]:[AWSアカウントID]:client/esp8266" }, { "Effect": "Allow", "Action": "iot:Subscribe", "Resource": "arn:aws:iot:[リージョン名]:[AWSアカウントID]:topicfilter/esp8266/sub" }, { "Effect": "Allow", "Action": "iot:Receive", "Resource": "arn:aws:iot:[リージョン名]:[AWSアカウントID]:topic/esp8266/sub" }, { "Effect": "Allow", "Action": "iot:Publish", "Resource": "arn:aws:iot:[リージョン名]:[AWSアカウントID]:topic/esp8266/pub" } ] |
7-3. ポリシー選択画面で作成したポリシーを選択し、「モノを作成」をクリックします。
8. 以下の画像のようなモーダルが表示されるので、①デバイス証明書、②プライベートキー、③ルートCA証明書を忘れずにダウンロードします。
ダウンロード後、完了をクリックします。これでモノの作成が完了です!
9. モノの作成後、左のサイドメニューから「設定」をクリックし、エンドポイントをコピーしておきます。(後にプログラムで使用します)
プログラム
次に、PCとマイコンボードを接続し、ArduinoIDE(開発環境)を開きプログラムをマイコンにアップロードしていきます。
Arduino IDEのインストール
ArduinoのIDEはこちらからインストールできます。
事前設定
インストール後の各種設定(NodeMCU esp8266用の開発ボード追加)はwayintop社のGitHubの手順書を参考に行います。
開発環境情報
ArduinoIDEの開発ボードはesp8266 (v3.1.2)を使用しています。
ライブラリは、
- ArduinoJson (v7.1.0)
- PubSubClient (v2.8)
を追加でインストールしておきます。
プログラム
ArduinoIDEで、
- secret.h
- auto-watering-system.ino
の2ファイル作成します。
secret.hには、WiFi のパスワードや秘密鍵などの機密情報を以下のように定義します。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
/** * 各種定義ファイル */ #define TIME_ZONE 9 // タイムゾーン(日本はUTC+9) // MQTTトピックの定義 const char* awsIotPublishTopic = "esp8266/pub"; // データを送信するトピック const char* awsIotSubscribeTopic = "esp8266/sub"; // データを受信するトピック const char* thingName = "esp8266"; // モノの名前(上記手順7-2で指定したクライアント名) // AWS IOTエンドポイント const char* awsIotEndpoint = "xxxxxxxxx.iot.ap-northeast-1.amazonaws.com"; // 接続端子 const int pumpPin = 4; // ポンプ制御用デジタル出力ピン番号 const int sensePin = 0; // 湿度センサーの接続アナログ入力ピン番号 float moistureValue = 0; // 湿度センサーからの読み取り値 // Wifi接続情報 const char* ssid = "ssid"; // WifiのSSID const char* password = "password"; // WiFiのパスワード // 現在の時刻を保持する変数 time_t now; // NTP同期のタイムスタンプとして使用 ※適切な値に設定する必要があります time_t nowish = 1725002268; // Amazon Root CA 1 (AmazonRootCA1.pemの内容) static const char* AWS_CERT_CA = "-----BEGIN CERTIFICATE-----\n" \ "xxxxxxxxxxxxxxxxxxxxxxxxxxx\n" \ "xxxxxxxxxxxxxxxxxxxxxxxxxxx\n" \ "-----END CERTIFICATE-----\n"; // Device Certificate (xxxxx-certificate.pem.crtの内容) static const char* AWS_CERT_CRT = "-----BEGIN CERTIFICATE-----\n" \ "xxxxxxxxxxxxxxxxxxxxxxxxxxx\n" \ "xxxxxxxxxxxxxxxxxxxxxxxxxxx\n" \ "-----END CERTIFICATE-----\n"; // Device Private Key (xxxxxx-private.pem.keyの内容) static const char* AWS_CERT_PRIVATE = "-----BEGIN RSA PRIVATE KEY-----\n" \ "xxxxxxxxxxxxxxxxxxxxxxxxxxx\n" \ "xxxxxxxxxxxxxxxxxxxxxxxxxxx\n" \ "-----END RSA PRIVATE KEY-----\n"; |
auto-watering-system.inoでは、土壌センサーの値を毎分AWS IoT CoreのトピックにMQTTプロトコルでpublishしており、土壌センサー値が閾値600を超えた場合、ポンプを作動させ水やりをするプログラムを書きます。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
#include "secrets.h" // 秘密情報を定義したファイル #include <ESP8266WiFi.h> // ESP8266用のWiFiライブラリをインクルード #include <WiFiClientSecure.h> // SSL/TLS通信クライアントライブラリをインクルード #include <PubSubClient.h> // MQTTプロトコルライブラリをインクルード #include <ArduinoJson.h> // JSON処理用ライブラリをインクルード #include <time.h> // 時間関連の関数をインクルード BearSSL::WiFiClientSecure wifiClient; // HTTPS通信を行うためのクライアントオブジェクト PubSubClient mqttClient(wifiClient); // MQTTクライアントオブジェクト /** * NTPサーバーを使って現在時刻を設定する関数 */ void NTPConnect(void) { Serial.print("Setting time using SNTP"); // NTPサーバーから時間を取得 configTime(TIME_ZONE * 3600, 0 * 3600, "pool.ntp.org", "time.nist.gov"); // 時間の同期 now = time(nullptr); while (now < nowish){ delay(500); Serial.print("."); now = time(nullptr); } Serial.println("done!"); // 現在の時刻をローカル時間に変換 struct tm timeinfo; localtime_r(&now, &timeinfo); Serial.print("Current time: "); Serial.print(asctime(&timeinfo)); } /* * 初期化関数 */ void setup() { // シリアル通信初期化 Serial.begin(9600); // SSL/TLS証明書と秘密鍵の設定 BearSSL::X509List cert(AWS_CERT_CA); BearSSL::X509List client_crt(AWS_CERT_CRT); BearSSL::PrivateKey key(AWS_CERT_PRIVATE); Serial.print("Attempting to connect to SSID: "); Serial.println(ssid); // 接続しようとしているWiFiのSSIDを表示 WiFi.begin(ssid, password); // WiFiネットワークに接続を開始 // WiFi接続が確立するまでループ while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(1000); } Serial.print("Connected to "); Serial.println(ssid); // 接続が成功したらSSIDを表示 // awsへ接続 // 現在時刻を取得 NTPConnect(); // SSL証明書の設定 wifiClient.setTrustAnchors(&cert); wifiClient.setClientRSACert(&client_crt, &key); // MQTTクライアントにWiFiクライアントを設定 mqttClient.setClient(wifiClient); mqttClient.setServer(awsIotEndpoint, 8883); // MQTTサーバーに接続 connectMQTT(); pinMode(pumpPin, OUTPUT); // pumpPinピンを出力モードに設定 pinMode(sensePin, INPUT); // sensePinを入力モードに設定(湿度センサー) digitalWrite(pumpPin, HIGH); // pumpPinピンをHIGHに設定(ポンプを初期状態で停止) delay(500); // 0.5秒待機 } /* * メインループ * 湿度センサーからの値を読み取り、毎分AWS IoTにセンサーデータを送信する */ void loop() { // MQTT クライアントが接続されていない場合、再接続を試みる if (!mqttClient.connected()) { connectMQTT(); } // MQTT クライアントのループ処理を行う mqttClient.loop(); Serial.print("MOISTURE LEVEL:"); // 湿度レベルの表示 moistureValue = analogRead(sensePin); // 湿度センサーの値を読み取る Serial.println(moistureValue); // 読み取った値をシリアルモニタに表示 // 現在の時刻を取得 time_t now; struct tm timeinfo; time(&now); localtime_r(&now, &timeinfo); // 秒が0の場合 if (timeinfo.tm_sec == 0) { // JSONドキュメントを作成 StaticJsonDocument<200> doc; doc["moistureValue"] = moistureValue; // JSON文字列を作成 char jsonBuffer[512]; serializeJson(doc, jsonBuffer); // MQTTトピックにデータを送信 if (mqttClient.connected()) { mqttClient.publish(awsIotPublishTopic, jsonBuffer); Serial.println("Data sent: "); Serial.println(jsonBuffer); } else { Serial.println("MQTT client not connected."); } } // 土壌センサー値が閾値600を超えた場合 ※600は参考閾値 if (moistureValue > 600) { digitalWrite(pumpPin, LOW); // ポンプを動作させる } else { digitalWrite(pumpPin, HIGH); // 湿度が閾値以下の場合、ポンプを停止させる } delay(1000); // 1秒待機 } /** * MQTT 接続関数 */ void connectMQTT() { while (!mqttClient.connected()) { Serial.print("Attempting MQTT connect..."); if (mqttClient.connect(thingName)) { // 適切なクライアントIDを設定 Serial.println("Connected to AWS IoT"); mqttClient.subscribe(awsIotSubscribeTopic); } else { Serial.print("failed, rc="); Serial.print(mqttClient.state()); Serial.println(" try again in 5 seconds"); delay(5000); // 再接続までの待機 } } } /** * NTPサーバーを使って現在時刻を設定する関数 */ void NTPConnect(void) { Serial.print("Setting time using SNTP"); // NTPサーバーから時間を取得 configTime(TIME_ZONE * 3600, 0 * 3600, "pool.ntp.org", "time.nist.gov"); // 時間の同期 now = time(nullptr); while (now < nowish){ delay(500); Serial.print("."); now = time(nullptr); } Serial.println("done!"); // 現在の時刻をローカル時間に変換 struct tm timeinfo; localtime_r(&now, &timeinfo); Serial.print("Current time: "); Serial.print(asctime(&timeinfo)); } |
上記2ファイルのプログラムが書けたら、PCとマイコンボードを接続し、ArduinoIDEを開きプログラムをマイコンにアップロードします。
Arduino IDE左上の「→」ボタンをクリックすることで、プログラムをマイコンへアップロードすることができます。
また、ArduinoIDEのシリアルモニタを使うとデバッグが行えます。MOISTURE LEVELがシリアルモニタに表示されていれば正常動作しています。
ルール作成
次に、マイコンからAWS IoTに送信されたデータをCloudWatchに連携するためのルールを作成していきます。
1 . IoT Coreの画面で左のサイドメニューから「ルール」をクリックし、ルールを作成をクリックします。
2. SQLステートメントを以下のように設定し、次へをクリックします。
1 |
SELECT moistureValue FROM 'esp8266/pub' |
3. 次はルールアクションの設定です。
3-1. アクションにCloudWatch logsを選択し(画像①)、CloudWatch Logグループを作成(画像②)をクリックします。
3-2. CloudWatch Logグループ作成の画面に遷移するので、任意のロググループ名を入力の上、作成をクリックします。
3-3. ルールアクションの画面に戻り、新しいロールを作成ボタンをクリック(画像③)します。
ロールを作成のポップアップが表示されるので、ロール名を入力し、作成をクリックします。
3-4. ロググループに3-2で作成したロググループ、IAMロールに3-3で作成したIAMロールを選択し、次へをクリックします。
4. 確認と作成画面が表示されるので、問題なければ作成をクリックします。
これでIoT CoreからCloudWatch Logsへの連携ルールの作成が完了です!
CloudWatchLogsの設定
次にCloudWatch Logsのログをグラフ化していきます。
1 . AWSコンソールで、検索窓に「CloudWatch」と入力→検索します。
2. CloudWatchの画面に遷移したら、左のサイドメニューから「ロググループ」をクリックし、先ほど作成したログ名を検索し、クリックします。
3. ロググループの詳細画面に遷移したら、画面右上の「Logs Insightで表示」ボタンををクリックします。
4 . ログのインサイト画面に遷移したら、以下のクエリを実行します。
※実行対象の期間に、ログがない期間を選択すると結果が出てこないため注意
1 2 3 4 |
fields @timestamp, moistureValue | sort @timestamp desc | limit 10000 | stats sum(moistureValue) by bin(60s) |
5. グラフが表示されたら、ダッシュボードに追加をクリックし、保存します。
ダッシュボード追加後、Cloud Watchの左サイドメニューのダッシュボードからいつでもグラフを確認できるようになります。
結果&まとめ
お疲れ様でした!
これで土壌データがAWS IoT Coreに連携され、Cloud Watch Logsでグラフ化できるようになりました。
結果
早速結果を見ていきましょう。
以下の画像は、一週間の土壌測定値の推移を示した実際のグラフです。※鉢は屋外に設置
ここでセンサーの仕組みについて少し説明します。
使用したのは静電容量式土壌水分センサーで、市販のCapacitive Soil Moisture Sensor を使用しています。
静電容量式は地中の水分量に応じて端子間の静電容量が変化することで土壌水分を測定します。水分量が多いと静電容量が増し、アナログ出力電圧が低くなります。逆に、乾燥している状態では静電容量が減少し、アナログ電圧値が高くなります。
つまり普通の湿度とは逆で、水分量が多いほど測定値は小さくなり、水分量が少ないほど測定値は大きくなります。
センサーの仕組みを踏まえグラフを見てみると、測定値が600に達したあと跳ね返されている部分がいくつか確認でき、これが水やりされたタイミングとわかります。
計測を始めた当初、水やり後の測定値が思うように下がっていない、という問題に気がつきました。
水やりの様子を観察したところ、センサーと水やりホースの位置が近すぎて、水やり開始直後にセンサーが直接水にさらされ、その後すぐに水やりが停止していました。
センサー周りだけの一時的な水分増加しか反映できず、土壌全体に水分が行き渡っていない状態でした。
そこで水やりホースをセンサーから離れた位置に設置し、固定することで、水やりが長時間行われるようにしました。
調整後は水分が土全体に行き渡り、水やり後の測定値が大きく下がるようになったことが確認できました。
また、天候による測定値の変化も見られました。
雨の日には湿度が高くなるため、土壌の測定値は低いままであまり変化は見られませんでした。測定値の安定=雨が土壌に均等に浸透していることがわかります。
一方、晴れた日には土壌が乾燥しやすく、測定値が上昇=乾燥が進んでいることがわかります。
雨が止んだ後、土壌の乾燥が進んでいるので土の水はけは問題なさそうでした。
まとめ
センサーデータをクラウドに連携することで、土壌の状態をリアルタイムで把握でき、水やりタイミングや水はけの良し悪しなど、植物の健康状態の発見ができました!
具体的には、
- 鉢を置く場所や日当たり角度の調整
- 風通しの改善
- 土の水はけ改善、土壌改良剤等の検討
- 季節ごとのデータ推移で傾向をつかむ
- 効率的な水やり計画が立てやすくなる
等々、データを活用することでガーデニングの問題解決や発見につながるのではないかと思いました。
また、マイコン側でAWS IoTからの通知をsubscribeすることで遠隔でスマホで水やり、なんてことも実現できそうです。
あらゆるものがチップを持ちIoT化が進んでいる現代ですが、そんな中ガーデニングIoTを体験できて、ガーデニングやスマート農業が進化していく未来を実感することができました。
長くなりましたが、ここまでお読みいただきありがとうございました!
- 電子工作で自作した植物の自動水やり機から土壌データをAWS IoT Coreに連携してみた - 2024-09-13
- 【ESP8266 NodeMCU】電子工作で植物の自動水やり機を作ってみた - 2024-08-14
- Amazon Bedrock APIでTypeScriptのテストコードを出力してみた - 2024-05-07
- AWS CDK × CodeBuild × CypressでE2Eテストを自動化&実行動画をS3に保存してみる - 2024-04-16
- AWS ECSでブルーグリーンデプロイメント!~デプロイメントタイプ別の動作比較~ - 2024-04-03