mirror of
https://github.com/espressif/ESP8266_RTOS_SDK.git
synced 2025-06-29 05:07:20 +08:00
Merge branch 'feature/add_iperf_example' into 'master'
feat(iperf): Add iperf example See merge request sdk/ESP8266_RTOS_SDK!1520
This commit is contained in:
8
examples/wifi/iperf/CMakeLists.txt
Normal file
8
examples/wifi/iperf/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
# 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)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(iperf)
|
10
examples/wifi/iperf/Makefile
Normal file
10
examples/wifi/iperf/Makefile
Normal file
@ -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
|
73
examples/wifi/iperf/README.md
Normal file
73
examples/wifi/iperf/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# 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.
|
||||
|
||||
## Introduction
|
||||
This example implements the protocol used by the common performance measurement tool [iPerf](https://iperf.fr/).
|
||||
Performance can be measured between two ESP8266 running this example, or between a single ESP8266 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 ESP8266 side
|
||||
iperf -c 192.168.4.1 -i 3 -t 60
|
||||
|
||||
The console output, which is printed by station TCP TX throughput test, looks like:
|
||||
|
||||
>esp8266> sta aptest
|
||||
>
|
||||
>I (5325) iperf: sta connecting to 'aptest'
|
||||
>
|
||||
>esp8266> I (6017) event: ip: 192.168.4.2, mask: 255.255.255.0, gw: 192.168.4.1
|
||||
>
|
||||
>esp8266> iperf -s -i 3 -t 60
|
||||
>
|
||||
>I (14958) iperf: mode=tcp-server sip=192.168.4.1:5001, dip=0.0.0.0:5001, interval=3, time=60
|
||||
>
|
||||
>Interval Bandwidth
|
||||
>
|
||||
>esp8266> accept: 192.168.4.2,63201
|
||||
>
|
||||
>0- 3 sec 5.50 Mbits/sec
|
||||
>
|
||||
>3- 6 sec 6.83 Mbits/sec
|
||||
>
|
||||
>6- 9 sec 6.81 Mbits/sec
|
||||
>
|
||||
>9- 12 sec 6.88 Mbits/sec
|
||||
>
|
||||
>12- 15 sec 6.71 Mbits/sec
|
||||
>
|
||||
>15- 18 sec 6.80 Mbits/sec
|
||||
>
|
||||
>18- 21 sec 6.83 Mbits/sec
|
||||
|
||||
|
||||
Steps to test station/soft-AP TCP/UDP RX/TX throughput are similar as test steps in station TCP TX.
|
||||
|
||||
If you want to improve the performance, need choose the sdkconfig.defaults config to build bin.
|
||||
|
||||
1. OS configuration: i. Enable Full cache; ii. CPU frequence 160M.
|
||||
2. WIFI configuration: i. AMPDU enable; ii. Set RX Buffer Num to 26; iii. Set RX Packet Num to 7; iv. Set TX Packet Num to 6; v. Set WIFI CONTINUOUS RX Buffer Num to 4.
|
||||
3. LWIP configuration: 1. Set TCP MSS to 1460; ii. Set TCP SND WND to 11680 (8 * MSS); iii. Set TCP RCV WND to 7300 (5 * MSS); iv. Set TCP RECV BOX to 7 (5 + 2).
|
||||
|
||||
The following data is calculated by PC iperf with ESP8266 iperf example.
|
||||
|
||||
|Mbits/sec|TCP TX|TCP RX|UDP TX|UDP RX|
|
||||
|:-----:|:-----:|:-----:|:-----:|:-----:|
|
||||
||15.29|10.56|24.33|42.04|
|
||||
|Disable ampdu|15.50|11.36|24.12|21.61|
|
||||
|CPU 80M|10.54|7.91|16.08|25.71|
|
||||
|Disable Full cache|7.78|6.30|10.32|13.21|
|
||||
|Disable Full cache, disable ampdu, CPU 80M|5.99|5.19|8.09|11.25|
|
||||
|
||||
|
||||
See the README.md file in the upper level 'examples' directory for more information about examples.
|
3
examples/wifi/iperf/components/iperf/CMakeLists.txt
Normal file
3
examples/wifi/iperf/components/iperf/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "iperf.c"
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES lwip)
|
11
examples/wifi/iperf/components/iperf/component.mk
Normal file
11
examples/wifi/iperf/components/iperf/component.mk
Normal file
@ -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 := .
|
435
examples/wifi/iperf/components/iperf/iperf.c
Normal file
435
examples/wifi/iperf/components/iperf/iperf.c
Normal file
@ -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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#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;
|
||||
}
|
65
examples/wifi/iperf/components/iperf/iperf.h
Normal file
65
examples/wifi/iperf/components/iperf/iperf.h
Normal file
@ -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
|
621
examples/wifi/iperf/iperf_test.py
Normal file
621
examples/wifi/iperf/iperf_test.py
Normal file
@ -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")
|
3
examples/wifi/iperf/main/CMakeLists.txt
Normal file
3
examples/wifi/iperf/main/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "cmd_wifi.c"
|
||||
"iperf_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
20
examples/wifi/iperf/main/cmd_decl.h
Normal file
20
examples/wifi/iperf/main/cmd_decl.h
Normal file
@ -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
|
449
examples/wifi/iperf/main/cmd_wifi.c
Normal file
449
examples/wifi/iperf/main/cmd_wifi.c
Normal file
@ -0,0 +1,449 @@
|
||||
/* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#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 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; i<sta_number; i++) {
|
||||
ESP_LOGI(TAG, "[%s][rssi=%d]", ap_list_buffer[i].ssid, ap_list_buffer[i].rssi);
|
||||
}
|
||||
}
|
||||
free(ap_list_buffer);
|
||||
ESP_LOGI(TAG, "sta scan done");
|
||||
}
|
||||
|
||||
static void got_ip_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
xEventGroupClearBits(wifi_event_group, DISCONNECTED_BIT);
|
||||
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
|
||||
}
|
||||
|
||||
static void disconnect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
static void on_sta_start(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
ESP_LOGI(TAG, "WIFI STA START");
|
||||
ESP_ERROR_CHECK( esp_wifi_set_ps(WIFI_PS_NONE));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_protocol(ESP_IF_WIFI_STA,WIFI_PROTOCOL_11B|WIFI_PROTOCOL_11G|WIFI_PROTOCOL_11N));
|
||||
}
|
||||
|
||||
void initialise_wifi(void)
|
||||
{
|
||||
esp_log_level_set("wifi", ESP_LOG_WARN);
|
||||
static bool initialized = false;
|
||||
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
wifi_event_group = xEventGroupCreate();
|
||||
ESP_ERROR_CHECK( esp_event_loop_create_default() );
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
|
||||
ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_SCAN_DONE, &scan_done_handler, NULL) );
|
||||
ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, NULL) );
|
||||
ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_START, &on_sta_start, NULL) );
|
||||
ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &got_ip_handler, NULL) );
|
||||
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) {
|
||||
strlcpy((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_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);
|
||||
tcpip_adapter_if_t tcpip_if = 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) {
|
||||
tcpip_if = TCPIP_ADAPTER_IF_STA;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "sta has no IP");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
tcpip_adapter_get_ip_info(tcpip_if, &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;
|
||||
}
|
||||
|
||||
void register_wifi(void)
|
||||
{
|
||||
sta_args.ssid = arg_str1(NULL, NULL, "<ssid>", "SSID of AP");
|
||||
sta_args.password = arg_str0(NULL, NULL, "<pass>", "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>", "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>", "SSID of AP");
|
||||
ap_args.password = arg_str0(NULL, NULL, "<pass>", "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", "<ip>", "run in client mode, connecting to <host>");
|
||||
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", "<port>", "server port to listen on/connect to");
|
||||
iperf_args.interval = arg_int0("i", "interval", "<interval>", "seconds between periodic bandwidth reports");
|
||||
iperf_args.time = arg_int0("t", "time", "<time>", "time in seconds to transmit for (default 10 secs)");
|
||||
iperf_args.abort = arg_lit0("a", "abort", "abort running iperf");
|
||||
iperf_args.end = arg_end(1);
|
||||
const esp_console_cmd_t iperf_cmd = {
|
||||
.command = "iperf",
|
||||
.help = "iperf command",
|
||||
.hint = NULL,
|
||||
.func = &wifi_cmd_iperf,
|
||||
.argtable = &iperf_args
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&iperf_cmd) );
|
||||
}
|
13
examples/wifi/iperf/main/cmd_wifi.h
Normal file
13
examples/wifi/iperf/main/cmd_wifi.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Register WiFi functions
|
||||
void register_wifi(void);
|
||||
void initialise_wifi(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
0
examples/wifi/iperf/main/component.mk
Normal file
0
examples/wifi/iperf/main/component.mk
Normal file
144
examples/wifi/iperf/main/iperf_example_main.c
Normal file
144
examples/wifi/iperf/main/iperf_example_main.c
Normal file
@ -0,0 +1,144 @@
|
||||
/* Iperf 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 <errno.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "esp_console.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "driver/uart.h"
|
||||
#include "linenoise/linenoise.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "cmd_decl.h"
|
||||
|
||||
|
||||
#define WIFI_CONNECTED_BIT BIT0
|
||||
|
||||
static void initialize_console(void)
|
||||
{
|
||||
/* Disable buffering on stdin */
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
|
||||
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
|
||||
esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
|
||||
/* Move the caret to the beginning of the next line on '\n' */
|
||||
esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
|
||||
/* Install UART driver for interrupt-driven reads and writes */
|
||||
ESP_ERROR_CHECK( uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM,
|
||||
256, 0, 0, NULL, 0) );
|
||||
|
||||
/* Tell VFS to use UART driver */
|
||||
esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
|
||||
/* Initialize the console */
|
||||
esp_console_config_t console_config = {
|
||||
.max_cmdline_args = 32,
|
||||
.max_cmdline_length = 256,
|
||||
#if CONFIG_LOG_COLORS
|
||||
.hint_color = atoi(LOG_COLOR_CYAN)
|
||||
#endif
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_init(&console_config) );
|
||||
|
||||
/* Configure linenoise line completion library */
|
||||
/* Enable multiline editing. If not set, long commands will scroll within
|
||||
* single line.
|
||||
*/
|
||||
linenoiseSetMultiLine(1);
|
||||
|
||||
/* Tell linenoise where to get command completions and hints */
|
||||
linenoiseSetCompletionCallback(&esp_console_get_completion);
|
||||
linenoiseSetHintsCallback((linenoiseHintsCallback *) &esp_console_get_hint);
|
||||
|
||||
/* Set command history size */
|
||||
linenoiseHistorySetMaxLen(100);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
initialise_wifi();
|
||||
initialize_console();
|
||||
|
||||
/* Register commands */
|
||||
esp_console_register_help_command();
|
||||
register_system();
|
||||
register_wifi();
|
||||
|
||||
/* Prompt to be printed before each line.
|
||||
* This can be customized, made dynamic, etc.
|
||||
*/
|
||||
const char *prompt = LOG_COLOR_I "esp8266> " LOG_RESET_COLOR;
|
||||
|
||||
printf("\n ==================================================\n");
|
||||
printf(" | Steps to test WiFi throughput |\n");
|
||||
printf(" | |\n");
|
||||
printf(" | 1. Print 'help' to gain overview of commands |\n");
|
||||
printf(" | 2. Configure device to station or soft-AP |\n");
|
||||
printf(" | 3. Setup WiFi connection |\n");
|
||||
printf(" | 4. Run iperf to test UDP/TCP RX/TX throughput |\n");
|
||||
printf(" | |\n");
|
||||
printf(" =================================================\n\n");
|
||||
|
||||
/* Figure out if the terminal supports escape sequences */
|
||||
int probe_status = linenoiseProbe();
|
||||
if (probe_status) { /* zero indicates success */
|
||||
printf("\n"
|
||||
"Your terminal application does not support escape sequences.\n"
|
||||
"Line editing and history features are disabled.\n"
|
||||
"On Windows, try using Putty instead.\n");
|
||||
linenoiseSetDumbMode(1);
|
||||
#if CONFIG_LOG_COLORS
|
||||
/* Since the terminal doesn't support escape sequences,
|
||||
* don't use color codes in the prompt.
|
||||
*/
|
||||
prompt = "esp8266> ";
|
||||
#endif //CONFIG_LOG_COLORS
|
||||
}
|
||||
|
||||
/* Main loop */
|
||||
while (true) {
|
||||
/* Get a line using linenoise.
|
||||
* The line is returned when ENTER is pressed.
|
||||
*/
|
||||
char *line = linenoise(prompt);
|
||||
if (line == NULL) { /* Ignore empty lines */
|
||||
continue;
|
||||
}
|
||||
/* Add the command to the history */
|
||||
linenoiseHistoryAdd(line);
|
||||
|
||||
/* Try to run the command */
|
||||
int ret;
|
||||
esp_err_t err = esp_console_run(line, &ret);
|
||||
if (err == ESP_ERR_NOT_FOUND) {
|
||||
printf("Unrecognized command\n");
|
||||
} else if (err == ESP_OK && ret != ESP_OK) {
|
||||
printf("Command returned non-zero error code: 0x%x\n", ret);
|
||||
} else if (err != ESP_OK) {
|
||||
printf("Internal error: %s\n", esp_err_to_name(err));
|
||||
}
|
||||
/* linenoise allocates line buffer on the heap, so need to free it */
|
||||
linenoiseFree(line);
|
||||
}
|
||||
}
|
||||
|
9
examples/wifi/iperf/sdkconfig.defaults
Normal file
9
examples/wifi/iperf/sdkconfig.defaults
Normal file
@ -0,0 +1,9 @@
|
||||
CONFIG_ESP8266_WIFI_AMPDU_RX_ENABLED=y
|
||||
CONFIG_ESP8266_WIFI_RX_BUFFER_NUM=26
|
||||
CONFIG_ESP8266_WIFI_LEFT_CONTINUOUS_RX_BUFFER_NUM=4
|
||||
CONFIG_ESP8266_DEFAULT_CPU_FREQ_160=y
|
||||
CONFIG_SOC_FULL_ICACHE=y
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=11680
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=7300
|
||||
CONFIG_LWIP_TCP_MSS=1460
|
||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=7
|
Reference in New Issue
Block a user