Skip to content

v6.0 increase batterylife #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 47 additions & 40 deletions OpenCO2_Sensor.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -77,19 +76,13 @@ CRGB leds[1];
#include <Wire.h>
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.h>
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 */
Expand Down Expand Up @@ -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
}
}

Expand All @@ -348,7 +340,7 @@ void initOnce() {
if (HWSubRev < 3) {
Wire.begin(33, 34); // green, yellow
digitalWrite(LED_POWER, LOW); // LED on
FastLED.addLeds<APA102, 40, 39, RGB>(leds, 1);
FastLED.addLeds<APA102, 40, 39, BGR>(leds, 1);
} else {
Wire.begin(3, 2);
digitalWrite(LED_POWER, HIGH); // LED on
Expand All @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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) {
Expand All @@ -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();
}
}
Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -789,15 +783,13 @@ void setup() {
/* scd4x */
if (HWSubRev < 3) {
Wire.begin(33, 34); // green, yellow
FastLED.addLeds<APA102, 40, 39, RGB>(leds, 1);
FastLED.addLeds<APA102, 40, 39, BGR>(leds, 1);
} else {
Wire.begin(3, 2);
FastLED.addLeds<WS2812, LED_PIN, GRB>(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
Expand Down Expand Up @@ -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();
Expand All @@ -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) {
Expand All @@ -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();
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 0 additions & 3 deletions epd_abstraction.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ enum LEDMenuOptions {
NUM_LED_OPTIONS
};
enum DisplayMenuOptions {
UPDATE,
MAX_BATTERY,
INVERT,
TEMP_UNIT,
Expand Down Expand Up @@ -57,7 +56,6 @@ const char* EnglishLEDmenuItems[NUM_LED_OPTIONS] = {
"Exit"
};
const char* EnglishOptionsMenuItems[NUM_DISPLAY_OPTIONS] = {
"Update",
"Battery",
"Invert",
"Unit",
Expand Down Expand Up @@ -90,7 +88,6 @@ const char* GermanLEDmenuItems[NUM_LED_OPTIONS] = {
"Beenden"
};
const char* GermanOptionsMenuItems[NUM_DISPLAY_OPTIONS] = {
"Update",
"Battery",
"Invert",
"Einheit",
Expand Down
34 changes: 5 additions & 29 deletions epd_abstraction.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down