Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
13
Help us understand the problem. What is going on with this article?
@yukimatsu

M5StackでCO2モニターを作って、Teams通知で換気を促すものを作ってみた

背景

暖かい日の昼過ぎに眠くなってくるので、二酸化炭素濃度のせいではないかと思ったのが作ったきっかけです。
普段それほど広くはない部屋で仕事していることが多いのですが、昼過ぎになんだか眠気を感じることもたまに。人数自体は少ないので、狭く感じるということはないのですが、そういえば、二酸化炭素濃度ってどうなのだろ?と思って作り始めたものです(まぁ、ただの思い付きですね)。

作ったもの

これです!
M5Stackにガスセンサユニットを取り付けて、ケースに入れてものです。二酸化炭素濃度は、eCO2(二酸化炭素相当値)なので厳密なものではありません。ただ、今回の用途ではこれで十分。
DSC_0264.JPG

材料

・M5Stack
https://www.switch-science.com/catalog/3648/
・TVOC/eCO2 ガスセンサユニット(SGP30)
https://www.switch-science.com/catalog/6619/
・Base15 産業用プロト基板モジュール
https://www.switch-science.com/catalog/6545/

作り方

  1. M5StackのBOTTOMを外す
    DSC_0267.JPG

  2. ガスセンサユニットをコネクタ部に接続
    DSC_0268.JPG

  3. 産業用プロト基板モジュールの基板を取り外して、BOTTOM側のケースを使う
    DSC_0269.JPG

  4. ガスセンサユニットを中に入れて、蓋を閉じる
    DSC_0271.JPG
    DSC_0270.JPG

  5. 組み立ててDINレールを嵌める
    DSC_0272.JPG

  6. 完成
    DSC_0266.JPG

Microsoft Teamsへの通知

二酸化炭素濃度が1000ppmを越えたら、Teamsに通知するようにします。CO2モニターなので、表示を見たら良いのですが、仕事をしていると気が付いたら高くなっていたということが想定されるので、通知機能を実装します。
こんな感じの通知が来るようにしました。他の会話と混ざると分かりにくくなるので、専用チャンネルを作って運用しています。
image.png

ソースコード

開発環境は、PlatformIOを使用しています。とりあえず動く、を目指してサクッと作った感じです。

main.cpp
#include <M5Stack.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include "Adafruit_SGP30.h"

#define JST 3600 * 9

void wifiConnect();
void wifiDisconnect();
void notifyTeams(String webhook, String text);

// ★★★★★設定項目★★★★★★★★★★
const char *ssid = "xxxxxxxx";
const char *password = "xxxxxxxx";
// for Notification of Teams.
String webhook = "https://xxxxxxxx.webhook.office.com/xxxxxxxx";
String text = "室内の二酸化炭素濃度が高くなっています。換気をお願いします。";
// ★★★★★★★★★★★★★★★★★★★

Adafruit_SGP30 sgp;
int i = 15;
long last_millis = 0;
// Wifi
bool hasWifi = false;
// date
time_t t;
struct tm *tm;
// Notification
int count_over = 0;
unsigned long tm_start = 0;

void header(const char *string, uint16_t color)
{
  M5.Lcd.fillScreen(color);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
  M5.Lcd.fillRect(0, 0, 320, 30, TFT_BLACK);
  M5.Lcd.setTextDatum(TC_DATUM);
  M5.Lcd.drawString(string, 160, 10, 4);
}

