Google Home の声で知らせる自作 IoT ガス・温湿度報知器 ( M5Stack , BME680 使用 )

記事公開日:2018年8月11日


スポンサーリンク

最新版 Arduino – ESP32 のインストール

2018/07中旬以降、Arduino core for the ESP32 では Stable Development が発表され、インストール方法もボードマネージャからインストールできるようになりました
それによって、ライブラリ群は ESP8266 と同様、Arduino15フォルダ(Windows10の場合)に集約されていますのでご注意ください。

古い Arduino core for the ESP32 はフォルダごと削除してから、最新版をインストールしてください。
インストール方法は以下の記事を参照してください。
ボードマネージャからのインストール方法に修正してあります。

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

ESP32 用 google-home-notifier ライブラリのインストール

これは、M5Stack および ESP32 ( ESP-WROOM-32 )から、直接 Google Home を喋らせることができる素晴らしいライブラリです。
「OK. Google」という呼び出し不要で自由にマイコンから喋らせることができます。
これは、Wi-Fi の同じローカルネットワーク内の Google Home を喋らせることができます。

@hori__hiro さん作のもので、以下の記事を参照して、予め Arduino IDE にインストールしておいてください。

ESP32 および M5Stack で Google Home を自発的に喋らせる実験

BME680 & M5Stack ガス報知器を作って置く

以下の記事を参照して、pimoroni製 BME680モジュール周りの組み立て、および BOSCH 純正 BME680 ドライバライブラリのインストールをしておきます。
そして、Ambient ライブラリや、私の自作 mgo-tec ライブラリをインストールしておきます。

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

ただし、私の自作ライブラリは今回バージョンアップしていて、
beta ver 1.0.40
になっていますのでご注意ください。

ESP32_mgo_tec ライブラリ

古いライブラリはフォルダごと削除して、新たに beta ver 1.0.31 を再インストールしてください
https://github.com/mgo-tec/ESP32_mgo_tec

※コンパイルエラー
display_bme280_i2c.h:44:64: 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

Ambient ( クラウドサービス)に登録しておく

BME680 のセンサ値は micro SDHC カードには保存しません。
今の時代は、書込み回数に制限のある micro SD カードに保存するよりも、クラウドサービスに保存した方が外出先でも見られるし、データをダウンロードしてエクセルで見ることもできるので、絶対的にお勧めします。

以下の記事を参照して、クラウドサービスの Ambient に事前に登録しておいてください。
無料で登録できます。

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

スケッチの入力

では、今回新たに作った以下のスケッチを入力してみてください。

500行を超えてしまっていますが、内容はフォント設定やグラフ設定、ウィンドウ設定が多いだけで、それほど難しいことはやっておりません。
あとはライブラリ任せになっています。

因みに私は素人アマチュアですので、誤りや意味不明なことを書いているかも知れません。
何かありましたらコメント投稿等でご連絡いただけると助かります。

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.31-
#include "Ambient.h"
#include <driver/dac.h> //Arduino-ESP32 standard driver
#include <esp8266-google-home-notifier.h>

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, google_home_msg, alarm_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 wifi_connect_last_time = 0;
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;
//------Alarm関連定義-------------------
uint8_t alarm_volume = 1; //min 1, max 255
boolean isAlarm_on = false;
boolean isAlarm_set = false;
uint8_t alarm_type = 0;
uint32_t alarm_last_time = 0;
uint32_t alarm_cont_last_time = 0;
uint16_t alarm_period = 5000; //アラームの鳴る間隔5秒
//------Google Home-----------------
GoogleHomeNotifier ghn;
const char displayName[] = "test"; //スマホのGoogle Homeアプリのデバイス名
boolean isGoogle_home_set = false;
boolean isGas_google_home_on = false;
boolean isTh_google_home_on = false;
String th_talk_str; //温度湿度の警告トーク文字列
String gas_talk_str; //ガス警告トーク文字列

