※画像は生成AIによるイメージです

ESP32自動水やり機自作:第3回 観葉植物のための土壌水分センサーとポンプ制御プログラム実装

連載: ESP32で構築する観葉植物の自動水やりシステム自作ガイド スマートホームDIY電子工作観葉植物

前回の記事では、ESP32を核とした自動水やりシステムのハードウェア組み立て、特に土壌水分センサーと水中ポンプの確実な配線、そして水漏れ対策について詳細に解説しました。物理的な準備が整ったところで、いよいよシステムの「脳」となるソフトウェアの開発に取り掛かります。

本記事では、ESP32が土壌水分センサーからデータを正確に取得し、その情報に基づいてポンプを適切に制御するためのプログラミング手法を深掘りします。単にセンサー値を読み取るだけでなく、植物の種類や環境に合わせた水やりロジックの構築、そしてシステムの安定稼働を保証する安全なポンプ制御について、具体的なコード例を交えながら解説を進めます。

自動水やりシステムを動かすソフトウェアの全体像

ESP32を用いた自動水やりシステムにおけるソフトウェアの役割は多岐にわたります。主な機能は以下のサイクルで構成されます。

  1. センサー入力: 土壌水分センサーから現在の土壌の湿度データを取得します。
  2. データ処理: 取得したセンサー値のノイズを除去し、必要に応じてキャリブレーションを適用します。
  3. ロジック判断: 処理されたセンサー値と事前に設定された閾値を比較し、水やりが必要か否かを判断します。
  4. アクチュエータ出力: 水やりが必要と判断された場合、リレーモジュールを介して水中ポンプをON/OFF制御します。
  5. 状態監視: ポンプの作動時間や水やり頻度を管理し、過剰な水やりや連続稼働を防ぎます。

このサイクルを効率的かつ安定的に実行するためには、ESP32の性能を最大限に引き出すプログラミングが必要です。開発環境にはArduino IDEを使用し、ESP32用のボードマネージャーを導入していることを前提に解説を進めます。

土壌水分センサーからのデータ取得とアナログ値のデジタル変換

土壌水分センサーは土壌の水分量をアナログ電圧として出力します。ESP32はこのアナログ電圧をデジタル値に変換(ADC: Analog-to-Digital Conversion)して読み取ります。正確な水やりには、このセンサーからのデータ取得が最も重要です。

ESP32のADC入力設定とピン配置

ESP32は複数のADCチャンネルを備えており、多くのGPIOピンをアナログ入力として利用できます。一般的に、静電容量式土壌水分センサーは出力電圧範囲が0Vから3.3Vであり、ESP32のADC入力範囲(通常0Vから3.3V、または設定により変更可能)と親和性が高いです。

前回の配線で土壌水分センサーのデータ出力ピンをESP32の特定のGPIOピンに接続しているはずです。例えば、GPIO34はADC1_CH6として利用可能です。ESP32のADCは12ビット分解能を持つため、0から4095までの値でアナログ電圧を表します。

const int soilMoistureSensorPin = 34; // 土壌水分センサーのデータピン (GPIO34)

void setup() {
  Serial.begin(115200); // シリアル通信の初期化
}

void loop() {
  int sensorValue = analogRead(soilMoistureSensorPin); // センサー値を読み取り
  Serial.print("Soil Moisture Sensor Value: ");
  Serial.println(sensorValue);
  delay(1000); // 1秒ごとに読み取り
}

この基本コードで、センサーからアナログ値を読み取り、シリアルモニターで確認できます。

センサー値の読み取りと平均化処理

土壌水分センサーの読み取り値は、土壌の不均一性や外部ノイズによってわずかに変動する場合があります。より安定した値を得るためには、複数回の読み取りを行い、その平均値を採用する「平均化処理」が有効です。

int readSoilMoisture(int pin) {
  int sum = 0;
  for (int i = 0; i < 10; i++) { // 10回読み取り
    sum += analogRead(pin);
    delay(10); // 短いディレイを挟むことでノイズの影響を軽減
  }
  return sum / 10; // 平均値を返す
}

void loop() {
  int averagedSensorValue = readSoilMoisture(soilMoistureSensorPin);
  Serial.print("Averaged Soil Moisture Value: ");
  Serial.println(averagedSensorValue);
  delay(5000); // 5秒ごとに平均値を表示
}

このreadSoilMoisture関数を使用することで、単一の読み取りよりも信頼性の高いデータが得られます。

また、センサーの初期キャリブレーションは非常に重要です。完全に乾いた土での読み取り値(例: 3000-4000)と、十分に湿った土での読み取り値(例: 1000-2000)を把握し、水やり判断の基準とします。これらの値はセンサーの種類や土壌、鉢の大きさによって異なるため、実際に測定して記録しておくことが推奨されます。

水やり判断ロジックの実装:閾値設定と状態管理

正確なセンサー値が得られたら、次にその値に基づいて水やりが必要かどうかを判断するロジックを実装します。このロジックは、植物の健康を維持するために最も重要な部分です。

適切な水分閾値の決定方法

水やり判断の核心は、土壌水分センサーの読み取り値と「閾値」の比較にあります。閾値とは、これ以下の値になったら水やりを開始するという基準値です。

  • 植物の種類: 乾燥を好む植物(サボテンなど)と湿潤を好む植物(シダなど)では、適切な水分量が大きく異なります。
  • 鉢のサイズと土の種類: 小さな鉢や水はけの良い土は乾燥しやすく、大きな鉢や保水性の高い土は湿潤を保ちやすいです。
  • 環境: 室温、湿度、日当たりなども水分蒸発速度に影響を与えます。

これらの要素を考慮し、デバッグモニターでセンサーのリアルタイム値を確認しながら、最適な閾値を設定します。例えば、完全に乾いた状態が4000、適度に湿っている状態が1500、水浸しが500という範囲で、1800を下回ったら水やりを開始する、といった具体的な数値を決定します。

const int dryThreshold = 2000; // この値より低いと乾燥と判断(例)
// 実際の値はキャリブレーションで決定する

void loop() {
  int currentMoisture = readSoilMoisture(soilMoistureSensorPin);
  Serial.print("Current Moisture: ");
  Serial.println(currentMoisture);

  if (currentMoisture > dryThreshold) {
    Serial.println("Soil is dry. Watering needed.");
    // ポンプ作動関数を呼び出す
  } else {
    Serial.println("Soil is moist enough.");
  }
  delay(60000); // 1分ごとにチェック
}

ポンプ作動タイマーとインターバル制御

水やりが必要と判断された場合でも、ポンプを無制限に作動させるのは避けるべきです。過剰な水やりは根腐れの原因となります。また、一度水やりを開始したら、土壌に水が浸透するまである程度の時間が必要であり、すぐに再度センサー値をチェックして水やりを繰り返すのも非効率的です。

これを解決するために、「ポンプ作動タイマー」と「インターバル制御」を実装します。

  • ポンプ作動タイマー: 一度の水やりでポンプを稼働させる時間を設定します(例: 5秒、10秒など)。
  • インターバル制御: 水やりを行った後、一定期間(例: 2時間、1日など)は再度の水やり判断をスキップする期間を設定します。これにより、土壌が水を吸収する時間を確保し、頻繁なポンプのON/OFFを防ぎます。

millis()関数を使用することで、ESP32が他の処理をブロックすることなく、タイマー処理を実装できます。

unsigned long lastWateringTime = 0;
const long wateringInterval = 2 * 60 * 60 * 1000; // 2時間 (ミリ秒)
const int pumpDuration = 5000; // ポンプ作動時間 5秒 (ミリ秒)

void waterPlant() {
  // ポンプをONにする処理
  Serial.println("Pump ON for 5 seconds.");
  digitalWrite(pumpControlPin, HIGH); // リレーをONにする(アクティブHIGHの場合)
  delay(pumpDuration); // ポンプ作動時間だけ待つ
  digitalWrite(pumpControlPin, LOW);  // リレーをOFFにする
  Serial.println("Pump OFF.");
  lastWateringTime = millis(); // 最後に水やりした時刻を更新
}

void loop() {
  // ... センサー値の読み取り ...

  if (currentMoisture > dryThreshold && (millis() - lastWateringTime > wateringInterval)) {
    Serial.println("Watering initiated due to dry soil and elapsed interval.");
    waterPlant();
  }
  // ...
}
解説画像 1 ※画像は生成AIによるイメージです

水中ポンプ制御プログラムと安全なリレー駆動

水中ポンプの制御は、ESP32のGPIOピンを通じてリレーモジュールをON/OFFすることで行われます。この際、単にON/OFFするだけでなく、システムの安全性と信頼性を高めるための考慮が必要です。

リレーモジュールを介したポンプのON/OFF制御

前回の記事で解説した通り、ESP32のGPIOピンは直接高電流のポンプを駆動できません。そのため、リレーモジュールを介してポンプの電源を制御します。リレーモジュールは、ESP32からの微弱なデジタル信号(HIGH/LOW)を受け取り、ポンプが必要とする高電圧・高電流の回路を物理的に開閉します。

リレーモジュールには「アクティブHIGH」と「アクティブLOW」のタイプがあります。

  • アクティブHIGH: GPIOがHIGH(3.3V)の時にリレーがONになるタイプ。
  • アクティブLOW: GPIOがLOW(0V)の時にリレーがONになるタイプ。

