最終更新日
記事公開日

【ESP32】特定の音が鳴ったらLINEで通知する(ご飯よ〜!を家族全員にお知らせシステム)

今回は、ESP32-DevKitCを使った「特定の音が鳴ったらLINEで通知するシステム」を紹介します。

「音が届かないところにいる人にも、音が鳴ったことを知らせたい」

「既存の装置を改造せずに、LINEで通知するシステムを取り付けたい」

という方、ぜひ参考にしてみて下さい。

呼び出しボタンの「ピンポーン」という音がトリガーです。

あらかじめ設定しておいた音と同じ音だと判断されると、LINEグループにメッセージが配信されます。

目次

使用パーツ

配線・回路図

プログラム

以下の変数は各自書き換えて下さい↓

  1. ssid
  2. password
  3. token
  4. message

※事前にLINE Notifyの登録を行い、トークンを発行しておく必要があります。詳しくは、下記の記事をご覧ください↓

【ESP32】ボタンを押したらLINE通知

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include<Wire.h>

// WiFi設定
const char* ssid     = "your-ssid";
const char* password = "your-password";

// LINE Notify設定
const char* host = "notify-api.line.me";
const char* token = "your-token";
const char* message = "ピンポーン!";

// サウンドセンサーに使用するPin
int sensor_pin = 15;

// I2Cアドレス(LCDのスレーブ値を設定)
#define LCD_ADRS 0x3E

void setup() {

  // センサーに使用するPinを入力に設定
  pinMode(sensor_pin, INPUT);

  // LCDディスプレイ表示に使用するI2C用Pinの設定
  Wire.begin(21,22);

  // シリアルモニタの通信速度
  Serial.begin(115200);
  
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  // WiFi接続
  WiFi.begin(ssid, password);

  // WiFiの接続状態を確認
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  
}

// line通知
void send_line() {

  // HTTPSへアクセス(SSL通信)するためのライブラリ
  WiFiClientSecure client;

  // サーバー証明書の検証を行わずに接続する場合に必要
  client.setInsecure();
  
  Serial.println("Try");
  
  //LineのAPIサーバにSSL接続(ポート443:https)
  if (!client.connect(host, 443)) {
    Serial.println("Connection failed");
    return;
  }
  Serial.println("Connected");

  // リクエスト送信
  String query = String("message=") + String(message);
  String request = String("") +
               "POST /api/notify HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Authorization: Bearer " + token + "\r\n" +
               "Content-Length: " + String(query.length()) +  "\r\n" + 
               "Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
                query + "\r\n";
  client.print(request);
 
  // 受信完了まで待機 
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    if (line == "\r") {
      break;
    }
  }
  
  String line = client.readStringUntil('\n');
  Serial.println(line);
}

// 稼働時間
unsigned long uptime;

// 一つの音の始まりから終わりまでの総時間
int total_time = 0;

// サウンドセンサの信号がHIGHを示していた総時間
int high_total_time = 0;

// サウンドセンサの信号がLOWを示していた時間
int low_time;

// 音の終わりと判断するための無音時間(us)
int limit_time = 1000000;

// 信号がHIGHになった瞬間の時間を記録(最初の一回のみ)
int first_rise_point;

// 信号がHIGHになった瞬間の時間を記録
int rise_point;

// 信号がLOWになった瞬間の時間を記録
int fall_point;

// HIGH信号が継続しているときのフラグ
int high_state = 0;

// LOW信号が継続しているときのフラグ
int low_state = 0;

// 信号がHIGHになった回数
int cnt = 0;

// センサーの値
int sensor_val;


void loop() {

  // マイコンの稼働時間(us)
  uptime = micros();

  // センサーの値を読み取る
  sensor_val =  digitalRead(sensor_pin);

  // センサーの値がHIGHのとき
  if (sensor_val == 1) {

    // LOW信号の継続フラグを解除
    low_state = 0;

    // 信号がHIGHになった瞬間(立ち上がり)
    if (high_state == 0) {

      // HIGH信号の継続フラグを立てる
      high_state = 1;

      // 信号がHIGHになった瞬間の時間を記録
      rise_point = uptime;

      // パルスの数を数える
      cnt = cnt + 1;

      // 一つの音の長さを求めるための準備
      if (cnt == 1) {
        
        // 一つの音の始まりとなる時間を記録
        first_rise_point = uptime;
        
      }

    }
    
  // センサーの値がLOWのとき
  } else {

   // HIGH信号の継続フラグを解除
    high_state = 0;

    // 信号がLOWになった瞬間(立ち下がり)
    if (low_state == 0) {

       // LOW信号の継続フラグを立てる
      low_state = 1;

      // 信号がLOWになった瞬間の時間を記録
      fall_point = uptime;

      // サウンドセンサの信号がHIGHを示していた総時間を計測
      high_total_time = high_total_time + (uptime - rise_point);

    // LOW信号の継続
    } else {

      // LOW信号の時間を計測(稼働時間 - 信号がLOWになった瞬間の時間)
      low_time = uptime - fall_point;

      // 音の終わりと判断した場合(無音の時間が続いた)
      if (low_time > limit_time) {

        // ずっと無音状態だったときは処理しない
        if (cnt > 0) {

          // 音の始まりから終わりまでの総時間を算出
          total_time = uptime - first_rise_point - limit_time;

          Serial.print(total_time);
          Serial.print("[");
          Serial.print(high_total_time);
          Serial.print("]");

          // LCDディスプレイに表示するために数値を文字列に変換
          String str_total_time = String(total_time);
          String str_high_total_time = String(high_total_time);

          // 文字列を一文字ずつ配列に格納する
          char text1[16];
          str_total_time.toCharArray(text1, 16); 
          char text2[16];
          str_high_total_time.toCharArray(text2, 16); 

          // LCD初期化
          init_LCD();
          
          // LCDディスプレイ(1行目)に音の総時間を表示
          for(int i=0;i<10;i++){
            writeData(text1[i]);
          }
          // LCDディスプレイ(2行目)にHIGHの総時間を表示
          writeCommand(0x40+0x80);
          for(int i=0;i<10;i++){
            writeData(text2[i]);
          }

          // 全体音の長さで検証
          if (total_time > 3000000 && total_time < 4000000) {

             // 信号がHIGHになったときの総時間で検証
             if (high_total_time > 350000 && high_total_time < 600000) {
              
              Serial.println(" HIT!");

              // Lineにリクエストを送信する
              send_line();

             }
            
          }
          
          Serial.println("");

          // 総時間をリセット
          total_time = 0;
          
        }

        // 一つの音の読み取り終了
        high_state = 0;
        low_state = 0;
        low_time = 0;
        high_total_time = 0;
        first_rise_point = 0;
        rise_point = uptime;
        fall_point = uptime;
        cnt = 0;
        
      }
    }
  }
}

