Skip to content

Commit 59ef8eb

Browse files
Initial commit
0 parents  commit 59ef8eb

16 files changed

+677
-0
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.idea
2+
build
3+
cmake-build-debug
4+
sdkconfig
5+
dependencies.lock
6+
.clang-format

CMakeLists.txt

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
# The following lines of boilerplate have to be in your project's CMakeLists
3+
# in this exact order for cmake to work correctlycmake_minimum_required(VERSION 3.5)
4+
cmake_minimum_required(VERSION 3.5)
5+
add_compile_options(-fdiagnostics-color=always)
6+
7+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
8+
9+
message("EXTRA_COMPONENT_DIRS: " ${EXTRA_COMPONENT_DIRS})
10+
11+
string(REGEX REPLACE ".*/\(.*\)" "\\1" CURDIR ${CMAKE_CURRENT_SOURCE_DIR})
12+
project(${CURDIR})
13+
14+
git_describe(PROJECT_VERSION ${COMPONENT_DIR})
15+
message("Project commit: " ${PROJECT_VERSION})

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Nipun Chamikara Weerasiri
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
IDF_PATH := $(HOME)/esp/esp-idf
2+
PORTS = /dev/tty.usbserial-0001 /dev/tty.usbserial-4 /dev/tty.usbserial-5
3+
DEVICE := 0
4+
5+
get_port = $(word $(shell echo $$(($(DEVICE) + 1))),$(PORTS))
6+
7+
.PHONY: install build flash clean flash-monitor help
8+
9+
install: ## Install ESP-IDF
10+
$(IDF_PATH)/install.sh
11+
12+
build: ## Build the project
13+
. $(IDF_PATH)/export.sh && \
14+
idf.py build
15+
16+
run-cmd:
17+
. $(IDF_PATH)/export.sh && \
18+
idf.py -p $(call get_port,$(DEVICE)) $(CMD)
19+
20+
flash: ## Flash firmware to ESP32
21+
$(MAKE) run-cmd CMD=flash
22+
23+
monitor: ## Monitor the serial output
24+
$(MAKE) run-cmd CMD=monitor
25+
26+
flash-monitor: ## Flash firmware to ESP32 and monitor the serial output
27+
$(MAKE) run-cmd CMD="flash monitor"
28+
29+
clean: ## Clean the project
30+
. $(IDF_PATH)/export.sh && \
31+
idf.py clean
32+
33+
help: ## List all available commands
34+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
35+