使用しているリレーモジュールの仕様を確認し、適切なdigitalWrite()の値を選択してください。

const int pumpControlPin = 2; // ポンプ制御用のGPIOピン (例: GPIO2)

void setup() {
  Serial.begin(115200);
  pinMode(pumpControlPin, OUTPUT); // ポンプ制御ピンを出力に設定
  digitalWrite(pumpControlPin, LOW); // 初期状態はポンプOFF (アクティブHIGHの場合)
}

void waterPlant() {
  Serial.println("Pump ON.");
  digitalWrite(pumpControlPin, HIGH); // ポンプON (アクティブHIGHの場合)
  delay(pumpDuration);
  Serial.println("Pump OFF.");
  digitalWrite(pumpControlPin, LOW);  // ポンプOFF
  lastWateringTime = millis();
}

このwaterPlant()関数は、pumpControlPinにHIGHを出力してリレーをONにし、pumpDurationで設定された時間後にLOWを出力してリレーをOFFにします。

二重停止と誤作動防止のためのコーディング

リレーは機械的な接点を持つため、電気的なノイズや振動によって意図しないON/OFFが発生する「チャタリング」と呼ばれる現象が起こる可能性があります。また、ソフトウェアのバグや予期せぬ電源トラブルにより、ポンプが長時間稼働し続ける事態も考慮しなければなりません。

これらのリスクを軽減するために、以下の対策を講じます。

  • 最大作動時間の設定: waterPlant()関数内でdelay(pumpDuration)を使用していますが、これはブロッキング処理です。より堅牢なシステムでは、millis()ベースの非ブロッキングタイマーを使って、ポンプが設定された時間以上に稼働しないように強制的に停止させるロジックを実装します。

  • フェイルセーフ設計: 電源が一時的に落ちた場合やESP32がリセットされた場合でも、ポンプが勝手にONにならないように、setup()関数内で必ずポンプ制御ピンをLOW(OFF)に初期化します。

  • 状態変数の利用: ポンプが現在稼働中であるかを示す状態変数を導入し、二重にONコマンドが送られるのを防ぎます。

bool isPumpRunning = false;
unsigned long pumpStartTime = 0;

void controlPump(bool state) {
  if (state && !isPumpRunning) { // ポンプをONにする場合
    Serial.println("Pump activated.");
    digitalWrite(pumpControlPin, HIGH); // ポンプON
    isPumpRunning = true;
    pumpStartTime = millis();
  } else if (!state && isPumpRunning) { // ポンプをOFFにする場合
    Serial.println("Pump deactivated.");
    digitalWrite(pumpControlPin, LOW); // ポンプOFF
    isPumpRunning = false;
  }
}

void loop() {
  // ... センサー値と水やりインターバルのチェック ...

  if (currentMoisture > dryThreshold && (millis() - lastWateringTime > wateringInterval) && !isPumpRunning) {
    controlPump(true); // ポンプON
  }

  // ポンプが稼働中の場合、最大作動時間をチェック
  if (isPumpRunning && (millis() - pumpStartTime > pumpDuration)) {
    controlPump(false); // 最大作動時間を超えたら強制OFF
    lastWateringTime = millis(); // 強制OFF後もインターバルを適用
  }
  // ...
}

このcontrolPump関数とisPumpRunning変数を用いることで、より安全で信頼性の高いポンプ制御が可能になります。

プログラム全体の統合とArduino IDEでの開発手順

これまで解説してきた個々の要素を統合し、一つの完全なESP32スケッチとしてまとめます。Arduino IDEでの開発は、setup()loop()という二つの主要な関数を中心に進められます。

スケッチの基本構造とライブラリの活用

ESP32のスケッチは、以下の基本構造に従います。

  • 変数宣言: グローバル変数として、ピン番号、閾値、タイマー関連の変数などを宣言します。
  • setup()関数: ESP32が起動した際に一度だけ実行される処理を記述します。これには、シリアル通信の開始、GPIOピンモードの設定、初期状態のポンプOFFなどが含まれます。
  • loop()関数: setup()関数が完了した後、ESP32が稼働している間、繰り返し実行されるメインループです。センサー値の読み取り、水やり判断ロジック、ポンプ制御などがこの中に記述されます。

また、将来的にWi-Fi接続やクラウド連携などの機能を追加する場合、関連するライブラリ(例: WiFi.h, HTTPClient.hなど)をスケッチの先頭でインクルードして利用します。現時点では、基本的なセンサーとポンプ制御のみであれば、特別なライブラリは不要です。

// グローバル変数宣言
const int soilMoistureSensorPin = 34; // GPIO34
const int pumpControlPin = 2;       // GPIO2

