最近、水耕栽培を始めました。水耕栽培は水の管理が大切なのですが、なかなか大変です。
もっと簡単に水を管理したいと思い、今回の方法を考えました。
何をしたいかは本編に記載しています。本記事ではスケッチについて書いています。
本編・・・・ESP32でGoogle sheetとLineに水耕栽培の水温と流量を通知して管理する方法【本篇】
全スケッチ
マイコンボードとしては、XIAO ESP32C3を使用して、Arduino IDEで作りました。
いきなりですが、全スケッチはこのとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | #include <WiFi.h> #include <WebServer.h> #include <HTTPClient.h> #include <DallasTemperature.h> #include <OneWire.h> #include "time.h" #include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> const int ONE_WIRE_BUS = 8 ; const int ledPin = D10; // LED connected to digital pin 10 const int isrPin = D7; // OneWire oneWire(ONE_WIRE_BUS); //温度計インタフェース設定 DallasTemperature sensors( & oneWire); DeviceAddress Temp_Sensor1 = {0x * * , 0x * * , 0x * * , 0x * * , 0x * * , 0x * * , 0x * * , 0x * * }; DeviceAddress Temp_Sensor2 = {0x * * , 0x * * , 0x * * , 0x * * , 0x * * , 0x * * , 0x * * , 0x * * }; float temp1,temp2,flow1; hw_timer_t * timer = NULL; volatile unsigned long flowperiod; volatile int flowCheck,preflowCheck; volatile int ledCounter = 0 ; const float flowAlarm = 0.7 ; //流量アラーム値 単位はリットル/分 String suion,kion,ryuuryou; const char * ssid = "**********" ; //ssid const char * passwd = "*************" ; //ネットワークパスワード WebServer server( 80 ); //スマホ表示用 通信を受けるポート番号80 //Google sheet const String url = "https://script.google.com/macros/s/AKfy******************************************************************************Ng/exec" ; volatile int gglshtCounter = 0 ; int gglshtPeriod = 20 * 60 ; //20分ごとにgoogle sheet 書き込み // LINE Notify設定 String LINE_NOTIFY_TOKEN = "****************************************************" ; //BLE server BLEServer * pServer = NULL; BLECharacteristic * pTxCharacteristic; bool deviceConnected = false ; bool oldDeviceConnected = false ; uint8_t txValue = 0 ; #define SERVICE_UUID "6be4****-****-****-****-************" // UART service UUID #define CHARACTERISTIC_UUID_RX "6be4****-****-****-****-************" #define CHARACTERISTIC_UUID_TX "6be4****-****-****-****-************" void mySerial(String txdata,bool lineFeed = true ){ if (lineFeed)txdata + = "\n" ; Serial.print (txdata); if (deviceConnected) { pTxCharacteristic - >setValue(txdata.c_str()); pTxCharacteristic - >notify(); delay ( 10 ); } } class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer * pServer) {deviceConnected = true ;}; void onDisconnect(BLEServer * pServer) {deviceConnected = false ;} }; class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic * pCharacteristic) { std:: string rxValue = pCharacteristic - >getValue(); if (rxValue.length() > 0 ) { mySerial(rxValue.c_str()); mySerial(suion);mySerial(kion);mySerial(ryuuryou); } } }; void IRAM_ATTR onTimer() { //タイマー割込みでカウントアップ ledCounter + + ; gglshtCounter + + ; } void ISR_R() { //水量センサ割込みの都度、間隔測定 static unsigned long rapTime1,rapTime2; rapTime2 = millis (); flowperiod = rapTime2 - rapTime1; rapTime1 = rapTime2; flowCheck + + ; } void setup () { Serial.begin ( 115200 ); //シリアルポート設定 bleSetup(); //BLEのセットアップ pinMode (ledPin, OUTPUT ); attachInterrupt(isrPin, ISR_R, RISING ); sensors.begin(); timer = timerBegin( 0 , 80 , true ); timerAttachInterrupt(timer, & onTimer, true ); timerAlarmWrite(timer, 1000000 , true ); //1秒ごとにタイマー割込み timerAlarmEnable(timer); WiFi.begin(ssid, passwd); //アクセスポイント接続のためのIDとパスワードの設定 while (WiFi.status() ! = WL_CONNECTED) { //接続状態の確認 delay ( 300 ); //接続していなければ0.3秒待つ mySerial( "." , false ); //接続しなかったらシリアルモニタに「.」と表示 } //WiFi通信が可能となったら各種情報を表示する mySerial( "" ); //改行 mySerial( "WiFi Connected" ); //接続したらシリアルモニタに「WiFi Connected」と表示 mySerial( "IP Address : " + WiFi.localIP().toString()); //IPアドレスをシリアルモニタに表示 //スマホ表示 serverアクセスされたときの処理 server.on( "/" , handleRoot); //TOPページのアドレスにアクセス処理 server.onNotFound(handleNotFound); //異常アドレスへのアクセス処理 server.begin(); //WebServer起動 configTime( 9 * 3600 , 0 , "ntp.nict.jp" , "ntp.jst.mfeed.ad.jp" ); //現在時刻取得 mesureSensor(); } void loop () { delay ( 100 ); sensors.requestTemperatures(); server.handleClient(); //LED点滅 センサー測定 if (ledCounter > = 6 ){ //6秒に1回点滅 digitalWrite (ledPin, LOW ); ledCounter = 0 ; mesureSensor(); //センサー測定 } else if (ledCounter > = 5 ){ digitalWrite (ledPin, HIGH ); } else if (flow1 < flowAlarm){ //流量小で毎秒点滅 digitalWrite (ledPin, ! digitalRead (ledPin)); } //1時間チェック (時刻補正、Line通知用) static int hourNow,lastDoneHour,minuteNow; time_t t; struct tm * tm; t = time(NULL); tm = localtime( & t); hourNow = tm - >tm_hour; minuteNow = tm - >tm_min; if (minuteNow > 57 ) gglshtCounter = 0 ; //時刻補正数分前は、google sheet更新停止 if (hourNow ! = lastDoneHour){ mySerial( "時刻;" + String(hourNow) + "時 oneHourProcess" ); oneHourProcess(); lastDoneHour = hourNow; } //google sheet更新 if (gglshtCounter > = gglshtPeriod){ //更新間隔到達で更新 String urlFinal = url + "?waterTemp=" + temp1 + "&airTemp=" + temp2 + "&waterFlow=" + flow1; mySerial(urlFinal); HTTPClient http; http.begin(urlFinal.c_str()); http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); int httpCode = http.GET(); mySerial( "HTTP Status Code: " , false ); mySerial(String(httpCode)); //--------------------------------------------------------------------- //getting response from google sheet String payload; if (httpCode > 0 ) { payload = http.getString(); mySerial( "Payload: " + payload); } //--------------------------------------------------------------------- http.end(); gglshtCounter = 0 ; } // BLE disconnecting if ( ! deviceConnected & & oldDeviceConnected) { delay ( 500 ); // give the bluetooth stack the chance to get things ready pServer - >startAdvertising(); // restart advertising mySerial( "start advertising" ); oldDeviceConnected = deviceConnected; } // BLE connecting if (deviceConnected & & ! oldDeviceConnected) { delay ( 500 ); mySerial(suion);mySerial(kion);mySerial(ryuuryou); oldDeviceConnected = deviceConnected; } } void oneHourProcess(){ //毎時に処理(時計補正、アラーム時にLine通知) configTime( 9 * 3600 , 0 , "ntp.nict.jp" , "ntp.jst.mfeed.ad.jp" ); //時計補正 gglshtCounter = gglshtPeriod; //補正時にgoogle sheet更新 if (flow1 < flowAlarm){ //アラーム時にLine通知 String almText( "!!アラーム!! \r\n" + ryuuryou + "\r\n" + suion + " " + kion + "\r\n" ); mySerial( "ToLine:" + almText); sendLine(almText) ; } } void mesureSensor(){ //温度、水量測定 temp1 = sensors.getTempC(Temp_Sensor1); temp2 = sensors.getTempC(Temp_Sensor2); static float flowMinute,preflowMinute,pre2flowMinute; //流量は3回平均 flowMinute = ( float ) 60 * 1000 / flowperiod / 553 ; if (flowCheck = = preflowCheck) flowMinute = 0 ; //センサー停止時は流量ゼロ preflowCheck = flowCheck; flow1 = (pre2flowMinute + preflowMinute + flowMinute) / 3 ; pre2flowMinute = preflowMinute; preflowMinute = flowMinute; suion = "水温 : " + String(temp1, 2 ) + "℃" ; kion = "気温 : " + String(temp2, 2 ) + "℃" ; ryuuryou = "流量 : " + String(flow1, 2 ) + "リットル/分" ; } void handleRoot() { //スマホブラウザ表示 String html; //HTML記述 html = "<!DOCTYPE html>" ; html + = "<html lang='ja'>" ; html + = "<head>" ; html + = "<meta charset=\"utf-8\">" ; html + = "<title>Flow Status</title>" ; html + = "</head>" ; html + = "<body>" ; html + = "<h1>水耕栽培の状況</h1>" ; html + = "<p><h2>" + suion + "</h2></p>" ; html + = "<p><h2>" + kion + "</h2></p>" ; html + = "<p><h2>" + ryuuryou + "</h2></p>" ; html + = "</body>" ; html + = "</html>" ; // HTML出力 server.send( 200 , "text/html" , html); } //スマホブラウザ表示 異常アドレスへのアクセス処理 void handleNotFound( void ) { server.send( 404 , "text/plain" , "Not Found " ); } // line通知 void sendLine(String body){ HTTPClient httpClient; httpClient.begin(postUrl); httpClient.addHeader( "Content-Type" , "application/x-www-form-urlencoded" ); httpClient.addHeader( "Authorization" , "Bearer " + LINE_NOTIFY_TOKEN); // POSTしてステータスコードを取得 int status_code = httpClient.POST( "message=" + body); mySerial(httpClient.getString()); if (status_code = = 200 ) { mySerial( "[SUCCESS]LINE Notify (URL:" + postUrl); } else { mySerial( "[SUCCESS]LINE Notify (URL:" + postUrl + ")Code:" + status_code); } mySerial( "" ); httpClient.end(); // HTTPClinetを終了する } void bleSetup(){ // Create the BLE Device BLEDevice::init( "myESP32_UART" ); //スマホなどのBLEターミナルから見えるデバイス名 // Create the BLE Server pServer = BLEDevice::createServer(); pServer - >setCallbacks(new MyServerCallbacks()); // Create the BLE Service BLEService * pService = pServer - >createService(SERVICE_UUID); // Create a BLE Characteristic pTxCharacteristic = pService - >createCharacteristic( CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY ); pTxCharacteristic - >addDescriptor(new BLE2902()); BLECharacteristic * pRxCharacteristic = pService - >createCharacteristic( CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE ); pRxCharacteristic - >setCallbacks(new MyCallbacks()); // Start the service pService - >start(); // Start advertising pServer - >getAdvertising() - >start(); mySerial( "Waiting a client connection to notify..." ); } |
スケッチのサイズについて
スケッチが大きくなりすぎたため、自分の環境では設定を変更する必要がありました。
Arduino IDEのメニュー > ツール > Pertition Scheme > Huge APP

