#pragma once
#include <Arduino.h>
#include <HardwareSerial.h>
/*
=========================================================
LD2420 Radar — конфигурация
Модуль: LD2420 v2.1
Режим: Energy mode (бинарный фрейм, fw >= v1.5.4)
Формат фрейма данных (Energy mode, 31 байт):
Offset Size Поле
0 4 Header: F4 F3 F2 F1
4 2 Length: uint16_t LE (обычно 0x0023)
6 1 Presence: 0=нет, 1=движение, 2=стационарный
7 2 Distance: uint16_t LE, сантиметры
9 16 Gate[0..15]: uint8_t, энергия каждых ворот
25 2 Padding: 00 00
27 4 Footer: F8 F7 F6 F5
Итого: 31 байт
=========================================================
*/
static constexpr uint8_t LD2420_TOTAL_GATES = 16;
struct RadarConfig {
/* UART пины */
uint8_t uart_rx_pin = 4;
uint8_t uart_tx_pin = 15;
uint32_t baud_rate = 115200;
/* Удерживать presence после пропадания данных */
uint32_t presence_hold_ms = 1500;
/* Считать online пока данные свежее stale_after_ms */
uint32_t stale_after_ms = 2000;
/* EMA для дистанции (0 < α ≤ 1; больше = быстрее, меньше = плавнее) */
float distance_ema_alpha = 0.35f;
/*
Расстояние одних ворот в метрах.
Для LD2420 при стандартной конфигурации ≈ 0.70 м.
Подгони по реальным замерам.
*/
float gate_size_m = 0.70f;
/*
Максимальная суммарная энергия по всем воротам,
соответствующая activity_score_current = 10.
Калибруй под реальную комнату и типичное движение.
*/
uint32_t total_energy_max = 2000;
/* Окно усреднения activity_score, секунды (1–60) */
uint8_t activity_avg_window_s = 60;
/* Окно тренда для dynamics, минуты (1–10) */
uint8_t activity_trend_window_min = 10;
bool enable_debug_frames = false;
};
/*
=========================================================
Ld2420Radar — публичный интерфейс
=========================================================
*/
class Ld2420Radar {
public:
Ld2420Radar() = default;
/* Вызвать один раз в setup() */
void begin(HardwareSerial& serial, const RadarConfig& config);
/* Вызывать каждый loop() */
void update();
/* JSON-снимок состояния (статический буфер, валиден до следующего вызова) */
const char* get_state_json();
/* ---- Прямой доступ к полям ---- */
bool is_online() const { return _online; }
bool is_presence() const { return _presence; }
uint8_t get_activity_score() const { return _activity_score; }
uint8_t get_activity_score_current() const { return _activity_score_current; }
float get_distance_m() const { return _distance_m; }
/* "constant" | "increasing" | "decreasing" | "variable" */
const char* get_activity_dynamics() const;
/* Сырые уровни энергии ворот 0–15 из последнего фрейма */
uint8_t get_gate_energy(uint8_t gate) const {
return (gate < LD2420_TOTAL_GATES) ? _gate_energy[gate] : 0;
}
private:
/* ---- Конфигурация и железо ---- */
HardwareSerial* _serial = nullptr;
RadarConfig _cfg;
/* ---- Бинарный парсер фрейма ---- */
static constexpr uint8_t FRAME_SIZE = 31;
static constexpr uint8_t FRAME_PRESENCE_OFF = 6;
static constexpr uint8_t FRAME_DIST_OFFSET = 7;
static constexpr uint8_t FRAME_GATE_OFFSET = 9;
static constexpr uint8_t FRAME_FOOTER_OFF = 27;
/* Байты заголовка */
static constexpr uint8_t HDR[4] = {0xF4, 0xF3, 0xF2, 0xF1};
/* Байты футера */
static constexpr uint8_t FTR[4] = {0xF8, 0xF7, 0xF6, 0xF5};
uint8_t _rx_buf[FRAME_SIZE] = {};
uint8_t _rx_idx = 0;
bool _synced = false; // заголовок пойман, накапливаем оставшиеся байты
void _feed_byte(uint8_t b);
bool _validate_footer() const;
void _process_frame();
/* ---- Данные последнего фрейма ---- */
uint8_t _gate_energy[LD2420_TOTAL_GATES] = {};
float _dist_ema = -1.0f;
float _prev_dist_m = -1.0f;
/* ---- Онлайн / присутствие ---- */
uint32_t _last_frame_ms = 0;
uint32_t _last_on_ms = 0;
bool _online = false;
bool _presence = false;
/* ---- Дистанция ---- */
float _distance_m = 0.0f;
/* ---- activity_score_current ---- */
uint8_t _activity_score_current = 0;
/* ---- activity_score (скользящее среднее 1 сэмпл/с, окно 60 с) ---- */
static constexpr uint8_t ACT_AVG_MAX = 60;
uint8_t _act_avg_buf[ACT_AVG_MAX] = {};
uint8_t _act_avg_idx = 0;
uint8_t _act_avg_count = 0;
uint32_t _act_avg_last_ms = 0;
uint8_t _activity_score = 0;
void _tick_activity_avg(uint32_t now_ms);
/* ---- Тренд активности (1 точка/мин, окно 10 мин) ---- */
static constexpr uint8_t TREND_MAX = 10;
uint8_t _trend_buf[TREND_MAX] = {};
uint8_t _trend_idx = 0;
uint8_t _trend_count = 0;
uint32_t _trend_last_ms = 0;
int8_t _trend_slope = 0; // 1 = рост, 0 = плоско, -1 = спад
float _trend_std_dev = 0.0f;
void _tick_trend(uint32_t now_ms);
void _compute_trend();
/* ---- JSON-буфер ---- */
char _json_buf[200];
};