ESP32エアモニターにGPSモニター機能を追加

ESP32エアモニターにGPSモニター機能を追加

ESP32エアモニターにGPSモニター機能を追加

前回作成した”ESP32で海面気圧付きエアモニター”で未着手だった”スイッチで切り替えてOLEDにGPSの受信データを表示させる予定”を実施しました。
スイッチを実装したついでに設定された測定値表示間隔(10秒)では無く毎秒更新するモードも付加しました。

追加した機能と目的
・GPSモニター モードスイッチが押される度に通常のエアモニターと切り替え、位置や精度情報などを観る。
・連続表示更新 モードスイッチをおしたまま起動した場合、表示を毎秒更新させてGPSやセンサーの挙動を観る。
・表示更新LED データ更新プロセスでESP32DevボードのLEDを点灯、表示値が変化しない場合でも動作中を確認。

末尾にコードを置きますが、前回のコードに機能追加しただけです。
ベースにしたスケッチはスイッチサイエンスのサイト”AmbientでIoTをはじめよう 空気品質を測定し、記録する”です。

 

/*
 * This sketch is modified from "Ambient_GPS_BME280_CCS811". 
 * Change MCU to ESP32, Added Display SSD1305 OLED.
 * And stopping send data to Ambient by WiFi.
 */
#include <SoftwareSerial.h>   // No need this line when use Hardware Serial.
#include <WiFi.h>
#include "Ambient.h"

#include <TimeLib.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include "bme280_i2c.h" 
#include "SparkFunCCS811.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeMono9pt7b.h>


#define Rxpin 16              // No need this line when use Hardware Serial.
#define Txpin 17              // No need this line when use Hardware Serial.
// /HardwareSerial ss(2);     //use HardwareSerial-2 (Pins are fixed: Rx=16 Tx=17)
SoftwareSerial ss;            //use SoftwareSerial

TinyGPSPlus gps;
TinyGPSCustom pdop(gps, "GPGSA", 15);   // $GPGSA sentence, 15th element
TinyGPSCustom vdop(gps, "GPGSA", 17);   // $GPGSA sentence, 17th element

#define SDA 21
#define SCL 22

#define CCS811_ADDR 0x5A      // CCS811 Address
#define CCS811_HW_RESET 5
#define CCS811_WAKE 4
CCS811 ccs811(CCS811_ADDR);

#define SCREEN_WIDTH 128      // OLED display width, in pixels
#define SCREEN_HEIGHT 64      // OLED display height, in pixels
#define SCREEN_ADDRESS 0x3C   // I2C:SSD1306
#define OLED_RESET  -1        // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

BME280 bme280(BME280_I2C_ADDR_PRIM);


#define ModePin 27
int NowMode = 0 ;             // 0:Air測定モード  1:GPSモニターモード  

int PERIOD = 10 ;             // Set measuring interval in sec.

const int WFuse = 0;          // 0:WiFi,Ambientを使わない  1:使う 
WiFiClient client;
const char* ssid = "xxxx";
const char* password = "xxxxxxxx";
Ambient ambient;
unsigned int channelId = 1234;          // AmbientチャンネルID
const char* writeKey = "xxxxxxxxx";     // Ambientライトキー

const int offset = 9;                   // 時刻差(ローカル時刻 - UTC)
int Year;
byte Month, Day, Hour, Minute, Second;
int Lastday = 0;

uint16_t CO2, TVOC;

void ccs811_hw_reset() {      // CCS811のRESETピンをLOWにしてリセットする
    digitalWrite(CCS811_HW_RESET, LOW);
    delay(10);
    digitalWrite(CCS811_HW_RESET, HIGH);
}

void ccs811_wake() {          // CCS811のnWAKEピンをLOWにしてI2C通信できるようにする
    digitalWrite(CCS811_WAKE, LOW);
    delay(10);
}

void ccs811_sleep() {         // CCS811のnWAKEピンをHIGHにして休ませる
    digitalWrite(CCS811_WAKE, HIGH);
}

