ESPHome 2025.12.1
Loading...
Searching...
No Matches
ld2420.cpp
Go to the documentation of this file.
1#include "ld2420.h"
4
5/*
6Configure commands - little endian
7
8No command can exceed 64 bytes, otherwise they would need be to be split up into multiple sends.
9
10All send command frames will have:
11 Header = FD FC FB FA, Bytes 0 - 3, uint32_t 0xFAFBFCFD
12 Length, bytes 4 - 5, uint16_t 0x0002, must be at least 2 for the command byte if no addon data.
13 Command bytes 6 - 7, uint16_t
14 Footer = 04 03 02 01 - uint32_t 0x01020304, Always last 4 Bytes.
15Receive
16 Error bytes 8-9 uint16_t, 0 = success, all other positive values = error
17
18Enable config mode:
19Send:
20 UART Tx: FD FC FB FA 04 00 FF 00 02 00 04 03 02 01
21 Command = FF 00 - uint16_t 0x00FF
22 Protocol version = 02 00, can be 1 or 2 - uint16_t 0x0002
23Reply:
24 UART Rx: FD FC FB FA 06 00 FF 01 00 00 02 00 04 03 02 01
25
26Disable config mode:
27Send:
28 UART Tx: FD FC FB FA 02 00 FE 00 04 03 02 01
29 Command = FE 00 - uint16_t 0x00FE
30Receive:
31 UART Rx: FD FC FB FA 04 00 FE 01 00 00 04 03 02 01
32
33Configure system parameters:
34
35UART Tx: FD FC FB FA 08 00 12 00 00 00 64 00 00 00 04 03 02 01 Set system parms
36Command = 12 00 - uint16_t 0x0012, Param
37There are three documented parameters for modes:
38 00 64 = Basic status mode
39 This mode outputs text as presence "ON" or "OFF" and "Range XXXX"
40 where XXXX is a decimal value for distance in cm
41 00 04 = Energy output mode
42 This mode outputs detailed signal energy values for each gate and the target distance.
43 The data format consist of the following.
44 Header HH, Length LL, Presence PP, Distance DD, 16 Gate Energies EE, Footer FF
45 HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF
46 F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5
47 00 00 = debug output mode
48 This mode outputs detailed values consisting of 20 Dopplers, 16 Ranges for a total 20 * 16 * 4 bytes
49 The data format consist of the following.
50 Header HH, Doppler DD, Range RR, Footer FF
51 HH HH HH HH DD DD DD DD .. 20x .. RR RR RR RR .. 16x .. FF FF FF FF
52 AA BF 10 14 00 00 00 00 .. .. .. .. 00 00 00 00 .. .. .. .. FD FC FB FA
53
54Configure gate sensitivity parameters:
55UART 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
56Command = 12 00 - uint16_t 0x0007
57Gate 0 high thresh = 10 00 uint16_t 0x0010, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60
58Gate 0 low thresh = 20 00 uint16_t 0x0020, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60
59*/
60
61namespace esphome::ld2420 {
62
63static const char *const TAG = "ld2420";
64
65// Local const's
66static const uint16_t REFRESH_RATE_MS = 1000;
67
68// Command sets
69static const uint16_t CMD_DISABLE_CONF = 0x00FE;
70static const uint16_t CMD_ENABLE_CONF = 0x00FF;
71static const uint16_t CMD_PARM_HIGH_TRESH = 0x0012;
72static const uint16_t CMD_PARM_LOW_TRESH = 0x0021;
73static const uint16_t CMD_PROTOCOL_VER = 0x0002;
74static const uint16_t CMD_READ_ABD_PARAM = 0x0008;
75static const uint16_t CMD_READ_REG_ADDR = 0x0020;
76static const uint16_t CMD_READ_REGISTER = 0x0002;
77static const uint16_t CMD_READ_SERIAL_NUM = 0x0011;
78static const uint16_t CMD_READ_SYS_PARAM = 0x0013;
79static const uint16_t CMD_READ_VERSION = 0x0000;
80static const uint16_t CMD_RESTART = 0x0068;
81static const uint16_t CMD_SYSTEM_MODE = 0x0000;
82static const uint16_t CMD_SYSTEM_MODE_GR = 0x0003;
83static const uint16_t CMD_SYSTEM_MODE_MTT = 0x0001;
84static const uint16_t CMD_SYSTEM_MODE_SIMPLE = 0x0064;
85static const uint16_t CMD_SYSTEM_MODE_DEBUG = 0x0000;
86static const uint16_t CMD_SYSTEM_MODE_ENERGY = 0x0004;
87static const uint16_t CMD_SYSTEM_MODE_VS = 0x0002;
88static const uint16_t CMD_WRITE_ABD_PARAM = 0x0007;
89static const uint16_t CMD_WRITE_REGISTER = 0x0001;
90static const uint16_t CMD_WRITE_SYS_PARAM = 0x0012;
91
92static const uint8_t CMD_ABD_DATA_REPLY_SIZE = 0x04;
93static const uint8_t CMD_ABD_DATA_REPLY_START = 0x0A;
94static const uint8_t CMD_MAX_BYTES = 0x64;
95static const uint8_t CMD_REG_DATA_REPLY_SIZE = 0x02;
96
97static const uint8_t LD2420_ERROR_NONE = 0x00;
98static const uint8_t LD2420_ERROR_TIMEOUT = 0x02;
99static const uint8_t LD2420_ERROR_UNKNOWN = 0x01;
100
101// Register address values
102static const uint16_t CMD_MIN_GATE_REG = 0x0000;
103static const uint16_t CMD_MAX_GATE_REG = 0x0001;
104static const uint16_t CMD_TIMEOUT_REG = 0x0004;
105static const uint16_t CMD_GATE_MOVE_THRESH[TOTAL_GATES] = {0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015,
106 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B,
107 0x001C, 0x001D, 0x001E, 0x001F};
108static const uint16_t CMD_GATE_STILL_THRESH[TOTAL_GATES] = {0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025,
109 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B,
110 0x002C, 0x002D, 0x002E, 0x002F};
111static const uint32_t FACTORY_MOVE_THRESH[TOTAL_GATES] = {60000, 30000, 400, 250, 250, 250, 250, 250,
112 250, 250, 250, 250, 250, 250, 250, 250};
113static const uint32_t FACTORY_STILL_THRESH[TOTAL_GATES] = {40000, 20000, 200, 200, 200, 200, 200, 150,
114 150, 100, 100, 100, 100, 100, 100, 100};
115static const uint16_t FACTORY_TIMEOUT = 120;
116static const uint16_t FACTORY_MIN_GATE = 1;
117static const uint16_t FACTORY_MAX_GATE = 12;
118
119// COMMAND_BYTE Header & Footer
120static const uint32_t CMD_FRAME_FOOTER = 0x01020304;
121static const uint32_t CMD_FRAME_HEADER = 0xFAFBFCFD;
122static const uint32_t DEBUG_FRAME_FOOTER = 0xFAFBFCFD;
123static const uint32_t DEBUG_FRAME_HEADER = 0x1410BFAA;
124static const uint32_t ENERGY_FRAME_FOOTER = 0xF5F6F7F8;
125static const uint32_t ENERGY_FRAME_HEADER = 0xF1F2F3F4;
126static const int CALIBRATE_VERSION_MIN = 154;
127static const uint8_t CMD_FRAME_COMMAND = 6;
128static const uint8_t CMD_FRAME_DATA_LENGTH = 4;
129static const uint8_t CMD_FRAME_STATUS = 7;
130static const uint8_t CMD_ERROR_WORD = 8;
131static const uint8_t ENERGY_SENSOR_START = 9;
132static const uint8_t CALIBRATE_REPORT_INTERVAL = 4;
133static const char *const OP_NORMAL_MODE_STRING = "Normal";
134static const char *const OP_SIMPLE_MODE_STRING = "Simple";
135
136// Memory-efficient lookup tables
137struct StringToUint8 {
138 const char *str;
139 const uint8_t value;
140};
141
142static constexpr StringToUint8 OP_MODE_BY_STR[] = {
143 {"Normal", OP_NORMAL_MODE},
144 {"Calibrate", OP_CALIBRATE_MODE},
145 {"Simple", OP_SIMPLE_MODE},
146};
147
148static constexpr const char *ERR_MESSAGE[] = {
149 "None",
150 "Unknown",
151 "Timeout",
152};
153
154// Helper function for lookups
155template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
156 for (const auto &entry : arr) {
157 if (str == entry.str) {
158 return entry.value;
159 }
160 }
161 return 0xFF; // Not found
162}
163
164static uint8_t calc_checksum(void *data, size_t size) {
165 uint8_t checksum = 0;
166 uint8_t *data_bytes = (uint8_t *) data;
167 for (size_t i = 0; i < size; i++) {
168 checksum ^= data_bytes[i]; // XOR operation
169 }
170 return checksum;
171}
172
173static int get_firmware_int(const char *version_string) {
174 std::string version_str = version_string;
175 if (version_str[0] == 'v') {
176 version_str.erase(0, 1);
177 }
178 version_str.erase(remove(version_str.begin(), version_str.end(), '.'), version_str.end());
179 int version_integer = stoi(version_str);
180 return version_integer;
181}
182
184
186 ESP_LOGCONFIG(TAG,
187 "LD2420:\n"
188 " Firmware version: %7s",
189 this->firmware_ver_);
190#ifdef USE_NUMBER
191 ESP_LOGCONFIG(TAG, "Number:");
192 LOG_NUMBER(" ", "Gate Timeout:", this->gate_timeout_number_);
193 LOG_NUMBER(" ", "Gate Max Distance:", this->max_gate_distance_number_);
194 LOG_NUMBER(" ", "Gate Min Distance:", this->min_gate_distance_number_);
195 LOG_NUMBER(" ", "Gate Select:", this->gate_select_number_);
196 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
197 LOG_NUMBER(" ", "Gate Move Threshold:", this->gate_move_threshold_numbers_[gate]);
198 LOG_NUMBER(" ", "Gate Still Threshold::", this->gate_still_threshold_numbers_[gate]);
199 }
200#endif
201#ifdef USE_BUTTON
202 LOG_BUTTON(" ", "Apply Config:", this->apply_config_button_);
203 LOG_BUTTON(" ", "Revert Edits:", this->revert_config_button_);
204 LOG_BUTTON(" ", "Factory Reset:", this->factory_reset_button_);
205 LOG_BUTTON(" ", "Restart Module:", this->restart_module_button_);
206#endif
207#ifdef USE_SELECT
208 ESP_LOGCONFIG(TAG, "Select:");
209 LOG_SELECT(" ", "Operating Mode", this->operating_selector_);
210#endif
211 if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) {
212 ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_);
213 }
214}
215
217 if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
218 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
219 this->mark_failed();
220 return;
221 }
223#ifdef USE_NUMBER
225#endif
226 this->get_firmware_version_();
227 const char *pfw = this->firmware_ver_;
228 std::string fw_str(pfw);
229
230 for (auto &listener : this->listeners_) {
231 listener->on_fw_version(fw_str);
232 }
233
234 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
236 this->get_gate_threshold_(gate);
237 }
238
239 memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
240 if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) {
241 this->set_operating_mode(OP_SIMPLE_MODE_STRING);
242#ifdef USE_SELECT
243 if (this->operating_selector_ != nullptr) {
244 this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
245 }
246#endif
247 this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
248 ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_);
249 } else {
250 this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
251#ifdef USE_SELECT
252 if (this->operating_selector_ != nullptr) {
253 this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
254 }
255#endif
256 }
257#ifdef USE_NUMBER
259#endif
260 this->set_system_mode(this->system_mode_);
261 this->set_config_mode(false);
262}
263
265 const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
266 if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
267 ESP_LOGD(TAG, "No configuration change detected");
268 return;
269 }
270 ESP_LOGD(TAG, "Reconfiguring");
271 if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
272 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
273 this->mark_failed();
274 return;
275 }
276 this->set_min_max_distances_timeout(this->new_config.max_gate, this->new_config.min_gate, this->new_config.timeout);
277 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
279 this->set_gate_threshold(gate);
280 }
281 memcpy(&current_config, &new_config, sizeof(new_config));
282#ifdef USE_NUMBER
284#endif
285 this->set_system_mode(this->system_mode_);
286 this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
287 this->set_operating_mode(OP_NORMAL_MODE_STRING);
288}
289
291 ESP_LOGD(TAG, "Setting factory defaults");
292 if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
293 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
294 this->mark_failed();
295 return;
296 }
297 this->set_min_max_distances_timeout(FACTORY_MAX_GATE, FACTORY_MIN_GATE, FACTORY_TIMEOUT);
298#ifdef USE_NUMBER
299 this->gate_timeout_number_->state = FACTORY_TIMEOUT;
300 this->min_gate_distance_number_->state = FACTORY_MIN_GATE;
301 this->max_gate_distance_number_->state = FACTORY_MAX_GATE;
302#endif
303 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
304 this->new_config.move_thresh[gate] = FACTORY_MOVE_THRESH[gate];
305 this->new_config.still_thresh[gate] = FACTORY_STILL_THRESH[gate];
307 this->set_gate_threshold(gate);
308 }
309 memcpy(&this->current_config, &this->new_config, sizeof(this->new_config));
310 this->set_system_mode(this->system_mode_);
311 this->set_config_mode(false);
312#ifdef USE_NUMBER
315#endif
316}
317
319 ESP_LOGD(TAG, "Restarting");
320 this->send_module_restart();
321 this->set_timeout(250, [this]() {
322 this->set_config_mode(true);
323 this->set_system_mode(this->system_mode_);
324 this->set_config_mode(false);
325 });
326}
327
329 memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
330#ifdef USE_NUMBER
332#endif
333 ESP_LOGD(TAG, "Reverted config number edits");
334}
335
337 // If there is a active send command do not process it here, the send command call will handle it.
338 while (!this->cmd_active_ && this->available()) {
339 this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH);
340 }
341}
342
343void LD2420Component::update_radar_data(uint16_t const *gate_energy, uint8_t sample_number) {
344 for (uint8_t gate = 0; gate < TOTAL_GATES; ++gate) {
345 this->radar_data[gate][sample_number] = gate_energy[gate];
346 }
348}
349
351 // Calculate average and peak values for each gate
352 const float move_factor = gate_move_sensitivity_factor + 1;
353 const float still_factor = (gate_still_sensitivity_factor / 2) + 1;
354 for (uint8_t gate = 0; gate < TOTAL_GATES; ++gate) {
355 uint32_t sum = 0;
356 uint16_t peak = 0;
357
358 for (uint8_t sample_number = 0; sample_number < CALIBRATE_SAMPLES; ++sample_number) {
359 // Calculate average
360 sum += this->radar_data[gate][sample_number];
361
362 // Calculate max value
363 if (this->radar_data[gate][sample_number] > peak) {
364 peak = this->radar_data[gate][sample_number];
365 }
366 }
367
368 // Store average and peak values
369 this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
370 if (this->gate_peak[gate] < peak) {
371 this->gate_peak[gate] = peak;
372 }
373
374 uint32_t calculated_value =
375 (static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
376 this->new_config.move_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
377 calculated_value =
378 (static_cast<uint32_t>(this->gate_peak[gate]) + (still_factor * static_cast<uint32_t>(this->gate_peak[gate])));
379 this->new_config.still_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
380 }
381}
382
384 for (uint8_t gate = 0; gate < TOTAL_GATES; ++gate) {
385 // Output results
386 ESP_LOGI(TAG, "Gate: %2d Avg: %5d Peak: %5d", gate, this->gate_avg[gate], this->gate_peak[gate]);
387 }
388 ESP_LOGI(TAG, "Total samples: %d", this->total_sample_number_counter);
389}
390
392 // If unsupported firmware ignore mode select
393 if (ld2420::get_firmware_int(firmware_ver_) >= CALIBRATE_VERSION_MIN) {
394 this->current_operating_mode = find_uint8(OP_MODE_BY_STR, state);
395 // Entering Auto Calibrate we need to clear the previous data collection
396#ifdef USE_SELECT
397 if (this->operating_selector_ != nullptr) {
399 }
400#endif
402 this->set_calibration_(true);
403 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
404 this->gate_avg[gate] = 0;
405 this->gate_peak[gate] = 0;
406 for (uint8_t i = 0; i < CALIBRATE_SAMPLES; i++) {
407 this->radar_data[gate][i] = 0;
408 }
410 }
411 } else {
412 // Set the current data back so we don't have new data that can be applied in error.
413 if (this->get_calibration_()) {
414 memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
415 }
416 this->set_calibration_(false);
417 }
418 } else {
420#ifdef USE_SELECT
421 if (this->operating_selector_ != nullptr) {
422 this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
423 }
424#endif
425 }
426}
427
428void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
429 if (rx_data < 0) {
430 return; // No data available
431 }
432 if (this->buffer_pos_ < len - 1) {
433 buffer[this->buffer_pos_++] = rx_data;
434 buffer[this->buffer_pos_] = 0;
435 } else {
436 // We should never get here, but just in case...
437 ESP_LOGW(TAG, "Max command length exceeded; ignoring");
438 this->buffer_pos_ = 0;
439 }
440 if (this->buffer_pos_ < 4) {
441 return; // Not enough data to process yet
442 }
443 if (memcmp(&buffer[this->buffer_pos_ - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
444 this->cmd_active_ = false; // Set command state to inactive after response
445 this->handle_ack_data_(buffer, this->buffer_pos_);
446 this->buffer_pos_ = 0;
447 } else if ((buffer[this->buffer_pos_ - 2] == 0x0D && buffer[this->buffer_pos_ - 1] == 0x0A) &&
448 (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
449 this->handle_simple_mode_(buffer, this->buffer_pos_);
450 this->buffer_pos_ = 0;
451 } else if ((memcmp(&buffer[this->buffer_pos_ - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
452 (this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
453 this->handle_energy_mode_(buffer, this->buffer_pos_);
454 this->buffer_pos_ = 0;
455 }
456}
457
458void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
459 uint8_t index = 6; // Start at presence byte position
460 uint16_t range;
461 const uint8_t elements = sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]);
462 this->set_presence_(buffer[index]);
463 index++;
464 memcpy(&range, &buffer[index], sizeof(range));
465 index += sizeof(range);
466 this->set_distance_(range);
467 for (uint8_t i = 0; i < elements; i++) { // NOLINT
468 memcpy(&this->gate_energy_[i], &buffer[index], sizeof(this->gate_energy_[0]));
469 index += sizeof(this->gate_energy_[0]);
470 }
471
474 this->sample_number_counter > CALIBRATE_SAMPLES ? this->sample_number_counter = 0 : this->sample_number_counter++;
475 }
476
477 // Resonable refresh rate for home assistant database size health
478 const int32_t current_millis = App.get_loop_component_start_time();
479 if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) {
480 return;
481 }
482 this->last_periodic_millis = current_millis;
483 for (auto &listener : this->listeners_) {
484 listener->on_distance(this->get_distance_());
485 listener->on_presence(this->get_presence_());
486 listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
487 }
488
491 if (current_millis - this->report_periodic_millis > REFRESH_RATE_MS * CALIBRATE_REPORT_INTERVAL) {
492 this->report_periodic_millis = current_millis;
493 this->report_gate_data();
494 }
495 }
496}
497
498void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
499 const uint8_t bufsize = 16;
500 uint8_t index{0};
501 uint8_t pos{0};
502 char *endptr{nullptr};
503 char outbuf[bufsize]{0};
504 while (true) {
505 if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
506 this->set_presence_(false);
507 } else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
508 this->set_presence_(true);
509 }
510 if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
511 if (index < bufsize - 1) {
512 outbuf[index++] = inbuf[pos];
513 pos++;
514 }
515 } else {
516 if (pos < len - 1) {
517 pos++;
518 } else {
519 break;
520 }
521 }
522 }
523 outbuf[index] = '\0';
524 if (index > 1) {
525 this->set_distance_(strtol(outbuf, &endptr, 10));
526 }
527
528 if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
529 // Resonable refresh rate for home assistant database size health
530 const int32_t current_millis = App.get_loop_component_start_time();
531 if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) {
532 return;
533 }
534 this->last_normal_periodic_millis = current_millis;
535 for (auto &listener : this->listeners_)
536 listener->on_distance(this->get_distance_());
537 for (auto &listener : this->listeners_)
538 listener->on_presence(this->get_presence_());
539 }
540}
541
542void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
543 this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND];
544 this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH];
545 uint8_t reg_element = 0;
546 uint8_t data_element = 0;
547 uint16_t data_pos = 0;
548 if (this->cmd_reply_.length > CMD_MAX_BYTES) {
549 ESP_LOGW(TAG, "Reply frame too long");
550 return;
551 } else if (this->cmd_reply_.length < 2) {
552 ESP_LOGW(TAG, "Command frame too short");
553 return;
554 }
555 memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
556 const char *result = this->cmd_reply_.error ? "failure" : "success";
557 if (this->cmd_reply_.error > 0) {
558 return;
559 };
560 this->cmd_reply_.ack = true;
561 switch ((uint16_t) this->cmd_reply_.command) {
562 case (CMD_ENABLE_CONF):
563 ESP_LOGV(TAG, "Set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
564 break;
565 case (CMD_DISABLE_CONF):
566 ESP_LOGV(TAG, "Set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
567 break;
568 case (CMD_READ_REGISTER):
569 ESP_LOGV(TAG, "Read register: CMD = %2X %s", CMD_READ_REGISTER, result);
570 // TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
571 data_pos = 0x0A;
572 for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
573 ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_REG_DATA_REPLY_SIZE));
574 index += CMD_REG_DATA_REPLY_SIZE) {
575 memcpy(&this->cmd_reply_.data[reg_element], &buffer[data_pos + index], sizeof(CMD_REG_DATA_REPLY_SIZE));
576 byteswap(this->cmd_reply_.data[reg_element]);
577 reg_element++;
578 }
579 break;
580 case (CMD_WRITE_REGISTER):
581 ESP_LOGV(TAG, "Write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
582 break;
583 case (CMD_WRITE_ABD_PARAM):
584 ESP_LOGV(TAG, "Write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
585 break;
586 case (CMD_READ_ABD_PARAM):
587 ESP_LOGV(TAG, "Read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
588 data_pos = CMD_ABD_DATA_REPLY_START;
589 for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
590 ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
591 index += CMD_ABD_DATA_REPLY_SIZE) {
592 memcpy(&this->cmd_reply_.data[data_element], &buffer[data_pos + index],
593 sizeof(this->cmd_reply_.data[data_element]));
594 byteswap(this->cmd_reply_.data[data_element]);
595 data_element++;
596 }
597 break;
598 case (CMD_WRITE_SYS_PARAM):
599 ESP_LOGV(TAG, "Set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
600 break;
601 case (CMD_READ_VERSION):
602 memcpy(this->firmware_ver_, &buffer[12], buffer[10]);
603 ESP_LOGV(TAG, "Firmware version: %7s %s", this->firmware_ver_, result);
604 break;
605 default:
606 break;
607 }
608}
609
611 uint32_t start_millis = millis();
612 uint8_t error = 0;
613 uint8_t ack_buffer[MAX_LINE_LENGTH];
614 uint8_t cmd_buffer[MAX_LINE_LENGTH];
615 this->cmd_reply_.ack = false;
616 if (frame.command != CMD_RESTART) {
617 this->cmd_active_ = true;
618 } // Restart does not reply, thus no ack state required
619 uint8_t retry = 3;
620 while (retry) {
621 frame.length = 0;
622 uint16_t frame_data_bytes = frame.data_length + 2; // Always add two bytes for the cmd size
623
624 memcpy(&cmd_buffer[frame.length], &frame.header, sizeof(frame.header));
625 frame.length += sizeof(frame.header);
626
627 memcpy(&cmd_buffer[frame.length], &frame_data_bytes, sizeof(frame.data_length));
628 frame.length += sizeof(frame.data_length);
629
630 memcpy(&cmd_buffer[frame.length], &frame.command, sizeof(frame.command));
631 frame.length += sizeof(frame.command);
632
633 for (uint16_t index = 0; index < frame.data_length; index++) {
634 memcpy(&cmd_buffer[frame.length], &frame.data[index], sizeof(frame.data[index]));
635 frame.length += sizeof(frame.data[index]);
636 }
637
638 memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
639 frame.length += sizeof(frame.footer);
640 this->write_array(cmd_buffer, frame.length);
641
642 error = 0;
643 if (frame.command == CMD_RESTART) {
644 return 0; // restart does not reply exit now
645 }
646
647 while (!this->cmd_reply_.ack) {
648 while (this->available()) {
649 this->readline_(this->read(), ack_buffer, sizeof(ack_buffer));
650 }
652 // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT.
653 if ((millis() - start_millis) > 1000) {
654 start_millis = millis();
655 error = LD2420_ERROR_TIMEOUT;
656 retry--;
657 break;
658 }
659 }
660 if (this->cmd_reply_.ack) {
661 retry = 0;
662 }
663 if (this->cmd_reply_.error > 0) {
664 this->handle_cmd_error(error);
665 }
666 }
667 return error;
668}
669
671 CmdFrameT cmd_frame;
672 cmd_frame.data_length = 0;
673 cmd_frame.header = CMD_FRAME_HEADER;
674 cmd_frame.command = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
675 if (enable) {
676 memcpy(&cmd_frame.data[0], &CMD_PROTOCOL_VER, sizeof(CMD_PROTOCOL_VER));
677 cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
678 }
679 cmd_frame.footer = CMD_FRAME_FOOTER;
680 ESP_LOGV(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
681 return this->send_cmd_from_array(cmd_frame);
682}
683
684// Sends a restart and set system running mode to normal
686
688 CmdFrameT cmd_frame;
689 cmd_frame.data_length = 0;
690 cmd_frame.header = CMD_FRAME_HEADER;
691 cmd_frame.command = CMD_RESTART;
692 cmd_frame.footer = CMD_FRAME_FOOTER;
693 ESP_LOGV(TAG, "Sending restart command: %2X", cmd_frame.command);
694 this->send_cmd_from_array(cmd_frame);
695}
696
698 CmdFrameT cmd_frame;
699 cmd_frame.data_length = 0;
700 cmd_frame.header = CMD_FRAME_HEADER;
701 cmd_frame.command = CMD_READ_REGISTER;
702 cmd_frame.data[1] = reg;
703 cmd_frame.data_length += 2;
704 cmd_frame.footer = CMD_FRAME_FOOTER;
705 ESP_LOGV(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
706 this->send_cmd_from_array(cmd_frame);
707}
708
709void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
710 CmdFrameT cmd_frame;
711 cmd_frame.data_length = 0;
712 cmd_frame.header = CMD_FRAME_HEADER;
713 cmd_frame.command = CMD_WRITE_REGISTER;
714 memcpy(&cmd_frame.data[cmd_frame.data_length], &reg, sizeof(CMD_REG_DATA_REPLY_SIZE));
715 cmd_frame.data_length += 2;
716 memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
717 cmd_frame.data_length += 2;
718 cmd_frame.footer = CMD_FRAME_FOOTER;
719 ESP_LOGV(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
720 this->send_cmd_from_array(cmd_frame);
721}
722
723void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGE(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
724
726 uint8_t error;
727 CmdFrameT cmd_frame;
728 cmd_frame.data_length = 0;
729 cmd_frame.header = CMD_FRAME_HEADER;
730 cmd_frame.command = CMD_READ_ABD_PARAM;
731 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_MOVE_THRESH[gate], sizeof(CMD_GATE_MOVE_THRESH[gate]));
732 cmd_frame.data_length += 2;
733 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
734 cmd_frame.data_length += 2;
735 cmd_frame.footer = CMD_FRAME_FOOTER;
736 ESP_LOGV(TAG, "Sending read gate %d high/low threshold command: %2X", gate, cmd_frame.command);
737 error = this->send_cmd_from_array(cmd_frame);
738 if (error == 0) {
739 this->current_config.move_thresh[gate] = cmd_reply_.data[0];
741 }
742 return error;
743}
744
746 uint8_t error;
747 CmdFrameT cmd_frame;
748 cmd_frame.data_length = 0;
749 cmd_frame.header = CMD_FRAME_HEADER;
750 cmd_frame.command = CMD_READ_ABD_PARAM;
751 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
752 sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
753 cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
754 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
755 sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
756 cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
757 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
758 sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
759 cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
760 cmd_frame.footer = CMD_FRAME_FOOTER;
761 ESP_LOGV(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
762 error = this->send_cmd_from_array(cmd_frame);
763 if (error == 0) {
764 this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
765 this->current_config.max_gate = (uint16_t) cmd_reply_.data[1];
766 this->current_config.timeout = (uint16_t) cmd_reply_.data[2];
767 }
768 return error;
769}
770
772 CmdFrameT cmd_frame;
773 uint16_t unknown_parm = 0x0000;
774 cmd_frame.data_length = 0;
775 cmd_frame.header = CMD_FRAME_HEADER;
776 cmd_frame.command = CMD_WRITE_SYS_PARAM;
777 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_SYSTEM_MODE, sizeof(CMD_SYSTEM_MODE));
778 cmd_frame.data_length += sizeof(CMD_SYSTEM_MODE);
779 memcpy(&cmd_frame.data[cmd_frame.data_length], &mode, sizeof(mode));
780 cmd_frame.data_length += sizeof(mode);
781 memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
782 cmd_frame.data_length += sizeof(unknown_parm);
783 cmd_frame.footer = CMD_FRAME_FOOTER;
784 ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
785 if (this->send_cmd_from_array(cmd_frame) == 0) {
786 this->set_mode_(mode);
787 }
788}
789
791 CmdFrameT cmd_frame;
792 cmd_frame.data_length = 0;
793 cmd_frame.header = CMD_FRAME_HEADER;
794 cmd_frame.command = CMD_READ_VERSION;
795 cmd_frame.footer = CMD_FRAME_FOOTER;
796
797 ESP_LOGV(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
798 this->send_cmd_from_array(cmd_frame);
799}
800
801void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, // NOLINT
802 uint32_t timeout) {
803 // Header H, Length L, Register R, Value V, Footer F
804 // |Min Gate |Max Gate |Timeout |
805 // 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
806 // 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.
807
808 CmdFrameT cmd_frame;
809 cmd_frame.data_length = 0;
810 cmd_frame.header = CMD_FRAME_HEADER;
811 cmd_frame.command = CMD_WRITE_ABD_PARAM;
812 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
813 sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
814 cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
815 memcpy(&cmd_frame.data[cmd_frame.data_length], &min_gate_distance, sizeof(min_gate_distance));
816 cmd_frame.data_length += sizeof(min_gate_distance);
817 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
818 sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
819 cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
820 memcpy(&cmd_frame.data[cmd_frame.data_length], &max_gate_distance, sizeof(max_gate_distance));
821 cmd_frame.data_length += sizeof(max_gate_distance);
822 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
823 sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
824 cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
825 memcpy(&cmd_frame.data[cmd_frame.data_length], &timeout, sizeof(timeout));
826 ;
827 cmd_frame.data_length += sizeof(timeout);
828 cmd_frame.footer = CMD_FRAME_FOOTER;
829
830 ESP_LOGV(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
831 this->send_cmd_from_array(cmd_frame);
832}
833
835 // Header H, Length L, Command C, Register R, Value V, Footer F
836 // HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
837 // FD FC FB FA 14 00 07 00 10 00 00 FF 00 00 00 01 00 0F 00 00 04 03 02 01
838
839 uint16_t move_threshold_gate = CMD_GATE_MOVE_THRESH[gate];
840 uint16_t still_threshold_gate = CMD_GATE_STILL_THRESH[gate];
841 CmdFrameT cmd_frame;
842 cmd_frame.data_length = 0;
843 cmd_frame.header = CMD_FRAME_HEADER;
844 cmd_frame.command = CMD_WRITE_ABD_PARAM;
845 memcpy(&cmd_frame.data[cmd_frame.data_length], &move_threshold_gate, sizeof(move_threshold_gate));
846 cmd_frame.data_length += sizeof(move_threshold_gate);
847 memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.move_thresh[gate],
848 sizeof(this->new_config.move_thresh[gate]));
849 cmd_frame.data_length += sizeof(this->new_config.move_thresh[gate]);
850 memcpy(&cmd_frame.data[cmd_frame.data_length], &still_threshold_gate, sizeof(still_threshold_gate));
851 cmd_frame.data_length += sizeof(still_threshold_gate);
852 memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.still_thresh[gate],
853 sizeof(this->new_config.still_thresh[gate]));
854 cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
855 cmd_frame.footer = CMD_FRAME_FOOTER;
856 ESP_LOGV(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
857 this->send_cmd_from_array(cmd_frame);
858}
859
860#ifdef USE_NUMBER
862 if (this->gate_timeout_number_ != nullptr) {
863 this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
864 }
865 if (this->gate_select_number_ != nullptr) {
867 }
868 if (this->min_gate_distance_number_ != nullptr) {
869 this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
870 }
871 if (this->max_gate_distance_number_ != nullptr) {
872 this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
873 }
874 if (this->gate_move_sensitivity_factor_number_ != nullptr) {
876 }
877 if (this->gate_still_sensitivity_factor_number_ != nullptr) {
879 }
880 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
881 if (this->gate_still_threshold_numbers_[gate] != nullptr) {
882 this->gate_still_threshold_numbers_[gate]->publish_state(
883 static_cast<uint16_t>(this->current_config.still_thresh[gate]));
884 }
885 if (this->gate_move_threshold_numbers_[gate] != nullptr) {
886 this->gate_move_threshold_numbers_[gate]->publish_state(
887 static_cast<uint16_t>(this->current_config.move_thresh[gate]));
888 }
889 }
890}
891
897
898#endif
899
900} // namespace esphome::ld2420
BedjetMode mode
BedJet operating mode.
uint8_t checksum
Definition bl0906.h:3
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
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.
void readline_(int rx_data, uint8_t *buffer, int len)
Definition ld2420.cpp:428
void set_system_mode(uint16_t mode)
Definition ld2420.cpp:771
void handle_ack_data_(uint8_t *buffer, int len)
Definition ld2420.cpp:542
std::vector< number::Number * > gate_still_threshold_numbers_
Definition ld2420.h:178
number::Number * gate_select_number_
Definition ld2420.h:173
uint8_t set_config_mode(bool enable)
Definition ld2420.cpp:670
button::Button * factory_reset_button_
Definition ld2420.h:141
void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number)
Definition ld2420.cpp:343
float get_setup_priority() const override
Definition ld2420.cpp:183
void set_distance_(uint16_t distance)
Definition ld2420.h:163
void set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, uint32_t timeout)
Definition ld2420.cpp:801
void set_gate_threshold(uint8_t gate)
Definition ld2420.cpp:834
void handle_energy_mode_(uint8_t *buffer, int len)
Definition ld2420.cpp:458
button::Button * revert_config_button_
Definition ld2420.h:139
void set_calibration_(bool state)
Definition ld2420.h:168
number::Number * min_gate_distance_number_
Definition ld2420.h:174
button::Button * apply_config_button_
Definition ld2420.h:138
uint16_t gate_peak[TOTAL_GATES]
Definition ld2420.h:127
std::vector< LD2420Listener * > listeners_
Definition ld2420.h:192
std::vector< number::Number * > gate_move_threshold_numbers_
Definition ld2420.h:179
void set_mode_(uint16_t mode)
Definition ld2420.h:159
uint16_t gate_avg[TOTAL_GATES]
Definition ld2420.h:126
uint8_t buffer_data_[MAX_LINE_LENGTH]
Definition ld2420.h:186
int get_gate_threshold_(uint8_t gate)
Definition ld2420.cpp:725
number::Number * gate_still_sensitivity_factor_number_
Definition ld2420.h:177
void handle_cmd_error(uint8_t error)
Definition ld2420.cpp:723
int send_cmd_from_array(CmdFrameT cmd_frame)
Definition ld2420.cpp:610
void get_reg_value_(uint16_t reg)
Definition ld2420.cpp:697
uint16_t gate_energy_[TOTAL_GATES]
Definition ld2420.h:184
void handle_simple_mode_(const uint8_t *inbuf, int len)
Definition ld2420.cpp:498
number::Number * max_gate_distance_number_
Definition ld2420.h:175
select::Select * operating_selector_
Definition ld2420.h:135
button::Button * restart_module_button_
Definition ld2420.h:140
number::Number * gate_timeout_number_
Definition ld2420.h:172
void set_operating_mode(const char *state)
Definition ld2420.cpp:391
uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES]
Definition ld2420.h:125
void set_presence_(bool presence)
Definition ld2420.h:161
number::Number * gate_move_sensitivity_factor_number_
Definition ld2420.h:176
void set_reg_value(uint16_t reg, uint16_t value)
Definition ld2420.cpp:709
void publish_state(float state)
Definition number.cpp:31
void publish_state(const std::string &state)
Definition select.cpp:11
void write_array(const uint8_t *data, size_t len)
Definition uart.h:26
bool state
Definition fan.h:0
Range range
Definition msa3xx.h:0
@ OP_CALIBRATE_MODE
Definition ld2420.h:28
uint8_t find_uint8(const StringToUint8(&arr)[N], const std::string &str)
Definition ld2420.cpp:155
const float BUS
For communication buses like i2c/spi.
Definition component.cpp:78
std::string size_t len
Definition helpers.h:503
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:695
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:30
Application App
Global storage of Application pointer - only one Application can exist.
void byteswap()