【DIY × IoT】電子工作でお風呂のセンサーを作ってみる♬ – アラーム編

DIY × IoT

お風呂を溜めている時に水量が一定まで溜まったらお知らせしてくれると嬉しいですよね!
お風呂の設備についていれば良いのですが、残念ながら私の家にはついていないので、作ってみることにしました。

この記事では、お風呂に置いて水位を測るアラーム部分を作っていこうと思います。

全体の要件についてはこちらの記事をご覧ください。

アラームの要件

スポンサーリンク

センサーから信号を受信し、水位レベルがある一定の値を超えたらアラームを鳴らします。

ただし、センサー編で作った水位センサーから送られてくる水位レベルは値がどんどん下がるので、「水位レベルがある一定の値を超えたら」ではなく、「水位レベルがある一定の値を下回ったら」に仕様変更する必要がありそうです。

材料を揃える

スピーカー

D級アンプに繋いで音を出します。

後を鳴らすためのD級アンプ

マイコンから出力された音声データをスピーカーに伝えて音を出します。

届いた時にはピンが繋がっていない状態でした。

ピンを繋げるためにハンダ付けが必要です。

Bluetooth通信で通知するマイコン

センサーからBluetooth通信を受信するためにマイコンを使用します。
アラームは部屋でコンセントが使えるので「ESP32 Devkit V1」を使用することにしました。

ESP32 Devkit V1

配線する

スポンサーリンク

「ESP32 Devkit V1」のPINOUTはこちらになります。

PINOUTに合わせて配線します。
最終的に配線は下記のようになりました。

ESP32D級アンプ
D25LRC
D26BCLK
D22DIN
3V3GAIN
SD
GND
3V3Vin
※GNDは電源のマイナスに繋ぎます。

電源はUSBポートから取ります。

GAINとVinを3v3に繋ぐので、途中から分岐させて繋いでいます。

Arduinoスケッチ

アンプデバイスと繋ぎ、音を鳴らす

まずは音を鳴らすための回路を構築します。

SPIFFS領域にMP3ファイルを格納しておき、再生に利用します。
詳細な方法については「ESP32の内蔵DACを使ってSPIFFS領域のMP3ファイルを再生する」で紹介されています。

void biginMp3() {
  Serial.println("Mp3 Begin");
  SPIFFS.begin();

  pFile = new AudioFileSourceSPIFFS("/nc200771.mp3" );
  pOutput = new AudioOutputI2S();
  pMp3 = new AudioGeneratorMP3();

  pMp3->begin(pFile, pOutput);

  while(pMp3->isRunning()) {
    if( !pMp3->loop() )
    {
      pOutput->stop();
      pMp3->stop();
      pFile->close();
      delete pFile;
      Serial.println("MP3 end");
    }
  }
  SPIFFS.end();
}

BLEを組み込む

音を鳴らせるようになったら、次はBLE(Bluetooth Low Energy)で信号を受け取る仕組みを作成します。

センサー編で作ったブロードキャスターから信号を受け取るにはオブザーバーが必要になりますので作成していきます。
詳細はこちらのサイトを参考にさせていただきました。
とても細かく解説されていて、初心者の私でも理解できました。
http://marchan.e5.valueserver.jp/cabin/comp/jbox/arc212/doc21203.html

センサーからは読み取った値がそのまま送信されてくるので、オブザーバーで閾値を決めてアラームを鳴らしています。

/*
 * Function:  BLEブロードキャスト通信のオブザーバーとして動作します。
 *            ブロードキャストしているデバイスを見つけて、それが目的のデバイスであれば、温湿度
 *            計測データを受信してシリアルモニターに詳細を表示し、OLEDディスプレイに温度と湿度
 *            を表示します(OLED表示はデバイス識別番号1のもののみ)。
 *            デバイスからの異常情報をキャッチするとLEDを点灯して知らせ、プッシュボタンを押すと
 *            消灯します。
 */
#include <BLEDevice.h>
#include <Wire.h>                   // I2C interface
#include <Adafruit_SSD1306.h>       // SSD1306 display
#include <Adafruit_GFX.h>           // SSD1306 display

#include <Arduino.h>
#ifdef ESP32
  #include <WiFi.h>
  #include "SPIFFS.h"
#else
  #include <ESP8266WiFi.h>
#endif
#include "AudioFileSourceSPIFFS.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2S.h"

/* 基本属性定義  */
#define SPI_SPEED   115200          // SPI通信速度

/* スキャン制御用 */
#define DEVICE_NAME "ESP32"         // 対象デバイス名
#define MAX_DEVICES  8              // 最大デバイス数
#define ManufacturerId 0xffff       // 既定のManufacturer ID
const int scanning_time = 3;        // スキャン時間(秒)
BLEScan* pBLEScan;                  // Scanオブジェクトへのポインター
int prev_seq[MAX_DEVICES] = { -1, -1, -1, -1, -1, -1, -1, -1 };

/* 受信データ構造体 */
struct tmpData {
    int     device_number;          // デバイス識別番号
    bool    abnormal;               // デバイス異常
    int     seq_number;             // シーケンス番号
    float   sensor_output;          // 水位
};

/* OLEDディスプレイの設定 */
#define SCREEN_WIDTH 128            // 幅 (単位:ピクセル)
#define SCREEN_HEIGHT 64            // 高さ(単位:ピクセル)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);

