ESP32とBME280で、温湿度計を作った
Table of Contents
ESP32(Arduino)とBME280(温湿度センサ)をつかって、部屋の温湿度をスマホに表示するダッシュボードを実装しました。
概要 #
ESP32にWebサーバを立てて、BME280で測定した部屋の温湿度をWebページとして表示し、スマホやPCで見られるようにします。
回路側 #
BME280とESP32を接続するだけの、シンプルな実装です。 センサーとの接続は、I2Cにしました。BME280とESP32の接続は、SCK - 22番、SDI - 21番で接続しています。
ESP32の開発ボードは、ESP32-DevKit-C-32E(ESP-WROOM-32E)を使用しています。 32D以下は、2021年現在、利用が推奨されていないそうです。
プログラム側 #
Arduinoで動かしているプログラムには、大きく4つの機能を実装しました。
- WebサーバとしてスマホやPCからのリクエストに応答(Webページのレンダリング)
- mDNSに対応(.localのアドレスでアクセスできるようにする)
- BME280からセンサーの値を取得
- NTPで時刻を同期
こんな画面が表示されるようにしました。
スマホからアクセスしたとき
PCからアクセスしたとき
実装 #
| #include <WiFi.h> | |
| #include <WebServer.h> | |
| #include <ESPmDNS.h> | |
| #include <Wire.h> | |
| #include "SparkFunBME280.h" | |
| #include <time.h> | |
| #define HTML "<!doctype html>"\ | |
| "<html lang=\"ja\">"\ | |
| "<head>"\ | |
| " <meta charset=\"UTF-8\" />"\ | |
| " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />"\ | |
| " <meta http-equiv=\"refresh\" content=\"60; URL=./\">"\ | |
| " <link href=\"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css\" rel=\"stylesheet\">"\ | |
| " <link rel=\"stylesheet\" href=\"https://pro.fontawesome.com/releases/v5.10.0/css/all.css\" integrity=\"sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p\" crossorigin=\"anonymous\"/>"\ | |
| " <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">"\ | |
| " <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>"\ | |
| " <link href=\"https://fonts.googleapis.com/css2?family=Outfit:wght@600&display=swap\" rel=\"stylesheet\">"\ | |
| " <title></title>"\ | |
| "</head>"\ | |
| "<body class=\"p-4 bg-gray-200\">"\ | |
| " <main>"\ | |
| " <div class=\"flex flex-col md:flex-row md:flex-wrap\">"\ | |
| " <div class=\"flex-auto p-4 mb-4 md:mr-4 bg-gray-100 rounded-lg shadow-xl\">"\ | |
| " <h2 class=\"font-sans text-xl font-bold text-gray-800\"><i class=\"fas fa-thermometer-half mr-2\"></i>気温</h2>"\ | |
| " <p class=\"text-8xl text-gray-800\" style=\"font-family: 'Outfit', sans-serif;\">%0.1f<span class=\"text-6xl\">˚C</span></p>"\ | |
| " </div>"\ | |
| " <div class=\"flex-auto p-4 mb-4 md:mr-4 bg-gray-100 rounded-lg shadow-xl\">"\ | |
| " <h2 class=\"font-sans text-xl font-bold text-gray-800\"><i class=\"fas fa-water mr-2\"></i>湿度</h2>"\ | |
| " <p class=\"text-8xl text-gray-800\" style=\"font-family: 'Outfit', sans-serif;\">%0.0f<span class=\"text-6xl\">%</span></p>"\ | |
| " </div>"\ | |
| " <div class=\"flex-auto p-4 mb-4 md:mr-4 bg-gray-100 rounded-lg shadow-xl\">"\ | |
| " <h2 class=\"font-sans text-xl font-bold text-gray-800\"><i class=\"fas fa-tachometer-alt mr-2\"></i>気圧</h2>"\ | |
| " <p class=\"text-8xl text-gray-800\" style=\"font-family: 'Outfit', sans-serif;\">%0.0f<span class=\"text-6xl\">hPa</span></p>"\ | |
| " </div>"\ | |
| " <div class=\"flex-auto p-4 mb-4 md:mr-4 bg-gray-100 rounded-lg shadow-xl\">"\ | |
| " "\ | |
| " <h2 class=\"font-sans text-xl font-bold text-gray-800\"> <i class=\"fas fa-tint mr-2\"></i>露点</h2>"\ | |
| " <p class=\"text-8xl text-gray-800\" style=\"font-family: 'Outfit', sans-serif;\">%0.1f<span class=\"text-6xl\">˚C</span></p>"\ | |
| " </div>"\ | |
| " </div>"\ | |
| " <p class=\"text-sm text-gray-800 tracking-widest\" style=\"font-family: 'Outfit', sans-serif;\">%s</p>"\ | |
| " </main>"\ | |
| "</body>"\ | |
| "</html>" | |
| WebServer server(80); | |
| // WiFi情報 | |
| const char* ssid = WIFI_SSID; // define by yourself | |
| const char* pass = WIFI_PASS; // define by yourself | |
| // センサ | |
| BME280 sensor; | |
| // NTP | |
| #define TIMEZONE_JST "JST-9" // Arduino/cores/esp8266/TZ.h | |
| #define NTP_SERVER1 "ntp.nict.jp" // NTPサーバー | |
| #define NTP_SERVER2 "ntp.jst.mfeed.ad.jp" // NTPサーバー | |
| // ダッシュボード画面を表示する | |
| void handleRoot() { | |
| // センサーから各データを取得 | |
| float humidity = sensor.readFloatHumidity(); | |
| float pressure = sensor.readFloatPressure(); | |
| float temp = sensor.readTempC(); | |
| float due = sensor.dewPointC(); | |
| // 現在時刻の取得 | |
| time_t timeNow = time(NULL); | |
| struct tm* tmNow = localtime(&timeNow); | |
| char szDateTime[32]; | |
| strftime(szDateTime, sizeof(szDateTime), "%Y/%m/%d %H:%M:%S", tmNow); | |
| // レスポンスとなるHTMLを生成 | |
| char html[3000]; | |
| sprintf(html, HTML, temp, humidity, pressure / 100, due, szDateTime); | |
| server.send(200, "text/html", html); | |
| } | |
| // 存在しないアドレスが指定された時の処理 | |
| void handleNotFound() { | |
| server.send(404, "text/plain", "Not Found."); | |
| } | |
| void setup() { | |
| // BME280とI2C接続する | |
| Wire.begin(21, 22); // SDA, SCK | |
| sensor.setI2CAddress(0x76); | |
| if (sensor.beginI2C() == false) { | |
| Serial.println("Sensor connect failed."); | |
| } | |
| // WiFiのアクセスポイントに接続する | |
| WiFi.begin(ssid, pass); | |
| while (WiFi.status() != WL_CONNECTED) { | |
| delay(500); | |
| } | |
| delay(2000); | |
| // NTP同期 | |
| configTzTime(TIMEZONE_JST, NTP_SERVER1, NTP_SERVER2); | |
| // mDNS: esp32.local で接続できるようにする | |
| MDNS.begin("esp32"); | |
| // リクエストに対する処理を登録 | |
| server.on("", handleRoot); | |
| server.on("/", handleRoot); | |
| server.onNotFound(handleNotFound); | |
| // Webサーバーを起動 | |
| server.begin(); | |
| } | |
| void loop() { | |
| server.handleClient(); | |
| } |
雑感と参考資料 #
Webサーバの実装 #
いくつかのサイトを参考にしながら実装しました。
- https://qiita.com/nayuki_eng/items/dc04b2082724ad22d042
- https://qiita.com/mine820/items/01d5b7dbf65296c6f6e8
- https://iot.keicode.com/esp8266/esp8266-webserver.php
- 8266の例ですが、includeが変わるくらいでほとんど一緒
mDNSが超便利 #
ESP32はマルチキャストDNSに対応しています。これにより、ESP32に割り振られたIPアドレスを調べずとも、ブラウザから「http://esp32.local」のようにアドレスを入力するだけでアクセスできます。
https://lipoyang.hatenablog.com/entry/2020/03/25/112815
ページスタイル #
以下のフレームワークを使いました。
- Tailwind CSS
- Font Awesome
- Google Fonts
Tailwind CSSは初めて使用しましたが、ドキュメントがよく整備されているので、HTMLめったにやらない私でもサクッとスタイルをつけることができました。
ページの自動リロード #
1分で自動リロードするようにしたのですが、こちらのサイトが参考になりました。
https://designsupply-web.com/media/programming/4431/
<meta http-equiv="refresh" content="60; URL=./">
I2C #
Wire.begin()でピン番号を指定できるとは知らなかった……。
NTPで時刻同期 #
今回の記事とは関係ないけど、 https://tool-lab.com/ というサイトに遭遇した。 電子工作関連の情報がまとまっていて、勉強になりそう……。