diff --git a/.github/PULL_REQUEST_TEMPLATE/dev_pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/dev_pull_request_template.md
new file mode 100644
index 00000000..7493c033
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/dev_pull_request_template.md
@@ -0,0 +1,34 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+# What does this implement/fix?
+
+
+
+## Types of changes
+
+- [ ] Bugfix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] Other
+
+**Related issue or feature (if applicable):** fixes
+
+## Test Environment
+
+- [ ] TTGO T-Display
+- [ ] TTGO T-Display Sandwich
+- [ ] ESP8266T-Display S3
+- [ ] Other (specify)
+
+## Checklist:
+ - [ ] The code change is tested and works locally.
+
+If platfomio.ini or preferences are added/changed:
+ - [ ] Add explanation
\ No newline at end of file
diff --git a/.github/workflows/release3.yml b/.github/workflows/release3.yml
index 22e27307..c83c15b4 100644
--- a/.github/workflows/release3.yml
+++ b/.github/workflows/release3.yml
@@ -1,4 +1,4 @@
-name: Release V2.3
+name: Release V2.4
# # # # # # # # # # #
# To create new release, just push a new tag with the format v* (v1.0, v2.0, v2.1, v2.2, etc)
@@ -48,11 +48,7 @@ jobs:
strategy:
matrix:
# environment: ${{ fromJSON(needs.get_default_envs.outputs.environments) }}
- environment: [esp32dev, esp32dev_OLED, TTGO_TDISPLAY, TTGO_TDISPLAY_SANDWICH, TDISPLAY_S3]
- # environment: [TDISPLAY_S3]
- # environment: [TTGO_TDISPLAY_SANDWICH]
- # environment: [esp32dev, esp32dev-sandwich]
- # environment: [esp32dev-sandwich]
+ environment: [esp32dev, esp32dev_OLED, TTGO_TDISPLAY, TTGO_TDISPLAY_SANDWICH, TDISPLAY_S3, esp32dev_ST7789_240x320]
env:
CHIP_FAMILY: ${{ matrix.environment == 'TDISPLAY_S3' && 'ESP32-S3' || 'ESP32' }}
diff --git a/CO2_Gadget.ino b/CO2_Gadget.ino
index 4a1dd17f..5b55ac34 100644
--- a/CO2_Gadget.ino
+++ b/CO2_Gadget.ino
@@ -15,6 +15,24 @@
#endif
/*****************************************************************************************************/
+// Functions and enum definitions
+void reverseButtons(bool reversed);
+void outputsLoop();
+
+// Define enum for toneBuzzerBeep
+enum ToneBuzzerBeep {
+ BUZZER_TONE_LOW = 300,
+ BUZZER_TONE_MED = 1000,
+ BUZZER_TONE_HIGH = 2000
+};
+
+// Define enum for durationBuzzerBeep
+enum DurationBuzzerBeep {
+ DURATION_BEEP_SHORT = 50,
+ DURATION_BEEP_MEDIUM = 150,
+ DURATION_BEEP_LONG = 300
+};
+
// Next data always defined to be able to configure in menu
String hostName = UNITHOSTNAME;
String rootTopic = UNITHOSTNAME;
@@ -25,7 +43,6 @@ String mqttUser = "";
String mqttPass = "";
String wifiSSID = WIFI_SSID_CREDENTIALS;
String wifiPass = WIFI_PW_CREDENTIALS;
-String mDNSName = "Unset";
String MACAddress = "Unset";
uint8_t peerESPNowAddress[] = ESPNOW_PEER_MAC_ADDRESS;
@@ -57,10 +74,18 @@ bool displayShowCO2 = true;
bool displayShowPM25 = true;
bool debugSensors = false;
bool inMenu = false;
+bool shouldWakeUpDisplay = false;
uint16_t measurementInterval = 10;
bool bleInitialized = false;
int8_t selectedCO2Sensor = -1;
bool outputsModeRelay = false;
+
+// Variables for buzzer functionality
+bool buzzerBeeping = false;
+uint16_t toneBuzzerBeep = BUZZER_TONE_MED;
+uint16_t durationBuzzerBeep = DURATION_BEEP_MEDIUM;
+int16_t timeBetweenBuzzerBeeps = -1;
+
uint8_t channelESPNow = 1;
uint16_t boardIdESPNow = 0;
uint64_t timeInitializationCompleted = 0;
@@ -129,10 +154,6 @@ uint16_t co2RedRange = 1000;
Stream& miSerialPort = Serial;
-// Functions and enum definitions
-void reverseButtons(bool reversed);
-void outputsLoop();
-
enum notificationTypes { notifyNothing,
notifyInfo,
notifyWarning,
@@ -143,9 +164,6 @@ bool displayNotification(String notificationText, String notificationText2, noti
bool displayNotification(String notificationText, String notificationText2, notificationTypes notificationType) { return true; }
bool displayNotification(String notificationText, notificationTypes notificationType) { return true; }
#endif
-#if defined(SUPPORT_OLED) || defined(SUPPORT_TFT)
-// void setDisplayBrightness(uint32_t newBrightness);
-#endif
/*****************************************************************************************************/
/********* *********/
@@ -240,6 +258,13 @@ uint16_t batteryFullyChargedMillivolts = 4200; // Voltage of battery when it is
#include "CO2_Gadget_TFT.h"
#endif
+/*****************************************************************************************************/
+/********* *********/
+/********* INCLUDE BUZZER FUNCIONALITY *********/
+/********* *********/
+/*****************************************************************************************************/
+#include "CO2_Gadget_Buzzer.h"
+
/*****************************************************************************************************/
/********* *********/
/********* INCLUDE MENU FUNCIONALITY *********/
@@ -260,6 +285,16 @@ uint16_t batteryFullyChargedMillivolts = 4200; // Voltage of battery when it is
static int64_t lastReadingsCommunicationTime = 0;
static int startCheckingAfterUs = 1900000;
+void wakeUpDisplay() {
+ if (actualDisplayBrightness == 0) {
+#if defined(SUPPORT_OLED) || defined(SUPPORT_TFT)
+ setDisplayBrightness(DisplayBrightness);
+#endif
+ lastTimeButtonPressed = millis();
+ }
+ return;
+}
+
void processPendingCommands() {
if (pendingCalibration == true) {
if (calibrationValue != 0) {
@@ -341,6 +376,7 @@ void outputsLoop() {
outputsRelays();
outputsRGBLeds();
neopixelLoop();
+ buzzerLoop();
}
void readingsLoop() {
@@ -370,6 +406,21 @@ void readingsLoop() {
void adjustBrightnessLoop() {
#if defined(SUPPORT_OLED) || defined(SUPPORT_TFT)
+ if (shouldWakeUpDisplay) {
+ wakeUpDisplay();
+ shouldWakeUpDisplay = false;
+ }
+
+ // If battery pin not connected, assume it's working on external power
+ if (battery_voltage < 1) {
+ workingOnExternalPower = true;
+ }
+
+ if (inMenu) {
+ setDisplayBrightness(DisplayBrightness);
+ return;
+ }
+
// Display backlight IS sleeping
if ((actualDisplayBrightness == 0) && (actualDisplayBrightness != DisplayBrightness)) {
if ((!displayOffOnExternalPower) && (workingOnExternalPower)) {
@@ -391,7 +442,7 @@ void adjustBrightnessLoop() {
return;
}
- if ((actualDisplayBrightness != 0) && (millis() - lastTimeButtonPressed >= timeToDisplayOff * 1000)) {
+ if ((actualDisplayBrightness != 0) && (millis() - lastTimeButtonPressed >= timeToDisplayOff * 1000) && DisplayBrightness > 0) {
Serial.println("-->[MAIN] Turning off display to save power. Actual brightness: " + String(actualDisplayBrightness));
turnOffDisplay();
}
@@ -471,6 +522,7 @@ void setup() {
initBattery();
initGPIO();
initNeopixel();
+ initBuzzer();
#if defined(SUPPORT_OLED) || defined(SUPPORT_TFT)
initDisplay();
#endif
diff --git a/CO2_Gadget_Buttons.h b/CO2_Gadget_Buttons.h
index b5014915..5acf2bfc 100644
--- a/CO2_Gadget_Buttons.h
+++ b/CO2_Gadget_Buttons.h
@@ -10,9 +10,7 @@ Button2 btnDwn(BTN_DWN); // Initialize the down button
void IRAM_ATTR buttonUpISR() {
if (actualDisplayBrightness == 0) // Turn on the display only if it's OFF
{
-#if defined(SUPPORT_OLED) || defined(SUPPORT_TFT)
- setDisplayBrightness(DisplayBrightness); // Turn on the display at DisplayBrightness brightness
-#endif
+ shouldWakeUpDisplay = true;
lastTimeButtonPressed = millis();
}
}
diff --git a/CO2_Gadget_Buzzer.h b/CO2_Gadget_Buzzer.h
new file mode 100644
index 00000000..c3a5aef6
--- /dev/null
+++ b/CO2_Gadget_Buzzer.h
@@ -0,0 +1,64 @@
+#ifndef CO2_Gadget_Buzzer_h
+#define CO2_Gadget_Buzzer_h
+
+/*****************************************************************************************************/
+/********* *********/
+/********* SETUP BUZZER *********/
+/********* *********/
+/*****************************************************************************************************/
+
+// #define BUZZER_DEBUG
+bool belowRedRange = true;
+
+void beepBuzzer() {
+ static uint16_t numberOfBeepsLeft = 2;
+ static uint64_t timeNextBeep = 0;
+ if (millis() > timeNextBeep) {
+ if (numberOfBeepsLeft == 0) {
+ if (timeBetweenBuzzerBeeps > 0) {
+ timeNextBeep = millis() + timeBetweenBuzzerBeeps * 1000;
+ } else {
+ belowRedRange = false;
+ }
+ numberOfBeepsLeft = 2;
+ buzzerBeeping = false;
+ } else {
+#ifdef BUZZER_DEBUG
+ Serial.printf("[BUZZ] Beeps left: %d. Next beep in: %d sec. Beep duration: %d ms\n", numberOfBeepsLeft, timeBetweenBuzzerBeeps, durationBuzzerBeep);
+#endif
+ tone(BUZZER_PIN, toneBuzzerBeep, durationBuzzerBeep);
+ timeNextBeep = millis() + durationBuzzerBeep + (durationBuzzerBeep * 1.3);
+ --numberOfBeepsLeft;
+ buzzerBeeping = true;
+ }
+ }
+}
+
+void buzzerLoop() {
+#ifdef SUPPORT_BUZZER
+ if (timeBetweenBuzzerBeeps == -1 || inMenu) { // Inside Menu stop BEEPING
+ buzzerBeeping = false;
+ belowRedRange = true;
+ return;
+ }
+
+ if (co2 > co2RedRange && belowRedRange) {
+ shouldWakeUpDisplay = true;
+ beepBuzzer();
+ } else if (co2 < (co2RedRange - BUZZER_HYSTERESIS))
+ belowRedRange = true;
+#endif
+}
+
+void initBuzzer() {
+#ifdef SUPPORT_BUZZER
+ Serial.printf("-->[BUZZ] Initializing Buzzer on GPIO %d...\n", BUZZER_PIN);
+ pinMode(BUZZER_PIN, OUTPUT);
+
+ // LEDC initialization
+ ledcSetup(0, 5000, 8);
+ ledcAttachPin(BUZZER_PIN, 0);
+#endif
+}
+
+#endif
diff --git a/CO2_Gadget_Menu.h b/CO2_Gadget_Menu.h
index 60d4c519..41a3af70 100644
--- a/CO2_Gadget_Menu.h
+++ b/CO2_Gadget_Menu.h
@@ -59,8 +59,8 @@ result systemReboot() {
// do some termination stuff here
if (sensorsGetMainDeviceSelected().equals("SCD30")) {
Serial.println("-->[MENU] Resetting SCD30 sensor...");
- delay(100);
sensors.scd30.reset();
+ delay(100);
}
ESP.restart();
return quit;
@@ -68,7 +68,7 @@ result systemReboot() {
// using the customized menu class
// note that first parameter is the class name
-altMENU(confirmReboot, subMenu, "Reboot?", doNothing, noEvent, wrapStyle, (Menu::_menuData | Menu::_canNav), OP("Yes", systemReboot, enterEvent), EXIT("Cancel"));
+altMENU(confirmReboot, rebootMenu, "Reboot?", doNothing, noEvent, wrapStyle, (Menu::_menuData | Menu::_canNav), OP("Yes", systemReboot, enterEvent), EXIT("Cancel"));
char tempIPAddress[16];
@@ -77,7 +77,6 @@ const char *const hexChars[] MEMMODE = {"0123456789ABCDEF"};
const char *const alphaNum[] MEMMODE = {" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,+-_"};
const char *const allChars[] MEMMODE = {" 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_!#@$%&/()=+-*^~:.[]{}?¿"};
const char *const ssidChars[] MEMMODE = {" 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_!#@%&/()=-*^~:.{}¿"};
-
const char *const reducedSet[] MEMMODE = {" 0123456789abcdefghijklmnopqrstuvwxyz.-_"};
// field will initialize its size by this string length
@@ -200,7 +199,7 @@ result doSavePreferences(eventMask e, navNode &nav, prompt &item) {
return quit;
}
-result dosetDisplayBrightness(eventMask e, navNode &nav, prompt &item) {
+result doSetDisplayBrightness(eventMask e, navNode &nav, prompt &item) {
#ifdef DEBUG_ARDUINOMENU
Serial.printf("-->[MENU] Setting TFT brightness at %d", DisplayBrightness);
Serial.print(F("-->[MENU] action1 event:"));
@@ -315,7 +314,7 @@ result doSetActiveWIFI(eventMask e, navNode &nav, prompt &item) {
} else {
initWifi();
nav.target-> dirty = true;
- activeMQTT = preferences.getBool("activeMQTT", false);
+ activeMQTT = preferences.getBool("activeMQTT", false); // TO-DO: Check if this is needed. It do not looks fine.
if ((activeMQTT) && (WiFi.isConnected())) {
initMQTT();
}
@@ -681,7 +680,7 @@ TOGGLE(displayShowPM25, activeDisplayShowPM25, "PM2.5: ", doNothing, noEvent, wr
,VALUE("Show", true, doDisplayReverse, enterEvent));
MENU(displayConfigMenu, "Display Config", doNothing, noEvent, wrapStyle
- ,FIELD(DisplayBrightness, "Brightness:", "", 10, 255, 10, 10, dosetDisplayBrightness, anyEvent, wrapStyle)
+ ,FIELD(DisplayBrightness, "Brightness:", "", 10, 255, 10, 10, doSetDisplayBrightness, anyEvent, wrapStyle)
,FIELD(timeToDisplayOff, "Time To Off:", "", 0, 900, 5, 5, doNothing, noEvent, wrapStyle)
,SUBMENU(activeDisplayOffMenuOnBattery)
,SUBMENU(activeDisplayReverse)
@@ -723,7 +722,7 @@ result doSetNeopixelBrightness(eventMask e, navNode &nav, prompt &item) {
return proceed;
}
-result doSetoutOuputsRelayMode(eventMask e, navNode &nav, prompt &item) {
+result doSetOuputsRelayMode(eventMask e, navNode &nav, prompt &item) {
#ifdef DEBUG_ARDUINOMENU
Serial.printf("-->[MENU] Setting outputsModeRelay to %d", outputsModeRelay);
Serial.print(F("-->[MENU] action1 event:"));
@@ -735,10 +734,43 @@ result doSetoutOuputsRelayMode(eventMask e, navNode &nav, prompt &item) {
}
TOGGLE(outputsModeRelay, outputsModeMenu, "GPIO Outs: ", doNothing,noEvent, wrapStyle
- ,VALUE("RGB LED", false, doSetoutOuputsRelayMode, anyEvent)
- ,VALUE("Relays", true, doSetoutOuputsRelayMode, anyEvent));
+ ,VALUE("RGB LED", false, doSetOuputsRelayMode, anyEvent)
+ ,VALUE("Relays", true, doSetOuputsRelayMode, anyEvent));
+
+
+ #ifdef SUPPORT_BUZZER
+TOGGLE(timeBetweenBuzzerBeeps, timeBetweenBuzzerBeepMenu, "Buzzer: ", doNothing, noEvent, wrapStyle
+ ,VALUE("OFF", -1, doNothing, noEvent)
+ ,VALUE("One time", 0, doNothing, noEvent)
+ ,VALUE("Every 5s", 5, doNothing, noEvent)
+ ,VALUE("Every 10s", 10, doNothing, noEvent)
+ ,VALUE("Every 15s", 15, doNothing, noEvent)
+ ,VALUE("Every 30s", 30, doNothing, noEvent)
+ ,VALUE("Every 1min", 60, doNothing, noEvent)
+ ,VALUE("Every 2min", 120, doNothing, noEvent)
+ ,VALUE("Every 5min", 300, doNothing, noEvent));
+
+TOGGLE(toneBuzzerBeep, toneBuzzerBeepMenu, "Tone: ", doNothing, noEvent, wrapStyle
+ ,VALUE("HIGH", BUZZER_TONE_HIGH, doNothing, noEvent)
+ ,VALUE("MED", BUZZER_TONE_MED, doNothing, noEvent)
+ ,VALUE("LOW", BUZZER_TONE_LOW, doNothing, noEvent));
+
+TOGGLE(durationBuzzerBeep, durationBuzzerBeepMenu, "Span: ", doNothing, noEvent, wrapStyle
+ ,VALUE("SHORT", DURATION_BEEP_SHORT, doNothing, noEvent)
+ ,VALUE("MED", DURATION_BEEP_MEDIUM, doNothing, noEvent)
+ ,VALUE("LONG", DURATION_BEEP_LONG, doNothing, noEvent));
+
+MENU(buzzerConfigMenu, "Buzzer Config", doNothing, noEvent, wrapStyle
+ ,SUBMENU(timeBetweenBuzzerBeepMenu)
+ ,SUBMENU(toneBuzzerBeepMenu)
+ ,SUBMENU(durationBuzzerBeepMenu)
+ ,EXIT(" colors[6] MEMMODE = {
#define tft_WIDTH 320
#define tft_HEIGHT 170
#endif
+#if TFT_WIDTH == 240 && TFT_HEIGHT == 320 // Display is rotated 90 degrees
+#undef tft_WIDTH
+#undef tft_HEIGHT
+#define tft_WIDTH 320
+#define tft_HEIGHT 240
+#endif
#endif
-
const panel panels[] MEMMODE = {{0, 0, tft_WIDTH / fontW, tft_HEIGHT / fontH}};
navNode *nodes[sizeof(panels) /
@@ -1002,6 +1058,7 @@ result idle(menuOut &o, idleEvent e) {
}
void menuLoop() {
+ uint16_t timeToWaitForImprov = 5; // Time to wait for Improv-WiFi to connect on startup
if (Serial.available() && Serial.peek() == 0x2A) { // 0x2A is the '*' character.
inMenu = true;
if (inMenu) {
@@ -1009,7 +1066,10 @@ void menuLoop() {
}
}
- if (millis() < timeInitializationCompleted + 5000) { // Wait 10 seconds before starting the menu to avoid issues with Improv-WiFi
+ if (millis() < timeInitializationCompleted + timeToWaitForImprov * 1000) { // Wait before starting the menu to avoid issues with Improv-WiFi
+#if defined(SUPPORT_TFT) || defined(SUPPORT_OLED)
+ displayShowValues();
+#endif
return;
}
@@ -1076,8 +1136,8 @@ void menu_init() {
temperatureConfigMenu[0].disable();
setCO2Sensor = selectedCO2Sensor;
#ifdef DEBUG_ARDUINOMENU
- Serial.printf("-->[MENU] Loaded CO2 Sensor in menu (setCO2Sensor): %d", setCO2Sensor);
- Serial.printf("-->[MENU] Loaded CO2 Sensor in menu (selectedCO2Sensor): %d", selectedCO2Sensor);
+ Serial.printf("-->[MENU] Loaded CO2 Sensor in menu (setCO2Sensor): %d\n", setCO2Sensor);
+ Serial.printf("-->[MENU] Loaded CO2 Sensor in menu (selectedCO2Sensor): %d\n", selectedCO2Sensor);
#endif
loadTempArraysWithActualValues();
@@ -1087,4 +1147,4 @@ void menu_init() {
Serial.println("");
}
-#endif // CO2_Gadget_Menu_h
\ No newline at end of file
+#endif // CO2_Gadget_Menu_h
diff --git a/CO2_Gadget_Preferences.h b/CO2_Gadget_Preferences.h
index 1458c4d7..cd04b3f2 100644
--- a/CO2_Gadget_Preferences.h
+++ b/CO2_Gadget_Preferences.h
@@ -47,7 +47,7 @@ void printPreferences() {
Serial.printf("-->[PREF] selCO2Sensor:\t #%d#\n", selectedCO2Sensor);
Serial.printf("-->[PREF] debugSensors is:\t#%s# (%d)\n", ((debugSensors) ? "Enabled" : "Disabled"), debugSensors);
Serial.printf("-->[PREF] displayReverse is:\t#%s# (%d)\n", ((displayReverse) ? "Reversed" : "Normal"), displayReverse);
- Serial.printf("-->[PREF] showFahrenheit is:\t#%s#\n", ((showFahrenheit) ? "Farenheit" : "Celsius"));
+ Serial.printf("-->[PREF] showFahrenheit is:\t#%s#\n", ((showFahrenheit) ? "Fahrenheit" : "Celsius"));
Serial.printf("-->[PREF] measInterval:\t #%d#\n", measurementInterval);
Serial.printf("-->[PREF] outModeRelay is:\t#%s#\n", ((outputsModeRelay) ? "Relay" : "RGB LED"));
Serial.printf("-->[PREF] channelESPNow:\t #%d#\n", channelESPNow);
@@ -59,6 +59,12 @@ void printPreferences() {
Serial.printf("-->[PREF] showBattery:\t #%s#\n", ((displayShowBattery) ? "Show" : "Hide"));
Serial.printf("-->[PREF] showCO2:\t #%s#\n", ((displayShowCO2) ? "Show" : "Hide"));
Serial.printf("-->[PREF] showPM25:\t #%s#\n", ((displayShowPM25) ? "Show" : "Hide"));
+
+ // Buzzer preferences
+ Serial.printf("-->[PREF] toneBuzzerBeep is:\t#%d#\n", toneBuzzerBeep);
+ Serial.printf("-->[PREF] durationBuzzerBeep is:\t#%d#\n", durationBuzzerBeep);
+ Serial.printf("-->[PREF] timeBetweenBuzzerBeeps is:\t#%d#\n", timeBetweenBuzzerBeeps);
+
Serial.printf("-->[PREF] \n");
}
@@ -145,6 +151,11 @@ void initPreferences() {
displayShowCO2 = preferences.getBool("showCO2", true);
displayShowPM25 = preferences.getBool("showPM25", true);
+ // Retrieve buzzer preferences
+ toneBuzzerBeep = preferences.getUInt("toneBzrBeep", BUZZER_TONE_MED); // Frequency of the buzzer beep
+ durationBuzzerBeep = preferences.getUInt("durBzrBeep", DURATION_BEEP_MEDIUM); // Duration of the buzzer beep
+ timeBetweenBuzzerBeeps = preferences.getUInt("timeBtwnBzr", 65535); // Time between consecutive beeps
+
rootTopic.trim();
mqttClientId.trim();
mqttBroker.trim();
@@ -154,9 +165,10 @@ void initPreferences() {
wifiPass.trim();
hostName.trim();
preferences.end();
- #ifdef DEBUG_PREFERENCES
+#define DEBUG_PREFERENCES
+#ifdef DEBUG_PREFERENCES
printPreferences();
- #endif
+#endif
}
void putPreferences() {
@@ -219,7 +231,16 @@ void putPreferences() {
preferences.putBool("showCO2", displayShowCO2);
preferences.putBool("showPM25", displayShowPM25);
+ // Buzzer preferences
+ preferences.putUInt("toneBzrBeep", toneBuzzerBeep); // Buzzer frequency
+ preferences.putUInt("durBzrBeep", durationBuzzerBeep); // Buzzer duration
+ preferences.putUInt("timeBtwnBzr", timeBetweenBuzzerBeeps); // Time between beeps
+
preferences.end();
+
+#ifdef DEBUG_PREFERENCES
+ printPreferences();
+#endif
}
String getPreferencesAsJson() {
@@ -273,6 +294,12 @@ String getPreferencesAsJson() {
doc["showCO2"] = preferences.getBool("showCO2", true);
doc["showPM25"] = preferences.getBool("showPM25", true);
doc["measInterval"] = preferences.getInt("measInterval", 10);
+
+ // Buzzer preferences
+ doc["toneBzrBeep"] = preferences.getUInt("toneBzrBeep", 1000); // Buzzer frequency
+ doc["durBzrBeep"] = preferences.getUInt("durBzrBeep", 100); // Buzzer duration
+ doc["timeBtwnBzr"] = preferences.getUInt("timeBtwnBzr", 65535); // Time between beeps
+
preferences.end();
String preferencesJson;
@@ -340,6 +367,11 @@ String getActualSettingsAsJson() {
doc["showPM25"] = displayShowPM25;
doc["measInterval"] = measurementInterval;
+ // Buzzer preferences
+ doc["toneBzrBeep"] = toneBuzzerBeep; // Buzzer frequency
+ doc["durBzrBeep"] = durationBuzzerBeep; // Buzzer duration
+ doc["timeBtwnBzr"] = timeBetweenBuzzerBeeps; // Time between beeps
+
String preferencesJson;
serializeJson(doc, preferencesJson);
// Serial.printf("-->[PREF] Preferences JSON: %s\n", preferencesJson.c_str());
@@ -429,6 +461,11 @@ bool handleSavePreferencesfromJSON(String jsonPreferences) {
displayShowCO2 = JsonDocument["showCO2"];
displayShowPM25 = JsonDocument["showPM25"];
+ // Buzzer preferences
+ toneBuzzerBeep = JsonDocument["toneBzrBeep"]; // Buzzer frequency
+ durationBuzzerBeep = JsonDocument["durBzrBeep"]; // Buzzer duration
+ timeBetweenBuzzerBeeps = JsonDocument["timeBtwnBzr"]; // Time between beeps
+
// mqttPass = JsonDocument["mqttPass"].as().c_str();
// wifiPass = JsonDocument["wifiPass"].as().c_str();
preferences.end();
diff --git a/CO2_Gadget_Sensors.h b/CO2_Gadget_Sensors.h
index 5cd1d59b..8d1dd23c 100644
--- a/CO2_Gadget_Sensors.h
+++ b/CO2_Gadget_Sensors.h
@@ -15,6 +15,7 @@ bool autoSelfCalibration = false;
float tempOffset = 0.0f;
volatile uint16_t co2 = 0;
+volatile uint16_t previousCO2Value = 0;
float temp, tempFahrenheit, hum = 0;
String mainDeviceSelected = "";
@@ -38,25 +39,18 @@ void printSensorsDetected() {
}
void onSensorDataOk() {
- if (!inMenu) {
- Serial.print("-->[SENS] CO2: " + String(sensors.getCO2()));
- Serial.print(" CO2humi: " + String(sensors.getCO2humi()));
- Serial.print(" CO2temp: " + String(sensors.getCO2temp()));
- Serial.print(" H: " + String(sensors.getHumidity()));
- Serial.println(" T: " + String(sensors.getTemperature()));
- }
-
+ previousCO2Value = co2;
co2 = sensors.getCO2();
-
hum = sensors.getHumidity();
if (hum == 0.0) hum = sensors.getCO2humi();
-
temp = sensors.getTemperature();
if (temp == 0.0) temp = sensors.getCO2temp(); // TO-DO: temp could be 0.0
-
tempFahrenheit = (temp * 1.8 + 32);
-
+ if (!inMenu) {
+ Serial.printf("-->[SENS] CO2: %d CO2temp: %.2f CO2humi: %.2f H: %.2f T: %.2f\n", co2, sensors.getCO2temp(), sensors.getCO2humi(), sensors.getHumidity(), sensors.getTemperature());
+ }
newReadingsAvailable = true;
+ // Serial.printf("-->[SENS] Free heap: %d\n", ESP.getFreeHeap());
}
void onSensorDataError(const char *msg) { Serial.println(msg); }
@@ -85,9 +79,9 @@ void initSensors() {
Wire.begin();
#endif
-Serial.println("-->[SENS] Detecting sensors..");
+ Serial.println("-->[SENS] Detecting sensors..");
-uint16_t defaultCO2MeasurementInterval = 5; // TO-DO: Move to preferences
+ uint16_t defaultCO2MeasurementInterval = 5; // TO-DO: Move to preferences
// Breaking change: https://github.com/kike-canaries/canairio_sensorlib/pull/110
// CanAirIO Sensorlib was multipliying sample time by two until rev 340 (inclusive). Adjust to avoid need for recalibration.
#ifdef CSL_REVISION // CanAirIO Sensorlib Revision > 340 (341 where CSL_REVISION was included)
@@ -104,6 +98,7 @@ uint16_t defaultCO2MeasurementInterval = 5; // TO-DO: Move to preferences
sensors.setOnErrorCallBack(&onSensorDataError); // [optional] error callback
sensors.setDebugMode(debugSensors); // [optional] debug mode
sensors.setTempOffset(tempOffset);
+ sensors.setCO2AltitudeOffset(altitudeMeters);
// sensors.setAutoSelfCalibration(false); // TO-DO: Implement in CanAirIO Sensors Lib
Serial.printf("-->[SENS] Selected CO2 Sensor: %d\n", selectedCO2Sensor);
@@ -134,7 +129,9 @@ uint16_t defaultCO2MeasurementInterval = 5; // TO-DO: Move to preferences
}
void sensorsLoop() {
- sensors.loop();
+ if (!buzzerBeeping) {
+ sensors.loop();
+ }
}
#endif // CO2_Gadget_Sensors_h
\ No newline at end of file
diff --git a/CO2_Gadget_TFT.h b/CO2_Gadget_TFT.h
index 3916e520..f5fcfaeb 100644
--- a/CO2_Gadget_TFT.h
+++ b/CO2_Gadget_TFT.h
@@ -21,9 +21,9 @@
// Load fonts for TTGO T-Display and others with 240x135 resolution
#if defined(TFT_WIDTH) && defined(TFT_HEIGHT)
#if TFT_WIDTH == 135 && TFT_HEIGHT == 240
-#include "FontNotoSansBold90ptDigits.h"
#include "FontNotoSansBold15pt_mp.h"
#include "FontNotoSansBold20.h"
+#include "FontNotoSansBold90ptDigits.h"
#define GFXFF 1
#define MINI_FONT FontNotoSansBold15pt_mp
#define SMALL_FONT FontNotoSansBold20
@@ -46,10 +46,24 @@
#endif
#endif
+// Load fonts for ST7789_240x320 and others with 320x240 resolution
+#if defined(TFT_WIDTH) && defined(TFT_HEIGHT)
+#if TFT_WIDTH == 240 && TFT_HEIGHT == 320
+#include "FontNotoSansBold120ptDigits.h"
+#include "FontNotoSansBold15pt_mp.h"
+#include "FontNotoSansBold20.h"
+#define GFXFF 1
+#define MINI_FONT FontNotoSansBold15pt_mp
+#define SMALL_FONT FontNotoSansBold20
+#define BIG_FONT FontNotoSansBold120ptDigits
+#define FONTS_LOADED
+#endif
+#endif
+
// Default fonts
#ifndef FONTS_LOADED
-#include "FontNotoSansBold90ptDigits.h"
#include "FontNotoSansBold15pt_mp.h"
+#include "FontNotoSansBold90ptDigits.h"
#include "FontNotoSansRegular20.h"
#define GFXFF 1
#define MINI_FONT FontNotoSansBold15pt_mp
@@ -72,6 +86,7 @@ struct ElementLocations {
int32_t co2X;
int32_t co2Y;
u_int16_t co2FontDigitsHeight;
+ u_int16_t pixelsToBaseline;
int32_t co2UnitsX;
int32_t co2UnitsY;
int32_t tempX;
@@ -99,8 +114,9 @@ ElementLocations elementPosition;
void setElementLocations() {
if (displayWidth == 240 && displayHeight == 135) { // TTGO T-Display and similar
elementPosition.co2X = displayWidth - 32;
- elementPosition.co2Y = displayHeight - 38;
+ elementPosition.co2Y = displayHeight - 33;
elementPosition.co2FontDigitsHeight = 70; // Digits (0..9) height for the font used (not the same as whole font height)
+ elementPosition.pixelsToBaseline = 18; // Pixels bellow baseline (p.ej "y" in "y" or "g" in "g" they draw bellow the baseline))
elementPosition.co2UnitsX = displayWidth - 33;
elementPosition.co2UnitsY = displayHeight - 50;
elementPosition.tempX = 1;
@@ -123,8 +139,9 @@ void setElementLocations() {
if (displayWidth == 320 && displayHeight == 170) { // T-Display-S3 and similar
elementPosition.co2X = displayWidth - 33;
- elementPosition.co2Y = displayHeight - 38;
- elementPosition.co2FontDigitsHeight = 100; // Digits (0..9) height for the font used (not the same as whole font height)
+ elementPosition.co2Y = displayHeight - 33;
+ elementPosition.co2FontDigitsHeight = 100;
+ elementPosition.pixelsToBaseline = 20;
elementPosition.co2UnitsX = displayWidth - 33;
elementPosition.co2UnitsY = displayHeight - 50;
elementPosition.tempX = 1;
@@ -144,6 +161,31 @@ void setElementLocations() {
elementPosition.espNowIconX = 74;
elementPosition.espNowIconY = 2;
}
+
+ if (displayWidth == 320 && displayHeight == 240) { // ST7789_240x320 and similar
+ elementPosition.co2X = displayWidth - 33;
+ elementPosition.co2Y = displayHeight - 108;
+ elementPosition.co2FontDigitsHeight = 100;
+ elementPosition.pixelsToBaseline = 20;
+ elementPosition.co2UnitsX = displayWidth - 33;
+ elementPosition.co2UnitsY = displayHeight - 130;
+ elementPosition.tempX = 1;
+ elementPosition.tempY = displayHeight - 25;
+ elementPosition.humidityX = displayWidth - 60;
+ elementPosition.humidityY = displayHeight - 25;
+ elementPosition.batteryIconX = displayWidth - 36;
+ elementPosition.batteryIconY = 4;
+ elementPosition.batteryVoltageX = displayWidth - 92;
+ elementPosition.batteryVoltageY = 2;
+ elementPosition.bleIconX = 2;
+ elementPosition.bleIconY = 2;
+ elementPosition.wifiIconX = 26;
+ elementPosition.wifiIconY = 2;
+ elementPosition.mqttIconX = 50;
+ elementPosition.mqttIconY = 2;
+ elementPosition.espNowIconX = 74;
+ elementPosition.espNowIconY = 2;
+ }
}
void setDisplayBrightness(uint16_t newBrightness) {
@@ -169,6 +211,12 @@ void setDisplayBrightness(uint16_t newBrightness) {
actualDisplayBrightness = newBrightness;
}
#endif
+#ifdef ST7789_240x320
+ if (actualDisplayBrightness != newBrightness) {
+ analogWrite(TFT_BL, newBrightness);
+ actualDisplayBrightness = newBrightness;
+ }
+#endif
}
void turnOffDisplay() {
@@ -198,6 +246,14 @@ void displaySplashScreen() {
uint16_t GadgetLogoX = 152;
uint16_t GadgetLogoY = 95;
#endif
+#if TFT_WIDTH == 240 && TFT_HEIGHT == 320
+ uint16_t eMarieteLogoX = 100;
+ uint16_t eMarieteLogoY = 150;
+ uint16_t CO2LogoX = 50;
+ uint16_t CO2LogoY = 78;
+ uint16_t GadgetLogoX = 152;
+ uint16_t GadgetLogoY = 95;
+#endif
tft.fillScreen(TFT_WHITE);
tft.setSwapBytes(true);
@@ -207,7 +263,7 @@ void displaySplashScreen() {
}
void initBacklight() {
-#ifdef TTGO_TDISPLAY
+#if defined(TTGO_TDISPLAY) || defined(ST7789_240x320)
pinMode(TFT_BL, OUTPUT);
setDisplayBrightness(DisplayBrightness);
#endif
@@ -315,6 +371,7 @@ uint16_t getBatteryColor(uint16_t battery_voltage) {
}
void showBatteryVoltage(int32_t posX, int32_t posY) {
+ if ((!displayShowBattery) || (battery_voltage < 1)) return;
String batteryVoltageString = " " + String(battery_voltage, 1) + "V ";
tft.setTextDatum(TL_DATUM);
tft.setCursor(posX, posY);
@@ -325,7 +382,7 @@ void showBatteryVoltage(int32_t posX, int32_t posY) {
}
void showBatteryIcon(int32_t posX, int32_t posY) { // For TTGO T-Display posX=tft.width() - 32, posY=4
- if (!displayShowBattery) return;
+ if ((!displayShowBattery) || (battery_voltage < 1)) return;
uint8_t batteryLevel = battery.level();
uint16_t color;
if (batteryLevel < 20) {
@@ -453,17 +510,6 @@ void showHumidity(float hum, int32_t posX, int32_t posY) {
spr.unloadFont();
}
-void OLDshowHumidity(float hum, int32_t posX, int32_t posY) {
- if (!displayShowHumidity) return;
- showHumidityIcon(posX - 20, posY - 2);
- tft.setTextColor(getHumidityColor(hum), TFT_BLACK);
- tft.setTextDatum(BR_DATUM);
- showHumidityIcon(tft.width() - 60, tft.height() - 22);
- tft.loadFont(SMALL_FONT);
- tft.drawString(String(hum, 0) + "%", posX, posY);
- tft.unloadFont();
-}
-
uint16_t getCO2Color(uint16_t co2) {
uint16_t color;
if (co2 < co2OrangeRange) {
@@ -476,7 +522,7 @@ uint16_t getCO2Color(uint16_t co2) {
return color;
}
-void showCO2(uint16_t co2, int32_t posX, int32_t posY) {
+void OLDshowCO2(uint16_t co2, int32_t posX, int32_t posY, uint16_t pixelsToBaseline) {
if (co2 > 9999) co2 = 9999;
spr.loadFont(BIG_FONT);
@@ -486,7 +532,12 @@ void showCO2(uint16_t co2, int32_t posX, int32_t posY) {
uint16_t posSpriteY = posY - height;
if (posSpriteX < 0) posSpriteX = 0;
if (posSpriteY < 0) posSpriteY = 0;
- spr.createSprite(width, height);
+ if (spr.createSprite(width, height) == nullptr) {
+ Serial.printf("-->[TFT ] Error: sprite not created, not enough free RAM! Free RAM: %d\n", ESP.getFreeHeap());
+ spr.unloadFont();
+ spr.deleteSprite();
+ return;
+ }
// spr.drawRect(0, 0, width, height, TFT_WHITE);
spr.setTextColor(getCO2Color(co2), TFT_BLACK);
spr.setTextDatum(TR_DATUM);
@@ -496,6 +547,53 @@ void showCO2(uint16_t co2, int32_t posX, int32_t posY) {
spr.deleteSprite();
}
+void showCO2(uint16_t co2, int32_t posX, int32_t posY, uint16_t pixelsToBaseline) {
+ if ((co2 == previousCO2Value) || (co2 == 0) || (co2 > 9999)) return;
+
+ spr.loadFont(BIG_FONT);
+ uint16_t digitWidth = spr.textWidth("0");
+ uint16_t height = spr.fontHeight() - pixelsToBaseline;
+ uint16_t totalWidth = digitWidth * 4; // Four digits
+ uint16_t posSpriteY = posY - height;
+ uint16_t color = getCO2Color(co2);
+ if (posSpriteY < 0) posSpriteY = 0;
+ spr.createSprite(digitWidth, height);
+ if (spr.createSprite(digitWidth, height) == nullptr) {
+ // Serial.printf("-->[TFT ] Error: sprite not created, not enough free RAM! Free RAM: %d\n", ESP.getFreeHeap());
+ spr.unloadFont();
+ spr.deleteSprite();
+ return;
+ }
+ spr.setTextColor(color, TFT_BLACK);
+ spr.setTextDatum(TR_DATUM);
+
+ // Store the last CO2 digits in an array
+ uint8_t lastCO2ValueDigits[4];
+ for (int i = 0; i < 4; ++i) {
+ lastCO2ValueDigits[i] = previousCO2Value % 10;
+ previousCO2Value /= 10;
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ uint16_t digit = co2 % 10; // Get the rightmost digit
+ co2 /= 10; // Move to the next digit
+
+ // if (digit == lastCO2ValueDigits[i]) continue; // Skip if the digit is equal to the corresponding digit of previousCO2Value
+ spr.fillSprite(TFT_BLACK);
+ if ((i == 3) && (digit == 0)) { // Don't draw leading zero and fill black
+ spr.pushSprite(posX - totalWidth + digitWidth * (3 - i), posSpriteY);
+ } else {
+ spr.drawNumber(digit, digitWidth, 0);
+ uint16_t posSpriteX = posX - totalWidth + digitWidth * (3 - i); // Calculate X position for the sprite
+ // if (posSpriteX < 0) posSpriteX = 0;
+ spr.pushSprite(posSpriteX, posSpriteY);
+ }
+ }
+
+ spr.deleteSprite(); // Clear sprite memory
+ spr.unloadFont();
+}
+
void showCO2units(int32_t posX, int32_t posY) {
spr.loadFont(MINI_FONT);
spr.setTextColor(getCO2Color(co2), TFT_BLACK);
@@ -506,7 +604,7 @@ void showCO2units(int32_t posX, int32_t posY) {
void displayShowValues() {
uint8_t currentDatum = tft.getTextDatum();
- showCO2(co2, elementPosition.co2X, elementPosition.co2Y);
+ showCO2(co2, elementPosition.co2X, elementPosition.co2Y, elementPosition.pixelsToBaseline);
showCO2units(elementPosition.co2UnitsX, elementPosition.co2UnitsY);
showTemperature(temp, elementPosition.tempX, elementPosition.tempY);
showHumidity(hum, elementPosition.humidityX, elementPosition.humidityY);
diff --git a/CO2_Gadget_WIFI.h b/CO2_Gadget_WIFI.h
index 37f7f3f4..591d91a5 100644
--- a/CO2_Gadget_WIFI.h
+++ b/CO2_Gadget_WIFI.h
@@ -446,8 +446,8 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
#endif // DEBUG_WIFI_EVENTS
}
-#ifdef SUPPORT_MDNS
void initMDNS() {
+#ifdef SUPPORT_MDNS
/*use mdns for host name resolution*/
if (!MDNS.begin(hostName.c_str())) { // http://esp32.local
Serial.println("-->[WiFi] Error setting up MDNS responder!");
@@ -455,12 +455,13 @@ void initMDNS() {
delay(100);
}
}
- Serial.print("-->[WiFi] mDNS responder started. CO2 Gadget web interface at: http://");
- Serial.print(hostName);
- Serial.println(".local");
+ // Serial.print("-->[WiFi] mDNS responder started. CO2 Gadget web interface at: http://");
+ // Serial.print(hostName);
+ // Serial.println(".local");
+ Serial.printf("-->[WiFi] mDNS responder started. CO2 Gadget web interface at: http://%s.local\n", hostName.c_str());
MDNS.addService("http", "tcp", 80);
-}
#endif
+}
void disableWiFi() {
WiFi.disconnect(true); // Disconnect from the network
@@ -537,6 +538,7 @@ void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
}
const char *PARAM_INPUT_1 = "MeasurementInterval";
+const char *PARAM_INPUT_2 = "CalibrateCO2";
void initWebServer() {
SPIFFS.begin();
@@ -563,9 +565,19 @@ void initWebServer() {
if (checkStringIsNumerical(inputString)) {
Serial.printf("-->[WiFi] Received /settings command MeasurementInterval with parameter %s\n", inputString);
measurementInterval = inputString.toInt();
+ request->send(200, "text/plain", "OK. Setting MeasurementInterval to " + inputString + ", please re-calibrate your sensor.");
+ }
+ };
+ // /settings?CalibrateCO2=400
+ if (request->hasParam(PARAM_INPUT_2)) {
+ inputString = request->getParam(PARAM_INPUT_2)->value();
+ if (checkStringIsNumerical(inputString)) {
+ Serial.printf("-->[WiFi] Received /settings command CalibrateCO2 with parameter %s\n", inputString);
+ calibrationValue = inputString.toInt();
+ pendingCalibration = true;
+ request->send(200, "text/plain", "OK. Recalibrating CO2 sensor to " + inputString);
}
};
- request->send(200, "text/plain", "OK. Setting MeasurementInterval to " + inputString + ", please re-calibrate your sensor.");
});
server.on("/getPreferences", HTTP_GET, [](AsyncWebServerRequest *request) {
@@ -581,10 +593,9 @@ void initWebServer() {
request->send(SPIFFS, "/preferences.html", String(), false, processor);
});
- server.on("/restart-esp32", HTTP_POST, [](AsyncWebServerRequest *request) {
- // Trigger a software reset
- Serial.flush();
- request->send(200, "text/plain", "ESP32 reset initiated");
+ // Trigger a software reset
+ server.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request) {
+ request->send(200, "text/plain", "ESP32 restart initiated");
delay(100);
ESP.restart();
});
@@ -631,88 +642,89 @@ boolean TimePeriodIsOver(unsigned long &startOfPeriod, unsigned long TimePeriod)
return false; // actual TimePeriod is NOT yet over
}
-unsigned long MyTestTimer = 0; // Timer-variables MUST be of type unsigned long
+bool connectToWiFi() {
+ displayNotification("Init WiFi", notifyInfo);
+ Serial.print("\n-->[WiFi] Initializing WiFi...\n");
+ WiFi.disconnect(true); // disconnect form wifi to set new wifi connection
+ delay(100);
+ WiFi.mode(WIFI_STA);
+ WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
+ WiFi.setHostname(hostName.c_str());
+ Serial.printf("-->[WiFi] Setting hostname: %s\n", hostName.c_str());
+ Serial.printf("-->[WiFi] Connecting to WiFi (SSID: %s)\n", wifiSSID.c_str());
+
+ WiFi.onEvent(WiFiStationConnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED);
+ WiFi.onEvent(WiFiStationGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);
+ WiFi.onEvent(WiFiStationDisconnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
+ WiFi.onEvent(customWiFiEventHandler);
+
+ unsigned long checkTimer = 0; // Timer-variables MUST be of type unsigned long
+ troubledWIFI = false;
+ WiFiConnectionRetries = 0;
-void initWifi() {
- if (wifiSSID == "") {
- activeWIFI = false;
- }
- if (activeWIFI) {
- wifiChanged = true;
- troubledWIFI = false;
- WiFiConnectionRetries = 0;
- displayNotification("Init WiFi", notifyInfo);
- Serial.print("-->[WiFi] Initializing WiFi...\n");
- WiFi.disconnect(true); // disconnect form wifi to set new wifi connection
- delay(500);
- WiFi.mode(WIFI_STA);
- WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
- Serial.printf("-->[WiFi] Setting hostname %s: %d\n", hostName.c_str(),
- WiFi.setHostname(hostName.c_str()));
-
- WiFi.onEvent(WiFiStationConnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED);
- WiFi.onEvent(WiFiStationGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);
- WiFi.onEvent(WiFiStationDisconnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
- WiFi.onEvent(customWiFiEventHandler);
-
- // Possible to optimize battery? (further investigation needed)
- // WiFi.setSleep(true);
- // WiFi.setSleep(WIFI_PS_NONE);
-
- String connectMessage = "-->[WiFi] Connecting to WiFi (SSID: " + String(wifiSSID) + ")\n";
- Serial.print(connectMessage);
- WiFi.begin(wifiSSID.c_str(), wifiPass.c_str());
-
- // Wait for connection
- while (WiFi.status() != WL_CONNECTED && WiFiConnectionRetries < maxWiFiConnectionRetries) {
- yield(); // very important to execute yield to make it work
- if (TimePeriodIsOver(MyTestTimer, 500)) { // once every 500 miliseconds
- Serial.print("."); // print a dot
- WiFiConnectionRetries++;
- if (WiFiConnectionRetries > maxWiFiConnectionRetries) { // after maxWiFiConnectionRetries dots
- Serial.println();
- Serial.print("not connected ");
- }
+ WiFi.begin(wifiSSID.c_str(), wifiPass.c_str());
+
+ // Wait for connection until maxWiFiConnectionRetries or WiFi is connected
+ while (WiFi.status() != WL_CONNECTED && WiFiConnectionRetries < maxWiFiConnectionRetries) {
+ if (TimePeriodIsOver(checkTimer, 500)) { // Once every 500 miliseconds
+ Serial.print(".");
+ WiFiConnectionRetries++;
+ if (WiFiConnectionRetries > maxWiFiConnectionRetries) {
+ Serial.println();
+ Serial.print("not connected ");
}
}
- if ((WiFiConnectionRetries > maxWiFiConnectionRetries) && (WiFi.status() != WL_CONNECTED)) {
- disableWiFi();
- troubledWIFI = true;
- timeTroubledWIFI = millis();
- Serial.printf(
- "-->[WiFi] Not possible to connect to WiFi after %d tries. Will try later.\n",
- WiFiConnectionRetries);
- }
- if (troubledWIFI) {
- return;
- }
+ yield();
+ }
+ if ((WiFiConnectionRetries > maxWiFiConnectionRetries) && (WiFi.status() != WL_CONNECTED)) {
+ disableWiFi();
+ troubledWIFI = true;
+ timeTroubledWIFI = millis();
+ Serial.printf("-->[WiFi] Not possible to connect to WiFi after %d tries. Will try later.\n", WiFiConnectionRetries);
+ }
+
+ if (troubledWIFI) {
+ Serial.println("");
+ return false;
+ } else {
Serial.println("");
Serial.print("-->[WiFi] MAC: ");
Serial.println(MACAddress);
Serial.print("-->[WiFi] WiFi connected - IP = ");
Serial.println(WiFi.localIP());
-#ifdef SUPPORT_MDNS
- mDNSName = WiFi.getHostname();
- initMDNS();
-#endif
- initWebServer();
+ return true;
+ }
+}
+void initOTA() {
#ifdef SUPPORT_OTA
- AsyncElegantOTA.begin(&server); // Start ElegantOTA
- Serial.println("-->[WiFi] OTA ready");
+ AsyncElegantOTA.begin(&server);
+ Serial.println("-->[WiFi] OTA ready");
#endif
+}
+
+void initWifi() {
+ if (wifiSSID == "") {
+ activeWIFI = false;
+ }
+ if (activeWIFI) {
+ wifiChanged = true;
+
+ if (!connectToWiFi()) {
+ return;
+ }
+
+ initWebServer();
+ initMDNS();
+ initOTA();
server.begin();
Serial.println("-->[WiFi] HTTP server started");
-
printWiFiStatus();
// Try to connect to MQTT broker on next loop if needed
troubledMQTT = false;
-
- } else {
- disableWiFi();
}
}
@@ -720,11 +732,18 @@ void wifiClientLoop() {
if (activeWIFI && troubledWIFI && (millis() - timeTroubledWIFI >= timeToRetryTroubledWIFI * 1000)) {
initWifi();
}
+<<<<<<< HEAD
+=======
+
+>>>>>>> f01a523dfc3c256f7d0c92319264d5aa4bf22a83
// This is a workaround until I can directly determine whether the Wi-Fi data has been changed via BLE
// Only checks for SSID changed (not password)
if ((WiFi.SSID() != wifiSSID) && (!inMenu)) {
Serial.println("-->[WiFi] Wi-Fi SSID changed. Old SSID: " + wifiSSID + ", new SSID: " + WiFi.SSID());
+ Serial.println("-->[WiFi] IP address: " + WiFi.localIP().toString());
+ Serial.println("-->[WiFi] RSSI: " + String(WiFi.RSSI()) + " dBm");
wifiSSID = WiFi.SSID();
+ activeWIFI = true;
putPreferences();
// initWifi();
wifiChanged = true;
diff --git a/README.md b/README.md
index 55d95069..3ab55a86 100644
--- a/README.md
+++ b/README.md
@@ -4,13 +4,15 @@
# CO2-Gadget
-An easy to build CO2 Monitor/Meter with cell phone App for real time visualization and charting of air quality data, datalogger, a variety of communication options (BLE, WIFI, MQTT, ESP-NOW) and many supported popular sensors.
+An advanced fimware for CO2 Monitor/Meter. It's really flexible, you can use this firmware with **any supported CO2 Monitor/Meter** based on ESP32 (99,99% of them).
-This repository is mainly addressed at developers. If you are an end user willing to build the CO2 Gadget you will find complete instructions at [my blog](https://emariete.com/en/meter-co2-display-tft-color-ttgo-t-display-sensirion-scd30-2/) including instructions in how to build the hardware and load the firmware very easily, with just two clicks in your browser (without having edit files, compile or install anything on your computer).
+With cell phone App for real time visualization and charting of air quality data, datalogger, a variety of communication options (BLE, WIFI, MQTT, ESP-NOW) and many supported popular sensors.
-
-
-
+This repository is mainly addressed at developers. If you are an end user willing to install and use the CO2 Gadget firmware, you will find complete instructions at [my blog](https://emariete.com/en/co2-meter-gadget/) including instructions in how to install the firmware very easily, with just two clicks in your browser (without having edit files, compile or install anything on your computer).
+
+If you don't have a CO2 Monitor you will also find some complete tutorials to build your own.
+
+![CO2_Gadget_DIY_CO2_Monitor](https://github.com/melkati/CO2-Gadget/assets/11509521/58e1f306-af46-416f-a399-5900965e8c10)
# Features
@@ -30,24 +32,24 @@ This repository is mainly addressed at developers. If you are an end user willin
- Easy WiFi setup via bluetooth with the MyAmbiance App in iOS and Android
- ESP-NOW communications protocol from Espressif for long range and low power consuption ([more info here](https://emariete.com/en/gateway-esp-now-mqtt/))
- Over the air updates OTA
+- Support for buzzer alarms on CO2 level
- Support for Neopixel (WS2812B) addressable LEDs (RGB, GBR and RGBW)
- Support for RGB LEDs
-- GPIO outputs for alarms and activation of air circulation on CO2 concentration threshold with hysteresis. Check GPIO to use at [my blog CO2 Gadget firmware page](https://emariete.com/medidor-co2-gadget/)
-- ~~-LoRa/LoRaWAN in study. If you are interested, please [join this conversation](https://github.com/melkati/CO2-Gadget/issues/35).~~
+- GPIO outputs for alarms and activation of air circulation on CO2 concentration threshold with hysteresis. Check GPIO to use at [my blog CO2 Gadget firmware page](https://emariete.com/en/co2-meter-gadget/)
# Supported hardware and build
This project support a large selection of ESP32 boards, displays and sensors.
-As an example you can find a very detailed tutorial with step-by-step video on how to build a very compact CO2 Gadget with a TTGO T-Display board and a high quality Sensirion SCD30 dual channel NDIR CO2 sensor and support for battery [here](https://emariete.com/en/meter-co2-display-tft-color-ttgo-t-display-sensirion-scd30-2/).
+As an example you can find a very detailed tutorial with step-by-step video on how to build a very compact CO2 Gadget with a TTGO T-Display board and a high quality Sensirion SCD30 dual channel NDIR CO2 sensor (and battery support) [here](https://emariete.com/en/meter-co2-display-tft-color-ttgo-t-display-sensirion-scd30-2/).
![image](https://user-images.githubusercontent.com/11509521/146636210-ee11a49a-5ebc-4e3c-a11e-91e2d8676410.png)
-For latest information on other hardware use (boards, sensors, displays, etc), please check options and GPIO to use at [my blog CO2 Gadget firmware page](https://emariete.com/medidor-co2-gadget/)
+For latest information on other hardware use (boards, sensors, displays, etc), please check options and GPIO to use at [my blog CO2 Gadget firmware page](https://emariete.com/en/co2-meter-gadget/)
## OLED Displays
-CO2 Gadget right now has support for many different OLED displays (by using the U8g2 library). There are precompiled versions for OLED I2C 1.3" 128x64 pixels display for real time measurements.
+CO2 Gadget right now has support for many different OLED displays. There are precompiled versions for OLED I2C 1.3" 128x64 pixels display.
![CO2 Gadget OLED MH-Z1311A](https://user-images.githubusercontent.com/11509521/154486542-703653f0-ba0c-4bca-9616-ee5c35d4d19c.jpg)
## ESP32 Boards
@@ -56,26 +58,28 @@ Supporting any other ESP32 board is very easy. Yoy just have to setup the pines
These are the GPIOs used by each predefined board:
-| Flavor | Display | RX/TX | I2C | UP/DWN | GPIO EN | GPIO Green | GPIO Orange | GPIO Red | GPIO Battery | GPIO Neopixel
-|:-----------------------|:----------------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
-| TTGO_TDISPLAY TFT | TFT 240×135 | 13/12 | 21/22 | 35/0 | 27 | 25 | 32 | 33 | 34 | 26
-| TTGO_TDISPLAY_SANDWICH | TFT 240×135 | 13/12 | 22/21 | 35/0 | 27 | 25 | 32 | 33 | 34 | 26
-| TDISPLAY_S3 | TFT 320x170 | 18/17 | 42/43 | 14/0 | -- | 02 | 03 | 01 | 04 | 16
-| esp32dev_OLED SSH1106 | SSH1106 128×64 | 17/16 | 21/22 | 15/0 | 27 | 25 | 32 | 33 | 34 | 26
-| esp32dev | No display | 17/16 | 21/22 | 15/0 | 27 | 25 | 32 | 33 | 34 | 26
-| esp32dev-sandwich | No display | 17/16 | 22/21 | 15/0 | 27 | 25 | 32 | 33 | 34 | 26
-
-- Flavor: Name of the firmware variant.
-- Display: Display supported by each flavor.
+| Flavor | Display | RX/TX | I2C | UP/DWN | GPIO EN | GPIO Green | GPIO Orange | GPIO Red | GPIO Battery | GPIO Neopixel | GPIO Buzzer
+|:-----------------------|:----------------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
+| TTGO_TDISPLAY TFT | TFT 240×135 | 13/12 | 21/22 | 35/0 | -- | 25 | 32 | 33 | 34 | 26 | 2
+| TTGO_TDISPLAY_SANDWICH | TFT 240×135 | 13/12 | 22/21 | 35/0 | -- | 25 | 32 | 33 | 34 | 26 | 2
+| TDISPLAY_S3 | TFT 320x170 | 18/17 | 42/43 | 14/0 | -- | 02 | 03 | 01 | 04 | 16 | 2
+| esp32dev_OLED SSH1106 | SSH1106 128×64 | 17/16 | 21/22 | 15/0 | -- | 25 | 32 | 33 | 34 | 26 | 2
+| esp32dev | No display | 17/16 | 21/22 | 15/0 | -- | 25 | 32 | 33 | 34 | 26 | 2
+| esp32dev-sandwich | No display | 17/16 | 22/21 | 15/0 | -- | 25 | 32 | 33 | 34 | 26 | 2
+| esp32dev-ST7789_240x320 | ST7789_240x320 | 17/16 | 21/22 | 19/0 | -- | 25 | 32 | 33 | 34 | 26 | 2
+
+- Flavour: Name of the firmware variant.
+- Display: Display supported by each flavour.
- RX / TX: Pins (GPIO) used for sensors connected by serial port.
- I2C: Pins (GPIO) corresponding to the I2C bus for connection of I2C sensors and displays.
- UP / DWN: Pins (GPIO) to which to connect the "Up" and "Down" buttons. They are optional as CO2 Gadget is fully functional with no buttons attached.
-- EN: Pin (GPIO) that supplies an ENABLE signal for switching the sensors on and off.
+- EN: Pin (GPIO) that supplies an ENABLE signal for switching the sensors on and off (reserved for future use).
- Green GPIO: Pin (GPIO) corresponding to the output before reaching the orange level (for relays, alarms, and RGB LED).
- GPIO Orange: Pin (GPIO) corresponding to the output when the orange level is reached (for relays, alarms, and RGB LED).
- GPIO Red: Pin (GPIO) corresponding to the output when the orange level is reached (for relays, alarms, and RGB LED).
- GPIO Battery: Pin for battery voltage measurement.
- Neopixel GPIO: Pin to which you must connect the data line of the Neopixel (WS2812B) LEDs.
+- Buzzer: Pin to connect a passive buzzer for CO2 level sound alarms (built in transistor recommended).
# Supported sensors
@@ -157,7 +161,7 @@ I recommend PlatformIO because it is more easy than Arduino IDE. For this, pleas
```python
pio run pio run -e TTGO_TDISPLAY_SANDWICH --target upload
```
-You must replace "TTGO_TDISPLAY_SANDWICH" with the flavor of CO2 Gadget you want compiled and uploaded (they are defined in platformio.ini or you can define your own).
+You must replace "TTGO_TDISPLAY_SANDWICH" with the flavour of CO2 Gadget you want compiled and uploaded (they are defined in platformio.ini or you can define your own).
If using PlatformIO **GUI**, to compile and upload CO2-Gadget into your board, press the "Alien head" -> Project tasks -> Choose flavour -> Upload and Monitor .
diff --git a/data/preferences.html b/data/preferences.html
index 96205728..e06a644d 100644
--- a/data/preferences.html
+++ b/data/preferences.html
@@ -20,7 +20,7 @@