2019年7月31日水曜日

ESP32とBME280によるIoT気象計のスケッチ

今日は猛烈に暑かったですね。昼間、IoT気象計を置いたリビングが35度を越えていました。まあ、先日書いたように使っているセンサBME280は温度が高めにでるので、それほどではないと思いますが。

Machinistの気温グラフです。所々データが抜けているのは、データがうまくアップできなかったようです。21:00前後に抜けているのは、電源をオフ/オンしていたからです。


ThingSpeakの方は、昨日から今日にかけてごっそりとデータが抜けています。処理的には、WiFiをオンしてMachinistにアップしてWiFiをオフし、再度WiFiをオンしてThingSpeakにアップしていますが、WiFiオフからオンの時間を少し空けた方が安定するのかもしれません。


スケッチはバグがあるかもしれません、無保証です。著作権は留保しますが、改変などご自由にどうぞ。もしお気づきの点があればコメント頂けると幸いです。回路図は簡単なので省略します。 写真を参考にしてください。


今回試したIoTプラットフォームは、どちらも無料で試せます(それなりに制限あり)。

ThingSpeakは以下のページがわかりやすくて参考にさせて頂きました。
 http://iwathi3.hatenablog.com/entry/Data-to-Graph-ThingSpeak

MachinistはIIJがやっていて日本語なので判りやすかったです。
https://machinist.iij.jp/getting-started/


//--------------------------------------------------------------------
//  IoT気象計                                          2019.07.31 naka
//     気圧・気温・湿度をクラウド(IoTプラットフォーム)にアップする                                              //
//  Device
//    ・WiFiマイコン ESP32
//    ・気圧・気温・湿度センサ BME280 (I2C)
//    ・小型LCD AQM1602XA-RN-GBW (I2C)
//    ・モニタ用LED (pin2)
//
//  IoTプラットフォーム
//    ・ThingSpeak https://thingspeak.com/
//    ・Machinist  https://machinist.iij.jp/
//
//  処理
//    ・1分毎に測定しLCDに測定値を表示(更新間隔1分)
//    ・5分毎に過去5回の平均を出してIoTプラットフォームにアップ
//
//--------------------------------------------------------------------
#include <Wire.h>
#include <FaBoLCDmini_AQM0802A.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <SparkFunBME280.h>
#include <ThingSpeak.h>

// WiFiアクセスポイント情報
const char *ssid = "******";
const char *pass = "********";

// Machinist情報
const String MachinistApi_key  = "****************";
const String MachinistURL = "https://gw.machinist.iij.jp/endpoint";

// ThingSpeak情報
unsigned long myChannelNumber = ******;
const char* myWriteAPIKey = "****************";
int   ThingSpeak_DelayCount;  // ポスト間隔を15秒以上あける必要があり、そのためのカウンタ

WiFiClient client;
FaBoLCDmini_AQM0802A lcd;
BME280 sensor;

#define LED_PIN  2
int LedBlink = 0;

// タイマー割り込み用
volatile int interruptCounter;
hw_timer_t  *timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

int  secCount = -1;
int  minCount = -1;
int  nCount;
float temp, hum, pres;
float avetemp,avehum,avepres;

void IRAM_ATTR secEvent() {  //1000ms=1s毎に呼び出される
  portENTER_CRITICAL_ISR(&timerMux);
  interruptCounter++;
  portEXIT_CRITICAL_ISR(&timerMux);
}

