育苗箱ベンチレータ/時間制御

気づけばもう12月。次の春シーズンに向けて、苗づくりをする時期に差し掛かってきました。冬の間にいろいろ検討して、じっくり作りこみたいと思っていましたが、12月には絹さやが始まるんですよね。すっかり忘れていました。24年の春シーズンで作成していた育苗箱の記事も書き終わっていませんが、既に絹さや用に急遽作り始めたので、こちらを先に書いていくことにします。

仕様

とりあえず考えている仕様は以下の通り。

・温度制御は給気ファン、排気ファンの2台で箱内の空気を入れ替えることで実施
・マイコン制御で設定温度でファン起動、設定マイナス5℃くらいでファン停止
・WiFi接続/自動再接続実装
・RTC時刻合わせ(NTPサーバ同期)
・遠隔監視(ambient)
・日中は5秒間隔で温度測定、データ送信
・夜間は省電力の為、deep sleepで機能停止するが、1時間毎に温度測定、データ送信。
・電源は太陽光パネル+6Vバッテリとして、オフグリットで運用。
・ポット内の土の温度も見ておきたい

機器

他のテーマで調達したものを流用するので、新しく用意するものは無し。

機器名/メーカーメモ
制御XIAO ESP32C3 / Seeed Studioマイコン/WiFi接続
時計SD3031 / DF_RobotRTC
日中は5秒間隔、夜間は1時間間隔
温湿度計DHT22 / OSOYOO
給気ファンYDH3010C05 / YCCFAN
土壌温度DS18B20サーミスタ温度計。防水。
バッテリWP9-6A / YMT6Vバッテリ
電源単結晶シリコンソーラーパネル
/ FOHOA
5V4W。2台使用。


まだこれから寒くなる時期なので、排熱は5Vファンで十分足りると思います。それにしても、本当に全部水耕栽培や冷水塔の流用パーツですね。いろいろ組みなおして遊べるのはいいですね。組み上げた後の形は、前々回の育苗箱ベンチレータ試作2号機と全く同じになる予定です。

スケッチ

最大の難関はここですね。普通に温度制御を組むだけなら悩まないですが、今回は日中と夜間の運転間隔モードを変えようとしています。日が出ている間は5秒毎の温度監視、ファン起動停止、運転データ送信、夜間は1時間毎の温度データ測定、送信です。

RTC現在時刻と設定時間でのプログラム分離

SD3031から現在時刻を読み出して、時間単位の範囲を設定することは、水耕栽培システムで作成出来ています。そのままやっても良かったですが、折角なので分単位まで設定できるように拡張しました。これがなかなか難題でしたね。

RTCの時間読み出しは、年、月、曜日、時、分、秒、それぞれバラバラです。毎週月曜日とか、毎日朝9時とか、毎時30分に、とかは容易に設定出来ますが、例えば7時15分から15時20分にLEDを点灯する、とかいうのを直感的に設定出来ません。

一時間くらい考えたり検索したりしましたが、答えは見つからなかったので、流行りのAIに頼ることにしました。これがなかなかスゴイものでした。

Geminiの回答

googleのAI、Geminiに
”arduino のスケッチで、RTCを使って何時何分から何時何分までD10ピンのLEDをオンにするスケッチを作って”
という質問を投げて、返ってきた回答を下にそのまま貼ります。質問は原文のまま、与えた情報もこれだけです。



ArduinoでRTCを使ってLEDを特定の時間だけ点灯させるスケッチ
概要

ArduinoでRTC(リアルタイムクロック)モジュールを使用し、D10ピンに接続されたLEDを指定した時間帯に点灯させるスケッチを作成します。

準備
  • Arduinoボード: Uno、Nanoなど
  • RTCモジュール: DS3231など
  • LED: 1個
  • 抵抗: LEDの電流制限用
  • ブレッドボードまたは基板: 部品を接続するためのもの
  • ジャンパーワイヤー: 接続するためのワイヤー
  • Arduino IDE: プログラム作成環境
