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