M5 / プログラミング

M5Atomと100均加湿器で作る自動苔水やりシステム

こんにちは、Lineaです

久しぶりの更新となってしまいました。

半年ほど前から苔を育てているのですが、1日数回の霧吹きというのをつい忘れてしまいます。
そのため、水不足で何度か苔が茶色くなったり黒ずんだりしてダメにしてしまいました。

以前、M5Atomでガーデニングの水分管理を行ってみましたが、その延長で小型の加湿器を使って自動水やりができるのではないかと考えて作ってみました。

ハードウェア

水やりシステムで使用したものは以下のとおりです。M5Atomはすでに持っていたのでセンサ代と加湿器、部品代で1500円ぐらいです。

システムの配線は下の図のとおりです

湿度センサとM5はGroveで接続し、計測した湿度値に応じてUSBコネクタの5Vラインを簡単なトランジスタ回路でスイッチングして加湿器への電力のON/OFFを制御します。

湿度センサにはM5シリーズ向けのセンサユニットを使用しています。画像では以前買ったM5StickC用のHATタイプをAtomに接続したものを本体ごと水槽に入れていますが、流石に湿度でAtom本体が逝きそうなので後で替えています。ユニット化されてGroveケーブルで回路本体と分離できるので、もし壊れてもセンサユニットの替えがきくので安心です。

加湿器にはダイソーの300円USB加湿器を使用しました。ここで重要なのがスイッチやタイマー機能がついておらず、USB端子に電流が流れるだけで動作する単純なものを使用することでした。M5から加湿器に対して制御できるのが電流のON/OFFだけなので、余計な機能がついていると制御できない可能性があるからです。
最近の100均製品も進化していてスイッチやタイマーがついているものが多く、意外と単純なものを探すのに苦労しました。作り始めたのが4月頭だったので加湿器のシーズンが終わっていたこともあって1週間ほど100均めぐりをしてやっと見つけたのがダイソーの300円加湿器でしたので、同じこと考えている人はご参考ください。

加湿器への電流のオンオフはM5のピンから直接流すのは流石に怖かったので簡単なスイッチング回路を組んでいます。抵抗計算はいい加減ですがUSB端子に150mAぐらい流せるように計算してみました。とりあえず加湿器は元気に動いてくれているので大丈夫そうです。

プログラム

プログラムは以前つくった水分計測システムを元にしています。
動作としては5分おきに湿度測定、加湿器の動作決定、ディープスリープを繰り返します。湿度値などはデータベースサービスのAmbientへ送信しています

#include <M5Atom.h>
#include <WiFi.h>
#include <Ambient.h>
#include "driver/rtc_io.h"
#include "SHT3X.h"
#include "key.h"

//define
#define SLEEP_MIN(min) min*60000000ULL
//GRB?
#define LED_PINK 0x55FFAA
#define LED_GREEN 0xFF0022
#define LED_BLUE 0x0022FF
#define LED_YELLOW 0xAAAA00
#define LED_RED 0x00FF22
#define LED_OFF 0x000000
//明るさ
const uint8_t kLEDBright = 15;
//pin定義
const gpio_num_t kPowerPin = GPIO_NUM_33; //電力供給
//スリープ時間
const uint64_t sleepMin = 5;

//加湿稼働閾値
const float minHumidity = 70;
const float targetHumidity = 90;
//static変数
WiFiClient client;
Ambient ambient;

//温湿度センサ
const uint8_t kI2CSclPin = 32;
const uint8_t kI2CSdaPin = 26;
SHT3X sht3x = SHT3X(kI2CSclPin,kI2CSdaPin);

//保存変数
RTC_DATA_ATTR bool humidifierActive = false;
RTC_DATA_ATTR float humidityInteglal = 0;

//functions

//加湿ONOFF
void sethHumidifier(bool sw)
{
  humidifierActive = sw;
  rtc_gpio_set_level(kPowerPin, sw ? HIGH:LOW);
}

