水耕栽培用自動給気給水機QLi-kun2/フロート式液面計

表面型静電容量式の液面計の表面加工が肥料成分と反応してしまい、使用不能になったことに衝撃を受け、液面計選びの優先項目であった駆動部分なし、を早々に撤回する事態となりました。駆動部分への根の絡みつきは、月一回くらい確認、清掃のメンテナンスをすればいいか、と思い直し、フロート液面計を探すことにしました。

参考にしたのは、アイエンターさんの記事、

水位センサー(レベルスイッチ)3種類をArduino UNOで使ってみた
https://qiita.com/s_fujii/items/45f4eab31df13367a57c

です。海水水槽の液面検知をやっていて、無機塩肥料を溶かしている水耕栽培水槽と環境が似ていると思いました。採用されているフロート液面計も、接液部の材質がポリプロピレンで、確かに塩には耐えそうです。

HL-S1A/Watty
HL-S1A/Watty

メーカーのサイトには最大電圧DC100Vとあって、5V程度の低電圧でもいけるかうっすら不安がありましたが、全く問題なかったです。写真はすでに3か月使用したものなので、うっすら汚れが付いています。

Watty Sensor ワッティー フロートスイッチのご紹介

フランジ下27mmなので、水槽の蓋に据え付けて水面上限を検知するのにも丁度いいサイズですね。

スケッチはanalogReadでHL-S1Aの信号を読むだけです。アナログセンサーはスケッチがシンプルになるのがいいですね。

int LI1 = 0;  // UPPER_WaterLevel
int LI1_PIN = A0;
void setup() {
  Serial.begin(115200);
  pinMode(LI1_PIN, INPUT);  //A0
void loop() {
    LI1 = analogRead(LI1_PIN);  //Check UPPER WaterLevel
    Serial.print("UPPER Water Level = ");  //UPPER Water Level
    Serial.println(LI1, DEC);
    delay(1000);
}

回路は、アイエンターさんの記事そのままで、10kΩの半固定抵抗全開で使ってみています。少し触った感じだと微調整は要らなそうなので、10kΩの固定抵抗でプルアップに変えようかな。

水槽蓋への固定は、長さ調整した塩ビパイプと、穴を空けたエンドキャップを使用しました。

組み立て前
左がフロートセンサー固定用、右が上部の蓋用
組み立て後

手作り感満載ですね。高さ調整は、高さ調整環の位置を決めて固定し、塩ビパイプの径に合わせた水槽の蓋の穴に調整環を乗せて行います。イメージとしては下の図のような感じ。

下は固定用の塩ビエンドキャップを付けて試運転した時の様子です。

・RTCの時間制御で給気ブロア起動(代替として青色LEDが点灯)
・液面が下がって設定液面に到達したことを検知
・給水ポンプ起動(代替の黄色LEDが点灯)
・給水が進んで液面が上がり、上限の液面に到達を検知
・給水ポンプ停止(代替の黄色LEDが消灯)

試運転での動作良好。analogReadの数字はオンで4000台でした。オンオフだけなので数字の変換は不要で、analogRead値で3500以上を液面検知としました。

自動給気給水機QLi-kun2へインストール

スケッチは使用するanalogピンを指定し、analogRead()を追加するくらいなので、悩むところはないですね。ambientに送るデータをanalogReadそのままでもよかったですが、水の減少を検知して液面計がONになった時を80、水が入って規定量以上になったら100で送信するようにしました。実際は上も下も92%台ですね。

液面計LI1、給水ポンプP1、RTCと給気ブロアB1を入れたスケッチは以下のようになります。上側液面計が液面検知でP1スタート、水位が上がってLI1オフでP1ストップ。これとは無関係に、給気ブロアはRTCの時刻を拾ってきて、設定時間でスタート、ストップです。

#include <DFRobot_SD3031.h>
#include <Wire.h>
DFRobot_SD3031 rtc;  
const int P1 = D8;    //water charge pump
const int B1 = D10;  //Aeration
int LI1 = 0;  // UPPER_WaterLevel
int LI1_PIN = A0;
#include <WiFi.h>  //wifi setting
const char SSID[] = "******";
const char PASSWORD[] = "***********";
#include <Ambient.h>  //Ambient setting
unsigned int channelId = 00000;
const char* writeKey = "*******************";
WiFiClient client;
Ambient ambient;
void setup() {
  Serial.begin(115200);
  while (rtc.begin() != 0) {
    Serial.println("Failed to init chip, please check if the chip connection is fine. ");
    delay(1000);
  }
  pinMode(P1, OUTPUT);      //D8
  pinMode(B1, OUTPUT);     //D10
  pinMode(LI1_PIN, INPUT);  //A0
                            //D4 --SDA for RTC
                            //D5 --SCL for RTC
                            //D7 --INT from RTC
  bool connecting = true;
  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 + 20000 > 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);
}
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(' ');
    LI1 = analogRead(LI1_PIN);  //Check UPPER WaterLevel
    if (LI1 >= 3500) {
      digitalWrite(P1, HIGH);   //P1 water pump start
    } else {
      digitalWrite(P1, LOW);
    }
    Serial.print("UPPER Water Level = ");  //UPPER Water Level
    Serial.println(LI1, DEC);
    if (LI1 >= 3500) {
      ambient.set(1, 80);
    } else {
      (ambient.set(1, 100));
    }
    if ((sTime.hour >= 7) && (sTime.hour <= 17)) {  //aeration start ,stop time
      digitalWrite(B1, HIGH);
    } else {
      digitalWrite(B1, LOW);
    }
    int P1_motion = digitalRead(P1);
      ambient.set(2, P1_motion);
    int B1_motion = digitalRead(B1);
    if (B1_motion == HIGH) {
      ambient.set(3, HIGH);
    } else if (B1_motion == LOW) {
      ambient.set(3, LOW);
    }
    ambient.send();
    delay(1000);
}

液面計を稼働させた時のシリアル表示は下のような感じです。屋外にPCを持って行って撮影したのと、シリアル表示の位置が一定ではないので、ちょっと見難いですね。液面が高くフロートが浮いている間はUPPER Water Level(=LI1)の値は20以下です。液面が下がってフロートが下がるとUPPER Water Level(=LI1)は4095まで上がります。

長くなったので、とりあえずここまで。次は光学式液面計と水替え機能かな。

コメントを残す

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

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


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

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

続きを読む