README.md

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# ESP32 CSI Data Collection Project
2+
3+
This project involves multiple ESP32 devices that communicate with each other using ESP-NOW to collect and log Channel
4+
State Information (CSI) data. Each device is flashed with the same program, with the default device ID changing the
5+
first time. The device ID is persisted, allowing the same program to be flashed to all devices. Device 0 logs all sent
6+
and received payloads, which contain CSI data. The system uses a timeout mechanism and follows a round-robin protocol
7+
where each device sends a packet once the previous device has sent a packet.
8+
9+
## Project Structure
10+
11+
- `CMakeLists.txt`: CMake build configuration file.
12+
- `app_init.c/h`: Source and header file for application initialization.
13+
- `app_main.c`: Main application source file.
14+
- `app_nvs.c/h`: Source and header file for non-volatile storage (NVS) operations.
15+
- `constants.h`: Defines constants used throughout the project.
16+
- `csi_data.c/h`: Source and header file for handling CSI data.
17+
- `idf_component.yml`: Component configuration file.
18+
19+
## Files
20+
21+
### `CMakeLists.txt`
22+
23+
This file contains the build configuration for the project using CMake.
24+
25+
### `app_init.c` and `app_init.h`
26+
27+
These files handle the initialization of the application, including setting up the ESP-NOW communication and configuring
28+
the device.
29+
30+
### `app_main.c`
31+
32+
This is the main application file where the primary logic of the project is implemented.
33+
34+
### `app_nvs.c` and `app_nvs.h`
35+
36+
These files handle operations related to non-volatile storage (NVS), such as storing and retrieving the device ID.
37+
38+
### `constants.h`
39+
40+
Defines various constants used in the project, such as device IDs, timeout values, and buffer sizes.
41+
42+
### `csi_data.c` and `csi_data.h`
43+
44+
These files define structures and functions for handling CSI data. Key structures include:
45+
46+
- `csi_data_t`: Holds CSI data received from a device.
47+
- `payload_t`: Holds the payload of CSI data received from all devices.
48+
49+
Key functions include:
50+
51+
- `send_csi_data()`: Sends the CSI data to the broadcast address using ESP-NOW.
52+
- `print_csi_data(const csi_data_t *csi_data)`: Prints the CSI data to the console.
53+
- `print_payload(const payload_t *payload)`: Prints the payload to the console.
54+
- `wifi_csi_rx_cb(void *ctx, wifi_csi_info_t *info)`: Callback function to handle received CSI data.
55+
- `turns_away(const uint8_t device_id)`: Returns the number of devices between the current and last device IDs.
56+
57+
## How It Works
58+
59+
1. **Flashing the Devices**: Each ESP32 device is flashed with the same program. The default device ID is set during the
60+
first run and is persisted for subsequent runs.
61+
2. **Device 0**: Device 0 logs all sent and received payloads, which contain CSI data.
62+
3. **Timeout System**: The system uses a timeout mechanism where each device waits for a certain number of iterations
63+
before sending a packet. The number of iterations is based on how many devices away the device is from the previous
64+
device.
65+
4. **Round-Robin Protocol**: The devices follow a round-robin protocol where each device sends a packet once the
66+
previous device has sent a packet.
67+
5. **Piggybacking CSI Data**: The devices piggyback CSI data on the ESP-NOW packets they send to the broadcast address.
68+
69+
## Building and Flashing
70+
71+
First, add the ports for the ESP32 devices to the `Makefile`:
72+
73+
```makefile
74+
PORTS = /dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2
75+
```
76+
77+
To build and flash the firmware to the ESP32 devices, use the following commands:
78+
79+
```sh
80+
make install
81+
make build
82+
make flash DEVICE=<device_id>
83+
make monitor DEVICE=<device_id>
84+
```
85+
86+
Replace `<device_id>` with the device ID of the ESP32 device you want to flash. The device ID should be an integer.
87+
If the device ID is not specified, the default device ID is 0.
88+
89+
To flash the firmware and then monitor the serial output, use:
90+
91+
```sh
92+
make flash-monitor DEVICE=<device_id>
93+
```
94+
95+
To clean the build directory, use:
96+
97+
```sh
98+
make clean
99+
```
100+
101+
## Contributing
102+
103+
Contributions are welcome! For feature requests, bug reports, or questions, please open an issue.
104+
105+
## License
106+
107+
This project is licensed under the MIT License. See the `LICENSE` file for more information.
108+
109+
## Appendix
110+
111+
### Obtaining payload data from callback function `wifi_csi_rx_cb`
112+
113+
#### Vendor Specific Action Frame
114+
115+
| MAC Header | Category Code | Organization Identifier | Random Values | Vendor Specific Content | FCS |
116+
|------------|---------------|-------------------------|---------------|-------------------------|---------|
117+
| 24 bytes | 1 byte | 3 bytes | 4 bytes | 7-1532 bytes | 4 bytes |
118+
119+
#### Vendor Specific Element Frame
120+
121+
| Element ID | Length | Organization Identifier | Type | Reserved | More data | Version | Body |
122+
|------------|--------|-------------------------|--------|----------|-----------|----------|--------------|
123+
| 1 byte | 1 byte | 3 bytes | 1 byte | 7-5 bits | 1 bit | 3-0 bits | 0-1490 bytes |
124+
125+
The payload data of the Wi-Fi packet is obtained from the `wifi_csi_info_t` structure using the `payload` field.
126+
It starts with the Category Code of the Vendor Specific Action frame. This means, the payload of the ESP-NOW packet
127+
begins at index 15.

main/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
idf_component_register(SRC_DIRS "."
2+
INCLUDE_DIRS ".")

main/app_init.c

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#include <stdio.h>
2+
#include <string.h>
3+
#include <unistd.h>
4+
#include "esp_log.h"
5+
#include "esp_netif.h"
6+
#include "esp_now.h"
7+
#include "esp_wifi.h"
8+
9+
#include "app_init.h"
10+
#include "constants.h"
11+
#include "csi_data.h"
12+
13+
void wifi_init() {
14+
ESP_ERROR_CHECK(esp_event_loop_create_default());
15+
ESP_ERROR_CHECK(esp_netif_init());
16+
const wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
17+
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
18+
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
19+
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
20+
ESP_ERROR_CHECK(esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, WIFI_BW_HT40));
21+
ESP_ERROR_CHECK(esp_wifi_start());
22+
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
23+
ESP_ERROR_CHECK(esp_wifi_set_channel(CONFIG_LESS_INTERFERENCE_CHANNEL, WIFI_SECOND_CHAN_BELOW));
24+
ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true));
25+
ESP_LOGI(TAG, "Wi-Fi initialized");
26+
}
27+
28+
void now_init() {
29+
ESP_ERROR_CHECK(esp_now_init());
30+
ESP_ERROR_CHECK(esp_now_set_pmk((uint8_t *) "pmk1234567890123"));
31+
32+
// Sends data to all peers
33+
const esp_now_peer_info_t peer = {
34+
.channel = CONFIG_LESS_INTERFERENCE_CHANNEL,
35+
.ifidx = WIFI_IF_STA,
36+
.encrypt = false,
37+
.peer_addr = BROADCAST_ADDR,
38+
};
39+
ESP_ERROR_CHECK(esp_now_add_peer(&peer));
40+
41+
esp_now_rate_config_t rate_config = {
42+
.phymode = WIFI_PHY_MODE_HT20,
43+
.rate = WIFI_PHY_RATE_MCS0_SGI,
44+
};
45+
const uint8_t peer_addr[] = BROADCAST_ADDR;
46+
ESP_ERROR_CHECK(esp_now_set_peer_rate_config(peer_addr, &rate_config));
47+
ESP_LOGI(TAG, "ESP-NOW initialized");
48+
}
49+
50+
void csi_init() {
51+
const wifi_csi_config_t csi_config = {
52+
.lltf_en = true,
53+
.htltf_en = true,
54+
.stbc_htltf2_en = true,
55+
.ltf_merge_en = true,
56+
.channel_filter_en = true,
57+
.manu_scale = false,
58+
.shift = false,
59+
};
60+
ESP_ERROR_CHECK(esp_wifi_set_csi_config(&csi_config));
61+
ESP_ERROR_CHECK(esp_wifi_set_csi_rx_cb(wifi_csi_rx_cb, NULL));
62+
ESP_ERROR_CHECK(esp_wifi_set_csi(true));
63+
ESP_LOGI(TAG, "Wi-Fi CSI initialized");
64+
}