//***********セットアップ****************************
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送信有効または無効表示
  alarmMessage(); //Alarm有効または無効表示
  googleHomeMessage(); //Google Home 警告トーク有効または無効表示
  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){
    alarm(); //アラーム発信

    if( ambient_send_ok == true ){
      if( (isWeb_connect_first_time == true) || (millis() - web_connect_last_time > 180000) ){ //3分毎に記事取得
        WiFi_AP_Connect(); //WiFi起動、アクセスポイント接続

        if( millis() - web_connect_last_time > 10000 ){
          sendDataToAmbient(); //Ambient へデータ送信
          web_connect_last_time = millis();
        }
        isWeb_connect_first_time = false;
      }
    }

    if( (isGoogle_home_set == true) && (isGas_google_home_on == true || isTh_google_home_on == true) ){
      WiFi_AP_Connect(); //WiFi起動、アクセスポイント接続
      googleHomeConnect( isGas_google_home_on, gas_talk_str );
      delay(1500); //この遅延が短すぎると、gas_task_str を全て喋り終わることができないので注意。
      googleHomeConnect( isTh_google_home_on, th_talk_str );
      if( millis() - web_connect_last_time > 10000 ){
        sendDataToAmbient(); //Ambient へデータ送信
        web_connect_last_time = millis();
      }
    }

    if( (WiFi.status() == WL_CONNECTED) && (millis() - wifi_connect_last_time > 30000) ){
      WiFi.mode(WIFI_OFF); //省電力化のため、WiFi接続完了30秒後にESP32のWiFiをOFF
      Serial.println("############### WiFi OFF ###############");
    }

    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(){
  uint8_t padding = 4;
  //WiFiメッセージ設定
  wifi_msg.m_x0 = 0;
  wifi_msg.m_y0 = 0;
  wifi_msg.m_size = 1;
  wifi_msg.m_padding = padding; //pixel単位
  wifi_msg.m_txt_length = 6; //文字表示数(半角相当)
  wifi_msg.WifiStatus = wifi_msg.WifiFailed;
  //Ambientメッセージ設定
  ambient_msg.m_x0 = wifi_msg.m_x0 + wifi_msg.m_txt_length * 8 + padding * 2;
  ambient_msg.m_y0 = 0;
  ambient_msg.m_size = 1;
  ambient_msg.m_padding = padding; //pixel単位
  ambient_msg.m_txt_length = 9; //文字表示数(半角相当)
  //Alarm設定表示
  alarm_msg.m_x0 = ambient_msg.m_x0 + ambient_msg.m_txt_length * 8 + padding * 2;
  alarm_msg.m_y0 = 0;
  alarm_msg.m_size = 1;
  alarm_msg.m_padding = padding;
  alarm_msg.m_txt_length = 7;
  //Google Homeメッセージ設定
  google_home_msg.m_x0 = alarm_msg.m_x0 + alarm_msg.m_txt_length * 8 + padding * 2;
  google_home_msg.m_y0 = 0;
  google_home_msg.m_size = 1;
  google_home_msg.m_padding = padding; //pixel単位
  google_home_msg.m_txt_length = 14; //文字表示数(半角相当)
  //温度・湿度警告メッセージ設定
  t_h_msg.m_x0 = 0;
  t_h_msg.m_y0 = 24;
  t_h_msg.m_txt_length = 10;
  t_h_msg.m_padding = padding;
  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 = padding;
  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, "熱中症警告" );
    th_talk_str = "熱中症になるよ。エアコンいれなさい。エアコン絶対いれなさい。";
    isTh_google_home_on = true;
  }else if( bme_disp.m_fHumidity > 65.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, " 湿度注意 " );
    th_talk_str = "湿度高いよ。除湿したほうがエエよ。";
    isTh_google_home_on = true;
  }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, " 乾燥注意 " );
    th_talk_str = "乾燥してるよ。加湿したほうがイイかもねー。";
    isTh_google_home_on = true;
  }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危険 " );
    gas_talk_str = "ガス危険、やばいよやばいよ。換気しなさい。";
    isAlarm_on = true;
    isGas_google_home_on = true;
    alarm_type = 2;
    if( isAlarm_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警告 " );
    gas_talk_str = "匂ってる。匂ってるよー。臭い。臭いよー。";
    isAlarm_on = true;
    isGas_google_home_on = true;
    alarm_type = 1;
    if( isAlarm_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 );
    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×" );
  }
}
//*******Google Home用ON-OFFウィンドウ表示*****************
void googleHomeMessage(){
  if( isGoogle_home_set == true ){
    google_home_msg.font.red = 0;
    google_home_msg.font.green = 63;
    google_home_msg.font.blue = 0;
    google_home_msg.m_bg_red = 0; //背景色
    google_home_msg.m_bg_green = 0; //背景色
    google_home_msg.m_bg_blue = 0; //背景色
    google_home_msg.dispMsgWindow( 0, " Google Home◎" );
  }else{
    google_home_msg.font.red = 31;
    google_home_msg.font.green = 0;
    google_home_msg.font.blue = 0;
    google_home_msg.m_bg_red = 0; //背景色
    google_home_msg.m_bg_green = 0; //背景色
    google_home_msg.m_bg_blue = 0; //背景色
    google_home_msg.dispMsgWindow( 1, " Google Home×" );
  }
}
//*******Alarm用ON-OFFウィンドウ表示*****************
void alarmMessage(){
  if( isAlarm_set == true ){
    alarm_msg.font.red = 0;
    alarm_msg.font.green = 0;
    alarm_msg.font.blue = 0;
    alarm_msg.m_bg_red = 0; //背景色
    alarm_msg.m_bg_green = 63; //背景色
    alarm_msg.m_bg_blue = 0; //背景色
    alarm_msg.dispMsgWindow( 0, "Alarm◎" );
  }else{
    alarm_msg.font.red = 31;
    alarm_msg.font.green = 0;
    alarm_msg.font.blue = 0;
    alarm_msg.m_bg_red = 0; //背景色
    alarm_msg.m_bg_green = 0; //背景色
    alarm_msg.m_bg_blue = 0; //背景色
    alarm_msg.dispMsgWindow( 1, "Alarm×" );
  }
}
//********ガスセンサ数値表示******************
void dispGasValue(){
  if( bme_disp.m_gas_value_disp_ok == true ){
    uint16_t sample = 50; //直近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アクセスポイント接続*************
void WiFi_AP_Connect(){
  wifi_msg.WifiStatus = wifi_msg.WifiConnecting; //WiFiメッセージウィンドウ設定
  Serial.println();
  Serial.println(F("Connecting Wifi..."));
  Serial.println(ssid);

  int16_t wifi_state = WiFi.status();
  Serial.printf("\r\nWiFi.status = %d\r\n", wifi_state);

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

  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");
  }
}
//******Google Home 接続およびメッセージ送信****************
void googleHomeConnect( boolean &google_home_on, String talk_str ){
  if( isGoogle_home_set == true && google_home_on == true ){
    Serial.println("connecting to Google Home...");
    if (ghn.device(displayName, "ja") != true) {
      Serial.println(ghn.getLastError());
      return;
    }
    Serial.print("found Google Home(");
    Serial.print(ghn.getIPAddress());
    Serial.print(":");
    Serial.print(ghn.getPort());
    Serial.println(")");

    if (ghn.notify( talk_str.c_str() ) != true) {
      Serial.println(ghn.getLastError());
      return;
    }
    Serial.println("Done.");
    google_home_on = false;
  }
}
//******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 alarm(){
  if( isAlarm_on == true && isAlarm_set == true && (millis() - alarm_cont_last_time > alarm_period*2) ){
    uint16_t interval = 500; //alarm の断続間隔 500ms
    if( alarm_type == 2 ){
      interval = 250;
    }

    alarm_cont_last_time = millis();

    while( millis() - alarm_cont_last_time < alarm_period ){ //アラーム5秒間発信
      uint32_t b_period = millis() - alarm_last_time;
      if( b_period < interval ){
        dac_output_voltage(DAC_CHANNEL_1, 0);
        delay( 1 ); //delay(1)で約500Hz。これ以下にするとウォッチドッグタイマが動作しないので注意。
        dac_output_voltage(DAC_CHANNEL_1, alarm_volume);
        delay( 1 ); //delay(1)で約500Hz。これ以下にするとウォッチドッグタイマが動作しないので注意。
      }else if( b_period >= interval && b_period < (interval*2) ){
        dac_output_voltage(DAC_CHANNEL_1, 0);
      }else{
        alarm_last_time = millis();
      }
    }

    isAlarm_on = false;
  }
}
//****************************************
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( isAlarm_set == false ){
        isAlarm_set = true;
        dac_output_enable( DAC_CHANNEL_1 );
      }else{
        isAlarm_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");
      if( isGoogle_home_set == false ){
        isGoogle_home_set = true;
      }else{
        isGoogle_home_set = false;
      }
      break;
    case btnC.ContPress:
      Serial.println("-------------Button C Cont 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;
    default:
      break;
  }
}

