mirror of
https://github.com/espressif/esp-csi.git
synced 2026-07-03 03:38:49 +00:00
Merge branch 'update_csi_viewer_html' into 'master'
feat: update_csi_viewer_html & add esp wifi sensing example See merge request esp-components/esp-csi-ext!75
This commit is contained in:
commit
8633d67152
21 changed files with 5908 additions and 407 deletions
|
|
@ -73,6 +73,7 @@ before_script:
|
|||
get-started/csi_recv_router
|
||||
esp-radar/console_test
|
||||
esp-radar/connect_rainmaker
|
||||
esp-radar/wifi_sensing_demo
|
||||
esp-crab/master_recv
|
||||
esp-crab/slave_recv
|
||||
esp-crab/slave_send
|
||||
|
|
@ -82,6 +83,7 @@ before_script:
|
|||
get-started/csi_recv_router: esp32 esp32s3 esp32s2 esp32c3 esp32c6 esp32c5 esp32c61
|
||||
esp-radar/console_test: esp32 esp32s3 esp32s2 esp32c3 esp32c6 esp32c5 esp32c61
|
||||
esp-radar/connect_rainmaker: esp32s3 esp32s2 esp32c3 esp32c6 esp32c5 esp32c61
|
||||
esp-radar/wifi_sensing_demo: esp32 esp32s3 esp32s2 esp32c3 esp32c6 esp32c5 esp32c61
|
||||
esp-crab/master_recv: esp32c5
|
||||
esp-crab/slave_recv: esp32c5
|
||||
esp-crab/slave_send: esp32c5
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ Provides some applications using CSI data, including RainMaker cloud reporting a
|
|||
|
||||
- [connect_rainmaker](./examples/esp-radar/connect_rainmaker) demonstrates capturing CSI data and uploading it to Espressif's RainMaker cloud platform.
|
||||
- [console_test](./examples/esp-radar/console_test) demonstrates an interactive console that allows dynamic configuration and capture of CSI data, with applications for human activity detection algorithms.
|
||||
- [wifi_sensing_demo](./examples/esp-radar/wifi_sensing_demo) demonstrates motion and human presence detection on top of the `esp_wifi_sensing` component, with on-site training, LED feedback, and a browser-based Web Serial monitor for live diagnostics and tuning.
|
||||
|
||||
## How to get CSI
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
|
||||
- [connect_rainmaker](./examples/esp-radar/connect_rainmaker) 演示了将 CSI 数据捕获并上传到 Espressif 的 RainMaker 云平台
|
||||
- [console_test](./examples/esp-radar/console_test) 演示一个交互式控制台,允许动态配置和捕获 CSI 数据,并提供了人体活动检测的算法应用
|
||||
- [wifi_sensing_demo](./examples/esp-radar/wifi_sensing_demo) 基于 `esp_wifi_sensing` 组件的 Wi-Fi 感知演示,包含运动和人体存在检测、现场训练标定、LED 状态反馈,以及配套的浏览器端 Web Serial 监控页面,用于实时诊断与调参
|
||||
|
||||
## 如何获取CSI
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ This directory contains multiple example projects for esp-csi. These examples ar
|
|||
- `get-started/csi_recv_router`: Receives CSI in router communication mode by pinging a router and parsing the CSI from its reply packets.
|
||||
- `esp-radar/connect_rainmaker`: Connects CSI data to Espressif's Rainmaker cloud platform for remote visualization or control.
|
||||
- `esp-radar/console_test`: A console-based testing example for debugging and evaluating CSI data and algorithm performance.
|
||||
- `esp-radar/wifi_sensing_demo`: A Wi-Fi sensing demo built on the `esp_wifi_sensing` component, featuring motion and human presence detection, on-site training, LED feedback, and a browser-based Web Serial monitor for live diagnostics and tuning.
|
||||
- `esp-crab/master_recv`: The master receiver for the esp-crab hardware platform; responsible for acquiring and parsing Wi-Fi CIR/CSI data.
|
||||
- `esp-crab/slave_recv`: The slave receiver on the esp-crab platform, assisting the master receiver with multi-channel data collection.
|
||||
- `esp-crab/slave_send`: The transmitter on the esp-crab platform, responsible for sending packets periodically for CSI extraction by other nodes.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
- `get-started/csi_recv_router`:在路由器通信模式下接收 CSI,通过 ping 路由器并解析其应答包中的 CSI 信息。
|
||||
- `esp-radar/connect_rainmaker`:将 CSI 数据与 Espressif 的云平台 Rainmaker 连接,用于远程展示或控制。
|
||||
- `esp-radar/console_test`:用于控制台调试测试 CSI 数据和算法效果的示例。
|
||||
- `esp-radar/wifi_sensing_demo`:基于 `esp_wifi_sensing` 组件的 Wi-Fi 感知演示示例,包含运动和人体存在检测、现场训练标定、LED 状态反馈,以及配套的浏览器端 Web Serial 监控页面,便于调试和调参。
|
||||
- `esp-crab/master_recv`:esp-crab 硬件平台上的主接收端,支持获取并解析 Wi-Fi CIR/CSI 数据。
|
||||
- `esp-crab/slave_recv`:esp-crab 平台的从接收端,辅助主接收端进行多通道数据收集。
|
||||
- `esp-crab/slave_send`:esp-crab 平台的发送端,负责定时发送数据包供其他节点进行接收和 CSI 提取。
|
||||
17
examples/esp-radar/wifi_sensing_demo/CMakeLists.txt
Normal file
17
examples/esp-radar/wifi_sensing_demo/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
add_compile_options(-fdiagnostics-color=always)
|
||||
|
||||
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})
|
||||
96
examples/esp-radar/wifi_sensing_demo/README.md
Normal file
96
examples/esp-radar/wifi_sensing_demo/README.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# ESP Wi-Fi Sensing Demo
|
||||
|
||||
This directory contains a self-contained ESP-IDF demo used to exercise the
|
||||
`esp_wifi_sensing` component in a realistic bring-up flow.
|
||||
|
||||
The example combines:
|
||||
|
||||
- Wi-Fi STA connection and sensing FSM startup
|
||||
- Three monitored channels: the connected AP plus two fixed peer MAC addresses
|
||||
- A status LED policy for quick visual feedback
|
||||
- A lightweight serial protocol that can be consumed by the browser-based Web
|
||||
Serial monitor in `tools/web_serial_monitor.html`
|
||||
|
||||
The example follows the same public workflow recommended by the component:
|
||||
|
||||
1. Create the FSM with `esp_wifi_sensing_fsm_create()`.
|
||||
2. Add the AP channel and two extra peer channels.
|
||||
3. Register ACTIVE / INACTIVE callbacks.
|
||||
4. Start the FSM with `esp_wifi_sensing_fsm_control(..., ESP_WIFI_SENSING_FSM_CTRL_START, NULL)`.
|
||||
5. Optionally keep CSI traffic flowing with `esp_wifi_sensing_fsm_ping_router_start()`.
|
||||
|
||||
## Directory Layout
|
||||
|
||||
- `main/app_main.c`: example entry point and sensing FSM wiring
|
||||
- `main/led_control.*`: LED backend and state mapping
|
||||
- `main/web_serial_monitor.*`: line-oriented serial protocol for diagnostics and runtime tuning
|
||||
- `tools/web_serial_monitor.html`: browser UI for charts, live diagnostics, and configuration
|
||||
|
||||
## Build and Run
|
||||
|
||||
Open the `wifi_sensing_demo` directory as an ESP-IDF project, then build and
|
||||
flash it in the usual way:
|
||||
|
||||
```bash
|
||||
idf.py set-target <chip>
|
||||
idf.py flash monitor
|
||||
```
|
||||
|
||||
The example uses `protocol_examples_common` for Wi-Fi provisioning, so configure
|
||||
your Wi-Fi credentials before flashing if your local workflow requires it.
|
||||
|
||||
## Web Serial Monitor
|
||||
|
||||
When `CONFIG_ESP_WIFI_SENSING_WEB_SERIAL_ENABLE=y`, the firmware emits
|
||||
line-delimited JSON messages prefixed with `HMS:` and accepts commands prefixed
|
||||
with `HMSCMD `. This allows ordinary ESP logs and structured diagnostics to
|
||||
share the same serial port.
|
||||
|
||||
The browser UI currently focuses on the minimum public diagnostics exported by
|
||||
the component: `jitter_value`, `smooth_scaled`, `enter_level_scaled`,
|
||||
`exit_level_scaled`, `state`, and `init_stage`.
|
||||
|
||||
The serial monitor also exposes the main runtime controls used by the demo:
|
||||
|
||||
- `HMSCMD HELLO`
|
||||
- `HMSCMD START_STREAM`
|
||||
- `HMSCMD STOP_STREAM`
|
||||
- `HMSCMD FSM_START`
|
||||
- `HMSCMD FSM_STOP`
|
||||
- `HMSCMD RESET_BASELINE`
|
||||
- `HMSCMD GET_CFG ALL`
|
||||
- `HMSCMD GET_CFG <peer>`
|
||||
- `HMSCMD GET_RUNTIME`
|
||||
- `HMSCMD SET_AMPLITUDE_LOG on`
|
||||
- `HMSCMD SET_AMPLITUDE_LOG off`
|
||||
- `HMSCMD SET_CFG <peer> motion_sensitivity=<float> active_jitter_min=<float> active_filter_ms=<ms>`
|
||||
|
||||
Here `<peer>` can be either the demo name (`AP`, `MAC_1`, `MAC_2`) or the peer
|
||||
MAC string.
|
||||
|
||||
To use the browser monitor:
|
||||
|
||||
1. Build and run the firmware.
|
||||
2. Open `tools/web_serial_monitor.html` in a Chromium-based browser.
|
||||
3. Connect to the board through Web Serial.
|
||||
4. Inspect live channel diagnostics or update channel parameters from the page.
|
||||
|
||||
If the browser blocks `file://` access for Web Serial, serve the directory
|
||||
locally:
|
||||
|
||||
```bash
|
||||
cd tools
|
||||
python3 -m http.server
|
||||
```
|
||||
|
||||
Then open `http://127.0.0.1:8000/web_serial_monitor.html`.
|
||||
|
||||
## Demo Behavior
|
||||
|
||||
- The AP BSSID channel is treated as the primary channel for LED feedback.
|
||||
- The LED blinks while Wi-Fi is disconnected.
|
||||
- When the AP channel becomes ACTIVE, the LED turns fully on.
|
||||
- When the AP channel returns to INACTIVE, the LED fades out.
|
||||
- The two fixed peer channels remain visible in logs and the browser monitor as
|
||||
extra sensing references.
|
||||
|
||||
12
examples/esp-radar/wifi_sensing_demo/main/CMakeLists.txt
Normal file
12
examples/esp-radar/wifi_sensing_demo/main/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
set(reqs esp_wifi_sensing
|
||||
protocol_examples_common
|
||||
driver
|
||||
esp_driver_ledc
|
||||
led_strip)
|
||||
|
||||
idf_component_register(SRCS "app_main.c"
|
||||
"led_control.c"
|
||||
"web_serial_monitor.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES ${reqs}
|
||||
PRIV_REQUIRES esp_driver_usb_serial_jtag)
|
||||
55
examples/esp-radar/wifi_sensing_demo/main/Kconfig.projbuild
Normal file
55
examples/esp-radar/wifi_sensing_demo/main/Kconfig.projbuild
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
menu "Wi-Fi sensing demo"
|
||||
|
||||
choice ESP_WIFI_SENSING_DEMO_LED_TYPE
|
||||
prompt "LED type"
|
||||
default ESP_WIFI_SENSING_DEMO_LED_TYPE_GPIO
|
||||
help
|
||||
Choose between a single GPIO LED and a single WS2812 LED.
|
||||
|
||||
config ESP_WIFI_SENSING_DEMO_LED_TYPE_GPIO
|
||||
bool "GPIO LED"
|
||||
|
||||
config ESP_WIFI_SENSING_DEMO_LED_TYPE_WS2812
|
||||
bool "WS2812 LED"
|
||||
endchoice
|
||||
|
||||
config ESP_WIFI_SENSING_DEMO_LED_GPIO
|
||||
int "LED GPIO"
|
||||
default 2
|
||||
help
|
||||
GPIO used for the LED output. For WS2812 this is the data pin.
|
||||
|
||||
if ESP_WIFI_SENSING_DEMO_LED_TYPE_WS2812
|
||||
|
||||
choice ESP_WIFI_SENSING_DEMO_WS2812_BACKEND
|
||||
prompt "WS2812 backend"
|
||||
default ESP_WIFI_SENSING_DEMO_WS2812_BACKEND_RMT
|
||||
help
|
||||
Select the peripheral used to drive the WS2812 LED.
|
||||
|
||||
config ESP_WIFI_SENSING_DEMO_WS2812_BACKEND_RMT
|
||||
bool "RMT backend"
|
||||
|
||||
config ESP_WIFI_SENSING_DEMO_WS2812_BACKEND_SPI
|
||||
bool "SPI backend"
|
||||
endchoice
|
||||
|
||||
endif
|
||||
|
||||
config ESP_WIFI_SENSING_WEB_SERIAL_ENABLE
|
||||
bool "Enable Web Serial monitor output"
|
||||
default y
|
||||
help
|
||||
Stream sensing diagnostics to the host over the standard serial console.
|
||||
The browser Web Serial page can connect to whichever serial port is
|
||||
exposed by the current console configuration.
|
||||
|
||||
config ESP_WIFI_SENSING_WEB_SERIAL_STREAM_PERIOD_MS
|
||||
int "Web Serial stream period (ms)"
|
||||
default 50
|
||||
range 20 1000
|
||||
depends on ESP_WIFI_SENSING_WEB_SERIAL_ENABLE
|
||||
help
|
||||
Period used to push one diagnostic snapshot per channel to the Web Serial UI.
|
||||
|
||||
endmenu
|
||||
240
examples/esp-radar/wifi_sensing_demo/main/app_main.c
Normal file
240
examples/esp-radar/wifi_sensing_demo/main/app_main.c
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_now.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
#include "esp_wifi_sensing.h"
|
||||
|
||||
#include "protocol_examples_common.h"
|
||||
#include "led_control.h"
|
||||
#include "web_serial_monitor.h"
|
||||
|
||||
static const char *TAG = "wifi_sensing_demo";
|
||||
static esp_wifi_sensing_fsm_handle_t s_hms = NULL;
|
||||
static uint8_t s_mac_ap[6] = {0};
|
||||
|
||||
/*
|
||||
* The demo monitors three channels:
|
||||
* 1. The connected AP BSSID.
|
||||
* 2. A fixed CSI sender peer.
|
||||
* 3. A second fixed peer used as an extra reference channel.
|
||||
*/
|
||||
static const uint8_t CONFIG_CSI_SEND_MAC[] = {0x1a, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
static const uint8_t CONFIG_CSI_SEND_MAC_2[] = {0x1a, 0x00, 0x00, 0x00, 0x00, 0x01};
|
||||
|
||||
static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
||||
{
|
||||
(void)arg;
|
||||
(void)event_data;
|
||||
|
||||
if (event_base == WIFI_EVENT) {
|
||||
if (event_id == WIFI_EVENT_STA_CONNECTED) {
|
||||
led_set_wifi_connected(true);
|
||||
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
led_set_wifi_connected(false);
|
||||
}
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
led_set_wifi_connected(true);
|
||||
}
|
||||
}
|
||||
|
||||
static void espnow_recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *data, int data_len)
|
||||
{
|
||||
if (!recv_info || !data || data_len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp(recv_info->src_addr, CONFIG_CSI_SEND_MAC_2, 6) != 0) {
|
||||
/* Keep the console focused on the dedicated CSI sender. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (data_len >= (int)sizeof(uint32_t)) {
|
||||
uint32_t cnt = 0;
|
||||
memcpy(&cnt, data, sizeof(cnt));
|
||||
ESP_LOGD(TAG, "ESPNOW RX <- " MACSTR " len=%d cnt=%" PRIu32, MAC2STR(recv_info->src_addr), data_len, cnt);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "ESPNOW RX <- " MACSTR " len=%d", MAC2STR(recv_info->src_addr), data_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void espnow_rx_init(void)
|
||||
{
|
||||
esp_err_t err = esp_now_init();
|
||||
if (err == ESP_ERR_ESPNOW_EXIST) {
|
||||
err = ESP_OK;
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
/*
|
||||
* The current sender keeps ESPNOW encryption disabled.
|
||||
* Setting a PMK here is optional and harmless for the receiver path.
|
||||
*/
|
||||
ESP_ERROR_CHECK(esp_now_set_pmk((uint8_t *)"pmk1234567890123"));
|
||||
ESP_ERROR_CHECK(esp_now_register_recv_cb(espnow_recv_cb));
|
||||
|
||||
/*
|
||||
* Adding a broadcast peer is not strictly required for RX, but it makes the
|
||||
* setup more tolerant across targets and console workflows.
|
||||
*/
|
||||
if (!esp_now_is_peer_exist((const uint8_t[6]) {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
})) {
|
||||
esp_now_peer_info_t peer = {0};
|
||||
memcpy(peer.peer_addr, (const uint8_t[6]) {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
}, 6);
|
||||
peer.channel = 0; /* Follow the current STA channel from the connected AP. */
|
||||
peer.encrypt = false;
|
||||
peer.ifidx = WIFI_IF_STA;
|
||||
ESP_ERROR_CHECK(esp_now_add_peer(&peer));
|
||||
}
|
||||
}
|
||||
|
||||
static const char *channel_name(const uint8_t mac[6])
|
||||
{
|
||||
if (memcmp(mac, s_mac_ap, 6) == 0) {
|
||||
return "AP";
|
||||
}
|
||||
if (memcmp(mac, CONFIG_CSI_SEND_MAC, 6) == 0) {
|
||||
return "MAC_1";
|
||||
}
|
||||
if (memcmp(mac, CONFIG_CSI_SEND_MAC_2, 6) == 0) {
|
||||
return "MAC_2";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
static void on_motion_event(esp_wifi_sensing_fsm_handle_t handle,
|
||||
const uint8_t peer_mac[6],
|
||||
esp_wifi_sensing_fsm_event_t event,
|
||||
uint32_t data,
|
||||
void *usr_data)
|
||||
{
|
||||
(void)handle;
|
||||
(void)usr_data;
|
||||
const char *name = channel_name(peer_mac);
|
||||
bool is_ap_channel = (memcmp(peer_mac, s_mac_ap, 6) == 0);
|
||||
if (event == ESP_WIFI_SENSING_FSM_EVENT_ACTIVE) {
|
||||
if (is_ap_channel) {
|
||||
led_notify_ap_active();
|
||||
}
|
||||
ESP_LOGI(TAG, "[%s] ACTIVE peer=" MACSTR " data=%" PRIu32,
|
||||
name, MAC2STR(peer_mac), data);
|
||||
} else if (event == ESP_WIFI_SENSING_FSM_EVENT_INACTIVE) {
|
||||
if (is_ap_channel) {
|
||||
led_notify_ap_inactive();
|
||||
}
|
||||
ESP_LOGI(TAG, "[%s] INACTIVE peer=" MACSTR,
|
||||
name, MAC2STR(peer_mac));
|
||||
}
|
||||
}
|
||||
|
||||
static void demo_init(void)
|
||||
{
|
||||
/*
|
||||
* Follow the public component workflow:
|
||||
* 1. Resolve the peers used by this demo.
|
||||
* 2. Create one FSM handle.
|
||||
* 3. Add one channel per peer.
|
||||
* 4. Register ACTIVE / INACTIVE callbacks.
|
||||
* 5. Start the FSM and optionally enable router-ping-assisted sampling.
|
||||
*/
|
||||
wifi_ap_record_t ap_info = {0};
|
||||
ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&ap_info));
|
||||
memcpy(s_mac_ap, ap_info.bssid, 6);
|
||||
ESP_LOGI(TAG, "peer mac (AP BSSID): " MACSTR, MAC2STR(ap_info.bssid));
|
||||
ESP_LOGI(TAG, "peer mac 1 (CONFIG_CSI_SEND_MAC): " MACSTR, MAC2STR(CONFIG_CSI_SEND_MAC));
|
||||
ESP_LOGI(TAG, "peer mac 2 (CONFIG_CSI_SEND_MAC_2): " MACSTR, MAC2STR(CONFIG_CSI_SEND_MAC_2));
|
||||
|
||||
esp_wifi_sensing_fsm_config_t cfg = DEFAULT_ESP_WIFI_SENSING_FSM_CONFIG();
|
||||
cfg.max_channel_num = 3;
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_sensing_fsm_create(&cfg, &s_hms));
|
||||
|
||||
/* The demo monitors the connected AP plus two fixed reference peers. */
|
||||
ESP_ERROR_CHECK(esp_wifi_sensing_fsm_add_channel(s_hms, ap_info.bssid));
|
||||
ESP_ERROR_CHECK(esp_wifi_sensing_fsm_add_channel(s_hms, CONFIG_CSI_SEND_MAC));
|
||||
ESP_ERROR_CHECK(esp_wifi_sensing_fsm_add_channel(s_hms, CONFIG_CSI_SEND_MAC_2));
|
||||
|
||||
/* Register the same callback for both state transitions to keep logging symmetric. */
|
||||
ESP_ERROR_CHECK(esp_wifi_sensing_fsm_register_event_cb(s_hms, ESP_WIFI_SENSING_FSM_EVENT_ACTIVE, on_motion_event, NULL));
|
||||
ESP_ERROR_CHECK(esp_wifi_sensing_fsm_register_event_cb(s_hms, ESP_WIFI_SENSING_FSM_EVENT_INACTIVE, on_motion_event, NULL));
|
||||
ESP_ERROR_CHECK(esp_wifi_sensing_fsm_control(s_hms, ESP_WIFI_SENSING_FSM_CTRL_START, NULL));
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"default config: motion_detection_sensitivity=%.3f active_jitter_min=%.3f hold=%" PRIu32 "ms confirm=%d ping=%" PRIu32 "Hz",
|
||||
cfg.default_channel_config.sensitivity,
|
||||
cfg.default_channel_config.active_jitter_min,
|
||||
cfg.default_channel_config.active_filter_ms,
|
||||
CONFIG_ESP_WIFI_SENSING_CONFIRM_COUNT,
|
||||
cfg.ping_frequency_hz);
|
||||
|
||||
esp_err_t ping_err = esp_wifi_sensing_fsm_ping_router_start(s_hms);
|
||||
if (ping_err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "router ping started");
|
||||
} else if (ping_err == ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGW(TAG, "router ping start failed: Wi-Fi STA not connected or no gateway");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "router ping start failed: %s", esp_err_to_name(ping_err));
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Standard ESP-IDF bring-up followed by Wi-Fi connection and sensing start. */
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
ESP_ERROR_CHECK(led_init());
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, wifi_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, wifi_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL));
|
||||
led_set_wifi_connected(false);
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
/* Disable Wi-Fi power save to keep CSI sampling cadence stable. */
|
||||
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
|
||||
espnow_rx_init();
|
||||
demo_init();
|
||||
|
||||
#if CONFIG_ESP_WIFI_SENSING_WEB_SERIAL_ENABLE
|
||||
/* Web UI also maps esp-radar train (TRAIN_START/STOP/REMOVE) and streams wander / train thresholds. */
|
||||
/* Export the same three demo channels to the browser monitor UI. */
|
||||
web_serial_monitor_peer_t serial_peers[] = {
|
||||
{ .name = "AP" },
|
||||
{ .name = "MAC_1" },
|
||||
{ .name = "MAC_2" },
|
||||
};
|
||||
memcpy(serial_peers[0].mac, s_mac_ap, sizeof(s_mac_ap));
|
||||
memcpy(serial_peers[1].mac, CONFIG_CSI_SEND_MAC, sizeof(serial_peers[1].mac));
|
||||
memcpy(serial_peers[2].mac, CONFIG_CSI_SEND_MAC_2, sizeof(serial_peers[2].mac));
|
||||
web_serial_monitor_config_t serial_cfg = {
|
||||
.fsm = s_hms,
|
||||
.peers = serial_peers,
|
||||
.peer_num = sizeof(serial_peers) / sizeof(serial_peers[0]),
|
||||
.stream_period_ms = CONFIG_ESP_WIFI_SENSING_WEB_SERIAL_STREAM_PERIOD_MS,
|
||||
};
|
||||
ESP_ERROR_CHECK(web_serial_monitor_init(&serial_cfg));
|
||||
#endif
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
idf: '>=5.4'
|
||||
espressif/led_strip: "^3.0.0"
|
||||
|
||||
esp_wifi_sensing:
|
||||
version: '>=0.1.1'
|
||||
285
examples/esp-radar/wifi_sensing_demo/main/led_control.c
Normal file
285
examples/esp-radar/wifi_sensing_demo/main/led_control.c
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "led_control.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_LED_TYPE_WS2812
|
||||
#include "led_strip.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "demo_led";
|
||||
|
||||
#define LED_UPDATE_PERIOD_MS 20
|
||||
#define LED_BLINK_PERIOD_MS 500
|
||||
#define LED_BLINK_DUTY_PERCENT 50
|
||||
#define LED_FADE_OUT_TIME_MS 1000
|
||||
|
||||
typedef enum {
|
||||
LED_MOTION_IDLE = 0,
|
||||
LED_MOTION_ACTIVE,
|
||||
LED_MOTION_FADING,
|
||||
} led_motion_state_t;
|
||||
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
bool wifi_connected;
|
||||
led_motion_state_t motion_state;
|
||||
TickType_t fade_start_tick;
|
||||
TaskHandle_t task_handle;
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_LED_TYPE_WS2812
|
||||
led_strip_handle_t strip_handle;
|
||||
uint8_t last_r;
|
||||
uint8_t last_g;
|
||||
uint8_t last_b;
|
||||
#else
|
||||
uint32_t last_duty;
|
||||
#endif
|
||||
} led_ctx_t;
|
||||
|
||||
static led_ctx_t s_led_ctx = {0};
|
||||
static portMUX_TYPE s_led_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_LED_TYPE_GPIO
|
||||
static const ledc_mode_t s_ledc_mode = LEDC_LOW_SPEED_MODE;
|
||||
static const ledc_timer_t s_ledc_timer = LEDC_TIMER_0;
|
||||
static const ledc_channel_t s_ledc_channel = LEDC_CHANNEL_0;
|
||||
static const uint32_t s_ledc_max_duty = (1U << LEDC_TIMER_8_BIT) - 1U;
|
||||
#endif
|
||||
|
||||
static void led_task(void *arg);
|
||||
static void led_apply_state(TickType_t now);
|
||||
|
||||
static void led_trigger_update(void)
|
||||
{
|
||||
if (s_led_ctx.task_handle != NULL) {
|
||||
xTaskNotifyGive(s_led_ctx.task_handle);
|
||||
}
|
||||
}
|
||||
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_LED_TYPE_GPIO
|
||||
static void led_apply_gpio_brightness(float brightness)
|
||||
{
|
||||
if (brightness < 0.0f) {
|
||||
brightness = 0.0f;
|
||||
} else if (brightness > 1.0f) {
|
||||
brightness = 1.0f;
|
||||
}
|
||||
|
||||
uint32_t duty = (uint32_t)(brightness * s_ledc_max_duty + 0.5f);
|
||||
if (duty == s_led_ctx.last_duty) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(ledc_set_duty(s_ledc_mode, s_ledc_channel, duty));
|
||||
ESP_ERROR_CHECK(ledc_update_duty(s_ledc_mode, s_ledc_channel));
|
||||
s_led_ctx.last_duty = duty;
|
||||
}
|
||||
#else
|
||||
static void led_apply_ws2812_color(uint8_t red, uint8_t green, uint8_t blue)
|
||||
{
|
||||
if (red == s_led_ctx.last_r && green == s_led_ctx.last_g && blue == s_led_ctx.last_b) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(led_strip_set_pixel(s_led_ctx.strip_handle, 0, red, green, blue));
|
||||
ESP_ERROR_CHECK(led_strip_refresh(s_led_ctx.strip_handle));
|
||||
s_led_ctx.last_r = red;
|
||||
s_led_ctx.last_g = green;
|
||||
s_led_ctx.last_b = blue;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void led_apply_state(TickType_t now)
|
||||
{
|
||||
bool wifi_connected;
|
||||
led_motion_state_t motion_state;
|
||||
TickType_t fade_start_tick;
|
||||
|
||||
portENTER_CRITICAL(&s_led_lock);
|
||||
wifi_connected = s_led_ctx.wifi_connected;
|
||||
motion_state = s_led_ctx.motion_state;
|
||||
fade_start_tick = s_led_ctx.fade_start_tick;
|
||||
portEXIT_CRITICAL(&s_led_lock);
|
||||
|
||||
if (!wifi_connected) {
|
||||
/* Wi-Fi not ready yet: blink to signal the demo is still connecting. */
|
||||
uint32_t phase_ms = (uint32_t)(now * portTICK_PERIOD_MS) % LED_BLINK_PERIOD_MS;
|
||||
bool on_phase = phase_ms < (LED_BLINK_PERIOD_MS / 2);
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_LED_TYPE_GPIO
|
||||
led_apply_gpio_brightness(on_phase ? (LED_BLINK_DUTY_PERCENT / 100.0f) : 0.0f);
|
||||
#else
|
||||
uint8_t red = (uint8_t)((255U * LED_BLINK_DUTY_PERCENT) / 100U);
|
||||
led_apply_ws2812_color(on_phase ? red : 0, 0, 0);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (motion_state == LED_MOTION_ACTIVE) {
|
||||
/* Motion on the AP channel is mapped to a fully lit "active" state. */
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_LED_TYPE_GPIO
|
||||
led_apply_gpio_brightness(1.0f);
|
||||
#else
|
||||
led_apply_ws2812_color(0, 255, 0);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (motion_state == LED_MOTION_FADING) {
|
||||
/* Fade out after motion stops so short inactive gaps remain readable. */
|
||||
uint32_t elapsed_ms = (uint32_t)((now - fade_start_tick) * portTICK_PERIOD_MS);
|
||||
if (elapsed_ms >= LED_FADE_OUT_TIME_MS) {
|
||||
portENTER_CRITICAL(&s_led_lock);
|
||||
if (s_led_ctx.motion_state == LED_MOTION_FADING) {
|
||||
s_led_ctx.motion_state = LED_MOTION_IDLE;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_led_lock);
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_LED_TYPE_GPIO
|
||||
led_apply_gpio_brightness(0.0f);
|
||||
#else
|
||||
led_apply_ws2812_color(0, 0, 0);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
float brightness = 1.0f - ((float)elapsed_ms / (float)LED_FADE_OUT_TIME_MS);
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_LED_TYPE_GPIO
|
||||
led_apply_gpio_brightness(brightness);
|
||||
#else
|
||||
uint8_t green = (uint8_t)(255.0f * brightness + 0.5f);
|
||||
led_apply_ws2812_color(0, green, 0);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_LED_TYPE_GPIO
|
||||
led_apply_gpio_brightness(0.0f);
|
||||
#else
|
||||
led_apply_ws2812_color(0, 0, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void led_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
while (true) {
|
||||
led_apply_state(xTaskGetTickCount());
|
||||
(void)ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(LED_UPDATE_PERIOD_MS));
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t led_init(void)
|
||||
{
|
||||
if (s_led_ctx.initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_LED_TYPE_GPIO
|
||||
ledc_timer_config_t timer_config = {
|
||||
.speed_mode = s_ledc_mode,
|
||||
.duty_resolution = LEDC_TIMER_8_BIT,
|
||||
.timer_num = s_ledc_timer,
|
||||
.freq_hz = 5000,
|
||||
.clk_cfg = LEDC_AUTO_CLK,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(ledc_timer_config(&timer_config), TAG, "configure LEDC timer failed");
|
||||
|
||||
ledc_channel_config_t channel_config = {
|
||||
.gpio_num = CONFIG_ESP_WIFI_SENSING_DEMO_LED_GPIO,
|
||||
.speed_mode = s_ledc_mode,
|
||||
.channel = s_ledc_channel,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.timer_sel = s_ledc_timer,
|
||||
.duty = 0,
|
||||
.hpoint = 0,
|
||||
.flags.output_invert = 0,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(ledc_channel_config(&channel_config), TAG, "configure LEDC channel failed");
|
||||
#else
|
||||
led_strip_config_t strip_config = {
|
||||
.strip_gpio_num = CONFIG_ESP_WIFI_SENSING_DEMO_LED_GPIO,
|
||||
.max_leds = 1,
|
||||
};
|
||||
#if CONFIG_ESP_WIFI_SENSING_DEMO_WS2812_BACKEND_RMT
|
||||
led_strip_rmt_config_t rmt_config = {
|
||||
.resolution_hz = 10 * 1000 * 1000,
|
||||
.flags.with_dma = false,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(led_strip_new_rmt_device(&strip_config, &rmt_config, &s_led_ctx.strip_handle),
|
||||
TAG, "create RMT strip failed");
|
||||
#else
|
||||
led_strip_spi_config_t spi_config = {
|
||||
.spi_bus = SPI2_HOST,
|
||||
.flags.with_dma = true,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(led_strip_new_spi_device(&strip_config, &spi_config, &s_led_ctx.strip_handle),
|
||||
TAG, "create SPI strip failed");
|
||||
#endif
|
||||
ESP_RETURN_ON_ERROR(led_strip_clear(s_led_ctx.strip_handle), TAG, "clear strip failed");
|
||||
#endif
|
||||
|
||||
BaseType_t task_created = xTaskCreate(led_task, "demo_led", 3072, NULL, 4, &s_led_ctx.task_handle);
|
||||
ESP_RETURN_ON_FALSE(task_created == pdPASS, ESP_ERR_NO_MEM, TAG, "create LED task failed");
|
||||
|
||||
s_led_ctx.initialized = true;
|
||||
s_led_ctx.wifi_connected = false;
|
||||
s_led_ctx.motion_state = LED_MOTION_IDLE;
|
||||
ESP_LOGI(TAG, "status LED initialized on GPIO %d", CONFIG_ESP_WIFI_SENSING_DEMO_LED_GPIO);
|
||||
led_trigger_update();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void led_set_wifi_connected(bool connected)
|
||||
{
|
||||
if (!s_led_ctx.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&s_led_lock);
|
||||
s_led_ctx.wifi_connected = connected;
|
||||
if (!connected) {
|
||||
s_led_ctx.motion_state = LED_MOTION_IDLE;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_led_lock);
|
||||
led_trigger_update();
|
||||
}
|
||||
|
||||
void led_notify_ap_active(void)
|
||||
{
|
||||
if (!s_led_ctx.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&s_led_lock);
|
||||
if (s_led_ctx.wifi_connected) {
|
||||
s_led_ctx.motion_state = LED_MOTION_ACTIVE;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_led_lock);
|
||||
led_trigger_update();
|
||||
}
|
||||
|
||||
void led_notify_ap_inactive(void)
|
||||
{
|
||||
if (!s_led_ctx.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&s_led_lock);
|
||||
if (s_led_ctx.wifi_connected) {
|
||||
s_led_ctx.motion_state = LED_MOTION_FADING;
|
||||
s_led_ctx.fade_start_tick = xTaskGetTickCount();
|
||||
}
|
||||
portEXIT_CRITICAL(&s_led_lock);
|
||||
led_trigger_update();
|
||||
}
|
||||
42
examples/esp-radar/wifi_sensing_demo/main/led_control.h
Normal file
42
examples/esp-radar/wifi_sensing_demo/main/led_control.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize the status LED backend used by the demo.
|
||||
*
|
||||
* The LED policy is intentionally simple for bring-up and public examples:
|
||||
* blinking while Wi-Fi is disconnected, solid on motion, then fade out after
|
||||
* the AP channel becomes inactive again.
|
||||
*/
|
||||
esp_err_t led_init(void);
|
||||
|
||||
/**
|
||||
* @brief Update the Wi-Fi connectivity state shown on the LED.
|
||||
*/
|
||||
void led_set_wifi_connected(bool connected);
|
||||
|
||||
/**
|
||||
* @brief Notify the LED state machine that AP-channel motion became active.
|
||||
*/
|
||||
void led_notify_ap_active(void);
|
||||
|
||||
/**
|
||||
* @brief Notify the LED state machine that AP-channel motion became inactive.
|
||||
*/
|
||||
void led_notify_ap_inactive(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
729
examples/esp-radar/wifi_sensing_demo/main/web_serial_monitor.c
Normal file
729
examples/esp-radar/wifi_sensing_demo/main/web_serial_monitor.c
Normal file
|
|
@ -0,0 +1,729 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#if CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED
|
||||
#include "driver/usb_serial_jtag.h"
|
||||
#endif
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_timer.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "web_serial_monitor.h"
|
||||
|
||||
#define WEB_SERIAL_PREFIX "HMS:"
|
||||
#define WEB_SERIAL_COMMAND_PREFIX "HMSCMD "
|
||||
#define WEB_SERIAL_TASK_STACK_SIZE 4096
|
||||
#define WEB_SERIAL_TASK_PRIORITY 4
|
||||
#define WEB_SERIAL_LINE_BUF_SIZE 512
|
||||
#define WEB_SERIAL_TX_BUF_SIZE 1024
|
||||
#define WEB_SERIAL_RX_POLL_BUF_SIZE 64
|
||||
#define WEB_SERIAL_DEFAULT_PERIOD_MS 50
|
||||
|
||||
static const char *TAG = "web_serial_monitor";
|
||||
|
||||
/*
|
||||
* This module exposes a tiny line-oriented JSON protocol over the serial
|
||||
* console so the browser UI can observe and tune the sensing FSM without any
|
||||
* additional transport layer.
|
||||
*/
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
bool stream_enabled;
|
||||
esp_wifi_sensing_fsm_handle_t fsm;
|
||||
web_serial_monitor_peer_t peers[WEB_SERIAL_MONITOR_MAX_PEERS];
|
||||
size_t peer_num;
|
||||
uint32_t stream_period_ms;
|
||||
TaskHandle_t task;
|
||||
#if CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED
|
||||
bool usb_driver_owned;
|
||||
#endif
|
||||
} web_serial_monitor_ctx_t;
|
||||
|
||||
static web_serial_monitor_ctx_t s_ctx = {0};
|
||||
|
||||
static void handle_command(char *line);
|
||||
|
||||
static esp_err_t configure_stdin_nonblocking(void)
|
||||
{
|
||||
int fd = fileno(stdin);
|
||||
if (fd < 0) {
|
||||
ESP_LOGE(TAG, "failed to get stdin fd");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0) {
|
||||
ESP_LOGE(TAG, "failed to get stdin flags: errno=%d", errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if ((flags & O_NONBLOCK) != 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
||||
ESP_LOGE(TAG, "failed to set stdin nonblocking: errno=%d", errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED
|
||||
static esp_err_t configure_usb_serial_jtag_input(void)
|
||||
{
|
||||
if (usb_serial_jtag_is_driver_installed()) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
usb_serial_jtag_driver_config_t usb_cfg = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT();
|
||||
usb_cfg.rx_buffer_size = 512;
|
||||
usb_cfg.tx_buffer_size = 256;
|
||||
esp_err_t err = usb_serial_jtag_driver_install(&usb_cfg);
|
||||
if (err == ESP_OK) {
|
||||
s_ctx.usb_driver_owned = true;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void process_rx_bytes(char *line_buf, size_t *line_len, const uint8_t *rx_buf, size_t read_len)
|
||||
{
|
||||
if (!line_buf || !line_len || !rx_buf || read_len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Commands are newline-delimited so both terminals and browsers can share the same protocol. */
|
||||
for (size_t i = 0; i < read_len; i++) {
|
||||
char ch = (char)rx_buf[i];
|
||||
if (ch == '\r') {
|
||||
continue;
|
||||
}
|
||||
if (ch == '\n') {
|
||||
line_buf[*line_len] = '\0';
|
||||
handle_command(line_buf);
|
||||
*line_len = 0;
|
||||
continue;
|
||||
}
|
||||
if (*line_len < (WEB_SERIAL_LINE_BUF_SIZE - 1)) {
|
||||
line_buf[(*line_len)++] = ch;
|
||||
} else {
|
||||
*line_len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char *state_name(esp_wifi_sensing_fsm_process_state_t state)
|
||||
{
|
||||
switch (state) {
|
||||
case ESP_WIFI_SENSING_FSM_PROCESS_IDLE:
|
||||
return "IDLE";
|
||||
case ESP_WIFI_SENSING_FSM_PROCESS_DEBOUNCE_ACTIVE:
|
||||
return "DEBOUNCE_ACTIVE";
|
||||
case ESP_WIFI_SENSING_FSM_PROCESS_ACTIVE:
|
||||
return "ACTIVE";
|
||||
case ESP_WIFI_SENSING_FSM_PROCESS_DEBOUNCE_INACTIVE:
|
||||
return "DEBOUNCE_INACTIVE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *init_stage_name(esp_wifi_sensing_fsm_init_stage_t stage)
|
||||
{
|
||||
switch (stage) {
|
||||
case ESP_WIFI_SENSING_FSM_INIT_STAGE_START:
|
||||
return "START";
|
||||
case ESP_WIFI_SENSING_FSM_INIT_STAGE_GOLD_FOUND:
|
||||
return "GOLD_FOUND";
|
||||
case ESP_WIFI_SENSING_FSM_INIT_STAGE_STABLE:
|
||||
return "STABLE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *train_status_name(esp_wifi_sensing_fsm_train_status_t status)
|
||||
{
|
||||
switch (status) {
|
||||
case ESP_WIFI_SENSING_FSM_TRAIN_STATUS_NONE:
|
||||
return "NONE";
|
||||
case ESP_WIFI_SENSING_FSM_TRAIN_STATUS_PROGRESS:
|
||||
return "PROGRESS";
|
||||
case ESP_WIFI_SENSING_FSM_TRAIN_STATUS_COMPLETE:
|
||||
return "COMPLETE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *train_action_name(esp_wifi_sensing_fsm_train_action_t action)
|
||||
{
|
||||
switch (action) {
|
||||
case ESP_WIFI_SENSING_FSM_TRAIN_ACTION_IDLE:
|
||||
return "IDLE";
|
||||
case ESP_WIFI_SENSING_FSM_TRAIN_ACTION_WAIT_BUFFER:
|
||||
return "WAIT_BUFFER";
|
||||
case ESP_WIFI_SENSING_FSM_TRAIN_ACTION_DISCARD_OUTLIER:
|
||||
return "DISCARD_OUTLIER";
|
||||
case ESP_WIFI_SENSING_FSM_TRAIN_ACTION_COLLECT_SAMPLE:
|
||||
return "COLLECT_SAMPLE";
|
||||
case ESP_WIFI_SENSING_FSM_TRAIN_ACTION_ACCUMULATE_BACKGROUND:
|
||||
return "ACCUMULATE_BACKGROUND";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const web_serial_monitor_peer_t *find_peer_by_name_or_mac(const char *token)
|
||||
{
|
||||
if (!token) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < s_ctx.peer_num; i++) {
|
||||
const web_serial_monitor_peer_t *peer = &s_ctx.peers[i];
|
||||
if (strcmp(token, peer->name) == 0) {
|
||||
return peer;
|
||||
}
|
||||
|
||||
char mac_str[18] = {0};
|
||||
snprintf(mac_str, sizeof(mac_str), MACSTR, MAC2STR(peer->mac));
|
||||
if (strcasecmp(token, mac_str) == 0) {
|
||||
return peer;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int write_line(const char *fmt, ...)
|
||||
{
|
||||
char line[WEB_SERIAL_TX_BUF_SIZE] = {0};
|
||||
/* Prefix structured messages so ordinary ESP logs can coexist on the same port. */
|
||||
int prefix_len = snprintf(line, sizeof(line), "%s", WEB_SERIAL_PREFIX);
|
||||
if (prefix_len <= 0 || prefix_len >= (int)sizeof(line)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int body_len = vsnprintf(line + prefix_len, sizeof(line) - prefix_len, fmt, args);
|
||||
va_end(args);
|
||||
if (body_len < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int total_len = prefix_len + body_len;
|
||||
if (total_len >= (int)sizeof(line)) {
|
||||
total_len = sizeof(line) - 1;
|
||||
line[total_len - 1] = '\n';
|
||||
line[total_len] = '\0';
|
||||
}
|
||||
|
||||
int written = (int)fwrite(line, 1, total_len, stdout);
|
||||
fflush(stdout);
|
||||
return written;
|
||||
}
|
||||
|
||||
static void send_error(const char *message)
|
||||
{
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
(void)write_line("{\"type\":\"error\",\"message\":\"%s\"}\n", message);
|
||||
}
|
||||
|
||||
static void send_ack(const char *cmd, bool ok, const char *detail)
|
||||
{
|
||||
(void)write_line("{\"type\":\"ack\",\"cmd\":\"%s\",\"ok\":%s,\"detail\":\"%s\"}\n",
|
||||
cmd ? cmd : "",
|
||||
ok ? "true" : "false",
|
||||
detail ? detail : "");
|
||||
}
|
||||
|
||||
static void send_runtime_config(void)
|
||||
{
|
||||
bool enabled = false;
|
||||
if (esp_wifi_sensing_fsm_get_amplitude_log_enabled(s_ctx.fsm, &enabled) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
(void)write_line("{\"type\":\"runtime\",\"amplitude_log_enabled\":%s}\n", enabled ? "true" : "false");
|
||||
}
|
||||
|
||||
static void send_hello(void)
|
||||
{
|
||||
char peers_json[256] = {0};
|
||||
size_t used = 0;
|
||||
for (size_t i = 0; i < s_ctx.peer_num; i++) {
|
||||
char item[48] = {0};
|
||||
snprintf(item, sizeof(item), "%s\"%s\"", (i == 0) ? "" : ",", s_ctx.peers[i].name);
|
||||
size_t item_len = strlen(item);
|
||||
if ((used + item_len + 1) >= sizeof(peers_json)) {
|
||||
break;
|
||||
}
|
||||
memcpy(peers_json + used, item, item_len);
|
||||
used += item_len;
|
||||
peers_json[used] = '\0';
|
||||
}
|
||||
|
||||
bool enabled = false;
|
||||
(void)esp_wifi_sensing_fsm_get_amplitude_log_enabled(s_ctx.fsm, &enabled);
|
||||
/* Send enough metadata for the web page to populate its initial UI state. */
|
||||
(void)write_line(
|
||||
"{\"type\":\"hello\",\"transport\":\"stdio\",\"target\":\"%s\",\"stream_period_ms\":%" PRIu32 ",\"amplitude_log_enabled\":%s,\"peers\":[%s]}\n",
|
||||
CONFIG_IDF_TARGET,
|
||||
s_ctx.stream_period_ms,
|
||||
enabled ? "true" : "false",
|
||||
peers_json);
|
||||
}
|
||||
|
||||
static void send_channel_config(const web_serial_monitor_peer_t *peer)
|
||||
{
|
||||
if (!peer) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_wifi_sensing_fsm_channel_config_t cfg = {0};
|
||||
if (esp_wifi_sensing_fsm_get_channel_config(s_ctx.fsm, peer->mac, &cfg) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)write_line(
|
||||
"{\"type\":\"config\",\"peer\":\"%s\",\"mac\":\"" MACSTR "\",\"sensitivity\":%.6f,"
|
||||
"\"presence_sensitivity\":%.6f,"
|
||||
"\"active_jitter_min\":%.6f,"
|
||||
"\"active_filter_ms\":%" PRIu32 "}\n",
|
||||
peer->name,
|
||||
MAC2STR(peer->mac),
|
||||
cfg.sensitivity,
|
||||
cfg.presence_sensitivity,
|
||||
cfg.active_jitter_min,
|
||||
cfg.active_filter_ms);
|
||||
}
|
||||
|
||||
static void send_all_configs(void)
|
||||
{
|
||||
for (size_t i = 0; i < s_ctx.peer_num; i++) {
|
||||
send_channel_config(&s_ctx.peers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void send_channel_sample(const web_serial_monitor_peer_t *peer)
|
||||
{
|
||||
if (!peer) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_wifi_sensing_fsm_channel_diag_t diag = {0};
|
||||
if (esp_wifi_sensing_fsm_get_channel_diag(s_ctx.fsm, peer->mac, &diag) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
esp_wifi_sensing_fsm_state_t motion_state = ESP_WIFI_SENSING_FSM_STATE_INACTIVE;
|
||||
if (esp_wifi_sensing_fsm_get_state(s_ctx.fsm, peer->mac, &motion_state) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Ship only the minimum public diagnostics required by the browser UI. */
|
||||
(void)write_line(
|
||||
"{\"type\":\"sample\",\"ts_ms\":%" PRIu64 ",\"peer\":\"%s\",\"mac\":\"" MACSTR "\","
|
||||
"\"motion_status\":%s,\"motion_state\":%d,"
|
||||
"\"jitter_value\":%.6f,"
|
||||
"\"wander_value\":%.6f,"
|
||||
"\"presence_ready\":%s,\"presence_wander_average\":%.6f,\"presence_someone_threshold\":%.6f,\"presence_someone_status\":%s,"
|
||||
"\"train_wander_threshold\":%.6f,\"train_jitter_threshold\":%.6f,\"train_thresholds_valid\":%s,"
|
||||
"\"train_status\":%d,\"train_status_name\":\"%s\",\"train_last_action\":%d,\"train_last_action_name\":\"%s\","
|
||||
"\"train_sample_count\":%" PRIu32 ",\"train_background_count\":%" PRIu32 ","
|
||||
"\"train_background_avg\":%.6f,\"train_last_basis_wander\":%.6f,"
|
||||
"\"smooth_scaled\":%" PRIu32 ",\"enter_level_scaled\":%" PRIu32 ",\"exit_level_scaled\":%" PRIu32 ","
|
||||
"\"state\":%d,\"state_name\":\"%s\",\"init_stage\":%d,\"init_stage_name\":\"%s\"}\n",
|
||||
(uint64_t)(esp_timer_get_time() / 1000ULL),
|
||||
peer->name,
|
||||
MAC2STR(peer->mac),
|
||||
(motion_state == ESP_WIFI_SENSING_FSM_STATE_ACTIVE) ? "true" : "false",
|
||||
(int)motion_state,
|
||||
diag.jitter_value,
|
||||
diag.wander_value,
|
||||
diag.presence_ready ? "true" : "false",
|
||||
diag.presence_wander_average,
|
||||
diag.presence_someone_threshold,
|
||||
diag.presence_someone_status ? "true" : "false",
|
||||
diag.train_wander_threshold,
|
||||
diag.train_jitter_threshold,
|
||||
diag.train_thresholds_valid ? "true" : "false",
|
||||
(int)diag.train_status,
|
||||
train_status_name(diag.train_status),
|
||||
(int)diag.train_last_action,
|
||||
train_action_name(diag.train_last_action),
|
||||
diag.train_sample_count,
|
||||
diag.train_background_count,
|
||||
diag.train_background_avg,
|
||||
diag.train_last_basis_wander,
|
||||
diag.smooth_scaled,
|
||||
diag.enter_level_scaled,
|
||||
diag.exit_level_scaled,
|
||||
(int)diag.state,
|
||||
state_name(diag.state),
|
||||
(int)diag.init_stage,
|
||||
init_stage_name(diag.init_stage));
|
||||
}
|
||||
|
||||
static void apply_cfg_value(esp_wifi_sensing_fsm_channel_config_t *cfg, const char *key, const char *value)
|
||||
{
|
||||
if (!cfg || !key || !value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(key, "motion_sensitivity") == 0) {
|
||||
cfg->sensitivity = strtof(value, NULL);
|
||||
} else if (strcmp(key, "presence_sensitivity") == 0) {
|
||||
cfg->presence_sensitivity = strtof(value, NULL);
|
||||
} else if (strcmp(key, "active_jitter_min") == 0) {
|
||||
cfg->active_jitter_min = strtof(value, NULL);
|
||||
} else if (strcmp(key, "active_filter_ms") == 0) {
|
||||
cfg->active_filter_ms = (uint32_t)strtoul(value, NULL, 10);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_set_cfg(char *args)
|
||||
{
|
||||
char *saveptr = NULL;
|
||||
char *peer_token = strtok_r(args, " ", &saveptr);
|
||||
if (!peer_token) {
|
||||
send_ack("SET_CFG", false, "missing peer");
|
||||
return;
|
||||
}
|
||||
|
||||
const web_serial_monitor_peer_t *peer = find_peer_by_name_or_mac(peer_token);
|
||||
if (!peer) {
|
||||
send_ack("SET_CFG", false, "peer not found");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_wifi_sensing_fsm_channel_config_t cfg = {0};
|
||||
if (esp_wifi_sensing_fsm_get_channel_config(s_ctx.fsm, peer->mac, &cfg) != ESP_OK) {
|
||||
send_ack("SET_CFG", false, "get cfg failed");
|
||||
return;
|
||||
}
|
||||
|
||||
char *token = NULL;
|
||||
while ((token = strtok_r(NULL, " ", &saveptr)) != NULL) {
|
||||
char *sep = strchr(token, '=');
|
||||
if (!sep) {
|
||||
continue;
|
||||
}
|
||||
*sep = '\0';
|
||||
apply_cfg_value(&cfg, token, sep + 1);
|
||||
}
|
||||
|
||||
if (esp_wifi_sensing_fsm_set_channel_config(s_ctx.fsm, peer->mac, &cfg) != ESP_OK) {
|
||||
send_ack("SET_CFG", false, "set cfg failed");
|
||||
return;
|
||||
}
|
||||
|
||||
send_ack("SET_CFG", true, peer->name);
|
||||
send_channel_config(peer);
|
||||
}
|
||||
|
||||
static void handle_command(char *line)
|
||||
{
|
||||
if (!line || strncmp(line, WEB_SERIAL_COMMAND_PREFIX, strlen(WEB_SERIAL_COMMAND_PREFIX)) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* The command grammar stays intentionally small to keep the demo debuggable from a plain terminal. */
|
||||
char *cmd = line + strlen(WEB_SERIAL_COMMAND_PREFIX);
|
||||
if (strcmp(cmd, "HELLO") == 0) {
|
||||
send_hello();
|
||||
send_runtime_config();
|
||||
send_all_configs();
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "START_STREAM") == 0) {
|
||||
s_ctx.stream_enabled = true;
|
||||
send_ack("START_STREAM", true, "stream enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "STOP_STREAM") == 0) {
|
||||
s_ctx.stream_enabled = false;
|
||||
send_ack("STOP_STREAM", true, "stream disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "FSM_START") == 0) {
|
||||
esp_err_t err = esp_wifi_sensing_fsm_control(s_ctx.fsm, ESP_WIFI_SENSING_FSM_CTRL_START, NULL);
|
||||
send_ack("FSM_START", err == ESP_OK, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "FSM_STOP") == 0) {
|
||||
esp_err_t err = esp_wifi_sensing_fsm_control(s_ctx.fsm, ESP_WIFI_SENSING_FSM_CTRL_STOP, NULL);
|
||||
send_ack("FSM_STOP", err == ESP_OK, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "RESET_BASELINE") == 0) {
|
||||
esp_err_t err = esp_wifi_sensing_fsm_control(s_ctx.fsm, ESP_WIFI_SENSING_FSM_CTRL_RESET_BASELINE, NULL);
|
||||
send_ack("RESET_BASELINE", err == ESP_OK, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(cmd, "GET_CFG", 7) == 0) {
|
||||
char *arg = cmd + 7;
|
||||
while (*arg == ' ') {
|
||||
arg++;
|
||||
}
|
||||
if (*arg == '\0' || strcmp(arg, "ALL") == 0) {
|
||||
send_all_configs();
|
||||
send_ack("GET_CFG", true, "all");
|
||||
return;
|
||||
}
|
||||
|
||||
const web_serial_monitor_peer_t *peer = find_peer_by_name_or_mac(arg);
|
||||
if (!peer) {
|
||||
send_ack("GET_CFG", false, "peer not found");
|
||||
return;
|
||||
}
|
||||
send_channel_config(peer);
|
||||
send_ack("GET_CFG", true, peer->name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "GET_RUNTIME") == 0) {
|
||||
send_runtime_config();
|
||||
send_ack("GET_RUNTIME", true, "runtime");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(cmd, "SET_AMPLITUDE_LOG ", 18) == 0) {
|
||||
const char *value = cmd + 18;
|
||||
bool enabled = false;
|
||||
if ((strcmp(value, "1") == 0) || (strcasecmp(value, "true") == 0) || (strcasecmp(value, "on") == 0)) {
|
||||
enabled = true;
|
||||
} else if ((strcmp(value, "0") == 0) || (strcasecmp(value, "false") == 0) || (strcasecmp(value, "off") == 0)) {
|
||||
enabled = false;
|
||||
} else {
|
||||
send_ack("SET_AMPLITUDE_LOG", false, "invalid value");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_wifi_sensing_fsm_set_amplitude_log_enabled(s_ctx.fsm, enabled);
|
||||
send_ack("SET_AMPLITUDE_LOG", err == ESP_OK, esp_err_to_name(err));
|
||||
if (err == ESP_OK) {
|
||||
send_runtime_config();
|
||||
send_hello();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(cmd, "SET_CFG ", 8) == 0) {
|
||||
handle_set_cfg(cmd + 8);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(cmd, "TRAIN_START ", 12) == 0) {
|
||||
const char *arg = cmd + 12;
|
||||
while (*arg == ' ') {
|
||||
arg++;
|
||||
}
|
||||
const web_serial_monitor_peer_t *peer = find_peer_by_name_or_mac(arg);
|
||||
if (!peer) {
|
||||
send_ack("TRAIN_START", false, "peer not found");
|
||||
return;
|
||||
}
|
||||
esp_err_t err = esp_wifi_sensing_fsm_train_start(s_ctx.fsm, peer->mac);
|
||||
send_ack("TRAIN_START", err == ESP_OK, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(cmd, "TRAIN_STOP ", 11) == 0) {
|
||||
const char *arg = cmd + 11;
|
||||
while (*arg == ' ') {
|
||||
arg++;
|
||||
}
|
||||
const web_serial_monitor_peer_t *peer = find_peer_by_name_or_mac(arg);
|
||||
if (!peer) {
|
||||
send_ack("TRAIN_STOP", false, "peer not found");
|
||||
return;
|
||||
}
|
||||
float wander_th = 0.0f;
|
||||
float jitter_th = 0.0f;
|
||||
esp_err_t err = esp_wifi_sensing_fsm_train_stop(s_ctx.fsm, peer->mac, &wander_th, &jitter_th);
|
||||
if (err == ESP_OK) {
|
||||
char detail[96] = {0};
|
||||
snprintf(detail, sizeof(detail), "%s wander=%.4f jitter=%.4f", peer->name, wander_th, jitter_th);
|
||||
send_ack("TRAIN_STOP", true, detail);
|
||||
} else {
|
||||
send_ack("TRAIN_STOP", false, esp_err_to_name(err));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strncmp(cmd, "TRAIN_REMOVE ", 13) == 0) {
|
||||
const char *arg = cmd + 13;
|
||||
while (*arg == ' ') {
|
||||
arg++;
|
||||
}
|
||||
const web_serial_monitor_peer_t *peer = find_peer_by_name_or_mac(arg);
|
||||
if (!peer) {
|
||||
send_ack("TRAIN_REMOVE", false, "peer not found");
|
||||
return;
|
||||
}
|
||||
esp_err_t err = esp_wifi_sensing_fsm_train_remove(s_ctx.fsm, peer->mac);
|
||||
send_ack("TRAIN_REMOVE", err == ESP_OK, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
send_error("unknown command");
|
||||
}
|
||||
|
||||
static void poll_rx_and_handle_commands(void)
|
||||
{
|
||||
static char stdin_line_buf[WEB_SERIAL_LINE_BUF_SIZE] = {0};
|
||||
static size_t stdin_line_len = 0;
|
||||
#if CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED
|
||||
static char usb_line_buf[WEB_SERIAL_LINE_BUF_SIZE] = {0};
|
||||
static size_t usb_line_len = 0;
|
||||
#endif
|
||||
uint8_t rx_buf[WEB_SERIAL_RX_POLL_BUF_SIZE] = {0};
|
||||
|
||||
/* Accept commands from both the standard console and USB Serial/JTAG when available. */
|
||||
ssize_t read_len = read(fileno(stdin), rx_buf, sizeof(rx_buf));
|
||||
if (read_len < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
ESP_LOGD(TAG, "stdin read failed: errno=%d", errno);
|
||||
}
|
||||
} else if (read_len > 0) {
|
||||
process_rx_bytes(stdin_line_buf, &stdin_line_len, rx_buf, (size_t)read_len);
|
||||
}
|
||||
|
||||
#if CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED
|
||||
int usb_read_len = usb_serial_jtag_read_bytes(rx_buf, sizeof(rx_buf), 0);
|
||||
if (usb_read_len > 0) {
|
||||
process_rx_bytes(usb_line_buf, &usb_line_len, rx_buf, (size_t)usb_read_len);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static esp_err_t configure_command_inputs(void)
|
||||
{
|
||||
esp_err_t err = configure_stdin_nonblocking();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
#if CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED
|
||||
err = configure_usb_serial_jtag_input();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void cleanup_command_inputs(void)
|
||||
{
|
||||
#if CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED
|
||||
if (s_ctx.usb_driver_owned) {
|
||||
(void)usb_serial_jtag_driver_uninstall();
|
||||
s_ctx.usb_driver_owned = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static const char *input_mode_name(void)
|
||||
{
|
||||
#if CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED
|
||||
return "stdio + usb_serial_jtag";
|
||||
#else
|
||||
return "stdio";
|
||||
#endif
|
||||
}
|
||||
|
||||
static void web_serial_monitor_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int64_t last_stream_us = 0;
|
||||
|
||||
/* Emit an initial snapshot so the browser can render immediately after connecting. */
|
||||
send_hello();
|
||||
send_runtime_config();
|
||||
send_all_configs();
|
||||
|
||||
while (true) {
|
||||
poll_rx_and_handle_commands();
|
||||
|
||||
int64_t now_us = esp_timer_get_time();
|
||||
if (s_ctx.stream_enabled &&
|
||||
(last_stream_us == 0 || (now_us - last_stream_us) >= ((int64_t)s_ctx.stream_period_ms * 1000LL))) {
|
||||
last_stream_us = now_us;
|
||||
for (size_t i = 0; i < s_ctx.peer_num; i++) {
|
||||
send_channel_sample(&s_ctx.peers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t web_serial_monitor_init(const web_serial_monitor_config_t *config)
|
||||
{
|
||||
if (!config || !config->fsm || !config->peers || config->peer_num == 0 ||
|
||||
config->peer_num > WEB_SERIAL_MONITOR_MAX_PEERS) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (s_ctx.initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
memset(&s_ctx, 0, sizeof(s_ctx));
|
||||
s_ctx.initialized = true;
|
||||
s_ctx.stream_enabled = true;
|
||||
s_ctx.fsm = config->fsm;
|
||||
s_ctx.peer_num = config->peer_num;
|
||||
s_ctx.stream_period_ms = (config->stream_period_ms > 0) ? config->stream_period_ms : WEB_SERIAL_DEFAULT_PERIOD_MS;
|
||||
memcpy(s_ctx.peers, config->peers, config->peer_num * sizeof(web_serial_monitor_peer_t));
|
||||
|
||||
esp_err_t err = configure_command_inputs();
|
||||
if (err != ESP_OK) {
|
||||
memset(&s_ctx, 0, sizeof(s_ctx));
|
||||
return err;
|
||||
}
|
||||
|
||||
BaseType_t ok = xTaskCreate(web_serial_monitor_task,
|
||||
"web_serial_monitor",
|
||||
WEB_SERIAL_TASK_STACK_SIZE,
|
||||
NULL,
|
||||
WEB_SERIAL_TASK_PRIORITY,
|
||||
&s_ctx.task);
|
||||
if (ok != pdPASS) {
|
||||
cleanup_command_inputs();
|
||||
memset(&s_ctx, 0, sizeof(s_ctx));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "web serial monitor started, input=%s, output=stdio, period=%" PRIu32 "ms",
|
||||
input_mode_name(), s_ctx.stream_period_ms);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_wifi_sensing.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Maximum number of peers supported by the demo's browser monitor.
|
||||
*/
|
||||
#define WEB_SERIAL_MONITOR_MAX_PEERS 8
|
||||
|
||||
/**
|
||||
* @brief Peer entry exported to the browser monitor.
|
||||
*/
|
||||
typedef struct {
|
||||
const char *name; /**< Human-readable peer name shown by the UI. */
|
||||
uint8_t mac[6]; /**< Peer MAC address bound to the sensing channel. */
|
||||
} web_serial_monitor_peer_t;
|
||||
|
||||
/**
|
||||
* @brief Configuration passed to `web_serial_monitor_init()`.
|
||||
*/
|
||||
typedef struct {
|
||||
esp_wifi_sensing_fsm_handle_t fsm; /**< Sensing FSM instance to inspect and control. */
|
||||
const web_serial_monitor_peer_t *peers; /**< Peer table visible to the UI. */
|
||||
size_t peer_num; /**< Number of valid entries in `peers`. */
|
||||
uint32_t stream_period_ms; /**< Streaming period in milliseconds. Set `0` to use the default. */
|
||||
} web_serial_monitor_config_t;
|
||||
|
||||
/**
|
||||
* @brief Start the lightweight serial protocol used by the browser monitor.
|
||||
*
|
||||
* The module emits line-delimited JSON messages with the `HMS:` prefix and
|
||||
* accepts line-delimited commands prefixed with `HMSCMD `. This keeps the demo
|
||||
* easy to inspect from a terminal while still being simple for a Web Serial UI
|
||||
* to parse.
|
||||
*
|
||||
* @param config Browser-monitor configuration.
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: `config` is invalid
|
||||
* - ESP_ERR_INVALID_STATE: The monitor is already initialized
|
||||
* - ESP_FAIL: Failed to create the background task or configure inputs
|
||||
*/
|
||||
esp_err_t web_serial_monitor_init(const web_serial_monitor_config_t *config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
12
examples/esp-radar/wifi_sensing_demo/sdkconfig.defaults
Normal file
12
examples/esp-radar/wifi_sensing_demo/sdkconfig.defaults
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# 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
|
||||
CONFIG_ESP32_WIFI_CSI_ENABLED=y
|
||||
CONFIG_ESP_WIFI_SENSING_DEFAULT_PRESENCE_SENSITIVITY=250
|
||||
1585
examples/esp-radar/wifi_sensing_demo/tools/web_serial_monitor.html
Normal file
1585
examples/esp-radar/wifi_sensing_demo/tools/web_serial_monitor.html
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -171,17 +171,17 @@ static void wifi_csi_rx_cb(void *ctx, wifi_csi_info_t *info)
|
|||
#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("type,seq,mac,rssi,rate,noise_floor,fft_gain,agc_gain,channel,local_timestamp,sig_len,rx_format,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);
|
||||
rx_ctrl->timestamp, rx_ctrl->sig_len, rx_ctrl->cur_bb_format);
|
||||
#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("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_format,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",
|
||||
|
|
@ -189,7 +189,7 @@ static void wifi_csi_rx_cb(void *ctx, wifi_csi_info_t *info)
|
|||
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);
|
||||
rx_ctrl->timestamp, rx_ctrl->ant, rx_ctrl->sig_len, rx_ctrl->sig_mode);
|
||||
|
||||
#endif
|
||||
#if (CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61) && CSI_FORCE_LLTF
|
||||
|
|
|
|||
|
|
@ -87,23 +87,23 @@ static void wifi_csi_rx_cb(void *ctx, wifi_csi_info_t *info)
|
|||
#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("type,seq,mac,rssi,rate,noise_floor,fft_gain,agc_gain,channel,local_timestamp,sig_len,rx_format,len,first_word,data\n");
|
||||
}
|
||||
ets_printf("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);
|
||||
rx_ctrl->timestamp, rx_ctrl->sig_len, rx_ctrl->cur_bb_format);
|
||||
#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("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_format,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",
|
||||
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);
|
||||
rx_ctrl->timestamp, rx_ctrl->ant, rx_ctrl->sig_len, rx_ctrl->sig_mode);
|
||||
#endif
|
||||
|
||||
#if (CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61) && CSI_FORCE_LLTF
|
||||
|
|
|
|||
|
|
@ -1,399 +0,0 @@
|
|||
#!/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())
|
||||
2754
examples/get-started/tools/csi_viewer.html
Normal file
2754
examples/get-started/tools/csi_viewer.html
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue