見出し画像

環境データを収集する①~ESP-NOWでセンサ情報を収集する~

こういうシステムを作って、自宅の環境情報を簡単に収集・保存・閲覧できるようにしたいと思います。

まずは、複数のマイコンモジュールで別々に取得したセンサ情報を、ワイヤレスで1台のマイコンモジュールが収集する、システムの基本構成を作成します。

ESP-NOW

ESP32マイコンを搭載しているM5Stackシリーズでは、ESP-NOWという通信プロトコルが使用可能です。ESP-NOWを用いることで、アクセスポイントなしに高速にデータを送受信できます。

今回は、M5StackシリーズをESP-NOWで接続してセンサ情報を収集することにします。

データ送信時の注意

ESP-NOWでのデータ送信はuint8_t型の変数を格納したリストしか送ることができません。つまり、データは符号なし8ビットで表現できる0~255までの値しか一度に送信できません。

0~255の整数以外の値を送信する場合は、少し工夫が必要です。

スケッチ例

mametarou963さんの記事を参考にさせて頂きました。

https://elchika.com/article/748b00ae-c320-406c-9bc6-8566090b882e/

センサ側

以下のスケッチ例は、ATOM Liteに温湿度気圧センサユニットとTVOC/eCO2 ガスセンサユニットを接続した場合です。

////////////////////////////////////////////////////////////////////////////////
// ライブラリ読み込み
////////////////////////////////////////////////////////////////////////////////
#include <EEPROM.h>
#include <esp_now.h>
#include <WiFi.h>
#include <M5Atom.h>
#include <M5_ENV.h>
#include <Adafruit_SGP30.h>

////////////////////////////////////////////////////////////////////////////////
// グローバル変数、定数
////////////////////////////////////////////////////////////////////////////////
// peer設定
esp_now_peer_info_t slave;

// EEPROM
#define EEPROM_SIZE 64
#define DEVICE_NUMBER_ADDRESS 1

// デバイス番号
int deviceNumber = 1;

// ENV III
SHT3X sht30;
QMP6988 qmp6988;

// SGP30
Adafruit_SGP30 sgp;

// 温度、湿度、気圧
float tmp = 0.0;
float hum = 0.0;
float pressure = 0.0;

// ループカウント
uint16_t count = 0;

////////////////////////////////////////////////////////////////////////////////
// LEDの色
////////////////////////////////////////////////////////////////////////////////
CRGB dispColor(uint8_t g, uint8_t r, uint8_t b) {
  return (CRGB)((g << 16) | (r << 8) | b);
}

////////////////////////////////////////////////////////////////////////////////
// デバイス番号を読み込む
////////////////////////////////////////////////////////////////////////////////
void readDeviceNumber() {
  deviceNumber = EEPROM.readInt(DEVICE_NUMBER_ADDRESS);
  if ((deviceNumber < DEVICE_NUMBER_MIN) || (deviceNumber > DEVICE_NUMBER_MAX)) {
    deviceNumber = DEVICE_NUMBER_MIN;
  }
}

////////////////////////////////////////////////////////////////////////////////
// デバイス番号をカウントアップする
////////////////////////////////////////////////////////////////////////////////
void countUpDeviceNumber() {
  deviceNumber = deviceNumber + 1;
  if(deviceNumber  >  DEVICE_NUMBER_MAX) {
     deviceNumber = DEVICE_NUMBER_MIN;
  }
  EEPROM.writeInt(DEVICE_NUMBER_ADDRESS, deviceNumber);
  EEPROM.commit();
}

////////////////////////////////////////////////////////////////////////////////
// ESP-NOW送信コールバック
////////////////////////////////////////////////////////////////////////////////
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  Serial.print("Last Packet Sent to: ");
  Serial.println(macStr);
  Serial.print("Last Packet Send Status: ");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}

////////////////////////////////////////////////////////////////////////////////
// 初期設定
/////////////////////////////////////////////////////////////////////////////////
void setup() {
  // シリアルの初期設定
  Serial.begin(115200); 

  // 動作周波数 240MHz → 80MHz
  setCpuFrequencyMhz(80);

  // EEPROMの初期化
  if (!EEPROM.begin(EEPROM_SIZE)) {
    Serial.println("Failed to initialise EEPROM");
    Serial.println("Restarting...");
    delay(1000);
    ESP.restart();
  }
  
  // m5stickcの初期設定
  M5.begin(false, true, true);  // UART:false, I2C:true, 本体LED:true
  Wire.begin(26, 32);

  // deviceNumberの読み出し
  readDeviceNumber();

  // SGP30の初期設定  
  while (! sgp.begin()) {
    Serial.println("Could not find a valid SGP30 sensor, check wiring!");
  }

  // ESP-NOW初期化
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  } else {
    Serial.println("ESPNow Init Failed");
    ESP.restart();
  }

  // マルチキャスト用Slave登録
  memset(&slave, 0, sizeof(slave));
  for (int i = 0; i < 6; ++i) {
    slave.peer_addr[i] = (uint8_t)0xff;
  }
  esp_err_t addStatus = esp_now_add_peer(&slave);
  if (addStatus == ESP_OK) {
    // Pair success
    Serial.println("Pair success");
  }

  // ESP-NOWコールバック登録
  esp_now_register_send_cb(OnDataSent);

  // QMP6988初期化
  qmp6988.init();
}