void setup() {
  char buff[17];
  IPAddress IP;

//  Serial.begin(115200);
//  Serial.print("started\n");

  // タイマー割り込み設定
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &secEvent, true);
  timerAlarmWrite(timer, 1000000, true);  // 1sec (us単位)

  // モニタLED設定
  pinMode(LED_PIN, OUTPUT);

  // モニタLCD設定
  SetupLCD();
  lcd.clear();
  lcd.print("hello!");  // 起動メッセージ

  // 温湿度センサ
  SetupSensor();

  // WiFi接続(起動時に確認のために一旦繋いでみる)
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
  }

  lcd.clear();
  lcd.print("WiFi connected");
  IP = WiFi.localIP();
  WiFi.disconnect(WIFI_OFF);  // 省電力のために切断

  sprintf(buff, "IP=%d:%d:%d:%d", IP[0], IP[1], IP[2], IP[3]);
  lcd.setCursor(0, 1); // Col,Raw
  lcd.print(buff);

  ThingSpeak_DelayCount = -1;
  
  delay(5000);  // 5秒待ち(IPアドレスを目視確認のため)
  lcd.clear();

  nCount = 0;  // センサー値の平均用変数初期化
  temp = hum = pres = 0.0;

  timerAlarmEnable(timer);  // 割り込みタイマー開始
}

void loop() {
  float ftemp,fhum,fpres;
  char buff[17];

  if (interruptCounter > 0) { // タイマー割り込み処理(1秒毎に割り込み)
    portENTER_CRITICAL(&timerMux);  // ここから3行は決まり文句らしい
    interruptCounter--;
    portEXIT_CRITICAL(&timerMux);

    // 以下、1秒ごとに実行する処理
    secCount++;
    LedBlink ^= 1;  // トグル操作
    digitalWrite(LED_PIN, LedBlink);

    if (secCount == 60 || secCount==0) {
      minCount++;
      secCount = 0;
      // センサーから値を取り出す
      ftemp = sensor.readTempC();
      fhum  = sensor.readFloatHumidity();
      fpres = sensor.readFloatPressure() / 100.0;

      DispLCD(ftemp,fhum,fpres); // LCDに表示
      
      temp += ftemp; // 平均を出すためにΣ
      hum  += fhum;
      pres += fpres;
      nCount++;
      
      if (minCount == 5) { // 5分毎に平均してIoTプラットフォームへポストする
        minCount = 0;
        avetemp = temp / nCount;
        avehum  = hum  / nCount;
        avepres = pres / nCount;
        temp = hum = pres = 0.0;
        nCount = 0;

        // IoTプラットフォームにポストする
        PostMachinist(avetemp,avehum,avepres);
        PostThingSpeak('T', avetemp);
        ThingSpeak_DelayCount = 0;  // カウントスタート
      }
    }
    
    sprintf(buff, "%02d:%02d", minCount, secCount);
    lcd.setCursor(11, 0); // Col,Raw
    lcd.print(buff);

    if (ThingSpeak_DelayCount>=0) {  // ThingSpeakはポスト間隔を15秒以上開ける必要があり、
      ThingSpeak_DelayCount++;       // 一応20秒経ったら湿度、さらに20秒経ったら気圧をポスト
      if (ThingSpeak_DelayCount==20) {
        PostThingSpeak('H',avehum);
      }
      else if (ThingSpeak_DelayCount==40) {
        PostThingSpeak('P',avepres);
        ThingSpeak_DelayCount = -1; // カウントストップ
      }
    }

  }
}

void SetupSensor() {
  sensor.setI2CAddress(0x76); // 0x77(default) or 0x76
  sensor.beginI2C();
  // 以降は省略可(室内の状況を測定するには以下がよいらしい)
  sensor.setFilter(4); //0 to 4 is valid. Filter coefficient. See 3.4.4
  sensor.setStandbyTime(0); //0 to 7 valid. Time between readings. See table 27.
  sensor.setTempOverSample(2); //0 to 16 are valid. 0 disables temp sensing. See table 24.
  sensor.setPressureOverSample(5); //0 to 16 are valid. 0 disables pressure sensing. See table 23.
  sensor.setHumidityOverSample(1); //0 to 16 are valid. 0 disables humidity sensing. See table 19.
  sensor.setMode(MODE_NORMAL); //MODE_SLEEP, MODE_FORCED, MODE_NORMAL is valid. See 3.3
}

