ESPHome 2025.9.0-dev
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 std::string OP_NORMAL_MODE_STRING = "Normal";
135static const std::string 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 = version_str.substr(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 ESP_LOGCONFIG(TAG, "Select:");
209 LOG_SELECT(" ", "Operating Mode", this->operating_selector_);
210 if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) {
211 ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_);
212 }
213}
214
216 if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
217 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
218 this->mark_failed();
219 return;
220 }
222#ifdef USE_NUMBER
224#endif
225 this->get_firmware_version_();
226 const char *pfw = this->firmware_ver_;
227 std::string fw_str(pfw);
228
229 for (auto &listener : this->listeners_) {
230 listener->on_fw_version(fw_str);
231 }
232
233 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
235 this->get_gate_threshold_(gate);
236 }
237
238 memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
239 if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) {
240 this->set_operating_mode(OP_SIMPLE_MODE_STRING);
241 this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
242 this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
243 ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_);
244 } else {
245 this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
246 this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
247 }
248#ifdef USE_NUMBER
250#endif
251 this->set_system_mode(this->system_mode_);
252 this->set_config_mode(false);
253}
254
256 const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
257 if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
258 ESP_LOGD(TAG, "No configuration change detected");
259 return;
260 }
261 ESP_LOGD(TAG, "Reconfiguring");
262 if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
263 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
264 this->mark_failed();
265 return;
266 }
267 this->set_min_max_distances_timeout(this->new_config.max_gate, this->new_config.min_gate, this->new_config.timeout);
268 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
270 this->set_gate_threshold(gate);
271 }
272 memcpy(&current_config, &new_config, sizeof(new_config));
273#ifdef USE_NUMBER
275#endif
276 this->set_system_mode(this->system_mode_);
277 this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
278 this->set_operating_mode(OP_NORMAL_MODE_STRING);
279}
280
282 ESP_LOGD(TAG, "Setting factory defaults");
283 if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
284 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
285 this->mark_failed();
286 return;
287 }
288 this->set_min_max_distances_timeout(FACTORY_MAX_GATE, FACTORY_MIN_GATE, FACTORY_TIMEOUT);
289#ifdef USE_NUMBER
290 this->gate_timeout_number_->state = FACTORY_TIMEOUT;
291 this->min_gate_distance_number_->state = FACTORY_MIN_GATE;
292 this->max_gate_distance_number_->state = FACTORY_MAX_GATE;
293#endif
294 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
295 this->new_config.move_thresh[gate] = FACTORY_MOVE_THRESH[gate];
296 this->new_config.still_thresh[gate] = FACTORY_STILL_THRESH[gate];
298 this->set_gate_threshold(gate);
299 }
300 memcpy(&this->current_config, &this->new_config, sizeof(this->new_config));
301 this->set_system_mode(this->system_mode_);
302 this->set_config_mode(false);
303#ifdef USE_NUMBER
306#endif
307}
308
310 ESP_LOGD(TAG, "Restarting");
311 this->send_module_restart();
312 this->set_timeout(250, [this]() {
313 this->set_config_mode(true);
314 this->set_system_mode(this->system_mode_);
315 this->set_config_mode(false);
316 });
317}
318
320 memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
321#ifdef USE_NUMBER
323#endif
324 ESP_LOGD(TAG, "Reverted config number edits");
325}
326
328 // If there is a active send command do not process it here, the send command call will handle it.
329 while (!this->cmd_active_ && this->available()) {
330 this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH);
331 }
332}
333
334void LD2420Component::update_radar_data(uint16_t const *gate_energy, uint8_t sample_number) {
335 for (uint8_t gate = 0; gate < TOTAL_GATES; ++gate) {
336 this->radar_data[gate][sample_number] = gate_energy[gate];
337 }
339}
340
342 // Calculate average and peak values for each gate
343 const float move_factor = gate_move_sensitivity_factor + 1;
344 const float still_factor = (gate_still_sensitivity_factor / 2) + 1;
345 for (uint8_t gate = 0; gate < TOTAL_GATES; ++gate) {
346 uint32_t sum = 0;
347 uint16_t peak = 0;
348
349 for (uint8_t sample_number = 0; sample_number < CALIBRATE_SAMPLES; ++sample_number) {
350 // Calculate average
351 sum += this->radar_data[gate][sample_number];
352
353 // Calculate max value
354 if (this->radar_data[gate][sample_number] > peak) {
355 peak = this->radar_data[gate][sample_number];
356 }
357 }
358
359 // Store average and peak values
360 this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
361 if (this->gate_peak[gate] < peak) {
362 this->gate_peak[gate] = peak;
363 }
364
365 uint32_t calculated_value =
366 (static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
367 this->new_config.move_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
368 calculated_value =
369 (static_cast<uint32_t>(this->gate_peak[gate]) + (still_factor * static_cast<uint32_t>(this->gate_peak[gate])));
370 this->new_config.still_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
371 }
372}
373
375 for (uint8_t gate = 0; gate < TOTAL_GATES; ++gate) {
376 // Output results
377 ESP_LOGI(TAG, "Gate: %2d Avg: %5d Peak: %5d", gate, this->gate_avg[gate], this->gate_peak[gate]);
378 }
379 ESP_LOGI(TAG, "Total samples: %d", this->total_sample_number_counter);
380}
381
383 // If unsupported firmware ignore mode select
384 if (ld2420::get_firmware_int(firmware_ver_) >= CALIBRATE_VERSION_MIN) {
385 this->current_operating_mode = find_uint8(OP_MODE_BY_STR, state);
386 // Entering Auto Calibrate we need to clear the privoiuos data collection
389 this->set_calibration_(true);
390 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
391 this->gate_avg[gate] = 0;
392 this->gate_peak[gate] = 0;
393 for (uint8_t i = 0; i < CALIBRATE_SAMPLES; i++) {
394 this->radar_data[gate][i] = 0;
395 }
397 }
398 } else {
399 // Set the current data back so we don't have new data that can be applied in error.
400 if (this->get_calibration_()) {
401 memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
402 }
403 this->set_calibration_(false);
404 }
405 } else {
407 this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
408 }
409}
410
411void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
412 if (rx_data < 0) {
413 return; // No data available
414 }
415 if (this->buffer_pos_ < len - 1) {
416 buffer[this->buffer_pos_++] = rx_data;
417 buffer[this->buffer_pos_] = 0;
418 } else {
419 // We should never get here, but just in case...
420 ESP_LOGW(TAG, "Max command length exceeded; ignoring");
421 this->buffer_pos_ = 0;
422 }
423 if (this->buffer_pos_ < 4) {
424 return; // Not enough data to process yet
425 }
426 if (memcmp(&buffer[this->buffer_pos_ - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
427 this->cmd_active_ = false; // Set command state to inactive after response
428 this->handle_ack_data_(buffer, this->buffer_pos_);
429 this->buffer_pos_ = 0;
430 } else if ((buffer[this->buffer_pos_ - 2] == 0x0D && buffer[this->buffer_pos_ - 1] == 0x0A) &&
431 (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
432 this->handle_simple_mode_(buffer, this->buffer_pos_);
433 this->buffer_pos_ = 0;
434 } else if ((memcmp(&buffer[this->buffer_pos_ - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
435 (this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
436 this->handle_energy_mode_(buffer, this->buffer_pos_);
437 this->buffer_pos_ = 0;
438 }
439}
440
441void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
442 uint8_t index = 6; // Start at presence byte position
443 uint16_t range;
444 const uint8_t elements = sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]);
445 this->set_presence_(buffer[index]);
446 index++;
447 memcpy(&range, &buffer[index], sizeof(range));
448 index += sizeof(range);
449 this->set_distance_(range);
450 for (uint8_t i = 0; i < elements; i++) { // NOLINT
451 memcpy(&this->gate_energy_[i], &buffer[index], sizeof(this->gate_energy_[0]));
452 index += sizeof(this->gate_energy_[0]);
453 }
454
457 this->sample_number_counter > CALIBRATE_SAMPLES ? this->sample_number_counter = 0 : this->sample_number_counter++;
458 }
459
460 // Resonable refresh rate for home assistant database size health
461 const int32_t current_millis = App.get_loop_component_start_time();
462 if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) {
463 return;
464 }
465 this->last_periodic_millis = current_millis;
466 for (auto &listener : this->listeners_) {
467 listener->on_distance(this->get_distance_());
468 listener->on_presence(this->get_presence_());
469 listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
470 }
471
474 if (current_millis - this->report_periodic_millis > REFRESH_RATE_MS * CALIBRATE_REPORT_INTERVAL) {
475 this->report_periodic_millis = current_millis;
476 this->report_gate_data();
477 }
478 }
479}
480
481void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
482 const uint8_t bufsize = 16;
483 uint8_t index{0};
484 uint8_t pos{0};
485 char *endptr{nullptr};
486 char outbuf[bufsize]{0};
487 while (true) {
488 if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
489 this->set_presence_(false);
490 } else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
491 this->set_presence_(true);
492 }
493 if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
494 if (index < bufsize - 1) {
495 outbuf[index++] = inbuf[pos];
496 pos++;
497 }
498 } else {
499 if (pos < len - 1) {
500 pos++;
501 } else {
502 break;
503 }
504 }
505 }
506 outbuf[index] = '\0';
507 if (index > 1) {
508 this->set_distance_(strtol(outbuf, &endptr, 10));
509 }
510
511 if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
512 // Resonable refresh rate for home assistant database size health
513 const int32_t current_millis = App.get_loop_component_start_time();
514 if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) {
515 return;
516 }
517 this->last_normal_periodic_millis = current_millis;
518 for (auto &listener : this->listeners_)
519 listener->on_distance(this->get_distance_());
520 for (auto &listener : this->listeners_)
521 listener->on_presence(this->get_presence_());
522 }
523}
524
525void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
526 this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND];
527 this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH];
528 uint8_t reg_element = 0;
529 uint8_t data_element = 0;
530 uint16_t data_pos = 0;
531 if (this->cmd_reply_.length > CMD_MAX_BYTES) {
532 ESP_LOGW(TAG, "Reply frame too long");
533 return;
534 } else if (this->cmd_reply_.length < 2) {
535 ESP_LOGW(TAG, "Command frame too short");
536 return;
537 }
538 memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
539 const char *result = this->cmd_reply_.error ? "failure" : "success";
540 if (this->cmd_reply_.error > 0) {
541 return;
542 };
543 this->cmd_reply_.ack = true;
544 switch ((uint16_t) this->cmd_reply_.command) {
545 case (CMD_ENABLE_CONF):
546 ESP_LOGV(TAG, "Set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
547 break;
548 case (CMD_DISABLE_CONF):
549 ESP_LOGV(TAG, "Set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
550 break;
551 case (CMD_READ_REGISTER):
552 ESP_LOGV(TAG, "Read register: CMD = %2X %s", CMD_READ_REGISTER, result);
553 // TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
554 data_pos = 0x0A;
555 for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
556 ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_REG_DATA_REPLY_SIZE));
557 index += CMD_REG_DATA_REPLY_SIZE) {
558 memcpy(&this->cmd_reply_.data[reg_element], &buffer[data_pos + index], sizeof(CMD_REG_DATA_REPLY_SIZE));
559 byteswap(this->cmd_reply_.data[reg_element]);
560 reg_element++;
561 }
562 break;
563 case (CMD_WRITE_REGISTER):
564 ESP_LOGV(TAG, "Write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
565 break;
566 case (CMD_WRITE_ABD_PARAM):
567 ESP_LOGV(TAG, "Write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
568 break;
569 case (CMD_READ_ABD_PARAM):
570 ESP_LOGV(TAG, "Read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
571 data_pos = CMD_ABD_DATA_REPLY_START;
572 for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
573 ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
574 index += CMD_ABD_DATA_REPLY_SIZE) {
575 memcpy(&this->cmd_reply_.data[data_element], &buffer[data_pos + index],
576 sizeof(this->cmd_reply_.data[data_element]));
577 byteswap(this->cmd_reply_.data[data_element]);
578 data_element++;
579 }
580 break;
581 case (CMD_WRITE_SYS_PARAM):
582 ESP_LOGV(TAG, "Set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
583 break;
584 case (CMD_READ_VERSION):
585 memcpy(this->firmware_ver_, &buffer[12], buffer[10]);
586 ESP_LOGV(TAG, "Firmware version: %7s %s", this->firmware_ver_, result);
587 break;
588 default:
589 break;
590 }
591}
592
594 uint32_t start_millis = millis();
595 uint8_t error = 0;
596 uint8_t ack_buffer[MAX_LINE_LENGTH];
597 uint8_t cmd_buffer[MAX_LINE_LENGTH];
598 this->cmd_reply_.ack = false;
599 if (frame.command != CMD_RESTART) {
600 this->cmd_active_ = true;
601 } // Restart does not reply, thus no ack state required
602 uint8_t retry = 3;
603 while (retry) {
604 frame.length = 0;
605 uint16_t frame_data_bytes = frame.data_length + 2; // Always add two bytes for the cmd size
606
607 memcpy(&cmd_buffer[frame.length], &frame.header, sizeof(frame.header));
608 frame.length += sizeof(frame.header);
609
610 memcpy(&cmd_buffer[frame.length], &frame_data_bytes, sizeof(frame.data_length));
611 frame.length += sizeof(frame.data_length);
612
613 memcpy(&cmd_buffer[frame.length], &frame.command, sizeof(frame.command));
614 frame.length += sizeof(frame.command);
615
616 for (uint16_t index = 0; index < frame.data_length; index++) {
617 memcpy(&cmd_buffer[frame.length], &frame.data[index], sizeof(frame.data[index]));
618 frame.length += sizeof(frame.data[index]);
619 }
620
621 memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
622 frame.length += sizeof(frame.footer);
623 this->write_array(cmd_buffer, frame.length);
624
625 error = 0;
626 if (frame.command == CMD_RESTART) {
627 return 0; // restart does not reply exit now
628 }
629
630 while (!this->cmd_reply_.ack) {
631 while (this->available()) {
632 this->readline_(this->read(), ack_buffer, sizeof(ack_buffer));
633 }
635 // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT.
636 if ((millis() - start_millis) > 1000) {
637 start_millis = millis();
638 error = LD2420_ERROR_TIMEOUT;
639 retry--;
640 break;
641 }
642 }
643 if (this->cmd_reply_.ack) {
644 retry = 0;
645 }
646 if (this->cmd_reply_.error > 0) {
647 this->handle_cmd_error(error);
648 }
649 }
650 return error;
651}
652
654 CmdFrameT cmd_frame;
655 cmd_frame.data_length = 0;
656 cmd_frame.header = CMD_FRAME_HEADER;
657 cmd_frame.command = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
658 if (enable) {
659 memcpy(&cmd_frame.data[0], &CMD_PROTOCOL_VER, sizeof(CMD_PROTOCOL_VER));
660 cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
661 }
662 cmd_frame.footer = CMD_FRAME_FOOTER;
663 ESP_LOGV(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
664 return this->send_cmd_from_array(cmd_frame);
665}
666
667// Sends a restart and set system running mode to normal
669
671 CmdFrameT cmd_frame;
672 cmd_frame.data_length = 0;
673 cmd_frame.header = CMD_FRAME_HEADER;
674 cmd_frame.command = CMD_RESTART;
675 cmd_frame.footer = CMD_FRAME_FOOTER;
676 ESP_LOGV(TAG, "Sending restart command: %2X", cmd_frame.command);
677 this->send_cmd_from_array(cmd_frame);
678}
679
681 CmdFrameT cmd_frame;
682 cmd_frame.data_length = 0;
683 cmd_frame.header = CMD_FRAME_HEADER;
684 cmd_frame.command = CMD_READ_REGISTER;
685 cmd_frame.data[1] = reg;
686 cmd_frame.data_length += 2;
687 cmd_frame.footer = CMD_FRAME_FOOTER;
688 ESP_LOGV(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
689 this->send_cmd_from_array(cmd_frame);
690}
691
692void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
693 CmdFrameT cmd_frame;
694 cmd_frame.data_length = 0;
695 cmd_frame.header = CMD_FRAME_HEADER;
696 cmd_frame.command = CMD_WRITE_REGISTER;
697 memcpy(&cmd_frame.data[cmd_frame.data_length], &reg, sizeof(CMD_REG_DATA_REPLY_SIZE));
698 cmd_frame.data_length += 2;
699 memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
700 cmd_frame.data_length += 2;
701 cmd_frame.footer = CMD_FRAME_FOOTER;
702 ESP_LOGV(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
703 this->send_cmd_from_array(cmd_frame);
704}
705
706void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGE(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
707
709 uint8_t error;
710 CmdFrameT cmd_frame;
711 cmd_frame.data_length = 0;
712 cmd_frame.header = CMD_FRAME_HEADER;
713 cmd_frame.command = CMD_READ_ABD_PARAM;
714 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_MOVE_THRESH[gate], sizeof(CMD_GATE_MOVE_THRESH[gate]));
715 cmd_frame.data_length += 2;
716 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
717 cmd_frame.data_length += 2;
718 cmd_frame.footer = CMD_FRAME_FOOTER;
719 ESP_LOGV(TAG, "Sending read gate %d high/low threshold command: %2X", gate, cmd_frame.command);
720 error = this->send_cmd_from_array(cmd_frame);
721 if (error == 0) {
722 this->current_config.move_thresh[gate] = cmd_reply_.data[0];
724 }
725 return error;
726}
727
729 uint8_t error;
730 CmdFrameT cmd_frame;
731 cmd_frame.data_length = 0;
732 cmd_frame.header = CMD_FRAME_HEADER;
733 cmd_frame.command = CMD_READ_ABD_PARAM;
734 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
735 sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
736 cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
737 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
738 sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
739 cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
740 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
741 sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
742 cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
743 cmd_frame.footer = CMD_FRAME_FOOTER;
744 ESP_LOGV(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
745 error = this->send_cmd_from_array(cmd_frame);
746 if (error == 0) {
747 this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
748 this->current_config.max_gate = (uint16_t) cmd_reply_.data[1];
749 this->current_config.timeout = (uint16_t) cmd_reply_.data[2];
750 }
751 return error;
752}
753
755 CmdFrameT cmd_frame;
756 uint16_t unknown_parm = 0x0000;
757 cmd_frame.data_length = 0;
758 cmd_frame.header = CMD_FRAME_HEADER;
759 cmd_frame.command = CMD_WRITE_SYS_PARAM;
760 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_SYSTEM_MODE, sizeof(CMD_SYSTEM_MODE));
761 cmd_frame.data_length += sizeof(CMD_SYSTEM_MODE);
762 memcpy(&cmd_frame.data[cmd_frame.data_length], &mode, sizeof(mode));
763 cmd_frame.data_length += sizeof(mode);
764 memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
765 cmd_frame.data_length += sizeof(unknown_parm);
766 cmd_frame.footer = CMD_FRAME_FOOTER;
767 ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
768 if (this->send_cmd_from_array(cmd_frame) == 0) {
769 this->set_mode_(mode);
770 }
771}
772
774 CmdFrameT cmd_frame;
775 cmd_frame.data_length = 0;
776 cmd_frame.header = CMD_FRAME_HEADER;
777 cmd_frame.command = CMD_READ_VERSION;
778 cmd_frame.footer = CMD_FRAME_FOOTER;
779
780 ESP_LOGV(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
781 this->send_cmd_from_array(cmd_frame);
782}
783
784void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, // NOLINT
785 uint32_t timeout) {
786 // Header H, Length L, Register R, Value V, Footer F
787 // |Min Gate |Max Gate |Timeout |
788 // 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
789 // 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.
790
791 CmdFrameT cmd_frame;
792 cmd_frame.data_length = 0;
793 cmd_frame.header = CMD_FRAME_HEADER;
794 cmd_frame.command = CMD_WRITE_ABD_PARAM;
795 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
796 sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
797 cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
798 memcpy(&cmd_frame.data[cmd_frame.data_length], &min_gate_distance, sizeof(min_gate_distance));
799 cmd_frame.data_length += sizeof(min_gate_distance);
800 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
801 sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
802 cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
803 memcpy(&cmd_frame.data[cmd_frame.data_length], &max_gate_distance, sizeof(max_gate_distance));
804 cmd_frame.data_length += sizeof(max_gate_distance);
805 memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
806 sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
807 cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
808 memcpy(&cmd_frame.data[cmd_frame.data_length], &timeout, sizeof(timeout));
809 ;
810 cmd_frame.data_length += sizeof(timeout);
811 cmd_frame.footer = CMD_FRAME_FOOTER;
812
813 ESP_LOGV(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
814 this->send_cmd_from_array(cmd_frame);
815}
816
818 // Header H, Length L, Command C, Register R, Value V, Footer F
819 // HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
820 // FD FC FB FA 14 00 07 00 10 00 00 FF 00 00 00 01 00 0F 00 00 04 03 02 01
821
822 uint16_t move_threshold_gate = CMD_GATE_MOVE_THRESH[gate];
823 uint16_t still_threshold_gate = CMD_GATE_STILL_THRESH[gate];
824 CmdFrameT cmd_frame;
825 cmd_frame.data_length = 0;
826 cmd_frame.header = CMD_FRAME_HEADER;
827 cmd_frame.command = CMD_WRITE_ABD_PARAM;
828 memcpy(&cmd_frame.data[cmd_frame.data_length], &move_threshold_gate, sizeof(move_threshold_gate));
829 cmd_frame.data_length += sizeof(move_threshold_gate);
830 memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.move_thresh[gate],
831 sizeof(this->new_config.move_thresh[gate]));
832 cmd_frame.data_length += sizeof(this->new_config.move_thresh[gate]);
833 memcpy(&cmd_frame.data[cmd_frame.data_length], &still_threshold_gate, sizeof(still_threshold_gate));
834 cmd_frame.data_length += sizeof(still_threshold_gate);
835 memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.still_thresh[gate],
836 sizeof(this->new_config.still_thresh[gate]));
837 cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
838 cmd_frame.footer = CMD_FRAME_FOOTER;
839 ESP_LOGV(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
840 this->send_cmd_from_array(cmd_frame);
841}
842
843#ifdef USE_NUMBER
845 if (this->gate_timeout_number_ != nullptr) {
846 this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
847 }
848 if (this->gate_select_number_ != nullptr) {
850 }
851 if (this->min_gate_distance_number_ != nullptr) {
852 this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
853 }
854 if (this->max_gate_distance_number_ != nullptr) {
855 this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
856 }
857 if (this->gate_move_sensitivity_factor_number_ != nullptr) {
859 }
860 if (this->gate_still_sensitivity_factor_number_ != nullptr) {
862 }
863 for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
864 if (this->gate_still_threshold_numbers_[gate] != nullptr) {
865 this->gate_still_threshold_numbers_[gate]->publish_state(
866 static_cast<uint16_t>(this->current_config.still_thresh[gate]));
867 }
868 if (this->gate_move_threshold_numbers_[gate] != nullptr) {
869 this->gate_move_threshold_numbers_[gate]->publish_state(
870 static_cast<uint16_t>(this->current_config.move_thresh[gate]));
871 }
872 }
873}
874
880
881#endif
882
883} // namespace ld2420
884} // 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:411
void set_system_mode(uint16_t mode)
Definition ld2420.cpp:754
void handle_ack_data_(uint8_t *buffer, int len)
Definition ld2420.cpp:525
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:653
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:334
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:784
void set_gate_threshold(uint8_t gate)
Definition ld2420.cpp:817
void handle_energy_mode_(uint8_t *buffer, int len)
Definition ld2420.cpp:441
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:708
number::Number * gate_still_sensitivity_factor_number_
Definition ld2420.h:178
void handle_cmd_error(uint8_t error)
Definition ld2420.cpp:706
int send_cmd_from_array(CmdFrameT cmd_frame)
Definition ld2420.cpp:593
void get_reg_value_(uint16_t reg)
Definition ld2420.cpp:680
uint16_t gate_energy_[TOTAL_GATES]
Definition ld2420.h:185
void handle_simple_mode_(const uint8_t *inbuf, int len)
Definition ld2420.cpp:481
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
void set_operating_mode(const std::string &state)
Definition ld2420.cpp:382
number::Number * gate_timeout_number_
Definition ld2420.h:173
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:692
void publish_state(float state)
Definition number.cpp:9
void publish_state(const std::string &state)
Definition select.cpp:9
void write_array(const uint8_t *data, size_t len)
Definition uart.h:21
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:47
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:279
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:611
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.
void byteswap()