ESP-32 Arduinoによる、BMP180(気圧、温度) DHT11( 温度、湿度)、WIFI接続とWebServer化とデータベースへのデータ保管 「外部からのエアコン制御」への道 その6

BMP180(気圧と温度のセンサー)が届きましたので、現在、稼働しているDHT11と併用したシステムを作って行きます。

前回までの仕様↓

上図が完成図です。

温度が二つあるのは、センサーの違いです。

上二つの温度と湿度はDHT11のデータです。

上から3番目から下が新しいBMP180のデータとなっています。

高度、海面気圧はどうでも良いと言えばどうでも良いのですが、測れるならついでに表示しておいてもいいかぁ。という感じです。

1.ESP32とセンサーの配線図

DHT11はGPIO16でデータを受けています。

BMP180ですが、デフォルトだと配線するところが指定されています。

SCL→GPIO22、SDA→GPIO21です。

ESP32の場合、どこに接続するのか良く分からなくて、色々調べる羽目になりました。

その時に参考になった記事↓

(英語のサイトですがGoogle翻訳で読むといいです。)

古いセンサー(現在生産していない)なので、日本語の記事が少なかったでしたね。

しかし、fritzingという配線図を書くソフトですが、これ、凄く使いやすくて便利です。

試しに使って良かったので購入しました。(千円位、8€です。)

書き残して置けるのは良いですね。後で見直せるし。

その時のプログラムも一緒に保存できるのが、痒い所に手が届く感じで更に良いです。

2.ESP32のプログラム

上図のWebを表示するプログラムです。

#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <DHT.h>                    // DHTセンサー用
#include <Adafruit_BMP085.h>        // BMP180センサー用 ESP32の場合 SCL信号→D22 SDA信号→D21へ接続する

/* Function Prototype */
String getTemperature();
String getHumidity();
String getTemp();
String getPressure();
String getAltitude();
String getRPressure();
String processor(const String&);
void doInitialize();
void connectToWifi();
Adafruit_BMP085 bmp;

// ルーター接続情報
#define WIFI_SSID "SSID変更してください"
#define WIFI_PASSWORD "パスワード変更してください"

/* 基本属性定義  */
#define SPI_SPEED   115200          // SPI通信速度
#define DHTTYPE     DHT11           // DHTセンサーの型式

// Webサーバーオブジェクト
#define HTTP_PORT 80
AsyncWebServer server(HTTP_PORT);

/* DHTセンサー*/
const int DHTPin = 16;                  // DHTセンサーの接続ピン
DHT   dht(DHTPin, DHTTYPE);             // DHTクラスの生成

/* HTMLページ */
const char* strHtml = R"rawliteral(
<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      html { font-family: Helvetica; display: inline-block; margin: 0px auto;text-align: center;} 
      h1 {font-size:28px;}
      body {text-align: center;} 
      table { border-collapse: collapse; margin-left:auto; margin-right:auto;}
      th { padding: 12px; background-color: #0000cd; color: white; border: solid 2px #c0c0c0;}
      tr { border: solid 2px #c0c0c0; padding: 12px;}
      td { border: solid 2px #c0c0c0; padding: 12px;}
      .value { color:blue; font-weight: bold; padding: 1px;}
    </style>
  </head>
  <body>
    <h1>温度測定中</h1>
    <p style='color:brown; font-weight: bold'>計測値はアクセスが無いときは1時間ごとに自動更新</p>
    <p><table>
      <tr><th>要素</th><th>値</th><th>単位</th></tr>
      <tr><td>温度</td><td><span id="temperature" class="value">%TEMPERATURE%</span></td><td>℃</td>
      <tr><td>湿度</td><td><span id="humidity" class="value">%HUMIDITY%</span></td><td>%</td>
      <tr><td>温度</td><td><span id="temp" class="value">%TEMP%</span></td><td>℃</td>
      <tr><td>気圧</td><td><span id="Pressure" class="value">%Pressure%</span></td><td>Pa</td>
      <tr><td>高度</td><td><span id="Altitude" class="value">%Altitude%</span></td><td>m</td>
      <tr><td>海面気圧</td><td><span id="RPressure" class="value">%RPressure%</span></td><td>Pa</td>
      </td></tr>
    </table></p>
  </body>
  <script>
    var getTemperature = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("temperature").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/temperature", true);
      xhr.send(null);
    }
    var getHumidity = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("humidity").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/humidity", true);
      xhr.send(null);
    }
    var getTemp = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("temp").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/temp", true);
      xhr.send(null);
    }
    var getPressure = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("Pressure").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/Pressure", true);
      xhr.send(null);
    }
    var getAltitude = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("Altitude").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/Altitude", true);
      xhr.send(null);
    }
    var getRPressure = function () {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("RPressure").innerHTML = this.responseText;
        }
      };
      xhr.open("GET", "/RPressure", true);
      xhr.send(null);
    }

    setInterval(getTemperature, 3600000);
    setInterval(getHumidity, 3600000);
    setInterval(getTemp, 3600000);
    setInterval(getPressure, 3600000);
    setInterval(getAltitude, 3600000);
    setInterval(getRPressure, 3600000);
  </script>
