M5Stack と BME680 でガス報知器(アラーム)および気圧・温湿度グラフで「熱中症」警告表示器を作ってみた

M5Stack

BME680 センサと M5Stack との I2C 接続

BOSCH BME680 と M5Stack を I2C 接続しておきます。
接続方法は、以下の記事を参照してください。

M5Stack ( ESP32 )で、ガス・気圧・温度湿度センサ BME680 を使ってみた

最新版 Arduino – ESP32 のインストールを済ませておく

Arduino IDE で開発するので、最新版の Arduino core for the ESP32 ( 以下Arduino – ESP32 )をインストールしておいてください。
必ず2018/07/10以前の古いバージョンは削除してから再インストールしてください

Arduino – ESP32 のインストール方法は以下の記事を参照してください。

Arduino core for the ESP32 のインストール方法

自作ライブラリインストールやAmbientライブラリインストール、日本語漢字フォント等について

以下の記事を参照し、私の自作ライブラリや、クラウドサービスの Ambientライブラリインストール、日本語漢字フォントの micro SDHC への保存などを済ませておいてください。

M5Stack に BME280 のグラフと、ニュース、天気予報を表示させ、Ambient へデータ送信させてみた

今回から私の自作ライブラリでは、BOSCH製 BME680ドライバを使うライブラリを追加し、その他諸々修正してバージョンアップしています。

ESP32_mgo_tec ライブラリ

beta ver 1.0.40 に更新しています
https://github.com/mgo-tec/ESP32_mgo_tec

※コンパイルエラー
display_bme280_i2c.h: fatal error: bme280.h: No such file or directory
が出る場合、インストールした私の自作ライブラリのSensorフォルダの中の以下の2つのファイルを削除してみてください。
Windows 10 の場合、以下のパスになります。

C:\Users\__user_name__\Documents\Arduino\libraries\ESP32_mgo_tec-master\src\ESP32_mgo_tec_bV1\Sensor

削除するファイルは以下の2つです。
display_bme280_i2c.h
display_bme280_i2c.cpp
(2018/09/24)

 

また、この自作ライブラリには、Arduino IDE 標準の Time ライブラリが必要です。
これは外部ライブラリになっているので、GitHub の以下のページから ZIPファイルをダウンロードしてインストールしてください。
https://github.com/PaulStoffregen/Time

BOSCH 純正 BME680 ドライバライブラリのインストール

私の自作ライブラリでは、BOSCH 純正の BME680 ドライバライブラリが必要です。
正確な測定をするなら、やはりメーカー純正のドライバを使った方が確実です。
インストール方法は以下の記事を参照してください。

M5Stack ( ESP32 )で、ガス・気圧・温度湿度センサ BME680 を使ってみた

異常値検知の警告メッセージ表示、および警告音について

では、Arduino IDE にスケッチを入力する前に、ディスプレイに表示させる異常値検知警告メッセージ表示や、異常値検知の方法、および閾値について自己流の方法を説明します。

温度・湿度の警告メッセージ

温度湿度の警告メッセージ表示は、

湿度60%以上 → 湿度警告
湿度 40%以下 → 乾燥警告
温度 30℃以上湿度60%以上 → 熱中症警告

としました。

湿度については、人間が快適に過ごせる 40~60%という範囲を超えたら警告表示を出すようにしてみました。
これは、各自好きなように設定すれば良いと思います。

今回は温度・湿度については アラーム(Beep音)は鳴らさないようにしました。

ガスセンサの警告メッセージおよび警告音( Beep音 )アラーム

今回の警告アラーム音( Beep音 )は、BME680 のガスセンサの異常値検知の時のみ鳴らします。
警告アラーム音 ( Beep音 )の出力方法は以下の記事を参照してください。

M5Stack の内蔵スピーカーから Beep 音(電子音)を出す実験

ガスセンサは、40000ohm を下回ったら、問答無用で警告音と危険メッセージを発するようにしました。

ただ、以下のグラフの様に、ピーク的に急激に下がっても、40000ohmを下回らない時があります。
下図の最初のピークは40000 を下回らないのですが、やっぱりこれは異常なので、見逃さず検知してもらいたいものです。
下図では、その後、再度ピークがあり、40000 を下回ったので危険レッドライン表示と警告音が鳴りました。

40000 を下回らないでも異常なピークを検出したら警告音を発するためには、数学の統計学上でいろいろなやり方があるらしいです。
いろいろ調べて、私が考えた結果、一番簡単なのは、上図の様に過去のデータ直近50 サンプルだけ平均値を取っておいて、現在センサ値との差が 10000 ohm を超えたらアラームと警告表示を発するようにしました。
私の作ったプログラムでは、過去318サンプルのデータを SRAM に蓄積していますので、平均値を出すのは簡単です。
この場合、値が上昇して、差が10000 以内に収まるとアラームや警告表示が消えます。

