diff --git a/OpenCO2_Sensor.ino b/OpenCO2_Sensor.ino index d3825ee..d7cc522 100644 --- a/OpenCO2_Sensor.ino +++ b/OpenCO2_Sensor.ino @@ -10,13 +10,12 @@ - WiFiManager: https://github.com/tzapu/WiFiManager - ArduinoMqttClient (if MQTT is defined) */ -#define VERSION "v5.9" +#define VERSION "v6.0" #define HEIGHT_ABOVE_SEA_LEVEL 50 // Berlin #define TZ_DATA "CET-1CEST,M3.5.0,M10.5.0/3" // Europe/Berlin time zone from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv #define LIGHT_SLEEP_TIME 500 #define DEEP_SLEEP_TIME 29124 // 30 sec -#define LOW_ENERGY_DEEP_SLEEP_TIME 287924 // 5 min #define DEEP_SLEEP_TIME_NO_DISPLAY_UPDATE DEEP_SLEEP_TIME + 965 // offset for no display update static unsigned long lastMeasurementTimeMs = 0; @@ -77,19 +76,13 @@ CRGB leds[1]; #include SensirionI2cScd4x scd4x; - -#ifndef ARDUINO_USB_MODE -#error This ESP32 SoC has no Native USB interface -#elif ARDUINO_USB_MODE == 1 -#error This sketch should be used when USB is in OTG mode and MSC On Boot enabled -#endif #include "USB.h" -#include -USBMSC usbmsc; +#include "FirmwareMSC.h" +FirmwareMSC MSC_Update; RTC_DATA_ATTR bool USB_ACTIVE = false, initDone = false, BatteryMode = false, comingFromDeepSleep = false; -RTC_DATA_ATTR bool LEDonBattery, LEDonUSB, useSmoothLEDcolor, invertDisplay, useFahrenheit, useWiFi, lowEnergyMode, english, limitMaxBattery; -RTC_DATA_ATTR uint8_t ledbrightness, HWSubRev, font; +RTC_DATA_ATTR bool LEDonBattery, LEDonUSB, useSmoothLEDcolor, invertDisplay, useFahrenheit, useWiFi, english, limitMaxBattery; +RTC_DATA_ATTR uint8_t ledbrightness, HWSubRev, font, skipMeasurement = 10; RTC_DATA_ATTR float maxBatteryVoltage; /* TEST_MODE */ @@ -327,8 +320,7 @@ float getTempOffset() { if (useWiFi) return 12.2; return 4.4; } else { - if (lowEnergyMode) return 0.0; - return 0.8; + return 0.0; // was with periodic measurment 0.8 } } @@ -348,7 +340,7 @@ void initOnce() { if (HWSubRev < 3) { Wire.begin(33, 34); // green, yellow digitalWrite(LED_POWER, LOW); // LED on - FastLED.addLeds(leds, 1); + FastLED.addLeds(leds, 1); } else { Wire.begin(3, 2); digitalWrite(LED_POWER, HIGH); // LED on @@ -370,7 +362,6 @@ void initOnce() { preferences.begin("co2-sensor", true); maxBatteryVoltage = preferences.getFloat("MBV", 3.95); useWiFi = preferences.getBool("WiFi", false); - lowEnergyMode = preferences.getBool("lowEnergy", false); LEDonBattery = preferences.getBool("LEDonBattery", false); LEDonUSB = preferences.getBool("LEDonUSB", true); ledbrightness = preferences.getInt("ledbrightness", 5); @@ -389,7 +380,7 @@ void initOnce() { scd4x.setSensorAltitude(HEIGHT_ABOVE_SEA_LEVEL); scd4x.setAutomaticSelfCalibrationEnabled(1); // Or use setAutomaticSelfCalibrationTarget if needed scd4x.setTemperatureOffset(getTempOffset()); - if (!(BatteryMode && lowEnergyMode)) scd4x.startPeriodicMeasurement(); + if (!BatteryMode) scd4x.startPeriodicMeasurement(); displayInit(); delay(3000); // Wait for co2 measurement @@ -497,7 +488,7 @@ void goto_deep_sleep(int ms) { esp_deep_sleep_start(); } -static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { +static void usbEventCallback(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == ARDUINO_USB_EVENTS) { switch (event_id) { case ARDUINO_USB_STARTED_EVENT: @@ -521,7 +512,7 @@ static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t eve void goto_light_sleep(int ms) { comingFromDeepSleep = false; - + if (useWiFi || TEST_MODE || USB_ACTIVE) { for (int i = 0; i < (ms / 100); i++) { if (digitalRead(BUTTON) == 0) { @@ -534,7 +525,7 @@ void goto_light_sleep(int ms) { gpio_wakeup_enable(BUTTON, GPIO_INTR_LOW_LEVEL); esp_sleep_enable_gpio_wakeup(); - esp_sleep_enable_timer_wakeup(ms * 1000); // periodic measurement every 5 sec -1.1 sec awake + esp_sleep_enable_timer_wakeup(ms * 1000); // periodic measurement every 5 sec -1.1 sec awake esp_light_sleep_start(); } } @@ -594,6 +585,9 @@ void calibrate() { /* Only run this, if calibration is needed! let the Sensor run outside for 3+ minutes before. */ + scd4x.stopPeriodicMeasurement(); + scd4x.wakeUp(); + scd4x.startLowPowerPeriodicMeasurement(); displayCalibrationWarning(); delay(500); for (int i = 0; i < 180; i++) { @@ -789,15 +783,13 @@ void setup() { /* scd4x */ if (HWSubRev < 3) { Wire.begin(33, 34); // green, yellow - FastLED.addLeds(leds, 1); + FastLED.addLeds(leds, 1); } else { Wire.begin(3, 2); FastLED.addLeds(leds, 1); } scd4x.begin(Wire, 0x62); // 0x62 is the default I2C address for SCD4x - USB.onEvent(usbEventCallback); - usbmsc.isWritable(true); if (!initDone) initOnce(); #if ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE @@ -826,11 +818,17 @@ void setup() { } if (useWiFi && !BatteryMode) startWiFi(); + + if (!BatteryMode && !comingFromDeepSleep) { + USB.onEvent(usbEventCallback); + MSC_Update.begin(); + USB.begin(); + } } void loop() { - if ((!useWiFi || (lowEnergyMode && BatteryMode)) && esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_GPIO) handleButtonPress(); + if ((!useWiFi || BatteryMode) && esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_GPIO) handleButtonPress(); updateBatteryMode(); // check again in USB Power mode updateCharging(); measureESP32temperature(); @@ -856,7 +854,7 @@ void loop() { goto_light_sleep(5000 - (millis() - lastMeasurementTimeMs)); } - if (!(BatteryMode && lowEnergyMode) && !TEST_MODE) { + if (!BatteryMode && !TEST_MODE) { bool isDataReady = false; uint16_t ready_error = scd4x.getDataReadyStatus(isDataReady); if (ready_error || !isDataReady) { @@ -870,11 +868,27 @@ void loop() { uint16_t new_co2 = 420; float new_temperature = 0.0f; uint16_t error; - if (BatteryMode && lowEnergyMode) { - scd4x.stopPeriodicMeasurement(); + if (BatteryMode) { + if (!comingFromDeepSleep) scd4x.stopPeriodicMeasurement(); scd4x.wakeUp(); scd4x.setTemperatureOffset(getTempOffset()); - //delay(10); + + /* check if temp/humidity changed */ + scd4x.measureSingleShotRhtOnly(); + uint16_t dummyCo2; // CO2 output is returned as 0 ppm + float new_humidity = 0.0f; + error = scd4x.readMeasurement(dummyCo2, new_temperature, new_humidity); + if (!error + && (skipMeasurement > 1) + && (fabs(new_temperature - temperature) < 0.5) + && (fabs(new_humidity - humidity) < 2.0)) { + scd4x.powerDown(); + skipMeasurement--; + saveMeasurement(co2, new_temperature, new_humidity); + goto_deep_sleep(DEEP_SLEEP_TIME_NO_DISPLAY_UPDATE); + } + skipMeasurement = 10; // force update every 5 minutes + if (HWSubRev < 3) scd4x.measureSingleShot(); // Ignore first measurement after wake up. error = scd4x.measureAndReadSingleShot(new_co2, new_temperature, humidity); scd4x.powerDown(); @@ -895,19 +909,12 @@ void loop() { extern uint16_t refreshes; if (BatteryMode || (refreshes % 6 == 1)) { saveMeasurement(new_co2, new_temperature, humidity); - - if (BatteryMode && lowEnergyMode) { // fill measurements of past 5 min - for (int i=0; i<9; i++) { - saveMeasurement(new_co2, new_temperature, humidity); - } - } } /* don't update in Battery mode, unless CO2 has changed by 4% or temperature by 0.5°C */ if (!TEST_MODE && BatteryMode && comingFromDeepSleep) { if ((abs(new_co2 - co2) < (0.04 * co2)) && (fabs(new_temperature - temperature) < 0.5)) { - if (lowEnergyMode) goto_deep_sleep(LOW_ENERGY_DEEP_SLEEP_TIME); - else goto_deep_sleep(DEEP_SLEEP_TIME_NO_DISPLAY_UPDATE); + goto_deep_sleep(DEEP_SLEEP_TIME_NO_DISPLAY_UPDATE); } } @@ -957,11 +964,11 @@ void loop() { if (BatteryMode) { if (!comingFromDeepSleep) { scd4x.stopPeriodicMeasurement(); - scd4x.setTemperatureOffset(getTempOffset()); - if (!lowEnergyMode) scd4x.startLowPowerPeriodicMeasurement(); + MSC_Update.end(); + USB_ACTIVE = false; + delay(100); } - if (lowEnergyMode) goto_deep_sleep(LOW_ENERGY_DEEP_SLEEP_TIME); - else goto_deep_sleep(DEEP_SLEEP_TIME); + goto_deep_sleep(DEEP_SLEEP_TIME); } goto_light_sleep(LIGHT_SLEEP_TIME); diff --git a/README.md b/README.md index fa41dc9..c758e96 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ OpenCO2 Sensor is an Arduino IDE compatible Repository for an E-Ink Indoor air q ## Buy it [here on Tindie](https://www.tindie.com/products/davidkreidler/open-co2-sensor/) -Especially in winter, when windows are closed, a reminder to ventilate regularly is useful for health, comfort and well-being. Poor indoor air quality can lead to decreased productivity and learning disabilities. Therefore, I developed an Open-source ESP32 project that uses an E-Ink display and a LED to show the indoor CO2 content. Take the small Sensor anywhere you go to monitor the Air Quality, with Battery life of 11+ days. +Especially in winter, when windows are closed, a reminder to ventilate regularly is useful for health, comfort and well-being. Poor indoor air quality can lead to decreased productivity and learning disabilities. Therefore, I developed an Open-source ESP32 project that uses an E-Ink display and a LED to show the indoor CO2 content. Take the small Sensor anywhere you go to monitor the Air Quality, with Battery life of up to 11 weeks. # CO2 Sensor diff --git a/epd_abstraction.h b/epd_abstraction.h index cb38234..be0aa5f 100644 --- a/epd_abstraction.h +++ b/epd_abstraction.h @@ -22,7 +22,6 @@ enum LEDMenuOptions { NUM_LED_OPTIONS }; enum DisplayMenuOptions { - UPDATE, MAX_BATTERY, INVERT, TEMP_UNIT, @@ -57,7 +56,6 @@ const char* EnglishLEDmenuItems[NUM_LED_OPTIONS] = { "Exit" }; const char* EnglishOptionsMenuItems[NUM_DISPLAY_OPTIONS] = { - "Update", "Battery", "Invert", "Unit", @@ -90,7 +88,6 @@ const char* GermanLEDmenuItems[NUM_LED_OPTIONS] = { "Beenden" }; const char* GermanOptionsMenuItems[NUM_DISPLAY_OPTIONS] = { - "Update", "Battery", "Invert", "Einheit", diff --git a/epd_abstraction.ino b/epd_abstraction.ino index 0f1a89f..80053d2 100644 --- a/epd_abstraction.ino +++ b/epd_abstraction.ino @@ -210,18 +210,6 @@ void OptionsMenu() { } if (mspressed > 1000) { // long press switch (selectedOption) { - case UPDATE: - lowEnergyMode = !lowEnergyMode; - preferences.begin("co2-sensor", false); - preferences.putBool("lowEnergy", lowEnergyMode); - preferences.end(); - - if (BatteryMode) { - scd4x.stopPeriodicMeasurement(); - scd4x.setTemperatureOffset(getTempOffset()); - if (!lowEnergyMode) scd4x.startPeriodicMeasurement(); - } - break; case MAX_BATTERY: toggleMaxBattery(); break; @@ -806,15 +794,13 @@ void displayOptionsMenu(uint8_t selectedOption) { else OptionsMenuItem = GermanOptionsMenuItems[i]; Paint_DrawString_EN(5, 25*(i+1), OptionsMenuItem, &Font24, WHITE, BLACK); } - if (lowEnergyMode) Paint_DrawString_EN(200-17*4, 25, "5min", &Font24, WHITE, BLACK); - else Paint_DrawString_EN(200-17*5, 25, "30sec", &Font24, WHITE, BLACK); - if (limitMaxBattery && HWSubRev > 2) Paint_DrawString_EN(200-17*4, 25*2, "~80%", &Font24, WHITE, BLACK); - else Paint_DrawString_EN(200-17*4, 25*2, "100%", &Font24, WHITE, BLACK); + if (limitMaxBattery && HWSubRev > 2) Paint_DrawString_EN(200-17*4, 25, "~80%", &Font24, WHITE, BLACK); + else Paint_DrawString_EN(200-17*4, 25, "100%", &Font24, WHITE, BLACK); - Paint_DrawString_EN(166, 25*4, (useFahrenheit? "*F":"*C"), &Font24, WHITE, BLACK); - Paint_DrawNum(149, 25*6, (int32_t)(font+1), &Font24, BLACK, WHITE); - Paint_DrawString_EN(166, 25*6, "/2", &Font24, WHITE, BLACK); + Paint_DrawString_EN(166, 25*3, (useFahrenheit? "*F":"*C"), &Font24, WHITE, BLACK); + Paint_DrawNum(149, 25*5, (int32_t)(font+1), &Font24, BLACK, WHITE); + Paint_DrawString_EN(166, 25*5, "/2", &Font24, WHITE, BLACK); invertSelected(selectedOption); updateDisplay(); @@ -1252,7 +1238,6 @@ void displayinfo() { delay(10); scd4x.getTemperatureOffset(tOffset); if (!BatteryMode) scd4x.startPeriodicMeasurement(); - else if (!lowEnergyMode) scd4x.startLowPowerPeriodicMeasurement(); Paint_DrawString_EN(1, 145, "T_offset:", &Font16, WHITE, BLACK); char offset[20]; snprintf(offset, sizeof(offset), "%.2f", tOffset); @@ -1336,15 +1321,6 @@ void displayBattery(uint8_t percentage) { BlackImage[y+x*25] = ~BlackImage[y+x*25]; } } - - /* low Energy Mode */ - if (lowEnergyMode) { - Paint_DrawRectangle(97, 13, 115, 47, BLACK, DOT_PIXEL_2X2, DRAW_FILL_EMPTY); //case - Paint_DrawLine(103, 10, 109, 10, BLACK, DOT_PIXEL_3X3, LINE_STYLE_SOLID);//nippel - Paint_DrawLine(106, 25, 106, 35, BLACK, DOT_PIXEL_2X2, LINE_STYLE_SOLID);//+ - Paint_DrawLine(102, 30, 110, 30, BLACK, DOT_PIXEL_2X2, LINE_STYLE_SOLID);//+ - //Xstart, Ystart, Xend, Yend - } #endif /* EINK_1IN54V2 */ #ifdef EINK_4IN2