ArduinoとESP32による大気質モニタリングアプリケーションの実装 (1)-センサーの概要

作成者:カランカラン
💡

質問やフィードバックがありましたら、フォームからお願いします

本文は台湾華語で、ChatGPT で翻訳している記事なので、不確かな部分や間違いがあるかもしれません。ご了承ください

前言

最近、さまざまな IoT アプリケーションに夢中になっています。Arduino やさまざまなセンサーをたくさん購入しました。手元にある Arduino だけでも、Arduino Uno、Arduino Mega2560、Arduino nano * 3 があります。時間があるときに遊びながら、面白いアプリケーションを作れないかと思っています。この機会に、高校の電子工学やさまざまなハードウェアの動作を復習するつもりです。

ちょうど会社で社内ハッカソンが開催されたので、テーマにあまり制約がなかったこともあり、以前からやりたかった「空気品質監視器」を実装しました。空気品質というからには、実装後は二酸化炭素濃度と温湿度の監視しかできませんでしたが、センサーがあれば追加するのはそれほど難しくはありません。

このアイデアを思いついたのは、二酸化炭素濃度が人の思考や生産性に本当に影響を与えるからです。会議室で突然頭がぼんやりしたり、思考が混乱したりすることは、皆さんも経験があると思います。その大きな原因は、二酸化炭素濃度が高すぎるためかもしれません。換気が悪い密閉空間では、二酸化炭素濃度が非常に速く上昇し、数分で 2000 ppm を超えることもあります。

実装そのものだけでなく、私は単にキットを使って完了させるのが好きではないため、できるだけ多くの詳細に触れるようにしています。そうしないと、「ああ、キットを使い終わったけれど、何が起こったのかわからない」という虚しさを感じるからです。(実装にはキットを使っていますが、少なくとも納得して使っていますXD)

このシリーズでは、以下のテーマを取り上げます:

  1. センサー紹介 - DHT11 と MH-Z14A
  2. データ通信 - UART
  3. Arduino の落とし穴:実装時に遭遇した問題について、ほとんどの場合は落とし穴ではなく、Arduino やハードウェアに不慣れなことが原因です
  4. WiFi:デバッグの時間を節約するために、ESP32 開発ボードを追加購入しました。これには WiFi および Bluetooth 機能が含まれています。
  5. MQTT:データを他のデバイスに送信するために、軽量な通信プロトコル MQTT を使用しました。
  6. Grafana / Web:データをデータベースに保存する以上、魅力的な方法で表示しなければなりません!ここでは Grafana + Prometheus と Svelte を使用してデータを表示します。

架構

わかりやすく説明するために、アーキテクチャはおおよそこのようになります:

Infrastructure

  • Arduino Uno が CO2 センサーに指令を送り、CO2 センサーが Arduino にデータを返します。両者は UART を介して通信します。

  • Arduino Uno が CO2 データを ESP32 に(これも UART を使用します)。

  • DHT11 を通じて温湿度データを取得し、ESP32 に送信します。

  • ESP32 は温湿度データと CO2 データを WiFi 経由で MQTT Broker に送信します。

  • イベントを購読するアプリケーションは 2 つあります:分析サーバーとアプリサーバー。

    • 分析サーバーはデータを Prometheus に送信し、Grafana によってデータを表示します。
    • アプリサーバーはデータを受信後、データベースに保存し、外部に API を提供します。
  • 二酸化炭素が一定濃度を超えると、Slack 通知を送信します。

全体の構成はこのようになります。次に、いくつかの可能な質問に答えます:

1. なぜ Arduino Uno を追加で使用するのですか?ESP32 だけで解決できるのでは?

当初の考えは、ESP32 を仲介者として使い、全体のアーキテクチャをすっきりさせることでしたが、実装が完了した後はそれほど必要ではないようです。唯一注意が必要なのは、いくつかの Arduino に内蔵されているライブラリが ESP32 では使用できない点です。今回の実装で使用した SoftwareSerial(デジタルピンを TX、RX ピンとして使用できるライブラリ)もそのひとつです。このライブラリがなければ、内蔵の HardwareSerial を使用するしかなく、最大でも 1 つの UART 通信しか行えません。

もう一つの理由は、見た目がクールだからです。Arduino を使わないのはかっこよくないですからね。

2. DHT11 はなぜ ESP32 に接続するのですか?Arduino Uno ではなく?

前述のように、Arduino Uno を使う場合、データを ESP32 に送信するために別途 UART の方法を使わなければならず、面倒なので直接 ESP32 で実装しました。ちょうどライブラリも ESP32 をサポートしていました。Arduino Uno で実装することももちろん可能です。

3. なぜ MQTT を選択したのですか?

