From 547210f7a5f37d5a577f4fd489f6b77f53f05e1d Mon Sep 17 00:00:00 2001 From: Liu Han Date: Wed, 4 Dec 2019 18:27:28 +0800 Subject: [PATCH 1/4] socket-example: Add tcp client example for multiple interfaces --- .../tcp_client_multi_net/CMakeLists.txt | 10 ++ .../sockets/tcp_client_multi_net/Makefile | 11 ++ .../sockets/tcp_client_multi_net/README.md | 133 ++++++++++++++++ .../tcp_client_multi_net/main/CMakeLists.txt | 2 + .../main/Kconfig.projbuild | 16 ++ .../tcp_client_multi_net/main/component.mk | 4 + .../main/tcp_client_multiple.c | 144 ++++++++++++++++++ .../tcp_client_multi_net/sdkconfig.defaults | 2 + 8 files changed, 322 insertions(+) create mode 100644 examples/protocols/sockets/tcp_client_multi_net/CMakeLists.txt create mode 100644 examples/protocols/sockets/tcp_client_multi_net/Makefile create mode 100644 examples/protocols/sockets/tcp_client_multi_net/README.md create mode 100644 examples/protocols/sockets/tcp_client_multi_net/main/CMakeLists.txt create mode 100644 examples/protocols/sockets/tcp_client_multi_net/main/Kconfig.projbuild create mode 100644 examples/protocols/sockets/tcp_client_multi_net/main/component.mk create mode 100644 examples/protocols/sockets/tcp_client_multi_net/main/tcp_client_multiple.c create mode 100644 examples/protocols/sockets/tcp_client_multi_net/sdkconfig.defaults diff --git a/examples/protocols/sockets/tcp_client_multi_net/CMakeLists.txt b/examples/protocols/sockets/tcp_client_multi_net/CMakeLists.txt new file mode 100644 index 0000000000..423899f2eb --- /dev/null +++ b/examples/protocols/sockets/tcp_client_multi_net/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following 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.5) + +# (Not part of the boilerplate) +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(tcp_client_multiple) diff --git a/examples/protocols/sockets/tcp_client_multi_net/Makefile b/examples/protocols/sockets/tcp_client_multi_net/Makefile new file mode 100644 index 0000000000..1505a97add --- /dev/null +++ b/examples/protocols/sockets/tcp_client_multi_net/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := tcp_client_multiple + +EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/sockets/tcp_client_multi_net/README.md b/examples/protocols/sockets/tcp_client_multi_net/README.md new file mode 100644 index 0000000000..0217fce72c --- /dev/null +++ b/examples/protocols/sockets/tcp_client_multi_net/README.md @@ -0,0 +1,133 @@ +# Multiple Ethernet Example +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +## Overview + +This example demonstrates basic usage of Ethernet interface and WiFi station together. The workflow of the example could be as follow: + +1. Connects to both WiFi and Ethernet using common-connect component +2. Starts two tasks, one for each interface to resolve configured host name and connect to it periodically +3. Connection to host endpoint is handled by: + - creating a socket as TCP client + - binding it to the related interface (Ethernet of WiFi) + - send and receive a trivial HTTP request and response + +If you have a new multiple interface application to go (for example, connect to IoT cloud via Ethernet and WiFi), try this as a basic template, then add your own code. + +## How to use example + +### Hardware Required + +To run this example, you need to have one ESP32 development board integrated with an Ethernet interface, for example, ESP32-Ethernet-Kit, or just connect your ESP32-DevkitC board to a breakout board which features RMII Ethernet PHY. + +### Configure the project + +Enter project configuration by `idf.py menuconfig` (or `make menuconfig` if using legacy GNU Make build system) and navigate into: + +* `Example Connection Configuration` menu to choose the connection details: + + - Enter SSID and password for WiFi connection + - Set Ethernet type and configuration for Ethernet connection + - Note that the project is preconfigured to have both WiFi and Ethernet interface enabled by default + - See the [README.md](../../README.md) for more details about common example connection component + +* `Example Configuration` menu: + + - Set host name and port for the tcp_client to connect to + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build and flash the project.. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +```bash +I (695) example_connect: Connecting to DavidsAP... +I (795) phy: phy_version: 4180, cb3948e, Sep 12 2019, 16:39:13, 0, 0 +I (795) wifi:mode : sta (30:ae:a4:c6:b4:f8) +W (795) event: handler already registered, overwriting +I (815) esp_eth.netif.glue: 30:ae:a4:c6:b4:fb +I (815) esp_eth.netif.glue: ethernet attached to netif +I (825) example_connect: Waiting for IP(s) +I (1525) wifi:new:<6,0>, old:<1,0>, ap:<255,255>, sta:<6,0>, prof:1 +I (2295) wifi:state: init -> auth (b0) +I (2295) wifi:state: auth -> assoc (0) +I (2305) wifi:state: assoc -> run (10) +I (2315) wifi:connected with DavidsAP, aid = 2, channel 6, BW20, bssid = 16:f7:28:37:58:36 +I (2315) wifi:security type: 3, phy: bgn, rssi: -35 +I (2315) wifi:pm start, type: 1 + +I (2325) wifi:AP's beacon interval = 102400 us, DTIM period = 3 +I (3125) esp_netif_handlers: example_connect: sta ip: 192.168.2.15, mask: 255.255.255.0, gw: 192.168.2.1 +I (3125) example_connect: Interface desciption example_connect: sta +I (3135) example_connect: Interface "example_connect: sta" got IPv4 address: 192.168.2.15 +I (3625) example_connect: Interface "example_connect: sta" got IPv6 address: fe80:0000:0000:0000:32ae:a4ff:fec6:b4f8, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (4825) example_connect: Ethernet Link Up +I (5625) esp_netif_handlers: example_connect: eth ip: 192.168.32.148, mask: 255.255.252.0, gw: 192.168.32.3 +I (5625) example_connect: Interface desciption example_connect: eth +I (5635) example_connect: Interface "example_connect: eth" got IPv4 address: 192.168.32.148 +I (6625) example_connect: Interface "example_connect: eth" got IPv6 address: fe80:0000:0000:0000:32ae:a4ff:fec6:b4fb, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (6625) example_connect: Connected to example_connect: eth +I (6635) example_connect: - IPv4 address: 192.168.32.148 +I (6635) example_connect: - IPv6 address: fe80:0000:0000:0000:32ae:a4ff:fec6:b4fbtype: ESP_IP6_ADDR_IS_LINK_LOCAL +I (6645) example_connect: Connected to example_connect: sta +I (6655) example_connect: - IPv4 address: 192.168.2.15 +I (6655) example_connect: - IPv6 address: fe80:0000:0000:0000:32ae:a4ff:fec6:b4f8type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (6675) example_connect: Connected to Ethernet +I (6675) tcp_client_multiple: netif described as "sta" corresponds to esp-netif ptr:0x3ffba3ac +I (6675) tcp_client_multiple: netif described as "eth" corresponds to esp-netif ptr:0x3ffc608c +I (6895) tcp_client_multiple: "example_connect: eth" Socket created +I (6895) tcp_client_multiple: "example_connect: sta" Socket created +I (6895) tcp_client_multiple: "example_connect: eth" Successfully connected +I (6905) tcp_client_multiple: "example_connect: sta" Successfully connected +I (6965) tcp_client_multiple: "example_connect: eth" Received Data 127 bytes +I (6965) tcp_client_multiple: HTTP/1.1 200 OK +Date: Thu, 23 Apr 2020 07:02:58 GMT +Expires: -1 +Cache-Control: private, max-age=0 +Content-Type: text/html; +I (6965) tcp_client_multiple: "example_connect: sta" Received Data 127 bytes +I (6985) tcp_client_multiple: HTTP/1.1 200 OK +Date: Thu, 23 Apr 2020 07:02:58 GMT +Expires: -1 +Cache-Control: private, max-age=0 +Content-Type: text/html; +I (7675) tcp_client_multiple: "example_connect: eth" Socket created +I (7675) tcp_client_multiple: "example_connect: eth" Successfully connected +I (7695) tcp_client_multiple: "example_connect: sta" Socket created +I (7705) tcp_client_multiple: "example_connect: sta" Successfully connected +I (7735) tcp_client_multiple: "example_connect: eth" Received Data 127 bytes +I (7735) tcp_client_multiple: HTTP/1.1 200 OK +Date: Thu, 23 Apr 2020 07:02:59 GMT +Expires: -1 +Cache-Control: private, max-age=0 +Content-Type: text/html; +I (7955) tcp_client_multiple: "example_connect: sta" Received Data 127 bytes +I (7955) tcp_client_multiple: HTTP/1.1 200 OK +Date: Thu, 23 Apr 2020 07:02:59 GMT +Expires: -1 +Cache-Control: private, max-age=0 +Content-Type: text/html; +I (8445) tcp_client_multiple: "example_connect: eth" Socket created +I (8445) tcp_client_multiple: "example_connect: eth" Successfully connected +I (8505) tcp_client_multiple: "example_connect: eth" Received Data 127 bytes +I (8505) tcp_client_multiple: HTTP/1.1 200 OK +Date: Thu, 23 Apr 2020 07:03:00 GMT +Expires: -1 +Cache-Control: private, max-age=0 +Content-Type: text/html; +I (8675) tcp_client_multiple: "example_connect: sta" Socket created +``` + +## Troubleshooting + +* When connecting using Ethernet, please consult troubleshooting described in [Ethernet common readme](../../../ethernet/README.md) +or [Ethernet documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_eth.html). +If using Ethernet for the first time, it is recommended to start with the [Ethernet example readme](../../../ethernet/basic/README.md), which contains instructions for connecting and configuring the PHY. +Once Ethernet example obtains IP address successfully, proceed to this example. + +* When connecting using Wi-Fi, please refer to the WiFi examples in [examples/wifi/getting_started/](../wifi/getting_started). diff --git a/examples/protocols/sockets/tcp_client_multi_net/main/CMakeLists.txt b/examples/protocols/sockets/tcp_client_multi_net/main/CMakeLists.txt new file mode 100644 index 0000000000..65a87f685f --- /dev/null +++ b/examples/protocols/sockets/tcp_client_multi_net/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "tcp_client_multiple.c" + INCLUDE_DIRS ".") diff --git a/examples/protocols/sockets/tcp_client_multi_net/main/Kconfig.projbuild b/examples/protocols/sockets/tcp_client_multi_net/main/Kconfig.projbuild new file mode 100644 index 0000000000..af1a705526 --- /dev/null +++ b/examples/protocols/sockets/tcp_client_multi_net/main/Kconfig.projbuild @@ -0,0 +1,16 @@ +menu "Example Configuration" + + config EXAMPLE_HOST_NAME + string "Host Name" + default "baidu.com" + help + host name + + config EXAMPLE_HOST_PORT + int "Host Port" + default 80 + range 0 65535 + help + host port + +endmenu diff --git a/examples/protocols/sockets/tcp_client_multi_net/main/component.mk b/examples/protocols/sockets/tcp_client_multi_net/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/protocols/sockets/tcp_client_multi_net/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/protocols/sockets/tcp_client_multi_net/main/tcp_client_multiple.c b/examples/protocols/sockets/tcp_client_multi_net/main/tcp_client_multiple.c new file mode 100644 index 0000000000..f69262f2f8 --- /dev/null +++ b/examples/protocols/sockets/tcp_client_multi_net/main/tcp_client_multiple.c @@ -0,0 +1,144 @@ +/* multiple network interface Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_netif.h" +#include "esp_eth.h" +#include "esp_event.h" +#include "esp_log.h" +#include "sdkconfig.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include +#include +#include "protocol_examples_common.h" + +static const char *TAG = "tcp_client_multiple"; + +#define HOST_NAME CONFIG_EXAMPLE_HOST_NAME +#define HOST_IP_PORT CONFIG_EXAMPLE_HOST_PORT + +static const char *payload = "GET / HTTP/1.1\r\n\r\n"; + +static void app_multiple_handle(esp_ip4_addr_t *ip4_addr, esp_netif_t *esp_netif) +{ + esp_netif_ip_info_t ip; + char rx_buffer[128] = {0}; + const char *netif_name = esp_netif_get_desc(esp_netif); + + /* Create a socket */ + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (sock < 0) { + ESP_LOGE(TAG, "\"%s\" Unable to create socket: errno %d", netif_name, errno); + goto app_multiple_handle_fail; + } + ESP_LOGI(TAG, "\"%s\" Socket created", netif_name); + + /* Bind local IP of the network interface */ + memset(&ip, 0, sizeof(esp_netif_ip_info_t)); + ESP_ERROR_CHECK(esp_netif_get_ip_info(esp_netif, &ip)); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(0); + addr.sin_addr.s_addr = ip.ip.addr; + + int ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + ESP_LOGE(TAG, "\"%s\" Unable to bind socket: errno %d", netif_name, errno); + goto app_multiple_handle_fail; + } + + /* Connect to the host by the network interface */ + struct sockaddr_in destAddr; + destAddr.sin_addr.s_addr = ip4_addr->addr; + destAddr.sin_family = AF_INET; + destAddr.sin_port = htons(HOST_IP_PORT); + ret = connect(sock, (struct sockaddr *)&destAddr, sizeof(destAddr)); + if (ret != 0) { + ESP_LOGE(TAG, "\"%s\" Socket unable to connect: errno %d", netif_name, errno); + goto app_multiple_handle_fail; + } + ESP_LOGI(TAG, "\"%s\" Successfully connected", netif_name); + + ret = send(sock, payload, strlen(payload), 0); + if (ret < 0) { + ESP_LOGE(TAG, "\"%s\" Error occured during sending: errno %d", netif_name, errno); + goto app_multiple_handle_fail; + } + + ret = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); + if (ret < 0) { + ESP_LOGE(TAG, "\"%s\" Error occured during receiving: errno %d", netif_name, errno); + } else if (ret > 0){ + rx_buffer[ret] = 0; // Null-terminate whatever we received and treat like a string + ESP_LOGI(TAG, "\"%s\" Received Data %d bytes", netif_name, ret); + ESP_LOGI(TAG, "%s", rx_buffer); + } else { + ESP_LOGE(TAG, "\"%s\" Closed connection during receiving", netif_name); + } + +app_multiple_handle_fail: + close(sock); +} + +static void app_connection_task(void *pvParameters) +{ + esp_ip4_addr_t ip4_addr; + const char *netif_desc = pvParameters; + + esp_netif_t *netif = get_example_netif_from_desc(netif_desc); + ESP_LOGD(TAG, "netif described as \"%s\" corresponds to esp-netif ptr:%p", netif_desc, netif); + while(netif) { + /* Wait for the host name to get */ + const struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + }; + struct addrinfo *res; + + int err = getaddrinfo(HOST_NAME, NULL, &hints, &res); + if(err != 0 || res == NULL) { + ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res); + break; + } + memcpy(&ip4_addr, &((struct sockaddr_in *)(res->ai_addr))->sin_addr, sizeof(ip4_addr)); + freeaddrinfo(res); + + /* Connect the host using the corresponding network interface */ + app_multiple_handle(&ip4_addr, netif); + + vTaskDelay(500 / portTICK_PERIOD_MS); + } + ESP_LOGE(TAG, "%s with netif desc:%s Failed! exiting", __func__, netif_desc); + vTaskDelete(NULL); +} + +void app_main(void) +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + esp_netif_init(); + + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + ESP_ERROR_CHECK(example_connect()); + + xTaskCreate(&app_connection_task, "app_ethernet_task", 4096, "eth", 5, NULL); + xTaskCreate(&app_connection_task, "app_wifi_task", 4096, "sta", 5, NULL); +} diff --git a/examples/protocols/sockets/tcp_client_multi_net/sdkconfig.defaults b/examples/protocols/sockets/tcp_client_multi_net/sdkconfig.defaults new file mode 100644 index 0000000000..148809bf1e --- /dev/null +++ b/examples/protocols/sockets/tcp_client_multi_net/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_EXAMPLE_CONNECT_WIFI=y +CONFIG_EXAMPLE_CONNECT_ETHERNET=y From 06711c7c367cc89b3525e01c5dfe490edfe64c5a Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 23 Apr 2020 16:23:46 +0200 Subject: [PATCH 2/4] examples: common connect component to use both interfaces at once --- components/esp_netif/include/esp_netif.h | 11 + components/esp_netif/lwip/esp_netif_lwip.c | 29 ++- .../Kconfig.projbuild | 26 +-- .../protocol_examples_common/connect.c | 192 +++++++++++++----- .../include/protocol_examples_common.h | 14 ++ examples/protocols/README.md | 4 +- 6 files changed, 208 insertions(+), 68 deletions(-) diff --git a/components/esp_netif/include/esp_netif.h b/components/esp_netif/include/esp_netif.h index 22874808fb..d0cda153a2 100644 --- a/components/esp_netif/include/esp_netif.h +++ b/components/esp_netif/include/esp_netif.h @@ -648,6 +648,17 @@ esp_err_t esp_netif_get_ip6_linklocal(esp_netif_t *esp_netif, esp_ip6_addr_t *if */ esp_err_t esp_netif_get_ip6_global(esp_netif_t *esp_netif, esp_ip6_addr_t *if_ip6); +/** + * @brief Get all IPv6 addresses of the specified interface + * + * @param[in] esp_netif Handle to esp-netif instance + * @param[out] if_ip6 Array of IPv6 addresses will be copied to the argument + * + * @return + * number of returned IPv6 addresses + */ +int esp_netif_get_all_ip6(esp_netif_t *esp_netif, esp_ip6_addr_t if_ip6[]); + /** * @brief Sets IPv4 address to the specified octets * diff --git a/components/esp_netif/lwip/esp_netif_lwip.c b/components/esp_netif/lwip/esp_netif_lwip.c index 6849021834..0d57b66489 100644 --- a/components/esp_netif/lwip/esp_netif_lwip.c +++ b/components/esp_netif/lwip/esp_netif_lwip.c @@ -1446,7 +1446,7 @@ static esp_err_t esp_netif_create_ip6_linklocal_api(esp_netif_api_msg_t *msg) { esp_netif_t *esp_netif = msg->esp_netif; - ESP_LOGD(TAG, "%s esp-netif:%p", __func__, esp_netif); + ESP_LOGV(TAG, "%s esp-netif:%p", __func__, esp_netif); struct netif *p_netif = esp_netif->lwip_netif; if (p_netif != NULL && netif_is_up(p_netif)) { @@ -1462,7 +1462,7 @@ esp_err_t esp_netif_create_ip6_linklocal(esp_netif_t *esp_netif) _RUN_IN_LWIP_TA esp_err_t esp_netif_get_ip6_linklocal(esp_netif_t *esp_netif, esp_ip6_addr_t *if_ip6) { - ESP_LOGD(TAG, "%s esp-netif:%p", __func__, esp_netif); + ESP_LOGV(TAG, "%s esp-netif:%p", __func__, esp_netif); if (esp_netif == NULL || if_ip6 == NULL || esp_netif->is_ppp_netif) { return ESP_ERR_ESP_NETIF_INVALID_PARAMS; @@ -1479,7 +1479,7 @@ esp_err_t esp_netif_get_ip6_linklocal(esp_netif_t *esp_netif, esp_ip6_addr_t *if esp_err_t esp_netif_get_ip6_global(esp_netif_t *esp_netif, esp_ip6_addr_t *if_ip6) { - ESP_LOGD(TAG, "%s esp-netif:%p", __func__, esp_netif); + ESP_LOGV(TAG, "%s esp-netif:%p", __func__, esp_netif); if (esp_netif == NULL || if_ip6 == NULL) { return ESP_ERR_ESP_NETIF_INVALID_PARAMS; @@ -1496,10 +1496,31 @@ esp_err_t esp_netif_get_ip6_global(esp_netif_t *esp_netif, esp_ip6_addr_t *if_ip } } } - + return ESP_FAIL; } +int esp_netif_get_all_ip6(esp_netif_t *esp_netif, esp_ip6_addr_t if_ip6[]) +{ + ESP_LOGV(TAG, "%s esp-netif:%p", __func__, esp_netif); + + if (esp_netif == NULL || if_ip6 == NULL) { + return 0; + } + + int addr_count = 0; + struct netif *p_netif = esp_netif->lwip_netif; + + if (p_netif != NULL && netif_is_up(p_netif)) { + for (int i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { + if (!ip_addr_cmp(&p_netif->ip6_addr[i], IP6_ADDR_ANY)) { + memcpy(&if_ip6[addr_count++], &p_netif->ip6_addr[i], sizeof(ip6_addr_t)); + } + } + } + return addr_count; +} + esp_netif_flags_t esp_netif_get_flags(esp_netif_t *esp_netif) { return esp_netif->flags; diff --git a/examples/common_components/protocol_examples_common/Kconfig.projbuild b/examples/common_components/protocol_examples_common/Kconfig.projbuild index f47b8a6af5..eccf778890 100644 --- a/examples/common_components/protocol_examples_common/Kconfig.projbuild +++ b/examples/common_components/protocol_examples_common/Kconfig.projbuild @@ -1,18 +1,11 @@ menu "Example Connection Configuration" - choice EXAMPLE_CONNECT_INTERFACE - prompt "Connect using" - default EXAMPLE_CONNECT_WIFI + + config EXAMPLE_CONNECT_WIFI + bool "connect using WiFi interface" + default y help - Protocol examples can use Wi-Fi or Ethernet to connect to the network. - Choose which interface to use. - - config EXAMPLE_CONNECT_WIFI - bool "Wi-Fi" - - config EXAMPLE_CONNECT_ETHERNET - bool "Ethernet" - - endchoice + Protocol examples can use Wi-Fi and/or Ethernet to connect to the network. + Choose this option to connect with WiFi if EXAMPLE_CONNECT_WIFI config EXAMPLE_WIFI_SSID @@ -29,6 +22,13 @@ menu "Example Connection Configuration" Can be left blank if the network has no security set. endif + config EXAMPLE_CONNECT_ETHERNET + bool "connect using Ethernet interface" + default n + help + Protocol examples can use Wi-Fi and/or Ethernet to connect to the network. + Choose this option to connect with Ethernet + if EXAMPLE_CONNECT_ETHERNET choice EXAMPLE_USE_ETHERNET prompt "Ethernet Type" diff --git a/examples/common_components/protocol_examples_common/connect.c b/examples/common_components/protocol_examples_common/connect.c index e845837cb6..653a43ea00 100644 --- a/examples/common_components/protocol_examples_common/connect.c +++ b/examples/common_components/protocol_examples_common/connect.c @@ -25,11 +25,9 @@ #include "lwip/err.h" #include "lwip/sys.h" -#define GOT_IPV4_BIT BIT(0) -#define GOT_IPV6_BIT BIT(1) - #ifdef CONFIG_EXAMPLE_CONNECT_IPV6 -#define CONNECTED_BITS (GOT_IPV4_BIT | GOT_IPV6_BIT) +#define MAX_IP6_ADDRS_PER_NETIF (5) +#define NR_OF_IP_ADDRESSES_TO_WAIT_FOR (s_active_interfaces*2) #if defined(CONFIG_EXAMPLE_CONNECT_IPV6_PREF_LOCAL_LINK) #define EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE ESP_IP6_ADDR_IS_LINK_LOCAL @@ -42,12 +40,12 @@ #endif // if-elif CONFIG_EXAMPLE_CONNECT_IPV6_PREF_... #else -#define CONNECTED_BITS (GOT_IPV4_BIT) +#define NR_OF_IP_ADDRESSES_TO_WAIT_FOR (s_active_interfaces) #endif -static EventGroupHandle_t s_connect_event_group; +static int s_active_interfaces = 0; +static xSemaphoreHandle s_semph_get_ip_addrs; static esp_ip4_addr_t s_ip_addr; -static const char *s_connection_name; static esp_netif_t *s_example_esp_netif = NULL; #ifdef CONFIG_EXAMPLE_CONNECT_IPV6 @@ -66,19 +64,72 @@ static const char *s_ipv6_addr_types[] = { static const char *TAG = "example_connect"; -/* set up connection, Wi-Fi or Ethernet */ -static void start(void); +#if CONFIG_EXAMPLE_CONNECT_WIFI +static esp_netif_t* wifi_start(void); +static void wifi_stop(void); +#endif +#if CONFIG_EXAMPLE_CONNECT_ETHERNET +static esp_netif_t* eth_start(void); +static void eth_stop(void); +#endif + +/** + * @brief Checks the netif description if it contains specified prefix. + * All netifs created withing common connect component are prefixed with the module TAG, + * so it returns true if the specified netif is owned by this module + */ +static bool is_our_netif(const char *prefix, esp_netif_t *netif) +{ + return strncmp(prefix, esp_netif_get_desc(netif), strlen(prefix)-1) == 0; +} + +/* set up connection, Wi-Fi and/or Ethernet */ +static void start(void) +{ + +#if CONFIG_EXAMPLE_CONNECT_WIFI + s_example_esp_netif = wifi_start(); + s_active_interfaces++; +#endif + +#if CONFIG_EXAMPLE_CONNECT_ETHERNET + s_example_esp_netif = eth_start(); + s_active_interfaces++; +#endif + +#if CONFIG_EXAMPLE_CONNECT_WIFI && CONFIG_EXAMPLE_CONNECT_ETHERNET + /* if both intefaces at once, clear out to indicate that multiple netifs are active */ + s_example_esp_netif = NULL; +#endif + + s_semph_get_ip_addrs = xSemaphoreCreateCounting(NR_OF_IP_ADDRESSES_TO_WAIT_FOR, 0); +} /* tear down connection, release resources */ -static void stop(void); +static void stop(void) +{ +#if CONFIG_EXAMPLE_CONNECT_WIFI + wifi_stop(); + s_active_interfaces--; +#endif + +#if CONFIG_EXAMPLE_CONNECT_ETHERNET + eth_stop(); + s_active_interfaces--; +#endif +} static void on_got_ip(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { - ESP_LOGI(TAG, "Got IP event!"); ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + if (!is_our_netif(TAG, event->esp_netif)) { + ESP_LOGW(TAG, "Got IPv4 from another interface \"%s\": ignored", esp_netif_get_desc(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)); memcpy(&s_ip_addr, &event->ip_info.ip, sizeof(s_ip_addr)); - xEventGroupSetBits(s_connect_event_group, GOT_IPV4_BIT); + xSemaphoreGive(s_semph_get_ip_addrs); } #ifdef CONFIG_EXAMPLE_CONNECT_IPV6 @@ -87,15 +138,16 @@ static void 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 (event->esp_netif != s_example_esp_netif) { - ESP_LOGD(TAG, "Got IPv6 from another netif: ignored"); + if (!is_our_netif(TAG, event->esp_netif)) { + ESP_LOGW(TAG, "Got IPv6 from another netif: ignored"); return; } esp_ip6_addr_type_t ipv6_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip); - ESP_LOGI(TAG, "Got IPv6 address: " IPV6STR ", type: %s", IPV62STR(event->ip6_info.ip), s_ipv6_addr_types[ipv6_type]); + ESP_LOGI(TAG, "Got IPv6 event: Interface \"%s\" address: " IPV6STR ", type: %s", esp_netif_get_desc(event->esp_netif), + IPV62STR(event->ip6_info.ip), s_ipv6_addr_types[ipv6_type]); if (ipv6_type == EXAMPLE_CONNECT_PREFERRED_IPV6_TYPE) { memcpy(&s_ipv6_addr, &event->ip6_info.ip, sizeof(s_ipv6_addr)); - xEventGroupSetBits(s_connect_event_group, GOT_IPV6_BIT); + xSemaphoreGive(s_semph_get_ip_addrs); } } @@ -103,32 +155,47 @@ static void on_got_ipv6(void *arg, esp_event_base_t event_base, esp_err_t example_connect(void) { - if (s_connect_event_group != NULL) { + if (s_semph_get_ip_addrs != NULL) { return ESP_ERR_INVALID_STATE; } - s_connect_event_group = xEventGroupCreate(); start(); ESP_ERROR_CHECK(esp_register_shutdown_handler(&stop)); - ESP_LOGI(TAG, "Waiting for IP"); - xEventGroupWaitBits(s_connect_event_group, CONNECTED_BITS, true, true, portMAX_DELAY); - ESP_LOGI(TAG, "Connected to %s", s_connection_name); - ESP_LOGI(TAG, "IPv4 address: " IPSTR, IP2STR(&s_ip_addr)); + ESP_LOGI(TAG, "Waiting for IP(s)"); + for (int i=0; idel(s_phy)); ESP_ERROR_CHECK(s_mac->del(s_mac)); - esp_netif_destroy(s_example_esp_netif); + esp_netif_destroy(eth_netif); s_example_esp_netif = NULL; } @@ -334,3 +413,18 @@ esp_netif_t *get_example_netif(void) { return s_example_esp_netif; } + +esp_netif_t *get_example_netif_from_desc(const char *desc) +{ + esp_netif_t *netif = NULL; + char *expected_desc; + asprintf(&expected_desc, "%s: %s", TAG, desc); + while ((netif = esp_netif_next(netif)) != NULL) { + if (strcmp(esp_netif_get_desc(netif), expected_desc) == 0) { + free(expected_desc); + return netif; + } + } + free(expected_desc); + return netif; +} diff --git a/examples/common_components/protocol_examples_common/include/protocol_examples_common.h b/examples/common_components/protocol_examples_common/include/protocol_examples_common.h index 98f67e3486..859264df06 100644 --- a/examples/common_components/protocol_examples_common/include/protocol_examples_common.h +++ b/examples/common_components/protocol_examples_common/include/protocol_examples_common.h @@ -57,8 +57,22 @@ esp_err_t example_configure_stdin_stdout(void); /** * @brief Returns esp-netif pointer created by example_connect() * + * @note If multiple interfaces active at once, this API return NULL + * In that case the get_example_netif_from_desc() should be used + * to get esp-netif pointer based on interface description */ esp_netif_t *get_example_netif(void); + +/** + * @brief Returns esp-netif pointer created by example_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 *get_example_netif_from_desc(const char *desc); + #ifdef __cplusplus } #endif diff --git a/examples/protocols/README.md b/examples/protocols/README.md index 3e8570622b..701daebf94 100644 --- a/examples/protocols/README.md +++ b/examples/protocols/README.md @@ -8,13 +8,13 @@ See the [README.md](../README.md) file in the upper level [examples](../) direct ### About the `example_connect()` Function -Protocols examples use a simple helper function, `example_connect()`, to establish Wi-Fi or Ethernet connection. This function is implemented in [examples/common_components/protocol_examples/common/connect.c](../common_components/protocol_examples_common/connect.c), and has a very simple behavior: block until connection is established and IP address is obtained, then return. This function is used to reduce the amount of boilerplate and to keep the example code focused on the protocol or library being demonstrated. +Protocols examples use a simple helper function, `example_connect()`, to establish Wi-Fi and/or Ethernet connection. This function is implemented in [examples/common_components/protocol_examples/common/connect.c](../common_components/protocol_examples_common/connect.c), and has a very simple behavior: block until connection is established and IP address is obtained, then return. This function is used to reduce the amount of boilerplate and to keep the example code focused on the protocol or library being demonstrated. The simple `example_connect()` function does not handle timeouts, does not gracefully handle various error conditions, and is only suited for use in examples. When developing real applications, this helper function needs to be replaced with full Wi-Fi / Ethernet connection handling code. Such code can be found in [examples/wifi/getting_started/](../wifi/getting_started) and [examples/ethernet/basic/](../ethernet/basic) examples. ### Configuring the Example -To configure the example to use Wi-Fi or Ethernet connection, open the project configuration menu (`idf.py menuconfig`) and navigate to "Example Connection Configuration" menu. Select either "Wi-Fi" or "Ethernet" in the "Connect using" choice. +To configure the example to use Wi-Fi, Ethernet or both connections, open the project configuration menu (`idf.py menuconfig`) and navigate to "Example Connection Configuration" menu. Select either "Wi-Fi" or "Ethernet" or both in the "Connect using" choice. When connecting using Wi-Fi, enter SSID and password of your Wi-Fi access point into the corresponding fields. If connecting to an open Wi-Fi network, keep the password field empty. From 8a45f074fc69bb939b159162713ed67c983d833f Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 23 Apr 2020 14:13:56 +0200 Subject: [PATCH 3/4] esp-netif: Added API to get the underlying interface name The interface name can be used in socket API, i.e. setsockopt(). The API esp_netif_get_netif_impl_name() shoudl be used to populate standard interface structure struct ifreq. --- components/esp_netif/include/esp_netif.h | 16 ++++++++++++++++ components/esp_netif/lwip/esp_netif_lwip.c | 11 +++++++++++ .../main/Kconfig.projbuild | 8 ++++++++ .../main/tcp_client_multiple.c | 19 ++++++++++++++++--- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/components/esp_netif/include/esp_netif.h b/components/esp_netif/include/esp_netif.h index d0cda153a2..a8df60e674 100644 --- a/components/esp_netif/include/esp_netif.h +++ b/components/esp_netif/include/esp_netif.h @@ -402,6 +402,22 @@ esp_err_t esp_netif_set_old_ip_info(esp_netif_t *esp_netif, const esp_netif_ip_i */ int esp_netif_get_netif_impl_index(esp_netif_t *esp_netif); +/** + * @brief Get net interface name from network stack implementation + * + * @note This name could be used in `setsockopt()` to bind socket with appropriate interface + * + * @param[in] esp_netif Handle to esp-netif instance + * @param[out] name Interface name as specified in underlying TCP/IP stack. Note that the + * actual name will be copied to the specified buffer, which must be allocated to hold + * maximum interface name size (6 characters for lwIP) + * + * @return + * - ESP_OK + * - ESP_ERR_ESP_NETIF_INVALID_PARAMS +*/ +esp_err_t esp_netif_get_netif_impl_name(esp_netif_t *esp_netif, char* name); + /** * @} */ diff --git a/components/esp_netif/lwip/esp_netif_lwip.c b/components/esp_netif/lwip/esp_netif_lwip.c index 0d57b66489..c55684162a 100644 --- a/components/esp_netif/lwip/esp_netif_lwip.c +++ b/components/esp_netif/lwip/esp_netif_lwip.c @@ -1736,4 +1736,15 @@ int esp_netif_get_netif_impl_index(esp_netif_t *esp_netif) return netif_get_index(esp_netif->lwip_netif); } +esp_err_t esp_netif_get_netif_impl_name(esp_netif_t *esp_netif, char* name) +{ + ESP_LOGD(TAG, "%s esp_netif:%p", __func__, esp_netif); + + if (esp_netif == NULL || esp_netif->lwip_netif == NULL) { + return ESP_ERR_ESP_NETIF_INVALID_PARAMS; + } + netif_index_to_name(netif_get_index(esp_netif->lwip_netif), name); + return ESP_OK; +} + #endif /* CONFIG_ESP_NETIF_TCPIP_LWIP */ diff --git a/examples/protocols/sockets/tcp_client_multi_net/main/Kconfig.projbuild b/examples/protocols/sockets/tcp_client_multi_net/main/Kconfig.projbuild index af1a705526..680c485e15 100644 --- a/examples/protocols/sockets/tcp_client_multi_net/main/Kconfig.projbuild +++ b/examples/protocols/sockets/tcp_client_multi_net/main/Kconfig.projbuild @@ -13,4 +13,12 @@ menu "Example Configuration" help host port + config EXAMPLE_BIND_SOCKET_TO_NETIF_NAME + bool "Bind to interface by its name" + default y + help + By default example uses setsockopt() to bind the tcp-client's socket + to specific interface. Setting this option to true demonstrates binding + the socket to the local ip address of the network interface. + endmenu diff --git a/examples/protocols/sockets/tcp_client_multi_net/main/tcp_client_multiple.c b/examples/protocols/sockets/tcp_client_multi_net/main/tcp_client_multiple.c index f69262f2f8..d95e95445d 100644 --- a/examples/protocols/sockets/tcp_client_multi_net/main/tcp_client_multiple.c +++ b/examples/protocols/sockets/tcp_client_multi_net/main/tcp_client_multiple.c @@ -30,7 +30,6 @@ static const char *payload = "GET / HTTP/1.1\r\n\r\n"; static void app_multiple_handle(esp_ip4_addr_t *ip4_addr, esp_netif_t *esp_netif) { - esp_netif_ip_info_t ip; char rx_buffer[128] = {0}; const char *netif_name = esp_netif_get_desc(esp_netif); @@ -42,7 +41,20 @@ static void app_multiple_handle(esp_ip4_addr_t *ip4_addr, esp_netif_t *esp_netif } ESP_LOGI(TAG, "\"%s\" Socket created", netif_name); - /* Bind local IP of the network interface */ + /* Bind the socket to an interface (based on example config option) + * - using netif local IP address + * - using netif name + */ +#if CONFIG_EXAMPLE_BIND_SOCKET_TO_NETIF_NAME + struct ifreq ifr; + esp_netif_get_netif_impl_name(esp_netif, ifr.ifr_name); + int ret = setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(struct ifreq)); + if (ret < 0) { + ESP_LOGE(TAG, "\"%s\" Unable to bind socket to specified interface: errno %d", netif_name, errno); + goto app_multiple_handle_fail; + } +#else + esp_netif_ip_info_t ip; memset(&ip, 0, sizeof(esp_netif_ip_info_t)); ESP_ERROR_CHECK(esp_netif_get_ip_info(esp_netif, &ip)); @@ -51,12 +63,13 @@ static void app_multiple_handle(esp_ip4_addr_t *ip4_addr, esp_netif_t *esp_netif addr.sin_family = AF_INET; addr.sin_port = htons(0); addr.sin_addr.s_addr = ip.ip.addr; - + int ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) { ESP_LOGE(TAG, "\"%s\" Unable to bind socket: errno %d", netif_name, errno); goto app_multiple_handle_fail; } +#endif /* CONFIG_EXAMPLE_BIND_SOCKET_TO_NETIF_NAME */ /* Connect to the host by the network interface */ struct sockaddr_in destAddr; From 761c3a394256effa29e7d0b2e30494651bde1b8e Mon Sep 17 00:00:00 2001 From: David Cermak Date: Thu, 23 Apr 2020 15:53:11 +0200 Subject: [PATCH 4/4] examples: make sure WiFi interface is disabled when connecting with Ethernet --- examples/protocols/esp_http_client/sdkconfig.ci | 1 + examples/protocols/esp_http_client/sdkconfig.ci.ssldyn | 1 + examples/protocols/https_request/sdkconfig.ci | 1 + examples/protocols/https_request/sdkconfig.ci.ssldyn | 1 + 4 files changed, 4 insertions(+) diff --git a/examples/protocols/esp_http_client/sdkconfig.ci b/examples/protocols/esp_http_client/sdkconfig.ci index 8158d63aeb..6132bbfb89 100644 --- a/examples/protocols/esp_http_client/sdkconfig.ci +++ b/examples/protocols/esp_http_client/sdkconfig.ci @@ -1,4 +1,5 @@ CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y CONFIG_EXAMPLE_ETH_PHY_IP101=y CONFIG_EXAMPLE_ETH_MDC_GPIO=23 diff --git a/examples/protocols/esp_http_client/sdkconfig.ci.ssldyn b/examples/protocols/esp_http_client/sdkconfig.ci.ssldyn index ec427a62cf..d25890eb1c 100644 --- a/examples/protocols/esp_http_client/sdkconfig.ci.ssldyn +++ b/examples/protocols/esp_http_client/sdkconfig.ci.ssldyn @@ -1,4 +1,5 @@ CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y CONFIG_EXAMPLE_ETH_PHY_IP101=y CONFIG_EXAMPLE_ETH_MDC_GPIO=23 diff --git a/examples/protocols/https_request/sdkconfig.ci b/examples/protocols/https_request/sdkconfig.ci index b1ae1b9e87..42f4b389e1 100644 --- a/examples/protocols/https_request/sdkconfig.ci +++ b/examples/protocols/https_request/sdkconfig.ci @@ -1,6 +1,7 @@ CONFIG_ESP32_SPIRAM_SUPPORT=y CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y CONFIG_EXAMPLE_ETH_PHY_IP101=y CONFIG_EXAMPLE_ETH_MDC_GPIO=23 diff --git a/examples/protocols/https_request/sdkconfig.ci.ssldyn b/examples/protocols/https_request/sdkconfig.ci.ssldyn index 6f3894bbdf..e7d69f8aa8 100644 --- a/examples/protocols/https_request/sdkconfig.ci.ssldyn +++ b/examples/protocols/https_request/sdkconfig.ci.ssldyn @@ -1,6 +1,7 @@ CONFIG_ESP32_SPIRAM_SUPPORT=y CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y CONFIG_EXAMPLE_ETH_PHY_IP101=y CONFIG_EXAMPLE_ETH_MDC_GPIO=23