main/app_init.h

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#ifndef APP_INIT_H
2+
#define APP_INIT_H
3+
4+
/**
5+
* @brief Initialize Wi-Fi in station mode with specific configurations.
6+
*
7+
* This function sets up the Wi-Fi in station mode, initializes the default event loop,
8+
* network interface, and configures various Wi-Fi settings such as bandwidth, power save mode,
9+
* channel, and promiscuous mode.
10+
*/
11+
void wifi_init();
12+
13+
/**
14+
* @brief Initialize ESP-NOW with specific configurations.
15+
*
16+
* This function initializes ESP-NOW, sets the primary master key (PMK), adds a peer with
17+
* specific configurations, and sets the peer rate configuration.
18+
*/
19+
void now_init();
20+
21+
/**
22+
* @brief Initialize Wi-Fi Channel State Information (CSI) with specific configurations.
23+
*
24+
* This function sets up the CSI configuration, registers the CSI receive callback, and
25+
* enables CSI collection.
26+
*/
27+
void csi_init();
28+
29+
#endif // APP_INIT_H

main/app_main.c

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#include <rom/ets_sys.h>
2+
#include <stdio.h>
3+
#include <string.h>
4+
#include <unistd.h>
5+
6+
#include "esp_log.h"
7+
#include "esp_netif.h"
8+
#include "nvs.h"
9+
#include "nvs_flash.h"
10+
11+
#include "app_init.h"
12+
#include "app_nvs.h"
13+
#include "constants.h"
14+
#include "csi_data.h"
15+
16+
/**
17+
* Returns the number of devices between the current and last device IDs.
18+
* @param device_id The current device ID.
19+
* @return The number of devices between the current and last device IDs.
20+
*/
21+
uint8_t turns_away(const uint8_t device_id) {
22+
// device 2, last device 0, 2 - 0 = 2
23+
if (device_id > last_id) {
24+
return device_id - last_id;
25+
}
26+
// device 0, last device 2, 3 - 2 + 0 = 1
27+
return TOTAL_DEVICES - last_id + device_id;
28+
}
29+
30+
void app_main() {
31+
// Initialize NVS
32+
esp_err_t ret = nvs_flash_init();
33+
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
34+
ESP_ERROR_CHECK(nvs_flash_erase());
35+
ret = nvs_flash_init();
36+
}
37+
ESP_ERROR_CHECK(ret);
38+
39+
// Get the device ID from NVS or set the default ID
40+
payload.device_id = get_device_id();
41+
ESP_LOGI(TAG, "Device ID: %d", payload.device_id);
42+
last_id = (payload.device_id + TOTAL_DEVICES - 1) % TOTAL_DEVICES;
43+
44+
// Initialize the payload length and index
45+
payload.csi_data_arr_len = 0;
46+
payload_index = 0;
47+
48+
// Initialize Wi-Fi, ESP-NOW, and CSI
49+
wifi_init();
50+
now_init();
51+
csi_init();
52+
53+
// If the device ID is 0, start sending CSI data
54+
if (payload.device_id == 0) {
55+
send_csi_data();
56+
}
57+
58+
// Infinite loop to send CSI data
59+
// ReSharper disable once CppDFAEndlessLoop
60+
while (1) {
61+
usleep(MICROSECONDS_IN_SECOND / CONFIG_SEND_FREQUENCY);
62+
timeout_count++;
63+
64+
// Determine the multiplier based on the distance between the current and last device IDs
65+
// This multiplier is used to increase the timeout count for devices further away
66+
const int multiplier = turns_away(payload.device_id);
67+
68+
// If the timeout count exceeds the timeout threshold, send CSI data
69+
if (timeout_count >= TIMEOUT * multiplier * multiplier) {
70+
ESP_LOGW(TAG, "ESP-NOW Timeout %d", timeout_count);
71+
send_csi_data();
72+
timeout_count = 0;
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)