////////////////////////////////////////////////////////////////////////////////
// メインループ
////////////////////////////////////////////////////////////////////////////////
void loop() {
  M5.update();

  // 温湿度の取得
  if (sht30.get() == 0) {  // Obtain the data of shT30.
    tmp = sht30.cTemp;   // Store the temperature obtained from shT30.
    hum = sht30.humidity;  // Store the humidity obtained from the SHT30.
  } else {
    tmp = 0, hum = 0;
  }

 // 圧力の取得
  pressure = qmp6988.calcPressure() * 0.01;

  // TVOC、eCO2の取得
  sgp.IAQmeasure();

  // 表示の更新
  uint8_t tmpUpper = (uint8_t)tmp;
  uint8_t tmpLower = (uint8_t)((tmp - (float)tmpUpper) * 100);
  uint8_t humUpper = (uint8_t)hum;
  uint8_t humLower = (uint8_t)((hum - (float)humUpper) * 100);
  uint8_t pressureUpper1 = (uint8_t)(((int)pressure) / 100);
  uint8_t pressureUpper2 = (uint8_t)(((int)pressure) % 100);
  uint8_t pressureLower = (uint8_t)((pressure - (int)pressure) * 100);
  uint8_t tvocUpper = (uint8_t)(sgp.TVOC / 100);
  uint8_t tvocLower = (uint8_t)(sgp.TVOC % 100);
  uint8_t eco2Upper = (uint8_t)(sgp.eCO2 / 100);
  uint8_t eco2Lower = (uint8_t)(sgp.eCO2 % 100);
  
  uint8_t data[30] = {
    deviceNumber,
    tmpUpper,
    tmpLower,
    humUpper,
    humLower,
    pressureUpper1,
    pressureUpper2,
    pressureLower,
    tvocUpper,
    tvocLower,
    eco2Upper,
    eco2Lower,
    };
    esp_err_t result = esp_now_send(slave.peer_addr, data, sizeof(data));

  // ボタン操作
  if (M5.Btn.wasPressed()) {
    Serial.println("Button pressed");
    countUpDeviceNumber();
  }

  // 通常時のLED
  if (deviceNumber == 1) M5.dis.drawpix(0, 0x0f0000);  // RED
  if (deviceNumber == 2) M5.dis.drawpix(0, 0x0f0f00);  // YELLOW
  if (deviceNumber == 3) M5.dis.drawpix(0, 0x000f00);  // GREEN
  if (deviceNumber == 4) M5.dis.drawpix(0, 0x000f0f);  // CYAN
  
  // 5秒稼働、55秒ディープスリープ
  count++;
  if (count > 5) {
    esp_sleep_enable_timer_wakeup(55000000);
    esp_deep_sleep_start();
    count = 0;
  }
  delay(1000);
}

ATOM LiteのLEDボタンを押すことで、デバイス番号(1~4)を切り替えることができます。

受信側

デバイス番号1~4のセンサデータをM5Stack Core2のLCDに表示します。

////////////////////////////////////////////////////////////////////////////////
// ライブラリ読み込み
////////////////////////////////////////////////////////////////////////////////
#include <M5Core2.h>
#include <esp_now.h>
#include <WiFi.h>

////////////////////////////////////////////////////////////////////////////////
// グローバル変数、定数
////////////////////////////////////////////////////////////////////////////////
// peer設定
esp_now_peer_info_t slave;

// デバイス番号
#define DEVICE_NUMBER_MIN 1
#define DEVICE_NUMBER_MAX 4

struct deviceInfo {
  double tmp;
  double humi;
  double pressure;
  int tvoc;
  int eco2;
};

struct deviceInfo devInfo[DEVICE_NUMBER_MAX] = {0};

