Gameran blog

日常の気になったことなどを書いていきます。ご参考になれば幸いです。

IOT

ESP32でGoogle sheetとLineに水耕栽培の水温と流量を通知して管理する方法【スケッチ(前編)】

更新日:

最近、水耕栽培を始めました。水耕栽培は水の管理が大切なのですが、なかなか大変です。
もっと簡単に水を管理したいと思い、今回の方法を考えました。

何をしたいかは本編に記載しています。本記事ではスケッチについて書いています。
 本編・・・・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に水耕栽培の水温と流量を通知して管理する方法【ハードウェア】

-IOT
-, , , , , , , ,

執筆者:


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連記事

ESP32でGoogle sheetとLineに水耕栽培の水温と流量を通知して管理する方法【スケッチ(後編)】

最近、水耕栽培を始めました。水耕栽培は水の管理が大切なのですが、なかなか大変です。もっと簡単に水を管理したいと思い、今回の方法を考えました。 本記事は後編です。前編(スケッチ含む)はこちら。 前編・・ …

ESP32でGoogle sheetとLineに水耕栽培の水温と流量を通知して管理する方法【本編】

最近、水耕栽培を始めました。水耕栽培は水の管理が大切なのですが、なかなか大変です。もっと簡単に水を管理したいと思い、今回の方法を考えました。 やっている水耕栽培は図のようにタンクに水を入れ、そこから上 …

ESP32C3でバッテリーを使って振動検知してLineに通知してDeepSleepする

ESP32C3でバッテリーを使って、振動を検知してLineに通知してみました。通知後、すぐにDeepSleepするようにしたので、一度充電しておけば、単独で何か月かは稼働するかと思います。なお、Ard …

ESP32でGoogle sheetとLineに水耕栽培の水温と流量を通知して管理する方法【ハードウェア】

最近、水耕栽培を始めました。水耕栽培は水の管理が大切なのですが、なかなか大変です。もっと簡単に水を管理したいと思い、今回の方法を考えました。 何をしたいかは本編に記載しています。本記事ではハードウェア …

ESP32C3でバッテリーをつないでみた

ESP32C3をバッテリーをつないで何かをしようと思ったのですが、つないだときの扱い?がよく判らないので、充放電の状況を確認してみました。 (注意)バッテリーは扱いを間違えると危険ですので真似をしない …

S