見出し画像

【SALZ製作】循環式自動潅水装置SALZ3完成しました。

盆栽向けの循環式自動潅水装置、SALZ3がようやく完成しました。

画像1

SALZ3の給水テスト中水分計を外し、常に乾燥状態と判断させて、動作チェックを行っているところ

現在、稼働テスト中ですが、お披露目できるレベルに達したと判断し、公開いたします。

画像2

テスト用の鉢は今うちの棚場で一番乾きが早いピラカンサス。

できること:

・静電容量方式水分計で土壌の水分量をモニタリングします。
・モニタリングした数値は、Wifiを通じて、クラウドのAmbientサービスへ送られ、蓄積、視覚化されます。PCでもスマホでも、どこからでも鉢の乾き具合、SALZ3の稼働状態をグラフで観察することができます。
・水分量が一定の閾値を超えると、サーボモーターを動かして、排水弁を閉じ、リレーをオンにし、水中モーターを一定時間動作させます。
・一定時間、ドブ浸け状態を維持します。
・排水弁を開き、排水します。
・コントローラの画面を押すと、LEDが表示し、閾値の設定ができます。左右に傾けると赤い矢印が出てきますので、増減させたい矢印が表示された状態でボタンを押すと、閾値が変更できます。


必要なもの:

・Wifi
 ・データの送信に必要です。
・電源
 ・100V電源またはモバイルブースター
  100V電源は、漏電保護タップを用いて、安全に配慮しました。
  モバイルブースターは短時間での運用であれば稼働します。
  こちらも、破裂などの可能性があるため、気を付けます。
・水
 ・今回はデモンストレーション用装置のため、下部トレーについては穴をあけていませんが、棚場で使用するためには、下部トレーにもオーバーフロー穴をあけておきます。SALZ1/2では、塩ビ管コネクタを取り付けましたが、単に穴をあける形でよいと思います。循環式の潅水装置のため、水の腐敗が気になります。水腐れ防止用にミリオンを少し撒いておくとよいと思います。

画像3

コントロール部のアップ。紆余曲折の末、コンパクトに納まりました。

材料(おおよその価格):

