以前に書いた次の記事で、勝者となったESP8266でベランダのIoT温湿度計を運用することにしました。
テストの時は温湿度センサーにDHT20を使っていましたが、部屋に気圧も計れるBME280が沢山あったので気圧は必要ないと思いながらセンサーを変更しました。
また、テストの時は状況を随時グラフで知りたかったためにUbidotsを使いましたが、部屋の温湿度計やCO2濃度計に合わせてMQTTでデータを送るようにし、Node-REDやスマホアプリのIoT OnOffで受信することで自由度を高めた構成にしました。
設置の工夫
住んでいる部屋の外壁がでこぼこの塗装でIoT温湿度計を付けられそうな場所が無かったため、仕方なく換気で開けない側の窓枠に設置しました。
写真をよく見るとブレッドボードと電池ボックスの間に目盛りが見えると思います。
取り外しを楽にするためにブレッドボードと電池ボックスの裏にマグネットテープを貼り付けたのですが、窓枠がアルミ製のために磁石が付かなかったため、ステンレス製の定規を両面テープで窓枠に貼り付けました。
マグネットテープはこんな感じで貼り付けています。
最初は窓枠にマグネットテープを貼って、その上にマグネットテープを貼ったブレッドボードと電池ボックスを付けたら、くっ付きはするものの強度が弱くて直ぐに落ちてしまったんです。
窓枠に貼り付けたマグネットテープをステンレス製の定規に変えたら、強度が上がってびくともしなくなりました。
回路
実装図
センサーをDHT20からBME280に変更した影響で、AE-ESP-WROOM-02の取り付け位置が少し上に上がっています。
配線図
DHT20とBME280はどちらもI2C接続できるので、ピンに合わせた配線に変えるだけです。
黄色と緑色はプログラムの書き込みの際に使うピンです。
黄色のIO0とGNDを繋いでESP-WROOM-02の電源を入れるとプログラムの書き込みモードになります。
緑色のTXDとRXDはUSBシリアル変換モジュールのTXとRXにクロスで接続します。
秋月電子通商の「FT234X 超小型USBシリアル変換モジュール」と3.3V出力の三端子レギュレータを使ったプログラム書き込み用の回路例はこんな感じです。
プログラム
ボードパッケージ
- esp8266 by ESP8266 Community バージョン3.0.2
ライブラリ
- PubSubClient by Nick O'Leary バージョン2.8.0
- Adafruit BME280 Library by Adafruit バージョン2.2.2
Arduinoスケッチ
#include <ESP8266WiFi.h> // WiFiライブラリ #include <PubSubClient.h> // MQTTライブラリ #include <Adafruit_BME280.h> // BME280ライブラリ #define WIFI_SSID "ssid" // ssidをWiFiのSSIDに置き換える #define WIFI_PASS "pass" // passをWiFiのパスワードに置き換える #define MESURE_PERIOD 30 // 計測間隔(分単位) // MQTT用 #define BROKER "192.168.1.1" // ブローカーのIPアドレスで置き換える #define MQTT_USER "user" // userを登録したユーザーで置き換える #define MQTT_PASS "pass" // passを登録したパスワードで置き換える #define TOPIC_TEMP "veranda/temperature" // 温度のトピック #define TOPIC_HUM "veranda/humidity" // 湿度のトピック #define TOPIC_PRES "veranda/pressure" // 気圧のトピック #define TOPIC_VOL "veranda/voltage" // 電圧のトピック #define CLIENT_ID "ESP-WROOM-02-DHT20" // クライアントID // グローバル変数/オブジェクト WiFiClient wclient; // WiFi接続用オブジェクト PubSubClient client(BROKER, 1883, wclient); // MQTT接続用オブジェクト Adafruit_BME280 bme; // BME280用オブジェクト char client_id[30]; // クライアントID ADC_MODE(ADC_VCC); // 電源電圧を測る設定 float temperature = NAN; // 温度の変数 float humidity = NAN; // 湿度の変数 float pressure = NAN; // 気圧の変数 int voltage; // 電圧の変数 // WiFi接続 void initWiFi() { Serial.print(WIFI_SSID "に接続"); // ログにWiFiのSSIDを表示 WiFi.begin(WIFI_SSID, WIFI_PASS); // WiFiに接続 int i = 1; while (WiFi.status() != WL_CONNECTED) { // WiFiが繋がるまでループ if (i > 10) { // 10秒でタイムアウト Serial.println("\n=== タイムアウトしました。再起動します。 ==="); ESP.restart(); // 再起動 } delay(1000); // 1秒待つ Serial.print("."); i++; } Serial.println("完了"); } // 計測 void measurement() { pinMode(4, OUTPUT); // センサーの電源ピンを出力に設定 digitalWrite(4, HIGH); // センサーの電源を入れる Wire.begin(12, 13); // I2Cの初期化(SDA, SCL) bme.begin(0x76); // センサーの初期化(I2Cアドレス) while (isnan(temperature) || isnan(humidity) || isnan(pressure)) { // 変数に値が入るまでループする delay(100); // 100ミリ秒待つ temperature = bme.readTemperature(); // 温度取得 humidity = bme.readHumidity(); // 湿度取得 pressure = bme.readPressure() / 100.0F; // 気圧取得 } voltage = ESP.getVcc(); // 電圧取得(mV) Serial.printf("\n温度:%.2f 湿度:%.2f 気圧:%.f 電圧:%d\n", temperature, humidity, pressure, voltage); } // サブスクライブしているトピックを受信した時に呼び出される関数 void callback(char* topic, byte* payload, unsigned int length) { Serial.printf("%d分寝ます。\n", MESURE_PERIOD); // ログに分数を表示 WiFi.disconnect(true); // WiFiの切断 ESP.deepSleep(MESURE_PERIOD * 60 * 1000 * 1000UL); // ディープスリープを実行(マイクロ秒) } // データパブリッシュ void sendData() { // MQTTブローカーに接続 Serial.print("ブローカーに接続..."); // 画面にブローカーへの接続を表示 if (!client.connect(CLIENT_ID, MQTT_USER, MQTT_PASS)) { // 接続に失敗した場合は再起動する Serial.println("\n=== 接続に失敗しました。再起動します。 ==="); ESP.restart(); // 再起動 } client.setCallback(callback); // データを受信した時に呼び出される関数 client.subscribe(TOPIC_VOL); // 最後のトピック(電圧)をサブスクライブ Serial.println("完了"); // 指定トピックで温度・湿度・気圧・電圧をパブリッシュ client.publish(TOPIC_TEMP, String(temperature).c_str(), true); client.publish(TOPIC_HUM, String(humidity).c_str(), true); client.publish(TOPIC_PRES, String(pressure).c_str(), true); client.publish(TOPIC_VOL, String(voltage).c_str(), true); Serial.println("データパブリッシュ"); } void setup() { Serial.begin(74880); // シリアルモニタの通信速度 delay(10); initWiFi(); // WiFiに接続 measurement(); // 計測 sendData(); // データパブリッシュ } void loop() { client.loop(); // サブスクライブ用 }
プログラムのポイント
- ユーザー認証を行うブローカーを使う場合の注意点
74行目のclient.connect()
でユーザーとパスワードを指定して、ブローカーへの接続時にユーザー認証を行うようにしています。if (!client.connect(CLIENT_ID, MQTT_USER, MQTT_PASS)) {
このプログラムはブローカーへの接続にTLSを使用していないため、ユーザーとパスワードは暗号化されずに平文で送られてしまします。
そのため、セキュリティをそこまで気にする必要のないローカル環境のブローカーを使うようにしてください。 - 複数トピックのデータをパブリッシュするプログラムでディープスリープを行う場合の工夫点
84行目から87行目にかけて、温度・湿度・気圧・電圧の4つのデータをパブリッシュしていますが、この直後にディープスリープに入ると全てのデータがパブリッシュされないことがありました。
これはディープスリープ前にディレイを入れても確実に送られるとは限りませんでした。
確実に全てのデータをパブリッシュしてからディープスリープするためには、最後にパブリッシュするトピックを自分でサブスクライブして、サブスクライブのコールバック関数内でディープスリープを実行する必要がありました。
簡易的なTLS接続に対応する方法(2023年1月31日追記)
- 20行目をTLS接続用のオブジェクトに変更
WiFiClient wclient; // WiFi接続用オブジェクト
↓ WiFiClientSecure wclient; // TLS接続用オブジェクト
- 21行目のポート番号をTLS通信用に変更
PubSubClient client(BROKER, 1883, wclient); // MQTT接続用オブジェクト
↓ PubSubClient client(BROKER, 8883, wclient); // MQTT接続用オブジェクト
8883はMQTTのTLS通信用のポート番号です。 - 73行目の後ろに次の行を追加
wclient.setInsecure(); // X.509の証明書パス検証をスキップ
X.509の証明書パス検証をスキップすると、DNSスプーフィング攻撃に対する脆弱性が生まれます。
表示
最後にIoT OnOffとNode-REDでダッシュボードを使った表示をご紹介します。
部屋のRaspberry PiにインストールしたmosquittoとNode-REDを使っていますが、localtunnelというトンネリングソフトを使ってインターネット上からでもブローカーとNode-REDにTLS接続ができるようにしています。
ローカルのブローカー接続に認証を必要にしていた本当の理由は、インターネットからのアクセスが可能になっていたからです。
IoT OnOff
Node-RED