#ifndef LD2420_RADAR_H
#define LD2420_RADAR_H
#include <Arduino.h>
/*
Упрощённая динамика активности.
constant:
Активность в среднем не растёт и не падает.
increasing:
Во второй половине 10-минутного окна активность заметно выше,
чем в первой.
decreasing:
Во второй половине 10-минутного окна активность заметно ниже,
чем в первой.
variable:
Активность слишком рваная/хаотичная, простая линейная трактовка
не даёт хорошего описания.
*/
enum RadarActivityDynamics {
RADAR_ACTIVITY_DYNAMICS_CONSTANT = 0,
RADAR_ACTIVITY_DYNAMICS_INCREASING,
RADAR_ACTIVITY_DYNAMICS_DECREASING,
RADAR_ACTIVITY_DYNAMICS_VARIABLE
};
/*
Конфигурация радара и алгоритмов обработки.
Сейчас это именно практичная конфигурация под первый рабочий слой,
без лишней "умности", которую можно нарастить позже.
*/
struct RadarConfig {
/*
UART-настройки для LD2420.
Под текущую схему:
OT1 радара -> RX ESP32
RX радара -> TX ESP32
*/
int uart_rx_pin = 4;
int uart_tx_pin = 15;
uint32_t baud_rate = 115200;
/*
Если сырое присутствие перестало приходить, presence ещё немного
удерживается, чтобы не дёргаться от кратких провалов.
*/
uint32_t presence_hold_ms = 1500;
/*
Если слишком давно не было новых строк по UART, считаем радар
неактуальным и помечаем online=false.
*/
uint32_t stale_after_ms = 2000;
/*
Базовые фильтры Range.
*/
uint8_t median_window_size = 5;
uint16_t max_zone_step_per_sample = 4;
/*
EMA для дистанции и скорости.
*/
float distance_ema_alpha = 0.35f;
float speed_ema_alpha = 0.25f;
/*
Границы допустимых зон.
Пока можно держать широкий диапазон, а потом сузить.
*/
uint16_t min_valid_zone = 1;
uint16_t max_valid_zone = 200;
/*
Преобразование Range -> distance_m
Пока это приближённая калибровка.
Позже подгонишь по реальным замерам.
*/
float zone_to_meter_k = 0.7f;
float zone_to_meter_b = 0.0f;
/*
Настройка расчёта текущей активности из скорости.
Ниже этого значения считаем, что активность почти отсутствует.
*/
float activity_min_speed_m_s = 0.03f;
/*
При такой скорости current activity уже выходит примерно к 10.
Это не "физический предел", а просто верхняя точка шкалы.
*/
float activity_max_speed_m_s = 1.20f;
/*
Если нужно логировать строки, которые не распарсились.
*/
bool enable_debug_unknown_lines = false;
};
/*
Итоговое упрощённое состояние, которое сейчас действительно нужно
устройству и системе умного дома.
online:
Есть актуальные данные от радара.
presence:
Есть присутствие после фильтрации и hold-механизма.
activity_score:
Средняя активность за последнюю минуту, шкала 0..10.
activity_score_current:
Текущая активность, шкала 0..10.
activity_score_dynamics:
Характер динамики активности за последние 10 минут.
distance_m:
Отфильтрованная текущая дистанция до цели.
*/
struct RadarState {
bool online = false;
bool presence = false;
uint8_t activity_score = 0;
uint8_t activity_score_current = 0;
RadarActivityDynamics activity_score_dynamics = RADAR_ACTIVITY_DYNAMICS_CONSTANT;
float distance_m = -1.0f;
/*
Ниже — служебные поля, полезные для внутренней логики
и отладки, но их не обязательно отдавать наружу.
*/
bool raw_presence = false;
bool stale = true;
uint16_t raw_distance_zone = 0;
uint16_t filtered_distance_zone = 0;
float radial_speed_m_s = 0.0f;
uint32_t last_on_ms = 0;
uint32_t last_off_ms = 0;
uint32_t last_range_ms = 0;
uint32_t last_update_ms = 0;
uint32_t line_counter = 0;
uint32_t parse_error_counter = 0;
uint32_t ignored_range_counter = 0;
};
/*
Первый рабочий слой для LD2420 в текстовом UART-режиме.
Что умеет:
- читать ON / OFF / Range N
- фильтровать Range
- считать distance_m
- считать текущую активность
- считать усреднённую активность за минуту
- оценивать динамику активности за 10 минут
- отдавать данные как JSON-строку
Что пока не умеет:
- несколько целей
- кошки/люди
- углы и направления по комнате
- fusion нескольких радаров
*/
class Ld2420Radar {
public:
Ld2420Radar();
void begin(HardwareSerial &uart_port, const RadarConfig &config);
void update();
const RadarState &get_state() const;
String get_state_json() const;
void set_config(const RadarConfig &config);
const RadarConfig &get_config() const;
private:
static const uint8_t max_supported_median_window = 9;
/*
10-минутная история активности:
60 бакетов по 10 секунд.
Этого достаточно, чтобы:
- получить последнюю минуту как последние 6 бакетов
- получить динамику за 10 минут как анализ всех 60 бакетов
*/
static const uint8_t activity_bucket_count = 60;
static const uint32_t activity_bucket_duration_ms = 10000UL;
static const uint16_t ACTIVITY_HISTORY_SIZE = 120; // если update ~2 раза в секунду
uint8_t activity_history[ACTIVITY_HISTORY_SIZE];
uint16_t activity_history_index = 0;
bool activity_history_filled = false;
HardwareSerial *uart;
RadarConfig config;
RadarState state;
String line_buffer;
uint16_t zone_window[max_supported_median_window];
uint8_t zone_window_count;
uint8_t zone_window_index;
bool has_filtered_zone;
uint16_t last_filtered_zone;
bool has_smoothed_distance;
float smoothed_distance_m;
bool has_smoothed_speed;
float smoothed_speed_m_s;
/*
История активности.
В каждом бакете храним усреднённую активность за 10 секунд.
*/
uint8_t activity_buckets[activity_bucket_count];
uint32_t activity_bucket_start_ms;
uint32_t activity_bucket_accum_sum;
uint16_t activity_bucket_accum_count;
uint8_t activity_bucket_index;
bool activity_history_filled;
void reset_runtime_state();
void read_uart_lines();
void handle_line(const String &line);
void handle_on_line();
void handle_off_line();
void handle_range_line(const String &line);
void push_zone_sample(uint16_t zone);
uint16_t get_median_zone() const;
uint16_t apply_zone_step_limit(uint16_t candidate_zone);
float zone_to_distance_m(uint16_t zone) const;
float apply_distance_smoothing(float new_distance_m);
void update_speed(float filtered_distance_m, uint32_t now_ms);
void apply_presence_hold();
void update_online_state();
uint8_t speed_to_activity_score(float abs_speed_m_s) const;
void update_activity_history();
void commit_current_activity_bucket(uint32_t now_ms);
void update_activity_outputs();
RadarActivityDynamics calculate_activity_dynamics() const;
const char *activity_dynamics_to_string(RadarActivityDynamics dynamics) const;
bool is_zone_valid(uint16_t zone) const;
};
#endif