feat: Initial esp32-hacking project with firmware sources and docs

This commit is contained in:
user
2026-02-04 12:59:28 +01:00
commit 298e98befb
120 changed files with 22094 additions and 0 deletions

78
get-started/README.md Normal file
View File

@@ -0,0 +1,78 @@
# Get Started Examples
[[中文]](./README_cn.md)
This example demonstrates how to obtain CSI data through communication between two espressif chips, and uses a graphical interface to display real-time data of CSI subcarriers
## hardware
You need to prepare two development boards for espressif chips, one as the sender and the other as the receiver
![example_display](./docs/_static/example_display.png)
1. Use ESP32-C5 / ESP32-C6: ESP32-C5 supports dual-band Wi-Fi communication and is one of the best RF chips available. ESP32-C6 is the best RF chip among the currently released models.
2. Use an external antenna: The PCB antenna has poor directivity and is easily interfered with by the motherboard.
3. The distance between the two devices should be greater than 1 meter.
## Binding
1. Burn the firmware of `csi_recv` and `csi_send` on two development boards respectively
![device_log](./docs/_static/device_log.png)
```shell
# csi_send
cd esp-csi/examples/get-started/csi_send
idf.py set-target esp32c3
idf.py flash -b 921600 -p /dev/ttyUSB0 monitor
# csi_recv
cd esp-csi/examples/get-started/csi_recv
idf.py set-target esp32c3
idf.py flash -b 921600 -p /dev/ttyUSB1
```
2. Run `csi_data_read_parse.py` in `csi_recv` for data analysis. Please close `idf.py monitor` before running
```shell
cd esp-csi-gitlab/examples/get-started/tools
# Install python related dependencies
pip install -r requirements.txt
# Graphical display
python csi_data_read_parse.py -p /dev/ttyUSB1
```
## CSI Data Format
Taking a line of CSI raw data as an example:
> type,id,mac,rssi,rate,sig_mode,mcs,bandwidth,smoothing,not_sounding,aggregation,stbc,fec_coding,sgi,noise_floor,ampdu_cnt,channel,secondary_channel,local_timestamp,ant,sig_len,rx_state,len,first_word,data
CSI_DATA,0,94:d9:b3:80:8c:81,-30,11,1,6,1,0,1,0,1,0,0,-93,0,13,2,2751923,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,5,0,20,1,20,1,19,0,17,1,16,2,15,2,14,1,12,0,12,-1,12,-3,12,-4,13,-6,15,-7,16,-8,16,-8,16,-8,16,-6,15,-5,15,-4,14,-4,13,-4,12,-4,11,-4,10,-4,9,-5,8,-6,4,-4,8,-9,9,-10,9,-10,10,-11,11,-10,11,-10,12,-9,11,-8,11,-7,10,-6,9,-6,7,-6,6,-7,5,-7,5,-8,5,-9,5,-10,5,-11,5,-11,6,-11,7,-11,8,-11,9,-10,9,-9,8,-8,8,-7,1,-2,0,0,0,0,0,0,0,0]"
*ESP32-C5 and ESP32-C6 are:*
>type,seq,mac,rssi,rate,noise_floor,fft_gain,agc_gain,channel,local_timestamp,sig_len,rx_state,len,first_word,data
CSI_DATA,7,1a:00:00:00:00:00,-23,11,-96,32,4,11,372852,47,0,256,0,"[0,0,0,0,0,0,0,0,0,0,0,0,-6,-13,-6,-14,-3,-15,-2,-16,-2,-18,-3,-17,-1,-18,2,-19,0,-21,3,-21,1,-20,4,-21,4,-23,6,-22,7,-21,8,-23,9,-23,10,-21,10,-22,11,-20,12,-19,11,-25,13,-22,12,-23,14,-23,14,-22,14,-21,13,-21,13,-19,14,-22,14,-19,14,-23,16,-22,14,-22,13,-22,14,-21,13,-22,13,-23,13,-23,12,-26,13,-24,13,-24,12,-25,14,-29,13,-26,14,-26,15,-26,13,-27,15,-28,16,-27,14,-30,15,-28,16,-28,18,-27,16,-31,18,-31,19,-31,0,0,0,0,0,0,-29,-23,-28,-21,-30,-18,-26,-20,-30,-21,-25,-23,-26,-21,-25,-22,-26,-19,-22,-22,-24,-19,-22,-20,-24,-20,-24,-18,-23,-18,-22,-18,-25,-17,-23,-18,-21,-18,-21,-17,-24,-14,-22,-16,-21,-14,-22,-15,-21,-19,-23,-16,-22,-17,-23,-13,-23,-16,-25,-15,-21,-17,-22,-15,-21,-17,-23,-16,-20,-16,-21,-20,-21,-19,-21,-19,-19,-20,-17,-20,-18,-20,-16,-21,-15,-21,-15,-20,-13,-21,-11,-20,-10,-20,-11,-19,-9,-20,-8,-22,-6,-19,-7,-20,-4,-19,-2,-18,-2,-18,1,-17,4,-18,0,0,0,0,0,0,0,0,0,0]"
- **Metadata Fields**: Including type, id, mac, rssi, rate, ... len, first_word, etc.
- **CSI Data**: Stored in the last item data array, enclosed in [...]. It contains the channel state information for each subcarrier. For detailed structure, refer to the Long Training Field (LTF) section of the [ESP-WIFI-CSI Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-channel-state-information). For each subcarrier, the imaginary part is stored first, followed by the real part (i.e., [Imaginary part of subcarrier 1, Real part of subcarrier 1, Imaginary part of subcarrier 2, Real part of subcarrier 2, Imaginary part of subcarrier 3, Real part of subcarrier 3, ...]).
The order of LTF is: LLTF, HT-LTF, STBC-HT-LTF. Depending on the channel and grouping information, not all 3 LTFs may appear.
## A&Q
### 1. `csi_send` prints no memory
- **Phenomenon**: The following log appears on the serial port:
```shell
W (510693) csi_send: <ESP_ERR_ESPNOW_NO_MEM> ESP-NOW send error
````
- **Reason**: The current channel is congested causing congestion in sending packets, so that the ESP-NOW buffer space is full
- **Solution**: Change the Wi-Fi channel or change to a place with a better network environment
### 2. `csi_data_read_parse.py` Serial port printing exception
- **Phenomenon**: The following log appears on the serial port:
```shell
element number is not equal
data is not incomplete
````
- **Reason**: PYQT takes up a lot of CPU when drawing, causing the PC to be unable to read the serial port buffer queue in time, resulting in data confusion
- **Solution**: Advance the baud rate of the serial port

83
get-started/README_cn.md Normal file
View File

@@ -0,0 +1,83 @@
# Get Started Examples
[[English]](./README.md)
本示例演示了如何通过两个 ESP32 芯片之间的通信获取 CSI 数据,并使用图形界面显示 CSI 子载波的实时数据。
## 硬件
您需要准备两块开发板,一块作为发送方,一块作为接收方。
![example_display](./docs/_static/example_display.png)
为了保证 CSI 的感知效果,请尽量满足以下要求:
1. 使用 ESP32-C5 / ESP32-C6ESP32-C5 支持双频WIFI通讯是目前最好的射频芯片ESP32-C6是目前已上市芯片中最好的射频芯片。
2. 使用外部天线PCB 天线的方向性较差,容易受到主板干扰。
3. 两个设备之间的距离应大于 1 米。
## 绑定
1. 分别烧录 `csi_recv` and `csi_send` 到两块 ESP32 上。
![device_log](./docs/_static/device_log.png)
```shell
# csi_send
cd esp-csi/examples/get-started/csi_send
idf.py set-target esp32c3
idf.py flash -b 921600 -p /dev/ttyUSB0 monitor
# csi_recv
cd esp-csi/examples/get-started/csi_recv
idf.py set-target esp32c3
idf.py flash -b 921600 -p /dev/ttyUSB1
```
2. 在 `csi_recv` 里运行 `csi_data_read_parse.py` 做数据分析。 在运行前请关闭 `idf.py monitor` 。
```shell
cd esp-csi-gitlab/examples/get-started/tools
# Install python related dependencies
pip install -r requirements.txt
# Graphical display
python csi_data_read_parse.py -p /dev/ttyUSB1
```
## CSI 数据格式
以一行 CSI raw data 为例:
> type,id,mac,rssi,rate,sig_mode,mcs,bandwidth,smoothing,not_sounding,aggregation,stbc,fec_coding,sgi,noise_floor,ampdu_cnt,channel,secondary_channel,local_timestamp,ant,sig_len,rx_state,len,first_word,data
CSI_DATA,0,94:d9:b3:80:8c:81,-30,11,1,6,1,0,1,0,1,0,0,-93,0,13,2,2751923,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,5,0,20,1,20,1,19,0,17,1,16,2,15,2,14,1,12,0,12,-1,12,-3,12,-4,13,-6,15,-7,16,-8,16,-8,16,-8,16,-6,15,-5,15,-4,14,-4,13,-4,12,-4,11,-4,10,-4,9,-5,8,-6,4,-4,8,-9,9,-10,9,-10,10,-11,11,-10,11,-10,12,-9,11,-8,11,-7,10,-6,9,-6,7,-6,6,-7,5,-7,5,-8,5,-9,5,-10,5,-11,5,-11,6,-11,7,-11,8,-11,9,-10,9,-9,8,-8,8,-7,1,-2,0,0,0,0,0,0,0,0]"
*esp32-c5 和 esp32-c6 为:*
>type,seq,mac,rssi,rate,noise_floor,fft_gain,agc_gain,channel,local_timestamp,sig_len,rx_state,len,first_word,data
CSI_DATA,7,1a:00:00:00:00:00,-23,11,-96,32,4,11,372852,47,0,256,0,"[0,0,0,0,0,0,0,0,0,0,0,0,-6,-13,-6,-14,-3,-15,-2,-16,-2,-18,-3,-17,-1,-18,2,-19,0,-21,3,-21,1,-20,4,-21,4,-23,6,-22,7,-21,8,-23,9,-23,10,-21,10,-22,11,-20,12,-19,11,-25,13,-22,12,-23,14,-23,14,-22,14,-21,13,-21,13,-19,14,-22,14,-19,14,-23,16,-22,14,-22,13,-22,14,-21,13,-22,13,-23,13,-23,12,-26,13,-24,13,-24,12,-25,14,-29,13,-26,14,-26,15,-26,13,-27,15,-28,16,-27,14,-30,15,-28,16,-28,18,-27,16,-31,18,-31,19,-31,0,0,0,0,0,0,-29,-23,-28,-21,-30,-18,-26,-20,-30,-21,-25,-23,-26,-21,-25,-22,-26,-19,-22,-22,-24,-19,-22,-20,-24,-20,-24,-18,-23,-18,-22,-18,-25,-17,-23,-18,-21,-18,-21,-17,-24,-14,-22,-16,-21,-14,-22,-15,-21,-19,-23,-16,-22,-17,-23,-13,-23,-16,-25,-15,-21,-17,-22,-15,-21,-17,-23,-16,-20,-16,-21,-20,-21,-19,-21,-19,-19,-20,-17,-20,-18,-20,-16,-21,-15,-21,-15,-20,-13,-21,-11,-20,-10,-20,-11,-19,-9,-20,-8,-22,-6,-19,-7,-20,-4,-19,-2,-18,-2,-18,1,-17,4,-18,0,0,0,0,0,0,0,0,0,0]"
- **元数据字段**:包括 type, id, mac, ... first_word等。
- **CSI 数据**:由最后一项 data 数组存储,用 [...] 框起。包含每个子载波的信道状态信息。具体结构可参考 [ESP-WIFI-CSI Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-channel-state-information) 的 Long Training Field (LTF) 部分。对于每一个子载波,先储存虚数部分,再储存实数部分(即:[子载波1的虚部子载波1的实部子载波2的虚部子载波2的实部子载波3的虚部子载波3的实部...])。
LTF的顺序为LLTF、HT-LTF、STBC-HT-LTF。根据通道和分组信息可能不会出现所有3个LTF。
he order of LTF is: LLTF, HT-LTF, STBC-HT-LTF. Depending on the channel and grouping information, not all 3 LTFs may appear.
## A&Q
### 1. csi_send 打印无内存
- **现象**:串口上出现以下日志:
```shell
W (510693) csi_send: ESP-NOW 发送错误
```
- **原因**当前信道拥堵导致发送数据包时出现拥堵ESP-NOW 缓冲区空间满
- **解决方案**:更换 Wi-Fi 信道或更换到网络环境更好的地方
### 2. csi_data_read_parse.py 串口打印异常
- **现象**:串口上出现以下日志:
```shell
element number is not equal
data is not incomplete
```
- **原因**:绘图时 PYQT 占用大量 CPU导致 PC 无法及时读取串口缓冲队列,造成数据混乱
- **解决方案**:提高串口的波特率

