07.Basic等で3ヶ所の温度測定 ESP-NOW


07.Basic等で3ヶ所の温度測定 ESP-NOW

ATOM Lite 2台とM5Stack Basic 1台を使用し、3ヶ所の温湿度・気圧を測定し、ATOMのデータをBasicにESP-NOWで送信し表示します。
↓ 快晴のPM3
↓ その日のPM5
センサー精度は、温度:±0.2℃, 湿度:±2%, 気圧:±1hPa です。

必要ハード

・ATOM Lite x2
・M5Stack Basic x1
・温湿度・気圧センサー ENV2 x3

ESP-NOW

前回に引き続きESP-NOWです。今回は、
ESP32を使用したESP-NOW:複数のボードからデータを受信(多対1)
https://randomnerdtutorials.com/esp-now-many-to-one-esp32/
を参考にしています。

MACアドレスは前回調べました。以下の通りです。
Basic 2号機 8C:AA:B5:82:26:5C
 居間(南側の室内)に置きます。

ATOM 3号機 50:02:91:92:2F:E4
 倉庫(北側の室内)に置きます。

ATOM 4号機 50:02:91:91:FF:94
 室外(仮 南側のベランダで野ざらし)に置きます。

スケッチ説明

データ配列
boardsStruct[0]は、Basic 2号機
boardsStruct[1]は、board3 id=3 で ATOM 3号機
boardsStruct[2]は、board4 id=4 で ATOM 4号機
の測定データとします。

受信側Basic

前回からの変更点
・センサのライブラリ読込み
・データ構造 (id,測定データ,配列)
・コールバック変数 (大幅変更)
・loop() 画面表示・表示間隔


・日本語サイズを32から36にしたら
 "スケッチが大きすぎます。"
 と表示されたので、
 Arduino-IDE > ツール > Partition Scheme = Default → No OTA (Large APP)
 に変更しました。
・受信は随時で、データ配列を更新します。
・表示間隔は10秒で、その時自分の測定値も表示します。
・画面右上に受信号機(3 or 4)を表示します。
・測定値の配列は受信したら書換えるので、途中から送信できなくなっても前のデータのままです。そこで、測定値を表示後に配列を0リセットします。
・気圧データが0の時は、受信できていないとして、その号機のデータを全て" -- "と表示します。

送信側ATOM

3号機 (前回からの変更点)
・センサのライブラリ読込み
・データ構造 (id,測定データ)
・loop内のデータ作成(測定)内容

4号機 (3号機からの変更点)
・loop内の myData.id=3 → 4

倉庫と室外に置き、5秒ごとに測定と送信をして、送信成功すると緑点灯します。(受信できているかは判定しません)

動作の不具合の対処

Basic側表示

●何も表示しない (電源off) → 電源を確認。
●数値が異常 (センサー断) → センサーを接続しBasicを再起動。

ATOM側表示

●" -- "と表示
・(起動直後) → 10秒待つ
・ATOMのLEDは点滅 (通信が届かない) → 近くへ持ってくる。見晴らしの良い場所へ移動。
・ATOMのLEDは消灯 (電源off) → 電源を確認。
●数値が異常 (センサー断) → センサーを接続しBasicを再起動

スケッチ

Basic(受信)側


// 受信側 Basic  ATOMから送信
// https://randomnerdtutorials.com/esp-now-many-to-one-esp32/ より変更
#include <M5Stack.h>                           // M5Stack Basicを使用
#include <esp_now.h>                           // ESP-NOW通信を使用
#include <WiFi.h>                              // wifiを使用
#include <Wire.h>                              // I2Cを使用
#define LGFX_AUTODETECT                        // LovyanGFX自動認識
#define LGFX_USE_V1                            // LovyanGFX Ver1を使用
#include <LGFX_AUTODETECT.hpp>                 // クラス"LGFX"を用意
static LGFX lcd;                               // LGFXのインスタンスを作成
#include <Adafruit_SHT31.h>                    // 温湿度センサを使用
#include <Adafruit_BMP280.h>                   // 気圧センサを使用
Adafruit_SHT31 sht = Adafruit_SHT31(&Wire);    // sht定義
Adafruit_BMP280 bme = Adafruit_BMP280(&Wire);  // bmp定義だがbmeとする
int xx1 = 3, yy1 = 80, dxx1 = 93, dyy1 = 40;   // タイトルの表示位置と表示刻み


