ネタとしての真新しさはないのですが、「これ作ったら生活が便利になるなぁ」と思ったので作ってみます。そしてせっかくなので記事化しておく。
そんな動機の記事でございます。
背景
わたくし、フィッシュカレーを作ることを生業としております。
・・嘘です、生業じゃないです。ただの趣味でした。
で、本格的なフィッシュカレーを作るには生のカレーリーフが必要になるわけで、日本ではそれが入手困難だったりするわけだったりします。
なのでけっこうな人がカレーリーフを自家栽培しているはずです。
ただしかし・・
単純に水やりがめんどくさい。いつか忘れてしまいそう。
ということがあり、これを自動化したいわけです。
何を作るのか
要件
- カレーリーフの鉢がベランダにある。窓を開ければ手が届く。
- wifiもたぶん届く
- 1日1回水をあげられればいい
- 水気がすでに多いときはスキップしたい
- ちゃんと動作してるかも日々watchしたい
そんなわけでこうします
- 我が家にあったM5StickCを活用する(基盤剥き出しではないので野外でも多少の耐久性はあるでしょう
- 動作にはモバイルバッテリーを使う。ただdeep sleepを活用してバッテリーは節約したい
- 土壌センサーで水分量を測りつつよしなにやろう
- LineNotifyを使ってLineに水分量を通知しよう
材料
- M5StickC
- M5Stack用 水分測定センサ付き給水ポンプユニット
- モバイルバッテリー
- グルーガン
そんなわけではじめていきます
- センサの数値を拾う
- LineNotifyで通知
- ポンプで水を汲み上げてみる
1.センサの数値を拾う
コードはこちらのサイト様を参考にさせていただきました。
ここでやりたいことの半分はできちゃってたりしますが。
#include <M5StickC.h>
#define INPUT_PIN 33
#define PUMP_PIN 32
String sensor_moisture;
float vol, moisture, threshold_moisture;
void setup() {
M5.begin();
Serial.begin(115200);
pinMode(INPUT_PIN, INPUT);
pinMode(PUMP_PIN,OUTPUT);
}
void loop() {
Serial.println();
Serial.println("---------------------------------------------");
//Get the measured value
float adc = analogRead(INPUT_PIN);
adc += analogRead(INPUT_PIN);
adc += analogRead(INPUT_PIN);
adc += analogRead(INPUT_PIN);
adc += analogRead(INPUT_PIN);
adc = adc / 5;
vol = (adc + 1) * 3.3 / (4095 + 1);
moisture = 100 * (1.65 - vol) / (1.65 - 1.2);
Serial.printf("Voltage: %2.2fV Moisture: %0.2f%%\r\n", vol, moisture);
threshold_moisture = 20.0;//Turn on when 50% or less
if (moisture <= threshold_moisture){
Serial.println("Needs watering");
digitalWrite(PUMP_PIN,HIGH);
delay(1000*1);
digitalWrite(PUMP_PIN,LOW);
}
Serial.println("---------------------------------------------");
delay(10*1000);
}
すごい。あっさり動いた。
乾いた状態で起動したらおおよそ0%の水分量だったけど濡らしてみたりしたら51%まで上がった。このへんはじっさいに使ってみてアジャストしていかないとですね。
通常時の電圧と、濡れた時の電圧で比較してるんでしょうが、計算式の詳しいとこはよくわかりません。。ただ、ガッツリ濡らして50だったし、20%ぐらいを閾値にしちゃおうかな、とりあえず。
LineNotifyで通知
こちらのサイト様を参考にさせていただきます。
- アクセストークンの発行
https://notify-bot.line.me/ja/
こちらでログインし、
https://notify-bot.line.me/my/
でトークンを発行しました
-
Lineグループに通知したい場合はL
Line Notify
というユーザーをグループに追加します。個人宛に通知の場合は不要です。 -
コードを書きます
こんな感じで通知されるようになりました。
完成したコードがこちら
なお、deepsleepのmaxは71分程度という話だったので60分毎に動作を繰り返すようにしました。notifyが1時間毎にくるとうざいので水をあげなかったパターンの場合には専用のグループを作って通知するようにしました。あと一回の水やり時にモーターを5秒動作させることにしたので、おおよそ40mlが一気に流れるように?
https://mag.switch-science.com/2017/03/03/esp-wroom-02-deep-sleep/
#include <M5StickC.h>
#include <WiFiClientSecure.h>
#include <ssl_client.h>
#define INPUT_PIN 33
#define PUMP_PIN 32
const char wifi_ssid[] = "replase here";
const char wifi_key[] = "replase here";
const char* notify_host = "notify-api.line.me";
const char* notify_token1 = "replase here"; //乾いてた時
const char* notify_token2 = "replase here"; //潤ってた時
String sensor_moisture;
float vol, moisture, threshold_moisture;
void setup() {
M5.begin();
Serial.begin(115200);
pinMode(INPUT_PIN, INPUT);
pinMode(PUMP_PIN,OUTPUT);
// WiFi接続
Serial.printf("Connecting to %s ", wifi_ssid);
WiFi.begin(wifi_ssid, wifi_key);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("CONNECTED");
M5.Lcd.setRotation(1);
M5.Axp.ScreenBreath(10);
M5.lcd.setTextFont(2);
}
void send_notify(String msg, String token) {
WiFiClientSecure client;
client.setInsecure();
Serial.println("Try Line Notify");
if (!client.connect(notify_host, 443)) {
delay(2000);
return;
}
String query = String("message=") + String(msg);
query = query + "&stickerId=10858&stickerPackageId=789";
String request = String("") +
"POST /api/notify HTTP/1.1\r\n" +
"Host: " + notify_host + "\r\n" +
"Authorization: Bearer " + token + "\r\n" +
"Content-Length: " + String(query.length()) + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
query + "\r\n";
client.print(request);
while (client.connected()) {
String line = client.readStringUntil('\n');
Serial.println(line);
if (line == "\r") {
break;
}
}
String line = client.readStringUntil('\n');
Serial.println(line);
client.stop();
}
void loop() {
Serial.println();
Serial.println("started....................");
M5.lcd.fillScreen(BLACK);
M5.Lcd.setTextColor(WHITE, BLACK);
delay(5*1000);
float test = analogRead(INPUT_PIN);
//Get the measured value
float adc = analogRead(INPUT_PIN);
adc += analogRead(INPUT_PIN);
adc += analogRead(INPUT_PIN);
adc += analogRead(INPUT_PIN);
adc += analogRead(INPUT_PIN);
adc = adc / 5;
vol = (adc + 1) * 3.3 / (4095 + 1);
moisture = 100 * (1.65 - vol) / (1.65 - 1.2);
Serial.printf("Voltage: %2.2fV Moisture: %0.2f%%\r\n", vol, moisture);
//Judge the watering
threshold_moisture = 20.0;//Turn on when 50% or less
int32_t drawX=4 ,drawY = 16;
char valStr[32];
M5.lcd.drawString("watering ratio",drawX,drawY);
drawY += 24;
sprintf(valStr,"value : %2.1f%",moisture);
M5.lcd.drawString(valStr,drawX,drawY);
if (moisture <= threshold_moisture){
Serial.println("Needs watering");
digitalWrite(PUMP_PIN,HIGH);
delay(1000*5);//pump discharge:8.3 ml/s * 5 (without height difference)
digitalWrite(PUMP_PIN,LOW);
sprintf(valStr,"乾いていたから水をあげた: %2.1f%",moisture);
send_notify(valStr, notify_token1);
}else{
sprintf(valStr,"土は潤っている: %2.1f%",moisture);
send_notify(valStr, notify_token2);
}
Serial.println(valStr);
delay(5*1000);
//スリープ
Serial.println("...............Enter sleep");
// 画面を消す
M5.Axp.ScreenBreath(0);
//60分deep sleep
esp_sleep_enable_timer_wakeup(SLEEP_MIN(60));
esp_deep_sleep_start();
}
単品のファイルだけですが、githubにも置いてみました
https://github.com/ikegam1/m5stickc-auto-watering-curry-leaf/tree/main
OK。水分具合がどの程度あってるかはあれだけど、動いてそう。
というわけで
実際の鉢にセットしてテスト運用してみます。
だいぶ見にくいですが、現状としての水分量は34.3%のようです。
なので給水は行われませんでした。明日になってどうなるか観察していきます。
20%の閾値だとちょっと低いかもしれないですね。
あと防水とかもやんないと。基本濡れない場所なんですが、風の強い日はわからんですね。
ケーブルの継ぎ目とかはグルーガンで埋めちゃう予定。