View File

@@ -0,0 +1,15 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctlycmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.5)
add_compile_options(-fdiagnostics-color=always)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
message("EXTRA_COMPONENT_DIRS: " ${EXTRA_COMPONENT_DIRS})
string(REGEX REPLACE ".*/\(.*\)" "\\1" CURDIR ${CMAKE_CURRENT_SOURCE_DIR})
project(${CURDIR})
git_describe(PROJECT_VERSION ${COMPONENT_DIR})
message("Project commit: " ${PROJECT_VERSION})

View File

@@ -0,0 +1 @@
# CSI_RECV

View File

@@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,297 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/* Get Start 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "nvs_flash.h"
#include "esp_mac.h"
#include "rom/ets_sys.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_now.h"
#include "esp_csi_gain_ctrl.h"
#define CONFIG_LESS_INTERFERENCE_CHANNEL 11
#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61 || (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0))
#define CONFIG_WIFI_BAND_MODE WIFI_BAND_MODE_2G_ONLY
#define CONFIG_WIFI_2G_BANDWIDTHS WIFI_BW_HT40
#define CONFIG_WIFI_5G_BANDWIDTHS WIFI_BW_HT40
#define CONFIG_WIFI_2G_PROTOCOL WIFI_PROTOCOL_11N
#define CONFIG_WIFI_5G_PROTOCOL WIFI_PROTOCOL_11N
#else
#define CONFIG_WIFI_BANDWIDTH WIFI_BW_HT40
#endif
#define CONFIG_ESP_NOW_PHYMODE WIFI_PHY_MODE_HT40
#define CONFIG_ESP_NOW_RATE WIFI_PHY_RATE_MCS0_LGI
#define CONFIG_FORCE_GAIN 0
#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61
#define CSI_FORCE_LLTF 0
#endif
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61
#define CONFIG_GAIN_CONTROL 1
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
#define ESP_IF_WIFI_STA ESP_MAC_WIFI_STA
#endif
static const uint8_t CONFIG_CSI_SEND_MAC[] = {0x1a, 0x00, 0x00, 0x00, 0x00, 0x00};
static const char *TAG = "csi_recv";
static void wifi_init()
{
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_netif_init());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
#if CONFIG_IDF_TARGET_ESP32C5
ESP_ERROR_CHECK(esp_wifi_start());
esp_wifi_set_band_mode(CONFIG_WIFI_BAND_MODE);
wifi_protocols_t protocols = {
.ghz_2g = CONFIG_WIFI_2G_PROTOCOL,
.ghz_5g = CONFIG_WIFI_5G_PROTOCOL
};
ESP_ERROR_CHECK(esp_wifi_set_protocols(ESP_IF_WIFI_STA, &protocols));
wifi_bandwidths_t bandwidth = {
.ghz_2g = CONFIG_WIFI_2G_BANDWIDTHS,
.ghz_5g = CONFIG_WIFI_5G_BANDWIDTHS
};
ESP_ERROR_CHECK(esp_wifi_set_bandwidths(ESP_IF_WIFI_STA, &bandwidth));
#elif (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) || CONFIG_IDF_TARGET_ESP32C61
ESP_ERROR_CHECK(esp_wifi_start());
esp_wifi_set_band_mode(CONFIG_WIFI_BAND_MODE);
wifi_protocols_t protocols = {
.ghz_2g = CONFIG_WIFI_2G_PROTOCOL,
};
ESP_ERROR_CHECK(esp_wifi_set_protocols(ESP_IF_WIFI_STA, &protocols));
wifi_bandwidths_t bandwidth = {
.ghz_2g = CONFIG_WIFI_2G_BANDWIDTHS,
};
ESP_ERROR_CHECK(esp_wifi_set_bandwidths(ESP_IF_WIFI_STA, &bandwidth));
#else
ESP_ERROR_CHECK(esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, CONFIG_WIFI_BANDWIDTH));
ESP_ERROR_CHECK(esp_wifi_start());
#endif
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
#if CONFIG_IDF_TARGET_ESP32C5
if ((CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_2G_ONLY && CONFIG_WIFI_2G_BANDWIDTHS == WIFI_BW_HT20)
|| (CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_5G_ONLY && CONFIG_WIFI_5G_BANDWIDTHS == WIFI_BW_HT20)) {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE));
} else {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW));
}
#elif (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) || CONFIG_IDF_TARGET_ESP32C61
if (CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_2G_ONLY && CONFIG_WIFI_2G_BANDWIDTHS == WIFI_BW_HT20) {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE));
} else {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW));
}
#else
if (CONFIG_WIFI_BANDWIDTH == WIFI_BW_HT20) {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE));
} else {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW));
}
#endif
ESP_ERROR_CHECK(esp_wifi_set_mac(WIFI_IF_STA, CONFIG_CSI_SEND_MAC));
}
static void wifi_esp_now_init(esp_now_peer_info_t peer)
{
ESP_ERROR_CHECK(esp_now_init());
ESP_ERROR_CHECK(esp_now_set_pmk((uint8_t *)"pmk1234567890123"));
esp_now_rate_config_t rate_config = {
.phymode = CONFIG_ESP_NOW_PHYMODE,
.rate = CONFIG_ESP_NOW_RATE,// WIFI_PHY_RATE_MCS0_LGI,
.ersu = false,
.dcm = false
};
ESP_ERROR_CHECK(esp_now_add_peer(&peer));
ESP_ERROR_CHECK(esp_now_set_peer_rate_config(peer.peer_addr, &rate_config));
}
static void wifi_csi_rx_cb(void *ctx, wifi_csi_info_t *info)
{
if (!info || !info->buf) {
ESP_LOGW(TAG, "<%s> wifi_csi_cb", esp_err_to_name(ESP_ERR_INVALID_ARG));
return;
}
if (memcmp(info->mac, CONFIG_CSI_SEND_MAC, 6)) {
return;
}
const wifi_pkt_rx_ctrl_t *rx_ctrl = &info->rx_ctrl;
static int s_count = 0;
float compensate_gain = 1.0f;
static uint8_t agc_gain = 0;
static int8_t fft_gain = 0;
#if CONFIG_GAIN_CONTROL
static uint8_t agc_gain_baseline = 0;
static int8_t fft_gain_baseline = 0;
esp_csi_gain_ctrl_get_rx_gain(rx_ctrl, &agc_gain, &fft_gain);
if (s_count < 100) {
esp_csi_gain_ctrl_record_rx_gain(agc_gain, fft_gain);
} else if (s_count == 100) {
esp_csi_gain_ctrl_get_rx_gain_baseline(&agc_gain_baseline, &fft_gain_baseline);
#if CONFIG_FORCE_GAIN
esp_csi_gain_ctrl_set_rx_force_gain(agc_gain_baseline, fft_gain_baseline);
ESP_LOGD(TAG, "fft_force %d, agc_force %d", fft_gain_baseline, agc_gain_baseline);
#endif
}
esp_csi_gain_ctrl_get_gain_compensation(&compensate_gain, agc_gain, fft_gain);
ESP_LOGI(TAG, "compensate_gain %f, agc_gain %d, fft_gain %d", compensate_gain, agc_gain, fft_gain);
#endif
uint32_t rx_id = *(uint32_t *)(info->payload + 15);
#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61
if (!s_count) {
ESP_LOGI(TAG, "================ CSI RECV ================");
ets_printf("type,seq,mac,rssi,rate,noise_floor,fft_gain,agc_gain,channel,local_timestamp,sig_len,rx_state,len,first_word,data\n");
}
ets_printf("CSI_DATA,%d," MACSTR ",%d,%d,%d,%d,%d,%d,%d,%d,%d",
rx_id, MAC2STR(info->mac), rx_ctrl->rssi, rx_ctrl->rate,
rx_ctrl->noise_floor, fft_gain, agc_gain, rx_ctrl->channel,
rx_ctrl->timestamp, rx_ctrl->sig_len, rx_ctrl->rx_state);
#else
if (!s_count) {
ESP_LOGI(TAG, "================ CSI RECV ================");
ets_printf("type,id,mac,rssi,rate,sig_mode,mcs,bandwidth,smoothing,not_sounding,aggregation,stbc,fec_coding,sgi,noise_floor,ampdu_cnt,channel,secondary_channel,local_timestamp,ant,sig_len,rx_state,len,first_word,data\n");
}
ets_printf("CSI_DATA,%d," MACSTR ",%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
rx_id, MAC2STR(info->mac), rx_ctrl->rssi, rx_ctrl->rate, rx_ctrl->sig_mode,
rx_ctrl->mcs, rx_ctrl->cwb, rx_ctrl->smoothing, rx_ctrl->not_sounding,
rx_ctrl->aggregation, rx_ctrl->stbc, rx_ctrl->fec_coding, rx_ctrl->sgi,
rx_ctrl->noise_floor, rx_ctrl->ampdu_cnt, rx_ctrl->channel, rx_ctrl->secondary_channel,
rx_ctrl->timestamp, rx_ctrl->ant, rx_ctrl->sig_len, rx_ctrl->rx_state);
#endif
#if (CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61) && CSI_FORCE_LLTF
int16_t csi = ((int16_t)(((((uint16_t)info->buf[1]) << 8) | info->buf[0]) << 4) >> 4);
ets_printf(",%d,%d,\"[%d", (info->len - 2) / 2, info->first_word_invalid, (int16_t)(compensate_gain * csi));
for (int i = 2; i < (info->len - 2); i += 2) {
csi = ((int16_t)(((((uint16_t)info->buf[i + 1]) << 8) | info->buf[i]) << 4) >> 4);
ets_printf(",%d", (int16_t)(compensate_gain * csi));
}
#else
ets_printf(",%d,%d,\"[%d", info->len, info->first_word_invalid, (int16_t)(compensate_gain * info->buf[0]));
for (int i = 1; i < info->len; i++) {
ets_printf(",%d", (int16_t)(compensate_gain * info->buf[i]));
}
#endif
ets_printf("]\"\n");
s_count++;
}
static void wifi_csi_init()
{
ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true));
/**< default config */
#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61
wifi_csi_config_t csi_config = {
.enable = true,
.acquire_csi_legacy = false,
.acquire_csi_force_lltf = CSI_FORCE_LLTF,
.acquire_csi_ht20 = true,
.acquire_csi_ht40 = true,
.acquire_csi_vht = false,
.acquire_csi_su = false,
.acquire_csi_mu = false,
.acquire_csi_dcm = false,
.acquire_csi_beamformed = false,
.acquire_csi_he_stbc_mode = 2,
.val_scale_cfg = 0,
.dump_ack_en = false,
.reserved = false
};
#elif CONFIG_IDF_TARGET_ESP32C6
wifi_csi_config_t csi_config = {
.enable = true,
.acquire_csi_legacy = false,
.acquire_csi_ht20 = true,
.acquire_csi_ht40 = true,
.acquire_csi_su = true,
.acquire_csi_mu = true,
.acquire_csi_dcm = true,
.acquire_csi_beamformed = true,
.acquire_csi_he_stbc = 2,
.val_scale_cfg = false,
.dump_ack_en = false,
.reserved = false
};
#else
wifi_csi_config_t csi_config = {
.lltf_en = true,
.htltf_en = true,
.stbc_htltf2_en = true,
.ltf_merge_en = true,
.channel_filter_en = true,
.manu_scale = false,
.shift = false,
};
#endif
ESP_ERROR_CHECK(esp_wifi_set_csi_config(&csi_config));
ESP_ERROR_CHECK(esp_wifi_set_csi_rx_cb(wifi_csi_rx_cb, NULL));
ESP_ERROR_CHECK(esp_wifi_set_csi(true));
}
void app_main()
{
/**
* @brief Initialize NVS
*/
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
/**
* @brief Initialize Wi-Fi
*/
wifi_init();
/**
* @brief Initialize ESP-NOW
* ESP-NOW protocol see: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html
*/
esp_now_peer_info_t peer = {
.channel = CONFIG_LESS_INTERFERENCE_CHANNEL,
.ifidx = WIFI_IF_STA,
.encrypt = false,
.peer_addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
};
wifi_esp_now_init(peer);
wifi_csi_init();
}

