自宅IoT ESP8266 + 土壌水分センサー+ポンプの水やり機を作ってみた

別のブログ記事に書きましたが、今回ジャーマンカモミールを種から栽培する過程において、生育中のカモミールの水やりを自動化したいと思い、ESP8266等を使って自動水やり機を作ってみましたので、そのやったことになります。

水やり機の仕組み

ざっくりこんな感じでやろうと思います。土壌センサーで取得した値から含水率を計算し、所定の値を下回った場合に自動的にポンプを動かして水やりを行うことを考えています。これにより、上回った場合はポンプを止めて水やりを止めます。

含水率のログは必須ではありませんが、状況確認のために送ります。

用意したもの

用意したものは次の通りです。なお、一部のものについては今回のテーマに必須でないもの(例:ファン)が含まれていますので、それに該当する場合は(※)マークをつけさせていただきました。

ESP8266の本体です。いろんなメーカーが作っていますが、今回は「HiLetgo」を使いました。

「HiLetgo」の土壌水分センサーです。今回は安価な抵抗型のセンサーを使用しました。

生育状態を確認するために屋外の温度収集目的で使いました。合わせて、今回の水やり機を格納するボックス内の温度が上昇した時に内部の冷却を行うための目的としても使っています。気圧も測れますが、今回は温度のみ測ります。なお、このセンサーはBME280と異なり、湿度までは測れませんので気をつけてください。

LEDはサーバへデータ送信した時のステータス、充電池利用を確認する時に使用します。送信ステータスのほうは成功の「緑」、失敗の「赤」を使用しました。サーバ送信で成功であれば「緑」を、エラーがあった場合は「赤」を点灯するようにしました。

充電池利用については「青」を使用しました。

  • 抵抗:470Ω×3個・・・LEDにつなぐ抵抗用

ESP8266と各モジュールを接続するために使用します。

2回路2接点の小型スイッチです。後述する切り替え用で購入しました。実際は1回路しか使用しませんでした。

3000mAhの充電池です。こちらは楽天での購入です。保護回路機能付きの「懐中電灯」用を選択しました。

5V 1.5W出力のソーラーパネルです(110mm×80mm)

ロガーからの制御信号により、モーター等を駆動させるためのモジュールです。

充電モジュール、昇圧電源モジュール、今回購入したリチウムイオン電池の収納のための充電ソケットがセットになっています。

日陰や夜間等、充電池から電流の逆流を防止するために使用

12V用が多かったのですが、今回作った機器への負担を抑えるため、5V出力ものを選びました。USBコネクタ部分は切断して、コードを直接スクリューピンのコネクタへ接続します。

5V出力、30mm×30mm×10mmサイズの小型用のCPUファンです。こちらはebayで購入しました。機器を入れたケース内の冷却用として使用しています。水やりをする上では必須ではありません。

噴霧ノズルと繋ぐためのチューブです。水槽で使用する小型のエアポンプで空気を供給するためのチューブを使用します。

モーターと繋ぐためのチューブです。この手のモーターの入出口径が比較的大きく、リンク先で確認できる設計仕様では、内径8mmのチューブを使用する必要があります。

噴霧ノズル側のチューブとモーター側のチューブを繋ぐためのジョイントです。それぞれ内径が異なるため、これらを繋ぐために使用します。

  • 機器格納用プラスチックケース・・・ダイソーで購入。和泉化成さんの「中が見えるBox」。今回のそうですが、機器を入れるケースについては非常にお世話になっています。
内寸で165mm×105mm×45mmです。

ちなみに、今回の中心となるポンプですが、動作確認はこんな感じ。5Vなので、ポンプからの水圧はあまり高くないようです。でも、これでやってみるしかないですね。

USB接続で試したところ。水柱はおよそ15cmぐらい。

噴霧ノズルをつなげて試してみたところ。ホースの長さにもよるが、勢いはあまり良くない。

配置例

各パーツの配置例は次の通りです。前回作ったこちらブログで記載したモジュールをベースにして、次の追加、変更をしています。

今回の水やり機では、中央の部分のポンプ(モーター)/ファン駆動のためのモジュールを追加しています。