接続
  1. RTCモジュール: ArduinoとRTCモジュールをI2C通信で接続します。
  2. LED: D10ピンに抵抗を介してLEDのアノードを接続し、カソードをGNDに接続します。
スケッチ

C++

#include <DS3231.h>

DS3231 rtc;

// LEDを点灯させる開始時刻と終了時刻を設定
const byte hourStart = 20; // 20時
const byte minuteStart = 0; // 0分
const byte hourEnd = 22; // 22時
const byte minuteEnd = 0; // 0分

void setup() {
  rtc.begin();
  pinMode(10, OUTPUT);
}

void loop() {
  DateTime now = rtc.now();

  // 設定した時間帯の場合、LEDを点灯
  if (now.hour() >= hourStart && now.hour() < hourEnd) {
    if (now.hour() == hourStart && now.minute() < minuteStart) {
      // 開始時刻より前
    } else if (now.hour() == hourEnd && now.minute() >= minuteEnd) {
      // 終了時刻以降
    } else {
      digitalWrite(10, HIGH);
    }
  } else {
    digitalWrite(10, LOW);
  }
}

スケッチの説明
  1. ライブラリのインクルード: DS3231.hライブラリをインクルードします。
  2. オブジェクトの作成: RTCモジュールのオブジェクトrtcを作成します。
  3. 定数の定義: LEDを点灯させる開始時刻と終了時刻を定数で定義します。
  4. setup関数:
    • rtc.begin(): RTCモジュールを初期化します。
    • pinMode(10, OUTPUT): D10ピンを出力モードに設定します。
  5. loop関数:
    • DateTime now = rtc.now(): 現在時刻を取得します。
    • if文で、現在時刻が設定した時間帯かどうかを判断します。
    • 時間帯内であれば、LEDを点灯させ、そうでなければ消灯させます。
    • 開始時刻より前、または終了時刻以降の場合は、LEDを消灯します。
注意点
  • 時間設定: RTCモジュールに正しい時刻を設定する必要があります。
  • ライブラリ: 使用するRTCモジュールに合わせて適切なライブラリを使用してください。
  • 時間帯: 時刻の比較の際に、分や秒も考慮する必要がある場合があります。
  • 電源: RTCモジュールは、Arduinoの電源が切れても時刻を保持できるよう、別電源を用意することをおすすめします。
拡張
  • 曜日による制御: now.dayOfTheWeek()を使って曜日を取得し、曜日によって点灯時間を変えることができます。
  • 複数のLED: 複数のLEDを異なる時間帯に点灯させることができます。
  • PWM制御: analogWrite()関数を使ってLEDの明るさを調整できます。
  • 外部入力: ボタンやセンサの入力によってLEDの点灯を制御できます。

このスケッチをベースに、様々な機能を追加して、より複雑な制御を実現することができます。

その他
  • RTCモジュールの種類: DS3231以外にも、さまざまなRTCモジュールがあります。
  • I2Cアドレス: RTCモジュールのI2Cアドレスが異なる場合があります。
  • Arduinoのバージョン: Arduinoのバージョンによって、ライブラリや関数の使い方が異なる場合があります。

ご自身の環境に合わせて、スケッチを調整してご利用ください。

何かご不明な点があれば、お気軽にご質問ください。

以下、より詳細な解説を求める場合の質問例です。

  • 特定のRTCモジュールを使った場合の注意点
  • PWM制御による明るさの調整方法
  • 曜日や外部入力による制御の具体的な実装方法
  • スケッチのエラーが発生した場合の対処法

これらの質問にご回答することで、より最適なスケッチを作成することができます。



Arduinoで、と指定したことと、RTCの型番は与えなかったので、適当に選んできたRTCでのスケッチになっており、XIAO ESP32C3でRTCがSD3031のこちらの環境にはそのまま流用出来ませんが、大事な考え方のヒントがスケッチの説明の中に出ています。

5.loop関数:
if文で、現在時刻が設定した時間帯かどうかを判断します。