温度センサー・・・DS18B20を使用した温度プローブ
温度センサーとしては、DS18B20を使用した温度プローブを使いました。
DS18B20は、シリアルインタフェースを持った温度計です。そのインタフェースを使用するため、DallasTemperature.hとOneWire.hをインクルードしてます。
4 5 | #include <DallasTemperature.h> #include <OneWire.h> |
次にピン番号を設定しますが、ひとつのピンに複数のセンサーを接続できるので宣言するのは、ひとつのピンだけになります。
13 | const int ONE_WIRE_BUS = 8 ; |
続いて、sensorsと宣言した後に、センサー個体ごとに持つ固有のシリアルコードを設定します。なお、購入した温度センサーにはシリアルコードの記載は無かったため、別のスケッチで読みだして、本スケッチに入れました。
17 18 19 20 | OneWire oneWire(ONE_WIRE_BUS); //温度計インタフェース設定 DallasTemperature sensors( & oneWire); DeviceAddress Temp_Sensor1 = {0x * * , 0x * * , 0x * * , 0x * * , 0x * * , 0x * * , 0x * * ,0x * * }; DeviceAddress Temp_Sensor2 = {0x * * , 0x * * , 0x * * , 0x * * , 0x * * , 0x * * , 0x * * ,0x * * }; |
あとは、読み出したいときにセンサーから温度を読み出すだけです。
211 212 | temp1 = sensors.getTempC(Temp_Sensor1); temp2 = sensors.getTempC(Temp_Sensor2); |
流量センサー・・・SEN-HZ21WI G1/2
流量センサーSEN-HZ21WI G1/2 は、パイプの中のスクリュー?のようなものが回転し、回転ごとにパルス出力するものです。
スケッチでは、まず接続するピンを宣言します。
15 | const int isrPin = D7; |
あとは、setupの中で、パルスが来たら割込みが上がるようにして
96 | attachInterrupt(isrPin, ISR_R, RISING ); |
割込みで前回割込みとの時間を常に測定します。
83 84 85 86 87 88 89 | void ISR_R() { //水量センサ割込みの都度、間隔測定 static unsigned long rapTime1,rapTime2; rapTime2 = millis(); flowperiod = rapTime2 - rapTime1; rapTime1 = rapTime2; flowCheck++; } |
そして、流量が欲しいときに測定した時間から流量を算出します。ネットで検索したところ、流量1リットルあたり553パルス発生するとのこと。当面の目的は、水が流れているか、とまっているかの検出なので、誤差は気にしていません。時間ができたら、実際の流量と比較したいと思います。
213 214 215 216 217 218 | static float flowMinute,preflowMinute,pre2flowMinute; //流量は3回平均 flowMinute=(float)60*1000/flowperiod/553; if (flowCheck == preflowCheck) flowMinute = 0; //センサー停止時は流量ゼロ preflowCheck = flowCheck; flow1 = (pre2flowMinute + preflowMinute + flowMinute)/3 ; pre2flowMinute = preflowMinute; preflowMinute = flowMinute; |
続きは後編記事へ。
記事が長くなったので、続きは後編記事としました。
ESP32でGoogle sheetとLineに水耕栽培の水温と流量を通知して管理する方法【スケッチ(後編)】
- Google spreadsheetにデータ保存
- スマホで水の流れていることを確認[ブラウザとwebサーバー]
- 水の流量が減ったらLINEに通知
- マイコンボードの動作状況は、スマホでBluetooth LE経由で確認
本記事のハードでやっていること
ー>ESP32でGoogle sheetとLineに水耕栽培の水温と流量を通知して管理する方法【本篇】
本記事のハード
ー>ESP32でGoogle sheetとLineに水耕栽培の水温と流量を通知して管理する方法【ハードウェア】