Gameran blog

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

IOT

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

更新日:

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

何をしたいかは本編に記載しています。本記事ではスケッチについて書いています。
 本編・・・・ESP32でGoogle sheetとLineに水耕栽培の水温と流量を通知して管理する方法【本篇】

全スケッチ

マイコンボードとしては、XIAO ESP32C3を使用して、Arduino IDEで作りました。
いきなりですが、全スケッチはこのとおりです。

#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;
  String postUrl = "https://notify-api.line.me/api/notify";
  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をインクルードしてます。

#include <DallasTemperature.h> 
#include <OneWire.h>

次にピン番号を設定しますが、ひとつのピンに複数のセンサーを接続できるので宣言するのは、ひとつのピンだけになります。

const int ONE_WIRE_BUS = 8;

続いて、sensorsと宣言した後に、センサー個体ごとに持つ固有のシリアルコードを設定します。なお、購入した温度センサーにはシリアルコードの記載は無かったため、別のスケッチで読みだして、本スケッチに入れました。

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**};

あとは、読み出したいときにセンサーから温度を読み出すだけです。

  temp1  = sensors.getTempC(Temp_Sensor1);
  temp2 = sensors.getTempC(Temp_Sensor2);

流量センサー・・・SEN-HZ21WI G1/2

流量センサーSEN-HZ21WI G1/2 は、パイプの中のスクリュー?のようなものが回転し、回転ごとにパルス出力するものです。

スケッチでは、まず接続するピンを宣言します。

const int isrPin = D7;     

あとは、setupの中で、パルスが来たら割込みが上がるようにして

attachInterrupt(isrPin, ISR_R, RISING);

割込みで前回割込みとの時間を常に測定します。

void ISR_R() {        //水量センサ割込みの都度、間隔測定
 static unsigned long rapTime1,rapTime2;
 rapTime2 = millis();
 flowperiod = rapTime2 - rapTime1;
 rapTime1 = rapTime2;
 flowCheck++;
}

そして、流量が欲しいときに測定した時間から流量を算出します。ネットで検索したところ、流量1リットルあたり553パルス発生するとのこと。当面の目的は、水が流れているか、とまっているかの検出なので、誤差は気にしていません。時間ができたら、実際の流量と比較したいと思います。

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