追加・変更した箇所
  • 【左側】ESP8266/ロガー
    • BH1750の削除
    • LM393に接続するセンサーを土壌センサー(HW-080)に変更
    • ポンプ・ファン供給用接続箇所の追加
  • 【中央】ポンプ・ファン駆動用モジュール
    • それぞれ5Vリレーモジュールと昇圧電源モジュールを接続
    • 駆動側はそれぞれ5V出力とするため、入力となるリチウムイオン電池の電圧が3.7Vにつき、昇圧電源モジュールで5Vに昇圧して供給
  • 【右側】電源供給・充電用モジュール
    • コンデンサの削除

ESP8266での各ピンの接続
  • ESP8266 -> LM393との接続
    • A0 -> A0
    • D7 -> D0
    • GND -> GND
    • D3 -> VCC
  • ESP8266 -> BMP280との接続
    • 3V -> VCC
    • GND -> GND
    • D1 -> SCL
    • D2 -> SDA
  • ESP8266 -> 成功LED(緑)との接続
    • D5ピン -> 抵抗+アノード(+)
    • GND -> カソード(-)
  • ESP8266 -> 失敗LED(赤)との接続
    • D6ピン -> 抵抗+アノード(+)
    • GND -> カソード(-)
  • ESP8266でのプログラムインストール/DeepSleep有効切り替え
    • D0とRSTをスイッチ経由で接続(DeepSleep時はスイッチにより、双方のピンを接続する)
  • ESP8266 -> リレー(ポンプ)との接続
    • 5V -> VCC
    • GND -> GND
    • D4 -> IN
  • ESP8266 -> リレー(ファン)との接続
    • 5V -> VCC
    • GND -> GND
    • D8 -> IN

サンプルコード

サンプルコードは次の通りです。今回はブログ記載向けに、必要な箇所のみ(土壌センサーでの値取得とポンプ駆動、温度取得とファン駆動)絞って記述していますが、実際はセンサーで収集したログをサーバーへ送信する等、もう少しプログラムに手を加えています。必要に応じてしてカスタマイズしてください。

#include <Wire.h>
#include <Adafruit_BMP280.h>
#include <ESP8266WiFi.h>
#include <time.h>

// for LM393
#define RAIN_PIN_A A0    // Raindrop sensor pin(Analog)
#define RAIN_PIN_D D7    // Raindrop sensor pin(Digital)
#define RAIN_PIN_V D3    // Raindrop power pin (for saving power)

// LED OK/NG by HTTP status
#define LED_OK D5        // D5 pin
#define LED_NG D6        // D6 pin

// Motor
#define MOTOR_PIN D4
#define WATERING_PERCENT 30
#define WATERING_DURATION 30

// Fan
#define FAN_PIN D8
#define COOLING_TEMP 30
#define COOLING_DURATION 30

// Constant values
#define JST     3600*9
#define DEF_VAL -255.0
#define BMP280_ADDRESS 0x76  // Check it beforehand

// Debug option for using Serial
#define DEBUG false
#define DEBUG_SERIAL if(DEBUG)Serial

// Valid options for cheking sensors
#define SENSOR_LM393_VALID true
#define SENSOR_BMP280_VALID true
#define MOTOR_VALID true
#define FAN_VALID true

Adafruit_BMP280 bmp; // I2C

// Const values for range of measurement
const int AirValue = 446;
const int WaterValue = 130;

// Wifi Credential
const char* ssid = "xxxxx";
const char* password = "xxxxx";

void setup() {

  // Initialization
  DEBUG_SERIAL.begin(9600);
  delay(100);
  DEBUG_SERIAL.println("Start");

  // Preprocessing
  Wire.begin();
  connect();
  delay(3000);  // Wait for a time is refreshed by NTP

  // BMP280
  if (SENSOR_BMP280_VALID) {
    if (!bmp.begin(BMP280_ADDRESS)) {
      DEBUG_SERIAL.println("Could not find BMP280...");
      //while (1) {}
    }
  }

  // LM393
  if (SENSOR_LM393_VALID) {
    pinMode(RAIN_PIN_D, INPUT);
    pinMode(RAIN_PIN_V, OUTPUT);
    digitalWrite(RAIN_PIN_V, LOW);
  }

  // Motor
  if (MOTOR_VALID) {
    pinMode(MOTOR_PIN, OUTPUT);
  }

  // Fan
  if (FAN_VALID) {
    pinMode(FAN_PIN, OUTPUT);
  }

  // LEDs setup
  pinMode(LED_OK, OUTPUT);
  pinMode(LED_NG, OUTPUT);

}

