【SALZmini2022作製日誌】キックオフ
はじめに
SALZminiの運用終了後、以前より温めていた自動潅水装置の後継機を作製することにしました。
2022年の年初に自動潅水装置の構想を温めていた時点では、水分計による方式ではなく、別の方法(例えば、ロードセルを用いる方法)等を試したいと考えていましたが、まだ未使用のWateringUnitが残っていること。自動潅水装置にぴったりのパーツをダイソーで見つけてしまっていたことなどから、今回も、WateringUnitを用いた自動潅水装置を作ることにしました。
SALZminiシリーズの後継ということで、2022年度版の意味を込め「SALZmini2022」と命名いたします。
基本構想
まずは振り返りを。
で、次の自動潅水装置になりそうな組合せを考えておりました。
ただどれも ノー・ジョイ。いまいち心に響きません。進めることなくお蔵入りさせていました。
今回、SALZminiの運用停止で今までの様子を振り返った時、忘れていた装置に託す想いを再び思い出すことができました。
一番の目的は、
・黒松のザルつくりがしたい。
・排水性の良い用土を用いて多肥多水栽培がしたい。
で、
・データのモニタリング
はできればいいな程度の感じです。
・黒松のザルつくりがしたい。
のためには、適当なザルがやっぱり欲しくて、スリット鉢ではなくて、ザルのようなもので。。。そうすると、浅鉢になってしまい、土壌水分センサが差し込めなくなって。。。というジレンマでモヤモヤしていました。
ある日、ダイソーで
ダイソー「ザル・ボウル(スタッキング)」
を見つけました。店頭で見かけて、スパーク・ジョイ!!!
今までのモヤモヤが一気に晴れました。
WaterringUnitを横挿しで使うためのスリットは、少しカッターナイフで調整するだけで使えました。
下部タンクも適度な水量を蓄えられ、水量も確認しやすいです。
・排水性の良い用土を用いて多肥多水栽培がしたい。
春からの取り組みとして、排水性のよさげな砂を中心に少し赤玉土を混ぜてオリジナルブレンド用土を作り1ガロン不織布ポットで松を育てています。
今までは肥料を置き、そこに水が掛かるようにしていましたが、今回は給水ホースを地中に埋め、必要な肥料分は下部タンクに溶かし込んでやろうと思っています。水耕栽培の培養液のような感じでしょうか。
マイコンの選定
このところ、使用するマイコンは、M5Stack社の AtomMatrixか AtomLiteになっていました。温湿度計ゲートウェイを見直した際もM5StackからAtomMatrixへ変更しました。
M5Stackと比べて、電波の送受信能力が劣るような感じがしていますが、私にとって安価で扱いやすいマイコンになっています。
今回は昨年発売されたM5StackシリーズのM5Stamp C3 Mateを使ってみたいと思います。値段はAtomLiteより少しお安くなっていますが、ソケットをはんだ付けする必要があります。
M5Stamp C3 Mate
この製品が発売される前に、M5Stamp Pico Mateというものも発売されていました。
M5Stamp Pico Mate
こちらも入手して何かに使えないか考えていました。ちっちゃい基板でかわいらしいのですが、残念なことにUSBがありませんでした。プログラムの入力や電源供給のために便利に使えているコネクタがないとなると、これまた一苦労することが目に見えています。もっと工作がうまくなったら、いつかきっと使えるはずです。
また今回記事を書くにあたり色々調べていると、M5Stamp C3 Mate の姉妹品 M5Stamp C3U Mate があるようです。
M5Stamp C3U Mate
USBシリアル変換ICが省略された製品だそうですが、おもしろいですね。短い期間にこれだけ似た製品を販売するM5Stack社の社風が垣間見えます。
まずは実験してみます。
以前もM5StackにWateringUnitを接続したとき、一度つまづいています。
事前にテストをしないと分かりません。はんだ付けを行い、サンプルプログラムを走らせて、期待した動作ができるか試してみました。
まずは、M5StampC3用にプログラムがコンパイルでき、プログラムが書き込める所までを目指します。
数少ないながらも、先人の情報を頼りに進めて行きます。
Lチカ以前
コンパイル、シリアルプリントができるようにしてみました。M5Unifiedは らびあんさんのライブラリです。
ArduinoIDEの[環境設定]-[追加のボードマネージャのURL]で
を指定し、[M5Unified]で「STAMP-C3」を選択しています。
#include <M5Unified.h>
void setup() {
// put your setup code here, to run once:
M5.begin();
}
void loop() {
// put your main code here, to run repeatedly:
Serial.print("Hello, World!\n");
}
ArduinoIDEのシリアルモニタには「Hello, World!」が表示されました。
ここまではできたのですが、ここから、内蔵LEDを点滅させる、いわゆる「Lチカ」ができません。M5Unifiedライブラリをインクルードしたものの、M5.begin();しかしていない状態です。
別のサンプルを試してみることにしました。
ArduinoIDEの[環境設定]-[追加のボードマネージャのURL]で
を指定し、「ESP32」で「ESP32C3 DEV Module」を選択しています。
#include <Adafruit_NeoPixel.h>
#define LED_PIN 2
Adafruit_NeoPixel pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800);
void setup() {
pixels.begin();
}
void loop() {
pixels.setPixelColor(0, pixels.Color(0, 255, 255)); //Colorメソッド内の引数の順番は赤、緑、青
pixels.show();
delay(1 * 1000);
pixels.setPixelColor(0, pixels.Color(0, 0, 0)); //明るさを3原色全て0にして消灯
pixels.show();
delay(1 * 1000);
}
これはこれでOK。
なのですが、次の一歩が踏み出せません。
M5Stamp C3で使いたい機能は、
・シリアル
・LED
・ポンプのON/OFF
・水分計の読み取り
・マルチタスク処理
・Bluetooth
です。
さらに目標の解像度を上げていくと、
・シリアル:Serial.print();が使えるか?
・LED:色と明るさ、Adafruit_NeoPixelが使いたい。
・ポンプのON/OFF:degitalWrite();が使えるか?
・水分計の読み取り:analogRead();が使えるか?
・マルチタスク処理:xTaskCreatePinnedToCore();が使えるか?
・Bluetooth:BLEでブロードキャスト通信できるライブラリが使えるか?
になると思います。
ということで、次の一手が見えてきました。
今回作る装置は、SALZminiとSALZminiSoloの間のようなものを作ろうと思っています。つまり、SALZminiSoloのように単独稼働できて、稼働情報はBLEでブロードキャスト発信するようなものです。
というわけで、少しずつ機能を盛り込みながら作っていきます。
ESP32C3 DEV Moduleを元に作りました。最初なかなかanalogRead、degitalWriteがうまく働かずどうしたものかと悩んでいましたが、よく見てみると、コネクタの取り付けを反対に行っていることがわかりました。
アナログポート対応のコネクタは、G0,G1です。コネクタを付け替えて、なんとか、センサーとモーターの制御ができることを確認しました。
水分計の返り値は値が以前のものと異なっていたため、校正する必要がありました。
int
WlRate(int wl)
{
//校正済の水分%を返す。
//センサーを水に浸けた状態:1900
//センサーを外に出した状態:2500
const int wetWl = 1900;
const int dryWl = 2500;
int ret = (int)((1.0 - ((float)(wl - wetWl) / (float)(dryWl - wetWl))) * 100.0);
return (ret < 0) ? 0 : ((ret > 99) ? 99 : ret);
}
途方に暮れていましたが、ごにょごにょしているうちにできてしまいました。できてしまうと新たな課題にチャレンジしたくなります。
次のチャレンジ目標はBLEによる情報発信です。
以前、Switchbot温湿度計のブロードキャスト信号を拾い、Ambientへ飛ばすゲートウェイを作成しました。
これは今でも稼働していて、とても便利に使っています。今回は、発信、受信の双方を作ってみたいと思います。
Arduino IDEのESP32BLE Aruduinoサンプル群から参考になりそうなものを選んでみます。
を見ていますが、今やりたいことがぴったりとはまりません。もう少し、サンプルを探してみます。
Ambientのドキュメントを再度確認してみました。
M5StackでBLE環境センサー端末を作る
昼休みにソースコードを見ながら、必要な部分をポチポチ持って来て試してみると。。。
とても簡単に実装することができました。
#include "BLEDevice.h"
#include "BLEServer.h"
#include "BLEUtils.h"
(略)
void
setAdvData(BLEAdvertising *pAdvertising, uint8_t wl, uint8_t st, uint16_t c) {
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
oAdvertisementData.setFlags(0x06); // BR_EDR_NOT_SUPPORTED | LE General Discoverable Mode
std::string strServiceData = "";
strServiceData += (char)0x07; // 長さ
strServiceData += (char)0xff; // AD Type 0xFF: Manufacturer specific data
strServiceData += (char)0xff; // Test manufacture ID low byte
strServiceData += (char)0xff; // Test manufacture ID high byte
strServiceData += (char)wl; // 水分量%
strServiceData += (char)st; // 状態
strServiceData += (char)(c & 0xff); // カウントの下位バイト
strServiceData += (char)((c >> 8) & 0xff); // カウントの上位バイト
oAdvertisementData.addData(strServiceData);
pAdvertising->setAdvertisementData(oAdvertisementData);
}
(略)
void
SendData(int wl, int st) {
static uint16_t c = 0;
c++;
Serial.printf("SendData():\n");
Serial.printf("wl:%d st:%d c:%d\n", wl, st, c);
BLEDevice::init("SALZmini2022-1"); // デバイスを初期化
BLEServer *pServer = BLEDevice::createServer(); // サーバーを生成
BLEAdvertising *pAdvertising = pServer->getAdvertising(); // アドバタイズオブジェクトを取得
setAdvData(pAdvertising, (uint8_t)wl, (uint8_t)st, c);
pAdvertising->start(); // アドバタイズ起動
Serial.println("Advertizing started...");
delay(SECONDS(10));
pAdvertising->stop();
}
そして、その確認はスマホのアプリでできるとい手軽さ。こんなに簡単にデータをモニタする方法があったとは。目からウロコです。
さらに、グラフの説明を読んでいると、
状態表示
というものがありました。
今まで、装置の稼働状況は、カウンタを用いて行っていました。プログラムが生きている間、数字がカウントアップしています。
装置の状態をモニタ出来るのはこれまたありがたい機能です。
例えばこんな感じ。
0:ST_ERROR エラー
1:ST_WATERING 水やり中
2:ST_WAIT_RED 待機中(水分量%30-40)赤
3:ST_WAIT_YELLOW 待機中(水分量%40-60)黄
4:ST_WAIT_BLUE 待機中(水分量%60-100)青
これもすんなり組み込めました。
アプリで確認するとこんな様子です。
BLEのブロードキャスト通信を行うことで、制御がとても簡単になりました。今の所、電源についてはACアダプターを使う予定なので、こちらの仕様で進めて行きたいと思います。
ここまでの成果
これまで作成したSALZmini2022のソースコードを紹介いたします。
// SALZmini2022 bbd
/* for M5Stamp C3 using Wartering Unit
* Arduino 1.8.19
* [環境設定]-[追加のボードマネージャのURL]
* https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
* [ESP32]-[ESP32C3 Dev Module]
* Ambientのサンプルコードを参考
* https://github.com/AmbientDataInc/EnvSensorBleGw/blob/master/src/envSensor_esp32/BLE_BME280_bcast/BLE_BME280_bcast.ino
*/
#include <Adafruit_NeoPixel.h>
#include "BLEDevice.h"
#include "BLEServer.h"
#include "BLEUtils.h"
#define INPUT_PIN 0
#define PUMP_PIN 1
#define LED_PIN 2
#define SECONDS(s) ((s) * 1000)
#define MINUTES(m) SECONDS(m * 60)
enum {
ST_ERROR,
ST_WATERING,
ST_WAIT_RED,
ST_WAIT_YELLOW,
ST_WAIT_BLUE
};
const int WAIT_TIME = SECONDS(10);
const int PUMP_TIME = SECONDS(10);
static int pValue = 0;
#define NCOLORS 5
static int colorRs[NCOLORS] = { 0x60, 0x50, 0x30, 0x00, 0x00 };
static int colorGs[NCOLORS] = { 0x00, 0x10, 0x30, 0x50, 0x00 };
static int colorBs[NCOLORS] = { 0x00, 0x00, 0x00, 0x10, 0x60 };
static int delays[NCOLORS] = { 4, 6, 8, 10, 12 };
Adafruit_NeoPixel pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800);
void
setAdvData(BLEAdvertising *pAdvertising, uint8_t wl, uint8_t st, uint16_t c) {
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
oAdvertisementData.setFlags(0x06); // BR_EDR_NOT_SUPPORTED | LE General Discoverable Mode
std::string strServiceData = "";
strServiceData += (char)0x07; // 長さ
strServiceData += (char)0xff; // AD Type 0xFF: Manufacturer specific data
strServiceData += (char)0xff; // Test manufacture ID low byte
strServiceData += (char)0xff; // Test manufacture ID high byte
strServiceData += (char)wl; // 水分量%
strServiceData += (char)st; // 状態
strServiceData += (char)(c & 0xff); // カウントの下位バイト
strServiceData += (char)((c >> 8) & 0xff); // カウントの上位バイト
oAdvertisementData.addData(strServiceData);
pAdvertising->setAdvertisementData(oAdvertisementData);
}
void
DispLevel(int lv) {
pValue = lv;
}
int
WlRate(int wl)
{
//校正済の水分%を返す。
//センサーを水に浸けた状態:1900
//センサーを外に出した状態:2500
const int wetWl = 1900;
const int dryWl = 2500;
int ret = (int)((1.0 - ((float)(wl - wetWl) / (float)(dryWl - wetWl))) * 100.0);
return (ret < 0) ? 0 : ((ret > 99) ? 99 : ret);
}
int
GetWl(void)
{
float val = 0.0;
pinMode(INPUT_PIN, INPUT);
for (int i = 0; i < 10; i++) {
float v = analogRead(INPUT_PIN);
//Serial.printf("ML(%d):%8.3f\r\n", i, v);
val += v;
delay(10);
}
return (int)(val / 10.0);
}
void
SendData(int wl, int st) {
static uint16_t c = 0;
Serial.printf("SendData():\n");
Serial.printf("wl:%d st:%d c:%d\n", wl, st, c);
BLEDevice::init("SALZmini2022-1"); // デバイスを初期化
BLEServer *pServer = BLEDevice::createServer(); // サーバーを生成
BLEAdvertising *pAdvertising = pServer->getAdvertising(); // アドバタイズオブジェクトを取得
setAdvData(pAdvertising, (uint8_t)wl, (uint8_t)st, c);
pAdvertising->start(); // アドバタイズ起動
Serial.println("Advertizing started...");
delay(SECONDS(10));
pAdvertising->stop();
c = (++c < UINT16_MAX) ? c : 0;
}
int
GetDispLevel(void)
{
int ret;
ret = pValue / NCOLORS;
ret = (ret > NCOLORS - 1) ? (NCOLORS - 1) : ret;
return ret;
}
//---------------------------------------
void task1(void * pvParameters)
{
int a = 0;
int rPrev = 0;
int gPrev = 0;
int bPrev = 0;
while (1) {
int lv = GetDispLevel();
a = (a < 360) ? ++a : 0;
float v = (sin(PI * (float)a / 180.0) + 1.0) / 2.0;
int r = (float)colorRs[lv] * v;
int g = (float)colorGs[lv] * v;
int b = (float)colorBs[lv] * v;
if (r != rPrev || g != gPrev || b != bPrev) {
pixels.setPixelColor(0, pixels.Color(r, g, b)); //Colorメソッド内の引数の順番は赤、緑、青
pixels.show();
}
delay(delays[lv]);
rPrev = r;
gPrev = g;
bPrev = b;
}
}
void setup() {
Serial.begin(115200);
Serial.print("SALZmini2022\n");
pixels.begin();
pinMode(INPUT_PIN, INPUT);
pinMode(PUMP_PIN, OUTPUT);
// Task 1
xTaskCreatePinnedToCore(
task1, /* Function to implement the task */
"task1", /* Name of the task */
4096, /* Stack size in words */
NULL, /* Task input parameter */
1, /* Priority of the task */
NULL, /* Task handle. */
0); /* Core where the task should run */
}
void loop() {
static int st = ST_ERROR;
int wl = WlRate(GetWl());
DispLevel(wl);
if (wl < 30) {
digitalWrite(PUMP_PIN, true);
Serial.println("PUMP ON");
delay(PUMP_TIME);
digitalWrite(PUMP_PIN, false);
Serial.println("PUMP OFF");
st = ST_WATERING;
}
else if (wl < 40) {
st = ST_WAIT_RED;
}
else if (wl < 60) {
st = ST_WAIT_YELLOW;
}
else {
st = ST_WAIT_BLUE;
}
SendData(wl, st);
delay(WAIT_TIME);
}
M5StampC3のGND,5V,G1,G0にGrove互換コネクタをはんだづけし、WateringUnitを接続します。
10秒ごとに水分計のチェックを行い、30%未満で10秒間ポンプを作動させます。
別タスクを動作させ、水分量%に応じて5段階のLED明滅を繰り返します。
10秒ごとにBLEアドバタイジング通信を行い、水分量%、現在のステータス、動作カウントを発信します。
今後の展望
ここまでの作業で装置はおおよそ完成しました。
あとは、育てる松の苗木を選定し、植え付けすればOKです。
今の所、上部トレーの安定感がなく、ちょっと不安です。支えが必要かもしれません。
BLEのデータを受信するためのレシーバーも作る必要があります。
今回は一つの装置にたくさんの機能を盛り込むのではなく、モジュール化して組合せていきたいと思っています。
ソフトウェア・エンジニアの性に合ったやりかたかな。
「拡張の可能性がある。」これが大事です。
さいごに
こうして時々装置を作っていると、だんだん失敗しにくくなっている気がします。ちいさなつまづきはたくさんしているのですが、対策を考えるのがうまくなっているような感じがしています。
私のテーマは盆栽のための装置作りですが、今回作った装置は屋内での植物用にも十分対応できるものです。植物の根は水分や養分を必要としますが、常に浸かりっぱなしでは機能しません。適度な乾燥と湿潤のサイクルが必要です。
最後までお読みいただき誠にありがとうございました。
#自動潅水装置 #水やり #盆栽 #園芸 #ザル作り #電子工作 #M5StampC3 #WateringUnit #M5Stack #ArduinoIDE #BLE #Ambient #IoT #100均 #ダイソー