diff --git a/Firmware_ESP8266/EEPROM_Utils.h b/Firmware_ESP8266/EEPROM_Utils.h index 2bc08d8..bcec943 100644 --- a/Firmware_ESP8266/EEPROM_Utils.h +++ b/Firmware_ESP8266/EEPROM_Utils.h @@ -12,9 +12,8 @@ static bool EEPROM_Scheduled_Write_Enabled = false; struct WifiSettings { char ssid_sta[64]; char password_sta[64]; - char ssid_ap[64]; - char password_ap[64]; - char hostname[64]; + bool wifi_enabled; + bool ota_enabled; }; struct OpenWeatherMapSettings { @@ -135,16 +134,15 @@ void EEPROM_Write_Default(Settings* data) { } Settings defaultSettings = { 0 }; - strcpy(defaultSettings.wifiSettings.hostname, DEFAULT_HOSTNAME); strcpy(defaultSettings.wifiSettings.ssid_sta, DEFAULT_STA_SSID); strcpy(defaultSettings.wifiSettings.password_sta, DEFAULT_STA_PASSWORD); - strcpy(defaultSettings.wifiSettings.ssid_ap, DEFAULT_AP_SSID_AND_PASSWORD); - strcpy(defaultSettings.wifiSettings.password_ap, DEFAULT_AP_SSID_AND_PASSWORD); + defaultSettings.wifiSettings.wifi_enabled = DEFAULT_WIFI_ENABLED; + defaultSettings.wifiSettings.ota_enabled = DEFAULT_WIFI_OTA_ENABLED; strcpy(defaultSettings.openWeatherMapSettings.appid, DEFAULT_OPENWEATHERMAP_APPID); defaultSettings.openWeatherMapSettings.lat = DEFAULT_OPENWEATHERMAP_LOCATION_LAT; defaultSettings.openWeatherMapSettings.lon = DEFAULT_OPENWEATHERMAP_LOCATION_LON; defaultSettings.buzzerSettings.general_volume = DEFAULT_BUZZER_VOLUME; - defaultSettings.buzzerSettings.isEnabled = false; + defaultSettings.buzzerSettings.isEnabled = DEFAULT_BUZZER_ENABLED; EEPROM_Write(&defaultSettings); *data = defaultSettings; } diff --git a/Firmware_ESP8266/ESP8266_Utils.h b/Firmware_ESP8266/ESP8266_Utils.h index 2c960e4..13d495e 100644 --- a/Firmware_ESP8266/ESP8266_Utils.h +++ b/Firmware_ESP8266/ESP8266_Utils.h @@ -6,21 +6,29 @@ #include "basic_defines.h" #include "persistentVars.h" -#define HTTP_SERVER_PING_ADDRESS "1.1.1.1" -#define HTTP_SERVER_PING_INTERVAL_MS (10000) - #define WIFI_CONNECTION_TIMEOUT_MS (10000) +#define WIFI_CONNECTION_INTERVAL_MS (60 * 60 * 1000) // 60 min +#define WIFI_CONNECTION_MAX_CONSECUTIVE_ATTEMPTS (5) + +#define WEATHER_UPDATE_INTERVAL_MS (30 * 60 * 1000) // 30 min +#define WEATHER_UPDATE_MAX_CONSECUTIVE_ATTEMPTS (10) +#define WEATHER_SERVER_REQUEST_TIMEOUT (2000) + #define WIFI_SCAN_NOT_SEEN_MAX_COUNT (5) #define WIFI_SCAN_MINIMUM_RSSI_FOR_TRACKING (-80) #define WIFI_SCAN_MAX_TRACKED_NETWORKS_COUNT (10) -#define TEMPERATURE_DEGREE_INVALID (65535) +#define TEMPERATURE_DEGREE_INVALID (0xFFFF) -extern struct Settings settings; +enum WifiErrorType { + WIFI_ERROR_NONE = (0 << 0), + WIFI_ERROR_CONNECTION_FAILED_MASK = (1 << 0), + WIFI_ERROR_WEATHER_UPDATE_FAILED_MASK = (1 << 1), +}; -struct WeatherData { - double TemperatureDegree = TEMPERATURE_DEGREE_INVALID; -} MyWeather; +typedef uint8_t WifiErrorType_t; + +extern struct Settings settings; struct WifiNetworkInfo { String ssid; @@ -47,132 +55,71 @@ struct WifiNetworkInfo { }; static std::vector wifiNetworks; -static bool wifiScanInProgress = false; -static unsigned long lastConnectAttemptMs = 0; - -bool ESP8266Utils_Connect_STA( - const char* ssid, - const char* password, - const char* hostname, - int32_t timeout_ms = 10000) { - Serial.println(""); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - - while (WiFi.status() != WL_CONNECTED && timeout_ms > 0) { - delay(100); - timeout_ms -= 100; - yield(); +static bool wifiScanInProgress = false; +static unsigned long lastConnectUpdateMs = WIFI_CONNECTION_TIMEOUT_MS; +static unsigned long lastWeatherUpdateMs = WEATHER_UPDATE_INTERVAL_MS; +static double TemperatureDegree = TEMPERATURE_DEGREE_INVALID; +static bool wifiError = false; +static bool weatherError = false; + +bool ESP8266Utils_get_TemperatureDegree(double* temperature) { + if ((temperature == nullptr) || (TemperatureDegree == TEMPERATURE_DEGREE_INVALID)) { + return false; // Invalid temperature or null pointer } - - if (timeout_ms <= 0) { - Serial.println("STA Failed"); - return false; - } - - Serial.println(""); - Serial.print("Iniciado STA:\t"); - Serial.println(ssid); - Serial.print("IP address:\t"); - Serial.println(WiFi.localIP()); - + *temperature = TemperatureDegree; return true; } -bool ESP8266Utils_is_STA_connected() { return WiFi.isConnected(); } +bool ESP8266Utils_update_WeatherData(void) { + bool success = false; -bool ESP8266Utils_is_STA_online() { - if (!ESP8266Utils_is_STA_connected()) { - return false; - } - static bool isOnline; - static uint32_t timeout_ms = 0; - if ((millis() - timeout_ms) > HTTP_SERVER_PING_INTERVAL_MS) { - timeout_ms = millis(); - isOnline = true; - WiFiClient client; - // Ping the server to see if it is online - client.setTimeout(HTTP_SERVER_REQUEST_TIMEOUT); - if (!client.connect(HTTP_SERVER_PING_ADDRESS, 80)) { - isOnline = false; - } - client.stop(); - } - return isOnline; -} - -int8_t ESP8266Utils_get_STA_RSSI() { return WiFi.RSSI(); } - -String ESP8266Utils_get_STA_SSID() { return WiFi.SSID(); } + Serial.println("Updating weather data..."); -bool ESP8266Utils_update_WeatherData(struct Settings* myData) { - if (!ESP8266Utils_is_STA_online()) { - Serial.println("No internet connection"); - return false; - } - - String default_appid = DEFAULT_OPENWEATHERMAP_APPID; - if ((String(myData->openWeatherMapSettings.appid) == default_appid) - || (String(myData->openWeatherMapSettings.appid) == String(""))) { - Serial.println("Invalid Weather API key : " + String(myData->openWeatherMapSettings.appid)); - return false; - } - - WiFiClient client; - // Connect to HTTP server - client.setTimeout(HTTP_SERVER_REQUEST_TIMEOUT); - if (!client.connect(OPENWEATHERMAP_HOST, OPENWEATHERMAP_PORT)) { - Serial.println("Connection failed"); - // Disconnect - client.stop(); - return false; - } + if (String(settings.openWeatherMapSettings.appid) == String("")) { + Serial.println("Empty Weather API key"); + } else { + WiFiClient client; + client.setTimeout(WEATHER_SERVER_REQUEST_TIMEOUT); - // Send HTTP request - String HTTPrequest = DEFAULT_OPENWEATHERMAP_HTTP_REQUEST( - myData->openWeatherMapSettings.appid, - myData->openWeatherMapSettings.lat, - myData->openWeatherMapSettings.lon); - client.println(HTTPrequest); - client.println("Host: " + String(OPENWEATHERMAP_HOST)); - client.println("Connection: close"); - if (client.println() == 0) { - Serial.println("Failed to send request"); - // Disconnect - client.stop(); - return false; - } - // Skip HTTP headers - if (!client.find("\r\n\r\n")) { - Serial.println("Invalid response"); - // Disconnect - client.stop(); - return false; - } - if (client.find("\"temp\":")) { - double NewTemp = client.readStringUntil(',').toDouble(); - if (NewTemp > 273) { - NewTemp -= 273.15; + if (!client.connect(OPENWEATHERMAP_HOST, OPENWEATHERMAP_PORT)) { + Serial.println("Connection failed"); + } else { + String HTTPrequest = OPENWEATHERMAP_HTTP_REQUEST( + settings.openWeatherMapSettings.appid, + settings.openWeatherMapSettings.lat, + settings.openWeatherMapSettings.lon); + client.println(HTTPrequest); + client.println("Host: " + String(OPENWEATHERMAP_HOST)); + client.println("Connection: close"); + + if (client.println() == 0) { + Serial.println("Failed to send request"); + } else if (!client.find("\r\n\r\n")) { + Serial.println("Invalid response"); + } else if (client.find("\"temp\":")) { + double NewTemp = client.readStringUntil(',').toDouble(); + if (NewTemp > 273) { + NewTemp -= 273.15; + } + Serial.print("Temperature: "); + Serial.println(NewTemp); + TemperatureDegree = NewTemp; + + if (client.find("\"timezone\":")) { + long timezoneshift = strtol(client.readStringUntil(',').c_str(), NULL, 10); + Serial.print("Timezone shift: "); + Serial.println(timezoneshift); + struct rtcTime_t rtcTime + = { .myTime = time(NULL), .timezoneShift = timezoneshift }; + persistentVars_store_rtcTime(&rtcTime); + success = true; + } + } } - Serial.print("Temperature: "); - Serial.println(NewTemp); - MyWeather.TemperatureDegree = NewTemp; - } else { - Serial.println("No Temperature JSON object found"); - } - if (client.find("\"timezone\":")) { - long timezoneshift = strtol(client.readStringUntil(',').c_str(), NULL, 10); - Serial.print("Timezone shift: "); - Serial.println(timezoneshift); - struct rtcTime_t rtcTime = { .myTime = time(NULL), .timezoneShift = timezoneshift }; - persistentVars_store_rtcTime(&rtcTime); - } else { - Serial.println("No timezoneshift JSON object found"); + client.stop(); } - - // Disconnect - client.stop(); - return true; + lastWeatherUpdateMs = millis(); + return success; } void ESP8266Utils_clearWifiNetworksList() { @@ -316,17 +263,99 @@ int ESP8266Utils_getIndexBySsid(const String& targetSsid) { void ESP8266Utils_connectToWifi(const String& ssid, const String& password) { WiFi.mode(WIFI_STA); WiFi.begin(ssid.c_str(), password.c_str()); - lastConnectAttemptMs = millis(); + lastConnectUpdateMs = millis(); } -bool ESP8266Utils_isWifiConnected() { return WiFi.status() == WL_CONNECTED; } +bool ESP8266Utils_isWifiConnected(void) { return WiFi.status() == WL_CONNECTED; } -int ESP8266Utils_getWifiConnectionPercentage() { - if (lastConnectAttemptMs == 0) { +int ESP8266Utils_getWifiConnectionPercentage(void) { + if (lastConnectUpdateMs == 0) { return 0; } - unsigned long elapsed = millis() - lastConnectAttemptMs; + unsigned long elapsed = millis() - lastConnectUpdateMs; return (elapsed * 100) / WIFI_CONNECTION_TIMEOUT_MS; } -void Wifi_handler() {} +bool ESP8266Utils_getWifiConnectAttemptTmo(void) { + if (lastConnectUpdateMs == 0) { + return true; // No hay intento previo, se considera un timeout + } + return (millis() - lastConnectUpdateMs) >= WIFI_CONNECTION_INTERVAL_MS; +} + +bool ESP8266Utils_getWifiWeatherAttemptTmo(void) { + if (lastWeatherUpdateMs == 0) { + return true; // No hay intento previo, se considera un timeout + } + return (millis() - lastWeatherUpdateMs) >= WEATHER_UPDATE_INTERVAL_MS; +} + +WifiErrorType_t ESP8266Utils_get_errors(void) { + WifiErrorType_t error_bitmask = WIFI_ERROR_NONE; + if (wifiError) { + error_bitmask |= WIFI_ERROR_CONNECTION_FAILED_MASK; + } + if (weatherError) { + error_bitmask |= WIFI_ERROR_WEATHER_UPDATE_FAILED_MASK; + } + return error_bitmask; +} + +void Wifi_handler(bool inConfigMode) { + // Persistent state between calls + static unsigned long connectAttemptCount = 0; + static unsigned long weatherUpdateCount = 0; + static bool backFromConfigMode = false; // This is to force immediate reconnection + // attempts after leaving config mode + if (inConfigMode) { + connectAttemptCount = 0; + weatherUpdateCount = 0; + backFromConfigMode = true; // Force immediate actions after config mode + return; + } + + bool wifiEnabled = settings.wifiSettings.wifi_enabled; + bool wifiConnected = ESP8266Utils_isWifiConnected(); + bool WifiTmo = ESP8266Utils_getWifiConnectAttemptTmo() || backFromConfigMode; + bool WeatherTmo = ESP8266Utils_getWifiWeatherAttemptTmo() || backFromConfigMode; + + // ===== WiFi auto connection handling ===== + if (wifiEnabled && wifiConnected && connectAttemptCount > 0) { + Serial.println("Connected to WiFi"); + connectAttemptCount = 0; + wifiError = false; + } else if ((wifiEnabled && !wifiConnected) && WifiTmo) { + if (++connectAttemptCount < WIFI_CONNECTION_MAX_CONSECUTIVE_ATTEMPTS) { + Serial.println("Attempt " + String(connectAttemptCount) + " to connect to WiFi..."); + ESP8266Utils_connectToWifi( + settings.wifiSettings.ssid_sta, + settings.wifiSettings.password_sta); + } else { + wifiError = true; + } + } else if (!wifiEnabled && wifiConnected) { + Serial.println("Disconnecting WiFi..."); + WiFi.disconnect(); + connectAttemptCount = 0; + wifiError = false; + } + + // ===== Weather update handling ===== + if (wifiEnabled && wifiConnected && WeatherTmo) { + if (ESP8266Utils_update_WeatherData()) { + weatherUpdateCount = 0; + weatherError = false; + } else if (++weatherUpdateCount > WEATHER_UPDATE_MAX_CONSECUTIVE_ATTEMPTS) { + TemperatureDegree = TEMPERATURE_DEGREE_INVALID; + Serial.println("Attempt " + String(weatherUpdateCount) + " to update weather failed."); + weatherError = true; + } + } else if (!wifiConnected) { + weatherUpdateCount = 0; + TemperatureDegree = TEMPERATURE_DEGREE_INVALID; + weatherError = false; + } + + backFromConfigMode = false; // Indicate we are no longer back from config mode +} + diff --git a/Firmware_ESP8266/Firmware_ESP8266.ino b/Firmware_ESP8266/Firmware_ESP8266.ino index 5fd1e8e..59c7201 100644 --- a/Firmware_ESP8266/Firmware_ESP8266.ino +++ b/Firmware_ESP8266/Firmware_ESP8266.ino @@ -85,5 +85,18 @@ void loop() { buzzer_handler(); shutterHandler(); EEPROM_Handler(); - Wifi_handler(); + Wifi_handler(_seleccionMenu > SELECCION_MENU_CONFIG); + + static Settings previousSettings; + if (memcmp(&settings, &previousSettings, sizeof(Settings)) != 0) { + Serial.println("Cambio de configuración de settings detectado."); + Serial.print("Habilitado: "); + Serial.println(settings.wifiSettings.wifi_enabled ? "Si" : "No"); + Serial.print("OTA: "); + Serial.println(settings.wifiSettings.ota_enabled ? "Si" : "No"); + Serial.print("SSID: "); + Serial.println(settings.wifiSettings.ssid_sta); + + memcpy(&previousSettings, &settings, sizeof(Settings)); + } } diff --git a/Firmware_ESP8266/README.md b/Firmware_ESP8266/README.md index c903f1b..0fe3aca 100644 --- a/Firmware_ESP8266/README.md +++ b/Firmware_ESP8266/README.md @@ -1,51 +1,82 @@ # Roller Shutter Control Panel Main CPU -This serves as the primary CPU for the roller shutter control panel, based on the [ESP8266](https://en.wikipedia.org/wiki/ESP8266) microcontroller and programmed using the [Arduino IDE](https://www.arduino.cc/en/Main/Software). +This serves as the primary CPU for the roller shutter control panel, based on the +[ESP8266](https://en.wikipedia.org/wiki/ESP8266) microcontroller and programmed using the +[Arduino IDE](https://www.arduino.cc/en/Main/Software). ## Introduction -The main CPU handles several critical tasks, including displaying appropriate content on the LCD, processing user input from buttons, and communicating via I2C with the radio module to control the roller shutters. Additionally, it can connect to a Wi-Fi network, enabling data retrieval from the internet for functions like displaying weather forecasts and time. This data is crucial for specific user requirements, such as scheduling shutter control based on time and weather conditions. +The main CPU handles several critical tasks, including displaying appropriate content on the LCD, +processing user input from buttons, and communicating via I2C with the radio module to control the +roller shutters. Additionally, it can connect to a Wi-Fi network, enabling data retrieval from the +internet for functions like displaying weather forecasts and time. This data is crucial for specific +user requirements, such as scheduling shutter control based on time and weather conditions. ## Roadmap -Make the solution simpler!! There are many features that are not strictly necessary for the roller shutter control panel. I want a working solution soon and will remove unnecessary features to achieve that. In order to do that, I will only need the following features: +Make the solution simpler!! There are many features that are not strictly necessary for the roller +shutter control panel. I want a working solution soon and will remove unnecessary features to +achieve that. In order to do that, I will only need the following features: - Control of the roller shutters via the radio module. - Connect to a Wi-Fi network. - Get current time and weather forecast from the internet. - Display the current time and temperature on the LCD when idle. -- Allowing the user to change the time UTC and the Wi-Fi network settings via LCD buttons. +- Allow the user to change the time UTC and the Wi-Fi network settings via LCD buttons. ## Proposed Enhancements Here are several potential enhancements to consider: 1. **Dynamic Wi-Fi Configuration**: - - It would be beneficial to allow users to update their Wi-Fi network settings without reprogramming the microcontroller. This could involve creating a web server on the microcontroller with a user-friendly web interface for configuring Wi-Fi settings. The microcontroller would act as an access point if it's not connected to a Wi-Fi station , allowing users to connect and make necessary adjustments. + - It would be beneficial to allow users to update their Wi-Fi network settings without + reprogramming the microcontroller. This could involve creating a web server on the + microcontroller with a user-friendly web interface for configuring Wi-Fi settings. The + microcontroller would act as an access point if it's not connected to a Wi-Fi station, allowing + users to connect and make necessary adjustments. 2. **Access Point SSID/Password Update**: - - Providing the ability to update the SSID and password of the access point, particularly when the device cannot connect to the user's Wi-Fi network. Displaying this information on the LCD would be helpful. + - Providing the ability to update the SSID and password of the access point, particularly when + the device cannot connect to the user's Wi-Fi network. Displaying this information on the LCD + would be helpful. 3. **HTTPS Implementation**: - - Implement HTTPS on the microcontroller's web server to encrypt Wi-Fi settings transmission, preventing plain text transmission. This can be achieved using a self-signed certificate, requiring user acceptance in their browser. - Additionally, consider to whether to permanently disable this option once the user uploads a new certificate for enhanced security. This measure safeguards against potential attackers connected to the user access point intercepting traffic. + - Implement HTTPS on the microcontroller's web server to encrypt Wi-Fi settings transmission, + preventing plain text transmission. This can be achieved using a self-signed certificate, + requiring user acceptance in their browser. Additionally, consider whether to permanently + disable this option once the user uploads a new certificate for enhanced security. This + measure safeguards against potential attackers connected to the user access point intercepting + traffic. 4. **OTA Firmware Updates**: - - Exploring the possibility of updating the microcontroller's firmware without requiring a physical connection to a computer. This could involve using a web server on the microcontroller and a web interface for uploading new firmware. It's worth considering whether to erase all stored data on the microcontroller after a firmware update to prevent the new firmware from accessing old data. This step would enhance security and thwart potential attackers from reading sensitive information like Wi-Fi settings. + - Exploring the possibility of updating the microcontroller's firmware without requiring a + physical connection to a computer. This could involve using a web server on the microcontroller + and a web interface for uploading new firmware. It's worth considering whether to erase all + stored data on the microcontroller after a firmware update to prevent the new firmware from + accessing old data. This step would enhance security and thwart potential attackers from + reading sensitive information like Wi-Fi settings. 5. **Radio Module Key Updates**: - - While not the highest priority, it may be beneficial to provide a method for updating the keys of the radio module without requiring a computer connection. It's important to note that these keys are stored in the radio module, not the microcontroller. + - While not the highest priority, it may be beneficial to provide a method for updating the keys + of the radio module without requiring a computer connection. It's important to note that these + keys are stored in the radio module, not the microcontroller. 6. **GPS Coordinates and UTC Time Updates**: - - Consider adding functionality for updating GPS coordinates and UTC time to enhance internal calculations and improve system accuracy. + - Consider adding functionality for updating GPS coordinates and UTC time to enhance internal + calculations and improve system accuracy. 7. **Weather Forecast API Key Rotation**: - - Explore options for updating the API key used for weather forecast services while maintaining the security of the stored key. Avoid displaying the stored key on the microcontroller. + - Explore options for updating the API key used for weather forecast services while maintaining + the security of the stored key. Avoid displaying the stored key on the microcontroller. 8. **Additional Web Interface Features**: - - While not a top priority, other data could potentially be visualized through the web interface, enhancing the user experience. + - While not a top priority, other data could potentially be visualized through the web interface, + enhancing the user experience. 9. **User Menu**: - - Implement a user menu to display essential Wi-Fi settings, such as the SSID of the station or access point (excluding the password), and the microcontroller's IP address or domain name. + - Implement a user menu to display essential Wi-Fi settings, such as the SSID of the station or + access point (excluding the password), and the microcontroller's IP address or domain name. -These enhancements can help improve the functionality, security, and user-friendliness of the roller shutter control system. Prioritizing and addressing them based on their significance and potential security implications is essential for a robust and secure solution. \ No newline at end of file +These enhancements can help improve the functionality, security, and user-friendliness of the roller +shutter control system. Prioritizing and addressing them based on their significance and potential +security implications is essential for a robust and secure solution. \ No newline at end of file diff --git a/Firmware_ESP8266/basic_defines.h b/Firmware_ESP8266/basic_defines.h index d4b3f46..d0b4d38 100644 --- a/Firmware_ESP8266/basic_defines.h +++ b/Firmware_ESP8266/basic_defines.h @@ -3,22 +3,20 @@ #define SLAVE_I2C_ADDRESS (0x08) #define LCD_I2C_ADDRESS (0x3F) +#define DEFAULT_BUZZER_ENABLED false #define DEFAULT_BUZZER_VOLUME (4000) #define DEFAULT_STA_SSID "YOUR_SSID" #define DEFAULT_STA_PASSWORD "YOUR_PASSWORD" -#define DEFAULT_AP_SSID_AND_PASSWORD "ESP8266Config" -#define DEFAULT_HOSTNAME "esp8266config" +#define DEFAULT_WIFI_ENABLED false +#define DEFAULT_WIFI_OTA_ENABLED false #define OPENWEATHERMAP_HOST "api.openweathermap.org" #define OPENWEATHERMAP_PORT (80) -#define DEFAULT_OPENWEATHERMAP_APPID "YOUR_API_KEY" +#define DEFAULT_OPENWEATHERMAP_APPID "" #define DEFAULT_OPENWEATHERMAP_LOCATION_LAT (0) #define DEFAULT_OPENWEATHERMAP_LOCATION_LON (0) -#define HTTP_SERVER_REQUEST_TIMEOUT (2000) -#define WIFI_CONNECTION_TIMEOUT (10000) - -#define DEFAULT_OPENWEATHERMAP_HTTP_REQUEST(appid, lat, long) \ +#define OPENWEATHERMAP_HTTP_REQUEST(appid, lat, long) \ "GET /data/2.5/weather?lat=" + \ String(lat) + \ "&lon=" + \ @@ -54,6 +52,7 @@ enum seleccionMenu { SELECCION_MENU_CONFIG_DEBUG_SOFT_RST_COUNT, SELECCION_MENU_CONFIG_WIFI, SELECCION_MENU_CONFIG_WIFI_HABILITAR, + SELECCION_MENU_CONFIG_WIFI_OTA_HABILITAR, SELECCION_MENU_CONFIG_WIFI_SSID, SELECCION_MENU_CONFIG_WIFI_PASSWORD, SELECCION_MENU_CONFIG_WIFI_RESULTADO, diff --git a/Firmware_ESP8266/lcd.h b/Firmware_ESP8266/lcd.h index 04b3b78..1eb90c3 100644 --- a/Firmware_ESP8266/lcd.h +++ b/Firmware_ESP8266/lcd.h @@ -10,18 +10,35 @@ #include "persistentVars.h" #include "rtcTime.h" -extern struct WeatherData MyWeather; - LiquidCrystal_PCF8574 _lcd(LCD_I2C_ADDRESS); -static time_t adjustedTime = 0; -static long adjustedTimeZone = 0; -static uint32_t previousBuzzerVolume = 0; -static unsigned long currentAdjustedLevelIndex = 0; -static bool previousBuzzerEnabled = false; -static bool buttonIsReleased = false; -static String adjustedSSID = ""; -static String adjustedPassword = ""; -static unsigned long buttonHoldingTimeMs = 0; +static bool buttonIsReleased = false; +static unsigned long buttonHoldingTimeMs = 0; + +struct WifiConfig { + String ssid; + String password; + bool enabled; + bool ota_enabled; +}; + +struct VolumeConfig { + uint32_t previousVolume; + bool previousBuzzerEnabled; +}; + +struct TimezoneConfig { + time_t time; + unsigned long whichIncrementIndex; + long timezoneShift; +}; + +struct PendingConfig { + struct WifiConfig wifi; + struct VolumeConfig volume; + struct TimezoneConfig timezone; +}; + +static PendingConfig pendingConfig; #define LCD_SPECIAL_CHAR_BASE (char)(10) #define LCD_SPECIAL_CHAR_LEFT_ARROW (char)(LCD_SPECIAL_CHAR_BASE + 0) @@ -191,9 +208,9 @@ void pantalla_handleButtonInMenu( newMenu = SELECCION_MENU_CONFIG; break; case BUTTON_STATUS_RIGHT: - adjustedTime = time(NULL); - currentAdjustedLevelIndex = 0; - newMenu = SELECCION_MENU_CONFIG_FECHA_HORA_AJUSTE; + pendingConfig.timezone.time = time(NULL); + pendingConfig.timezone.whichIncrementIndex = 0; + newMenu = SELECCION_MENU_CONFIG_FECHA_HORA_AJUSTE; break; case BUTTON_STATUS_DOWN: newMenu = SELECCION_MENU_CONFIG_VOLUMEN; @@ -216,30 +233,31 @@ void pantalla_handleButtonInMenu( switch (currentButtonPressed) { case BUTTON_STATUS_LEFT: - if (currentAdjustedLevelIndex) { - currentAdjustedLevelIndex--; + if (pendingConfig.timezone.whichIncrementIndex) { + pendingConfig.timezone.whichIncrementIndex--; } else { newMenu = SELECCION_MENU_CONFIG_FECHA_HORA; } break; case BUTTON_STATUS_RIGHT: - if (currentAdjustedLevelIndex < (incsCount - 1)) { - currentAdjustedLevelIndex++; + if (pendingConfig.timezone.whichIncrementIndex < (incsCount - 1)) { + pendingConfig.timezone.whichIncrementIndex++; } else { - adjustedTimeZone = persistentVars_get_rtcTime().timezoneShift; - newMenu = SELECCION_MENU_CONFIG_ZONA_HORARIA_AJUSTE; + pendingConfig.timezone.timezoneShift + = persistentVars_get_rtcTime().timezoneShift; + newMenu = SELECCION_MENU_CONFIG_ZONA_HORARIA_AJUSTE; } break; case BUTTON_STATUS_UP: - adjustedTime += incs[currentAdjustedLevelIndex]; + pendingConfig.timezone.time += incs[pendingConfig.timezone.whichIncrementIndex]; break; case BUTTON_STATUS_DOWN: - adjustedTime -= incs[currentAdjustedLevelIndex]; + pendingConfig.timezone.time -= incs[pendingConfig.timezone.whichIncrementIndex]; break; } - if (adjustedTime < 0) { - adjustedTime = 0; + if (pendingConfig.timezone.time < 0) { + pendingConfig.timezone.time = 0; } break; @@ -247,24 +265,24 @@ void pantalla_handleButtonInMenu( case SELECCION_MENU_CONFIG_ZONA_HORARIA_AJUSTE: { switch (currentButtonPressed) { case BUTTON_STATUS_LEFT: - currentAdjustedLevelIndex = 0; - newMenu = SELECCION_MENU_CONFIG_FECHA_HORA_AJUSTE; + pendingConfig.timezone.whichIncrementIndex = 0; + newMenu = SELECCION_MENU_CONFIG_FECHA_HORA_AJUSTE; break; case BUTTON_STATUS_RIGHT: buzzer_sound_accept(); - rtc_set(adjustedTime, adjustedTimeZone); + rtc_set(pendingConfig.timezone.time, pendingConfig.timezone.timezoneShift); newMenu = SELECCION_MENU_CONFIG; break; case BUTTON_STATUS_UP: - adjustedTimeZone += 3600; // Increase by 1 hour - if (adjustedTimeZone > 43200) { // 12 hours in seconds - adjustedTimeZone = 43200; + pendingConfig.timezone.timezoneShift += 3600; // Increase by 1 hour + if (pendingConfig.timezone.timezoneShift > 43200) { // 12 hours in seconds + pendingConfig.timezone.timezoneShift = 43200; } break; case BUTTON_STATUS_DOWN: - adjustedTimeZone -= 3600; // Decrease by 1 hour - if (adjustedTimeZone < -43200) { // -12 hours in seconds - adjustedTimeZone = -43200; + pendingConfig.timezone.timezoneShift -= 3600; // Decrease by 1 hour + if (pendingConfig.timezone.timezoneShift < -43200) { // -12 hours in seconds + pendingConfig.timezone.timezoneShift = -43200; } break; } @@ -277,10 +295,10 @@ void pantalla_handleButtonInMenu( newMenu = SELECCION_MENU_CONFIG_FECHA_HORA; break; case BUTTON_STATUS_RIGHT: - previousBuzzerVolume = buzzer_get_volume(); - previousBuzzerEnabled = buzzer_is_enabled(); - buttonIsReleased = false; - newMenu = SELECCION_MENU_CONFIG_VOLUMEN_AJUSTE; + pendingConfig.volume.previousVolume = buzzer_get_volume(); + pendingConfig.volume.previousBuzzerEnabled = buzzer_is_enabled(); + buttonIsReleased = false; + newMenu = SELECCION_MENU_CONFIG_VOLUMEN_AJUSTE; break; case BUTTON_STATUS_DOWN: newMenu = SELECCION_MENU_CONFIG_DEBUG; @@ -324,8 +342,8 @@ void pantalla_handleButtonInMenu( newMenu = SELECCION_MENU_CONFIG; break; case BUTTON_STATUS_LEFT: - buzzer_set_volume(previousBuzzerVolume); - if (previousBuzzerEnabled) { + buzzer_set_volume(pendingConfig.volume.previousVolume); + if (pendingConfig.volume.previousBuzzerEnabled) { buzzer_enable(); } else { buzzer_disable(); @@ -370,8 +388,8 @@ void pantalla_handleButtonInMenu( newMenu = SELECCION_MENU_CONFIG_DEBUG; break; case BUTTON_STATUS_RIGHT: - ESP8266Utils_clearWifiNetworksList(); - newMenu = SELECCION_MENU_CONFIG_WIFI_SSID; + pendingConfig.wifi.enabled = settings.wifiSettings.wifi_enabled; + newMenu = SELECCION_MENU_CONFIG_WIFI_HABILITAR; break; case BUTTON_STATUS_DOWN: case BUTTON_STATUS_LEFT: @@ -379,6 +397,44 @@ void pantalla_handleButtonInMenu( break; } break; + case SELECCION_MENU_CONFIG_WIFI_HABILITAR: + switch (currentButtonPressed) { + case BUTTON_STATUS_LEFT: + settings.wifiSettings.wifi_enabled = pendingConfig.wifi.enabled; + EEPROM_Schedule_Write(0); + newMenu = SELECCION_MENU_CONFIG_WIFI; + break; + case BUTTON_STATUS_RIGHT: + pendingConfig.wifi.ota_enabled = settings.wifiSettings.ota_enabled; + settings.wifiSettings.wifi_enabled = pendingConfig.wifi.enabled; + EEPROM_Schedule_Write(0); + newMenu = SELECCION_MENU_CONFIG_WIFI_OTA_HABILITAR; + break; + case BUTTON_STATUS_DOWN: + case BUTTON_STATUS_UP: + pendingConfig.wifi.enabled = !pendingConfig.wifi.enabled; + break; + } + break; + case SELECCION_MENU_CONFIG_WIFI_OTA_HABILITAR: + switch (currentButtonPressed) { + case BUTTON_STATUS_LEFT: + settings.wifiSettings.ota_enabled = pendingConfig.wifi.ota_enabled; + EEPROM_Schedule_Write(0); + newMenu = SELECCION_MENU_CONFIG_WIFI_HABILITAR; + break; + case BUTTON_STATUS_RIGHT: + settings.wifiSettings.ota_enabled = pendingConfig.wifi.ota_enabled; + EEPROM_Schedule_Write(0); + ESP8266Utils_clearWifiNetworksList(); + newMenu = SELECCION_MENU_CONFIG_WIFI_SSID; + break; + case BUTTON_STATUS_DOWN: + case BUTTON_STATUS_UP: + pendingConfig.wifi.ota_enabled = !pendingConfig.wifi.ota_enabled; + break; + } + break; case SELECCION_MENU_CONFIG_WIFI_SSID: { // In this menu, an async wifi scan is performed. And we start to do RSSI avg on each // SSID. We display the SSID in a list sorted by RSSI, first the strongest. @@ -389,7 +445,7 @@ void pantalla_handleButtonInMenu( static int previousCurrentIndex = 1; // Start at 1 so in the first iteration we can // select the first network. int countNetworks = ESP8266Utils_getTrackedNetworkCount(); - int currentIndex = ESP8266Utils_getIndexBySsid(adjustedSSID); + int currentIndex = ESP8266Utils_getIndexBySsid(pendingConfig.wifi.ssid); if ((currentIndex < 0) && (countNetworks > 0) && (previousCurrentIndex > 0)) { currentIndex = previousCurrentIndex - 1; } else if (currentIndex < 0) { @@ -397,27 +453,27 @@ void pantalla_handleButtonInMenu( } previousCurrentIndex = currentIndex; - ESP8266Utils_getSsidAtIndex(currentIndex, adjustedSSID); + ESP8266Utils_getSsidAtIndex(currentIndex, pendingConfig.wifi.ssid); switch (currentButtonPressed) { case BUTTON_STATUS_UP: if (currentIndex > 0) { currentIndex--; } - ESP8266Utils_getSsidAtIndex(currentIndex, adjustedSSID); + ESP8266Utils_getSsidAtIndex(currentIndex, pendingConfig.wifi.ssid); break; case BUTTON_STATUS_RIGHT: - adjustedPassword = ""; - newMenu = SELECCION_MENU_CONFIG_WIFI_PASSWORD; + pendingConfig.wifi.password = ""; + newMenu = SELECCION_MENU_CONFIG_WIFI_PASSWORD; break; case BUTTON_STATUS_DOWN: if (currentIndex < (countNetworks - 1)) { currentIndex++; } - ESP8266Utils_getSsidAtIndex(currentIndex, adjustedSSID); + ESP8266Utils_getSsidAtIndex(currentIndex, pendingConfig.wifi.ssid); break; case BUTTON_STATUS_LEFT: - newMenu = SELECCION_MENU_CONFIG_WIFI; + newMenu = SELECCION_MENU_CONFIG_WIFI_OTA_HABILITAR; break; } break; @@ -437,30 +493,39 @@ void pantalla_handleButtonInMenu( if ((now - buttonHoldingTimeMs) >= LCD_HOLDING_TIME_CONFIRM_MS) { if (previousButtonHolding == BUTTON_STATUS_DOWN) { buzzer_sound_accept(); + ESP8266Utils_connectToWifi( + pendingConfig.wifi.ssid, + pendingConfig.wifi.password); newMenu = SELECCION_MENU_CONFIG_WIFI_RESULTADO; - ESP8266Utils_connectToWifi(adjustedSSID, adjustedPassword); } } else { switch (previousButtonHolding) { case BUTTON_STATUS_LEFT: - if (adjustedPassword.length() == 0) { + if (pendingConfig.wifi.password.length() == 0) { newMenu = SELECCION_MENU_CONFIG_WIFI_SSID; } else { - adjustedPassword.remove(adjustedPassword.length() - 1); + pendingConfig.wifi.password.remove( + pendingConfig.wifi.password.length() - 1); } break; case BUTTON_STATUS_UP: - if (adjustedPassword[adjustedPassword.length() - 1] < '~') { - adjustedPassword[adjustedPassword.length() - 1]++; + if (pendingConfig.wifi + .password[pendingConfig.wifi.password.length() - 1] + < '~') { + pendingConfig.wifi + .password[pendingConfig.wifi.password.length() - 1]++; } break; case BUTTON_STATUS_DOWN: - if (adjustedPassword[adjustedPassword.length() - 1] > '!') { - adjustedPassword[adjustedPassword.length() - 1]--; + if (pendingConfig.wifi + .password[pendingConfig.wifi.password.length() - 1] + > '!') { + pendingConfig.wifi + .password[pendingConfig.wifi.password.length() - 1]--; } break; case BUTTON_STATUS_RIGHT: - adjustedPassword += 'A'; + pendingConfig.wifi.password += 'A'; break; } } @@ -469,25 +534,17 @@ void pantalla_handleButtonInMenu( break; } case SELECCION_MENU_CONFIG_WIFI_RESULTADO: { - static bool wifiCredentialStored = false; - if (ESP8266Utils_isWifiConnected()) { - if (!wifiCredentialStored) { - wifiCredentialStored = true; - memcpy(settings.wifiSettings.ssid_sta, - adjustedSSID.c_str(), - sizeof(settings.wifiSettings.ssid_sta)); - memcpy(settings.wifiSettings.password_sta, - adjustedPassword.c_str(), - sizeof(settings.wifiSettings.password_sta)); - EEPROM_Schedule_Write(100); - } - } else { - wifiCredentialStored = false; - } switch (currentButtonPressed) { case BUTTON_STATUS_RIGHT: if (ESP8266Utils_isWifiConnected()) { buzzer_sound_accept(); + memcpy(settings.wifiSettings.ssid_sta, + pendingConfig.wifi.ssid.c_str(), + sizeof(settings.wifiSettings.ssid_sta)); + memcpy(settings.wifiSettings.password_sta, + pendingConfig.wifi.password.c_str(), + sizeof(settings.wifiSettings.password_sta)); + EEPROM_Schedule_Write(0); newMenu = SELECCION_MENU_CONFIG_WIFI; } else { buzzer_sound_error(); @@ -508,7 +565,12 @@ void pantalla_handleButtonInMenu( } void pantalla_actualizarReloj(String* lcdBuffer) { - *lcdBuffer = " "; + if (ESP8266Utils_isWifiConnected()) { + *lcdBuffer = LCD_SPECIAL_CHAR_CONNECTED_SYMBOL; + } else { + *lcdBuffer = " "; + } + *lcdBuffer += " "; time_t now; struct tm* timeinfo; now = time(&now) + persistentVars_get_rtcTime().timezoneShift; @@ -526,17 +588,31 @@ void pantalla_actualizarReloj(String* lcdBuffer) { *lcdBuffer += String(timeinfo->tm_min); *lcdBuffer += String(" "); - uint8_t _localLength = String((int)MyWeather.TemperatureDegree).length(); - // Limit degree to 2 digits - if (_localLength < 4) { - for (int i = 0; i < (3 - _localLength); i++) { - *lcdBuffer += String(" "); - } - *lcdBuffer += String((int)MyWeather.TemperatureDegree); - *lcdBuffer += (char)223; - *lcdBuffer += String("C"); + if ((ESP8266Utils_get_errors() & WIFI_ERROR_WEATHER_UPDATE_FAILED_MASK)) { + *lcdBuffer += " "; + *lcdBuffer += LCD_SPECIAL_CHAR_WARNING_SYMBOL; } else { - *lcdBuffer += String(" "); + double temp; + if (ESP8266Utils_get_TemperatureDegree(&temp)) { + int t = (int)temp; + + if (t < 0) { + *lcdBuffer += " <0"; + } else if (t > 50) { + *lcdBuffer += ">50"; + } else { + *lcdBuffer += " "; + if (t < 10) { + *lcdBuffer += " "; // leading space for single digit + } + *lcdBuffer += String(t); + } + + *lcdBuffer += (char)223; // degree symbol + *lcdBuffer += "C"; + } else { + *lcdBuffer += " "; // blank when no data + } } if ((timeinfo->tm_mday) < 10) { @@ -623,7 +699,7 @@ void pantalla_actualizarMenuConfigFechaHora(String* lcdBuffer) { } void pantalla_actualizarMenuConfigFechaHoraAjuste(String* lcdBuffer) { - struct tm* timeinfo = localtime(&adjustedTime); + struct tm* timeinfo = localtime(&pendingConfig.timezone.time); static unsigned long lastFlashMs = 0; static bool flashOn = false; @@ -634,7 +710,7 @@ void pantalla_actualizarMenuConfigFechaHoraAjuste(String* lcdBuffer) { } auto printField = [&](int value, unsigned long index, int width) { - bool visible = (currentAdjustedLevelIndex != index) || flashOn; + bool visible = (pendingConfig.timezone.whichIncrementIndex != index) || flashOn; if (visible) { if (value < 10 && width >= 2) { *lcdBuffer += '0'; @@ -666,7 +742,7 @@ void pantalla_actualizarMenuConfigFechaHoraAjuste(String* lcdBuffer) { void pantalla_actualizarMenuConfigZonaHorariaAjuste(String* lcdBuffer) { *lcdBuffer += String("ZONA UTC: "); - *lcdBuffer += String(adjustedTimeZone / 3600); + *lcdBuffer += String(pendingConfig.timezone.timezoneShift / 3600); *lcdBuffer += String(" h"); // Add spaces to fill the line. while (lcdBuffer->length() < 16) { @@ -740,13 +816,37 @@ void pantalla_actualizarMenuConfigWifi(String* lcdBuffer) { *lcdBuffer += String(" >"); } +void pantalla_actualizarMenuConfigWifiHabilitar(String* lcdBuffer) { + if (pendingConfig.wifi.enabled) { + *lcdBuffer += String(" WIFI : SI "); + } else { + *lcdBuffer += String(" WIFI : NO "); + } + *lcdBuffer += String("< "); + *lcdBuffer += LCD_SPECIAL_CHAR_UP_ARROW; + *lcdBuffer += LCD_SPECIAL_CHAR_DOWN_ARROW; + *lcdBuffer += String(" >"); +} + +void pantalla_actualizarMenuConfigWifiOTAHabilitar(String* lcdBuffer) { + if (pendingConfig.wifi.ota_enabled) { + *lcdBuffer += String(" WIFI OTA: SI "); + } else { + *lcdBuffer += String(" WIFI OTA: NO "); + } + *lcdBuffer += String("< "); + *lcdBuffer += LCD_SPECIAL_CHAR_UP_ARROW; + *lcdBuffer += LCD_SPECIAL_CHAR_DOWN_ARROW; + *lcdBuffer += String(" >"); +} + void pantalla_actualizarMenuConfigWifiPassword(String* lcdBuffer) { *lcdBuffer += String("PWD: "); // If password is larger than 11 characters, truncate it from the // left to right, keeping the last 11 characters. - String truncatedPassword = adjustedPassword; - if (adjustedPassword.length() > 11) { + String truncatedPassword = pendingConfig.wifi.password; + if (truncatedPassword.length() > 11) { truncatedPassword = truncatedPassword.substring(truncatedPassword.length() - 11); } *lcdBuffer += truncatedPassword; @@ -807,8 +907,8 @@ void pantalla_actualizarMenuConfigWifiPassword(String* lcdBuffer) { void pantalla_actualizarMenuConfigWifiSsid(String* lcdBuffer) { *lcdBuffer += String("SSID:"); - String mySSID = adjustedSSID; - int adjustedSSIDindex = ESP8266Utils_getIndexBySsid(adjustedSSID); + String mySSID = pendingConfig.wifi.ssid; + int adjustedSSIDindex = ESP8266Utils_getIndexBySsid(pendingConfig.wifi.ssid); if (adjustedSSIDindex < 0) { mySSID = "No hay redes"; } @@ -929,6 +1029,12 @@ void pantalla_actualizarMenu(uint8_t selectedMenu) { case SELECCION_MENU_CONFIG_WIFI: pantalla_actualizarMenuConfigWifi(&lcdBuffer); break; + case SELECCION_MENU_CONFIG_WIFI_HABILITAR: + pantalla_actualizarMenuConfigWifiHabilitar(&lcdBuffer); + break; + case SELECCION_MENU_CONFIG_WIFI_OTA_HABILITAR: + pantalla_actualizarMenuConfigWifiOTAHabilitar(&lcdBuffer); + break; case SELECCION_MENU_CONFIG_WIFI_SSID: pantalla_actualizarMenuConfigWifiSsid(&lcdBuffer); break; diff --git a/Firmware_ESP8266/persistentVars.h b/Firmware_ESP8266/persistentVars.h index 490bca7..5520885 100644 --- a/Firmware_ESP8266/persistentVars.h +++ b/Firmware_ESP8266/persistentVars.h @@ -31,9 +31,15 @@ struct rtcTime_t { static_assert(sizeof(rtcTime_t) % 4 == 0, "rtcTime_t must be aligned to 4 bytes"); +struct weatherData_t { + double TemperatureDegree; // Temperature in degrees Celsius + // Other weather data can be added here in the future +}; + struct persistentVars_t { uint32_t softResetCount; struct rtcTime_t rtcTime; + struct weatherData_t weatherData; uint32_t magicWord; }; @@ -74,6 +80,21 @@ void persistentVars_store_rtcTime(const struct rtcTime_t* rtcTime) { struct rtcTime_t persistentVars_get_rtcTime(void) { return persistentVars_data.rtcTime; } +struct weatherData_t persistentVars_get_weatherData(void) { + return persistentVars_data.weatherData; +} + +void persistentVars_store_weatherData(const struct weatherData_t* weatherData) { + if (weatherData == nullptr) { + return; + } + + memcpy(&persistentVars_data.weatherData, weatherData, sizeof(struct weatherData_t)); + persistentVars_data.magicWord = PERSISTENTVARS_USER_MEMORY_MAGIC_WORD; + + ESP.rtcUserMemoryWrite(0, (uint32_t*)&persistentVars_data, sizeof(persistentVars_data)); +} + // This function initializes the persistent variables by reading them from no-init RAM. void persistentVars_init(void) { // This will just call persistentVars_is_stored() which will read the data from no-init RAM.