この通信プロトコルは軽量で、他のプロトコルに比べてオーバーヘッドが少ないため、CPU の速度が遅く、メモリが少ない IoT のシナリオに非常に適しています。もちろん、可用性はそこまで良くはありませんが。

4. なぜデータベースを 2 つ作成するのですか?

注意深い読者は、分析用のデータベースとアプリ用のデータベースが別に作成されていることに気づくでしょう。まず、私は Prometheus に不慣れで、クエリを自分で書いてデータを取得するのに時間がかかるかもしれないからです。もう一つは、Postgres の SQL 構文が Node.js とより統合されているため、書きやすいという点です。後で Prometheus のクエリをもう少し触ってみるかもしれません!

成果

ネット上で有名な空気監視製品といえば AWAIR ですが、洗練された UI、温湿度と二酸化炭素の検出に加え、化学物質や PM2.5 も検出できます。ただし、価格は高く、1 台あたり 149 ドル、日本円で約 4,470 台湾元に相当します。それに対して、今回の実装は合計で:

  • DHT11 温湿度センサー:約 60 元
  • MH-Z14A CO2 センサー:約 800 元
  • ESP32 開発ボード:約 200 元
  • Arduino Uno:約 800 元(純正品で、nano に代替すればもっと安くなります)

合計:1,860 元

もちろん、見た目は非常に簡素ですが XD、少しプログラムを変更すれば、Google Home と組み合わせたり、簡単なモバイルアプリを作成したりすることもできるはずです。

回路

少し整理すれば、ぎりぎりボックスに収まりそうです。

Grafana

Web インターフェース

UI は非常に簡素ですが、ここではデモを作成しただけです。

gauge

センサー紹介:DHT11 と MH-Z14A

MH-Z14A CO2 センサー

二酸化炭素濃度を取得するためには、まずセンサーが必要です。ネットで調べたところ、二酸化炭素濃度を取得するには、NDIR(Non Dispersive Infra Red、非分散式赤外線)という方法が使われています。

その原理は、気体が特定の波長の赤外線を吸収する特性を利用して、気体の濃度を計算するものです。二酸化炭素が吸収する赤外線の波長範囲は 4.26μm なので、この波長範囲に対して計算を行うことで濃度を得ることができます。計算の公式は今回の焦点ではないので飛ばしますが、このセンサーの使用方法については触れておきたいです。

ネットで調べたところ、中国語のリソースは非常に少なかったので、ここでは概略を説明します。データシート Datasheet を参照しながら見ていきましょう。ネット上には多くのデータシートが存在しますが、現在私が見た中で最も詳細なものです。

このデータシートからいくつかのことがわかります:

  1. MH-Z14A は UART を介して通信を行い、コマンドを送信すると応答が返ります。コマンドは二酸化炭素濃度の測定だけでなく、ゼロ調整や測定範囲の調整なども含まれます。

    コマンド機能(原文 / 日本語訳)備考
    0x86ガス濃度の測定 / 二酸化炭素濃度の測定
    0x87ゼロポイント値のキャリブレーション / ゼロ調整このセンサーには 3 つのゼロ調整方法があり、コマンド送信がその一つです。
    0x99スパンポイント値のキャリブレーション / 二酸化炭素の検出範囲を変更
    0x79ゼロポイント値の自動キャリブレーション機能の開始/停止 / 自動キャリブレーション機能の使用(または停止)出荷時に、このセンサーは自動キャリブレーションがオンになっています。
  2. MH-Z14A は 3 種類の出力方式(超便利 XD)をサポートしており、それぞれ UARTPWM およびアナログです。二酸化炭素濃度を取得するために、必要な出力を選択できます。今回の実装では、便利で追加のデジタル変換が不要なため、UART を選択しました。

DHT11 センサー

DHT11 は温湿度センサーです。低消費電力で構造がシンプルで、ピンは 3 本だけです。Vcc と GND に接続すればすぐに動作を開始できます。ここで特に注目すべきは、DHT11 のデータ伝送方式です。DHT11 にはデータバス用のピンが 1 本しかないので、どのように温湿度のデータを伝送するのでしょうか?

その答えは時間です。実際、ハードウェアにおける多くのデータ通信は、正確な時間制御に依存しています。たとえば、8 ビットのデータを取得したい場合、2ms ごとに単位を設け、2ms ごとに読み取り、16ms で 8 ビットのデータを取得できます。

ただし、これを実現するためにはいくつかのことを考慮する必要があります:

  • 相手がいつデータの送信を開始したのか?つまり、私の 2ms の計測はいつから始まるのか?
  • 伝送中にエラーが発生した場合(環境、短絡、一時的なデータロスなど)、どのように対処するのか?

これらはすべてデータシートに記載されていますので、一緒に見てみましょう。