void setup()
{
  M5.begin(true, false, true, true);
  M5.Lcd.setBrightness(64);
  dacWrite(25, 0); //disable the speak noise

  // Init Serial.
  Serial.begin(9600);
  Serial.println("SGP30 test");

  header("CO2 Monitor", TFT_BLACK);
  if (!sgp.begin())
  {
    Serial.println("Sensor not found :(");
    while (1)
      ;
  }

  // WiFi接続
  int retry = 0;
  do
  {
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(0, 60);
    M5.Lcd.println("WIFI CONNECTING...");
    wifiConnect();
    retry++;
    if (retry > 3)
    {
      hasWifi = false;
      M5.Lcd.setTextSize(2);
      M5.Lcd.println("ERROR: NO WIFI.");
      delay(3000);
      break;
    }
  } while (WiFi.status() != WL_CONNECTED);

  if (WiFi.status() == WL_CONNECTED)
  {
    hasWifi = true;
  }

  // NTP同期処理
  if (hasWifi == true)
  {
    // NTP同期
    configTime(JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");

    // 時刻の同期待ち
    do
    {
      t = time(NULL);
      tm = localtime(&t);
      Serial.printf("%04d/%02d/%02d %02d:%02d:%02d\n",
                    tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
                    tm->tm_hour, tm->tm_min, tm->tm_sec);
      delay(100);
      M5.Lcd.setTextSize(2);
      M5.Lcd.println("DATE SETTING...");
    } while (tm->tm_year + 1900 < 2000);
  }

  M5.Lcd.clear();
  header("CO2 Monitor", TFT_BLACK);
  //M5.Lcd.drawString("TVOC:", 50, 40, 4);
  //M5.Lcd.drawString("eCO2:", 50, 80, 4);
  Serial.print("Found SGP30 serial #");
  Serial.print(sgp.serialnumber[0], HEX);
  Serial.print(sgp.serialnumber[1], HEX);
  Serial.println(sgp.serialnumber[2], HEX);
  M5.Lcd.drawString("Initialization...", 140, 120, 4);
}

void loop()
{
  t = time(NULL);
  tm = localtime(&t);

  // Initialize Sensor.
  while (i > 0)
  {
    if (millis() - last_millis > 1000)
    {
      last_millis = millis();
      i--;
      M5.Lcd.fillRect(198, 120, 40, 20, TFT_BLACK);
      M5.Lcd.drawNumber(i, 20, 120, 4);
    }
  }

  M5.Lcd.fillRect(0, 40, 320, 160, TFT_BLACK);

  if (!sgp.IAQmeasure())
  {
    Serial.println("Measurement failed");
    return;
  }
  M5.Lcd.fillRect(100, 40, 220, 90, TFT_BLACK);
  // M5.Lcd.drawNumber(sgp.TVOC, 120, 40, 4);
  // M5.Lcd.drawString("ppb", 200, 40, 4);

  // 色を付けて表示
  M5.Lcd.setTextSize(4);
  uint16_t eco2 = round(sgp.eCO2 / 10) * 10; // 1の位の四捨五入
  if (eco2 > 1000)
  {
    M5.Lcd.setTextColor(RED);
  }
  M5.Lcd.drawNumber(eco2, 160, 80, 4);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(1);
  M5.Lcd.drawString("ppm", 280, 200, 4);

  // for Debug.
  Serial.print("TVOC ");
  Serial.print(sgp.TVOC);
  Serial.print(" ppb\t");
  Serial.print("eCO2 ");
  Serial.print(sgp.eCO2);
  Serial.println(" ppm");

  // Teamsへの通知
  // 12回連続で値が越えたら通知
  // 一度通知したら30分は通知しない
  if (eco2 > 1000)
  {
    count_over++;
    if (count_over > 12)
    {
      // 前の通知から30分以上経過していないと通知しない
      if (millis() / (1000 * 60) < 30 && tm_start == 0)
      { // 起動後30分以内の場合
        // Teams通知
        notifyTeams(webhook, text);
        // 時間の測定開始
        tm_start = millis();
      }
      else if ((millis() - tm_start) / (1000 * 60) > 30)
      {
        // Teams通知
        notifyTeams(webhook, text);
        // 時間の測定開始
        tm_start = millis();
      }
    }
  }
  else
  {
    // リセット
    count_over = 0;
  }

  delay(5000);
}

void wifiConnect()
{
  Serial.print("Connecting to " + String(ssid));

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

  //接続を試みる(10秒)
  for (int i = 0; i < 20; i++)
  {
    if (WiFi.status() == WL_CONNECTED)
    {
      //接続に成功。IPアドレスを表示
      Serial.println();
      Serial.print("Connected! IP address: ");
      Serial.println(WiFi.localIP());
      break;
    }
    else
    {
      Serial.print(".");
      delay(500);
    }
  }

  // WiFiに接続出来ていない場合
  if (WiFi.status() != WL_CONNECTED)
  {
    Serial.println("");
    Serial.println("Failed, Wifi connecting error");
  }
}

void wifiDisconnect()
{
  Serial.println("Disconnecting WiFi...");
  WiFi.disconnect(true); // disconnect & WiFi power off
}

// Teams通知
void notifyTeams(String webhook, String text)
{
  int res;
  HTTPClient https;

  Serial.print("connect url :");
  Serial.println(webhook);

  Serial.print("[HTTPS] begin...\n");
  if (https.begin(webhook))
  { // HTTPS

    Serial.print("[HTTP] POST...\n");
    // start connection and send HTTP header
    String payload = "{'text':'" + text + "'}";
    int httpCode = https.POST(payload);

    // httpCode will be negative on error
    if (httpCode > 0)
    {
      // HTTP header has been send and Server response header has been handled
      Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
      //Serial.println(https.getSize());

      // file found at server
      String payload;
      if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)
      {
        payload = https.getString();
        Serial.println("HTTP_CODE_OK");
        Serial.println(payload);
      }
      res = 1;
    }
    else
    {
      Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
      res = -1;
    }
    https.end();
  }
  else
  {
    Serial.printf("[HTTPS] Unable to connect\n");
    res = -1;
  }
}

まとめ

二酸化炭素モニターは比較的安価なものが既にネットショップなどで出回っていますが、通知が来るタイプが良かったので、サクッと作りました。
あとは、現在はTeamsのチームのチャンネルに投稿されるのですが、テレワークで自宅で仕事をしていても問答無用に通知が来るので、社給スマホのBluetoothを使って在席チェックして、部屋で仕事をしている人にだけメンション付けて投稿するとか、もう少しやってみたいこともまだまだいろいろ。

13
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
yukimatsu
エンジニア。回路設計/組込みソフト/ソフトウェア。
hokuryodenko
三菱電機の代理店として、FA機器等の電気・電子機械器具の販売から、各種制御シス テム・電子機械器具の開発・設計・製造、そして建設業設備工事まで幅広く事業を展開しております。近年は農業事業や、様々な産業向けIoTシステムも手掛けており、新規事業の立上げ や産学連携を通して、“総合技術商社”としてユーザーのあらゆる要望に最適なシステムで対応できる総合力を構築しています。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
13
Help us understand the problem. What is going on with this article?