Skip to content

Commit 15cf994

Browse files
committed
v6.0 increase batterylife
New default to low energy mode with a smart algorithm to only measure CO2, if the temperature changed by more than 0.5°C or the humidity by 2% or 5 minutes passed. Increase battery runtime to up to 11 weeks. Bugfix: LED color fixed on HWSubRev < 3 Also USB updates are only enabeled if the OpenCO2 Sensor is plugged in already on the first boot.
1 parent ca87e23 commit 15cf994

File tree

4 files changed

+50
-70
lines changed

4 files changed

+50
-70
lines changed

OpenCO2_Sensor.ino

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@
1010
- WiFiManager: https://github.com/tzapu/WiFiManager
1111
- ArduinoMqttClient (if MQTT is defined)
1212
*/
13-
#define VERSION "v5.9"
13+
#define VERSION "v6.0"
1414

1515
#define HEIGHT_ABOVE_SEA_LEVEL 50 // Berlin
1616
#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
1717
#define LIGHT_SLEEP_TIME 500
1818
#define DEEP_SLEEP_TIME 29124 // 30 sec
19-
#define LOW_ENERGY_DEEP_SLEEP_TIME 287924 // 5 min
2019
#define DEEP_SLEEP_TIME_NO_DISPLAY_UPDATE DEEP_SLEEP_TIME + 965 // offset for no display update
2120
static unsigned long lastMeasurementTimeMs = 0;
2221

@@ -77,19 +76,13 @@ CRGB leds[1];
7776
#include <Wire.h>
7877
SensirionI2cScd4x scd4x;
7978

80-
81-
#ifndef ARDUINO_USB_MODE
82-
#error This ESP32 SoC has no Native USB interface
83-
#elif ARDUINO_USB_MODE == 1
84-
#error This sketch should be used when USB is in OTG mode and MSC On Boot enabled
85-
#endif
8679
#include "USB.h"
87-
#include <USBMSC.h>
88-
USBMSC usbmsc;
80+
#include "FirmwareMSC.h"
81+
FirmwareMSC MSC_Update;
8982

9083
RTC_DATA_ATTR bool USB_ACTIVE = false, initDone = false, BatteryMode = false, comingFromDeepSleep = false;
91-
RTC_DATA_ATTR bool LEDonBattery, LEDonUSB, useSmoothLEDcolor, invertDisplay, useFahrenheit, useWiFi, lowEnergyMode, english, limitMaxBattery;
92-
RTC_DATA_ATTR uint8_t ledbrightness, HWSubRev, font;
84+
RTC_DATA_ATTR bool LEDonBattery, LEDonUSB, useSmoothLEDcolor, invertDisplay, useFahrenheit, useWiFi, english, limitMaxBattery;
85+
RTC_DATA_ATTR uint8_t ledbrightness, HWSubRev, font, skipMeasurement = 10;
9386
RTC_DATA_ATTR float maxBatteryVoltage;
9487

9588
/* TEST_MODE */
@@ -327,8 +320,7 @@ float getTempOffset() {
327320
if (useWiFi) return 12.2;
328321
return 4.4;
329322
} else {
330-
if (lowEnergyMode) return 0.0;
331-
return 0.8;
323+
return 0.0; // was with periodic measurment 0.8
332324
}
333325
}
334326