・[P01]アステージ製NFボックス#7 400円 または NCボックス#7(蓋つき) 550円
・[P02]アステージ製NFボックス#11 500円 または NCボックス#11(蓋つき) 650円
・[P03]TSバルブソケット25 70円
・[P04]TSスイセンソケット25 80円
・[P05]盆栽用肥料ケース 200円(袋入り)
・[P06]水やりキット(Amazon) 1180円
・[P07]M5ATOM Matrix 1400円
・[P08]ATOMIC DIY PROTO KIT 500円
・[P09]サーボモーターSG90 500円
・[P10]EHコネクタ ベース付きポスト 3P(トップ型) × 3、2P(サイド型) × 2 100円
・[P11]EHコネクタ ソケットハウジング 3P × 3、2P × 1 100円
・[P12]EHコネクタ コンタクト × 11 200円
・[P13]ワイヤ線(AWG#24) 300円
・[P14]USB Type-C ケーブル(プログラム転送、電力供給用) 100円
・[P15]ペットボトルのフタ
・[P16]ペットボトルの上部
・[P17]バスコーク
・[P18]結束バンド
・[P19]ビニール袋
・[P20]ビニールテープ
・[P21]ビー玉
・[P22]テグス糸
・[P23]針金
・[P24]スズめっき線

作り方:

・筐体
[P01]NFボックス#7に給水・オーバーフロー用の穴と排水弁用の穴を開けます。
また、サーボモーター取り付け用の穴も開けます。

・給水ポンプ
[P06]水やりキットの水中モーターの線を伸ばします。このとき、熱収縮チューブなどを使い、防水加工を施します。キットに付属のチューブをはめ込みます。

画像4

画像5

・排水弁
[P16]ペットボトルの飲み口を切り取り、排水口にしました。[P15]キャップに穴あけをし、[P23]針金をボンドでつけた[P21]ビー玉を栓にしました。水漏れが気になり、[P17]バスコークで、栓とビー玉の漏れを少なくしました。栓の作りに苦戦しています。漏れが少なく、排水性の高い排水栓を探しています。これは今後の課題です。[P22]テグス糸を用いて、[P09]サーボモーターSG90と結びます。サーボモータは角度が決まっていますので、通電し、サーボの動きを確認した後、サーボホーン(サーボの先につける棒)を取り付けます。その後、栓の位置をよく確認し、テグスの長さを調整します。

画像6

画像7

・オーバーフロー
 ・オーバーフロー用の穴にTSバルブソケット25、TSスイセンソケット25を取りつけます。TSバルブソケット25を上に向けて取り付けます。この高さでドブ浸け時の水深を決めます。深さが足りないときは、塩ビ管VP25を差し込み、適当な長さで切断します。NFボックス#7の高さを超えないように気を付けます。パイプの中に、給水ホースと、給水ポンプ用の電源を通します。

画像8

・マイコン配線
ATOMIC DIY PROTKITを用いて、コネクタ配線できるようにしました。SALZ2ではブレッドボードを用いて配線を行いましたが、SALZ3では、装置を傾けてスイッチを押す機能があるため、コンパクトで操作しやすい作りにしました。ユニバーサル基板は初めて使いました。はんだ付けに苦戦しながらも、なんとか完成させることができました。テスターを用いて導通テストを念入りに行い、配線ミスをなくし、完成させました。コネクタにはEHコネクタを採用。

画像9

こんな感じのパッケージです。

写真

取り出してみました。

画像11

EHコネクタを取り付けたところ。EHコネクタ2Pを追加発注しました。

画像12

仮組みしたところ。良い感じで配置できました。

画像13

マスキングテープで部品を仮止めし、裏返しました。

写真

スマホの画像編集アプリを使い、写真に配線予定を書き込みました。あとで確認すると、少し配線が違っていましたが、初めて行う配線作業がとてもはかどりました。

画像15

はんだ付けのコツは「いかに手をあけるか」だと思いました。両手ははんだごてと、はんだ線だけを持ち、ほかのものは、固定具を用いることが大切であることを実感しました。ルーペは作業中は慣れなくて使えませんでしたが、確認作業がはかどりました。

画像16

今回、ツールの大切さを実感する工作となりました。

画像17

汚いはんだ面ですが、自分ではとても満足しています。この時は、少し大きめのワイヤーストリッパーを使っており、ワイヤ線の切断があまりスムーズにできていませんでした。この後、電子工作用のワイヤーストリッパーを手に入れ、とても簡単に被覆を剥けることがわかりました。道具って大切ですね。

画像18

苦手な、はんだ付けの克服は、固定具の適切な使用と、導通テスターを用いたチェック方法、間違った時の、はんだ吸い取り線による修正方法を会得したことが大きいです。

画像19

今の自分には、これが精いっぱい。後で振り返ると、GNDは極力[P24]スズめっき線で渡したほうが良かったこと。部品面にも配線することで、こんなにワイヤー線てんこ盛りにならなかったのではないでしょうか。

画像20

各ワイヤ線の先をコンタクトピンに変更し、ソケットハウジングに差し込みます。圧着工具を使用しての作業になりますが、丁寧にやれば、きちんとできます。何度も書いてしまいますが、やはりきちんとした工具を使って工作すれば、きちんとできるんだということを改めて実感しました。コネクタで接続したために、安心して、接続できます。

画像21

はんだ面のワイヤ線がてんこ盛りで筐体に収まるか不安でしたが、うまく収まりました。

画像22

ブレッドボードとの比較画像です。確かにコンパクトになりました。ワイヤ線は少しもじゃっとします。

画像23

同時に3つ作りましたが、線の長さがまちまちのため、一品一品違ったもののように見えます。導通テスターで動作チェックしているため、問題ありません。

画像24

配線図は以下のようになっています。

■3V3-->給水モータ用リレースイッチ VCC
□G21 未使用
□G22 未使用
□G25 未使用
■G19-->排水弁用サーボモータ PWM
■ 5V-->排水弁用サーボモータ VCC、水分計センサ VCC、給水モータ VCC
■G23-->給水モータ用リレースイッチ SIG
■GND-->各GND
■G33-->水分計センサ IN

・Ambientサービス

Wifiを経由してクラウドサービスのAmbientへデータを送信し、グラフ化しています。

ユーザー登録には、メールアドレスとパスワードが必要となります。
ログイン後、「チャネルを作る」を行うと、チャネルID、ライトキーがもらえます。この値を指示して、データ送信を行います。

Ambient ambient;

unsigned int channelId = 0000; // AmbientのチャネルID
const char * writeKey = "XXXX"; // ライトキー

void
setup(void) {
 M5.begin(true, false, true);

 if (M5.IMU.Init() != 0)
   IMU6886Flag = false;
 else
   IMU6886Flag = true;
   
 WiFi.begin(ssid, password);  //  Wi-Fi APに接続 ----A
 while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち
   delay(500);
   Serial.print(".");
 }
 Serial.print("WiFi connected\r\nIP address: ");
 Serial.println(WiFi.localIP());

 while (!ambient.begin(channelId, writeKey, &client)) { // チャネルIDとライトキーを指定してAmbientの初期化
   delay(500);
   Serial.print("*");
 }
   :
}

