ESP32/components/wifi_controller/wifi_controller.c

750 lines
29 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};
/* 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;
last_batch_flush_ms = start_ms;
last_stats_log_ms = start_ms;
/* Batch mutex should already be created by switch_to_monitor */
if (s_batch_mutex == NULL) {
ESP_LOGE(TAG, "Batch mutex not initialized");
task_running = false;
}
while (task_running && s_batch_mutex != NULL) {
vTaskDelay(pdMS_TO_TICKS(1000)); /* Check every 1 second for batching */
uint64_t now_ms = esp_timer_get_time() / 1000;
if (s_monitor_frame_count == last_frame_count) {
status_led_set_capture_active(false);
}
last_frame_count = s_monitor_frame_count;
/* 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 Params
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;
}
// --- 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) channel = 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;
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));
s_telemetry_gen_rate.last_update_ms = start_ms;
s_sd_write_rate.last_update_ms = start_ms;
/* 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;
}
}
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) {
printf(" Channel: %d\n", s_monitor_channel_active);
printf(" Frames: %lu\n", (unsigned long)s_monitor_frame_count);
/* 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) ---
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 (wifi_cfg_set_monitor_channel(s_monitor_channel_staging)) {
ESP_LOGI(TAG, "Monitor channel (%d) saved to NVS", s_monitor_channel_staging);
} 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;
ESP_LOGI(TAG, "Reloaded monitor channel: %d", 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);
}