ESPHome  2024.4.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  uint8_t error = 0;
497  uint8_t ack_buffer[64];
498  uint8_t cmd_buffer[64];
499  uint16_t loop_count;
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  // TODO setup a dynamic method e.g. millis time count etc. to tune for non ESP32 240Mhz devices
506  // this is ok for now since the module firmware is changing like the weather atm
507  frame.length = 0;
508  loop_count = 1250;
509  uint16_t frame_data_bytes = frame.data_length + 2; // Always add two bytes for the cmd size
510 
511  memcpy(&cmd_buffer[frame.length], &frame.header, sizeof(frame.header));
512  frame.length += sizeof(frame.header);
513 
514  memcpy(&cmd_buffer[frame.length], &frame_data_bytes, sizeof(frame.data_length));
515  frame.length += sizeof(frame.data_length);
516 
517  memcpy(&cmd_buffer[frame.length], &frame.command, sizeof(frame.command));
518  frame.length += sizeof(frame.command);
519 
520  for (uint16_t index = 0; index < frame.data_length; index++) {
521  memcpy(&cmd_buffer[frame.length], &frame.data[index], sizeof(frame.data[index]));
522  frame.length += sizeof(frame.data[index]);
523  }
524 
525  memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
526  frame.length += sizeof(frame.footer);
527  for (uint16_t index = 0; index < frame.length; index++) {
528  this->write_byte(cmd_buffer[index]);
529  }
530 
531  error = 0;
532  if (frame.command == CMD_RESTART) {
533  return 0; // restart does not reply exit now
534  }
535 
536  while (!this->cmd_reply_.ack) {
537  while (available()) {
538  this->readline_(read(), ack_buffer, sizeof(ack_buffer));
539  }
541  if (loop_count <= 0) {
542  error = LD2420_ERROR_TIMEOUT;
543  retry--;
544  break;
545  }
546  loop_count--;
547  }
548  if (this->cmd_reply_.ack)
549  retry = 0;
550  if (this->cmd_reply_.error > 0)
551  handle_cmd_error(error);
552  }
553  return error;
554 }
555 
556 uint8_t LD2420Component::set_config_mode(bool enable) {
557  CmdFrameT cmd_frame;
558  cmd_frame.data_length = 0;
559  cmd_frame.header = CMD_FRAME_HEADER;
560  cmd_frame.command = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
561  if (enable) {
562  memcpy(&cmd_frame.data[0], &CMD_PROTOCOL_VER, sizeof(CMD_PROTOCOL_VER));
563  cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
564  }
565  cmd_frame.footer = CMD_FRAME_FOOTER;
566  ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
567  return this->send_cmd_from_array(cmd_frame);
568 }
569 
570 // Sends a restart and set system running mode to normal
572 
574  CmdFrameT cmd_frame;
575  cmd_frame.data_length = 0;
576  cmd_frame.header = CMD_FRAME_HEADER;
577  cmd_frame.command = CMD_RESTART;
578  cmd_frame.footer = CMD_FRAME_FOOTER;
579  ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command);
580  this->send_cmd_from_array(cmd_frame);
581 }
582 
584  CmdFrameT cmd_frame;
585  cmd_frame.data_length = 0;
586  cmd_frame.header = CMD_FRAME_HEADER;
587  cmd_frame.command = CMD_READ_REGISTER;
588  cmd_frame.data[1] = reg;
589  cmd_frame.data_length += 2;
590  cmd_frame.footer = CMD_FRAME_FOOTER;
591  ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
592  this->send_cmd_from_array(cmd_frame);
593 }
594 
595 void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
596  CmdFrameT cmd_frame;
597  cmd_frame.data_length = 0;
598  cmd_frame.header = CMD_FRAME_HEADER;
599  cmd_frame.command = CMD_WRITE_REGISTER;
600  memcpy(&cmd_frame.data[cmd_frame.data_length], &reg, sizeof(CMD_REG_DATA_REPLY_SIZE));
601  cmd_frame.data_length += 2;
602  memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
603  cmd_frame.data_length += 2;
604  cmd_frame.footer = CMD_FRAME_FOOTER;
605  ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
606  this->send_cmd_from_array(cmd_frame);
607 }
608 
609 void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
610 
612  uint8_t error;
613  CmdFrameT cmd_frame;
614  cmd_frame.data_length = 0;
615  cmd_frame.header = CMD_FRAME_HEADER;
616  cmd_frame.command = CMD_READ_ABD_PARAM;
617  memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_MOVE_THRESH[gate], sizeof(CMD_GATE_MOVE_THRESH[gate]));
618  cmd_frame.data_length += 2;
619  memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
620  cmd_frame.data_length += 2;
621  cmd_frame.footer = CMD_FRAME_FOOTER;
622  ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command);
623  error = this->send_cmd_from_array(cmd_frame);
624  if (error == 0) {
625  this->current_config.move_thresh[gate] = cmd_reply_.data[0];
626  this->current_config.still_thresh[gate] = cmd_reply_.data[1];
627  }
628  return error;
629 }
630 
632  uint8_t error;
633  CmdFrameT cmd_frame;
634  cmd_frame.data_length = 0;
635  cmd_frame.header = CMD_FRAME_HEADER;
636  cmd_frame.command = CMD_READ_ABD_PARAM;
637  memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
638  sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
639  cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
640  memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
641  sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
642  cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
643  memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
644  sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
645  cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
646  cmd_frame.footer = CMD_FRAME_FOOTER;
647  ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
648  error = this->send_cmd_from_array(cmd_frame);
649  if (error == 0) {
650  this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
651  this->current_config.max_gate = (uint16_t) cmd_reply_.data[1];
652  this->current_config.timeout = (uint16_t) cmd_reply_.data[2];
653  }
654  return error;
655 }
656 
658  CmdFrameT cmd_frame;
659  uint16_t unknown_parm = 0x0000;
660  cmd_frame.data_length = 0;
661  cmd_frame.header = CMD_FRAME_HEADER;
662  cmd_frame.command = CMD_WRITE_SYS_PARAM;
663  memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_SYSTEM_MODE, sizeof(CMD_SYSTEM_MODE));
664  cmd_frame.data_length += sizeof(CMD_SYSTEM_MODE);
665  memcpy(&cmd_frame.data[cmd_frame.data_length], &mode, sizeof(mode));
666  cmd_frame.data_length += sizeof(mode);
667  memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
668  cmd_frame.data_length += sizeof(unknown_parm);
669  cmd_frame.footer = CMD_FRAME_FOOTER;
670  ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command);
671  if (this->send_cmd_from_array(cmd_frame) == 0)
672  set_mode_(mode);
673 }
674 
676  CmdFrameT cmd_frame;
677  cmd_frame.data_length = 0;
678  cmd_frame.header = CMD_FRAME_HEADER;
679  cmd_frame.command = CMD_READ_VERSION;
680  cmd_frame.footer = CMD_FRAME_FOOTER;
681 
682  ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
683  this->send_cmd_from_array(cmd_frame);
684 }
685 
686 void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, // NOLINT
687  uint32_t timeout) {
688  // Header H, Length L, Register R, Value V, Footer F
689  // |Min Gate |Max Gate |Timeout |
690  // HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
691  // FD FC FB FA 14 00 07 00 00 00 01 00 00 00 01 00 09 00 00 00 04 00 0A 00 00 00 04 03 02 01 e.g.
692 
693  CmdFrameT cmd_frame;
694  cmd_frame.data_length = 0;
695  cmd_frame.header = CMD_FRAME_HEADER;
696  cmd_frame.command = CMD_WRITE_ABD_PARAM;
697  memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
698  sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
699  cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
700  memcpy(&cmd_frame.data[cmd_frame.data_length], &min_gate_distance, sizeof(min_gate_distance));
701  cmd_frame.data_length += sizeof(min_gate_distance);
702  memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
703  sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
704  cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
705  memcpy(&cmd_frame.data[cmd_frame.data_length], &max_gate_distance, sizeof(max_gate_distance));
706  cmd_frame.data_length += sizeof(max_gate_distance);
707  memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
708  sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
709  cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
710  memcpy(&cmd_frame.data[cmd_frame.data_length], &timeout, sizeof(timeout));
711  ;
712  cmd_frame.data_length += sizeof(timeout);
713  cmd_frame.footer = CMD_FRAME_FOOTER;
714 
715  ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
716  this->send_cmd_from_array(cmd_frame);
717 }
718 
720  // Header H, Length L, Command C, Register R, Value V, Footer F
721  // HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
722  // FD FC FB FA 14 00 07 00 10 00 00 FF 00 00 00 01 00 0F 00 00 04 03 02 01
723 
724  uint16_t move_threshold_gate = CMD_GATE_MOVE_THRESH[gate];
725  uint16_t still_threshold_gate = CMD_GATE_STILL_THRESH[gate];
726  CmdFrameT cmd_frame;
727  cmd_frame.data_length = 0;
728  cmd_frame.header = CMD_FRAME_HEADER;
729  cmd_frame.command = CMD_WRITE_ABD_PARAM;
730  memcpy(&cmd_frame.data[cmd_frame.data_length], &move_threshold_gate, sizeof(move_threshold_gate));
731  cmd_frame.data_length += sizeof(move_threshold_gate);
732  memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.move_thresh[gate],
733  sizeof(this->new_config.move_thresh[gate]));
734  cmd_frame.data_length += sizeof(this->new_config.move_thresh[gate]);
735  memcpy(&cmd_frame.data[cmd_frame.data_length], &still_threshold_gate, sizeof(still_threshold_gate));
736  cmd_frame.data_length += sizeof(still_threshold_gate);
737  memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.still_thresh[gate],
738  sizeof(this->new_config.still_thresh[gate]));
739  cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
740  cmd_frame.footer = CMD_FRAME_FOOTER;
741  ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
742  this->send_cmd_from_array(cmd_frame);
743 }
744 
745 #ifdef USE_NUMBER
747  if (this->gate_timeout_number_ != nullptr)
748  this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
749  if (this->gate_select_number_ != nullptr)
751  if (this->min_gate_distance_number_ != nullptr)
752  this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
753  if (this->max_gate_distance_number_ != nullptr)
754  this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
755  if (this->gate_move_sensitivity_factor_number_ != nullptr)
757  if (this->gate_still_sensitivity_factor_number_ != nullptr)
759  for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
760  if (this->gate_still_threshold_numbers_[gate] != nullptr) {
761  this->gate_still_threshold_numbers_[gate]->publish_state(
762  static_cast<uint16_t>(this->current_config.still_thresh[gate]));
763  }
764  if (this->gate_move_threshold_numbers_[gate] != nullptr) {
765  this->gate_move_threshold_numbers_[gate]->publish_state(
766  static_cast<uint16_t>(this->current_config.move_thresh[gate]));
767  }
768  }
769 }
770 
775 }
776 
777 #endif
778 
779 } // namespace ld2420
780 } // 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:583
void set_system_mode(uint16_t mode)
Definition: ld2420.cpp:657
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:601
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:719
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:595
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:686
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:609
std::vector< LD2420Listener * > listeners_
Definition: ld2420.h:267
uint8_t set_config_mode(bool enable)
Definition: ld2420.cpp:556
int get_gate_threshold_(uint8_t gate)
Definition: ld2420.cpp:611
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