From 120c864f73a2e436c6d4f28833bff7d5e31e8387 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 6 Dec 2025 13:18:46 -0800 Subject: [PATCH] gps timestamps and gps sync works on C5 --- components/gps_sync/CMakeLists.txt | 4 +- components/gps_sync/gps_sync.c | 213 +++++++------- components/gps_sync/gps_sync.h | 9 +- main/CMakeLists.txt | 1 + main/main.c | 450 +++++++---------------------- 5 files changed, 225 insertions(+), 452 deletions(-) diff --git a/components/gps_sync/CMakeLists.txt b/components/gps_sync/CMakeLists.txt index 48764ed..a0c0ef0 100644 --- a/components/gps_sync/CMakeLists.txt +++ b/components/gps_sync/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( SRCS "gps_sync.c" - INCLUDE_DIRS "include" - REQUIRES driver + INCLUDE_DIRS "." + REQUIRES driver log esp_timer esp_driver_gpio esp_driver_uart ) diff --git a/components/gps_sync/gps_sync.c b/components/gps_sync/gps_sync.c index f9c37d4..5b5e2ae 100644 --- a/components/gps_sync/gps_sync.c +++ b/components/gps_sync/gps_sync.c @@ -3,15 +3,19 @@ #include "driver/uart.h" #include "esp_timer.h" #include "esp_log.h" +#include "esp_rom_sys.h" #include #include #include #include +#include +#include // Required for PRIu64 +// --- SAFE WIRING FOR ESP32-C5 --- #define GPS_UART_NUM UART_NUM_1 -#define GPS_RX_PIN GPIO_NUM_4 -#define GPS_TX_PIN GPIO_NUM_5 -#define PPS_GPIO GPIO_NUM_1 +#define GPS_RX_PIN GPIO_NUM_23 +#define GPS_TX_PIN GPIO_NUM_24 +#define PPS_GPIO GPIO_NUM_25 #define GPS_BAUD_RATE 9600 #define UART_BUF_SIZE 1024 @@ -25,13 +29,17 @@ static bool gps_has_fix = false; static bool use_gps_for_logs = false; static SemaphoreHandle_t sync_mutex; -// For decimal timestamp formatting - stores last timestamp parts -static uint32_t last_timestamp_sec = 0; -static uint16_t last_timestamp_ms = 0; +// Force update flag (defaults to true so boot-up snaps immediately) +static volatile bool force_sync_update = true; // PPS interrupt - captures exact monotonic time at second boundary static void IRAM_ATTR pps_isr_handler(void* arg) { + static bool onetime = true; last_pps_monotonic = esp_timer_get_time(); + if (onetime) { + esp_rom_printf("PPS connected!\n"); + onetime = false; + } } // Parse GPS time from NMEA sentence @@ -39,38 +47,38 @@ static bool parse_gprmc(const char* nmea, struct tm* tm_out, bool* valid) { if (strncmp(nmea, "$GPRMC", 6) != 0 && strncmp(nmea, "$GNRMC", 6) != 0) { return false; } - + char *p = strchr(nmea, ','); if (!p) return false; - + // Time field p++; int hour, min, sec; if (sscanf(p, "%2d%2d%2d", &hour, &min, &sec) != 3) { return false; } - + // Status field (A=valid, V=invalid) p = strchr(p, ','); if (!p) return false; p++; *valid = (*p == 'A'); - + // Skip to date field (8 commas ahead from time) for (int i = 0; i < 7; i++) { p = strchr(p, ','); if (!p) return false; p++; } - + // Date field: ddmmyy int day, month, year; if (sscanf(p, "%2d%2d%2d", &day, &month, &year) != 3) { return false; } - + year += (year < 80) ? 2000 : 1900; - + tm_out->tm_sec = sec; tm_out->tm_min = min; tm_out->tm_hour = hour; @@ -78,49 +86,60 @@ static bool parse_gprmc(const char* nmea, struct tm* tm_out, bool* valid) { tm_out->tm_mon = month - 1; tm_out->tm_year = year - 1900; tm_out->tm_isdst = 0; - + return true; } +// Force the next GPS update to snap immediately (bypass filter) +void gps_force_next_update(void) { + force_sync_update = true; + ESP_LOGW(TAG, "Requesting forced GPS sync update"); +} + // GPS processing task static void gps_task(void* arg) { char line[128]; int pos = 0; - + while (1) { uint8_t data; int len = uart_read_bytes(GPS_UART_NUM, &data, 1, 100 / portTICK_PERIOD_MS); - + if (len > 0) { if (data == '\n') { line[pos] = '\0'; - + struct tm gps_tm; bool valid; if (parse_gprmc(line, &gps_tm, &valid)) { if (valid) { time_t gps_time = mktime(&gps_tm); - + xSemaphoreTake(sync_mutex, portMAX_DELAY); next_pps_gps_second = gps_time + 1; xSemaphoreGive(sync_mutex); - + vTaskDelay(pdMS_TO_TICKS(300)); - + xSemaphoreTake(sync_mutex, portMAX_DELAY); if (last_pps_monotonic > 0) { int64_t gps_us = (int64_t)next_pps_gps_second * 1000000LL; int64_t new_offset = gps_us - last_pps_monotonic; - - if (monotonic_offset_us == 0) { + + if (monotonic_offset_us == 0 || force_sync_update) { monotonic_offset_us = new_offset; + + if (force_sync_update) { + ESP_LOGW(TAG, "GPS sync SNAP: Offset forced to %lld us", monotonic_offset_us); + force_sync_update = false; + } } else { // Low-pass filter: 90% old + 10% new monotonic_offset_us = (monotonic_offset_us * 9 + new_offset) / 10; } - + gps_has_fix = true; - + ESP_LOGI(TAG, "GPS sync: %04d-%02d-%02d %02d:%02d:%02d, offset=%lld us", gps_tm.tm_year + 1900, gps_tm.tm_mon + 1, gps_tm.tm_mday, gps_tm.tm_hour, gps_tm.tm_min, gps_tm.tm_sec, @@ -131,7 +150,7 @@ static void gps_task(void* arg) { gps_has_fix = false; } } - + pos = 0; } else if (pos < sizeof(line) - 1) { line[pos++] = data; @@ -142,17 +161,20 @@ static void gps_task(void* arg) { void gps_sync_init(bool use_gps_log_timestamps) { ESP_LOGI(TAG, "Initializing GPS sync"); - + use_gps_for_logs = use_gps_log_timestamps; - + + // Ensure we start with a forced update + gps_force_next_update(); + if (use_gps_log_timestamps) { ESP_LOGI(TAG, "ESP_LOG timestamps: GPS time in seconds.milliseconds format"); // Override vprintf to add decimal point to timestamps esp_log_set_vprintf(gps_log_vprintf); } - + sync_mutex = xSemaphoreCreateMutex(); - + uart_config_t uart_config = { .baud_rate = GPS_BAUD_RATE, .data_bits = UART_DATA_8_BITS, @@ -161,12 +183,12 @@ void gps_sync_init(bool use_gps_log_timestamps) { .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .source_clk = UART_SCLK_DEFAULT, }; - + ESP_ERROR_CHECK(uart_driver_install(GPS_UART_NUM, UART_BUF_SIZE, 0, 0, NULL, 0)); ESP_ERROR_CHECK(uart_param_config(GPS_UART_NUM, &uart_config)); - ESP_ERROR_CHECK(uart_set_pin(GPS_UART_NUM, GPS_TX_PIN, GPS_RX_PIN, + ESP_ERROR_CHECK(uart_set_pin(GPS_UART_NUM, GPS_TX_PIN, GPS_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); - + gpio_config_t io_conf = { .intr_type = GPIO_INTR_POSEDGE, .mode = GPIO_MODE_INPUT, @@ -175,47 +197,43 @@ void gps_sync_init(bool use_gps_log_timestamps) { .pull_down_en = GPIO_PULLDOWN_DISABLE, }; ESP_ERROR_CHECK(gpio_config(&io_conf)); - + ESP_ERROR_CHECK(gpio_install_isr_service(0)); ESP_ERROR_CHECK(gpio_isr_handler_add(PPS_GPIO, pps_isr_handler, NULL)); - + xTaskCreate(gps_task, "gps_task", 4096, NULL, 5, NULL); - + ESP_LOGI(TAG, "GPS sync initialized (RX=GPIO%d, PPS=GPIO%d)", GPS_RX_PIN, PPS_GPIO); } gps_timestamp_t gps_get_timestamp(void) { gps_timestamp_t ts; - + // Using clock_gettime (POSIX standard, portable) - // ESP32 supports CLOCK_MONOTONIC for monotonic time clock_gettime(CLOCK_MONOTONIC, &ts.mono_ts); - + xSemaphoreTake(sync_mutex, portMAX_DELAY); - + // Convert timespec to microseconds - ts.monotonic_us = (int64_t)ts.mono_ts.tv_sec * 1000000LL + + ts.monotonic_us = (int64_t)ts.mono_ts.tv_sec * 1000000LL + ts.mono_ts.tv_nsec / 1000; - + // Convert to milliseconds ts.monotonic_ms = ts.monotonic_us / 1000; - + // Calculate GPS time ts.gps_us = ts.monotonic_us + monotonic_offset_us; ts.gps_ms = ts.gps_us / 1000; - + ts.synced = gps_has_fix; xSemaphoreGive(sync_mutex); - + return ts; } -// Alternative: Get just milliseconds using clock_gettime -// Useful for simple logging where you only need millisecond resolution int64_t gps_get_monotonic_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - // Convert: seconds to ms + nanoseconds to ms return (int64_t)ts.tv_sec * 1000LL + ts.tv_nsec / 1000000; } @@ -223,88 +241,77 @@ bool gps_is_synced(void) { return gps_has_fix; } -// Custom log timestamp function - returns value formatted as seconds*1000000 + milliseconds*1000 -// This allows us to extract both seconds and milliseconds when needed -// When printed directly, shows full milliseconds (we format it with decimal in custom logger) -uint32_t esp_log_timestamp(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - int64_t monotonic_us = (int64_t)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000; - - int64_t time_us; - if (!use_gps_for_logs || !gps_has_fix) { - time_us = monotonic_us; - } else { - time_us = monotonic_us + monotonic_offset_us; - } - - // Convert to milliseconds and store parts - uint64_t time_ms = time_us / 1000; - last_timestamp_sec = time_ms / 1000; - last_timestamp_ms = time_ms % 1000; - - // Return total milliseconds (ESP-IDF will print this) - // Our custom vprintf will reformat it - return (uint32_t)time_ms; +// ---------------- LOGGING SYSTEM INTERCEPTION ---------------- + +// We now only return standard system time (ms) to ESP-IDF. +// We do NOT return GPS time here because it overflows 32 bits. +uint32_t gps_log_timestamp(void) { + return (uint32_t)(esp_timer_get_time() / 1000ULL); } -// Custom vprintf that reformats log timestamps to show decimal point and sync status -// Converts: I (1733424645234) TAG: message -// To: I (+1733424645.234) TAG: message (GPS synced) -// Or: I (*1.234) TAG: message (not synced - monotonic) +// Intercepts the log line string before it is printed. +// It detects the timestamp `(1234)` which is monotonic ms, +// and mathematically converts it to `(+17544234.123)` GPS sec.ms int gps_log_vprintf(const char *fmt, va_list args) { static char buffer[512]; - + // Format the message into our buffer int ret = vsnprintf(buffer, sizeof(buffer), fmt, args); - + assert(ret >= 0); + if (use_gps_for_logs) { - // Look for timestamp pattern: "I (", "W (", "E (", etc. + // Look for timestamp pattern: "I (", "W (", etc. char *timestamp_start = NULL; for (int i = 0; buffer[i] != '\0' && i < sizeof(buffer) - 20; i++) { - if ((buffer[i] == 'I' || buffer[i] == 'W' || buffer[i] == 'E' || - buffer[i] == 'D' || buffer[i] == 'V') && + if ((buffer[i] == 'I' || buffer[i] == 'W' || buffer[i] == 'E' || + buffer[i] == 'D' || buffer[i] == 'V') && buffer[i+1] == ' ' && buffer[i+2] == '(') { timestamp_start = &buffer[i+3]; break; } } - + if (timestamp_start) { - // Find the closing parenthesis char *timestamp_end = strchr(timestamp_start, ')'); if (timestamp_end) { - // Extract timestamp value - uint32_t timestamp_ms = 0; - if (sscanf(timestamp_start, "%lu", ×tamp_ms) == 1) { - uint32_t sec = timestamp_ms / 1000; - uint32_t ms = timestamp_ms % 1000; - - // Choose prefix based on GPS sync status - char prefix = gps_has_fix ? '+' : '*'; - - // Rebuild the string with decimal point and prefix + // Parse the MONOTONIC ms that ESP-IDF put there + uint32_t monotonic_log_ms = 0; + if (sscanf(timestamp_start, "%lu", &monotonic_log_ms) == 1) { + char reformatted[512]; size_t prefix_len = timestamp_start - buffer; - - // Copy everything before timestamp memcpy(reformatted, buffer, prefix_len); - - // Add prefix, formatted timestamp with decimal - int decimal_len = snprintf(reformatted + prefix_len, + int decimal_len = 0; + + if (gps_has_fix) { + // MATH: Calculate GPS time based on the log's monotonic time + int64_t log_mono_us = (int64_t)monotonic_log_ms * 1000; + int64_t log_gps_us = log_mono_us + monotonic_offset_us; + + // Split into Seconds and Milliseconds + uint64_t gps_sec = log_gps_us / 1000000; + uint32_t gps_ms = (log_gps_us % 1000000) / 1000; + + decimal_len = snprintf(reformatted + prefix_len, sizeof(reformatted) - prefix_len, - "%c%lu.%03u", prefix, sec, ms); - - // Copy everything after timestamp + "+%" PRIu64 ".%03lu", gps_sec, gps_ms); + } else { + // No fix: just show monotonic nicely + uint32_t sec = monotonic_log_ms / 1000; + uint32_t ms = monotonic_log_ms % 1000; + decimal_len = snprintf(reformatted + prefix_len, + sizeof(reformatted) - prefix_len, + "*%lu.%03lu", sec, ms); + } + + // Copy the rest of the message (from the closing parenthesis onwards) strcpy(reformatted + prefix_len + decimal_len, timestamp_end); - - // Print the reformatted string + return printf("%s", reformatted); } } } } - - // If not reformatting or something went wrong, just print original + return printf("%s", buffer); } diff --git a/components/gps_sync/gps_sync.h b/components/gps_sync/gps_sync.h index 96f972b..9348a17 100644 --- a/components/gps_sync/gps_sync.h +++ b/components/gps_sync/gps_sync.h @@ -6,7 +6,7 @@ #include "freertos/semphr.h" typedef struct { - int64_t monotonic_us; // Microseconds - never jumps backward + int64_t monotonic_us; // Microseconds - never jumps backward int64_t monotonic_ms; // Milliseconds - for easier logging int64_t gps_us; // GPS UTC time in microseconds int64_t gps_ms; // GPS UTC time in milliseconds @@ -21,6 +21,11 @@ typedef struct { // I (*1.234) TAG: message <-- * indicates not synced (monotonic) void gps_sync_init(bool use_gps_log_timestamps); +// FORCE UPDATE: Ignore the low-pass filter for the next valid GPS fix. +// This snaps the time offset immediately to the new value. +// Useful on boot or if you detect a massive time discrepancy. +void gps_force_next_update(void); + // Get current timestamp (with both us and ms) gps_timestamp_t gps_get_timestamp(void); @@ -31,5 +36,5 @@ int64_t gps_get_monotonic_ms(void); bool gps_is_synced(void); // Internal functions (called automatically by ESP-IDF - don't call directly) -uint32_t esp_log_timestamp(void); +uint32_t gps_log_timestamp(void); int gps_log_vprintf(const char *fmt, va_list args); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 5b61280..3f524ad 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -4,6 +4,7 @@ idf_component_register( INCLUDE_DIRS "." PRIV_REQUIRES csi_log + gps_sync wifi_cfg wifi_monitor ) diff --git a/main/main.c b/main/main.c index 0a3cb8b..b240ca9 100644 --- a/main/main.c +++ b/main/main.c @@ -17,104 +17,86 @@ #include "led_strip.h" +// Custom Components #include "iperf.h" #include "wifi_cfg.h" #include "csi_log.h" #include "wifi_monitor.h" +#include "gps_sync.h" // <--- ADDED: GPS Support +static const char *TAG = "MAIN"; -static const char *TAG = "main"; - -#if CONFIG_IDF_TARGET_ESP32S3 - #define RGB_LED_GPIO 48 -#elif CONFIG_IDF_TARGET_ESP32C5 +// --- Hardware Configuration --- +#if CONFIG_IDF_TARGET_ESP32C5 #define RGB_LED_GPIO 27 -#elif CONFIG_IDF_TARGET_ESP32C6 - #define RGB_LED_GPIO 8 -#elif CONFIG_IDF_TARGET_ESP32C3 - #define RGB_LED_GPIO 8 -#elif CONFIG_IDF_TARGET_ESP32S2 - #define RGB_LED_GPIO 18 -#elif CONFIG_IDF_TARGET_ESP32 - #define RGB_LED_GPIO 2 #else - #error "Unsupported target - define RGB_LED_GPIO for your board" + // Fallback for other chips if you switch boards + #define RGB_LED_GPIO 8 #endif +// --- LED State Machine --- static led_strip_handle_t led_strip; static bool wifi_connected = false; static bool has_config = false; typedef enum { - LED_STATE_NO_CONFIG, - LED_STATE_WAITING, - LED_STATE_CONNECTED, - LED_STATE_FAILED, - LED_STATE_MONITORING + LED_STATE_NO_CONFIG, // Yellow Solid + LED_STATE_WAITING, // Blue Blink (Connecting) + LED_STATE_CONNECTED, // Green Solid (Connected to AP) + LED_STATE_FAILED, // Red Blink + LED_STATE_MONITORING // Blue Solid (Sniffing Air) } led_state_t; static led_state_t current_led_state = LED_STATE_NO_CONFIG; -static void rgb_led_init(void) -{ +static void rgb_led_init(void) { + ESP_LOGI(TAG, "Initializing RGB LED on GPIO %d", RGB_LED_GPIO); led_strip_config_t strip_config = { .strip_gpio_num = RGB_LED_GPIO, .max_leds = 1, }; - led_strip_rmt_config_t rmt_config = { .resolution_hz = 10 * 1000 * 1000, + .flags.with_dma = false, }; - ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip)); led_strip_clear(led_strip); - - ESP_LOGI(TAG, "WS2812 RGB LED initialized on GPIO %d", RGB_LED_GPIO); } -static void set_led_color(uint8_t r, uint8_t g, uint8_t b) -{ +static void set_led_color(uint8_t r, uint8_t g, uint8_t b) { led_strip_set_pixel(led_strip, 0, r, g, b); led_strip_refresh(led_strip); } -static void led_task(void *arg) -{ +static void led_task(void *arg) { int blink_state = 0; - while(1) { switch(current_led_state) { case LED_STATE_NO_CONFIG: - set_led_color(255, 255, 0); + set_led_color(25, 25, 0); // Yellow (Dimmed) vTaskDelay(pdMS_TO_TICKS(1000)); break; case LED_STATE_WAITING: - if (blink_state) { - set_led_color(0, 0, 255); - } else { - set_led_color(0, 0, 0); - } + if (blink_state) set_led_color(0, 0, 50); // Blue + else set_led_color(0, 0, 0); blink_state = !blink_state; - vTaskDelay(pdMS_TO_TICKS(1000)); + vTaskDelay(pdMS_TO_TICKS(500)); break; case LED_STATE_CONNECTED: - set_led_color(0, 255, 0); + set_led_color(0, 25, 0); // Green vTaskDelay(pdMS_TO_TICKS(1000)); break; case LED_STATE_MONITORING: - set_led_color(0, 0, 255); // Solid BLUE + set_led_color(0, 0, 50); // Blue Solid vTaskDelay(pdMS_TO_TICKS(1000)); break; case LED_STATE_FAILED: - if (blink_state) { - set_led_color(255, 0, 0); - } else { - set_led_color(0, 0, 0); - } + if (blink_state) set_led_color(50, 0, 0); // Red + else set_led_color(0, 0, 0); blink_state = !blink_state; vTaskDelay(pdMS_TO_TICKS(200)); break; @@ -122,429 +104,207 @@ static void led_task(void *arg) } } -// --- CSI support --------------------------------------------------- +// --- GPS Logging Helper --- +// Replaces the old plain text log with your CSV + GPS Timestamp format +void log_collapse_event(float nav_duration_us, int rssi, int retry) { + gps_timestamp_t ts = gps_get_timestamp(); + + // Format: COLLAPSE,MonoMS,GpsMS,Synced,Duration,RSSI,Retry + printf("COLLAPSE,%lld,%lld,%d,%.2f,%d,%d\n", + ts.monotonic_ms, + ts.gps_ms, + ts.synced ? 1 : 0, + nav_duration_us, + rssi, + retry); +} + +// --- CSI Support --------------------------------------------------- static bool s_csi_enabled = false; static uint32_t s_csi_packet_count = 0; -static void csi_dump_task(void *arg) { - vTaskDelay(pdMS_TO_TICKS(20000)); - csi_log_dump_over_uart(); - vTaskDelete(NULL); -} - -static void csi_cb(void *ctx, wifi_csi_info_t *info) -{ +static void csi_cb(void *ctx, wifi_csi_info_t *info) { csi_log_append_record(info); s_csi_packet_count++; - if ((s_csi_packet_count % 100) == 0) { ESP_LOGI("CSI", "Captured %lu CSI packets", (unsigned long)s_csi_packet_count); } } static void wifi_enable_csi_once(void) { - if (s_csi_enabled) { - return; - } - - esp_err_t err; + if (s_csi_enabled) return; vTaskDelay(pdMS_TO_TICKS(2000)); - wifi_csi_config_t csi_cfg; - -#if CONFIG_IDF_TARGET_ESP32C5 memset(&csi_cfg, 0, sizeof(csi_cfg)); - csi_cfg.enable = true; - -#elif CONFIG_IDF_TARGET_ESP32S3 - memset(&csi_cfg, 0, sizeof(csi_cfg)); - csi_cfg.lltf_en = true; - csi_cfg.htltf_en = true; - csi_cfg.stbc_htltf2_en = false; - csi_cfg.ltf_merge_en = false; - csi_cfg.channel_filter_en = false; - csi_cfg.manu_scale = false; - csi_cfg.shift = 0; - -#else -#warning "CSI not supported for this target" - return; -#endif + csi_cfg.enable = true; // C5 specific simple config ESP_LOGI("CSI", "Configuring CSI..."); + if (esp_wifi_set_csi_config(&csi_cfg) != ESP_OK) return; + if (esp_wifi_set_csi_rx_cb(csi_cb, NULL) != ESP_OK) return; + if (esp_wifi_set_csi(true) != ESP_OK) return; - err = esp_wifi_set_csi_config(&csi_cfg); - if (err != ESP_OK) { - ESP_LOGE("CSI", "esp_wifi_set_csi_config failed: %s (0x%x)", esp_err_to_name(err), err); - return; - } - ESP_LOGI("CSI", "CSI config OK"); - - err = esp_wifi_set_csi_rx_cb(csi_cb, NULL); - if (err != ESP_OK) { - ESP_LOGE("CSI", "esp_wifi_set_csi_rx_cb failed: %s", esp_err_to_name(err)); - return; - } - ESP_LOGI("CSI", "CSI callback OK"); - - err = esp_wifi_set_csi(true); - if (err != ESP_OK) { - ESP_LOGE("CSI", "esp_wifi_set_csi(true) failed: %s", esp_err_to_name(err)); - return; - } ESP_LOGI("CSI", "CSI enabled!"); - s_csi_enabled = true; } +static void csi_dump_task(void *arg) { + vTaskDelay(pdMS_TO_TICKS(20000)); // Dump after 20 seconds + csi_log_dump_over_uart(); + vTaskDelete(NULL); +} + static void csi_init_task(void *arg) { wifi_enable_csi_once(); vTaskDelete(NULL); } -// --- WiFi Monitor Mode support --------------------------------------------------- +// --- WiFi Monitor Mode Support ------------------------------------- static bool s_monitor_enabled = false; static uint32_t s_monitor_frame_count = 0; +// This is the core analysis function static void monitor_frame_callback(const wifi_frame_info_t *frame, const uint8_t *payload, uint16_t len) { s_monitor_frame_count++; - // Log first 10 frames in detail - if (s_monitor_frame_count <= 10) { - const char *type_str = wifi_frame_type_str(frame->type, frame->subtype); - ESP_LOGI("MONITOR", "Frame #%lu: %s, NAV: %u us, RSSI: %d dBm, Retry: %d", - s_monitor_frame_count, type_str, frame->duration_id, - frame->rssi, frame->retry); - } - - // Warn on collision indicators + // 1. Check for Collapse (High NAV + Retry) if (frame->retry && frame->duration_id > 5000) { - ESP_LOGW("MONITOR", "⚠ COLLISION: Retry frame with high NAV (%u us)", frame->duration_id); + // USE GPS LOGGING HERE + log_collapse_event((float)frame->duration_id, frame->rssi, frame->retry); } + // 2. Also warn on extremely high NAV (blocking the channel) if (frame->duration_id > 30000) { - ESP_LOGW("MONITOR", "⚠ VERY HIGH NAV: %u us - possible collapse!", frame->duration_id); + ESP_LOGW("MONITOR", "⚠ VERY HIGH NAV: %u us", frame->duration_id); } } static void monitor_stats_task(void *arg) { while (1) { - vTaskDelay(pdMS_TO_TICKS(10000)); // Every 10 seconds - + vTaskDelay(pdMS_TO_TICKS(10000)); // Every 10 seconds wifi_collapse_stats_t stats; if (wifi_monitor_get_stats(&stats) == ESP_OK) { - ESP_LOGI("MONITOR", "========================================"); - ESP_LOGI("MONITOR", "WiFi Monitor Statistics:"); - ESP_LOGI("MONITOR", " Total frames: %lu", stats.total_frames); - ESP_LOGI("MONITOR", " Retry frames: %lu (%.2f%%)", - stats.retry_frames, stats.retry_rate); - ESP_LOGI("MONITOR", " High NAV frames: %lu", stats.high_nav_frames); - ESP_LOGI("MONITOR", " Average NAV: %u us", stats.avg_nav); - ESP_LOGI("MONITOR", " Max NAV: %u us", stats.max_nav); - ESP_LOGI("MONITOR", " Collision events: %lu", stats.collision_events); + ESP_LOGI("MONITOR", "--- Stats: %lu frames, Retry Rate: %.2f%%, Avg NAV: %u us ---", + stats.total_frames, stats.retry_rate, stats.avg_nav); - // Check for collapse if (wifi_monitor_is_collapsed()) { ESP_LOGW("MONITOR", "⚠⚠⚠ WiFi COLLAPSE DETECTED! ⚠⚠⚠"); - ESP_LOGW("MONITOR", " High retry rate: %.2f%%", stats.retry_rate); - ESP_LOGW("MONITOR", " High avg NAV: %u us", stats.avg_nav); - } else { - ESP_LOGI("MONITOR", "✓ WiFi operating normally"); } - ESP_LOGI("MONITOR", "========================================"); } } } static void wifi_enable_monitor_mode(uint8_t channel) { - if (s_monitor_enabled) { - return; - } + if (s_monitor_enabled) return; ESP_LOGI("MONITOR", "Starting WiFi monitor mode on channel %d", channel); - - esp_err_t err = wifi_monitor_init(channel, monitor_frame_callback); - if (err != ESP_OK) { - ESP_LOGE("MONITOR", "WiFi monitor init failed: %s", esp_err_to_name(err)); - return; - } - - err = wifi_monitor_start(); - if (err != ESP_OK) { - ESP_LOGE("MONITOR", "WiFi monitor start failed: %s", esp_err_to_name(err)); - return; - } + if (wifi_monitor_init(channel, monitor_frame_callback) != ESP_OK) return; + if (wifi_monitor_start() != ESP_OK) return; s_monitor_enabled = true; current_led_state = LED_STATE_MONITORING; - ESP_LOGI("MONITOR", "WiFi monitor started - BLUE LED solid"); - ESP_LOGI("MONITOR", "Capturing 802.11 frames for collapse detection"); - - // Start statistics task + ESP_LOGI("MONITOR", "WiFi monitor started"); xTaskCreate(monitor_stats_task, "monitor_stats", 4096, NULL, 5, NULL); } static void monitor_init_task(void *arg) { - // Get the channel from the connected AP wifi_ap_record_t ap_info; + // Try to sniff the same channel our AP is using if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) { wifi_enable_monitor_mode(ap_info.primary); } else { - // Default to channel 6 if we can't get AP info - wifi_enable_monitor_mode(6); + wifi_enable_monitor_mode(6); // Default fallback } vTaskDelete(NULL); } +// --- Event Handler (Connection Logic) ------------------------------ static void event_handler(void* arg, esp_event_base_t event_base, - int32_t event_id, void* event_data) -{ + int32_t event_id, void* event_data) { if (event_base == WIFI_EVENT) { - switch (event_id) { - case WIFI_EVENT_STA_START: - ESP_LOGI(TAG, "WiFi started, attempting connection..."); - if (has_config) { - current_led_state = LED_STATE_WAITING; - } - break; - - case WIFI_EVENT_STA_DISCONNECTED: - wifi_event_sta_disconnected_t* event = (wifi_event_sta_disconnected_t*) event_data; - - // Get SSID for better error messages - wifi_config_t wifi_cfg; - const char *ssid = "unknown"; - if (esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) == ESP_OK) { - ssid = (const char*)wifi_cfg.sta.ssid; - } - - // Log disconnect with reason-specific messages - switch (event->reason) { - case 201: // WIFI_REASON_NO_AP_FOUND - ESP_LOGE(TAG, "WiFi disconnected (reason 201): NO AP FOUND"); - ESP_LOGE(TAG, " SSID attempted: '%s'", ssid); - ESP_LOGE(TAG, " Verify AP is broadcasting and in range"); - break; - case 202: // WIFI_REASON_AUTH_FAIL - ESP_LOGE(TAG, "WiFi disconnected (reason 202): AUTH FAILED"); - ESP_LOGE(TAG, " SSID: '%s'", ssid); - ESP_LOGE(TAG, " Check password is correct"); - break; - case 15: // WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT - ESP_LOGE(TAG, "WiFi disconnected (reason 15): 4-WAY HANDSHAKE TIMEOUT"); - ESP_LOGE(TAG, " SSID: '%s'", ssid); - ESP_LOGE(TAG, " Password may be incorrect"); - break; - case 2: // WIFI_REASON_AUTH_EXPIRE - ESP_LOGW(TAG, "WiFi disconnected (reason 2): AUTH EXPIRED"); - ESP_LOGW(TAG, " SSID: '%s' - will retry", ssid); - break; - case 8: // WIFI_REASON_ASSOC_LEAVE - ESP_LOGW(TAG, "WiFi disconnected (reason 8): STATION LEFT"); - ESP_LOGW(TAG, " SSID: '%s' - normal disconnect", ssid); - break; - default: - ESP_LOGW(TAG, "WiFi disconnected from '%s', reason: %d", ssid, event->reason); - break; - } - - if (!wifi_connected && has_config) { - current_led_state = LED_STATE_FAILED; - ESP_LOGE(TAG, "WiFi connection FAILED - RED LED blinking"); - } - break; + if (event_id == WIFI_EVENT_STA_START) { + if (has_config) current_led_state = LED_STATE_WAITING; } - - } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + else if (event_id == WIFI_EVENT_STA_DISCONNECTED) { + wifi_event_sta_disconnected_t* event = (wifi_event_sta_disconnected_t*) event_data; + ESP_LOGW(TAG, "WiFi Disconnected (Reason: %d)", event->reason); + if (!wifi_connected && has_config) current_led_state = LED_STATE_FAILED; + } + } + else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; - ESP_LOGI(TAG, "got ip:" IPSTR " gw:" IPSTR " netmask:" IPSTR, - IP2STR(&event->ip_info.ip), - IP2STR(&event->ip_info.gw), - IP2STR(&event->ip_info.netmask)); + ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip)); wifi_connected = true; current_led_state = LED_STATE_CONNECTED; - // Log connection details: SSID, band, channel, bandwidth, RSSI - wifi_config_t wifi_cfg; - wifi_ap_record_t ap_info; - if (esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) == ESP_OK && - esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) { - - // Determine band from channel - const char *band_str = "Unknown"; - if (ap_info.primary >= 1 && ap_info.primary <= 14) { - band_str = "2.4GHz"; - } else if (ap_info.primary >= 36) { - band_str = "5GHz"; - } - - // Get bandwidth - try dual-band API first, fallback to single-band - wifi_bandwidth_t bw = WIFI_BW_HT20; // Default to 20MHz - const char *bw_str = "Unknown"; - bool bw_detected = false; - - // Try esp_wifi_get_bandwidths() first (works on dual-band chips in auto mode) - wifi_bandwidths_t bandwidths = {0}; - esp_err_t err = esp_wifi_get_bandwidths(WIFI_IF_STA, &bandwidths); - - if (err == ESP_OK) { - // Dual-band API succeeded - select bandwidth based on current band - if (ap_info.primary >= 1 && ap_info.primary <= 14) { - // 2.4GHz band - bw = (wifi_bandwidth_t)bandwidths.ghz_2g; - bw_detected = true; - } else if (ap_info.primary >= 36) { - // 5GHz band - bw = (wifi_bandwidth_t)bandwidths.ghz_5g; - bw_detected = true; - } - } else { - // Dual-band API failed - try single-band API (ESP32-S3, older IDF) - err = esp_wifi_get_bandwidth(WIFI_IF_STA, &bw); - if (err == ESP_OK) { - bw_detected = true; - } - } - - // Convert bandwidth enum to string - if (bw_detected) { - switch (bw) { - case WIFI_BW_HT20: - bw_str = "20MHz (HT20)"; - break; - case WIFI_BW_HT40: - bw_str = "40MHz (HT40)"; - break; - case WIFI_BW80: - bw_str = "80MHz (VHT80)"; - break; - default: - bw_str = "Unknown"; - break; - } - } - - ESP_LOGI(TAG, "========================================"); - ESP_LOGI(TAG, "WiFi CONNECTED - BLUE LED solid"); - ESP_LOGI(TAG, " SSID: '%s'", wifi_cfg.sta.ssid); - ESP_LOGI(TAG, " Band: %s", band_str); - - // Get configured bandwidth from NVS - char configured_bw[16] = {0}; - wifi_cfg_get_bandwidth(configured_bw, sizeof(configured_bw)); - - // Show both configured and requested bandwidth, look for wifi_connect in logs to get negotiated - if (bw_detected) { - ESP_LOGI(TAG, " Bandwidth: %s (requested) - NVS configured: %s", bw_str, configured_bw); - - // Warn if mismatch - bool mismatch = false; - if (strcmp(configured_bw, "VHT80") == 0 && bw != WIFI_BW80) { - mismatch = true; - } else if (strcmp(configured_bw, "HT40") == 0 && bw != WIFI_BW_HT40) { - mismatch = true; - } else if (strcmp(configured_bw, "HT20") == 0 && bw != WIFI_BW_HT20) { - mismatch = true; - } - - if (mismatch) { - ESP_LOGW(TAG, " ⚠ Bandwidth mismatch! Configured %s but negotiated %s", configured_bw, bw_str); - ESP_LOGW(TAG, " Check: router channel width setting, channel selection, RF interference"); - } - } else { - ESP_LOGI(TAG, " Bandwidth: Unknown (configured: %s)", configured_bw); - } - - ESP_LOGI(TAG, " Channel: %d", ap_info.primary); - ESP_LOGI(TAG, " RSSI: %d dBm", ap_info.rssi); - - // Get and display power save mode - wifi_ps_type_t ps_mode = wifi_cfg_get_power_save_mode(); - const char *ps_str = "Unknown"; - switch (ps_mode) { - case WIFI_PS_NONE: - ps_str = "None (best for CSI)"; - break; - case WIFI_PS_MIN_MODEM: - ps_str = "Minimum Modem"; - break; - case WIFI_PS_MAX_MODEM: - ps_str = "Maximum Modem"; - break; - default: - ps_str = "Unknown"; - break; - } - ESP_LOGI(TAG, " PowerSave: %s", ps_str); - - ESP_LOGI(TAG, " BSSID: %02x:%02x:%02x:%02x:%02x:%02x", - ap_info.bssid[0], ap_info.bssid[1], ap_info.bssid[2], - ap_info.bssid[3], ap_info.bssid[4], ap_info.bssid[5]); - ESP_LOGI(TAG, "========================================"); - } else { - ESP_LOGI(TAG, "WiFi CONNECTED - BLUE LED solid"); - } - - // Try CSI first (might fail on ESP32-C5) + // Sequence: 1. Start CSI, 2. Start Monitor, 3. Start Iperf xTaskCreate(csi_init_task, "csi_init", 4096, NULL, 5, NULL); - // Start WiFi monitor mode (works reliably) vTaskDelay(pdMS_TO_TICKS(2000)); xTaskCreate(monitor_init_task, "monitor_init", 4096, NULL, 5, NULL); vTaskDelay(pdMS_TO_TICKS(1000)); - iperf_cfg_t cfg; memset(&cfg, 0, sizeof(cfg)); cfg.flag = IPERF_FLAG_SERVER | IPERF_FLAG_TCP; cfg.sport = 5001; - iperf_start(&cfg); ESP_LOGI(TAG, "iperf TCP server started on port 5001"); + // Optional: Dump CSI data later xTaskCreate(csi_dump_task, "csi_dump_task", 4096, NULL, 5, NULL); } } +// --- Main Application Entry ---------------------------------------- void app_main(void) { + // 1. Initialize Non-Volatile Storage (needed for WiFi config) ESP_ERROR_CHECK(nvs_flash_init()); + + // 2. Initialize Netif (TCP/IP stack) ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); + + // 3. Initialize Custom Logging & LED ESP_ERROR_CHECK(csi_log_init()); rgb_led_init(); - xTaskCreate(led_task, "led_task", 4096, NULL, 5, NULL); + // 4. Initialize GPS (The new addition!) + // We do this EARLY so timestamps are ready when WiFi events happen + ESP_LOGI(TAG, "Starting GPS Sync..."); + gps_sync_init(true); // true = Use GPS for system log timestamps + + // 5. Register WiFi Events ESP_ERROR_CHECK(esp_event_handler_instance_register( WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, NULL)); ESP_ERROR_CHECK(esp_event_handler_instance_register( IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, NULL)); + // 6. Initialize WiFi Configuration wifi_cfg_init(); if (wifi_cfg_apply_from_nvs()) { has_config = true; current_led_state = LED_STATE_WAITING; - ESP_LOGI(TAG, "WiFi config loaded from NVS"); + ESP_LOGI(TAG, "WiFi config loaded. Connecting..."); } else { has_config = false; current_led_state = LED_STATE_NO_CONFIG; - ESP_LOGI(TAG, "No WiFi config - YELLOW LED"); + ESP_LOGI(TAG, "No WiFi config found. Yellow LED."); + ESP_LOGI(TAG, "Use CLI 'wifi_config_set ' to configure."); } - ESP_LOGI(TAG, "LED Status:"); - ESP_LOGI(TAG, " YELLOW solid = NO CONFIG (send CFG/END)"); - ESP_LOGI(TAG, " BLUE slow blink = Connecting"); - ESP_LOGI(TAG, " GREEN solid = Connected ✓"); - ESP_LOGI(TAG, " BLUE solid = Monitor Mode (capturing 802.11 frames)"); - ESP_LOGI(TAG, " RED fast blink = Failed ✗"); - + // 7. Loop forever (Logic is handled by tasks and events) while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); + // Optional: Print GPS status occasionally + if (!gps_is_synced()) { + // ESP_LOGI(TAG, "Waiting for GPS lock..."); + } } }