ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
ld2410.cpp
Go to the documentation of this file.
1#include "ld2410.h"
2
3#ifdef USE_NUMBER
5#endif
6#ifdef USE_SENSOR
8#endif
9
12
13namespace esphome::ld2410 {
14
15ESPHOME_LOG_TAG(TAG, "ld2410");
16
27
32
38
39enum OutPinLevel : uint8_t {
42};
43
60
61enum PeriodicDataValue : uint8_t {
62 HEADER = 0xAA,
63 FOOTER = 0x55,
64 CHECK = 0x00,
65};
66
67enum AckData : uint8_t {
70};
71
72// Memory-efficient lookup tables
73struct StringToUint8 {
74 const char *str;
75 const uint8_t value;
76};
77
78struct Uint8ToString {
79 const uint8_t value;
80 const char *str;
81};
82
83constexpr StringToUint8 BAUD_RATES_BY_STR[] = {
84 {"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
85 {"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
86 {"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800},
87};
88
89constexpr StringToUint8 DISTANCE_RESOLUTIONS_BY_STR[] = {
91 {"0.75m", DISTANCE_RESOLUTION_0_75},
92};
93
94constexpr Uint8ToString DISTANCE_RESOLUTIONS_BY_UINT[] = {
96 {DISTANCE_RESOLUTION_0_75, "0.75m"},
97};
98
99constexpr StringToUint8 LIGHT_FUNCTIONS_BY_STR[] = {
100 {"off", LIGHT_FUNCTION_OFF},
101 {"below", LIGHT_FUNCTION_BELOW},
102 {"above", LIGHT_FUNCTION_ABOVE},
103};
104
105constexpr Uint8ToString LIGHT_FUNCTIONS_BY_UINT[] = {
106 {LIGHT_FUNCTION_OFF, "off"},
107 {LIGHT_FUNCTION_BELOW, "below"},
108 {LIGHT_FUNCTION_ABOVE, "above"},
109};
110
111constexpr StringToUint8 OUT_PIN_LEVELS_BY_STR[] = {
112 {"low", OUT_PIN_LEVEL_LOW},
113 {"high", OUT_PIN_LEVEL_HIGH},
114};
115
116constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[] = {
117 {OUT_PIN_LEVEL_LOW, "low"},
118 {OUT_PIN_LEVEL_HIGH, "high"},
119};
120
121constexpr uint32_t BAUD_RATES[] = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800};
122
123// Helper functions for lookups
124template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const char *str) {
125 for (const auto &entry : arr) {
126 if (strcmp(str, entry.str) == 0)
127 return entry.value;
128 }
129 return 0xFF; // Not found
130}
131
132template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t value) {
133 for (const auto &entry : arr) {
134 if (value == entry.value)
135 return entry.str;
136 }
137 return ""; // Not found
138}
139
140// Commands
141static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
142static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
143static constexpr uint8_t CMD_ENABLE_ENG = 0x62;
144static constexpr uint8_t CMD_DISABLE_ENG = 0x63;
145static constexpr uint8_t CMD_MAXDIST_DURATION = 0x60;
146static constexpr uint8_t CMD_QUERY = 0x61;
147static constexpr uint8_t CMD_GATE_SENS = 0x64;
148static constexpr uint8_t CMD_QUERY_VERSION = 0xA0;
149static constexpr uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0xAB;
150static constexpr uint8_t CMD_SET_DISTANCE_RESOLUTION = 0xAA;
151static constexpr uint8_t CMD_QUERY_LIGHT_CONTROL = 0xAE;
152static constexpr uint8_t CMD_SET_LIGHT_CONTROL = 0xAD;
153static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1;
154static constexpr uint8_t CMD_BT_PASSWORD = 0xA9;
155static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5;
156static constexpr uint8_t CMD_RESET = 0xA2;
157static constexpr uint8_t CMD_RESTART = 0xA3;
158static constexpr uint8_t CMD_BLUETOOTH = 0xA4;
159// Commands values
160static constexpr uint8_t CMD_MAX_MOVE_VALUE = 0x00;
161static constexpr uint8_t CMD_MAX_STILL_VALUE = 0x01;
162static constexpr uint8_t CMD_DURATION_VALUE = 0x02;
163// Bitmasks for target states
164static constexpr uint8_t MOVE_BITMASK = 0x01;
165static constexpr uint8_t STILL_BITMASK = 0x02;
166// Header & Footer size
167static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
168// Command Header & Footer
169static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA};
170static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01};
171// Data Header & Footer
172static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xF4, 0xF3, 0xF2, 0xF1};
173static constexpr uint8_t DATA_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0xF8, 0xF7, 0xF6, 0xF5};
174// MAC address the module uses when Bluetooth is disabled
175static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
176
177static inline bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
178 return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0;
179}
180
181void LD2410Component::dump_config() {
182 char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
183 char version_s[20];
184 const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
185 ld24xx::format_version_str(this->version_, version_s);
186 ESP_LOGCONFIG(TAG,
187 "LD2410:\n"
188 " Firmware version: %s\n"
189 " MAC address: %s",
190 version_s, mac_str);
191#ifdef USE_BINARY_SENSOR
192 ESP_LOGCONFIG(TAG, "Binary Sensors:");
193 LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
194 LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_);
195 LOG_BINARY_SENSOR(" ", "StillTarget", this->still_target_binary_sensor_);
196 LOG_BINARY_SENSOR(" ", "OutPinPresenceStatus", this->out_pin_presence_status_binary_sensor_);
197#endif
198#ifdef USE_SENSOR
199 ESP_LOGCONFIG(TAG, "Sensors:");
200 LOG_SENSOR_WITH_DEDUP_SAFE(" ", "Light", this->light_sensor_);
201 LOG_SENSOR_WITH_DEDUP_SAFE(" ", "DetectionDistance", this->detection_distance_sensor_);
202 LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetDistance", this->moving_target_distance_sensor_);
203 LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetEnergy", this->moving_target_energy_sensor_);
204 LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetDistance", this->still_target_distance_sensor_);
205 LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetEnergy", this->still_target_energy_sensor_);
206 for (auto &s : this->gate_move_sensors_) {
207 LOG_SENSOR_WITH_DEDUP_SAFE(" ", "GateMove", s);
208 }
209 for (auto &s : this->gate_still_sensors_) {
210 LOG_SENSOR_WITH_DEDUP_SAFE(" ", "GateStill", s);
211 }
212#endif
213#ifdef USE_TEXT_SENSOR
214 ESP_LOGCONFIG(TAG, "Text Sensors:");
215 LOG_TEXT_SENSOR(" ", "Mac", this->mac_text_sensor_);
216 LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
217#endif
218#ifdef USE_NUMBER
219 ESP_LOGCONFIG(TAG, "Numbers:");
220 LOG_NUMBER(" ", "LightThreshold", this->light_threshold_number_);
221 LOG_NUMBER(" ", "MaxMoveDistanceGate", this->max_move_distance_gate_number_);
222 LOG_NUMBER(" ", "MaxStillDistanceGate", this->max_still_distance_gate_number_);
223 LOG_NUMBER(" ", "Timeout", this->timeout_number_);
224 for (number::Number *n : this->gate_move_threshold_numbers_) {
225 LOG_NUMBER(" ", "MoveThreshold", n);
226 }
227 for (number::Number *n : this->gate_still_threshold_numbers_) {
228 LOG_NUMBER(" ", "StillThreshold", n);
229 }
230#endif
231#ifdef USE_SELECT
232 ESP_LOGCONFIG(TAG, "Selects:");
233 LOG_SELECT(" ", "BaudRate", this->baud_rate_select_);
234 LOG_SELECT(" ", "DistanceResolution", this->distance_resolution_select_);
235 LOG_SELECT(" ", "LightFunction", this->light_function_select_);
236 LOG_SELECT(" ", "OutPinLevel", this->out_pin_level_select_);
237#endif
238#ifdef USE_SWITCH
239 ESP_LOGCONFIG(TAG, "Switches:");
240 LOG_SWITCH(" ", "Bluetooth", this->bluetooth_switch_);
241 LOG_SWITCH(" ", "EngineeringMode", this->engineering_mode_switch_);
242#endif
243#ifdef USE_BUTTON
244 ESP_LOGCONFIG(TAG, "Buttons:");
245 LOG_BUTTON(" ", "FactoryReset", this->factory_reset_button_);
246 LOG_BUTTON(" ", "Query", this->query_button_);
247 LOG_BUTTON(" ", "Restart", this->restart_button_);
248#endif
249}
250
251void LD2410Component::setup() { this->read_all_info(); }
252
253void LD2410Component::read_all_info() {
254 this->set_config_mode_(true);
255 this->get_version_();
256 this->get_mac_();
258 this->query_light_control_();
259 this->query_parameters_();
260 this->set_config_mode_(false);
261#ifdef USE_SELECT
262 if (this->baud_rate_select_ != nullptr) {
263 if (auto index = ld24xx::find_index(BAUD_RATES, this->parent_->get_baud_rate())) {
264 this->baud_rate_select_->publish_state(*index);
265 }
266 }
267#endif
268}
269
270void LD2410Component::restart_and_read_all_info() {
271 this->set_config_mode_(true);
272 this->restart_();
273 this->set_timeout(1000, [this]() { this->read_all_info(); });
274}
275
276void LD2410Component::loop() {
277 // Read all available bytes in batches to reduce UART call overhead.
278 size_t avail = this->available();
279 uint8_t buf[MAX_LINE_LENGTH];
280 while (avail > 0) {
281 size_t to_read = std::min(avail, sizeof(buf));
282 if (!this->read_array(buf, to_read)) {
283 break;
284 }
285 avail -= to_read;
286
287 for (size_t i = 0; i < to_read; i++) {
288 this->readline_(buf[i]);
289 }
290 }
291}
292
293void LD2410Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
294 ESP_LOGV(TAG, "Sending COMMAND %02X", command);
295 // frame header bytes
296 this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER));
297 // length bytes
298 uint8_t len = 2;
299 if (command_value != nullptr) {
300 len += command_value_len;
301 }
302 // 2 length bytes (low, high) + 2 command bytes (low, high)
303 uint8_t len_cmd[] = {len, 0x00, command, 0x00};
304 this->write_array(len_cmd, sizeof(len_cmd));
305 // command value bytes
306 if (command_value != nullptr) {
307 this->write_array(command_value, command_value_len);
308 }
309 // frame footer bytes
310 this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER));
311
312 if (command != CMD_ENABLE_CONF && command != CMD_DISABLE_CONF) {
313 delay(50); // NOLINT
314 }
315}
316
318 // 4 frame header bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame footer bytes
319 // data header=0xAA, data footer=0x55, crc=0x00
320 if (this->buffer_pos_ < 12 || !ld2410::validate_header_footer(DATA_FRAME_HEADER, this->buffer_data_) ||
321 this->buffer_data_[7] != HEADER || this->buffer_data_[this->buffer_pos_ - 6] != FOOTER ||
322 this->buffer_data_[this->buffer_pos_ - 5] != CHECK) {
323 return;
324 }
325 /*
326 Data Type: 7th
327 0x01: Engineering mode
328 0x02: Normal mode
329 */
330 bool engineering_mode = this->buffer_data_[DATA_TYPES] == 0x01;
331#ifdef USE_SWITCH
332 if (this->engineering_mode_switch_ != nullptr) {
333 this->engineering_mode_switch_->publish_state(engineering_mode);
334 }
335#endif
336#ifdef USE_BINARY_SENSOR
337 /*
338 Target states: 9th
339 0x00 = No target
340 0x01 = Moving targets
341 0x02 = Still targets
342 0x03 = Moving+Still targets
343 */
344 char target_state = this->buffer_data_[TARGET_STATES];
345 if (this->target_binary_sensor_ != nullptr) {
346 this->target_binary_sensor_->publish_state(target_state != 0x00);
347 }
348 if (this->moving_target_binary_sensor_ != nullptr) {
349 this->moving_target_binary_sensor_->publish_state(target_state & MOVE_BITMASK);
350 }
351 if (this->still_target_binary_sensor_ != nullptr) {
352 this->still_target_binary_sensor_->publish_state(target_state & STILL_BITMASK);
353 }
354#endif
355 /*
356 Moving target distance: 10~11th bytes
357 Moving target energy: 12th byte
358 Still target distance: 13~14th bytes
359 Still target energy: 15th byte
360 Detect distance: 16~17th bytes
361 */
362#ifdef USE_SENSOR
363 SAFE_PUBLISH_SENSOR(this->moving_target_distance_sensor_,
365 SAFE_PUBLISH_SENSOR(this->moving_target_energy_sensor_, this->buffer_data_[MOVING_ENERGY]);
366 SAFE_PUBLISH_SENSOR(this->still_target_distance_sensor_,
368 SAFE_PUBLISH_SENSOR(this->still_target_energy_sensor_, this->buffer_data_[STILL_ENERGY]);
369 SAFE_PUBLISH_SENSOR(this->detection_distance_sensor_,
371
372 if (engineering_mode) {
373 /*
374 Moving distance range: 18th byte
375 Still distance range: 19th byte
376 Moving energy: 20~28th bytes
377 */
378 for (uint8_t i = 0; i < TOTAL_GATES; i++) {
379 SAFE_PUBLISH_SENSOR(this->gate_move_sensors_[i], this->buffer_data_[MOVING_SENSOR_START + i]);
380 }
381 /*
382 Still energy: 29~37th bytes
383 */
384 for (uint8_t i = 0; i < TOTAL_GATES; i++) {
385 SAFE_PUBLISH_SENSOR(this->gate_still_sensors_[i], this->buffer_data_[STILL_SENSOR_START + i]);
386 }
387 /*
388 Light sensor: 38th bytes
389 */
390 SAFE_PUBLISH_SENSOR(this->light_sensor_, this->buffer_data_[LIGHT_SENSOR]);
391 } else {
392 for (auto &gate_move_sensor : this->gate_move_sensors_) {
393 SAFE_PUBLISH_SENSOR_UNKNOWN(gate_move_sensor);
394 }
395 for (auto &gate_still_sensor : this->gate_still_sensors_) {
396 SAFE_PUBLISH_SENSOR_UNKNOWN(gate_still_sensor);
397 }
398 SAFE_PUBLISH_SENSOR_UNKNOWN(this->light_sensor_);
399 }
400#endif
401#ifdef USE_BINARY_SENSOR
402 if (this->out_pin_presence_status_binary_sensor_ != nullptr) {
403 this->out_pin_presence_status_binary_sensor_->publish_state(
404 engineering_mode ? this->buffer_data_[OUT_PIN_SENSOR] == 0x01 : false);
405 }
406#endif
407}
408
409#ifdef USE_NUMBER
410void set_number_value(number::Number *n, float value) {
411 if (n != nullptr && (!n->has_state() || n->state != value)) {
412 n->publish_state(value);
413 }
414}
415#endif
416
418 ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]);
419 if (this->buffer_pos_ < 10) {
420 ESP_LOGE(TAG, "Invalid length");
421 return true;
422 }
423 if (!ld2410::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
424 char hex_buf[format_hex_pretty_size(HEADER_FOOTER_SIZE)];
425 ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, HEADER_FOOTER_SIZE));
426 return true;
427 }
428 if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
429 ESP_LOGE(TAG, "Invalid status");
430 return true;
431 }
432 if (this->buffer_data_[8] || this->buffer_data_[9]) {
433 ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]);
434 return true;
435 }
436
437 switch (this->buffer_data_[COMMAND]) {
438 case CMD_ENABLE_CONF:
439 ESP_LOGV(TAG, "Enable conf");
440 break;
441
442 case CMD_DISABLE_CONF:
443 ESP_LOGV(TAG, "Disabled conf");
444 break;
445
446 case CMD_SET_BAUD_RATE:
447 ESP_LOGV(TAG, "Baud rate change");
448#ifdef USE_SELECT
449 if (this->baud_rate_select_ != nullptr) {
450 auto baud = this->baud_rate_select_->current_option();
451 ESP_LOGE(TAG, "Change baud rate to %.*s and reinstall", (int) baud.size(), baud.c_str());
452 }
453#endif
454 break;
455
456 case CMD_QUERY_VERSION: {
457 std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
458 char version_s[20];
459 ld24xx::format_version_str(this->version_, version_s);
460 ESP_LOGV(TAG, "Firmware version: %s", version_s);
461#ifdef USE_TEXT_SENSOR
462 if (this->version_text_sensor_ != nullptr) {
463 this->version_text_sensor_->publish_state(version_s);
464 }
465#endif
466 break;
467 }
468
469 case CMD_QUERY_DISTANCE_RESOLUTION: {
470 const auto *distance_resolution = find_str(DISTANCE_RESOLUTIONS_BY_UINT, this->buffer_data_[10]);
471 ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution);
472#ifdef USE_SELECT
473 if (this->distance_resolution_select_ != nullptr) {
474 this->distance_resolution_select_->publish_state(distance_resolution);
475 }
476#endif
477 break;
478 }
479
480 case CMD_QUERY_LIGHT_CONTROL: {
481 this->light_function_ = this->buffer_data_[10];
482 this->light_threshold_ = this->buffer_data_[11];
483 this->out_pin_level_ = this->buffer_data_[12];
484 const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_);
485 const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_);
486 ESP_LOGV(TAG, "Light function: %s, threshold: %u, out pin level: %s", light_function_str, this->light_threshold_,
487 out_pin_level_str);
488#ifdef USE_SELECT
489 if (this->light_function_select_ != nullptr) {
490 this->light_function_select_->publish_state(light_function_str);
491 }
492 if (this->out_pin_level_select_ != nullptr) {
493 this->out_pin_level_select_->publish_state(out_pin_level_str);
494 }
495#endif
496#ifdef USE_NUMBER
497 if (this->light_threshold_number_ != nullptr) {
498 this->light_threshold_number_->publish_state(static_cast<float>(this->light_threshold_));
499 }
500#endif
501 break;
502 }
503 case CMD_QUERY_MAC_ADDRESS: {
504 if (this->buffer_pos_ < 20) {
505 return false;
506 }
507
508 this->bluetooth_on_ = std::memcmp(&this->buffer_data_[10], NO_MAC, sizeof(NO_MAC)) != 0;
509 if (this->bluetooth_on_) {
510 std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
511 }
512
513 char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
514 const char *mac_str = ld24xx::format_mac_str(this->mac_address_, mac_s);
515 ESP_LOGV(TAG, "MAC address: %s", mac_str);
516#ifdef USE_TEXT_SENSOR
517 if (this->mac_text_sensor_ != nullptr) {
518 this->mac_text_sensor_->publish_state(mac_str);
519 }
520#endif
521#ifdef USE_SWITCH
522 if (this->bluetooth_switch_ != nullptr) {
523 this->bluetooth_switch_->publish_state(this->bluetooth_on_);
524 }
525#endif
526 break;
527 }
528
529 case CMD_GATE_SENS:
530 ESP_LOGV(TAG, "Sensitivity");
531 break;
532
533 case CMD_BLUETOOTH:
534 ESP_LOGV(TAG, "Bluetooth");
535 break;
536
537 case CMD_SET_DISTANCE_RESOLUTION:
538 ESP_LOGV(TAG, "Set distance resolution");
539 break;
540
541 case CMD_SET_LIGHT_CONTROL:
542 ESP_LOGV(TAG, "Set light control");
543 break;
544
545 case CMD_BT_PASSWORD:
546 ESP_LOGV(TAG, "Set bluetooth password");
547 break;
548
549 case CMD_QUERY: { // Query parameters response
550 // Config values can contain the footer bytes, so wait for the whole 38 byte frame
551 if (this->buffer_pos_ < 38) {
552 return false;
553 }
554 if (this->buffer_data_[10] != HEADER)
555 return true; // value head=0xAA
556#ifdef USE_NUMBER
557 /*
558 Moving distance range: 13th byte
559 Still distance range: 14th byte
560 */
561 set_number_value(this->max_move_distance_gate_number_, this->buffer_data_[12]);
562 set_number_value(this->max_still_distance_gate_number_, this->buffer_data_[13]);
563 /*
564 Moving Sensitivities: 15~23th bytes
565 */
566 for (size_t i = 0; i < TOTAL_GATES; i++) {
567 set_number_value(this->gate_move_threshold_numbers_[i], this->buffer_data_[14 + i]);
568 }
569 /*
570 Still Sensitivities: 24~32th bytes
571 */
572 for (size_t i = 0; i < TOTAL_GATES; i++) {
574 }
575 /*
576 None Duration: 33~34th bytes
577 */
578 set_number_value(this->timeout_number_, encode_uint16(this->buffer_data_[33], this->buffer_data_[32]));
579#endif
580 break;
581 }
582 default:
583 break;
584 }
585
586 return true;
587}
588
590 if (readch < 0) {
591 return; // No data available
592 }
593
594 if (this->buffer_pos_ < MAX_LINE_LENGTH - 1) {
595 this->buffer_data_[this->buffer_pos_++] = readch;
596 this->buffer_data_[this->buffer_pos_] = 0;
597 } else {
598 // We should never get here, but just in case...
599 ESP_LOGW(TAG, "Max command length exceeded; ignoring");
600 this->buffer_pos_ = 0;
601 return;
602 }
603 if (this->buffer_pos_ < HEADER_FOOTER_SIZE) {
604 return; // Not enough data to process yet
605 }
606 if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
607 ESP_LOGV_HEX(TAG, this->buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH, "Handling Periodic Data: %s");
608 this->handle_periodic_data_();
609 this->buffer_pos_ = 0; // Reset position index for next message
610 } else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
611 ESP_LOGV_HEX(TAG, this->buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH, "Handling Ack Data: %s");
612 if (this->handle_ack_data_()) {
613 this->buffer_pos_ = 0; // Reset position index for next message
614 } else {
615 ESP_LOGV(TAG, "Ack Data incomplete");
616 }
617 }
618}
619
621 const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
622 const uint8_t cmd_value[2] = {0x01, 0x00};
623 this->send_command_(cmd, enable ? cmd_value : nullptr, sizeof(cmd_value));
624}
625
626void LD2410Component::set_bluetooth(bool enable) {
627 this->set_config_mode_(true);
628 const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00};
629 this->send_command_(CMD_BLUETOOTH, cmd_value, sizeof(cmd_value));
630 this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
631}
632
633void LD2410Component::set_distance_resolution(const char *state) {
634 this->set_config_mode_(true);
635 const uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00};
636 this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, sizeof(cmd_value));
637 this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
638}
639
640void LD2410Component::set_baud_rate(const char *state) {
641 this->set_config_mode_(true);
642 const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00};
643 this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value));
644 this->set_timeout(200, [this]() { this->restart_(); });
645}
646
647void LD2410Component::set_bluetooth_password(const std::string &password) {
648 if (password.length() != 6) {
649 ESP_LOGE(TAG, "Password must be exactly 6 chars");
650 return;
651 }
652 this->set_config_mode_(true);
653 uint8_t cmd_value[6];
654 std::copy(password.begin(), password.end(), std::begin(cmd_value));
655 this->send_command_(CMD_BT_PASSWORD, cmd_value, sizeof(cmd_value));
656 this->set_config_mode_(false);
657}
658
659void LD2410Component::set_engineering_mode(bool enable) {
660 const uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
661 this->set_config_mode_(true);
662 this->send_command_(cmd, nullptr, 0);
663 this->set_config_mode_(false);
664}
665
666void LD2410Component::factory_reset() {
667 this->set_config_mode_(true);
668 this->send_command_(CMD_RESET, nullptr, 0);
669 this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
670}
671
672void LD2410Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); }
673
674void LD2410Component::query_parameters_() { this->send_command_(CMD_QUERY, nullptr, 0); }
675
676void LD2410Component::get_version_() { this->send_command_(CMD_QUERY_VERSION, nullptr, 0); }
677
679 const uint8_t cmd_value[2] = {0x01, 0x00};
680 this->send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value, sizeof(cmd_value));
681}
682
683void LD2410Component::get_distance_resolution_() { this->send_command_(CMD_QUERY_DISTANCE_RESOLUTION, nullptr, 0); }
684
685void LD2410Component::query_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); }
686
687#ifdef USE_NUMBER
688void LD2410Component::set_max_distances_timeout() {
689 if (!this->max_move_distance_gate_number_->has_state() || !this->max_still_distance_gate_number_->has_state() ||
690 !this->timeout_number_->has_state()) {
691 return;
692 }
693 int max_moving_distance_gate_range = static_cast<int>(this->max_move_distance_gate_number_->state);
694 int max_still_distance_gate_range = static_cast<int>(this->max_still_distance_gate_number_->state);
695 int timeout = static_cast<int>(this->timeout_number_->state);
696 uint8_t value[18] = {0x00,
697 0x00,
698 lowbyte(max_moving_distance_gate_range),
699 highbyte(max_moving_distance_gate_range),
700 0x00,
701 0x00,
702 0x01,
703 0x00,
704 lowbyte(max_still_distance_gate_range),
705 highbyte(max_still_distance_gate_range),
706 0x00,
707 0x00,
708 0x02,
709 0x00,
710 lowbyte(timeout),
711 highbyte(timeout),
712 0x00,
713 0x00};
714 this->set_config_mode_(true);
715 this->send_command_(CMD_MAXDIST_DURATION, value, sizeof(value));
716 this->query_parameters_();
717 this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
718 this->set_config_mode_(false);
719}
720
721void LD2410Component::set_gate_threshold(uint8_t gate) {
722 number::Number *motionsens = this->gate_move_threshold_numbers_[gate];
723 number::Number *stillsens = this->gate_still_threshold_numbers_[gate];
724
725 if (!motionsens->has_state() || !stillsens->has_state()) {
726 return;
727 }
728 int motion = static_cast<int>(motionsens->state);
729 int still = static_cast<int>(stillsens->state);
730
731 this->set_config_mode_(true);
732 // reference
733 // https://drive.google.com/drive/folders/1p4dhbEJA3YubyIjIIC7wwVsSo8x29Fq-?spm=a2g0o.detail.1000023.17.93465697yFwVxH
734 // Send data: configure the motion sensitivity of distance gate 3 to 40, and the static sensitivity of 40
735 // 00 00 (gate)
736 // 03 00 00 00 (gate number)
737 // 01 00 (motion sensitivity)
738 // 28 00 00 00 (value)
739 // 02 00 (still sensitivtiy)
740 // 28 00 00 00 (value)
741 uint8_t value[18] = {0x00, 0x00, lowbyte(gate), highbyte(gate), 0x00, 0x00,
742 0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00,
743 0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00};
744 this->send_command_(CMD_GATE_SENS, value, sizeof(value));
745 this->query_parameters_();
746 this->set_config_mode_(false);
747}
748
749void LD2410Component::set_gate_still_threshold_number(uint8_t gate, number::Number *n) {
750 this->gate_still_threshold_numbers_[gate] = n;
751}
752
753void LD2410Component::set_gate_move_threshold_number(uint8_t gate, number::Number *n) {
754 this->gate_move_threshold_numbers_[gate] = n;
755}
756#endif
757
758void LD2410Component::set_light_out_control() {
759#ifdef USE_NUMBER
760 if (this->light_threshold_number_ != nullptr && this->light_threshold_number_->has_state()) {
761 this->light_threshold_ = static_cast<uint8_t>(this->light_threshold_number_->state);
762 }
763#endif
764#ifdef USE_SELECT
765 if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) {
766 this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option().c_str());
767 }
768 if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) {
769 this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option().c_str());
770 }
771#endif
772 this->set_config_mode_(true);
773 uint8_t value[4] = {this->light_function_, this->light_threshold_, this->out_pin_level_, 0x00};
774 this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value));
775 this->query_light_control_();
776 this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
777 this->set_config_mode_(false);
778}
779
780#ifdef USE_SENSOR
781void LD2410Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) {
782 this->gate_move_sensors_[gate].set_sensor(s);
783}
784
785void LD2410Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) {
786 this->gate_still_sensors_[gate].set_sensor(s);
787}
788#endif
789
790} // namespace esphome::ld2410
virtual void setup()
Where the component's initialization should happen.
Definition component.cpp:84
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
bool has_state() const
std::array< number::Number *, TOTAL_GATES > gate_still_threshold_numbers_
Definition ld2410.h:128
void set_config_mode_(bool enable)
Definition ld2410.cpp:620
std::array< number::Number *, TOTAL_GATES > gate_move_threshold_numbers_
Definition ld2410.h:127
std::array< SensorWithDedup< uint8_t >, TOTAL_GATES > gate_still_sensors_
Definition ld2410.h:132
uint8_t mac_address_[MAC_ADDRESS_SIZE]
Definition ld2410.h:123
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len)
Definition ld2410.cpp:293
uint8_t buffer_data_[MAX_LINE_LENGTH]
Definition ld2410.h:122
std::array< SensorWithDedup< uint8_t >, TOTAL_GATES > gate_move_sensors_
Definition ld2410.h:131
Base-class for all numbers.
Definition number.h:29
void publish_state(float state)
Definition number.cpp:20
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:43
UARTComponent * parent_
Definition uart.h:134
void write_array(const uint8_t *data, size_t len)
Definition uart.h:30
bool state
Definition fan.h:2
uint32_t buffer
Definition gif.cpp:50
void set_number_value(number::Number *n, float value)
Definition ld2410.cpp:410
constexpr Uint8ToString DISTANCE_RESOLUTIONS_BY_UINT[]
Definition ld2410.cpp:94
constexpr StringToUint8 DISTANCE_RESOLUTIONS_BY_STR[]
Definition ld2410.cpp:89
constexpr StringToUint8 BAUD_RATES_BY_STR[]
Definition ld2410.cpp:83
uint8_t find_uint8(const StringToUint8(&arr)[N], const char *str)
Definition ld2410.cpp:124
constexpr StringToUint8 OUT_PIN_LEVELS_BY_STR[]
Definition ld2410.cpp:111
const char * find_str(const Uint8ToString(&arr)[N], uint8_t value)
Definition ld2410.cpp:132
@ DISTANCE_RESOLUTION_0_2
Definition ld2410.cpp:29
@ DISTANCE_RESOLUTION_0_75
Definition ld2410.cpp:30
ESPHOME_LOG_TAG(TAG, "ld2410")
constexpr uint32_t BAUD_RATES[]
Definition ld2410.cpp:121
constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[]
Definition ld2410.cpp:116
constexpr StringToUint8 LIGHT_FUNCTIONS_BY_STR[]
Definition ld2410.cpp:99
constexpr Uint8ToString LIGHT_FUNCTIONS_BY_UINT[]
Definition ld2410.cpp:105
void format_version_str(const uint8_t *version, std::span< char, 20 > buffer)
Definition ld24xx.h:51
const char * format_mac_str(const uint8_t *mac_address, std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buffer)
Definition ld24xx.h:41
optional< size_t > find_index(const uint32_t(&arr)[N], uint32_t value)
Definition ld24xx.h:28
value[LAYOUT.day_index()]
const void size_t len
Definition hal.h:68
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:498
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1609
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:998
void HOT delay(uint32_t ms)
Definition hal.cpp:85
static void uint32_t