</html>)rawliteral";

/*****************************************************************************
 *                          Predetermined Sequence                           *
 *****************************************************************************/
void setup(){                
    Serial.begin(SPI_SPEED);           // 初期化処理
    dht.begin();                       // DHTセンサーを起動
    Serial.print("Connecting to Wi-Fi ");   // Wi-Fiルーターに接続する
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    // モニターにローカル IPアドレスを表示する
    Serial.println("WiFi connected.");
    Serial.print("  *IP address: ");
    Serial.println(WiFi.localIP());
    server.begin();

    // GETリクエストに対するハンドラーを登録して
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/html", strHtml, editPlaceHolder);
    });
    server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getTemperature().c_str());
    });
    server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getHumidity().c_str());
    });
    server.on("/temp", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getTemp().c_str());
    });
    server.on("/Pressure", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getPressure().c_str());
    });
    server.on("/Altitude", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getAltitude().c_str());
    });
    server.on("/RPressure", HTTP_GET, [](AsyncWebServerRequest *request){
        request->send_P(200, "text/plain", getRPressure().c_str());
    });
    
    if (!bmp.begin()) {
    Serial.println("Could not find a valid BMP085 sensor, check wiring!");
    while (1) {}
    }
    server.begin();     // サーバーを開始する
}
 
// void loop(void)
void loop(void) {
  int i;                  // WiFi Lan 接続を監視します。
  if(WiFi.status() !=3){
  }
  if(WiFi.status() != WL_CONNECTED){
    WiFi.disconnect();
    delay(50);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    i=50;
    while (WiFi.status() != WL_CONNECTED) {
      delay(250);
      i--;
      if(i<0){
        delay(250);
        ESP.restart();
      }
    }
  }
}


/* 計測処理ロジック */
String getTemperature() {
    float t = dht.readTemperature();
    if (isnan(t)) {    
        Serial.println("Failed to get temperature!");
        return "--";
    }
    else {
        Serial.println(t);
        return String(t) ;
    }
}

String getHumidity() {
    float h = dht.readHumidity();
    if (isnan(h)) {
        Serial.println("Failed to get humidity!");
        return "--";
    }
    else {
        Serial.println(h);
        return String(h) ;
    }
}

String getTemp() {
    float h = bmp.readTemperature();
    if (isnan(h)) {
        Serial.println("Failed to get BMP180 temp!");
        return "--";
    }
    else {
        Serial.println(h);
        return String(h) ;
    }
}

String getPressure() {
    float h = bmp.readPressure();
    if (isnan(h)) {
        Serial.println("Failed to get Pressure!");
        return "--";
    }
    else {
        Serial.println(h);
        return String(h) ;
    }
}

String getAltitude() {
    float h = bmp.readAltitude();
    if (isnan(h)) {
        Serial.println("Failed to get Altitude!");
        return "--";
    }
    else {
        Serial.println(h);
        return String(h) ;
    }
}

String getRPressure() {
    float h = bmp.readSealevelPressure();
    if (isnan(h)) {
        Serial.println("Failed to get SealevelPressure!");
        return "--";
    }
    else {
        Serial.println(h);
        return String(h) ;
    }
}


/* プレースホルダー処理 */
String editPlaceHolder(const String& var){
    if(var == "TEMPERATURE"){
        return getTemperature();
    }
    if(var == "HUMIDITY"){
        return getHumidity();
    }
    if(var == "TEMP"){
        return getTemp();
    }
    if(var == "Pressure"){
        return getPressure();
    }
    if(var == "Altitude"){
        return getAltitude();
    }
    if(var == "RPressure"){
        return getRPressure();
    }
}

とまぁ、長くなりましたね。

でも、色々なデータが見れるので面白いです。

3.Mysqlへセンサーの値を保存するPythonプログラム

自分の場合、Synologyのタスクスケジューラーで以下のプログラムを15分毎に実行してデータを保存しています。

要するに上で作成したWebページをPythonでスクレイピングしているという、何しているのかな?という感じでもあります。

# coding: utf-8
from urllib.request import urlopen
from bs4 import BeautifulSoup
import pymysql.cursors
import datetime
import time

#日付と時間のフォーマット
today0 = datetime.date.today()
today01 = today0.strftime('%Y%m%d')
Time0 = datetime.datetime.now()
Time01 = Time0.strftime('%H:%M:%S')

# Mysqlへ接続
database = pymysql.connect(user='ユーザー名',passwd='パスワード',host='ホスト名、Ipアドレスなど',port = ポート番号,db='データベース名',charset='utf8mb4',cursorclass=pymysql.cursors.DictCursor)


# arduinoサイトを開く
html = urlopen("http://192.168.1.64/")
bsObj = BeautifulSoup(html, "html.parser")
#urlオープンでの安定化時間
time.sleep(3)