シングルバスデータフォーマットは、MCU と DHT11 センサー間の通信および同期に使用されます。1 回の通信プロセスは約 4ms です。データは小数部と整数部で構成されています。完全なデータ送信は 40 ビットで、センサーは最上位ビットを最初に送信します。データフォーマット:8 ビット整数 RH データ + 8 ビット小数 RH データ + 8 ビット整数 T データ + 8 ビット小数 T データ + 8 ビットチェックサム。データ送信が正しい場合、チェックサムは「8 ビット整数 RH データ + 8 ビット小数 RH データ + 8 ビット整数 T データ + 8 ビット小数 T データ」の最終の 8 ビットであるべきです。

データシートの説明によると、各通信プロセスには 4ms の時間がかかり、データの構成は 8 ビット相対湿度(整数部)+ 8 ビット相対湿度(小数部)+ 8 ビット温度(整数部)+ 8 ビット温度(小数部)+ 8 ビットチェックサムです。

ここでのチェックサムは、2 番目の質問に対する回答となります。計算方法は、すべてのデータを合計し、最後の 8 ビットを取得することです。(humidity + humidityDec + temp + tempDec != parity

これにより、計算結果がチェックサムと等しくない場合、送信の過程に問題があったことがわかります。もちろん、送信過程での問題がたまたまチェックサムと一致することもありますが、その場合は認めざるを得ません。(通常の環境下ではその確率は低いはずです)

DHT11 はどのようにデータを送信するのか?

最初の質問に答えるために、データシートの図を参照すると、通信プロセス全体が説明されています。

Screenshot from 2020-07-21 21-34-22

  1. 温度を感知する準備をして(Arduino が DHT11 にデータを要求する時)、最初に HIGH -> LOW の信号を送信し、少なくとも 18ms 維持します。その後、信号を LOW -> HIGH に変更します。Arduino では delay()digitalWrite の2つの関数を使って実現できます。p.s:ここで、なぜ DHT11 がこの信号を検出するのにそんなに長い時間が必要なのかよくわかりません。ハードウェアの専門家の方々に教えていただければ幸いです。
  2. ここで MCU は 20us 経過すると、信号を HIGH から LOW に戻します。(この時、Arduino のピンを INPUT に変更することを忘れないでください。pinMode(PIN, INPUT) で実現できます)
  3. この時、信号は LOW になっており、80us 後には HIGH に変わります。そしてさらに 80us 後に、信号は LOW に戻ります。
  4. データの送信が開始されます(次の図へ続く)

Screenshot from 2020-07-21 21-44-40

ついにデータ送信が始まります!データシートには、0 と 1 をどのように区別するかが説明されています。図に示されているように、電圧が高位のまま 26-28us 維持されると、応答データは 0 を意味し、電圧が高位で 70us 維持されると、応答データは 1 を意味します。各ビットの間では、高位から低位に変わり、50us 維持した後に再び高位に戻ります。これにより、各ビット間の間隔を明確に区別し、Arduino が読み取り時に誤判定を減らすことができます。

この時間を計算するには、Arduino の micros() を使用します。簡単な実装は以下のようになります:

void blockUntil(int state)
{
  while (digitalRead(PIN) != state)
  {
  }
}

void main() {
  auto timeS = micros();
  blockUntil(LOW);
  auto timeE = micros();
  auto result = timeE - timeS > 60 ? 1 : 0;
}

ここではデータシートで説明されている 50us ではなく 60us を使っています。なぜなら、時間がちょうど合うと、読み取り時にエラーが発生することがあるからです。私の推測ですが、プログラムを実行する際に、コンパイルされてマシンコードに変換されるため、CPU が実行中にいくつかの命令サイクルを消費し、累積結果がこの 50us の不正確さを引き起こすのではないかと思います。しかし、もしそうであれば、50us を小さくするべきで、逆に大きくするのはおかしいと思うので、DHT11 自体が遅いのかもしれません。

全プロセスが完了すれば、データを取得できるはずです!今回の実装はライブラリを使って完成しましたが、機会があれば、次の記事でライブラリなしで機能を実装する方法を紹介したいと思います。実際、データシートを読み込むことにさえ情熱を注げば、実装はそれほど難しくはないはずです。

まとめ

今日は、このアプリケーションを実現するために必要なセンサーや用途、データシートを閲覧しました。これで、皆さんはこの 2 つのセンサーがどのようにデータを伝送するかについて概念を持ったのではないでしょうか!次回は、ハードウェア間の一般的な通信手段である UART について説明し、実装時に遭遇した問題とその解決方法(実際にはライブラリを使うことですが!)を共有します。

この記事が役に立ったと思ったら、下のリンクからコーヒーを奢ってくれると嬉しいです ☕ 私の普通の一日が輝かしいものになります ✨

Buy me a coffee