/* LEDピン */
const int ledPin = 25;              // LEDの接続ピン

/* プッシュボタン */
const int buttonPin = 32;           // プッシュボタンの接続ピン

/* Audio関連の定義 */
AudioGeneratorMP3* pMp3;
AudioFileSourceSPIFFS* pFile;
AudioOutput* pOutput;

/*****************************************************************************
 *                          Predetermined Sequence                           *
 *****************************************************************************/
void setup() {
    doInitialize();                             // 初期化処理をして
    BLEDevice::init("");                        // BLEデバイスを作成する
    Serial.println("Client application start...");
    pBLEScan = BLEDevice::getScan();            // Scanオブジェクトを取得して、
    pBLEScan->setActiveScan(false);             // パッシブスキャンに設定する
}

void loop() {
    struct tmpData td;

    // 所定時間だけスキャンして、見つかったデバイス数を取得する
    BLEScanResults foundDevices = pBLEScan->start(scanning_time);
    int count = foundDevices.getCount();

    for (int i = 0; i < count; i++) {     // 受信したアドバタイズデータを調べて
        BLEAdvertisedDevice dev = foundDevices.getDevice(i);
        std::string device_name = dev.getName();
        if (device_name == DEVICE_NAME 
                && dev.haveManufacturerData()) {   // デバイス名が一致しManufacturer dataがあって
            std::string data = dev.getManufacturerData();
            int manu_code = data[1] << 8 | data[0];
            int device_number = data[2];
            int seq_number = data[4];

            // デバイス識別番号が有効かつシーケンス番号が更新されていたら
            if (device_number >= 1 && device_number <= MAX_DEVICES
                      && seq_number != prev_seq[device_number - 1]) {
                // 受信データを取り出す
                prev_seq[device_number - 1] = seq_number;
                td.device_number = device_number;
                td.abnormal = (bool)data[3];
                td.seq_number = seq_number;
                td.sensor_output = (float)(data[6] << 8 | data[5]);
                if (!td.abnormal) {
                    displayData(&td);
                } else {
                    displayAlarm(&td);
                }
                if (td.sensor_output < 2400) {
                  biginMp3();
                }
            }
        }
    }
    // プッシュボタン押下でLEDを消灯してディスプレイ表示を消去する
    int buttonState = digitalRead(buttonPin);
    if (buttonState == HIGH) {
        displayValues("", "", "");
        digitalWrite(ledPin, LOW);
    }
}

/*  初期化処理  */
void doInitialize() {
    Serial.begin(SPI_SPEED);
    pinMode(buttonPin, INPUT);        // GPIO設定:プッシュボタン        
    pinMode(ledPin, OUTPUT);          // GPIO設定:LED
    digitalWrite(ledPin, LOW);

    // OLEDディスプレイを初期化する
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
        Serial.println(F("*SSD1306 allocation failed!"));
        for(;;); // 失敗したら処理を進ませない!
    }
    displayValues("DHT SENSOR", "", "");
    Serial.println("BLE Client start ...");
}

/*****************************< Other functions >*****************************/
/* 受信データを表示する */
void displayData(struct tmpData* td) {
    char suii[10];
    sprintf(suii, "%5.2f", td->sensor_output);
    
    Serial.print("Received from device ");  Serial.print(td->device_number);
    Serial.print("  Data No.");             Serial.print(td->seq_number);
    Serial.print("  Suii: ");        Serial.print(suii);
    Serial.print("\n");
    if (td->device_number == 1) {   // (デバイス識別が1のもののみOLEDに表示)
    }
}

/* デバイス異常を表示する */
void displayAlarm(struct tmpData* td) {
    Serial.print("*Device error No.");   Serial.println(td->device_number);
    digitalWrite(ledPin, HIGH);
    char device[10];
    sprintf(device, "  (%d)", td->device_number);
    displayValues("*ERROR*", "Device", device);
}

/* 測定値をOLEDディスプレイに表示する */
void displayValues(char* line1, char* line2, char* line3) {
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(0,0);    display.print(line1);
    display.setCursor(24,20);  display.print(line2);
    display.setCursor(24,40);  display.print(line3);
    display.display();
}

void biginMp3() {
  Serial.println("Mp3 Begin");
  SPIFFS.begin();

  pFile = new AudioFileSourceSPIFFS("/nc200771.mp3" );
  pOutput = new AudioOutputI2S();
  pMp3 = new AudioGeneratorMP3();

  pMp3->begin(pFile, pOutput);

  while(pMp3->isRunning()) {
    if( !pMp3->loop() )
    {
      pOutput->stop();
      pMp3->stop();
      pFile->close();
      delete pFile;
      Serial.println("MP3 end");
    }
  }
  SPIFFS.end();
}

最後に

スポンサーリンク

配線する際はいきなりハンダ付けしてしまうと間違えた場合に取り返しがつかなくなるので、ブレッドボードという道具を使いました。
最終的にはプリント基盤に配置して動線を繋いで、ハンダ付けをして完成です。

電池では動きませんが、USBから電気を供給することでギリギリ動作させることができました。
用途に応じて工夫すれば実現可能だなと思うと、面白さを感じますね(^^)