void
SendData(void) {
 static int c = 0;

 Serial.printf("SendData():\n");
 Serial.printf("1:waterLevel:%d\n", waterLevel);
 Serial.printf("2:wlThreshold:%d\n", wlThreshold);
 Serial.printf("3:state:%d\n", state);
 Serial.printf("4:c:%d\n", c);
 
 ambient.set(1, waterLevel);
 ambient.set(2, wlThreshold);
 ambient.set(3, state);
 ambient.set(4, c++);
 ambient.send();
}

で、Ambientへデータを送信しています。

SALZ3初号機のデータを公開チャネルにしました。稼働状況をご覧ください。

・プログラム

SALZ3用に準備したプログラムを用います。
現時点では、WIFIとAmbientサービスのID、PWをハードコーディングしています。各環境における値をセットします。

// SALZ3 bbd

 #include <M5Atom.h>
 #include <ESP32Servo.h>
 #include <Wire.h>
 #include "Ambient.h"

//デモモード(ON:1, OFF:0)
//#define DEMO_MODE 1
#define DEMO_MODE 0

 #define P(col, row)   (((row) * 5) + (col))
 #define RGB(r, g, b)  (((g) << 16) + ((r) << 8) + (b))

 #define SECONDS(s)    ((s) * 1000)
 #define MINUTES(m)    SECONDS(m * 60)

const int INITIAL_WL_THRESHOLD = 2200;
const int WDRN_TIME = (DEMO_MODE) ? MINUTES(2) : MINUTES(10);
const int DOBU_TIME = (DEMO_MODE) ? MINUTES(2) : MINUTES(10);
const int WAIT_TIME = (DEMO_MODE) ? MINUTES(2) : MINUTES(60);

WiFiClient client;
Ambient ambient;

const char * ssid     = "XXXX";
const char * password = "XXXX";

unsigned int channelId = 0000; // AmbientのチャネルID
const char * writeKey = "XXXX"; // ライトキー

//ピン配置
//      ■3V3   
//G21□  □G22
//G25□  ■G19:排水弁用サーボモータ
// 5V■  ■G23:給水モータ用リレースイッチ
//GND■  ■G33:水分計センサ

const uint8_t WD_PIN = 19;  // 排水弁用サーボモータ
const uint8_t WS_PIN = 23;  // 給水モータ用リレースイッチ
const uint8_t WL_PIN = 33;  // 水分計センサ

const uint8_t WD_CH0 = 0;   //チャンネル
const float PWM_HLZ = 50.0; //PWM周波数
const uint8_t PWM_LVL = 16; //PWM 16bit(0~65535)

Servo servo1;

// Published values for SG90 servos; adjust if needed
const int minUs = 500;
const int maxUs = 2400;

float accX = 0, accY = 0, accZ = 0;
float gyroX = 0, gyroY = 0, gyroZ = 0;
float temp = 0;
bool IMU6886Flag = false;

int waterLevel = 0;
int wlThreshold = INITIAL_WL_THRESHOLD;

enum {
 ST_WDRN,
 ST_DOBU,
 ST_WAIT
};

int state = ST_WDRN;

void
CheckIMU6886(void) {
 if (IMU6886Flag == true) {
   M5.IMU.getGyroData(&gyroX, &gyroY, &gyroZ);
   M5.IMU.getAccelData(&accX, &accY, &accZ);
   M5.IMU.getTempData(&temp);

//    Serial.printf("%6.2f,%6.2f,%6.2f o/s \r\n", gyroX, gyroY, gyroZ);
//    Serial.printf("%6.2f,%6.2f,%6.2f mg\r\n", accX * 1000, accY * 1000, accZ * 1000);
//    Serial.printf("Temperature : %6.2f C \r\n", temp);
 }
}

