843 lines
34 KiB
C
843 lines
34 KiB
C
/*
|
|
* wifi_controller.c
|
|
*
|
|
* Copyright (c) 2025 Umber Networks & Robert McMahon
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#include "wifi_controller.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/semphr.h"
|
|
#include "esp_log.h"
|
|
#include "esp_wifi.h"
|
|
#include "esp_event.h"
|
|
#include "esp_netif.h"
|
|
#include "esp_timer.h"
|
|
#include "inttypes.h"
|
|
#include <string.h>
|
|
#include "wifi_cfg.h"
|
|
|
|
// Dependencies
|
|
#include "iperf.h"
|
|
#include "status_led.h"
|
|
#include "wifi_monitor.h"
|
|
#include "gps_sync.h"
|
|
|
|
#ifdef CONFIG_ESP_WIFI_CSI_ENABLED
|
|
#include "csi_manager.h"
|
|
#endif
|
|
|
|
#include "mcs_telemetry.h"
|
|
#include "sd_card.h"
|
|
|
|
#define FIWI_TELEMETRY_FILE "fiwi-telemetry"
|
|
#define TELEMETRY_JSON_BUF_SIZE 4096
|
|
|
|
/* SD card write optimization:
|
|
* - 16KB batch size: Optimal for high-frequency telemetry (4KB-64KB range, sweet spot 4-16KB)
|
|
* - Multiple of 512 bytes (SD sector size): 16KB = 32 sectors, ensures good alignment
|
|
* - Balances performance vs RAM usage for embedded systems
|
|
* - For ESP32: 16KB is reasonable RAM usage and provides excellent write efficiency
|
|
*/
|
|
#define TELEMETRY_BATCH_SIZE (16 * 1024) /* 16KB batch buffer for SD card writes */
|
|
#define TELEMETRY_BATCH_FLUSH_INTERVAL_MS 5000 /* Flush batch every 5 seconds or when 80% full */
|
|
|
|
static const char *TAG = "WIFI_CTL";
|
|
|
|
static wifi_ctl_mode_t s_current_mode = WIFI_CTL_MODE_STA;
|
|
static uint8_t s_monitor_channel_active = 6;
|
|
static uint8_t s_monitor_channel_staging = 6;
|
|
|
|
static bool s_monitor_enabled = false;
|
|
static uint32_t s_monitor_frame_count = 0;
|
|
static TaskHandle_t s_monitor_stats_task_handle = NULL;
|
|
static bool s_monitor_debug = false; /* Debug mode: enable serial logging */
|
|
|
|
/* Telemetry rate tracking (Welford algorithm) */
|
|
typedef struct {
|
|
uint64_t total_bytes; /* Total bytes generated/written */
|
|
double mean_rate_bps; /* Mean rate in bytes per second */
|
|
double m2; /* Sum of squares of differences (for variance) */
|
|
uint32_t sample_count; /* Number of samples */
|
|
uint64_t last_update_ms; /* Last update timestamp */
|
|
} rate_tracker_t;
|
|
|
|
static rate_tracker_t s_telemetry_gen_rate = {0};
|
|
static rate_tracker_t s_sd_write_rate = {0};
|
|
static rate_tracker_t s_frame_rate = {0};
|
|
static uint32_t s_last_frame_count_for_rate = 0;
|
|
|
|
/* Batch buffer for telemetry (shared between task and flush function) */
|
|
static char s_batch_buf[TELEMETRY_BATCH_SIZE];
|
|
static size_t s_batch_offset = 0;
|
|
static SemaphoreHandle_t s_batch_mutex = NULL;
|
|
|
|
/* Static flush buffer to avoid large stack allocations (16KB) */
|
|
static char s_flush_buf[TELEMETRY_BATCH_SIZE];
|
|
|
|
// --- Event Handler ---
|
|
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
|
|
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
|
ESP_LOGI(TAG, "Got IP -> LED Connected");
|
|
status_led_set_state(LED_STATE_CONNECTED);
|
|
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
|
if (s_current_mode == WIFI_CTL_MODE_STA) {
|
|
status_led_set_state(LED_STATE_NO_CONFIG);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ... [Log Collapse / Monitor Callback Logic] ...
|
|
static void log_collapse_event(uint32_t nav_duration_us, int rssi, int retry) {
|
|
if (s_monitor_debug) { /* Only log in debug mode */
|
|
gps_timestamp_t ts = gps_get_timestamp();
|
|
int64_t now_ms = ts.gps_us / 1000;
|
|
ESP_LOGI(TAG, "COLLAPSE: Time=%" PRId64 "ms, Sync=%d, Dur=%lu us, RSSI=%d, Retry=%d",
|
|
now_ms, ts.synced ? 1 : 0, nav_duration_us, rssi, retry);
|
|
}
|
|
}
|
|
|
|
static void monitor_frame_callback(const wifi_frame_info_t *frame, const uint8_t *payload, uint16_t len) {
|
|
(void)payload;
|
|
(void)len;
|
|
s_monitor_frame_count++;
|
|
status_led_set_capture_active(true);
|
|
if (frame->retry && frame->duration_id > 5000) {
|
|
log_collapse_event((float)frame->duration_id, frame->rssi, frame->retry);
|
|
}
|
|
/* MCS telemetry: feed frames to fiwi-telemetry (default on monitor start) */
|
|
mcs_telemetry_process_frame(frame, NULL);
|
|
}
|
|
|
|
/**
|
|
* @brief Update rate tracker using Welford's online algorithm
|
|
* @param tracker Rate tracker to update
|
|
* @param bytes Bytes generated/written in this interval
|
|
* @param interval_ms Time interval in milliseconds
|
|
*/
|
|
static void update_rate_tracker(rate_tracker_t *tracker, size_t bytes, uint32_t interval_ms) {
|
|
if (interval_ms > 0) {
|
|
double rate_bps = (bytes * 1000.0) / interval_ms; /* Convert to bytes per second */
|
|
tracker->total_bytes += bytes;
|
|
tracker->sample_count++;
|
|
|
|
/* Welford's online algorithm for running mean and variance */
|
|
double delta = rate_bps - tracker->mean_rate_bps;
|
|
tracker->mean_rate_bps += delta / tracker->sample_count;
|
|
double delta2 = rate_bps - tracker->mean_rate_bps;
|
|
tracker->m2 += delta * delta2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Flush pending telemetry batch to SD card
|
|
*/
|
|
static void flush_telemetry_batch(void) {
|
|
size_t offset = 0;
|
|
|
|
if (s_batch_mutex != NULL) {
|
|
if (xSemaphoreTake(s_batch_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
offset = s_batch_offset;
|
|
xSemaphoreGive(s_batch_mutex);
|
|
}
|
|
|
|
if (offset > 0 && sd_card_is_ready()) {
|
|
size_t flush_size = 0;
|
|
|
|
if (xSemaphoreTake(s_batch_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
/* Copy batch to static buffer for safe write */
|
|
memcpy(s_flush_buf, s_batch_buf, offset);
|
|
flush_size = offset;
|
|
s_batch_offset = 0;
|
|
xSemaphoreGive(s_batch_mutex);
|
|
}
|
|
|
|
if (flush_size > 0) {
|
|
if (sd_card_write_file(FIWI_TELEMETRY_FILE, s_flush_buf, flush_size, true) == ESP_OK) {
|
|
uint64_t now_ms = esp_timer_get_time() / 1000;
|
|
uint32_t write_interval_ms = (uint32_t)(now_ms - s_sd_write_rate.last_update_ms);
|
|
if (write_interval_ms > 0) {
|
|
update_rate_tracker(&s_sd_write_rate, flush_size, write_interval_ms);
|
|
s_sd_write_rate.last_update_ms = now_ms;
|
|
}
|
|
if (s_monitor_debug) {
|
|
ESP_LOGD(TAG, "Batch flushed on stop: %zu bytes", flush_size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void monitor_stats_task(void *arg) {
|
|
(void)arg;
|
|
static char json_buf[TELEMETRY_JSON_BUF_SIZE];
|
|
uint32_t flush_count = 0;
|
|
uint32_t last_frame_count = 0;
|
|
uint64_t last_batch_flush_ms = 0;
|
|
uint64_t last_stats_log_ms = 0;
|
|
bool task_running = true;
|
|
|
|
/* Initialize rate trackers */
|
|
uint64_t start_ms = esp_timer_get_time() / 1000;
|
|
s_telemetry_gen_rate.last_update_ms = start_ms;
|
|
s_sd_write_rate.last_update_ms = start_ms;
|
|
s_frame_rate.last_update_ms = start_ms;
|
|
s_last_frame_count_for_rate = s_monitor_frame_count;
|
|
last_batch_flush_ms = start_ms;
|
|
last_stats_log_ms = start_ms;
|
|
|
|
/* Batch mutex should already be created by switch_to_monitor */
|
|
if (s_batch_mutex == NULL) {
|
|
ESP_LOGE(TAG, "Batch mutex not initialized");
|
|
task_running = false;
|
|
}
|
|
|
|
while (task_running && s_batch_mutex != NULL) {
|
|
vTaskDelay(pdMS_TO_TICKS(1000)); /* Check every 1 second for batching */
|
|
uint64_t now_ms = esp_timer_get_time() / 1000;
|
|
|
|
if (s_monitor_frame_count == last_frame_count) {
|
|
status_led_set_capture_active(false);
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* Generate telemetry JSON (regardless of SD card status) */
|
|
if (mcs_telemetry_to_json(json_buf, sizeof(json_buf), "esp32") == ESP_OK) {
|
|
size_t len = strlen(json_buf);
|
|
if (len > 0) {
|
|
json_buf[len] = '\n'; /* NDJSON: one object per line */
|
|
size_t total_len = len + 1;
|
|
|
|
/* Update telemetry generation rate (always track generation) */
|
|
uint32_t interval_ms = (uint32_t)(now_ms - s_telemetry_gen_rate.last_update_ms);
|
|
if (interval_ms > 0) {
|
|
update_rate_tracker(&s_telemetry_gen_rate, total_len, interval_ms);
|
|
s_telemetry_gen_rate.last_update_ms = now_ms;
|
|
}
|
|
|
|
/* Only write to SD card if ready */
|
|
bool card_ready = sd_card_is_ready();
|
|
if (!card_ready && s_monitor_debug) {
|
|
/* Log when card is not ready (only in debug mode to avoid spam) */
|
|
static uint64_t last_not_ready_log_ms = 0;
|
|
uint64_t now_ms_check = esp_timer_get_time() / 1000;
|
|
if (now_ms_check - last_not_ready_log_ms > 10000) { /* Log at most every 10 seconds */
|
|
ESP_LOGW(TAG, "SD card not ready, telemetry not being written");
|
|
last_not_ready_log_ms = now_ms_check;
|
|
}
|
|
}
|
|
if (card_ready) {
|
|
/* Add to batch buffer (with mutex protection) */
|
|
if (s_batch_mutex && xSemaphoreTake(s_batch_mutex, portMAX_DELAY) == pdTRUE) {
|
|
if (s_batch_offset + total_len < TELEMETRY_BATCH_SIZE) {
|
|
memcpy(s_batch_buf + s_batch_offset, json_buf, total_len);
|
|
s_batch_offset += total_len;
|
|
xSemaphoreGive(s_batch_mutex);
|
|
} else {
|
|
/* Batch buffer full, flush immediately */
|
|
size_t flush_size = s_batch_offset;
|
|
if (flush_size > 0) {
|
|
memcpy(s_flush_buf, s_batch_buf, flush_size);
|
|
s_batch_offset = 0;
|
|
xSemaphoreGive(s_batch_mutex);
|
|
|
|
esp_err_t write_err = sd_card_write_file(FIWI_TELEMETRY_FILE, s_flush_buf, flush_size, true);
|
|
if (write_err == ESP_OK) {
|
|
flush_count++;
|
|
uint32_t write_interval_ms = (uint32_t)(now_ms - s_sd_write_rate.last_update_ms);
|
|
if (write_interval_ms > 0) {
|
|
update_rate_tracker(&s_sd_write_rate, flush_size, write_interval_ms);
|
|
s_sd_write_rate.last_update_ms = now_ms;
|
|
}
|
|
if (s_monitor_debug) {
|
|
ESP_LOGD(TAG, "Batch flushed: %zu bytes (#%lu)", flush_size, (unsigned long)flush_count);
|
|
}
|
|
} else {
|
|
/* Log write failures - this helps diagnose SD card issues */
|
|
ESP_LOGE(TAG, "Failed to write telemetry batch (buffer full): %s (%zu bytes)", esp_err_to_name(write_err), flush_size);
|
|
}
|
|
} else {
|
|
xSemaphoreGive(s_batch_mutex);
|
|
}
|
|
|
|
/* Add current JSON to fresh batch */
|
|
if (total_len < TELEMETRY_BATCH_SIZE) {
|
|
if (xSemaphoreTake(s_batch_mutex, portMAX_DELAY) == pdTRUE) {
|
|
memcpy(s_batch_buf, json_buf, total_len);
|
|
s_batch_offset = total_len;
|
|
xSemaphoreGive(s_batch_mutex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Flush batch periodically or if buffer is getting full */
|
|
size_t current_offset = 0;
|
|
if (s_batch_mutex && xSemaphoreTake(s_batch_mutex, 0) == pdTRUE) {
|
|
current_offset = s_batch_offset;
|
|
xSemaphoreGive(s_batch_mutex);
|
|
}
|
|
|
|
uint32_t time_since_flush_ms = (uint32_t)(now_ms - last_batch_flush_ms);
|
|
bool should_flush = (time_since_flush_ms >= TELEMETRY_BATCH_FLUSH_INTERVAL_MS) ||
|
|
(current_offset > TELEMETRY_BATCH_SIZE * 0.8); /* Flush at 80% full */
|
|
|
|
if (should_flush && current_offset > 0 && s_batch_mutex && sd_card_is_ready()) {
|
|
size_t flush_size = 0;
|
|
|
|
if (xSemaphoreTake(s_batch_mutex, portMAX_DELAY) == pdTRUE) {
|
|
memcpy(s_flush_buf, s_batch_buf, current_offset);
|
|
flush_size = current_offset;
|
|
s_batch_offset = 0;
|
|
xSemaphoreGive(s_batch_mutex);
|
|
}
|
|
|
|
if (flush_size > 0) {
|
|
esp_err_t write_err = sd_card_write_file(FIWI_TELEMETRY_FILE, s_flush_buf, flush_size, true);
|
|
if (write_err == ESP_OK) {
|
|
flush_count++;
|
|
uint32_t write_interval_ms = (uint32_t)(now_ms - s_sd_write_rate.last_update_ms);
|
|
if (write_interval_ms > 0) {
|
|
update_rate_tracker(&s_sd_write_rate, flush_size, write_interval_ms);
|
|
s_sd_write_rate.last_update_ms = now_ms;
|
|
}
|
|
if (s_monitor_debug) {
|
|
ESP_LOGD(TAG, "Batch flushed: %zu bytes (#%lu, gen: %.1f B/s, write: %.1f B/s)",
|
|
flush_size, (unsigned long)flush_count,
|
|
s_telemetry_gen_rate.mean_rate_bps, s_sd_write_rate.mean_rate_bps);
|
|
}
|
|
last_batch_flush_ms = now_ms;
|
|
} else {
|
|
/* Log write failures - this helps diagnose SD card issues */
|
|
ESP_LOGE(TAG, "Failed to write telemetry batch: %s (%zu bytes)", esp_err_to_name(write_err), flush_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Log stats periodically (only in debug mode) */
|
|
uint32_t time_since_log_ms = (uint32_t)(now_ms - last_stats_log_ms);
|
|
if (s_monitor_debug && time_since_log_ms >= 10000) {
|
|
wifi_collapse_stats_t stats;
|
|
if (wifi_monitor_get_stats(&stats) == ESP_OK) {
|
|
ESP_LOGD("MONITOR", "--- Stats: %lu frames, Retry: %.2f%%, Avg NAV: %u us ---",
|
|
(unsigned long)stats.total_frames, stats.retry_rate, stats.avg_nav);
|
|
ESP_LOGD("MONITOR", "Telemetry: gen=%.1f B/s (total=%llu), write=%.1f B/s (total=%llu)",
|
|
s_telemetry_gen_rate.mean_rate_bps, (unsigned long long)s_telemetry_gen_rate.total_bytes,
|
|
s_sd_write_rate.mean_rate_bps, (unsigned long long)s_sd_write_rate.total_bytes);
|
|
if (wifi_monitor_is_collapsed()) {
|
|
ESP_LOGW("MONITOR", "⚠️ COLLAPSE DETECTED! ⚠️");
|
|
}
|
|
}
|
|
last_stats_log_ms = now_ms;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Helper to apply IP settings ---
|
|
static void apply_ip_settings(void) {
|
|
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
|
|
|
if (netif != NULL) {
|
|
if (wifi_cfg_get_dhcp()) {
|
|
esp_netif_dhcpc_start(netif);
|
|
} else {
|
|
esp_netif_dhcpc_stop(netif);
|
|
|
|
char ip[16], mask[16], gw[16];
|
|
if (wifi_cfg_get_ipv4(ip, mask, gw)) {
|
|
esp_netif_ip_info_t info = {0};
|
|
// API Fix: esp_ip4addr_aton returns uint32_t
|
|
info.ip.addr = esp_ip4addr_aton(ip);
|
|
info.netmask.addr = esp_ip4addr_aton(mask);
|
|
info.gw.addr = esp_ip4addr_aton(gw);
|
|
|
|
esp_netif_set_ip_info(netif, &info);
|
|
ESP_LOGI(TAG, "Static IP applied: %s", ip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// PUBLIC API IMPLEMENTATION
|
|
// ============================================================================
|
|
|
|
void wifi_ctl_init(void) {
|
|
s_current_mode = WIFI_CTL_MODE_STA;
|
|
s_monitor_enabled = false;
|
|
s_monitor_frame_count = 0;
|
|
|
|
// 1. Initialize Network Interface
|
|
esp_netif_create_default_wifi_sta();
|
|
|
|
// 2. Apply IP Settings (Static vs DHCP)
|
|
apply_ip_settings();
|
|
|
|
// 3. Initialize Wi-Fi Driver
|
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
|
|
|
// 4. Register Events
|
|
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL));
|
|
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &wifi_event_handler, NULL, NULL));
|
|
|
|
// 5. Configure Storage & Mode
|
|
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
|
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
|
ESP_ERROR_CHECK(esp_wifi_start());
|
|
|
|
// 6. Apply Saved Config
|
|
if (!wifi_cfg_apply_from_nvs()) {
|
|
ESP_LOGW(TAG, "No saved WiFi config found, driver initialized in defaults.");
|
|
status_led_set_state(LED_STATE_NO_CONFIG);
|
|
} else {
|
|
ESP_LOGI(TAG, "WiFi driver initialized from NVS.");
|
|
status_led_set_state(LED_STATE_WAITING);
|
|
esp_wifi_connect();
|
|
}
|
|
|
|
// Load Staging and Active Params from NVS
|
|
char mode_ignored[16];
|
|
wifi_cfg_get_mode(mode_ignored, &s_monitor_channel_staging);
|
|
if (s_monitor_channel_staging == 0) s_monitor_channel_staging = 6;
|
|
|
|
// Load active channel from NVS (persists across reboots)
|
|
uint8_t saved_active_channel = 0;
|
|
if (wifi_cfg_get_monitor_channel(&saved_active_channel) && saved_active_channel > 0 && saved_active_channel <= 14) {
|
|
s_monitor_channel_active = saved_active_channel;
|
|
// Also update staging to match active if staging wasn't set
|
|
if (s_monitor_channel_staging == 6) {
|
|
s_monitor_channel_staging = saved_active_channel;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Mode Control (Core) ---
|
|
|
|
esp_err_t wifi_ctl_switch_to_monitor(uint8_t channel, wifi_bandwidth_t bw) {
|
|
esp_err_t result = ESP_OK;
|
|
|
|
if (channel == 0) {
|
|
// Use active channel if set, otherwise fall back to staging
|
|
channel = (s_monitor_channel_active > 0) ? s_monitor_channel_active : s_monitor_channel_staging;
|
|
}
|
|
|
|
if (s_current_mode == WIFI_CTL_MODE_MONITOR && s_monitor_channel_active == channel) {
|
|
ESP_LOGW(TAG, "Already in monitor mode (Ch %d)", channel);
|
|
result = 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_err_t wifi_ctl_switch_to_sta(void) {
|
|
esp_err_t result = ESP_OK;
|
|
|
|
if (s_current_mode == WIFI_CTL_MODE_STA) {
|
|
ESP_LOGI(TAG, "Already in STA mode");
|
|
result = 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;
|
|
}
|
|
|
|
// --- Wrappers for cmd_monitor.c ---
|
|
|
|
void wifi_ctl_monitor_start(int channel) {
|
|
wifi_ctl_switch_to_monitor((uint8_t)channel, WIFI_BW_HT20);
|
|
}
|
|
|
|
void wifi_ctl_stop(void) {
|
|
wifi_ctl_switch_to_sta();
|
|
}
|
|
|
|
void wifi_ctl_start_station(void) {
|
|
wifi_ctl_switch_to_sta();
|
|
}
|
|
|
|
void wifi_ctl_start_ap(void) {
|
|
ESP_LOGW(TAG, "AP Mode not fully implemented, using STA");
|
|
wifi_ctl_switch_to_sta();
|
|
}
|
|
|
|
// --- Settings ---
|
|
|
|
void wifi_ctl_set_channel(int channel) {
|
|
if (channel < 1 || channel > 14) {
|
|
ESP_LOGE(TAG, "Invalid channel %d", channel);
|
|
return;
|
|
}
|
|
s_monitor_channel_staging = (uint8_t)channel;
|
|
|
|
if (s_current_mode == WIFI_CTL_MODE_MONITOR) {
|
|
ESP_LOGI(TAG, "Switching live channel to %d", channel);
|
|
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
|
|
s_monitor_channel_active = (uint8_t)channel;
|
|
/* Save active channel to NVS so it persists across reboots */
|
|
wifi_cfg_set_monitor_channel(channel);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Calculate WiFi channel frequency in MHz
|
|
* @param channel Channel number (1-14 for 2.4GHz, 36+ for 5GHz)
|
|
* @return Frequency in MHz, or 0 if invalid channel
|
|
*/
|
|
static uint32_t wifi_channel_to_frequency(uint8_t channel) {
|
|
uint32_t freq = 0;
|
|
|
|
if (channel >= 1 && channel <= 14) {
|
|
/* 2.4 GHz band: 2407 + (channel * 5) MHz */
|
|
freq = 2407 + (channel * 5);
|
|
} else if (channel >= 36 && channel <= 64) {
|
|
/* 5 GHz UNII-1/2A: 5000 + (channel * 5) MHz */
|
|
freq = 5000 + (channel * 5);
|
|
} else if (channel >= 100 && channel <= 144) {
|
|
/* 5 GHz UNII-2C: 5000 + (channel * 5) MHz */
|
|
freq = 5000 + (channel * 5);
|
|
} else if (channel >= 149 && channel <= 165) {
|
|
/* 5 GHz UNII-3: 5000 + (channel * 5) MHz */
|
|
freq = 5000 + (channel * 5);
|
|
} else if (channel >= 169 && channel <= 177) {
|
|
/* 5 GHz UNII-4: 5000 + (channel * 5) MHz */
|
|
freq = 5000 + (channel * 5);
|
|
}
|
|
|
|
return freq;
|
|
}
|
|
|
|
void wifi_ctl_status(void) {
|
|
const char *mode_str = (s_current_mode == WIFI_CTL_MODE_MONITOR) ? "MONITOR" :
|
|
(s_current_mode == WIFI_CTL_MODE_AP) ? "AP" : "STATION";
|
|
|
|
printf("WiFi Status:\n");
|
|
printf(" Mode: %s\n", mode_str);
|
|
if (s_current_mode == WIFI_CTL_MODE_MONITOR) {
|
|
uint8_t channel = s_monitor_channel_active;
|
|
uint32_t freq_mhz = wifi_channel_to_frequency(channel);
|
|
|
|
/* Display channel info: channel N (FREQ MHz), width: 20 MHz, center1: FREQ MHz */
|
|
/* Note: Monitor mode currently uses single channel (20 MHz width) */
|
|
if (freq_mhz > 0) {
|
|
printf(" Channel: %d (%" PRIu32 " MHz), width: 20 MHz, center1: %" PRIu32 " MHz\n",
|
|
channel, freq_mhz, freq_mhz);
|
|
} else {
|
|
printf(" Channel: %d\n", channel);
|
|
}
|
|
/* Get frame rate (frames per second) */
|
|
double frame_rate_fps = 0.0;
|
|
if (s_frame_rate.sample_count > 0) {
|
|
frame_rate_fps = s_frame_rate.mean_rate_bps; /* Already in per-second units for frames */
|
|
}
|
|
printf(" Frames: %lu, %.1f fps\n", (unsigned long)s_monitor_frame_count, frame_rate_fps);
|
|
|
|
/* Show telemetry rate statistics */
|
|
uint64_t gen_bytes = 0, write_bytes = 0;
|
|
double gen_rate = 0.0, write_rate = 0.0;
|
|
wifi_ctl_get_telemetry_gen_stats(&gen_bytes, &gen_rate);
|
|
wifi_ctl_get_sd_write_stats(&write_bytes, &write_rate);
|
|
|
|
/* Get bytes pending in batch buffer (awaiting flush) */
|
|
size_t bytes_in_batch = 0;
|
|
if (s_batch_mutex != NULL && xSemaphoreTake(s_batch_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
bytes_in_batch = s_batch_offset;
|
|
xSemaphoreGive(s_batch_mutex);
|
|
}
|
|
|
|
printf(" Telemetry (SD Card = %s):\n", sd_card_is_ready() ? "ready" : "not ready");
|
|
printf(" Generated: %llu bytes, %.1f B/s\n", (unsigned long long)gen_bytes, gen_rate);
|
|
printf(" Written: %llu bytes, %.1f B/s\n", (unsigned long long)write_bytes, write_rate);
|
|
if (gen_bytes > write_bytes) {
|
|
/* Account for bytes in batch buffer - they're not dropped, just pending */
|
|
uint64_t pending = (uint64_t)bytes_in_batch;
|
|
uint64_t dropped = (gen_bytes > write_bytes + pending) ? (gen_bytes - write_bytes - pending) : 0;
|
|
if (dropped > 0) {
|
|
printf(" Dropped: %llu bytes (%.1f%%)\n", (unsigned long long)dropped,
|
|
(dropped * 100.0) / gen_bytes);
|
|
}
|
|
if (pending > 0) {
|
|
printf(" Pending: %zu bytes (in batch buffer)\n", bytes_in_batch);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Params (NVS) ---
|
|
|
|
bool wifi_ctl_param_is_unsaved(void) {
|
|
return wifi_cfg_monitor_channel_is_unsaved(s_monitor_channel_staging);
|
|
}
|
|
|
|
void wifi_ctl_param_save(const char *dummy) {
|
|
(void)dummy;
|
|
/* If monitor mode is running, save the active channel; otherwise save staging */
|
|
uint8_t channel_to_save = (s_current_mode == WIFI_CTL_MODE_MONITOR) ?
|
|
s_monitor_channel_active : s_monitor_channel_staging;
|
|
if (wifi_cfg_set_monitor_channel(channel_to_save)) {
|
|
ESP_LOGI(TAG, "Monitor channel (%d) saved to NVS", channel_to_save);
|
|
/* Update staging to match if we saved active */
|
|
if (s_current_mode == WIFI_CTL_MODE_MONITOR) {
|
|
s_monitor_channel_staging = channel_to_save;
|
|
}
|
|
} else {
|
|
ESP_LOGI(TAG, "No changes to save.");
|
|
}
|
|
}
|
|
|
|
void wifi_ctl_param_init(void) {
|
|
char mode_ignored[16];
|
|
uint8_t ch = 0;
|
|
wifi_cfg_get_mode(mode_ignored, &ch);
|
|
if (ch > 0) s_monitor_channel_staging = ch;
|
|
|
|
// 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) {
|
|
wifi_cfg_clear_monitor_channel();
|
|
s_monitor_channel_staging = 6;
|
|
ESP_LOGI(TAG, "Monitor config cleared (Defaulting to Ch 6).");
|
|
}
|
|
|
|
// --- Getters ---
|
|
|
|
wifi_ctl_mode_t wifi_ctl_get_mode(void) { return s_current_mode; }
|
|
int wifi_ctl_get_channel(void) { return s_monitor_channel_active; }
|
|
|
|
/**
|
|
* @brief Get telemetry generation rate statistics
|
|
* @param total_bytes Total bytes generated (output)
|
|
* @param rate_bps Mean generation rate in bytes per second (output)
|
|
* @return ESP_OK on success
|
|
*/
|
|
esp_err_t wifi_ctl_get_telemetry_gen_stats(uint64_t *total_bytes, double *rate_bps) {
|
|
esp_err_t result = ESP_OK;
|
|
|
|
if (total_bytes == NULL || rate_bps == NULL) {
|
|
result = ESP_ERR_INVALID_ARG;
|
|
} else {
|
|
if (s_batch_mutex != NULL && xSemaphoreTake(s_batch_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
*total_bytes = s_telemetry_gen_rate.total_bytes;
|
|
*rate_bps = s_telemetry_gen_rate.mean_rate_bps;
|
|
xSemaphoreGive(s_batch_mutex);
|
|
} else {
|
|
*total_bytes = s_telemetry_gen_rate.total_bytes;
|
|
*rate_bps = s_telemetry_gen_rate.mean_rate_bps;
|
|
}
|
|
result = ESP_OK;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief Get SD card write rate statistics
|
|
* @param total_bytes Total bytes written (output)
|
|
* @param rate_bps Mean write rate in bytes per second (output)
|
|
* @return ESP_OK on success
|
|
*/
|
|
esp_err_t wifi_ctl_get_sd_write_stats(uint64_t *total_bytes, double *rate_bps) {
|
|
esp_err_t result = ESP_OK;
|
|
|
|
if (total_bytes == NULL || rate_bps == NULL) {
|
|
result = ESP_ERR_INVALID_ARG;
|
|
} else {
|
|
if (s_batch_mutex != NULL && xSemaphoreTake(s_batch_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
*total_bytes = s_sd_write_rate.total_bytes;
|
|
*rate_bps = s_sd_write_rate.mean_rate_bps;
|
|
xSemaphoreGive(s_batch_mutex);
|
|
} else {
|
|
*total_bytes = s_sd_write_rate.total_bytes;
|
|
*rate_bps = s_sd_write_rate.mean_rate_bps;
|
|
}
|
|
result = ESP_OK;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief Enable/disable monitor debug mode (serial logging)
|
|
* @param enable true to enable debug logging, false to disable
|
|
*/
|
|
void wifi_ctl_set_monitor_debug(bool enable) {
|
|
s_monitor_debug = enable;
|
|
wifi_monitor_set_debug(enable); /* Also set debug mode in wifi_monitor */
|
|
}
|
|
|
|
/**
|
|
* @brief Get monitor debug mode status
|
|
* @return true if debug mode enabled, false otherwise
|
|
*/
|
|
bool wifi_ctl_get_monitor_debug(void) {
|
|
return s_monitor_debug;
|
|
}
|
|
|
|
// --- Deprecated ---
|
|
static void auto_monitor_task_func(void *arg) {
|
|
uint8_t channel = (uint8_t)(uintptr_t)arg;
|
|
ESP_LOGI(TAG, "Waiting for WiFi connection before switching to monitor mode...");
|
|
while (status_led_get_state() != LED_STATE_CONNECTED) {
|
|
vTaskDelay(pdMS_TO_TICKS(500));
|
|
}
|
|
ESP_LOGI(TAG, "WiFi connected, waiting for GPS sync (2s)...");
|
|
vTaskDelay(pdMS_TO_TICKS(2000));
|
|
wifi_ctl_switch_to_monitor(channel, WIFI_BW_HT20);
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
void wifi_ctl_auto_monitor_start(uint8_t channel) {
|
|
xTaskCreate(auto_monitor_task_func, "auto_monitor", 4096, (void*)(uintptr_t)channel, 5, NULL);
|
|
}
|