【簡単な解説】

基本的に、ソースコードを見て頂ければ理解できるように作ったつもりです。
ですから、詳しい解説は省きます。

●2行目:
クラウドサービスの Ambient ライブラリをインクルードしています。

●3行目:
このドライバはこちらの記事にあるように、Arduino – ESP32 に標準で同梱されている、DAコンバータライブラリのインクルードです。

●4行目:
ここで、前回記事にあるように、ESP32/esp8266 用 google-home-notifier をインクルードしています。

●19-20行;
ここでご自分の Wi-Fiルーター(アクセスポイント)の SSID と パスワードに書き換えてください。

●22-24行:
micro SDHC に保存された、日本語フリーフォント、東雲フォント自作 UTF-8 → Shift_JIS コード変換テーブルファイルを定義しています。

●40-41行:
ご自分のクラウドサービス Ambient のチャンネル ID とライトキーに書き換えてください。

●56行:
M5Stack から発信されるガスセンサの異常検知アラームのボリュームです。
現在、最小の1にしてあります。
ご自分の好きな音量にしてください。
ただし、ちょっと上げただけでビックリするくらい大音量になりますので、気を付けてください。

●65行;
スマホの Google Home アプリのデバイス名に書き換えてください。
もしかしたら、英語アルファベットでないと認識しないかも知れません。
その場合、スマホ側のデバイス名を英語表記に変更すれば良いと思います。

