Compare commits
2 Commits
6d1dfe4da6
...
bb6bd568ce
| Author | SHA1 | Date |
|---|---|---|
|
|
bb6bd568ce | |
|
|
0311ad21a1 |
|
|
@ -49,6 +49,11 @@ static struct {
|
|||
struct arg_end *end;
|
||||
} channel_args;
|
||||
|
||||
static struct {
|
||||
struct arg_str *enable;
|
||||
struct arg_end *end;
|
||||
} debug_args;
|
||||
|
||||
static void print_monitor_usage(void) {
|
||||
printf("Usage: monitor <subcommand> [args]\n");
|
||||
printf("Subcommands:\n");
|
||||
|
|
@ -56,6 +61,7 @@ static void print_monitor_usage(void) {
|
|||
printf(" stop Stop Monitor Mode\n");
|
||||
printf(" status Show current status\n");
|
||||
printf(" channel <n> Switch channel (while running)\n");
|
||||
printf(" debug [on|off] Enable/disable debug logging (default: show status)\n");
|
||||
printf(" save Save current config to NVS\n");
|
||||
printf(" reload Reload config from NVS\n");
|
||||
printf(" clear Clear NVS config\n");
|
||||
|
|
@ -99,6 +105,40 @@ static int do_monitor_channel(int argc, char **argv) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int do_monitor_debug(int argc, char **argv) {
|
||||
debug_args.enable = arg_str0(NULL, NULL, "<on|off>", "Enable (on) or disable (off) debug logging");
|
||||
debug_args.end = arg_end(1);
|
||||
|
||||
int nerrors = arg_parse(argc, argv, (void **)&debug_args);
|
||||
if (nerrors > 0) {
|
||||
arg_print_errors(stderr, debug_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (debug_args.enable->count > 0) {
|
||||
const char *value = debug_args.enable->sval[0];
|
||||
bool enable = false;
|
||||
|
||||
if (strcmp(value, "on") == 0 || strcmp(value, "1") == 0 || strcmp(value, "true") == 0) {
|
||||
enable = true;
|
||||
} else if (strcmp(value, "off") == 0 || strcmp(value, "0") == 0 || strcmp(value, "false") == 0) {
|
||||
enable = false;
|
||||
} else {
|
||||
printf("Invalid value '%s'. Use 'on' or 'off'.\n", value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
wifi_ctl_set_monitor_debug(enable);
|
||||
printf("Debug mode %s\n", enable ? "enabled" : "disabled");
|
||||
} else {
|
||||
/* No argument: show current status */
|
||||
bool enabled = wifi_ctl_get_monitor_debug();
|
||||
printf("Debug mode: %s\n", enabled ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_monitor(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
print_monitor_usage();
|
||||
|
|
@ -113,6 +153,7 @@ static int cmd_monitor(int argc, char **argv) {
|
|||
if (strcmp(argv[1], "clear") == 0) { wifi_ctl_param_clear(); printf("Cleared.\n"); return 0; }
|
||||
|
||||
if (strcmp(argv[1], "channel") == 0) return do_monitor_channel(argc - 1, &argv[1]);
|
||||
if (strcmp(argv[1], "debug") == 0) return do_monitor_debug(argc - 1, &argv[1]);
|
||||
|
||||
if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0) {
|
||||
print_monitor_usage();
|
||||
|
|
@ -131,6 +172,9 @@ void register_monitor_cmd(void) {
|
|||
channel_args.channel = arg_int1(NULL, NULL, "<n>", "Channel");
|
||||
channel_args.end = arg_end(1);
|
||||
|
||||
debug_args.enable = arg_str0(NULL, NULL, "<on|off>", "Enable or disable debug logging");
|
||||
debug_args.end = arg_end(1);
|
||||
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "monitor",
|
||||
.help = "Monitor Mode: start, stop, channel, status",
|
||||
|
|
|
|||
|
|
@ -109,6 +109,39 @@ static const uint32_t PHY_RATES_40MHZ_1SS_400NS[] = {
|
|||
320300 // MCS 11
|
||||
};
|
||||
|
||||
// 802.11ax MCS rates for 80MHz, 800ns GI, 1 Spatial Stream
|
||||
// (80MHz rates are approximately 2x 40MHz rates)
|
||||
static const uint32_t PHY_RATES_80MHZ_1SS_800NS[] = {
|
||||
34400, // MCS 0
|
||||
68800, // MCS 1
|
||||
103200, // MCS 2
|
||||
137600, // MCS 3
|
||||
206500, // MCS 4
|
||||
275200, // MCS 5
|
||||
309800, // MCS 6
|
||||
344200, // MCS 7
|
||||
413000, // MCS 8
|
||||
458800, // MCS 9
|
||||
516200, // MCS 10
|
||||
573600 // MCS 11
|
||||
};
|
||||
|
||||
// 802.11ax MCS rates for 80MHz, 400ns GI, 1 Spatial Stream
|
||||
static const uint32_t PHY_RATES_80MHZ_1SS_400NS[] = {
|
||||
38400, // MCS 0
|
||||
76800, // MCS 1
|
||||
115200, // MCS 2
|
||||
153600, // MCS 3
|
||||
230400, // MCS 4
|
||||
307200, // MCS 5
|
||||
345600, // MCS 6
|
||||
384000, // MCS 7
|
||||
460800, // MCS 8
|
||||
512400, // MCS 9
|
||||
576400, // MCS 10
|
||||
640600 // MCS 11
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get device index by MAC address, or create new entry
|
||||
*/
|
||||
|
|
@ -253,15 +286,20 @@ esp_err_t mcs_telemetry_process_frame(const wifi_frame_info_t *frame_info, const
|
|||
mcs_sample_t sample = {0};
|
||||
sample.timestamp_ms = esp_timer_get_time() / 1000;
|
||||
sample.mcs = frame_info->mcs;
|
||||
sample.ss = 1; // TODO: Extract from HT/VHT/HE headers
|
||||
sample.ss = frame_info->spatial_streams; // Now extracted from HT/VHT/HE headers
|
||||
sample.rssi = frame_info->rssi;
|
||||
sample.channel = frame_info->channel;
|
||||
sample.bandwidth = (frame_info->bandwidth == 0) ? MCS_BW_20MHZ :
|
||||
(frame_info->bandwidth == 1) ? MCS_BW_40MHZ : MCS_BW_20MHZ;
|
||||
(frame_info->bandwidth == 1) ? MCS_BW_40MHZ :
|
||||
(frame_info->bandwidth == 2) ? MCS_BW_80MHZ : MCS_BW_20MHZ;
|
||||
sample.frame_len = frame_info->frame_len;
|
||||
sample.is_retry = frame_info->retry;
|
||||
sample.sig_mode = frame_info->sig_mode;
|
||||
|
||||
// Ensure spatial streams is valid (1-8)
|
||||
if (sample.ss < 1) sample.ss = 1;
|
||||
if (sample.ss > MCS_TELEMETRY_MAX_SS) sample.ss = MCS_TELEMETRY_MAX_SS;
|
||||
|
||||
// Calculate PHY rate if we have MCS info
|
||||
if (sample.mcs <= MCS_TELEMETRY_MAX_MCS && sample.ss >= 1 && sample.ss <= MCS_TELEMETRY_MAX_SS) {
|
||||
sample.phy_rate_kbps = mcs_calculate_phy_rate_ax(sample.mcs, sample.ss, sample.bandwidth, frame_info->sgi);
|
||||
|
|
@ -274,10 +312,6 @@ esp_err_t mcs_telemetry_process_frame(const wifi_frame_info_t *frame_info, const
|
|||
mcs_update_device_telemetry(dev_idx, &sample);
|
||||
s_stats.total_frames_captured++;
|
||||
|
||||
// TODO: Parse HT/VHT/HE headers to extract actual SS count
|
||||
// This requires parsing the PLCP/HT Control/VHT Control/HE Control fields
|
||||
// which follow the MAC header in HT/VHT/HE frames
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
|
@ -377,6 +411,8 @@ uint32_t mcs_calculate_phy_rate_ax(uint8_t mcs, uint8_t ss, mcs_bandwidth_t band
|
|||
rate_table = sgi ? PHY_RATES_20MHZ_1SS_400NS : PHY_RATES_20MHZ_1SS_800NS;
|
||||
} else if (bandwidth == MCS_BW_40MHZ) {
|
||||
rate_table = sgi ? PHY_RATES_40MHZ_1SS_400NS : PHY_RATES_40MHZ_1SS_800NS;
|
||||
} else if (bandwidth == MCS_BW_80MHZ) {
|
||||
rate_table = sgi ? PHY_RATES_80MHZ_1SS_400NS : PHY_RATES_80MHZ_1SS_800NS;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,11 +67,12 @@ extern "C" {
|
|||
#define MCS_TELEMETRY_WINDOW_MS 1000
|
||||
|
||||
/**
|
||||
* @brief 802.11ax Bandwidth types (ESP32-C5 supports 20MHz and 40MHz)
|
||||
* @brief 802.11ax Bandwidth types (ESP32-C5 supports 20MHz and 40MHz, but we track 80MHz for compatibility)
|
||||
*/
|
||||
typedef enum {
|
||||
MCS_BW_20MHZ = 0,
|
||||
MCS_BW_40MHZ = 1
|
||||
MCS_BW_40MHZ = 1,
|
||||
MCS_BW_80MHZ = 2
|
||||
} mcs_bandwidth_t;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
idf_component_register(
|
||||
SRCS "sd_card.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES driver fatfs esp_driver_sdspi sdmmc
|
||||
REQUIRES driver fatfs esp_driver_sdspi sdmmc esp_timer
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
#include "sd_card.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
|
|
@ -45,6 +46,19 @@
|
|||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
|
||||
/* Format bytes as human-readable (e.g. 1.2K, 4.5M). Writes into buf, max len chars. */
|
||||
static void fmt_size_human(size_t bytes, char *buf, size_t len) {
|
||||
if (bytes < 1024) {
|
||||
snprintf(buf, len, "%zu B", bytes);
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
snprintf(buf, len, "%.1f K", bytes / 1024.0);
|
||||
} else if (bytes < 1024ULL * 1024 * 1024) {
|
||||
snprintf(buf, len, "%.1f M", bytes / (1024.0 * 1024.0));
|
||||
} else {
|
||||
snprintf(buf, len, "%.1f G", bytes / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
// Pin definitions for SparkFun microSD Transflash Breakout
|
||||
// ESP32-C5: no SDMMC host, use SD SPI mode
|
||||
// SparkFun in SPI: CLK, MOSI(DI), MISO(DO), CS, CD(optional)
|
||||
|
|
@ -85,11 +99,12 @@ static bool s_cd_configured = false;
|
|||
static bool s_spi_bus_inited = false;
|
||||
static sdmmc_card_t *s_card = NULL;
|
||||
static const char *s_mount_point = "/sdcard";
|
||||
static uint64_t s_last_status_check_ms = 0;
|
||||
static bool s_last_status_result = false;
|
||||
#define SD_STATUS_CHECK_INTERVAL_MS 1000 /* Check card status at most once per second */
|
||||
|
||||
static void sd_card_cd_ensure_configured(void) {
|
||||
if (s_cd_configured || SD_CD_PIN < 0) {
|
||||
return;
|
||||
}
|
||||
if (!s_cd_configured && SD_CD_PIN >= 0) {
|
||||
/* Limit shift to 0..63 so compiler does not warn; valid GPIOs are 0..48 */
|
||||
const unsigned int cd_pin = (unsigned int)SD_CD_PIN & 0x3Fu;
|
||||
gpio_config_t io = {
|
||||
|
|
@ -103,13 +118,15 @@ static void sd_card_cd_ensure_configured(void) {
|
|||
s_cd_configured = true;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t sd_card_init(void) {
|
||||
if (s_sd_card_mounted) {
|
||||
ESP_LOGW(TAG, "SD card already initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t sd_card_init(void) {
|
||||
esp_err_t result = ESP_OK;
|
||||
|
||||
if (s_sd_card_mounted) {
|
||||
ESP_LOGW(TAG, "SD card already initialized");
|
||||
result = ESP_OK;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Initializing SD card via SPI...");
|
||||
|
||||
// Initialize SPI bus (required before sdspi mount)
|
||||
|
|
@ -121,11 +138,8 @@ esp_err_t sd_card_init(void) {
|
|||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = 4000,
|
||||
};
|
||||
esp_err_t err = spi_bus_initialize(SDSPI_HOST_ID, &bus_cfg, SPI_DMA_CH_AUTO);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
result = spi_bus_initialize(SDSPI_HOST_ID, &bus_cfg, SPI_DMA_CH_AUTO);
|
||||
if (result == ESP_OK) {
|
||||
s_spi_bus_inited = true;
|
||||
|
||||
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
|
||||
|
|
@ -145,52 +159,102 @@ esp_err_t sd_card_init(void) {
|
|||
.allocation_unit_size = 16 * 1024
|
||||
};
|
||||
|
||||
err = esp_vfs_fat_sdspi_mount(s_mount_point, &host, &slot_config, &mount_config, &s_card);
|
||||
result = esp_vfs_fat_sdspi_mount(s_mount_point, &host, &slot_config, &mount_config, &s_card);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
if (result != ESP_OK) {
|
||||
if (s_spi_bus_inited) {
|
||||
spi_bus_free(SDSPI_HOST_ID);
|
||||
s_spi_bus_inited = false;
|
||||
}
|
||||
if (err == ESP_FAIL) {
|
||||
/* Reset status cache on init failure */
|
||||
s_last_status_result = false;
|
||||
s_last_status_check_ms = 0;
|
||||
if (result == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount filesystem. "
|
||||
"If you want the card to be formatted, set format_if_mount_failed = true.");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
|
||||
"Make sure SD card is inserted and wiring is correct.", esp_err_to_name(err));
|
||||
"Make sure SD card is inserted and wiring is correct.", esp_err_to_name(result));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
} else {
|
||||
sdmmc_card_print_info(stdout, s_card);
|
||||
s_sd_card_mounted = true;
|
||||
/* Reset status cache on successful mount */
|
||||
s_last_status_result = true;
|
||||
s_last_status_check_ms = esp_timer_get_time() / 1000;
|
||||
ESP_LOGI(TAG, "SD card mounted successfully at %s", s_mount_point);
|
||||
return ESP_OK;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(result));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t sd_card_deinit(void) {
|
||||
if (!s_sd_card_mounted) {
|
||||
return ESP_OK;
|
||||
}
|
||||
esp_err_t result = ESP_OK;
|
||||
|
||||
if (s_sd_card_mounted) {
|
||||
ESP_LOGI(TAG, "Unmounting SD card...");
|
||||
esp_err_t ret = esp_vfs_fat_sdcard_unmount(s_mount_point, s_card);
|
||||
if (ret == ESP_OK) {
|
||||
result = esp_vfs_fat_sdcard_unmount(s_mount_point, s_card);
|
||||
if (result == ESP_OK) {
|
||||
s_sd_card_mounted = false;
|
||||
s_card = NULL;
|
||||
/* Reset status cache on unmount */
|
||||
s_last_status_result = false;
|
||||
s_last_status_check_ms = 0;
|
||||
if (s_spi_bus_inited) {
|
||||
spi_bus_free(SDSPI_HOST_ID);
|
||||
s_spi_bus_inited = false;
|
||||
}
|
||||
ESP_LOGI(TAG, "SD card unmounted successfully");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to unmount SD card: %s", esp_err_to_name(ret));
|
||||
ESP_LOGE(TAG, "Failed to unmount SD card: %s", esp_err_to_name(result));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool sd_card_is_ready(void) {
|
||||
return s_sd_card_mounted;
|
||||
bool result = false;
|
||||
|
||||
/* If mounted, assume card is ready (since CD pin doesn't work) */
|
||||
/* Status check is used to detect removal, but we're lenient about failures */
|
||||
if (s_sd_card_mounted && s_card != NULL) {
|
||||
/* Since CD pin doesn't work, probe the card periodically to detect removal */
|
||||
/* Cache the result to avoid checking too frequently (SDMMC commands have overhead) */
|
||||
uint64_t now_ms = esp_timer_get_time() / 1000;
|
||||
uint64_t time_since_check = now_ms - s_last_status_check_ms;
|
||||
|
||||
if (time_since_check >= SD_STATUS_CHECK_INTERVAL_MS) {
|
||||
/* Use CMD13 (SEND_STATUS) to check if card is actually responsive */
|
||||
/* This is a lightweight operation that will fail if card is removed */
|
||||
esp_err_t err = sdmmc_get_status(s_card);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
/* Status check passed - card is definitely ready */
|
||||
s_last_status_result = true;
|
||||
} else {
|
||||
/* Status check failed - but be lenient: only mark as not ready if we've */
|
||||
/* had multiple consecutive failures (to avoid false negatives) */
|
||||
/* For now, if mounted, assume ready - status check failures might be transient */
|
||||
/* Only log a warning, don't block writes */
|
||||
ESP_LOGW(TAG, "SD card status check failed (but assuming ready): %s", esp_err_to_name(err));
|
||||
/* Keep s_last_status_result as true - trust mount status over status check */
|
||||
/* This handles cases where sdmmc_get_status() fails but card is still functional */
|
||||
}
|
||||
s_last_status_check_ms = now_ms;
|
||||
}
|
||||
/* If mounted, always return true (optimistic) - let actual write operations fail if card is gone */
|
||||
result = true;
|
||||
} else {
|
||||
s_last_status_result = false;
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool sd_card_cd_available(void) {
|
||||
|
|
@ -198,42 +262,43 @@ bool sd_card_cd_available(void) {
|
|||
}
|
||||
|
||||
bool sd_card_cd_is_inserted(void) {
|
||||
if (SD_CD_PIN < 0) {
|
||||
return false;
|
||||
}
|
||||
bool result = false;
|
||||
|
||||
if (SD_CD_PIN >= 0) {
|
||||
sd_card_cd_ensure_configured();
|
||||
int level = gpio_get_level(SD_CD_PIN);
|
||||
#if defined(CONFIG_SD_CD_ACTIVE_LOW) && !(CONFIG_SD_CD_ACTIVE_LOW)
|
||||
return (level == 1); /* HIGH = inserted (inverted breakout) */
|
||||
result = (level == 1); /* HIGH = inserted (inverted breakout) */
|
||||
#else
|
||||
return (level == 0); /* LOW = inserted (SparkFun default) */
|
||||
result = (level == 0); /* LOW = inserted (SparkFun default) */
|
||||
#endif
|
||||
}
|
||||
|
||||
int sd_card_cd_get_level(void) {
|
||||
if (SD_CD_PIN < 0) {
|
||||
return -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
int sd_card_cd_get_level(void) {
|
||||
int result = -1;
|
||||
|
||||
if (SD_CD_PIN >= 0) {
|
||||
sd_card_cd_ensure_configured();
|
||||
return gpio_get_level(SD_CD_PIN);
|
||||
result = gpio_get_level(SD_CD_PIN);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t sd_card_get_info(uint64_t *total_bytes, uint64_t *free_bytes) {
|
||||
if (!s_sd_card_mounted) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
||||
|
||||
if (s_sd_card_mounted) {
|
||||
FATFS *fs;
|
||||
DWORD fre_clust, fre_sect, tot_sect;
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "%s", s_mount_point);
|
||||
|
||||
FRESULT res = f_getfree(path, &fre_clust, &fs);
|
||||
if (res != FR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get free space: %d", res);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (res == FR_OK) {
|
||||
tot_sect = (fs->n_fatent - 2) * fs->csize;
|
||||
fre_sect = fre_clust * fs->csize;
|
||||
|
||||
|
|
@ -243,15 +308,20 @@ esp_err_t sd_card_get_info(uint64_t *total_bytes, uint64_t *free_bytes) {
|
|||
if (free_bytes) {
|
||||
*free_bytes = (uint64_t)fre_sect * 512;
|
||||
}
|
||||
result = ESP_OK;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to get free space: %d", res);
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t sd_card_write_file(const char *filename, const void *data, size_t len, bool append) {
|
||||
if (!s_sd_card_mounted) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
||||
|
||||
if (s_sd_card_mounted) {
|
||||
char full_path[128];
|
||||
snprintf(full_path, sizeof(full_path), "%s%s%s", s_mount_point,
|
||||
(filename[0] == '/') ? "" : "/", filename);
|
||||
|
|
@ -263,38 +333,36 @@ esp_err_t sd_card_write_file(const char *filename, const void *data, size_t len,
|
|||
flags |= O_TRUNC;
|
||||
}
|
||||
int fd = open(full_path, flags, 0644);
|
||||
if (fd < 0) {
|
||||
ESP_LOGE(TAG, "Failed to open file for writing: %s", full_path);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (fd >= 0) {
|
||||
ssize_t written = write(fd, data, len);
|
||||
close(fd);
|
||||
|
||||
if (written < 0 || (size_t)written != len) {
|
||||
if (written >= 0 && (size_t)written == len) {
|
||||
ESP_LOGD(TAG, "Wrote %zu bytes to %s", (size_t)written, full_path);
|
||||
result = ESP_OK;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to write all data: wrote %zd of %zu bytes", (ssize_t)written, len);
|
||||
return ESP_FAIL;
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to open file for writing: %s", full_path);
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Wrote %zu bytes to %s", (size_t)written, full_path);
|
||||
return ESP_OK;
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t sd_card_read_file(const char *filename, void *data, size_t len, size_t *bytes_read) {
|
||||
if (!s_sd_card_mounted) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
||||
|
||||
if (s_sd_card_mounted) {
|
||||
char full_path[128];
|
||||
snprintf(full_path, sizeof(full_path), "%s%s%s", s_mount_point,
|
||||
(filename[0] == '/') ? "" : "/", filename);
|
||||
|
||||
FILE *f = fopen(full_path, "r");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open file for reading: %s", full_path);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (f != NULL) {
|
||||
size_t read = fread(data, 1, len, f);
|
||||
fclose(f);
|
||||
|
||||
|
|
@ -303,72 +371,88 @@ esp_err_t sd_card_read_file(const char *filename, void *data, size_t len, size_t
|
|||
}
|
||||
|
||||
ESP_LOGD(TAG, "Read %zu bytes from %s", read, full_path);
|
||||
return ESP_OK;
|
||||
result = ESP_OK;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to open file for reading: %s", full_path);
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t sd_card_get_file_size(const char *filename, size_t *size_bytes) {
|
||||
if (!s_sd_card_mounted || size_bytes == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
||||
|
||||
if (s_sd_card_mounted && size_bytes != NULL) {
|
||||
char full_path[128];
|
||||
snprintf(full_path, sizeof(full_path), "%s%s%s", s_mount_point,
|
||||
(filename[0] == '/') ? "" : "/", filename);
|
||||
|
||||
struct stat st;
|
||||
if (stat(full_path, &st) != 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (stat(full_path, &st) == 0) {
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
*size_bytes = (size_t)st.st_size;
|
||||
return ESP_OK;
|
||||
result = ESP_OK;
|
||||
} else {
|
||||
result = ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
} else {
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t sd_card_read_file_at(const char *filename, size_t offset, void *data, size_t len, size_t *bytes_read) {
|
||||
if (!s_sd_card_mounted) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
||||
|
||||
if (s_sd_card_mounted) {
|
||||
char full_path[128];
|
||||
snprintf(full_path, sizeof(full_path), "%s%s%s", s_mount_point,
|
||||
(filename[0] == '/') ? "" : "/", filename);
|
||||
|
||||
FILE *f = fopen(full_path, "rb");
|
||||
if (f == NULL) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (fseek(f, (long)offset, SEEK_SET) != 0) {
|
||||
fclose(f);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (f != NULL) {
|
||||
if (fseek(f, (long)offset, SEEK_SET) == 0) {
|
||||
size_t n = fread(data, 1, len, f);
|
||||
fclose(f);
|
||||
if (bytes_read) {
|
||||
*bytes_read = n;
|
||||
}
|
||||
return ESP_OK;
|
||||
result = ESP_OK;
|
||||
} else {
|
||||
fclose(f);
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
} else {
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool sd_card_file_exists(const char *filename) {
|
||||
if (!s_sd_card_mounted) {
|
||||
return false;
|
||||
}
|
||||
bool result = false;
|
||||
|
||||
if (s_sd_card_mounted) {
|
||||
char full_path[128];
|
||||
snprintf(full_path, sizeof(full_path), "%s%s%s", s_mount_point,
|
||||
(filename[0] == '/') ? "" : "/", filename);
|
||||
|
||||
struct stat st;
|
||||
return (stat(full_path, &st) == 0);
|
||||
result = (stat(full_path, &st) == 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t sd_card_list_dir(const char *path) {
|
||||
if (!s_sd_card_mounted) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
||||
|
||||
if (s_sd_card_mounted) {
|
||||
char full_path[128];
|
||||
if (!path || path[0] == '\0') {
|
||||
snprintf(full_path, sizeof(full_path), "%s", s_mount_point);
|
||||
|
|
@ -378,10 +462,7 @@ esp_err_t sd_card_list_dir(const char *path) {
|
|||
}
|
||||
|
||||
DIR *d = opendir(full_path);
|
||||
if (!d) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (d != NULL) {
|
||||
struct dirent *e;
|
||||
while ((e = readdir(d)) != NULL) {
|
||||
if (e->d_name[0] == '.') {
|
||||
|
|
@ -397,30 +478,40 @@ esp_err_t sd_card_list_dir(const char *path) {
|
|||
if (S_ISDIR(st.st_mode)) {
|
||||
printf(" %-32s <DIR>\n", e->d_name);
|
||||
} else {
|
||||
printf(" %-32s %10zu bytes\n", e->d_name, (size_t)st.st_size);
|
||||
char hr_size[16];
|
||||
fmt_size_human((size_t)st.st_size, hr_size, sizeof(hr_size));
|
||||
printf(" %-32s %10zu bytes (%s)\n", e->d_name, (size_t)st.st_size, hr_size);
|
||||
}
|
||||
} else {
|
||||
printf(" %-32s ?\n", e->d_name);
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
return ESP_OK;
|
||||
result = ESP_OK;
|
||||
} else {
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t sd_card_delete_file(const char *filename) {
|
||||
if (!s_sd_card_mounted) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
||||
|
||||
if (s_sd_card_mounted) {
|
||||
char full_path[128];
|
||||
snprintf(full_path, sizeof(full_path), "%s%s%s", s_mount_point,
|
||||
(filename[0] == '/') ? "" : "/", filename);
|
||||
|
||||
if (unlink(full_path) != 0) {
|
||||
if (unlink(full_path) == 0) {
|
||||
ESP_LOGI(TAG, "Deleted file: %s", full_path);
|
||||
result = ESP_OK;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to delete file: %s", full_path);
|
||||
return ESP_FAIL;
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Deleted file: %s", full_path);
|
||||
return ESP_OK;
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,26 @@ bool wifi_cfg_set_monitor_channel(uint8_t channel) {
|
|||
return (err == ESP_OK);
|
||||
}
|
||||
|
||||
bool wifi_cfg_get_monitor_channel(uint8_t *channel_out) {
|
||||
bool result = false;
|
||||
|
||||
if (channel_out != NULL) {
|
||||
nvs_handle_t h;
|
||||
uint8_t saved_val = DEFAULT_MONITOR_CHANNEL;
|
||||
|
||||
if (nvs_open(NVS_NS, NVS_READONLY, &h) == ESP_OK) {
|
||||
esp_err_t err = nvs_get_u8(h, "mon_chan", &saved_val);
|
||||
nvs_close(h);
|
||||
if (err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
*channel_out = saved_val;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void wifi_cfg_clear_monitor_channel(void) {
|
||||
nvs_handle_t h;
|
||||
if (nvs_open(NVS_NS, NVS_READWRITE, &h) == ESP_OK) {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ bool wifi_cfg_set_password(const char *password);
|
|||
|
||||
// Monitor Specific
|
||||
bool wifi_cfg_set_monitor_channel(uint8_t channel);
|
||||
bool wifi_cfg_get_monitor_channel(uint8_t *channel_out);
|
||||
void wifi_cfg_clear_monitor_channel(void);
|
||||
bool wifi_cfg_monitor_channel_is_unsaved(uint8_t current_val);
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,12 @@
|
|||
#include "wifi_controller.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_timer.h"
|
||||
#include "inttypes.h"
|
||||
#include <string.h>
|
||||
#include "wifi_cfg.h"
|
||||
|
|
@ -55,7 +57,16 @@
|
|||
#include "sd_card.h"
|
||||
|
||||
#define FIWI_TELEMETRY_FILE "fiwi-telemetry"
|
||||
#define FIWI_TELEMETRY_JSON_BUF_SIZE 4096
|
||||
#define TELEMETRY_JSON_BUF_SIZE 4096
|
||||
|
||||
/* SD card write optimization:
|
||||
* - 16KB batch size: Optimal for high-frequency telemetry (4KB-64KB range, sweet spot 4-16KB)
|
||||
* - Multiple of 512 bytes (SD sector size): 16KB = 32 sectors, ensures good alignment
|
||||
* - Balances performance vs RAM usage for embedded systems
|
||||
* - For ESP32: 16KB is reasonable RAM usage and provides excellent write efficiency
|
||||
*/
|
||||
#define TELEMETRY_BATCH_SIZE (16 * 1024) /* 16KB batch buffer for SD card writes */
|
||||
#define TELEMETRY_BATCH_FLUSH_INTERVAL_MS 5000 /* Flush batch every 5 seconds or when 80% full */
|
||||
|
||||
static const char *TAG = "WIFI_CTL";
|
||||
|
||||
|
|
@ -66,6 +77,29 @@ static uint8_t s_monitor_channel_staging = 6;
|
|||
static bool s_monitor_enabled = false;
|
||||
static uint32_t s_monitor_frame_count = 0;
|
||||
static TaskHandle_t s_monitor_stats_task_handle = NULL;
|
||||
static bool s_monitor_debug = false; /* Debug mode: enable serial logging */
|
||||
|
||||
/* Telemetry rate tracking (Welford algorithm) */
|
||||
typedef struct {
|
||||
uint64_t total_bytes; /* Total bytes generated/written */
|
||||
double mean_rate_bps; /* Mean rate in bytes per second */
|
||||
double m2; /* Sum of squares of differences (for variance) */
|
||||
uint32_t sample_count; /* Number of samples */
|
||||
uint64_t last_update_ms; /* Last update timestamp */
|
||||
} rate_tracker_t;
|
||||
|
||||
static rate_tracker_t s_telemetry_gen_rate = {0};
|
||||
static rate_tracker_t s_sd_write_rate = {0};
|
||||
static rate_tracker_t s_frame_rate = {0};
|
||||
static uint32_t s_last_frame_count_for_rate = 0;
|
||||
|
||||
/* Batch buffer for telemetry (shared between task and flush function) */
|
||||
static char s_batch_buf[TELEMETRY_BATCH_SIZE];
|
||||
static size_t s_batch_offset = 0;
|
||||
static SemaphoreHandle_t s_batch_mutex = NULL;
|
||||
|
||||
/* Static flush buffer to avoid large stack allocations (16KB) */
|
||||
static char s_flush_buf[TELEMETRY_BATCH_SIZE];
|
||||
|
||||
// --- Event Handler ---
|
||||
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
|
||||
|
|
@ -81,11 +115,13 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t e
|
|||
|
||||
// ... [Log Collapse / Monitor Callback Logic] ...
|
||||
static void log_collapse_event(uint32_t nav_duration_us, int rssi, int retry) {
|
||||
if (s_monitor_debug) { /* Only log in debug mode */
|
||||
gps_timestamp_t ts = gps_get_timestamp();
|
||||
int64_t now_ms = ts.gps_us / 1000;
|
||||
ESP_LOGI(TAG, "COLLAPSE: Time=%" PRId64 "ms, Sync=%d, Dur=%lu us, RSSI=%d, Retry=%d",
|
||||
now_ms, ts.synced ? 1 : 0, nav_duration_us, rssi, retry);
|
||||
}
|
||||
}
|
||||
|
||||
static void monitor_frame_callback(const wifi_frame_info_t *frame, const uint8_t *payload, uint16_t len) {
|
||||
(void)payload;
|
||||
|
|
@ -99,42 +135,249 @@ static void monitor_frame_callback(const wifi_frame_info_t *frame, const uint8_t
|
|||
mcs_telemetry_process_frame(frame, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update rate tracker using Welford's online algorithm
|
||||
* @param tracker Rate tracker to update
|
||||
* @param bytes Bytes generated/written in this interval
|
||||
* @param interval_ms Time interval in milliseconds
|
||||
*/
|
||||
static void update_rate_tracker(rate_tracker_t *tracker, size_t bytes, uint32_t interval_ms) {
|
||||
if (interval_ms > 0) {
|
||||
double rate_bps = (bytes * 1000.0) / interval_ms; /* Convert to bytes per second */
|
||||
tracker->total_bytes += bytes;
|
||||
tracker->sample_count++;
|
||||
|
||||
/* Welford's online algorithm for running mean and variance */
|
||||
double delta = rate_bps - tracker->mean_rate_bps;
|
||||
tracker->mean_rate_bps += delta / tracker->sample_count;
|
||||
double delta2 = rate_bps - tracker->mean_rate_bps;
|
||||
tracker->m2 += delta * delta2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Flush pending telemetry batch to SD card
|
||||
*/
|
||||
static void flush_telemetry_batch(void) {
|
||||
size_t offset = 0;
|
||||
|
||||
if (s_batch_mutex != NULL) {
|
||||
if (xSemaphoreTake(s_batch_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
offset = s_batch_offset;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
}
|
||||
|
||||
if (offset > 0 && sd_card_is_ready()) {
|
||||
size_t flush_size = 0;
|
||||
|
||||
if (xSemaphoreTake(s_batch_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
/* Copy batch to static buffer for safe write */
|
||||
memcpy(s_flush_buf, s_batch_buf, offset);
|
||||
flush_size = offset;
|
||||
s_batch_offset = 0;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
}
|
||||
|
||||
if (flush_size > 0) {
|
||||
if (sd_card_write_file(FIWI_TELEMETRY_FILE, s_flush_buf, flush_size, true) == ESP_OK) {
|
||||
uint64_t now_ms = esp_timer_get_time() / 1000;
|
||||
uint32_t write_interval_ms = (uint32_t)(now_ms - s_sd_write_rate.last_update_ms);
|
||||
if (write_interval_ms > 0) {
|
||||
update_rate_tracker(&s_sd_write_rate, flush_size, write_interval_ms);
|
||||
s_sd_write_rate.last_update_ms = now_ms;
|
||||
}
|
||||
if (s_monitor_debug) {
|
||||
ESP_LOGD(TAG, "Batch flushed on stop: %zu bytes", flush_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void monitor_stats_task(void *arg) {
|
||||
(void)arg;
|
||||
static char json_buf[FIWI_TELEMETRY_JSON_BUF_SIZE];
|
||||
static char json_buf[TELEMETRY_JSON_BUF_SIZE];
|
||||
uint32_t flush_count = 0;
|
||||
uint32_t last_frame_count = 0;
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
uint64_t last_batch_flush_ms = 0;
|
||||
uint64_t last_stats_log_ms = 0;
|
||||
bool task_running = true;
|
||||
|
||||
/* Initialize rate trackers */
|
||||
uint64_t start_ms = esp_timer_get_time() / 1000;
|
||||
s_telemetry_gen_rate.last_update_ms = start_ms;
|
||||
s_sd_write_rate.last_update_ms = start_ms;
|
||||
s_frame_rate.last_update_ms = start_ms;
|
||||
s_last_frame_count_for_rate = s_monitor_frame_count;
|
||||
last_batch_flush_ms = start_ms;
|
||||
last_stats_log_ms = start_ms;
|
||||
|
||||
/* Batch mutex should already be created by switch_to_monitor */
|
||||
if (s_batch_mutex == NULL) {
|
||||
ESP_LOGE(TAG, "Batch mutex not initialized");
|
||||
task_running = false;
|
||||
}
|
||||
|
||||
while (task_running && s_batch_mutex != NULL) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000)); /* Check every 1 second for batching */
|
||||
uint64_t now_ms = esp_timer_get_time() / 1000;
|
||||
|
||||
if (s_monitor_frame_count == last_frame_count) {
|
||||
status_led_set_capture_active(false);
|
||||
}
|
||||
last_frame_count = s_monitor_frame_count;
|
||||
wifi_collapse_stats_t stats;
|
||||
if (wifi_monitor_get_stats(&stats) == ESP_OK) {
|
||||
ESP_LOGI("MONITOR", "--- Stats: %lu frames, Retry: %.2f%%, Avg NAV: %u us ---",
|
||||
(unsigned long)stats.total_frames, stats.retry_rate, stats.avg_nav);
|
||||
if (wifi_monitor_is_collapsed()) ESP_LOGW("MONITOR", "⚠️ COLLAPSE DETECTED! ⚠️");
|
||||
|
||||
/* Update frame rate tracker */
|
||||
uint32_t frames_delta = s_monitor_frame_count - s_last_frame_count_for_rate;
|
||||
uint32_t frame_interval_ms = (uint32_t)(now_ms - s_frame_rate.last_update_ms);
|
||||
if (frame_interval_ms > 0) {
|
||||
update_rate_tracker(&s_frame_rate, frames_delta, frame_interval_ms);
|
||||
s_frame_rate.last_update_ms = now_ms;
|
||||
s_last_frame_count_for_rate = s_monitor_frame_count;
|
||||
}
|
||||
/* Write MCS telemetry to fiwi-telemetry on SD card (default on monitor start) */
|
||||
if (sd_card_is_ready() && mcs_telemetry_to_json(json_buf, sizeof(json_buf), "esp32") == ESP_OK) {
|
||||
|
||||
last_frame_count = s_monitor_frame_count;
|
||||
|
||||
/* Generate telemetry JSON (regardless of SD card status) */
|
||||
if (mcs_telemetry_to_json(json_buf, sizeof(json_buf), "esp32") == ESP_OK) {
|
||||
size_t len = strlen(json_buf);
|
||||
if (len > 0) {
|
||||
json_buf[len] = '\n'; /* NDJSON: one object per line */
|
||||
if (sd_card_write_file(FIWI_TELEMETRY_FILE, json_buf, len + 1, true) == ESP_OK) {
|
||||
size_t total_len = len + 1;
|
||||
|
||||
/* Update telemetry generation rate (always track generation) */
|
||||
uint32_t interval_ms = (uint32_t)(now_ms - s_telemetry_gen_rate.last_update_ms);
|
||||
if (interval_ms > 0) {
|
||||
update_rate_tracker(&s_telemetry_gen_rate, total_len, interval_ms);
|
||||
s_telemetry_gen_rate.last_update_ms = now_ms;
|
||||
}
|
||||
|
||||
/* Only write to SD card if ready */
|
||||
bool card_ready = sd_card_is_ready();
|
||||
if (!card_ready && s_monitor_debug) {
|
||||
/* Log when card is not ready (only in debug mode to avoid spam) */
|
||||
static uint64_t last_not_ready_log_ms = 0;
|
||||
uint64_t now_ms_check = esp_timer_get_time() / 1000;
|
||||
if (now_ms_check - last_not_ready_log_ms > 10000) { /* Log at most every 10 seconds */
|
||||
ESP_LOGW(TAG, "SD card not ready, telemetry not being written");
|
||||
last_not_ready_log_ms = now_ms_check;
|
||||
}
|
||||
}
|
||||
if (card_ready) {
|
||||
/* Add to batch buffer (with mutex protection) */
|
||||
if (s_batch_mutex && xSemaphoreTake(s_batch_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
if (s_batch_offset + total_len < TELEMETRY_BATCH_SIZE) {
|
||||
memcpy(s_batch_buf + s_batch_offset, json_buf, total_len);
|
||||
s_batch_offset += total_len;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
} else {
|
||||
/* Batch buffer full, flush immediately */
|
||||
size_t flush_size = s_batch_offset;
|
||||
if (flush_size > 0) {
|
||||
memcpy(s_flush_buf, s_batch_buf, flush_size);
|
||||
s_batch_offset = 0;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
|
||||
esp_err_t write_err = sd_card_write_file(FIWI_TELEMETRY_FILE, s_flush_buf, flush_size, true);
|
||||
if (write_err == ESP_OK) {
|
||||
flush_count++;
|
||||
ESP_LOGD(TAG, "fiwi-telemetry flushed (#%lu)", (unsigned long)flush_count);
|
||||
uint32_t write_interval_ms = (uint32_t)(now_ms - s_sd_write_rate.last_update_ms);
|
||||
if (write_interval_ms > 0) {
|
||||
update_rate_tracker(&s_sd_write_rate, flush_size, write_interval_ms);
|
||||
s_sd_write_rate.last_update_ms = now_ms;
|
||||
}
|
||||
if (s_monitor_debug) {
|
||||
ESP_LOGD(TAG, "Batch flushed: %zu bytes (#%lu)", flush_size, (unsigned long)flush_count);
|
||||
}
|
||||
} else {
|
||||
/* Log write failures - this helps diagnose SD card issues */
|
||||
ESP_LOGE(TAG, "Failed to write telemetry batch (buffer full): %s (%zu bytes)", esp_err_to_name(write_err), flush_size);
|
||||
}
|
||||
} else {
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
}
|
||||
|
||||
/* Add current JSON to fresh batch */
|
||||
if (total_len < TELEMETRY_BATCH_SIZE) {
|
||||
if (xSemaphoreTake(s_batch_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
memcpy(s_batch_buf, json_buf, total_len);
|
||||
s_batch_offset = total_len;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush batch periodically or if buffer is getting full */
|
||||
size_t current_offset = 0;
|
||||
if (s_batch_mutex && xSemaphoreTake(s_batch_mutex, 0) == pdTRUE) {
|
||||
current_offset = s_batch_offset;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
}
|
||||
|
||||
uint32_t time_since_flush_ms = (uint32_t)(now_ms - last_batch_flush_ms);
|
||||
bool should_flush = (time_since_flush_ms >= TELEMETRY_BATCH_FLUSH_INTERVAL_MS) ||
|
||||
(current_offset > TELEMETRY_BATCH_SIZE * 0.8); /* Flush at 80% full */
|
||||
|
||||
if (should_flush && current_offset > 0 && s_batch_mutex && sd_card_is_ready()) {
|
||||
size_t flush_size = 0;
|
||||
|
||||
if (xSemaphoreTake(s_batch_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
memcpy(s_flush_buf, s_batch_buf, current_offset);
|
||||
flush_size = current_offset;
|
||||
s_batch_offset = 0;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
}
|
||||
|
||||
if (flush_size > 0) {
|
||||
esp_err_t write_err = sd_card_write_file(FIWI_TELEMETRY_FILE, s_flush_buf, flush_size, true);
|
||||
if (write_err == ESP_OK) {
|
||||
flush_count++;
|
||||
uint32_t write_interval_ms = (uint32_t)(now_ms - s_sd_write_rate.last_update_ms);
|
||||
if (write_interval_ms > 0) {
|
||||
update_rate_tracker(&s_sd_write_rate, flush_size, write_interval_ms);
|
||||
s_sd_write_rate.last_update_ms = now_ms;
|
||||
}
|
||||
if (s_monitor_debug) {
|
||||
ESP_LOGD(TAG, "Batch flushed: %zu bytes (#%lu, gen: %.1f B/s, write: %.1f B/s)",
|
||||
flush_size, (unsigned long)flush_count,
|
||||
s_telemetry_gen_rate.mean_rate_bps, s_sd_write_rate.mean_rate_bps);
|
||||
}
|
||||
last_batch_flush_ms = now_ms;
|
||||
} else {
|
||||
/* Log write failures - this helps diagnose SD card issues */
|
||||
ESP_LOGE(TAG, "Failed to write telemetry batch: %s (%zu bytes)", esp_err_to_name(write_err), flush_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Log stats periodically (only in debug mode) */
|
||||
uint32_t time_since_log_ms = (uint32_t)(now_ms - last_stats_log_ms);
|
||||
if (s_monitor_debug && time_since_log_ms >= 10000) {
|
||||
wifi_collapse_stats_t stats;
|
||||
if (wifi_monitor_get_stats(&stats) == ESP_OK) {
|
||||
ESP_LOGD("MONITOR", "--- Stats: %lu frames, Retry: %.2f%%, Avg NAV: %u us ---",
|
||||
(unsigned long)stats.total_frames, stats.retry_rate, stats.avg_nav);
|
||||
ESP_LOGD("MONITOR", "Telemetry: gen=%.1f B/s (total=%llu), write=%.1f B/s (total=%llu)",
|
||||
s_telemetry_gen_rate.mean_rate_bps, (unsigned long long)s_telemetry_gen_rate.total_bytes,
|
||||
s_sd_write_rate.mean_rate_bps, (unsigned long long)s_sd_write_rate.total_bytes);
|
||||
if (wifi_monitor_is_collapsed()) {
|
||||
ESP_LOGW("MONITOR", "⚠️ COLLAPSE DETECTED! ⚠️");
|
||||
}
|
||||
}
|
||||
last_stats_log_ms = now_ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helper to apply IP settings ---
|
||||
static void apply_ip_settings(void) {
|
||||
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
if (!netif) return;
|
||||
|
||||
if (netif != NULL) {
|
||||
if (wifi_cfg_get_dhcp()) {
|
||||
esp_netif_dhcpc_start(netif);
|
||||
} else {
|
||||
|
|
@ -153,6 +396,7 @@ static void apply_ip_settings(void) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PUBLIC API IMPLEMENTATION
|
||||
|
|
@ -192,22 +436,36 @@ void wifi_ctl_init(void) {
|
|||
esp_wifi_connect();
|
||||
}
|
||||
|
||||
// Load Staging Params
|
||||
// Load Staging and Active Params from NVS
|
||||
char mode_ignored[16];
|
||||
wifi_cfg_get_mode(mode_ignored, &s_monitor_channel_staging);
|
||||
if (s_monitor_channel_staging == 0) s_monitor_channel_staging = 6;
|
||||
|
||||
// Load active channel from NVS (persists across reboots)
|
||||
uint8_t saved_active_channel = 0;
|
||||
if (wifi_cfg_get_monitor_channel(&saved_active_channel) && saved_active_channel > 0 && saved_active_channel <= 14) {
|
||||
s_monitor_channel_active = saved_active_channel;
|
||||
// Also update staging to match active if staging wasn't set
|
||||
if (s_monitor_channel_staging == 6) {
|
||||
s_monitor_channel_staging = saved_active_channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Mode Control (Core) ---
|
||||
|
||||
esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel, wifi_bandwidth_t bw) {
|
||||
if (channel == 0) channel = s_monitor_channel_staging;
|
||||
esp_err_t result = ESP_OK;
|
||||
|
||||
if (channel == 0) {
|
||||
// Use active channel if set, otherwise fall back to staging
|
||||
channel = (s_monitor_channel_active > 0) ? s_monitor_channel_active : s_monitor_channel_staging;
|
||||
}
|
||||
|
||||
if (s_current_mode == WIFI_CTL_MODE_MONITOR && s_monitor_channel_active == channel) {
|
||||
ESP_LOGW(TAG, "Already in monitor mode (Ch %d)", channel);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
result = ESP_OK;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Switching to MONITOR MODE (Ch %d)", channel);
|
||||
|
||||
iperf_stop();
|
||||
|
|
@ -224,9 +482,8 @@ esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel, wifi_bandwidth_t bw) {
|
|||
status_led_set_capture_active(false);
|
||||
if (wifi_monitor_init(channel, monitor_frame_callback) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to init monitor mode");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
result = ESP_FAIL;
|
||||
} else {
|
||||
/* MCS telemetry -> fiwi-telemetry on SD (default on monitor start) */
|
||||
if (mcs_telemetry_init(NULL) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "MCS telemetry init failed");
|
||||
|
|
@ -238,27 +495,63 @@ esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel, wifi_bandwidth_t bw) {
|
|||
|
||||
if (wifi_monitor_start() != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start monitor mode");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
result = ESP_FAIL;
|
||||
} else {
|
||||
s_monitor_enabled = true;
|
||||
s_current_mode = WIFI_CTL_MODE_MONITOR;
|
||||
s_monitor_channel_active = channel;
|
||||
/* Save active channel to NVS so it persists across reboots */
|
||||
wifi_cfg_set_monitor_channel(channel);
|
||||
status_led_set_state(LED_STATE_MONITORING);
|
||||
|
||||
if (s_monitor_stats_task_handle == NULL) {
|
||||
xTaskCreate(monitor_stats_task, "monitor_stats", 4096, NULL, 5, &s_monitor_stats_task_handle);
|
||||
/* Reset rate trackers when starting monitor */
|
||||
uint64_t start_ms = esp_timer_get_time() / 1000;
|
||||
memset(&s_telemetry_gen_rate, 0, sizeof(rate_tracker_t));
|
||||
memset(&s_sd_write_rate, 0, sizeof(rate_tracker_t));
|
||||
memset(&s_frame_rate, 0, sizeof(rate_tracker_t));
|
||||
s_telemetry_gen_rate.last_update_ms = start_ms;
|
||||
s_sd_write_rate.last_update_ms = start_ms;
|
||||
s_frame_rate.last_update_ms = start_ms;
|
||||
s_last_frame_count_for_rate = s_monitor_frame_count;
|
||||
|
||||
/* Initialize batch buffer and mutex */
|
||||
if (s_batch_mutex == NULL) {
|
||||
s_batch_mutex = xSemaphoreCreateMutex();
|
||||
if (s_batch_mutex == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create batch mutex");
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
}
|
||||
if (result == ESP_OK && s_batch_mutex != NULL) {
|
||||
if (xSemaphoreTake(s_batch_mutex, portMAX_DELAY) == pdTRUE) {
|
||||
s_batch_offset = 0;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
if (result == ESP_OK && s_monitor_stats_task_handle == NULL) {
|
||||
/* Task stack size: 12KB needed for:
|
||||
* - 4KB static json_buf (TELEMETRY_JSON_BUF_SIZE)
|
||||
* - Local variables and function call frames
|
||||
* - ESP-IDF API call overhead (esp_timer_get_time, sd_card_write_file, etc.)
|
||||
* - Mutex operations and nested function calls
|
||||
*/
|
||||
xTaskCreate(monitor_stats_task, "monitor_stats", 12 * 1024, NULL, 5, &s_monitor_stats_task_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t wifi_ctl_switch_to_sta(void) {
|
||||
esp_err_t result = ESP_OK;
|
||||
|
||||
if (s_current_mode == WIFI_CTL_MODE_STA) {
|
||||
ESP_LOGI(TAG, "Already in STA mode");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
result = ESP_OK;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Switching to STA MODE");
|
||||
|
||||
if (s_monitor_stats_task_handle != NULL) {
|
||||
|
|
@ -268,6 +561,8 @@ esp_err_t wifi_ctl_switch_to_sta(void) {
|
|||
|
||||
if (s_monitor_enabled) {
|
||||
status_led_set_capture_active(false);
|
||||
/* Flush any pending telemetry before stopping */
|
||||
flush_telemetry_batch();
|
||||
mcs_telemetry_stop();
|
||||
wifi_monitor_stop();
|
||||
s_monitor_enabled = false;
|
||||
|
|
@ -282,8 +577,10 @@ esp_err_t wifi_ctl_switch_to_sta(void) {
|
|||
|
||||
s_current_mode = WIFI_CTL_MODE_STA;
|
||||
status_led_set_state(LED_STATE_WAITING);
|
||||
result = ESP_OK;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- Wrappers for cmd_monitor.c ---
|
||||
|
|
@ -318,9 +615,39 @@ void wifi_ctl_set_channel(int channel) {
|
|||
ESP_LOGI(TAG, "Switching live channel to %d", channel);
|
||||
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
|
||||
s_monitor_channel_active = (uint8_t)channel;
|
||||
/* Save active channel to NVS so it persists across reboots */
|
||||
wifi_cfg_set_monitor_channel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate WiFi channel frequency in MHz
|
||||
* @param channel Channel number (1-14 for 2.4GHz, 36+ for 5GHz)
|
||||
* @return Frequency in MHz, or 0 if invalid channel
|
||||
*/
|
||||
static uint32_t wifi_channel_to_frequency(uint8_t channel) {
|
||||
uint32_t freq = 0;
|
||||
|
||||
if (channel >= 1 && channel <= 14) {
|
||||
/* 2.4 GHz band: 2407 + (channel * 5) MHz */
|
||||
freq = 2407 + (channel * 5);
|
||||
} else if (channel >= 36 && channel <= 64) {
|
||||
/* 5 GHz UNII-1/2A: 5000 + (channel * 5) MHz */
|
||||
freq = 5000 + (channel * 5);
|
||||
} else if (channel >= 100 && channel <= 144) {
|
||||
/* 5 GHz UNII-2C: 5000 + (channel * 5) MHz */
|
||||
freq = 5000 + (channel * 5);
|
||||
} else if (channel >= 149 && channel <= 165) {
|
||||
/* 5 GHz UNII-3: 5000 + (channel * 5) MHz */
|
||||
freq = 5000 + (channel * 5);
|
||||
} else if (channel >= 169 && channel <= 177) {
|
||||
/* 5 GHz UNII-4: 5000 + (channel * 5) MHz */
|
||||
freq = 5000 + (channel * 5);
|
||||
}
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
void wifi_ctl_status(void) {
|
||||
const char *mode_str = (s_current_mode == WIFI_CTL_MODE_MONITOR) ? "MONITOR" :
|
||||
(s_current_mode == WIFI_CTL_MODE_AP) ? "AP" : "STATION";
|
||||
|
|
@ -328,10 +655,53 @@ void wifi_ctl_status(void) {
|
|||
printf("WiFi Status:\n");
|
||||
printf(" Mode: %s\n", mode_str);
|
||||
if (s_current_mode == WIFI_CTL_MODE_MONITOR) {
|
||||
printf(" Channel: %d\n", s_monitor_channel_active);
|
||||
printf(" Frames: %lu\n", (unsigned long)s_monitor_frame_count);
|
||||
uint8_t channel = s_monitor_channel_active;
|
||||
uint32_t freq_mhz = wifi_channel_to_frequency(channel);
|
||||
|
||||
/* Display channel info: channel N (FREQ MHz), width: 20 MHz, center1: FREQ MHz */
|
||||
/* Note: Monitor mode currently uses single channel (20 MHz width) */
|
||||
if (freq_mhz > 0) {
|
||||
printf(" Channel: %d (%" PRIu32 " MHz), width: 20 MHz, center1: %" PRIu32 " MHz\n",
|
||||
channel, freq_mhz, freq_mhz);
|
||||
} else {
|
||||
printf(" Channel: %d\n", channel);
|
||||
}
|
||||
/* Get frame rate (frames per second) */
|
||||
double frame_rate_fps = 0.0;
|
||||
if (s_frame_rate.sample_count > 0) {
|
||||
frame_rate_fps = s_frame_rate.mean_rate_bps; /* Already in per-second units for frames */
|
||||
}
|
||||
printf(" Frames: %lu, %.1f fps\n", (unsigned long)s_monitor_frame_count, frame_rate_fps);
|
||||
|
||||
/* Show telemetry rate statistics */
|
||||
uint64_t gen_bytes = 0, write_bytes = 0;
|
||||
double gen_rate = 0.0, write_rate = 0.0;
|
||||
wifi_ctl_get_telemetry_gen_stats(&gen_bytes, &gen_rate);
|
||||
wifi_ctl_get_sd_write_stats(&write_bytes, &write_rate);
|
||||
|
||||
/* Get bytes pending in batch buffer (awaiting flush) */
|
||||
size_t bytes_in_batch = 0;
|
||||
if (s_batch_mutex != NULL && xSemaphoreTake(s_batch_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
bytes_in_batch = s_batch_offset;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
}
|
||||
|
||||
printf(" Telemetry (SD Card = %s):\n", sd_card_is_ready() ? "ready" : "not ready");
|
||||
printf(" Generated: %llu bytes, %.1f B/s\n", (unsigned long long)gen_bytes, gen_rate);
|
||||
printf(" Written: %llu bytes, %.1f B/s\n", (unsigned long long)write_bytes, write_rate);
|
||||
if (gen_bytes > write_bytes) {
|
||||
/* Account for bytes in batch buffer - they're not dropped, just pending */
|
||||
uint64_t pending = (uint64_t)bytes_in_batch;
|
||||
uint64_t dropped = (gen_bytes > write_bytes + pending) ? (gen_bytes - write_bytes - pending) : 0;
|
||||
if (dropped > 0) {
|
||||
printf(" Dropped: %llu bytes (%.1f%%)\n", (unsigned long long)dropped,
|
||||
(dropped * 100.0) / gen_bytes);
|
||||
}
|
||||
if (pending > 0) {
|
||||
printf(" Pending: %zu bytes (in batch buffer)\n", bytes_in_batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
printf(" Staging Ch: %d\n", s_monitor_channel_staging);
|
||||
}
|
||||
|
||||
// --- Params (NVS) ---
|
||||
|
|
@ -342,8 +712,15 @@ bool wifi_ctl_param_is_unsaved(void) {
|
|||
|
||||
void wifi_ctl_param_save(const char *dummy) {
|
||||
(void)dummy;
|
||||
if (wifi_cfg_set_monitor_channel(s_monitor_channel_staging)) {
|
||||
ESP_LOGI(TAG, "Monitor channel (%d) saved to NVS", s_monitor_channel_staging);
|
||||
/* If monitor mode is running, save the active channel; otherwise save staging */
|
||||
uint8_t channel_to_save = (s_current_mode == WIFI_CTL_MODE_MONITOR) ?
|
||||
s_monitor_channel_active : s_monitor_channel_staging;
|
||||
if (wifi_cfg_set_monitor_channel(channel_to_save)) {
|
||||
ESP_LOGI(TAG, "Monitor channel (%d) saved to NVS", channel_to_save);
|
||||
/* Update staging to match if we saved active */
|
||||
if (s_current_mode == WIFI_CTL_MODE_MONITOR) {
|
||||
s_monitor_channel_staging = channel_to_save;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "No changes to save.");
|
||||
}
|
||||
|
|
@ -354,7 +731,17 @@ void wifi_ctl_param_init(void) {
|
|||
uint8_t ch = 0;
|
||||
wifi_cfg_get_mode(mode_ignored, &ch);
|
||||
if (ch > 0) s_monitor_channel_staging = ch;
|
||||
ESP_LOGI(TAG, "Reloaded monitor channel: %d", s_monitor_channel_staging);
|
||||
|
||||
// Reload active channel from NVS
|
||||
uint8_t saved_active_channel = 0;
|
||||
if (wifi_cfg_get_monitor_channel(&saved_active_channel) && saved_active_channel > 0 && saved_active_channel <= 14) {
|
||||
s_monitor_channel_active = saved_active_channel;
|
||||
// Update staging to match active if staging wasn't set
|
||||
if (s_monitor_channel_staging == 6 && ch == 0) {
|
||||
s_monitor_channel_staging = saved_active_channel;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Reloaded monitor channel: active=%d, staging=%d", s_monitor_channel_active, s_monitor_channel_staging);
|
||||
}
|
||||
|
||||
void wifi_ctl_param_clear(void) {
|
||||
|
|
@ -368,6 +755,75 @@ void wifi_ctl_param_clear(void) {
|
|||
wifi_ctl_mode_t wifi_ctl_get_mode(void) { return s_current_mode; }
|
||||
int wifi_ctl_get_channel(void) { return s_monitor_channel_active; }
|
||||
|
||||
/**
|
||||
* @brief Get telemetry generation rate statistics
|
||||
* @param total_bytes Total bytes generated (output)
|
||||
* @param rate_bps Mean generation rate in bytes per second (output)
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t wifi_ctl_get_telemetry_gen_stats(uint64_t *total_bytes, double *rate_bps) {
|
||||
esp_err_t result = ESP_OK;
|
||||
|
||||
if (total_bytes == NULL || rate_bps == NULL) {
|
||||
result = ESP_ERR_INVALID_ARG;
|
||||
} else {
|
||||
if (s_batch_mutex != NULL && xSemaphoreTake(s_batch_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
*total_bytes = s_telemetry_gen_rate.total_bytes;
|
||||
*rate_bps = s_telemetry_gen_rate.mean_rate_bps;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
} else {
|
||||
*total_bytes = s_telemetry_gen_rate.total_bytes;
|
||||
*rate_bps = s_telemetry_gen_rate.mean_rate_bps;
|
||||
}
|
||||
result = ESP_OK;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get SD card write rate statistics
|
||||
* @param total_bytes Total bytes written (output)
|
||||
* @param rate_bps Mean write rate in bytes per second (output)
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t wifi_ctl_get_sd_write_stats(uint64_t *total_bytes, double *rate_bps) {
|
||||
esp_err_t result = ESP_OK;
|
||||
|
||||
if (total_bytes == NULL || rate_bps == NULL) {
|
||||
result = ESP_ERR_INVALID_ARG;
|
||||
} else {
|
||||
if (s_batch_mutex != NULL && xSemaphoreTake(s_batch_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||
*total_bytes = s_sd_write_rate.total_bytes;
|
||||
*rate_bps = s_sd_write_rate.mean_rate_bps;
|
||||
xSemaphoreGive(s_batch_mutex);
|
||||
} else {
|
||||
*total_bytes = s_sd_write_rate.total_bytes;
|
||||
*rate_bps = s_sd_write_rate.mean_rate_bps;
|
||||
}
|
||||
result = ESP_OK;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable/disable monitor debug mode (serial logging)
|
||||
* @param enable true to enable debug logging, false to disable
|
||||
*/
|
||||
void wifi_ctl_set_monitor_debug(bool enable) {
|
||||
s_monitor_debug = enable;
|
||||
wifi_monitor_set_debug(enable); /* Also set debug mode in wifi_monitor */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get monitor debug mode status
|
||||
* @return true if debug mode enabled, false otherwise
|
||||
*/
|
||||
bool wifi_ctl_get_monitor_debug(void) {
|
||||
return s_monitor_debug;
|
||||
}
|
||||
|
||||
// --- Deprecated ---
|
||||
static void auto_monitor_task_func(void *arg) {
|
||||
uint8_t channel = (uint8_t)(uintptr_t)arg;
|
||||
|
|
|
|||
|
|
@ -74,6 +74,14 @@ void wifi_ctl_param_clear(void);
|
|||
wifi_ctl_mode_t wifi_ctl_get_mode(void);
|
||||
int wifi_ctl_get_channel(void);
|
||||
|
||||
// Telemetry rate statistics
|
||||
esp_err_t wifi_ctl_get_telemetry_gen_stats(uint64_t *total_bytes, double *rate_bps);
|
||||
esp_err_t wifi_ctl_get_sd_write_stats(uint64_t *total_bytes, double *rate_bps);
|
||||
|
||||
// Debug mode control
|
||||
void wifi_ctl_set_monitor_debug(bool enable);
|
||||
bool wifi_ctl_get_monitor_debug(void);
|
||||
|
||||
// Deprecated / Compatibility
|
||||
void wifi_ctl_auto_monitor_start(uint8_t channel);
|
||||
|
||||
|
|
|
|||
|
|
@ -56,10 +56,97 @@ uint32_t threshold_duration_multiplier = 2; // NAV > expected * this = mism
|
|||
// Logging control
|
||||
uint32_t log_every_n_mismatches = 1; // Log every Nth mismatch (1 = all, 10 = every 10th)
|
||||
static uint32_t s_mismatch_log_counter = 0;
|
||||
static bool s_monitor_debug = false; // Debug mode: enable serial logging
|
||||
|
||||
// Forward declarations
|
||||
static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type);
|
||||
|
||||
/**
|
||||
* @brief Parse HT/VHT/HE headers to extract PHY parameters
|
||||
* @param payload Raw frame payload
|
||||
* @param len Total frame length
|
||||
* @param mac_hdr_len Length of MAC header (24 or 30 bytes)
|
||||
* @param frame_info Frame info structure to populate
|
||||
*/
|
||||
static void wifi_parse_phy_headers(const uint8_t *payload, uint16_t len, uint16_t mac_hdr_len, wifi_frame_info_t *frame_info) {
|
||||
if (len < mac_hdr_len + 4) {
|
||||
return; // Not enough data for any PHY headers
|
||||
}
|
||||
|
||||
uint16_t offset = mac_hdr_len;
|
||||
|
||||
// Check for QoS Control field (4 bytes) - present in QoS data frames
|
||||
bool has_qos = (frame_info->type == FRAME_TYPE_DATA &&
|
||||
(frame_info->subtype == DATA_QOS_DATA ||
|
||||
frame_info->subtype == DATA_QOS_DATA_CF_ACK ||
|
||||
frame_info->subtype == DATA_QOS_DATA_CF_POLL ||
|
||||
frame_info->subtype == DATA_QOS_DATA_CF_ACK_POLL ||
|
||||
frame_info->subtype == DATA_QOS_NULL ||
|
||||
frame_info->subtype == DATA_QOS_CF_POLL ||
|
||||
frame_info->subtype == DATA_QOS_CF_ACK_POLL));
|
||||
|
||||
if (has_qos && len >= offset + 4) {
|
||||
offset += 4; // Skip QoS Control field
|
||||
}
|
||||
|
||||
// Check for HT Control field (4 bytes) - present if Order bit set
|
||||
// HT Control field format (IEEE 802.11-2016):
|
||||
// Bit 0-3: Control ID (0=HT, 1-3=VHT, 4+=HE)
|
||||
// For VHT/HE: Additional fields for MCS, bandwidth, SGI, NSS
|
||||
bool has_ht_ctrl = frame_info->order;
|
||||
if (has_ht_ctrl && len >= offset + 4) {
|
||||
// HT Control field is little-endian
|
||||
uint32_t ht_ctrl = payload[offset] | (payload[offset + 1] << 8) |
|
||||
(payload[offset + 2] << 16) | (payload[offset + 3] << 24);
|
||||
|
||||
// Control ID is in bits 0-3
|
||||
uint8_t ctrl_id = ht_ctrl & 0x0F;
|
||||
|
||||
if (ctrl_id == 0) {
|
||||
// HT Control (802.11n) - MCS info might be elsewhere
|
||||
frame_info->sig_mode = 1; // HT
|
||||
// Note: For HT, spatial streams are typically in HT Capabilities element
|
||||
// or can be inferred from MCS index (MCS 0-7 = 1 SS, 8-15 = 2 SS, etc.)
|
||||
} else if (ctrl_id >= 1 && ctrl_id <= 3) {
|
||||
// VHT Control (802.11ac) - bits layout varies by variant
|
||||
frame_info->sig_mode = 3; // VHT
|
||||
|
||||
// VHT Control variant 1: MCS in bits 4-7, bandwidth in 8-9, SGI in 10, NSS in 12-14
|
||||
if (ctrl_id == 1) {
|
||||
frame_info->mcs = (ht_ctrl >> 4) & 0x0F;
|
||||
frame_info->bandwidth = (ht_ctrl >> 8) & 0x03;
|
||||
frame_info->sgi = ((ht_ctrl >> 10) & 0x01) != 0;
|
||||
uint8_t nss = (ht_ctrl >> 12) & 0x07;
|
||||
frame_info->spatial_streams = (nss == 0) ? 1 : nss; // NSS encoding varies
|
||||
}
|
||||
} else if (ctrl_id >= 4) {
|
||||
// HE Control (802.11ax)
|
||||
frame_info->sig_mode = 4; // HE
|
||||
|
||||
// HE Control: Similar structure to VHT
|
||||
frame_info->mcs = (ht_ctrl >> 4) & 0x0F;
|
||||
frame_info->bandwidth = (ht_ctrl >> 8) & 0x03;
|
||||
frame_info->sgi = ((ht_ctrl >> 10) & 0x01) != 0;
|
||||
uint8_t nss = (ht_ctrl >> 12) & 0x07;
|
||||
frame_info->spatial_streams = (nss == 0) ? 1 : nss;
|
||||
}
|
||||
}
|
||||
|
||||
// For HT frames without HT Control, try to infer from rate/MCS
|
||||
// MCS index encoding: MCS 0-7 = 1 SS, 8-15 = 2 SS, 16-23 = 3 SS, 24-31 = 4 SS
|
||||
if (frame_info->sig_mode == 1 && frame_info->mcs > 0) {
|
||||
if (frame_info->mcs < 8) {
|
||||
frame_info->spatial_streams = 1;
|
||||
} else if (frame_info->mcs < 16) {
|
||||
frame_info->spatial_streams = 2;
|
||||
} else if (frame_info->mcs < 24) {
|
||||
frame_info->spatial_streams = 3;
|
||||
} else {
|
||||
frame_info->spatial_streams = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse 802.11 MAC header
|
||||
*/
|
||||
|
|
@ -103,10 +190,21 @@ esp_err_t wifi_parse_frame(const uint8_t *payload, uint16_t len, wifi_frame_info
|
|||
|
||||
// Check for Address 4 (only present if To DS and From DS both set)
|
||||
frame_info->has_addr4 = frame_info->to_ds && frame_info->from_ds;
|
||||
uint16_t mac_hdr_len = frame_info->has_addr4 ? 30 : 24;
|
||||
if (frame_info->has_addr4 && len >= 30) {
|
||||
memcpy(frame_info->addr4, &payload[24], 6);
|
||||
}
|
||||
|
||||
// Initialize PHY parameters
|
||||
frame_info->spatial_streams = 1; // Default to 1 SS
|
||||
frame_info->mcs = 0;
|
||||
frame_info->sig_mode = 0;
|
||||
frame_info->sgi = false;
|
||||
frame_info->bandwidth = 0;
|
||||
|
||||
// Parse HT/VHT/HE headers to extract PHY parameters
|
||||
wifi_parse_phy_headers(payload, len, mac_hdr_len, frame_info);
|
||||
|
||||
frame_info->frame_len = len;
|
||||
|
||||
return ESP_OK;
|
||||
|
|
@ -135,13 +233,27 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
|||
frame_info.timestamp = rx_ctrl->timestamp;
|
||||
|
||||
// Extract PHY rate info from RX control
|
||||
frame_info.mcs = 0;
|
||||
frame_info.rate = rx_ctrl->rate; // This is the rate index
|
||||
frame_info.sig_mode = 0;
|
||||
frame_info.sgi = false;
|
||||
frame_info.bandwidth = 0;
|
||||
|
||||
// Estimate PHY rate from rate index (rough approximation)
|
||||
// If MCS wasn't parsed from headers but we have HT/VHT/HE mode, try to extract from rate
|
||||
if (frame_info.sig_mode > 0 && frame_info.mcs == 0 && rx_ctrl->rate >= 128) {
|
||||
// For HT/VHT/HE, rate >= 128 might encode MCS
|
||||
// ESP-IDF encoding: rate 128+ might be MCS index
|
||||
frame_info.mcs = rx_ctrl->rate - 128;
|
||||
if (frame_info.mcs > 11) frame_info.mcs = 11; // Cap at max MCS
|
||||
}
|
||||
|
||||
// Calculate PHY rate using parsed MCS/spatial streams if available
|
||||
// Otherwise fall back to rate table estimation
|
||||
if (frame_info.sig_mode > 0 && frame_info.mcs > 0 && frame_info.spatial_streams > 0) {
|
||||
// Use parsed MCS/SS info - rate will be calculated accurately in mcs_telemetry
|
||||
// For monitor stats, keep the parsed value (may be 0 if not calculated yet)
|
||||
if (frame_info.phy_rate_kbps == 0) {
|
||||
// Fallback estimate if not set by parsing
|
||||
frame_info.phy_rate_kbps = 100000; // Default estimate
|
||||
}
|
||||
} else {
|
||||
// Estimate PHY rate from rate index (rough approximation for legacy frames)
|
||||
static const uint32_t rate_table[] = {
|
||||
1000, 2000, 5500, 11000, // 1, 2, 5.5, 11 Mbps (DSSS)
|
||||
6000, 9000, 12000, 18000, 24000, 36000, 48000, 54000, // OFDM rates
|
||||
|
|
@ -153,6 +265,7 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
|||
} else {
|
||||
frame_info.phy_rate_kbps = 100000; // Assume 100 Mbps default
|
||||
}
|
||||
}
|
||||
|
||||
// Update statistics
|
||||
stats.total_frames++;
|
||||
|
|
@ -203,7 +316,7 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
|||
|
||||
if (frame_info.duration_id > threshold_duration_mismatch_us) {
|
||||
s_mismatch_log_counter++;
|
||||
if ((s_mismatch_log_counter % log_every_n_mismatches) == 0) {
|
||||
if (s_monitor_debug && (s_mismatch_log_counter % log_every_n_mismatches) == 0) {
|
||||
ESP_LOGW("MONITOR", "Duration mismatch: %s frame, %u bytes @ %u Mbps",
|
||||
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
||||
frame_info.frame_len, phy_rate_mbps);
|
||||
|
|
@ -227,6 +340,7 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
|||
// ---------------------------------------------------------
|
||||
if (frame_info.retry && frame_info.duration_id > threshold_high_nav_us &&
|
||||
phy_rate_mbps < threshold_phy_rate_fallback_mbps) {
|
||||
if (s_monitor_debug) {
|
||||
ESP_LOGW("MONITOR", "⚠⚠⚠ COLLISION DETECTED!");
|
||||
|
||||
// NEW: Log the Attacker MAC
|
||||
|
|
@ -241,6 +355,7 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
|||
frame_info.duration_id, expected_duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Count frame types
|
||||
switch (frame_info.type) {
|
||||
|
|
@ -477,3 +592,19 @@ const char* wifi_frame_type_str(uint8_t type, uint8_t subtype) {
|
|||
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable/disable monitor debug mode (serial logging)
|
||||
* @param enable true to enable debug logging, false to disable
|
||||
*/
|
||||
void wifi_monitor_set_debug(bool enable) {
|
||||
s_monitor_debug = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get monitor debug mode status
|
||||
* @return true if debug mode enabled, false otherwise
|
||||
*/
|
||||
bool wifi_monitor_get_debug(void) {
|
||||
return s_monitor_debug;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,11 +168,12 @@ typedef struct {
|
|||
uint32_t timestamp;
|
||||
|
||||
// PHY rate info
|
||||
uint8_t mcs; // MCS index (for HT/VHT frames)
|
||||
uint8_t mcs; // MCS index (for HT/VHT/HE frames)
|
||||
uint8_t rate; // Legacy rate or rate index
|
||||
uint8_t sig_mode; // Signal mode (0=legacy, 1=HT, 3=VHT)
|
||||
uint8_t sig_mode; // Signal mode (0=legacy, 1=HT, 3=VHT, 4=HE)
|
||||
uint8_t spatial_streams; // Number of spatial streams (NSS) - 1-8
|
||||
bool sgi; // Short Guard Interval
|
||||
uint8_t bandwidth; // 0=20MHz, 1=40MHz, 2=80MHz
|
||||
uint8_t bandwidth; // 0=20MHz, 1=40MHz, 2=80MHz, 3=160MHz
|
||||
uint32_t phy_rate_kbps; // Calculated PHY rate in Kbps (uint32_t to handle >65 Mbps)
|
||||
|
||||
// Frame size
|
||||
|
|
@ -287,6 +288,18 @@ bool wifi_monitor_is_collapsed(void);
|
|||
*/
|
||||
const char* wifi_frame_type_str(uint8_t type, uint8_t subtype);
|
||||
|
||||
/**
|
||||
* @brief Enable/disable monitor debug mode (serial logging)
|
||||
* @param enable true to enable debug logging, false to disable
|
||||
*/
|
||||
void wifi_monitor_set_debug(bool enable);
|
||||
|
||||
/**
|
||||
* @brief Get monitor debug mode status
|
||||
* @return true if debug mode enabled, false otherwise
|
||||
*/
|
||||
bool wifi_monitor_get_debug(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue