ESP32+BME680+Ambientで温度・湿度・気圧・ガスの測定値をOLEDとスマホにグラフ表示する

## ［１］概要
BME680で測定した温度・湿度・気圧・ガスの値をOLEDに表示し、さらにデータをwifiでAmbientに送信し、スマホやパソコンにグラフ表示するシステムを作りました。
マイコンはESP32 WROOM32（モジュールはESP32 DEVKIT V1）、開発環境はArduino IDE、使用言語はC, C++です。
![開発フェーズ３.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834831/c1701650-45aa-80ff-7f48-6e82667bbd64.jpeg)
![OLED+スマホ表示.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834831/1a5dd058-209d-20ab-7fc5-31b52745c11c.jpeg)
![BME680-Ambientグラフ表示600.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834831/af5a0ae1-5bce-bdbe-8482-03293d088658.jpeg)

## ［２］開発手順
ハード的にはESP32に外付けでBME680とOLEDをI2Cで接続します。ESP32に内蔵されているシリアル通信(RS232)とWifiの機能も使用します。
ソフト面ではWire(I2C), BME680, OLED, Wifi, Ambient と5つのライブラリを使用します。

多くの要素を含むシステムを作成する場合、一度で全てを入れたものを作ろうすると大概うまくいかずデバッグに大変苦労します。というか、めっちゃ苦労したけど結局分からず断念したのでした（泣）。

そして新たに３つのフェーズに分けて再出発しました。
第１フェーズ：BME680のデータをシリアルモニタに表示（下記の図）
第２フェーズ：上記に加え、BME680のデータをOLEDにも表示する（下記の図）
第３フェーズ：さらにBME680のデータをWifiでAmbientに送信する（この記事の冒頭の図）
![開発フェーズ１.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834831/1723392b-e6e2-81d5-4b22-ea9c32c77072.jpeg)
![開発フェーズ２.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834831/470b6660-46a5-a448-6575-d549c17e4e76.jpeg)
第２フェーズで手こずったという話は下記に書きました。

https://qiita.com/totuto/items/885ee3939d0a06c5edc4

## ［３］回路図と実体配線図
回路図と ~~実態~~ 実体配線図はFritzingで作成しました。
［問題点１］BME680のデータは、Adafruitのピン数が多いのはネットにあるけど秋月電子やストロベリーリナックスの4ピンのデータは見つかりません。
［問題点２］OLEDのデータはネットにあり、配線図用データは問題ないけど回路図がめちゃくちゃ巨大！他の部品との整合性とかバランスとか大問題！しかも端子がグリッドに乗ってないので配線が斜めになってめっちゃ気持ち悪い！
![OLED回路図でかすぎ！.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834831/72b56d28-6237-e842-5d5a-63c9f569dbb4.jpeg)
「人は見た目が9割」という過激なタイトルの本が何年か前に話題になりました。タイトルから「じゃあ韓国行って整形手術しましょう」って話かと思いきや、そうではなく内面を磨きましょうという内容だったかな、大学教授が書いた極めて真面目な本です。それはともかく回路図・配線図は見た目が10割、簡単な回路をややこしく描くのはど素人、複雑な回路も単純に見えるように ~~書く~~ 描くのが玄人の仕事というポリシーの私には、強いこだわりがあるのでこんな回路図データを使用する訳にはいきません。

などと大見得きったけど、どちらも自分で作るのは面倒だなぁ・・ということで、BME680ではなく4ピンのBME280のデータで取り敢えず作成し、その後画像処理ソフトで修正しました。
![ESP32+BME680+OLED回路図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834831/0cad052d-fe91-bd84-9bc2-a5202158abe4.jpeg)
I2Cインタフェースのプルアップ抵抗は省略しました。またESP32の場合、内蔵されている抵抗を使用するようプログラムで記述することも不要、というのをどこかで読んだので今回はそのようにしてみました。
![ESP32+BME680+OLED配線図.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834831/6a9a8df0-05a1-f857-3531-bc932132bf9e.jpeg)
BME680の画像はPowerPointで作成し画像処理ソフトで貼り付けました（インチキ）。

ブレッドボードは普通の5穴のものではなく6穴のものを使用しました。6穴ブレッドボードの製品とFritzingデータの入手先なども下記に書きました。

https://qiita.com/totuto/items/885ee3939d0a06c5edc4

![ESP32+BME680+OLED+BB(2)small.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834831/833509a8-1398-3d80-6721-682d19ecbcf0.jpeg)

## ［４］プログラム
今回は、ライブラリのインストール手順などは省略します。先人がネットに色々投稿されてますので検索して下さい。プログラムは最後に載せます。説明は省略します。コメントを見ても分からない場合はネットで検索してみて下さい（手抜き！）。

但し１点だけ、気圧に関するよく分からない変数について解説したサイトがあったので少し編集・引用し且つ補足説明を加えて紹介します。

## 【参考１】BME280 搭載、温度・湿度・気圧センサーを SPI で動かしてみた(ESP-WROOM-02 (ESP8266)使用)

https://www.mgo-tec.com/blog-entry-bme280-spi-esp8266-wroom-01.html/2

気圧から標高を計算する関数と、それに関連した詳しい説明があります。以下要約です。

SeaLevelPressure_hPa という変数は、標高計算のための海面（海抜０ｍ）の気圧です。
海面気圧は頻繁に変動するため、正確にはアメダスのデータから観測地点の現在の海面気圧を入力します。
①気象庁のアメダスの情報：　http://www.jma.go.jp/jp/amedas_h/
②場所の選択：左上のボックスの「一覧表へ」→「都道府県選択」か、または地図の地点をクリック
③表示形式を選択：1時間毎・10分毎の表、またはグラフ
![アメダス東京640.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834831/e7a6b765-aa9d-7bc5-b4c6-1b40b09e4a60.jpeg)
しかし測定のたびにアメダスのデータを確認して、しかも「コンパイル」して・・というのは全く実用的ではないので標準大気圧(1気圧)とされる 1013.25hPa を設定します。
#define SEALEVELPRESSURE_HPA (1013.25)

