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