水管理デバイスの試作【農業IoT・AgriTechデバイス開発#9】

IoT

はじめに

計画通りに進めていても記事の投稿にラグがある可能性がありますが、悪しからず。
まあ、計画通りに進められていない場合もありますが・・・

前回の記事では、水管理デバイスを構想してみました。

今回は水管理デバイスの試作に取り組んでみようかと思います。

計画のうち、下計画の赤枠部分を進めていきます。全体計画としては遅れに遅れています。

水管理デバイスの構成

前回考えたデバイス構想から若干の変更があります。

変更後の構成は下のようにしました。主な変更部は赤い部分のM5Stack用水分センサ付きユニットへ変更したことと土壌水分量センサーは廃止したことです。

M5Stack用水分測定センサ付き給水ポンプユニットは以下のものです。便利ですね。

手を抜いてるだけでは?と思う方もいるかもしれませんが、使えるものは使った方がいいのです(迫真)

デバイスの外観

今回は以下のような外観です。基本は今まで作製したものを流用しています。貯水タンクは一旦500mLペットボトルにしています。必要に応じてサイズ変更や水道直結にすることも考えています。

もう少しコンパクトにしたいと思っているのですが、ちょうど良いサイズの防水ケースがなかなかないんですよね。3Dプリンタで作ろうかとも思いましたが、防水性は流石に期待できなさそうですね。

ちなみにM5Stack basic v2.6にはPORT Bがついていないので、以下のようなものを購入して使用しました。

防水ケースはこちらを自分で穴を加工したりして使っています。

配線同士を接続する際にはこのようなコネクターを使っています。

スケッチ

今回のスケッチは以下のようにしました。いろいろ凝りすぎて時間がかかりました。そして、いつも通り詳細はコードの中にコメントで書いていますが、書きすぎてものすごく見づらいですね。

//***水分測定用センサ付き給水ポンプユニットについて***
// Port B に接続「水分センサ=GPIO36/ポンプ制御=GPIO26)
// しきい値は実環境にて調整する必要あり

#include <M5Unified.h> //M5Stack本体の機能を扱うためのライブラリ
#include <M5UnitUnified.h> //M5Stackに接続して使うユニット(ここだとセンサー類)を扱うためのライブラリ
#include <M5UnitUnifiedENV.h> //注意:M5_ENV.hだと見つからないエラーが出ました

// UnitUnified
m5::unit::UnitUnified Units;

// ENVⅢ
m5::unit::UnitENV3 env;

//以下の値でコンパイル後に変更しないものは予めconstexprで固定する
constexpr int PIN_MOIST  = 36;   // 水分センサ(ADC, PortB)
constexpr int PIN_PUMP   = 26;   // ポンプ制御(OUTPUT, PortB)

// ---- 調整箇所ここから ----
constexpr int DRY_THR = 2000;  // これより大きい=乾いている状態
constexpr int WET_THR = 1800;  // これより小さい=湿っている状態
//※DRY_THRとWET_THRの間に幅を持たせてチャタリング防止
constexpr uint32_t PUMP_ON_TIME   = 2000;  // 1回の給水時間[ms]
constexpr uint32_t SETTLE_TIME    = 8000;  // 給水後浸透するまでの待機時間[ms]
constexpr uint32_t LOOP_DELAYTIME = 1000;   // ループ間隔[ms] 間隔が短すぎるととENVⅢがうまく機能しないので注意
// ---- 調整箇所ここまで ----

bool pumpOn = false; //ポンプのOn/Off状態、初期はfalseにしておく
uint32_t pumpChangeTime = 0; //状態を切り替えた時刻を保存する変数、intでもOKだが、メモリ削減のため(らしいです・・・)

void pumpWrite(bool on) {
  pumpOn = on; //ポンプの状態を記憶
  digitalWrite(PIN_PUMP, on ? HIGH : LOW);
  //上記は三項演算子で書いていますが、意味としては
  //if (on) digitalWrite(PIN_PUMP, HIGH);
  //else
  //digitalWrite(PIN_PUMP, LOW);
  //と同じです
  pumpChangeTime = millis(); //電源ONからの時間を記憶する変数
}