typedef struct struct_message {  // 受信データ構造体(max250byte)
  int id;                        // myData.idは送信機No. (3-4)の2台
  int tmp;                       // myData.tmpは10倍した温度(x0.1℃)
  int hum;                       // myData.humは湿度(%)
  int pre;                       // myData.preは気圧(hPa)
} struct_message;
struct_message myData;  // 受信データを格納するmyDataというstruct_message変数を作成
struct_message board2;  // ボード2(Basic 2号機)から読取った内容を保持する構造
struct_message board3;  // ボード3(ATOM 3号機)から読取った内容を保持する構造
struct_message board4;  // ボード4(ATOM 4号機)から読取った内容を保持する構造
// 各ボードにstruct_message変数を作成し、受信データを対応ボードに割当てる
struct_message boardsStruct[3] = { board2, board3, board4 };  // 全構造体を含む配列を作成 id=2,3,4


void OnDataRecv(const uint8_t *mac_addr, const uint8_t *incomingData, int len) {  // コールバック関数
  lcd.setCursor(280, 0);                                                          // 表示位置(x,y)
  lcd.setTextColor(WHITE, BLACK);                                                 // 黒地(前の字を消す)に白文字
  char macStr[18];                                                                // =2x6+5+1
  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]);  // ボードのMACアドレスを取得
  //Serial.print(macStr);                                                     // 受信したMACアドレスを表示
  memcpy(&myData, incomingData, sizeof(myData));  // 変数incomingDataの内容を変数myDataにコピー
  // メモリコピー(コピー先ポインタ,コピー元ポインタ,コピーバイト数)
  Serial.printf("%dbyte", len);    // バイト数表示 整数4bytex4個=16
  lcd.setCursor(280, 0);           // 表示位置(x,y)
  lcd.setTextColor(WHITE, BLACK);  // 黒地(前の字を消す)に白文字
  if (myData.id == 3) {            // id=3なら
    lcd.print("3");                // 受信時表示
    Serial.print(" ATOM No.3=");   // 表示
  } else if (myData.id == 4) {     // id=4なら
    lcd.print("4");                // 受信時表示
    Serial.print(" ATOM No.4=");   // 表示
  }
  boardsStruct[myData.id - 2].tmp = myData.tmp;                               // 受信温度更新
  boardsStruct[myData.id - 2].hum = myData.hum;                               // 受信湿度更新
  boardsStruct[myData.id - 2].pre = myData.pre;                               // 受信気圧更新
  Serial.printf(" %4.1f℃", (float)(boardsStruct[myData.id - 2].tmp) / 10.0);  // 受信温度表示
  Serial.printf("%3d%%", boardsStruct[myData.id - 2].hum);                    // 受信湿度表示
  Serial.printf(" %4dhPa\n", boardsStruct[myData.id - 2].pre);                // 受信気圧表示
  delay(100);                                                                 // 0.1秒待つ
  lcd.setCursor(280, 0);                                                      // 表示位置(x,y)
  lcd.print(" ");                                                            // 表示を消す
}


void LCDset() {                             // 初期画面設定関数
  lcd.init();                               // 画面初期化
  lcd.setRotation(1);                       // 回転方向(0-3)
  lcd.setBrightness(20);                    // バックライト輝度(0-255)
  lcd.setColorDepth(24);                    // RGB888 24bit (16=RGB565 16bit)
  lcd.setCursor(0, 0);                      // 表示位置(x,y)
  lcd.setFont(&fonts::lgfxJapanGothic_36);  // ゴシック(8,12,16,20,24,28,32,36,40)
  lcd.setTextColor(GREEN, BLACK);           // 色設定(文字色[,背景色])
}


void setup() {
  M5.begin();                               // M5stack初期化
  Serial.begin(115200);                     // シリアルモニタ通信速度設定
  LCDset();                                 // 初期画面設定関数へ
  while (!bme.begin(0x76)) {                // BMP280のアドレスで開始できない時
    M5.lcd.println("エラー BMP280未接続");  // 表示
  }
  while (!sht.begin(0x44)) {               // shtのアドレスで開始できない時
    M5.lcd.println("エラー SHT3X未接続");  // 表示
  }
  WiFi.mode(WIFI_STA);                       // Wi-Fiステーション(子機)に設定
  if (esp_now_init() != ESP_OK) {            // ESP-NOWの初期化が成功しなかったら
    Serial.println("エラー ESP-NOW初期化");  // 表示
    return;                                  //
  }
  esp_now_register_recv_cb(OnDataRecv);  // ESPNOWデータ受信のコールバック関数を登録
}

