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()
送られたデータ
というわけで、データ盛り沢山で届くようになりました。