diff --git a/Firmware_ESP8266/EEPROM_Utils.h b/Firmware_ESP8266/EEPROM_Utils.h index 0640604..2bc08d8 100644 --- a/Firmware_ESP8266/EEPROM_Utils.h +++ b/Firmware_ESP8266/EEPROM_Utils.h @@ -5,8 +5,9 @@ #include "basic_defines.h" -static uint32_t EEPROM_Scheduled_Write_Delay = 0; -static bool EEPROM_Scheduled_Write_Enabled = false; +static unsigned long EEPROM_Scheduled_Write_At = 0; +static unsigned long EEPROM_Scheduled_Write_Delay = 0; +static bool EEPROM_Scheduled_Write_Enabled = false; struct WifiSettings { char ssid_sta[64]; @@ -53,7 +54,8 @@ uint16_t CRC16(const uint8_t* data, uint16_t size) { } void EEPROM_Schedule_Write(unsigned long delayMs) { - EEPROM_Scheduled_Write_Delay = delayMs + millis(); + EEPROM_Scheduled_Write_At = millis(); + EEPROM_Scheduled_Write_Delay = delayMs; EEPROM_Scheduled_Write_Enabled = true; } @@ -85,15 +87,12 @@ void EEPROM_Write(Settings* data) { } void EEPROM_Handler() { - if (!EEPROM_Scheduled_Write_Enabled) { - return; - } - if (millis() < EEPROM_Scheduled_Write_Delay) { - return; + if (EEPROM_Scheduled_Write_Enabled) { + if ((millis() - EEPROM_Scheduled_Write_At) >= EEPROM_Scheduled_Write_Delay) { + EEPROM_Scheduled_Write_Enabled = false; + EEPROM_Write(&settings); + } } - EEPROM_Scheduled_Write_Enabled = false; - - EEPROM_Write(&settings); } bool EEPROM_Read(Settings* out) { diff --git a/Firmware_ESP8266/ESP8266_Utils.h b/Firmware_ESP8266/ESP8266_Utils.h index 1d74fd0..2c960e4 100644 --- a/Firmware_ESP8266/ESP8266_Utils.h +++ b/Firmware_ESP8266/ESP8266_Utils.h @@ -9,6 +9,11 @@ #define HTTP_SERVER_PING_ADDRESS "1.1.1.1" #define HTTP_SERVER_PING_INTERVAL_MS (10000) +#define WIFI_CONNECTION_TIMEOUT_MS (10000) +#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) extern struct Settings settings; @@ -17,6 +22,34 @@ struct WeatherData { double TemperatureDegree = TEMPERATURE_DEGREE_INVALID; } MyWeather; +struct WifiNetworkInfo { + String ssid; + String bssid; + int channel; + String encryption; + float avgRssi; + int rssiSamples; + int notSeenCount = 0; + bool seenInThisScan = false; + + // Constructor por defecto necesario para vector::resize + WifiNetworkInfo() = default; + + WifiNetworkInfo(String ssid_, String bssid_, int channel_, String encryption_, int rssi_) + : ssid(ssid_), + bssid(bssid_), + channel(channel_), + encryption(encryption_), + avgRssi(rssi_), + rssiSamples(1), + notSeenCount(0), + seenInThisScan(true) {} +}; + +static std::vector wifiNetworks; +static bool wifiScanInProgress = false; +static unsigned long lastConnectAttemptMs = 0; + bool ESP8266Utils_Connect_STA( const char* ssid, const char* password, @@ -141,3 +174,159 @@ bool ESP8266Utils_update_WeatherData(struct Settings* myData) { client.stop(); return true; } + +void ESP8266Utils_clearWifiNetworksList() { + wifiNetworks.clear(); + Serial.println("Lista de redes WiFi borrada completamente."); +} + +// Compara por RSSI promedio, descendente +bool compareByRssiDesc(const WifiNetworkInfo& a, const WifiNetworkInfo& b) { + return a.avgRssi > b.avgRssi; +} + +// Elimina redes no vistas en los últimos 5 escaneos +void cleanNetworksNotSeen() { + for (auto it = wifiNetworks.begin(); it != wifiNetworks.end();) { + if (!it->seenInThisScan) { + it->notSeenCount++; + } else { + it->notSeenCount = 0; + } + + if (it->notSeenCount >= WIFI_SCAN_NOT_SEEN_MAX_COUNT) { + it = wifiNetworks.erase(it); + } else { + ++it; + } + } +} + +// Inserta o actualiza una red escaneada +void updateOrInsertNetwork( + const String& ssid, + const String& bssid, + int channel, + const String& encryption, + int rssi) { + for (auto& net : wifiNetworks) { + if (net.bssid == bssid) { + net.avgRssi = (net.avgRssi * net.rssiSamples + rssi) / (net.rssiSamples + 1); + net.rssiSamples++; + net.seenInThisScan = true; + net.notSeenCount = 0; + return; + } + } + + // Filtro para evitar agregar redes volátiles de 1 sola muestra + if (rssi < WIFI_SCAN_MINIMUM_RSSI_FOR_TRACKING) { + // Si la red es débil y no estaba antes, ignorarla + return; + } + + wifiNetworks.emplace_back(ssid, bssid, channel, encryption, rssi); +} + +// Inicia el escaneo WiFi si no está en curso +void ESP8266Utils_startWifiScanIfNeeded(void) { + if (!wifiScanInProgress) { + if (WiFi.status() == WL_CONNECTED) { + WiFi.disconnect(); + } + WiFi.mode(WIFI_STA); + WiFi.scanNetworks(true); // Async scan + wifiScanInProgress = true; + Serial.println("Started async WiFi scan..."); + } +} + +// Verifica si ya terminó el escaneo y actualiza la lista +void ESP8266Utils_checkScanResults() { + if (wifiScanInProgress) { + int n = WiFi.scanComplete(); + if (n == WIFI_SCAN_RUNNING) { + return; + } else if (n < 0) { + Serial.println("Scan failed"); + wifiScanInProgress = false; + return; + } + + wifiScanInProgress = false; + + // Marcar todas como no vistas inicialmente + for (auto& net : wifiNetworks) { + net.seenInThisScan = false; + } + + for (int i = 0; i < n; ++i) { + String ssid = WiFi.SSID(i); + int32_t rssi = WiFi.RSSI(i); + String bssid = WiFi.BSSIDstr(i); + uint8_t channel = WiFi.channel(i); + String encryptionType = WiFi.encryptionType(i) == ENC_TYPE_NONE ? "None" : "Encrypted"; + + updateOrInsertNetwork(ssid, bssid, channel, encryptionType, rssi); + } + + cleanNetworksNotSeen(); + + std::sort(wifiNetworks.begin(), wifiNetworks.end(), compareByRssiDesc); + + if (wifiNetworks.size() > WIFI_SCAN_MAX_TRACKED_NETWORKS_COUNT) { + wifiNetworks.resize(WIFI_SCAN_MAX_TRACKED_NETWORKS_COUNT); + } + + Serial.println("Lista de redes ordenada por RSSI promedio:"); + for (size_t i = 0; i < wifiNetworks.size(); ++i) { + const auto& net = wifiNetworks[i]; + Serial.printf( + "%d: %s (%0.1f dBm avg, %d muestras) [%s] Canal: %d, Encriptación: %s\n", + int(i + 1), + net.ssid.c_str(), + net.avgRssi, + net.rssiSamples, + net.bssid.c_str(), + net.channel, + net.encryption.c_str()); + } + } +} + +bool ESP8266Utils_getSsidAtIndex(int index, String& outSsid) { + if (index < 0 || index >= static_cast(wifiNetworks.size())) { + return false; + } + outSsid = wifiNetworks[index].ssid; + return true; +} + +int ESP8266Utils_getTrackedNetworkCount() { return static_cast(wifiNetworks.size()); } + +int ESP8266Utils_getIndexBySsid(const String& targetSsid) { + for (size_t i = 0; i < wifiNetworks.size(); ++i) { + if (wifiNetworks[i].ssid == targetSsid) { + return static_cast(i); + } + } + return -1; // No encontrado +} + +void ESP8266Utils_connectToWifi(const String& ssid, const String& password) { + WiFi.mode(WIFI_STA); + WiFi.begin(ssid.c_str(), password.c_str()); + lastConnectAttemptMs = millis(); +} + +bool ESP8266Utils_isWifiConnected() { return WiFi.status() == WL_CONNECTED; } + +int ESP8266Utils_getWifiConnectionPercentage() { + if (lastConnectAttemptMs == 0) { + return 0; + } + unsigned long elapsed = millis() - lastConnectAttemptMs; + return (elapsed * 100) / WIFI_CONNECTION_TIMEOUT_MS; +} + +void Wifi_handler() {} diff --git a/Firmware_ESP8266/Firmware_ESP8266.ino b/Firmware_ESP8266/Firmware_ESP8266.ino index 582579e..5fd1e8e 100644 --- a/Firmware_ESP8266/Firmware_ESP8266.ino +++ b/Firmware_ESP8266/Firmware_ESP8266.ino @@ -85,4 +85,5 @@ void loop() { buzzer_handler(); shutterHandler(); EEPROM_Handler(); + Wifi_handler(); } diff --git a/Firmware_ESP8266/basic_defines.h b/Firmware_ESP8266/basic_defines.h index 729d0f6..d4b3f46 100644 --- a/Firmware_ESP8266/basic_defines.h +++ b/Firmware_ESP8266/basic_defines.h @@ -53,7 +53,10 @@ enum seleccionMenu { SELECCION_MENU_CONFIG_DEBUG, SELECCION_MENU_CONFIG_DEBUG_SOFT_RST_COUNT, SELECCION_MENU_CONFIG_WIFI, + SELECCION_MENU_CONFIG_WIFI_HABILITAR, + SELECCION_MENU_CONFIG_WIFI_SSID, SELECCION_MENU_CONFIG_WIFI_PASSWORD, + SELECCION_MENU_CONFIG_WIFI_RESULTADO, SELECCION_MENU_MAX }; diff --git a/Firmware_ESP8266/lcd.h b/Firmware_ESP8266/lcd.h index 3e4c6f1..04b3b78 100644 --- a/Firmware_ESP8266/lcd.h +++ b/Firmware_ESP8266/lcd.h @@ -19,6 +19,7 @@ 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; @@ -28,7 +29,11 @@ static unsigned long buttonHoldingTimeMs = 0; #define LCD_SPECIAL_CHAR_DOWN_ARROW (char)(LCD_SPECIAL_CHAR_BASE + 2) #define LCD_SPECIAL_CHAR_RIGHT_ARROW (char)(LCD_SPECIAL_CHAR_BASE + 3) #define LCD_SPECIAL_CHAR_STOP_ARROW (char)(LCD_SPECIAL_CHAR_BASE + 4) -#define LCD_SPECIAL_CHAR_UP_ARROW_CAN (char)(LCD_SPECIAL_CHAR_BASE + 5) +#define LCD_SPECIAL_CHAR_CONNECTED_SYMBOL (char)(LCD_SPECIAL_CHAR_BASE + 5) +#define LCD_SPECIAL_CHAR_WARNING_SYMBOL (char)(LCD_SPECIAL_CHAR_BASE + 6) +// Keep this in sync with the number of custom characters defined above. +// Maximum is 8, but we use only 7. +#define LCD_SPECIAL_CHAR_TOTAL_CHARS (7) #define LCD_SLIDE_OR_FLASH_SPEED_MS (500) #define LCD_HOLDING_TIME_CONFIRM_MS (2000) @@ -44,13 +49,15 @@ void _sendLcdBuffer(String line1, String line2) { _lcd.clear(); for (int i = 0; i < 16; i++) { _lcd.setCursor(i, 0); - if ((line1[i] >= LCD_SPECIAL_CHAR_BASE) && (line1[i] < LCD_SPECIAL_CHAR_BASE + 6)) { + if ((line1[i] >= LCD_SPECIAL_CHAR_BASE) + && (line1[i] < LCD_SPECIAL_CHAR_BASE + LCD_SPECIAL_CHAR_TOTAL_CHARS)) { _lcd.write(line1[i] - LCD_SPECIAL_CHAR_BASE); } else { _lcd.print(line1[i]); } _lcd.setCursor(i, 1); - if ((line2[i] >= LCD_SPECIAL_CHAR_BASE) && (line2[i] < LCD_SPECIAL_CHAR_BASE + 6)) { + if ((line2[i] >= LCD_SPECIAL_CHAR_BASE) + && (line2[i] < LCD_SPECIAL_CHAR_BASE + LCD_SPECIAL_CHAR_TOTAL_CHARS)) { _lcd.write(line2[i] - LCD_SPECIAL_CHAR_BASE); } else { _lcd.print(line2[i]); @@ -90,20 +97,23 @@ bool pantalla_iniciar(int32_t timeout_ms) { } _lcd.begin(16, 2); - uint8_t customArrayChar[6][8] = { - /* Flecha izquierda */ { 0x00, 0x07, 0x0E, 0x1C, 0x1C, 0x0E, 0x07, 0x00 }, - /* Flecha arriba */ + uint8_t customArrayChar[LCD_SPECIAL_CHAR_TOTAL_CHARS][8] = { + // LCD_SPECIAL_CHAR_LEFT_ARROW + { 0x00, 0x07, 0x0E, 0x1C, 0x1C, 0x0E, 0x07, 0x00 }, + // LCD_SPECIAL_CHAR_UP_ARROW { 0x00, 0x04, 0x0E, 0x1F, 0x1B, 0x11, 0x00, 0x00 }, - /* Flecha abajo */ + // LCD_SPECIAL_CHAR_DOWN_ARROW { 0x00, 0x00, 0x11, 0x1B, 0x1F, 0x0E, 0x04, 0x00 }, - /* Flecha derecha */ + // LCD_SPECIAL_CHAR_RIGHT_ARROW { 0x00, 0x1C, 0x0E, 0x07, 0x07, 0x0E, 0x1C, 0x00 }, - /* Flecha STOP */ + // LCD_SPECIAL_CHAR_STOP_ARROW { 0x00, 0x0E, 0x1B, 0x11, 0x11, 0x1B, 0x0E, 0x00 }, - /* Flecha arribacan */ - { 0x04, 0x0E, 0x1F, 0x15, 0x04, 0x04, 0x07, 0x00 } + // LCD_SPECIAL_CHAR_CONNECTED_SYMBOL + { 0x04, 0x0E, 0x1B, 0x1B, 0x0E, 0x04, 0x04, 0x00 }, + // LCD_SPECIAL_CHAR_WARNING_SYMBOL + { 0x0E, 0x11, 0x15, 0x15, 0x11, 0x15, 0x11, 0x0E } }; - for (int i = 0; i < 6; i++) { + for (int i = 0; i < LCD_SPECIAL_CHAR_TOTAL_CHARS; i++) { _lcd.createChar(i, customArrayChar[i]); } return true; @@ -359,36 +369,82 @@ void pantalla_handleButtonInMenu( case BUTTON_STATUS_UP: newMenu = SELECCION_MENU_CONFIG_DEBUG; break; + case BUTTON_STATUS_RIGHT: + ESP8266Utils_clearWifiNetworksList(); + newMenu = SELECCION_MENU_CONFIG_WIFI_SSID; + break; + case BUTTON_STATUS_DOWN: + case BUTTON_STATUS_LEFT: + buzzer_sound_error(); + 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. + // Do as much as possible RSSI avg, so we can display the list ASAP, since it's cleared + // each time we enter this menu. + ESP8266Utils_startWifiScanIfNeeded(); + ESP8266Utils_checkScanResults(); + 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); + if ((currentIndex < 0) && (countNetworks > 0) && (previousCurrentIndex > 0)) { + currentIndex = previousCurrentIndex - 1; + } else if (currentIndex < 0) { + currentIndex = 0; + } + previousCurrentIndex = currentIndex; + + ESP8266Utils_getSsidAtIndex(currentIndex, adjustedSSID); + + switch (currentButtonPressed) { + case BUTTON_STATUS_UP: + if (currentIndex > 0) { + currentIndex--; + } + ESP8266Utils_getSsidAtIndex(currentIndex, adjustedSSID); + break; case BUTTON_STATUS_RIGHT: adjustedPassword = ""; newMenu = SELECCION_MENU_CONFIG_WIFI_PASSWORD; break; case BUTTON_STATUS_DOWN: + if (currentIndex < (countNetworks - 1)) { + currentIndex++; + } + ESP8266Utils_getSsidAtIndex(currentIndex, adjustedSSID); + break; case BUTTON_STATUS_LEFT: - buzzer_sound_error(); + newMenu = SELECCION_MENU_CONFIG_WIFI; break; } break; - case SELECCION_MENU_CONFIG_WIFI_PASSWORD: + } + case SELECCION_MENU_CONFIG_WIFI_PASSWORD: { static uint8_t previousButtonHolding = BUTTON_STATUS_NONE; + unsigned long now = millis(); + if ((previousButtonHolding != currentButtonHolding) && (currentButtonHolding != BUTTON_STATUS_NONE)) { - buttonHoldingTimeMs = millis(); + buttonHoldingTimeMs = now; previousButtonHolding = currentButtonHolding; } else if ( (previousButtonHolding != currentButtonHolding) && (currentButtonHolding == BUTTON_STATUS_NONE)) { - if ((millis() - buttonHoldingTimeMs) >= LCD_HOLDING_TIME_CONFIRM_MS) { + if ((now - buttonHoldingTimeMs) >= LCD_HOLDING_TIME_CONFIRM_MS) { if (previousButtonHolding == BUTTON_STATUS_DOWN) { buzzer_sound_accept(); - newMenu = SELECCION_MENU_CONFIG_WIFI; + newMenu = SELECCION_MENU_CONFIG_WIFI_RESULTADO; + ESP8266Utils_connectToWifi(adjustedSSID, adjustedPassword); } } else { switch (previousButtonHolding) { case BUTTON_STATUS_LEFT: if (adjustedPassword.length() == 0) { - newMenu = SELECCION_MENU_CONFIG_WIFI; + newMenu = SELECCION_MENU_CONFIG_WIFI_SSID; } else { adjustedPassword.remove(adjustedPassword.length() - 1); } @@ -404,13 +460,49 @@ void pantalla_handleButtonInMenu( } break; case BUTTON_STATUS_RIGHT: - adjustedPassword += 'a'; + adjustedPassword += 'A'; break; } } previousButtonHolding = currentButtonHolding; } 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(); + newMenu = SELECCION_MENU_CONFIG_WIFI; + } else { + buzzer_sound_error(); + } + break; + case BUTTON_STATUS_LEFT: + newMenu = SELECCION_MENU_CONFIG_WIFI_PASSWORD; + break; + case BUTTON_STATUS_DOWN: + case BUTTON_STATUS_UP: + buzzer_sound_error(); + break; + } + break; + } } *currentMenu = newMenu; } @@ -712,6 +804,89 @@ void pantalla_actualizarMenuConfigWifiPassword(String* lcdBuffer) { } } +void pantalla_actualizarMenuConfigWifiSsid(String* lcdBuffer) { + *lcdBuffer += String("SSID:"); + + String mySSID = adjustedSSID; + int adjustedSSIDindex = ESP8266Utils_getIndexBySsid(adjustedSSID); + if (adjustedSSIDindex < 0) { + mySSID = "No hay redes"; + } + + static unsigned long lastScrollTime = 0; + static unsigned int scrollOffset = 0; + + // Espacio visible para SSID + const int visibleChars = 11; + + // Añadir separación al final para el efecto loop + String scrollingSSID = mySSID + " "; // espacio entre loops + + // Actualizar desplazamiento cada 200ms + if (millis() - lastScrollTime >= LCD_SLIDE_OR_FLASH_SPEED_MS) { + lastScrollTime = millis(); + scrollOffset++; + if (scrollOffset >= scrollingSSID.length()) { + scrollOffset = 0; + } + } + + // Construir ventana deslizante + String scrolled; + for (int i = 0; i < visibleChars; ++i) { + int charIndex = (scrollOffset + i) % scrollingSSID.length(); + scrolled += scrollingSSID.charAt(charIndex); + } + + *lcdBuffer += scrolled; + + // Asegurar longitud mínima de 16 + while (lcdBuffer->length() < 16) { + *lcdBuffer += " "; + } + + // Agregar navegación + *lcdBuffer += String("< "); + *lcdBuffer += LCD_SPECIAL_CHAR_UP_ARROW; + adjustedSSIDindex++; + if (adjustedSSIDindex == 0) { + *lcdBuffer += String(" "); + } else if (adjustedSSIDindex < 10) { + *lcdBuffer += String("0"); + *lcdBuffer += String(adjustedSSIDindex); + } else { + *lcdBuffer += String(adjustedSSIDindex); + } + *lcdBuffer += LCD_SPECIAL_CHAR_DOWN_ARROW; + *lcdBuffer += String(" >"); +} + +void pantalla_actualizarMenuConfigWifiResultado(String* lcdBuffer) { + int porcentaje = ESP8266Utils_getWifiConnectionPercentage(); + if (porcentaje > 100) { + porcentaje = 100; // Limit to 100% + } + if (ESP8266Utils_isWifiConnected()) { + *lcdBuffer += String("WIFI: CONECTADO "); + *lcdBuffer += String("< OK>"); + } else { + if (porcentaje == 100) { + *lcdBuffer += String("WIFI: FALLIDO "); + } else { + *lcdBuffer += String("WIFI: ESPERE "); + } + *lcdBuffer += String("< ["); + for (int i = 0; i < 10; i++) { + if (i < (porcentaje / 10)) { + *lcdBuffer += String("="); + } else { + *lcdBuffer += String(" "); + } + } + *lcdBuffer += String("] "); + } +} + void pantalla_actualizarMenu(uint8_t selectedMenu) { String lcdBuffer = ""; switch (selectedMenu) { @@ -754,9 +929,15 @@ void pantalla_actualizarMenu(uint8_t selectedMenu) { case SELECCION_MENU_CONFIG_WIFI: pantalla_actualizarMenuConfigWifi(&lcdBuffer); break; + case SELECCION_MENU_CONFIG_WIFI_SSID: + pantalla_actualizarMenuConfigWifiSsid(&lcdBuffer); + break; case SELECCION_MENU_CONFIG_WIFI_PASSWORD: pantalla_actualizarMenuConfigWifiPassword(&lcdBuffer); break; + case SELECCION_MENU_CONFIG_WIFI_RESULTADO: + pantalla_actualizarMenuConfigWifiResultado(&lcdBuffer); + break; } pantalla_sendLcdBuffer(lcdBuffer); }