●85行:
M5Stack のディスプレイの明るさを決めます。
この値が 255 のフル点灯だと、常時 160mA 程の電流が消費されますが、値を 50 にしても十分視認でき、70mA 程度に抑えられます。

●88行:
BME680 の初期化で、周辺温度を 28℃ としています。
ご自分の環境に合わせて変えてください。

●118-153行:
メインloop とは別の CPU core 0 のタスクです。
ここで、アラーム発信、WiFiコネクション、Google Home コネクションを実行しています。
まず、アラーム設定ONになっていて、異常値検知したら、5秒間鳴り、その後にWiFi接続します。
Ambient へデータ送信し、そして137行でガスセンサの警告を Google Home へメッセージを送信します。
また、139行で温湿度センサの警告を Google Home へ送信しています。
ここで注意していただきたいのは、138行目の delay(1500) です。
これが短すぎると、ガスセンサ警告と温湿度センサ警告の2つのメッセージを発する時に、ガスセンサのメッセージが途中で途切れてしまいます。
ここはメッセージの長さとの兼ね合いで、ご自分で調整してください。
また、Ambient へは普段は3分毎に送信していますが、140-144行にあるように、異常値検知した場合は10秒毎に頻度を上げて送信しています。
これは、146-149行とも関係していて、Webとのコネクションが30秒間無ければ、省電力化のため WIFI_OFF します。
異常値検知した場合はつねに Web とのコネクションがあるので、M5Stack ( ESP32 )の WiFi機能はONのままになります。

●241-271行:
BME680 の温度・湿度センサの閾値で警告表示や Google Home メッセージを決めています。
これはご自分の環境に合った値に変更すると良いと思います。