例えば、「おなら」をした場合は素早く反応し、警告音を発しますが、すぐに解除されるようになります。

とりあえず自作ガスセンサ報知器としてはこれで良いのではないかなと思っています。

ガス抵抗値の危険ラインの 40000 ohm という値については、各自いろいろ測ってみて、これ以上下がったら危険値というものを見つけたら良いと思います。

また、過去データ 50 サンプルというのも、いろいろ測定してみて、自分の環境に合ったサンプル数を決めれば良いと思います。

この他、センサ異常値検出の手法は様々で、複雑な方法がありますので、いつかいろいろトライしてみたいと思っています。

BME680 タスクとBeep(アラーム)音発生タスクは必ず別タスクにする

前回記事で取り上げた方法で警告アラーム ( Beep音 )を発生させる場合、音を発している間は必ずタスクが停止します。

でも、BME680 のガスセンサを動作させる場合、タスクを停止してしまうとガスセンサの値が不安定になります。
ですから、ガスセンサは必ず一定時間間隔で常時起動させ続けなければなりません。

また、こちらの記事でも述べた通り、Arduino – ESP32 をマルチタスクで動作させる場合、I2Cライブラリ関数は同じタスクに置かねばなりません。

それを考えると、例えば、BME680 関連関数は setup関数、メインloop関数内に置き、別タスクの core 0 で動かせば良いと思います。

ここで注意!!

今回のプログラムでは、3分毎に WiFiに接続し、クラウドサービスの Ambient に接続して、センサデータを送っています。

そうなると、このタスクをどこに置くかが問題になります。
BME680 のセンサ取得は絶対に途切れさせたくないので、必然と core1 のメインloopに置くことになります。

すると、WiFi と Web のコネクション処理は Beep音発生関数と同じタスクに置くことになり、Webコネクション中は Beep音が停止します。

Beep音発生プログラム関数を無限ループにしてしまうと、Beep音発生中に Web に接続できなくなってしまうので、そうならないようなプログラムにしたいですね。

また、Beep音発生を core 0 にすると、その無限ループはウォッチドッグタイマを動作させるために、必ず delay(1) を置かねばなりません。

そうすると、前回の記事の Beep音発生プログラムをそのまま使うと、Beep音の音程が変わってしまいます。
音程が変わらないようにうまく delay(1)を置くプログラミングを考えねばなりません。

以上を考慮して、私なりに考えたプログラム構成は次の項のようになります。

スケッチの入力

では、前項のセンサデータと警告表示、警告音の発生する閾値や、Beep音発生とWeb接続タスクの置き場所を考慮しながら、以下のスケッチを入力してみて下さい。

WiFiClientSecureを使って SSLサイトから記事を取得し、その間、WiFi.mode( WIFI_OFF )と WiFi.begin の使用を繰り返すと、heap memory が減り続け、接続できなくなる現象が出ています。
以下の記事も合わせて参照してください。
ESP32 および M5Stack で数時間後に Web 記事取得失敗する問題について
(2018/09/25時点)

 

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#include "mgo_tec_m5stack_bme680.h" //beta ver 1.0.3-
#include "Ambient.h"
#include <driver/dac.h> //Arduino-ESP32 driver

const int8_t sck = 18; // SPI clock pin
const int8_t miso = -1; // MISO(master input slave output) don't using
const int8_t mosi = 23; // MOSI(master output slave input) pin
const int8_t cs = 14; // Chip Select pin
const int8_t dc = 27; // Data/Command pin
const int8_t rst = 33; // Reset pin
const int8_t lcd_led_pin = 32;

const uint8_t cs_sd = 4; //SD card CS ( Chip Select )

const int sda = 21; //BME680 I2C
const int scl = 22; //BME680 I2C

const char* ssid = "xxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください

const char* utf8sjis_file = "/font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく
const char* shino_full_font_file = "/font/shnmk16.bdf"; //オリジナル東雲全角フォントファイル
const char* shino_half_font_file = "/font/shnm8x16.bdf"; //半角フォントファイル名を定義

mgo_tec_esp32_bv1::SdShinonomeFont SFR(cs_sd, 40000000);
mgo_tec_esp32_bv1::ILI9341Spi LCD;