-----------------------------------------------------------------------------
## 【参考２】測定した気圧の値のずれに関する考察
symamoneの技術ノート
あさがおIoT観察日記②：ArduinoとBME280で気圧を測定する【大人の自由研究】

https://symamone-tec.com/asagao_iot_3/#i-4

-----------------------------------------------------------------------------

```C++:ESP32+BME680+OLED+Wifi+Ambient
/*
 * ESP32 + BME680で温度・湿度・気圧・ガスを測定し、OLEDに表示し、Ambientに送信する
 *   ボード：ESP32 DEVKIT V1
 *   2022-08-04
 * （注1）BME680 I2Cアドレス=0x76
 *       Adafruit_BME680.h は、I2Cアドレス=0x77 なので要変更！
 * （注2）ストロベリーリナックス社のBME680モジュールはSDA,SCLピンのプルアップが必要。 
 * （注3）OLED I2Cアドレス=0x3C
 */
#include <Wire.h>             // I2C
#include "Adafruit_BME680.h"
#include <Adafruit_GFX.h>     // グラフィックのコア・ライブラリ
#include <Adafruit_SSD1306.h> // OLED
#include <WiFi.h>
#include "Ambient.h"

Adafruit_BME680 bme680;       // I2C
#define SEALEVELPRESSURE_HPA (1013.25)  // 海面気圧

#define OLED_I2C_ADRS 0x3C    // I2C address of OLED
Adafruit_SSD1306 OLED(128, 64, &Wire, 4);
    // 引数 WIDTH, HEIGHT, &Wire, OLED_RESET
    // 4つ目の引数はRESETピン -1はArduinoのRESETピンと共有

WiFiClient client;
Ambient ambient;

#define PERIOD 5
const char*  ssid      = "***************";  // WiFi
const char*  password  = "***************";  //
unsigned int channelId = *****;              // AmbientのチャネルID
const char*  writeKey  = "****************"; // ライトキー

//--------------------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(10);
  Serial.println("Start");

  WiFi.begin(ssid, password);              //  WiFi APに接続
  while (WiFi.status() != WL_CONNECTED) {  //  WiFi AP接続待ち
      delay(100);
  }
  Serial.print("WiFi connected\r\nIP address: ");
  Serial.println(WiFi.localIP());

  // SSD1306_SWITCHCAPVCC ：表示用電圧を3.3Vから生成する
  if(!OLED.begin(SSD1306_SWITCHCAPVCC, OLED_I2C_ADRS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  // OLEDに初期表示バッファの内容を表示する
  // ライブラリはこれをAdafruitスプラッシュ画面で初期化する
  OLED.display();
  delay(2000);               // 2秒待つ
  OLED.clearDisplay();
  OLED.display();
  OLED.setTextSize(1);       // フォントサイズを1～で指定
  OLED.setTextColor(WHITE);  // フォント色

  if (!bme680.begin()) {
      Serial.println("Could not find a valid BME680 sensor, check wiring!");
      while (1) {
          delay(0);
      }
  }
  bme680.setTemperatureOversampling(BME680_OS_8X); // 温度のオーバーサンプリング
  bme680.setHumidityOversampling(BME680_OS_2X);    // 湿度のオーバーサンプリング
  bme680.setPressureOversampling(BME680_OS_4X);    // 気圧のオーバーサンプリング
  bme680.setIIRFilterSize(BME680_FILTER_SIZE_3);   // フィルタを初期化
  bme680.setGasHeater(320, 150);                   // ガス測定用ヒーター 320℃ 150 ms

  ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientを初期化
}
//--------------------------------------------------------------------------
void loop() {
  int   t = millis();
  float temp, humid, pressure, gas;

  OLED.setCursor(0,0);
  OLED.clearDisplay();
  if (! bme680.performReading()) {
    Serial.println("Failed to perform reading :(");
    return;
  }
  temp =     (float)bme680.temperature;
  humid =    (float)bme680.humidity;
  pressure = (float)bme680.pressure / 100.0;
  gas =      (float)bme680.gas_resistance / 1000.0;

  Serial.print("temp: ");       Serial.print(temp);     Serial.print(" *C");
  Serial.print(", humid: ");    Serial.print(humid);    Serial.print(" %");
  Serial.print(", pressure: "); Serial.print(pressure); Serial.print(" hPa");
  Serial.print(", gas: ");      Serial.print(gas);      Serial.println(" KOhm");

  OLED.print("Temperature: ");  OLED.print(temp);       OLED.println(" *C");
  OLED.print("Humidity:    ");  OLED.print(humid);      OLED.println(" %");
  OLED.print("Pressure: ");     OLED.print(pressure);   OLED.println(" hPa");
  OLED.print("Gas:     ");      OLED.print(gas);        OLED.println(" KOhm");
  OLED.display();

  // 温度、湿度、気圧、CO2、TVOCの値をAmbientに送信する
  ambient.set(1, String(temp).c_str());
  ambient.set(2, String(humid).c_str());
  ambient.set(3, String(pressure).c_str());
  ambient.set(4, String(gas).c_str());
  ambient.send();

  t = millis() - t;
  t = (t < PERIOD * 1000) ? (PERIOD * 1000 - t) : 1;
  delay(t);
}
//--------------------------------------------------------------------------