View File

@@ -0,0 +1,4 @@
## IDF Component Manager Manifest File
dependencies:
idf: ">=4.4.1"
esp_csi_gain_ctrl: ">=0.1.4"

View File

@@ -0,0 +1,10 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration
#
CONFIG_PARTITION_TABLE_OFFSET=0xa000
CONFIG_ESP_CONSOLE_UART_CUSTOM=y
CONFIG_ESP_CONSOLE_UART_BAUDRATE=921600
CONFIG_ESP_TASK_WDT_TIMEOUT_S=30
CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=128
CONFIG_ESP_WIFI_CSI_ENABLED=y
CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n

View File

@@ -0,0 +1,19 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctlycmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.5)
add_compile_options(-fdiagnostics-color=always)
# (Not part of the boilerplate)
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
message("EXTRA_COMPONENT_DIRS: " ${EXTRA_COMPONENT_DIRS})
string(REGEX REPLACE ".*/\(.*\)" "\\1" CURDIR ${CMAKE_CURRENT_SOURCE_DIR})
project(${CURDIR})
git_describe(PROJECT_VERSION ${COMPONENT_DIR})
message("Project commit: " ${PROJECT_VERSION})

View File

@@ -0,0 +1,119 @@
# Recv router CSI example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
The device triggers the router to send packets through the Ping command to obtain the CSI data between the device and the router
## How to use example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
### Hardware Required
* A development board with ESP32/ESP32-S2/ESP32-C3/ESP32-S3/ESP32-C5/ESP32-C6/ESP32-C61 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for power supply and programming
### Configure the project
```
idf.py menuconfig
```
Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](https://github.com/espressif/esp-idf/tree/master/examples/protocols#establishing-wi-fi-or-ethernet-connection) for more details.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```shell
I (0) cpu_start: App cpu up.
I (288) cpu_start: Pro cpu start user code
I (288) cpu_start: cpu freq: 240000000
I (288) cpu_start: Application information:
I (289) cpu_start: Project name: csi_recv_router
I (289) cpu_start: App version: e1dd673-dirty
I (290) cpu_start: Compile time: Jul 14 2022 18:55:08
I (291) cpu_start: ELF file SHA256: 81680e58e5d0e53c...
I (292) cpu_start: ESP-IDF: v4.4.1-dirty
I (293) heap_init: Initializing. RAM available for dynamic allocation:
I (293) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (294) heap_init: At 3FFB7310 len 00028CF0 (163 KiB): DRAM
I (295) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (296) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (296) heap_init: At 40093AA4 len 0000C55C (49 KiB): IRAM
I (298) spi_flash: detected chip: generic
I (299) spi_flash: flash io: dio
W (299) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
I (301) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (331) wifi:wifi driver task: 3ffbfa74, prio:23, stack:6656, core=0
I (332) system_api: Base MAC address is not set
I (332) system_api: read default base MAC address from EFUSE
I (335) wifi:wifi firmware version: 63017e0
I (335) wifi:wifi certification version: v7.0
I (335) wifi:config NVS flash: enabled
I (335) wifi:config nano formatting: disabled
I (336) wifi:Init data frame dynamic rx buffer num: 128
I (336) wifi:Init management frame dynamic rx buffer num: 128
I (337) wifi:Init management short buffer num: 32
I (337) wifi:Init dynamic tx buffer num: 32
I (338) wifi:Init static rx buffer size: 2212
I (338) wifi:Init static rx buffer num: 10
I (339) wifi:Init dynamic rx buffer num: 128
I (340) wifi_init: tcpip mbox: 32
I (340) wifi_init: udp mbox: 6
I (340) wifi_init: tcp mbox: 6
I (341) wifi_init: tcp tx win: 5744
I (341) wifi_init: tcp rx win: 5744
I (342) wifi_init: tcp mss: 1440
I (342) wifi_init: WiFi IRAM OP enabled
I (343) wifi_init: WiFi RX IRAM OP enabled
I (344) example_connect: Connecting to TP-LINK_8C81...
I (345) phy_init: phy_version 4670,719f9f6,Feb 18 2021,17:07:07
I (433) wifi:mode : sta (ac:67:b2:53:78:d0)
I (434) wifi:enable tsf
I (437) example_connect: Waiting for IP(s)
I (2483) wifi:new:<13,2>, old:<1,0>, ap:<255,255>, sta:<13,2>, prof:1
I (2485) wifi:state: init -> auth (b0)
I (2503) wifi:state: auth -> assoc (0)
I (2509) wifi:state: assoc -> run (10)
I (2510) wifi:connected with TP-LINK_8C81, aid = 1, channel 13, 40D, bssid = 94:d9:b3:80:8c:81
I (2511) wifi:security: Open Auth, phy: bgn, rssi: -28
I (2511) wifi:pm start, type: 1
I (2535) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (3331) esp_netif_handlers: example_connect: sta ip: 192.168.0.107, mask: 255.255.255.0, gw: 192.168.0.1
I (3332) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.0.107
I (4330) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:ae67:b2ff:fe53:78d0, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (4332) example_connect: Connected to example_connect: sta
I (4332) example_connect: - IPv4 address: 192.168.0.107
I (4333) example_connect: - IPv6 address: fe80:0000:0000:0000:ae67:b2ff:fe53:78d0, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (4335) csi_recv_router: got ip:192.168.0.107, gw: 192.168.0.1
I (4346) csi_recv_router: ================ CSI RECV ================
type,seq,mac,rssi,rate,sig_mode,mcs,bandwidth,smoothing,not_sounding,aggregation,stbc,fec_coding,sgi,noise_floor,ampdu_cnt,channel,secondary_channel,local_timestamp,ant,sig_len,rx_state,len,first_word,data
CSI_DATA,0,94:d9:b3:80:8c:81,-30,11,1,6,1,0,1,0,1,0,0,-93,0,13,2,2751923,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,5,0,20,1,20,1,19,0,17,1,16,2,15,2,14,1,12,0,12,-1,12,-3,12,-4,13,-6,15,-7,16,-8,16,-8,16,-8,16,-6,15,-5,15,-4,14,-4,13,-4,12,-4,11,-4,10,-4,9,-5,8,-6,4,-4,8,-9,9,-10,9,-10,10,-11,11,-10,11,-10,12,-9,11,-8,11,-7,10,-6,9,-6,7,-6,6,-7,5,-7,5,-8,5,-9,5,-10,5,-11,5,-11,6,-11,7,-11,8,-11,9,-10,9,-9,8,-8,8,-7,1,-2,0,0,0,0,0,0,0,0]"
CSI_DATA,1,94:d9:b3:80:8c:81,-31,11,1,6,1,0,1,0,1,0,0,-93,0,13,2,2794394,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,5,-2,21,-7,20,-7,20,-6,19,-6,17,-5,15,-5,13,-5,11,-6,11,-7,10,-9,9,-10,10,-11,11,-13,12,-14,13,-14,13,-13,13,-12,13,-12,13,-11,13,-9,12,-8,10,-8,9,-8,8,-8,7,-9,6,-10,2,-6,5,-13,5,-13,5,-14,6,-14,7,-14,8,-14,8,-13,8,-12,7,-11,7,-11,6,-10,5,-10,4,-9,3,-10,2,-10,1,-11,1,-11,1,-12,1,-13,2,-14,3,-14,4,-14,5,-13,5,-12,5,-11,4,-10,1,-3,0,0,0,0,0,0,0,0]"
CSI_DATA,2,94:d9:b3:80:8c:81,-30,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2801990,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,-4,-7,-16,-27,-15,-27,-13,-27,-12,-25,-10,-24,-10,-21,-10,-19,-11,-16,-12,-14,-14,-13,-16,-12,-18,-12,-20,-12,-21,-13,-22,-14,-22,-15,-21,-16,-20,-16,-19,-16,-17,-16,-16,-14,-15,-13,-14,-11,-14,-9,-15,-7,-15,-6,-9,-2,-19,-3,-19,-3,-21,-3,-22,-3,-22,-4,-22,-5,-21,-6,-20,-6,-19,-7,-17,-6,-16,-5,-15,-4,-14,-2,-14,-1,-14,1,-15,2,-16,3,-18,4,-19,4,-20,3,-21,2,-21,1,-20,0,-19,-1,-18,-3,-16,-3,-4,-1,0,0,0,0,0,0,0,0]"
CSI_DATA,3,94:d9:b3:80:8c:81,-31,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2810526,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,-6,-1,-22,-3,-22,-4,-21,-5,-20,-5,-18,-5,-17,-4,-15,-3,-14,-2,-14,0,-14,2,-14,3,-15,4,-16,5,-17,5,-18,5,-18,4,-18,3,-18,3,-18,2,-17,2,-15,2,-14,2,-13,2,-12,3,-11,4,-10,5,-5,3,-10,8,-11,8,-11,9,-12,9,-13,9,-13,9,-13,8,-13,7,-12,6,-11,5,-10,5,-9,5,-8,6,-7,7,-7,7,-6,8,-7,9,-7,10,-7,10,-8,10,-9,10,-10,10,-10,9,-10,8,-9,7,-9,6,-2,1,0,0,0,0,0,0,0,0]"
CSI_DATA,4,94:d9:b3:80:8c:81,-31,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2835649,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,-5,3,-19,15,-19,14,-19,13,-19,11,-17,10,-16,10,-14,9,-12,9,-10,10,-8,12,-7,13,-7,15,-7,16,-8,17,-9,18,-10,18,-10,17,-10,17,-11,15,-11,14,-10,13,-9,12,-7,11,-6,11,-4,11,-3,11,-1,6,-1,14,-1,14,-1,15,-1,16,-2,17,-3,17,-3,16,-4,15,-4,14,-3,13,-3,12,-2,11,-1,10,0,10,2,10,3,10,4,11,4,12,4,13,3,14,2,14,1,15,0,14,0,13,-1,12,-1,11,-1,2,0,0,0,0,0,0,0,0]"
CSI_DATA,5,94:d9:b3:80:8c:81,-31,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2848462,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,-1,5,-2,21,-3,21,-4,20,-4,18,-4,17,-3,16,-2,14,-1,13,1,12,2,12,4,13,5,13,6,14,6,15,6,16,6,17,5,17,4,17,3,16,3,15,2,14,3,12,3,11,4,10,5,9,6,8,3,4,9,9,9,9,10,9,10,10,10,11,9,11,9,11,8,11,7,11,7,10,6,9,6,7,6,6,7,6,8,5,9,5,9,4,10,4,11,5,11,6,11,7,11,7,10,8,9,8,8,8,7,7,1,1,0,0,0,0,0,0,0,0]"
CSI_DATA,6,94:d9:b3:80:8c:81,-31,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2860318,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,-2,-6,-7,-22,-6,-21,-5,-21,-4,-20,-3,-19,-3,-17,-4,-15,-5,-13,-6,-12,-8,-12,-9,-12,-10,-12,-12,-13,-12,-13,-12,-14,-12,-15,-11,-15,-11,-15,-10,-15,-9,-14,-8,-13,-8,-12,-8,-11,-8,-9,-9,-8,-10,-7,-6,-4,-12,-6,-13,-6,-13,-7,-14,-8,-14,-8,-13,-9,-13,-9,-12,-9,-11,-9,-10,-8,-10,-7,-9,-6,-9,-5,-9,-4,-10,-3,-11,-3,-11,-2,-12,-2,-13,-3,-14,-3,-14,-4,-13,-5,-13,-5,-12,-6,-11,-6,-10,-6,-3,-2,0,0,0,0,0,0,0,0]"
CSI_DATA,7,94:d9:b3:80:8c:81,-30,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2867650,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,6,3,25,15,25,15,24,16,22,15,20,14,18,13,16,11,16,8,16,6,17,4,19,2,20,1,22,1,24,1,25,1,25,2,25,3,24,4,23,4,21,5,19,4,18,4,17,2,16,1,15,-1,15,-3,7,-3,17,-6,17,-7,18,-8,19,-7,20,-7,20,-6,20,-5,20,-4,19,-3,17,-3,16,-3,14,-4,13,-5,12,-6,12,-8,12,-9,12,-11,13,-11,14,-12,15,-11,17,-11,17,-10,18,-9,17,-7,16,-6,14,-5,3,-2,0,0,0,0,0,0,0,0]"
CSI_DATA,8,94:d9:b3:80:8c:81,-31,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2874562,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,-6,0,-22,1,-22,0,-21,-1,-20,-2,-18,-2,-17,-1,-15,-1,-14,1,-14,2,-13,4,-13,5,-14,6,-15,7,-16,8,-17,8,-17,7,-17,6,-17,5,-17,5,-16,4,-15,4,-13,4,-12,4,-11,4,-10,5,-9,6,-5,4,-9,9,-10,10,-10,10,-11,10,-12,10,-12,10,-12,9,-12,8,-11,7,-11,6,-10,6,-9,6,-8,7,-7,7,-6,8,-6,8,-6,9,-6,10,-6,11,-7,11,-8,11,-9,10,-10,9,-10,8,-9,7,-8,7,-2,1,0,0,0,0,0,0,0,0]"
CSI_DATA,9,94:d9:b3:80:8c:81,-31,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2881588,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,3,-7,13,-26,13,-25,14,-23,14,-22,13,-20,11,-18,9,-17,7,-16,4,-16,2,-17,0,-18,-1,-20,-1,-22,-1,-23,-1,-24,0,-25,1,-25,1,-24,2,-23,3,-21,3,-19,2,-18,0,-16,-1,-15,-3,-14,-4,-14,-4,-8,-8,-16,-9,-16,-9,-17,-10,-18,-9,-19,-8,-19,-7,-19,-6,-19,-6,-18,-5,-17,-5,-15,-5,-13,-6,-12,-8,-11,-9,-10,-11,-10,-12,-10,-13,-11,-14,-12,-14,-13,-13,-14,-12,-15,-11,-15,-10,-15,-9,-14,-8,-13,-2,-4,0,0,0,0,0,0,0,0]"
CSI_DATA,10,94:d9:b3:80:8c:81,-32,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2893341,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,-5,-3,-20,-13,-20,-13,-18,-14,-17,-13,-15,-13,-14,-11,-13,-9,-13,-7,-13,-5,-14,-4,-15,-2,-16,-2,-18,-1,-19,-1,-20,-2,-20,-3,-20,-4,-19,-4,-18,-4,-17,-4,-15,-4,-14,-4,-13,-3,-12,-1,-12,0,-12,2,-7,1,-13,4,-14,4,-15,5,-15,5,-16,5,-16,4,-16,3,-15,2,-15,2,-13,1,-12,2,-11,2,-10,3,-9,4,-9,5,-9,6,-9,7,-10,8,-11,8,-12,8,-13,8,-13,7,-13,6,-13,5,-12,4,-11,4,-3,1,0,0,0,0,0,0,0,0]"
CSI_DATA,11,94:d9:b3:80:8c:81,-32,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2912696,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,5,1,22,4,22,5,21,5,19,5,17,5,16,5,14,3,13,1,13,0,13,-2,14,-4,15,-5,16,-6,17,-6,18,-6,18,-5,18,-4,18,-4,17,-3,16,-2,15,-2,13,-2,12,-3,11,-4,10,-5,9,-6,4,-4,10,-10,10,-10,11,-11,11,-10,12,-10,13,-10,13,-9,13,-8,12,-7,11,-7,10,-6,8,-7,7,-7,6,-8,6,-9,5,-10,5,-11,5,-12,6,-12,7,-12,8,-12,9,-12,9,-11,9,-10,9,-9,8,-7,2,-2,0,0,0,0,0,0,0,0]"
CSI_DATA,12,94:d9:b3:80:8c:81,-32,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2921403,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,-6,0,-24,2,-23,1,-22,0,-21,-1,-20,-1,-18,-1,-16,0,-15,1,-14,3,-13,5,-13,7,-14,8,-15,9,-16,10,-17,10,-17,9,-18,9,-17,8,-17,7,-16,6,-15,5,-13,5,-12,6,-10,6,-9,7,-8,8,-4,5,-8,11,-8,12,-8,12,-9,13,-10,13,-11,12,-11,12,-11,11,-10,10,-9,9,-8,8,-7,8,-6,8,-4,9,-3,9,-3,10,-3,11,-3,12,-3,12,-4,13,-5,13,-6,13,-6,12,-7,11,-6,10,-6,9,-2,2,0,0,0,0,0,0,0,0]"
CSI_DATA,13,94:d9:b3:80:8c:81,-32,11,1,7,1,0,1,0,1,0,0,-93,0,13,2,2936234,0,67,0,128,1,"[67,48,4,0,0,0,0,0,0,0,-6,-1,-23,-1,-23,-1,-22,-2,-21,-3,-19,-3,-18,-2,-16,-1,-14,0,-14,2,-13,4,-14,5,-14,7,-16,8,-17,8,-18,8,-18,7,-18,6,-18,6,-18,5,-17,5,-15,4,-14,4,-12,4,-11,5,-9,6,-9,7,-5,4,-9,10,-9,11,-10,12,-10,12,-11,12,-12,11,-12,10,-11,9,-11,9,-10,8,-9,7,-8,7,-6,8,-5,8,-5,9,-4,10,-4,11,-4,11,-5,12,-5,12,-6,13,-7,12,-8,12,-8,10,-8,9,-7,8,-2,2,0,0,0,0,0,0,0,0]"
C
```

View File

@@ -0,0 +1,156 @@
dependencies:
espressif/cmake_utilities:
component_hash: 351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f
dependencies:
- name: idf
require: private
version: '>=4.1'
source:
registry_url: https://components.espressif.com
type: service
version: 0.5.3
espressif/esp_csi_gain_ctrl:
component_hash: bb7b9e28f47f4de968d4aaa2a083ea5764e5994dd0c5146146958060e6b66247
dependencies:
- name: espressif/cmake_utilities
registry_url: https://components.espressif.com
require: private
version: 0.*
- name: idf
require: private
version: '>=4.4.1'
source:
registry_url: https://components.espressif.com/
type: service
targets:
- esp32
- esp32s3
- esp32s2
- esp32c3
- esp32c5
- esp32c6
- esp32c61
version: 0.1.4~1
espressif/ethernet_init:
component_hash: b7303f1482091766b9b653ec8332775a9485f4df3c69141577de82847c4d2adf
dependencies:
- matches:
- if: $CONFIG{ETHERNET_SPI_USE_CH390} == 1
name: espressif/ch390
registry_url: https://components.espressif.com
require: private
version: '*'
- matches:
- if: $CONFIG{ETHERNET_SPI_USE_DM9051} == 1
name: espressif/dm9051
registry_url: https://components.espressif.com
require: private
rules:
- if: idf_version >=6.0
version: ^1.0.0
version: '*'
- matches:
- if: idf_version >=6.0
version: ^1.0.0
name: espressif/dp83848
registry_url: https://components.espressif.com
require: private
rules:
- if: target in [esp32, esp32p4]
- if: $CONFIG{ETHERNET_PHY_USE_DP83848} == 1
version: '*'
- matches:
- if: $CONFIG{ETHERNET_SPI_USE_ENC28J60} == 1
name: espressif/enc28j60
registry_url: https://components.espressif.com
require: private
version: '*'
- matches:
- if: idf_version >=6.0
version: ^1.0.0
name: espressif/ip101
registry_url: https://components.espressif.com
require: private
rules:
- if: target in [esp32, esp32p4]
- if: $CONFIG{ETHERNET_PHY_USE_IP101} == 1
version: '*'
- matches:
- if: idf_version >=6.0
version: ^1.0.0
name: espressif/ksz80xx
registry_url: https://components.espressif.com
require: private
rules:
- if: target in [esp32, esp32p4]
- if: $CONFIG{ETHERNET_PHY_USE_KSZ80XX} == 1
version: '*'
- matches:
- if: $CONFIG{ETHERNET_SPI_USE_KSZ8851SNL} == 1
name: espressif/ksz8851snl
registry_url: https://components.espressif.com
require: private
rules:
- if: idf_version >=6.0
version: ^1.0.0
version: '*'
- matches:
- if: $CONFIG{ETHERNET_SPI_USE_LAN865X} == 1
name: espressif/lan865x
registry_url: https://components.espressif.com
require: private
version: ^0.1.1
- name: espressif/lan867x
registry_url: https://components.espressif.com
require: private
rules:
- if: target in [esp32, esp32p4]
- if: $CONFIG{ETHERNET_PHY_USE_LAN867X} == 1
version: '>=2.0.0'
- matches:
- if: idf_version >=6.0
version: ^1.0.0
name: espressif/lan87xx
registry_url: https://components.espressif.com
require: private
rules:
- if: target in [esp32, esp32p4]
- if: $CONFIG{ETHERNET_PHY_USE_LAN87XX} == 1
version: '*'
- matches:
- if: idf_version >=6.0
version: ^1.0.0
name: espressif/rtl8201
registry_url: https://components.espressif.com
require: private
rules:
- if: target in [esp32, esp32p4]
- if: $CONFIG{ETHERNET_PHY_USE_RTL8201} == 1
version: '*'
- matches:
- if: $CONFIG{ETHERNET_SPI_USE_W5500} == 1
name: espressif/w5500
registry_url: https://components.espressif.com
require: private
rules:
- if: idf_version >=6.0
version: ^1.0.0
version: '*'
- name: idf
require: private
version: '>=5.4.3,!=5.5.0,!=5.5.1'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.2.0
idf:
source:
type: idf
version: 6.1.0
direct_dependencies:
- espressif/esp_csi_gain_ctrl
- espressif/ethernet_init
- idf
manifest_hash: 612e7d3604727e429e9b873bb7becd2967d5d7c5dc868ee1ff7595b6ee816ffd
target: esp32
version: 2.0.0

View File

@@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,16 @@
menu "CSI UDP Configuration"
config CSI_UDP_TARGET_IP
string "UDP target IP address"
default "192.168.129.11"
help
IP address of the host receiving CSI data (e.g., Raspberry Pi).
config CSI_UDP_TARGET_PORT
int "UDP target port"
default 5500
range 1024 65535
help
UDP port on the target host for receiving CSI data.
endmenu

View File

@@ -0,0 +1,254 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/* Get recv router csi
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 <stdlib.h>
#include <errno.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "nvs_flash.h"
#include "esp_mac.h"
#include "rom/ets_sys.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_now.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "ping/ping_sock.h"
#include "protocol_examples_common.h"
#include "esp_csi_gain_ctrl.h"
#define CONFIG_SEND_FREQUENCY 100
#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61
#define CSI_FORCE_LLTF 0
#endif
#define CONFIG_FORCE_GAIN 0
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61
#define CONFIG_GAIN_CONTROL 1
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
#define ESP_IF_WIFI_STA ESP_MAC_WIFI_STA
#endif
static const char *TAG = "csi_recv_router";
/* UDP socket for CSI data transmission */
static int s_udp_socket = -1;
static struct sockaddr_in s_dest_addr;
static char s_udp_buffer[2048];
static void wifi_csi_rx_cb(void *ctx, wifi_csi_info_t *info)
{
if (!info || !info->buf) {
ESP_LOGW(TAG, "<%s> wifi_csi_cb", esp_err_to_name(ESP_ERR_INVALID_ARG));
return;
}
if (memcmp(info->mac, ctx, 6)) {
return;
}
const wifi_pkt_rx_ctrl_t *rx_ctrl = &info->rx_ctrl;
static int s_count = 0;
float compensate_gain = 1.0f;
static uint8_t agc_gain = 0;
static int8_t fft_gain = 0;
#if CONFIG_GAIN_CONTROL
static uint8_t agc_gain_baseline = 0;
static int8_t fft_gain_baseline = 0;
esp_csi_gain_ctrl_get_rx_gain(rx_ctrl, &agc_gain, &fft_gain);
if (s_count < 100) {
esp_csi_gain_ctrl_record_rx_gain(agc_gain, fft_gain);
} else if (s_count == 100) {
esp_csi_gain_ctrl_get_rx_gain_baseline(&agc_gain_baseline, &fft_gain_baseline);
#if CONFIG_FORCE_GAIN
esp_csi_gain_ctrl_set_rx_force_gain(agc_gain_baseline, fft_gain_baseline);
ESP_LOGI(TAG, "fft_force %d, agc_force %d", fft_gain_baseline, agc_gain_baseline);
#endif
}
esp_csi_gain_ctrl_get_gain_compensation(&compensate_gain, agc_gain, fft_gain);
ESP_LOGD(TAG, "compensate_gain %f, agc_gain %d, fft_gain %d", compensate_gain, agc_gain, fft_gain);
#endif
/* Build CSI data into buffer for UDP transmission */
int pos = 0;
#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61
if (!s_count) {
ESP_LOGI(TAG, "================ CSI RECV (UDP) ================");
}
pos = snprintf(s_udp_buffer, sizeof(s_udp_buffer),
"CSI_DATA,%d," MACSTR ",%d,%d,%d,%d,%d,%d,%d,%d,%d",
s_count, MAC2STR(info->mac), rx_ctrl->rssi, rx_ctrl->rate,
rx_ctrl->noise_floor, fft_gain, agc_gain, rx_ctrl->channel,
rx_ctrl->timestamp, rx_ctrl->sig_len, rx_ctrl->rx_state);
#else
if (!s_count) {
ESP_LOGI(TAG, "================ CSI RECV (UDP) ================");
}
pos = snprintf(s_udp_buffer, sizeof(s_udp_buffer),
"CSI_DATA,%d," MACSTR ",%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
s_count, MAC2STR(info->mac), rx_ctrl->rssi, rx_ctrl->rate, rx_ctrl->sig_mode,
rx_ctrl->mcs, rx_ctrl->cwb, rx_ctrl->smoothing, rx_ctrl->not_sounding,
rx_ctrl->aggregation, rx_ctrl->stbc, rx_ctrl->fec_coding, rx_ctrl->sgi,
rx_ctrl->noise_floor, rx_ctrl->ampdu_cnt, rx_ctrl->channel, rx_ctrl->secondary_channel,
rx_ctrl->timestamp, rx_ctrl->ant, rx_ctrl->sig_len, rx_ctrl->rx_state);
#endif
#if (CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61) && CSI_FORCE_LLTF
int16_t csi = ((int16_t)(((((uint16_t)info->buf[1]) << 8) | info->buf[0]) << 4) >> 4);
pos += snprintf(s_udp_buffer + pos, sizeof(s_udp_buffer) - pos,
",%d,%d,\"[%d", (info->len - 2) / 2, info->first_word_invalid, (int16_t)(compensate_gain * csi));
for (int i = 2; i < (info->len - 2); i += 2) {
csi = ((int16_t)(((((uint16_t)info->buf[i + 1]) << 8) | info->buf[i]) << 4) >> 4);
pos += snprintf(s_udp_buffer + pos, sizeof(s_udp_buffer) - pos, ",%d", (int16_t)(compensate_gain * csi));
}
#else
pos += snprintf(s_udp_buffer + pos, sizeof(s_udp_buffer) - pos,
",%d,%d,\"[%d", info->len, info->first_word_invalid, (int16_t)(compensate_gain * info->buf[0]));
for (int i = 1; i < info->len; i++) {
pos += snprintf(s_udp_buffer + pos, sizeof(s_udp_buffer) - pos, ",%d", (int16_t)(compensate_gain * info->buf[i]));
}
#endif
pos += snprintf(s_udp_buffer + pos, sizeof(s_udp_buffer) - pos, "]\"\n");
/* Send via UDP */
if (s_udp_socket >= 0) {
sendto(s_udp_socket, s_udp_buffer, pos, 0, (struct sockaddr *)&s_dest_addr, sizeof(s_dest_addr));
}
s_count++;
}
static void wifi_csi_init()
{
/**
* @brief In order to ensure the compatibility of routers, only LLTF sub-carriers are selected.
*/
#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61
wifi_csi_config_t csi_config = {
.enable = true,
.acquire_csi_legacy = true,
.acquire_csi_force_lltf = CSI_FORCE_LLTF,
.acquire_csi_ht20 = true,
.acquire_csi_ht40 = true,
.acquire_csi_vht = false,
.acquire_csi_su = false,
.acquire_csi_mu = false,
.acquire_csi_dcm = false,
.acquire_csi_beamformed = false,
.acquire_csi_he_stbc_mode = 2,
.val_scale_cfg = 0,
.dump_ack_en = false,
.reserved = false
};
#elif CONFIG_IDF_TARGET_ESP32C6
wifi_csi_config_t csi_config = {
.enable = true,
.acquire_csi_legacy = true,
.acquire_csi_ht20 = true,
.acquire_csi_ht40 = true,
.acquire_csi_su = false,
.acquire_csi_mu = false,
.acquire_csi_dcm = false,
.acquire_csi_beamformed = false,
.acquire_csi_he_stbc = 2,
.val_scale_cfg = false,
.dump_ack_en = false,
.reserved = false
};
#else
wifi_csi_config_t csi_config = {
.lltf_en = true,
.htltf_en = false,
.stbc_htltf2_en = false,
.ltf_merge_en = true,
.channel_filter_en = true,
.manu_scale = true,
.shift = true,
};
#endif
static wifi_ap_record_t s_ap_info = {0};
ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&s_ap_info));
ESP_ERROR_CHECK(esp_wifi_set_csi_config(&csi_config));
ESP_ERROR_CHECK(esp_wifi_set_csi_rx_cb(wifi_csi_rx_cb, s_ap_info.bssid));
ESP_ERROR_CHECK(esp_wifi_set_csi(true));
}
static void udp_socket_init(void)
{
s_udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s_udp_socket < 0) {
ESP_LOGE(TAG, "Failed to create UDP socket: errno %d", errno);
return;
}
memset(&s_dest_addr, 0, sizeof(s_dest_addr));
s_dest_addr.sin_family = AF_INET;
s_dest_addr.sin_port = htons(CONFIG_CSI_UDP_TARGET_PORT);
inet_pton(AF_INET, CONFIG_CSI_UDP_TARGET_IP, &s_dest_addr.sin_addr);
ESP_LOGI(TAG, "UDP socket initialized, sending to %s:%d",
CONFIG_CSI_UDP_TARGET_IP, CONFIG_CSI_UDP_TARGET_PORT);
}
static esp_err_t wifi_ping_router_start()
{
static esp_ping_handle_t ping_handle = NULL;
esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG();
ping_config.count = 0;
ping_config.interval_ms = 1000 / CONFIG_SEND_FREQUENCY;
ping_config.task_stack_size = 3072;
ping_config.data_size = 1;
esp_netif_ip_info_t local_ip;
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &local_ip);
ESP_LOGI(TAG, "got ip:" IPSTR ", gw: " IPSTR, IP2STR(&local_ip.ip), IP2STR(&local_ip.gw));
ping_config.target_addr.u_addr.ip4.addr = ip4_addr_get_u32(&local_ip.gw);
ping_config.target_addr.type = ESP_IPADDR_TYPE_V4;
esp_ping_callbacks_t cbs = { 0 };
esp_ping_new_session(&ping_config, &cbs, &ping_handle);
esp_ping_start(ping_handle);
return ESP_OK;
}
void app_main()
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/**
* @brief This helper function configures Wi-Fi, as selected in menuconfig.
* Read "Establishing Wi-Fi Connection" section in esp-idf/examples/protocols/README.md
* for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
udp_socket_init();
wifi_csi_init();
wifi_ping_router_start();
}

View File

@@ -0,0 +1,4 @@
## IDF Component Manager Manifest File
dependencies:
idf: ">=4.4.1"
esp_csi_gain_ctrl: ">=0.1.4"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
#
# Wi-Fi
#
CONFIG_ESP32_WIFI_CSI_ENABLED=y
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=128
#
# UART
#
CONFIG_ESP_CONSOLE_UART_CUSTOM=y
CONFIG_ESP_CONSOLE_UART=y
CONFIG_ESP_CONSOLE_UART_NUM=0
CONFIG_CONSOLE_UART_BAUDRATE=921600
CONFIG_ESP_TASK_WDT_TIMEOUT_S=30
CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B=y
CONFIG_ESPTOOLPY_MONITOR_BAUD=921600
#
# Wi-Fi
#
CONFIG_ESP32_WIFI_CSI_ENABLED=y
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=
#
# Compiler options
#
CONFIG_COMPILER_OPTIMIZATION_PERF=y
#
# FreeRTOS
#
CONFIG_FREERTOS_HZ=1000
#
# ESP32-specific
#
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
#
# ESP32S3-specific
#
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctlycmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.5)
add_compile_options(-fdiagnostics-color=always)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
string(REGEX REPLACE ".*/\(.*\)" "\\1" CURDIR ${CMAKE_CURRENT_SOURCE_DIR})
project(${CURDIR})
git_describe(PROJECT_VERSION ${COMPONENT_DIR})
message("Project commit: " ${PROJECT_VERSION})

View File

@@ -0,0 +1 @@
# CSI_SEND

View File

@@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,170 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/* Get Start 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "nvs_flash.h"
#include "esp_mac.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_now.h"
#define CONFIG_LESS_INTERFERENCE_CHANNEL 11
#if CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61 || (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0))
#define CONFIG_WIFI_BAND_MODE WIFI_BAND_MODE_2G_ONLY
#define CONFIG_WIFI_2G_BANDWIDTHS WIFI_BW_HT40
#define CONFIG_WIFI_5G_BANDWIDTHS WIFI_BW_HT40
#define CONFIG_WIFI_2G_PROTOCOL WIFI_PROTOCOL_11N
#define CONFIG_WIFI_5G_PROTOCOL WIFI_PROTOCOL_11N
#else
#define CONFIG_WIFI_BANDWIDTH WIFI_BW_HT40
#endif
#define CONFIG_ESP_NOW_PHYMODE WIFI_PHY_MODE_HT40
#define CONFIG_ESP_NOW_RATE WIFI_PHY_RATE_MCS0_LGI
#define CONFIG_SEND_FREQUENCY 100
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
#define ESP_IF_WIFI_STA ESP_MAC_WIFI_STA
#endif
static const uint8_t CONFIG_CSI_SEND_MAC[] = {0x1a, 0x00, 0x00, 0x00, 0x00, 0x00};
static const char *TAG = "csi_send";
static void wifi_init()
{
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_netif_init());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
#if CONFIG_IDF_TARGET_ESP32C5
ESP_ERROR_CHECK(esp_wifi_start());
esp_wifi_set_band_mode(CONFIG_WIFI_BAND_MODE);
wifi_protocols_t protocols = {
.ghz_2g = CONFIG_WIFI_2G_PROTOCOL,
.ghz_5g = CONFIG_WIFI_5G_PROTOCOL
};
ESP_ERROR_CHECK(esp_wifi_set_protocols(ESP_IF_WIFI_STA, &protocols));
wifi_bandwidths_t bandwidth = {
.ghz_2g = CONFIG_WIFI_2G_BANDWIDTHS,
.ghz_5g = CONFIG_WIFI_5G_BANDWIDTHS
};
ESP_ERROR_CHECK(esp_wifi_set_bandwidths(ESP_IF_WIFI_STA, &bandwidth));
#elif (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) || CONFIG_IDF_TARGET_ESP32C61
ESP_ERROR_CHECK(esp_wifi_start());
esp_wifi_set_band_mode(CONFIG_WIFI_BAND_MODE);
wifi_protocols_t protocols = {
.ghz_2g = CONFIG_WIFI_2G_PROTOCOL,
};
ESP_ERROR_CHECK(esp_wifi_set_protocols(ESP_IF_WIFI_STA, &protocols));
wifi_bandwidths_t bandwidth = {
.ghz_2g = CONFIG_WIFI_2G_BANDWIDTHS,
};
ESP_ERROR_CHECK(esp_wifi_set_bandwidths(ESP_IF_WIFI_STA, &bandwidth));
#else
ESP_ERROR_CHECK(esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, CONFIG_WIFI_BANDWIDTH));
ESP_ERROR_CHECK(esp_wifi_start());
#endif
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
#if CONFIG_IDF_TARGET_ESP32C5
if ((CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_2G_ONLY && CONFIG_WIFI_2G_BANDWIDTHS == WIFI_BW_HT20)
|| (CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_5G_ONLY && CONFIG_WIFI_5G_BANDWIDTHS == WIFI_BW_HT20)) {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE));
} else {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW));
}
#elif (CONFIG_IDF_TARGET_ESP32C6 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0)) || CONFIG_IDF_TARGET_ESP32C61
if (CONFIG_WIFI_BAND_MODE == WIFI_BAND_MODE_2G_ONLY && CONFIG_WIFI_2G_BANDWIDTHS == WIFI_BW_HT20) {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE));
} else {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW));
}
#else
if (CONFIG_WIFI_BANDWIDTH == WIFI_BW_HT20) {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_NONE));
} else {
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW));
}
#endif
ESP_ERROR_CHECK(esp_wifi_set_mac(WIFI_IF_STA, CONFIG_CSI_SEND_MAC));
}
static void wifi_esp_now_init(esp_now_peer_info_t peer)
{
ESP_ERROR_CHECK(esp_now_init());
ESP_ERROR_CHECK(esp_now_set_pmk((uint8_t *)"pmk1234567890123"));
ESP_ERROR_CHECK(esp_now_add_peer(&peer));
esp_now_rate_config_t rate_config = {
.phymode = CONFIG_ESP_NOW_PHYMODE,
.rate = CONFIG_ESP_NOW_RATE,
.ersu = false,
.dcm = false
};
ESP_ERROR_CHECK(esp_now_set_peer_rate_config(peer.peer_addr, &rate_config));
}
void app_main()
{
/**
* @brief Initialize NVS
*/
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
/**
* @brief Initialize Wi-Fi
*/
wifi_init();
/**
* @brief Initialize ESP-NOW
* ESP-NOW protocol see: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html
*/
esp_now_peer_info_t peer = {
.channel = CONFIG_LESS_INTERFERENCE_CHANNEL,
.ifidx = WIFI_IF_STA,
.encrypt = false,
.peer_addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
};
wifi_esp_now_init(peer);
ESP_LOGI(TAG, "================ CSI SEND ================");
ESP_LOGI(TAG, "wifi_channel: %d, send_frequency: %d, mac: " MACSTR,
CONFIG_LESS_INTERFERENCE_CHANNEL, CONFIG_SEND_FREQUENCY, MAC2STR(CONFIG_CSI_SEND_MAC));
for (uint32_t count = 0; ; ++count) {
esp_err_t ret = esp_now_send(peer.peer_addr, (const uint8_t *)&count, sizeof(count));
if (ret != ESP_OK) {
ESP_LOGW(TAG, "free_heap: %ld <%s> ESP-NOW send error", esp_get_free_heap_size(), esp_err_to_name(ret));
}
usleep(1000 * 1000 / CONFIG_SEND_FREQUENCY);
}
}

View File

@@ -0,0 +1,3 @@
## IDF Component Manager Manifest File
dependencies:
idf: ">=4.4.1"

View File

@@ -0,0 +1,8 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration
#
CONFIG_PARTITION_TABLE_OFFSET=0xa000
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=128
CONFIG_ESP_WIFI_CSI_ENABLED=y
CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n
CONFIG_FREERTOS_HZ=1000

2
get-started/tools/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.csv
*.txt

View File

@@ -0,0 +1,336 @@
#!/usr/bin/env python3
# -*-coding:utf-8-*-
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
# WARNING: we don't check for Python build-time dependencies until
# check_environment() function below. If possible, avoid importing
# any external libraries here - put in external script, or import in
# their specific function instead.
import sys
import csv
import json
import argparse
import pandas as pd
import numpy as np
import serial
from os import path
from io import StringIO
from PyQt5.Qt import *
from pyqtgraph import PlotWidget
from PyQt5 import QtCore
import pyqtgraph as pg
from pyqtgraph import ScatterPlotItem
from PyQt5.QtCore import pyqtSignal, QThread
import threading
import time
from scipy.optimize import minimize
import matplotlib.pyplot as plt
from scipy.stats import linregress
import statsmodels.api as sm
# Reduce displayed waveforms to avoid display freezes
CSI_VAID_SUBCARRIER_INTERVAL = 1
csi_vaid_subcarrier_len =0
CSI_DATA_INDEX = 200 # buffer size
CSI_DATA_COLUMNS = 490
DATA_COLUMNS_NAMES_C5C6 = ['type', 'id', 'mac', 'rssi', 'rate','noise_floor','fft_gain','agc_gain', 'channel', 'local_timestamp', 'sig_len', 'rx_state', 'len', 'first_word', 'data']
DATA_COLUMNS_NAMES = ['type', 'id', 'mac', 'rssi', 'rate', 'sig_mode', 'mcs', 'bandwidth', 'smoothing', 'not_sounding', 'aggregation', 'stbc', 'fec_coding',
'sgi', 'noise_floor', 'ampdu_cnt', 'channel', 'secondary_channel', 'local_timestamp', 'ant', 'sig_len', 'rx_state', 'len', 'first_word', 'data']
csi_data_array = np.zeros(
[CSI_DATA_INDEX, CSI_DATA_COLUMNS], dtype=np.float64)
csi_data_phase = np.zeros([CSI_DATA_INDEX, CSI_DATA_COLUMNS], dtype=np.float64)
csi_data_complex = np.zeros([CSI_DATA_INDEX, CSI_DATA_COLUMNS], dtype=np.complex64)
agc_gain_data = np.zeros([CSI_DATA_INDEX], dtype=np.float64)
fft_gain_data = np.zeros([CSI_DATA_INDEX], dtype=np.float64)
fft_gains = []
agc_gains = []
class csi_data_graphical_window(QWidget):
def __init__(self):
super().__init__()
self.resize(1280, 900)
self.plotWidget_ted = PlotWidget(self)
self.plotWidget_ted.setGeometry(QtCore.QRect(0, 0, 640, 300))
self.plotWidget_ted.setYRange(-2*np.pi, 2*np.pi)
self.plotWidget_ted.addLegend()
self.plotWidget_ted.setTitle('Phase Data - Last Frame') # 添加标题
self.plotWidget_ted.setLabel('left', 'Phase (rad)') # Y轴标签
self.plotWidget_ted.setLabel('bottom', 'Subcarrier Index') # X轴标签
self.csi_amplitude_array = np.abs(csi_data_complex)
self.csi_phase_array = np.angle(csi_data_complex)
self.curve = self.plotWidget_ted.plot([], name='CSI Row Data', pen='r')
self.plotWidget_multi_data = PlotWidget(self)
self.plotWidget_multi_data.setGeometry(QtCore.QRect(0, 300, 1280, 300))
self.plotWidget_multi_data.getViewBox().enableAutoRange(axis=pg.ViewBox.YAxis)
self.plotWidget_multi_data.addLegend()
self.plotWidget_multi_data.setTitle('Subcarrier Amplitude Data') # 添加标题
self.plotWidget_multi_data.setLabel('left', 'Amplitude') # Y轴标签
self.plotWidget_multi_data.setLabel('bottom', 'Time (Cumulative Packet Count)') # X轴标签
self.curve_list = []
agc_curve = self.plotWidget_multi_data.plot(
agc_gain_data, name='AGC Gain', pen=[255,255,0])
fft_curve = self.plotWidget_multi_data.plot(
fft_gain_data, name='FFT Gain', pen=[255,255,0])
self.curve_list.append(agc_curve)
self.curve_list.append(fft_curve)
for i in range(CSI_DATA_COLUMNS):
curve = self.plotWidget_multi_data.plot(
self.csi_amplitude_array[:, i], name=str(i), pen=(255, 255, 255))
self.curve_list.append(curve)
self.plotWidget_phase_data = PlotWidget(self)
self.plotWidget_phase_data.setGeometry(QtCore.QRect(0, 600, 1280, 300))
self.plotWidget_phase_data.getViewBox().enableAutoRange(axis=pg.ViewBox.YAxis)
self.plotWidget_phase_data.addLegend()
self.plotWidget_multi_data.setTitle('Subcarrier Phase Data') # 添加标题
self.plotWidget_multi_data.setLabel('left', 'Phase (rad)') # Y轴标签
self.plotWidget_multi_data.setLabel('bottom', 'Time (Cumulative Packet Count)') # X轴标签
self.curve_phase_list = []
for i in range(CSI_DATA_COLUMNS):
phase_curve = self.plotWidget_phase_data.plot(
np.angle(self.csi_amplitude_array[:, i]), name=str(i), pen=(255, 255, 255))
self.curve_phase_list.append(phase_curve)
# IQ 图窗口
self.plotWidget_iq = PlotWidget(self)
self.plotWidget_iq.setGeometry(QtCore.QRect(640, 0, 640, 300))
self.plotWidget_iq.setLabel('left', 'Q (Imag)')
self.plotWidget_iq.setLabel('bottom', 'I (Real)')
self.plotWidget_iq.setTitle('IQ Plot - Last Frame')
view_box = self.plotWidget_iq.getViewBox()
view_box.setRange(QtCore.QRectF(-30, -30, 60, 60)) # 可以调整范围的大小,保证原点在中间
self.plotWidget_iq.getViewBox().setAspectLocked(True)
self.iq_scatter = ScatterPlotItem(size=6)
self.plotWidget_iq.addItem(self.iq_scatter)
self.iq_colors = []
self.timer = pg.QtCore.QTimer()
self.timer.timeout.connect(self.update_data)
self.timer.start(100)
self.deta_len = 0
def update_curve_colors(self, color_list):
self.deta_len = len(color_list)
self.iq_colors = color_list
self.plotWidget_ted.setXRange(0, self.deta_len//2)
for i in range(self.deta_len):
self.curve_list[i].setPen(color_list[i])
self.curve_phase_list[i].setPen(color_list[i])
def update_data(self):
i = np.real(csi_data_complex[-1, :])
q = np.imag(csi_data_complex[-1, :])
points = []
for idx in range(self.deta_len):
if idx < len(self.iq_colors):
color = self.iq_colors[idx]
else:
color = (200, 200, 200)
points.append({'pos': (i[idx], q[idx]), 'brush': pg.mkBrush(color)})
self.iq_scatter.setData(points)
self.csi_amplitude_array = np.abs(csi_data_complex)
self.csi_phase_array = np.angle(csi_data_complex)
self.csi_row_data = self.csi_phase_array[-1, :]
self.curve.setData(self.csi_row_data)
self.curve_list[CSI_DATA_COLUMNS].setData(agc_gain_data)
self.curve_list[CSI_DATA_COLUMNS+1].setData(fft_gain_data)
for i in range(CSI_DATA_COLUMNS):
self.curve_list[i].setData(self.csi_amplitude_array[:, i])
self.curve_phase_list[i].setData(self.csi_phase_array[:, i])
def generate_subcarrier_colors(red_range, green_range, yellow_range, total_num,interval=1):
colors = []
for i in range(total_num):
if red_range and red_range[0] <= i <= red_range[1]:
intensity = int(255 * (i - red_range[0]) / (red_range[1] - red_range[0]))
colors.append((intensity, 0, 0))
elif green_range and green_range[0] <= i <= green_range[1]:
intensity = int(255 * (i - green_range[0]) / (green_range[1] - green_range[0]))
colors.append((0, intensity, 0))
elif yellow_range and yellow_range[0] <= i <= yellow_range[1]:
intensity = int(255 * (i - yellow_range[0]) / (yellow_range[1] - yellow_range[0]))
colors.append((0, intensity, intensity))
else:
colors.append((200, 200, 200))
return colors
def csi_data_read_parse(port: str, csv_writer, log_file_fd,callback=None):
global fft_gains, agc_gains
set = serial.Serial(port=port, baudrate=921600,bytesize=8, parity='N', stopbits=1)
count =0
if set.isOpen():
print('open success')
else:
print('open failed')
return
while True:
strings = str(set.readline())
if not strings:
break
strings = strings.lstrip('b\'').rstrip('\\r\\n\'')
index = strings.find('CSI_DATA')
if index == -1:
log_file_fd.write(strings + '\n')
log_file_fd.flush()
continue
csv_reader = csv.reader(StringIO(strings))
csi_data = next(csv_reader)
csi_data_len = int (csi_data[-3])
if len(csi_data) != len(DATA_COLUMNS_NAMES) and len(csi_data) != len(DATA_COLUMNS_NAMES_C5C6):
print('element number is not equal',len(csi_data),len(DATA_COLUMNS_NAMES) )
# print(csi_data)
log_file_fd.write('element number is not equal\n')
log_file_fd.write(strings + '\n')
log_file_fd.flush()
continue
try:
csi_raw_data = json.loads(csi_data[-1])
except json.JSONDecodeError:
print('data is incomplete')
log_file_fd.write('data is incomplete\n')
log_file_fd.write(strings + '\n')
log_file_fd.flush()
continue
if csi_data_len != len(csi_raw_data):
print('csi_data_len is not equal',csi_data_len,len(csi_raw_data))
log_file_fd.write('csi_data_len is not equal\n')
log_file_fd.write(strings + '\n')
log_file_fd.flush()
continue
fft_gain = int(csi_data[6])
agc_gain = int(csi_data[7])
fft_gains.append(fft_gain)
agc_gains.append(agc_gain)
csv_writer.writerow(csi_data)
# Rotate data to the left
# csi_data_array[:-1] = csi_data_array[1:]
# csi_data_phase[:-1] = csi_data_phase[1:]
csi_data_complex[:-1] = csi_data_complex[1:]
agc_gain_data[:-1] = agc_gain_data[1:]
fft_gain_data[:-1] = fft_gain_data[1:]
agc_gain_data[-1] = agc_gain
fft_gain_data[-1] = fft_gain
if count ==0:
count = 1
print('none',csi_data_len)
if csi_data_len == 106:
colors = generate_subcarrier_colors((0,25), (27,53), None, len(csi_raw_data))
elif csi_data_len == 114:
colors = generate_subcarrier_colors((0,27), (29,56), None, len(csi_raw_data))
elif csi_data_len == 52:
colors = generate_subcarrier_colors((0,12), (13,26), None, len(csi_raw_data))
elif csi_data_len == 234 :
colors = generate_subcarrier_colors((0,28), (29,56), (60,116), len(csi_raw_data))
elif csi_data_len == 228 :
colors = generate_subcarrier_colors((0,28), (29,57), (57,113), len(csi_raw_data))
elif csi_data_len == 490 :
colors = generate_subcarrier_colors((0,61), (62,122), (123,245), len(csi_raw_data))
elif csi_data_len == 128 :
colors = generate_subcarrier_colors((0,31), (32,63), None, len(csi_raw_data))
elif csi_data_len == 256 :
colors = generate_subcarrier_colors((0,32), (32,63), (64,128), len(csi_raw_data))
elif csi_data_len == 512 :
colors = generate_subcarrier_colors((0,63), (64,127), (128,256), len(csi_raw_data))
elif csi_data_len == 384 :
colors = generate_subcarrier_colors((0,63), (64,127), (128,192), len(csi_raw_data))
elif csi_data_len > 0 and csi_data_len <= 612:
raw_len = len(csi_raw_data)
colors = generate_subcarrier_colors((0,raw_len//2), (raw_len//2+1,raw_len-1), None, raw_len)
callback(colors)
for i in range(csi_data_len // 2):
csi_data_complex[-1][i] = complex(csi_raw_data[i * 2 + 1],
csi_raw_data[i * 2])
set.close()
return
class SubThread (QThread):
data_ready = pyqtSignal(object)
def __init__(self, serial_port, save_file_name, log_file_name):
super().__init__()
self.serial_port = serial_port
save_file_fd = open(save_file_name, 'w')
self.log_file_fd = open(log_file_name, 'w')
self.csv_writer = csv.writer(save_file_fd)
self.csv_writer.writerow(DATA_COLUMNS_NAMES)
def run(self):
csi_data_read_parse(self.serial_port, self.csv_writer, self.log_file_fd,callback=self.data_ready.emit)
def __del__(self):
self.wait()
self.log_file_fd.close()
if __name__ == '__main__':
if sys.version_info < (3, 6):
print(' Python version should >= 3.6')
exit()
parser = argparse.ArgumentParser(
description='Read CSI data from serial port and display it graphically')
parser.add_argument('-p', '--port', dest='port', action='store', required=True,
help='Serial port number of csv_recv device')
parser.add_argument('-s', '--store', dest='store_file', action='store', default='./csi_data.csv',
help='Save the data printed by the serial port to a file')
parser.add_argument('-l', '--log', dest='log_file', action='store', default='./csi_data_log.txt',
help='Save other serial data the bad CSI data to a log file')
args = parser.parse_args()
serial_port = args.port
file_name = args.store_file
log_file_name = args.log_file
app = QApplication(sys.argv)
subthread = SubThread(serial_port, file_name, log_file_name)
window = csi_data_graphical_window()
subthread.data_ready.connect(window.update_curve_colors)
subthread.start()
window.show()
sys.exit(app.exec())

View File

@@ -0,0 +1,399 @@
#!/usr/bin/env python3
# -*-coding:utf-8-*-
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
# WARNING: we don't check for Python build-time dependencies until
# check_environment() function below. If possible, avoid importing
# any external libraries here - put in external script, or import in
# their specific function instead.
import sys
import csv
import json
import argparse
import pandas as pd
import numpy as np
import serial
from os import path
from io import StringIO
from PyQt5.Qt import *
from pyqtgraph import PlotWidget
from PyQt5 import QtCore
import pyqtgraph as pg
from pyqtgraph import ScatterPlotItem
from PyQt5.QtCore import pyqtSignal, QThread
import threading
import time
from scipy.optimize import minimize
import matplotlib.pyplot as plt
from scipy.stats import linregress
import statsmodels.api as sm
# Reduce displayed waveforms to avoid display freezes
CSI_VAID_SUBCARRIER_INTERVAL = 1
csi_vaid_subcarrier_len =0
CSI_DATA_INDEX = 200 # buffer size
CSI_DATA_COLUMNS = 490
DATA_COLUMNS_NAMES_C5C6 = ['type', 'id', 'mac', 'rssi', 'rate','noise_floor','fft_gain','agc_gain', 'channel', 'local_timestamp', 'sig_len', 'rx_state', 'len', 'first_word', 'data']
DATA_COLUMNS_NAMES_NEW = ['type', 'mac','len', 'first_word', 'data']
DATA_COLUMNS_NAMES = ['type', 'id', 'mac', 'rssi', 'rate', 'sig_mode', 'mcs', 'bandwidth', 'smoothing', 'not_sounding', 'aggregation', 'stbc', 'fec_coding',
'sgi', 'noise_floor', 'ampdu_cnt', 'channel', 'secondary_channel', 'local_timestamp', 'ant', 'sig_len', 'rx_state', 'len', 'first_word', 'data']
csi_data_array = np.zeros(
[CSI_DATA_INDEX, CSI_DATA_COLUMNS], dtype=np.float64)
csi_data_phase = np.zeros([CSI_DATA_INDEX, CSI_DATA_COLUMNS], dtype=np.float64)
csi_data_complex = np.zeros([CSI_DATA_INDEX, CSI_DATA_COLUMNS], dtype=np.complex64)
agc_gain_data = np.zeros([CSI_DATA_INDEX], dtype=np.float64)
fft_gain_data = np.zeros([CSI_DATA_INDEX], dtype=np.float64)
fft_gains = []
agc_gains = []
# RAW_DATA 独立数据数组
RAW_DATA_COLUMNS = 612 # RAW_DATA 最大长度
raw_data_complex = np.zeros([CSI_DATA_INDEX, RAW_DATA_COLUMNS], dtype=np.complex64)
class csi_data_graphical_window(QWidget):
def __init__(self):
super().__init__()
self.resize(1280, 900)
self.plotWidget_ted = PlotWidget(self)
self.plotWidget_ted.setGeometry(QtCore.QRect(0, 0, 640, 300))
self.plotWidget_ted.setYRange(-2*np.pi, 2*np.pi)
self.plotWidget_ted.addLegend()
self.plotWidget_ted.setTitle('Phase Data - Last Frame') # 添加标题
self.plotWidget_ted.setLabel('left', 'Phase (rad)') # Y轴标签
self.plotWidget_ted.setLabel('bottom', 'Subcarrier Index') # X轴标签
self.csi_amplitude_array = np.abs(csi_data_complex)
self.csi_phase_array = np.angle(csi_data_complex)
self.curve = self.plotWidget_ted.plot([], name='CSI Phase', pen='r')
# RAW_DATA 相位曲线(蓝色)
self.raw_amplitude_array = np.abs(raw_data_complex)
self.raw_phase_array = np.angle(raw_data_complex)
self.curve_raw = self.plotWidget_ted.plot([], name='RAW Phase', pen='b')
self.plotWidget_multi_data = PlotWidget(self)
self.plotWidget_multi_data.setGeometry(QtCore.QRect(0, 300, 1280, 300))
self.plotWidget_multi_data.getViewBox().enableAutoRange(axis=pg.ViewBox.YAxis)
self.plotWidget_multi_data.addLegend()
self.plotWidget_multi_data.setTitle('Subcarrier Amplitude Data') # 添加标题
self.plotWidget_multi_data.setLabel('left', 'Amplitude') # Y轴标签
self.plotWidget_multi_data.setLabel('bottom', 'Time (Cumulative Packet Count)') # X轴标签
self.curve_list = []
agc_curve = self.plotWidget_multi_data.plot(
agc_gain_data, name='AGC Gain', pen=[255,255,0])
fft_curve = self.plotWidget_multi_data.plot(
fft_gain_data, name='FFT Gain', pen=[255,255,0])
self.curve_list.append(agc_curve)
self.curve_list.append(fft_curve)
for i in range(CSI_DATA_COLUMNS):
curve = self.plotWidget_multi_data.plot(
self.csi_amplitude_array[:, i], name=str(i), pen=(255, 255, 255))
self.curve_list.append(curve)
self.plotWidget_phase_data = PlotWidget(self)
self.plotWidget_phase_data.setGeometry(QtCore.QRect(0, 600, 1280, 300))
self.plotWidget_phase_data.getViewBox().enableAutoRange(axis=pg.ViewBox.YAxis)
self.plotWidget_phase_data.addLegend()
self.plotWidget_multi_data.setTitle('Subcarrier Phase Data') # 添加标题
self.plotWidget_multi_data.setLabel('left', 'Phase (rad)') # Y轴标签
self.plotWidget_multi_data.setLabel('bottom', 'Time (Cumulative Packet Count)') # X轴标签
self.curve_phase_list = []
for i in range(CSI_DATA_COLUMNS):
phase_curve = self.plotWidget_phase_data.plot(
np.angle(self.csi_amplitude_array[:, i]), name=str(i), pen=(255, 255, 255))
self.curve_phase_list.append(phase_curve)
# IQ 图窗口
self.plotWidget_iq = PlotWidget(self)
self.plotWidget_iq.setGeometry(QtCore.QRect(640, 0, 640, 300))
self.plotWidget_iq.setLabel('left', 'Q (Imag)')
self.plotWidget_iq.setLabel('bottom', 'I (Real)')
self.plotWidget_iq.setTitle('IQ Plot - Last Frame')
view_box = self.plotWidget_iq.getViewBox()
view_box.setRange(QtCore.QRectF(-30, -30, 60, 60)) # 可以调整范围的大小,保证原点在中间
self.plotWidget_iq.getViewBox().setAspectLocked(True)
self.iq_scatter = ScatterPlotItem(size=6)
self.plotWidget_iq.addItem(self.iq_scatter)
self.iq_colors = []
self.timer = pg.QtCore.QTimer()
self.timer.timeout.connect(self.update_data)
self.timer.start(100)
self.deta_len = 0
def update_curve_colors(self, color_list):
self.deta_len = len(color_list)
self.iq_colors = color_list
self.plotWidget_ted.setXRange(0, self.deta_len//2)
for i in range(self.deta_len):
self.curve_list[i].setPen(color_list[i])
self.curve_phase_list[i].setPen(color_list[i])
def update_data(self):
i = np.real(csi_data_complex[-1, :])
q = np.imag(csi_data_complex[-1, :])
# points = []
# for idx in range(self.deta_len):
# if idx < len(self.iq_colors):
# color = self.iq_colors[idx]
# else:
# color = (200, 200, 200)
# points.append({'pos': (i[idx], q[idx]), 'brush': pg.mkBrush(color)})
# self.iq_scatter.setData(points)
# CSI_DATA 相位更新
self.csi_amplitude_array = np.abs(csi_data_complex)
self.csi_phase_array = np.angle(csi_data_complex)
self.csi_row_data = self.csi_phase_array[-1, :]
self.csi_row_data = np.unwrap(self.csi_row_data)
self.curve.setData(self.csi_row_data)
# RAW_DATA 相位更新
self.raw_amplitude_array = np.abs(raw_data_complex)
self.raw_phase_array = np.angle(raw_data_complex)
self.raw_row_data = self.raw_phase_array[-1, :]
self.raw_row_data = np.unwrap(self.raw_row_data)
self.curve_raw.setData(self.raw_row_data)
# self.curve_list[CSI_DATA_COLUMNS].setData(agc_gain_data)
# self.curve_list[CSI_DATA_COLUMNS+1].setData(fft_gain_data)
for i in range(CSI_DATA_COLUMNS):
self.curve_list[i].setData(self.csi_amplitude_array[:, i])
# self.curve_phase_list[i].setData(self.csi_phase_array[:, i])
def generate_subcarrier_colors(red_range, green_range, yellow_range, total_num,interval=1):
colors = []
for i in range(total_num):
if red_range and red_range[0] <= i <= red_range[1]:
intensity = int(255 * (i - red_range[0]) / (red_range[1] - red_range[0]))
colors.append((intensity, 0, 0))
elif green_range and green_range[0] <= i <= green_range[1]:
intensity = int(255 * (i - green_range[0]) / (green_range[1] - green_range[0]))
colors.append((0, intensity, 0))
elif yellow_range and yellow_range[0] <= i <= yellow_range[1]:
intensity = int(255 * (i - yellow_range[0]) / (yellow_range[1] - yellow_range[0]))
colors.append((0, intensity, intensity))
else:
colors.append((200, 200, 200))
return colors
def csi_data_read_parse(port: str, csv_writer, log_file_fd,callback=None):
global fft_gains, agc_gains
set = serial.Serial(port=port, baudrate=921600,bytesize=8, parity='N', stopbits=1)
csi_count = 0 # CSI_DATA 计数器
raw_count = 0 # RAW_DATA 计数器
if set.isOpen():
print('open success')
else:
print('open failed')
return
while True:
strings = str(set.readline())
if not strings:
break
strings = strings.lstrip('b\'').rstrip('\\r\\n\'')
index = strings.find('CSI_DATA')
raw_index = strings.find('RAW_DATA')
if index == -1 and raw_index == -1:
log_file_fd.write(strings + '\n')
log_file_fd.flush()
continue
# RAW_DATA 单独处理
if raw_index != -1:
csv_reader = csv.reader(StringIO(strings))
csi_data = next(csv_reader)
csi_data_len = int(csi_data[-3])
try:
csi_raw_data = json.loads(csi_data[-1])
except json.JSONDecodeError:
print('RAW_DATA is incomplete')
log_file_fd.write('RAW_DATA is incomplete\n')
log_file_fd.write(strings + '\n')
log_file_fd.flush()
continue
if csi_data_len != len(csi_raw_data):
print('RAW_DATA csi_data_len is not equal',csi_data_len,len(csi_raw_data))
log_file_fd.write('RAW_DATA csi_data_len is not equal\n')
log_file_fd.write(strings + '\n')
log_file_fd.flush()
continue
# RAW_DATA 使用独立的数据数组
raw_data_complex[:-1] = raw_data_complex[1:]
# 清空当前行
raw_data_complex[-1, :] = 0
if raw_count == 0:
raw_count = 1
print('RAW_DATA detected, length:', csi_data_len)
# RAW_DATA 不需要初始化颜色,只是接收数据显示相位
# RAW_DATA 转换为复数存入独立数组
for i in range(csi_data_len // 2):
raw_data_complex[-1][i] = complex(csi_raw_data[i * 2 + 1],
csi_raw_data[i * 2])
continue
# CSI_DATA 处理
csv_reader = csv.reader(StringIO(strings))
csi_data = next(csv_reader)
csi_data_len = int (csi_data[-3])
if len(csi_data) != len(DATA_COLUMNS_NAMES) and len(csi_data) != len(DATA_COLUMNS_NAMES_C5C6 ) and len(csi_data) != len(DATA_COLUMNS_NAMES_NEW):
print('element number is not equal',len(csi_data),len(DATA_COLUMNS_NAMES) )
print(strings)
log_file_fd.write('element number is not equal\n')
log_file_fd.write(strings + '\n')
log_file_fd.flush()
continue
try:
csi_raw_data = json.loads(csi_data[-1])
except json.JSONDecodeError:
print('data is incomplete')
log_file_fd.write('data is incomplete\n')
log_file_fd.write(strings + '\n')
log_file_fd.flush()
continue
if csi_data_len != len(csi_raw_data):
print('csi_data_len is not equal',csi_data_len,len(csi_raw_data))
log_file_fd.write('csi_data_len is not equal\n')
log_file_fd.write(strings + '\n')
log_file_fd.flush()
continue
fft_gain = 0 # int(csi_data[6])
agc_gain = 0 # int(csi_data[7])
fft_gains.append(fft_gain)
agc_gains.append(agc_gain)
csv_writer.writerow(csi_data)
# Rotate data to the left
# csi_data_array[:-1] = csi_data_array[1:]
# csi_data_phase[:-1] = csi_data_phase[1:]
csi_data_complex[:-1] = csi_data_complex[1:]
agc_gain_data[:-1] = agc_gain_data[1:]
fft_gain_data[:-1] = fft_gain_data[1:]
agc_gain_data[-1] = agc_gain
fft_gain_data[-1] = fft_gain
if csi_count == 0:
csi_count = 1
print('CSI_DATA detected, length:',csi_data_len)
if csi_data_len == 106:
colors = generate_subcarrier_colors((0,25), (27,53), None, len(csi_raw_data))
elif csi_data_len == 114:
colors = generate_subcarrier_colors((0,27), (29,56), None, len(csi_raw_data))
elif csi_data_len == 52:
colors = generate_subcarrier_colors((0,12), (13,26), None, len(csi_raw_data))
elif csi_data_len == 234 :
colors = generate_subcarrier_colors((0,28), (29,56), (60,116), len(csi_raw_data))
elif csi_data_len == 228 :
colors = generate_subcarrier_colors((0,28), (29,56), (57,114), len(csi_raw_data))
elif csi_data_len == 328 :
colors = generate_subcarrier_colors((0,164), None, None, len(csi_raw_data))
elif csi_data_len == 490 :
colors = generate_subcarrier_colors((0,61), (62,122), (123,245), len(csi_raw_data))
elif csi_data_len == 128 :
colors = generate_subcarrier_colors((0,31), (32,63), None, len(csi_raw_data))
elif csi_data_len == 256 :
colors = generate_subcarrier_colors((0,32), (32,63), (64,128), len(csi_raw_data))
elif csi_data_len == 512 :
colors = generate_subcarrier_colors((0,63), (64,127), (128,256), len(csi_raw_data))
elif csi_data_len == 384 :
colors = generate_subcarrier_colors((0,63), (64,127), (128,192), len(csi_raw_data))
else:
print('Please add more color schemes.')
csi_count = 0
continue
callback(colors)
for i in range(csi_data_len // 2):
csi_data_complex[-1][i] = complex(csi_raw_data[i * 2 + 1],
csi_raw_data[i * 2])
set.close()
return
class SubThread (QThread):
data_ready = pyqtSignal(object)
def __init__(self, serial_port, save_file_name, log_file_name):
super().__init__()
self.serial_port = serial_port
save_file_fd = open(save_file_name, 'w')
self.log_file_fd = open(log_file_name, 'w')
self.csv_writer = csv.writer(save_file_fd)
self.csv_writer.writerow(DATA_COLUMNS_NAMES)
def run(self):
csi_data_read_parse(self.serial_port, self.csv_writer, self.log_file_fd,callback=self.data_ready.emit)
def __del__(self):
self.wait()
self.log_file_fd.close()
if __name__ == '__main__':
if sys.version_info < (3, 6):
print(' Python version should >= 3.6')
exit()
parser = argparse.ArgumentParser(
description='Read CSI data from serial port and display it graphically')
parser.add_argument('-p', '--port', dest='port', action='store', required=True,
help='Serial port number of csv_recv device')
parser.add_argument('-s', '--store', dest='store_file', action='store', default='./csi_data.csv',
help='Save the data printed by the serial port to a file')
parser.add_argument('-l', '--log', dest='log_file', action='store', default='./csi_data_log.txt',
help='Save other serial data the bad CSI data to a log file')
args = parser.parse_args()
serial_port = args.port
file_name = args.store_file
log_file_name = args.log_file
app = QApplication(sys.argv)
subthread = SubThread(serial_port, file_name, log_file_name)
window = csi_data_graphical_window()
subthread.data_ready.connect(window.update_curve_colors)
subthread.start()
window.show()
sys.exit(app.exec())