●273-301行;
BME680 のガスセンサの閾値で、警告表示や Google Home メッセージを決めています。
この閾値は何度も実験してみて、ご自分の環境に合った値にすると良いと思います。
threshold の値については、こちらの記事を参照してください。

では、次の項ではボタン操作方法やディスプレイ画面の説明をします。


スポンサーリンク


「Google Home の声で知らせる自作 IoT ガス・温湿度報知器 ( M5Stack , BME680 使用 )」への5件のフィードバック

  1. (以下、1週間程前に送信したつもりでしたが、上手くupされてなかった様なので再送します。)

    H.Wです。 大変ご無沙汰しております。
    約3ヶ月ぶりにブログを拝見したら、秀作スケッチが10数点以上も増えており、未だ8/11、16頃展示のスケッチ模写までしか追いつけていません…
    当方もその後、プロト基盤に何とかBME280を搭載(中国製のハーフサイズ品@650を納期も大分待って入手したものの、結局品質不良?で動作せず、最終的に過去使用した実績品を何とか内蔵させました。一応、交換性も考慮して直はんだ付けはせず、高さ制限の為に結線のコネクタからコンタクト部を抜き出しそっと折り曲げ加工し、ヘッダーピンに基盤裏から挿入する形で対応。)
    尚、BME680も試してみたかったのですが、入手性が悪く価格も高くて形状も大きく、今回は内臓に拘りたかったので敢えてBME280を使い、BME680のガス検知に該当するスケッチ部分を適宜コメントアウト&修正して対処してみました。
    1)その際、折角の接続状況表示、yahoo時報、ニュース、天気もそのまま残したかったので、Ambient◎とAlarm◎とGoogle Home◎は文字数を一部省略し、表示がズレないようにオーバーラップ表示させる仕様にしてみました。同じ場所でもボタンを押せば適宜表示は切り替わるので、これでも殆ど違和感はありません。
    2)ガス検知時のアラームが鳴らないのは寂しいので、代わりに「乾燥注意」、「高湿注意」、「熱中症注意」時にAlarm音1,2を割り当てて鳴らしてみました。(音量は確かに1か2で十分ですね…)
    3)勿論、Google Homeにも繋げて、喋るセリフも多少変更してみました。(こちらは最初に音量3を指示。)
    尚、デバイス名は特に変更しませんでしたが、懸念されてた名前に漢字が入っていても全く問題なく認識しています。
    (少し抑揚が気になる所もありますが、十分使い物になりますね? これは素晴らしい。。但、G.Hは応答が多少遅いので、Alexaでも同じ事ができるといいですね?)
    4)プロト基盤に別途内臓したLipo電池(850mAh)を保たせたい為、A,B,Cのキーを短&長押しした時に表示機能に影響しない範囲で、輝度を255、100、50、1に適宜切り替わるようにしてみました。
    (例えば、Aボタン長押しはグラフ表示設定モードが予め判ってるので別に暗くてもいいと言うことで最小の1に設定。これでも暗闇では十分認識でき、消費電力をかなり抑えられるので、就寝時間中は十分保ちそうです。)
    5)スケッチ改編時のミスか、起動時にWifi
    接続表示が✕のままとなる為、ディフォルトでAmbientに繋がる仕様に初期設定を変更して一応対応しました。(データ記録忘れがなくなるので結果オーライ。)
    6)残るは「警告表示」ですが、グラフ表示のスペースにアラーム時にフラッシュ表示できればいいと考え、y座標を変える他、適宜スケッチを改編してみました。
    しかし、論理が成立するデータ変化時の初回は表示するものの、その後の表示が継続しなかったり、途中でフリーズしてしまったりと安易なスケッチ書き換えではどうも上手くいきません…?
    これさえできれば、自分の使い方的には必要十分なのですが、何か簡単に対処できるアドバイス等を頂けると幸いです。

    1. H.Wさん

      いつもブログをご覧いただき、ありがとうございます。

      1週間前のこちらの記事でのコメント投稿は見当たりませんでした。
      ただ、ある別記事では事情により名前やハンドルネームを伏せてコメント返信させて頂いておりますので、お察し下さいませ。
      その節は、本当にありがとうございました。
      感謝感謝です。
      m(_ _)m

      さて、とても密度の濃いコメントありがとうございます。
      こういうコメントいただけると、とってもウレシイですね!
      (^^)

      1)そのアイデアはすばらしいです。
      オーバーラップは良いですね。

      2)なるほど。温湿度にもアラームを割り当てたとは、やっぱりそういうニーズもあるんですね。

      3)デバイス名が漢字でもOKだったんですね。
      情報ありがとうございます。
      Alexa は自発的に喋らせることはできません。
      残念ながらライブラリを作ってくれる人がおりません。

      4)なるほど。
      ボタンで明るさ調節は良いですね。
      明るさ1はやったことないので、今度試してみます。

      5)メッセージ表示は実はいろいろと面倒な処理があって、プログラムを変更するとうまく動かなくなる場合があります。
      これは、プログラムを読み解いて頂くしかありません。

      6)グラフ表示位置に警告表示は、グラフの再描画を考えると、かなり難しいですね。
      点滅表させたいのであれば、グラフ描画をストップして、グラフ上にメッセージを表示<->消灯を繰り返さねばなりません。
      すると、別途関数を作り、boolean変数などでフラグを新たに作って、閾値を超えたらその関数に入るようにします。
      そして、
      gas_msg.dispMsgWindow( 1, ” GAS警告 ” );

      gas_msg.clearMsgWindow( 1 );
      を交互に表示させることだと思います。
      ただし、delay関数を使ってしまうと、センサ計測とLCD表示が停止してしまうので、
      millis関数等を使って、表示させたらその関数をすぐ抜ける。
      また、消灯したらその関数をすぐ抜けて、センサ計測とLCD表示を継続するプログラムを組むことだと思います。
      コメントではこの程度しかアドバイスできず申し訳ございませんが、これで試してください。

  2. mgo-tec さん、
    H.Wです。応答がかなり遅れて申し訳ありません。

    先ず、他の項目で頂いたご返事については既に認識しております…m(_ _)m

    また、警告表示の対処方法についてのアドバイス、有難うございます。
    結果的に、最終の希望通りには行きませんでしたが、余りスケッチを弄くりまわすのは得策ではないと考え、最小限のdelay を20〜30
    ms 程度入れることにより、各論理判定が真に変化した最初の数秒間だけですが、各々の警告表示が出力されるので、一応これでヨシとしました。。。

    (p.s)
    後日、先のGmail 宛にでもこれまで製作した関連写真等を適宜添付させて頂きたいと思います。。。

    1. H.Wさん

      いえいえ、いくら遅れても全く問題ありません。
      こちらこそ、いつもありがとうございます。

      とりあえず、動作できているようでちょっと安心しました。
      あまりお役に立てず、申し訳ございません。

      写真については送って頂いてOKです。
      もし、このコメント欄に掲載してもよろしければ、おっしゃっていただければと思います。
      その際、サーバー負担軽減のため、容量によっては圧縮させていただきますのでご了承くださいませ。
      m(_ _)m

      1. H.Wさん

        写真ありがとうございました。
        では、紹介させていただきます。

        以下、H.Wさんからの説明文付きです。

        photo1: Lipo電池(850mAh)及びBME280のプロト基板実装状況、電池残量モニターLED付き
        (LEDは反対側で写真には輝度調整ボリュームのみ写ってます。)
        comment_hw_01.jpg

        photo2: スケッチ改変後の警告表示例
        (強制加温&加湿時。ちょっと映りが悪いですが…)
        comment_hw_02.jpg

        プロト基板からバッテリーをご自分で搭載されるところはスゴイですね。
        私は既製品を買ってしまいますが・・・。
        警告表示をアレンジするのはちょっと難しいのですが、よく改良されたと思います。
        素晴らしい!!!

        読者の皆さまにも参考になると思います。
        ありがとうございました。
        m(_ _)m

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください