🚰

M5stackによる自動給水器の作成

2021/11/22に公開

M5Stackで自動給水器を作成しました。
かなり簡単に作成できましたが、機器構成等で迷いましたので、購入した製品を含めて記載しておきます。
同じようなものを作成したい方は参考にしてください。

自動給水器の写真

機能

作成した自動給水器の機能は下記の通りです。

  • 乾いてくるか、一定時間経過で自動給水
  • 下部が水タンクになっていて、不要な水はタンクに戻る
  • 一回給水すると、一定時間は乾いていても給水しない(ホース外れ対策)
  • 乾いてきているかどうかのデータはクラウドに上げてグラフにする
  • ボタンを押すと強制的に給水

構成図

簡単な構成は下記の通りです。

構成図

水タンク用の2Lペットボトルに鉢用の1Lペットボトルを刺しています。
鉢用のペットボトルには底にいくつか穴が開けてあり、入れすぎた水は水タンクに戻ります。

鉢には水分センサー兼ポンプが刺してあり、M5StackがON/OFFを制御します。
また、M5StackはWiFi経由でAmbientにデータを送り、センサーの情報をグラフにしています。

ambient
Ambientのグラフ

利用した製品・サービス

  • M5Stack Basic
    M5Stackの基本モジュールです。
  • M5Stack PLUSエンコーダモジュール
    M5Stackを拡張するモジュールです。水分センサ兼給水ポンプと接続するために利用します。
    M5Stack Basicの背面を外して取り付けます。
  • M5Stack用 水分測定センサ付き給水ポンプユニット
    水分測定と給水ポンプがセットになったユニットです。エンコーダモジュールの黒色のソケットに接続して使います。
    個体差があるのかもしれませんが、まったく水分が無い状態で2000、水に浸した状態で1500程度の数値をM5Stackに返して来ました。
  • Ambient
    IoTのデータを可視化してくれるクラウドサービスです。手軽に使えますが、アカウント登録後、発行されたAPIキーが利用できるようになるまで30分程度かかるので注意してください。

ソースコード

Visual Studio CodeとPlatformIO環境で作成しました。
また、Ambient用のライブラリを追加しています。

main.cpp
#include <M5Stack.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <Ambient.h>

#define INPUT_PIN 36
#define PUMP_PIN 26

const char ssid[] = "";//WiFiのSSID
const char pass[] = "";//WiFiのパスワード
const char slack_information_room[] = "";//slackのWeb API
unsigned int ambientChannelId = 0;//AmbientのチャンネルID
const char *ambientWriteKey = "";//AmbientのAPIキー

int rawADC;

struct tm currentTimeInfo;
time_t lastTimeInfo;

String avatarMessageStr;

WiFiClient wclient;
Ambient ambient;
int sendCounter = 0;
int lowD = 2000, highD = 0;

void setup()
{
  M5.begin();
  M5.Power.begin();
  M5.Lcd.setTextSize(2);

  M5.Lcd.print("Initiating.");

  pinMode(INPUT_PIN, INPUT);
  pinMode(PUMP_PIN, OUTPUT);
  pinMode(25, OUTPUT);
  digitalWrite(25, 0);

  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    M5.Lcd.print(".");
  }
  M5.Lcd.println("\nWiFi connected");
  M5.Lcd.print("IP address = ");
  M5.Lcd.println(WiFi.localIP());
  configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");//時刻同期のためにNTPを設定
  ambient.begin(ambientChannelId, ambientWriteKey, &wclient);
  delay(2000);

  getLocalTime(&currentTimeInfo);
  char now[20];
  sprintf(now, "%02d:%02d:%02d",
          currentTimeInfo.tm_hour,
          currentTimeInfo.tm_min,
          currentTimeInfo.tm_sec);
  M5.Lcd.println(now);
  delay(2000);
}

void watering()
{
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0, 2);
  getLocalTime(&currentTimeInfo);
  char now[20];
  sprintf(now, "%02d:%02d:%02d",
          currentTimeInfo.tm_hour,
          currentTimeInfo.tm_min,
          currentTimeInfo.tm_sec);
  M5.Lcd.println(now);

  String slack_message = "{\"text\":\"Watering: " + avatarMessageStr + "\"}";
  M5.Lcd.println(slack_message);
  HTTPClient http;
  http.begin(slack_information_room);
  http.addHeader("Content-Type", "application/json");
  int statusCode = http.POST((uint8_t *)slack_message.c_str(), strlen(slack_message.c_str()));
  if (statusCode < 0)
  {
    statusCode = http.POST((uint8_t *)slack_message.c_str(), strlen(slack_message.c_str()));
  }
  slack_message = "Watering! " + avatarMessageStr + " " + String(statusCode);

  M5.Lcd.println(rawADC);
  M5.Lcd.println(statusCode);
  String payload = http.getString();
  M5.Lcd.println(payload);
  http.end();

  //10秒間ポンプを動かす
  digitalWrite(PUMP_PIN, true);
  delay(10000);
  digitalWrite(PUMP_PIN, false);
  getLocalTime(&currentTimeInfo);
  lastTimeInfo = mktime(&currentTimeInfo);
}

void loop()
{
  getLocalTime(&currentTimeInfo);
  rawADC = analogRead(INPUT_PIN);
  avatarMessageStr = String(rawADC);
  M5.Lcd.setCursor(0, 0, 2);
  char now[20];
  sprintf(now, "%02d:%02d:%02d",
          currentTimeInfo.tm_hour,
          currentTimeInfo.tm_min,
          currentTimeInfo.tm_sec);
  M5.Lcd.println(now);
  if (rawADC < lowD)
  {
    lowD = rawADC;
  }
  if (rawADC > highD)
  {
    highD = rawADC;
  }
  sendCounter++;

  if (sendCounter == 60 * 5)
  {
    ambient.set(1, String(lowD).c_str());
    ambient.set(2, String(highD).c_str());
    ambient.send();
    lowD = 2000;
    highD = 0;
    sendCounter = 0;
  }

  //乾いていて、前回の給水から1時間経過した場合。とりあえずカラッカラな状態の時だけ給水することにするため2000を設定
  if ((rawADC > 2000) & (difftime(mktime(&currentTimeInfo), lastTimeInfo) > 60 * 60))
  {
    watering();
  }
  //前回の給水から3時間経過した場合
  if (difftime(mktime(&currentTimeInfo), lastTimeInfo) > 60 * 60 * 3)
  {
    watering();
  }

  //真ん中のボタンが押された場合
  if (M5.BtnB.wasPressed())
  {
    watering();
  }

  delay(3000);
  M5.update();
}

platform.ini
[env:m5stack-core-esp32]
platform = espressif32
board = m5stack-core-esp32
framework = arduino
lib_deps = 
	m5stack/M5Stack@^0.3.6
	ambientdatainc/Ambient ESP32 ESP8266 lib@^1.0.1

所感

思った以上に簡単にできた、と言うのが率直な感想でした。
ラズパイのように大掛かりなところが無いのでシンプルなコードですぐ動く。
ただし、メモリが少ないのか電源が足りないのか無理をさせるとすぐに再起動したりしてしまう(Avatorを一緒に使おうとしたら再起動を繰り返して使えなかった)ようなので、複雑なことをさせるならやはりラズパイになるのかもしれない。

Discussion