@@ -348,7 +340,7 @@ void initOnce() {
348340
if (HWSubRev < 3) {
349341
Wire.begin(33, 34); // green, yellow
350342
digitalWrite(LED_POWER, LOW); // LED on
351-
FastLED.addLeds<APA102, 40, 39, RGB>(leds, 1);
343+
FastLED.addLeds<APA102, 40, 39, BGR>(leds, 1);
352344
} else {
353345
Wire.begin(3, 2);
354346
digitalWrite(LED_POWER, HIGH); // LED on
@@ -370,7 +362,6 @@ void initOnce() {
370362
preferences.begin("co2-sensor", true);
371363
maxBatteryVoltage = preferences.getFloat("MBV", 3.95);
372364
useWiFi = preferences.getBool("WiFi", false);
373-
lowEnergyMode = preferences.getBool("lowEnergy", false);
374365
LEDonBattery = preferences.getBool("LEDonBattery", false);
375366
LEDonUSB = preferences.getBool("LEDonUSB", true);
376367
ledbrightness = preferences.getInt("ledbrightness", 5);
@@ -389,7 +380,7 @@ void initOnce() {
389380
scd4x.setSensorAltitude(HEIGHT_ABOVE_SEA_LEVEL);
390381
scd4x.setAutomaticSelfCalibrationEnabled(1); // Or use setAutomaticSelfCalibrationTarget if needed
391382
scd4x.setTemperatureOffset(getTempOffset());
392-
if (!(BatteryMode && lowEnergyMode)) scd4x.startPeriodicMeasurement();
383+
if (!BatteryMode) scd4x.startPeriodicMeasurement();
393384

394385
displayInit();
395386
delay(3000); // Wait for co2 measurement
@@ -594,6 +585,9 @@ void calibrate() {
594585
/* Only run this, if calibration is needed!
595586
let the Sensor run outside for 3+ minutes before.
596587
*/
588+
scd4x.stopPeriodicMeasurement();
589+
scd4x.wakeUp();
590+
scd4x.startLowPowerPeriodicMeasurement();
597591
displayCalibrationWarning();
598592
delay(500);
599593
for (int i = 0; i < 180; i++) {
@@ -789,15 +783,13 @@ void setup() {
789783
/* scd4x */
790784
if (HWSubRev < 3) {
791785
Wire.begin(33, 34); // green, yellow
792-
FastLED.addLeds<APA102, 40, 39, RGB>(leds, 1);
786+
FastLED.addLeds<APA102, 40, 39, BGR>(leds, 1);
793787
} else {
794788
Wire.begin(3, 2);
795789
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, 1);
796790
}
797791
scd4x.begin(Wire, 0x62); // 0x62 is the default I2C address for SCD4x
798792

799-
USB.onEvent(usbEventCallback);
800-
usbmsc.isWritable(true);
801793
if (!initDone) initOnce();
802794

803795
#if ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE
@@ -826,11 +818,17 @@ void setup() {
826818
}
827819

828820
if (useWiFi && !BatteryMode) startWiFi();
821+
822+
if (!BatteryMode && !comingFromDeepSleep) {
823+
USB.onEvent(usbEventCallback);
824+
MSC_Update.begin();
825+
USB.begin();
826+
}
829827
}
830828

831829

832830
void loop() {
833-
if ((!useWiFi || (lowEnergyMode && BatteryMode)) && esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_GPIO) handleButtonPress();
831+
if ((!useWiFi || BatteryMode) && esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_GPIO) handleButtonPress();
834832
updateBatteryMode(); // check again in USB Power mode
835833
updateCharging();
836834
measureESP32temperature();
@@ -856,7 +854,7 @@ void loop() {
856854
goto_light_sleep(5000 - (millis() - lastMeasurementTimeMs));
857855
}
858856

859-
if (!(BatteryMode && lowEnergyMode) && !TEST_MODE) {
857+
if (!BatteryMode && !TEST_MODE) {
860858
bool isDataReady = false;
861859
uint16_t ready_error = scd4x.getDataReadyStatus(isDataReady);
862860
if (ready_error || !isDataReady) {
@@ -870,11 +868,27 @@ void loop() {
870868
uint16_t new_co2 = 420;
871869
float new_temperature = 0.0f;
872870
uint16_t error;
873-
if (BatteryMode && lowEnergyMode) {
874-
scd4x.stopPeriodicMeasurement();
871+
if (BatteryMode) {
872+
if (!comingFromDeepSleep) scd4x.stopPeriodicMeasurement();
875873
scd4x.wakeUp();
876874
scd4x.setTemperatureOffset(getTempOffset());
877-
//delay(10);
875+
876+
/* check if temp/humidity changed */
877+
scd4x.measureSingleShotRhtOnly();
878+
uint16_t dummyCo2; // CO2 output is returned as 0 ppm
879+
float new_humidity = 0.0f;
880+
error = scd4x.readMeasurement(dummyCo2, new_temperature, new_humidity);
881+
if (!error
882+
&& (skipMeasurement > 1)
883+
&& (fabs(new_temperature - temperature) < 0.5)
884+
&& (fabs(new_humidity - humidity) < 2.0)) {
885+
scd4x.powerDown();
886+
skipMeasurement--;
887+
saveMeasurement(co2, new_temperature, new_humidity);
888+
goto_deep_sleep(DEEP_SLEEP_TIME_NO_DISPLAY_UPDATE);
889+
}
890+
skipMeasurement = 10; // force update every 5 minutes
891+
878892
if (HWSubRev < 3) scd4x.measureSingleShot(); // Ignore first measurement after wake up.
879893
error = scd4x.measureAndReadSingleShot(new_co2, new_temperature, humidity);
880894
scd4x.powerDown();
@@ -895,19 +909,12 @@ void loop() {
895909
extern uint16_t refreshes;
896910
if (BatteryMode || (refreshes % 6 == 1)) {
897911
saveMeasurement(new_co2, new_temperature, humidity);
898-
899-
if (BatteryMode && lowEnergyMode) { // fill measurements of past 5 min
900-
for (int i=0; i<9; i++) {
901-
saveMeasurement(new_co2, new_temperature, humidity);
902-
}
903-
}
904912
}
905913

906914
/* don't update in Battery mode, unless CO2 has changed by 4% or temperature by 0.5°C */
907915
if (!TEST_MODE && BatteryMode && comingFromDeepSleep) {
908916
if ((abs(new_co2 - co2) < (0.04 * co2)) && (fabs(new_temperature - temperature) < 0.5)) {
909-
if (lowEnergyMode) goto_deep_sleep(LOW_ENERGY_DEEP_SLEEP_TIME);
910-
else goto_deep_sleep(DEEP_SLEEP_TIME_NO_DISPLAY_UPDATE);
917+
goto_deep_sleep(DEEP_SLEEP_TIME_NO_DISPLAY_UPDATE);
911918
}
912919
}
913920

@@ -957,11 +964,11 @@ void loop() {
957964
if (BatteryMode) {
958965
if (!comingFromDeepSleep) {
959966
scd4x.stopPeriodicMeasurement();
960-
scd4x.setTemperatureOffset(getTempOffset());
961-
if (!lowEnergyMode) scd4x.startLowPowerPeriodicMeasurement();
967+
MSC_Update.end();
968+
USB_ACTIVE = false;
969+
delay(100);
962970
}
963-
if (lowEnergyMode) goto_deep_sleep(LOW_ENERGY_DEEP_SLEEP_TIME);
964-
else goto_deep_sleep(DEEP_SLEEP_TIME);
971+
goto_deep_sleep(DEEP_SLEEP_TIME);
965972
}
966973

967974
goto_light_sleep(LIGHT_SLEEP_TIME);

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ OpenCO2 Sensor is an Arduino IDE compatible Repository for an E-Ink Indoor air q
88

99
## Buy it [here on Tindie](https://www.tindie.com/products/davidkreidler/open-co2-sensor/)
1010

11-
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.
11+
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.
1212

1313
# CO2 Sensor
1414

epd_abstraction.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ enum LEDMenuOptions {
2222
NUM_LED_OPTIONS
2323
};
2424
enum DisplayMenuOptions {
25-
UPDATE,
2625
MAX_BATTERY,
2726
INVERT,
2827
TEMP_UNIT,
@@ -57,7 +56,6 @@ const char* EnglishLEDmenuItems[NUM_LED_OPTIONS] = {
5756
"Exit"
5857
};
5958
const char* EnglishOptionsMenuItems[NUM_DISPLAY_OPTIONS] = {
60-
"Update",
6159
"Battery",
6260
"Invert",
6361
"Unit",
@@ -90,7 +88,6 @@ const char* GermanLEDmenuItems[NUM_LED_OPTIONS] = {
9088
"Beenden"
9189
};
9290
const char* GermanOptionsMenuItems[NUM_DISPLAY_OPTIONS] = {
93-
"Update",
9491
"Battery",
9592
"Invert",
9693
"Einheit",

epd_abstraction.ino

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -210,18 +210,6 @@ void OptionsMenu() {
210210
}
211211
if (mspressed > 1000) { // long press
212212
switch (selectedOption) {
213-
case UPDATE:
214-
lowEnergyMode = !lowEnergyMode;
215-
preferences.begin("co2-sensor", false);
216-
preferences.putBool("lowEnergy", lowEnergyMode);
217-
preferences.end();
218-
219-
if (BatteryMode) {
220-
scd4x.stopPeriodicMeasurement();
221-
scd4x.setTemperatureOffset(getTempOffset());
222-
if (!lowEnergyMode) scd4x.startPeriodicMeasurement();
223-
}
224-
break;
225213
case MAX_BATTERY:
226214
toggleMaxBattery();
227215
break;
@@ -806,15 +794,13 @@ void displayOptionsMenu(uint8_t selectedOption) {
806794
else OptionsMenuItem = GermanOptionsMenuItems[i];
807795
Paint_DrawString_EN(5, 25*(i+1), OptionsMenuItem, &Font24, WHITE, BLACK);
808796
}
809-
if (lowEnergyMode) Paint_DrawString_EN(200-17*4, 25, "5min", &Font24, WHITE, BLACK);
810-
else Paint_DrawString_EN(200-17*5, 25, "30sec", &Font24, WHITE, BLACK);
811797

812-
if (limitMaxBattery && HWSubRev > 2) Paint_DrawString_EN(200-17*4, 25*2, "~80%", &Font24, WHITE, BLACK);
813-
else Paint_DrawString_EN(200-17*4, 25*2, "100%", &Font24, WHITE, BLACK);
798+
if (limitMaxBattery && HWSubRev > 2) Paint_DrawString_EN(200-17*4, 25, "~80%", &Font24, WHITE, BLACK);
799+
else Paint_DrawString_EN(200-17*4, 25, "100%", &Font24, WHITE, BLACK);
814800

815-
Paint_DrawString_EN(166, 25*4, (useFahrenheit? "*F":"*C"), &Font24, WHITE, BLACK);
816-
Paint_DrawNum(149, 25*6, (int32_t)(font+1), &Font24, BLACK, WHITE);
817-
Paint_DrawString_EN(166, 25*6, "/2", &Font24, WHITE, BLACK);
801+
Paint_DrawString_EN(166, 25*3, (useFahrenheit? "*F":"*C"), &Font24, WHITE, BLACK);
802+
Paint_DrawNum(149, 25*5, (int32_t)(font+1), &Font24, BLACK, WHITE);
803+
Paint_DrawString_EN(166, 25*5, "/2", &Font24, WHITE, BLACK);
818804

819805
invertSelected(selectedOption);
820806
updateDisplay();
@@ -1252,7 +1238,6 @@ void displayinfo() {
12521238
delay(10);
12531239
scd4x.getTemperatureOffset(tOffset);
12541240
if (!BatteryMode) scd4x.startPeriodicMeasurement();
1255-
else if (!lowEnergyMode) scd4x.startLowPowerPeriodicMeasurement();
12561241
Paint_DrawString_EN(1, 145, "T_offset:", &Font16, WHITE, BLACK);
12571242
char offset[20];
12581243
snprintf(offset, sizeof(offset), "%.2f", tOffset);
@@ -1336,15 +1321,6 @@ void displayBattery(uint8_t percentage) {
13361321
BlackImage[y+x*25] = ~BlackImage[y+x*25];
13371322
}
13381323
}
1339-
1340-
/* low Energy Mode */
1341-
if (lowEnergyMode) {
1342-
Paint_DrawRectangle(97, 13, 115, 47, BLACK, DOT_PIXEL_2X2, DRAW_FILL_EMPTY); //case
1343-
Paint_DrawLine(103, 10, 109, 10, BLACK, DOT_PIXEL_3X3, LINE_STYLE_SOLID);//nippel
1344-
Paint_DrawLine(106, 25, 106, 35, BLACK, DOT_PIXEL_2X2, LINE_STYLE_SOLID);//+
1345-
Paint_DrawLine(102, 30, 110, 30, BLACK, DOT_PIXEL_2X2, LINE_STYLE_SOLID);//+
1346-
//Xstart, Ystart, Xend, Yend
1347-
}
13481324
#endif /* EINK_1IN54V2 */
13491325

13501326
#ifdef EINK_4IN2

0 commit comments

Comments
 (0)