Merge branch 'feature/add_iperf_example' into 'master'

feat(iperf): Add iperf example

See merge request 
This commit is contained in:
Dong Heng
2020-09-18 10:58:11 +08:00
15 changed files with 1864 additions and 0 deletions

@ -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)

@ -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

@ -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.

@ -0,0 +1,3 @@
idf_component_register(SRCS "iperf.c"
INCLUDE_DIRS .
REQUIRES lwip)

@ -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 := .

@ -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;
}

@ -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

@ -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")

@ -0,0 +1,3 @@
idf_component_register(SRCS "cmd_wifi.c"
"iperf_example_main.c"
INCLUDE_DIRS ".")

@ -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

@ -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) );
}

@ -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,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);
}
}

@ -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