////////////////////////////////////////////////////////////////////////////////
// esp-now 受信コールバック
////////////////////////////////////////////////////////////////////////////////
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
  char macStr[28];
  snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  //Serial.printf("Last Packet Recv from: %s\n\n", macStr);

  int deviceNumber = data[0];
  double tempValue = (double)data[1] + ((double)data[2] * 0.01);
  double humiValue = (double)data[3] + ((double)data[4] * 0.01);
  double pressureValue = ((double)data[5] * 100) + (double)data[6] + ((double)data[7] * 0.01);
  int tvocValue = (data[8] * 100) + data[9];
  int eco2Value = (data[10] * 100) + data[11];

  // 画面表示用/データ送信用のデータ更新
  if((deviceNumber >=  DEVICE_NUMBER_MIN) && (deviceNumber <=  DEVICE_NUMBER_MAX)) {
    devInfo[deviceNumber-1].tmp = tempValue;
    devInfo[deviceNumber-1].humi = humiValue;
    devInfo[deviceNumber-1].pressure = pressureValue;
    devInfo[deviceNumber-1].tvoc = tvocValue;
    devInfo[deviceNumber-1].eco2 = eco2Value;
  }
}

////////////////////////////////////////////////////////////////////////////////
// 初期設定
////////////////////////////////////////////////////////////////////////////////
void setup() {
  // serial
  Serial.begin(115200);

  // 動作周波数 240MHz → 80MHz
  setCpuFrequencyMhz(80);

  // M5Stack
  M5.begin();
  M5.Lcd.setTextSize(2);  // テキストサイズ指定
  M5.Lcd.setCursor(0, 0);  // カーソル位置を設定
  
  // ESP-NOW初期化
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
    M5.Lcd.print("ESPNow Init Success\n");
  } else {
    Serial.println("ESPNow Init Failed");
    M5.Lcd.print("ESPNow Init Failed\n");
    ESP.restart();
  }

  // マルチキャスト用Slave登録
  memset(&slave, 0, sizeof(slave));
  for (int i = 0; i < 6; ++i) {
    slave.peer_addr[i] = (uint8_t)0xff;
  }
  esp_err_t addStatus = esp_now_add_peer(&slave);
  if (addStatus == ESP_OK) {
    // Pair success
    Serial.println("Pair success");
  }

  // ESP-NOWコールバック登録
  esp_now_register_recv_cb(OnDataRecv);
}

////////////////////////////////////////////////////////////////////////////////
// メインループ
////////////////////////////////////////////////////////////////////////////////
void loop() {
  M5.update();
  
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.setTextColor(WHITE, BLACK);

  int i = 0;
  int error_flag = 0;
  for (i = 0; i < DEVICE_NUMBER_MAX; i=i+2) {
    M5.Lcd.setTextColor(WHITE, BLACK);
    M5.Lcd.printf("DvNo|    %d     |    %d\n", i+1, i+2);
    M5.Lcd.printf("----+----------+----------\n");

    // 温度
    M5.Lcd.printf("Temp|  ");
    M5.Lcd.printf("%4.1f C", devInfo[i].tmp);
    M5.Lcd.printf("  |  ");
    M5.Lcd.printf("%4.1f C\n", devInfo[i+1].tmp);

    // 湿度
    M5.Lcd.printf("Humi|  ");
    M5.Lcd.printf("%4.1f %%", devInfo[i].humi);
    M5.Lcd.printf("  |  ");
    M5.Lcd.printf("%4.1f %%\n", devInfo[i+1].humi);

    // 圧力
    M5.Lcd.printf("Pres|");
    M5.Lcd.printf("%6.1f hPa", devInfo[i].pressure);
    M5.Lcd.printf("|");
    M5.Lcd.printf("%6.1f hPa\n", devInfo[i+1].pressure);

    // TVOC
    M5.Lcd.printf("TVOC| ");
    M5.Lcd.printf("%5d ppb", devInfo[i].tvoc);
    M5.Lcd.printf("| ");
    M5.Lcd.printf("%5d ppb\n", devInfo[i+1].tvoc);

    // eCO2
    M5.Lcd.printf("eCO2| ");
    M5.Lcd.printf("%5d ppm", devInfo[i].eco2);
    M5.Lcd.printf("| ");
    M5.Lcd.printf("%5d ppm\n", devInfo[i+1].eco2);

    //
    M5.Lcd.printf("\n");
  }

  delay(1000);
}

受信結果

以下は、デバイス番号1と2にそれぞれ設定したATOM Liteから、温湿度気圧センサユニットとTVOC/eCO2 ガスセンサユニットのデータを取得したときの、受信側のM5Stack Core2のLCDに表示された内容です。

これでワイヤレスでセンサ情報を収集できるようになりました。

この記事が気に入ったらサポートをしてみませんか?