お盆休みの電子工作


 お盆休みがあり、じっくり電子工作に没頭しました。
M5Stack Core2を以前に買ったままで休眠状態だったので、起こしてやろう思い、M5Stack Core2とM5Stack用温湿度気圧センサユニット Ver.3(ENV Ⅲ)で、温湿度気圧を測定。
次に、そのデータをMQTTでパソコンに送信。受信は、node-redを利用しましました。 ネットの記事を参照しながら、作成しました。

概要

概要図
M5Stack Core2 データ取得画面

M5Stack Core2の設定

  • 画面には、温湿度気圧、取得時間を表示

  • バッテリーの残量を表示したけど、おかしい。調べてみようと思ってます。

  • SDカードにデータを保存可能

  • PubSubClientを利用して、Node-Redにデータを送信

コード

#include <M5Core2.h>
#include "M5_ENV.h"
#include <PubSubClient.h>
#include <ArduinoJson.h>

#include <WiFi.h>
#include <time.h>
//ファイル保存するかどうか。ファイルに保存するならここを true にする
#define FILEWRITE false

// 保存するファイル名
const char *fname = "/env_log.csv";

// ループのウェイト、何秒待つかをミリ秒で指定
const long DELAY = 10000;    // ミリ秒

// Wi-FiのSSID
char *ssid = "******";
// Wi-Fiのパスワード
char *password = "******";

// MQTTの接続先(ラズパイ)のIP 
const char* endpoint = "***.***.***.***";

// MQTTのポート
const int port = 1883;

// デバイスID
char* deviceID = "M5StackCore2";

// メッセージを知らせるトピック
char* pubTopic = "/pub/M5StackCore2";

// メッセージを待つトピック
char* subTopic = "/sub/M5StackCore2";

//ENV3
SHT3X sht30;
QMP6988 qmp6988;

//温度・湿度・圧力
float tmp      = 0.0;
float hum      = 0.0;
float pressure = 0.0;

// === 関数プロトタイプ宣言
uint8_t getBattery(uint16_t, uint16_t);

// Time
char ntpServer[] = "ntp.jst.mfeed.ad.jp";   // ntpサーバ 
const long gmtOffset_sec = 9 * 3600;
const int  daylightOffset_sec = 0;
struct tm timeinfo;
String dateStr;
String timeStr;

void getTimeFromNTP(){
 // NTPサーバと時刻を同期
 configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
 while (!getLocalTime(&timeinfo)) {
   delay(1000);
 }
}
File file;
// ================================================================== //
// バッテリ残量取得
// 戻り値 : uint8_t 0 ~ 100(%)
// ================================================================== //
uint8_t getBattery() {
 uint8_t vat = 0xFF;
 Wire.beginTransmission(0x75);
 Wire.write(0x78);                   // 0x78 バッテリ残量取得レジスタアドレスオフセット
 Wire.endTransmission(false);
 if (Wire.requestFrom(0x75, 1)) {
   vat = Wire.read() & 0xF0;         // 下位4ビット 0 マスク
   if      (vat == 0xF0) vat = 0;
   else if (vat == 0xE0) vat = 25;
   else if (vat == 0xC0) vat = 50;
   else if (vat == 0x80) vat = 75;
   else if (vat == 0x00) vat = 100;
   else                  vat = 0xFF;
 } else vat = 0xFF;

 return vat;
}

void writeData(char *paramStr) {
 // SDカードへの書き込み処理(ファイル追加モード)
 // SD.beginはM5.begin内で処理されているので不要
 file = SD.open(fname, FILE_APPEND);
 file.println(dateStr + "," + timeStr + "," + paramStr);
 file.close();
}


////////////////////////////////////////////////////////////////////////////////
   
WiFiClient httpsClient;
PubSubClient mqttClient(httpsClient);

// シリアル通信ブロックの初期化
void setup_serial(){
  Serial.begin(9600);
  while (!Serial) continue;
}

// Wi-Fiブロックの初期化と開始
void setup_wifi(){
  
  Serial.println("Connecting to ");
  Serial.print(ssid);

  // WiFi接続性改善のため、いったん切断
  WiFi.disconnect( true, true ); //WiFi OFF, eraseAP=true
  delay(500);

  // WiFi開始
  WiFi.begin(ssid, password);
 
  // Wi-Fi接続待ち
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
    M5.Lcd.print(".");
  }

  // WiFi接続成功メッセージの表示
  Serial.println("\nWiFi Connected.");
  M5.Lcd.setCursor(10, 40);
  M5.Lcd.setTextSize(2);
  M5.Lcd.println("WiFi Connected.");

  // M5StackのIPアドレスを表示
  M5.Lcd.print("IP address: ");
  M5.Lcd.println(WiFi.localIP());
  
}