スケッチ例として以下を挙げてくれています。
if (now.hour() >= hourStart && now.hour() < hourEnd) { if (now.hour() == hourStart && now.minute() < minuteStart) { // 開始時刻より前 } else if (now.hour() == hourEnd && now.minute() >= minuteEnd) {
// 終了時刻以降
} else {
digitalWrite(10, HIGH);
}
} else {
digitalWrite(10, LOW);
}

これこれ、こういうのが欲しかったんですよね。めっちゃ詳しい人が近くにいるわけではないので、大体検索で似たようなのを探しますが、なかなか見つからないです。こんな感じのことをやりたいけど、どうしたらいい、といった質問がwebでAIに出来るようになるとは、スゴイ時代になったもんです。

現在時刻の判定スケッチ作成

最初はGeminiの提案そのままでやってみました。SD3031用に関数を変えています。

void loop() {
  sTimeData_t sTime;  //now time
  sTime = rtc.getRTCTime();

  if (sTime.hour >= hourStart && sTime.hour <= hourEnd){
    if (sTime.hour == hourStart && sTime.minute < minuteStart){
    } else if (sTime.hour == hourEnd && sTime.minute >= minuteEnd){
    } else {
      digitalWrite(D10, HIGH);
    }
  } else {
    digitalWrite(D10, LOW);    
  }
}

この形だと、”分”のところの設定に関数が無いので、ESP32には無視されたようです。hourの設定は機能しますが、minuteは機能しないので設定の分の前でも後ろでも関係なく、hourで範囲内ならLEDが点灯するし、hourで範囲外なら消灯する結果でした。

これを改造していきます。空欄だった分の設定での関数を入れました。

void loop() {
  sTimeData_t sTime;  //now time
  sTime = rtc.getRTCTime();

  if (sTime.hour >= hourStart && sTime.hour <= hourEnd){
    if (sTime.hour == hourStart && sTime.minute < minuteStart){
      digitalWrite(D10, LOW);
      digitalWrite(D8, HIGH);
    } else if (sTime.hour == hourEnd && sTime.minute >= minuteEnd){
      digitalWrite(D10, LOW);
      digitalWrite(D8, HIGH);
      delay(100);
    } else {
      digitalWrite(D8, LOW);
      digitalWrite(D10, HIGH);
    }
  } else {
    digitalWrite(D10, LOW);
    digitalWrite(D8, HIGH);
    delay(500);
    digitalWrite(D8,LOW);
  }
}

建付けは、
1.時(hour)単位で範囲を設定。現在時間の時(hour)が設定時間の時(hour)の範囲内なら、LED(D10)点灯。
2.現在時間と設定開始時間の時(hour)が同じ場合、設定の開始時間(分)より現在時間が早ければ、LED(D10)消灯。代わりに別のLED(D8)点灯
3.現在時間と設定終了時間の時(hour)が同じ場合、設定の終了時間(分)より現在時間が遅ければ、LED(D10)消灯。代わりに別のLED(D8)点灯
ですね。

これはちゃんと機能しました。ただこの形だと、設定時間と現在時間の時(hour)が同じ場合の開始時と、終了時、それ以外の時のそれぞれに重複した内容の制御スケッチを入れることになり、スケッチ全体が長大なものになります。

そこで、それぞれの場合での時刻判定をbool関数を使って作り、最終的に一つの判定とすることにしました。時(hour)単位での設定範囲内判定(timeJudge1)、時(hour)が現在時刻と設定時刻で同じ場合の開始分(minute)判定(timeJudge2)、時(hour)が現在時刻と設定時刻で同じ場合の終了分(minute)判定(timeJuge3)の3つです。