void loop() {                                                       // 画面表示
  boardsStruct[0].tmp = (int)(sht.readTemperature() * 10.0 + 0.5);  // Basicのセンサー温度を読取る
  boardsStruct[0].hum = (int)(sht.readHumidity() + 0.5);            // Basicのセンサー湿度を読取る
  boardsStruct[0].pre = (int)(bme.readPressure() / 100.0 + 0.5);    // Basicのセンサー気圧を読取る

  lcd.setTextColor(WHITE, BLACK);      // 黒地(前の字を消す)に白文字
  lcd.setCursor(xx1, yy1);             // 表示位置(x,y)
  lcd.print("居間");                   // 表示
  lcd.setCursor(xx1 + dxx1, yy1);      // 表示位置(x,y)
  lcd.print("倉庫");                   // 表示
  lcd.setCursor(xx1 + dxx1 * 2, yy1);  // 表示位置(x,y)
  lcd.print("室外");                   // 表示

  for (int i = 0; i < 3; i++) {
    lcd.setCursor(xx1 + dxx1 * i, yy1 + dyy1);                   // 表示位置(x,y)
    lcd.setTextColor(GREEN, BLACK);                              // 黒地(前の字を消す)に緑文字
    if (boardsStruct[i].pre == 0) {                              // 0hPaなら
      lcd.print(" -- ");                                         // 表示
    } else {                                                     // 0hPaでなければ
      lcd.printf("%4.1f", (float)(boardsStruct[i].tmp / 10.0));  // 温度 文字サイズ1~7
    }
    boardsStruct[i].tmp = 0;                               // 表示後、データクリア
    if (i == 2) {                                          // もし最終号機だったら
      lcd.setTextSize(0.75);                               // txt倍率
      lcd.setCursor(xx1 + dxx1 * i + 85, yy1 + dyy1 + 5);  // 表示位置(x,y)10 8
      lcd.print("℃");                                      // 単位表示
      lcd.setTextSize(1);                                  // txt倍率を戻す
    }

    lcd.setCursor(xx1 + dxx1 * i, yy1 + dyy1 * 2);  // 表示位置(x,y)
    lcd.setTextColor(CYAN, BLACK);                  // 黒地にほぼ黄文字
    if (boardsStruct[i].pre == 0) {                 // 0hPaなら
      lcd.print(" -- ");                            // 表示
    } else {                                        // 0hPaでなければ
      lcd.printf("%4d", boardsStruct[i].hum);       // 湿度
    }
    boardsStruct[i].hum = 0;                                   // 表示後、データクリア
    if (i == 2) {                                              // もし最終号機だったら
      lcd.setTextSize(0.75);                                   // txt倍率
      lcd.setCursor(xx1 + dxx1 * i + 85, yy1 + dyy1 * 2 + 5);  // 表示位置(x,y)
      lcd.print("%");                                         // 単位表示
      lcd.setTextSize(1);                                      // txt倍率を戻す
    }

    lcd.setCursor(xx1 + dxx1 * i, yy1 + dyy1 * 3);  // 気圧の表示位置(x,y)
    lcd.setTextColor(PINK, BLACK);                  // 黒地にピンク色文字
    if (boardsStruct[i].pre == 0) {                 // 0hPaなら
      lcd.print(" -- ");                            // 表示
    } else {                                        // 0hPaでなければ
      lcd.printf("%4d", boardsStruct[i].pre);       // 気圧を表示
    }
    boardsStruct[i].pre = 0;                                   // 表示後、データクリア
    if (i == 2) {                                              // もし最終号機だったら
      lcd.setTextSize(0.86);                                   // txt倍率
      lcd.setCursor(xx1 + dxx1 * i + 85, yy1 + dyy1 * 3 + 5);  // 表示位置(x,y)
      lcd.print("hPa");                                        // 単位表示
      lcd.setTextSize(1);                                      // txt倍率を戻す
    }
    lcd.drawRoundRect(xx1 + dxx1 * i - 3, yy1 - 3, dxx1 - 14, dyy1 * 4, 10, WHITE);  // 角丸の矩形罫線11
  }
  delay(10000);  // 10秒待つ
}
* flash memory(2.0Mbyte)のうち、スケッチが65%使用。RAM(327kbyte)のうち、global変数が12%使用、local変数で287kbyte使用可能。(1000byte=1kbyteで計算)

ATOM側

受信側のMACアドレスは各自のに修正してください。