// Nothing to do
void loop() {

  // put your setup code here, to run once:
  // Main
  int elapsed_time = captureAndSendData();
  delay(60000); // 60s sleep

}

int captureAndSendData() {

  // put your main code here, to run repeatedly:

  time_t t, et;
  struct tm *tm;

  // time
  t = time(NULL);
  DEBUG_SERIAL.print("Epoch Time (Start): ");
  DEBUG_SERIAL.print(t);
  DEBUG_SERIAL.print(" \n");
  tm = localtime(&t);
  int it = t;

  // BMP280
  float temp = DEF_VAL;

  // LM393
  int soilMoisture = DEF_VAL;
  int soilMoistureValue = DEF_VAL;
  float soilMoisturePercent = DEF_VAL;

  // BMP280
  if (SENSOR_BMP280_VALID) {
    temp = bmp.readTemperature();
    DEBUG_SERIAL.print("Temp: ");
    DEBUG_SERIAL.print(temp);
    DEBUG_SERIAL.print(" °C");
    DEBUG_SERIAL.print(" \n");

    delay(100);
  }

  // LM393
  if (SENSOR_LM393_VALID) {
    digitalWrite(RAIN_PIN_V, HIGH);
    delay(100);

    soilMoisture = digitalRead(RAIN_PIN_D);
    soilMoistureValue = analogRead(RAIN_PIN_A);
    soilMoisturePercent = map(soilMoistureValue, AirValue, WaterValue, 0, 100);

    digitalWrite(RAIN_PIN_V, LOW);
    DEBUG_SERIAL.print("Moisture: ");
    DEBUG_SERIAL.print(soilMoisturePercent);
    DEBUG_SERIAL.print(" % ");
    DEBUG_SERIAL.print("(");
    DEBUG_SERIAL.print(soilMoistureValue);
    DEBUG_SERIAL.print(")");
    DEBUG_SERIAL.print(" \n");

    delay(100);
  }

  // Motor
  if (MOTOR_VALID) {
    if (soilMoisturePercent < WATERING_PERCENT) {
      DEBUG_SERIAL.println("Motor started. Watering is started.");
      digitalWrite(MOTOR_PIN, HIGH);
      delay(WATERING_DURATION*1000);
    }
    digitalWrite(MOTOR_PIN, LOW);
    DEBUG_SERIAL.println("Motor stopped.");
  }

  // Fan
  if (FAN_VALID) {
    if (COOLING_TEMP < temp) {
      DEBUG_SERIAL.println("Fan started. Cooling is started.");
      digitalWrite(FAN_PIN, HIGH);
      delay(COOLING_DURATION*1000);
    }
    digitalWrite(FAN_PIN, LOW);
    DEBUG_SERIAL.println("Fan stopped.");
  }

  // end time
  et = time(NULL);
  DEBUG_SERIAL.print("Epoch Time (End): ");
  DEBUG_SERIAL.print(et);
  DEBUG_SERIAL.print(" \n");
  tm = localtime(&et);
  int iet = et;

  int elapsed_time = iet - it;
  DEBUG_SERIAL.print("Elapsed Time: ");
  DEBUG_SERIAL.print(elapsed_time);
  DEBUG_SERIAL.print(" \n");

  return elapsed_time;
}

// WiFi Setup
void connect () {

  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
    DEBUG_SERIAL.print('.');
    delay(500);
  }
  DEBUG_SERIAL.println();
  DEBUG_SERIAL.printf("Connected, IP address: ");
  DEBUG_SERIAL.println(WiFi.localIP());
  configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");

}

組み立て前の準備と動作テスト

今回の自動水やり機の実装する上で、事前に行ったことを以下記載します。

