ESPHome  2025.3.2
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
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 
14 namespace esphome {
15 namespace ld2450 {
16 
17 static const char *const TAG = "ld2450";
18 static const char *const NO_MAC("08:05:04:03:02:01");
19 static const char *const UNKNOWN_MAC("unknown");
20 
21 // LD2450 UART Serial Commands
22 static const uint8_t CMD_ENABLE_CONF = 0x00FF;
23 static const uint8_t CMD_DISABLE_CONF = 0x00FE;
24 static const uint8_t CMD_VERSION = 0x00A0;
25 static const uint8_t CMD_MAC = 0x00A5;
26 static const uint8_t CMD_RESET = 0x00A2;
27 static const uint8_t CMD_RESTART = 0x00A3;
28 static const uint8_t CMD_BLUETOOTH = 0x00A4;
29 static const uint8_t CMD_SINGLE_TARGET_MODE = 0x0080;
30 static const uint8_t CMD_MULTI_TARGET_MODE = 0x0090;
31 static const uint8_t CMD_QUERY_TARGET_MODE = 0x0091;
32 static const uint8_t CMD_SET_BAUD_RATE = 0x00A1;
33 static const uint8_t CMD_QUERY_ZONE = 0x00C1;
34 static const uint8_t CMD_SET_ZONE = 0x00C2;
35 
36 static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; };
37 
38 static 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 
43 static 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 
51 static 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 
59 static 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 
67 static 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 
76 static 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 
85 static 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 
99 static 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 
104 static 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 
109 LD2450Component::LD2450Component() {}
110 
111 void 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 
122 void 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 
196 void LD2450Component::loop() {
197  while (this->available()) {
198  this->readline_(read(), this->buffer_data_, MAX_LINE_LENGTH);
199  }
200 }
201 
202 // Count targets in zone
203 uint8_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
215 void 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  }
223  this->send_set_zone_command_();
224 }
225 
226 void 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  }
239  this->send_set_zone_command_();
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
260 bool 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
272 void 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
293 void 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
310 void 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
317 void 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
324 void 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
353 void 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) {
463  tsd->publish_state(direction);
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 {
537  if (this->get_timeout_status_(this->moving_presence_millis_)) {
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 {
547  if (this->get_timeout_status_(this->still_presence_millis_)) {
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) {
562  this->still_presence_millis_ = millis();
563  }
564 #endif
565 }
566 
567 bool 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
685 void 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
721 void 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
730 void 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
738 void 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;
742  this->send_set_zone_command_();
743 }
744 
745 // Publish Zone Type to Select component
746 void 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
756 void 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
764 void 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
771 void LD2450Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); }
772 
773 // Get LD2450 firmware version
774 void 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
783 void LD2450Component::query_target_tracking_mode_() { this->send_command_(CMD_QUERY_TARGET_MODE, nullptr, 0); }
784 
785 // Query for zone info
786 void LD2450Component::query_zone_() { this->send_command_(CMD_QUERY_ZONE, nullptr, 0); }
787 
788 #ifdef USE_SENSOR
789 void LD2450Component::set_move_x_sensor(uint8_t target, sensor::Sensor *s) { this->move_x_sensors_[target] = s; }
790 void LD2450Component::set_move_y_sensor(uint8_t target, sensor::Sensor *s) { this->move_y_sensors_[target] = s; }
791 void LD2450Component::set_move_speed_sensor(uint8_t target, sensor::Sensor *s) {
792  this->move_speed_sensors_[target] = s;
793 }
794 void LD2450Component::set_move_angle_sensor(uint8_t target, sensor::Sensor *s) {
795  this->move_angle_sensors_[target] = s;
796 }
797 void LD2450Component::set_move_distance_sensor(uint8_t target, sensor::Sensor *s) {
798  this->move_distance_sensors_[target] = s;
799 }
800 void LD2450Component::set_move_resolution_sensor(uint8_t target, sensor::Sensor *s) {
801  this->move_resolution_sensors_[target] = s;
802 }
803 void LD2450Component::set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
804  this->zone_target_count_sensors_[zone] = s;
805 }
806 void LD2450Component::set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
807  this->zone_still_target_count_sensors_[zone] = s;
808 }
809 void 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
814 void 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
821 void 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);
833  this->send_set_zone_command_();
834 }
835 
836 void 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
849 void 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
864 void 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
bool state
Definition: fan.h:34
void set_config_mode_(bool enable)
Definition: ld2450.cpp:714
bool has_state() const
Return whether this number has gotten a full state yet.
Definition: number.h:52
void handle_periodic_data_(uint8_t *buffer, uint8_t len)
Definition: ld2450.cpp:353
uint32_t get_baud_rate() const
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
std::vector< sensor::Sensor * > move_speed_sensors_
Definition: ld2450.h:220
std::vector< sensor::Sensor * > move_resolution_sensors_
Definition: ld2450.h:223
const char * to_string(SHTCXType type)
Definition: shtcx.cpp:16
bool handle_ack_data_(uint8_t *buffer, uint8_t len)
Definition: ld2450.cpp:567
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len)
Definition: ld2450.cpp:324
void write_byte(uint8_t data)
Definition: uart.h:19
std::vector< sensor::Sensor * > move_angle_sensors_
Definition: ld2450.h:221
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:69
uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving)
Definition: ld2450.cpp:203
void readline_(int readch, uint8_t *buffer, uint8_t len)
Definition: ld2450.cpp:685
int speed
Definition: fan.h:35
Zone zone_config_[MAX_ZONES]
Definition: ld2450.h:201
mopeka_std_values val[4]
std::string format_mac(uint8_t *buffer)
Definition: ld2410.cpp:303
void publish_state(const std::string &state)
Definition: text_sensor.cpp:9
UARTComponent * parent_
Definition: uart.h:68
uint8_t buffer_data_[MAX_LINE_LENGTH]
Definition: ld2450.h:203
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
bool save(const T *src)
Definition: preferences.h:21
std::vector< sensor::Sensor * > move_distance_sensors_
Definition: ld2450.h:222
std::vector< sensor::Sensor * > move_y_sensors_
Definition: ld2450.h:219
FanDirection direction
Definition: fan.h:37
void process_zone_(uint8_t *buffer)
Definition: ld2450.cpp:272
ZoneOfNumbers zone_numbers_[MAX_ZONES]
Definition: ld2450.h:215
ESPPreferences * global_preferences
Base-class for all numbers.
Definition: number.h:39
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:324
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
Target target_info_[MAX_TARGETS]
Definition: ld2450.h:200
std::vector< sensor::Sensor * > zone_moving_target_count_sensors_
Definition: ld2450.h:226
std::vector< sensor::Sensor * > move_x_sensors_
Definition: ld2450.h:218
std::string size_t len
Definition: helpers.h:301
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
const std::string NO_MAC("08:05:04:03:02:01")
std::vector< text_sensor::TextSensor * > direction_text_sensors_
Definition: ld2450.h:229
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
bool get_timeout_status_(uint32_t check_millis)
Definition: ld2450.cpp:260
void save_to_flash_(float value)
Definition: ld2450.cpp:864
std::vector< uint8_t > bytes
Definition: sml_parser.h:12
Base-class for all sensors.
Definition: sensor.h:57
std::string str_snprintf(const char *fmt, size_t len,...)
Definition: helpers.cpp:310
std::vector< sensor::Sensor * > zone_target_count_sensors_
Definition: ld2450.h:224
const std::string UNKNOWN_MAC("unknown")
std::string format_version(uint8_t *buffer)
Definition: ld2410.cpp:286
stm32_cmd_t * cmd
Definition: stm32flash.h:96
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26
std::vector< sensor::Sensor * > zone_still_target_count_sensors_
Definition: ld2450.h:225
ESPPreferenceObject pref_
Definition: ld2450.h:214