const int dryThreshold = 2000;      // 乾き閾値 (キャリブレーションで調整)
const int pumpDuration = 5000;      // ポンプ作動時間 5秒
const long wateringInterval = 2 * 60 * 60 * 1000; // 水やりインターバル 2時間

unsigned long lastWateringTime = 0;
bool isPumpRunning = false;
unsigned long pumpStartTime = 0;

// 関数プロトタイプ宣言
int readSoilMoisture(int pin);
void controlPump(bool state);
void waterPlantLogic();

void setup() {
  Serial.begin(115200);
  pinMode(pumpControlPin, OUTPUT);
  digitalWrite(pumpControlPin, LOW); // ポンプ初期OFF
  Serial.println("ESP32 Auto Watering System Initialized.");
}

void loop() {
  waterPlantLogic(); // メインの水やりロジックを呼び出し
  // 他の処理(Wi-Fi通信など)があればここに追加
  delay(1000); // ループの頻度を調整
}

// センサー読み取り関数
int readSoilMoisture(int pin) {
  int sum = 0;
  for (int i = 0; i < 10; i++) {
    sum += analogRead(pin);
    delay(10);
  }
  return sum / 10;
}

// ポンプ制御関数
void controlPump(bool state) {
  if (state && !isPumpRunning) {
    Serial.println("Pump activated.");
    digitalWrite(pumpControlPin, HIGH);
    isPumpRunning = true;
    pumpStartTime = millis();
  } else if (!state && isPumpRunning) {
    Serial.println("Pump deactivated.");
    digitalWrite(pumpControlPin, LOW);
    isPumpRunning = false;
  }
}

// 水やり判断と実行ロジック
void waterPlantLogic() {
  int currentMoisture = readSoilMoisture(soilMoistureSensorPin);
  Serial.print("Current Moisture: ");
  Serial.println(currentMoisture);

  // ポンプが稼働中でない、かつ土壌が乾燥している、かつインターバルが経過している場合
  if (!isPumpRunning && currentMoisture > dryThreshold && (millis() - lastWateringTime > wateringInterval)) {
    Serial.println("Soil is dry and interval passed. Initiating watering.");
    controlPump(true); // ポンプON
  }

  // ポンプが稼働中の場合、最大作動時間をチェック
  if (isPumpRunning && (millis() - pumpStartTime > pumpDuration)) {
    Serial.println("Pump reached max duration. Shutting off.");
    controlPump(false); // 強制OFF
    lastWateringTime = millis(); // 強制OFF後もインターバルを適用
  }
}

ESP32へのファームウェア書き込みとデバッグ

Arduino IDEでスケッチを作成したら、ESP32開発ボードに書き込みます。

  1. ボードの選択: 「ツール」->「ボード」->「ESP32 Arduino」から使用しているESP32ボード(例:「ESP32 Dev Module」)を選択します。
  2. ポートの選択: ESP32が接続されているシリアルポート(例: COMxや/dev/ttyUSBx)を選択します。
  3. 書き込み: 矢印アイコンの「書き込み」ボタンをクリックして、スケッチをESP32にアップロードします。

書き込みが完了したら、シリアルモニターを開いてデバッグ出力を確認します。Serial.println()で出力されるメッセージは、センサー値の確認、水やり判断のログ、ポンプ作動状況の把握に非常に役立ちます。

もし期待通りの動作をしない場合は、以下の点を確認してください。

  • 配線: センサーやリレーの配線が正しいか、緩みがないか。
  • 閾値: dryThresholdが適切に設定されているか。
  • リレータイプ: digitalWriteのHIGH/LOWがリレーのアクティブ状態と一致しているか。
  • 電源: ESP32とポンプの電源が安定しているか。

まとめ

本記事では、ESP32自動水やりシステムの核となるソフトウェア開発に焦点を当て、土壌水分センサーからのデータ取得、賢い水やり判断ロジックの実装、そして水中ポンプの安全な制御方法について詳細に解説しました。具体的なコード例を通じて、ESP32のADC機能の活用、millis()関数を用いた非ブロッキングタイマー処理、そしてフェイルセーフ設計の重要性を理解していただけたことでしょう。

これらのソフトウェアをESP32に実装することで、あなたの観葉植物は常に最適な水分環境で管理されるようになります。市販品では得られない、自作ならではのカスタマイズ性と深い理解が、長期的なシステムの安定稼働を支えます。

次回の記事では、このシステムをさらに進化させ、Wi-Fi接続によるリモート監視・制御、そしてデータのクラウド連携による可視化と履歴管理について解説します。これにより、外出先からでも植物の状態を確認し、必要に応じて水やりを指示できる、真のスマートホームシステムが完成します。