void loop() {
  sTimeData_t sTime;  //現在時刻
  sTime = rtc.getRTCTime();

  bool timeJudge1 = true; //"時"単位での判定
  bool timeJudge2 = true; //同一"時"での開始"分"判定
  bool timeJudge3 = true; //同一"時"での終了"分"判定

  if (sTime.hour >= hourStart && sTime.hour <= hourEnd){
    timeJudge1 = true;
  } else {
    timeJudge1 = false;
  }

  if (sTime.hour == hourStart && sTime.minute < minuteStart){
    timeJudge2 = false;
  } else if (sTime.hour == hourStart && sTime.minute >= minuteStart){
    timeJudge2 = true;
  }

  if (sTime.hour == hourEnd && sTime.minute > minuteEnd){
    timeJudge3 = false;
  } else if (sTime.hour == hourEnd && sTime.minute <= minuteEnd){
    timeJudge3 = true;
  }

  if (timeJudge1 == true && timeJudge2 == true && timeJudge3 == true){
    digitalWrite(D10, HIGH);
    digitalWrite(D8, LOW);
  } else {
    digitalWrite(D10, LOW);
    digitalWrite(D8, HIGH);
  }  
}

timeJudge1,2,3はtrueで初期化します。falseで初期化すると、例えば、時(hour)単位で範囲内の場合(設定時間8時から16時とした場合の、現在時間10時とか)、timeJudge2やtimeJudge3がfalseのままなので、最終的な時間判定で全てtrueになりません。

