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