ESPHome 2025.5.0
Loading...
Searching...
No Matches
ld2450.cpp
Go to the documentation of this file.
1#include "ld2450.h"
2#include <utility>
3#ifdef USE_NUMBER
5#endif
6#ifdef USE_SENSOR
8#endif
10
11#define highbyte(val) (uint8_t)((val) >> 8)
12#define lowbyte(val) (uint8_t)((val) &0xff)
13
14namespace esphome {
15namespace ld2450 {
16
17static const char *const TAG = "ld2450";
18static const char *const NO_MAC("08:05:04:03:02:01");
19static const char *const UNKNOWN_MAC("unknown");
20
21// LD2450 UART Serial Commands
22static const uint8_t CMD_ENABLE_CONF = 0x00FF;
23static const uint8_t CMD_DISABLE_CONF = 0x00FE;
24static const uint8_t CMD_VERSION = 0x00A0;
25static const uint8_t CMD_MAC = 0x00A5;
26static const uint8_t CMD_RESET = 0x00A2;
27static const uint8_t CMD_RESTART = 0x00A3;
28static const uint8_t CMD_BLUETOOTH = 0x00A4;
29static const uint8_t CMD_SINGLE_TARGET_MODE = 0x0080;
30static const uint8_t CMD_MULTI_TARGET_MODE = 0x0090;
31static const uint8_t CMD_QUERY_TARGET_MODE = 0x0091;
32static const uint8_t CMD_SET_BAUD_RATE = 0x00A1;
33static const uint8_t CMD_QUERY_ZONE = 0x00C1;
34static const uint8_t CMD_SET_ZONE = 0x00C2;
35
36static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; };
37
38static inline std::string convert_signed_int_to_hex(int value) {
39 auto value_as_str = str_snprintf("%04x", 4, value & 0xFFFF);
40 return value_as_str;
41}
42
43static inline void convert_int_values_to_hex(const int *values, uint8_t *bytes) {
44 for (int i = 0; i < 4; i++) {
45 std::string temp_hex = convert_signed_int_to_hex(values[i]);
46 bytes[i * 2] = std::stoi(temp_hex.substr(2, 2), nullptr, 16); // Store high byte
47 bytes[i * 2 + 1] = std::stoi(temp_hex.substr(0, 2), nullptr, 16); // Store low byte
48 }
49}
50
51static inline int16_t decode_coordinate(uint8_t low_byte, uint8_t high_byte) {
52 int16_t coordinate = (high_byte & 0x7F) << 8 | low_byte;
53 if ((high_byte & 0x80) == 0) {
54 coordinate = -coordinate;
55 }
56 return coordinate; // mm
57}
58
59static inline int16_t decode_speed(uint8_t low_byte, uint8_t high_byte) {
60 int16_t speed = (high_byte & 0x7F) << 8 | low_byte;
61 if ((high_byte & 0x80) == 0) {
62 speed = -speed;
63 }
64 return speed * 10; // mm/s
65}
66
67static inline int16_t hex_to_signed_int(const uint8_t *buffer, uint8_t offset) {
68 uint16_t hex_val = (buffer[offset + 1] << 8) | buffer[offset];
69 int16_t dec_val = static_cast<int16_t>(hex_val);
70 if (dec_val & 0x8000) {
71 dec_val -= 65536;
72 }
73 return dec_val;
74}
75
76static inline float calculate_angle(float base, float hypotenuse) {
77 if (base < 0.0 || hypotenuse <= 0.0) {
78 return 0.0;
79 }
80 float angle_radians = std::acos(base / hypotenuse);
81 float angle_degrees = angle_radians * (180.0 / M_PI);
82 return angle_degrees;
83}
84
85static inline std::string get_direction(int16_t speed) {
86 static const char *const APPROACHING = "Approaching";
87 static const char *const MOVING_AWAY = "Moving away";
88 static const char *const STATIONARY = "Stationary";
89
90 if (speed > 0) {
91 return MOVING_AWAY;
92 }
93 if (speed < 0) {
94 return APPROACHING;
95 }
96 return STATIONARY;
97}
98
99static inline std::string format_mac(uint8_t *buffer) {
100 return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
101 buffer[15]);
102}
103
104static inline std::string format_version(uint8_t *buffer) {
105 return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
106 buffer[14]);
107}
108
109LD2450Component::LD2450Component() {}
110
111void LD2450Component::setup() {
112 ESP_LOGCONFIG(TAG, "Setting up HLK-LD2450...");
113#ifdef USE_NUMBER
114 if (this->presence_timeout_number_ != nullptr) {
115 this->pref_ = global_preferences->make_preference<float>(this->presence_timeout_number_->get_object_id_hash());
116 this->set_presence_timeout();
117 }
118#endif
119 this->restart_and_read_all_info();
120}
121
122void LD2450Component::dump_config() {
123 ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:");
124#ifdef USE_BINARY_SENSOR
125 LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_);
126 LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_);
127 LOG_BINARY_SENSOR(" ", "StillTargetBinarySensor", this->still_target_binary_sensor_);
128#endif
129#ifdef USE_SWITCH
130 LOG_SWITCH(" ", "BluetoothSwitch", this->bluetooth_switch_);
131 LOG_SWITCH(" ", "MultiTargetSwitch", this->multi_target_switch_);
132#endif
133#ifdef USE_BUTTON
134 LOG_BUTTON(" ", "ResetButton", this->reset_button_);
135 LOG_BUTTON(" ", "RestartButton", this->restart_button_);
136#endif
137#ifdef USE_SENSOR
138 LOG_SENSOR(" ", "TargetCountSensor", this->target_count_sensor_);
139 LOG_SENSOR(" ", "StillTargetCountSensor", this->still_target_count_sensor_);
140 LOG_SENSOR(" ", "MovingTargetCountSensor", this->moving_target_count_sensor_);
141 for (sensor::Sensor *s : this->move_x_sensors_) {
142 LOG_SENSOR(" ", "NthTargetXSensor", s);
143 }
144 for (sensor::Sensor *s : this->move_y_sensors_) {
145 LOG_SENSOR(" ", "NthTargetYSensor", s);
146 }
147 for (sensor::Sensor *s : this->move_speed_sensors_) {
148 LOG_SENSOR(" ", "NthTargetSpeedSensor", s);
149 }
150 for (sensor::Sensor *s : this->move_angle_sensors_) {
151 LOG_SENSOR(" ", "NthTargetAngleSensor", s);
152 }
153 for (sensor::Sensor *s : this->move_distance_sensors_) {
154 LOG_SENSOR(" ", "NthTargetDistanceSensor", s);
155 }
156 for (sensor::Sensor *s : this->move_resolution_sensors_) {
157 LOG_SENSOR(" ", "NthTargetResolutionSensor", s);
158 }
159 for (sensor::Sensor *s : this->zone_target_count_sensors_) {
160 LOG_SENSOR(" ", "NthZoneTargetCountSensor", s);
161 }
162 for (sensor::Sensor *s : this->zone_still_target_count_sensors_) {
163 LOG_SENSOR(" ", "NthZoneStillTargetCountSensor", s);
164 }
165 for (sensor::Sensor *s : this->zone_moving_target_count_sensors_) {
166 LOG_SENSOR(" ", "NthZoneMovingTargetCountSensor", s);
167 }
168#endif
169#ifdef USE_TEXT_SENSOR
170 LOG_TEXT_SENSOR(" ", "VersionTextSensor", this->version_text_sensor_);
171 LOG_TEXT_SENSOR(" ", "MacTextSensor", this->mac_text_sensor_);
172 for (text_sensor::TextSensor *s : this->direction_text_sensors_) {
173 LOG_TEXT_SENSOR(" ", "NthDirectionTextSensor", s);
174 }
175#endif
176#ifdef USE_NUMBER
177 for (auto n : this->zone_numbers_) {
178 LOG_NUMBER(" ", "ZoneX1Number", n.x1);
179 LOG_NUMBER(" ", "ZoneY1Number", n.y1);
180 LOG_NUMBER(" ", "ZoneX2Number", n.x2);
181 LOG_NUMBER(" ", "ZoneY2Number", n.y2);
182 }
183#endif
184#ifdef USE_SELECT
185 LOG_SELECT(" ", "BaudRateSelect", this->baud_rate_select_);
186 LOG_SELECT(" ", "ZoneTypeSelect", this->zone_type_select_);
187#endif
188#ifdef USE_NUMBER
189 LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_);
190#endif
191 ESP_LOGCONFIG(TAG, " Throttle : %ums", this->throttle_);
192 ESP_LOGCONFIG(TAG, " MAC Address : %s", const_cast<char *>(this->mac_.c_str()));
193 ESP_LOGCONFIG(TAG, " Firmware version : %s", const_cast<char *>(this->version_.c_str()));
194}
195
196void LD2450Component::loop() {
197 while (this->available()) {
198 this->readline_(read(), this->buffer_data_, MAX_LINE_LENGTH);
199 }
200}
201
202// Count targets in zone
203uint8_t LD2450Component::count_targets_in_zone_(const Zone &zone, bool is_moving) {
204 uint8_t count = 0;
205 for (auto &index : this->target_info_) {
206 if (index.x > zone.x1 && index.x < zone.x2 && index.y > zone.y1 && index.y < zone.y2 &&
207 index.is_moving == is_moving) {
208 count++;
209 }
210 }
211 return count;
212}
213
214// Service reset_radar_zone
215void LD2450Component::reset_radar_zone() {
216 this->zone_type_ = 0;
217 for (auto &i : this->zone_config_) {
218 i.x1 = 0;
219 i.y1 = 0;
220 i.x2 = 0;
221 i.y2 = 0;
222 }
224}
225
226void LD2450Component::set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_t zone1_y1, int32_t zone1_x2,
227 int32_t zone1_y2, int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2,
228 int32_t zone2_y2, int32_t zone3_x1, int32_t zone3_y1, int32_t zone3_x2,
229 int32_t zone3_y2) {
230 this->zone_type_ = zone_type;
231 int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1,
232 zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2};
233 for (int i = 0; i < MAX_ZONES; i++) {
234 this->zone_config_[i].x1 = zone_parameters[i * 4];
235 this->zone_config_[i].y1 = zone_parameters[i * 4 + 1];
236 this->zone_config_[i].x2 = zone_parameters[i * 4 + 2];
237 this->zone_config_[i].y2 = zone_parameters[i * 4 + 3];
238 }
240}
241
242// Set Zone on LD2450 Sensor
244 uint8_t cmd_value[26] = {};
245 uint8_t zone_type_bytes[2] = {static_cast<uint8_t>(this->zone_type_), 0x00};
246 uint8_t area_config[24] = {};
247 for (int i = 0; i < MAX_ZONES; i++) {
248 int values[4] = {this->zone_config_[i].x1, this->zone_config_[i].y1, this->zone_config_[i].x2,
249 this->zone_config_[i].y2};
250 ld2450::convert_int_values_to_hex(values, area_config + (i * 8));
251 }
252 std::memcpy(cmd_value, zone_type_bytes, 2);
253 std::memcpy(cmd_value + 2, area_config, 24);
254 this->set_config_mode_(true);
255 this->send_command_(CMD_SET_ZONE, cmd_value, 26);
256 this->set_config_mode_(false);
257}
258
259// Check presense timeout to reset presence status
260bool LD2450Component::get_timeout_status_(uint32_t check_millis) {
261 if (check_millis == 0) {
262 return true;
263 }
264 if (this->timeout_ == 0) {
265 this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT);
266 }
267 auto current_millis = millis();
268 return current_millis - check_millis >= this->timeout_;
269}
270
271// Extract, store and publish zone details LD2450 buffer
272void LD2450Component::process_zone_(uint8_t *buffer) {
273 uint8_t index, start;
274 for (index = 0; index < MAX_ZONES; index++) {
275 start = 12 + index * 8;
276 this->zone_config_[index].x1 = ld2450::hex_to_signed_int(buffer, start);
277 this->zone_config_[index].y1 = ld2450::hex_to_signed_int(buffer, start + 2);
278 this->zone_config_[index].x2 = ld2450::hex_to_signed_int(buffer, start + 4);
279 this->zone_config_[index].y2 = ld2450::hex_to_signed_int(buffer, start + 6);
280#ifdef USE_NUMBER
281 // only one null check as all coordinates are required for a single zone
282 if (this->zone_numbers_[index].x1 != nullptr) {
283 this->zone_numbers_[index].x1->publish_state(this->zone_config_[index].x1);
284 this->zone_numbers_[index].y1->publish_state(this->zone_config_[index].y1);
285 this->zone_numbers_[index].x2->publish_state(this->zone_config_[index].x2);
286 this->zone_numbers_[index].y2->publish_state(this->zone_config_[index].y2);
287 }
288#endif
289 }
290}
291
292// Read all info from LD2450 buffer
293void LD2450Component::read_all_info() {
294 this->set_config_mode_(true);
295 this->get_version_();
296 this->get_mac_();
298 this->query_zone_();
299 this->set_config_mode_(false);
300#ifdef USE_SELECT
301 const auto baud_rate = std::to_string(this->parent_->get_baud_rate());
302 if (this->baud_rate_select_ != nullptr && this->baud_rate_select_->state != baud_rate) {
303 this->baud_rate_select_->publish_state(baud_rate);
304 }
305 this->publish_zone_type();
306#endif
307}
308
309// Read zone info from LD2450 buffer
310void LD2450Component::query_zone_info() {
311 this->set_config_mode_(true);
312 this->query_zone_();
313 this->set_config_mode_(false);
314}
315
316// Restart LD2450 and read all info from buffer
317void LD2450Component::restart_and_read_all_info() {
318 this->set_config_mode_(true);
319 this->restart_();
320 this->set_timeout(1500, [this]() { this->read_all_info(); });
321}
322
323// Send command with values to LD2450
324void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
325 ESP_LOGV(TAG, "Sending command %02X", command);
326 // frame header
327 this->write_array(CMD_FRAME_HEADER, 4);
328 // length bytes
329 int len = 2;
330 if (command_value != nullptr) {
331 len += command_value_len;
332 }
333 this->write_byte(lowbyte(len));
334 this->write_byte(highbyte(len));
335 // command
336 this->write_byte(lowbyte(command));
337 this->write_byte(highbyte(command));
338 // command value bytes
339 if (command_value != nullptr) {
340 for (int i = 0; i < command_value_len; i++) {
341 this->write_byte(command_value[i]);
342 }
343 }
344 // footer
345 this->write_array(CMD_FRAME_END, 4);
346 // FIXME to remove
347 delay(50); // NOLINT
348}
349
350// LD2450 Radar data message:
351// [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC]
352// Header Target 1 Target 2 Target 3 End
353void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
354 if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
355 ESP_LOGE(TAG, "Periodic data: invalid message length");
356 return;
357 }
358 if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header
359 ESP_LOGE(TAG, "Periodic data: invalid message header");
360 return;
361 }
362 if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer
363 ESP_LOGE(TAG, "Periodic data: invalid message footer");
364 return;
365 }
366
367 auto current_millis = millis();
368 if (current_millis - this->last_periodic_millis_ < this->throttle_) {
369 ESP_LOGV(TAG, "Throttling: %d", this->throttle_);
370 return;
371 }
372
373 this->last_periodic_millis_ = current_millis;
374
375 int16_t target_count = 0;
376 int16_t still_target_count = 0;
377 int16_t moving_target_count = 0;
378 int16_t start = 0;
379 int16_t val = 0;
380 uint8_t index = 0;
381 int16_t tx = 0;
382 int16_t ty = 0;
383 int16_t td = 0;
384 int16_t ts = 0;
385 int16_t angle = 0;
386 std::string direction{};
387 bool is_moving = false;
388
389#if defined(USE_BINARY_SENSOR) || defined(USE_SENSOR) || defined(USE_TEXT_SENSOR)
390 // Loop thru targets
391 for (index = 0; index < MAX_TARGETS; index++) {
392#ifdef USE_SENSOR
393 // X
394 start = TARGET_X + index * 8;
395 is_moving = false;
396 sensor::Sensor *sx = this->move_x_sensors_[index];
397 if (sx != nullptr) {
398 val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]);
399 tx = val;
400 sx->publish_state(val);
401 }
402 // Y
403 start = TARGET_Y + index * 8;
404 sensor::Sensor *sy = this->move_y_sensors_[index];
405 if (sy != nullptr) {
406 val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]);
407 ty = val;
408 sy->publish_state(val);
409 }
410 // RESOLUTION
411 start = TARGET_RESOLUTION + index * 8;
412 sensor::Sensor *sr = this->move_resolution_sensors_[index];
413 if (sr != nullptr) {
414 val = (buffer[start + 1] << 8) | buffer[start];
415 sr->publish_state(val);
416 }
417#endif
418 // SPEED
419 start = TARGET_SPEED + index * 8;
420 val = ld2450::decode_speed(buffer[start], buffer[start + 1]);
421 ts = val;
422 if (val) {
423 is_moving = true;
424 moving_target_count++;
425 }
426#ifdef USE_SENSOR
427 sensor::Sensor *ss = this->move_speed_sensors_[index];
428 if (ss != nullptr) {
429 ss->publish_state(val);
430 }
431#endif
432 // DISTANCE
433 val = (uint16_t) sqrt(
434 pow(ld2450::decode_coordinate(buffer[TARGET_X + index * 8], buffer[(TARGET_X + index * 8) + 1]), 2) +
435 pow(ld2450::decode_coordinate(buffer[TARGET_Y + index * 8], buffer[(TARGET_Y + index * 8) + 1]), 2));
436 td = val;
437 if (val > 0) {
438 target_count++;
439 }
440#ifdef USE_SENSOR
441 sensor::Sensor *sd = this->move_distance_sensors_[index];
442 if (sd != nullptr) {
443 sd->publish_state(val);
444 }
445 // ANGLE
446 angle = calculate_angle(static_cast<float>(ty), static_cast<float>(td));
447 if (tx > 0) {
448 angle = angle * -1;
449 }
450 sensor::Sensor *sa = this->move_angle_sensors_[index];
451 if (sa != nullptr) {
452 sa->publish_state(angle);
453 }
454#endif
455#ifdef USE_TEXT_SENSOR
456 // DIRECTION
457 direction = get_direction(ts);
458 if (td == 0) {
459 direction = "NA";
460 }
461 text_sensor::TextSensor *tsd = this->direction_text_sensors_[index];
462 if (tsd != nullptr) {
464 }
465#endif
466
467 // Store target info for zone target count
468 this->target_info_[index].x = tx;
469 this->target_info_[index].y = ty;
470 this->target_info_[index].is_moving = is_moving;
471
472 } // End loop thru targets
473
474 still_target_count = target_count - moving_target_count;
475#endif
476
477#ifdef USE_SENSOR
478 // Loop thru zones
479 uint8_t zone_still_targets = 0;
480 uint8_t zone_moving_targets = 0;
481 uint8_t zone_all_targets = 0;
482 for (index = 0; index < MAX_ZONES; index++) {
483 zone_still_targets = this->count_targets_in_zone_(this->zone_config_[index], false);
484 zone_moving_targets = this->count_targets_in_zone_(this->zone_config_[index], true);
485 zone_all_targets = zone_still_targets + zone_moving_targets;
486
487 // Publish Still Target Count in Zones
488 sensor::Sensor *szstc = this->zone_still_target_count_sensors_[index];
489 if (szstc != nullptr) {
490 szstc->publish_state(zone_still_targets);
491 }
492 // Publish Moving Target Count in Zones
493 sensor::Sensor *szmtc = this->zone_moving_target_count_sensors_[index];
494 if (szmtc != nullptr) {
495 szmtc->publish_state(zone_moving_targets);
496 }
497 // Publish All Target Count in Zones
498 sensor::Sensor *sztc = this->zone_target_count_sensors_[index];
499 if (sztc != nullptr) {
500 sztc->publish_state(zone_all_targets);
501 }
502
503 } // End loop thru zones
504
505 // Target Count
506 if (this->target_count_sensor_ != nullptr) {
507 this->target_count_sensor_->publish_state(target_count);
508 }
509 // Still Target Count
510 if (this->still_target_count_sensor_ != nullptr) {
511 this->still_target_count_sensor_->publish_state(still_target_count);
512 }
513 // Moving Target Count
514 if (this->moving_target_count_sensor_ != nullptr) {
515 this->moving_target_count_sensor_->publish_state(moving_target_count);
516 }
517#endif
518
519#ifdef USE_BINARY_SENSOR
520 // Target Presence
521 if (this->target_binary_sensor_ != nullptr) {
522 if (target_count > 0) {
523 this->target_binary_sensor_->publish_state(true);
524 } else {
525 if (this->get_timeout_status_(this->presence_millis_)) {
526 this->target_binary_sensor_->publish_state(false);
527 } else {
528 ESP_LOGV(TAG, "Clear presence waiting timeout: %d", this->timeout_);
529 }
530 }
531 }
532 // Moving Target Presence
533 if (this->moving_target_binary_sensor_ != nullptr) {
534 if (moving_target_count > 0) {
535 this->moving_target_binary_sensor_->publish_state(true);
536 } else {
538 this->moving_target_binary_sensor_->publish_state(false);
539 }
540 }
541 }
542 // Still Target Presence
543 if (this->still_target_binary_sensor_ != nullptr) {
544 if (still_target_count > 0) {
545 this->still_target_binary_sensor_->publish_state(true);
546 } else {
548 this->still_target_binary_sensor_->publish_state(false);
549 }
550 }
551 }
552#endif
553#ifdef USE_SENSOR
554 // For presence timeout check
555 if (target_count > 0) {
556 this->presence_millis_ = millis();
557 }
558 if (moving_target_count > 0) {
560 }
561 if (still_target_count > 0) {
563 }
564#endif
565}
566
567bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
568 ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]);
569 if (len < 10) {
570 ESP_LOGE(TAG, "Ack data: invalid length");
571 return true;
572 }
573 if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // frame header
574 ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]);
575 return true;
576 }
577 if (buffer[COMMAND_STATUS] != 0x01) {
578 ESP_LOGE(TAG, "Ack data: invalid status");
579 return true;
580 }
581 if (buffer[8] || buffer[9]) {
582 ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]);
583 return true;
584 }
585
586 switch (buffer[COMMAND]) {
587 case lowbyte(CMD_ENABLE_CONF):
588 ESP_LOGV(TAG, "Got enable conf command");
589 break;
590 case lowbyte(CMD_DISABLE_CONF):
591 ESP_LOGV(TAG, "Got disable conf command");
592 break;
593 case lowbyte(CMD_SET_BAUD_RATE):
594 ESP_LOGV(TAG, "Got baud rate change command");
595#ifdef USE_SELECT
596 if (this->baud_rate_select_ != nullptr) {
597 ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str());
598 }
599#endif
600 break;
601 case lowbyte(CMD_VERSION):
602 this->version_ = ld2450::format_version(buffer);
603 ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str());
604#ifdef USE_TEXT_SENSOR
605 if (this->version_text_sensor_ != nullptr) {
606 this->version_text_sensor_->publish_state(this->version_);
607 }
608#endif
609 break;
610 case lowbyte(CMD_MAC):
611 if (len < 20) {
612 return false;
613 }
614 this->mac_ = ld2450::format_mac(buffer);
615 ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
616#ifdef USE_TEXT_SENSOR
617 if (this->mac_text_sensor_ != nullptr) {
618 this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_);
619 }
620#endif
621#ifdef USE_SWITCH
622 if (this->bluetooth_switch_ != nullptr) {
623 this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
624 }
625#endif
626 break;
627 case lowbyte(CMD_BLUETOOTH):
628 ESP_LOGV(TAG, "Got Bluetooth command");
629 break;
630 case lowbyte(CMD_SINGLE_TARGET_MODE):
631 ESP_LOGV(TAG, "Got single target conf command");
632#ifdef USE_SWITCH
633 if (this->multi_target_switch_ != nullptr) {
634 this->multi_target_switch_->publish_state(false);
635 }
636#endif
637 break;
638 case lowbyte(CMD_MULTI_TARGET_MODE):
639 ESP_LOGV(TAG, "Got multi target conf command");
640#ifdef USE_SWITCH
641 if (this->multi_target_switch_ != nullptr) {
642 this->multi_target_switch_->publish_state(true);
643 }
644#endif
645 break;
646 case lowbyte(CMD_QUERY_TARGET_MODE):
647 ESP_LOGV(TAG, "Got query target tracking mode command");
648#ifdef USE_SWITCH
649 if (this->multi_target_switch_ != nullptr) {
650 this->multi_target_switch_->publish_state(buffer[10] == 0x02);
651 }
652#endif
653 break;
654 case lowbyte(CMD_QUERY_ZONE):
655 ESP_LOGV(TAG, "Got query zone conf command");
656 this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16);
657 this->publish_zone_type();
658#ifdef USE_SELECT
659 if (this->zone_type_select_ != nullptr) {
660 ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->state.c_str());
661 }
662#endif
663 if (buffer[10] == 0x00) {
664 ESP_LOGV(TAG, "Zone: Disabled");
665 }
666 if (buffer[10] == 0x01) {
667 ESP_LOGV(TAG, "Zone: Area detection");
668 }
669 if (buffer[10] == 0x02) {
670 ESP_LOGV(TAG, "Zone: Area filter");
671 }
672 this->process_zone_(buffer);
673 break;
674 case lowbyte(CMD_SET_ZONE):
675 ESP_LOGV(TAG, "Got set zone conf command");
676 this->query_zone_info();
677 break;
678 default:
679 break;
680 }
681 return true;
682}
683
684// Read LD2450 buffer data
685void LD2450Component::readline_(int readch, uint8_t *buffer, uint8_t len) {
686 if (readch < 0) {
687 return;
688 }
689 if (this->buffer_pos_ < len - 1) {
690 buffer[this->buffer_pos_++] = readch;
691 buffer[this->buffer_pos_] = 0;
692 } else {
693 this->buffer_pos_ = 0;
694 }
695 if (this->buffer_pos_ < 4) {
696 return;
697 }
698 if (buffer[this->buffer_pos_ - 2] == 0x55 && buffer[this->buffer_pos_ - 1] == 0xCC) {
699 ESP_LOGV(TAG, "Handle periodic radar data");
700 this->handle_periodic_data_(buffer, this->buffer_pos_);
701 this->buffer_pos_ = 0; // Reset position index for next frame
702 } else if (buffer[this->buffer_pos_ - 4] == 0x04 && buffer[this->buffer_pos_ - 3] == 0x03 &&
703 buffer[this->buffer_pos_ - 2] == 0x02 && buffer[this->buffer_pos_ - 1] == 0x01) {
704 ESP_LOGV(TAG, "Handle command ack data");
705 if (this->handle_ack_data_(buffer, this->buffer_pos_)) {
706 this->buffer_pos_ = 0; // Reset position index for next frame
707 } else {
708 ESP_LOGV(TAG, "Command ack data invalid");
709 }
710 }
711}
712
713// Set Config Mode - Pre-requisite sending commands
715 uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
716 uint8_t cmd_value[2] = {0x01, 0x00};
717 this->send_command_(cmd, enable ? cmd_value : nullptr, 2);
718}
719
720// Set Bluetooth Enable/Disable
721void LD2450Component::set_bluetooth(bool enable) {
722 this->set_config_mode_(true);
723 uint8_t enable_cmd_value[2] = {0x01, 0x00};
724 uint8_t disable_cmd_value[2] = {0x00, 0x00};
725 this->send_command_(CMD_BLUETOOTH, enable ? enable_cmd_value : disable_cmd_value, 2);
726 this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
727}
728
729// Set Baud rate
730void LD2450Component::set_baud_rate(const std::string &state) {
731 this->set_config_mode_(true);
732 uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00};
733 this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2);
734 this->set_timeout(200, [this]() { this->restart_(); });
735}
736
737// Set Zone Type - one of: Disabled, Detection, Filter
738void LD2450Component::set_zone_type(const std::string &state) {
739 ESP_LOGV(TAG, "Set zone type: %s", state.c_str());
740 uint8_t zone_type = ZONE_TYPE_ENUM_TO_INT.at(state);
741 this->zone_type_ = zone_type;
743}
744
745// Publish Zone Type to Select component
746void LD2450Component::publish_zone_type() {
747#ifdef USE_SELECT
748 std::string zone_type = ZONE_TYPE_INT_TO_ENUM.at(static_cast<ZoneTypeStructure>(this->zone_type_));
749 if (this->zone_type_select_ != nullptr) {
750 this->zone_type_select_->publish_state(zone_type);
751 }
752#endif
753}
754
755// Set Single/Multiplayer target detection
756void LD2450Component::set_multi_target(bool enable) {
757 this->set_config_mode_(true);
758 uint8_t cmd = enable ? CMD_MULTI_TARGET_MODE : CMD_SINGLE_TARGET_MODE;
759 this->send_command_(cmd, nullptr, 0);
760 this->set_config_mode_(false);
761}
762
763// LD2450 factory reset
764void LD2450Component::factory_reset() {
765 this->set_config_mode_(true);
766 this->send_command_(CMD_RESET, nullptr, 0);
767 this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
768}
769
770// Restart LD2450 module
771void LD2450Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); }
772
773// Get LD2450 firmware version
774void LD2450Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); }
775
776// Get LD2450 mac address
778 uint8_t cmd_value[2] = {0x01, 0x00};
779 this->send_command_(CMD_MAC, cmd_value, 2);
780}
781
782// Query for target tracking mode
783void LD2450Component::query_target_tracking_mode_() { this->send_command_(CMD_QUERY_TARGET_MODE, nullptr, 0); }
784
785// Query for zone info
786void LD2450Component::query_zone_() { this->send_command_(CMD_QUERY_ZONE, nullptr, 0); }
787
788#ifdef USE_SENSOR
789void LD2450Component::set_move_x_sensor(uint8_t target, sensor::Sensor *s) { this->move_x_sensors_[target] = s; }
790void LD2450Component::set_move_y_sensor(uint8_t target, sensor::Sensor *s) { this->move_y_sensors_[target] = s; }
791void LD2450Component::set_move_speed_sensor(uint8_t target, sensor::Sensor *s) {
792 this->move_speed_sensors_[target] = s;
793}
794void LD2450Component::set_move_angle_sensor(uint8_t target, sensor::Sensor *s) {
795 this->move_angle_sensors_[target] = s;
796}
797void LD2450Component::set_move_distance_sensor(uint8_t target, sensor::Sensor *s) {
798 this->move_distance_sensors_[target] = s;
799}
800void LD2450Component::set_move_resolution_sensor(uint8_t target, sensor::Sensor *s) {
801 this->move_resolution_sensors_[target] = s;
802}
803void LD2450Component::set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
804 this->zone_target_count_sensors_[zone] = s;
805}
806void LD2450Component::set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
807 this->zone_still_target_count_sensors_[zone] = s;
808}
809void LD2450Component::set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
810 this->zone_moving_target_count_sensors_[zone] = s;
811}
812#endif
813#ifdef USE_TEXT_SENSOR
814void LD2450Component::set_direction_text_sensor(uint8_t target, text_sensor::TextSensor *s) {
815 this->direction_text_sensors_[target] = s;
816}
817#endif
818
819// Send Zone coordinates data to LD2450
820#ifdef USE_NUMBER
821void LD2450Component::set_zone_coordinate(uint8_t zone) {
822 number::Number *x1sens = this->zone_numbers_[zone].x1;
823 number::Number *y1sens = this->zone_numbers_[zone].y1;
824 number::Number *x2sens = this->zone_numbers_[zone].x2;
825 number::Number *y2sens = this->zone_numbers_[zone].y2;
826 if (!x1sens->has_state() || !y1sens->has_state() || !x2sens->has_state() || !y2sens->has_state()) {
827 return;
828 }
829 this->zone_config_[zone].x1 = static_cast<int>(x1sens->state);
830 this->zone_config_[zone].y1 = static_cast<int>(y1sens->state);
831 this->zone_config_[zone].x2 = static_cast<int>(x2sens->state);
832 this->zone_config_[zone].y2 = static_cast<int>(y2sens->state);
834}
835
836void LD2450Component::set_zone_numbers(uint8_t zone, number::Number *x1, number::Number *y1, number::Number *x2,
837 number::Number *y2) {
838 if (zone < MAX_ZONES) {
839 this->zone_numbers_[zone].x1 = x1;
840 this->zone_numbers_[zone].y1 = y1;
841 this->zone_numbers_[zone].x2 = x2;
842 this->zone_numbers_[zone].y2 = y2;
843 }
844}
845#endif
846
847// Set Presence Timeout load and save from flash
848#ifdef USE_NUMBER
849void LD2450Component::set_presence_timeout() {
850 if (this->presence_timeout_number_ != nullptr) {
851 if (this->presence_timeout_number_->state == 0) {
852 float timeout = this->restore_from_flash_();
853 this->presence_timeout_number_->publish_state(timeout);
854 this->timeout_ = ld2450::convert_seconds_to_ms(timeout);
855 }
856 if (this->presence_timeout_number_->has_state()) {
857 this->save_to_flash_(this->presence_timeout_number_->state);
858 this->timeout_ = ld2450::convert_seconds_to_ms(this->presence_timeout_number_->state);
859 }
860 }
861}
862
863// Save Presence Timeout to flash
864void LD2450Component::save_to_flash_(float value) { this->pref_.save(&value); }
865
866// Load Presence Timeout from flash
868 float value;
869 if (!this->pref_.load(&value)) {
870 value = DEFAULT_PRESENCE_TIMEOUT;
871 }
872 return value;
873}
874#endif
875
876} // namespace ld2450
877} // namespace esphome
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.cpp:72
bool save(const T *src)
Definition preferences.h:21
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
std::vector< sensor::Sensor * > move_angle_sensors_
Definition ld2450.h:221
void save_to_flash_(float value)
Definition ld2450.cpp:864
uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving)
Definition ld2450.cpp:203
std::vector< sensor::Sensor * > move_y_sensors_
Definition ld2450.h:219
void handle_periodic_data_(uint8_t *buffer, uint8_t len)
Definition ld2450.cpp:353
std::vector< sensor::Sensor * > move_speed_sensors_
Definition ld2450.h:220
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len)
Definition ld2450.cpp:324
void set_config_mode_(bool enable)
Definition ld2450.cpp:714
uint8_t buffer_data_[MAX_LINE_LENGTH]
Definition ld2450.h:203
std::vector< sensor::Sensor * > zone_still_target_count_sensors_
Definition ld2450.h:225
Target target_info_[MAX_TARGETS]
Definition ld2450.h:200
Zone zone_config_[MAX_ZONES]
Definition ld2450.h:201
void readline_(int readch, uint8_t *buffer, uint8_t len)
Definition ld2450.cpp:685
ESPPreferenceObject pref_
Definition ld2450.h:214
bool handle_ack_data_(uint8_t *buffer, uint8_t len)
Definition ld2450.cpp:567
std::vector< sensor::Sensor * > move_x_sensors_
Definition ld2450.h:218
std::vector< sensor::Sensor * > move_resolution_sensors_
Definition ld2450.h:223
std::vector< sensor::Sensor * > zone_target_count_sensors_
Definition ld2450.h:224
void process_zone_(uint8_t *buffer)
Definition ld2450.cpp:272
ZoneOfNumbers zone_numbers_[MAX_ZONES]
Definition ld2450.h:215
std::vector< text_sensor::TextSensor * > direction_text_sensors_
Definition ld2450.h:229
bool get_timeout_status_(uint32_t check_millis)
Definition ld2450.cpp:260
std::vector< sensor::Sensor * > move_distance_sensors_
Definition ld2450.h:222
std::vector< sensor::Sensor * > zone_moving_target_count_sensors_
Definition ld2450.h:226
Base-class for all sensors.
Definition sensor.h:57
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:39
void publish_state(const std::string &state)
UARTComponent * parent_
Definition uart.h:68
void write_byte(uint8_t data)
Definition uart.h:19
void write_array(const uint8_t *data, size_t len)
Definition uart.h:21
FanDirection direction
Definition fan.h:3
int speed
Definition fan.h:1
bool state
Definition fan.h:0
mopeka_std_values val[4]
std::string format_version(uint8_t *buffer)
Definition ld2410.cpp:286
const std::string NO_MAC("08:05:04:03:02:01")
const std::string UNKNOWN_MAC("unknown")
std::string format_mac(uint8_t *buffer)
Definition ld2410.cpp:303
std::vector< uint8_t > bytes
Definition sml_parser.h:13
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:301
ESPPreferences * global_preferences
std::string str_snprintf(const char *fmt, size_t len,...)
Definition helpers.cpp:309
std::string str_sprintf(const char *fmt,...)
Definition helpers.cpp:323
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27