# arduinoの表から値をもらう
Temp = bsObj.select('.value')[0].contents[0]
Humi = bsObj.select('.value')[1].contents[0]
Tem2 = bsObj.select('.value')[2].contents[0]
Pres = bsObj.select('.value')[3].contents[0]
Alti = bsObj.select('.value')[4].contents[0]
Rpre = bsObj.select('.value')[5].contents[0]

# temp,humiの値が--だったら、測定値が出なかったら。
while Temp == "--":

    html.close()

    # arduinoサイトを開く
    html = urlopen("http://192.168.1.64/")
    bsObj = BeautifulSoup(html, "html.parser")
    #urlオープンでの安定化時間
    time.sleep(3)

    # arduinoの表から値をもらう
    Temp = bsObj.select('.value')[0].contents[0]
    Humi = bsObj.select('.value')[1].contents[0]
    Tem2 = bsObj.select('.value')[2].contents[0]
    Pres = bsObj.select('.value')[3].contents[0]
    Alti = bsObj.select('.value')[4].contents[0]
    Rpre = bsObj.select('.value')[5].contents[0]

Temp01 = float(Temp)
Humi01 = float(Humi)
Temp02 = float(Tem2)
Pres01 = float(Pres)
Alti01 = float(Alti)
Rpre01 = float(Rpre)

# INSERT INTO SQLクエリを作成する
query = "INSERT INTO arduino (Time, Date, S1_Temp, S1_Humi, S2_Temp, S2_Pres, S2_Alti, S2_Rpre) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)"

# データベースを1行ずつ走査するために使用されるカーソルを取得します。
cursor = database.cursor()

# 各行から値を割り当てる
values = (Time01, today01, Temp01, Humi01, Temp02, Pres01, Alti01, Rpre01)

# SQLクエリを実行する
cursor.execute(query, values)

# カーソルを閉じる
cursor.close()

# トランザクションをコミットします
database.commit()

# データベース接続を閉じます
database.close()

# サイトクローズ
html.close()

Synologyのタスクスケジューラーの設定はパスが要らなくなったので、以下です。

cd ./Document/R_Temp/01_Python
python3 R_Temp_Humi.py

1行目 プログラムがあるフォルダーの場所へ移動、2行目でPythonの実行

4.Line Notifyを利用して通知

Line Notifyもついでに変更しました。

こちらもSynologyのタスクスケジューラーで30分毎にお知らせが来ます。

# coding: utf-8
from urllib.request import urlopen
from bs4 import BeautifulSoup
import time
import requests

# arduinoサイトを開く
html = urlopen("http://192.168.1.64/")
bsObj = BeautifulSoup(html, "html.parser")
#urlオープンでの安定化時間
time.sleep(3)

# arduinoの表から値をもらう
Temp = bsObj.select('.value')[0].contents[0]
Humi = bsObj.select('.value')[1].contents[0]
Tem2 = bsObj.select('.value')[2].contents[0]
Pres = bsObj.select('.value')[3].contents[0]
Alti = bsObj.select('.value')[4].contents[0]
Rpre = bsObj.select('.value')[5].contents[0]

# temp,humiの値が--だったら、測定値が出なかったら。
while Temp == "--":

    html.close()

    # arduinoサイトを開く
    html = urlopen("http://192.168.1.64/")
    bsObj = BeautifulSoup(html, "html.parser")
    #urlオープンでの安定化時間
    time.sleep(3)

    # arduinoの表から値をもらう
    Temp = bsObj.select('.value')[0].contents[0]
    Humi = bsObj.select('.value')[1].contents[0]
    Tem2 = bsObj.select('.value')[2].contents[0]
    Pres = bsObj.select('.value')[3].contents[0]
    Alti = bsObj.select('.value')[4].contents[0]
    Rpre = bsObj.select('.value')[5].contents[0]

Temp01 = float(Temp)
Humi01 = float(Humi)
Temp02 = float(Tem2)
Pres01 = float(Pres)
Alti01 = float(Alti)
Rpre01 = float(Rpre)

# サイトクローズ
html.close()

#Line notify で送る
Text01 = "温度  :" + str(Temp01) + "℃"
Text02 = "湿度  :" + str(Humi01) + "%"
Text03 = "温度  :" + str(Temp02) + "℃"
Text04 = "気圧  :" + str(Pres01) + "Pa"
Text05 = "高度  :" + str(Alti01) + "m"
Text06 = "海面気圧:" + str(Rpre01) + "Pa"


def send_line_msg():
    data = {"message": f"\n{Text01}\n{Text02}\n{Text03}\n{Text04}\n{Text05}\n{Text06}"}
    # headers
    access_token = 'xxxxxxxxxxxxxxxxxxxxxxxxx' # 発行されたトークンへ置き換える
    headers = {"Authorization": f"Bearer {access_token}"}
    # send massage
    requests.post("https://notify-api.line.me/api/notify", data=data, headers=headers)


if __name__ == "__main__":
    send_line_msg()

送られたデータ

というわけで、データ盛り沢山で届くようになりました。