diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 87771043ce..4a70a5ad44 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,8 +57,8 @@ repos: - repo: local hooks: - id: commit message scopes - name: "commit message must be scoped with: mdns, dns, modem, websocket, asio, mqtt_cxx, console, common, eppp, tls_cxx, mosq, sockutls, lws, modem_sim" - entry: '\A(?!(feat|fix|ci|bump|test|docs|chore)\((mdns|dns|modem|common|console|websocket|asio|mqtt_cxx|examples|eppp|tls_cxx|mosq|sockutls|lws|modem_sim)\)\:)' + name: "commit message must be scoped with: mdns, dns, modem, websocket, asio, mqtt_cxx, console, common, eppp, tls_cxx, mosq, sockutls, lws, modem_sim, net_connect" + entry: '\A(?!(feat|fix|ci|bump|test|docs|chore)\((mdns|dns|modem|common|console|websocket|asio|mqtt_cxx|examples|eppp|tls_cxx|mosq|sockutls|lws|modem_sim|net_connect)\)\:)' language: pygrep args: [--multiline] stages: [commit-msg] diff --git a/components/net_connect/CMakeLists.txt b/components/net_connect/CMakeLists.txt new file mode 100644 index 0000000000..dfe791f217 --- /dev/null +++ b/components/net_connect/CMakeLists.txt @@ -0,0 +1,49 @@ +idf_build_get_property(target IDF_TARGET) + +if(${target} STREQUAL "linux") + # Header only library for linux + + idf_component_register(INCLUDE_DIRS include) + return() +endif() + +set(srcs "stdin_out.c" + "connect.c" + "wifi_connect.c") + +if(CONFIG_NET_CONNECT_PROVIDE_WIFI_CONSOLE_CMD) + list(APPEND srcs "console_cmd.c") +endif() + +if(CONFIG_NET_CONNECT_ETHERNET) + list(APPEND srcs "eth_connect.c") +endif() + +if(CONFIG_NET_CONNECT_THREAD) + list(APPEND srcs "thread_connect.c") +endif() + +if(CONFIG_NET_CONNECT_PPP) + list(APPEND srcs "ppp_connect.c") +endif() + + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS "include" + PRIV_REQUIRES esp_netif esp_driver_gpio esp_driver_uart esp_wifi vfs openthread) + +if(CONFIG_NET_CONNECT_PROVIDE_WIFI_CONSOLE_CMD) + idf_component_optional_requires(PRIVATE console) +endif() + +if(CONFIG_NET_CONNECT_ETHERNET) + idf_component_optional_requires(PRIVATE esp_eth) +endif() + +if(CONFIG_NET_CONNECT_THREAD) + idf_component_optional_requires(PRIVATE openthread) +endif() + +if(CONFIG_NET_CONNECT_PPP) + idf_component_optional_requires(PRIVATE esp_tinyusb espressif__esp_tinyusb) +endif() diff --git a/components/net_connect/Kconfig.projbuild b/components/net_connect/Kconfig.projbuild new file mode 100644 index 0000000000..545543f37e --- /dev/null +++ b/components/net_connect/Kconfig.projbuild @@ -0,0 +1,316 @@ +menu "Net Connect Configuration" + + orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps" + + config NET_CONNECT_WIFI + bool "connect using WiFi interface" + depends on !IDF_TARGET_LINUX && (SOC_WIFI_SUPPORTED || ESP_WIFI_REMOTE_ENABLED || ESP_HOST_WIFI_ENABLED) + default y if SOC_WIFI_SUPPORTED + help + Protocol examples can use Wi-Fi, Ethernet and/or Thread to connect to the network. + Choose this option to connect with WiFi + + if NET_CONNECT_WIFI + config NET_CONNECT_WIFI_SSID_PWD_FROM_STDIN + bool "Get ssid and password from stdin" + default n + help + Give the WiFi SSID and password from stdin. + + config NET_CONNECT_PROVIDE_WIFI_CONSOLE_CMD + depends on !NET_CONNECT_WIFI_SSID_PWD_FROM_STDIN + bool "Provide wifi connect commands" + default y + help + Provide wifi connect commands for esp_console. + Please use `example_register_wifi_connect_commands` to register them. + + config NET_CONNECT_WIFI_SSID + depends on !NET_CONNECT_WIFI_SSID_PWD_FROM_STDIN + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + + config NET_CONNECT_WIFI_PASSWORD + depends on !NET_CONNECT_WIFI_SSID_PWD_FROM_STDIN + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + Can be left blank if the network has no security set. + + config NET_CONNECT_WIFI_CONN_MAX_RETRY + int "Maximum retry" + default 6 + help + Set the Maximum retry to avoid station reconnecting to the AP unlimited, + in case the AP is really inexistent. + + choice NET_CONNECT_WIFI_SCAN_METHOD + prompt "WiFi Scan Method" + default NET_CONNECT_WIFI_SCAN_METHOD_ALL_CHANNEL + help + WiFi scan method: + + If "Fast" is selected, scan will end after find SSID match AP. + + If "All Channel" is selected, scan will end after scan all the channel. + + config NET_CONNECT_WIFI_SCAN_METHOD_FAST + bool "Fast" + config NET_CONNECT_WIFI_SCAN_METHOD_ALL_CHANNEL + bool "All Channel" + endchoice + + menu "WiFi Scan threshold" + config NET_CONNECT_WIFI_SCAN_RSSI_THRESHOLD + int "WiFi minimum rssi" + range -127 0 + + default -127 + help + The minimum rssi to accept in the scan mode. + + choice NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD + prompt "WiFi Scan auth mode threshold" + default NET_CONNECT_WIFI_AUTH_OPEN + help + The weakest authmode to accept in the scan mode. + + config NET_CONNECT_WIFI_AUTH_OPEN + bool "OPEN" + config NET_CONNECT_WIFI_AUTH_WEP + bool "WEP" + config NET_CONNECT_WIFI_AUTH_WPA_PSK + bool "WPA PSK" + config NET_CONNECT_WIFI_AUTH_WPA2_PSK + bool "WPA2 PSK" + config NET_CONNECT_WIFI_AUTH_WPA_WPA2_PSK + bool "WPA WPA2 PSK" + config NET_CONNECT_WIFI_AUTH_WPA2_ENTERPRISE + bool "WPA2 ENTERPRISE" + config NET_CONNECT_WIFI_AUTH_WPA3_PSK + bool "WPA3 PSK" + config NET_CONNECT_WIFI_AUTH_WPA2_WPA3_PSK + bool "WPA2 WPA3 PSK" + config NET_CONNECT_WIFI_AUTH_WAPI_PSK + bool "WAPI PSK" + endchoice + endmenu + + choice NET_CONNECT_WIFI_CONNECT_AP_SORT_METHOD + prompt "WiFi Connect AP Sort Method" + default NET_CONNECT_WIFI_CONNECT_AP_BY_SIGNAL + help + WiFi connect AP sort method: + + If "Signal" is selected, Sort matched APs in scan list by RSSI. + + If "Security" is selected, Sort matched APs in scan list by security mode. + + config NET_CONNECT_WIFI_CONNECT_AP_BY_SIGNAL + bool "Signal" + config NET_CONNECT_WIFI_CONNECT_AP_BY_SECURITY + bool "Security" + endchoice + endif + + config NET_CONNECT_ETHERNET + bool "connect using Ethernet interface" + depends on !IDF_TARGET_LINUX + default y if !NET_CONNECT_WIFI && !NET_CONNECT_THREAD + help + Protocol examples can use Wi-Fi, Ethernet and/or Thread to connect to the network. + Choose this option to enable connection with Ethernet. + Go to `Top -> Ethernet Configuration` to configure the Ethernet interface. + + config NET_CONNECT_DISABLE_ETHERNET_CONFIG + bool + select ETHERNET_INIT_OVERRIDE_DISABLE + default y if !NET_CONNECT_ETHERNET + + config NET_CONNECT_PPP + bool "connect using Point to Point interface" + select LWIP_PPP_SUPPORT + help + Protocol examples can use PPP connection over serial line. + Choose this option to connect to the ppp server running + on your laptop over a serial line (either UART or USB ACM) + + if NET_CONNECT_PPP + choice NET_CONNECT_PPP_DEVICE + prompt "Choose PPP device" + default NET_CONNECT_PPP_DEVICE_USB + help + Select which peripheral to use to connect to the PPP server. + + config NET_CONNECT_PPP_DEVICE_USB + bool "USB" + depends on SOC_USB_OTG_SUPPORTED + select TINYUSB_CDC_ENABLED + help + Use USB ACM device. + + config NET_CONNECT_PPP_DEVICE_UART + bool "UART" + help + Use UART. + + endchoice + + menu "UART Configuration" + depends on NET_CONNECT_PPP_DEVICE_UART + config NET_CONNECT_UART_TX_PIN + int "TXD Pin Number" + default 4 + range 0 31 + help + Pin number of UART TX. + + config NET_CONNECT_UART_RX_PIN + int "RXD Pin Number" + default 5 + range 0 31 + help + Pin number of UART RX. + + config NET_CONNECT_UART_BAUDRATE + int "UART Baudrate" + default 115200 + range 9600 3000000 + help + Baudrate of the UART device + + endmenu + + config NET_CONNECT_PPP_CONN_MAX_RETRY + int "Maximum retry" + default 6 + help + Set the Maximum retry to avoid station reconnecting if the pppd + is not available + + endif # NET_CONNECT_PPP + + config NET_CONNECT_THREAD + bool "Connect using Thread interface" + depends on !IDF_TARGET_LINUX && OPENTHREAD_ENABLED + default y if SOC_IEEE802154_SUPPORTED + select NET_CONNECT_IPV6 + help + Protocol examples can use Wi-Fi, Ethernet and/or Thread to connect to the network. + Choose this option to connect with Thread. + The operational active dataset of the Thread network can be configured in openthread + component at '->Components->OpenThread->Thread Core Features->Thread Operational Dataset' + + if NET_CONNECT_THREAD + config NET_CONNECT_THREAD_TASK_STACK_SIZE + int "Example Thread task stack size" + default 8192 + help + Thread task stack size + + menu "Radio Spinel Options" + depends on OPENTHREAD_RADIO_SPINEL_UART || OPENTHREAD_RADIO_SPINEL_SPI + + config NET_CONNECT_THREAD_UART_RX_PIN + depends on OPENTHREAD_RADIO_SPINEL_UART + int "Uart Rx Pin" + default 17 + + config NET_CONNECT_THREAD_UART_TX_PIN + depends on OPENTHREAD_RADIO_SPINEL_UART + int "Uart Tx pin" + default 18 + + config NET_CONNECT_THREAD_UART_BAUD + depends on OPENTHREAD_RADIO_SPINEL_UART + int "Uart baud rate" + default 460800 + + config NET_CONNECT_THREAD_UART_PORT + depends on OPENTHREAD_RADIO_SPINEL_UART + int "Uart port" + default 1 + + config NET_CONNECT_THREAD_SPI_CS_PIN + depends on OPENTHREAD_RADIO_SPINEL_SPI + int "SPI CS Pin" + default 10 + + config NET_CONNECT_THREAD_SPI_SCLK_PIN + depends on OPENTHREAD_RADIO_SPINEL_SPI + int "SPI SCLK Pin" + default 12 + + config NET_CONNECT_THREAD_SPI_MISO_PIN + depends on OPENTHREAD_RADIO_SPINEL_SPI + int "SPI MISO Pin" + default 13 + + config NET_CONNECT_THREAD_SPI_MOSI_PIN + depends on OPENTHREAD_RADIO_SPINEL_SPI + int "SPI MOSI Pin" + default 11 + + config NET_CONNECT_THREAD_SPI_INTR_PIN + depends on OPENTHREAD_RADIO_SPINEL_SPI + int "SPI Interrupt Pin" + default 8 + endmenu + + endif + + config NET_CONNECT_IPV4 + bool + depends on LWIP_IPV4 + default n if NET_CONNECT_THREAD + default y + + config NET_CONNECT_IPV6 + depends on NET_CONNECT_WIFI || NET_CONNECT_ETHERNET || NET_CONNECT_PPP || NET_CONNECT_THREAD + bool "Obtain IPv6 address" + default y + select LWIP_IPV6 + select LWIP_PPP_ENABLE_IPV6 if NET_CONNECT_PPP + help + By default, examples will wait until IPv4 and IPv6 local link addresses are obtained. + Disable this option if the network does not support IPv6. + Choose the preferred IPv6 address type if the connection code should wait until other than + the local link address gets assigned. + Consider enabling IPv6 stateless address autoconfiguration (SLAAC) in the LWIP component. + + if NET_CONNECT_IPV6 + choice NET_CONNECT_PREFERRED_IPV6 + prompt "Preferred IPv6 Type" + default NET_CONNECT_IPV6_PREF_LOCAL_LINK + help + Select which kind of IPv6 address the connect logic waits for. + + config NET_CONNECT_IPV6_PREF_LOCAL_LINK + bool "Local Link Address" + help + Blocks until Local link address assigned. + + config NET_CONNECT_IPV6_PREF_GLOBAL + bool "Global Address" + help + Blocks until Global address assigned. + + config NET_CONNECT_IPV6_PREF_SITE_LOCAL + bool "Site Local Address" + help + Blocks until Site link address assigned. + + config NET_CONNECT_IPV6_PREF_UNIQUE_LOCAL + bool "Unique Local Link Address" + help + Blocks until Unique local address assigned. + + endchoice + + endif + + +endmenu diff --git a/components/net_connect/README.md b/components/net_connect/README.md new file mode 100644 index 0000000000..91d1305bbb --- /dev/null +++ b/components/net_connect/README.md @@ -0,0 +1,66 @@ +# net_connect + +This component implements the most common connection methods for ESP32 boards. It provides WiFi, Ethernet, Thread, and PPP connection functionality for network-enabled applications. + +## How to use this component + +Choose the preferred interface (WiFi, Ethernet, Thread, PPPoS) to connect to the network and configure the interface. + +It is possible to enable multiple interfaces simultaneously making the connection phase to block until all the chosen interfaces acquire IP addresses. +It is also possible to disable all interfaces, skipping the connection phase altogether. + +### WiFi + +Choose WiFi connection method (for chipsets that support it) and configure basic WiFi connection properties: +* WiFi SSID +* WiFI password +* Maximum connection retry (connection would be aborted if it doesn't succeed after specified number of retries) +* WiFi scan method (including RSSI and authorization mode threshold) + + + +### Ethernet + +Choose Ethernet connection if your board supports it. The most common settings is using Espressif Ethernet Kit, which is also the recommended HW for this selection. You can also select an SPI ethernet device (if your chipset doesn't support internal EMAC or if you prefer). It is also possible to use OpenCores Ethernet MAC if you're running the example under QEMU. + +### Thread + +Choose Thread connection if your board supports IEEE802.15.4 native radio or works with [OpenThread RCP](../../openthread/ot_rcp/README.md). You can configure the Thread network at menuconfig '->Components->OpenThread->Thread Core Features->Thread Operational Dataset'. + +If the Thread end-device joins a Thread network with a Thread Border Router that has the NAT64 feature enabled, the end-device can access the Internet with the standard DNS APIs after configuring the following properties: +* Enable DNS64 client ('->Components->OpenThread->Thread Core Features->Enable DNS64 client') +* Enable custom DNS external resolve Hook ('->Components->LWIP->Hooks->DNS external resolve Hook->Custom implementation') + +### PPP + +Point to point connection method creates a simple IP tunnel to the counterpart device (running PPP server), typically a Linux machine with pppd service. We currently support only PPP over Serial (using UART or USB CDC). This is useful for simple testing of networking layers, but with some additional configuration on the server side, we could simulate standard model of internet connectivity. The PPP server could be also represented by a cellular modem device with pre-configured connectivity and already switched to PPP mode (this setup is not very flexible though, so we suggest using a standard modem library implementing commands and modes, e.g. [esp_modem](https://components.espressif.com/component/espressif/esp_modem) ). + +> [!Note] +> Note that if you choose USB device, you have to manually add a dependency on `esp_tinyusb` component. This step is necessary to keep the `net_connect` component simple and dependency free. Please run this command from your project location to add the dependency: +> ```bash +> idf.py add-dependency espressif/esp_tinyusb^1 +> ``` + +#### Setup a PPP server + +Connect the board using UART or USB and note the device name, which would be typically: +* `/dev/ttyACMx` for USB devices +* `/dev/ttyUSBx` for UART devices + +Run the pppd server: + +```bash +sudo pppd /dev/ttyACM0 115200 192.168.11.1:192.168.11.2 ms-dns 8.8.8.8 modem local noauth debug nocrtscts nodetach +ipv6 +``` + +Please update the parameters with the correct serial device, baud rate, IP addresses, DNS server, use `+ipv6` if `CONFIG_NET_CONNECT_IPV6=y`. + +#### Connection to outside + +In order to access other network endpoints, we have to configure some IP/translation rules. The easiest method is to setup a masquerade of the PPPD created interface (`ppp0`) to your default networking interface (`${ETH0}`). Here is an example of such rule: + +```bash +sudo iptables -t nat -A POSTROUTING -o ${ETH0} -j MASQUERADE +sudo iptables -A FORWARD -i ${ETH0} -o ppp0 -m state --state RELATED,ESTABLISHED -j ACCEPT +sudo iptables -A FORWARD -i ppp0 -o ${ETH0} -j ACCEPT +``` diff --git a/components/net_connect/connect.c b/components/net_connect/connect.c new file mode 100644 index 0000000000..7cf369e100 --- /dev/null +++ b/components/net_connect/connect.c @@ -0,0 +1,232 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "net_connect.h" +#include "net_connect_private.h" +#include "sdkconfig.h" +#if CONFIG_NET_CONNECT_WIFI +#include "net_connect_wifi_config.h" +#endif +#include "esp_event.h" +#include "esp_wifi.h" +#include "esp_wifi_default.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "lwip/err.h" +#include "lwip/sys.h" + +static const char *TAG = "net_connect"; + +#if CONFIG_NET_CONNECT_IPV6 +/* types of ipv6 addresses to be displayed on ipv6 events */ +const char *net_connect_ipv6_addr_types_to_str[6] = { + "ESP_IP6_ADDR_IS_UNKNOWN", + "ESP_IP6_ADDR_IS_GLOBAL", + "ESP_IP6_ADDR_IS_LINK_LOCAL", + "ESP_IP6_ADDR_IS_SITE_LOCAL", + "ESP_IP6_ADDR_IS_UNIQUE_LOCAL", + "ESP_IP6_ADDR_IS_IPV4_MAPPED_IPV6" +}; +#endif + +/** + * @brief Checks the netif description if it contains specified prefix. + * All netifs created within common connect component are prefixed with the module TAG, + * so it returns true if the specified netif is owned by this module + */ +bool net_connect_is_our_netif(const char *prefix, esp_netif_t *netif) +{ + if (prefix == NULL || *prefix == '\0') { + return false; + } + return strncmp(prefix, esp_netif_get_desc(netif), strlen(prefix)) == 0; +} + +static bool netif_desc_matches_with(esp_netif_t *netif, void *ctx) +{ + return strcmp(ctx, esp_netif_get_desc(netif)) == 0; +} + +esp_netif_t *net_get_netif_from_desc(const char *desc) +{ + if (desc == NULL) { + return NULL; + } + return esp_netif_find_if(netif_desc_matches_with, (void*)desc); +} + +static esp_err_t print_all_ips_tcpip(void* ctx) +{ + const char *prefix = ctx; + // iterate over active interfaces, and print out IPs of "our" netifs + esp_netif_t *netif = NULL; + while ((netif = esp_netif_next_unsafe(netif)) != NULL) { + if (net_connect_is_our_netif(prefix, netif)) { + ESP_LOGI(TAG, "Connected to %s", esp_netif_get_desc(netif)); +#if CONFIG_NET_CONNECT_IPV4 + esp_netif_ip_info_t ip; + ESP_ERROR_CHECK(esp_netif_get_ip_info(netif, &ip)); + + ESP_LOGI(TAG, "- IPv4 address: " IPSTR ",", IP2STR(&ip.ip)); +#endif +#if CONFIG_NET_CONNECT_IPV6 + esp_ip6_addr_t ip6[MAX_IP6_ADDRS_PER_NETIF]; + int ip6_addrs = esp_netif_get_all_ip6(netif, ip6); + for (int j = 0; j < ip6_addrs; ++j) { + esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&(ip6[j])); + ESP_LOGI(TAG, "- IPv6 address: " IPV6STR ", type: %s", IPV62STR(ip6[j]), net_connect_ipv6_addr_types_to_str[ipv6_type]); + } +#endif + } + } + return ESP_OK; +} + +void net_connect_print_all_netif_ips(const char *prefix) +{ + // Print all IPs in TCPIP context to avoid potential races of removing/adding netifs when iterating over the list + esp_netif_tcpip_exec(print_all_ips_tcpip, (void*) prefix); +} + + +esp_err_t net_connect(void) +{ + bool eth_initialized = false; + bool wifi_initialized = false; + bool thread_initialized = false; + bool ppp_initialized = false; + +#if CONFIG_NET_CONNECT_ETHERNET + ESP_LOGI(TAG, "Initializing Ethernet interface..."); + if (net_connect_ethernet_connect() != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize Ethernet interface"); + goto cleanup; + } + ESP_ERROR_CHECK(esp_register_shutdown_handler(&net_connect_ethernet_shutdown)); + eth_initialized = true; + ESP_LOGI(TAG, "Ethernet interface initialized successfully"); +#endif +#if CONFIG_NET_CONNECT_WIFI + ESP_LOGI(TAG, "Initializing WiFi interface..."); + if (!net_connect_wifi_is_configured()) { + /* Configure WiFi with Kconfig defaults */ + if (net_configure_wifi_sta(NULL) == NULL) { + ESP_LOGE(TAG, "Failed to configure WiFi interface"); + goto cleanup; + } + } + /* Use new API for WiFi connection */ + if (net_connect_wifi() != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize WiFi interface"); + goto cleanup; + } + ESP_ERROR_CHECK(esp_register_shutdown_handler(&net_connect_wifi_shutdown)); + wifi_initialized = true; + ESP_LOGI(TAG, "WiFi interface initialized successfully"); +#endif +#if CONFIG_NET_CONNECT_THREAD + ESP_LOGI(TAG, "Initializing Thread interface..."); + if (net_connect_thread_connect() != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize Thread interface"); + goto cleanup; + } + ESP_ERROR_CHECK(esp_register_shutdown_handler(&net_connect_thread_shutdown)); + thread_initialized = true; + ESP_LOGI(TAG, "Thread interface initialized successfully"); +#endif +#if CONFIG_NET_CONNECT_PPP + ESP_LOGI(TAG, "Initializing PPP interface..."); + if (net_connect_ppp_connect() != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize PPP interface"); + goto cleanup; + } + ESP_ERROR_CHECK(esp_register_shutdown_handler(&net_connect_ppp_shutdown)); + ppp_initialized = true; + ESP_LOGI(TAG, "PPP interface initialized successfully"); +#endif + +#if CONFIG_NET_CONNECT_ETHERNET + net_connect_print_all_netif_ips(NET_CONNECT_NETIF_DESC_ETH); +#endif + +#if CONFIG_NET_CONNECT_WIFI + net_connect_print_all_netif_ips(NET_CONNECT_NETIF_DESC_STA); +#endif + +#if CONFIG_NET_CONNECT_THREAD + net_connect_print_all_netif_ips(NET_CONNECT_NETIF_DESC_THREAD); +#endif + +#if CONFIG_NET_CONNECT_PPP + net_connect_print_all_netif_ips(NET_CONNECT_NETIF_DESC_PPP); +#endif + + return ESP_OK; + +cleanup: + /* Clean up previously initialized interfaces in reverse order */ +#if CONFIG_NET_CONNECT_PPP + if (ppp_initialized) { + net_connect_ppp_shutdown(); + esp_unregister_shutdown_handler(&net_connect_ppp_shutdown); + } +#endif +#if CONFIG_NET_CONNECT_THREAD + if (thread_initialized) { + net_connect_thread_shutdown(); + esp_unregister_shutdown_handler(&net_connect_thread_shutdown); + } +#endif +#if CONFIG_NET_CONNECT_WIFI + if (wifi_initialized) { + net_connect_wifi_shutdown(); + esp_unregister_shutdown_handler(&net_connect_wifi_shutdown); + } +#endif +#if CONFIG_NET_CONNECT_ETHERNET + if (eth_initialized) { + net_connect_ethernet_shutdown(); + esp_unregister_shutdown_handler(&net_connect_ethernet_shutdown); + } +#endif + return ESP_FAIL; +} + + +esp_err_t net_disconnect(void) +{ + ESP_LOGI(TAG, "Disconnecting network interfaces..."); +#if CONFIG_NET_CONNECT_ETHERNET + ESP_LOGI(TAG, "Deinitializing Ethernet interface..."); + net_connect_ethernet_shutdown(); + ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&net_connect_ethernet_shutdown)); + ESP_LOGI(TAG, "Ethernet interface deinitialized"); +#endif +#if CONFIG_NET_CONNECT_WIFI + ESP_LOGI(TAG, "Deinitializing WiFi interface..."); + net_connect_wifi_shutdown(); + ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&net_connect_wifi_shutdown)); + ESP_LOGI(TAG, "WiFi interface deinitialized"); +#endif +#if CONFIG_NET_CONNECT_THREAD + ESP_LOGI(TAG, "Deinitializing Thread interface..."); + net_connect_thread_shutdown(); + ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&net_connect_thread_shutdown)); + ESP_LOGI(TAG, "Thread interface deinitialized"); +#endif +#if CONFIG_NET_CONNECT_PPP + ESP_LOGI(TAG, "Deinitializing PPP interface..."); + net_connect_ppp_shutdown(); + ESP_ERROR_CHECK(esp_unregister_shutdown_handler(&net_connect_ppp_shutdown)); + ESP_LOGI(TAG, "PPP interface deinitialized"); +#endif + ESP_LOGI(TAG, "All network interfaces disconnected"); + return ESP_OK; +} diff --git a/components/net_connect/console_cmd.c b/components/net_connect/console_cmd.c new file mode 100644 index 0000000000..d3ea4bb9a7 --- /dev/null +++ b/components/net_connect/console_cmd.c @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include +#include "net_connect.h" +#include "net_connect_private.h" +#include "esp_wifi.h" +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" + + +static const char *TAG = "example_console"; + +typedef struct { + struct arg_str *ssid; + struct arg_str *password; + struct arg_int *channel; + struct arg_end *end; +} wifi_connect_args_t; +static wifi_connect_args_t connect_args; + +static int cmd_do_wifi_connect(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &connect_args); + + if (nerrors != 0) { + arg_print_errors(stderr, connect_args.end, argv[0]); + return 1; + } + + wifi_config_t wifi_config = { + .sta = { + .scan_method = WIFI_ALL_CHANNEL_SCAN, + .sort_method = WIFI_CONNECT_AP_BY_SIGNAL, + }, + }; + if (connect_args.channel->count > 0) { + wifi_config.sta.channel = (uint8_t)(connect_args.channel->ival[0]); + } + const char *ssid = connect_args.ssid->sval[0]; + const char *pass = NULL; + if (connect_args.password->count > 0) { + pass = connect_args.password->sval[0]; + } + strlcpy((char *) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid)); + if (pass) { + strlcpy((char *) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password)); + } + net_connect_wifi_sta_do_connect(wifi_config, false); + return 0; +} + +static int cmd_do_wifi_disconnect(int argc, char **argv) +{ + net_connect_wifi_sta_do_disconnect(); + return 0; +} + +void net_register_wifi_connect_commands(void) +{ + ESP_LOGI(TAG, "Registering WiFi connect commands."); + net_connect_wifi_start(); + + connect_args.ssid = arg_str1(NULL, NULL, "", "SSID of AP"); + connect_args.password = arg_str0(NULL, NULL, "", "password of AP"); + connect_args.channel = arg_int0("n", "channel", "", "channel of AP"); + connect_args.end = arg_end(2); + const esp_console_cmd_t wifi_connect_cmd = { + .command = "wifi_connect", + .help = "WiFi is station mode, join specified soft-AP", + .hint = NULL, + .func = &cmd_do_wifi_connect, + .argtable = &connect_args + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&wifi_connect_cmd)); + + + const esp_console_cmd_t wifi_disconnect_cmd = { + .command = "wifi_disconnect", + .help = "Do wifi disconnect", + .hint = NULL, + .func = &cmd_do_wifi_disconnect, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&wifi_disconnect_cmd)); +} diff --git a/components/net_connect/eth_connect.c b/components/net_connect/eth_connect.c new file mode 100644 index 0000000000..fadde3d33a --- /dev/null +++ b/components/net_connect/eth_connect.c @@ -0,0 +1,195 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "net_connect.h" +#include "net_connect_private.h" +#include "esp_event.h" +#include "esp_eth.h" +#include "ethernet_init.h" +#include "esp_log.h" +#include "esp_mac.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" + + +static const char *TAG = "ethernet_connect"; +static SemaphoreHandle_t s_semph_get_ip_addrs = NULL; +#if CONFIG_NET_CONNECT_IPV6 +static SemaphoreHandle_t s_semph_get_ip6_addrs = NULL; +#endif + +static esp_netif_t *eth_start(void); +static void eth_stop(void); + + +/** Event handler for Ethernet events */ + +static void eth_on_got_ip(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + if (!net_connect_is_our_netif(NET_CONNECT_NETIF_DESC_ETH, event->esp_netif)) { + return; + } + ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip)); + xSemaphoreGive(s_semph_get_ip_addrs); +} + +#if CONFIG_NET_CONNECT_IPV6 + +static void eth_on_got_ipv6(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data; + if (!net_connect_is_our_netif(NET_CONNECT_NETIF_DESC_ETH, event->esp_netif)) { + return; + } + esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip); + ESP_LOGI(TAG, "Got IPv6 event: Interface \"%s\" address: " IPV6STR ", type: %s", esp_netif_get_desc(event->esp_netif), + IPV62STR(event->ip6_info.ip), net_connect_ipv6_addr_types_to_str[ipv6_type]); + if (ipv6_type == NET_CONNECT_PREFERRED_IPV6_TYPE) { + xSemaphoreGive(s_semph_get_ip6_addrs); + } +} + +static void on_eth_event(void *esp_netif, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + switch (event_id) { + case ETHERNET_EVENT_CONNECTED: + ESP_LOGI(TAG, "Ethernet Link Up"); + ESP_ERROR_CHECK(esp_netif_create_ip6_linklocal(esp_netif)); + break; + default: + break; + } +} + +#endif // CONFIG_NET_CONNECT_IPV6 + +static esp_eth_handle_t *s_eth_handles = NULL; +static uint8_t s_eth_count = 0; +static esp_eth_netif_glue_handle_t s_eth_glue = NULL; +static esp_netif_t *s_eth_netif = NULL; + +static esp_netif_t *eth_start(void) +{ + 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 = NET_CONNECT_NETIF_DESC_ETH; + esp_netif_config.route_prio = 64; + esp_netif_config_t netif_config = { + .base = &esp_netif_config, + .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH + }; + s_eth_netif = esp_netif_new(&netif_config); + if (s_eth_netif == NULL) { + ESP_LOGE(TAG, "Failed to create Ethernet netif"); + return NULL; + } + + s_eth_glue = esp_eth_new_netif_glue(s_eth_handles[0]); + if (s_eth_glue == NULL) { + ESP_LOGE(TAG, "Failed to create Ethernet netif glue"); + esp_netif_destroy(s_eth_netif); + s_eth_netif = NULL; + return NULL; + } + 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, ð_on_got_ip, NULL)); +#ifdef CONFIG_NET_CONNECT_IPV6 + ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_CONNECTED, &on_eth_event, s_eth_netif)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, ð_on_got_ipv6, NULL)); +#endif + + ESP_ERROR_CHECK(esp_eth_start(s_eth_handles[0])); + + return s_eth_netif; +} + +static void eth_stop(void) +{ + ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, ð_on_got_ip)); +#if CONFIG_NET_CONNECT_IPV6 + ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_GOT_IP6, ð_on_got_ipv6)); + ESP_ERROR_CHECK(esp_event_handler_unregister(ETH_EVENT, ETHERNET_EVENT_CONNECTED, &on_eth_event)); +#endif + 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 = NULL; + s_eth_netif = NULL; + s_eth_handles = NULL; + s_eth_count = 0; +} + +/* tear down connection, release resources */ +void net_connect_ethernet_shutdown(void) +{ +#if CONFIG_NET_CONNECT_IPV4 + if (s_semph_get_ip_addrs != NULL) { + vSemaphoreDelete(s_semph_get_ip_addrs); + s_semph_get_ip_addrs = NULL; + } +#endif +#if CONFIG_NET_CONNECT_IPV6 + if (s_semph_get_ip6_addrs != NULL) { + vSemaphoreDelete(s_semph_get_ip6_addrs); + s_semph_get_ip6_addrs = NULL; + } +#endif + if (s_eth_netif != NULL) { + eth_stop(); + } +} + +esp_err_t net_connect_ethernet_connect(void) +{ +#if CONFIG_NET_CONNECT_IPV4 + s_semph_get_ip_addrs = xSemaphoreCreateBinary(); + if (s_semph_get_ip_addrs == NULL) { + return ESP_ERR_NO_MEM; + } +#endif +#if CONFIG_NET_CONNECT_IPV6 + s_semph_get_ip6_addrs = xSemaphoreCreateBinary(); + if (s_semph_get_ip6_addrs == NULL) { +#if CONFIG_NET_CONNECT_IPV4 + vSemaphoreDelete(s_semph_get_ip_addrs); + s_semph_get_ip_addrs = NULL; +#endif + return ESP_ERR_NO_MEM; + } +#endif + if (eth_start() == NULL) { +#if CONFIG_NET_CONNECT_IPV4 + vSemaphoreDelete(s_semph_get_ip_addrs); + s_semph_get_ip_addrs = NULL; +#endif +#if CONFIG_NET_CONNECT_IPV6 + vSemaphoreDelete(s_semph_get_ip6_addrs); + s_semph_get_ip6_addrs = NULL; +#endif + return ESP_ERR_NO_MEM; + } + ESP_LOGI(TAG, "Waiting for IP(s)."); +#if CONFIG_NET_CONNECT_IPV4 + xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY); +#endif +#if CONFIG_NET_CONNECT_IPV6 + xSemaphoreTake(s_semph_get_ip6_addrs, portMAX_DELAY); +#endif + return ESP_OK; +} diff --git a/components/net_connect/examples/net_connect_example/CMakeLists.txt b/components/net_connect/examples/net_connect_example/CMakeLists.txt new file mode 100644 index 0000000000..015ecdc2c9 --- /dev/null +++ b/components/net_connect/examples/net_connect_example/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(net_connect_example) diff --git a/components/net_connect/examples/net_connect_example/main/CMakeLists.txt b/components/net_connect/examples/net_connect_example/main/CMakeLists.txt new file mode 100644 index 0000000000..1affd606e6 --- /dev/null +++ b/components/net_connect/examples/net_connect_example/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "net_connect_example.c" + INCLUDE_DIRS "." + PRIV_REQUIRES esp_netif nvs_flash esp_event net_connect) diff --git a/components/net_connect/examples/net_connect_example/main/idf_component.yml b/components/net_connect/examples/net_connect_example/main/idf_component.yml new file mode 100644 index 0000000000..f8b02102be --- /dev/null +++ b/components/net_connect/examples/net_connect_example/main/idf_component.yml @@ -0,0 +1,10 @@ +dependencies: + idf: + version: ">=5.0" + net_connect: + version: "*" + override_path: '../../../' + espressif/ethernet_init: + version: "~1.2.0" + rules: + - if: "target != linux" diff --git a/components/net_connect/examples/net_connect_example/main/net_connect_example.c b/components/net_connect/examples/net_connect_example/main/net_connect_example.c new file mode 100644 index 0000000000..1b3909476e --- /dev/null +++ b/components/net_connect/examples/net_connect_example/main/net_connect_example.c @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file net_connect_example.c + * @brief Example demonstrating basic usage of the net_connect component. + * + * This example demonstrates how to use the net_connect component to establish + * network connectivity using WiFi, Ethernet, Thread, or PPP interfaces. + * All configuration is done via Kconfig menuconfig options. + * + * The example: + * 1. Initializes the network stack (esp_netif, NVS, event loop) + * 2. Calls net_connect() to establish connection using Kconfig settings + * 3. Demonstrates how to get the network interface + * 4. Optionally disconnects after a delay + */ + +#include +#include "esp_log.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "nvs_flash.h" +#include "net_connect.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static const char *TAG = "net_connect_example"; + +void app_main(void) +{ + esp_err_t ret; + + ESP_LOGI(TAG, "Starting net_connect example..."); + + // ===== Initialize network interfaces ===== + ESP_ERROR_CHECK(esp_netif_init()); + + // ===== Initialize NVS (required for Wi-Fi and Ethernet) ===== + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGW(TAG, "NVS partition was truncated and needs to be erased"); + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + // ===== Initialize event loop ===== + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + ESP_LOGI(TAG, "Network stack initialized"); + +#if CONFIG_NET_CONNECT_WIFI + ESP_LOGI(TAG, "WiFi interface enabled in Kconfig"); + ESP_LOGI(TAG, "WiFi SSID: %s", CONFIG_NET_CONNECT_WIFI_SSID); +#endif + +#if CONFIG_NET_CONNECT_ETHERNET + ESP_LOGI(TAG, "Ethernet interface enabled in Kconfig"); +#endif + +#if CONFIG_NET_CONNECT_THREAD + ESP_LOGI(TAG, "Thread interface enabled in Kconfig"); +#endif + +#if CONFIG_NET_CONNECT_PPP + ESP_LOGI(TAG, "PPP interface enabled in Kconfig"); +#endif + + // ===== Connect using unified API ===== + // This function reads configuration from Kconfig and connects all + // enabled interfaces (WiFi, Ethernet, Thread, PPP) + ESP_LOGI(TAG, "Connecting to network..."); + ret = net_connect(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to connect network interfaces (err=0x%x)", ret); + return; + } + + ESP_LOGI(TAG, "Network connection established successfully!"); + + // ===== Demonstrate getting network interface ===== + // The net_connect() function already prints IP addresses, but we can + // also get the interface directly if needed +#if CONFIG_NET_CONNECT_WIFI + esp_netif_t *wifi_netif = net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_STA); + if (wifi_netif != NULL) { + ESP_LOGI(TAG, "WiFi netif retrieved: %s", esp_netif_get_desc(wifi_netif)); + } +#endif + +#if CONFIG_NET_CONNECT_ETHERNET + esp_netif_t *eth_netif = net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_ETH); + if (eth_netif != NULL) { + ESP_LOGI(TAG, "Ethernet netif retrieved: %s", esp_netif_get_desc(eth_netif)); + } +#endif + +#if CONFIG_NET_CONNECT_THREAD + esp_netif_t *thread_netif = net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_THREAD); + if (thread_netif != NULL) { + ESP_LOGI(TAG, "Thread netif retrieved: %s", esp_netif_get_desc(thread_netif)); + } +#endif + +#if CONFIG_NET_CONNECT_PPP + esp_netif_t *ppp_netif = net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_PPP); + if (ppp_netif != NULL) { + ESP_LOGI(TAG, "PPP netif retrieved: %s", esp_netif_get_desc(ppp_netif)); + } +#endif + + // ===== Keep connection alive for demonstration ===== + ESP_LOGI(TAG, "Network connection active. Waiting 30 seconds..."); + vTaskDelay(pdMS_TO_TICKS(30000)); + + // ===== Optionally: Disconnect after demonstration ===== + ESP_LOGI(TAG, "Disconnecting network interfaces..."); + ret = net_disconnect(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to disconnect (err=0x%x)", ret); + } else { + ESP_LOGI(TAG, "All interfaces disconnected successfully"); + } + + ESP_LOGI(TAG, "Example finished"); +} diff --git a/components/net_connect/idf_component.yml b/components/net_connect/idf_component.yml new file mode 100644 index 0000000000..7caf76f045 --- /dev/null +++ b/components/net_connect/idf_component.yml @@ -0,0 +1,10 @@ +version: 1.0.0 +url: https://github.com/espressif/esp-protocols/tree/master/components/net_connect +description: Network connection component providing WiFi, Ethernet, Thread, and PPP connection methods for ESP32 boards. +dependencies: + idf: + version: '>=5.4.3,!=5.5.0,!=5.5.1' + espressif/ethernet_init: + version: "~1.2.0" + rules: + - if: "target != linux" diff --git a/components/net_connect/include/net_connect.h b/components/net_connect/include/net_connect.h new file mode 100644 index 0000000000..dbdfa908bf --- /dev/null +++ b/components/net_connect/include/net_connect.h @@ -0,0 +1,146 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" +#include "esp_err.h" +#if !CONFIG_IDF_TARGET_LINUX +#include "esp_netif.h" +#endif // !CONFIG_IDF_TARGET_LINUX + +#ifdef __cplusplus +extern "C" { +#endif + +#if !CONFIG_IDF_TARGET_LINUX +#if CONFIG_NET_CONNECT_WIFI +#define NET_CONNECT_NETIF_DESC_STA "net_connect_netif_sta" +#endif + +#if CONFIG_NET_CONNECT_ETHERNET +#define NET_CONNECT_NETIF_DESC_ETH "net_connect_netif_eth" +#endif + +#if CONFIG_NET_CONNECT_THREAD +#define NET_CONNECT_NETIF_DESC_THREAD "net_connect_netif_thread" +#endif + +#if CONFIG_NET_CONNECT_PPP +#define NET_CONNECT_NETIF_DESC_PPP "net_connect_netif_ppp" +#endif + +#if CONFIG_NET_CONNECT_WIFI_SCAN_METHOD_FAST +#define NET_CONNECT_WIFI_SCAN_METHOD WIFI_FAST_SCAN +#elif CONFIG_NET_CONNECT_WIFI_SCAN_METHOD_ALL_CHANNEL +#define NET_CONNECT_WIFI_SCAN_METHOD WIFI_ALL_CHANNEL_SCAN +#endif + +#if CONFIG_NET_CONNECT_WIFI_CONNECT_AP_BY_SIGNAL +#define NET_CONNECT_WIFI_CONNECT_AP_SORT_METHOD WIFI_CONNECT_AP_BY_SIGNAL +#elif CONFIG_NET_CONNECT_WIFI_CONNECT_AP_BY_SECURITY +#define NET_CONNECT_WIFI_CONNECT_AP_SORT_METHOD WIFI_CONNECT_AP_BY_SECURITY +#endif + +#if CONFIG_NET_CONNECT_WIFI_AUTH_OPEN +#define NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN +#elif CONFIG_NET_CONNECT_WIFI_AUTH_WEP +#define NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP +#elif CONFIG_NET_CONNECT_WIFI_AUTH_WPA_PSK +#define NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK +#elif CONFIG_NET_CONNECT_WIFI_AUTH_WPA2_PSK +#define NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK +#elif CONFIG_NET_CONNECT_WIFI_AUTH_WPA_WPA2_PSK +#define NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK +#elif CONFIG_NET_CONNECT_WIFI_AUTH_WPA2_ENTERPRISE +#define NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_ENTERPRISE +#elif CONFIG_NET_CONNECT_WIFI_AUTH_WPA3_PSK +#define NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK +#elif CONFIG_NET_CONNECT_WIFI_AUTH_WPA2_WPA3_PSK +#define NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK +#elif CONFIG_NET_CONNECT_WIFI_AUTH_WAPI_PSK +#define NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK +#endif + +/* Network connect default interface, prefer the ethernet one if running in example-test (CI) configuration */ +#if CONFIG_NET_CONNECT_ETHERNET +#define NET_CONNECT_INTERFACE net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_ETH) +#define net_get_netif() net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_ETH) +#elif CONFIG_NET_CONNECT_WIFI +#define NET_CONNECT_INTERFACE net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_STA) +#define net_get_netif() net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_STA) +#elif CONFIG_NET_CONNECT_THREAD +#define NET_CONNECT_INTERFACE net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_THREAD) +#define net_get_netif() net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_THREAD) +#elif CONFIG_NET_CONNECT_PPP +#define NET_CONNECT_INTERFACE net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_PPP) +#define net_get_netif() net_get_netif_from_desc(NET_CONNECT_NETIF_DESC_PPP) +#endif + +/** + * @brief Configure Wi-Fi or Ethernet, connect, wait for IP + * + * This all-in-one helper function is used in protocols examples to + * reduce the amount of boilerplate in the example. + * + * It is not intended to be used in real world applications. + * See examples under examples/wifi/getting_started/ and examples/ethernet/ + * for more complete Wi-Fi or Ethernet initialization code. + * + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + * + * @return ESP_OK on successful connection + */ +esp_err_t net_connect(void); + +/** + * Counterpart to net_connect, de-initializes Wi-Fi or Ethernet + */ +esp_err_t net_disconnect(void); + +/** + * @brief Configure stdin and stdout to use blocking I/O + * + * This helper function is used in ASIO examples. It wraps installing the + * UART driver and configuring VFS layer to use UART driver for console I/O. + */ +esp_err_t net_configure_stdin_stdout(void); + +/** + * @brief Returns esp-netif pointer created by net_connect() described by + * the supplied desc field + * + * @param desc Textual interface of created network interface, for example "sta" + * indicate default WiFi station, "eth" default Ethernet interface. + * + */ +esp_netif_t *net_get_netif_from_desc(const char *desc); + +#if CONFIG_NET_CONNECT_PROVIDE_WIFI_CONSOLE_CMD +/** + * @brief Register wifi connect commands + * + * Provide a simple wifi_connect command in esp_console. + * This function can be used after esp_console is initialized. + */ +void net_register_wifi_connect_commands(void); +#endif + +#else +static inline esp_err_t net_connect(void) +{ + return ESP_OK; +} +#endif // !CONFIG_IDF_TARGET_LINUX + +#if CONFIG_NET_CONNECT_WIFI +#include "net_connect_wifi_config.h" +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/net_connect/include/net_connect_private.h b/components/net_connect/include/net_connect_private.h new file mode 100644 index 0000000000..09d725cfb0 --- /dev/null +++ b/components/net_connect/include/net_connect_private.h @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +/* Private Functions of protocol example common */ + +#pragma once + +#include "esp_err.h" +#include "esp_wifi.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_NET_CONNECT_IPV6 +#define MAX_IP6_ADDRS_PER_NETIF (5) + +#if defined(CONFIG_NET_CONNECT_IPV6_PREF_LOCAL_LINK) +#define NET_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_LINK_LOCAL +#elif defined(CONFIG_NET_CONNECT_IPV6_PREF_GLOBAL) +#define NET_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_GLOBAL +#elif defined(CONFIG_NET_CONNECT_IPV6_PREF_SITE_LOCAL) +#define NET_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_SITE_LOCAL +#elif defined(CONFIG_NET_CONNECT_IPV6_PREF_UNIQUE_LOCAL) +#define NET_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_UNIQUE_LOCAL +#endif // if-elif CONFIG_NET_CONNECT_IPV6_PREF_... + +#endif + + +#if CONFIG_NET_CONNECT_IPV6 +extern const char *net_connect_ipv6_addr_types_to_str[6]; +#endif + +void net_connect_wifi_start(void); +void net_connect_wifi_stop(void); +esp_err_t net_connect_wifi_sta_do_connect(wifi_config_t wifi_config, bool wait); +esp_err_t net_connect_wifi_sta_do_disconnect(void); +bool net_connect_is_our_netif(const char *prefix, esp_netif_t *netif); +void net_connect_print_all_netif_ips(const char *prefix); +void net_connect_wifi_shutdown(void); +void net_connect_ethernet_shutdown(void); +esp_err_t net_connect_ethernet_connect(void); +void net_connect_thread_shutdown(void); +esp_err_t net_connect_thread_connect(void); +esp_err_t net_connect_ppp_connect(void); +void net_connect_ppp_start(void); +void net_connect_ppp_shutdown(void); + + + +#ifdef __cplusplus +} +#endif diff --git a/components/net_connect/include/net_connect_wifi_config.h b/components/net_connect/include/net_connect_wifi_config.h new file mode 100644 index 0000000000..87861188c2 --- /dev/null +++ b/components/net_connect/include/net_connect_wifi_config.h @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "sdkconfig.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_NET_CONNECT_WIFI + +/* Forward declaration for interface handle */ +typedef void* net_iface_handle_t; + +/* ============================================================ + * Default Configuration Macros + * ============================================================ */ + +/* General network configuration defaults */ +#define NET_CONNECT_DEFAULT_USE_DHCP true +#define NET_CONNECT_DEFAULT_ENABLE_IPV6 false + +/* WiFi-specific defaults */ +#define NET_CONNECT_WIFI_DEFAULT_MAX_RETRY 5 +#define NET_CONNECT_WIFI_DEFAULT_ROUTE_PRIO 128 + +/** + * @brief IP configuration (reusable for any interface, supports IPv4 and IPv6) + */ +typedef struct { + /* ===== IPv4 Configuration ===== */ + bool use_dhcp; // true = use DHCP, false = use static IPv4 + char ip[16]; // IPv4 address (if use_dhcp = false) + char netmask[16]; // Subnet mask + char gateway[16]; // Gateway address + char dns_main[16]; // Primary DNS server (IPv4) + char dns_backup[16]; // Secondary DNS server (IPv4) + char dns_fallback[16]; // Fallback DNS server (IPv4) + + /* ===== IPv6 Configuration ===== */ + bool enable_ipv6; // true = enable IPv6 on this interface + int ipv6_mode; // 0 = SLAAC, 1 = Manual, 2 = DHCPv6 + char ipv6_addr[40]; // Static IPv6 address (if ipv6_mode = Manual) + char ipv6_gateway[40]; // IPv6 gateway address + char ipv6_dns_main[40]; // Primary DNS server (IPv6) + char ipv6_dns_backup[40]; // Secondary DNS server (IPv6) +} net_ip_config_t; + +/** + * @brief Wi-Fi Station (STA) configuration + */ +typedef struct { + char ssid[32]; + char password[64]; + int scan_method; // Active or Passive scan + int sort_method; // Sort by signal or by security + int threshold_rssi; // Minimum RSSI threshold for connection + int auth_mode_threshold; // Weakest acceptable authentication type + int max_retry; // Maximum retry attempts before giving up + net_ip_config_t ip; // IP settings for STA (DHCP or static) +} net_wifi_sta_config_t; + +/** + * @brief Configure Wi-Fi Station (STA) interface. + * + * Stores STA configuration and returns a handle for later connection. + * Passing NULL uses default/Kconfig settings. + * + * @param[in] config Pointer to Wi-Fi STA configuration (can be NULL) + * @return Interface handle for STA interface, or NULL on error + */ +net_iface_handle_t net_configure_wifi_sta(net_wifi_sta_config_t *config); + +/** + * @brief Connect Wi-Fi interface using stored configuration. + * + * Connects the Wi-Fi STA interface using the configuration previously + * stored via net_configure_wifi_sta(). + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if WiFi is not configured + * - ESP_FAIL or error code on failure + */ +esp_err_t net_connect_wifi(void); + +/** + * @brief Disconnect Wi-Fi interface. + * + * Disconnects the Wi-Fi STA interface that was connected via net_connect_wifi(). + * + * @return + * - ESP_OK on success + * - ESP_FAIL or error code on failure + */ +esp_err_t net_disconnect_wifi(void); + +/** + * @brief Check if WiFi is configured via new API. + * + * Internal function to check if WiFi was configured using the new API. + * + * @return true if configured, false otherwise + */ +bool net_connect_wifi_is_configured(void); + +#endif /* CONFIG_NET_CONNECT_WIFI */ + +#ifdef __cplusplus +} +#endif diff --git a/components/net_connect/include/protocol_examples_thread_config.h b/components/net_connect/include/protocol_examples_thread_config.h new file mode 100644 index 0000000000..cdd269599c --- /dev/null +++ b/components/net_connect/include/protocol_examples_thread_config.h @@ -0,0 +1,115 @@ +/* + * Thread configurations for protocol examples + * + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#include + +#ifdef CONFIG_OPENTHREAD_RADIO_NATIVE +#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \ + { \ + .radio_mode = RADIO_MODE_NATIVE, \ + } + +#elif defined(CONFIG_OPENTHREAD_RADIO_SPINEL_UART) +#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \ + { \ + .radio_mode = RADIO_MODE_UART_RCP, \ + .radio_uart_config = \ + { \ + .port = CONFIG_NET_CONNECT_THREAD_UART_PORT, \ + .uart_config = \ + { \ + .baud_rate = CONFIG_NET_CONNECT_THREAD_UART_BAUD, \ + .data_bits = UART_DATA_8_BITS, \ + .parity = UART_PARITY_DISABLE, \ + .stop_bits = UART_STOP_BITS_1, \ + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, \ + .rx_flow_ctrl_thresh = 0, \ + .source_clk = UART_SCLK_DEFAULT, \ + }, \ + .rx_pin = CONFIG_NET_CONNECT_THREAD_UART_RX_PIN, \ + .tx_pin = CONFIG_NET_CONNECT_THREAD_UART_TX_PIN, \ + }, \ + } +#elif defined(CONFIG_OPENTHREAD_RADIO_SPINEL_SPI) +#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \ + { \ + .radio_mode = RADIO_MODE_SPI_RCP, \ + .radio_spi_config = \ + { \ + .host_device = SPI2_HOST, \ + .dma_channel = 2, \ + .spi_interface = \ + { \ + .mosi_io_num = CONFIG_NET_CONNECT_THREAD_SPI_MOSI_PIN, \ + .miso_io_num = CONFIG_NET_CONNECT_THREAD_SPI_MISO_PIN, \ + .sclk_io_num = CONFIG_NET_CONNECT_THREAD_SPI_SCLK_PIN, \ + .quadwp_io_num = -1, \ + .quadhd_io_num = -1, \ + }, \ + .spi_device = \ + { \ + .cs_ena_pretrans = 2, \ + .input_delay_ns = 100, \ + .mode = 0, \ + .clock_speed_hz = 2500 * 1000, \ + .spics_io_num = CONFIG_NET_CONNECT_THREAD_SPI_CS_PIN, \ + .queue_size = 5, \ + }, \ + .intr_pin = CONFIG_NET_CONNECT_THREAD_SPI_INTR_PIN, \ + }, \ + } +#else +#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \ + { \ + .radio_mode = RADIO_MODE_TREL, \ + } +#endif + +#if CONFIG_OPENTHREAD_CONSOLE_TYPE_UART +#define ESP_OPENTHREAD_DEFAULT_HOST_CONFIG() \ + { \ + .host_connection_mode = HOST_CONNECTION_MODE_CLI_UART, \ + .host_uart_config = \ + { \ + .port = 0, \ + .uart_config = \ + { \ + .baud_rate = 115200, \ + .data_bits = UART_DATA_8_BITS, \ + .parity = UART_PARITY_DISABLE, \ + .stop_bits = UART_STOP_BITS_1, \ + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, \ + .rx_flow_ctrl_thresh = 0, \ + .source_clk = UART_SCLK_DEFAULT, \ + }, \ + .rx_pin = UART_PIN_NO_CHANGE, \ + .tx_pin = UART_PIN_NO_CHANGE, \ + }, \ + } +#elif CONFIG_OPENTHREAD_CONSOLE_TYPE_USB_SERIAL_JTAG +#define ESP_OPENTHREAD_DEFAULT_HOST_CONFIG() \ + { \ + .host_connection_mode = HOST_CONNECTION_MODE_CLI_USB, \ + .host_usb_config = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(), \ + } +#else +#define ESP_OPENTHREAD_DEFAULT_HOST_CONFIG() \ + { \ + .host_connection_mode = HOST_CONNECTION_MODE_NONE, \ + } +#endif + +#define ESP_OPENTHREAD_DEFAULT_PORT_CONFIG() \ + { \ + .storage_partition_name = "nvs", \ + .netif_queue_size = 10, \ + .task_queue_size = 10, \ + } diff --git a/components/net_connect/ppp_connect.c b/components/net_connect/ppp_connect.c new file mode 100644 index 0000000000..999c3b1c33 --- /dev/null +++ b/components/net_connect/ppp_connect.c @@ -0,0 +1,280 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "sdkconfig.h" +#include "net_connect.h" +#include "net_connect_private.h" + +#if CONFIG_NET_CONNECT_PPP +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_netif_ppp.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#if CONFIG_NET_CONNECT_PPP_DEVICE_USB +#include "tinyusb.h" +#include "tusb_cdc_acm.h" + +static int s_itf; +static uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE]; + +#else // DEVICE is UART + +#include "driver/uart.h" +#define BUF_SIZE (1024) +static bool s_stop_task = false; + +#endif // CONNECT_PPP_DEVICE + + +static const char *TAG = "example_connect_ppp"; +static int s_retry_num = 0; +static EventGroupHandle_t s_event_group = NULL; +static esp_netif_t *s_netif; +static const int GOT_IPV4 = BIT0; +static const int CONNECTION_FAILED = BIT1; +#if CONFIG_NET_CONNECT_IPV6 +static const int GOT_IPV6 = BIT2; +#define CONNECT_BITS (GOT_IPV4|GOT_IPV6|CONNECTION_FAILED) +#else +#define CONNECT_BITS (GOT_IPV4|CONNECTION_FAILED) +#endif + +static esp_err_t transmit(void *h, void *buffer, size_t len) +{ + ESP_LOG_BUFFER_HEXDUMP(TAG, buffer, len, ESP_LOG_VERBOSE); +#if CONFIG_NET_CONNECT_PPP_DEVICE_USB + tinyusb_cdcacm_write_queue(s_itf, buffer, len); + tinyusb_cdcacm_write_flush(s_itf, 0); +#else // DEVICE_UART + uart_write_bytes(UART_NUM_1, buffer, len); +#endif // CONNECT_PPP_DEVICE + return ESP_OK; +} + +static esp_netif_driver_ifconfig_t driver_cfg = { + .handle = (void *)1, // singleton driver, just to != NULL + .transmit = transmit, +}; +const esp_netif_driver_ifconfig_t *ppp_driver_cfg = &driver_cfg; + +static void on_ip_event(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + + if (event_id == IP_EVENT_PPP_GOT_IP) { + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + if (!net_connect_is_our_netif(NET_CONNECT_NETIF_DESC_PPP, event->esp_netif)) { + return; + } + esp_netif_t *netif = event->esp_netif; + esp_netif_dns_info_t dns_info; + ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip)); + esp_netif_get_dns_info(netif, ESP_NETIF_DNS_MAIN, &dns_info); + ESP_LOGI(TAG, "Main DNS server : " IPSTR, IP2STR(&dns_info.ip.u_addr.ip4)); + xEventGroupSetBits(s_event_group, GOT_IPV4); +#if CONFIG_NET_CONNECT_IPV6 + } else if (event_id == IP_EVENT_GOT_IP6) { + ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data; + if (!net_connect_is_our_netif(NET_CONNECT_NETIF_DESC_PPP, event->esp_netif)) { + return; + } + esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip); + ESP_LOGI(TAG, "Got IPv6 event: Interface \"%s\" address: " IPV6STR ", type: %s", esp_netif_get_desc(event->esp_netif), + IPV62STR(event->ip6_info.ip), net_connect_ipv6_addr_types_to_str[ipv6_type]); + if (ipv6_type == NET_CONNECT_PREFERRED_IPV6_TYPE) { + xEventGroupSetBits(s_event_group, GOT_IPV6); + } +#endif + } else if (event_id == IP_EVENT_PPP_LOST_IP) { + ESP_LOGI(TAG, "Disconnect from PPP Server"); + s_retry_num++; + if (s_retry_num > CONFIG_NET_CONNECT_PPP_CONN_MAX_RETRY) { + ESP_LOGE(TAG, "PPP Connection failed %d times, stop reconnecting.", s_retry_num); + xEventGroupSetBits(s_event_group, CONNECTION_FAILED); + } else { + ESP_LOGI(TAG, "PPP Connection failed %d times, try to reconnect.", s_retry_num); + esp_netif_action_start(s_netif, 0, 0, 0); + esp_netif_action_connected(s_netif, 0, 0, 0); + } + + } +} + +#if CONFIG_NET_CONNECT_PPP_DEVICE_USB +static void cdc_rx_callback(int itf, cdcacm_event_t *event) +{ + size_t rx_size = 0; + if (itf != s_itf) { + // Not our channel + return; + } + // Check if netif is still valid (not destroyed during shutdown) + if (s_netif == NULL) { + return; + } + esp_err_t ret = tinyusb_cdcacm_read(itf, buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size); + if (ret == ESP_OK) { + ESP_LOG_BUFFER_HEXDUMP(TAG, buf, rx_size, ESP_LOG_VERBOSE); + // pass the received data to the network interface + esp_netif_receive(s_netif, buf, rx_size, NULL); + } else { + ESP_LOGE(TAG, "Read error"); + } +} + +static void line_state_changed(int itf, cdcacm_event_t *event) +{ + s_itf = itf; // use this channel for the netif communication + ESP_LOGI(TAG, "Line state changed on channel %d", itf); +} +#else // DEVICE is UART + +static void ppp_task(void *args) +{ + uart_config_t uart_config = {}; + uart_config.baud_rate = CONFIG_NET_CONNECT_UART_BAUDRATE; + uart_config.data_bits = UART_DATA_8_BITS; + uart_config.parity = UART_PARITY_DISABLE; + uart_config.stop_bits = UART_STOP_BITS_1; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; + uart_config.source_clk = UART_SCLK_DEFAULT; + + QueueHandle_t event_queue; + ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, BUF_SIZE, 0, 16, &event_queue, 0)); + ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &uart_config)); + ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, CONFIG_NET_CONNECT_UART_TX_PIN, CONFIG_NET_CONNECT_UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + ESP_ERROR_CHECK(uart_set_rx_timeout(UART_NUM_1, 1)); + + char *buffer = (char*)malloc(BUF_SIZE); + if (buffer == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for UART buffer"); + uart_driver_delete(UART_NUM_1); + vTaskDelete(NULL); + return; + } + uart_event_t event; + esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected, s_netif); + esp_netif_action_start(s_netif, 0, 0, 0); + esp_netif_action_connected(s_netif, 0, 0, 0); + while (!s_stop_task) { + if (xQueueReceive(event_queue, &event, pdMS_TO_TICKS(1000)) == pdTRUE) { + if (event.type == UART_DATA) { + size_t len; + uart_get_buffered_data_len(UART_NUM_1, &len); + if (len) { + len = uart_read_bytes(UART_NUM_1, buffer, BUF_SIZE, 0); + ESP_LOG_BUFFER_HEXDUMP(TAG, buffer, len, ESP_LOG_VERBOSE); + esp_netif_receive(s_netif, buffer, len, NULL); + } + } else { + ESP_LOGW(TAG, "Received UART event: %d", event.type); + } + } + } + // Unregister event handler to prevent events after s_netif is destroyed + esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected); + // Clean up UART driver + uart_driver_delete(UART_NUM_1); + free(buffer); + vTaskDelete(NULL); +} + +#endif // CONNECT_PPP_DEVICE + +esp_err_t net_connect_ppp_connect(void) +{ + ESP_LOGI(TAG, "Start example_connect."); + +#if CONFIG_NET_CONNECT_PPP_DEVICE_USB + ESP_LOGI(TAG, "USB initialization"); + const tinyusb_config_t tusb_cfg = { + .device_descriptor = NULL, + .string_descriptor = NULL, + .external_phy = false, + .configuration_descriptor = NULL, + }; + + ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + + tinyusb_config_cdcacm_t acm_cfg = { + .usb_dev = TINYUSB_USBDEV_0, + .cdc_port = TINYUSB_CDC_ACM_0, + .callback_rx = &cdc_rx_callback, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL + }; + + ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg)); + /* the second way to register a callback */ + ESP_ERROR_CHECK(tinyusb_cdcacm_register_callback( + TINYUSB_CDC_ACM_0, + CDC_EVENT_LINE_STATE_CHANGED, + &line_state_changed)); +#endif // CONFIG_NET_CONNECT_PPP_DEVICE_USB + + s_event_group = xEventGroupCreate(); + + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event, NULL)); + + esp_netif_inherent_config_t base_netif_cfg = ESP_NETIF_INHERENT_DEFAULT_PPP(); + base_netif_cfg.if_desc = NET_CONNECT_NETIF_DESC_PPP; + esp_netif_config_t netif_ppp_config = { .base = &base_netif_cfg, + .driver = ppp_driver_cfg, + .stack = ESP_NETIF_NETSTACK_DEFAULT_PPP + }; + + s_netif = esp_netif_new(&netif_ppp_config); + assert(s_netif); +#if CONFIG_NET_CONNECT_PPP_DEVICE_USB + esp_netif_action_start(s_netif, 0, 0, 0); + esp_netif_action_connected(s_netif, 0, 0, 0); +#else // DEVICE is UART + s_stop_task = false; + if (xTaskCreate(ppp_task, "ppp connect", 4096, NULL, 5, NULL) != pdTRUE) { + ESP_LOGE(TAG, "Failed to create a ppp connection task"); + return ESP_FAIL; + } +#endif // CONNECT_PPP_DEVICE + + ESP_LOGI(TAG, "Waiting for IP address"); + EventBits_t bits = xEventGroupWaitBits(s_event_group, CONNECT_BITS, pdFALSE, pdFALSE, portMAX_DELAY); + if (bits & CONNECTION_FAILED) { + ESP_LOGE(TAG, "Connection failed!"); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Connected!"); + + return ESP_OK; +} + +void net_connect_ppp_shutdown(void) +{ + ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event)); +#if CONFIG_NET_CONNECT_PPP_DEVICE_USB + // Unregister CDC line state callback to prevent access to destroyed netif + // Note: RX callback is set via config and cannot be unregistered, but cdc_rx_callback + // has a NULL check to prevent crashes if called after shutdown + tinyusb_cdcacm_register_callback(TINYUSB_CDC_ACM_0, CDC_EVENT_LINE_STATE_CHANGED, NULL); +#elif CONFIG_NET_CONNECT_PPP_DEVICE_UART + s_stop_task = true; + vTaskDelay(pdMS_TO_TICKS(1000)); // wait for the ppp task to stop +#endif + + esp_netif_action_disconnected(s_netif, 0, 0, 0); + + vEventGroupDelete(s_event_group); + esp_netif_action_stop(s_netif, 0, 0, 0); + esp_netif_destroy(s_netif); + s_netif = NULL; + s_event_group = NULL; +} + +#endif // CONFIG_NET_CONNECT_PPP diff --git a/components/net_connect/sdkconfig.rename b/components/net_connect/sdkconfig.rename new file mode 100644 index 0000000000..ec3859d998 --- /dev/null +++ b/components/net_connect/sdkconfig.rename @@ -0,0 +1,91 @@ +CONFIG_EXAMPLE_CONNECT_WIFI CONFIG_NET_CONNECT_WIFI +CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN CONFIG_NET_CONNECT_WIFI_SSID_PWD_FROM_STDIN +CONFIG_EXAMPLE_PROVIDE_WIFI_CONSOLE_CMD CONFIG_NET_CONNECT_PROVIDE_WIFI_CONSOLE_CMD +CONFIG_EXAMPLE_WIFI_SSID CONFIG_NET_CONNECT_WIFI_SSID +CONFIG_EXAMPLE_WIFI_PASSWORD CONFIG_NET_CONNECT_WIFI_PASSWORD +CONFIG_EXAMPLE_WIFI_CONN_MAX_RETRY CONFIG_NET_CONNECT_WIFI_CONN_MAX_RETRY +CONFIG_EXAMPLE_WIFI_SCAN_METHOD_FAST CONFIG_NET_CONNECT_WIFI_SCAN_METHOD_FAST +CONFIG_EXAMPLE_WIFI_SCAN_METHOD_ALL_CHANNEL CONFIG_NET_CONNECT_WIFI_SCAN_METHOD_ALL_CHANNEL +CONFIG_EXAMPLE_WIFI_SCAN_RSSI_THRESHOLD CONFIG_NET_CONNECT_WIFI_SCAN_RSSI_THRESHOLD +CONFIG_EXAMPLE_WIFI_AUTH_OPEN CONFIG_NET_CONNECT_WIFI_AUTH_OPEN +CONFIG_EXAMPLE_WIFI_AUTH_WEP CONFIG_NET_CONNECT_WIFI_AUTH_WEP +CONFIG_EXAMPLE_WIFI_AUTH_WPA_PSK CONFIG_NET_CONNECT_WIFI_AUTH_WPA_PSK +CONFIG_EXAMPLE_WIFI_AUTH_WPA2_PSK CONFIG_NET_CONNECT_WIFI_AUTH_WPA2_PSK +CONFIG_EXAMPLE_WIFI_AUTH_WPA_WPA2_PSK CONFIG_NET_CONNECT_WIFI_AUTH_WPA_WPA2_PSK +CONFIG_EXAMPLE_WIFI_AUTH_WPA2_ENTERPRISE CONFIG_NET_CONNECT_WIFI_AUTH_WPA2_ENTERPRISE +CONFIG_EXAMPLE_WIFI_AUTH_WPA3_PSK CONFIG_NET_CONNECT_WIFI_AUTH_WPA3_PSK +CONFIG_EXAMPLE_WIFI_AUTH_WPA2_WPA3_PSK CONFIG_NET_CONNECT_WIFI_AUTH_WPA2_WPA3_PSK +CONFIG_EXAMPLE_WIFI_AUTH_WAPI_PSK CONFIG_NET_CONNECT_WIFI_AUTH_WAPI_PSK +CONFIG_EXAMPLE_WIFI_CONNECT_AP_BY_SIGNAL CONFIG_NET_CONNECT_WIFI_CONNECT_AP_BY_SIGNAL +CONFIG_EXAMPLE_WIFI_CONNECT_AP_BY_SECURITY CONFIG_NET_CONNECT_WIFI_CONNECT_AP_BY_SECURITY +CONFIG_EXAMPLE_CONNECT_ETHERNET CONFIG_NET_CONNECT_ETHERNET +CONFIG_EXAMPLE_ETHERNET_EMAC_TASK_STACK_SIZE CONFIG_ETHERNET_RX_TASK_STACK_SIZE +CONFIG_EXAMPLE_USE_SPI_ETHERNET CONFIG_ETHERNET_SPI_SUPPORT +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET CONFIG_ETHERNET_INTERNAL_SUPPORT +CONFIG_EXAMPLE_USE_DM9051 CONFIG_ETHERNET_SPI_DEV0_DM9051 +CONFIG_EXAMPLE_USE_W5500 CONFIG_ETHERNET_SPI_DEV0_W5500 +CONFIG_EXAMPLE_USE_OPENETH CONFIG_ETHERNET_OPENETH_SUPPORT +CONFIG_EXAMPLE_ETH_PHY_GENERIC CONFIG_ETHERNET_PHY_GENERIC +CONFIG_EXAMPLE_ETH_PHY_IP101 CONFIG_ETHERNET_PHY_IP101 +CONFIG_EXAMPLE_ETH_PHY_RTL8201 CONFIG_ETHERNET_PHY_RTL8201 +CONFIG_EXAMPLE_ETH_PHY_LAN87XX CONFIG_ETHERNET_PHY_LAN87XX +CONFIG_EXAMPLE_ETH_PHY_DP83848 CONFIG_ETHERNET_PHY_DP83848 +CONFIG_EXAMPLE_ETH_PHY_KSZ80XX CONFIG_ETHERNET_PHY_KSZ80XX +CONFIG_EXAMPLE_ETH_MDC_GPIO CONFIG_ETHERNET_MDC_GPIO +CONFIG_EXAMPLE_ETH_MDIO_GPIO CONFIG_ETHERNET_MDIO_GPIO +CONFIG_EXAMPLE_ETH_SPI_HOST CONFIG_ETHERNET_SPI_HOST +CONFIG_EXAMPLE_ETH_SPI_SCLK_GPIO CONFIG_ETHERNET_SPI_SCLK_GPIO +CONFIG_EXAMPLE_ETH_SPI_MOSI_GPIO CONFIG_ETHERNET_SPI_MOSI_GPIO +CONFIG_EXAMPLE_ETH_SPI_MISO_GPIO CONFIG_ETHERNET_SPI_MISO_GPIO +CONFIG_EXAMPLE_ETH_SPI_CS_GPIO CONFIG_ETHERNET_SPI_CS0_GPIO +CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ CONFIG_ETHERNET_SPI_CLOCK_MHZ +CONFIG_EXAMPLE_ETH_SPI_INT_GPIO CONFIG_ETHERNET_SPI_INT0_GPIO +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO CONFIG_ETHERNET_PHY_RST_GPIO +CONFIG_EXAMPLE_ETH_PHY_ADDR CONFIG_ETHERNET_PHY_ADDR +CONFIG_NET_CONNECT_ETHERNET_EMAC_TASK_STACK_SIZE CONFIG_ETHERNET_RX_TASK_STACK_SIZE +CONFIG_NET_CONNECT_USE_SPI_ETHERNET CONFIG_ETHERNET_SPI_SUPPORT +CONFIG_NET_CONNECT_USE_INTERNAL_ETHERNET CONFIG_ETHERNET_INTERNAL_SUPPORT +CONFIG_NET_CONNECT_USE_DM9051 CONFIG_ETHERNET_SPI_DEV0_DM9051 +CONFIG_NET_CONNECT_USE_W5500 CONFIG_ETHERNET_SPI_DEV0_W5500 +CONFIG_NET_CONNECT_USE_OPENETH CONFIG_ETHERNET_OPENETH_SUPPORT +CONFIG_NET_CONNECT_ETH_PHY_GENERIC CONFIG_ETHERNET_PHY_GENERIC +CONFIG_NET_CONNECT_ETH_PHY_IP101 CONFIG_ETHERNET_PHY_IP101 +CONFIG_NET_CONNECT_ETH_PHY_RTL8201 CONFIG_ETHERNET_PHY_RTL8201 +CONFIG_NET_CONNECT_ETH_PHY_LAN87XX CONFIG_ETHERNET_PHY_LAN87XX +CONFIG_NET_CONNECT_ETH_PHY_DP83848 CONFIG_ETHERNET_PHY_DP83848 +CONFIG_NET_CONNECT_ETH_PHY_KSZ80XX CONFIG_ETHERNET_PHY_KSZ80XX +CONFIG_NET_CONNECT_ETH_MDC_GPIO CONFIG_ETHERNET_MDC_GPIO +CONFIG_NET_CONNECT_ETH_MDIO_GPIO CONFIG_ETHERNET_MDIO_GPIO +CONFIG_NET_CONNECT_ETH_SPI_HOST CONFIG_ETHERNET_SPI_HOST +CONFIG_NET_CONNECT_ETH_SPI_SCLK_GPIO CONFIG_ETHERNET_SPI_SCLK_GPIO +CONFIG_NET_CONNECT_ETH_SPI_MOSI_GPIO CONFIG_ETHERNET_SPI_MOSI_GPIO +CONFIG_NET_CONNECT_ETH_SPI_MISO_GPIO CONFIG_ETHERNET_SPI_MISO_GPIO +CONFIG_NET_CONNECT_ETH_SPI_CS_GPIO CONFIG_ETHERNET_SPI_CS0_GPIO +CONFIG_NET_CONNECT_ETH_SPI_CLOCK_MHZ CONFIG_ETHERNET_SPI_CLOCK_MHZ +CONFIG_NET_CONNECT_ETH_SPI_INT_GPIO CONFIG_ETHERNET_SPI_INT0_GPIO +CONFIG_NET_CONNECT_ETH_PHY_RST_GPIO CONFIG_ETHERNET_PHY_RST_GPIO +CONFIG_NET_CONNECT_ETH_PHY_ADDR CONFIG_ETHERNET_PHY_ADDR +CONFIG_EXAMPLE_CONNECT_PPP CONFIG_NET_CONNECT_PPP +CONFIG_EXAMPLE_CONNECT_PPP_DEVICE_USB CONFIG_NET_CONNECT_PPP_DEVICE_USB +CONFIG_EXAMPLE_CONNECT_PPP_DEVICE_UART CONFIG_NET_CONNECT_PPP_DEVICE_UART +CONFIG_EXAMPLE_CONNECT_UART_TX_PIN CONFIG_NET_CONNECT_UART_TX_PIN +CONFIG_EXAMPLE_CONNECT_UART_RX_PIN CONFIG_NET_CONNECT_UART_RX_PIN +CONFIG_EXAMPLE_CONNECT_UART_BAUDRATE CONFIG_NET_CONNECT_UART_BAUDRATE +CONFIG_EXAMPLE_PPP_CONN_MAX_RETRY CONFIG_NET_CONNECT_PPP_CONN_MAX_RETRY +CONFIG_EXAMPLE_CONNECT_THREAD CONFIG_NET_CONNECT_THREAD +CONFIG_EXAMPLE_THREAD_TASK_STACK_SIZE CONFIG_NET_CONNECT_THREAD_TASK_STACK_SIZE +CONFIG_EXAMPLE_THREAD_UART_RX_PIN CONFIG_NET_CONNECT_THREAD_UART_RX_PIN +CONFIG_EXAMPLE_THREAD_UART_TX_PIN CONFIG_NET_CONNECT_THREAD_UART_TX_PIN +CONFIG_EXAMPLE_THREAD_UART_BAUD CONFIG_NET_CONNECT_THREAD_UART_BAUD +CONFIG_EXAMPLE_THREAD_UART_PORT CONFIG_NET_CONNECT_THREAD_UART_PORT +CONFIG_EXAMPLE_THREAD_SPI_CS_PIN CONFIG_NET_CONNECT_THREAD_SPI_CS_PIN +CONFIG_EXAMPLE_THREAD_SPI_SCLK_PIN CONFIG_NET_CONNECT_THREAD_SPI_SCLK_PIN +CONFIG_EXAMPLE_THREAD_SPI_MISO_PIN CONFIG_NET_CONNECT_THREAD_SPI_MISO_PIN +CONFIG_EXAMPLE_THREAD_SPI_MOSI_PIN CONFIG_NET_CONNECT_THREAD_SPI_MOSI_PIN +CONFIG_EXAMPLE_THREAD_SPI_INTR_PIN CONFIG_NET_CONNECT_THREAD_SPI_INTR_PIN +CONFIG_EXAMPLE_CONNECT_IPV4 CONFIG_NET_CONNECT_IPV4 +CONFIG_EXAMPLE_CONNECT_IPV6 CONFIG_NET_CONNECT_IPV6 +CONFIG_EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK CONFIG_NET_CONNECT_IPV6_PREF_LOCAL_LINK +CONFIG_EXAMPLE_CONNECT_IPV6_PREF_GLOBAL CONFIG_NET_CONNECT_IPV6_PREF_GLOBAL +CONFIG_EXAMPLE_CONNECT_IPV6_PREF_SITE_LOCAL CONFIG_NET_CONNECT_IPV6_PREF_SITE_LOCAL +CONFIG_EXAMPLE_CONNECT_IPV6_PREF_UNIQUE_LOCAL CONFIG_NET_CONNECT_IPV6_PREF_UNIQUE_LOCAL diff --git a/components/net_connect/stdin_out.c b/components/net_connect/stdin_out.c new file mode 100644 index 0000000000..ebf4939fc3 --- /dev/null +++ b/components/net_connect/stdin_out.c @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "net_connect.h" +#include "esp_err.h" +#include "driver/uart_vfs.h" +#include "driver/uart.h" +#include "sdkconfig.h" + +esp_err_t net_configure_stdin_stdout(void) +{ + if (uart_is_driver_installed((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM)) { + return ESP_OK; + } + // Initialize VFS & UART so we can use std::cout/cin + setvbuf(stdin, NULL, _IONBF, 0); + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK(uart_driver_install((uart_port_t)CONFIG_ESP_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0)); + /* Tell VFS to use UART driver */ + uart_vfs_dev_use_driver(CONFIG_ESP_CONSOLE_UART_NUM); + uart_vfs_dev_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + uart_vfs_dev_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF); + return ESP_OK; +} diff --git a/components/net_connect/tests/unit_test/CMakeLists.txt b/components/net_connect/tests/unit_test/CMakeLists.txt new file mode 100644 index 0000000000..50d80784ef --- /dev/null +++ b/components/net_connect/tests/unit_test/CMakeLists.txt @@ -0,0 +1,15 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0") + set(test_component_dir $ENV{IDF_PATH}/tools/test_apps/components) +else() + set(test_component_dir $ENV{IDF_PATH}/tools/unit-test-app/components) +endif() + +set(EXTRA_COMPONENT_DIRS ../.. + ${test_component_dir}) + +project(net_connect_test) diff --git a/components/net_connect/tests/unit_test/main/CMakeLists.txt b/components/net_connect/tests/unit_test/main/CMakeLists.txt new file mode 100644 index 0000000000..05c6e785b5 --- /dev/null +++ b/components/net_connect/tests/unit_test/main/CMakeLists.txt @@ -0,0 +1,5 @@ + +idf_component_register(SRCS "test_net_connect.c" + REQUIRES test_utils esp_netif esp_event + INCLUDE_DIRS "." + PRIV_REQUIRES unity net_connect) diff --git a/components/net_connect/tests/unit_test/main/test_net_connect.c b/components/net_connect/tests/unit_test/main/test_net_connect.c new file mode 100644 index 0000000000..f89f0edde5 --- /dev/null +++ b/components/net_connect/tests/unit_test/main/test_net_connect.c @@ -0,0 +1,310 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "net_connect.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_netif_defaults.h" +#include "unity.h" +#include "test_utils.h" +#include "unity_fixture.h" +#include "memory_checks.h" + +#define TEST_NETIF_DESC_STA "net_connect_netif_sta" +#define TEST_NETIF_DESC_ETH "net_connect_netif_eth" +#define TEST_NETIF_DESC_THREAD "net_connect_netif_thread" +#define TEST_NETIF_DESC_PPP "net_connect_netif_ppp" +#define TEST_NETIF_DESC_OTHER "other_netif_desc" + +TEST_GROUP(net_connect); + +static esp_netif_t *test_netif_sta = NULL; +static esp_netif_t *test_netif_eth = NULL; +static esp_netif_t *test_netif_thread = NULL; +static esp_netif_t *test_netif_ppp = NULL; +static esp_netif_t *test_netif_other = NULL; + +TEST_SETUP(net_connect) +{ + test_utils_record_free_mem(); + TEST_ESP_OK(test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL)); + + // Create default event loop for netif operations + esp_event_loop_create_default(); + + // Create test netifs with different descriptions + // Using WiFi STA config for all as it's the simplest and most reliable for unit tests + // Set unique if_key and description in the inherent config before creating the netif + // Note: if_key must be unique for each interface, while if_desc is what we search by + esp_netif_inherent_config_t base_cfg_sta = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA(); + base_cfg_sta.if_key = "WIFI_STA_TEST_STA"; + base_cfg_sta.if_desc = TEST_NETIF_DESC_STA; + esp_netif_config_t cfg_sta = { + .base = &base_cfg_sta, + .driver = NULL, + .stack = ESP_NETIF_NETSTACK_DEFAULT_WIFI_STA, + }; + test_netif_sta = esp_netif_new(&cfg_sta); + TEST_ASSERT_NOT_NULL(test_netif_sta); + + esp_netif_inherent_config_t base_cfg_eth = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA(); + base_cfg_eth.if_key = "WIFI_STA_TEST_ETH"; + base_cfg_eth.if_desc = TEST_NETIF_DESC_ETH; + esp_netif_config_t cfg_eth = { + .base = &base_cfg_eth, + .driver = NULL, + .stack = ESP_NETIF_NETSTACK_DEFAULT_WIFI_STA, + }; + test_netif_eth = esp_netif_new(&cfg_eth); + TEST_ASSERT_NOT_NULL(test_netif_eth); + + esp_netif_inherent_config_t base_cfg_thread = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA(); + base_cfg_thread.if_key = "WIFI_STA_TEST_THREAD"; + base_cfg_thread.if_desc = TEST_NETIF_DESC_THREAD; + esp_netif_config_t cfg_thread = { + .base = &base_cfg_thread, + .driver = NULL, + .stack = ESP_NETIF_NETSTACK_DEFAULT_WIFI_STA, + }; + test_netif_thread = esp_netif_new(&cfg_thread); + TEST_ASSERT_NOT_NULL(test_netif_thread); + + esp_netif_inherent_config_t base_cfg_ppp = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA(); + base_cfg_ppp.if_key = "WIFI_STA_TEST_PPP"; + base_cfg_ppp.if_desc = TEST_NETIF_DESC_PPP; + esp_netif_config_t cfg_ppp = { + .base = &base_cfg_ppp, + .driver = NULL, + .stack = ESP_NETIF_NETSTACK_DEFAULT_WIFI_STA, + }; + test_netif_ppp = esp_netif_new(&cfg_ppp); + TEST_ASSERT_NOT_NULL(test_netif_ppp); + + esp_netif_inherent_config_t base_cfg_other = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA(); + base_cfg_other.if_key = "WIFI_STA_TEST_OTHER"; + base_cfg_other.if_desc = TEST_NETIF_DESC_OTHER; + esp_netif_config_t cfg_other = { + .base = &base_cfg_other, + .driver = NULL, + .stack = ESP_NETIF_NETSTACK_DEFAULT_WIFI_STA, + }; + test_netif_other = esp_netif_new(&cfg_other); + TEST_ASSERT_NOT_NULL(test_netif_other); +} + +TEST_TEAR_DOWN(net_connect) +{ + // Clean up test netifs + if (test_netif_sta) { + esp_netif_destroy(test_netif_sta); + test_netif_sta = NULL; + } + if (test_netif_eth) { + esp_netif_destroy(test_netif_eth); + test_netif_eth = NULL; + } + if (test_netif_thread) { + esp_netif_destroy(test_netif_thread); + test_netif_thread = NULL; + } + if (test_netif_ppp) { + esp_netif_destroy(test_netif_ppp); + test_netif_ppp = NULL; + } + if (test_netif_other) { + esp_netif_destroy(test_netif_other); + test_netif_other = NULL; + } + + esp_event_loop_delete_default(); + test_utils_finish_and_evaluate_leaks(0, 0); +} + +/** + * @brief Test that net_get_netif_from_desc() can successfully find a WiFi STA netif by its description + * + * Objective: Verify that the function correctly locates a network interface with description + * "net_connect_netif_sta" and returns the correct netif pointer. This ensures basic lookup + * functionality works for STA interfaces. + */ +TEST(net_connect, net_get_netif_from_desc_finds_sta) +{ + esp_netif_t *found = net_get_netif_from_desc(TEST_NETIF_DESC_STA); + TEST_ASSERT_NOT_NULL(found); + TEST_ASSERT_EQUAL(test_netif_sta, found); + TEST_ASSERT_EQUAL_STRING(TEST_NETIF_DESC_STA, esp_netif_get_desc(found)); +} + +/** + * @brief Test that net_get_netif_from_desc() can successfully find an Ethernet netif by its description + * + * Objective: Verify that the function correctly locates a network interface with description + * "net_connect_netif_eth" and returns the correct netif pointer. This ensures lookup functionality + * works for Ethernet interfaces. + */ +TEST(net_connect, net_get_netif_from_desc_finds_eth) +{ + esp_netif_t *found = net_get_netif_from_desc(TEST_NETIF_DESC_ETH); + TEST_ASSERT_NOT_NULL(found); + TEST_ASSERT_EQUAL(test_netif_eth, found); + TEST_ASSERT_EQUAL_STRING(TEST_NETIF_DESC_ETH, esp_netif_get_desc(found)); +} + +/** + * @brief Test that net_get_netif_from_desc() can successfully find a Thread netif by its description + * + * Objective: Verify that the function correctly locates a network interface with description + * "net_connect_netif_thread" and returns the correct netif pointer. This ensures lookup functionality + * works for Thread interfaces. + */ +TEST(net_connect, net_get_netif_from_desc_finds_thread) +{ + esp_netif_t *found = net_get_netif_from_desc(TEST_NETIF_DESC_THREAD); + TEST_ASSERT_NOT_NULL(found); + TEST_ASSERT_EQUAL(test_netif_thread, found); + TEST_ASSERT_EQUAL_STRING(TEST_NETIF_DESC_THREAD, esp_netif_get_desc(found)); +} + +/** + * @brief Test that net_get_netif_from_desc() can successfully find a PPP netif by its description + * + * Objective: Verify that the function correctly locates a network interface with description + * "net_connect_netif_ppp" and returns the correct netif pointer. This ensures lookup functionality + * works for PPP interfaces. + */ +TEST(net_connect, net_get_netif_from_desc_finds_ppp) +{ + esp_netif_t *found = net_get_netif_from_desc(TEST_NETIF_DESC_PPP); + TEST_ASSERT_NOT_NULL(found); + TEST_ASSERT_EQUAL(test_netif_ppp, found); + TEST_ASSERT_EQUAL_STRING(TEST_NETIF_DESC_PPP, esp_netif_get_desc(found)); +} + +/** + * @brief Test that net_get_netif_from_desc() can successfully find a netif with a custom description + * + * Objective: Verify that the function correctly locates a network interface with a custom description + * "other_netif_desc" and returns the correct netif pointer. This ensures lookup functionality works + * for interfaces with non-standard descriptions. + */ +TEST(net_connect, net_get_netif_from_desc_finds_other) +{ + esp_netif_t *found = net_get_netif_from_desc(TEST_NETIF_DESC_OTHER); + TEST_ASSERT_NOT_NULL(found); + TEST_ASSERT_EQUAL(test_netif_other, found); + TEST_ASSERT_EQUAL_STRING(TEST_NETIF_DESC_OTHER, esp_netif_get_desc(found)); +} + +/** + * @brief Test that net_get_netif_from_desc() returns NULL when searching for a non-existent netif description + * + * Objective: Verify that the function correctly handles the case where no network interface matches + * the provided description. This ensures proper error handling when a netif doesn't exist. + */ +TEST(net_connect, net_get_netif_from_desc_returns_null_for_nonexistent) +{ + esp_netif_t *found = net_get_netif_from_desc("nonexistent_netif_desc"); + TEST_ASSERT_NULL(found); +} + +/** + * @brief Test that net_get_netif_from_desc() returns NULL when passed a NULL description pointer + * + * Objective: Verify that the function safely handles NULL input and returns NULL instead of + * crashing or dereferencing a NULL pointer. This ensures robust error handling for invalid inputs. + */ +TEST(net_connect, net_get_netif_from_desc_returns_null_for_null_desc) +{ + esp_netif_t *found = net_get_netif_from_desc(NULL); + TEST_ASSERT_NULL(found); +} + +/** + * @brief Test that net_get_netif_from_desc() returns NULL when passed an empty string description + * + * Objective: Verify that the function correctly handles empty string input and returns NULL. + * This ensures that empty descriptions are treated as invalid and don't match any netif. + */ +TEST(net_connect, net_get_netif_from_desc_returns_null_for_empty_desc) +{ + esp_netif_t *found = net_get_netif_from_desc(""); + TEST_ASSERT_NULL(found); +} + +/** + * @brief Test that net_get_netif_from_desc() performs case-sensitive matching + * + * Objective: Verify that description matching is case-sensitive. An uppercase version of a valid + * description should not match, while the exact case-sensitive description should match. This ensures + * that the function requires exact string matches including case. + */ +TEST(net_connect, net_get_netif_from_desc_case_sensitive) +{ + // Test that description matching is case-sensitive + esp_netif_t *found_upper = net_get_netif_from_desc("NET_CONNECT_NETIF_STA"); + TEST_ASSERT_NULL(found_upper); + + esp_netif_t *found_lower = net_get_netif_from_desc(TEST_NETIF_DESC_STA); + TEST_ASSERT_NOT_NULL(found_lower); +} + +/** + * @brief Test that net_get_netif_from_desc() requires exact string matches (no partial matching) + * + * Objective: Verify that the function only matches exact descriptions and rejects partial matches. + * Both a prefix substring and a string with extra characters should fail to match. This ensures + * that the lookup requires precise description strings. + */ +TEST(net_connect, net_get_netif_from_desc_partial_match_fails) +{ + // Test that partial matches don't work (must be exact match) + esp_netif_t *found = net_get_netif_from_desc("net_connect_netif_st"); + TEST_ASSERT_NULL(found); + + found = net_get_netif_from_desc("net_connect_netif_sta_extra"); + TEST_ASSERT_NULL(found); +} + +/** + * @brief Test that net_get_netif_from_desc() returns consistent results across multiple calls + * + * Objective: Verify that calling the function multiple times with the same description returns + * the same netif pointer each time. This ensures the function's behavior is deterministic and + * consistent, which is important for reliability in production code. + */ +TEST(net_connect, net_get_netif_from_desc_multiple_calls_same_result) +{ + // Test that multiple calls return the same result + esp_netif_t *found1 = net_get_netif_from_desc(TEST_NETIF_DESC_STA); + esp_netif_t *found2 = net_get_netif_from_desc(TEST_NETIF_DESC_STA); + esp_netif_t *found3 = net_get_netif_from_desc(TEST_NETIF_DESC_STA); + + TEST_ASSERT_NOT_NULL(found1); + TEST_ASSERT_EQUAL(found1, found2); + TEST_ASSERT_EQUAL(found2, found3); + TEST_ASSERT_EQUAL(test_netif_sta, found1); +} + +TEST_GROUP_RUNNER(net_connect) +{ + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_finds_sta); + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_finds_eth); + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_finds_thread); + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_finds_ppp); + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_finds_other); + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_returns_null_for_nonexistent); + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_returns_null_for_null_desc); + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_returns_null_for_empty_desc); + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_case_sensitive); + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_partial_match_fails); + RUN_TEST_CASE(net_connect, net_get_netif_from_desc_multiple_calls_same_result); +} + +void app_main(void) +{ + UNITY_MAIN(net_connect); +} diff --git a/components/net_connect/tests/unit_test/pytest_app_net_connect.py b/components/net_connect/tests/unit_test/pytest_app_net_connect.py new file mode 100644 index 0000000000..e8c7fa71e8 --- /dev/null +++ b/components/net_connect/tests/unit_test/pytest_app_net_connect.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +from pytest_embedded import Dut + + +def test_net_connect(dut: Dut) -> None: + dut.expect_unity_test_output() diff --git a/components/net_connect/tests/unit_test/sdkconfig.ci b/components/net_connect/tests/unit_test/sdkconfig.ci new file mode 100644 index 0000000000..9660719189 --- /dev/null +++ b/components/net_connect/tests/unit_test/sdkconfig.ci @@ -0,0 +1,3 @@ +CONFIG_IDF_TARGET="esp32" +CONFIG_UNITY_ENABLE_FIXTURE=y +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n diff --git a/components/net_connect/tests/unit_test/sdkconfig.defaults b/components/net_connect/tests/unit_test/sdkconfig.defaults new file mode 100644 index 0000000000..168e08d4cd --- /dev/null +++ b/components/net_connect/tests/unit_test/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_UNITY_ENABLE_FIXTURE=y +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n diff --git a/components/net_connect/thread_connect.c b/components/net_connect/thread_connect.c new file mode 100644 index 0000000000..46f65f61e8 --- /dev/null +++ b/components/net_connect/thread_connect.c @@ -0,0 +1,139 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_err.h" +#include "esp_event.h" +#include "esp_event_base.h" +#include "esp_vfs_eventfd.h" +#include "net_connect_private.h" +#include "net_connect.h" +#include "protocol_examples_thread_config.h" +#include "esp_log.h" +#include + +#include +#include +#include +#include +#include +#include +#include + +static TaskHandle_t s_ot_task_handle = NULL; +static esp_netif_t *s_openthread_netif = NULL; +static SemaphoreHandle_t s_semph_thread_attached = NULL; +static SemaphoreHandle_t s_semph_thread_set_dns_server = NULL; +static const char *TAG = "example_connect"; + +static void thread_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, + void* event_data) +{ + if (event_base == OPENTHREAD_EVENT) { + if (event_id == OPENTHREAD_EVENT_ATTACHED) { + xSemaphoreGive(s_semph_thread_attached); + } else if (event_id == OPENTHREAD_EVENT_SET_DNS_SERVER) { + xSemaphoreGive(s_semph_thread_set_dns_server); + } + } +} + +static void ot_task_worker(void *aContext) +{ + esp_openthread_platform_config_t config = { + .radio_config = ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG(), + .host_config = ESP_OPENTHREAD_DEFAULT_HOST_CONFIG(), + .port_config = ESP_OPENTHREAD_DEFAULT_PORT_CONFIG(), + }; + + esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_OPENTHREAD(); + esp_netif_config.if_desc = NET_CONNECT_NETIF_DESC_THREAD; + esp_netif_config_t cfg = { + .base = &esp_netif_config, + .stack = &g_esp_netif_netstack_default_openthread, + }; + s_openthread_netif = esp_netif_new(&cfg); + assert(s_openthread_netif != NULL); + + // Initialize the OpenThread stack + ESP_ERROR_CHECK(esp_openthread_init(&config)); + ESP_ERROR_CHECK(esp_netif_attach(s_openthread_netif, esp_openthread_netif_glue_init(&config))); + esp_openthread_lock_acquire(portMAX_DELAY); + (void)otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL); + esp_openthread_cli_init(); + esp_openthread_cli_create_task(); + otOperationalDatasetTlvs dataset; + otError error = otDatasetGetActiveTlvs(esp_openthread_get_instance(), &dataset); + if (error != OT_ERROR_NONE) { + ESP_ERROR_CHECK(esp_openthread_auto_start(NULL)); + } else { + ESP_ERROR_CHECK(esp_openthread_auto_start(&dataset)); + } + esp_openthread_lock_release(); + + // Run the main loop + esp_openthread_launch_mainloop(); + + // Clean up + // Clear the task handle before cleanup to prevent double-cleanup in net_connect_thread_shutdown() + s_ot_task_handle = NULL; + esp_openthread_netif_glue_deinit(); + esp_netif_destroy(s_openthread_netif); + esp_vfs_eventfd_unregister(); + vTaskDelete(NULL); +} + +/* tear down connection, release resources */ +void net_connect_thread_shutdown(void) +{ + if (s_ot_task_handle != NULL) { + vTaskDelete(s_ot_task_handle); + s_ot_task_handle = NULL; + // Only clean up resources if we deleted the task + // If s_ot_task_handle was NULL, the task already cleaned up + esp_openthread_netif_glue_deinit(); + esp_netif_destroy(s_openthread_netif); + esp_vfs_eventfd_unregister(); + } + esp_event_handler_unregister(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, thread_event_handler); + vSemaphoreDelete(s_semph_thread_set_dns_server); + vSemaphoreDelete(s_semph_thread_attached); +} + +esp_err_t net_connect_thread_connect(void) +{ + s_semph_thread_attached = xSemaphoreCreateBinary(); + if (s_semph_thread_attached == NULL) { + return ESP_ERR_NO_MEM; + } + s_semph_thread_set_dns_server = xSemaphoreCreateBinary(); + if (s_semph_thread_set_dns_server == NULL) { + vSemaphoreDelete(s_semph_thread_attached); + return ESP_ERR_NO_MEM; + } + // 4 eventfds might be used for Thread + // * netif + // * ot task queue + // * radio driver + // * border router + esp_vfs_eventfd_config_t eventfd_config = { + .max_fds = 4, + }; + esp_vfs_eventfd_register(&eventfd_config); + ESP_ERROR_CHECK(esp_event_handler_register(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, thread_event_handler, NULL)); + if (xTaskCreate(ot_task_worker, "ot_br_main", CONFIG_NET_CONNECT_THREAD_TASK_STACK_SIZE, NULL, 5, &s_ot_task_handle) != pdPASS) { + esp_event_handler_unregister(OPENTHREAD_EVENT, ESP_EVENT_ANY_ID, thread_event_handler); + vSemaphoreDelete(s_semph_thread_attached); + vSemaphoreDelete(s_semph_thread_set_dns_server); + ESP_LOGE(TAG, "Failed to create openthread task"); + return ESP_FAIL; + } + xSemaphoreTake(s_semph_thread_attached, portMAX_DELAY); + // Wait 1s for the Thread device to set its DNS server with the NAT64 prefix. + if (xSemaphoreTake(s_semph_thread_set_dns_server, 1000 / portTICK_PERIOD_MS) != pdPASS) { + ESP_LOGW(TAG, "DNS server is not set for the Thread device, might be unable to access the Internet"); + } + return ESP_OK; +} diff --git a/components/net_connect/wifi_connect.c b/components/net_connect/wifi_connect.c new file mode 100644 index 0000000000..7b6ac2a421 --- /dev/null +++ b/components/net_connect/wifi_connect.c @@ -0,0 +1,357 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "net_connect.h" +#include "net_connect_private.h" +#include "net_connect_wifi_config.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "sdkconfig.h" + +#if CONFIG_NET_CONNECT_WIFI + +static const char *TAG = "net_connect_wifi"; +static esp_netif_t *s_example_sta_netif = NULL; +static SemaphoreHandle_t s_semph_get_ip_addrs = NULL; +#if CONFIG_NET_CONNECT_IPV6 +static SemaphoreHandle_t s_semph_get_ip6_addrs = NULL; +#endif + +static int s_retry_num = 0; + +/* Static storage for WiFi STA configuration and handle */ +static net_wifi_sta_config_t s_wifi_sta_config; +static net_iface_handle_t s_wifi_sta_handle = NULL; +static bool s_wifi_sta_configured = false; + +static void example_handler_on_wifi_disconnect(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + s_retry_num++; + if (s_retry_num > CONFIG_NET_CONNECT_WIFI_CONN_MAX_RETRY) { + ESP_LOGI(TAG, "WiFi Connect failed %d times, stop reconnect.", s_retry_num); + /* let net_connect_wifi_sta_do_connect() return */ + if (s_semph_get_ip_addrs) { + xSemaphoreGive(s_semph_get_ip_addrs); + } +#if CONFIG_NET_CONNECT_IPV6 + if (s_semph_get_ip6_addrs) { + xSemaphoreGive(s_semph_get_ip6_addrs); + } +#endif + net_connect_wifi_sta_do_disconnect(); + return; + } + wifi_event_sta_disconnected_t *disconn = event_data; + if (disconn->reason == WIFI_REASON_ROAMING) { + ESP_LOGD(TAG, "station roaming, do nothing"); + return; + } + ESP_LOGI(TAG, "Wi-Fi disconnected %d, trying to reconnect...", disconn->reason); + esp_err_t err = esp_wifi_connect(); + if (err == ESP_ERR_WIFI_NOT_STARTED) { + return; + } + ESP_ERROR_CHECK(err); +} + +static void example_handler_on_wifi_connect(void *esp_netif, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ +#if CONFIG_NET_CONNECT_IPV6 + esp_netif_create_ip6_linklocal(esp_netif); +#endif // CONFIG_NET_CONNECT_IPV6 +} + +static void example_handler_on_sta_got_ip(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + s_retry_num = 0; + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + if (!net_connect_is_our_netif(NET_CONNECT_NETIF_DESC_STA, event->esp_netif)) { + return; + } + ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip)); + if (s_semph_get_ip_addrs) { + xSemaphoreGive(s_semph_get_ip_addrs); + } else { + ESP_LOGI(TAG, "- IPv4 address: " IPSTR ",", IP2STR(&event->ip_info.ip)); + } +} + +#if CONFIG_NET_CONNECT_IPV6 +static void example_handler_on_sta_got_ipv6(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data; + if (!net_connect_is_our_netif(NET_CONNECT_NETIF_DESC_STA, event->esp_netif)) { + return; + } + esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip); + ESP_LOGI(TAG, "Got IPv6 event: Interface \"%s\" address: " IPV6STR ", type: %s", esp_netif_get_desc(event->esp_netif), + IPV62STR(event->ip6_info.ip), net_connect_ipv6_addr_types_to_str[ipv6_type]); + + if (ipv6_type == NET_CONNECT_PREFERRED_IPV6_TYPE) { + if (s_semph_get_ip6_addrs) { + xSemaphoreGive(s_semph_get_ip6_addrs); + } else { + ESP_LOGI(TAG, "- IPv6 address: " IPV6STR ", type: %s", IPV62STR(event->ip6_info.ip), net_connect_ipv6_addr_types_to_str[ipv6_type]); + } + } +} +#endif // CONFIG_NET_CONNECT_IPV6 + +/** + * @brief Populate WiFi config from Kconfig defaults + */ +static void populate_config_from_kconfig(net_wifi_sta_config_t *config) +{ + memset(config, 0, sizeof(net_wifi_sta_config_t)); + +#if !CONFIG_NET_CONNECT_WIFI_SSID_PWD_FROM_STDIN + strncpy(config->ssid, CONFIG_NET_CONNECT_WIFI_SSID, sizeof(config->ssid) - 1); + strncpy(config->password, CONFIG_NET_CONNECT_WIFI_PASSWORD, sizeof(config->password) - 1); +#endif + + config->scan_method = NET_CONNECT_WIFI_SCAN_METHOD; + config->sort_method = NET_CONNECT_WIFI_CONNECT_AP_SORT_METHOD; + config->threshold_rssi = CONFIG_NET_CONNECT_WIFI_SCAN_RSSI_THRESHOLD; + config->auth_mode_threshold = NET_CONNECT_WIFI_SCAN_AUTH_MODE_THRESHOLD; + config->max_retry = CONFIG_NET_CONNECT_WIFI_CONN_MAX_RETRY; + + /* IP configuration defaults */ + config->ip.use_dhcp = NET_CONNECT_DEFAULT_USE_DHCP; + config->ip.enable_ipv6 = NET_CONNECT_DEFAULT_ENABLE_IPV6; +} + +/** + * @brief Convert net_wifi_sta_config_t to wifi_config_t + */ +static void convert_to_wifi_config(const net_wifi_sta_config_t *net_config, wifi_config_t *wifi_config) +{ + memset(wifi_config, 0, sizeof(wifi_config_t)); + + strncpy((char*)wifi_config->sta.ssid, net_config->ssid, sizeof(wifi_config->sta.ssid) - 1); + wifi_config->sta.ssid[sizeof(wifi_config->sta.ssid) - 1] = '\0'; + + strncpy((char*)wifi_config->sta.password, net_config->password, sizeof(wifi_config->sta.password) - 1); + wifi_config->sta.password[sizeof(wifi_config->sta.password) - 1] = '\0'; + + wifi_config->sta.scan_method = net_config->scan_method; + wifi_config->sta.sort_method = net_config->sort_method; + wifi_config->sta.threshold.rssi = net_config->threshold_rssi; + wifi_config->sta.threshold.authmode = net_config->auth_mode_threshold; +} + +void net_connect_wifi_start(void) +{ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_WIFI_STA(); + // Warning: the interface desc is used in tests to capture actual connection details (IP, gw, mask) + esp_netif_config.if_desc = NET_CONNECT_NETIF_DESC_STA; + esp_netif_config.route_prio = 128; + s_example_sta_netif = esp_netif_create_wifi(WIFI_IF_STA, &esp_netif_config); + esp_wifi_set_default_wifi_sta_handlers(); + + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); +} + + +void net_connect_wifi_stop(void) +{ + esp_err_t err = esp_wifi_stop(); + if (err == ESP_ERR_WIFI_NOT_INIT) { + return; + } + ESP_ERROR_CHECK(err); + ESP_ERROR_CHECK(esp_wifi_deinit()); + ESP_ERROR_CHECK(esp_wifi_clear_default_wifi_driver_and_handlers(s_example_sta_netif)); + esp_netif_destroy(s_example_sta_netif); + s_example_sta_netif = NULL; +} + + +esp_err_t net_connect_wifi_sta_do_connect(wifi_config_t wifi_config, bool wait) +{ + if (wait) { +#if CONFIG_NET_CONNECT_IPV4 + s_semph_get_ip_addrs = xSemaphoreCreateBinary(); + if (s_semph_get_ip_addrs == NULL) { + return ESP_ERR_NO_MEM; + } +#endif +#if CONFIG_NET_CONNECT_IPV6 + s_semph_get_ip6_addrs = xSemaphoreCreateBinary(); + if (s_semph_get_ip6_addrs == NULL) { +#if CONFIG_NET_CONNECT_IPV4 + vSemaphoreDelete(s_semph_get_ip_addrs); + s_semph_get_ip_addrs = NULL; +#endif + return ESP_ERR_NO_MEM; + } +#endif + } + s_retry_num = 0; + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &example_handler_on_wifi_disconnect, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &example_handler_on_sta_got_ip, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &example_handler_on_wifi_connect, s_example_sta_netif)); +#if CONFIG_NET_CONNECT_IPV6 + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &example_handler_on_sta_got_ipv6, NULL)); +#endif + + ESP_LOGI(TAG, "Connecting to %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + esp_err_t ret = esp_wifi_connect(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "WiFi connect failed! ret:%x", ret); + return ret; + } + if (wait) { + ESP_LOGI(TAG, "Waiting for IP(s)"); +#if CONFIG_NET_CONNECT_IPV4 + xSemaphoreTake(s_semph_get_ip_addrs, portMAX_DELAY); + vSemaphoreDelete(s_semph_get_ip_addrs); + s_semph_get_ip_addrs = NULL; +#endif +#if CONFIG_NET_CONNECT_IPV6 + xSemaphoreTake(s_semph_get_ip6_addrs, portMAX_DELAY); + vSemaphoreDelete(s_semph_get_ip6_addrs); + s_semph_get_ip6_addrs = NULL; +#endif + if (s_retry_num > CONFIG_NET_CONNECT_WIFI_CONN_MAX_RETRY) { + return ESP_FAIL; + } + } + return ESP_OK; +} + +esp_err_t net_connect_wifi_sta_do_disconnect(void) +{ + ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &example_handler_on_wifi_disconnect)); + ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &example_handler_on_sta_got_ip)); + ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &example_handler_on_wifi_connect)); +#if CONFIG_NET_CONNECT_IPV6 + ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_GOT_IP6, &example_handler_on_sta_got_ipv6)); +#endif + return esp_wifi_disconnect(); +} + +void net_connect_wifi_shutdown(void) +{ + ESP_LOGI(TAG, "WiFi shutdown handler called"); + net_connect_wifi_sta_do_disconnect(); + net_connect_wifi_stop(); + s_wifi_sta_configured = false; + s_wifi_sta_handle = NULL; +} + +net_iface_handle_t net_configure_wifi_sta(net_wifi_sta_config_t *config) +{ + ESP_LOGI(TAG, "Configuring Wi-Fi STA interface..."); + + if (config == NULL) { + /* Use Kconfig defaults */ + populate_config_from_kconfig(&s_wifi_sta_config); + } else { + /* Validate config */ + if (strlen(config->ssid) == 0) { + ESP_LOGE(TAG, "STA SSID is empty"); + return NULL; + } + + /* Store configuration */ + memcpy(&s_wifi_sta_config, config, sizeof(net_wifi_sta_config_t)); + } + + s_wifi_sta_configured = true; + s_wifi_sta_handle = (net_iface_handle_t)&s_wifi_sta_config; + + return s_wifi_sta_handle; +} + +esp_err_t net_connect_wifi(void) +{ + ESP_LOGI(TAG, "Connecting configured Wi-Fi interfaces..."); + + if (!s_wifi_sta_configured) { + ESP_LOGE(TAG, "Wi-Fi STA not configured. Call net_configure_wifi_sta() first"); + return ESP_ERR_INVALID_STATE; + } + + /* Start WiFi driver and create netif */ + net_connect_wifi_start(); + + /* Convert stored config to wifi_config_t */ + wifi_config_t wifi_config; + convert_to_wifi_config(&s_wifi_sta_config, &wifi_config); + +#if CONFIG_NET_CONNECT_WIFI_SSID_PWD_FROM_STDIN + net_configure_stdin_stdout(); + char buf[sizeof(wifi_config.sta.ssid) + sizeof(wifi_config.sta.password) + 2] = {0}; + ESP_LOGI(TAG, "Please input ssid password:"); + if (fgets(buf, sizeof(buf), stdin) == NULL) { + ESP_LOGE(TAG, "Failed to read SSID/password from stdin (EOF or error)"); + net_connect_wifi_stop(); + return ESP_ERR_INVALID_STATE; + } + int len = strlen(buf); + if (len > 0) { + buf[len - 1] = '\0'; /* removes '\n' */ + } + memset(wifi_config.sta.ssid, 0, sizeof(wifi_config.sta.ssid)); + + char *rest = NULL; + char *temp = strtok_r(buf, " ", &rest); + if (temp == NULL) { + ESP_LOGE(TAG, "SSID is empty or invalid"); + net_connect_wifi_stop(); + return ESP_ERR_INVALID_ARG; + } + strncpy((char*)wifi_config.sta.ssid, temp, sizeof(wifi_config.sta.ssid) - 1); + wifi_config.sta.ssid[sizeof(wifi_config.sta.ssid) - 1] = '\0'; + memset(wifi_config.sta.password, 0, sizeof(wifi_config.sta.password)); + temp = strtok_r(NULL, " ", &rest); + if (temp) { + strncpy((char*)wifi_config.sta.password, temp, sizeof(wifi_config.sta.password) - 1); + wifi_config.sta.password[sizeof(wifi_config.sta.password) - 1] = '\0'; + } else { + wifi_config.sta.threshold.authmode = WIFI_AUTH_OPEN; + } +#endif + + /* Connect using existing internal function */ + esp_err_t err = net_connect_wifi_sta_do_connect(wifi_config, true); + if (err != ESP_OK) { + net_connect_wifi_stop(); + return err; + } + + return ESP_OK; +} + +esp_err_t net_disconnect_wifi(void) +{ + ESP_LOGI(TAG, "Disconnecting Wi-Fi interfaces..."); + + net_connect_wifi_shutdown(); + + return ESP_OK; +} + +bool net_connect_wifi_is_configured(void) +{ + return s_wifi_sta_configured; +} + +#endif /* CONFIG_NET_CONNECT_WIFI */ diff --git a/test_app/CMakeLists.txt b/test_app/CMakeLists.txt index bda7a057a9..119f755005 100644 --- a/test_app/CMakeLists.txt +++ b/test_app/CMakeLists.txt @@ -14,6 +14,7 @@ set(EXTRA_COMPONENT_DIRS ../components/console_cmd_wifi ../components/console_simple_init ../components/mbedtls_cxx + ../components/net_connect ../components/sock_utils ../components/mdns)