feat: Add v0.4 adaptive sampling — wander detection, auto rate control
On-device CSI wander calculation (coefficient of variation over 50-packet window). Rate drops to 10 Hz when idle, jumps to 100 Hz on motion with 3s holdoff. EVENT notifications sent to Pi on rate changes. New commands: ADAPTIVE ON/OFF, THRESHOLD. RATE command disables adaptive mode. All settings NVS-persisted.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
@@ -80,6 +81,20 @@ static volatile int64_t s_last_csi_time = 0;
|
||||
static volatile int64_t s_identify_end_time = 0;
|
||||
static volatile bool s_ota_in_progress = false;
|
||||
|
||||
/* Adaptive sampling */
|
||||
#define WANDER_WINDOW 50
|
||||
#define RATE_ACTIVE 100
|
||||
#define RATE_IDLE 10
|
||||
#define IDLE_HOLDOFF_US 3000000LL /* 3s of no motion before dropping rate */
|
||||
#define DEFAULT_THRESHOLD 0.002f
|
||||
|
||||
static bool s_adaptive = false;
|
||||
static float s_motion_threshold = DEFAULT_THRESHOLD;
|
||||
static volatile bool s_motion_detected = false;
|
||||
static volatile int64_t s_last_motion_time = 0;
|
||||
static uint32_t s_energy_buf[WANDER_WINDOW];
|
||||
static uint32_t s_energy_idx = 0;
|
||||
|
||||
/* UDP socket for CSI data transmission */
|
||||
static int s_udp_socket = -1;
|
||||
static struct sockaddr_in s_dest_addr;
|
||||
@@ -99,8 +114,17 @@ static void config_load_nvs(void)
|
||||
if (nvs_get_i8(h, "tx_power", &pwr) == ESP_OK && pwr >= 2 && pwr <= 20) {
|
||||
s_tx_power_dbm = pwr;
|
||||
}
|
||||
int8_t adaptive;
|
||||
if (nvs_get_i8(h, "adaptive", &adaptive) == ESP_OK) {
|
||||
s_adaptive = (adaptive != 0);
|
||||
}
|
||||
int32_t thresh;
|
||||
if (nvs_get_i32(h, "threshold", &thresh) == ESP_OK && thresh > 0) {
|
||||
s_motion_threshold = (float)thresh / 1000000.0f;
|
||||
}
|
||||
nvs_close(h);
|
||||
ESP_LOGI(TAG, "NVS loaded: rate=%d tx_power=%d", s_send_frequency, s_tx_power_dbm);
|
||||
ESP_LOGI(TAG, "NVS loaded: rate=%d tx_power=%d adaptive=%d threshold=%.6f",
|
||||
s_send_frequency, s_tx_power_dbm, s_adaptive, s_motion_threshold);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "NVS: no saved config, using defaults");
|
||||
}
|
||||
@@ -286,6 +310,16 @@ static void wifi_csi_rx_cb(void *ctx, wifi_csi_info_t *info)
|
||||
sendto(s_udp_socket, s_udp_buffer, pos, 0, (struct sockaddr *)&s_dest_addr, sizeof(s_dest_addr));
|
||||
}
|
||||
|
||||
/* Compute CSI energy for adaptive sampling */
|
||||
if (s_adaptive) {
|
||||
uint32_t energy = 0;
|
||||
for (int i = 0; i < info->len; i++) {
|
||||
energy += abs(info->buf[i]);
|
||||
}
|
||||
s_energy_buf[s_energy_idx % WANDER_WINDOW] = energy;
|
||||
s_energy_idx++;
|
||||
}
|
||||
|
||||
s_count++;
|
||||
}
|
||||
|
||||
@@ -392,6 +426,71 @@ static esp_err_t wifi_ping_router_start(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* --- Adaptive sampling --- */
|
||||
|
||||
static void adaptive_task(void *arg)
|
||||
{
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
if (!s_adaptive || s_energy_idx < WANDER_WINDOW) continue;
|
||||
|
||||
/* Compute mean */
|
||||
float mean = 0;
|
||||
for (int i = 0; i < WANDER_WINDOW; i++) {
|
||||
mean += s_energy_buf[i];
|
||||
}
|
||||
mean /= WANDER_WINDOW;
|
||||
|
||||
if (mean < 1.0f) continue;
|
||||
|
||||
/* Compute variance */
|
||||
float var = 0;
|
||||
for (int i = 0; i < WANDER_WINDOW; i++) {
|
||||
float d = s_energy_buf[i] - mean;
|
||||
var += d * d;
|
||||
}
|
||||
var /= WANDER_WINDOW;
|
||||
|
||||
/* Wander = coefficient of variation squared */
|
||||
float wander = var / (mean * mean);
|
||||
|
||||
int64_t now = esp_timer_get_time();
|
||||
bool motion = wander > s_motion_threshold;
|
||||
|
||||
if (motion) {
|
||||
s_last_motion_time = now;
|
||||
}
|
||||
|
||||
int target_rate;
|
||||
if (motion || (now - s_last_motion_time < IDLE_HOLDOFF_US)) {
|
||||
target_rate = RATE_ACTIVE;
|
||||
} else {
|
||||
target_rate = RATE_IDLE;
|
||||
}
|
||||
|
||||
s_motion_detected = motion;
|
||||
|
||||
if (target_rate != s_send_frequency) {
|
||||
s_send_frequency = target_rate;
|
||||
wifi_ping_router_start();
|
||||
|
||||
/* Notify Pi */
|
||||
char event[80];
|
||||
int len = snprintf(event, sizeof(event),
|
||||
"EVENT motion=%d rate=%d wander=%.6f",
|
||||
motion ? 1 : 0, target_rate, wander);
|
||||
if (s_udp_socket >= 0) {
|
||||
sendto(s_udp_socket, event, len, 0,
|
||||
(struct sockaddr *)&s_dest_addr, sizeof(s_dest_addr));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Adaptive: %s -> %d Hz (wander=%.6f)",
|
||||
motion ? "motion" : "idle", target_rate, wander);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* --- OTA --- */
|
||||
|
||||
static void ota_task(void *arg)
|
||||
@@ -478,9 +577,10 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
|
||||
}
|
||||
|
||||
snprintf(reply, reply_size,
|
||||
"OK STATUS uptime=%s heap=%lu rssi=%d tx_power=%d rate=%d hostname=%s version=%s",
|
||||
"OK STATUS uptime=%s heap=%lu rssi=%d tx_power=%d rate=%d hostname=%s version=%s adaptive=%s motion=%d",
|
||||
uptime_str, (unsigned long)heap, rssi, (int)s_tx_power_dbm,
|
||||
s_send_frequency, CONFIG_CSI_HOSTNAME, app_desc->version);
|
||||
s_send_frequency, CONFIG_CSI_HOSTNAME, app_desc->version,
|
||||
s_adaptive ? "on" : "off", s_motion_detected ? 1 : 0);
|
||||
return strlen(reply);
|
||||
}
|
||||
|
||||
@@ -491,10 +591,15 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
|
||||
snprintf(reply, reply_size, "ERR RATE range 10-100");
|
||||
return strlen(reply);
|
||||
}
|
||||
if (s_adaptive) {
|
||||
s_adaptive = false;
|
||||
s_motion_detected = false;
|
||||
config_save_i8("adaptive", 0);
|
||||
}
|
||||
s_send_frequency = val;
|
||||
config_save_i32("send_rate", (int32_t)val);
|
||||
wifi_ping_router_start();
|
||||
snprintf(reply, reply_size, "OK RATE %d", val);
|
||||
snprintf(reply, reply_size, "OK RATE %d (adaptive off)", val);
|
||||
return strlen(reply);
|
||||
}
|
||||
|
||||
@@ -512,6 +617,38 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
|
||||
return strlen(reply);
|
||||
}
|
||||
|
||||
/* ADAPTIVE ON/OFF */
|
||||
if (strncmp(cmd, "ADAPTIVE ", 9) == 0) {
|
||||
const char *arg = cmd + 9;
|
||||
if (strncmp(arg, "ON", 2) == 0) {
|
||||
s_adaptive = true;
|
||||
s_energy_idx = 0;
|
||||
config_save_i8("adaptive", 1);
|
||||
snprintf(reply, reply_size, "OK ADAPTIVE on threshold=%.6f", s_motion_threshold);
|
||||
} else if (strncmp(arg, "OFF", 3) == 0) {
|
||||
s_adaptive = false;
|
||||
s_motion_detected = false;
|
||||
config_save_i8("adaptive", 0);
|
||||
snprintf(reply, reply_size, "OK ADAPTIVE off");
|
||||
} else {
|
||||
snprintf(reply, reply_size, "ERR ADAPTIVE ON or OFF");
|
||||
}
|
||||
return strlen(reply);
|
||||
}
|
||||
|
||||
/* THRESHOLD <value> */
|
||||
if (strncmp(cmd, "THRESHOLD ", 10) == 0) {
|
||||
float val = strtof(cmd + 10, NULL);
|
||||
if (val <= 0.0f || val > 1.0f) {
|
||||
snprintf(reply, reply_size, "ERR THRESHOLD range 0.000001-1.0");
|
||||
return strlen(reply);
|
||||
}
|
||||
s_motion_threshold = val;
|
||||
config_save_i32("threshold", (int32_t)(val * 1000000.0f));
|
||||
snprintf(reply, reply_size, "OK THRESHOLD %.6f", val);
|
||||
return strlen(reply);
|
||||
}
|
||||
|
||||
/* OTA <url> */
|
||||
if (strncmp(cmd, "OTA ", 4) == 0) {
|
||||
const char *url = cmd + 4;
|
||||
@@ -639,6 +776,7 @@ void app_main()
|
||||
wifi_ping_router_start();
|
||||
|
||||
xTaskCreate(cmd_task, "cmd_task", 4096, NULL, 5, NULL);
|
||||
xTaskCreate(adaptive_task, "adaptive", 3072, NULL, 3, NULL);
|
||||
|
||||
/* OTA rollback: mark firmware valid if we got this far */
|
||||
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||
|
||||
Reference in New Issue
Block a user