Added client folder for the client side of the project

Made the network component shared between two firmawre
This commit is contained in:
2026-03-17 21:09:17 -04:00
parent 2c79be36ef
commit e4b8ab4586
15 changed files with 323 additions and 83 deletions
+12 -1
View File
@@ -152,6 +152,16 @@ dependencies:
registry_url: https://components.espressif.com/
type: service
version: 9.4.0
network:
dependencies:
- name: idf
version: '>=4.1.0'
- name: espressif/ethernet_init
version: ^1.3.0
source:
path: C:\Dev\Classified\Calendink\components\network
type: local
version: 1.0.0
direct_dependencies:
- espressif/ethernet_init
- espressif/led_strip
@@ -159,6 +169,7 @@ direct_dependencies:
- idf
- joltwallet/littlefs
- lvgl/lvgl
manifest_hash: 0c7ea64d32655d6be4f726b7946e96626bce0de88c2dc8f091bb5e365d26a374
- network
manifest_hash: adc42d97d037e4815c3e1d03227cf1a5b29b8a914aa24fefa5760edd541a6bac
target: esp32s3
version: 2.0.0
+1 -2
View File
@@ -1,8 +1,7 @@
idf_component_register(SRCS "main.cpp"
# Needed as we use minimal build
PRIV_REQUIRES esp_http_server esp_eth
esp_wifi nvs_flash esp_netif vfs
json app_update esp_timer esp_psram mdns driver
json app_update esp_timer esp_psram mdns driver network
INCLUDE_DIRS ".")
if(CONFIG_CALENDINK_DEPLOY_WEB_PAGES)
-73
View File
@@ -1,76 +1,3 @@
menu "CalendarInk Network Configuration"
config CALENDINK_WIFI_SSID
string "WiFi SSID"
default ""
help
SSID (network name) for the WiFi connection.
config CALENDINK_WIFI_PASSWORD
string "WiFi Password"
default ""
help
Password for the WiFi connection.
config CALENDINK_ETH_RETRIES
int "Maximum Ethernet Connection Retries"
default 5
help
Number of times to retry the Ethernet connection before falling back to WiFi.
config CALENDINK_WIFI_RETRIES
int "Maximum WiFi Connection Retries"
default 5
help
Number of times to retry the WiFi connection before failing completely.
config CALENDINK_BLINK_IP
bool "Blink last IP digit on connect"
default n
help
If enabled, the LED will blink the last digit of the IP address
acquired to assist in debugging.
config CALENDINK_MDNS_HOSTNAME
string "mDNS Hostname"
default "calendink"
help
The hostname to use for mDNS. The device will be accessible
at <hostname>.local. (e.g., calendink.local)
config CALENDINK_UDP_LOG_TARGET_IP
string "UDP Logger Target IP Address"
default ""
help
The IP address to send UDP logs to via port 514.
If left blank, logs will be broadcast to 255.255.255.255.
choice CALENDINK_WIFI_PS_MODE
prompt "WiFi Power Save Mode"
default CALENDINK_WIFI_PS_NONE
help
Select the WiFi power save mode to balance power consumption and network stability.
config CALENDINK_WIFI_PS_NONE
bool "None (No power save, highest consumption)"
config CALENDINK_WIFI_PS_MIN_MODEM
bool "Minimum Modem (Wakes on beacon, balanced)"
config CALENDINK_WIFI_PS_MAX_MODEM
bool "Maximum Modem (Lowest consumption, may drop connection on strict routers)"
endchoice
config CALENDINK_ALLOW_LIGHT_SLEEP
bool "Allow Light Sleep (Tickless Idle)"
default n
help
If enabled, the device will heavily use light sleep to reduce power
consumption. Note that this may BREAK the UART console monitor since the
CPU sleeps and halts the UART! Use UDP logging if you need logs
while light sleep is enabled.
endmenu
menu "Calendink Web Server"
-455
View File
@@ -1,455 +0,0 @@
// C STD lib
#include <assert.h>
#include <string.h>
// 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,
&ethernet_event_handler));
ESP_ERROR_CHECK(esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID,
&ethernet_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,
&ethernet_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID,
&ethernet_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());
// Set Power Save mode based on sdkconfig selection
#if defined(CONFIG_CALENDINK_WIFI_PS_MAX_MODEM)
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_MAX_MODEM));
#elif defined(CONFIG_CALENDINK_WIFI_PS_NONE)
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
#else
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_MIN_MODEM));
#endif
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
+2
View File
@@ -19,3 +19,5 @@ dependencies:
espressif/ethernet_init: ^1.3.0
joltwallet/littlefs: "^1.20" # https://github.com/joltwallet/esp_littlefs
lvgl/lvgl: "9.4.0"
network:
path: "../../components/network"
+1 -2
View File
@@ -16,13 +16,12 @@
#include "soc/gpio_num.h"
// Project headers
#include "appstate.hpp"
#include "types.hpp"
#include "network.hpp"
// Project cpp (Unity Build entry)
// clang-format off
#include "led_status.cpp"
#include "connect.cpp"
#include "http_server.cpp"
#include "mdns_service.cpp"
#include "udp_logger.cpp"