void setup() {
  auto cfg = M5.config();
  M5.begin(cfg);

  //I2C(Wire)を準備・開始する
  //自動でSDAとSCLのPIN番号を取得
  //毎回確認するの面倒だなと思って生成AIに聞きました・・・
  auto sda = M5.getPin(m5::pin_name_t::port_a_sda);
  auto scl = M5.getPin(m5::pin_name_t::port_a_scl);
  Wire.begin(sda, scl, 400000); //400000=400kHzの高速通信

  //ENV3はUnitUnified経由で登録→begin(env.begin()は呼ばない)
  //Units.add(Unitsにenvを追加して、通信はWireを使う)がtrueならUnitsを初期化する
  //env=ENVⅢのセンサーをまとめて扱うイメージ
  bool ok = Units.add(env, Wire) && Units.begin();


  pinMode(PIN_MOIST, INPUT); //水分センサーの読み取り値をPIN_MOIST=36から読む=INPUTする
  pinMode(PIN_PUMP, OUTPUT); //ポンプ制御のための信号をPIN_PUMPから出力=OUTPUTする
  pumpWrite(false); //初期はポンプをオフ=false

  //M5Stackの画面設定(なくても動作可能、あると視覚的に分かりやすい)
  M5.Display.setTextSize(2);
  M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);
  M5.Display.fillScreen(TFT_BLACK);
  M5.Display.setCursor(8, 8);
  M5.Display.println("If you need, adjust DRY/WET thresholds.");

  //初期化失敗を表示する
  if (!ok) {
    M5.Display.println("ENV3 initial failed!");
  }
}

void loop() {
  int raw = analogRead(PIN_MOIST); //水分量を取得

  //ポンプの状態と水分量に応じて、ポンプを動作させる
  if (!pumpOn && raw > DRY_THR) {
    pumpWrite(true);
  } else if (pumpOn && raw < WET_THR) {
    pumpWrite(false);
  }

  //ポンプがオン状態かつポンプがPUMP_ON_TIMEで指定した時間以上動作していたら強制的に止める
  if (pumpOn && millis() - pumpChangeTime >= PUMP_ON_TIME) {
    pumpWrite(false);
  }
  //給水した水が浸透するのを待つ
  if (!pumpOn && (millis() - pumpChangeTime) < SETTLE_TIME) {
    // 何もしないで水の浸透を待つ
  }

  //ENVⅢをアップデートする
  Units.update(); // ENVⅢは毎ループupdateする必要があるようです

  //温度・湿度・気圧(念の為、0.0で初期化しておく)
  float temp = 0.0;
  float humi = 0.0;
  float pres = 0.0;

  //ENVⅢで温度・湿度・気圧を取得する
  //※updatedされたことを確認してから取得する
  if (env.sht30.updated()) {
    temp = env.sht30.temperature();
    humi = env.sht30.humidity();
  }
  if (env.qmp6988.updated()) {
    pres = env.qmp6988.pressure();
  }

  //水分量の生データを表示(調整の際に使用)する
  M5.Display.setCursor(8, 100);
  M5.Display.printf("RAW: %4d   \n", raw);
  //水分量を%表示する
  int moistPct = map(raw, 4095, 0, 0, 100); //センサーの測定値0~4095を0~100に変換
  moistPct = constrain(moistPct, 0, 100); //センサーの測定値が0~4095から外れてしまった場合に強制的に0~100の間にする
  //水分量を表示する
  M5.Display.setCursor(8, 124);
  M5.Display.printf("Moisture: %3d %%   \n", moistPct);
  //ポンプのオン・オフを表示する
  M5.Display.setCursor(8, 148);
  M5.Display.printf("Pump: %s      \n", pumpOn ? "ON " : "OFF");

  //温度・湿度・気圧を表示する
  M5.Display.setCursor(8, 172);
  M5.Display.printf("Temp: %.0f 'C\n", temp);
  M5.Display.setCursor(8, 196);
  M5.Display.printf("Humi: %.0f %%\n", humi);
  M5.Display.setCursor(8, 220);
  M5.Display.printf("Pres: %.0f hPa\n", pres/100);

  delay(LOOP_DELAYTIME); //間隔が短すぎるとENVⅢでうまく値が取得できす、温度・湿度・気圧がnanになってしまうので注意
}

動作結果

 正常に動作すると、M5Stackの画面には以下のように表示されるはずです。ポンプは設定した閾値に従って動くはずです。今回は水分センサー部分を水に浸してみたのが、左側ポンプOFFの状態です。水分量(Moisture)が少しおかしいですね・・・今後要修正ですが、今は動いているので一旦無視しました。

最後に

今回は水管理デバイスの試作を進めました。まだまだ改善点・改良点はありますが、ひとまず形になったのではないかと思います。必要最低限の機能が整ったら、外装をおしゃれにしてみましょうか。

それでは今回はこの辺で。

おすすめの書籍

 もっと詳しくM5Stackについて知りたい!という方はこちらの書籍がおすすめです。とても詳しく載っていてわかりやすいです。

とりあえず何か作ってみたい!という方にはこちらの書籍がおすすめです。書いてある通りにやるだけでお手軽にIoTデバイスを作ることができます。

Arduinoで電子工作したい方にはこちらもおすすめです!Arduinoでどんなことができるのか、一目でわかる一冊になっています。

おすすめ記事

相変わらず、本ブログでは資格系の記事が人気です。ぜひご覧ください。

コメント

タイトルとURLをコピーしました