温度/気圧センサー(BMP280)のI2Cアドレスの確認

BMP280はI2Cを使用するため、事前にI2Cアドレスを確認しておく必要があります。Arduinoサンプルコードにある「i2c_scannar」で確認ができます。通常は0x76になるはずです。

土壌水分センサーから取得した値からの含水率の取得

土壌水分センサー(HW-080)で取得した値は0から1023(1024段階、1023が一番乾いている状態)の整数値として送られます。しかしながら、実際に水分無しの状態と水分ありの状態でセンサーから取得した値は必ずしも0や1023の値にならない場合があります。

そこで、実際の土壌を使用し、土壌が乾ききっている状態と、土壌に沢山の水分を含ませた状態でのセンサーからの値を計測し、その範囲内で0から100の値の比に変換することにしました。この変換にはmap関数を使用しています。

土壌が乾いている状態

土壌に沢山の水分を含ませている状態。水が多すぎて土が浮いてくるぐらいの状態です。

実際に計測すると、当時の計測では0や1023にはならず、乾いている状態は446、水分を含んでいる状態は130でした。よって、この値をベースにmap関数で0-100で変換し、これを含水率として算出することにしました。実際の含水率の算出式があるようですが、あくまでも簡易的なやり方にしました。

#define RAIN_PIN_A A0
#define DEF_VAL -255.0

const int AirValue = 446;
const int WaterValue = 130;

int soilMoistureValue = DEF_VAL;
float soilMoisturePercent = DEF_VAL;

soilMoistureValue = analogRead(RAIN_PIN_A);
soilMoisturePercent = map(soilMoistureValue, AirValue, WaterValue, 0, 100);    

充電池からポンプ/ファンを駆動させるための昇圧

センサーによって駆動するポンプやファンの定格電圧は5Vになります。しかしながら、今回供給する充電池の電圧は3.7Vであるため、このままですと、ポンプやファンの勢いがさらになくなってしまう(もしくは動かない)可能性があります。

したがって、これを昇圧電源モジュールを使って、5Vまで昇圧をさせて供給します。購入時点では何ボルトで出力されるか分からないため、テスターを使って、出力電圧をモジュールについている可変抵抗器のツマミをマイナスドライバーで回しながら5Vに調整します。時計回りで電圧が上昇します。

調整は実際に供給する充電池を使いました。

これをポンプ用とファン用の2つのモジュールに対して調整しておきます。

ポンプ/ファンが駆動する条件

ポンプは土壌水分センサーからの値をもとに駆動、ファンは温度をもとに駆動させるようにしました。条件はなんとでもできると思いますが、ポンプの含水率を30パーセントを下回る、温度は30°を上回ったらそれぞれ駆動するように設定しました。駆動時間はいずれも30秒にしています。

実際の組み立て

実際の水やり機はこんな感じで作りました。ソーラーパネル、土壌水分センサー、ポンプは外だしするため、ケースにそれぞれ穴をあけています。

やっぱりコードが多いせいか、大きめのケースに入れていますが、ぐちゃっとしています。

ケース内の冷却用ファンはケース角に固定し、熱を逃すための穴をいくつかあけました。

最終的に作って配置したものです。こんな感じで空の2Lペットボトルの下半分を使い、そこからホース、ポンプ、噴霧ノズルを接続します。

水やり機を屋外におき、ペットボトルに水を入れ、土壌水分センサーと噴霧ノズルを鉢植えに設定して完了です。

実際の動き

実際の動きはこんな感じです。ポンプ等が駆動する時はリレーモジュールで「カチッ」と音がすれば、駆動のスイッチが入り、ポンプが動きます。しかし、やはり水の勢いが弱いですね。

振り返り

今回センサーからの値での土壌の乾き具合をもとに自動で水やりをやってみました。残念ながら5V出力では水の勢いがあまり良くないですが、それでも自分がやらなくても土が乾いたら水をやるという水やり機の機能としては果たせていると思います。

果たして、どこまで運用できるかわかりませんが、噴霧回りのところはもう少し改善してた上でしばらく様子を見てみることにします。これによって鉢植えしているジャーマンカモミールがすくすくと育ってくれることを期待します。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です