#include #include "Adafruit_CCS811.h" #include #include #include #include #include #include "soc/rtc_cntl_reg.h" #include "soc/rtc.h" #include "driver/rtc_io.h" RTC_DATA_ATTR int boot_count = 0; //---------VERSION---------// String own_version="2.7"; String server_version=""; int update_code = 0; int firmware_code = 0; //---------VERSION---------// //-------DEFINE PINS-------// int battPin = 0; int ThermistorPin = 1; int measurePin = 2; int solarPin = 3; int ledPower = 4; #define SENSORS_EN GPIO_NUM_5 #define LED_BUILTIN GPIO_NUM_7 //-------DEFINE PINS-------// #ifndef STASSID #define STASSID "###NETWORK##" #define STAPSK "###PASSWORD###" #endif const char* ssid = STASSID; const char* password = STAPSK; Adafruit_CCS811 ccs; Adafruit_ADS1115 ads; Adafruit_BME280 bme; float CO = 0; float NH3 = 0; float NO2 = 0; float eCO2 = 0; float TVOC = 0; float VIN = 0; float temp = 0; float humidity = 0; float pressure = 0; int sensorValue = 0; float voMeasured = 0; float calcVoltage = 0; float dustDensity = 0; float dustDensityAverage = 0; float thermistorAverage = 0; float battery_voltage = 0; float solar_voltage = 0; //-------THERMISTOR-------// float Vo; float R1 = 10000; // value of R1 on board float logR2, R2, T; float c1 = 0.001129148, c2 = 0.000234125, c3 = 0.0000000876741; //-------THERMISTOR-------// //-------DUST SENSOR-------// int samplingTime = 280; int deltaTime = 40; int sleepTime = 9680; //-------DUST SENSOR-------// float deepsleeptime = 600e6; unsigned long loop_time = 0; int wifi_timeout = 15000; //ms int http_timeout = 10000; //ms int heatingsleeptime = 30e6; //-------DEFINE SENSOR URLS-------// const char* version_file = "https://url.com/esp/weather_station_2/version.txt"; const char* firmware_file = "https://url.com/esp/weather_station_2/firmware.bin"; //---------Home Assistant---------// String apiKey = "Bearer ###LONG STRING OF CHARS###"; const char* air_quality_temp = "https://home-assistant-IP:8123/api/states/sensor.air_quality_temp"; const char* air_quality_humidity = "https://home-assistant-IP:8123/api/states/sensor.air_quality_humidity"; const char* air_quality_pressure = "https://home-assistant-IP:8123/api/states/sensor.air_quality_pressure"; const char* air_quality_eCO2 = "https://home-assistant-IP:8123/api/states/sensor.air_quality_eCO2"; const char* air_quality_TVOC = "https://home-assistant-IP:8123/api/states/sensor.air_quality_TVOC"; const char* air_quality_CO = "https://home-assistant-IP:8123/api/states/sensor.air_quality_CO"; const char* air_quality_NH3 = "https://home-assistant-IP:8123/api/states/sensor.air_quality_NH3"; const char* air_quality_NO2 = "https://home-assistant-IP:8123/api/states/sensor.air_quality_NO2"; const char* air_quality_dust = "https://home-assistant-IP:8123/api/states/sensor.air_quality_dust"; const char* air_quality_battery_voltage = "https://home-assistant-IP:8123/api/states/sensor.air_quality_battery_voltage"; const char* air_quality_solar_voltage = "https://home-assistant-IP:8123/api/states/sensor.air_quality_solar_voltage"; const char* air_quality_battery_temp = "https://home-assistant-IP:8123/api/states/sensor.air_quality_battery_temp"; const char* air_quality_bus_voltage = "https://home-assistant-IP:8123/api/states/sensor.air_quality_bus_voltage"; const char* air_quality_version = "https://home-assistant-IP:8123/api/states/sensor.air_quality_version"; //-------DEFINE SENSOR URLS-------// void setup() { Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); pinMode(ledPower,OUTPUT); delay(10); battery_voltage = get_battery_voltage(); delay(10); solar_voltage = get_solar_voltage(); delay(10); //Do not start if battery is too low //Ignore if battery voltage is under 2V which happens when no batter is installed during troubleshooting if(battery_voltage < 3.4 && battery_voltage > 2){ Serial.print("LOW BATTERY: "); Serial.print(battery_voltage); Serial.println("V"); Serial.println("Sleeping..."); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); pinMode(SENSORS_EN,OUTPUT); gpio_hold_dis(SENSORS_EN); digitalWrite(SENSORS_EN, LOW); boot_count = 0; esp_sleep_enable_timer_wakeup(deepsleeptime); esp_deep_sleep_start(); } //If battery is OK start sensors pinMode(SENSORS_EN,OUTPUT); gpio_hold_dis(SENSORS_EN); digitalWrite(SENSORS_EN, HIGH); //If Heating phase just enable the sensors and go to sleep while sensors heat up //With ESP32 you can use the RTC to hold a pin high even during sleep. Will not work with ESP8266 if(boot_count == 0){ Serial.print("Heating Phase"); digitalWrite(LED_BUILTIN, HIGH); delay(250); digitalWrite(LED_BUILTIN, LOW); delay(250); digitalWrite(LED_BUILTIN, HIGH); delay(250); digitalWrite(LED_BUILTIN, LOW); delay(1000); digitalWrite(LED_BUILTIN, HIGH); delay(1000); boot_count++; gpio_hold_en(SENSORS_EN); esp_sleep_enable_timer_wakeup(heatingsleeptime); esp_deep_sleep_start(); } //If waking up after heating phase start communication with the I2C sensors delay(1000); Wire.begin(10, 8); //Read battery and solar voltage again (while sensors are on) for reporting Serial.print("Battery voltage: "); Serial.print(battery_voltage); Serial.print("v"); if(battery_voltage < 3.55){ Serial.println(" (BAD)"); }else{ Serial.println(" (OK)"); } Serial.print("Solar voltage: "); Serial.print(solar_voltage); Serial.print("v"); if(solar_voltage < 3.8){ Serial.println(" (NO SUN)"); }else{ Serial.println(" (SUN)"); } // The ADC input range (or gain) can be changed via the following // functions, but be careful never to exceed VDD +0.3V max, or to // exceed the upper and lower limits if you adjust the input range! // Setting these values incorrectly may destroy your ADC! // ADS1015 ADS1115 // ------- ------- ads.setGain(GAIN_TWOTHIRDS); // 2/3x gain +/- 6.144V 1 bit = 3mV 0.1875mV (default) // activate this if you are using a 5V sensor, this one should be used with Arduino boards // ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V 1 bit = 2mV 0.125mV // As the sensor is powered up using 3.3V, this one should be used with 3.3v controller boards // ads.setGain(GAIN_TWO); // 2x gain +/- 2.048V 1 bit = 1mV 0.0625mV // ads.setGain(GAIN_FOUR); // 4x gain +/- 1.024V 1 bit = 0.5mV 0.03125mV // ads.setGain(GAIN_EIGHT); // 8x gain +/- 0.512V 1 bit = 0.25mV 0.015625mV // ads.setGain(GAIN_SIXTEEN); // 16x gain +/- 0.256V 1 bit = 0.125mV 0.0078125mV // Check the ADS sensor and sleep is not working Serial.print("Checking ADS Sensor: "); if(!ads.begin()){ Serial.println("Failed to start ADS sensor! Please check your wiring."); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); boot_count = 0; esp_sleep_enable_timer_wakeup(deepsleeptime); esp_deep_sleep_start(); }else{ Serial.println("OK"); } // Check the CCS sensor and sleep if not working Serial.print("Checking CCS Sensor: "); if(!ccs.begin()){ Serial.println("Failed to start CCS sensor! Please check your wiring."); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); boot_count = 0; esp_sleep_enable_timer_wakeup(deepsleeptime); esp_deep_sleep_start(); }else{ Serial.println("OK"); } // Wait for the CCS sensor to be ready since it takes 3 seconds to wake up Serial.print("Waiting for CCS sensor: "); loop_time = millis(); while(!ccs.available() && millis() < loop_time + 5000){ delay(500); } Serial.print("OK ("); Serial.print(((millis() - loop_time)/1000),1); Serial.println(" sec)"); Serial.print("Waiting for BME sensor: "); if (!bme.begin(0x76)) { Serial.println("BME Not Found!"); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); boot_count = 0; esp_sleep_enable_timer_wakeup(deepsleeptime); esp_deep_sleep_start(); } Serial.println("OK"); } void loop() { //Loop only executes once ccs.setDriveMode(CCS811_DRIVE_MODE_250MS); delay(2000); battery_voltage = get_battery_voltage(); //Read sensor voltages 5 times and average results for (int i = 1; i <= 5; i++) { VIN = VIN + ads.readADC_SingleEnded(3)*0.0001875; delay(10); CO = CO + ads.readADC_SingleEnded(0)*0.0001875; delay(10); NH3 = NH3 + ads.readADC_SingleEnded(1)*0.0001875; delay(10); NO2 = NO2 + ads.readADC_SingleEnded(2)*0.0001875; delay(10); } VIN=VIN/5; CO=CO/5; NH3=NH3/5; NO2=NO2/5; for (int i = 1; i <= 5; i++) { if(ccs.available()){ if(!ccs.readData()){ eCO2 = eCO2 + ccs.geteCO2(); delay(10); TVOC = TVOC + ccs.getTVOC(); delay(250); } else{ i--; Serial.println("ERROR!"); } } } eCO2=eCO2/5; TVOC=TVOC/5; temp = bme.readTemperature(); pressure = (bme.readPressure() / 100.0F)*0.75006; humidity = bme.readHumidity(); get_dust(); get_thermistor(); Serial.print("VIN: "); Serial.print(VIN); Serial.print(", CO: "); Serial.print(CO); Serial.print(", CO2: "); Serial.print(eCO2); Serial.print(", NH3: "); Serial.print(NH3); Serial.print(", NO2: "); Serial.print(NO2); Serial.print(", TVOC: "); Serial.print(TVOC); Serial.print(", Temp: "); Serial.print(temp); Serial.print(", Hum: "); Serial.print(humidity); Serial.print(", Pres: "); Serial.print(pressure); Serial.print(", Dust: "); Serial.print(dustDensityAverage); Serial.print(", Batt: "); Serial.print(thermistorAverage); Serial.print(", VBatt: "); Serial.print(battery_voltage); Serial.print(", VSol: "); Serial.println(solar_voltage); //Once all measurements are done, turn off sensors and connect to WIFI ccs.setDriveMode(CCS811_DRIVE_MODE_IDLE); digitalWrite(SENSORS_EN, LOW); WiFi.mode(WIFI_STA); WiFi.disconnect(); WiFi.begin(ssid, password); WiFi.setTxPower(WIFI_POWER_8_5dBm); Serial.print("Connecting WIFI: "); loop_time = millis(); while (WiFi.status() != WL_CONNECTED && millis() < loop_time + wifi_timeout) { digitalWrite(LED_BUILTIN, HIGH); delay(50); //Serial.print("."); digitalWrite(LED_BUILTIN, LOW); delay(50); } if(WiFi.status() != WL_CONNECTED){ Serial.println(""); Serial.println("WIFI timeout..."); ccs.setDriveMode(CCS811_DRIVE_MODE_IDLE); digitalWrite(SENSORS_EN, LOW); boot_count = 0; esp_sleep_enable_timer_wakeup(deepsleeptime); esp_deep_sleep_start(); } else { //Serial.println(""); Serial.print("OK ("); Serial.print(((millis() - loop_time)/1000),1); Serial.println(" sec)"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } //Send all sensor values to home assistant send_data(); //Check for new version of software and update. This allows for OTA updates update_code = check_version(); if(update_code==1){ firmware_code = check_firmware(); if(firmware_code==1){ update_firmware(); } } boot_count++; if(boot_count > 3){ boot_count=0; } esp_sleep_enable_timer_wakeup(deepsleeptime); esp_deep_sleep_start(); } int check_version(){ Serial.println("Checking for updates"); WiFiClientSecure client; client.setInsecure(); HTTPClient http; http.setTimeout(http_timeout); http.begin(client, version_file); int httpResponseCode = http.GET(); String payload = "{}"; if (httpResponseCode>0 && httpResponseCode<300) { //Serial.print("HTTP Response code: "); //Serial.println(httpResponseCode); server_version = http.getString(); Serial.print("Server version: "); Serial.println(server_version); if(own_version != server_version){ Serial.println("Update required"); http.end(); return 1; } else{ Serial.println("No update required"); http.end(); return 0; } } else { Serial.print("Error code: "); Serial.println(httpResponseCode); http.end(); return -1; } // Free resources http.end(); return 0; } int check_firmware(){ Serial.println("Checking for firmware file"); WiFiClientSecure client; client.setInsecure(); HTTPClient http; http.setTimeout(http_timeout); http.begin(client, firmware_file); int httpResponseCode = http.GET(); String payload = "{}"; if (httpResponseCode>0 && httpResponseCode<300) { return 1; } else { Serial.print("Error code: "); Serial.println(httpResponseCode); http.end(); return -1; } // Free resources http.end(); return 0; } void update_firmware(){ for (int i = 1; i <= 10; i++) { digitalWrite(LED_BUILTIN, LOW); delay(50); digitalWrite(LED_BUILTIN, HIGH); delay(50); } Serial.println("Updating..."); WiFiClientSecure client; client.setInsecure(); HTTPClient http; http.setTimeout(http_timeout); http.begin(client, version_file); ESPhttpUpdate.update("https://url.com/esp/weather_station_2/firmware.bin"); } float get_battery_voltage(){ sensorValue = 0; for (int i = 1; i <= 10; i++) { sensorValue = sensorValue + analogRead(battPin); delay(10); } sensorValue = sensorValue/10; //Serial.println(sensorValue); return (((sensorValue*3.3)/4096.0)*2)*0.883; } float get_solar_voltage(){ sensorValue = 0; for (int i = 1; i <= 10; i++) { sensorValue = sensorValue + analogRead(solarPin); delay(10); } sensorValue = sensorValue/10; //Serial.println(sensorValue); return (((sensorValue*3.3)/4096.0)*2)*0.88; } void get_thermistor(){ thermistorAverage = 0; for (int i=0; i<10; i++){ Vo = analogRead(ThermistorPin); R2 = R1 * (4096 / Vo - 1); //calculate resistance on thermistor logR2 = log(R2); T = (1.0 / (c1 + c2*logR2 + c3*logR2*logR2*logR2)) - 273.15; thermistorAverage = thermistorAverage + T; delay(10); } thermistorAverage = thermistorAverage / 10; } void get_dust(){ dustDensityAverage = 0; for (int i=0; i<5; i++){ digitalWrite(ledPower,HIGH); // power on the LED delayMicroseconds(samplingTime); voMeasured = analogRead(measurePin); // read the dust value delayMicroseconds(deltaTime); digitalWrite(ledPower,LOW); // turn the LED off delayMicroseconds(sleepTime); // 0 - 5V mapped to 0 - 1023 integer values // recover voltage calcVoltage = voMeasured * (3.3 / 4096.0); // linear eqaution taken from http://www.howmuchsnow.com/arduino/airquality/ // Chris Nafis (c) 2012 dustDensity = 170 * calcVoltage - 0.1; dustDensityAverage = dustDensityAverage + dustDensity; delay(10); } dustDensityAverage = dustDensityAverage / 5; } void send_data(){ WiFiClientSecure client; client.setInsecure(); digitalWrite(LED_BUILTIN, HIGH); String httpRequestData = ""; int httpResponseCode = 0; HTTPClient http; http.begin(client, air_quality_temp); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(temp)+"\", \"attributes\": {\"unit_of_measurement\": \"°C\",\"friendly_name\": \"Air Quality Temp\",\"icon\": \"mdi:thermometer\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_humidity); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(humidity)+"\", \"attributes\": {\"unit_of_measurement\": \"%\",\"friendly_name\": \"Air Quality Humidity\",\"icon\": \"mdi:water-percent\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_pressure); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(pressure)+"\", \"attributes\": {\"unit_of_measurement\": \"mmHg\",\"friendly_name\": \"Air Quality Pressure\",\"icon\": \"mdi:weather-tornado\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_eCO2); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(eCO2,0)+"\", \"attributes\": {\"unit_of_measurement\": \"ppm\",\"friendly_name\": \"Air Quality CO2\",\"icon\": \"mdi:air-filter\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_TVOC); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(TVOC,0)+"\", \"attributes\": {\"unit_of_measurement\": \"ppb\",\"friendly_name\": \"Air Quality TVOC\",\"icon\": \"mdi:air-filter\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); if(boot_count == 1){ http.begin(client, air_quality_NH3); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(NH3)+"\", \"attributes\": {\"unit_of_measurement\": \"V\",\"friendly_name\": \"Air Quality NH3\",\"icon\": \"mdi:current-dc\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_NO2); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(NO2)+"\", \"attributes\": {\"unit_of_measurement\": \"V\",\"friendly_name\": \"Air Quality NO2\",\"icon\": \"mdi:current-dc\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_CO); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(CO)+"\", \"attributes\": {\"unit_of_measurement\": \"V\",\"friendly_name\": \"Air Quality CO\",\"icon\": \"mdi:current-dc\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); } http.begin(client, air_quality_dust); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(dustDensityAverage)+"\", \"attributes\": {\"unit_of_measurement\": \"µg/m3\",\"friendly_name\": \"Air Quality Dust\",\"icon\": \"mdi:air-filter\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_battery_temp); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(thermistorAverage)+"\", \"attributes\": {\"unit_of_measurement\": \"°C\",\"friendly_name\": \"Air Quality Battery Temp\",\"icon\": \"mdi:thermometer\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_battery_voltage); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(battery_voltage)+"\", \"attributes\": {\"unit_of_measurement\": \"V\",\"friendly_name\": \"Air Quality Battery Voltage\",\"icon\": \"mdi:current-dc\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_solar_voltage); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(solar_voltage)+"\", \"attributes\": {\"unit_of_measurement\": \"V\",\"friendly_name\": \"Air Quality Solar Voltage\",\"icon\": \"mdi:current-dc\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_bus_voltage); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(VIN)+"\", \"attributes\": {\"unit_of_measurement\": \"V\",\"friendly_name\": \"Air Quality Bus Voltage\",\"icon\": \"mdi:current-dc\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); http.begin(client, air_quality_version); http.addHeader("Authorization", apiKey); http.addHeader("Content-Type", "application/json"); httpRequestData = "{\"state\": \""+String(own_version)+"\", \"attributes\": {\"friendly_name\": \"Air Quality Version\",\"icon\": \"mdi:atom-variant\"}}"; httpResponseCode = http.POST(httpRequestData); Serial.println(httpResponseCode); http.end(); delay(10); digitalWrite(LED_BUILTIN, LOW); return; } float mapfloat(float x, float in_min, float in_max, float out_min, float out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; }