Compare commits
No commits in common. "bb6bd568ce558d0e8f717d83eb7bc4283e2b0ec4" and "6d1dfe4da6a1426613610b9012886119846d002d" have entirely different histories.
bb6bd568ce
...
6d1dfe4da6
|
|
@ -49,11 +49,6 @@ static struct {
|
||||||
struct arg_end *end;
|
struct arg_end *end;
|
||||||
} channel_args;
|
} channel_args;
|
||||||
|
|
||||||
static struct {
|
|
||||||
struct arg_str *enable;
|
|
||||||
struct arg_end *end;
|
|
||||||
} debug_args;
|
|
||||||
|
|
||||||
static void print_monitor_usage(void) {
|
static void print_monitor_usage(void) {
|
||||||
printf("Usage: monitor <subcommand> [args]\n");
|
printf("Usage: monitor <subcommand> [args]\n");
|
||||||
printf("Subcommands:\n");
|
printf("Subcommands:\n");
|
||||||
|
|
@ -61,7 +56,6 @@ static void print_monitor_usage(void) {
|
||||||
printf(" stop Stop Monitor Mode\n");
|
printf(" stop Stop Monitor Mode\n");
|
||||||
printf(" status Show current status\n");
|
printf(" status Show current status\n");
|
||||||
printf(" channel <n> Switch channel (while running)\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(" save Save current config to NVS\n");
|
||||||
printf(" reload Reload config from NVS\n");
|
printf(" reload Reload config from NVS\n");
|
||||||
printf(" clear Clear NVS config\n");
|
printf(" clear Clear NVS config\n");
|
||||||
|
|
@ -105,40 +99,6 @@ static int do_monitor_channel(int argc, char **argv) {
|
||||||
return 0;
|
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) {
|
static int cmd_monitor(int argc, char **argv) {
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
print_monitor_usage();
|
print_monitor_usage();
|
||||||
|
|
@ -153,7 +113,6 @@ 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], "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], "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) {
|
if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0) {
|
||||||
print_monitor_usage();
|
print_monitor_usage();
|
||||||
|
|
@ -172,9 +131,6 @@ void register_monitor_cmd(void) {
|
||||||
channel_args.channel = arg_int1(NULL, NULL, "<n>", "Channel");
|
channel_args.channel = arg_int1(NULL, NULL, "<n>", "Channel");
|
||||||
channel_args.end = arg_end(1);
|
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 = {
|
const esp_console_cmd_t cmd = {
|
||||||
.command = "monitor",
|
.command = "monitor",
|
||||||
.help = "Monitor Mode: start, stop, channel, status",
|
.help = "Monitor Mode: start, stop, channel, status",
|
||||||
|
|
|
||||||
|
|
@ -109,39 +109,6 @@ static const uint32_t PHY_RATES_40MHZ_1SS_400NS[] = {
|
||||||
320300 // MCS 11
|
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
|
* @brief Get device index by MAC address, or create new entry
|
||||||
*/
|
*/
|
||||||
|
|
@ -286,20 +253,15 @@ esp_err_t mcs_telemetry_process_frame(const wifi_frame_info_t *frame_info, const
|
||||||
mcs_sample_t sample = {0};
|
mcs_sample_t sample = {0};
|
||||||
sample.timestamp_ms = esp_timer_get_time() / 1000;
|
sample.timestamp_ms = esp_timer_get_time() / 1000;
|
||||||
sample.mcs = frame_info->mcs;
|
sample.mcs = frame_info->mcs;
|
||||||
sample.ss = frame_info->spatial_streams; // Now extracted from HT/VHT/HE headers
|
sample.ss = 1; // TODO: Extract from HT/VHT/HE headers
|
||||||
sample.rssi = frame_info->rssi;
|
sample.rssi = frame_info->rssi;
|
||||||
sample.channel = frame_info->channel;
|
sample.channel = frame_info->channel;
|
||||||
sample.bandwidth = (frame_info->bandwidth == 0) ? MCS_BW_20MHZ :
|
sample.bandwidth = (frame_info->bandwidth == 0) ? MCS_BW_20MHZ :
|
||||||
(frame_info->bandwidth == 1) ? MCS_BW_40MHZ :
|
(frame_info->bandwidth == 1) ? MCS_BW_40MHZ : MCS_BW_20MHZ;
|
||||||
(frame_info->bandwidth == 2) ? MCS_BW_80MHZ : MCS_BW_20MHZ;
|
|
||||||
sample.frame_len = frame_info->frame_len;
|
sample.frame_len = frame_info->frame_len;
|
||||||
sample.is_retry = frame_info->retry;
|
sample.is_retry = frame_info->retry;
|
||||||
sample.sig_mode = frame_info->sig_mode;
|
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
|
// Calculate PHY rate if we have MCS info
|
||||||
if (sample.mcs <= MCS_TELEMETRY_MAX_MCS && sample.ss >= 1 && sample.ss <= MCS_TELEMETRY_MAX_SS) {
|
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);
|
sample.phy_rate_kbps = mcs_calculate_phy_rate_ax(sample.mcs, sample.ss, sample.bandwidth, frame_info->sgi);
|
||||||
|
|
@ -312,6 +274,10 @@ esp_err_t mcs_telemetry_process_frame(const wifi_frame_info_t *frame_info, const
|
||||||
mcs_update_device_telemetry(dev_idx, &sample);
|
mcs_update_device_telemetry(dev_idx, &sample);
|
||||||
s_stats.total_frames_captured++;
|
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;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -411,8 +377,6 @@ 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;
|
rate_table = sgi ? PHY_RATES_20MHZ_1SS_400NS : PHY_RATES_20MHZ_1SS_800NS;
|
||||||
} else if (bandwidth == MCS_BW_40MHZ) {
|
} else if (bandwidth == MCS_BW_40MHZ) {
|
||||||
rate_table = sgi ? PHY_RATES_40MHZ_1SS_400NS : PHY_RATES_40MHZ_1SS_800NS;
|
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 {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,12 +67,11 @@ extern "C" {
|
||||||
#define MCS_TELEMETRY_WINDOW_MS 1000
|
#define MCS_TELEMETRY_WINDOW_MS 1000
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 802.11ax Bandwidth types (ESP32-C5 supports 20MHz and 40MHz, but we track 80MHz for compatibility)
|
* @brief 802.11ax Bandwidth types (ESP32-C5 supports 20MHz and 40MHz)
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
MCS_BW_20MHZ = 0,
|
MCS_BW_20MHZ = 0,
|
||||||
MCS_BW_40MHZ = 1,
|
MCS_BW_40MHZ = 1
|
||||||
MCS_BW_80MHZ = 2
|
|
||||||
} mcs_bandwidth_t;
|
} mcs_bandwidth_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "sd_card.c"
|
SRCS "sd_card.c"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
REQUIRES driver fatfs esp_driver_sdspi sdmmc esp_timer
|
REQUIRES driver fatfs esp_driver_sdspi sdmmc
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@
|
||||||
|
|
||||||
#include "sd_card.h"
|
#include "sd_card.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_timer.h"
|
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
#include "esp_vfs_fat.h"
|
#include "esp_vfs_fat.h"
|
||||||
|
|
@ -46,19 +45,6 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <dirent.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
|
// Pin definitions for SparkFun microSD Transflash Breakout
|
||||||
// ESP32-C5: no SDMMC host, use SD SPI mode
|
// ESP32-C5: no SDMMC host, use SD SPI mode
|
||||||
// SparkFun in SPI: CLK, MOSI(DI), MISO(DO), CS, CD(optional)
|
// SparkFun in SPI: CLK, MOSI(DI), MISO(DO), CS, CD(optional)
|
||||||
|
|
@ -99,162 +85,112 @@ static bool s_cd_configured = false;
|
||||||
static bool s_spi_bus_inited = false;
|
static bool s_spi_bus_inited = false;
|
||||||
static sdmmc_card_t *s_card = NULL;
|
static sdmmc_card_t *s_card = NULL;
|
||||||
static const char *s_mount_point = "/sdcard";
|
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) {
|
static void sd_card_cd_ensure_configured(void) {
|
||||||
if (!s_cd_configured && SD_CD_PIN >= 0) {
|
if (s_cd_configured || SD_CD_PIN < 0) {
|
||||||
/* Limit shift to 0..63 so compiler does not warn; valid GPIOs are 0..48 */
|
return;
|
||||||
const unsigned int cd_pin = (unsigned int)SD_CD_PIN & 0x3Fu;
|
}
|
||||||
gpio_config_t io = {
|
/* Limit shift to 0..63 so compiler does not warn; valid GPIOs are 0..48 */
|
||||||
.pin_bit_mask = (1ULL << cd_pin),
|
const unsigned int cd_pin = (unsigned int)SD_CD_PIN & 0x3Fu;
|
||||||
.mode = GPIO_MODE_INPUT,
|
gpio_config_t io = {
|
||||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
.pin_bit_mask = (1ULL << cd_pin),
|
||||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
.mode = GPIO_MODE_INPUT,
|
||||||
.intr_type = GPIO_INTR_DISABLE,
|
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||||
};
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
if (gpio_config(&io) == ESP_OK) {
|
.intr_type = GPIO_INTR_DISABLE,
|
||||||
s_cd_configured = true;
|
};
|
||||||
}
|
if (gpio_config(&io) == ESP_OK) {
|
||||||
|
s_cd_configured = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t sd_card_init(void) {
|
esp_err_t sd_card_init(void) {
|
||||||
esp_err_t result = ESP_OK;
|
|
||||||
|
|
||||||
if (s_sd_card_mounted) {
|
if (s_sd_card_mounted) {
|
||||||
ESP_LOGW(TAG, "SD card already initialized");
|
ESP_LOGW(TAG, "SD card already initialized");
|
||||||
result = ESP_OK;
|
return ESP_OK;
|
||||||
} else {
|
|
||||||
ESP_LOGI(TAG, "Initializing SD card via SPI...");
|
|
||||||
|
|
||||||
// Initialize SPI bus (required before sdspi mount)
|
|
||||||
spi_bus_config_t bus_cfg = {
|
|
||||||
.mosi_io_num = SDSPI_MOSI_PIN,
|
|
||||||
.miso_io_num = SDSPI_MISO_PIN,
|
|
||||||
.sclk_io_num = SDSPI_CLK_PIN,
|
|
||||||
.quadwp_io_num = -1,
|
|
||||||
.quadhd_io_num = -1,
|
|
||||||
.max_transfer_sz = 4000,
|
|
||||||
};
|
|
||||||
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();
|
|
||||||
host.slot = SDSPI_HOST_ID;
|
|
||||||
|
|
||||||
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
|
||||||
slot_config.gpio_cs = SDSPI_CS_PIN;
|
|
||||||
slot_config.host_id = SDSPI_HOST_ID;
|
|
||||||
// Do not pass gpio_cd to driver: ESP-IDF expects LOW=inserted and blocks init if CD says no card.
|
|
||||||
// We use CD only for status (sd_card_cd_is_inserted) with configurable polarity.
|
|
||||||
slot_config.gpio_cd = SDSPI_SLOT_NO_CD;
|
|
||||||
slot_config.gpio_wp = SDSPI_SLOT_NO_WP;
|
|
||||||
|
|
||||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
|
||||||
.format_if_mount_failed = false,
|
|
||||||
.max_files = 5,
|
|
||||||
.allocation_unit_size = 16 * 1024
|
|
||||||
};
|
|
||||||
|
|
||||||
result = esp_vfs_fat_sdspi_mount(s_mount_point, &host, &slot_config, &mount_config, &s_card);
|
|
||||||
|
|
||||||
if (result != ESP_OK) {
|
|
||||||
if (s_spi_bus_inited) {
|
|
||||||
spi_bus_free(SDSPI_HOST_ID);
|
|
||||||
s_spi_bus_inited = false;
|
|
||||||
}
|
|
||||||
/* 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(result));
|
|
||||||
}
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(result));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
ESP_LOGI(TAG, "Initializing SD card via SPI...");
|
||||||
|
|
||||||
|
// Initialize SPI bus (required before sdspi mount)
|
||||||
|
spi_bus_config_t bus_cfg = {
|
||||||
|
.mosi_io_num = SDSPI_MOSI_PIN,
|
||||||
|
.miso_io_num = SDSPI_MISO_PIN,
|
||||||
|
.sclk_io_num = SDSPI_CLK_PIN,
|
||||||
|
.quadwp_io_num = -1,
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
s_spi_bus_inited = true;
|
||||||
|
|
||||||
|
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
|
||||||
|
host.slot = SDSPI_HOST_ID;
|
||||||
|
|
||||||
|
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||||
|
slot_config.gpio_cs = SDSPI_CS_PIN;
|
||||||
|
slot_config.host_id = SDSPI_HOST_ID;
|
||||||
|
// Do not pass gpio_cd to driver: ESP-IDF expects LOW=inserted and blocks init if CD says no card.
|
||||||
|
// We use CD only for status (sd_card_cd_is_inserted) with configurable polarity.
|
||||||
|
slot_config.gpio_cd = SDSPI_SLOT_NO_CD;
|
||||||
|
slot_config.gpio_wp = SDSPI_SLOT_NO_WP;
|
||||||
|
|
||||||
|
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||||
|
.format_if_mount_failed = false,
|
||||||
|
.max_files = 5,
|
||||||
|
.allocation_unit_size = 16 * 1024
|
||||||
|
};
|
||||||
|
|
||||||
|
err = esp_vfs_fat_sdspi_mount(s_mount_point, &host, &slot_config, &mount_config, &s_card);
|
||||||
|
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
if (s_spi_bus_inited) {
|
||||||
|
spi_bus_free(SDSPI_HOST_ID);
|
||||||
|
s_spi_bus_inited = false;
|
||||||
|
}
|
||||||
|
if (err == 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));
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
sdmmc_card_print_info(stdout, s_card);
|
||||||
|
s_sd_card_mounted = true;
|
||||||
|
ESP_LOGI(TAG, "SD card mounted successfully at %s", s_mount_point);
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t sd_card_deinit(void) {
|
esp_err_t sd_card_deinit(void) {
|
||||||
esp_err_t result = ESP_OK;
|
if (!s_sd_card_mounted) {
|
||||||
|
return ESP_OK;
|
||||||
if (s_sd_card_mounted) {
|
|
||||||
ESP_LOGI(TAG, "Unmounting SD card...");
|
|
||||||
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(result));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
ESP_LOGI(TAG, "Unmounting SD card...");
|
||||||
|
esp_err_t ret = esp_vfs_fat_sdcard_unmount(s_mount_point, s_card);
|
||||||
|
if (ret == ESP_OK) {
|
||||||
|
s_sd_card_mounted = false;
|
||||||
|
s_card = NULL;
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sd_card_is_ready(void) {
|
bool sd_card_is_ready(void) {
|
||||||
bool result = false;
|
return s_sd_card_mounted;
|
||||||
|
|
||||||
/* 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) {
|
bool sd_card_cd_available(void) {
|
||||||
|
|
@ -262,256 +198,229 @@ bool sd_card_cd_available(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sd_card_cd_is_inserted(void) {
|
bool sd_card_cd_is_inserted(void) {
|
||||||
bool result = false;
|
if (SD_CD_PIN < 0) {
|
||||||
|
return 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)
|
|
||||||
result = (level == 1); /* HIGH = inserted (inverted breakout) */
|
|
||||||
#else
|
|
||||||
result = (level == 0); /* LOW = inserted (SparkFun default) */
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
sd_card_cd_ensure_configured();
|
||||||
return result;
|
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) */
|
||||||
|
#else
|
||||||
|
return (level == 0); /* LOW = inserted (SparkFun default) */
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int sd_card_cd_get_level(void) {
|
int sd_card_cd_get_level(void) {
|
||||||
int result = -1;
|
if (SD_CD_PIN < 0) {
|
||||||
|
return -1;
|
||||||
if (SD_CD_PIN >= 0) {
|
|
||||||
sd_card_cd_ensure_configured();
|
|
||||||
result = gpio_get_level(SD_CD_PIN);
|
|
||||||
}
|
}
|
||||||
|
sd_card_cd_ensure_configured();
|
||||||
return result;
|
return gpio_get_level(SD_CD_PIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t sd_card_get_info(uint64_t *total_bytes, uint64_t *free_bytes) {
|
esp_err_t sd_card_get_info(uint64_t *total_bytes, uint64_t *free_bytes) {
|
||||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
if (!s_sd_card_mounted) {
|
||||||
|
return 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) {
|
|
||||||
tot_sect = (fs->n_fatent - 2) * fs->csize;
|
|
||||||
fre_sect = fre_clust * fs->csize;
|
|
||||||
|
|
||||||
if (total_bytes) {
|
|
||||||
*total_bytes = (uint64_t)tot_sect * 512;
|
|
||||||
}
|
|
||||||
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 result;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
tot_sect = (fs->n_fatent - 2) * fs->csize;
|
||||||
|
fre_sect = fre_clust * fs->csize;
|
||||||
|
|
||||||
|
if (total_bytes) {
|
||||||
|
*total_bytes = (uint64_t)tot_sect * 512;
|
||||||
|
}
|
||||||
|
if (free_bytes) {
|
||||||
|
*free_bytes = (uint64_t)fre_sect * 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t sd_card_write_file(const char *filename, const void *data, size_t len, bool append) {
|
esp_err_t sd_card_write_file(const char *filename, const void *data, size_t len, bool append) {
|
||||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
if (!s_sd_card_mounted) {
|
||||||
|
return 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);
|
|
||||||
|
|
||||||
int flags = O_WRONLY | O_CREAT;
|
|
||||||
if (append) {
|
|
||||||
flags |= O_APPEND;
|
|
||||||
} else {
|
|
||||||
flags |= O_TRUNC;
|
|
||||||
}
|
|
||||||
int fd = open(full_path, flags, 0644);
|
|
||||||
if (fd >= 0) {
|
|
||||||
ssize_t written = write(fd, data, len);
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
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);
|
|
||||||
result = ESP_FAIL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(TAG, "Failed to open file for writing: %s", full_path);
|
|
||||||
result = ESP_FAIL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
char full_path[128];
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s%s%s", s_mount_point,
|
||||||
|
(filename[0] == '/') ? "" : "/", filename);
|
||||||
|
|
||||||
|
int flags = O_WRONLY | O_CREAT;
|
||||||
|
if (append) {
|
||||||
|
flags |= O_APPEND;
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t written = write(fd, data, len);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (written < 0 || (size_t)written != len) {
|
||||||
|
ESP_LOGE(TAG, "Failed to write all data: wrote %zd of %zu bytes", (ssize_t)written, len);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Wrote %zu bytes to %s", (size_t)written, full_path);
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t sd_card_read_file(const char *filename, void *data, size_t len, size_t *bytes_read) {
|
esp_err_t sd_card_read_file(const char *filename, void *data, size_t len, size_t *bytes_read) {
|
||||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
if (!s_sd_card_mounted) {
|
||||||
|
return 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) {
|
|
||||||
size_t read = fread(data, 1, len, f);
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
if (bytes_read) {
|
|
||||||
*bytes_read = read;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Read %zu bytes from %s", read, full_path);
|
|
||||||
result = ESP_OK;
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(TAG, "Failed to open file for reading: %s", full_path);
|
|
||||||
result = ESP_FAIL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read = fread(data, 1, len, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (bytes_read) {
|
||||||
|
*bytes_read = read;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Read %zu bytes from %s", read, full_path);
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t sd_card_get_file_size(const char *filename, size_t *size_bytes) {
|
esp_err_t sd_card_get_file_size(const char *filename, size_t *size_bytes) {
|
||||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
if (!s_sd_card_mounted || size_bytes == NULL) {
|
||||||
|
return 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) {
|
|
||||||
if (S_ISREG(st.st_mode)) {
|
|
||||||
*size_bytes = (size_t)st.st_size;
|
|
||||||
result = ESP_OK;
|
|
||||||
} else {
|
|
||||||
result = ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = ESP_FAIL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
*size_bytes = (size_t)st.st_size;
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t sd_card_read_file_at(const char *filename, size_t offset, void *data, size_t len, size_t *bytes_read) {
|
esp_err_t sd_card_read_file_at(const char *filename, size_t offset, void *data, size_t len, size_t *bytes_read) {
|
||||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
if (!s_sd_card_mounted) {
|
||||||
|
return 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) {
|
|
||||||
if (fseek(f, (long)offset, SEEK_SET) == 0) {
|
|
||||||
size_t n = fread(data, 1, len, f);
|
|
||||||
fclose(f);
|
|
||||||
if (bytes_read) {
|
|
||||||
*bytes_read = n;
|
|
||||||
}
|
|
||||||
result = ESP_OK;
|
|
||||||
} else {
|
|
||||||
fclose(f);
|
|
||||||
result = ESP_FAIL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = ESP_FAIL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
size_t n = fread(data, 1, len, f);
|
||||||
|
fclose(f);
|
||||||
|
if (bytes_read) {
|
||||||
|
*bytes_read = n;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sd_card_file_exists(const char *filename) {
|
bool sd_card_file_exists(const char *filename) {
|
||||||
bool result = false;
|
if (!s_sd_card_mounted) {
|
||||||
|
return 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;
|
|
||||||
result = (stat(full_path, &st) == 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t sd_card_list_dir(const char *path) {
|
esp_err_t sd_card_list_dir(const char *path) {
|
||||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
if (!s_sd_card_mounted) {
|
||||||
|
return 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);
|
|
||||||
} else {
|
|
||||||
snprintf(full_path, sizeof(full_path), "%s/%s", s_mount_point,
|
|
||||||
(path[0] == '/') ? path + 1 : path);
|
|
||||||
}
|
|
||||||
|
|
||||||
DIR *d = opendir(full_path);
|
|
||||||
if (d != NULL) {
|
|
||||||
struct dirent *e;
|
|
||||||
while ((e = readdir(d)) != NULL) {
|
|
||||||
if (e->d_name[0] == '.') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
char entry_path[384]; /* full_path(128) + "/" + d_name(255) */
|
|
||||||
int n = snprintf(entry_path, sizeof(entry_path), "%s/%s", full_path, e->d_name);
|
|
||||||
if (n < 0 || n >= (int)sizeof(entry_path)) {
|
|
||||||
continue; /* path too long, skip */
|
|
||||||
}
|
|
||||||
struct stat st;
|
|
||||||
if (stat(entry_path, &st) == 0) {
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
|
||||||
printf(" %-32s <DIR>\n", e->d_name);
|
|
||||||
} else {
|
|
||||||
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);
|
|
||||||
result = ESP_OK;
|
|
||||||
} else {
|
|
||||||
result = ESP_FAIL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
char full_path[128];
|
||||||
|
if (!path || path[0] == '\0') {
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s", s_mount_point);
|
||||||
|
} else {
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s/%s", s_mount_point,
|
||||||
|
(path[0] == '/') ? path + 1 : path);
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR *d = opendir(full_path);
|
||||||
|
if (!d) {
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dirent *e;
|
||||||
|
while ((e = readdir(d)) != NULL) {
|
||||||
|
if (e->d_name[0] == '.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
char entry_path[384]; /* full_path(128) + "/" + d_name(255) */
|
||||||
|
int n = snprintf(entry_path, sizeof(entry_path), "%s/%s", full_path, e->d_name);
|
||||||
|
if (n < 0 || n >= (int)sizeof(entry_path)) {
|
||||||
|
continue; /* path too long, skip */
|
||||||
|
}
|
||||||
|
struct stat st;
|
||||||
|
if (stat(entry_path, &st) == 0) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf(" %-32s ?\n", e->d_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t sd_card_delete_file(const char *filename) {
|
esp_err_t sd_card_delete_file(const char *filename) {
|
||||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
if (!s_sd_card_mounted) {
|
||||||
|
return 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) {
|
|
||||||
ESP_LOGI(TAG, "Deleted file: %s", full_path);
|
|
||||||
result = ESP_OK;
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(TAG, "Failed to delete file: %s", full_path);
|
|
||||||
result = ESP_FAIL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
char full_path[128];
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s%s%s", s_mount_point,
|
||||||
|
(filename[0] == '/') ? "" : "/", filename);
|
||||||
|
|
||||||
|
if (unlink(full_path) != 0) {
|
||||||
|
ESP_LOGE(TAG, "Failed to delete file: %s", full_path);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Deleted file: %s", full_path);
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,21 +40,21 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize SD card using SDIO interface
|
* @brief Initialize SD card using SDIO interface
|
||||||
*
|
*
|
||||||
* @return ESP_OK on success, error code otherwise
|
* @return ESP_OK on success, error code otherwise
|
||||||
*/
|
*/
|
||||||
esp_err_t sd_card_init(void);
|
esp_err_t sd_card_init(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Deinitialize SD card
|
* @brief Deinitialize SD card
|
||||||
*
|
*
|
||||||
* @return ESP_OK on success
|
* @return ESP_OK on success
|
||||||
*/
|
*/
|
||||||
esp_err_t sd_card_deinit(void);
|
esp_err_t sd_card_deinit(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if SD card is mounted and ready
|
* @brief Check if SD card is mounted and ready
|
||||||
*
|
*
|
||||||
* @return true if SD card is ready, false otherwise
|
* @return true if SD card is ready, false otherwise
|
||||||
*/
|
*/
|
||||||
bool sd_card_is_ready(void);
|
bool sd_card_is_ready(void);
|
||||||
|
|
@ -81,7 +81,7 @@ int sd_card_cd_get_level(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get SD card capacity information
|
* @brief Get SD card capacity information
|
||||||
*
|
*
|
||||||
* @param total_bytes Output parameter for total capacity in bytes
|
* @param total_bytes Output parameter for total capacity in bytes
|
||||||
* @param free_bytes Output parameter for free space in bytes
|
* @param free_bytes Output parameter for free space in bytes
|
||||||
* @return ESP_OK on success
|
* @return ESP_OK on success
|
||||||
|
|
@ -90,7 +90,7 @@ esp_err_t sd_card_get_info(uint64_t *total_bytes, uint64_t *free_bytes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Write data to a file on the SD card
|
* @brief Write data to a file on the SD card
|
||||||
*
|
*
|
||||||
* @param filename File path (e.g., "/sdcard/telemetry.json")
|
* @param filename File path (e.g., "/sdcard/telemetry.json")
|
||||||
* @param data Data to write
|
* @param data Data to write
|
||||||
* @param len Length of data in bytes
|
* @param len Length of data in bytes
|
||||||
|
|
@ -101,7 +101,7 @@ esp_err_t sd_card_write_file(const char *filename, const void *data, size_t len,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read data from a file on the SD card
|
* @brief Read data from a file on the SD card
|
||||||
*
|
*
|
||||||
* @param filename File path
|
* @param filename File path
|
||||||
* @param data Buffer to read into
|
* @param data Buffer to read into
|
||||||
* @param len Maximum length to read
|
* @param len Maximum length to read
|
||||||
|
|
@ -133,7 +133,7 @@ esp_err_t sd_card_read_file_at(const char *filename, size_t offset, void *data,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if a file exists on the SD card
|
* @brief Check if a file exists on the SD card
|
||||||
*
|
*
|
||||||
* @param filename File path
|
* @param filename File path
|
||||||
* @return true if file exists, false otherwise
|
* @return true if file exists, false otherwise
|
||||||
*/
|
*/
|
||||||
|
|
@ -149,7 +149,7 @@ esp_err_t sd_card_list_dir(const char *path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Delete a file from the SD card
|
* @brief Delete a file from the SD card
|
||||||
*
|
*
|
||||||
* @param filename File path
|
* @param filename File path
|
||||||
* @return ESP_OK on success
|
* @return ESP_OK on success
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -141,26 +141,6 @@ bool wifi_cfg_set_monitor_channel(uint8_t channel) {
|
||||||
return (err == ESP_OK);
|
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) {
|
void wifi_cfg_clear_monitor_channel(void) {
|
||||||
nvs_handle_t h;
|
nvs_handle_t h;
|
||||||
if (nvs_open(NVS_NS, NVS_READWRITE, &h) == ESP_OK) {
|
if (nvs_open(NVS_NS, NVS_READWRITE, &h) == ESP_OK) {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ bool wifi_cfg_set_password(const char *password);
|
||||||
|
|
||||||
// Monitor Specific
|
// Monitor Specific
|
||||||
bool wifi_cfg_set_monitor_channel(uint8_t channel);
|
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);
|
void wifi_cfg_clear_monitor_channel(void);
|
||||||
bool wifi_cfg_monitor_channel_is_unsaved(uint8_t current_val);
|
bool wifi_cfg_monitor_channel_is_unsaved(uint8_t current_val);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,10 @@
|
||||||
#include "wifi_controller.h"
|
#include "wifi_controller.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "freertos/semphr.h"
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_wifi.h"
|
#include "esp_wifi.h"
|
||||||
#include "esp_event.h"
|
#include "esp_event.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
#include "esp_timer.h"
|
|
||||||
#include "inttypes.h"
|
#include "inttypes.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "wifi_cfg.h"
|
#include "wifi_cfg.h"
|
||||||
|
|
@ -57,16 +55,7 @@
|
||||||
#include "sd_card.h"
|
#include "sd_card.h"
|
||||||
|
|
||||||
#define FIWI_TELEMETRY_FILE "fiwi-telemetry"
|
#define FIWI_TELEMETRY_FILE "fiwi-telemetry"
|
||||||
#define TELEMETRY_JSON_BUF_SIZE 4096
|
#define FIWI_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";
|
static const char *TAG = "WIFI_CTL";
|
||||||
|
|
||||||
|
|
@ -77,29 +66,6 @@ static uint8_t s_monitor_channel_staging = 6;
|
||||||
static bool s_monitor_enabled = false;
|
static bool s_monitor_enabled = false;
|
||||||
static uint32_t s_monitor_frame_count = 0;
|
static uint32_t s_monitor_frame_count = 0;
|
||||||
static TaskHandle_t s_monitor_stats_task_handle = NULL;
|
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 ---
|
// --- Event Handler ---
|
||||||
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
|
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
|
||||||
|
|
@ -115,12 +81,10 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t e
|
||||||
|
|
||||||
// ... [Log Collapse / Monitor Callback Logic] ...
|
// ... [Log Collapse / Monitor Callback Logic] ...
|
||||||
static void log_collapse_event(uint32_t nav_duration_us, int rssi, int retry) {
|
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();
|
||||||
gps_timestamp_t ts = gps_get_timestamp();
|
int64_t now_ms = ts.gps_us / 1000;
|
||||||
int64_t now_ms = ts.gps_us / 1000;
|
ESP_LOGI(TAG, "COLLAPSE: Time=%" PRId64 "ms, Sync=%d, Dur=%lu us, RSSI=%d, Retry=%d",
|
||||||
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);
|
||||||
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) {
|
static void monitor_frame_callback(const wifi_frame_info_t *frame, const uint8_t *payload, uint16_t len) {
|
||||||
|
|
@ -135,265 +99,57 @@ static void monitor_frame_callback(const wifi_frame_info_t *frame, const uint8_t
|
||||||
mcs_telemetry_process_frame(frame, NULL);
|
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) {
|
static void monitor_stats_task(void *arg) {
|
||||||
(void)arg;
|
(void)arg;
|
||||||
static char json_buf[TELEMETRY_JSON_BUF_SIZE];
|
static char json_buf[FIWI_TELEMETRY_JSON_BUF_SIZE];
|
||||||
uint32_t flush_count = 0;
|
uint32_t flush_count = 0;
|
||||||
uint32_t last_frame_count = 0;
|
uint32_t last_frame_count = 0;
|
||||||
uint64_t last_batch_flush_ms = 0;
|
while (1) {
|
||||||
uint64_t last_stats_log_ms = 0;
|
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||||
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) {
|
if (s_monitor_frame_count == last_frame_count) {
|
||||||
status_led_set_capture_active(false);
|
status_led_set_capture_active(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
last_frame_count = s_monitor_frame_count;
|
last_frame_count = s_monitor_frame_count;
|
||||||
|
wifi_collapse_stats_t stats;
|
||||||
/* Generate telemetry JSON (regardless of SD card status) */
|
if (wifi_monitor_get_stats(&stats) == ESP_OK) {
|
||||||
if (mcs_telemetry_to_json(json_buf, sizeof(json_buf), "esp32") == 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! ⚠️");
|
||||||
|
}
|
||||||
|
/* 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) {
|
||||||
size_t len = strlen(json_buf);
|
size_t len = strlen(json_buf);
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
json_buf[len] = '\n'; /* NDJSON: one object per line */
|
json_buf[len] = '\n'; /* NDJSON: one object per line */
|
||||||
size_t total_len = len + 1;
|
if (sd_card_write_file(FIWI_TELEMETRY_FILE, json_buf, len + 1, true) == ESP_OK) {
|
||||||
|
|
||||||
/* 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++;
|
|
||||||
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++;
|
flush_count++;
|
||||||
uint32_t write_interval_ms = (uint32_t)(now_ms - s_sd_write_rate.last_update_ms);
|
ESP_LOGD(TAG, "fiwi-telemetry flushed (#%lu)", (unsigned long)flush_count);
|
||||||
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 ---
|
// --- Helper to apply IP settings ---
|
||||||
static void apply_ip_settings(void) {
|
static void apply_ip_settings(void) {
|
||||||
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||||
|
if (!netif) return;
|
||||||
|
|
||||||
if (netif != NULL) {
|
if (wifi_cfg_get_dhcp()) {
|
||||||
if (wifi_cfg_get_dhcp()) {
|
esp_netif_dhcpc_start(netif);
|
||||||
esp_netif_dhcpc_start(netif);
|
} else {
|
||||||
} else {
|
esp_netif_dhcpc_stop(netif);
|
||||||
esp_netif_dhcpc_stop(netif);
|
|
||||||
|
|
||||||
char ip[16], mask[16], gw[16];
|
char ip[16], mask[16], gw[16];
|
||||||
if (wifi_cfg_get_ipv4(ip, mask, gw)) {
|
if (wifi_cfg_get_ipv4(ip, mask, gw)) {
|
||||||
esp_netif_ip_info_t info = {0};
|
esp_netif_ip_info_t info = {0};
|
||||||
// API Fix: esp_ip4addr_aton returns uint32_t
|
// API Fix: esp_ip4addr_aton returns uint32_t
|
||||||
info.ip.addr = esp_ip4addr_aton(ip);
|
info.ip.addr = esp_ip4addr_aton(ip);
|
||||||
info.netmask.addr = esp_ip4addr_aton(mask);
|
info.netmask.addr = esp_ip4addr_aton(mask);
|
||||||
info.gw.addr = esp_ip4addr_aton(gw);
|
info.gw.addr = esp_ip4addr_aton(gw);
|
||||||
|
|
||||||
esp_netif_set_ip_info(netif, &info);
|
esp_netif_set_ip_info(netif, &info);
|
||||||
ESP_LOGI(TAG, "Static IP applied: %s", ip);
|
ESP_LOGI(TAG, "Static IP applied: %s", ip);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -436,151 +192,98 @@ void wifi_ctl_init(void) {
|
||||||
esp_wifi_connect();
|
esp_wifi_connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Staging and Active Params from NVS
|
// Load Staging Params
|
||||||
char mode_ignored[16];
|
char mode_ignored[16];
|
||||||
wifi_cfg_get_mode(mode_ignored, &s_monitor_channel_staging);
|
wifi_cfg_get_mode(mode_ignored, &s_monitor_channel_staging);
|
||||||
if (s_monitor_channel_staging == 0) s_monitor_channel_staging = 6;
|
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) ---
|
// --- Mode Control (Core) ---
|
||||||
|
|
||||||
esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel, wifi_bandwidth_t bw) {
|
esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel, wifi_bandwidth_t bw) {
|
||||||
esp_err_t result = ESP_OK;
|
if (channel == 0) channel = s_monitor_channel_staging;
|
||||||
|
|
||||||
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) {
|
if (s_current_mode == WIFI_CTL_MODE_MONITOR && s_monitor_channel_active == channel) {
|
||||||
ESP_LOGW(TAG, "Already in monitor mode (Ch %d)", channel);
|
ESP_LOGW(TAG, "Already in monitor mode (Ch %d)", channel);
|
||||||
result = ESP_OK;
|
return ESP_OK;
|
||||||
} else {
|
|
||||||
ESP_LOGI(TAG, "Switching to MONITOR MODE (Ch %d)", channel);
|
|
||||||
|
|
||||||
iperf_stop();
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(500));
|
|
||||||
#ifdef CONFIG_ESP_WIFI_CSI_ENABLED
|
|
||||||
csi_mgr_disable();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
esp_wifi_disconnect();
|
|
||||||
esp_wifi_stop();
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(500));
|
|
||||||
|
|
||||||
esp_wifi_set_mode(WIFI_MODE_NULL);
|
|
||||||
status_led_set_capture_active(false);
|
|
||||||
if (wifi_monitor_init(channel, monitor_frame_callback) != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "Failed to init monitor mode");
|
|
||||||
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");
|
|
||||||
} else if (mcs_telemetry_start() != ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "MCS telemetry start failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_wifi_set_bandwidth(WIFI_IF_STA, bw);
|
|
||||||
|
|
||||||
if (wifi_monitor_start() != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "Failed to start monitor mode");
|
|
||||||
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);
|
|
||||||
|
|
||||||
/* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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_LOGI(TAG, "Switching to MONITOR MODE (Ch %d)", channel);
|
||||||
|
|
||||||
|
iperf_stop();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
#ifdef CONFIG_ESP_WIFI_CSI_ENABLED
|
||||||
|
csi_mgr_disable();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
esp_wifi_disconnect();
|
||||||
|
esp_wifi_stop();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
|
||||||
|
esp_wifi_set_mode(WIFI_MODE_NULL);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MCS telemetry -> fiwi-telemetry on SD (default on monitor start) */
|
||||||
|
if (mcs_telemetry_init(NULL) != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "MCS telemetry init failed");
|
||||||
|
} else if (mcs_telemetry_start() != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "MCS telemetry start failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_wifi_set_bandwidth(WIFI_IF_STA, bw);
|
||||||
|
|
||||||
|
if (wifi_monitor_start() != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to start monitor mode");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_monitor_enabled = true;
|
||||||
|
s_current_mode = WIFI_CTL_MODE_MONITOR;
|
||||||
|
s_monitor_channel_active = 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t wifi_ctl_switch_to_sta(void) {
|
esp_err_t wifi_ctl_switch_to_sta(void) {
|
||||||
esp_err_t result = ESP_OK;
|
|
||||||
|
|
||||||
if (s_current_mode == WIFI_CTL_MODE_STA) {
|
if (s_current_mode == WIFI_CTL_MODE_STA) {
|
||||||
ESP_LOGI(TAG, "Already in STA mode");
|
ESP_LOGI(TAG, "Already in STA mode");
|
||||||
result = ESP_OK;
|
return ESP_OK;
|
||||||
} else {
|
|
||||||
ESP_LOGI(TAG, "Switching to STA MODE");
|
|
||||||
|
|
||||||
if (s_monitor_stats_task_handle != NULL) {
|
|
||||||
vTaskDelete(s_monitor_stats_task_handle);
|
|
||||||
s_monitor_stats_task_handle = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(500));
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_wifi_set_mode(WIFI_MODE_STA);
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(500));
|
|
||||||
|
|
||||||
esp_wifi_start();
|
|
||||||
esp_wifi_connect();
|
|
||||||
|
|
||||||
s_current_mode = WIFI_CTL_MODE_STA;
|
|
||||||
status_led_set_state(LED_STATE_WAITING);
|
|
||||||
result = ESP_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
ESP_LOGI(TAG, "Switching to STA MODE");
|
||||||
|
|
||||||
|
if (s_monitor_stats_task_handle != NULL) {
|
||||||
|
vTaskDelete(s_monitor_stats_task_handle);
|
||||||
|
s_monitor_stats_task_handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_monitor_enabled) {
|
||||||
|
status_led_set_capture_active(false);
|
||||||
|
mcs_telemetry_stop();
|
||||||
|
wifi_monitor_stop();
|
||||||
|
s_monitor_enabled = false;
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_wifi_set_mode(WIFI_MODE_STA);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(500));
|
||||||
|
|
||||||
|
esp_wifi_start();
|
||||||
|
esp_wifi_connect();
|
||||||
|
|
||||||
|
s_current_mode = WIFI_CTL_MODE_STA;
|
||||||
|
status_led_set_state(LED_STATE_WAITING);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Wrappers for cmd_monitor.c ---
|
// --- Wrappers for cmd_monitor.c ---
|
||||||
|
|
@ -615,39 +318,9 @@ void wifi_ctl_set_channel(int channel) {
|
||||||
ESP_LOGI(TAG, "Switching live channel to %d", channel);
|
ESP_LOGI(TAG, "Switching live channel to %d", channel);
|
||||||
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
|
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
|
||||||
s_monitor_channel_active = (uint8_t)channel;
|
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) {
|
void wifi_ctl_status(void) {
|
||||||
const char *mode_str = (s_current_mode == WIFI_CTL_MODE_MONITOR) ? "MONITOR" :
|
const char *mode_str = (s_current_mode == WIFI_CTL_MODE_MONITOR) ? "MONITOR" :
|
||||||
(s_current_mode == WIFI_CTL_MODE_AP) ? "AP" : "STATION";
|
(s_current_mode == WIFI_CTL_MODE_AP) ? "AP" : "STATION";
|
||||||
|
|
@ -655,53 +328,10 @@ void wifi_ctl_status(void) {
|
||||||
printf("WiFi Status:\n");
|
printf("WiFi Status:\n");
|
||||||
printf(" Mode: %s\n", mode_str);
|
printf(" Mode: %s\n", mode_str);
|
||||||
if (s_current_mode == WIFI_CTL_MODE_MONITOR) {
|
if (s_current_mode == WIFI_CTL_MODE_MONITOR) {
|
||||||
uint8_t channel = s_monitor_channel_active;
|
printf(" Channel: %d\n", s_monitor_channel_active);
|
||||||
uint32_t freq_mhz = wifi_channel_to_frequency(channel);
|
printf(" Frames: %lu\n", (unsigned long)s_monitor_frame_count);
|
||||||
|
|
||||||
/* 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) ---
|
// --- Params (NVS) ---
|
||||||
|
|
@ -712,15 +342,8 @@ bool wifi_ctl_param_is_unsaved(void) {
|
||||||
|
|
||||||
void wifi_ctl_param_save(const char *dummy) {
|
void wifi_ctl_param_save(const char *dummy) {
|
||||||
(void)dummy;
|
(void)dummy;
|
||||||
/* If monitor mode is running, save the active channel; otherwise save staging */
|
if (wifi_cfg_set_monitor_channel(s_monitor_channel_staging)) {
|
||||||
uint8_t channel_to_save = (s_current_mode == WIFI_CTL_MODE_MONITOR) ?
|
ESP_LOGI(TAG, "Monitor channel (%d) saved to NVS", s_monitor_channel_staging);
|
||||||
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 {
|
} else {
|
||||||
ESP_LOGI(TAG, "No changes to save.");
|
ESP_LOGI(TAG, "No changes to save.");
|
||||||
}
|
}
|
||||||
|
|
@ -731,17 +354,7 @@ void wifi_ctl_param_init(void) {
|
||||||
uint8_t ch = 0;
|
uint8_t ch = 0;
|
||||||
wifi_cfg_get_mode(mode_ignored, &ch);
|
wifi_cfg_get_mode(mode_ignored, &ch);
|
||||||
if (ch > 0) s_monitor_channel_staging = 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) {
|
void wifi_ctl_param_clear(void) {
|
||||||
|
|
@ -755,75 +368,6 @@ void wifi_ctl_param_clear(void) {
|
||||||
wifi_ctl_mode_t wifi_ctl_get_mode(void) { return s_current_mode; }
|
wifi_ctl_mode_t wifi_ctl_get_mode(void) { return s_current_mode; }
|
||||||
int wifi_ctl_get_channel(void) { return s_monitor_channel_active; }
|
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 ---
|
// --- Deprecated ---
|
||||||
static void auto_monitor_task_func(void *arg) {
|
static void auto_monitor_task_func(void *arg) {
|
||||||
uint8_t channel = (uint8_t)(uintptr_t)arg;
|
uint8_t channel = (uint8_t)(uintptr_t)arg;
|
||||||
|
|
|
||||||
|
|
@ -74,14 +74,6 @@ void wifi_ctl_param_clear(void);
|
||||||
wifi_ctl_mode_t wifi_ctl_get_mode(void);
|
wifi_ctl_mode_t wifi_ctl_get_mode(void);
|
||||||
int wifi_ctl_get_channel(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
|
// Deprecated / Compatibility
|
||||||
void wifi_ctl_auto_monitor_start(uint8_t channel);
|
void wifi_ctl_auto_monitor_start(uint8_t channel);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,97 +56,10 @@ uint32_t threshold_duration_multiplier = 2; // NAV > expected * this = mism
|
||||||
// Logging control
|
// Logging control
|
||||||
uint32_t log_every_n_mismatches = 1; // Log every Nth mismatch (1 = all, 10 = every 10th)
|
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 uint32_t s_mismatch_log_counter = 0;
|
||||||
static bool s_monitor_debug = false; // Debug mode: enable serial logging
|
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type);
|
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
|
* @brief Parse 802.11 MAC header
|
||||||
*/
|
*/
|
||||||
|
|
@ -190,21 +103,10 @@ 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)
|
// 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;
|
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) {
|
if (frame_info->has_addr4 && len >= 30) {
|
||||||
memcpy(frame_info->addr4, &payload[24], 6);
|
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;
|
frame_info->frame_len = len;
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
|
|
@ -233,38 +135,23 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
||||||
frame_info.timestamp = rx_ctrl->timestamp;
|
frame_info.timestamp = rx_ctrl->timestamp;
|
||||||
|
|
||||||
// Extract PHY rate info from RX control
|
// Extract PHY rate info from RX control
|
||||||
|
frame_info.mcs = 0;
|
||||||
frame_info.rate = rx_ctrl->rate; // This is the rate index
|
frame_info.rate = rx_ctrl->rate; // This is the rate index
|
||||||
|
frame_info.sig_mode = 0;
|
||||||
|
frame_info.sgi = false;
|
||||||
|
frame_info.bandwidth = 0;
|
||||||
|
|
||||||
// If MCS wasn't parsed from headers but we have HT/VHT/HE mode, try to extract from rate
|
// Estimate PHY rate from rate index (rough approximation)
|
||||||
if (frame_info.sig_mode > 0 && frame_info.mcs == 0 && rx_ctrl->rate >= 128) {
|
static const uint32_t rate_table[] = {
|
||||||
// For HT/VHT/HE, rate >= 128 might encode MCS
|
1000, 2000, 5500, 11000, // 1, 2, 5.5, 11 Mbps (DSSS)
|
||||||
// ESP-IDF encoding: rate 128+ might be MCS index
|
6000, 9000, 12000, 18000, 24000, 36000, 48000, 54000, // OFDM rates
|
||||||
frame_info.mcs = rx_ctrl->rate - 128;
|
65000, 130000, 195000, 260000 // Rough HT estimates
|
||||||
if (frame_info.mcs > 11) frame_info.mcs = 11; // Cap at max MCS
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate PHY rate using parsed MCS/spatial streams if available
|
if (rx_ctrl->rate < sizeof(rate_table) / sizeof(rate_table[0])) {
|
||||||
// Otherwise fall back to rate table estimation
|
frame_info.phy_rate_kbps = rate_table[rx_ctrl->rate];
|
||||||
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 {
|
} else {
|
||||||
// Estimate PHY rate from rate index (rough approximation for legacy frames)
|
frame_info.phy_rate_kbps = 100000; // Assume 100 Mbps default
|
||||||
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
|
|
||||||
65000, 130000, 195000, 260000 // Rough HT estimates
|
|
||||||
};
|
|
||||||
|
|
||||||
if (rx_ctrl->rate < sizeof(rate_table) / sizeof(rate_table[0])) {
|
|
||||||
frame_info.phy_rate_kbps = rate_table[rx_ctrl->rate];
|
|
||||||
} else {
|
|
||||||
frame_info.phy_rate_kbps = 100000; // Assume 100 Mbps default
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update statistics
|
// Update statistics
|
||||||
|
|
@ -316,7 +203,7 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type)
|
||||||
|
|
||||||
if (frame_info.duration_id > threshold_duration_mismatch_us) {
|
if (frame_info.duration_id > threshold_duration_mismatch_us) {
|
||||||
s_mismatch_log_counter++;
|
s_mismatch_log_counter++;
|
||||||
if (s_monitor_debug && (s_mismatch_log_counter % log_every_n_mismatches) == 0) {
|
if ((s_mismatch_log_counter % log_every_n_mismatches) == 0) {
|
||||||
ESP_LOGW("MONITOR", "Duration mismatch: %s frame, %u bytes @ %u Mbps",
|
ESP_LOGW("MONITOR", "Duration mismatch: %s frame, %u bytes @ %u Mbps",
|
||||||
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
||||||
frame_info.frame_len, phy_rate_mbps);
|
frame_info.frame_len, phy_rate_mbps);
|
||||||
|
|
@ -340,20 +227,18 @@ 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 &&
|
if (frame_info.retry && frame_info.duration_id > threshold_high_nav_us &&
|
||||||
phy_rate_mbps < threshold_phy_rate_fallback_mbps) {
|
phy_rate_mbps < threshold_phy_rate_fallback_mbps) {
|
||||||
if (s_monitor_debug) {
|
ESP_LOGW("MONITOR", "⚠⚠⚠ COLLISION DETECTED!");
|
||||||
ESP_LOGW("MONITOR", "⚠⚠⚠ COLLISION DETECTED!");
|
|
||||||
|
|
||||||
// NEW: Log the Attacker MAC
|
// NEW: Log the Attacker MAC
|
||||||
ESP_LOGW("MONITOR", " Attacker MAC: %02x:%02x:%02x:%02x:%02x:%02x",
|
ESP_LOGW("MONITOR", " Attacker MAC: %02x:%02x:%02x:%02x:%02x:%02x",
|
||||||
frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2],
|
frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2],
|
||||||
frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5]);
|
frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5]);
|
||||||
|
|
||||||
ESP_LOGW("MONITOR", " Type: %s, Size: %u bytes, Rate: %u Mbps",
|
ESP_LOGW("MONITOR", " Type: %s, Size: %u bytes, Rate: %u Mbps",
|
||||||
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
wifi_frame_type_str(frame_info.type, frame_info.subtype),
|
||||||
frame_info.frame_len, phy_rate_mbps);
|
frame_info.frame_len, phy_rate_mbps);
|
||||||
ESP_LOGW("MONITOR", " NAV: %u us (expected %lu us), Retry: YES",
|
ESP_LOGW("MONITOR", " NAV: %u us (expected %lu us), Retry: YES",
|
||||||
frame_info.duration_id, expected_duration);
|
frame_info.duration_id, expected_duration);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -592,19 +477,3 @@ const char* wifi_frame_type_str(uint8_t type, uint8_t subtype) {
|
||||||
|
|
||||||
return "UNKNOWN";
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -144,38 +144,37 @@ typedef struct {
|
||||||
bool more_data;
|
bool more_data;
|
||||||
bool protected_frame;
|
bool protected_frame;
|
||||||
bool order;
|
bool order;
|
||||||
|
|
||||||
// Duration/ID (NAV)
|
// Duration/ID (NAV)
|
||||||
uint16_t duration_id;
|
uint16_t duration_id;
|
||||||
|
|
||||||
// MAC Addresses
|
// MAC Addresses
|
||||||
uint8_t addr1[6]; // Receiver address
|
uint8_t addr1[6]; // Receiver address
|
||||||
uint8_t addr2[6]; // Transmitter address
|
uint8_t addr2[6]; // Transmitter address
|
||||||
uint8_t addr3[6]; // Filtering address (BSSID/SA/DA)
|
uint8_t addr3[6]; // Filtering address (BSSID/SA/DA)
|
||||||
|
|
||||||
// Sequence Control
|
// Sequence Control
|
||||||
uint16_t seq_ctrl;
|
uint16_t seq_ctrl;
|
||||||
uint16_t fragment_num;
|
uint16_t fragment_num;
|
||||||
uint16_t sequence_num;
|
uint16_t sequence_num;
|
||||||
|
|
||||||
// Optional: Address 4 (if To DS and From DS both set)
|
// Optional: Address 4 (if To DS and From DS both set)
|
||||||
uint8_t addr4[6];
|
uint8_t addr4[6];
|
||||||
bool has_addr4;
|
bool has_addr4;
|
||||||
|
|
||||||
// RX Info
|
// RX Info
|
||||||
int8_t rssi;
|
int8_t rssi;
|
||||||
uint8_t channel;
|
uint8_t channel;
|
||||||
uint32_t timestamp;
|
uint32_t timestamp;
|
||||||
|
|
||||||
// PHY rate info
|
// PHY rate info
|
||||||
uint8_t mcs; // MCS index (for HT/VHT/HE frames)
|
uint8_t mcs; // MCS index (for HT/VHT frames)
|
||||||
uint8_t rate; // Legacy rate or rate index
|
uint8_t rate; // Legacy rate or rate index
|
||||||
uint8_t sig_mode; // Signal mode (0=legacy, 1=HT, 3=VHT, 4=HE)
|
uint8_t sig_mode; // Signal mode (0=legacy, 1=HT, 3=VHT)
|
||||||
uint8_t spatial_streams; // Number of spatial streams (NSS) - 1-8
|
|
||||||
bool sgi; // Short Guard Interval
|
bool sgi; // Short Guard Interval
|
||||||
uint8_t bandwidth; // 0=20MHz, 1=40MHz, 2=80MHz, 3=160MHz
|
uint8_t bandwidth; // 0=20MHz, 1=40MHz, 2=80MHz
|
||||||
uint32_t phy_rate_kbps; // Calculated PHY rate in Kbps (uint32_t to handle >65 Mbps)
|
uint32_t phy_rate_kbps; // Calculated PHY rate in Kbps (uint32_t to handle >65 Mbps)
|
||||||
|
|
||||||
// Frame size
|
// Frame size
|
||||||
uint16_t frame_len;
|
uint16_t frame_len;
|
||||||
} wifi_frame_info_t;
|
} wifi_frame_info_t;
|
||||||
|
|
@ -192,13 +191,13 @@ typedef struct {
|
||||||
uint32_t ack_frames;
|
uint32_t ack_frames;
|
||||||
uint32_t data_frames;
|
uint32_t data_frames;
|
||||||
uint32_t mgmt_frames;
|
uint32_t mgmt_frames;
|
||||||
|
|
||||||
// Collapse indicators
|
// Collapse indicators
|
||||||
float retry_rate; // Percentage of retried frames
|
float retry_rate; // Percentage of retried frames
|
||||||
uint16_t avg_nav; // Average NAV duration
|
uint16_t avg_nav; // Average NAV duration
|
||||||
uint16_t max_nav; // Maximum NAV seen
|
uint16_t max_nav; // Maximum NAV seen
|
||||||
uint32_t collision_events; // Estimated collision count
|
uint32_t collision_events; // Estimated collision count
|
||||||
|
|
||||||
// Duration analysis
|
// Duration analysis
|
||||||
uint32_t duration_mismatch_frames; // NAV >> expected
|
uint32_t duration_mismatch_frames; // NAV >> expected
|
||||||
uint32_t rate_fallback_frames; // PHY rate < 100 Mbps
|
uint32_t rate_fallback_frames; // PHY rate < 100 Mbps
|
||||||
|
|
@ -209,18 +208,18 @@ typedef struct {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Callback function type for frame capture
|
* @brief Callback function type for frame capture
|
||||||
*
|
*
|
||||||
* @param frame_info Parsed frame information
|
* @param frame_info Parsed frame information
|
||||||
* @param payload Raw frame payload (starts with MAC header)
|
* @param payload Raw frame payload (starts with MAC header)
|
||||||
* @param len Frame length
|
* @param len Frame length
|
||||||
*/
|
*/
|
||||||
typedef void (*wifi_monitor_cb_t)(const wifi_frame_info_t *frame_info,
|
typedef void (*wifi_monitor_cb_t)(const wifi_frame_info_t *frame_info,
|
||||||
const uint8_t *payload,
|
const uint8_t *payload,
|
||||||
uint16_t len);
|
uint16_t len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize WiFi monitor mode
|
* @brief Initialize WiFi monitor mode
|
||||||
*
|
*
|
||||||
* @param channel WiFi channel to monitor (1-14 for 2.4GHz, 36+ for 5GHz)
|
* @param channel WiFi channel to monitor (1-14 for 2.4GHz, 36+ for 5GHz)
|
||||||
* @param callback Callback function for captured frames
|
* @param callback Callback function for captured frames
|
||||||
* @return esp_err_t ESP_OK on success
|
* @return esp_err_t ESP_OK on success
|
||||||
|
|
@ -229,21 +228,21 @@ esp_err_t wifi_monitor_init(uint8_t channel, wifi_monitor_cb_t callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Start WiFi monitoring
|
* @brief Start WiFi monitoring
|
||||||
*
|
*
|
||||||
* @return esp_err_t ESP_OK on success
|
* @return esp_err_t ESP_OK on success
|
||||||
*/
|
*/
|
||||||
esp_err_t wifi_monitor_start(void);
|
esp_err_t wifi_monitor_start(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Stop WiFi monitoring
|
* @brief Stop WiFi monitoring
|
||||||
*
|
*
|
||||||
* @return esp_err_t ESP_OK on success
|
* @return esp_err_t ESP_OK on success
|
||||||
*/
|
*/
|
||||||
esp_err_t wifi_monitor_stop(void);
|
esp_err_t wifi_monitor_stop(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set WiFi channel for monitoring
|
* @brief Set WiFi channel for monitoring
|
||||||
*
|
*
|
||||||
* @param channel WiFi channel (1-14 for 2.4GHz, 36+ for 5GHz)
|
* @param channel WiFi channel (1-14 for 2.4GHz, 36+ for 5GHz)
|
||||||
* @return esp_err_t ESP_OK on success
|
* @return esp_err_t ESP_OK on success
|
||||||
*/
|
*/
|
||||||
|
|
@ -251,7 +250,7 @@ esp_err_t wifi_monitor_set_channel(uint8_t channel);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parse 802.11 frame header
|
* @brief Parse 802.11 frame header
|
||||||
*
|
*
|
||||||
* @param payload Raw frame data
|
* @param payload Raw frame data
|
||||||
* @param len Frame length
|
* @param len Frame length
|
||||||
* @param frame_info Output: parsed frame information
|
* @param frame_info Output: parsed frame information
|
||||||
|
|
@ -261,7 +260,7 @@ esp_err_t wifi_parse_frame(const uint8_t *payload, uint16_t len, wifi_frame_info
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get WiFi collapse detection statistics
|
* @brief Get WiFi collapse detection statistics
|
||||||
*
|
*
|
||||||
* @param stats Output: statistics structure
|
* @param stats Output: statistics structure
|
||||||
* @return esp_err_t ESP_OK on success
|
* @return esp_err_t ESP_OK on success
|
||||||
*/
|
*/
|
||||||
|
|
@ -274,32 +273,20 @@ void wifi_monitor_reset_stats(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if current conditions indicate WiFi collapse
|
* @brief Check if current conditions indicate WiFi collapse
|
||||||
*
|
*
|
||||||
* @return true if collapse is detected, false otherwise
|
* @return true if collapse is detected, false otherwise
|
||||||
*/
|
*/
|
||||||
bool wifi_monitor_is_collapsed(void);
|
bool wifi_monitor_is_collapsed(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get string representation of frame type
|
* @brief Get string representation of frame type
|
||||||
*
|
*
|
||||||
* @param type Frame type
|
* @param type Frame type
|
||||||
* @param subtype Frame subtype
|
* @param subtype Frame subtype
|
||||||
* @return const char* String description
|
* @return const char* String description
|
||||||
*/
|
*/
|
||||||
const char* wifi_frame_type_str(uint8_t type, uint8_t subtype);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue