### Introduction ESP-NOW is a wireless communication protocol defined by Espressif that can control smart devices directly, quickly and with low power consumption without a router. It can coexist with Wi-Fi and Bluetooth LE, and supports Espressif's ESP8266, ESP32, ESP32-S and ESP32-C series SoCs. ESP-NOW is widely used in smart home appliances, remote control and sensors. When we use ESP-NOW, we are very interested in its performance indicators, such as bandwidth, latency and so on. When checking the source code of the espnow warehouse, we happened to find the relevant test code, so let's take a look. ### Source code for testing ESP-NOW performance indicators In the ESPNOW repository source code, you can find test-related code (components/esp-now/src/debug/src/commands/cmd_iperf.c) ```c static void espnow_iperf_initiator_task(void *arg) { esp_err_t ret = ESP_OK; espnow_iperf_data_t *iperf_data = ESP_CALLOC(1, g_iperf_cfg.packet_len); iperf_data->type = IPERF_BANDWIDTH; iperf_data->seq = 0; int64_t start_time = esp_timer_get_time(); int64_t end_time = start_time + g_iperf_cfg.transmit_time * 1000 * 1000; uint32_t total_count = 0; if (!g_iperf_cfg.frame_head.broadcast) { espnow_add_peer(g_iperf_cfg.addr, NULL); } ESP_LOGI(TAG, "[ Responder MAC ] Interval Transfer Frame_rate Bandwidth"); for (int64_t report_time = start_time + g_iperf_cfg.report_interval * 1000 * 10 00, report_count = 0; esp_timer_get_time() < end_time && !g_iperf_cfg.finish;) { ret = espnow_send(g_iperf_cfg.type, g_iperf_cfg.addr, iperf_data, g_iperf_cfg.packet_len, &g_iperf_cfg.frame_head, portMAX_DELAY); ESP_ERROR_CONTINUE(ret != ESP_OK && ret != ESP_ERR_WIFI_TIMEOUT, "<%s> espnow_send", esp_err_to_name(ret)); iperf_data->seq++; ++total_count; if (esp_timer_get_time() >= report_time) { uint32_t report_time_s = (report_time - start_time) / (1000 * 1000); double report_size = (iperf_data->seq - report_count) * g_iperf_cfg.packet_len / 1e6; ESP_LOGI(TAG, "["MACSTR"] %2d-%2d sec %2.2f MBytes %0.2f Hz %0.2f Mbps", MAC2STR(g_iperf_ cfg.addr), report_time_s - g_iperf_cfg.report_interval, report_time_s, report_size, (iperf_data->seq - report_count) * 1.0 / g_iperf_cfg.report_interval, report_size * 8 / g_iperf_cfg.report_interval); report_time = esp_timer_get_time() + g_iperf_cfg.report_interval * 1000 * 1000; report_count = iperf_data->seq; } } iperf_data->type = IPERF_BANDWIDTH_STOP; int retry_count = 5; wifi_pkt_rx_ctrl_t rx_ctrl = {0}; uint32_t spend_time_ms = (esp_timer_get_time() - start_time) / 1000; do { ret = espnow_send(g_iperf_cfg.type, g_iperf_cfg.addr, iperf_data, g_iperf_cfg.packet_len, &g_iperf_cfg.frame_head, portMAX_DELAY); ESP_ERROR_CONTINUE(ret != ESP_OK, "<%s> espnow_send", esp_err_to_name(ret)); set(iperf_data, 0, g_iperf_cfg.packet_len); iperf_recv_data_t recv_data = { 0 }; if (g_iperf_queue && xQueueReceive(g_iperf_queue, &recv_data,pdMS_TO_TICKS(1000)) == pdPASS) {
ret = ESP_OK;
memcpy(iperf_data, recv_data.data, recv_data.size);
memcpy(g_iperf_cfg.addr, recv_data.src_addr, 6);
memcpy(&rx_ctrl, &recv_data.rx_ctrl, sizeof(wifi_pkt_rx_ctrl_t));
ESP_FREE(recv_data.data);
} else {
ret = ESP_FAIL;
}
} while (ret != ESP_OK && retry_count-- > 0 && iperf_data->type != IPERF_BANDWIDTH_STOP_ACK);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "<%s> Receive responder response failed", esp_err_to_name(ret));
} else {
uint32_t write_count = iperf_data->seq > 0 ? iperf_data->seq - 1 : 0;
uint32_t lost_count= total_count - write_count;
double total_len = (total_count * g_iperf_cfg.packet_len) / 1e6;
if (total_count && write_count && spend_time_ms) {
ESP_LOGI(TAG, "initiator Report:");
ESP_LOGI(TAG, "[ ID] Interval Transfer Bandwidth Jitter Lost/Total DatagramsRSSIChannel");
ESP_LOGI(TAG, "[000] %2d-%2d sec %2.2f MBytes %0.2f Mbps %0.2f ms %d/%d (%0.2f%%) %d %d",
0, spend_time_ms / 1000, total_len, total_len * 8 * 1000 / spend_time_ms, spend_time_ms * 1.0 / write_count,
lost_count, total_count, lost_count * 100.0 / total_count, rx_ctrl.rssi, rx_ctrl.channel);
}
}
if (!g_iperf_cfg.frame_head.broadcast) {
espnow_del_peer(g_iperf_cfg.addr);
}
ESP_FREE(iperf_data);
g_iperf_cfg.finish = true;
espnow_set_config_for_data_type(ESPNOW_DATA_TYPE_RESERVED, 0, NULL);
if (g_iperf_queue) {
iperf_recv_data_t tmp_data ={ 0 };
while (xQueueReceive(g_iperf_queue, &tmp_data, 0)) {
ESP_FREE(tmp_data.data);
}
vQueueDelete(g_iperf_queue);
g_iperf_queue = NULL;
}
vTaskDelete(NULL);
}
```
这个函数的作用,是发起iperf测试任务,这个任务会向指定的地址发起测试,并打印相关的测试信息,例如传输数据的大小,传输带宽,信号强度等等内容。
-----------------------
光是一个设备跑这段代码也是没用的,我们需要另一个设备跑测试响应任务。测试响应任务也是在同一个文件里。
```c
static esp_err_t espnow_iperf_responder(uint8_t *src_addr, void *data,
size_t size, wifi_pkt_rx_ctrl_t *rx_ctrl)
{
ESP_PARAM_CHECK(src_addr);
ESP_PARAM_CHECK(data);
ESP_PARAM_CHECK(size);
ESP_PARAM_CHECK(rx_ctrl);
esp_err_t ret = ESP_OK;
espnow_iperf_data_t *iperf_data = (espnow_iperf_data_t *)data;
static int64_t start_time;
static uint32_t recv_count;
static int64_t report_time;
static uint32_t report_count;
memcpy(g_iperf_cfg.addr, src_addr, 6);
if (!g_iperf_cfg.finish) {
recv_count++;
if (iperf_data->seq == 0) {
recv_count = 0;
start_time= esp_timer_get_time();
report_time = start_time + g_iperf_cfg.report_interval * 1000 * 1000;
report_count = 0;
}
if (iperf_data->type == IPERF_BANDWIDTH && esp_timer_get_time() >= report_time) {
uint32_t report_time_s = (report_time - start_time) / (1000 * 1000);
double report_size = (recv_count - report_count) * size / 1e6;
ESP_LOGI(TAG, "["MACSTR"]%2d-%2d sec%2.2f MBytes%0.2f Mbps%d dbm",
MAC2STR(g_iperf_cfg.addr), report_time_s - g_iperf_cfg.report_interval, report_time_s,
report_size, report_size * 8 / g_iperf_cfg.report_interval, rx_ctrl->rssi);
report_time = esp_timer_get_time() + g_iperf_cfg.report_interval * 1000 * 1000;
report_count = recv_count;
} else if (iperf_data->type == IPERF_PING) {
ESP_LOGV(TAG, "Recv IPERF_PING, seq: %d, recv_count: %d", iperf_data->seq, recv_count);
iperf_data->type = IPERF_PING_ACK;
if (g_iperf_cfg.gpio_num >= 0) {
gpio_set_level(g_iperf_cfg.gpio_num, 0);
}
if (!g_iperf_cfg.frame_head.broadcast) {
espnow_add_peer(g_iperf_cfg.addr, NULL);
}
ret = espnow_send(g_iperf_cfg.type, g_iperf_cfg.addr, iperf_data,
size, &g_iperf_cfg.frame_head, portMAX_DELAY);
if (!g_iperf_cfg.frame_head.broadcast) {
espnow_del_peer(g_iperf_cfg.addr);
}
if (g_iperf_cfg.gpio_num >= 0) {
gpio_set_level(g_iperf_cfg.gpio_num, 1);
}
ESP_ERROR_RETURN(ret != ESP_OK, ret, "<%s> espnow_send", esp_err_to_name(ret));
} else if (iperf_data->type == IPERF_BANDWIDTH_STOP) {
uint32_t total_count = iperf_data->seq + 1;
uint32_t lost_count= total_count - recv_count;
double total_len = (total_count * size) / 1e6;
uint32_t spend_time_ms= (esp_timer_get_time() - start_time) / 1000;
ESP_LOGI(TAG, "[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams");
ESP_LOGI(TAG, "[000] %2d-%2d sec %2.2f MBytes %0.2f Mbps %0.2f ms %d/%d (%0.2f%%)",
0, spend_time_ms / 1000, total_len, total_len * 8 * 1000 / spend_time_ms, spend_time_ms * 1.0 / recv_count,
lost_count, total_count, lost_count * 100.0 / total_count);
iperf_data->seq = recv_count;
iperf_data->type = IPERF_BANDWIDTH_STOP_ACK;
ESP_LOGD(TAG, "iperf_data->seq: %d",iperf_data->seq);
espnow_frame_head_t frame_head = {
.filter_adjacent_channel = true,
};
espnow_add_peer(g_iperf_cfg.addr, NULL);
ret = espnow_send(g_iperf_cfg.type, g_iperf_cfg.addr, iperf_data,sizeof(espnow_iperf_data_t), &frame_head, portMAX_DELAY); ESP_ERROR_RETURN(ret != ESP_OK, ret, "<%s> espnow_send", esp_err_to_name(ret)); espnow_del_peer(g_iperf_cfg.addr); } } return ESP_OK; } ``` This function is the program that responds to the iperf test. We only need to modify the program according to the calling method on the two devices, and then we can test it. The following are my test results.
It can be seen that in 1 minute, a total of 17888 data packets were sent, 10 packets were lost, the packet loss rate was 0.06%, the signal strength was around -13 (very close), and the bandwidth was 0.51Mbps. Compared with the bandwidth performance of WiFi, the performance of ESPNOW long-distance mode is relatively weak, but considering that ESPNOW does not require a router, multiple devices can send data to each other when the devices are not connected to the Internet, and this speed is also stronger than the performance of the regular serial port 115200. That is not unacceptable. As for the transmission distance, the situation I tested here is roughly as follows: the board with PCB antenna can basically maintain a bandwidth of 0.4Mbps when passing through a wall and within a distance of 20 meters. The performance of ceramic antenna is slightly worse, and can only maintain a bandwidth of 0.3Mbps within 10 meters.