完結編。 複数の人感センサーを使えるようにし、

人感センサーが人を検知した回数を時系列に可視化

という github activity みたな表現で、空き室モニタを表示する。

これまで。

複数のセンサを扱う

M5StickC と PIR を2セットつくる。

プログラムはちょっと改変。 マイコン毎に20行目のtopicを変更する。

cpp
const char *topic = "sensor/room1";

また、人感センサが検知すると、LEDを赤く光らせるとうざいので、display にMQTTに送るpayloadを表示する。

text
ok [{status": true}]

program

mqtt.cpp · Gistcpp
#include <M5StickC.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include "PubSubClient.h"

//
// wifi
//
const char ssid[] = "AP SSID";
const char password[] = "AP PASSWORD";

WiFiClient wClient;

//
// MQTT
//
const char *mqttHost = "MQTTHOST IP"; // MQTTのIPかホスト名
const int mqttPort = 1883;              // MQTTのポート

const char *topic = "sensor/room1"; // 送信先のトピック名
char *payload;                      // 送信するデータ
PubSubClient mqttClient(wClient);

int Last_sec = -1;

#define WAIT_TIME 100
#define PIR_HAT_PIN 36

//
// setup RTC by NTP
//
void setup_rtc()
{
  struct tm timeinfo;
  uint8_t hh, mm, ss;
  RTC_TimeTypeDef TimeStruct;

  // connect NTP server
  configTime(9 * 3600, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");
  if (!getLocalTime(&timeinfo))
  {
    M5.Lcd.println("Failed to obtain time");
    return;
  }

  //
  // set localtime to RTC
  //
  hh = timeinfo.tm_hour;
  mm = timeinfo.tm_min;
  ss = timeinfo.tm_sec;

  TimeStruct.Hours = hh;
  TimeStruct.Minutes = mm;
  TimeStruct.Seconds = ss;
  M5.Rtc.SetTime(&TimeStruct);
}

//
// connect wifi
//
int setup_wifi()
{

  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(DARKGREY);

  WiFi.disconnect(true);

  WiFi.begin(ssid, password); // connect to wifi

  int ntry = 100;
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    M5.Lcd.print(".");

    ntry -= 1;
    if (ntry == 0)
    {
      // Serial.println("max try failed");
      M5.Lcd.setTextSize(2);
      M5.Lcd.setTextColor(RED);
      M5.Lcd.setCursor(0, 0);
      M5.Lcd.println("fail wifi connect");
      return -1;
    }
  }
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.print("IP address: ");
  M5.Lcd.println(WiFi.localIP());
  M5.Lcd.println();

  return 0;
}

/**
 * Connect MQTT
 *
 */
void connectMqtt()
{

  // LED ON
  digitalWrite(GPIO_NUM_10, 0);
  mqttClient.setServer(mqttHost, mqttPort);

  while (!mqttClient.connected())
  {
    Serial.println("Connecting to MQTT...");
    if (mqttClient.connect(topic))
    {
      Serial.println("connected");
    }
    delay(500);
  }

  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(8, 9 * 4);
  M5.Lcd.setTextColor(DARKGREEN);
  M5.Lcd.printf(topic);

  // LED OFF
  digitalWrite(GPIO_NUM_10, 1);
}

void setup()
{
  M5.begin();
  M5.Axp.ScreenBreath(10);
  M5.Lcd.setRotation(3); // 左を上にす
  M5.Lcd.setTextSize(1);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(DARKGREY);
  delay(500);

  pinMode(GPIO_NUM_10, OUTPUT);
  pinMode(PIR_HAT_PIN, INPUT_PULLUP);

  //
  // Wifi
  //
  setup_wifi();

  //
  // setup RTC time
  //
  setup_rtc();

  //
  // MQTT
  //
  connectMqtt();
}

void loop()
{

  RTC_TimeTypeDef RTC_TimeStruct;

  M5.update();

  // PIR HAT(AS312)
  int pir = digitalRead(PIR_HAT_PIN);

  if (pir)
  {
    payload = (char *)"{\"status\": true}";
  }
  else
  {
    payload = (char *)"{\"status\": false}";
  }

  M5.Rtc.GetTime(&RTC_TimeStruct);
  int diff = Last_sec - RTC_TimeStruct.Seconds;
  if (abs(diff) > 3)
  {
    // 時刻表示
    M5.Lcd.setTextSize(1);
    M5.Lcd.setCursor(32, 9 * 8);
    M5.Lcd.setTextColor(DARKGREEN);
    M5.Lcd.fillRect(0, 9 * 8, 256, 12, BLACK);
    M5.Lcd.printf("Time: %02d : %02d : %02d\n",
                  RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes, RTC_TimeStruct.Seconds);
    Last_sec = RTC_TimeStruct.Seconds;

    if (WiFi.status() == WL_CONNECTED)
    {
      if (!mqttClient.connected())
      {
        connectMqtt();
      }
      int st = mqttClient.publish(topic, payload);

      M5.Lcd.fillRect(0, 12, 256, 12, BLACK);
      M5.Lcd.setTextSize(1);
      M5.Lcd.setCursor(2, 12);
      M5.Lcd.setTextColor(DARKGREEN);
      M5.Lcd.printf("%s [%s]", st == 1 ? "ok" : "ng", payload);
    }
  }
  mqttClient.loop();
  delay(WAIT_TIME);
}

Grafana のStatus history

前回つくった棒グラフ。 複数のセンサを表示すると、いささか以上に、見づらい。

ここで、grafana の status history の出番。

Queryは、上記の棒グラフと感じ。

sql
SELECT count("status") as room1  FROM "sensor_pir"
WHERE ("topic" = 'room1' AND status = true) AND $timeFilter
GROUP BY time($__interval) fill(none)

センサー毎に、 room1 のところを room2 、 room3 と増やしていけばいい。 as room1 の指定のおかげで、グラフ毎にタイトル sensor_pir.room1 といったタイトルがつく。

visualizations で、 Status history を選択。

検知数毎に、表示する色を Threshholds で設定。

あまり細かいと見えにくいので、最少の集計単位時間を 5分 にする。

直近6時間の5分毎の検知数を色分表示で、なんとなく空き室具合がわかる。 room1 は、15時から17時まで空いてた。でも17時から使ってる。 room3(実はcommand line でpublishしたテストデータ)は、15時くらいから動いてた。とか。

さらに、最少の集計単位時間を 1時間 にしてみる。 実はホントにほしかったのは、これ。 会議室の空き状況が一目でわかる。 あと、検知数が多い時間帯は、きっと人数も多いはず。

この仕組みなら数十室程度の管理はできそう。

24時間後。