void SetupLCD() {
  lcd.begin();
  lcd.command(0x38);
  lcd.command(0x39);
  lcd.command(0x14);
  lcd.command(0x73);
  // lcd.command(0x51);
  lcd.command(0x56);  // 3.3V
  delay(2);
  lcd.command(0x6c);
  delay(300);
  lcd.command(0x38);
  delay(1);
  lcd.command(0x01);
  delay(2);
  lcd.command(0x0c);
  delay(2);
}

void DispLCD(float temp, float hum, float pres) { // LCDに表示
  char tempbuff[8], humbuff[8], presbuff[8], buff[17];
  
  dtostrf(temp, 4, 1, tempbuff);
  dtostrf(hum,  4, 1, humbuff);
  dtostrf(pres, 6, 1, presbuff);
  sprintf(buff,"%shPa",presbuff);
  lcd.setCursor(0, 0); // Col,Raw
  lcd.print(buff);

  sprintf(buff,"%s%cC   %s%%",tempbuff,0xDF,humbuff);
  lcd.setCursor(2, 1); // Col,Raw
  lcd.print(buff);
}

void PostThingSpeak(char kind, float val) {
  // WiFi接続
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) { delay(100); }
  
  ThingSpeak.begin(client); // ThingSpeak初期化
  switch (kind) {
    case 'T': // 気温
      ThingSpeak.writeField(myChannelNumber, 1, val, myWriteAPIKey); // 1 は気温用
      ThingSpeak_DelayCount = 0; // 送信用カウンタ初期化
      break;
    case 'H': // 湿度
      ThingSpeak.writeField(myChannelNumber, 2, val, myWriteAPIKey); // 2 は湿度用
      break;
    case 'P': // 気圧
      ThingSpeak.writeField(myChannelNumber, 3, val, myWriteAPIKey); // 3 は気圧用
      break;
  }
  
  WiFi.disconnect(WIFI_OFF);
}

void PostMachinist(float temp, float humid, float pres) {
  const String api_key = "Bearer " + MachinistApi_key;  
  HTTPClient http;
  String Json;
  
  Json = MkJson(temp, humid, pres); // JSONフォーマット作成

  // WiFi接続
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) { delay(100); }

  http.begin(MachinistURL);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", api_key);

  int http_rc = http.POST(Json);

  if (http_rc > 0) {
    String response = http.getString();
    //     Serial.println(http_rc);
    //     Serial.println(response);
  }
  else {
    //     Serial.print("Error on sending POST Request: ");
    //     Serial.println(http_rc);
  }
  http.end();
  WiFi.disconnect(WIFI_OFF);
}

String MkJson(float temp, float humid, float pres) {
  char buff[8];
  String strbuff;
  String Json;
  
  dtostrf(temp, 4, 1, buff);
  strbuff = buff;
  Json = "{";
  Json += "\"agent\": \"Home\",";
  Json += "\"metrics\": [";
  Json += "{ ";
  Json += "\"name\" : \"temperature\",";
  Json += "\"namespace\": \"Environment Sensor\",";
  Json += "\"data_point\": { ";
  Json += "\"value\":" + strbuff;
  Json += "}";
  Json += "},";

  dtostrf(humid, 4, 1, buff);
  strbuff = buff;
  Json += "{ ";
  Json += "\"name\" : \"humidity\",";
  Json += "\"namespace\": \"Environment Sensor\",";
  Json += "\"data_point\": { ";
  Json += "\"value\":" + strbuff;
  Json += "}";
  Json += "},";

  dtostrf(pres, 6, 1, buff);
  strbuff = buff;
  Json += "{ ";
  Json += "\"name\" : \"Pressure\",";
  Json += "\"namespace\": \"Environment Sensor\",";
  Json += "\"data_point\": { ";
  Json += "\"value\":" + strbuff;
  Json += "}";
  Json += "}";

  Json += "]";
  Json += "}";

  return Json;
}

//--------------------------------------------------------------------

0 件のコメント:

コメントを投稿