void
DispLevel(int lv) {
 int it = lv / 1000;
 int ih = (lv % 1000) / 100;

 static int lvPrev = 0;
 if (lv == lvPrev)
   return;
 lvPrev = lv;

 Serial.printf("level:%d \r\n", lv);

 for (int i = 0 ; i < 5; i++) {
   M5.dis.drawpix(P(i, 0), RGB(0, 0xf0, 0) * (it > i));
 }
 M5.dis.drawpix(P(0, 1), RGB(0, 0xf0, 0xf0) * (ih > 0));
 M5.dis.drawpix(P(0, 2), RGB(0, 0xf0, 0xf0) * (ih > 1));
 M5.dis.drawpix(P(1, 1), RGB(0, 0xf0, 0xf0) * (ih > 2));
 M5.dis.drawpix(P(1, 2), RGB(0, 0xf0, 0xf0) * (ih > 3));
 M5.dis.drawpix(P(2, 1), RGB(0, 0xf0, 0xf0) * (ih > 4));
 M5.dis.drawpix(P(2, 2), RGB(0, 0xf0, 0xf0) * (ih > 5));
 M5.dis.drawpix(P(3, 1), RGB(0, 0xf0, 0xf0) * (ih > 6));
 M5.dis.drawpix(P(3, 2), RGB(0, 0xf0, 0xf0) * (ih > 7));
 M5.dis.drawpix(P(4, 1), RGB(0, 0xf0, 0xf0) * (ih > 8));
 M5.dis.drawpix(P(4, 2), RGB(0, 0xf0, 0xf0) * (ih > 9));
}

void
SettingLoop(void) {
 static bool isSetting = false;
 static unsigned long prevMillis = 0L;
 unsigned long curMillis = millis();

 if (isSetting && (curMillis - prevMillis > SECONDS(10))) {
   isSetting = false;
   prevMillis = curMillis;
   Serial.println("SETTING OFF");
   M5.dis.clear();
   goto EXIT_SettingLoop;
 }

 CheckIMU6886();
 if (M5.Btn.wasPressed()) {
//Serial.printf("isSetting:%d\n", isSetting);
//Serial.printf("curMillis:%ld\n", curMillis);
//Serial.printf("prevMillis:%ld\n", prevMillis);

   prevMillis = curMillis;

   if (!isSetting) {
     isSetting = true;
     Serial.println("SETTING ON");
   }

   if (accX > 0.2)
     wlThreshold += 100;
   else if (accX < -0.2)
     wlThreshold -= 100;

   if (wlThreshold < 0)
     wlThreshold = 0;
   if (wlThreshold > 5000)
     wlThreshold = 5000;

   DispLevel(wlThreshold);
 }

 if (isSetting) {
   if (accX > 0.2) {
     M5.dis.drawpix(P(0, 4), 0);
     M5.dis.drawpix(P(1, 3), 0);
     M5.dis.drawpix(P(1, 4), 0);
     M5.dis.drawpix(P(4, 4), RGB(0xf0, 0, 0));
     M5.dis.drawpix(P(3, 3), RGB(0xf0, 0, 0));
     M5.dis.drawpix(P(3, 4), RGB(0xf0, 0, 0));
   }
   else if (accX < -0.2) {
     M5.dis.drawpix(P(0, 4), RGB(0xf0, 0, 0));
     M5.dis.drawpix(P(1, 3), RGB(0xf0, 0, 0));
     M5.dis.drawpix(P(1, 4), RGB(0xf0, 0, 0));
     M5.dis.drawpix(P(4, 4), 0);
     M5.dis.drawpix(P(3, 3), 0);
     M5.dis.drawpix(P(3, 4), 0);
   }
 }
 else {
   M5.dis.drawpix(P(0, 4), 0);
   M5.dis.drawpix(P(1, 3), 0);
   M5.dis.drawpix(P(1, 4), 0);
   M5.dis.drawpix(P(4, 4), 0);
   M5.dis.drawpix(P(3, 3), 0);
   M5.dis.drawpix(P(3, 4), 0);
 }

EXIT_SettingLoop:
 M5.update();
}

void
WdOpen(void) {
 //排水弁を開く
 Serial.println("排水弁を開く");
 servo1.write(170);
 delay(1000);
}

void
WdClose(void) {
 //排水弁を閉じる
 Serial.println("排水弁を閉じる");
 servo1.write(10);
 delay(1000);
}

void
WsOn(void) {
 //給水On
 Serial.println("給水On");
 digitalWrite(WS_PIN, LOW);
}

void
WsOff(void) {
 //給水Off
 Serial.println("給水Off");
 digitalWrite(WS_PIN, HIGH);
}

void 
WsInit(void) {
 pinMode(WS_PIN, OUTPUT);
}

void
WdInit(void) {
 servo1.setPeriodHertz(PWM_HLZ);
 servo1.attach(WD_PIN, minUs, maxUs);
}

void
WlInit(void) {
 pinMode(WL_PIN, INPUT_PULLUP);
}

void
WdTest(void) {
 // 排水弁のテスト
 for (int i = 0; i < 1; i++) {
   WdOpen();
   WdClose();
 }
}

