ESPHome 2025.6.3
Loading...
Searching...
No Matches
ld2420.cpp
Go to the documentation of this file.
1#include "ld2420.h"
3
4/*
5Configure commands - little endian
6
7No command can exceed 64 bytes, otherwise they would need be to be split up into multiple sends.
8
9All send command frames will have:
10 Header = FD FC FB FA, Bytes 0 - 3, uint32_t 0xFAFBFCFD
11 Length, bytes 4 - 5, uint16_t 0x0002, must be at least 2 for the command byte if no addon data.
12 Command bytes 6 - 7, uint16_t
13 Footer = 04 03 02 01 - uint32_t 0x01020304, Always last 4 Bytes.
14Receive
15 Error bytes 8-9 uint16_t, 0 = success, all other positive values = error
16
17Enable config mode:
18Send:
19 UART Tx: FD FC FB FA 04 00 FF 00 02 00 04 03 02 01
20 Command = FF 00 - uint16_t 0x00FF
21 Protocol version = 02 00, can be 1 or 2 - uint16_t 0x0002
22Reply:
23 UART Rx: FD FC FB FA 06 00 FF 01 00 00 02 00 04 03 02 01
24
25Disable config mode:
26Send:
27 UART Tx: FD FC FB FA 02 00 FE 00 04 03 02 01
28 Command = FE 00 - uint16_t 0x00FE
29Receive:
30 UART Rx: FD FC FB FA 04 00 FE 01 00 00 04 03 02 01
31
32Configure system parameters:
33
34UART Tx: FD FC FB FA 08 00 12 00 00 00 64 00 00 00 04 03 02 01 Set system parms
35Command = 12 00 - uint16_t 0x0012, Param
36There are three documented parameters for modes:
37 00 64 = Basic status mode
38 This mode outputs text as presence "ON" or "OFF" and "Range XXXX"
39 where XXXX is a decimal value for distance in cm
40 00 04 = Energy output mode
41 This mode outputs detailed signal energy values for each gate and the target distance.
42 The data format consist of the following.
43 Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF
44 HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF
45 F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5
46 00 00 = debug output mode
47 This mode outputs detailed values consisting of 20 Dopplers, 16 Ranges for a total 20 * 16 * 4 bytes
48 The data format consist of the following.
49 Header HH, Doppler DD, Range RR, Footer FF
50 HH HH HH HH DD DD DD DD .. 20x .. RR RR RR RR .. 16x .. FF FF FF FF
51 AA BF 10 14 00 00 00 00 .. .. .. .. 00 00 00 00 .. .. .. .. FD FC FB FA
52
53Configure gate sensitivity parameters:
54UART Tx: FD FC FB FA 0E 00 07 00 10 00 60 EA 00 00 20 00 60 EA 00 00 04 03 02 01
55Command = 12 00 - uint16_t 0x0007
56Gate 0 high thresh = 10 00 uint16_t 0x0010, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60
57Gate 0 low thresh = 20 00 uint16_t 0x0020, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60
58*/
59
60namespace esphome {
61namespace ld2420 {
62
63static const char *const TAG = "ld2420";
64
66
68 ESP_LOGCONFIG(TAG,
69 "LD2420:\n"
70 " Firmware Version : %7s\n"
71 "LD2420 Number:",
73#ifdef USE_NUMBER
74 LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_);
75 LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_);
76 LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_);
77 LOG_NUMBER(TAG, " Gate Select:", this->gate_select_number_);
78 for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
79 LOG_NUMBER(TAG, " Gate Move Threshold:", this->gate_move_threshold_numbers_[gate]);
80 LOG_NUMBER(TAG, " Gate Still Threshold::", this->gate_still_threshold_numbers_[gate]);
81 }
82#endif
83#ifdef USE_BUTTON
84 LOG_BUTTON(TAG, " Apply Config:", this->apply_config_button_);
85 LOG_BUTTON(TAG, " Revert Edits:", this->revert_config_button_);
86 LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_);
87 LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_);
88#endif
89 ESP_LOGCONFIG(TAG, "LD2420 Select:");
90 LOG_SELECT(TAG, " Operating Mode", this->operating_selector_);
91 if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
92 ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
93 }
94}
95
96uint8_t LD2420Component::calc_checksum(void *data, size_t size) {
97 uint8_t checksum = 0;
98 uint8_t *data_bytes = (uint8_t *) data;
99 for (size_t i = 0; i < size; i++) {
100 checksum ^= data_bytes[i]; // XOR operation
101 }
102 return checksum;
103}
104
105int LD2420Component::get_firmware_int_(const char *version_string) {
106 std::string version_str = version_string;
107 if (version_str[0] == 'v') {
108 version_str = version_str.substr(1);
109 }
110 version_str.erase(remove(version_str.begin(), version_str.end(), '.'), version_str.end());
111 int version_integer = stoi(version_str);
112 return version_integer;
113}
114
116 ESP_LOGCONFIG(TAG, "Running setup");
117 if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
118 ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
119 this->mark_failed();
120 return;
121 }
123#ifdef USE_NUMBER
125#endif
126 this->get_firmware_version_();
127 const char *pfw = this->ld2420_firmware_ver_;
128 std::string fw_str(pfw);
129
130 for (auto &listener : listeners_) {
131 listener->on_fw_version(fw_str);
132 }
133
134 for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
136 this->get_gate_threshold_(gate);
137 }
138
139 memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
140 if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
141 this->set_operating_mode(OP_SIMPLE_MODE_STRING);
142 this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
143 this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
144 ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
145 } else {
146 this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
147 this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
148 }
149#ifdef USE_NUMBER
151#endif
152 this->set_system_mode(this->system_mode_);
153 this->set_config_mode(false);
154 ESP_LOGCONFIG(TAG, "LD2420 setup complete.");
155}
156
158 const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
159 if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
160 ESP_LOGCONFIG(TAG, "No configuration change detected");
161 return;
162 }
163 ESP_LOGCONFIG(TAG, "Reconfiguring LD2420");
164 if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
165 ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
166 this->mark_failed();
167 return;
168 }
169 this->set_min_max_distances_timeout(this->new_config.max_gate, this->new_config.min_gate, this->new_config.timeout);
170 for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
172 this->set_gate_threshold(gate);
173 }
174 memcpy(&current_config, &new_config, sizeof(new_config));
175#ifdef USE_NUMBER
177#endif
178 this->set_system_mode(this->system_mode_);
179 this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
180 this->set_operating_mode(OP_NORMAL_MODE_STRING);
181 ESP_LOGCONFIG(TAG, "LD2420 reconfig complete.");
182}
183
185 ESP_LOGCONFIG(TAG, "Setting factory defaults");
186 if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
187 ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
188 this->mark_failed();
189 return;
190 }
191 this->set_min_max_distances_timeout(FACTORY_MAX_GATE, FACTORY_MIN_GATE, FACTORY_TIMEOUT);
192#ifdef USE_NUMBER
193 this->gate_timeout_number_->state = FACTORY_TIMEOUT;
194 this->min_gate_distance_number_->state = FACTORY_MIN_GATE;
195 this->max_gate_distance_number_->state = FACTORY_MAX_GATE;
196#endif
197 for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
198 this->new_config.move_thresh[gate] = FACTORY_MOVE_THRESH[gate];
199 this->new_config.still_thresh[gate] = FACTORY_STILL_THRESH[gate];
201 this->set_gate_threshold(gate);
202 }
203 memcpy(&this->current_config, &this->new_config, sizeof(this->new_config));
204 this->set_system_mode(this->system_mode_);
205 this->set_config_mode(false);
206#ifdef USE_NUMBER
209#endif
210 ESP_LOGCONFIG(TAG, "LD2420 factory reset complete.");
211}
212
214 ESP_LOGCONFIG(TAG, "Restarting LD2420 module");
215 this->send_module_restart();
216 this->set_timeout(250, [this]() {
217 this->set_config_mode(true);
219 this->set_config_mode(false);
220 });
221 ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
222}
223
225 memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
226#ifdef USE_NUMBER
228#endif
229 ESP_LOGCONFIG(TAG, "Reverted config number edits.");
230}
231
233 // If there is a active send command do not process it here, the send command call will handle it.
234 if (!get_cmd_active_()) {
235 if (!available())
236 return;
237 static uint8_t buffer[2048];
238 static uint8_t rx_data;
239 while (available()) {
240 rx_data = read();
241 this->readline_(rx_data, buffer, sizeof(buffer));
242 }
243 }
244}
245
246void LD2420Component::update_radar_data(uint16_t const *gate_energy, uint8_t sample_number) {
247 for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
248 this->radar_data[gate][sample_number] = gate_energy[gate];
249 }
251}
252
254 // Calculate average and peak values for each gate
255 const float move_factor = gate_move_sensitivity_factor + 1;
256 const float still_factor = (gate_still_sensitivity_factor / 2) + 1;
257 for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
258 uint32_t sum = 0;
259 uint16_t peak = 0;
260
261 for (uint8_t sample_number = 0; sample_number < CALIBRATE_SAMPLES; ++sample_number) {
262 // Calculate average
263 sum += this->radar_data[gate][sample_number];
264
265 // Calculate max value
266 if (this->radar_data[gate][sample_number] > peak) {
267 peak = this->radar_data[gate][sample_number];
268 }
269 }
270
271 // Store average and peak values
272 this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
273 if (this->gate_peak[gate] < peak)
274 this->gate_peak[gate] = peak;
275
276 uint32_t calculated_value =
277 (static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
278 this->new_config.move_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
279 calculated_value =
280 (static_cast<uint32_t>(this->gate_peak[gate]) + (still_factor * static_cast<uint32_t>(this->gate_peak[gate])));
281 this->new_config.still_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
282 }
283}
284
286 for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
287 // Output results
288 ESP_LOGI(TAG, "Gate: %2d Avg: %5d Peak: %5d", gate, this->gate_avg[gate], this->gate_peak[gate]);
289 }
290 ESP_LOGI(TAG, "Total samples: %d", this->total_sample_number_counter);
291}
292
294 // If unsupported firmware ignore mode select
295 if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
296 this->current_operating_mode = OP_MODE_TO_UINT.at(state);
297 // Entering Auto Calibrate we need to clear the privoiuos data collection
300 this->set_calibration_(true);
301 for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
302 this->gate_avg[gate] = 0;
303 this->gate_peak[gate] = 0;
304 for (uint8_t i = 0; i < CALIBRATE_SAMPLES; i++) {
305 this->radar_data[gate][i] = 0;
306 }
308 }
309 } else {
310 // Set the current data back so we don't have new data that can be applied in error.
311 if (this->get_calibration_())
312 memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
313 this->set_calibration_(false);
314 }
315 } else {
317 this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
318 }
319}
320
321void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
322 static int pos = 0;
323
324 if (rx_data >= 0) {
325 if (pos < len - 1) {
326 buffer[pos++] = rx_data;
327 buffer[pos] = 0;
328 } else {
329 pos = 0;
330 }
331 if (pos >= 4) {
332 if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
333 this->set_cmd_active_(false); // Set command state to inactive after responce.
334 this->handle_ack_data_(buffer, pos);
335 pos = 0;
336 } else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) && (get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
337 this->handle_simple_mode_(buffer, pos);
338 pos = 0;
339 } else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
340 (get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
341 this->handle_energy_mode_(buffer, pos);
342 pos = 0;
343 }
344 }
345 }
346}
347
348void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
349 uint8_t index = 6; // Start at presence byte position
350 uint16_t range;
351 const uint8_t elements = sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]);
352 this->set_presence_(buffer[index]);
353 index++;
354 memcpy(&range, &buffer[index], sizeof(range));
355 index += sizeof(range);
356 this->set_distance_(range);
357 for (uint8_t i = 0; i < elements; i++) { // NOLINT
358 memcpy(&this->gate_energy_[i], &buffer[index], sizeof(this->gate_energy_[0]));
359 index += sizeof(this->gate_energy_[0]);
360 }
361
364 this->sample_number_counter > CALIBRATE_SAMPLES ? this->sample_number_counter = 0 : this->sample_number_counter++;
365 }
366
367 // Resonable refresh rate for home assistant database size health
368 const int32_t current_millis = millis();
369 if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
370 return;
371 this->last_periodic_millis = current_millis;
372 for (auto &listener : this->listeners_) {
373 listener->on_distance(get_distance_());
374 listener->on_presence(get_presence_());
375 listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
376 }
377
380 if (current_millis - this->report_periodic_millis > REFRESH_RATE_MS * CALIBRATE_REPORT_INTERVAL) {
381 this->report_periodic_millis = current_millis;
382 this->report_gate_data();
383 }
384 }
385}
386
387void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
388 const uint8_t bufsize = 16;
389 uint8_t index{0};
390 uint8_t pos{0};
391 char *endptr{nullptr};
392 char outbuf[bufsize]{0};
393 while (true) {
394 if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
395 set_presence_(false);
396 } else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
397 set_presence_(true);
398 }
399 if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
400 if (index < bufsize - 1) {
401 outbuf[index++] = inbuf[pos];
402 pos++;
403 }
404 } else {
405 if (pos < len - 1) {
406 pos++;
407 } else {
408 break;
409 }
410 }
411 }
412 outbuf[index] = '\0';
413 if (index > 1)
414 set_distance_(strtol(outbuf, &endptr, 10));
415
416 if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
417 // Resonable refresh rate for home assistant database size health
418 const int32_t current_millis = millis();
419 if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
420 return;
421 this->last_normal_periodic_millis = current_millis;
422 for (auto &listener : this->listeners_)
423 listener->on_distance(get_distance_());
424 for (auto &listener : this->listeners_)
425 listener->on_presence(get_presence_());
426 }
427}
428
429void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
430 this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND];
431 this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH];
432 uint8_t reg_element = 0;
433 uint8_t data_element = 0;
434 uint16_t data_pos = 0;
435 if (this->cmd_reply_.length > CMD_MAX_BYTES) {
436 ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES);
437 return;
438 } else if (this->cmd_reply_.length < 2) {
439 ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes.");
440 return;
441 }
442 memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
443 const char *result = this->cmd_reply_.error ? "failure" : "success";
444 if (this->cmd_reply_.error > 0) {
445 return;
446 };
447 this->cmd_reply_.ack = true;
448 switch ((uint16_t) this->cmd_reply_.command) {
449 case (CMD_ENABLE_CONF):
450 ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
451 break;
452 case (CMD_DISABLE_CONF):
453 ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
454 break;
455 case (CMD_READ_REGISTER):
456 ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result);
457 // TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
458 data_pos = 0x0A;
459 for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
460 ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_REG_DATA_REPLY_SIZE));
461 index += CMD_REG_DATA_REPLY_SIZE) {
462 memcpy(&this->cmd_reply_.data[reg_element], &buffer[data_pos + index], sizeof(CMD_REG_DATA_REPLY_SIZE));
463 byteswap(this->cmd_reply_.data[reg_element]);
464 reg_element++;
465 }
466 break;
467 case (CMD_WRITE_REGISTER):
468 ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
469 break;
470 case (CMD_WRITE_ABD_PARAM):
471 ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
472 break;
473 case (CMD_READ_ABD_PARAM):
474 ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
475 data_pos = CMD_ABD_DATA_REPLY_START;
476 for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
477 ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
478 index += CMD_ABD_DATA_REPLY_SIZE) {
479 memcpy(&this->cmd_reply_.data[data_element], &buffer[data_pos + index],
480 sizeof(this->cmd_reply_.data[data_element]));
481 byteswap(this->cmd_reply_.data[data_element]);
482 data_element++;
483 }
484 break;
485 case (CMD_WRITE_SYS_PARAM):
486 ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
487 break;
488 case (CMD_READ_VERSION):
489 memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]);
490 ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result);
491 break;
492 default:
493 break;
494 }
495}
496
498 uint32_t start_millis = millis();
499 uint8_t error = 0;
500 uint8_t ack_buffer[64];
501 uint8_t cmd_buffer[64];
502 this->cmd_reply_.ack = false;
503 if (frame.command != CMD_RESTART)
504 this->set_cmd_active_(true); // Restart does not reply, thus no ack state required.
505 uint8_t retry = 3;
506 while (retry) {
507 frame.length = 0;
508 uint16_t frame_data_bytes = frame.data_length + 2; // Always add two bytes for the cmd size
509
510 memcpy(&cmd_buffer[frame.length], &frame.header, sizeof(frame.header));
511 frame.length += sizeof(frame.header);
512
513 memcpy(&cmd_buffer[frame.length], &frame_data_bytes, sizeof(frame.data_length));
514 frame.length += sizeof(frame.data_length);
515
516 memcpy(&cmd_buffer[frame.length], &frame.command, sizeof(frame.command));
517 frame.length += sizeof(frame.command);
518
519 for (uint16_t index = 0; index < frame.data_length; index++) {
520 memcpy(&cmd_buffer[frame.length], &frame.data[index], sizeof(frame.data[index]));
521 frame.length += sizeof(frame.data[index]);
522 }
523
524 memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
525 frame.length += sizeof(frame.footer);
526 for (uint16_t index = 0; index < frame.length; index++) {
527 this->write_byte(cmd_buffer[index]);
528 }
529
530 error = 0;
531 if (frame.command == CMD_RESTART) {
532 return 0; // restart does not reply exit now
533 }
534
535 while (!this->cmd_reply_.ack) {
536 while (available()) {
537 this->readline_(read(), ack_buffer, sizeof(ack_buffer));
538 }
540 // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT.
541 if ((millis() - start_millis) > 1000) {
542 start_millis = millis();
543 error = LD2420_ERROR_TIMEOUT;
544 retry--;
545 break;
546 }
547 }
548 if (this->cmd_reply_.ack)
549 retry = 0;
550 if (this->cmd_reply_.error > 0)
551 handle_cmd_error(error);
552 }
553 return error;
554}
555
557 CmdFrameT cmd_frame;
558 cmd_frame.data_length = 0;
559 cmd_frame.header = CMD_FRAME_HEADER;
560 cmd_frame.command = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
561 if (enable) {
562 memcpy(&cmd_frame.data[0], &CMD_PROTOCOL_VER, sizeof(CMD_PROTOCOL_VER));
563 cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
564 }
565 cmd_frame.footer = CMD_FRAME_FOOTER;
566 ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
567 return this->send_cmd_from_array(cmd_frame);
568}
569
570// Sends a restart and set system running mode to normal
572
574 CmdFrameT cmd_frame;
575 cmd_frame.data_length = 0;
576 cmd_frame.header = CMD_FRAME_HEADER;
577 cmd_frame.command = CMD_RESTART;
578 cmd_frame.footer = CMD_FRAME_FOOTER;
579 ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command);
580 this->send_cmd_from_array(cmd_frame);
581}
582
584 CmdFrameT cmd_frame;
585 cmd_frame.data_length = 0;
586 cmd_frame.header = CMD_FRAME_HEADER;
587 cmd_frame.command = CMD_READ_REGISTER;
588 cmd_frame.data[1] = reg;
589 cmd_frame.data_length += 2;
590 cmd_frame.footer = CMD_FRAME_FOOTER;
591 ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
592 this->send_cmd_from_array(cmd_frame);
593}
594
595void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
596 CmdFrameT cmd_frame;
597 cmd_frame.data_length = 0;
598 cmd_frame.header = CMD_FRAME_HEADER;
599 cmd_frame.command = CMD_WRITE_REGISTER;
600 memcpy(&cmd_frame.data[cmd_frame.data_length], &reg, sizeof(CMD_REG_DATA_REPLY_SIZE));
601 cmd_frame.data_length += 2;
602 memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
603 cmd_frame.data_length += 2;
604 cmd_frame.footer = CMD_FRAME_FOOTER;
605 ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
606 this->send_cmd_from_array(cmd_frame);
607}
608
609void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
610
612 uint8_t error;
613 CmdFrameT cmd_frame;
614 cmd_frame.data_length = 0;
615 cmd_frame.header = CMD_FRAME_HEADER;
616 cmd_frame.command = CMD_READ_ABD_PARAM;
617 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_MOVE_THRESH[gate], sizeof(CMD_GATE_MOVE_THRESH[gate]));
618 cmd_frame.data_length += 2;
619 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
620 cmd_frame.data_length += 2;
621 cmd_frame.footer = CMD_FRAME_FOOTER;
622 ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command);
623 error = this->send_cmd_from_array(cmd_frame);
624 if (error == 0) {
625 this->current_config.move_thresh[gate] = cmd_reply_.data[0];
627 }
628 return error;
629}
630
632 uint8_t error;
633 CmdFrameT cmd_frame;
634 cmd_frame.data_length = 0;
635 cmd_frame.header = CMD_FRAME_HEADER;
636 cmd_frame.command = CMD_READ_ABD_PARAM;
637 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
638 sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
639 cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
640 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
641 sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
642 cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
643 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
644 sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
645 cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
646 cmd_frame.footer = CMD_FRAME_FOOTER;
647 ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
648 error = this->send_cmd_from_array(cmd_frame);
649 if (error == 0) {
650 this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
651 this->current_config.max_gate = (uint16_t) cmd_reply_.data[1];
652 this->current_config.timeout = (uint16_t) cmd_reply_.data[2];
653 }
654 return error;
655}
656
658 CmdFrameT cmd_frame;
659 uint16_t unknown_parm = 0x0000;
660 cmd_frame.data_length = 0;
661 cmd_frame.header = CMD_FRAME_HEADER;
662 cmd_frame.command = CMD_WRITE_SYS_PARAM;
663 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_SYSTEM_MODE, sizeof(CMD_SYSTEM_MODE));
664 cmd_frame.data_length += sizeof(CMD_SYSTEM_MODE);
665 memcpy(&cmd_frame.data[cmd_frame.data_length], &mode, sizeof(mode));
666 cmd_frame.data_length += sizeof(mode);
667 memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
668 cmd_frame.data_length += sizeof(unknown_parm);
669 cmd_frame.footer = CMD_FRAME_FOOTER;
670 ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command);
671 if (this->send_cmd_from_array(cmd_frame) == 0)
673}
674
676 CmdFrameT cmd_frame;
677 cmd_frame.data_length = 0;
678 cmd_frame.header = CMD_FRAME_HEADER;
679 cmd_frame.command = CMD_READ_VERSION;
680 cmd_frame.footer = CMD_FRAME_FOOTER;
681
682 ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
683 this->send_cmd_from_array(cmd_frame);
684}
685
686void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, // NOLINT
687 uint32_t timeout) {
688 // Header H, Length L, Register R, Value V, Footer F
689 // |Min Gate |Max Gate |Timeout |
690 // HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
691 // FD FC FB FA 14 00 07 00 00 00 01 00 00 00 01 00 09 00 00 00 04 00 0A 00 00 00 04 03 02 01 e.g.
692
693 CmdFrameT cmd_frame;
694 cmd_frame.data_length = 0;
695 cmd_frame.header = CMD_FRAME_HEADER;
696 cmd_frame.command = CMD_WRITE_ABD_PARAM;
697 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
698 sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
699 cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
700 memcpy(&cmd_frame.data[cmd_frame.data_length], &min_gate_distance, sizeof(min_gate_distance));
701 cmd_frame.data_length += sizeof(min_gate_distance);
702 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
703 sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
704 cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
705 memcpy(&cmd_frame.data[cmd_frame.data_length], &max_gate_distance, sizeof(max_gate_distance));
706 cmd_frame.data_length += sizeof(max_gate_distance);
707 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
708 sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
709 cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
710 memcpy(&cmd_frame.data[cmd_frame.data_length], &timeout, sizeof(timeout));
711 ;
712 cmd_frame.data_length += sizeof(timeout);
713 cmd_frame.footer = CMD_FRAME_FOOTER;
714
715 ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
716 this->send_cmd_from_array(cmd_frame);
717}
718
720 // Header H, Length L, Command C, Register R, Value V, Footer F
721 // HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
722 // FD FC FB FA 14 00 07 00 10 00 00 FF 00 00 00 01 00 0F 00 00 04 03 02 01
723
724 uint16_t move_threshold_gate = CMD_GATE_MOVE_THRESH[gate];
725 uint16_t still_threshold_gate = CMD_GATE_STILL_THRESH[gate];
726 CmdFrameT cmd_frame;
727 cmd_frame.data_length = 0;
728 cmd_frame.header = CMD_FRAME_HEADER;
729 cmd_frame.command = CMD_WRITE_ABD_PARAM;
730 memcpy(&cmd_frame.data[cmd_frame.data_length], &move_threshold_gate, sizeof(move_threshold_gate));
731 cmd_frame.data_length += sizeof(move_threshold_gate);
732 memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.move_thresh[gate],
733 sizeof(this->new_config.move_thresh[gate]));
734 cmd_frame.data_length += sizeof(this->new_config.move_thresh[gate]);
735 memcpy(&cmd_frame.data[cmd_frame.data_length], &still_threshold_gate, sizeof(still_threshold_gate));
736 cmd_frame.data_length += sizeof(still_threshold_gate);
737 memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.still_thresh[gate],
738 sizeof(this->new_config.still_thresh[gate]));
739 cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
740 cmd_frame.footer = CMD_FRAME_FOOTER;
741 ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
742 this->send_cmd_from_array(cmd_frame);
743}
744
745#ifdef USE_NUMBER
747 if (this->gate_timeout_number_ != nullptr)
748 this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
749 if (this->gate_select_number_ != nullptr)
751 if (this->min_gate_distance_number_ != nullptr)
752 this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
753 if (this->max_gate_distance_number_ != nullptr)
754 this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
755 if (this->gate_move_sensitivity_factor_number_ != nullptr)
757 if (this->gate_still_sensitivity_factor_number_ != nullptr)
759 for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
760 if (this->gate_still_threshold_numbers_[gate] != nullptr) {
761 this->gate_still_threshold_numbers_[gate]->publish_state(
762 static_cast<uint16_t>(this->current_config.still_thresh[gate]));
763 }
764 if (this->gate_move_threshold_numbers_[gate] != nullptr) {
765 this->gate_move_threshold_numbers_[gate]->publish_state(
766 static_cast<uint16_t>(this->current_config.move_thresh[gate]));
767 }
768 }
769}
770
776
777#endif
778
779} // namespace ld2420
780} // namespace esphome
BedjetMode mode
BedJet operating mode.
uint8_t checksum
Definition bl0906.h:3
virtual void mark_failed()
Mark this component as failed.
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:75
int get_firmware_int_(const char *version_string)
Definition ld2420.cpp:105
void readline_(int rx_data, uint8_t *buffer, int len)
Definition ld2420.cpp:321
void set_system_mode(uint16_t mode)
Definition ld2420.cpp:657
void handle_ack_data_(uint8_t *buffer, int len)
Definition ld2420.cpp:429
std::vector< number::Number * > gate_still_threshold_numbers_
Definition ld2420.h:252
number::Number * gate_select_number_
Definition ld2420.h:247
uint8_t set_config_mode(bool enable)
Definition ld2420.cpp:556
button::Button * factory_reset_button_
Definition ld2420.h:206
void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number)
Definition ld2420.cpp:246
float get_setup_priority() const override
Definition ld2420.cpp:65
void set_distance_(uint16_t distance)
Definition ld2420.h:235
void set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, uint32_t timeout)
Definition ld2420.cpp:686
uint8_t calc_checksum(void *data, size_t size)
Definition ld2420.cpp:96
void set_gate_threshold(uint8_t gate)
Definition ld2420.cpp:719
void handle_energy_mode_(uint8_t *buffer, int len)
Definition ld2420.cpp:348
uint16_t gate_avg[LD2420_TOTAL_GATES]
Definition ld2420.h:193
button::Button * revert_config_button_
Definition ld2420.h:204
void set_calibration_(bool state)
Definition ld2420.h:242
number::Number * min_gate_distance_number_
Definition ld2420.h:248
button::Button * apply_config_button_
Definition ld2420.h:203
uint16_t radar_data[LD2420_TOTAL_GATES][CALIBRATE_SAMPLES]
Definition ld2420.h:192
std::vector< LD2420Listener * > listeners_
Definition ld2420.h:267
std::vector< number::Number * > gate_move_threshold_numbers_
Definition ld2420.h:253
void set_mode_(uint16_t mode)
Definition ld2420.h:231
uint16_t gate_energy_[LD2420_TOTAL_GATES]
Definition ld2420.h:256
int get_gate_threshold_(uint8_t gate)
Definition ld2420.cpp:611
number::Number * gate_still_sensitivity_factor_number_
Definition ld2420.h:251
void handle_cmd_error(uint8_t error)
Definition ld2420.cpp:609
int send_cmd_from_array(CmdFrameT cmd_frame)
Definition ld2420.cpp:497
void get_reg_value_(uint16_t reg)
Definition ld2420.cpp:583
uint16_t gate_peak[LD2420_TOTAL_GATES]
Definition ld2420.h:194
void handle_simple_mode_(const uint8_t *inbuf, int len)
Definition ld2420.cpp:387
number::Number * max_gate_distance_number_
Definition ld2420.h:249
select::Select * operating_selector_
Definition ld2420.h:200
button::Button * restart_module_button_
Definition ld2420.h:205
void set_operating_mode(const std::string &state)
Definition ld2420.cpp:293
number::Number * gate_timeout_number_
Definition ld2420.h:246
void set_presence_(bool presence)
Definition ld2420.h:233
number::Number * gate_move_sensitivity_factor_number_
Definition ld2420.h:250
void set_reg_value(uint16_t reg, uint16_t value)
Definition ld2420.cpp:595
void set_cmd_active_(bool active)
Definition ld2420.h:237
void publish_state(float state)
Definition number.cpp:9
void publish_state(const std::string &state)
Definition select.cpp:9
void write_byte(uint8_t data)
Definition uart.h:19
bool state
Definition fan.h:0
Range range
Definition msa3xx.h:0
@ OP_CALIBRATE_MODE
Definition ld2420.h:97
const float BUS
For communication buses like i2c/spi.
Definition component.cpp:17
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:302
void IRAM_ATTR HOT delay_microseconds_safe(uint32_t us)
Delay for the given amount of microseconds, possibly yielding to other processes during the wait.
Definition helpers.cpp:773
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
uint32_t still_thresh[LD2420_TOTAL_GATES]
Definition ld2420.h:167
uint32_t move_thresh[LD2420_TOTAL_GATES]
Definition ld2420.h:166
void byteswap()