// C STD lib #include #include // SDK #include "driver/gpio.h" #include "esp_err.h" #include "esp_eth.h" #include "esp_eth_driver.h" #include "esp_event.h" #include "esp_log.h" #include "esp_sleep.h" #include "esp_wifi.h" #include "ethernet_init.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/timers.h" // Project includes #include "types.hpp" // Forward declarations #if CONFIG_CALENDINK_BLINK_IP internal esp_err_t get_ip_info(esp_netif_ip_info_t *ip_info); internal void led_blink_number(int n, uint8_t r, uint8_t g, uint8_t b); internal void blink_last_ip_octet(); #endif // Internal states internal esp_netif_ip_info_t s_current_ip_info = {}; // === Ethernet === internal constexpr char kLogEthernet[] = "ETH"; internal constexpr char kNetifDescEth[] = "eth0"; internal esp_eth_handle_t *s_eth_handles = nullptr; internal uint8_t s_eth_count = 0; internal esp_eth_netif_glue_handle_t s_eth_glue = nullptr; internal esp_netif_t *s_eth_netif = nullptr; internal SemaphoreHandle_t s_semph_get_ip_addrs = nullptr; internal SemaphoreHandle_t s_semph_eth_link = nullptr; internal volatile bool s_eth_link_up = false; #ifndef NDEBUG internal bool s_ethernet_connected = false; #endif void ethernet_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == IP_EVENT && event_id == IP_EVENT_ETH_GOT_IP) { ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; if (event->esp_netif != s_eth_netif) { return; } ESP_LOGI(kLogEthernet, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip)); s_current_ip_info = event->ip_info; xSemaphoreGive(s_semph_get_ip_addrs); } else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_CONNECTED) { ESP_LOGI(kLogEthernet, "Ethernet Link Up"); s_eth_link_up = true; if (s_semph_eth_link) { xSemaphoreGive(s_semph_eth_link); } } else if (event_base == ETH_EVENT && event_id == ETHERNET_EVENT_DISCONNECTED) { ESP_LOGI(kLogEthernet, "Ethernet Link Down"); s_eth_link_up = false; if (s_semph_eth_link) { xSemaphoreGive(s_semph_eth_link); } } } void teardown_ethernet() { if (s_semph_eth_link != nullptr) { vSemaphoreDelete(s_semph_eth_link); s_semph_eth_link = nullptr; } if (s_semph_get_ip_addrs != nullptr) { vSemaphoreDelete(s_semph_get_ip_addrs); s_semph_get_ip_addrs = nullptr; } if (s_eth_netif) { ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, ðernet_event_handler)); ESP_ERROR_CHECK(esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, ðernet_event_handler)); ESP_ERROR_CHECK(esp_eth_stop(s_eth_handles[0])); ESP_ERROR_CHECK(esp_eth_del_netif_glue(s_eth_glue)); esp_netif_destroy(s_eth_netif); ethernet_deinit_all(s_eth_handles); } s_eth_glue = nullptr; s_eth_netif = nullptr; s_eth_handles = nullptr; s_eth_count = 0; esp_netif_deinit(); } internal esp_err_t connect_ethernet(bool blockUntilIPAcquired) { #ifndef NDEBUG assert(!s_ethernet_connected && "Ethernet connect called but already connected!"); s_ethernet_connected = true; #endif s_semph_get_ip_addrs = xSemaphoreCreateBinary(); s_semph_eth_link = xSemaphoreCreateBinary(); if (s_semph_get_ip_addrs == NULL || s_semph_eth_link == NULL) { return ESP_ERR_NO_MEM; } // Connection is split in two steps. First we open the connection and ask for // an ip. Second a semaphor will block until the ip is acquired. If we dont // block then the user have to verify the semaphore before continuing. ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(ethernet_init_all(&s_eth_handles, &s_eth_count)); esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH(); // Warning: the interface desc is used in tests to capture actual connection // details (IP, gw, mask) esp_netif_config.if_desc = kNetifDescEth; esp_netif_config.route_prio = 64; esp_netif_config_t netif_config = {.base = &esp_netif_config, .driver = nullptr, .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH}; s_eth_netif = esp_netif_new(&netif_config); s_eth_glue = esp_eth_new_netif_glue(s_eth_handles[0]); esp_netif_attach(s_eth_netif, s_eth_glue); // Register user defined event handlers ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, ðernet_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ðernet_event_handler, NULL)); ESP_ERROR_CHECK(esp_eth_start(s_eth_handles[0])); // Will teardown connection properly on shutdown ESP_ERROR_CHECK(esp_register_shutdown_handler(&teardown_ethernet)); if (blockUntilIPAcquired) { ESP_LOGI(kLogEthernet, "Waiting for IP address."); xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY); #if CONFIG_CALENDINK_BLINK_IP blink_last_ip_octet(); #endif } return ESP_OK; } void disconnect_ethernet() { #ifndef NDEBUG assert(s_ethernet_connected && "Ethernet disconnect called but not connected!"); s_ethernet_connected = false; #endif teardown_ethernet(); ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&teardown_ethernet)); } internal esp_err_t check_ethernet_connection(uint32_t timeoutSeconds) { // Wait up to 5000ms for the physical link to negotiate if (!s_eth_link_up) { if (!xSemaphoreTake(s_semph_eth_link, pdMS_TO_TICKS(5000))) { ESP_LOGE( kLogEthernet, "No physical Ethernet link detected (Timeout). Skipping DHCP wait."); return ESP_ERR_INVALID_STATE; // Special error to skip retries } if (!s_eth_link_up) { ESP_LOGE(kLogEthernet, "No physical Ethernet link detected " "(Disconnected). Skipping DHCP wait."); return ESP_ERR_INVALID_STATE; } } ESP_LOGI(kLogEthernet, "Waiting for IP address for %d seconds.", timeoutSeconds); if (xSemaphoreTake(s_semph_get_ip_addrs, pdMS_TO_TICKS(timeoutSeconds * 1000))) { #if CONFIG_CALENDINK_BLINK_IP blink_last_ip_octet(); #endif return ESP_OK; } else { return ESP_ERR_TIMEOUT; } } // === WiFi === internal constexpr char kLogWifi[] = "WIFI"; internal esp_netif_t *s_wifi_netif = nullptr; internal SemaphoreHandle_t s_semph_get_wifi_ip_addrs = nullptr; internal SemaphoreHandle_t s_semph_wifi_link = nullptr; internal volatile bool s_wifi_link_up = false; #ifndef NDEBUG internal bool s_wifi_connected = false; #endif void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { ESP_LOGI(kLogWifi, "WiFi associated with AP."); s_wifi_link_up = true; if (s_semph_wifi_link) { xSemaphoreGive(s_semph_wifi_link); } } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { ESP_LOGI(kLogWifi, "WiFi disconnected."); s_wifi_link_up = false; if (s_semph_wifi_link) { xSemaphoreGive(s_semph_wifi_link); } } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; if (event->esp_netif != s_wifi_netif) { return; } ESP_LOGI(kLogWifi, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip)); s_current_ip_info = event->ip_info; xSemaphoreGive(s_semph_get_wifi_ip_addrs); } } void teardown_wifi() { if (s_semph_wifi_link != nullptr) { vSemaphoreDelete(s_semph_wifi_link); s_semph_wifi_link = nullptr; } if (s_semph_get_wifi_ip_addrs != nullptr) { vSemaphoreDelete(s_semph_get_wifi_ip_addrs); s_semph_get_wifi_ip_addrs = nullptr; } esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &wifi_event_handler); esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &wifi_event_handler); esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler); if (s_wifi_netif) { esp_wifi_disconnect(); esp_wifi_stop(); esp_wifi_deinit(); esp_netif_destroy(s_wifi_netif); s_wifi_netif = nullptr; } } internal esp_err_t connect_wifi(const char *ssid, const char *password, bool blockUntilIPAcquired) { #ifndef NDEBUG assert(!s_wifi_connected && "WiFi connect called but already connected!"); s_wifi_connected = true; #endif s_semph_get_wifi_ip_addrs = xSemaphoreCreateBinary(); s_semph_wifi_link = xSemaphoreCreateBinary(); if (s_semph_get_wifi_ip_addrs == NULL || s_semph_wifi_link == NULL) { return ESP_ERR_NO_MEM; } // esp_netif_init() is already called by Ethernet, but safe to call multiple // times or we can assume it's initialized. s_wifi_netif = esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); ESP_ERROR_CHECK(esp_event_handler_register( WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &wifi_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register( WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &wifi_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL)); wifi_config_t wifi_config = {}; strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1); strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1); // Setting a default auth mode if password is provided wifi_config.sta.threshold.authmode = password[0] != '\0' ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); // Reverting to MIN_MODEM because MAX_MODEM causes disconnects on many routers ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_MAX_MODEM)); ESP_ERROR_CHECK(esp_wifi_connect()); ESP_ERROR_CHECK(esp_register_shutdown_handler(&teardown_wifi)); if (blockUntilIPAcquired) { ESP_LOGI(kLogWifi, "Waiting for IP address."); xSemaphoreTake(s_semph_get_wifi_ip_addrs, portMAX_DELAY); #if CONFIG_CALENDINK_BLINK_IP blink_last_ip_octet(); #endif } return ESP_OK; } void disconnect_wifi() { #ifndef NDEBUG assert(s_wifi_connected && "WiFi disconnect called but not connected!"); s_wifi_connected = false; #endif teardown_wifi(); ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&teardown_wifi)); } internal esp_err_t check_wifi_connection(uint32_t timeoutSeconds) { // Wait up to 10000ms for the physical link to associate with the AP if (!s_wifi_link_up) { ESP_LOGI(kLogWifi, "Physical link is down. Requesting immediate AP association..."); esp_err_t err = esp_wifi_connect(); if (err != ESP_OK && err != ESP_ERR_WIFI_CONN) { ESP_LOGE(kLogWifi, "esp_wifi_connect failed: %s", esp_err_to_name(err)); } if (!xSemaphoreTake(s_semph_wifi_link, pdMS_TO_TICKS(10000))) { ESP_LOGE(kLogWifi, "Failed to associate with WiFi AP (Timeout)."); return ESP_ERR_TIMEOUT; // Return timeout so main.cpp triggers a retry } // After semaphore is taken, check if it was because of a disconnect if (!s_wifi_link_up) { ESP_LOGE(kLogWifi, "Failed to associate with WiFi AP (Disconnected)."); return ESP_ERR_TIMEOUT; } } ESP_LOGI(kLogWifi, "Waiting for IP address for %d seconds.", timeoutSeconds); if (xSemaphoreTake(s_semph_get_wifi_ip_addrs, pdMS_TO_TICKS(timeoutSeconds * 1000))) { #if CONFIG_CALENDINK_BLINK_IP blink_last_ip_octet(); #endif return ESP_OK; } else { return ESP_ERR_TIMEOUT; } } #if CONFIG_CALENDINK_BLINK_IP internal esp_err_t get_ip_info(esp_netif_ip_info_t *ip_info) { *ip_info = s_current_ip_info; return ESP_OK; } internal void blink_last_ip_octet() { esp_netif_ip_info_t ip_info; if (get_ip_info(&ip_info) == ESP_OK) { uint8_t last_octet = (ip_info.ip.addr >> 24) & 0xFF; printf("IP Address: " IPSTR "\n", IP2STR(&ip_info.ip)); // Blink digits of last_octet int h = last_octet / 100; int t = (last_octet / 10) % 10; int u = last_octet % 10; if (h > 0) { led_blink_number(h, 255, 255, 255); } if (h > 0 || t > 0) { led_blink_number(t, 255, 255, 255); } led_blink_number(u, 255, 255, 255); } } #endif