void setup()
{
    pinMode (ModePin, INPUT_PULLUP);    // 表示モード切替スイッチ入力
    pinMode (2, OUTPUT);                // 測定結果更新ランプ on board LED 
    Serial.begin(115200);
    delay(100);
    if(!digitalRead(ModePin)){  // 立ち上がり時にModeスイッチが押されていたら連続測定モード
      PERIOD = 0;
    }
    Serial.println("\r\nAir quality with location");
    // ss.begin(9600);        //use HardwareSerial2
    ss.begin(9600,SWSERIAL_8N1, Rxpin, Txpin, false); //use SoftwareSerial
    if (!ss) {                // If the object did not initialize, then its configuration is invalid
        Serial.println("Invalid SoftwareSerial pin configuration, check config"); 
        while (1) {           // Don't continue with invalid configuration
            smartDelay(1000);
        }
    } 
    if (WFuse == 1){
        WiFi.begin(ssid, password);              //  Wi-Fi APに接続
        while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち
            Serial.print(".");
            smartDelay(1000);
       }
       Serial.print("WiFi connected\r\nIP address: ");
       Serial.println(WiFi.localIP());
    }

    pinMode(CCS811_HW_RESET, OUTPUT);
    pinMode(CCS811_WAKE, OUTPUT);
    ccs811_hw_reset(); // CCS811のリセット
    ccs811_wake(); // CCS811をI2C通信可能にする

    Wire.begin(SDA, SCL);
    bme280.begin(); // BME280の初期化
    CCS811Core::status returnCode = ccs811.begin(); // CCS811を初期化
    if (returnCode != CCS811Core::SENSOR_SUCCESS) { // 初期化に失敗したら
        Serial.print(".begin() returned with an error: ");
        Serial.println(returnCode, HEX);
        while (1) {
            Serial.print("CCS811 stopiing prog. in setup ");
            display.clearDisplay(); display.setCursor(0, 30); display.print("Err CCS811");
            display.display();
            delay(0); // プログラムを停止する
        }
    }
    ccs811_sleep(); // CCS811のI2C通信を止める

    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
    if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
       Serial.println(F("SSD1306 allocation failed"));
       for(;;); // Don't proceed, loop forever
    }
    // Show initial display buffer contents on the screen --
    // the library initializes this with an Adafruit splash screen.
    display.setTextColor(SSD1306_WHITE);     // Draw white text
    display.clearDisplay();
    display.setFont(&FreeMono9pt7b);
    display.setCursor(0, 30); display.print("in progress");
    display.display();
    smartDelay(1000); // Pause for 1 seconds
    
    if (WFuse == 1){
      ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化
    }
}

void readCCS811(float humid, float temp)
{
    ccs811_wake(); // CCS811をI2C通信可能にする。
    // BME280で読んだ温度、湿度の値を使って補正をおこなう
    ccs811.setEnvironmentalData(humid, temp);
    CO2 = 0;
    while (CO2 < 400 || CO2 > 8192) {       // CCS811の測定範囲は400〜8192ppmなので、範囲外の値は読み飛ばす
        long t = millis();
        while (!ccs811.dataAvailable()) {   // CCS811のデータが読み出し可能かチェック
            smartDelay(100);
            long er = millis() - t;
            Serial.print("waiting for CCS811 data (max:3000 msec) : ");
            Serial.print("Elapsed waiting time is "); Serial.print( er ); Serial.println(" msec.");
            if (er > 3000) {
                // 3秒以上データーが読み出し可能にならなければCCS811をリセットして再起動する
                t = millis();
                Serial.println("data unavailable for too long.");
                ccs811_hw_reset();
                CCS811Core::status returnCode = ccs811.begin(); // CCS811を初期化
                Serial.println("Begin CCS811");
                if (returnCode != CCS811Core::SENSOR_SUCCESS) { // 初期化に失敗したら
                    Serial.print("CCS811.begin() returned with an error: ");
                    Serial.print(returnCode, HEX); Serial.print(":");
                    display.clearDisplay(); display.setCursor(0, 30); display.print("Err CCS811"); display.display();
                    while (1) {
                        delay(0); // プログラムを停止する
                    }
                }
            }
        }
        ccs811.readAlgorithmResults(); // CO2とTVOCの値をCSS811から読み出す。
        CO2 = ccs811.getCO2(); // CO2の値を取得する。
        TVOC = ccs811.getTVOC(); // TVOCの値を取得する。
    }
    ccs811_sleep(); // CCS811のI2C通信を停止する。
}