//-----メッセージウィンドウ関連定義----
mgo_tec_esp32_bv1::MessageWindow wifi_msg, ambient_msg;
mgo_tec_esp32_bv1::MessageWindow t_h_msg, gas_msg, beep_msg;
//------BME680 初期化------------------------
mgo_tec_esp32_bv1::DisplayBme680I2c bme_disp;
mgo_tec_esp32_bv1::FontParameter pres_font, temp_font, hum_font, gas_font;
mgo_tec_esp32_bv1::FontParameter graph_font;
mgo_tec_esp32_bv1::GraphParameter pres_graph, temp_graph, hum_graph, gas_graph;
int time_measure_mode = 0;
uint32_t average = 0;
//------Ambient関連初期化-------------
unsigned int channelId = 1000; // AmbientのチャネルID
const char* writeKey = "**********"; // ライトキー
WiFiClient client;
Ambient ambient;
boolean ambient_send_ok = false;
//------WiFi Web 接続関連-------------
boolean isWifi_connect_first = true;
uint32_t web_connect_last_time = 0;
bool isWeb_connect_first_time = true;
//-----ボタンスイッチ 引数初期化--------
mgo_tec_esp32_bv1::ButtonSwitch btnA, btnB, btnC;
const uint8_t buttonA_GPIO = 39;
const uint8_t buttonB_GPIO = 38;
const uint8_t buttonC_GPIO = 37;
//------Beep音関連定義-------------------
uint8_t beep_volume = 1; //min 1, max 255
boolean isBeep_on = false;
boolean isBeep_set = false;
uint32_t beep_last_time = 0;