最終的な時間判定は、
if (timeJudge1 == true && timeJudge2 == true && timeJudge3 == true){
としているので、timeJudge1,2,3全てがtrueとなった時を設定時間範囲内としています。

動作の様子は下の通り。分単位なので最初から最後まで入れると2分を超える動画になってしまいます。短くする為、設定時間内に入る時と、設定時間外へ出る時の2つに分けました。緑LED点灯が設定時間内に入ったこと、黄LED点灯が設定時間外に出ていることを表しています。

動作状況/右上の黄LED点灯が設定時間範囲外、緑LED点灯で設定時間範囲内に入った
動作状況/設定時間範囲内の緑LED点灯から、範囲外に移って黄LED点灯

スケッチ全体像

記録の為、スケッチの全体像を載せておきます。WiFi接続、自動WiFi再接続、RTC時刻合わせ(NTPサーバ同期)、ambient接続が入っています。

//SD3031(RTC)設定
#include <DFRobot_SD3031.h>
#include <Wire.h>
DFRobot_SD3031 rtc;  

#include <time.h>

//WiFi設定
#include <WiFi.h> 
const char SSID[] = "*******";
const char PASSWORD[] = "**********";
WiFiClient client;

//DHT22(温湿度計)設定
#include <DHT.h> 
#include <DHT_U.h>
#include <Adafruit_Sensor.h>
#define DHT_pin D0  //DHTピンはD0とする
#define DHTTYPE DHT22 //使用するDHTはDHT22を指定(別型式にDHT11がある)
DHT dht(DHT_pin, DHTTYPE);

//ambient設定
#include <Ambient.h> 
unsigned int channelId = *******; 
const char* writeKey = "*******************";
Ambient ambient;

//時間設定
const byte hourStart = 18; //Set start time in hour
const byte minuteStart = 00; //Set start time in minute
const byte hourEnd = 18; //Set End time in hour
const byte minuteEnd = 1; //Set End time in minute


void setup() {
  unsigned long start_time = millis(); //稼働時間。deep sleepで使用予定
  Serial.begin(115200); //シリアル通信開始
  struct tm timeInfo;
  rtc.begin();
  dht.begin();

  //ピンモード指定。使わないところは無くていいけど、一覧代わり。
  pinMode(DHT_pin, INPUT);       //D0 --DHT sensor, ambient temp/humidity
  pinMode(B1, OUTPUT);           //D1 --cooling fan(intake, outtake)
                                 //D2 --vacant
                                 //D3 --vacant
                                 //D4 --vacant
                                 //D5 --vacant
                                 //D6 --vacant
                                 //D7 --vacant
  pinMode(D8, OUTPUT);           //D8 --Signal, out of setting timezone
                                 //D9 --vacant/caution when you use as LED pin
  pinMode(D10, OUTPUT);          //D10 --Signal, inside setting timezone
 
//WiFi接続、自動再接続
  bool connecting = true;  //WiFi connection, true = connection in progress, false = connected
  WiFi.begin(SSID, PASSWORD);
  while (connecting){
    Serial.print("WiFi connecting to the AP: ");
    Serial.println(SSID);
    unsigned long connect_start = millis();
    while (WiFi.status() != WL_CONNECTED && connect_start + 10000 > millis()){
      Serial.print (".");
      delay(500);
    }
    if (WiFi.status() == WL_CONNECTED){
      connecting = false;
      delay(500);
    } else {
      Serial.println("retry");
      delay(500);
      esp_deep_sleep(500);
    }
  }

  Serial.println("connected.");
  Serial.print("IP adress:");
  Serial.println(WiFi.localIP());

  ambient.begin(channelId, writeKey, &client);

//RTC時刻合わせ。WiFi接続時に、毎回NTPサーバ同期します。
  configTzTime("JST-9", "ntp.nict.jp","ntp.jst.mfeed.ad.jp");
  getLocalTime(&timeInfo);

  rtc.setTime(timeInfo.tm_year +1900, timeInfo.tm_mon +1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
}

void loop() {
  sTimeData_t sTime;  //now time
  sTime = rtc.getRTCTime();

    Serial.print(sTime.year, DEC);  //year
    Serial.print('/');
    Serial.print(sTime.month, DEC);  //month
    Serial.print('/');
    Serial.print(sTime.day, DEC);  //day
    Serial.print(" (");
    Serial.print(sTime.week);  //week
    Serial.print(") ");
    Serial.print(sTime.hour, DEC);  //hour
    Serial.print(':');
    Serial.print(sTime.minute, DEC);  //minute
    Serial.print(':');
    Serial.print(sTime.second, DEC);  //second
    Serial.println(' ');

    delay(1000);

  bool timeJudge1 = true; //"時(hour)"単位の時刻判定
  bool timeJudge2 = true; //現在と設定で"時"が同じ場合の、開始"分"判定
  bool timeJudge3 = true; //現在と設定で"時"が同じ場合の、終了"分"判定

  if (sTime.hour >= hourStart && sTime.hour <= hourEnd){
    timeJudge1 = true;
  } else {
    timeJudge1 = false;
  }

  if (sTime.hour == hourStart && sTime.minute < minuteStart){
    timeJudge2 = false;
  } else if (sTime.hour == hourStart && sTime.minute >= minuteStart){
    timeJudge2 = true;
  }

  if (sTime.hour == hourEnd && sTime.minute > minuteEnd){
    timeJudge3 = false;
  } else if (sTime.hour == hourEnd && sTime.minute <= minuteEnd){
    timeJudge3 = true;
  }

//timeJudge1,2,3全てがtrueとなる場合、設定時間範囲内と判定
  if (timeJudge1 == true && timeJudge2 == true && timeJudge3 == true){
    digitalWrite(D10, HIGH);
    digitalWrite(D8, LOW);
  } else {
    digitalWrite(D10, LOW);
    digitalWrite(D8, HIGH);
  }  
}

長くなってしまったので、とりあえずここまで。次回はブレッドボードでの基板試作と試作機組み立てかな。

それはそうと、日時は全部秒に換算すればいいんですよね。ここまでやった後でなんですが。

コメントを残す

しろすけ技術研究所
主任研究員

こんにちは。市民農園で野菜作りをしているしろすけです。極小規模の栽培管理システム開発をいつかやってみたいと思っていました。2024年から育苗ケース給排気温調や、水耕栽培管理システムの試作を電子工作で始めました。想像以上に検討することがあったので、記録を残すことにします。バイク(SR400)と耕運機(FV200)の整備記録もついでに記録に残しておきます。


Twitter/X

https://twitter.com/L4M3SsppilsCQ8C

しろすけ技術研究所をもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む