mirror of
https://github.com/ruvnet/RuView.git
synced 2026-04-28 05:59:32 +00:00
feat: happiness scoring pipeline + ESP32 swarm with Cognitum Seed (#285)
* feat: happiness scoring pipeline with ESP32 swarm + Cognitum Seed coordinator ADR-065: Hotel guest happiness scoring from WiFi CSI physiological proxies. ADR-066: ESP32 swarm with Cognitum Seed as coordinator for multi-zone analytics. Firmware: - swarm_bridge.c/h: FreeRTOS task on Core 0, HTTP client with Bearer auth, registers with Seed, sends heartbeats (30s) and happiness vectors (5s) - nvs_config: seed_url, seed_token, zone_name, swarm intervals - provision.py: --seed-url, --seed-token, --zone CLI args - esp32-hello-world: capability discovery firmware for 4MB ESP32-S3 variant WASM edge modules: - exo_happiness_score.rs: 8-dim happiness vector from gait speed, stride regularity, movement fluidity, breathing calm, posture, dwell time (events 690-694, 11 tests, ESP32-optimized buffers + event decimation) - ghost_hunter.rs standalone binary: 5.7 KB WASM, feature-gated default pipeline RuView Live: - --mode happiness dashboard with bar visualization - --seed flag for Cognitum Seed bridge (urllib, background POST) - HappinessScorer + SeedBridge classes (stdlib only, no deps) Examples: - seed_query.py: CLI tool (status, search, witness, monitor, report) - provision_swarm.sh: batch provisioning for multi-node deployment - happiness_vector_schema.json: 8-dim vector format documentation Verified live: ESP32 on COM5 (4MB flash) registered with Seed at 10.1.10.236, vectors flowing, witness chain growing (epoch 455, chain 1108). Co-Authored-By: claude-flow <ruv@ruv.net> * ci: raise firmware binary size gate to 1100 KB for HTTP client stack The swarm bridge (ADR-066) adds esp_http_client for Seed communication, which pulls in the HTTP/TLS stack (~150 KB). Binary grew from ~978 KB to ~1077 KB. Raise the gate from 950 KB to 1100 KB. Still fits comfortably in both 4MB (1856 KB OTA slot, 43% free) and 8MB flash variants. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
8a84748a83
commit
2b8a7cc458
22 changed files with 3071 additions and 39 deletions
|
|
@ -3,6 +3,7 @@ set(SRCS
|
|||
"edge_processing.c" "ota_update.c" "power_mgmt.c"
|
||||
"wasm_runtime.c" "wasm_upload.c" "rvf_parser.c"
|
||||
"mmwave_sensor.c"
|
||||
"swarm_bridge.c"
|
||||
)
|
||||
|
||||
set(REQUIRES "")
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include "wasm_upload.h"
|
||||
#include "display_task.h"
|
||||
#include "mmwave_sensor.h"
|
||||
#include "swarm_bridge.h"
|
||||
#ifdef CONFIG_CSI_MOCK_ENABLED
|
||||
#include "mock_csi.h"
|
||||
#endif
|
||||
|
|
@ -240,6 +241,29 @@ void app_main(void)
|
|||
ESP_LOGI(TAG, "No mmWave sensor detected (CSI-only mode)");
|
||||
}
|
||||
|
||||
/* ADR-066: Initialize swarm bridge to Cognitum Seed (if configured). */
|
||||
esp_err_t swarm_ret = ESP_ERR_INVALID_ARG;
|
||||
#ifndef CONFIG_CSI_MOCK_SKIP_WIFI_CONNECT
|
||||
if (g_nvs_config.seed_url[0] != '\0') {
|
||||
swarm_config_t swarm_cfg = {
|
||||
.heartbeat_sec = g_nvs_config.swarm_heartbeat_sec,
|
||||
.ingest_sec = g_nvs_config.swarm_ingest_sec,
|
||||
.enabled = 1,
|
||||
};
|
||||
strncpy(swarm_cfg.seed_url, g_nvs_config.seed_url, sizeof(swarm_cfg.seed_url) - 1);
|
||||
strncpy(swarm_cfg.seed_token, g_nvs_config.seed_token, sizeof(swarm_cfg.seed_token) - 1);
|
||||
strncpy(swarm_cfg.zone_name, g_nvs_config.zone_name, sizeof(swarm_cfg.zone_name) - 1);
|
||||
swarm_ret = swarm_bridge_init(&swarm_cfg, g_nvs_config.node_id);
|
||||
if (swarm_ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Swarm bridge init failed: %s", esp_err_to_name(swarm_ret));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Swarm bridge disabled (no seed_url configured)");
|
||||
}
|
||||
#else
|
||||
ESP_LOGI(TAG, "Mock CSI mode: skipping swarm bridge");
|
||||
#endif
|
||||
|
||||
/* Initialize power management. */
|
||||
power_mgmt_init(g_nvs_config.power_duty);
|
||||
|
||||
|
|
@ -251,12 +275,13 @@ void app_main(void)
|
|||
}
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "CSI streaming active → %s:%d (edge_tier=%u, OTA=%s, WASM=%s, mmWave=%s)",
|
||||
ESP_LOGI(TAG, "CSI streaming active → %s:%d (edge_tier=%u, OTA=%s, WASM=%s, mmWave=%s, swarm=%s)",
|
||||
g_nvs_config.target_ip, g_nvs_config.target_port,
|
||||
g_nvs_config.edge_tier,
|
||||
(ota_ret == ESP_OK) ? "ready" : "off",
|
||||
(wasm_ret == ESP_OK) ? "ready" : "off",
|
||||
(mmwave_ret == ESP_OK) ? "active" : "off");
|
||||
(mmwave_ret == ESP_OK) ? "active" : "off",
|
||||
(swarm_ret == ESP_OK) ? g_nvs_config.seed_url : "off");
|
||||
|
||||
/* Main loop — keep alive */
|
||||
while (1) {
|
||||
|
|
|
|||
|
|
@ -302,6 +302,26 @@ void nvs_config_load(nvs_config_t *cfg)
|
|||
cfg->filter_mac[3], cfg->filter_mac[4], cfg->filter_mac[5]);
|
||||
}
|
||||
|
||||
/* ADR-066: Swarm bridge */
|
||||
len = sizeof(cfg->seed_url);
|
||||
if (nvs_get_str(handle, "seed_url", cfg->seed_url, &len) != ESP_OK) {
|
||||
cfg->seed_url[0] = '\0'; /* Disabled by default */
|
||||
}
|
||||
len = sizeof(cfg->seed_token);
|
||||
if (nvs_get_str(handle, "seed_token", cfg->seed_token, &len) != ESP_OK) {
|
||||
cfg->seed_token[0] = '\0';
|
||||
}
|
||||
len = sizeof(cfg->zone_name);
|
||||
if (nvs_get_str(handle, "zone_name", cfg->zone_name, &len) != ESP_OK) {
|
||||
strncpy(cfg->zone_name, "default", sizeof(cfg->zone_name) - 1);
|
||||
}
|
||||
if (nvs_get_u16(handle, "swarm_hb", &cfg->swarm_heartbeat_sec) != ESP_OK) {
|
||||
cfg->swarm_heartbeat_sec = 30;
|
||||
}
|
||||
if (nvs_get_u16(handle, "swarm_ingest", &cfg->swarm_ingest_sec) != ESP_OK) {
|
||||
cfg->swarm_ingest_sec = 5;
|
||||
}
|
||||
|
||||
/* Validate tdm_slot_index < tdm_node_count */
|
||||
if (cfg->tdm_slot_index >= cfg->tdm_node_count) {
|
||||
ESP_LOGW(TAG, "tdm_slot_index=%u >= tdm_node_count=%u, clamping to 0",
|
||||
|
|
|
|||
|
|
@ -55,6 +55,13 @@ typedef struct {
|
|||
uint8_t csi_channel; /**< Explicit CSI channel override (0 = auto-detect). */
|
||||
uint8_t filter_mac[6]; /**< MAC address to filter CSI frames. */
|
||||
uint8_t filter_mac_set; /**< 1 if filter_mac was loaded from NVS. */
|
||||
|
||||
/* ADR-066: Swarm bridge configuration */
|
||||
char seed_url[64]; /**< Cognitum Seed base URL (empty = disabled). */
|
||||
char seed_token[64]; /**< Seed Bearer token (from pairing). */
|
||||
char zone_name[16]; /**< Zone name for this node (e.g. "lobby"). */
|
||||
uint16_t swarm_heartbeat_sec; /**< Heartbeat interval (seconds, default 30). */
|
||||
uint16_t swarm_ingest_sec; /**< Vector ingest interval (seconds, default 5). */
|
||||
} nvs_config_t;
|
||||
|
||||
/**
|
||||
|
|
|
|||
327
firmware/esp32-csi-node/main/swarm_bridge.c
Normal file
327
firmware/esp32-csi-node/main/swarm_bridge.c
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
/**
|
||||
* @file swarm_bridge.c
|
||||
* @brief ADR-066: ESP32 Swarm Bridge — Cognitum Seed coordinator client.
|
||||
*
|
||||
* Runs a FreeRTOS task on Core 0 that periodically POSTs registration,
|
||||
* heartbeat, and happiness vectors to a Cognitum Seed ingest endpoint.
|
||||
*/
|
||||
|
||||
#include "swarm_bridge.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_app_desc.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_http_client.h"
|
||||
|
||||
static const char *TAG = "swarm";
|
||||
|
||||
/* ---- Task parameters ---- */
|
||||
#define SWARM_TASK_STACK 3072 /**< 3 KB stack — HTTP client uses ~2.5 KB. */
|
||||
#define SWARM_TASK_PRIO 3
|
||||
#define SWARM_TASK_CORE 0
|
||||
#define SWARM_HTTP_TIMEOUT 3000 /**< HTTP timeout in ms (Seed responds <100ms on LAN). */
|
||||
|
||||
/* ---- Ingest endpoint path ---- */
|
||||
#define SWARM_INGEST_PATH "/api/v1/store/ingest"
|
||||
|
||||
/* ---- JSON buffer size (Seed tuple format: max ~120 bytes per vector) ---- */
|
||||
#define SWARM_JSON_BUF 256
|
||||
|
||||
/* ---- Module state ---- */
|
||||
static swarm_config_t s_cfg;
|
||||
static uint8_t s_node_id;
|
||||
static SemaphoreHandle_t s_mutex;
|
||||
static TaskHandle_t s_task_handle;
|
||||
|
||||
/* ---- Protected shared data ---- */
|
||||
static edge_vitals_pkt_t s_vitals;
|
||||
static float s_happiness[SWARM_VECTOR_DIM];
|
||||
static bool s_vitals_valid;
|
||||
|
||||
/* ---- Counters ---- */
|
||||
static uint32_t s_cnt_regs;
|
||||
static uint32_t s_cnt_heartbeats;
|
||||
static uint32_t s_cnt_ingests;
|
||||
static uint32_t s_cnt_errors;
|
||||
|
||||
/* ---- Forward declarations ---- */
|
||||
static void swarm_task(void *arg);
|
||||
static esp_err_t swarm_post_json(esp_http_client_handle_t client,
|
||||
const char *json, int json_len);
|
||||
static void swarm_get_ip_str(char *buf, size_t buf_len);
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
esp_err_t swarm_bridge_init(const swarm_config_t *cfg, uint8_t node_id)
|
||||
{
|
||||
if (cfg == NULL || cfg->seed_url[0] == '\0') {
|
||||
ESP_LOGW(TAG, "seed_url is empty — swarm bridge disabled");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
memcpy(&s_cfg, cfg, sizeof(s_cfg));
|
||||
s_node_id = node_id;
|
||||
|
||||
/* Apply defaults for zero-valued intervals. */
|
||||
if (s_cfg.heartbeat_sec == 0) {
|
||||
s_cfg.heartbeat_sec = 30;
|
||||
}
|
||||
if (s_cfg.ingest_sec == 0) {
|
||||
s_cfg.ingest_sec = 5;
|
||||
}
|
||||
|
||||
s_mutex = xSemaphoreCreateMutex();
|
||||
if (s_mutex == NULL) {
|
||||
ESP_LOGE(TAG, "failed to create mutex");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
s_vitals_valid = false;
|
||||
memset(s_happiness, 0, sizeof(s_happiness));
|
||||
s_cnt_regs = 0;
|
||||
s_cnt_heartbeats = 0;
|
||||
s_cnt_ingests = 0;
|
||||
s_cnt_errors = 0;
|
||||
|
||||
BaseType_t ret = xTaskCreatePinnedToCore(
|
||||
swarm_task, "swarm", SWARM_TASK_STACK, NULL,
|
||||
SWARM_TASK_PRIO, &s_task_handle, SWARM_TASK_CORE);
|
||||
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "failed to create swarm task");
|
||||
vSemaphoreDelete(s_mutex);
|
||||
s_mutex = NULL;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "bridge init OK — seed=%s zone=%s hb=%us ingest=%us",
|
||||
s_cfg.seed_url, s_cfg.zone_name,
|
||||
s_cfg.heartbeat_sec, s_cfg.ingest_sec);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void swarm_bridge_update_vitals(const edge_vitals_pkt_t *vitals)
|
||||
{
|
||||
if (vitals == NULL || s_mutex == NULL) {
|
||||
return;
|
||||
}
|
||||
xSemaphoreTake(s_mutex, portMAX_DELAY);
|
||||
memcpy(&s_vitals, vitals, sizeof(s_vitals));
|
||||
s_vitals_valid = true;
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
|
||||
void swarm_bridge_update_happiness(const float *vector, uint8_t dim)
|
||||
{
|
||||
if (vector == NULL || s_mutex == NULL) {
|
||||
return;
|
||||
}
|
||||
uint8_t n = (dim < SWARM_VECTOR_DIM) ? dim : SWARM_VECTOR_DIM;
|
||||
|
||||
xSemaphoreTake(s_mutex, portMAX_DELAY);
|
||||
memcpy(s_happiness, vector, n * sizeof(float));
|
||||
/* Zero-fill remaining dimensions. */
|
||||
for (uint8_t i = n; i < SWARM_VECTOR_DIM; i++) {
|
||||
s_happiness[i] = 0.0f;
|
||||
}
|
||||
xSemaphoreGive(s_mutex);
|
||||
}
|
||||
|
||||
void swarm_bridge_get_stats(uint32_t *regs, uint32_t *heartbeats,
|
||||
uint32_t *ingests, uint32_t *errors)
|
||||
{
|
||||
if (regs) *regs = s_cnt_regs;
|
||||
if (heartbeats) *heartbeats = s_cnt_heartbeats;
|
||||
if (ingests) *ingests = s_cnt_ingests;
|
||||
if (errors) *errors = s_cnt_errors;
|
||||
}
|
||||
|
||||
/* ---- HTTP POST helper ---- */
|
||||
|
||||
static esp_err_t swarm_post_json(esp_http_client_handle_t client,
|
||||
const char *json, int json_len)
|
||||
{
|
||||
esp_http_client_set_post_field(client, json, json_len);
|
||||
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
if (err != ESP_OK) {
|
||||
/* Connection may have been closed by Seed between requests.
|
||||
* Close our end and let the next perform() reconnect. */
|
||||
esp_http_client_close(client);
|
||||
/* Retry once. */
|
||||
err = esp_http_client_perform(client);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "HTTP POST failed: %s", esp_err_to_name(err));
|
||||
s_cnt_errors++;
|
||||
esp_http_client_close(client);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
int status = esp_http_client_get_status_code(client);
|
||||
/* Close connection after each request to avoid stale keep-alive. */
|
||||
esp_http_client_close(client);
|
||||
|
||||
if (status < 200 || status >= 300) {
|
||||
ESP_LOGW(TAG, "HTTP POST status %d", status);
|
||||
s_cnt_errors++;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* ---- Get local IP address as string ---- */
|
||||
|
||||
static void swarm_get_ip_str(char *buf, size_t buf_len)
|
||||
{
|
||||
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
|
||||
if (netif == NULL) {
|
||||
snprintf(buf, buf_len, "0.0.0.0");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t ip_info;
|
||||
if (esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
|
||||
snprintf(buf, buf_len, "0.0.0.0");
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(buf, buf_len, IPSTR, IP2STR(&ip_info.ip));
|
||||
}
|
||||
|
||||
/* ---- Swarm bridge task ---- */
|
||||
|
||||
static void swarm_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
/* Build the full ingest URL once. */
|
||||
char url[128];
|
||||
snprintf(url, sizeof(url), "%s%s", s_cfg.seed_url, SWARM_INGEST_PATH);
|
||||
|
||||
/* Create a reusable HTTP client. */
|
||||
esp_http_client_config_t http_cfg = {
|
||||
.url = url,
|
||||
.method = HTTP_METHOD_POST,
|
||||
.timeout_ms = SWARM_HTTP_TIMEOUT,
|
||||
};
|
||||
esp_http_client_handle_t client = esp_http_client_init(&http_cfg);
|
||||
if (client == NULL) {
|
||||
ESP_LOGE(TAG, "failed to create HTTP client — task exiting");
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_http_client_set_header(client, "Content-Type", "application/json");
|
||||
|
||||
/* ADR-066: Set Bearer token for Seed WiFi auth (from pairing). */
|
||||
if (s_cfg.seed_token[0] != '\0') {
|
||||
char auth_hdr[80];
|
||||
snprintf(auth_hdr, sizeof(auth_hdr), "Bearer %s", s_cfg.seed_token);
|
||||
esp_http_client_set_header(client, "Authorization", auth_hdr);
|
||||
ESP_LOGI(TAG, "Bearer token configured for Seed auth");
|
||||
}
|
||||
|
||||
/* Get firmware version string. */
|
||||
const esp_app_desc_t *app = esp_app_get_description();
|
||||
const char *fw_ver = app ? app->version : "unknown";
|
||||
|
||||
/* Get local IP. */
|
||||
char ip_str[16];
|
||||
swarm_get_ip_str(ip_str, sizeof(ip_str));
|
||||
|
||||
/* ---- Registration POST ---- */
|
||||
/* Seed ingest format: {"vectors":[[u64_id, [f32; dim]]]} */
|
||||
{
|
||||
/* ID scheme: node_id * 1000000 + type_code (0=reg, 1=hb, 2=happiness) */
|
||||
uint32_t reg_id = (uint32_t)s_node_id * 1000000U;
|
||||
char json[SWARM_JSON_BUF];
|
||||
int len = snprintf(json, sizeof(json),
|
||||
"{\"vectors\":[[%lu,[0,0,0,0,0,0,0,0]]]}",
|
||||
(unsigned long)reg_id);
|
||||
|
||||
if (swarm_post_json(client, json, len) == ESP_OK) {
|
||||
s_cnt_regs++;
|
||||
ESP_LOGI(TAG, "registered node %u with seed (id=%lu)", s_node_id, (unsigned long)reg_id);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "registration failed — will retry on next heartbeat");
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Main loop ---- */
|
||||
TickType_t last_heartbeat = xTaskGetTickCount();
|
||||
TickType_t last_ingest = xTaskGetTickCount();
|
||||
const TickType_t poll_interval = pdMS_TO_TICKS(1000); /* Wake every 1 s. */
|
||||
|
||||
for (;;) {
|
||||
vTaskDelay(poll_interval);
|
||||
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
|
||||
/* Snapshot shared data under mutex. */
|
||||
float hv[SWARM_VECTOR_DIM];
|
||||
edge_vitals_pkt_t vit;
|
||||
bool vit_valid;
|
||||
|
||||
xSemaphoreTake(s_mutex, portMAX_DELAY);
|
||||
memcpy(hv, s_happiness, sizeof(hv));
|
||||
memcpy(&vit, &s_vitals, sizeof(vit));
|
||||
vit_valid = s_vitals_valid;
|
||||
xSemaphoreGive(s_mutex);
|
||||
|
||||
uint32_t uptime_s = (uint32_t)(esp_timer_get_time() / 1000000ULL);
|
||||
uint32_t free_heap = esp_get_free_heap_size();
|
||||
uint32_t ts = (uint32_t)(esp_timer_get_time() / 1000ULL);
|
||||
|
||||
/* ---- Heartbeat ---- */
|
||||
if ((now - last_heartbeat) >= pdMS_TO_TICKS(s_cfg.heartbeat_sec * 1000U)) {
|
||||
last_heartbeat = now;
|
||||
|
||||
bool presence = vit_valid && (vit.flags & 0x01);
|
||||
|
||||
/* Heartbeat ID: node_id * 1000000 + 100000 + ts_sec */
|
||||
uint32_t hb_id = (uint32_t)s_node_id * 1000000U + 100000U + (uptime_s % 100000U);
|
||||
char json[SWARM_JSON_BUF];
|
||||
int len = snprintf(json, sizeof(json),
|
||||
"{\"vectors\":[[%lu,[%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f]]]}",
|
||||
(unsigned long)hb_id,
|
||||
hv[0], hv[1], hv[2], hv[3], hv[4], hv[5], hv[6], hv[7]);
|
||||
|
||||
if (swarm_post_json(client, json, len) == ESP_OK) {
|
||||
s_cnt_heartbeats++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Happiness ingest (only when presence detected) ---- */
|
||||
if ((now - last_ingest) >= pdMS_TO_TICKS(s_cfg.ingest_sec * 1000U)) {
|
||||
last_ingest = now;
|
||||
|
||||
bool presence = vit_valid && (vit.flags & 0x01);
|
||||
if (presence) {
|
||||
/* Happiness ID: node_id * 1000000 + 200000 + ts_sec */
|
||||
uint32_t h_id = (uint32_t)s_node_id * 1000000U + 200000U + (ts / 1000U % 100000U);
|
||||
char json[SWARM_JSON_BUF];
|
||||
int len = snprintf(json, sizeof(json),
|
||||
"{\"vectors\":[[%lu,[%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f]]]}",
|
||||
(unsigned long)h_id,
|
||||
hv[0], hv[1], hv[2], hv[3], hv[4], hv[5], hv[6], hv[7]);
|
||||
|
||||
if (swarm_post_json(client, json, len) == ESP_OK) {
|
||||
s_cnt_ingests++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Unreachable, but clean up for completeness. */
|
||||
esp_http_client_cleanup(client);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
67
firmware/esp32-csi-node/main/swarm_bridge.h
Normal file
67
firmware/esp32-csi-node/main/swarm_bridge.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* @file swarm_bridge.h
|
||||
* @brief ADR-066: ESP32 Swarm Bridge — Cognitum Seed coordinator client.
|
||||
*
|
||||
* Registers this node with a Cognitum Seed, sends periodic heartbeats,
|
||||
* and pushes happiness vectors for cross-zone analytics.
|
||||
* Runs as a FreeRTOS task on Core 0.
|
||||
*/
|
||||
|
||||
#ifndef SWARM_BRIDGE_H
|
||||
#define SWARM_BRIDGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "edge_processing.h"
|
||||
|
||||
/** Happiness vector dimension. */
|
||||
#define SWARM_VECTOR_DIM 8
|
||||
|
||||
/** Swarm bridge configuration. */
|
||||
typedef struct {
|
||||
char seed_url[64]; /**< Cognitum Seed base URL (e.g. "http://192.168.1.10:8080"). */
|
||||
char seed_token[64]; /**< Bearer token for Seed WiFi API auth (from pairing). */
|
||||
char zone_name[16]; /**< Zone name for this node (e.g. "bedroom"). */
|
||||
uint16_t heartbeat_sec; /**< Heartbeat interval in seconds (default 30). */
|
||||
uint16_t ingest_sec; /**< Happiness ingest interval in seconds (default 5). */
|
||||
uint8_t enabled; /**< 1 = bridge active, 0 = disabled. */
|
||||
} swarm_config_t;
|
||||
|
||||
/**
|
||||
* Initialize the swarm bridge and start the background task.
|
||||
* Registers this node with the Cognitum Seed on first successful POST.
|
||||
*
|
||||
* @param cfg Swarm bridge configuration.
|
||||
* @param node_id This node's identifier (from NVS).
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if seed_url is empty.
|
||||
*/
|
||||
esp_err_t swarm_bridge_init(const swarm_config_t *cfg, uint8_t node_id);
|
||||
|
||||
/**
|
||||
* Feed the latest vitals packet into the swarm bridge.
|
||||
* Called from the main loop whenever new vitals are available.
|
||||
*
|
||||
* @param vitals Pointer to the latest vitals packet.
|
||||
*/
|
||||
void swarm_bridge_update_vitals(const edge_vitals_pkt_t *vitals);
|
||||
|
||||
/**
|
||||
* Update the happiness vector to be pushed at the next ingest cycle.
|
||||
*
|
||||
* @param vector Float array of happiness values.
|
||||
* @param dim Number of elements (clamped to SWARM_VECTOR_DIM).
|
||||
*/
|
||||
void swarm_bridge_update_happiness(const float *vector, uint8_t dim);
|
||||
|
||||
/**
|
||||
* Get cumulative bridge statistics.
|
||||
*
|
||||
* @param regs Output: number of successful registrations.
|
||||
* @param heartbeats Output: number of successful heartbeats sent.
|
||||
* @param ingests Output: number of successful happiness ingests sent.
|
||||
* @param errors Output: number of HTTP errors encountered.
|
||||
*/
|
||||
void swarm_bridge_get_stats(uint32_t *regs, uint32_t *heartbeats,
|
||||
uint32_t *ingests, uint32_t *errors);
|
||||
|
||||
#endif /* SWARM_BRIDGE_H */
|
||||
|
|
@ -71,6 +71,17 @@ def build_nvs_csv(args):
|
|||
mac_bytes = bytes(int(b, 16) for b in args.filter_mac.split(":"))
|
||||
# NVS blob: write as hex-encoded string for CSV compatibility
|
||||
writer.writerow(["filter_mac", "data", "hex2bin", mac_bytes.hex()])
|
||||
# ADR-066: Swarm bridge configuration
|
||||
if args.seed_url is not None:
|
||||
writer.writerow(["seed_url", "data", "string", args.seed_url])
|
||||
if args.seed_token is not None:
|
||||
writer.writerow(["seed_token", "data", "string", args.seed_token])
|
||||
if args.zone is not None:
|
||||
writer.writerow(["zone_name", "data", "string", args.zone])
|
||||
if args.swarm_hb is not None:
|
||||
writer.writerow(["swarm_hb", "data", "u16", str(args.swarm_hb)])
|
||||
if args.swarm_ingest is not None:
|
||||
writer.writerow(["swarm_ingest", "data", "u16", str(args.swarm_ingest)])
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
|
|
@ -170,6 +181,12 @@ def main():
|
|||
parser.add_argument("--channel", type=int, help="CSI channel (1-14 for 2.4GHz, 36-177 for 5GHz). "
|
||||
"Overrides auto-detection from connected AP.")
|
||||
parser.add_argument("--filter-mac", type=str, help="MAC address to filter CSI frames (AA:BB:CC:DD:EE:FF)")
|
||||
# ADR-066: Swarm bridge
|
||||
parser.add_argument("--seed-url", type=str, help="Cognitum Seed base URL (e.g. http://10.1.10.236)")
|
||||
parser.add_argument("--seed-token", type=str, help="Seed Bearer token (from pairing)")
|
||||
parser.add_argument("--zone", type=str, help="Zone name for this node (e.g. lobby, hallway)")
|
||||
parser.add_argument("--swarm-hb", type=int, help="Swarm heartbeat interval in seconds (default 30)")
|
||||
parser.add_argument("--swarm-ingest", type=int, help="Swarm vector ingest interval in seconds (default 5)")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Generate NVS binary but don't flash")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
|
@ -182,6 +199,7 @@ def main():
|
|||
args.fall_thresh is not None, args.vital_win is not None,
|
||||
args.vital_int is not None, args.subk_count is not None,
|
||||
args.channel is not None, args.filter_mac is not None,
|
||||
args.seed_url is not None, args.zone is not None,
|
||||
])
|
||||
if not has_value:
|
||||
parser.error("At least one config value must be specified")
|
||||
|
|
@ -238,6 +256,14 @@ def main():
|
|||
print(f" CSI Channel: {args.channel}")
|
||||
if args.filter_mac is not None:
|
||||
print(f" Filter MAC: {args.filter_mac}")
|
||||
if args.seed_url is not None:
|
||||
print(f" Seed URL: {args.seed_url}")
|
||||
if args.zone is not None:
|
||||
print(f" Zone: {args.zone}")
|
||||
if args.swarm_hb is not None:
|
||||
print(f" Swarm HB: {args.swarm_hb}s")
|
||||
if args.swarm_ingest is not None:
|
||||
print(f" Swarm Ingest: {args.swarm_ingest}s")
|
||||
|
||||
csv_content = build_nvs_csv(args)
|
||||
|
||||
|
|
|
|||
5
firmware/esp32-hello-world/CMakeLists.txt
Normal file
5
firmware/esp32-hello-world/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# ESP32-S3 Hello World — Capability Discovery
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(esp32-hello-world)
|
||||
4
firmware/esp32-hello-world/main/CMakeLists.txt
Normal file
4
firmware/esp32-hello-world/main/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
idf_component_register(
|
||||
SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
)
|
||||
437
firmware/esp32-hello-world/main/main.c
Normal file
437
firmware/esp32-hello-world/main/main.c
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
/**
|
||||
* @file main.c
|
||||
* @brief ESP32-S3 Hello World — Full Capability Discovery
|
||||
*
|
||||
* Boots up, prints "Hello World!", then probes and reports every major
|
||||
* hardware/software capability of the ESP32-S3: chip info, flash, PSRAM,
|
||||
* WiFi (including CSI), Bluetooth, GPIOs, peripherals, FreeRTOS stats,
|
||||
* and power management features. No WiFi connection required.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_chip_info.h"
|
||||
#include "esp_flash.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_efuse.h"
|
||||
#include "esp_pm.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/temperature_sensor.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static const char *TAG = "hello";
|
||||
|
||||
/* ── Helpers ─────────────────────────────────────────────────────────── */
|
||||
|
||||
static const char *chip_model_str(esp_chip_model_t model)
|
||||
{
|
||||
switch (model) {
|
||||
case CHIP_ESP32: return "ESP32";
|
||||
case CHIP_ESP32S2: return "ESP32-S2";
|
||||
case CHIP_ESP32S3: return "ESP32-S3";
|
||||
case CHIP_ESP32C3: return "ESP32-C3";
|
||||
case CHIP_ESP32H2: return "ESP32-H2";
|
||||
case CHIP_ESP32C2: return "ESP32-C2";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void print_separator(const char *title)
|
||||
{
|
||||
printf("\n╔══════════════════════════════════════════════════════════╗\n");
|
||||
printf("║ %-55s ║\n", title);
|
||||
printf("╚══════════════════════════════════════════════════════════╝\n");
|
||||
}
|
||||
|
||||
/* ── Capability Probes ───────────────────────────────────────────────── */
|
||||
|
||||
static void probe_chip_info(void)
|
||||
{
|
||||
print_separator("CHIP INFO");
|
||||
|
||||
esp_chip_info_t info;
|
||||
esp_chip_info(&info);
|
||||
|
||||
printf(" Model: %s (rev %d.%d)\n",
|
||||
chip_model_str(info.model),
|
||||
info.revision / 100, info.revision % 100);
|
||||
printf(" Cores: %d\n", info.cores);
|
||||
printf(" Features: ");
|
||||
if (info.features & CHIP_FEATURE_WIFI_BGN) printf("WiFi ");
|
||||
if (info.features & CHIP_FEATURE_BLE) printf("BLE ");
|
||||
if (info.features & CHIP_FEATURE_BT) printf("BT-Classic ");
|
||||
if (info.features & CHIP_FEATURE_IEEE802154) printf("802.15.4 ");
|
||||
if (info.features & CHIP_FEATURE_EMB_FLASH) printf("EmbFlash ");
|
||||
if (info.features & CHIP_FEATURE_EMB_PSRAM) printf("EmbPSRAM ");
|
||||
printf("\n");
|
||||
|
||||
/* MAC addresses */
|
||||
uint8_t mac[6];
|
||||
if (esp_read_mac(mac, ESP_MAC_WIFI_STA) == ESP_OK) {
|
||||
printf(" WiFi STA MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
}
|
||||
if (esp_read_mac(mac, ESP_MAC_BT) == ESP_OK) {
|
||||
printf(" BT MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
}
|
||||
|
||||
printf(" IDF Version: %s\n", esp_get_idf_version());
|
||||
printf(" Reset Reason: %d\n", esp_reset_reason());
|
||||
}
|
||||
|
||||
static void probe_memory(void)
|
||||
{
|
||||
print_separator("MEMORY");
|
||||
|
||||
/* Internal RAM */
|
||||
printf(" Internal DRAM:\n");
|
||||
printf(" Total: %"PRIu32" bytes\n",
|
||||
(uint32_t)heap_caps_get_total_size(MALLOC_CAP_INTERNAL));
|
||||
printf(" Free: %"PRIu32" bytes\n",
|
||||
(uint32_t)heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
|
||||
printf(" Min Free: %"PRIu32" bytes\n",
|
||||
(uint32_t)heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
|
||||
|
||||
/* PSRAM */
|
||||
size_t psram_total = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
|
||||
if (psram_total > 0) {
|
||||
printf(" External PSRAM:\n");
|
||||
printf(" Total: %"PRIu32" bytes (%.1f MB)\n",
|
||||
(uint32_t)psram_total, psram_total / (1024.0 * 1024.0));
|
||||
printf(" Free: %"PRIu32" bytes\n",
|
||||
(uint32_t)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
|
||||
} else {
|
||||
printf(" External PSRAM: Not available\n");
|
||||
}
|
||||
|
||||
/* DMA-capable */
|
||||
printf(" DMA-capable: %"PRIu32" bytes free\n",
|
||||
(uint32_t)heap_caps_get_free_size(MALLOC_CAP_DMA));
|
||||
}
|
||||
|
||||
static void probe_flash(void)
|
||||
{
|
||||
print_separator("FLASH STORAGE");
|
||||
|
||||
uint32_t flash_size = 0;
|
||||
if (esp_flash_get_size(NULL, &flash_size) == ESP_OK) {
|
||||
printf(" Flash Size: %"PRIu32" bytes (%.0f MB)\n",
|
||||
flash_size, flash_size / (1024.0 * 1024.0));
|
||||
}
|
||||
|
||||
/* Partition table */
|
||||
printf(" Partitions:\n");
|
||||
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY,
|
||||
ESP_PARTITION_SUBTYPE_ANY, NULL);
|
||||
while (it != NULL) {
|
||||
const esp_partition_t *p = esp_partition_get(it);
|
||||
printf(" %-16s type=0x%02x sub=0x%02x offset=0x%06"PRIx32" size=%"PRIu32" KB\n",
|
||||
p->label, p->type, p->subtype, p->address, p->size / 1024);
|
||||
it = esp_partition_next(it);
|
||||
}
|
||||
esp_partition_iterator_release(it);
|
||||
|
||||
/* Running partition */
|
||||
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||
if (running) {
|
||||
printf(" Running from: %s (0x%06"PRIx32")\n", running->label, running->address);
|
||||
}
|
||||
}
|
||||
|
||||
static void probe_wifi_capabilities(void)
|
||||
{
|
||||
print_separator("WiFi CAPABILITIES");
|
||||
|
||||
/* Init WiFi just enough to query capabilities (no connection) */
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_create_default_wifi_sta();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
/* Protocol capabilities */
|
||||
printf(" Protocols: 802.11 b/g/n\n");
|
||||
|
||||
/* CSI (Channel State Information) */
|
||||
#ifdef CONFIG_ESP_WIFI_CSI_ENABLED
|
||||
printf(" CSI: ENABLED (Channel State Information)\n");
|
||||
printf(" - Subcarrier amplitude & phase data\n");
|
||||
printf(" - Per-packet callback available\n");
|
||||
printf(" - Use for: presence detection, gesture recognition,\n");
|
||||
printf(" breathing/heart rate, indoor positioning\n");
|
||||
#else
|
||||
printf(" CSI: DISABLED (enable CONFIG_ESP_WIFI_CSI_ENABLED)\n");
|
||||
#endif
|
||||
|
||||
/* Scan to show what's visible */
|
||||
printf(" WiFi Scan: Scanning nearby APs...\n");
|
||||
wifi_scan_config_t scan_cfg = {
|
||||
.show_hidden = true,
|
||||
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
|
||||
.scan_time.active.min = 100,
|
||||
.scan_time.active.max = 300,
|
||||
};
|
||||
esp_wifi_scan_start(&scan_cfg, true); /* blocking scan */
|
||||
|
||||
uint16_t ap_count = 0;
|
||||
esp_wifi_scan_get_ap_num(&ap_count);
|
||||
printf(" APs Found: %d\n", ap_count);
|
||||
|
||||
if (ap_count > 0) {
|
||||
uint16_t max_show = (ap_count > 10) ? 10 : ap_count;
|
||||
wifi_ap_record_t *ap_list = malloc(sizeof(wifi_ap_record_t) * max_show);
|
||||
if (ap_list) {
|
||||
esp_wifi_scan_get_ap_records(&max_show, ap_list);
|
||||
printf(" %-32s CH RSSI Auth\n", " SSID");
|
||||
printf(" %-32s -- ---- ----\n", " ----");
|
||||
for (int i = 0; i < max_show; i++) {
|
||||
const char *auth_str = "OPEN";
|
||||
switch (ap_list[i].authmode) {
|
||||
case WIFI_AUTH_WEP: auth_str = "WEP"; break;
|
||||
case WIFI_AUTH_WPA_PSK: auth_str = "WPA"; break;
|
||||
case WIFI_AUTH_WPA2_PSK: auth_str = "WPA2"; break;
|
||||
case WIFI_AUTH_WPA_WPA2_PSK: auth_str = "WPA/2"; break;
|
||||
case WIFI_AUTH_WPA3_PSK: auth_str = "WPA3"; break;
|
||||
case WIFI_AUTH_WPA2_WPA3_PSK: auth_str = "WPA2/3"; break;
|
||||
default: break;
|
||||
}
|
||||
printf(" %-30s %2d %4d %s\n",
|
||||
(char *)ap_list[i].ssid,
|
||||
ap_list[i].primary,
|
||||
ap_list[i].rssi,
|
||||
auth_str);
|
||||
}
|
||||
free(ap_list);
|
||||
if (ap_count > max_show)
|
||||
printf(" ... and %d more\n", ap_count - max_show);
|
||||
}
|
||||
}
|
||||
|
||||
/* WiFi modes supported */
|
||||
printf("\n Supported Modes:\n");
|
||||
printf(" - STA (Station / Client)\n");
|
||||
printf(" - AP (Access Point / Soft-AP)\n");
|
||||
printf(" - STA+AP (Concurrent)\n");
|
||||
printf(" - Promiscuous (raw 802.11 frame capture)\n");
|
||||
printf(" - ESP-NOW (peer-to-peer, no router needed)\n");
|
||||
printf(" - WiFi Aware / NAN (Neighbor Awareness)\n");
|
||||
|
||||
esp_wifi_stop();
|
||||
esp_wifi_deinit();
|
||||
}
|
||||
|
||||
static void probe_bluetooth(void)
|
||||
{
|
||||
print_separator("BLUETOOTH CAPABILITIES");
|
||||
|
||||
esp_chip_info_t info;
|
||||
esp_chip_info(&info);
|
||||
|
||||
if (info.features & CHIP_FEATURE_BLE) {
|
||||
printf(" BLE: Supported (Bluetooth 5.0 LE)\n");
|
||||
printf(" - GATT Server/Client\n");
|
||||
printf(" - Advertising & Scanning\n");
|
||||
printf(" - Mesh Networking\n");
|
||||
printf(" - Long Range (Coded PHY)\n");
|
||||
printf(" - 2 Mbps PHY\n");
|
||||
} else {
|
||||
printf(" BLE: Not supported on this chip\n");
|
||||
}
|
||||
|
||||
if (info.features & CHIP_FEATURE_BT) {
|
||||
printf(" BT Classic: Supported (A2DP, SPP, HFP)\n");
|
||||
} else {
|
||||
printf(" BT Classic: Not available (ESP32-S3 is BLE-only)\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void probe_peripherals(void)
|
||||
{
|
||||
print_separator("PERIPHERAL CAPABILITIES");
|
||||
|
||||
printf(" GPIOs: %d total\n", SOC_GPIO_PIN_COUNT);
|
||||
printf(" ADC:\n");
|
||||
printf(" - ADC1: %d channels (12-bit SAR)\n", SOC_ADC_CHANNEL_NUM(0));
|
||||
printf(" - ADC2: %d channels (shared with WiFi)\n", SOC_ADC_CHANNEL_NUM(1));
|
||||
printf(" DAC: Not available on ESP32-S3\n");
|
||||
printf(" Touch Sensors: %d channels (capacitive)\n", SOC_TOUCH_SENSOR_NUM);
|
||||
printf(" SPI: %d controllers (SPI2/SPI3 for user)\n", SOC_SPI_PERIPH_NUM);
|
||||
printf(" I2C: %d controllers\n", SOC_I2C_NUM);
|
||||
printf(" I2S: %d controllers (audio/PDM/TDM)\n", SOC_I2S_NUM);
|
||||
printf(" UART: %d controllers\n", SOC_UART_NUM);
|
||||
printf(" USB: USB-OTG 1.1 (Host & Device)\n");
|
||||
printf(" USB-Serial: Built-in USB-JTAG/Serial (this console)\n");
|
||||
printf(" TWAI (CAN): 1 controller (CAN 2.0B compatible)\n");
|
||||
printf(" RMT: %d channels (IR/WS2812/NeoPixel)\n", SOC_RMT_TX_CANDIDATES_PER_GROUP + SOC_RMT_RX_CANDIDATES_PER_GROUP);
|
||||
printf(" LEDC (PWM): %d channels\n", SOC_LEDC_CHANNEL_NUM);
|
||||
printf(" MCPWM: %d groups (motor control)\n", SOC_MCPWM_GROUPS);
|
||||
printf(" PCNT: %d units (pulse counter / encoder)\n", SOC_PCNT_UNITS_PER_GROUP);
|
||||
printf(" LCD: Parallel 8/16-bit + SPI + I2C interfaces\n");
|
||||
printf(" Camera: DVP 8/16-bit parallel interface\n");
|
||||
printf(" SDMMC: SD/MMC host controller (1-bit / 4-bit)\n");
|
||||
}
|
||||
|
||||
static void probe_security(void)
|
||||
{
|
||||
print_separator("SECURITY & CRYPTO");
|
||||
|
||||
printf(" AES: 128/256-bit hardware accelerator\n");
|
||||
printf(" SHA: SHA-1/224/256 hardware accelerator\n");
|
||||
printf(" RSA: Up to 4096-bit hardware accelerator\n");
|
||||
printf(" HMAC: Hardware HMAC (eFuse key)\n");
|
||||
printf(" Digital Sig: Hardware digital signature (RSA)\n");
|
||||
printf(" Flash Encrypt: AES-256-XTS (eFuse controlled)\n");
|
||||
printf(" Secure Boot: V2 (RSA-3072 / ECDSA)\n");
|
||||
printf(" eFuse: %d bits (MAC, keys, config)\n", 256 * 11);
|
||||
printf(" World Ctrl: Dual-world isolation (TEE)\n");
|
||||
printf(" Random: Hardware TRNG available\n");
|
||||
}
|
||||
|
||||
static void probe_power(void)
|
||||
{
|
||||
print_separator("POWER MANAGEMENT");
|
||||
|
||||
printf(" Clock Modes:\n");
|
||||
printf(" - 240 MHz (max performance)\n");
|
||||
printf(" - 160 MHz (balanced)\n");
|
||||
printf(" - 80 MHz (low power)\n");
|
||||
printf(" Sleep Modes:\n");
|
||||
printf(" - Modem Sleep (WiFi off, CPU active)\n");
|
||||
printf(" - Light Sleep (CPU paused, fast wake)\n");
|
||||
printf(" - Deep Sleep (RTC only, ~10 uA)\n");
|
||||
printf(" - Hibernation (RTC timer only, ~5 uA)\n");
|
||||
printf(" Wake Sources: GPIO, timer, touch, ULP, UART\n");
|
||||
printf(" ULP Coprocessor: RISC-V + FSM (runs in deep sleep)\n");
|
||||
}
|
||||
|
||||
static void probe_temperature(void)
|
||||
{
|
||||
print_separator("TEMPERATURE SENSOR");
|
||||
|
||||
temperature_sensor_handle_t tsens = NULL;
|
||||
temperature_sensor_config_t tsens_cfg = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
|
||||
|
||||
esp_err_t ret = temperature_sensor_install(&tsens_cfg, &tsens);
|
||||
if (ret == ESP_OK) {
|
||||
temperature_sensor_enable(tsens);
|
||||
float temp_c = 0;
|
||||
temperature_sensor_get_celsius(tsens, &temp_c);
|
||||
printf(" Chip Temp: %.1f °C (%.1f °F)\n", temp_c, temp_c * 9.0 / 5.0 + 32.0);
|
||||
temperature_sensor_disable(tsens);
|
||||
temperature_sensor_uninstall(tsens);
|
||||
} else {
|
||||
printf(" Chip Temp: Sensor not available (%s)\n", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
|
||||
static void probe_freertos(void)
|
||||
{
|
||||
print_separator("FreeRTOS / SYSTEM");
|
||||
|
||||
printf(" FreeRTOS: v%s\n", tskKERNEL_VERSION_NUMBER);
|
||||
printf(" Tick Rate: %d Hz\n", configTICK_RATE_HZ);
|
||||
printf(" Task Count: %"PRIu32"\n", (uint32_t)uxTaskGetNumberOfTasks());
|
||||
printf(" Main Stack: %d bytes\n", CONFIG_ESP_MAIN_TASK_STACK_SIZE);
|
||||
printf(" Uptime: %lld ms\n", esp_timer_get_time() / 1000LL);
|
||||
}
|
||||
|
||||
static void probe_csi_details(void)
|
||||
{
|
||||
print_separator("CSI (Channel State Information) DETAILS");
|
||||
|
||||
#ifdef CONFIG_ESP_WIFI_CSI_ENABLED
|
||||
printf(" Status: ENABLED in this build\n");
|
||||
printf("\n What is CSI?\n");
|
||||
printf(" WiFi CSI captures the amplitude and phase of each OFDM\n");
|
||||
printf(" subcarrier in received WiFi frames. This gives a detailed\n");
|
||||
printf(" view of how radio signals propagate through a space.\n");
|
||||
printf("\n Subcarriers: 52 (20 MHz) / 114 (40 MHz) per frame\n");
|
||||
printf(" Data Rate: Up to ~100 frames/sec\n");
|
||||
printf(" Data per Frame: ~200-500 bytes (amplitude + phase)\n");
|
||||
printf("\n Applications:\n");
|
||||
printf(" 1. Presence Detection — detect humans in a room\n");
|
||||
printf(" 2. Gesture Recognition — classify hand gestures\n");
|
||||
printf(" 3. Activity Recognition — walking, sitting, falling\n");
|
||||
printf(" 4. Breathing/Heart Rate — contactless vital signs\n");
|
||||
printf(" 5. Indoor Positioning — sub-meter localization\n");
|
||||
printf(" 6. Fall Detection — elderly safety monitoring\n");
|
||||
printf(" 7. People Counting — crowd estimation\n");
|
||||
printf(" 8. Sleep Monitoring — non-contact sleep staging\n");
|
||||
printf("\n How to use:\n");
|
||||
printf(" esp_wifi_set_csi_config(&csi_config);\n");
|
||||
printf(" esp_wifi_set_csi_rx_cb(my_callback, NULL);\n");
|
||||
printf(" esp_wifi_set_csi(true);\n");
|
||||
#else
|
||||
printf(" Status: DISABLED\n");
|
||||
printf(" To enable: Set CONFIG_ESP_WIFI_CSI_ENABLED=y in sdkconfig\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ── Main ────────────────────────────────────────────────────────────── */
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* NVS required for WiFi */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
nvs_flash_erase();
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
/* ── Hello World! ── */
|
||||
printf("\n");
|
||||
printf(" ╭─────────────────────────────────────────────────╮\n");
|
||||
printf(" │ │\n");
|
||||
printf(" │ HELLO WORLD from ESP32-S3! │\n");
|
||||
printf(" │ │\n");
|
||||
printf(" │ WiFi-DensePose Capability Discovery v1.0 │\n");
|
||||
printf(" │ │\n");
|
||||
printf(" ╰─────────────────────────────────────────────────╯\n");
|
||||
printf("\n");
|
||||
|
||||
/* Run all probes */
|
||||
probe_chip_info();
|
||||
probe_memory();
|
||||
probe_flash();
|
||||
probe_temperature();
|
||||
probe_peripherals();
|
||||
probe_security();
|
||||
probe_power();
|
||||
probe_freertos();
|
||||
probe_wifi_capabilities();
|
||||
probe_bluetooth();
|
||||
probe_csi_details();
|
||||
|
||||
print_separator("DONE — ALL CAPABILITIES REPORTED");
|
||||
printf("\n This ESP32-S3 is ready for WiFi-DensePose!\n");
|
||||
printf(" Flash the full firmware (esp32-csi-node) to begin CSI sensing.\n\n");
|
||||
|
||||
/* Keep alive — blink a status message every 10 seconds */
|
||||
int tick = 0;
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
tick++;
|
||||
printf("[hello] Still running... uptime=%lld sec, free_heap=%"PRIu32"\n",
|
||||
esp_timer_get_time() / 1000000LL,
|
||||
(uint32_t)heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
|
||||
}
|
||||
}
|
||||
18
firmware/esp32-hello-world/sdkconfig.defaults
Normal file
18
firmware/esp32-hello-world/sdkconfig.defaults
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# ESP32-S3 Hello World — SDK Configuration
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
|
||||
# Flash: 4MB (this chip has Embedded Flash 4MB)
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
|
||||
|
||||
# Enable WiFi CSI so we can probe it
|
||||
CONFIG_ESP_WIFI_CSI_ENABLED=y
|
||||
|
||||
# Verbose logging so user sees everything
|
||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
||||
|
||||
# Bigger main task stack for printf-heavy capability dump
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
|
||||
# Enable temperature sensor driver
|
||||
CONFIG_SOC_TEMP_SENSOR_SUPPORTED=y
|
||||
Loading…
Add table
Add a link
Reference in a new issue