//***********セットアップ****************************
void setup() {
  delay(1000);
  Serial.begin(115200);
  pinMode(buttonA_GPIO, INPUT); //GPIO #39 は内部プルアップ無し
  pinMode(buttonB_GPIO, INPUT); //GPIO #38 は内部プルアップ無し
  pinMode(buttonC_GPIO, INPUT); //GPIO #37 は内部プルアップ無し

  //ライブラリ初期化。3ファイル同時に開く。
  SFR.init3File(utf8sjis_file, shino_half_font_file, shino_full_font_file);

  LCD.ILI9341init(sck, miso, mosi, cs, dc, rst, lcd_led_pin, 40000000, false); //microSDを使う場合、必ず false にする
  LCD.displayClear();
  LCD.brightness(255); //LCD LED max brightness 255

  //-----BME680関連--------------------
  bme_disp.initBme680Force( sda, scl, 100000, 28 ); //I2C周波数100kHz, センサ周辺気温28℃
  setupBmeValueDisp(); //BME680 数値表示セットアップ
  setupBmeGraphDisp(); //BME680 グラフ表示セットアップ
  setupMessageWindow(); //メッセージウィンドウ表示セットアップ
  //----Ambientライブラリ初期化--------
  ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化
  //----------------------------------
  TaskHandle_t th; //マルチタスクハンドル定義
  xTaskCreatePinnedToCore(Task1, "Task1", 8192, NULL, 5, &th, 0); //マルチタスク起動
}
//***********メインループ****************************
void loop(){
  //メッセージウィンドウ表示
  wifi_msg.dispWifiStatusMsgShort(); //WiFiステータス表示
  ambientMessage(); //Ambient送信有効または無効表示
  beepMessage(); //Beep有効または無効表示
  dispTempHumMessage(); //熱中症、湿度警告表示
  dispGasMessage(); //ガス警告表示

  //BME680関連表示
  bme_disp.getData();
  bme_disp.displayValue( pres_font, temp_font, hum_font );
  dispGasValue();
  bme_disp.drawGraphNowAll( pres_graph, temp_graph, hum_graph, gas_graph );
  bme_disp.displayMinMaxValue( pres_graph, temp_graph, hum_graph, gas_graph, pres_font, temp_font, hum_font, gas_font );

  button_action(); //ボタン操作
}
//************ マルチタスクループ ******************
void Task1(void *pvParameters) {
  //Wireライブラリ関数は全て同じタスクに置かねばならないので注意
  while(1){
    if( ambient_send_ok == true ){
      connectWeb(); //WiFi接続、Ambient接続
    }
    beep();
    if( isBeep_on == false || isBeep_set == false ){
      delay(1); //マルチタスクの場合、これ絶対必要!
    }
  }
}
//*************************************************
void setupBmeValueDisp(){
  //センサー数値フォント色 red(0-31), green(0-63), blue(0-31)
  pres_font.Xsize = 2, pres_font.Ysize = 2;
  pres_font.red = 31, pres_font.green = 0, pres_font.blue = 0;
  pres_font.bg_red = 0, pres_font.bg_green = 0, pres_font.bg_blue = 0;

  temp_font.Xsize = 2, temp_font.Ysize = 2;
  temp_font.red = 0, temp_font.green = 63, temp_font.blue = 0;
  temp_font.bg_red = 0, temp_font.bg_green = 0, temp_font.bg_blue = 0;

  hum_font.Xsize = 2, hum_font.Ysize = 2;
  hum_font.red = 0, hum_font.green = 55, hum_font.blue = 31;
  hum_font.bg_red = 0, hum_font.bg_green = 0, hum_font.bg_blue = 0;

  gas_font.Xsize = 2, gas_font.Ysize = 1;
  gas_font.red = 31, gas_font.green = 63, gas_font.blue = 0;

  char unit_c[4][5] = { "hPa ", "℃", "%", " ohm" };
  uint16_t x0 = 0, y0 = 64;
  bme_disp.initDispValue( pres_font, temp_font, hum_font, x0, y0, unit_c );
  y0 = y0 + 32;
  bme_disp.initDispGasValue( gas_font, x0, y0, unit_c[3] );
}
//*************************************************
void setupBmeGraphDisp(){
  bme_disp.m_meas_period = 3000; //BME680測定間隔デフォルト値3秒

  pres_graph.frame_y0 = 112;
  pres_graph.x_total_points = 317;
  pres_graph.y_total_points = 100;

  graph_font.red = 31, graph_font.green = 63, graph_font.blue = 31;

  pres_graph.red = 31, pres_graph.green = 0, pres_graph.blue = 0;
  temp_graph.red = 0, temp_graph.green = 60, temp_graph.blue = 0;
  hum_graph.red = 0, hum_graph.green = 55, hum_graph.blue = 31;
  gas_graph.red = 31, gas_graph.green = 63, gas_graph.blue = 0;

  pres_graph.max_value = 1050.0;
  pres_graph.min_value = 850.0;
  temp_graph.max_value = 100.0;
  temp_graph.min_value = 0.0;
  hum_graph.max_value = 100.0;
  hum_graph.min_value = 0.0;
  gas_graph.max_value = 100000.0;
  gas_graph.min_value = 3000.0;
  bme_disp.m_time_measure_mode = (int8_t)bme_disp.Period3sec;
  bme_disp.initGraphAll( pres_graph, temp_graph, hum_graph, gas_graph, graph_font );
}
//*****メッセージウィンドウ設定**************************
void setupMessageWindow(){
  //WiFiメッセージ設定
  wifi_msg.m_x0 = 0;
  wifi_msg.m_y0 = 0;
  wifi_msg.m_size = 1;
  wifi_msg.m_padding = 4; //pixel単位
  wifi_msg.m_txt_length = 6; //文字表示数(半角相当)
  wifi_msg.WifiStatus = wifi_msg.WifiFailed;
  //Ambientメッセージ設定
  ambient_msg.m_x0 = wifi_msg.m_x0 + 6 * 8 + 8;
  ambient_msg.m_y0 = 0;
  ambient_msg.m_size = 1;
  ambient_msg.m_padding = 4; //pixel単位
  ambient_msg.m_txt_length = 27; //文字表示数(半角相当)
  //Beep設定表示
  beep_msg.m_x0 = ambient_msg.m_x0 + 8 * ambient_msg.m_txt_length + 8;
  beep_msg.m_y0 = 0;
  beep_msg.m_size = 1;
  beep_msg.m_padding = 4;
  beep_msg.m_txt_length = 4;
  //温度・湿度警告メッセージ設定
  t_h_msg.m_x0 = 0;
  t_h_msg.m_y0 = 24;
  t_h_msg.m_txt_length = 10;
  t_h_msg.m_padding = 4;
  t_h_msg.m_size = 2;
  //ガス警告メッセージ設定
  gas_msg.m_x0 = 168;
  gas_msg.m_y0 = 24;
  gas_msg.m_txt_length = 9;
  gas_msg.m_padding = 4;
  gas_msg.m_size = 2;
}
//*************************************************
void dispTempHumMessage(){
  if( bme_disp.m_fTemperature > 30.0 && bme_disp.m_fHumidity > 60.0 ){
    t_h_msg.font.red = 31, t_h_msg.font.green = 63, t_h_msg.font.blue = 31;
    t_h_msg.m_bg_red = 31, t_h_msg.m_bg_green = 0, t_h_msg.m_bg_blue = 0;
    t_h_msg.dispMsgWindow( 0, "熱中症警告" );
  }else if( bme_disp.m_fHumidity > 60.0 ){
    t_h_msg.font.red = 0, t_h_msg.font.green = 0, t_h_msg.font.blue = 31;
    t_h_msg.m_bg_red = 31, t_h_msg.m_bg_green = 63, t_h_msg.m_bg_blue = 0;
    t_h_msg.dispMsgWindow( 1, " 湿度警告 " );
  }else if( bme_disp.m_fHumidity < 35.0 ){
    t_h_msg.font.red = 0, t_h_msg.font.green = 0, t_h_msg.font.blue = 31;
    t_h_msg.m_bg_red = 31, t_h_msg.m_bg_green = 63, t_h_msg.m_bg_blue = 0;
    t_h_msg.dispMsgWindow( 2, " 乾燥警告 " );
  }else{
    t_h_msg.m_bg_red = 0, t_h_msg.m_bg_green = 0, t_h_msg.m_bg_blue = 0;
    t_h_msg.clearMsgWindow( 0 );
    t_h_msg.clearMsgWindow( 1 );
    t_h_msg.clearMsgWindow( 2 );
  }
}
//*************************************************
void dispGasMessage(){
  float danger_gas_level = 40000.0; //ガス検出の危険レベル
  int32_t threshold = 10000; //異常ピーク検出の閾値(ohm)
  int32_t now_av = average - bme_disp.m_fGas;
  if( bme_disp.m_fGas < danger_gas_level ){
    gas_msg.font.red = 31, gas_msg.font.green = 63, gas_msg.font.blue = 0;
    gas_msg.m_bg_red = 31, gas_msg.m_bg_green = 0, gas_msg.m_bg_blue = 0;
    gas_msg.dispMsgWindow( 0, " GAS危険 " );
    isBeep_on = true;
    if( isBeep_set == true ) dac_output_enable( DAC_CHANNEL_1 );
  }else if( now_av > threshold ){ 
    gas_msg.font.red = 31, gas_msg.font.green = 0, gas_msg.font.blue = 0;
    gas_msg.m_bg_red = 31, gas_msg.m_bg_green = 63, gas_msg.m_bg_blue = 0;
    gas_msg.dispMsgWindow( 1, " GAS警告 " );
    isBeep_on = true;
    if( isBeep_set == true ) dac_output_enable( DAC_CHANNEL_1 );
  }else{
    gas_msg.m_bg_red = 0, gas_msg.m_bg_green = 0, gas_msg.m_bg_blue = 0;
    gas_msg.clearMsgWindow( 0 );
    gas_msg.clearMsgWindow( 1 );
    isBeep_on = false;
    dac_output_disable( DAC_CHANNEL_1 );
  }
}
//*******Ambient用メッセージウィンドウ表示*****************
void ambientMessage(){
  if( ambient_send_ok == true ){
    //Ambient用メッセージウィンドウ色設定 red(0-31), green(0-63), blue(0-31)
    ambient_msg.font.red = 31;
    ambient_msg.font.green = 63;
    ambient_msg.font.blue = 31;
    ambient_msg.m_bg_red = 0; //背景色
    ambient_msg.m_bg_green = 50; //背景色
    ambient_msg.m_bg_blue = 0; //背景色
    ambient_msg.dispMsgWindow( 1, "         Ambient ◎" );
  }else{
    ambient_msg.font.red = 31;
    ambient_msg.font.green = 0;
    ambient_msg.font.blue = 0;
    ambient_msg.m_bg_red = 15; //背景色
    ambient_msg.m_bg_green = 0; //背景色
    ambient_msg.m_bg_blue = 0; //背景色
    ambient_msg.dispMsgWindow( 2, "         Ambient ×" );
  }
}
//*******beep用メッセージウィンドウ表示*****************
void beepMessage(){
  if( isBeep_set == true ){
    beep_msg.font.red = 0;
    beep_msg.font.green = 0;
    beep_msg.font.blue = 0;
    beep_msg.m_bg_red = 0; //背景色
    beep_msg.m_bg_green = 63; //背景色
    beep_msg.m_bg_blue = 0; //背景色
    beep_msg.dispMsgWindow( 0, "Beep" );
  }else{
    beep_msg.font.red = 31;
    beep_msg.font.green = 0;
    beep_msg.font.blue = 0;
    beep_msg.m_bg_red = 0; //背景色
    beep_msg.m_bg_green = 0; //背景色
    beep_msg.m_bg_blue = 0; //背景色
    beep_msg.dispMsgWindow( 1, " -- " );
  }
}
//*******************************************
void dispGasValue(){
  if( bme_disp.m_gas_value_disp_ok == true ){
    uint16_t sample = 50;
    average = bme_disp.calcGasAverageValue( gas_graph, sample );
    Serial.printf("sample %d, GAS average = %lu ohm\r\n", sample, (long unsigned int)average);
  }
  bme_disp.displayGasValue( gas_font );
}
//*******WiFi接続、Ambientデータ送信**********
void connectWeb(){
  if( (isWeb_connect_first_time == true) || (millis() - web_connect_last_time > 180000) ){ //3分毎に記事取得
    WiFi_AP_Connect(); //WiFi起動、アクセスポイント接続
    sendDataToAmbient(); //Ambient へデータ送信
    WiFi.mode(WIFI_OFF); //省電力化のため、ESP32のWiFiをOFF
    isWeb_connect_first_time = false;
    web_connect_last_time = millis();
  }
}
//*******WiFiアクセスポイント接続*************
void WiFi_AP_Connect(){
  wifi_msg.WifiStatus = wifi_msg.WifiConnecting; //WiFiメッセージウィンドウ設定
  Serial.println();
  Serial.println(F("Connecting Wifi..."));
  Serial.println(ssid);

  if( isWifi_connect_first == true){
    //WiFiが急に接続できなくなった場合の応急処置
    WiFi.disconnect(true, true); //WiFi OFF, eraseAP=true
    delay(1000);
    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    isWifi_connect_first = false;
  }else{
    WiFi.begin(ssid, password);
    uint32_t last_time = millis();
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
      if( millis() - last_time > 20000 ) break; //Time OUT
    }
    //マルチタスクでメッセージウィンドウを正しく表示させるための処置
    if( millis() - last_time < 1000 ) delay(2000); 
  }

  int16_t wifi_state = WiFi.status();
  Serial.printf("\r\nWiFi.status = %d\r\n", wifi_state);
  if( wifi_state == WL_CONNECTED ){
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
  }

  if( wifi_state == WL_CONNECTED ){
    wifi_msg.WifiStatus = wifi_msg.WifiConnected; //WiFiメッセージウィンドウ設定
  }else{
    wifi_msg.WifiStatus = wifi_msg.WifiFailed; //WiFiメッセージウィンドウ設定
    Serial.println("WiFi AP Not Found");
  }
}
//******Ambientクラウドへデータ送信**************************
void sendDataToAmbient(){
  if( ambient_send_ok && bme_disp.m_isNewData == true ){
    if( bme_disp.m_fPressure != -500.0 && bme_disp.m_fTemperature != -500.0 && bme_disp.m_fHumidity != -500.0 ){
      String pres_str = String(bme_disp.m_fPressure);
      String temp_str = String(bme_disp.m_fTemperature);
      String hum_str = String(bme_disp.m_fHumidity);
      String gas_str = String(bme_disp.m_fGas);

      Serial.println("-------------Send to Ambient--------------");
      Serial.printf("pressure = %s, temperature = %s, humidity = %s\r\n", pres_str.c_str(), temp_str.c_str(), hum_str.c_str() );
      Serial.printf("Gas = %s\r\n", gas_str.c_str() );
      Serial.println("------------------------------------------");
      ambient.set(1, pres_str.c_str());
      ambient.set(2, temp_str.c_str());
      ambient.set(3, hum_str.c_str());
      ambient.set(4, gas_str.c_str());
      ambient.send();
    }
    bme_disp.m_isNewData = false;
  }
}
//***************************************************
void beep(){
  if( isBeep_on == true && isBeep_set == true ){
    uint32_t b_period = millis() - beep_last_time;
    if( b_period < 500 ){
      dac_output_voltage(DAC_CHANNEL_1, 0);
      delay(1); //このdelay(1)で約500Hz
      dac_output_voltage(DAC_CHANNEL_1, beep_volume);
      delay(1); //このdelay(1)で約500Hz
    }else if( b_period >= 500 && b_period < 1000 ){
      dac_output_voltage(DAC_CHANNEL_1, 0);
    }else{
      beep_last_time = millis();
    }
  }
}
//****************************************
void button_action(){
  btnA.buttonAction( buttonA_GPIO, true, 30, 500 ); //チャタリング対策 30ms, 長押し500ms
  switch( btnA.ButtonStatus ){
    case btnA.MomentPress:
      Serial.println("Button A Moment Press");
      pres_graph.max_value = 1020.0;
      pres_graph.min_value = 980.0;
      temp_graph.max_value = 30.0;
      temp_graph.min_value = 25.0;
      hum_graph.max_value = 70.0;
      hum_graph.min_value = 40.0;
      gas_graph.max_value = 60000.0;
      gas_graph.min_value = 3000.0;
      bme_disp.m_isDisp_min_max = true;
      break;
    case btnA.ContPress:
      Serial.println("-------------Button A Cont Press");
      pres_graph.max_value = 1050.0;
      pres_graph.min_value = 850.0;
      temp_graph.max_value = 100.0;
      temp_graph.min_value = 0.0;
      hum_graph.max_value = 100.0;
      hum_graph.min_value = 0.0;
      gas_graph.max_value = 100000.0;
      gas_graph.min_value = 3000.0;
      bme_disp.m_isDisp_min_max = true;
    default:
      break;
  }

  btnB.buttonAction(buttonB_GPIO, true, 30, 500); //チャタリング対策 30ms, 長押し500ms
  switch( btnB.ButtonStatus ){
    case btnB.MomentPress:
      Serial.println("Button B Moment Press");
      if( isBeep_set == false ){
        isBeep_set = true;
        dac_output_enable( DAC_CHANNEL_1 );
      }else{
        isBeep_set = false;
        dac_output_disable( DAC_CHANNEL_1 );
      }
      break;
    case btnB.ContPress:
      Serial.println("-------------Button B Cont Press");
      if( ambient_send_ok == true ){
        ambient_send_ok = false;
        wifi_msg.WifiStatus = wifi_msg.WifiFailed;
      }else{
        ambient_send_ok = true;
        isWifi_connect_first = true;
        isWeb_connect_first_time = true;
      }
      break;
    default:
      break;
  }

  btnC.buttonAction(buttonC_GPIO, true, 30, 500); //チャタリング対策 30ms, 長押し500ms
  switch( btnC.ButtonStatus ){
    case btnC.MomentPress:
      Serial.println("Button C Moment Press");
      bme_disp.m_time_measure_mode++;
      if( bme_disp.m_time_measure_mode > bme_disp.Period4m48s ){
        bme_disp.m_time_measure_mode = (int8_t)bme_disp.Period3sec;
      }
      bme_disp.reDrawGraphAll( pres_graph, temp_graph, hum_graph, gas_graph, graph_font );
      Serial.printf("mode_time_measure=%d\r\n", bme_disp.TimeMeasureMode);
      break;
    case btnC.ContPress:
      Serial.println("-------------Button C Cont Press");
      break;
    default:
      break;
  }
}