//測定
void measure(){

  //温湿度計測
  if(sht3x.get() ==0){
    //ambient セット
    float humidity = sht3x.humidity;
    ambient.set(7,sht3x.cTemp);
    ambient.set(8,humidity);
    Serial.printf("temp:%.2f \n",sht3x.cTemp);
    Serial.printf("humidty:%.2f \n",humidity);

    //湿度によってLED色変える
    CRGB col = LED_GREEN;
    #ifdef false
    //カラー無段階変化
    fract8 frac = humidity / 100.f * UINT8_MAX;
    col = col.lerp8(CRGB::Red,frac);
    Serial.printf("humid color R:%d, G:%d, B:%d\n",col.r,col.g,col.b);
    #else
    //段階カラー
    if(humidity < targetHumidity){
      col = LED_YELLOW;
      if(humidity < minHumidity)
      {
        col = LED_RED;
      }
    }
    #endif

    M5.dis.fillpix(col);

    //加湿器稼働
    humidityInteglal = min(max((humidityInteglal + (targetHumidity - humidity))*0.5f,-10.f),10.f);
    sethHumidifier(humidityInteglal > 0);
    ambient.set(6,humidifierActive);
    Serial.printf("humidifier:%d \n",humidifierActive);
  }

  //Ambient送信
  ambient.send();  
}

//Wifi接続
void connectNet()
{
  // WiFi接続
  Serial.printf("Connecting to %s ", wifi_ssid);
  WiFi.begin(wifi_ssid, wifi_key);
  while (WiFi.status() != WL_CONNECTED) {
    M5.dis.fillpix(LED_BLUE);
    delay(250);
    M5.dis.fillpix(LED_OFF);
    delay(250);
    Serial.print(".");
  }
  Serial.println("CONNECTED");

  //Ambient setup
  ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化
}

//カウントダウン
void countDown(const char *dispTxt,const int sec)
{
  int count = sec;
  while (count >= 0)
  {
    count--;
    M5.dis.fillpix(LED_PINK);
    //1秒待つ
    delay(500);
    M5.dis.fillpix(LED_OFF);
    delay(500);
  }
}

void setup() {
  Serial.println("Setup");

  //M5スタート
  M5.begin(true,false,true);

  //ピンをセットアップ
  rtc_gpio_init(kPowerPin);
  rtc_gpio_set_direction(kPowerPin,RTC_GPIO_MODE_OUTPUT_ONLY);
  //rtc_gpio_set_level(kPowerPin,LOW);

  M5.dis.setBrightness(kLEDBright);

  //Wifi接続はクロック上げたほうがいいらしい?CPUクロックを戻す
  setCpuFrequencyMhz(160);

  //ネットワーク系初期化
  connectNet();

  //CPUクロックを下げる 80より下はWifi使えないらしい
  setCpuFrequencyMhz(80);
}

void loop() {
  //計測カウントダウン
  countDown("measure",3);

  //計測
  measure();
  
  //wait
  delay(100);

  //wifiオフ
  WiFi.disconnect();
  //pinもオフにする
  //pinMode(kPowerPin,DISABLED);

  //スリープカウントダウン
  //countDown("sleep",3);
  //LED停止
  //M5.dis.stop();

  //スリープ
  Serial.println("Enter sleep");
  Serial.printf("sleep time: %ld \n",SLEEP_MIN(sleepMin));

  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH,ESP_PD_OPTION_ON);
  esp_sleep_enable_timer_wakeup(SLEEP_MIN(sleepMin));
  esp_deep_sleep_start();
}

Wifiやデータベース(Ambient)へのアクセス情報はmainに書くとGit管理などでうっかり上げてしまいそうなので、別途”key.h”と別ヘッダに置くようにしました。

#ifndef __key_H
#define __key_H