time_t lasttime = 0;
void loop()
{
    while (ss.available() > 0) {
        if (gps.encode(ss.read())) {
            break;
        }
        // /delay(0);
    }
    NowMode = (digitalRead(ModePin) == NowMode) ? 1 : 0 ;    //change display mode, if mode switch on
    
    if ((millis() - lasttime) > PERIOD * 1000 && gps.location.isValid() && gps.altitude.meters()) {
        if (gps.date.day()!= Lastday){
            setTime((gps.time.hour()), (gps.time.minute()), (gps.time.second()), (gps.date.day()), (gps.date.month()), (gps.date.year()));
            adjustTime(offset * SECS_PER_HOUR); // /UTCとJSTの時間差(hour)を秒数に変換してarduinoの時計を調整
            Serial.println("MCU date and time adjusted by GPS ");
            Lastday = gps.date.day();
          }
        digitalWrite(2, HIGH);    // データ更新中LED点灯
        Serial.print(year()); Serial.print("/"); Serial.print(month()); Serial.print("/"); Serial.print(day()); Serial.print(" "); 
        Serial.print(hour()); Serial.print(":"); Serial.print(minute()); Serial.print(":"); Serial.print(second()); Serial.print(" "); 
        Serial.print("Elapse: "); Serial.print((millis() - lasttime)); Serial.print("msec");
        Serial.print(" SATs:"); Serial.println(gps.satellites.value());
        lasttime = millis();
        
        char buf[16];        
        // /緯度・経度はTinyGPS++ライブラリでGPS度分データの分を60で割った10進数の度単位に換算されている
        dtostrf(gps.location.lat(), 10, 6, buf); Serial.print("lat: "); Serial.print(buf);
        dtostrf(gps.location.lng(), 10, 6, buf); Serial.print(", lng: "); Serial.print(buf);
        dtostrf(gps.altitude.meters(), 6, 1, buf); Serial.print(", altitude: "); Serial.print(buf);
        dtostrf(gps.hdop.hdop(), 4, 2, buf); Serial.print(", hdop: "); Serial.print(buf);
        Serial.print(", vdop: "); Serial.print(vdop.value());
        Serial.print(", pdop: "); Serial.println(pdop.value());

        struct bme280_data data;
        bme280.get_sensor_data(&data);
        dtostrf(data.temperature, 5, 2, buf); Serial.print(" temp: "); Serial.print(buf);
        dtostrf(data.humidity, 5, 2, buf); Serial.print(", humid: "); Serial.print(buf);
        dtostrf(data.pressure / 100, 7, 2, buf); Serial.print(", press: "); Serial.print(buf);
        float press0 = (data.pressure/100) / (pow((1-((0.0065*gps.altitude.meters())/(data.temperature+0.0065*gps.altitude.meters()+273.15))),5.257));
        Serial.print(", press(0m): "); Serial.println(press0);

        // 温度、湿度を渡して、CO2、TVOCの値を測定する
        readCCS811(data.humidity, data.temperature);
        Serial.print("CO2: "); 
        if(CO2 != 400){
           Serial.print(CO2); Serial.print(" ppm,   TVOC: "); Serial.print(TVOC);Serial.println(" ppb");
        }
        else{
          Serial.println("Lower limit");
        }
        Serial.print("\r\n");
     
        //Display
        display.clearDisplay();
        if (NowMode == 0){             // Air測定結果表示
          display.setFont(&FreeMono9pt7b);
          display.setCursor(0, 10);
          display.print("Temp ");
          display.print(data.temperature, 1);
          display.println("'C");
          display.setCursor(0, 27);
          display.print("RHum ");
          display.print(data.humidity, 1);
          display.println("%");
          display.setCursor(0, 44);
          display.print("Pres ");
          display.print(press0,0);
          display.println("hP");
          display.setCursor(0, 61);
          display.print("CO2 ");
             if(CO2 != 400){
                display.print(CO2);
                display.println("ppm");
             }
             else{
               display.print("Lower");
             }          
        }
        else{                          // GPSモニター表示
          display.setFont(NULL);
          display.setTextSize(1);
          display.setCursor(10, 0);
          display.print("GPS DATA MONITOR");
          display.setCursor(0, 9);
          display.print(year()); display.print("/"); display.print(month()); display.print("/"); display.print(day()); display.print(" "); 
          display.print(hour()); display.print(":"); display.print(minute()); display.print(":"); display.print(second());
          display.setCursor(0, 18);
          dtostrf(gps.location.lat(), 10, 6, buf); display.print("Lat: "); display.print(buf);
          display.setCursor(0, 27);
          dtostrf(gps.location.lng(), 10, 6, buf); display.print("Lng: "); display.print(buf);
          display.setCursor(0, 36);
          dtostrf(gps.altitude.meters(), 6, 1, buf); display.print("Alt: "); display.print(buf); display.print("m");
          display.setCursor(0, 45);
          display.print("SATs:"); display.print(gps.satellites.value());display.print(", pdop: "); display.print(pdop.value());
          display.setCursor(0, 54);
          dtostrf(gps.hdop.hdop(), 4, 2, buf); display.print("hdop: "); display.print(buf);display.print(", vdop: "); display.print(vdop.value());
          }
        display.display();

        if (WFuse == 1){        // 温度、湿度、気圧、CO2の値をAmbientに送信する
           ambient.set(1, data.temperature);
           ambient.set(2, data.humidity);
           ambient.set(3, data.pressure / 100);
           ambient.set(4, press0);
           ambient.set(5, CO2);
           
           dtostrf(gps.location.lat(), 12, 8, buf);
           ambient.set(6, buf);
           dtostrf(gps.location.lng(), 12, 8, buf);
           ambient.set(7, buf);
           ambient.send();
        }
    }
    else{
        if (! gps.location.isValid()){
          Serial.print("\r\nElapse:"); Serial.print(millis()/1000);Serial.println("sec   Waiting for GPS data to stable.");
        }
    }
    digitalWrite(2, LOW);    // データ更新中LED消灯
    smartDelay(800);         // 測定インターバル最小値
}
static void smartDelay(unsigned long ms){  // 通常のdelay()を使用してプロセスを止めないように、TinyGPS++がsmartDelayを推奨している
  unsigned long start = millis();
  do 
  {
    while (ss.available())
      gps.encode(ss.read());
  } while (millis() - start < ms);
}