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