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