void getTime(){
 // 時刻の取得と表示
 getLocalTime(&timeinfo);
 dateStr = (String)(timeinfo.tm_year + 1900)
         + "/" + (String)(timeinfo.tm_mon + 1)
         + "/" + (String)timeinfo.tm_mday;
 timeStr = (String)timeinfo.tm_hour
         + ":" + (String)timeinfo.tm_min
         + ":" + (String)timeinfo.tm_sec;

 //M5.Lcd.setTextColor(WHITE,BLACK);
 //M5.Lcd.setCursor(0, 200, 1);
 M5.Lcd.println(dateStr + "   ");
 M5.Lcd.println(timeStr + "   ");
}
//MQTT接続処理   
void connectMQTT(){
  // MQTT接続待ち
  while (!mqttClient.connected()) {
    if (mqttClient.connect(deviceID)) {
      // 所定のデバイスと接続した
      Serial.println("Connected.");
      int qos = 0;
      mqttClient.subscribe(subTopic, qos);
      Serial.println("Subscribed.");
    } else {
      // 所定のデバイスと接続失敗 ⇒ 再トライする
      Serial.print("Failed. Error state=");
      Serial.print(mqttClient.state());
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

// MQTT接続管理  
void mqttLoop(){
  
  // MQTT接続状態を監視し、切断されていたら、再接続を試みる
  if (!mqttClient.connected()) {
    connectMQTT();
  }
  // MQTTクライアント側の定期処理の実行
  mqttClient.loop();
 
}

//センサーデータ取得   
void readData(){
  //温度・湿度
  if (sht30.get()==0){
  // 温度の取得
  tmp = sht30.cTemp;
  // 湿度の取得
  hum = sht30.humidity;
  }else{
    tmp=0;
    hum=0;  
  }

  // 気圧の取得[hPa = Pa * 0.01]
  pressure = qmp6988.calcPressure() * 0.01;
}


// M5Stackの初期化
void setup() {
  // M5Stack objectの初期化
  M5.begin();
  // M5.Power.begin();
  // M5.IMU.Init();

 setup_serial();

  // 画面初期設定
  M5.Lcd.fillScreen(TFT_NAVY);
  M5.Lcd.setCursor(10, 10);
  M5.Lcd.setTextSize(2);
  M5.Lcd.printf("START");

  // Wi-Fi処理の開始
  setup_wifi();

  // MQTTクライアントの初期化 
  mqttClient.setServer(endpoint, port);

  // MQTT接続開始
  connectMQTT();

  qmp6988.init();//気圧初期化
 // timeSet
   getTimeFromNTP();

}

// M5Stackの定期処理
void loop() {

  StaticJsonDocument<500> doc;
  char pubMessage[256];

  
  uint8_t batt = getBattery();
  //センサーデータ読込
  readData();

  

   // 温度、湿度、気圧をシリアル通信で送信
   Serial.printf("Temperatura: %2.2f*C  Humedad: %0.2f%%  Pressure: %0.2fPa\r\n", tmp, hum, pressure);
   

   //LCD表示クリア&色設定
   M5.Lcd.setCursor(0, 0); // カーソル
   M5.Lcd.setTextColor(WHITE, BLACK);  // 色
   M5.Lcd.setTextSize(4);  // 文字サイズ

   // 時刻表示
   getTime();
   M5.Lcd.printf("--------\n");

   // 温度、湿度、気圧、バッテリー情報
   M5.Lcd.printf("Temp:%2.1f \nHumi:%2.0f%% \nPres:%2.0fhPa \nBatt: %d%%", tmp, hum, pressure,batt);
  
  
  // MQTT接続監視
   mqttLoop();

  // メッセージの作成
  sprintf(pubMessage, "{\"tmp\": %5.1f,\"hum\": %5.1f,\"pressure\": %5.1f}",tmp, hum, pressure);
   
  // JSONメッセージの作成
  JsonArray idValues = doc.createNestedArray("ID");
  idValues.add(deviceID);

  JsonArray tmpValues = doc.createNestedArray("tmp");
  tmpValues.add(tmp);
 
  JsonArray humValues = doc.createNestedArray("hum");
  humValues.add(hum);
 
  JsonArray pressureValues = doc.createNestedArray("pressure");
  pressureValues.add(pressure);
  
  serializeJson(doc, pubMessage);
  
  // メッセージの画面表示
  Serial.print("topic= ");
  Serial.println(pubTopic);
  Serial.print("Message= ");
  Serial.println(pubMessage);
  M5.Lcd.clear();  
  M5.Lcd.setCursor(0, 0); // カーソル
  M5.Lcd.setTextColor(GREENYELLOW, BLACK);  // 色
  M5.Lcd.setTextSize(3);  // 文字サイズ
  
  // 温度、湿度、気圧、バッテリー情報
  M5.Lcd.printf("Temp:%2.1f \nHumi:%2.0f%% \nPres:%2.0fhPa \nBatt: %d%%", tmp, hum, pressure,batt);
  
  
  M5.Lcd.setCursor(0, 100); // カーソル
  M5.Lcd.setTextColor(YELLOW, BLACK);  // 色
  M5.Lcd.setTextSize(2);  // 文字サイズ
  getTime();
  M5.Lcd.printf("");
  M5.Lcd.setTextColor(WHITE, BLACK);  // 色
  M5.Lcd.printf(pubMessage);  
  

  // メッセージのPublish
  mqttClient.publish(pubTopic, pubMessage);
  Serial.println("Published.");
  Serial.println("");
  
  #if FILEWRITE
   // ファイル出力
   char buff[128];
   sprintf(buff,"%2.1f ,%2.0f% ,%2.0f, %d%", tmp, hum, pressure,batt);
   writeData(buff);
  #endif

   // ボタンイベント処理
   //Aボタンを押したときに明るくするテスト。今はDELAYの切り替わりのタイミングでしか動作しない
   if (M5.BtnA.wasPressed()) {
     M5.Lcd.setBrightness(100);
   } else {
     M5.Lcd.setBrightness(10);
   }
   M5.update();
   
   delay(DELAY);
}

Node-Red設定

Node Redのフロー図

  • ブロガーとMQTT設定

  • データからダッシュボード作成

  • データのデータベース登録

フロー図
aedes brokerノード
mqtt in ノード

5M5Core2側で指定した、ポート・トピックを設定
Node-Redでデータ受信
JSON形式で送信されてくる。データをデバッグノードで確認する。

Node Redで受信したデータ
  • JSON形式のデータからchangeノードを利用し、温度、湿度、気圧データに分解

  • chartノードに出力する。

change設定
Chartノード設定
Node Red ダッシュボード

SQLite3へデータを登録

SQLite3にテーブル準備

テーブル作成、選択、削除のフロー
Injectノードのmsg.topicにSQL文を設定
sqliteノード設定

データベースにに保存先.DB名を入力する。
Injectノードのmsg.topicにSQL文を設定

--msg.topicSQL(登録)
CREATE TABLE readings(
id INTEGER PRIMARY KEY AUTOINCREMENT, datetime timestamp, 
temperature NUMERIC, humidity NUMERIC, pressure NUMERIC)

--msg.topicSQL(選択)
SELECT * FROM readings order by datetime desc

--msg.topicSQL(削除)DROP TABLE readings

MQTT-insqliteノード⇒Functionsqliteノード⇒sqliteノードのフロー
functionノードでtopicにInsert文を設定
debugノードでInsert SQL文を確認
debugノードでSelet SQL文と結果の確認
Select 結果

まとめ(乾燥)

M5core2を利用し、温度・湿度・気圧データを利用し、データを可視化・保存するところまでを、自前行ってみた。少しずつ、ホームIOTを進めていきたいと思っていいます。
コロナで外に出られないので、じっくりできることができた。
データ取得・可視化は,Raspberry pi + BeebotteやAmbientを利用して可視化、データ保存は試したことがあった。
自前でデータ取得、保存、可視化までを試してみたかったので、とりあえず、目標は達成できた。
 M5Stackのプログラム作成は、Visual Studio Code & PlatformIOで試してみました。
最近は、便利なツールがあり、電子工作もいろいろやりたくなります。
 それと、Node-Redは、すごいですね。いろいろな連携が可能で、Rasberry  pi やUbuntuで利用できるので、すごく便利です。
Windows 7で利用していたPCにUbuntuを入れて、サーバとして利用しています。
すこしずつ、ためしたら記事にして残したいと思います。
 

いいなと思ったら応援しよう!