mirror of
https://github.com/ruvnet/RuView
synced 2026-08-01 19:01:42 +00:00
feat: dynamic classifier classes, per-node UI, XSS fix, RSSI fix
Complements #326 (per-node state pipeline) with additional features: - Dynamic adaptive classifier: discover activity classes from training data filenames instead of hardcoded array. Users add classes via filename convention (train_<class>_<desc>.jsonl), no code changes. - Per-node UI cards: SensingTab shows individual node status with color-coded markers, RSSI, variance, and classification per node. - Colored node markers in 3D gaussian splat view (8-color palette). - Per-node RSSI history tracking in sensing service. - XSS fix: UI uses createElement/textContent instead of innerHTML. - RSSI sign fix: ensure dBm values are always negative. - GET /api/v1/nodes endpoint for per-node health monitoring. - node_features field in WebSocket SensingUpdate messages. - Firmware watchdog fix: yield after every frame to prevent IDLE1 starvation. Addresses #237, #276, #282 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,14 +41,12 @@ static const char *TAG = "edge_proc";
|
||||
* ====================================================================== */
|
||||
|
||||
static edge_ring_buf_t s_ring;
|
||||
static uint32_t s_ring_drops; /* Frames dropped due to full ring buffer. */
|
||||
|
||||
static inline bool ring_push(const uint8_t *iq, uint16_t len,
|
||||
int8_t rssi, uint8_t channel)
|
||||
{
|
||||
uint32_t next = (s_ring.head + 1) % EDGE_RING_SLOTS;
|
||||
if (next == s_ring.tail) {
|
||||
s_ring_drops++;
|
||||
return false; /* Full — drop frame. */
|
||||
}
|
||||
|
||||
@@ -790,13 +788,12 @@ static void process_frame(const edge_ring_slot_t *slot)
|
||||
|
||||
if ((s_frame_count % 200) == 0) {
|
||||
ESP_LOGI(TAG, "Vitals: br=%.1f hr=%.1f motion=%.4f pres=%s "
|
||||
"fall=%s persons=%u frames=%lu drops=%lu",
|
||||
"fall=%s persons=%u frames=%lu",
|
||||
s_breathing_bpm, s_heartrate_bpm, s_motion_energy,
|
||||
s_presence_detected ? "YES" : "no",
|
||||
s_fall_detected ? "YES" : "no",
|
||||
(unsigned)s_latest_pkt.n_persons,
|
||||
(unsigned long)s_frame_count,
|
||||
(unsigned long)s_ring_drops);
|
||||
(unsigned long)s_frame_count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,32 +831,18 @@ static void edge_task(void *arg)
|
||||
|
||||
edge_ring_slot_t slot;
|
||||
|
||||
/* Maximum frames to process before a longer yield. On busy LANs
|
||||
* (corporate networks, many APs), the ring buffer fills continuously.
|
||||
* Without a batch limit the task processes frames back-to-back with
|
||||
* only 1-tick yields, which on high frame rates can still starve
|
||||
* IDLE1 enough to trip the 5-second task watchdog. See #266, #321. */
|
||||
const uint8_t BATCH_LIMIT = 4;
|
||||
|
||||
while (1) {
|
||||
uint8_t processed = 0;
|
||||
|
||||
while (processed < BATCH_LIMIT && ring_pop(&slot)) {
|
||||
if (ring_pop(&slot)) {
|
||||
process_frame(&slot);
|
||||
processed++;
|
||||
/* 1-tick yield between frames within a batch. */
|
||||
/* Yield after every frame to feed the Core 1 watchdog.
|
||||
* process_frame() is CPU-intensive (biquad filters, Welford stats,
|
||||
* BPM estimation, multi-person vitals) and can take several ms.
|
||||
* Without this yield, edge_dsp at priority 5 starves IDLE1 at
|
||||
* priority 0, triggering the task watchdog. See issue #266. */
|
||||
vTaskDelay(1);
|
||||
}
|
||||
|
||||
if (processed > 0) {
|
||||
/* Post-batch yield: 2 ticks (~20 ms at 100 Hz) so IDLE1 can
|
||||
* run and feed the Core 1 watchdog even under sustained load.
|
||||
* This is intentionally longer than the 1-tick inter-frame yield. */
|
||||
vTaskDelay(2);
|
||||
} else {
|
||||
/* No frames available — sleep one full tick.
|
||||
* NOTE: pdMS_TO_TICKS(5) == 0 at 100 Hz, which would busy-spin. */
|
||||
vTaskDelay(1);
|
||||
/* No frames available — yield briefly. */
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user