West Gate Laboratory

人生を少しでも,面白く便利にするモノづくり

M5Stackからスマホ/PCにCO2濃度上昇を通知する(Pushbullet)

概要

先日の記事で紹介した、M5StackによるCO2濃度モニタはただ数値とグラフを表示するだけだった。
このままではCO2濃度が高いのか低いのか、人が忘れないようにチェックする必要がある。

westgate-lab.hatenablog.com

そこで今回は、それに改良を加えCO2濃度が一定レベルを超えたらスマホ/PCに通知を送るようにした。
通知にはPushbulletを使っているが、M5StackからPushbulletを使って通知を送る方法について述べる。

ソースコードGithubで公開しているので、記事を読むのが面倒な人はこちらを参照のこと。 (この記事に対応するソースコードのバージョンはv2.0である)

github.com

最終的にはPushbulletのアプリを通じてこんな感じにスマホへプッシュ通知される。PushbulletにPC(Chrome拡張)を登録しておけば、PCにも同時に通知される。

f:id:kaname_m:20200405153127j:plain

スマホ・PCへのプッシュ通知はPushbulletが便利

Pushbulletは、様々な端末同士をつないでチャットしたりメッセージや写真を送ったり通知を共有するサービスである。
情報は、テキスト・URL・写真など何でも良い。

www.pushbullet.com

スマホとPC間のちょっとしたデータの共有に便利なので、電子工作に限らず私はよく使っている。
また、APIが豊富に用意されているため、インターネットに繋がってHTTPが使えるデバイスからも利用可能である。

以前ブログに書いた「RaspberryPiで再配達を撲滅するシステム」でも、スマホ/PCへの通知にPushbulletを使っている。

westgate-lab.hatenablog.com

M5Stackからプッシュ通知を送る

さて、ここからM5Stackからスマホ・PCへPushbulletを使ってプッシュ通知を行う方法を述べる。

Pushbulletアカウント、Access Tokenの取得

この後は、すでにPushbulletのアカウントを持ち、Access Tokenを取得している前提とする。
Access Tokenの取得方法については以下の過去記事参照。

westgate-lab.hatenablog.com

プッシュ通知タイミングの設計

今回は、以下のしきい値でプッシュ通知を送ることとした。

  • 1000ppmを超えたら、注意レベルとし、換気を促す。

  • 2000ppmを超えたら、危険レベルとし、速やかな換気を要請する。

しきい値付近では値がふらつくことが予想されるため、一度通知をプッシュしたら一定時間は通知をしないようにした。

ソースコード

ソースコードは全てGithub上で公開している。詳細はそちらを参照のこと。

github.com

なお、この記事では詳細は書いていないが、Ambientを使ったセンサ値のログも同時に実装している。ESPによるAmbientの使い方は公式チュートリアルが豊富にあるのでそちらを参照のこと。 以下では、ソースコードのうち特に重要な点について抜粋して述べる。

まず、Pushbulletではhttpsを使うため、WiFi.hに加えてWiFiClientSecure.hが必要である。

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

const char *ssid = "YOUR-WIFI-SSID";        // write your WiFi SSID (2.4GHz)
const char *password = "YOUR-WIFI-PASSWORD";     // write your WiFi password
WiFiClientSecure secureClient;
#define PB_APIKEY "YOUR-PUSHBULLET-API-KEY"      // write your Pushbullet API key

ESP32は2.4GHzのWiFiのみ対応しているので、SSIDには2.4GHzの方を記述すること。

setup()内で、WIFIをセットアップしておく。Pushbullet用関数(後述)はWIFIさえセットアップしておけば、他にセットアップは必要ない。

    // Wifi setup
    M5.Lcd.print("WiFi setup...");
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }
    M5.Lcd.println("done");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.print(WiFi.localIP());

次に、loop()内でセンサの値を取得したら、前回のCO2濃度と比較し、濃度しきい値を超えたらユーザへ通知を送るnotifyUser()、後述)。
co2_ppmにCO2濃度が格納されているものとする。

ここでは、CO2濃度をNORMAL, CAUTION, WARNINGの三段階に分け、前回取得時よりもレベルが上がっていたら通知を出している。
ただし、一度通知を出すと通知がpauseされ(pause_notify_[caution|warning])、一定時間通知は出されない。また、プログラム開始からも一定時間は通知を出さない(起動直後の値のふらつき対策)。

#define CO2_CAUTION_PPM 1000
#define CO2_WARNING_PPM 2000

int notify_timer_caution = 0;
int notify_timer_warning = 0;
bool pause_notify_caution = true;
bool pause_notify_warning = true;
#define PAUSE_LENGTH 600 // do not notify PAUSE_LENGTH [s] once notified

enum
{
    LEVEL_NORMAL,
    LEVEL_CAUTION,
    LEVEL_WARNING
};

int co2_level_last = LEVEL_NORMAL;

