Newer
Older
smart-home-server / devices / sensor / ld2420_radar.h
@root root 3 hours ago 9 KB Device. Sensor
#ifndef LD2420_RADAR_H
#define LD2420_RADAR_H

#include <Arduino.h>

/*
    Перечисление направления движения цели относительно радара.
    Здесь мы оцениваем только радиальную составляющую:
    - approaching  -> объект приближается к радару
    - leaving      -> объект удаляется от радара
    - static       -> заметного движения по дистанции нет
    - unknown      -> данных недостаточно
*/
enum RadarDirection {
    RADAR_DIRECTION_UNKNOWN = 0,
    RADAR_DIRECTION_STATIC,
    RADAR_DIRECTION_APPROACHING,
    RADAR_DIRECTION_LEAVING
};

/*
    Упрощённый статус сенсорного слоя.

    online:
        Есть ли признак того, что UART от радара вообще живой.

    stale:
        Данные давно не обновлялись, значит состояние устарело.

    presence:
        Отфильтрованное присутствие с удержанием presence_hold_ms.

    raw_presence:
        Сырое присутствие из последних сообщений ON/OFF/Range.

    raw_distance_zone:
        Какой Range пришёл от радара напрямую.

    median_distance_zone:
        После медианного фильтра по окну последних измерений.

    filtered_distance_zone:
        После ограничения скачка между соседними измерениями.

    distance_m:
        Отфильтрованная и откалиброванная дистанция в метрах.

    radial_speed_m_s:
        Оценка радиальной скорости в м/с.

    activity_score:
        Грубая активность 0..10 на основе модуля скорости.

    confidence:
        Оценка качества текущего состояния сенсора, а не вероятность
        обнаружения человека.

    state_json_cache:
        Кэш JSON-строки. Формируется по запросу через get_state_json().
*/
struct RadarState {
    uint32_t timestamp_ms = 0;

    bool online = false;
    bool stale = true;

    bool presence = false;
    bool raw_presence = false;

    uint16_t raw_distance_zone = 0;
    uint16_t median_distance_zone = 0;
    uint16_t filtered_distance_zone = 0;

    float distance_m = -1.0f;
    float radial_speed_m_s = 0.0f;

    RadarDirection direction = RADAR_DIRECTION_UNKNOWN;

    uint8_t activity_score = 0;
    float confidence = 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;

    String state_json_cache;
};

/*
    Настройки драйвера радара.

    uart_rx_pin / uart_tx_pin:
        Пины ESP32, на которые заведён UART радара.

    baud_rate:
        Скорость UART. Для твоего экземпляра сейчас 115200.

    presence_hold_ms:
        Сколько миллисекунд удерживать presence=true после исчезновения
        сырых сообщений о присутствии.

    stale_after_ms:
        Через сколько миллисекунд без данных считать сенсор stale.

    median_window_size:
        Размер окна медианного фильтра.
        Для простоты поддерживаются значения до 9.

    max_zone_step_per_sample:
        Максимально допустимый скачок filtered_distance_zone за один шаг.
        Если медиана прыгнула сильнее, скачок будет ограничен.

    distance_ema_alpha:
        Коэффициент EMA для дистанции. Чем больше, тем быстрее реакция
        и меньше сглаживание.

    speed_ema_alpha:
        Коэффициент EMA для скорости.

    speed_epsilon_m_s:
        Порог, ниже которого скорость считается статической.

    min_valid_zone / max_valid_zone:
        Диапазон допустимых Range. Всё вне диапазона отбрасывается.

    zone_to_meter_k:
        Коэффициент перевода зоны в метры.

    zone_to_meter_b:
        Смещение перевода зоны в метры:
            distance_m = zone * k + b

    enable_debug_unknown_lines:
        Если true, можно будет смотреть строки, которые не распарсились.
*/
struct RadarConfig {
    int uart_rx_pin = 4;
    int uart_tx_pin = 15;
    uint32_t baud_rate = 115200;

    uint32_t presence_hold_ms = 1500;
    uint32_t stale_after_ms = 2000;

    uint8_t median_window_size = 5;
    uint16_t max_zone_step_per_sample = 4;

    float distance_ema_alpha = 0.35f;
    float speed_ema_alpha = 0.25f;
    float speed_epsilon_m_s = 0.08f;

    uint16_t min_valid_zone = 1;
    uint16_t max_valid_zone = 200;

    float zone_to_meter_k = 0.7f;
    float zone_to_meter_b = 0.0f;

    bool enable_debug_unknown_lines = false;
};

/*
    Класс первого сенсорного слоя для HLK-LD2420 в текстовом режиме UART.

    Назначение класса:
    - читать строки вида ON / OFF / Range N
    - фильтровать поток
    - хранить актуальное состояние радара
    - отдавать состояние как структуру и как JSON

    Этот слой намеренно не занимается:
    - Wi-Fi / HTTP / MQTT
    - объединением нескольких радаров
    - распознаванием людей/котов
    - логикой комнаты/дома
*/
class Ld2420Radar {
public:
    Ld2420Radar();

    /*
        Инициализация драйвера.

        uart_port:
            Ссылка на UART-порт, например Serial2.

        config:
            Пользовательская конфигурация.
    */
    void begin(HardwareSerial &uart_port, const RadarConfig &config);

    /*
        Обновление состояния радара.
        Должен вызываться часто в loop().
    */
    void update();

    /*
        Есть ли новые осмысленные данные после последнего чтения.
        Полезно, если позже захочешь отправлять обновления только по событию.
    */
    bool has_new_data() const;

    /*
        Сброс флага новых данных после обработки.
    */
    void clear_new_data_flag();

    /*
        Получить текущее состояние сенсора.
    */
    const RadarState &get_state() const;

    /*
        Получить JSON-строку текущего состояния.

        Формат, например:
        {
            "timestamp_ms":123,
            "online":true,
            ...
        }
    */
    String get_state_json();

    /*
        Обновить конфигурацию на лету.
        Полезно для будущей интеграции с системой настроек.
    */
    void set_config(const RadarConfig &config);

    /*
        Получить текущую конфигурацию.
    */
    const RadarConfig &get_config() const;

    /*
        Включить/выключить режим калибровочного лога.
        Если включён, можно удобно печатать короткие строки для ручной
        калибровки зависимости Range -> distance.
    */
    void set_calibration_mode(bool enabled);

    /*
        Узнать, включён ли режим калибровки.
    */
    bool is_calibration_mode() const;

    /*
        Сформировать компактную строку для режима калибровки.
        Например:
            raw=57 median=56 filtered=55 distance=3.24 speed=-0.12
    */
    String get_calibration_line() const;

private:
    static const uint8_t max_supported_median_window = 9;

    HardwareSerial *uart;
    RadarConfig config;
    RadarState state;

    String line_buffer;
    bool new_data_available;
    bool calibration_mode;

    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;

    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 apply_distance_smoothing(float new_distance_m);
    void update_speed(float filtered_distance_m, uint32_t now_ms);
    void update_direction();
    void update_activity_score();

    void apply_presence_hold();
    void update_stale_flag();
    void update_confidence();

    bool is_zone_valid(uint16_t zone) const;
    float zone_to_distance_m(uint16_t zone) const;

    const char *direction_to_string(RadarDirection direction) const;
    String escape_json_string(const String &value) const;
};

#endif