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