#include "ld2420_radar.h"
/* ------------------------------------------------------------------ */
/* ------------------------ Инициализация --------------------------*/
/* ------------------------------------------------------------------ */
void Ld2420Radar::begin(Stream &uart, const RadarConfig &cfg) {
_uart = &uart;
_cfg = cfg;
/* Установим параметры UART */
pinMode(_cfg.uart_rx_pin, INPUT_PULLUP);
pinMode(_cfg.uart_tx_pin, OUTPUT);
// На ESP32 Serial2 уже привязан к этим пинам, но оставляем для совместимости
}
/* ------------------------------------------------------------------ */
/* -------------------------- Основной цикл ------------------------*/
/* ------------------------------------------------------------------ */
void Ld2420Radar::update() {
if (!_uart) return;
/* Читаем все доступные байты */
while (_uart->available()) {
char c = _uart->read();
if (c == '\n' || c == '\r') {
if (_line_pos > 0) {
_parse_line(_line_buf, _line_pos);
_line_pos = 0;
}
} else {
if (_line_pos < LINE_BUF_SIZE - 1) {
_line_buf[_line_pos++] = c;
} else {
/* Буфер переполнен – сбрасываем */
_line_pos = 0;
}
}
}
_update_online_state();
}
/* ------------------------------------------------------------------ */
/* ------------------------ Парсинг строки -----------------------*/
/* ------------------------------------------------------------------ */
void Ld2420Radar::_parse_line(const char *line, size_t len) {
if (len < 5) return; // минимальная длина +<zone>,<dist>
/* Время прихода */
_last_line_ms = millis();
/* Если строка начинается с '+' – это данные */
if (line[0] == '+') {
const char *p = line + 1;
uint16_t zone = 0;
float distance_m = NAN, speed_m_s = NAN;
/* Парсим <zone> */
while (*p && *p != ',') {
if (!isdigit(*p)) return; // Неверный формат
zone = zone * 10 + (*p - '0');
++p;
}
if (*p != ',') return;
++p;
/* Парсим <distance> (число с плавающей точкой) */
char dist_str[16];
size_t i = 0;
while (*p && *p != ',' && i < sizeof(dist_str)-1) {
dist_str[i++] = *p++;
}
if (*p != ',') return;
++p;
dist_str[i] = '\0';
distance_m = atof(dist_str);
/* Парсим <speed> (число с плавающей точкой) */
char speed_str[16];
i = 0;
while (*p && i < sizeof(speed_str)-1) {
speed_str[i++] = *p++;
}
speed_str[i] = '\0';
speed_m_s = atof(speed_str);
/* Обновляем состояние */
_update_presence_state(zone);
_update_distance_speed(distance_m, speed_m_s);
} else if (line[0] == '-') {
/* Ошибка от радара – просто игнорируем */
return;
} else {
/* Неизвестная строка */
if (_cfg.enable_debug_unknown_lines) {
Serial.printf("Unknown radar line: %.*s\n", (int)len, line);
}
}
}
/* ------------------------------------------------------------------ */
/* ----------------------- Обновление online ----------------------*/
/* ------------------------------------------------------------------ */
void Ld2420Radar::_update_online_state() {
uint32_t now = millis();
_state.online = (now - _last_line_ms <= _cfg.stale_after_ms);
}
/* ------------------------------------------------------------------ */
/* --------------------- Обновление presence ---------------------*/
/* ------------------------------------------------------------------ */
void Ld2420Radar::_update_presence_state(uint16_t zone) {
if (zone < _cfg.min_valid_zone || zone > _cfg.max_valid_zone) return;
/* Если пришел первый сигнал, запоминаем время */
if (!_state.presence) {
_presence_start_ms = millis();
}
_state.presence = true;
}
/* ------------------------------------------------------------------ */
/* -------------------- Обновление расстояния/скорости ----------*/
/* ------------------------------------------------------------------ */
void Ld2420Radar::_update_distance_speed(float distance_m, float speed_m_s) {
/* Сглаживаем дистанцию */
if (isnan(_state.distance_m)) _state.distance_m = distance_m;
else
_state.distance_m =
_cfg.distance_ema_alpha * distance_m +
(1.0f - _cfg.distance_ema_alpha) * _state.distance_m;
/* Сглаживаем скорость */
float speed_smooth = 0.0f;
if (!isnan(speed_m_s)) {
static float prev_speed = 0.0f;
speed_smooth =
_cfg.speed_ema_alpha * speed_m_s +
(1.0f - _cfg.speed_ema_alpha) * prev_speed;
prev_speed = speed_smooth;
}
/* Обновляем активность */
_calc_activity_score_current();
/* Сохраняем в историю */
_activity_hist[_hist_idx++] = _state.activity_score_current;
if (_hist_idx >= ACTIVITY_HISTORY) {
_hist_idx = 0;
_hist_filled = true;
}
/* Вычисляем среднюю активность за минуту (последние N значений) */
_calc_activity_score_average();
_determine_dynamics();
}
/* ------------------------------------------------------------------ */
/* -------------------- Расчёт текущей активности ----------------*/
/* ------------------------------------------------------------------ */
void Ld2420Radar::_calc_activity_score_current() {
/* Прямое линейное преобразование скорости к шкале 0…10 */
float speed = _cfg.speed_ema_alpha * _state.distance_m; // placeholder
if (speed < _cfg.activity_min_speed_m_s) {
_state.activity_score_current = 0;
} else if (speed >= _cfg.activity_max_speed_m_s) {
_state.activity_score_current = 10;
} else {
float v = (speed - _cfg.activity_min_speed_m_s) /
(_cfg.activity_max_speed_m_s - _cfg.activity_min_speed_m_s);
_state.activity_score_current = round(v * 10.0f);
}
}
/* ------------------------------------------------------------------ */
/* --------------------- Средняя активность за минуту -------------*/
/* ------------------------------------------------------------------ */
void Ld2420Radar::_calc_activity_score_average() {
if (!_hist_filled && _hist_idx == 0) return; // не хватает данных
uint8_t sum = 0;
uint8_t cnt = _hist_filled ? ACTIVITY_HISTORY : _hist_idx;
for (uint8_t i = 0; i < cnt; ++i)
sum += _activity_hist[i];
_state.activity_score = round((float)sum / cnt);
}
/* ------------------------------------------------------------------ */
/* ----------------------- Динамика активности --------------------*/
/* ------------------------------------------------------------------ */
void Ld2420Radar::_determine_dynamics() {
/* Сравниваем последние два значения (или средние) */
uint8_t last2[ACTIVITY_HISTORY];
if (_hist_filled) {
for (int i = 0; i < ACTIVITY_HISTORY; ++i)
last2[i] = _activity_hist[(ACTIVITY_HISTORY - 1 - i)];
} else {
for (uint8_t i = 0; i < _hist_idx; ++i)
last2[i] = _activity_hist[(_hist_idx - 1 - i)];
}
/* Считаем среднее за последние 5 значений */
uint8_t cnt = min<uint8_t>(5, ACTIVITY_HISTORY);
uint8_t avg_prev = 0, avg_curr = 0;
for (uint8_t i = 0; i < cnt; ++i) {
avg_prev += last2[i];
if (i >= cnt/2) avg_curr += last2[i];
}
avg_prev /= cnt;
avg_curr /= cnt;
/* Выбираем динамику */
if (avg_curr > avg_prev + 1)
_state.activity_score_dynamics = "increasing";
else if (avg_curr < avg_prev - 1)
_state.activity_score_dynamics = "decreasing";
else
_state.activity_score_dynamics = "constant";
}
/* ------------------------------------------------------------------ */
/* -------------------------- JSON‑вывод --------------------------*/
/* ------------------------------------------------------------------ */
String Ld2420Radar::get_state_json() const {
String s;
s.reserve(200);
s += "{";
s += "\"online\": ";
s += _state.online ? "true" : "false";
s += ", ";
s += "\"presence\": ";
s += _state.presence ? "true" : "false";
s += ", ";
s += "\"activity_score\": ";
s += String(_state.activity_score);
s += ", ";
s += "\"activity_score_current\": ";
s += String(_state.activity_score_current);
s += ", ";
s += "\"activity_score_dynamics\": \"";
s += _state.activity_score_dynamics;
s += "\", ";
s += "\"distance_m\": ";
if (isnan(_state.distance_m)) {
s += "null";
} else {
s += String(_state.distance_m, 2); // 2 знака после запятой
}
s += "}";
return s;
}