// The ESP8266 RTC memory is arranged into blocks of 4 bytes. The access methods read and write 4 bytes at a time, // so the RTC data structure should be padded to a 4-byte multiple. struct { uint32_t crc32; // 4 bytes uint8_t channel; // 1 byte, 5 in total uint8_t bssid[6]; // 6 bytes, 11 in total uint8_t txPow = 0; // 1 byte, 12 in total IPAddress mqttServer = mqttBroker; //save the dns lookup #ifdef CCS_USE bool ccsInit = false; #endif #ifdef SGP30_USE bool sgpInit = false; #endif } rtcData; /************************* RTC Mem *********************************/ #define WiFi_RTC 0 /************************* SSD1306 SPI *********************************/ #ifdef OLED_OUTPUT #include SSD1306 display(0x3c, D4, D3); //#include "OLEDDisplayUi.h" #include "DejaVu_Sans_Mono_18.h" // Initialize the OLED display using SPI // D5 -> CLK // D7 -> MOSI (DOUT) // D1 -> RES // D2 -> DC // D8 -> CS SSD1306Spi display(D1, D2, D8); OLEDDisplayUi ui( &display ); #endif /************************* BMP280 I2C *********************************/ #ifdef BME2_USE //https://github.com/finitespace/BME280#usage #include #include "EnvironmentCalculations.h" BME280I2C bme; BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); BME280::PresUnit presUnit(BME280::PresUnit_hPa); #endif /************************* BMP680 I2C *********************************/ #ifdef BME6_USE //ToDo /* //https://github.com/finitespace/BME280#usage #include #include "EnvironmentCalculations.h" BME280I2C bme6; const bool metric = true; BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); BME280::PresUnit presUnit(BME280::PresUnit_hPa); */ #endif /************************* SHTXX I2C *********************************/ #ifdef SHT_USE #include SHTSensor sht(SHTSensor::SHT3X_ALT); #endif /************************* VEML6075 I2C *********************************/ #ifdef VEML_USE //https://github.com/schizobovine/VEML6075 #include //#include VEML6075 uvMeter; #endif /************************* BH1750 I2C *********************************/ #ifdef BH_USE //https://github.com/claws/BH1750 #include BH1750 lightMeter; #endif /************************* CCS811 I2C *********************************/ #ifdef CCS_USE //https://github.com/LucAce/CCS811 #include "CCS811.h" // Error ID code enum { CCS811_ERROR_CODE_WRITE_REG_INVALID = B00000001, CCS811_ERROR_CODE_READ_REG_INVALID = B00000010, CCS811_ERROR_CODE_MEASMODE_INVALID = B00000100, CCS811_ERROR_CODE_MAX_RESISTANCE = B00001000, CCS811_ERROR_CODE_HEATER_FAULT = B00010000, CCS811_ERROR_CODE_HEATER_SUPPLY = B00100000, }; #ifdef SERIAL_DEBUG //***************************************************************************** // Function: errorDecode // Environment decodes the single bits from the error return //***************************************************************************** void errorDecode(uint8_t error_code) { if (bitRead(error_code, CCS811_ERROR_CODE_WRITE_REG_INVALID)) Serial.println(F("ERROR: Wrong write request received")); if (bitRead(error_code, CCS811_ERROR_CODE_READ_REG_INVALID)) Serial.println(F("ERROR: Wrong read request received")); if (bitRead(error_code, CCS811_ERROR_CODE_MEASMODE_INVALID)) Serial.println(F("ERROR: Wrong meausrement mode request received")); if (bitRead(error_code, CCS811_ERROR_CODE_MAX_RESISTANCE)) Serial.println(F("ERROR: Maximum sensor resistance reached!")); if (bitRead(error_code, CCS811_ERROR_CODE_HEATER_FAULT)) Serial.println(F("ERROR: Heater current out of range!")); if (bitRead(error_code, CCS811_ERROR_CODE_HEATER_SUPPLY)) Serial.println(F("ERROR: Heater supply voltage out of bounce!")); } #endif // CCS811 I2C Interface CCS811 ccs; #define CCSINTERRUPT D7 //GPIO13 #define CCSRESET D6 //GPIO12 #endif #ifdef SGP30_USE #include "SparkFun_SGP30_Arduino_Library.h" SGP30 sgp; #endif void messageReceived(String &topic, String &payload) { #ifdef SERIAL_DEBUG Serial.println("incoming: " + topic + " - " + payload); #endif } /************** generic settings **************************/ void sleep(int duration, RFMode type) { #ifdef SERIAL_DEBUG //flush UART (pg. 11 esp8266 low power solution) Serial.flush(); delay(10); #endif ESP.deepSleep(duration, type); yield(); delay(100); } uint32_t calculateCRC32( const uint8_t *data, size_t length ) { uint32_t crc = 0xffffffff; while ( length-- ) { uint8_t c = *data++; for ( uint32_t i = 0x80; i > 0; i >>= 1 ) { bool bit = crc & 0x80000000; if ( c & i ) { bit = !bit; } crc <<= 1; if ( bit ) { crc ^= 0x04c11db7; } } } return crc; } #ifdef BATTERY_USE uint32_t voltageDivCorrelation(uint32_t u3) { const unsigned int offset = 8; if (u3 < offset) { return BATT_MEASUREMENT_OFFSET; } //translate the 10bit value to reference voltage of 1.0V //Resistors are usually in kOhm //ToDo: Not working! adc contains millivoltage at native adc port uint32_t adc = map (u3 - offset, 0, 1023, 0 , 1000); const uint32_t r1 = 395; const uint32_t r2 = 220; const uint32_t r3 = 100; uint32_t rges = r1 + r2 + r3; uint32_t uges = u3 * rges / r3; //TODO why?! uges = uges / 10; return uges; } uint32_t adc = 0; #endif #if defined(BME2_USE) || defined (BME6_USE) float temperature(NAN); float humidity(NAN); float absHumidity(NAN); float pressure(NAN); float dew(NAN); String temp_str; String hum_str; String absHum_str; String pres_str; char temp[7]; char hum[8]; char absHum[8]; char pres[7]; #endif #ifdef BME6_USE float iaqF(NAN); float iaqprecF(NAN); String iaq_str; String iaqprec_str; char iaq[5]; char iaqprec[5]; #endif #if defined(SHT_USE) || defined(SI7021_USE) float temperature(NAN); float humidity(NAN); float absHumidity(NAN); float dew(NAN); String temp_str; String hum_str; String absHum_str; char temp[7]; char hum[8]; char absHum[8]; #endif #ifdef BH_USE float vislight(NAN); String light_str; char light[8]; #endif #ifdef VEML_USE float uvalight(NAN); float uvblight(NAN); float uvi(NAN); String uva_str; String uvb_str; String uvindex_str; char uva[8]; char uvb[8]; char uvindex[5]; #endif #if defined(CCS_USE) || defined(SGP30_USE) float eco2(NAN); String eco2_str; char eco2C[8]; float tvoc(NAN); String tvoc_str; char tvocC[8]; #endif #ifdef BATTERY_USE float battery(NAN); String batt_str; char batt[7]; #endif bool wifiCheck = false; bool mqttCheck = false; bool bme280Check = false; bool bme680Check = false; bool veml6075Check = false; bool bh1750Check = false; bool ccs811Check = false; bool sht30Check = false; bool sgp30Check = false; WiFiClient mqttSocket; MQTTClient mqttClient; /*************************** Sketch Code ************************************/ void initSensors() { //assign i2c pins to different pins. SPI is hardware defined. | Wire.begin(SDA,SCL) | CLK D3 | DATA D4 --> be aware that a lot libs do that internally //Wire.begin(D4, D3); //nodemcu Wire.begin(D2, D1); //wemos D1 mini /*********************SSD1306 OLED init **************************************/ #ifdef OLED_OUTPUT display.init(); display.flipScreenVertically(); //display.setFont(DejaVu_Sans_Mono_18); display.setFont(ArialMT_Plain_10); display.setTextAlignment(TEXT_ALIGN_LEFT); display.displayOn(); #endif /*********************BH1750 I2C init **************************************/ #ifdef BH_USE #ifdef SERIAL_DEBUG Serial.print("\tBH1750 init...\t"); #endif #ifdef OLED_OUTPUT // clear the display display.clear(); display.drawStringMaxWidth(0, 0, 128, "BH1750 init" ); // write the buffer to the display display.display(); #endif if (!lightMeter.begin(BH1750::ONE_TIME_HIGH_RES_MODE)) { #ifdef SERIAL_DEBUG Serial.println("no BH1750 anwering"); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "BH1750 FAILED" ); // write the buffer to the display display.display(); #endif } else { bh1750Check = true; #ifdef SERIAL_DEBUG Serial.println("BH1750 ready."); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "BH1750 ready" ); // write the buffer to the display display.display(); #endif } #endif /*********************VEML6075 I2C init **************************************/ #ifdef VEML_USE #ifdef SERIAL_DEBUG Serial.print(F("\tVEML6075 init...\t")); #endif #ifdef OLED_OUTPUT // clear the display display.clear(); display.drawStringMaxWidth(0, 0, 128, "VEML6075 init" ); // write the buffer to the display display.display(); #endif uvMeter.begin(); if (!uvMeter.getDevID()) { delay(10); #ifdef SERIAL_DEBUG Serial.println(F("no VEML6075 detected!")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "VEML6075 FAILED" ); // write the buffer to the display display.display(); #endif } else { veml6075Check = true; //set one time measurementw #ifdef SERIAL_DEBUG Serial.println(F("VEML6075 ready.")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "VEML6075 ready" ); // write the buffer to the display display.display(); #endif } #endif /*********************BME680 I2C init **************************************/ #ifdef BME6_USE #ifdef SERIAL_DEBUG Serial.print(F("\tBME680 init...\t")); #endif #ifdef OLED_OUTPUT // clear the display display.clear(); display.drawStringMaxWidth(0, 0, 128, "BME680 init" ); // write the buffer to the display display.display(); #endif if (!bme6.begin()) { delay(10); #ifdef SERIAL_DEBUG Serial.println(F("no BME680 detected!")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "BME680 FAILED" ); // write the buffer to the display display.display(); #endif } else { bme680Check = true; #ifdef SERIAL_DEBUG Serial.println(F("BME680 ready.")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "BME680 ready" ); // write the buffer to the display display.display(); #endif } #endif /*********************BME280 I2C init **************************************/ #ifdef BME2_USE #ifdef SERIAL_DEBUG Serial.print(F("\tBME280 init...\t")); #endif #ifdef OLED_OUTPUT // clear the display display.clear(); display.drawStringMaxWidth(0, 0, 128, "BME280 init" ); // write the buffer to the display display.display(); #endif if (!bme.begin()) { #ifdef SERIAL_DEBUG Serial.println(F("no BME280 detected!")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "BME280 FAILED" ); // write the buffer to the display display.display(); #endif } else { bme280Check = true; #ifdef SERIAL_DEBUG Serial.println(F("BME280 ready.")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "BME280 ready" ); // write the buffer to the display display.display(); #endif } #endif /*********************SHT3X I2C init **************************************/ #ifdef SHT_USE #ifdef SERIAL_DEBUG Serial.print(F("\tSHT30 init...\t")); #endif #ifdef OLED_OUTPUT // clear the display display.clear(); display.drawStringMaxWidth(0, 0, 128, "SHT30 init" ); // write the buffer to the display display.display(); #endif if (!sht.init()) { #ifdef SERIAL_DEBUG Serial.println(F("no SHT30 detected!")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "SHT30 FAILED" ); // write the buffer to the display display.display(); #endif } else { sht30Check = true; sht.setAccuracy(SHTSensor::SHT_ACCURACY_MEDIUM); // only supported by SHT3x yield(); #ifdef SERIAL_DEBUG Serial.println(F("SHT30 ready.")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "SHT30 ready" ); // write the buffer to the display display.display(); #endif } #endif /*********************CCS811 I2C init **************************************/ #ifdef CCS_USE // Extend I2C clock stretch timeout (See Notes) //Wire.setClockStretchLimit(500); Wire.setClockStretchLimit(200000); pinMode(CCSINTERRUPT, INPUT); //pinMode(CCSRESET, OUTPUT); delay(10); digitalWrite(CCSRESET, HIGH); delay(10); #ifdef SERIAL_DEBUG Serial.print(F("\tCCS811 init...\t")); #endif #ifdef OLED_OUTPUT // clear the display display.clear(); display.drawStringMaxWidth(0, 0, 128, "CCS811 init" ); // write the buffer to the display display.display(); #endif if (!ccs.begin()) { delay(10); #ifdef SERIAL_DEBUG Serial.println(F("no CCS811 detected!")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "CCS811 FAILED" ); // write the buffer to the display display.display(); #endif } else { ccs811Check = true; if (!rtcData.ccsInit) { Serial.println(F("CCS811 not initialised")); rtcData.ccsInit = true; } //set CCS811 measurement mode ccs.writeMeasModeRegister(CCS811_DRIVE_MODE_1SEC , 1 , 0); //Serial.println(F("INFO: Set measurement refresh to 1s")); //enable Interrupt ccs.enableInterrupt(); //Serial.println(F("INFO: Enable interrupt")); /*while (!digitalRead(CCSINTERRUPT)) { delay(200); Serial.print("."); } while (!ccs.isDATA_READY()) { delay(100); ccs.readStatusRegister(); Serial.print("."); }; */ // ToDo why and reasons //float temp = ccs.calculateTemperature(); ccs.setTempOffset(temperature - 25.0); #ifdef SERIAL_DEBUG Serial.println(F("CCS811 ready.")); /* // Print CCS811 sensor information Serial.println(F("CCS811 Sensor Enabled:")); Serial.print(F("Hardware ID: 0x")); Serial.println(ccs.getHWID(), HEX); Serial.print(F("Hardware Version: 0x")); Serial.println(ccs.getHWVersion(), HEX); Serial.print(F("Firmware Boot Version: 0x")); Serial.println(ccs.getFWBootVersion(), HEX); Serial.print(F("Firmware App Version: 0x")); Serial.println(ccs.getFWAppVersion(), HEX); Serial.println(); */ #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "CCS811 ready" ); // write the buffer to the display display.display(); #endif } #endif /*********************SGP30 I2C init **************************************/ #ifdef SGP30_USE // Extend I2C clock stretch timeout (See Notes) //Wire.setClockStretchLimit(500); Wire.setClockStretchLimit(400000); #ifdef SERIAL_DEBUG Serial.print(F("\tSGP30 init...\t")); #endif #ifdef OLED_OUTPUT // clear the display display.clear(); display.drawStringMaxWidth(0, 0, 128, "SGP30 init" ); // write the buffer to the display display.display(); #endif if (!sgp.begin()) { delay(10); #ifdef SERIAL_DEBUG Serial.println(F("no SGP30 detected!")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "SGP30 FAILED" ); // write the buffer to the display display.display(); #endif } else { sgp30Check = true; delay(100); /* //Get SGP30's ID sgp.getSerialID(); //Get version number sgp.getFeatureSetVersion(); Serial.print("SerialID: 0x"); Serial.print((unsigned long)sgp.serialID, HEX); Serial.print("\tFeature Set Version: 0x"); Serial.println(sgp.featureSetVersion, HEX);*/ SGP30ERR error; //measureTest() should not be called after a call to initAirQuality() error = sgp.measureTest(); if (error == SUCCESS) { Serial.println("Success!"); } else if (error == ERR_BAD_CRC) { Serial.println("CRC Failed"); } else if (error == ERR_I2C_TIMEOUT) { Serial.println("I2C Timed out"); } else if (error == SELF_TEST_FAIL) { Serial.println("Self Test Failed"); } //if (!rtcData.sgpInit) { if (error == SELF_TEST_FAIL) { Serial.println(F("SGP30 not initialised")); sgp.initAirQuality(); Serial.println(F("SGP30 warm up ")); error = sgp.measureTest(); if (error == SUCCESS) { rtcData.sgpInit = true; } } #ifdef SERIAL_DEBUG Serial.println(F("SGP30 ready.")); #endif #ifdef OLED_OUTPUT display.drawStringMaxWidth(0, 16, 128, "SGP30 ready" ); // write the buffer to the display display.display(); #endif } #endif /******************** i2c init END ****************/ } void setup() { pinMode(D0, WAKEUP_PULLUP); // if serial is not initialized all following calls to serial end dead. #ifdef SERIAL_DEBUG Serial.flush(); Serial.begin(9600); while (!Serial) { delay(1); } // Wait Serial.print(F("\nReset reason : \t\t\t")); Serial.println(ESP.getResetReason()); #endif WiFi.forceSleepBegin(); // send wifi to sleep to reduce power consumption yield(); delay(100); #ifdef BATTERY_USE //check battery status //Battery Voltage Measurement //pinMode(D6, OUTPUT); //digitalWrite(D6, HIGH); //activate transistor //yield(); //delay(10); pinMode(A0, INPUT); yield(); adc = analogRead(A0); yield(); delay(10); #ifdef SERIAL_DEBUG Serial.print(F("ADC_measurement: \t\t\t")); Serial.println (String(adc)); Serial.print(F("VoltageDividerCorrelation in V: \t")); Serial.println(String(voltageDivCorrelation(adc))); #endif yield(); delay(10); // go to sleep if to low if (voltageDivCorrelation(adc) - BATT_MEASUREMENT_OFFSET < BATT_WARNING_VOLTAGE) { #ifdef SERIAL_DEBUG Serial.println(F("Low Battery Voltage. Got to sleep")); #endif sleep(SLEEP_TIME_LOW_BAT, WAKE_RF_DISABLED); } #endif //go further if not //init wifi wifiCheck = wifi_connect(); if (wifiCheck) { //connect mqtt if (!mqttClient.loop()) { //connect mqtt mqttCheck = mqtt_connect(); } if (mqttCheck) { #ifdef SERIAL_DEBUG Serial.println(F("Sensor init routine")); yield(); #endif initSensors(); yield(); #ifdef SERIAL_DEBUG Serial.println(F("--------measurement--------")); yield(); #endif measurement(); yield(); #ifdef SERIAL_DEBUG Serial.println(F("---------------------------")); yield(); #endif mqtt_disconnect(); yield(); } //firmware update check /*if (firmware_available() //{ // get firmware }*/ //regular --check whether 5min leads to wifi timeout wifi_disconnect(); yield(); if (mqttCheck) { #ifdef SERIAL_DEBUG Serial.println(F("regular sleep")); Serial.println(F("***********************************************************")); #endif sleep(SLEEP_TIME_MEASUREMENT, WAKE_RF_DEFAULT); } else { //ToDo Do more useful stuff #ifdef SERIAL_DEBUG Serial.println(F("mqtt failed - wifi timeout sleep")); #endif sleep(SLEEP_TIME_WIFI_TIMEOUT, WAKE_RF_DEFAULT); } } //no wifi available else { #ifdef SERIAL_DEBUG Serial.println(F("Wifi failed")); #endif sleep(SLEEP_TIME_WIFI_TIMEOUT, WAKE_RFCAL); } } void loop() { yield(); }