//wifi情報
const char wifi_ssid[32]="SSID";
const char wifi_key[64]="Wifi_Pass";
//ambient情報
const unsigned int channelId = 00000; // AmbientのチャネルID
const char* writeKey = "Write_Key"; // ライトキー

#endif

温湿度センサで使用しているライブラリ「SHT3X」については過去記事で導入の解説をしているので参考にしてください

要所解説

湿度計測

//温湿度センサ定義
const uint8_t kI2CSclPin = 32;
const uint8_t kI2CSdaPin = 26;
SHT3X sht3x = SHT3X(kI2CSclPin,kI2CSdaPin);

//温湿度計測
void measure(){
  if(sht3x.get() ==0){
    //計測完了時の動作
    float humidity = sht3x.humidity;
    }
}

湿度センサの使い方は過去記事とほぼ同様です。今回はSHT3Xのオブジェクトを作成時にGroveのデータピン32,26を指定します。計測はSHT3X::get()を呼び出します。このとき返り値0であれば成功ですのでif文などで分岐して計測時の動作を記述します。測定値はオブジェクト内に保存されているので必要な値を取り出します。

加湿器の動作

//加湿稼働閾値
const float targetHumidity = 90;

//加湿器稼働
    humidityInteglal = min(max((humidityInteglal + (targetHumidity - humidity))*0.5f,-10.f),10.f);
    sethHumidifier(humidityInteglal > 0);
    ambient.set(6,humidifierActive);
    Serial.printf("humidifier:%d \n",humidifierActive);

加湿器の稼働の判断は目標の湿度値との差分をもとにオンオフの判定を行っています。単純にその時の湿度値で判定すると値の変化に過敏になりすぎるので前回の計算値との移動平均の値で判定をしてみています。後述する結果でもわかりますが、それでもオンオフを交互に繰り返すような結果なので計算方法は今後も考えていきたいです。

//保存変数
RTC_DATA_ATTR bool humidifierActive = false;
RTC_DATA_ATTR float humidityInteglal = 0;

//加湿ONOFF
void sethHumidifier(bool sw)
{
  humidifierActive = sw;
  rtc_gpio_set_level(kPowerPin, sw ? HIGH:LOW);
}

void setup() {
  //ピンをセットアップ
  rtc_gpio_init(kPowerPin);
  rtc_gpio_set_direction(kPowerPin,RTC_GPIO_MODE_OUTPUT_ONLY);

  //スリープ時にピン出力し続ける設定
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH,ESP_PD_OPTION_ON);
}

ディープスリープを使用しているので、加湿器の稼働判定用の変数や出力ピンの動作をスリープ中でも継続する必要があります。
スリープ中に変数を保存するには変数の定義の先頭にRTC_DATA_ATTRを設定することで保存されます。
ピンについては通常のピン入出力関数ではなく、rtc_gpio_で始まる関数を使用する必要があります。
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH,ESP_PD_OPTION_ON)を呼ぶことでスリープ時にrtc_gpio_~で設定したピンの動作が継続できるようになります。

結果

結果はAmbientへ送信しているので、現在IoTの部屋でグラフとして確認することができます。

上は4/30の結果ですが、目標の90%を行ったり来たりしています。もうちょっとなだらかな結果を期待していたのですが、半開きのテラリウム水槽だと5分加湿器を止めるだけで結構水分が逃げていくみたいですね。
16時頃から加湿器用の水が切れたみたいで湿度が下がりっぱなしですが、稼働しないとこれだけ下がるということで効果はしっかりありそうです。

ゆくゆくは加湿器を単純にオンオフではなく、PWM的に間欠動作させて加湿量をコントロールしてよりフラットなグラフを目指すのも考えています。
でもその前に苔ルームに照明が欲しくなってきたのでそっちですかね

それではまた!

投稿者

Linea
本業はゲームプログラマー プログラミングやIT系の記事を書いていきます アイコンは愛車のシトロエン DS3です

コメントを残す

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

CAPTCHA