// 送信側 ATOM (id=3 or 4)  Basic 2号機へ温湿度・気圧送信
#include <M5Atom.h>                                                   // ATOM Lightを使用
#include <esp_now.h>                                                  // ESP-NOW通信を使用
#include <WiFi.h>                                                     // wifiを使用
#include <Adafruit_BMP280.h>                                          // 気圧センサ
#include <Adafruit_SHT31.h>                                           // 温湿度センサ
uint8_t broadcastAddress[] = { 0x8C, 0xAA, 0xB5, 0x82, 0x26, 0x5C };  // 相手(Basic)のMACアドレス
Adafruit_SHT31 sht = Adafruit_SHT31(&Wire);                           // 定義
Adafruit_BMP280 bme = Adafruit_BMP280(&Wire);                         // 定義

typedef struct struct_message {  // 送信データ構造体
  int id;                        // myData.idは送信機No. (3-4)の2台
  int tmp;                       // myData.tmpは10倍した温度(x0.1℃)
  int hum;                       // myData.humは湿度(%)
  int pre;                       // myData.preは気圧(hPa)
} struct_message;
struct_message myData;         // struct_messageをmyDataとする
esp_now_peer_info_t peerInfo;  // 定義

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {  // コールバック関数
  if (status == ESP_NOW_SEND_SUCCESS) {                                   // 送信成功なら
    Serial.print("パケット送信:成功   ");                                 // 表示
  } else {                                                                // さもなければ
    Serial.print("パケット送信:失敗   ");                                 // 表示
  }
}

void setup() {
  M5.begin(true, false, true);         // 初期化(Serial,I2C?,Display)
  Wire.begin(26, 32);                  // I2CでG26とG32を使用
  while (!bme.begin(0x76)) {           // BMP280(スレーブアドレス)を初期化できなければ
    Serial.println("BMP280が未接続");  // 表示
  }
  while (!sht.begin(0x44)) {          // SHT30を初期化できなければ
    Serial.println("SHT30が未接続");  // 表示
  }
  M5.dis.setBrightness(100);                 // LEDの明るさ
  Serial.println();                          // シリアルモニタ 改行
  WiFi.mode(WIFI_STA);                       // Wi-Fiステーション(子機)に設定
  if (esp_now_init() != ESP_OK) {            // ESP-NOWの初期化が成功しなかったら
    Serial.println("エラー ESP-NOW初期化");  // 表示
    return;
  }
  esp_now_register_send_cb(OnDataSent);  // 送信後のコールバック関数を登録
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  // メモリコピー(コピー先ポインタ,コピー元ポインタ,コピーバイト数)
  peerInfo.channel = 0;                         // デバイスのチャネル(0-14)
  peerInfo.encrypt = false;                     // 暗号化しない
  if (esp_now_add_peer(&peerInfo) != ESP_OK) {  // デバイスを追加できなかったら
    Serial.println("エラー デバイス追加");      // 表示
    return;
  }
}

void loop() {
  myData.id = 4;                                           // ATOM 3号機(id=3) or 4号機(id=4)
  myData.tmp = (int)(sht.readTemperature() * 10.0 + 0.5);  // センサー温度を読取る
  myData.hum = (int)(sht.readHumidity() + 0.5);            // センサー湿度を読取る
  myData.pre = (int)(bme.readPressure() / 100.0 + 0.5);    // センサー気圧を読取る
  Serial.printf("id=%1d %3.1f℃ %3d%% %4dhPa   ", myData.id, (float)(myData.tmp / 10.0), myData.hum, myData.pre);
  esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *)&myData, sizeof(myData));
  // ESP-NOW送信(相手のMACアドレス,データ,データ長さ)
  if (result == ESP_OK) {          // 戻り値が成功なら
    Serial.println("送信成功");    // シリアルモニタに表示
    M5.dis.drawpix(0, 0x00ff00);   // 緑
  } else {                         // 戻り値が成功でなければ
    Serial.println("送信エラー");  // シリアルモニタに表示
    M5.dis.drawpix(0, 0xff0000);   // 赤
  }
  delay(10);       // 10mS待つ
  M5.dis.clear();  // 消灯
  delay(4990);     // 4.99秒待つ
  //delay(59990);  // 計1分待つ
}
* flash memory(1.3Mbyte)のうち、スケッチが55%使用。RAM(327kbyte)のうち、global変数が11%使用、local変数で288kbyte使用可能。(1000byte=1kbyteで計算)