【ザッと解説】

大部分はソースコードを見て頂ければ分かるようにしているつもりです。
特筆すべきところだけ、説明します。

●3行目:
前回記事で述べた、Arduino – ESP32 同梱のライブラリインクルードです。

●18-19行目;
ご自分のルーターの SSID とパスワードに書き換えてください。

●21-23行目:
micro SDHC カードに保存した東雲フォント関連ファイルの定義です。

●39-40行目:
クラウドサービスの Ambient のご自分のwriteKey と channelID に書き換えてください。

●54行目:
警告アラーム(Beep音)の音量を最小の1にしています。
好みによって変えてください。

●75行目:
こちらの記事でも述べたように、I2Cデバイスをマルチタスクで動作させる場合、メインloopを使う時には、BME680デバイスの初期化は setup関数内で行うことが原則です。

●72行目:
M5Stack の画面の明るさを決めます。
255がフル点灯で、常時 140mA の消費電流が流れます。
50 でも十分視認できます。
その場合 70mA くらいに抑えられます。

●83行目:
ここで、メインloopのタスクはcore1なので、それとは別のタスクをcore 0 と定義します。
マルチタスクに関してはこちらの記事を参照してください。

●104-115行目:
メインloopとは別タスクの core 0 の無限ループです。
前項でも述べたように、Webコネクションと警告アラーム(Beep音)発生はこのタスクで行います。

