diff --git a/components/lwip/Kconfig b/components/lwip/Kconfig index 65c76017c..c9c796f87 100644 --- a/components/lwip/Kconfig +++ b/components/lwip/Kconfig @@ -68,6 +68,36 @@ config LWIP_IP_REASSEMBLY help Enabling this option allows reassemblying incoming fragmented IP packets. +config LWIP_STATS + bool "Enable LWIP statistics" + default n + help + Enabling this option allows LWIP statistics + +config LWIP_ETHARP_TRUST_IP_MAC + bool "Enable LWIP ARP trust" + default y + help + Enabling this option allows ARP table to be updated. + + If this option is enabled, the incoming IP packets cause the ARP table to be + updated with the source MAC and IP addresses supplied in the packet. + You may want to disable this if you do not trust LAN peers to have the + correct addresses, or as a limited approach to attempt to handle + spoofing. If disabled, lwIP will need to make a new ARP request if + the peer is not already in the ARP table, adding a little latency. + The peer *is* in the ARP table if it requested our address before. + Also notice that this slows down input processing of every IP packet! + + +config TCPIP_RECVMBOX_SIZE + int "TCPIP receive mail box size" + default 32 + range 6 64 + help + Set TCPIP receive mail box size. Generally bigger value means higher throughput + but more memory. The value should be bigger than UDP/TCP mail box size. + menu "TCP" config TCP_MAXRTX @@ -131,7 +161,7 @@ config TCP_WND_DEFAULT config TCP_RECVMBOX_SIZE int "Default TCP receive mail box size" default 6 - range 6 32 + range 6 64 help Set TCP receive mail box size. Generally bigger value means higher throughput but more memory. The recommended value is: TCP_WND_DEFAULT/TCP_MSS + 2, e.g. if @@ -188,7 +218,7 @@ menu "UDP" config UDP_RECVMBOX_SIZE int "Default UDP receive mail box size" default 6 - range 6 32 + range 6 64 help Set UDP receive mail box size. The recommended value is 6. diff --git a/components/lwip/include/lwip/port/lwipopts.h b/components/lwip/include/lwip/port/lwipopts.h index 7bc4021e5..89d09d2d7 100644 --- a/components/lwip/include/lwip/port/lwipopts.h +++ b/components/lwip/include/lwip/port/lwipopts.h @@ -416,7 +416,7 @@ * The queue size value itself is platform-dependent, but is passed to * sys_mbox_new() when tcpip_init is called. */ -#define TCPIP_MBOX_SIZE 32 +#define TCPIP_MBOX_SIZE CONFIG_TCPIP_RECVMBOX_SIZE /** * DEFAULT_UDP_RECVMBOX_SIZE: The mailbox size for the incoming packets on a @@ -520,10 +520,20 @@ ---------- Statistics options ---------- ---------------------------------------- */ + /** * LWIP_STATS==1: Enable statistics collection in lwip_stats. */ -#define LWIP_STATS 0 +#define LWIP_STATS CONFIG_LWIP_STATS + +#if LWIP_STATS + +/** + * LWIP_STATS_DISPLAY==1: Compile in the statistics output functions. + */ +#define LWIP_STATS_DISPLAY CONFIG_LWIP_STATS +#endif + /* --------------------------------- @@ -674,7 +684,7 @@ * The peer *is* in the ARP table if it requested our address before. * Also notice that this slows down input processing of every IP packet! */ -#define ETHARP_TRUST_IP_MAC 1 +#define ETHARP_TRUST_IP_MAC CONFIG_LWIP_ETHARP_TRUST_IP_MAC /* Enable all Espressif-only options */ @@ -692,8 +702,8 @@ #define ESP_IP4_ATON 1 #define ESP_LIGHT_SLEEP 1 #define ESP_L2_TO_L3_COPY CONFIG_L2_TO_L3_COPY -#define ESP_STATS_MEM 0 -#define ESP_STATS_DROP 0 +#define ESP_STATS_MEM CONFIG_LWIP_STATS +#define ESP_STATS_DROP CONFIG_LWIP_STATS #define ESP_STATS_TCP 0 #define ESP_DHCP_TIMER 1 #define ESP_LWIP_LOGI(...) ESP_LOGI("lwip", __VA_ARGS__) diff --git a/examples/wifi/iperf/Makefile b/examples/wifi/iperf/Makefile new file mode 100755 index 000000000..46d657866 --- /dev/null +++ b/examples/wifi/iperf/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := iperf + +include $(IDF_PATH)/make/project.mk diff --git a/examples/wifi/iperf/README.md b/examples/wifi/iperf/README.md new file mode 100644 index 000000000..c5fd968fc --- /dev/null +++ b/examples/wifi/iperf/README.md @@ -0,0 +1,52 @@ +# Iperf Example + +This example implements the protocol used by the common performance measurement tool [iPerf](https://iperf.fr/). +Performance can be measured between two ESP32s running this example, or between a single ESP32 and a computer running the iPerf tool + +Demo steps to test station TCP Tx performance: + +1. Build the iperf example with sdkconfig.defaults, which contains performance test specific configurations + +2. Run the demo as station mode and join the target AP + sta ssid password + +3. Run iperf as server on AP side + iperf -s -i 3 + +4. Run iperf as client on ESP32 side + iperf -c 192.168.10.42 -i 3 -t 60 + +The console output, which is printed by station TCP RX throughput test, looks like: + +>esp32> sta aptest +> +>I (5325) iperf: sta connecting to 'aptest' +> +>esp32> I (6017) event: ip: 192.168.10.248, mask: 255.255.255.0, gw: 192.168.10.1 +> +>esp32> iperf -s -i 3 -t 1000 +> +>I (14958) iperf: mode=tcp-server sip=192.168.10.248:5001, dip=0.0.0.0:5001, interval=3, time=1000 +> +>Interval Bandwidth +> +>esp32> accept: 192.168.10.42,62958 +> +>0- 3 sec 8.43 Mbits/sec +> +>3- 6 sec 36.16 Mbits/sec +> +>6- 9 sec 36.22 Mbits/sec +> +>9- 12 sec 36.44 Mbits/sec +> +>12- 15 sec 36.25 Mbits/sec +> +>15- 18 sec 24.36 Mbits/sec +> +>18- 21 sec 27.79 Mbits/sec + + +Steps to test station/soft-AP TCP/UDP RX/TX throughput are similar as test steps in station TCP TX. + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/wifi/iperf/components/component.mk b/examples/wifi/iperf/components/component.mk new file mode 100755 index 000000000..da115f7d4 --- /dev/null +++ b/examples/wifi/iperf/components/component.mk @@ -0,0 +1,11 @@ +# +# Component Makefile +# +# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the SDK documents if you need to do this. +# + +#include $(IDF_PATH)/make/component_common.mk +COMPONENT_ADD_INCLUDEDIRS := . diff --git a/examples/wifi/iperf/components/iperf.c b/examples/wifi/iperf/components/iperf.c new file mode 100644 index 000000000..03927d627 --- /dev/null +++ b/examples/wifi/iperf/components/iperf.c @@ -0,0 +1,455 @@ +/* Iperf Example - iperf implementation + + 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 +#include +#include "esp_log.h" + +#include "iperf.h" + +typedef struct { + iperf_cfg_t cfg; + bool finish; + uint32_t total_len; + uint32_t buffer_len; + uint8_t *buffer; + uint32_t socket; +} iperf_ctrl_t; + +typedef struct { + int32_t id; + uint32_t sec; + uint32_t usec; +} iperf_udp_pkt_t; + +static bool s_iperf_is_running = false; +static iperf_ctrl_t s_iperf_ctrl; +static const char *TAG = "iperf"; + +inline static bool iperf_is_udp_client(void) +{ + return ((s_iperf_ctrl.cfg.flag & IPERF_FLAG_CLIENT) && (s_iperf_ctrl.cfg.flag & IPERF_FLAG_UDP)); +} + +inline static bool iperf_is_udp_server(void) +{ + return ((s_iperf_ctrl.cfg.flag & IPERF_FLAG_SERVER) && (s_iperf_ctrl.cfg.flag & IPERF_FLAG_UDP)); +} + +inline static bool iperf_is_tcp_client(void) +{ + return ((s_iperf_ctrl.cfg.flag & IPERF_FLAG_CLIENT) && (s_iperf_ctrl.cfg.flag & IPERF_FLAG_TCP)); +} + +inline static bool iperf_is_tcp_server(void) +{ + return ((s_iperf_ctrl.cfg.flag & IPERF_FLAG_SERVER) && (s_iperf_ctrl.cfg.flag & IPERF_FLAG_TCP)); +} + +int iperf_get_socket_error_code(int socket) +{ + uint32_t optlen = sizeof(int); + int result; + int err; + + err = getsockopt(socket, SOL_SOCKET, SO_ERROR, &result, &optlen); + if (err == -1) { + ESP_LOGE(TAG, "getsockopt failed: ret=%d", err); + return -1; + } + + return result; +} + +int iperf_show_socket_error_reason(const char *str, int socket) +{ + int err = iperf_get_socket_error_code(socket); + + if (err != 0) { + ESP_LOGW(TAG, "%s error, error code: %d, reason: %s", str, err, strerror(err)); + } + + return err; +} + +void iperf_report_task(void* arg) +{ + TickType_t delay_interval = (s_iperf_ctrl.cfg.interval * 1000)/portTICK_RATE_MS; + uint32_t interval = s_iperf_ctrl.cfg.interval; + uint32_t time = s_iperf_ctrl.cfg.time; + uint32_t last_len = 0; + uint32_t cur = 0; + + printf("\n%16s %s\n", "Interval", "Bandwidth"); + while (!s_iperf_ctrl.finish) { + vTaskDelay(delay_interval); + printf("%4d-%4d sec %.2f Mbits/sec\n", cur, cur+interval, (double)((s_iperf_ctrl.total_len - last_len)*8)/interval/1e6); + cur += interval; + last_len = s_iperf_ctrl.total_len; + if (cur == time) { + break; + } + } + + if (cur != 0) { + printf("%4d-%4d sec %.2f Mbits/sec\n", 0, time, (double)(s_iperf_ctrl.total_len*8)/cur/1e6); + } + + s_iperf_ctrl.finish = true; + vTaskDelete(NULL); +} + +esp_err_t iperf_start_report(void) +{ + int ret; + + ret = xTaskCreate(iperf_report_task, IPERF_REPORT_TASK_NAME, IPERF_REPORT_TASK_STACK, NULL, IPERF_REPORT_TASK_PRIORITY, NULL); + if (ret != pdPASS) { + ESP_LOGE(TAG, "create task %s failed", IPERF_REPORT_TASK_NAME); + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t iperf_run_tcp_server(void) +{ + socklen_t addr_len = sizeof(struct sockaddr); + struct sockaddr_in remote_addr; + struct sockaddr_in addr; + int actual_recv = 0; + int want_recv = 0; + uint8_t *buffer; + int listen_socket; + struct timeval t; + int socket; + int opt; + + listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listen_socket < 0) { + iperf_show_socket_error_reason("tcp server create", listen_socket); + return ESP_FAIL; + } + + setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(s_iperf_ctrl.cfg.sport); + addr.sin_addr.s_addr = s_iperf_ctrl.cfg.sip; + if (bind(listen_socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + iperf_show_socket_error_reason("tcp server bind", listen_socket); + close(listen_socket); + return ESP_FAIL; + } + + if (listen(listen_socket, 5) < 0) { + iperf_show_socket_error_reason("tcp server listen", listen_socket); + close(listen_socket); + return ESP_FAIL; + } + + iperf_start_report(); + buffer = s_iperf_ctrl.buffer; + want_recv = s_iperf_ctrl.buffer_len; + while (!s_iperf_ctrl.finish) { + + /*TODO need to change to non-block mode */ + socket = accept(listen_socket, (struct sockaddr*)&remote_addr, &addr_len); + if (socket < 0) { + iperf_show_socket_error_reason("tcp server listen", listen_socket); + close(listen_socket); + return ESP_FAIL; + } else { + printf("accept: %s,%d\n", inet_ntoa(remote_addr.sin_addr), htons(remote_addr.sin_port)); + + t.tv_sec = IPERF_SOCKET_RX_TIMEOUT; + setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)); + } + + while (!s_iperf_ctrl.finish) { + actual_recv = recv(socket, buffer, want_recv, 0); + if (actual_recv < 0) { + iperf_show_socket_error_reason("tcp server recv", listen_socket); + break; + } else { + s_iperf_ctrl.total_len += actual_recv; + } + } + + close(socket); + } + + s_iperf_ctrl.finish = true; + close(listen_socket); + return ESP_OK; +} + +esp_err_t iperf_run_udp_server(void) +{ + socklen_t addr_len = sizeof(struct sockaddr_in); + struct sockaddr_in addr; + int actual_recv = 0; + struct timeval t; + int want_recv = 0; + uint8_t *buffer; + int socket; + int opt; + + socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (socket < 0) { + iperf_show_socket_error_reason("udp server create", socket); + return ESP_FAIL; + } + + setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(s_iperf_ctrl.cfg.sport); + addr.sin_addr.s_addr = s_iperf_ctrl.cfg.sip; + if (bind(socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + iperf_show_socket_error_reason("udp server bind", socket); + return ESP_FAIL; + } + + + addr.sin_family = AF_INET; + addr.sin_port = htons(s_iperf_ctrl.cfg.sport); + addr.sin_addr.s_addr = s_iperf_ctrl.cfg.sip; + + iperf_start_report(); + buffer = s_iperf_ctrl.buffer; + want_recv = s_iperf_ctrl.buffer_len; + ESP_LOGI(TAG, "want recv=%d", want_recv); + + t.tv_sec = IPERF_SOCKET_RX_TIMEOUT; + setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)); + + while (!s_iperf_ctrl.finish) { + actual_recv = recvfrom(socket, buffer, want_recv, 0, (struct sockaddr *)&addr, &addr_len); + if (actual_recv < 0) { + iperf_show_socket_error_reason("udp server recv", socket); + } else { + s_iperf_ctrl.total_len += actual_recv; + } + } + + s_iperf_ctrl.finish = true; + close(socket); + return ESP_OK; +} + +esp_err_t iperf_run_udp_client(void) +{ + struct sockaddr_in addr; + iperf_udp_pkt_t *udp; + int actual_send = 0; + bool retry = false; + uint32_t delay = 1; + int want_send = 0; + uint8_t *buffer; + int socket; + int opt; + int err; + int id; + + socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (socket < 0) { + iperf_show_socket_error_reason("udp server create", socket); + return ESP_FAIL; + } + + setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = s_iperf_ctrl.cfg.sip; + if (bind(socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + iperf_show_socket_error_reason("udp server bind", socket); + return ESP_FAIL; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(s_iperf_ctrl.cfg.dport); + addr.sin_addr.s_addr = s_iperf_ctrl.cfg.dip; + + iperf_start_report(); + buffer = s_iperf_ctrl.buffer; + udp = (iperf_udp_pkt_t *)buffer; + want_send = s_iperf_ctrl.buffer_len; + id = 0; + + while (!s_iperf_ctrl.finish) { + if (false == retry) { + id ++; + udp->id = htonl(id); + delay = 1; + } + + retry = false; + actual_send = sendto(socket, buffer, want_send, 0, (struct sockaddr *)&addr, sizeof(addr)); + + if (actual_send != want_send) { + err = iperf_get_socket_error_code(socket); + if (err == ENOMEM) { + vTaskDelay(delay); + if (delay < IPERF_MAX_DELAY) { + delay <<= 1; + } + retry = true; + continue; + } else { + ESP_LOGE(TAG, "udp client send abort: err=%d", err); + break; + } + } else { + s_iperf_ctrl.total_len += actual_send; + } + } + + s_iperf_ctrl.finish = true; + close(socket); + return ESP_OK; +} + + +esp_err_t iperf_run_tcp_client(void) +{ + struct sockaddr_in addr; + int actual_send = 0; + int want_send = 0; + uint8_t *buffer; + int socket; + int opt; + + socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socket < 0) { + iperf_show_socket_error_reason("tcp client create", socket); + return ESP_FAIL; + } + + setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = s_iperf_ctrl.cfg.sip; + if (bind(socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + iperf_show_socket_error_reason("tcp client bind", socket); + return ESP_FAIL; + } + + + addr.sin_family = AF_INET; + addr.sin_port = htons(s_iperf_ctrl.cfg.dport); + addr.sin_addr.s_addr = s_iperf_ctrl.cfg.dip; + if (connect(socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + iperf_show_socket_error_reason("tcp client connect", socket); + return ESP_FAIL; + } + + iperf_start_report(); + buffer = s_iperf_ctrl.buffer; + want_send = s_iperf_ctrl.buffer_len; + while (!s_iperf_ctrl.finish) { + actual_send = send(socket, buffer, want_send, 0); + if (actual_send <= 0) { + vTaskDelay(1); + } else { + s_iperf_ctrl.total_len += actual_send; + } + } + + close(socket); + return ESP_OK; +} + +void iperf_task_traffic(void *arg) +{ + if (iperf_is_udp_client()) { + iperf_run_udp_client(); + } else if (iperf_is_udp_server()) { + iperf_run_udp_server(); + } else if (iperf_is_tcp_client()) { + iperf_run_tcp_client(); + } else { + iperf_run_tcp_server(); + } + + if (s_iperf_ctrl.buffer) { + free(s_iperf_ctrl.buffer); + s_iperf_ctrl.buffer = 0; + } + ESP_LOGI(TAG, "iperf exit"); + s_iperf_is_running = false; + vTaskDelete(NULL); +} + +uint32_t iperf_get_buffer_len(void) +{ + if (iperf_is_udp_client()) { + return IPERF_UDP_TX_LEN; + } else if (iperf_is_udp_server()) { + return IPERF_UDP_RX_LEN; + } else if (iperf_is_tcp_client()) { + return IPERF_TCP_TX_LEN; + } else { + return IPERF_TCP_RX_LEN; + } + return 0; +} + +esp_err_t iperf_start(iperf_cfg_t *cfg) +{ + int ret; + + if (!cfg) { + return ESP_FAIL; + } + + if (s_iperf_is_running) { + ESP_LOGW(TAG, "iperf is running"); + return ESP_FAIL; + } + + memset(&s_iperf_ctrl, 0, sizeof(s_iperf_ctrl)); + memcpy(&s_iperf_ctrl.cfg, cfg, sizeof(*cfg)); + s_iperf_is_running = true; + s_iperf_ctrl.finish = false; + s_iperf_ctrl.buffer_len = iperf_get_buffer_len(); + s_iperf_ctrl.buffer = (uint8_t *)malloc(s_iperf_ctrl.buffer_len); + if (!s_iperf_ctrl.buffer) { + ESP_LOGE(TAG, "create buffer: out of memory"); + return ESP_FAIL; + } + + ret = xTaskCreate(iperf_task_traffic, IPERF_TRAFFIC_TASK_NAME, IPERF_TRAFFIC_TASK_STACK, NULL, IPERF_TRAFFIC_TASK_PRIORITY, NULL); + if (ret != pdPASS) { + ESP_LOGE(TAG, "create task %s failed", IPERF_TRAFFIC_TASK_NAME); + free(s_iperf_ctrl.buffer); + s_iperf_ctrl.buffer = 0; + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t iperf_stop(void) +{ + if (s_iperf_is_running) { + s_iperf_ctrl.finish = true; + } + + while (s_iperf_is_running) { + ESP_LOGI(TAG, "wait current iperf to stop ..."); + vTaskDelay(300/portTICK_RATE_MS); + } + + return ESP_OK; +} + diff --git a/examples/wifi/iperf/components/iperf.h b/examples/wifi/iperf/components/iperf.h new file mode 100644 index 000000000..1f5a4ac8c --- /dev/null +++ b/examples/wifi/iperf/components/iperf.h @@ -0,0 +1,61 @@ +/* Iperf Example - iperf declaration + + 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. +*/ + +#ifndef __IPERF_H_ +#define __IPERF_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define IPERF_FLAG_CLIENT (1) +#define IPERF_FLAG_SERVER (1<<1) +#define IPERF_FLAG_TCP (1<<2) +#define IPERF_FLAG_UDP (1<<3) + +#define IPERF_DEFAULT_PORT 5001 +#define IPERF_DEFAULT_INTERVAL 3 +#define IPERF_DEFAULT_TIME 30 + +#define IPERF_TRAFFIC_TASK_NAME "iperf_traffic" +#define IPERF_TRAFFIC_TASK_PRIORITY 19 +#define IPERF_TRAFFIC_TASK_STACK 4096 +#define IPERF_REPORT_TASK_NAME "iperf_report" +#define IPERF_REPORT_TASK_PRIORITY 10 +#define IPERF_REPORT_TASK_STACK 4096 +#define IPERF_REPORT_TASK_NAME "iperf_report" + +#define IPERF_UDP_TX_LEN (1472) +#define IPERF_UDP_RX_LEN (32<<10) +#define IPERF_TCP_TX_LEN (32<<10) +#define IPERF_TCP_RX_LEN (64<<10) + +#define IPERF_MAX_DELAY 64 + +#define IPERF_SOCKET_RX_TIMEOUT 10 +#define IPERF_SOCKET_ACCEPT_TIMEOUT 5 + +typedef struct { + uint32_t flag; + uint32_t dip; + uint32_t sip; + uint16_t dport; + uint16_t sport; + uint32_t interval; + uint32_t time; +} iperf_cfg_t; + +esp_err_t iperf_start(iperf_cfg_t *cfg); +esp_err_t iperf_stop(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/examples/wifi/iperf/main/cmd_decl.h b/examples/wifi/iperf/main/cmd_decl.h new file mode 100644 index 000000000..78707eabd --- /dev/null +++ b/examples/wifi/iperf/main/cmd_decl.h @@ -0,0 +1,14 @@ +/* Iperf example — declarations of command registration functions. + + 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. +*/ +#pragma once + +// Register WiFi functions +void register_wifi(void); +void initialise_wifi(void); + diff --git a/examples/wifi/iperf/main/cmd_wifi.c b/examples/wifi/iperf/main/cmd_wifi.c new file mode 100644 index 000000000..e26900dfb --- /dev/null +++ b/examples/wifi/iperf/main/cmd_wifi.c @@ -0,0 +1,381 @@ +/* Iperf Example - wifi commands + + 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 +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" +#include "cmd_decl.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_wifi.h" +#include "tcpip_adapter.h" +#include "esp_event_loop.h" +#include "iperf.h" + +typedef struct { + struct arg_str *ip; + struct arg_lit *server; + struct arg_lit *udp; + struct arg_int *port; + struct arg_int *interval; + struct arg_int *time; + struct arg_lit *abort; + struct arg_end *end; +} wifi_iperf_t; +static wifi_iperf_t iperf_args; + +typedef struct { + struct arg_str *ssid; + struct arg_str *password; + struct arg_end *end; +} wifi_args_t; +static wifi_args_t sta_args; +static wifi_args_t ap_args; +static bool reconnect = true; +static const char *TAG="iperf"; + +static EventGroupHandle_t wifi_event_group; +const int CONNECTED_BIT = BIT0; +const int DISCONNECTED_BIT = BIT1; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupClearBits(wifi_event_group, DISCONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + if (reconnect) { + ESP_LOGI(TAG, "sta disconnect, reconnect..."); + esp_wifi_connect(); + } else { + ESP_LOGI(TAG, "sta disconnect"); + } + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, DISCONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void initialise_wifi(void) +{ + esp_log_level_set("wifi", ESP_LOG_WARN); + static bool initialized = false; + + if (initialized) { + return; + } + + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) ); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_NULL) ); + ESP_ERROR_CHECK( esp_wifi_start() ); + initialized = true; +} + +static bool wifi_cmd_sta_join(const char* ssid, const char* pass) +{ + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + + wifi_config_t wifi_config = { 0 }; + + strlcpy((char*) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid)); + if (pass) { + strncpy((char*) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password)); + } + + if (bits & CONNECTED_BIT) { + reconnect = false; + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + ESP_ERROR_CHECK( esp_wifi_disconnect() ); + xEventGroupWaitBits(wifi_event_group, DISCONNECTED_BIT, 0, 1, portTICK_RATE_MS); + } + + reconnect = true; + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) ); + ESP_ERROR_CHECK( esp_wifi_connect() ); + + xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 5000/portTICK_RATE_MS); + + return true; +} + +static int wifi_cmd_sta(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &sta_args); + + if (nerrors != 0) { + arg_print_errors(stderr, sta_args.end, argv[0]); + return 1; + } + + ESP_LOGI(TAG, "sta connecting to '%s'", sta_args.ssid->sval[0]); + wifi_cmd_sta_join(sta_args.ssid->sval[0], sta_args.password->sval[0]); + return 0; +} + + +static bool wifi_cmd_ap_set(const char* ssid, const char* pass) +{ + wifi_config_t wifi_config = { + .ap = { + .ssid = "", + .ssid_len = 0, + .max_connection = 4, + .password = "", + .authmode = WIFI_AUTH_WPA_WPA2_PSK + }, + }; + + reconnect = false; + strncpy((char*) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); + if (pass) { + if (strlen(pass) != 0 && strlen(pass) < 8) { + reconnect = true; + ESP_LOGE(TAG, "password less than 8"); + return false; + } + strncpy((char*) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); + } + + if (strlen(pass) == 0) { + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + } + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config)); + return true; +} + +static int wifi_cmd_ap(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &ap_args); + + if (nerrors != 0) { + arg_print_errors(stderr, ap_args.end, argv[0]); + return 1; + } + + wifi_cmd_ap_set(ap_args.ssid->sval[0], ap_args.password->sval[0]); + ESP_LOGI(TAG, "AP mode, %s %s", ap_args.ssid->sval[0], ap_args.password->sval[0]); + return 0; +} + +static int wifi_cmd_query(int argc, char** argv) +{ + wifi_config_t cfg; + wifi_mode_t mode; + + esp_wifi_get_mode(&mode); + if (WIFI_MODE_AP == mode) { + esp_wifi_get_config(WIFI_IF_AP, &cfg); + ESP_LOGI(TAG, "AP mode, %s %s", cfg.ap.ssid, cfg.ap.password); + } else if (WIFI_MODE_STA == mode) { + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + if (bits & CONNECTED_BIT) { + esp_wifi_get_config(WIFI_IF_STA, &cfg); + ESP_LOGI(TAG, "sta mode, connected %s", cfg.ap.ssid); + } else { + ESP_LOGI(TAG, "sta mode, disconnected"); + } + } else { + ESP_LOGI(TAG, "NULL mode"); + return 0; + } + + return 0; +} + +static uint32_t wifi_get_local_ip(void) +{ + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + tcpip_adapter_if_t ifx = TCPIP_ADAPTER_IF_AP; + tcpip_adapter_ip_info_t ip_info; + wifi_mode_t mode; + + esp_wifi_get_mode(&mode); + if (WIFI_MODE_STA == mode) { + bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + if (bits & CONNECTED_BIT) { + ifx = TCPIP_ADAPTER_IF_STA; + } else { + ESP_LOGE(TAG, "sta has no IP"); + return 0; + } + } + + tcpip_adapter_get_ip_info(ifx, &ip_info); + return ip_info.ip.addr; +} + +static int wifi_cmd_iperf(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &iperf_args); + iperf_cfg_t cfg; + + if (nerrors != 0) { + arg_print_errors(stderr, iperf_args.end, argv[0]); + return 0; + } + + memset(&cfg, 0, sizeof(cfg)); + + if ( iperf_args.abort->count != 0) { + iperf_stop(); + return 0; + } + + if ( ((iperf_args.ip->count == 0) && (iperf_args.server->count == 0)) || + ((iperf_args.ip->count != 0) && (iperf_args.server->count != 0)) ) { + ESP_LOGE(TAG, "should specific client/server mode"); + return 0; + } + + if (iperf_args.ip->count == 0) { + cfg.flag |= IPERF_FLAG_SERVER; + } else { + cfg.dip = ipaddr_addr(iperf_args.ip->sval[0]); + cfg.flag |= IPERF_FLAG_CLIENT; + } + + cfg.sip = wifi_get_local_ip(); + if (cfg.sip == 0) { + return 0; + } + + if (iperf_args.udp->count == 0) { + cfg.flag |= IPERF_FLAG_TCP; + } else { + cfg.flag |= IPERF_FLAG_UDP; + } + + if (iperf_args.port->count == 0) { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + if (cfg.flag & IPERF_FLAG_SERVER) { + cfg.sport = iperf_args.port->ival[0]; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = iperf_args.port->ival[0]; + } + } + + if (iperf_args.interval->count == 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } else { + cfg.interval = iperf_args.interval->ival[0]; + if (cfg.interval <= 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } + } + + if (iperf_args.time->count == 0) { + cfg.time = IPERF_DEFAULT_TIME; + } else { + cfg.time = iperf_args.time->ival[0]; + if (cfg.time <= cfg.interval) { + cfg.time = cfg.interval; + } + } + + ESP_LOGI(TAG, "mode=%s-%s sip=%d.%d.%d.%d:%d, dip=%d.%d.%d.%d:%d, interval=%d, time=%d", + cfg.flag&IPERF_FLAG_TCP?"tcp":"udp", + cfg.flag&IPERF_FLAG_SERVER?"server":"client", + cfg.sip&0xFF, (cfg.sip>>8)&0xFF, (cfg.sip>>16)&0xFF, (cfg.sip>>24)&0xFF, cfg.sport, + cfg.dip&0xFF, (cfg.dip>>8)&0xFF, (cfg.dip>>16)&0xFF, (cfg.dip>>24)&0xFF, cfg.dport, + cfg.interval, cfg.time); + + iperf_start(&cfg); + + return 0; +} + +static int restart(int argc, char** argv) +{ + ESP_LOGI(TAG, "Restarting"); + esp_restart(); +} + +void register_wifi() +{ + sta_args.ssid = arg_str1(NULL, NULL, "", "SSID of AP"); + sta_args.password = arg_str0(NULL, NULL, "", "password of AP"); + sta_args.end = arg_end(2); + + const esp_console_cmd_t sta_cmd = { + .command = "sta", + .help = "WiFi is station mode, join specified soft-AP", + .hint = NULL, + .func = &wifi_cmd_sta, + .argtable = &sta_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&sta_cmd) ); + + ap_args.ssid = arg_str1(NULL, NULL, "", "SSID of AP"); + ap_args.password = arg_str0(NULL, NULL, "", "password of AP"); + ap_args.end = arg_end(2); + + const esp_console_cmd_t ap_cmd = { + .command = "ap", + .help = "AP mode, configure ssid and password", + .hint = NULL, + .func = &wifi_cmd_ap, + .argtable = &ap_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&ap_cmd) ); + + const esp_console_cmd_t query_cmd = { + .command = "query", + .help = "query WiFi info", + .hint = NULL, + .func = &wifi_cmd_query, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&query_cmd) ); + + const esp_console_cmd_t restart_cmd = { + .command = "restart", + .help = "Restart the program", + .hint = NULL, + .func = &restart, + }; + ESP_ERROR_CHECK( esp_console_cmd_register(&restart_cmd) ); + + iperf_args.ip = arg_str0("c", "client", "", "run in client mode, connecting to "); + iperf_args.server = arg_lit0("s", "server", "run in server mode"); + iperf_args.udp = arg_lit0("u", "udp", "use UDP rather than TCP"); + iperf_args.port = arg_int0("p", "port", "", "server port to listen on/connect to"); + iperf_args.interval = arg_int0("i", "interval", "", "seconds between periodic bandwidth reports"); + iperf_args.time = arg_int0("t", "time", "