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>
This commit is contained in:
ruv
2026-03-20 18:32:10 -04:00
parent 8a84748a83
commit a2d1739c07
21 changed files with 3067 additions and 35 deletions
+27 -2
View File
@@ -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) {