mirror of
https://github.com/ruvnet/RuView
synced 2026-07-20 17:03:24 +00:00
feat(firmware): ADR-063 mmWave sensor fusion — full implementation
Phase 1-2 of ADR-063: mmwave_sensor.c/h: - MR60BHA2 UART parser (60 GHz: HR, BR, presence, distance) - LD2410 UART parser (24 GHz: presence, distance) - Auto-detection: probes UART for known frame headers at boot - Mock generator for QEMU testing (synthetic HR 72±2, BR 16±1) - Capability flag registration per sensor type edge_processing.c/h: - 48-byte fused vitals packet (magic 0xC5110004) - Kalman-style fusion: mmWave 80% + CSI 20% when both available - Automatic fallback to CSI-only 32-byte packet when no mmWave - Dual presence flag (Bit3 = mmwave_present) main.c: - mmwave_sensor_init() called at boot with auto-detect - Status logged in startup banner Fuzz stubs updated for mmwave_sensor API. Build verified: QEMU mock build passes. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "edge_processing.h"
|
||||
#include "mmwave_sensor.h"
|
||||
#include "wasm_runtime.h"
|
||||
#include "stream_sender.h"
|
||||
|
||||
@@ -577,8 +578,58 @@ static void send_vitals_packet(void)
|
||||
s_latest_pkt = pkt;
|
||||
s_pkt_valid = true;
|
||||
|
||||
/* Send over UDP. */
|
||||
stream_sender_send((const uint8_t *)&pkt, sizeof(pkt));
|
||||
/* ADR-063: If mmWave is active, send fused 48-byte packet instead. */
|
||||
mmwave_state_t mw;
|
||||
if (mmwave_sensor_get_state(&mw) && mw.detected) {
|
||||
edge_fused_vitals_pkt_t fpkt;
|
||||
memset(&fpkt, 0, sizeof(fpkt));
|
||||
|
||||
fpkt.magic = EDGE_FUSED_MAGIC;
|
||||
fpkt.node_id = pkt.node_id;
|
||||
fpkt.flags = pkt.flags;
|
||||
if (mw.person_present) fpkt.flags |= 0x08; /* Bit3 = mmwave_present */
|
||||
fpkt.rssi = pkt.rssi;
|
||||
fpkt.n_persons = pkt.n_persons;
|
||||
fpkt.mmwave_type = (uint8_t)mw.type;
|
||||
fpkt.motion_energy = pkt.motion_energy;
|
||||
fpkt.presence_score = pkt.presence_score;
|
||||
fpkt.timestamp_ms = pkt.timestamp_ms;
|
||||
|
||||
/* Kalman-style fusion: prefer mmWave when available, CSI as fallback. */
|
||||
if (mw.heart_rate_bpm > 0.0f && s_heartrate_bpm > 0.0f) {
|
||||
/* Weighted average: mmWave 80%, CSI 20% (mmWave is more accurate). */
|
||||
float fused_hr = mw.heart_rate_bpm * 0.8f + s_heartrate_bpm * 0.2f;
|
||||
fpkt.heartrate = (uint32_t)(fused_hr * 10000.0f);
|
||||
fpkt.fusion_confidence = 90;
|
||||
} else if (mw.heart_rate_bpm > 0.0f) {
|
||||
fpkt.heartrate = (uint32_t)(mw.heart_rate_bpm * 10000.0f);
|
||||
fpkt.fusion_confidence = 85;
|
||||
} else {
|
||||
fpkt.heartrate = pkt.heartrate;
|
||||
fpkt.fusion_confidence = 50;
|
||||
}
|
||||
|
||||
if (mw.breathing_rate > 0.0f && s_breathing_bpm > 0.0f) {
|
||||
float fused_br = mw.breathing_rate * 0.8f + s_breathing_bpm * 0.2f;
|
||||
fpkt.breathing_rate = (uint16_t)(fused_br * 100.0f);
|
||||
} else if (mw.breathing_rate > 0.0f) {
|
||||
fpkt.breathing_rate = (uint16_t)(mw.breathing_rate * 100.0f);
|
||||
} else {
|
||||
fpkt.breathing_rate = pkt.breathing_rate;
|
||||
}
|
||||
|
||||
/* Raw mmWave values for server-side analysis. */
|
||||
fpkt.mmwave_hr_bpm = mw.heart_rate_bpm;
|
||||
fpkt.mmwave_br_bpm = mw.breathing_rate;
|
||||
fpkt.mmwave_distance = mw.distance_cm;
|
||||
fpkt.mmwave_targets = mw.target_count;
|
||||
fpkt.mmwave_confidence = (mw.frame_count > 10) ? 80 : 40;
|
||||
|
||||
stream_sender_send((const uint8_t *)&fpkt, sizeof(fpkt));
|
||||
} else {
|
||||
/* No mmWave — send standard 32-byte packet. */
|
||||
stream_sender_send((const uint8_t *)&pkt, sizeof(pkt));
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================================================================
|
||||
|
||||
Reference in New Issue
Block a user