void
WsTest(void) {
 // 給水テスト
 Serial.println("WsTest() Start");
 for (int i = 0; i < 1; i++) {
   WsOn();
   delay(3000);
   WsOff();
   delay(3000);
 }
 Serial.println("WsTest() End");
}

float
GetWl(void)
{
 float val = 0.0;
 pinMode(WL_PIN, INPUT_PULLUP);
 for (int i = 0; i < 5; i++) {
   float v = analogRead(WL_PIN);
   //Serial.printf("ML(%d):%8.3f\r\n", i, v);
   val += v;
 }
 return (val / 5);
}

void
WlTest(void) {
 for (int i = 0; i < 5; i++) {
   GetWl();
 }
}

void
InitialTest(void) {
 Serial.println("InitialTest()");
 WsTest();
 WdTest();
 WlTest();

}

void
SendData(void) {
 static int c = 0;

 Serial.printf("SendData():\n");
 Serial.printf("1:waterLevel:%d\n", waterLevel);
 Serial.printf("2:wlThreshold:%d\n", wlThreshold);
 Serial.printf("3:state:%d\n", state);
 Serial.printf("4:c:%d\n", c);
 
 ambient.set(1, waterLevel);
 ambient.set(2, wlThreshold);
 ambient.set(3, state);
 ambient.set(4, c++);
 ambient.send();
}

void
Task0(void * arg) {
 while (1) {
   SettingLoop();
 }
}

//---------------------------------------

void
setup(void) {
 M5.begin(true, false, true);

 if (M5.IMU.Init() != 0)
   IMU6886Flag = false;
 else
   IMU6886Flag = true;
   
 WiFi.begin(ssid, password);  //  Wi-Fi APに接続 ----A
 while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち
   delay(500);
   Serial.print(".");
 }
 Serial.print("WiFi connected\r\nIP address: ");
 Serial.println(WiFi.localIP());

 while (!ambient.begin(channelId, writeKey, &client)) { // チャネルIDとライトキーを指定してAmbientの初期化
   delay(500);
   Serial.print("*");
 }
 
 WsInit();
 WdInit();
 WlInit();

 InitialTest();

 //初期状態のセット
 WsOff();
 WdOpen();
 waterLevel = GetWl();
 state = ST_WAIT;
 SendData();

 xTaskCreatePinnedToCore(Task0, "Task0", 4096, NULL, 1, NULL, 0);
}

void
loop(void) {
 waterLevel = GetWl();
 Serial.printf("waterLevel:%d\n", waterLevel);

 if(waterLevel > wlThreshold) {
   Serial.println("ドライ");
   state = ST_WDRN;
   SendData();
   WdClose();
   WsOn();
   Serial.printf("給水:%d秒\n", WDRN_TIME / 1000);
   delay(WDRN_TIME);

   state = ST_DOBU;
   SendData();
   WsOff();
   Serial.printf("ドブ浸け:%d秒\n", DOBU_TIME / 1000);
   delay(DOBU_TIME);

   WdOpen();
 }
 state = ST_WAIT;
 SendData();
 Serial.printf("待機:%d秒\n", WAIT_TIME / 1000);
 delay(WAIT_TIME);
}

次への課題:

・Wifiがないときの処理を丁寧に作る。
 ・現時点では、指定されたWifiが必ずあるものとして、接続できるまで待ち続けます。Wifiはデータ送信のために必要な機能ですが、水やり装置としては、先に進めると思います。今後の改良点にいたします。

・いま!ボタンの追加
 ・SALZ2での改良点として、閾値の設定がありました。今回、閾値を設定することが可能となりましたが、あともうちょっとUIに工夫をしたいと考えています。これが、「いま!ボタン」です。このボタンを押すと、閾値を現在の値か過去の「いま!ボタン」の値の平均値にセットし、水やりを行います。その鉢にあったベストな水やりタイミングを得られます。

・もっと簡単に作れるように工夫する。
 ・SALZ3は電子工作、水回り工作、プログラミングなど、様々な要素が絡み合っています。
 ・要素ごとに、簡単にできる方法はないか、模索する必要がありそうです。

・SALZ3初号機と同じ作りで、ひと回り大きくした NFボックス#13、NFボックス#25を用いて作成したいと考えています。
 ・お盆休みにここまでやろうと思っていたのですが、残作業になってしまいました。

画像25


#bonsai #盆栽 #循環式自動潅水装置 #SALZ #電子工作 #M5Atom #園芸 #WaterWorks #M5Stack #ArduinoIDE #SG90 #水分計 #リレー


この記事が気に入ったらサポートをしてみませんか?