// LCDデータ書き込み
void writeData(byte t_data){
  
  Wire.beginTransmission(LCD_ADRS);
  Wire.write(0x40);
  Wire.write(t_data);
  Wire.endTransmission();
  delay(1);
  
}

// LCDコマンド書き込み
void writeCommand(byte t_command){
  
  Wire.beginTransmission(LCD_ADRS);
  Wire.write(0x00);
  Wire.write(t_command);
  Wire.endTransmission();
  delay(10);
  
}

// LCD初期化
void init_LCD(){
  
  delay(100);
  writeCommand(0x38);
  delay(20);
  writeCommand(0x39);
  delay(20);
  writeCommand(0x14);
  delay(20);
  writeCommand(0x73);
  delay(20);
  writeCommand(0x52);
  delay(20);
  writeCommand(0x6C);
  delay(20);
  writeCommand(0x38);
  delay(20);
  writeCommand(0x01);
  delay(20);
  writeCommand(0x0C);
  delay(20);
  
}

技術解説

さらに詳しく解説していきます。

音の検知・特定方法

音の検知を担っているのは「サウンドセンサ」です。

サウンドセンサーについては、過去記事で詳しく解説していますので、そちらをご覧下さい↓

ESP32で特定の音を検知(サウンドセンサー)

なお、今回は音の特定精度を上げるためにプログラムの改良を行いました。

過去記事で紹介しているプログラムでは、「音の長さ」だけで音の見極めを行っていましたが、やはり誤検知が発生してしまいます。

そこで、「音の長さ」に加えて、サウンドセンサからのパルスがHIGHのときの総時間を検知対象としました。

今のところ、誤検知は一回。

なぜか、子供の「はぁぁぁぁーーー!」というドラゴンボールの真似をしたときの叫び声に検知してしまいました。

たしかに音の長さは同じくらいです。

しかし、一つの音で100以上のパルスが出ているので、HIGHになった総時間が一致するのは、かなり低い確率なのですが・・・

LINE通知

LINE通知を行うためには、事前準備が必要です。

過去記事で詳しく解説していますので、そちらをご覧下さい↓

【ESP32】ボタンを押したらLINE通知

LCDモニタの役割

開発は自分の部屋のパソコンの前で行いましたが、この装置の設置場所は「キッチン」です。

場所によって音の反響具合も変わるので、サウンドセンサから送られてくる信号も変わってきます。

そこで、実際の設置場所でも「音の長さ」「パルスがHIGHの総時間」を確認できるようにLCDモニタを取付けました。

その数値を元にプログラムを書き換えたあとは、LCDモニタを取り外します。

※文字化けしていますが、確認用できれば問題ないので修正していません。

もちろん、LCDは無くても構いません。

ノートパソコンを近くまで持って行けばいいだけなので・・・

モバイルバッテリー電源

今回、電源には「モバイルバッテリー」を使用しました。

過去記事で詳しく解説していますので、そちらをご覧下さい↓

ESP32をモバイルバッテリーで動かす(5Vピンへの電源入力について)

使用例

このシステムを作った目的は、それぞれの部屋にいる家族にご飯ができたことを知らせるためです。

他にも、ナースコールとか館内放送のチャイムの音とか、本元の装置を改造せずにシステムを追加できるので、色々なことに応用できると思います。

関連情報

運営者プロフィール
コダマ

職業はIT系フリーランス。過去、電子配線業務の経験が10年ある為、はんだづけも得意です。宮崎県在住、30代・2児の父親。

プロが教える!イチからわかるハンダ付けのコツ(工学社)の著者です。

カテゴリー