From f5559acd2017884598f5ef2dc43bb6744c64edcb Mon Sep 17 00:00:00 2001 From: yuanjm Date: Fri, 4 Sep 2020 18:47:35 +0800 Subject: [PATCH 1/2] feat(iperf): Brinf iperf from idf release/v4.1 Commit ID:5ef1b39 --- examples/wifi/iperf/CMakeLists.txt | 9 + examples/wifi/iperf/Makefile | 10 + examples/wifi/iperf/README.md | 61 ++ .../iperf/components/iperf/CMakeLists.txt | 3 + .../wifi/iperf/components/iperf/component.mk | 11 + examples/wifi/iperf/components/iperf/iperf.c | 435 ++++++++++++ examples/wifi/iperf/components/iperf/iperf.h | 65 ++ examples/wifi/iperf/iperf_test.py | 621 ++++++++++++++++++ examples/wifi/iperf/main/CMakeLists.txt | 3 + examples/wifi/iperf/main/cmd_decl.h | 20 + examples/wifi/iperf/main/cmd_wifi.c | 447 +++++++++++++ examples/wifi/iperf/main/cmd_wifi.h | 13 + examples/wifi/iperf/main/component.mk | 0 examples/wifi/iperf/main/iperf_example_main.c | 144 ++++ examples/wifi/iperf/sdkconfig.ci.00 | 6 + examples/wifi/iperf/sdkconfig.ci.01 | 25 + examples/wifi/iperf/sdkconfig.ci.02 | 25 + examples/wifi/iperf/sdkconfig.ci.03 | 25 + examples/wifi/iperf/sdkconfig.ci.04 | 25 + examples/wifi/iperf/sdkconfig.ci.05 | 29 + examples/wifi/iperf/sdkconfig.ci.06 | 26 + examples/wifi/iperf/sdkconfig.ci.07 | 30 + examples/wifi/iperf/sdkconfig.ci.99 | 30 + examples/wifi/iperf/sdkconfig.defaults | 31 + 24 files changed, 2094 insertions(+) create mode 100644 examples/wifi/iperf/CMakeLists.txt create mode 100644 examples/wifi/iperf/Makefile create mode 100644 examples/wifi/iperf/README.md create mode 100644 examples/wifi/iperf/components/iperf/CMakeLists.txt create mode 100644 examples/wifi/iperf/components/iperf/component.mk create mode 100644 examples/wifi/iperf/components/iperf/iperf.c create mode 100644 examples/wifi/iperf/components/iperf/iperf.h create mode 100644 examples/wifi/iperf/iperf_test.py create mode 100644 examples/wifi/iperf/main/CMakeLists.txt create mode 100644 examples/wifi/iperf/main/cmd_decl.h create mode 100644 examples/wifi/iperf/main/cmd_wifi.c create mode 100644 examples/wifi/iperf/main/cmd_wifi.h create mode 100644 examples/wifi/iperf/main/component.mk create mode 100644 examples/wifi/iperf/main/iperf_example_main.c create mode 100644 examples/wifi/iperf/sdkconfig.ci.00 create mode 100644 examples/wifi/iperf/sdkconfig.ci.01 create mode 100644 examples/wifi/iperf/sdkconfig.ci.02 create mode 100644 examples/wifi/iperf/sdkconfig.ci.03 create mode 100644 examples/wifi/iperf/sdkconfig.ci.04 create mode 100644 examples/wifi/iperf/sdkconfig.ci.05 create mode 100644 examples/wifi/iperf/sdkconfig.ci.06 create mode 100644 examples/wifi/iperf/sdkconfig.ci.07 create mode 100644 examples/wifi/iperf/sdkconfig.ci.99 create mode 100644 examples/wifi/iperf/sdkconfig.defaults diff --git a/examples/wifi/iperf/CMakeLists.txt b/examples/wifi/iperf/CMakeLists.txt new file mode 100644 index 00000000..e2869fde --- /dev/null +++ b/examples/wifi/iperf/CMakeLists.txt @@ -0,0 +1,9 @@ +# 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) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/components) + +set(SUPPORTED_TARGETS esp32) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(iperf) diff --git a/examples/wifi/iperf/Makefile b/examples/wifi/iperf/Makefile new file mode 100644 index 00000000..bf2a3d11 --- /dev/null +++ b/examples/wifi/iperf/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := iperf + +EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/system/console/components + +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 00000000..ce7247f0 --- /dev/null +++ b/examples/wifi/iperf/README.md @@ -0,0 +1,61 @@ +# Iperf Example + +## Note about iperf version +The iperf example doesn't support all features in standard iperf. It's compitable with iperf version 2.x. + +## Note about 80MHz flash frequency +The iperf can get better throughput if the SPI flash frequency is set to 80MHz, but the system may crash in 80MHz mode for ESP-WROVER-KIT. +Removing R140~R145 from the board can fix this issue. Currently the default SPI frequency is set to 40MHz, if you want to change the SPI flash +frequency to 80MHz, please make sure R140~R145 are removed from ESP-WROVER-KIT or use ESP32 DevKitC. + +## Introduction +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/iperf/CMakeLists.txt b/examples/wifi/iperf/components/iperf/CMakeLists.txt new file mode 100644 index 00000000..29cca1e3 --- /dev/null +++ b/examples/wifi/iperf/components/iperf/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "iperf.c" + INCLUDE_DIRS . + REQUIRES lwip) \ No newline at end of file diff --git a/examples/wifi/iperf/components/iperf/component.mk b/examples/wifi/iperf/components/iperf/component.mk new file mode 100644 index 00000000..e5a56dfd --- /dev/null +++ b/examples/wifi/iperf/components/iperf/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/iperf.c b/examples/wifi/iperf/components/iperf/iperf.c new file mode 100644 index 00000000..71d09fee --- /dev/null +++ b/examples/wifi/iperf/components/iperf/iperf.c @@ -0,0 +1,435 @@ +/* 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 "freertos/FreeRTOS.h" +#include "freertos/task.h" +#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 sockfd; +} 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)); +} + +static int iperf_get_socket_error_code(int sockfd) +{ + + return errno; +} + +static int iperf_show_socket_error_reason(const char *str, int sockfd) +{ + int err = errno; + if (err != 0) { + ESP_LOGW(TAG, "%s error, error code: %d, reason: %s", str, err, strerror(err)); + } + + return err; +} + +static void iperf_report_task(void *arg) +{ + uint32_t interval = s_iperf_ctrl.cfg.interval; + uint32_t time = s_iperf_ctrl.cfg.time; + TickType_t delay_interval = (interval * 1000) / portTICK_PERIOD_MS; + 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); +} + +static esp_err_t iperf_start_report(void) +{ + int ret; + + ret = xTaskCreatePinnedToCore(iperf_report_task, IPERF_REPORT_TASK_NAME, IPERF_REPORT_TASK_STACK, NULL, IPERF_REPORT_TASK_PRIORITY, NULL, portNUM_PROCESSORS - 1); + + if (ret != pdPASS) { + ESP_LOGE(TAG, "create task %s failed", IPERF_REPORT_TASK_NAME); + return ESP_FAIL; + } + + return ESP_OK; +} + +static esp_err_t IRAM_ATTR 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 sockfd; + 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; + } + + 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 */ + sockfd = accept(listen_socket, (struct sockaddr *)&remote_addr, &addr_len); + if (sockfd < 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)); + iperf_start_report(); + + t.tv_sec = IPERF_SOCKET_RX_TIMEOUT; + setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)); + } + + while (!s_iperf_ctrl.finish) { + actual_recv = recv(sockfd, buffer, want_recv, 0); + if (actual_recv < 0) { + iperf_show_socket_error_reason("tcp server recv", listen_socket); + s_iperf_ctrl.finish = true; + break; + } else { + s_iperf_ctrl.total_len += actual_recv; + } + } + + close(sockfd); + } + + s_iperf_ctrl.finish = true; + close(listen_socket); + return ESP_OK; +} + +static esp_err_t IRAM_ATTR 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 sockfd; + int opt; + bool udp_recv_start = true ; + + sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sockfd < 0) { + iperf_show_socket_error_reason("udp server create", sockfd); + return ESP_FAIL; + } + + setsockopt(sockfd, 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(sockfd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + iperf_show_socket_error_reason("udp server bind", sockfd); + 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; + + 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(sockfd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)); + + while (!s_iperf_ctrl.finish) { + actual_recv = recvfrom(sockfd, buffer, want_recv, 0, (struct sockaddr *)&addr, &addr_len); + if (actual_recv < 0) { + iperf_show_socket_error_reason("udp server recv", sockfd); + } else { + if(udp_recv_start){ + iperf_start_report(); + udp_recv_start = false; + } + s_iperf_ctrl.total_len += actual_recv; + } + } + + s_iperf_ctrl.finish = true; + close(sockfd); + return ESP_OK; +} + +static 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 sockfd; + int opt; + int err; + int id; + + sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sockfd < 0) { + iperf_show_socket_error_reason("udp client create", sockfd); + return ESP_FAIL; + } + + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + 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(sockfd, buffer, want_send, 0, (struct sockaddr *)&addr, sizeof(addr)); + + if (actual_send != want_send) { + err = iperf_get_socket_error_code(sockfd); + 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(sockfd); + return ESP_OK; +} + +static esp_err_t iperf_run_tcp_client(void) +{ + struct sockaddr_in remote_addr; + int actual_send = 0; + int want_send = 0; + uint8_t *buffer; + int sockfd; + + sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sockfd < 0) { + iperf_show_socket_error_reason("tcp client create", sockfd); + return ESP_FAIL; + } + + memset(&remote_addr, 0, sizeof(remote_addr)); + remote_addr.sin_family = AF_INET; + remote_addr.sin_port = htons(s_iperf_ctrl.cfg.dport); + remote_addr.sin_addr.s_addr = s_iperf_ctrl.cfg.dip; + if (connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(remote_addr)) < 0) { + iperf_show_socket_error_reason("tcp client connect", sockfd); + 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(sockfd, buffer, want_send, 0); + if (actual_send <= 0) { + iperf_show_socket_error_reason("tcp client send", sockfd); + break; + } else { + s_iperf_ctrl.total_len += actual_send; + } + } + + s_iperf_ctrl.finish = true; + close(sockfd); + return ESP_OK; +} + +static 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 = NULL; + } + ESP_LOGI(TAG, "iperf exit"); + s_iperf_is_running = false; + vTaskDelete(NULL); +} + +static 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) +{ + BaseType_t 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: not enough memory"); + return ESP_FAIL; + } + memset(s_iperf_ctrl.buffer, 0, s_iperf_ctrl.buffer_len); + + ret = xTaskCreatePinnedToCore(iperf_task_traffic, IPERF_TRAFFIC_TASK_NAME, IPERF_TRAFFIC_TASK_STACK, NULL, IPERF_TRAFFIC_TASK_PRIORITY, NULL, portNUM_PROCESSORS - 1); + if (ret != pdPASS) { + ESP_LOGE(TAG, "create task %s failed", IPERF_TRAFFIC_TASK_NAME); + free(s_iperf_ctrl.buffer); + s_iperf_ctrl.buffer = NULL; + 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_PERIOD_MS); + } + + return ESP_OK; +} diff --git a/examples/wifi/iperf/components/iperf/iperf.h b/examples/wifi/iperf/components/iperf/iperf.h new file mode 100644 index 00000000..e84e9649 --- /dev/null +++ b/examples/wifi/iperf/components/iperf/iperf.h @@ -0,0 +1,65 @@ +/* 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 + +#include "esp_types.h" +#include "esp_err.h" + +#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 10 +#define IPERF_TRAFFIC_TASK_STACK 4096 +#define IPERF_REPORT_TASK_NAME "iperf_report" +#define IPERF_REPORT_TASK_PRIORITY 20 +#define IPERF_REPORT_TASK_STACK 4096 +#define IPERF_REPORT_TASK_NAME "iperf_report" + +#define IPERF_UDP_TX_LEN (1472) +#define IPERF_UDP_RX_LEN (16 << 10) +#define IPERF_TCP_TX_LEN (16 << 10) +#define IPERF_TCP_RX_LEN (16 << 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/iperf_test.py b/examples/wifi/iperf/iperf_test.py new file mode 100644 index 00000000..882314ee --- /dev/null +++ b/examples/wifi/iperf/iperf_test.py @@ -0,0 +1,621 @@ +""" +Test case for iperf example. + +This test case might have problem running on windows: + +1. direct use of `make` +2. use `sudo killall iperf` to force kill iperf, didn't implement windows version + +The test env Example_ShieldBox do need the following config:: + + Example_ShieldBox: + ap_list: + - ssid: "ssid" + password: "password" + outlet: 1 + apc_ip: "192.168.1.88" + attenuator_port: "/dev/ttyUSB0" + iperf: "/dev/ttyUSB1" + apc_ip: "192.168.1.88" + pc_nic: "eth0" +""" +from __future__ import division +from __future__ import unicode_literals +from builtins import str +from builtins import range +from builtins import object +import re +import os +import time +import subprocess + +from tiny_test_fw import TinyFW, DUT, Utility +import ttfw_idf +from idf_iperf_test_util import (Attenuator, PowerControl, LineChart, TestReport) + + +# configurations +TEST_TIME = TEST_TIMEOUT = 60 +WAIT_AP_POWER_ON_TIMEOUT = 90 +SCAN_TIMEOUT = 3 +SCAN_RETRY_COUNT = 3 +RETRY_COUNT_FOR_BEST_PERFORMANCE = 2 +ATTEN_VALUE_LIST = range(0, 60, 2) + +# constants +FAILED_TO_SCAN_RSSI = -97 +INVALID_HEAP_SIZE = 0xFFFFFFFF + +PC_IPERF_TEMP_LOG_FILE = ".tmp_iperf.log" +CONFIG_NAME_PATTERN = re.compile(r"sdkconfig\.ci\.(.+)") + +# We need to auto compare the difference between adjacent configs (01 -> 00, 02 -> 01, ...) and put them to reports. +# Using numbers for config will make this easy. +# Use default value `99` for config with best performance. +BEST_PERFORMANCE_CONFIG = "99" + + +class TestResult(object): + """ record, analysis test result and convert data to output format """ + + PC_BANDWIDTH_LOG_PATTERN = re.compile(r"(\d+).0\s*-\s*(\d+).0\s+sec\s+[\d.]+\s+MBytes\s+([\d.]+)\s+Mbits/sec") + DUT_BANDWIDTH_LOG_PATTERN = re.compile(r"(\d+)-\s+(\d+)\s+sec\s+([\d.]+)\s+Mbits/sec") + + ZERO_POINT_THRESHOLD = -88 # RSSI, dbm + ZERO_THROUGHPUT_THRESHOLD = -92 # RSSI, dbm + BAD_POINT_RSSI_THRESHOLD = -85 # RSSI, dbm + BAD_POINT_MIN_THRESHOLD = 3 # Mbps + BAD_POINT_PERCENTAGE_THRESHOLD = 0.3 + + # we need at least 1/2 valid points to qualify the test result + THROUGHPUT_QUALIFY_COUNT = TEST_TIME // 2 + + def __init__(self, proto, direction, config_name): + self.proto = proto + self.direction = direction + self.config_name = config_name + self.throughput_by_rssi = dict() + self.throughput_by_att = dict() + self.att_rssi_map = dict() + self.heap_size = INVALID_HEAP_SIZE + self.error_list = [] + + def _save_result(self, throughput, ap_ssid, att, rssi, heap_size): + """ + save the test results: + + * record the better throughput if att/rssi is the same. + * record the min heap size. + """ + if ap_ssid not in self.att_rssi_map: + # for new ap, create empty dict() + self.throughput_by_att[ap_ssid] = dict() + self.throughput_by_rssi[ap_ssid] = dict() + self.att_rssi_map[ap_ssid] = dict() + + self.att_rssi_map[ap_ssid][att] = rssi + + def record_throughput(database, key_value): + try: + # we save the larger value for same att + if throughput > database[ap_ssid][key_value]: + database[ap_ssid][key_value] = throughput + except KeyError: + database[ap_ssid][key_value] = throughput + + record_throughput(self.throughput_by_att, att) + record_throughput(self.throughput_by_rssi, rssi) + + if int(heap_size) < self.heap_size: + self.heap_size = int(heap_size) + + def add_result(self, raw_data, ap_ssid, att, rssi, heap_size): + """ + add result for one test + + :param raw_data: iperf raw data + :param ap_ssid: ap ssid that tested + :param att: attenuate value + :param rssi: AP RSSI + :param heap_size: min heap size during test + :return: throughput + """ + fall_to_0_recorded = 0 + throughput_list = [] + result_list = self.PC_BANDWIDTH_LOG_PATTERN.findall(raw_data) + if not result_list: + # failed to find raw data by PC pattern, it might be DUT pattern + result_list = self.DUT_BANDWIDTH_LOG_PATTERN.findall(raw_data) + + for result in result_list: + if int(result[1]) - int(result[0]) != 1: + # this could be summary, ignore this + continue + throughput_list.append(float(result[2])) + if float(result[2]) == 0 and rssi > self.ZERO_POINT_THRESHOLD \ + and fall_to_0_recorded < 1: + # throughput fall to 0 error. we only record 1 records for one test + self.error_list.append("[Error][fall to 0][{}][att: {}][rssi: {}]: 0 throughput interval: {}-{}" + .format(ap_ssid, att, rssi, result[0], result[1])) + fall_to_0_recorded += 1 + + if len(throughput_list) > self.THROUGHPUT_QUALIFY_COUNT: + throughput = sum(throughput_list) / len(throughput_list) + else: + throughput = 0.0 + + if throughput == 0 and rssi > self.ZERO_THROUGHPUT_THRESHOLD: + self.error_list.append("[Error][Fatal][{}][att: {}][rssi: {}]: No throughput data found" + .format(ap_ssid, att, rssi)) + + self._save_result(throughput, ap_ssid, att, rssi, heap_size) + + return throughput + + def post_analysis(self): + """ + some rules need to be checked after we collected all test raw data: + + 1. throughput value 30% worse than the next point with lower RSSI + 2. throughput value 30% worse than the next point with larger attenuate + """ + def analysis_bad_point(data, index_type): + for ap_ssid in data: + result_dict = data[ap_ssid] + index_list = list(result_dict.keys()) + index_list.sort() + if index_type == "att": + index_list.reverse() + + for i, index_value in enumerate(index_list[1:]): + if index_value < self.BAD_POINT_RSSI_THRESHOLD or \ + result_dict[index_list[i]] < self.BAD_POINT_MIN_THRESHOLD: + continue + _percentage = result_dict[index_value] / result_dict[index_list[i]] + if _percentage < 1 - self.BAD_POINT_PERCENTAGE_THRESHOLD: + self.error_list.append("[Error][Bad point][{}][{}: {}]: drop {:.02f}%" + .format(ap_ssid, index_type, index_value, + (1 - _percentage) * 100)) + + analysis_bad_point(self.throughput_by_rssi, "rssi") + analysis_bad_point(self.throughput_by_att, "att") + + @staticmethod + def _convert_to_draw_format(data, label): + keys = data.keys() + keys.sort() + return { + "x-axis": keys, + "y-axis": [data[x] for x in keys], + "label": label, + } + + def draw_throughput_figure(self, path, ap_ssid, draw_type): + """ + :param path: folder to save figure. make sure the folder is already created. + :param ap_ssid: ap ssid string or a list of ap ssid string + :param draw_type: "att" or "rssi" + :return: file_name + """ + if draw_type == "rssi": + type_name = "RSSI" + data = self.throughput_by_rssi + elif draw_type == "att": + type_name = "Att" + data = self.throughput_by_att + else: + raise AssertionError("draw type not supported") + if isinstance(ap_ssid, list): + file_name = "ThroughputVs{}_{}_{}_{}.png".format(type_name, self.proto, self.direction, + hash(ap_ssid)[:6]) + data_list = [self._convert_to_draw_format(data[_ap_ssid], _ap_ssid) + for _ap_ssid in ap_ssid] + else: + file_name = "ThroughputVs{}_{}_{}_{}.png".format(type_name, self.proto, self.direction, ap_ssid) + data_list = [self._convert_to_draw_format(data[ap_ssid], ap_ssid)] + + LineChart.draw_line_chart(os.path.join(path, file_name), + "Throughput Vs {} ({} {})".format(type_name, self.proto, self.direction), + "Throughput (Mbps)", + "{} (dbm)".format(type_name), + data_list) + return file_name + + def draw_rssi_vs_att_figure(self, path, ap_ssid): + """ + :param path: folder to save figure. make sure the folder is already created. + :param ap_ssid: ap to use + :return: file_name + """ + if isinstance(ap_ssid, list): + file_name = "AttVsRSSI_{}.png".format(hash(ap_ssid)[:6]) + data_list = [self._convert_to_draw_format(self.att_rssi_map[_ap_ssid], _ap_ssid) + for _ap_ssid in ap_ssid] + else: + file_name = "AttVsRSSI_{}.png".format(ap_ssid) + data_list = [self._convert_to_draw_format(self.att_rssi_map[ap_ssid], ap_ssid)] + LineChart.draw_line_chart(os.path.join(path, file_name), + "Att Vs RSSI", + "Att (dbm)", + "RSSI (dbm)", + data_list) + return file_name + + def get_best_throughput(self): + """ get the best throughput during test """ + best_for_aps = [max(self.throughput_by_att[ap_ssid].values()) + for ap_ssid in self.throughput_by_att] + return max(best_for_aps) + + def __str__(self): + """ + returns summary for this test: + + 1. test result (success or fail) + 2. best performance for each AP + 3. min free heap size during test + """ + if self.throughput_by_att: + ret = "[{}_{}][{}]: {}\r\n\r\n".format(self.proto, self.direction, self.config_name, + "Fail" if self.error_list else "Success") + ret += "Performance for each AP:\r\n" + for ap_ssid in self.throughput_by_att: + ret += "[{}]: {:.02f} Mbps\r\n".format(ap_ssid, max(self.throughput_by_att[ap_ssid].values())) + if self.heap_size != INVALID_HEAP_SIZE: + ret += "Minimum heap size: {}".format(self.heap_size) + else: + ret = "" + return ret + + +class IperfTestUtility(object): + """ iperf test implementation """ + + def __init__(self, dut, config_name, ap_ssid, ap_password, + pc_nic_ip, pc_iperf_log_file, test_result=None): + self.config_name = config_name + self.dut = dut + + self.pc_iperf_log_file = pc_iperf_log_file + self.ap_ssid = ap_ssid + self.ap_password = ap_password + self.pc_nic_ip = pc_nic_ip + + if test_result: + self.test_result = test_result + else: + self.test_result = { + "tcp_tx": TestResult("tcp", "tx", config_name), + "tcp_rx": TestResult("tcp", "rx", config_name), + "udp_tx": TestResult("udp", "tx", config_name), + "udp_rx": TestResult("udp", "rx", config_name), + } + + def setup(self): + """ + setup iperf test: + + 1. kill current iperf process + 2. reboot DUT (currently iperf is not very robust, need to reboot DUT) + 3. scan to get AP RSSI + 4. connect to AP + """ + try: + subprocess.check_output("sudo killall iperf 2>&1 > /dev/null", shell=True) + except subprocess.CalledProcessError: + pass + self.dut.write("restart") + self.dut.expect("esp32>") + self.dut.write("scan {}".format(self.ap_ssid)) + for _ in range(SCAN_RETRY_COUNT): + try: + rssi = int(self.dut.expect(re.compile(r"\[{}]\[rssi=(-\d+)]".format(self.ap_ssid)), + timeout=SCAN_TIMEOUT)[0]) + break + except DUT.ExpectTimeout: + continue + else: + raise AssertionError("Failed to scan AP") + self.dut.write("sta {} {}".format(self.ap_ssid, self.ap_password)) + dut_ip = self.dut.expect(re.compile(r"sta ip: ([\d.]+), mask: ([\d.]+), gw: ([\d.]+)"))[0] + return dut_ip, rssi + + def _save_test_result(self, test_case, raw_data, att, rssi, heap_size): + return self.test_result[test_case].add_result(raw_data, self.ap_ssid, att, rssi, heap_size) + + def _test_once(self, proto, direction): + """ do measure once for one type """ + # connect and scan to get RSSI + dut_ip, rssi = self.setup() + + assert direction in ["rx", "tx"] + assert proto in ["tcp", "udp"] + + # run iperf test + if direction == "tx": + with open(PC_IPERF_TEMP_LOG_FILE, "w") as f: + if proto == "tcp": + process = subprocess.Popen(["iperf", "-s", "-B", self.pc_nic_ip, + "-t", str(TEST_TIME), "-i", "1", "-f", "m"], + stdout=f, stderr=f) + self.dut.write("iperf -c {} -i 1 -t {}".format(self.pc_nic_ip, TEST_TIME)) + else: + process = subprocess.Popen(["iperf", "-s", "-u", "-B", self.pc_nic_ip, + "-t", str(TEST_TIME), "-i", "1", "-f", "m"], + stdout=f, stderr=f) + self.dut.write("iperf -c {} -u -i 1 -t {}".format(self.pc_nic_ip, TEST_TIME)) + + for _ in range(TEST_TIMEOUT): + if process.poll() is not None: + break + time.sleep(1) + else: + process.terminate() + + with open(PC_IPERF_TEMP_LOG_FILE, "r") as f: + pc_raw_data = server_raw_data = f.read() + else: + with open(PC_IPERF_TEMP_LOG_FILE, "w") as f: + if proto == "tcp": + self.dut.write("iperf -s -i 1 -t {}".format(TEST_TIME)) + process = subprocess.Popen(["iperf", "-c", dut_ip, + "-t", str(TEST_TIME), "-f", "m"], + stdout=f, stderr=f) + else: + self.dut.write("iperf -s -u -i 1 -t {}".format(TEST_TIME)) + process = subprocess.Popen(["iperf", "-c", dut_ip, "-u", "-b", "100M", + "-t", str(TEST_TIME), "-f", "m"], + stdout=f, stderr=f) + + for _ in range(TEST_TIMEOUT): + if process.poll() is not None: + break + time.sleep(1) + else: + process.terminate() + + server_raw_data = self.dut.read() + with open(PC_IPERF_TEMP_LOG_FILE, "r") as f: + pc_raw_data = f.read() + + # save PC iperf logs to console + with open(self.pc_iperf_log_file, "a+") as f: + f.write("## [{}] `{}`\r\n##### {}" + .format(self.config_name, + "{}_{}".format(proto, direction), + time.strftime("%m-%d %H:%M:%S", time.localtime(time.time())))) + f.write('\r\n```\r\n\r\n' + pc_raw_data + '\r\n```\r\n') + self.dut.write("heap") + heap_size = self.dut.expect(re.compile(r"min heap size: (\d+)\D"))[0] + + # return server raw data (for parsing test results) and RSSI + return server_raw_data, rssi, heap_size + + def run_test(self, proto, direction, atten_val): + """ + run test for one type, with specified atten_value and save the test result + + :param proto: tcp or udp + :param direction: tx or rx + :param atten_val: attenuate value + """ + rssi = FAILED_TO_SCAN_RSSI + heap_size = INVALID_HEAP_SIZE + try: + server_raw_data, rssi, heap_size = self._test_once(proto, direction) + throughput = self._save_test_result("{}_{}".format(proto, direction), + server_raw_data, atten_val, + rssi, heap_size) + Utility.console_log("[{}][{}_{}][{}][{}]: {:.02f}" + .format(self.config_name, proto, direction, rssi, self.ap_ssid, throughput)) + except Exception as e: + self._save_test_result("{}_{}".format(proto, direction), "", atten_val, rssi, heap_size) + Utility.console_log("Failed during test: {}".format(e)) + + def run_all_cases(self, atten_val): + """ + run test for all types (udp_tx, udp_rx, tcp_tx, tcp_rx). + + :param atten_val: attenuate value + """ + self.run_test("tcp", "tx", atten_val) + self.run_test("tcp", "rx", atten_val) + self.run_test("udp", "tx", atten_val) + self.run_test("udp", "rx", atten_val) + + def wait_ap_power_on(self): + """ + AP need to take sometime to power on. It changes for different APs. + This method will scan to check if the AP powers on. + + :return: True or False + """ + self.dut.write("restart") + self.dut.expect("esp32>") + for _ in range(WAIT_AP_POWER_ON_TIMEOUT // SCAN_TIMEOUT): + try: + self.dut.write("scan {}".format(self.ap_ssid)) + self.dut.expect(re.compile(r"\[{}]\[rssi=(-\d+)]".format(self.ap_ssid)), + timeout=SCAN_TIMEOUT) + ret = True + break + except DUT.ExpectTimeout: + pass + else: + ret = False + return ret + + +@ttfw_idf.idf_example_test(env_tag="Example_ShieldBox_Basic", category="stress") +def test_wifi_throughput_with_different_configs(env, extra_data): + """ + steps: | + 1. build iperf with specified configs + 2. test throughput for all routers + """ + pc_nic_ip = env.get_pc_nic_info("pc_nic", "ipv4")["addr"] + pc_iperf_log_file = os.path.join(env.log_path, "pc_iperf_log.md") + ap_info = { + "ssid": env.get_variable("ap_ssid"), + "password": env.get_variable("ap_password"), + } + + config_names_raw = subprocess.check_output(["ls", os.path.dirname(os.path.abspath(__file__))]) + config_names = CONFIG_NAME_PATTERN.findall(config_names_raw) + if not config_names: + raise ValueError("no configs found in {}".format(os.path.dirname(__file__))) + + test_result = dict() + sdkconfig_files = dict() + + for config_name in config_names: + # 1. get the config + sdkconfig_files[config_name] = os.path.join(os.path.dirname(__file__), + "sdkconfig.ci.{}".format(config_name)) + + # 2. get DUT and download + dut = env.get_dut("iperf", "examples/wifi/iperf", dut_class=ttfw_idf.ESP32DUT, + app_config_name=config_name) + dut.start_app() + dut.expect("esp32>") + + # 3. run test for each required att value + test_result[config_name] = { + "tcp_tx": TestResult("tcp", "tx", config_name), + "tcp_rx": TestResult("tcp", "rx", config_name), + "udp_tx": TestResult("udp", "tx", config_name), + "udp_rx": TestResult("udp", "rx", config_name), + } + + test_utility = IperfTestUtility(dut, config_name, ap_info["ssid"], + ap_info["password"], pc_nic_ip, pc_iperf_log_file, test_result[config_name]) + + for _ in range(RETRY_COUNT_FOR_BEST_PERFORMANCE): + test_utility.run_all_cases(0) + + for result_type in test_result[config_name]: + summary = str(test_result[config_name][result_type]) + if summary: + Utility.console_log(summary, color="orange") + + # 4. check test results + env.close_dut("iperf") + + # 5. generate report + report = TestReport.ThroughputForConfigsReport(os.path.join(env.log_path, "ThroughputForConfigsReport"), + ap_info["ssid"], test_result, sdkconfig_files) + report.generate_report() + + +@ttfw_idf.idf_example_test(env_tag="Example_ShieldBox", category="stress") +def test_wifi_throughput_vs_rssi(env, extra_data): + """ + steps: | + 1. build with best performance config + 2. switch on one router + 3. set attenuator value from 0-60 for each router + 4. test TCP tx rx and UDP tx rx throughput + """ + att_port = env.get_variable("attenuator_port") + ap_list = env.get_variable("ap_list") + pc_nic_ip = env.get_pc_nic_info("pc_nic", "ipv4")["addr"] + apc_ip = env.get_variable("apc_ip") + pc_iperf_log_file = os.path.join(env.log_path, "pc_iperf_log.md") + + test_result = { + "tcp_tx": TestResult("tcp", "tx", BEST_PERFORMANCE_CONFIG), + "tcp_rx": TestResult("tcp", "rx", BEST_PERFORMANCE_CONFIG), + "udp_tx": TestResult("udp", "tx", BEST_PERFORMANCE_CONFIG), + "udp_rx": TestResult("udp", "rx", BEST_PERFORMANCE_CONFIG), + } + + # 1. get DUT and download + dut = env.get_dut("iperf", "examples/wifi/iperf", dut_class=ttfw_idf.ESP32DUT, + app_config_name=BEST_PERFORMANCE_CONFIG) + dut.start_app() + dut.expect("esp32>") + + # 2. run test for each required att value + for ap_info in ap_list: + test_utility = IperfTestUtility(dut, BEST_PERFORMANCE_CONFIG, ap_info["ssid"], ap_info["password"], + pc_nic_ip, pc_iperf_log_file, test_result) + + PowerControl.Control.control_rest(apc_ip, ap_info["outlet"], "OFF") + PowerControl.Control.control(apc_ip, {ap_info["outlet"]: "ON"}) + Attenuator.set_att(att_port, 0) + + if not test_utility.wait_ap_power_on(): + Utility.console_log("[{}] failed to power on, skip testing this AP" + .format(ap_info["ssid"]), color="red") + continue + + for atten_val in ATTEN_VALUE_LIST: + assert Attenuator.set_att(att_port, atten_val) is True + test_utility.run_all_cases(atten_val) + + # 3. check test results + env.close_dut("iperf") + + # 4. generate report + report = TestReport.ThroughputVsRssiReport(os.path.join(env.log_path, "ThroughputVsRssiReport"), + test_result) + report.generate_report() + + +@ttfw_idf.idf_example_test(env_tag="Example_ShieldBox_Basic") +def test_wifi_throughput_basic(env, extra_data): + """ + steps: | + 1. test TCP tx rx and UDP tx rx throughput + 2. compare with the pre-defined pass standard + """ + pc_nic_ip = env.get_pc_nic_info("pc_nic", "ipv4")["addr"] + pc_iperf_log_file = os.path.join(env.log_path, "pc_iperf_log.md") + ap_info = { + "ssid": env.get_variable("ap_ssid"), + "password": env.get_variable("ap_password"), + } + + # 1. get DUT + dut = env.get_dut("iperf", "examples/wifi/iperf", dut_class=ttfw_idf.ESP32DUT, + app_config_name=BEST_PERFORMANCE_CONFIG) + dut.start_app() + dut.expect("esp32>") + + # 2. preparing + test_result = { + "tcp_tx": TestResult("tcp", "tx", BEST_PERFORMANCE_CONFIG), + "tcp_rx": TestResult("tcp", "rx", BEST_PERFORMANCE_CONFIG), + "udp_tx": TestResult("udp", "tx", BEST_PERFORMANCE_CONFIG), + "udp_rx": TestResult("udp", "rx", BEST_PERFORMANCE_CONFIG), + } + + test_utility = IperfTestUtility(dut, BEST_PERFORMANCE_CONFIG, ap_info["ssid"], + ap_info["password"], pc_nic_ip, pc_iperf_log_file, test_result) + + # 3. run test for TCP Tx, Rx and UDP Tx, Rx + for _ in range(RETRY_COUNT_FOR_BEST_PERFORMANCE): + test_utility.run_all_cases(0) + + # 4. log performance and compare with pass standard + performance_items = [] + for throughput_type in test_result: + ttfw_idf.log_performance("{}_throughput".format(throughput_type), + "{:.02f} Mbps".format(test_result[throughput_type].get_best_throughput())) + performance_items.append(["{}_throughput".format(throughput_type), + "{:.02f} Mbps".format(test_result[throughput_type].get_best_throughput())]) + + # 5. save to report + TinyFW.JunitReport.update_performance(performance_items) + # do check after logging, otherwise test will exit immediately if check fail, some performance can't be logged. + for throughput_type in test_result: + ttfw_idf.check_performance("{}_throughput".format(throughput_type), + test_result[throughput_type].get_best_throughput()) + + env.close_dut("iperf") + + +if __name__ == '__main__': + test_wifi_throughput_basic(env_config_file="EnvConfig.yml") + test_wifi_throughput_with_different_configs(env_config_file="EnvConfig.yml") + test_wifi_throughput_vs_rssi(env_config_file="EnvConfig.yml") diff --git a/examples/wifi/iperf/main/CMakeLists.txt b/examples/wifi/iperf/main/CMakeLists.txt new file mode 100644 index 00000000..978cc99e --- /dev/null +++ b/examples/wifi/iperf/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "cmd_wifi.c" + "iperf_example_main.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/examples/wifi/iperf/main/cmd_decl.h b/examples/wifi/iperf/main/cmd_decl.h new file mode 100644 index 00000000..4fbc4794 --- /dev/null +++ b/examples/wifi/iperf/main/cmd_decl.h @@ -0,0 +1,20 @@ +/* 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 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "cmd_system.h" +#include "cmd_wifi.h" + +#ifdef __cplusplus +} +#endif diff --git a/examples/wifi/iperf/main/cmd_wifi.c b/examples/wifi/iperf/main/cmd_wifi.c new file mode 100644 index 00000000..128fe470 --- /dev/null +++ b/examples/wifi/iperf/main/cmd_wifi.c @@ -0,0 +1,447 @@ +/* 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 "esp_netif.h" +#include "esp_event.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; + +typedef struct { + struct arg_str *ssid; + struct arg_end *end; +} wifi_scan_arg_t; + +static wifi_args_t sta_args; +static wifi_scan_arg_t scan_args; +static wifi_args_t ap_args; +static bool reconnect = true; +static const char *TAG="cmd_wifi"; +static esp_netif_t *netif_ap = NULL; +static esp_netif_t *netif_sta = NULL; + +static EventGroupHandle_t wifi_event_group; +const int CONNECTED_BIT = BIT0; +const int DISCONNECTED_BIT = BIT1; + +static void scan_done_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + uint16_t sta_number = 0; + uint8_t i; + wifi_ap_record_t *ap_list_buffer; + + esp_wifi_scan_get_ap_num(&sta_number); + ap_list_buffer = malloc(sta_number * sizeof(wifi_ap_record_t)); + if (ap_list_buffer == NULL) { + ESP_LOGE(TAG, "Failed to malloc buffer to print scan results"); + return; + } + + if (esp_wifi_scan_get_ap_records(&sta_number,(wifi_ap_record_t *)ap_list_buffer) == ESP_OK) { + for(i=0; isval[0]); + wifi_cmd_sta_join(sta_args.ssid->sval[0], sta_args.password->sval[0]); + return 0; +} + +static bool wifi_cmd_sta_scan(const char* ssid) +{ + wifi_scan_config_t scan_config = { 0 }; + scan_config.ssid = (uint8_t *) ssid; + + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_scan_start(&scan_config, false) ); + + return true; +} + +static int wifi_cmd_scan(int argc, char** argv) +{ + int nerrors = arg_parse(argc, argv, (void**) &scan_args); + + if (nerrors != 0) { + arg_print_errors(stderr, scan_args.end, argv[0]); + return 1; + } + + ESP_LOGI(TAG, "sta start to scan"); + if ( scan_args.ssid->count == 1 ) { + wifi_cmd_sta_scan(scan_args.ssid->sval[0]); + } else { + wifi_cmd_sta_scan(NULL); + } + 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; + strlcpy((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; + } + strlcpy((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); + esp_netif_t * netif = netif_ap; + esp_netif_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) { + netif = netif_sta; + } else { + ESP_LOGE(TAG, "sta has no IP"); + return 0; + } + } + + esp_netif_get_ip_info(netif, &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 = esp_ip4addr_aton(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; +} + +void register_wifi(void) +{ + 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) ); + + scan_args.ssid = arg_str0(NULL, NULL, "", "SSID of AP want to be scanned"); + scan_args.end = arg_end(1); + + const esp_console_cmd_t scan_cmd = { + .command = "scan", + .help = "WiFi is station mode, start scan ap", + .hint = NULL, + .func = &wifi_cmd_scan, + .argtable = &scan_args + }; + + 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); + + + ESP_ERROR_CHECK( esp_console_cmd_register(&scan_cmd) ); + + 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) ); + + 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", "