ここで注目!!

Arduino – ESP32 の core 0 タスクの無限ループは、ウォッチドッグタイマが動作する時間(最低1ms)必要です。
ですから、必ず delay(1) を置きます。
ただし、Beep音の基音周波数 500 Hz を変えたくないので、Beep音発生時はここの delay(1) を使わず、375-389行目の beep() 関数内にある delay(1) を使います。
Beep音が発生しない時は、フラグを使って delay(1) を動作させるようにしています。
ここは頭を使う所ですね。

●222-245行:
BME680 ガスセンサの値を M5Stack のディスプレイに表示させる関数です。
ガス検知の危険レッドラインを 40000 としています。
そして、前項で述べたように、ピーク検出は過去の直近50サンプルの平均との差が 10000 以上下がったら警告音を鳴らすようにしています。
平均値は 291行にあるように、私の自作ライブラリで関数化しています。
sample数は自分の好きなように変更してください。

●288-295 行:
ガスセンサの数値を表示する関数です。
ここでついでに過去の直近50サンプルの平均値を計算しています。

●297-305行:
WiFi ルーター(アクセスポイント)への接続、およびクラウドサービスの Ambient へのデータ送信は、3分毎に接続しています。
データ送信し終わったら、省電力化の為に WiFi_OFF にしています。