// (中略)
void loop()
{
    if (airSensor.dataAvailable())
    {
        // get sensor data(前回記事参照)
        co2_ppm = airSensor.getCO2();

        int co2_level_now;
        // check co2 level
        if (co2_ppm < CO2_CAUTION_PPM)
            co2_level_now = LEVEL_NORMAL;
        else if (co2_ppm < CO2_WARNING_PPM)
            co2_level_now = LEVEL_CAUTION;
        else
            co2_level_now = LEVEL_WARNING;

        // notify user when co2 level exceed threshold
        if (co2_level_now > co2_level_last)
        {
            if (co2_level_now == LEVEL_CAUTION && !pause_notify_caution)
            {
                if(notifyUser(co2_level_now)){
                    Serial.println("notifyUser(): CAUTION");
                }else{
                    Serial.println("notifyUser(): failed!");
                }
                pause_notify_caution = true;
            }
            if (co2_level_now == LEVEL_WARNING && !pause_notify_warning)
            {
                if(notifyUser(co2_level_now)){
                    Serial.println("notifyUser(): WARNING");
                }else{
                    Serial.println("notifyUser(): failed!");
                }                
                pause_notify_warning = true;
            }
        }

        co2_level_last = co2_level_now;
    }

    delay(SENSOR_INTERVAL_S * 1000); // SENSOR_INTERVAL_S [秒]ごとにセンサ値取得

    // 一度通知を出したら一定時間通知を出さないためのタイマー
    if (pause_notify_caution)
    {
        notify_timer_caution += SENSOR_INTERVAL_S;
        Serial.printf("notify_timer_caution: %d\n", notify_timer_caution);
        if (notify_timer_caution > PAUSE_LENGTH)
        {
            notify_timer_caution = 0;
            pause_notify_caution = false;
            Serial.println("notify_timer_caution set false");
        }
    }
    if (pause_notify_warning)
    {
        notify_timer_warning += SENSOR_INTERVAL_S;
        Serial.printf("notify_timer_warning: %d\n", notify_timer_warning);
        if (notify_timer_warning > PAUSE_LENGTH)
        {
            notify_timer_warning = 0;
            pause_notify_warning = false;
            Serial.println("notify_timer_warning set false");
        }
    }
}

最後に、ユーザへ通知をプッシュする関数は以下の通りである。

PushbulletへのHTTP通信は、コード中にも記載の通りこちらのサイトを使わせていただいた。

PushbulletはREST APIが利用でき、HTTP POSTでプッシュ通知を実装できる。
通知中の情報としては、title(通知タイトル、ここではCO2 Monitor)、body(通知文)を与えられる。

// reference: https://fipsok.de/Esp32-Webserver/push-Esp32-tab
bool pushbullet(const String &message)
{
    const char *APIKEY{PB_APIKEY};
    const uint16_t timeout{5000};
    const char *HOST{"api.pushbullet.com"};
    String messagebody = R"({"type": "note", "title": "CO2 Monitor", "body": ")" + message + R"("})";
    uint32_t broadcastingTime{millis()};
    if (!secureClient.connect(HOST, 443))
    {
        Serial.println("Pushbullet connection failed!");
        return false;
    }
    else
    {
        secureClient.printf("POST /v2/pushes HTTP/1.1\r\nHost: %s\r\nAuthorization: Bearer %s\r\nContent-Type: application/json\r\nContent-Length: %d\r\n\r\n%s\r\n", HOST, APIKEY, messagebody.length(), messagebody.c_str());
        Serial.println("Push sent");
    }
    while (!secureClient.available())
    {
        if (millis() - broadcastingTime > timeout)
        {
            Serial.println("Pushbullet Client Timeout !");
            secureClient.stop();
            return false;
        }
    }
    while (secureClient.available())
    {
        String line = secureClient.readStringUntil('\n');
        if (line.startsWith("HTTP/1.1 200 OK"))
        {
            secureClient.stop();
            return true;
        }
    }
    return false;
}

bool notifyUser(int level)
{
    const char *title = "CO2 Monitor";
    char body[100];

    switch (level)
    {
    case LEVEL_CAUTION:
        sprintf(body, "CO2 exceeded %d ppm. Ventilate please.", CO2_CAUTION_PPM);
        return pushbullet(body);

    case LEVEL_WARNING:
        sprintf(body, "CO2 exceeded %d ppm. Ventilate immediately.", CO2_WARNING_PPM);
        return pushbullet(body);

    default:
        return false;
    }
}

ここまでできれば、CO2が一定レベルを超えるとプッシュ通知される。

スマホへは記事トップのように通知されるが、PC(Windows10)の場合は以下のような感じである。

f:id:kaname_m:20200405154931p:plain

まとめ

前回作成したCO2濃度モニタに、Pushbulletを使ったプッシュ通知機能を実装した。
これでより意識的に換気できるだろう。

さぁ、空気を入れ替えよう。