●307-351行:
WiFiアクセスポイントに接続する関数です。
2回目以降の接続は、アクセスポイントが見つからない時はタイムアウトします。

●375-389行:
前回の記事で紹介した Beep音発信関数です。

では、次の項ではボタン操作方法や、メッセージ表示の意味、台所(キッチン)で測定した様子を紹介します。

コメント

  1. juchang より:

    mgo-tec 様

    「 M5Stack で、ガス・気圧・温度湿度センサ BME680 を使ってみた」をクリアしていたので、本テキストが出るのを心待ちにしていました。
    Arduino – ESP32 と ESP32_mgo_tec を最新版にし、スケッチをコンパイルすると、次のようなエラーメッセージとなります。
    error : expected ‘ , ‘ or ‘ ; ‘ before ‘ int32_t’
    int32_t threshold = 10000;
    error : ‘ threshold’ was not declared in this scope
    }else if( now_av > threshold ){
    閾値が宣言されていないとのメッセージのようですが、対応の仕方が解りません、ご教示の程お願いいたします。

    • mgo-tec mgo-tec より:

      juchangさん

      いつもいつも試していただき、そして指摘してくださって、ありがとうございます。

      すみません、今気づきました。
      その前の223行の文、

      float danger_gas_level = 40000.0;
      

      とのころの最後のセミコロンが抜けておりました。
      大変失礼しました。
      私のチェックが甘かったです。
      ほんと申し訳ありませんでした。
      m(_ _)m

      なお、juchangさんが要望されていた、SSD1351ライブラリのESP32化は、あまり進んでおりません。
      ESP8266のものをそのままESP32に移行できるように考えていましたが、いろいろ仕様が異なっていて、しばらく時間がかかります。
      私もいろいろとごった返していて、作業もあまり進めない状況です。
      気長にお待ちいただければと思います。
      m(_ _)m

  2. juchang より:

    mgo-tec 様

    早速アドバイスをいただきありがとうございます。
    無事、動作確認致しました。
    次のテキストを楽しみにお待ちしております。

  3. juchang より:

    mgo-tec 様

    My5Stack(仮称)を作ってみました。
    3Dプリンターでケースを作り、ボタンを配置してみました。
    是非とも mgo-tec さんに見ていただきたく、ご承諾いただけましたら写真を送る方法( E-mail とか)をお知らせいただけると幸いです。
    さて、リクエストの第二弾です。
    「 M5Stack と BME680 でガス報知器…」を、ILI9341 LCD で表示させてみたいのですがブザーの接続が解りません。
    プログラムのリニューアル発表をお待ちいたしております。

    • mgo-tec mgo-tec より:

      juchangさん

      いつもお世話になっております。
      3Dプリンターケース、面白そうですね。
      ぜひ、見させていただきたいと思います。

      ただ、このコメント欄でのメールアドレス表記は、スパムの餌食になるので、こちらの記事の Amazon ギフトカード支援用のアドレスへ送って頂ければと思います。

      また、ブザーは別途アンプ回路とスピーカーが必要になります。
      M5Stack には予めマウントされています。
      残念ながらこのブログでは、しばらく音声出力回路を作る予定はございません。
      ネットには、スピーカーから音を鳴らす方法がもの凄い沢山あるので、そこを参考にして頂ければと思います。
      お力になれず、申し訳ございません。
      いつか、ものすごく余裕ができたら取り組んでみたいと思います。
      m(_ _)m

  4. donko より:

    コメント失礼いたします。
    後継であるM5Stack Core2でもこちらの記事にあるソースコードを用いて同様のものを作成することができるのでしょうか?ご教示の程よろしくお願いいたします。

    • mgo-tec mgo-tec より:

      donko さん

      記事をご覧いただき、ありがとうございます。
      2件、同じ内容のコメント投稿があったので、おそらく同一人物と思われたので、1件目は削除させていただきました。

      M5Stack Core2 は実機を持っておらず、今は新たに買うこともできないので検証することができませんが、まずお聞きしたいのは、液晶ディスプレイに表示はできますか?

タイトルとURLをコピーしました