ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
helpers.cpp
Go to the documentation of this file.
2
4#include "esphome/core/hal.h"
5#include "esphome/core/log.h"
8
9#include <strings.h>
10#include <algorithm>
11#include <cmath>
12#include <cstdarg>
13#include <cstdio>
14#include <cstdlib>
15#include <cstring>
16
17#ifdef USE_ESP32
18#include "esp_rom_crc.h"
19#endif
20
21namespace esphome {
22
23#ifdef USE_ESP8266
24std::string progmem_string(ProgmemStr str) {
25 auto *src = reinterpret_cast<PGM_P>(str);
26 std::string result(strlen_P(src), '\0');
27 memcpy_P(result.data(), src, result.size());
28 return result;
29}
30#endif
31
32ESPHOME_LOG_TAG(TAG, "helpers");
33
34__attribute__((noinline, cold)) void *callback_manager_grow(void *data, uint16_t size, uint16_t &capacity,
35 size_t elem_size) {
36 ESPHOME_DEBUG_ASSERT(size < UINT16_MAX);
37 uint16_t new_cap = size + 1;
38 auto *new_data = ::operator new(new_cap * elem_size);
39 if (data) {
40 __builtin_memcpy(new_data, data, size * elem_size);
41 ::operator delete(data);
42 }
44 return new_data;
45}
46
47static const uint16_t CRC16_A001_LE_LUT_L[] = {0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
48 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440};
49static const uint16_t CRC16_A001_LE_LUT_H[] = {0x0000, 0xcc01, 0xd801, 0x1400, 0xf001, 0x3c00, 0x2800, 0xe401,
50 0xa001, 0x6c00, 0x7800, 0xb401, 0x5000, 0x9c01, 0x8801, 0x4400};
51
52#ifndef USE_ESP32
53static const uint16_t CRC16_8408_LE_LUT_L[] = {0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
54 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7};
55static const uint16_t CRC16_8408_LE_LUT_H[] = {0x0000, 0x1081, 0x2102, 0x3183, 0x4204, 0x5285, 0x6306, 0x7387,
56 0x8408, 0x9489, 0xa50a, 0xb58b, 0xc60c, 0xd68d, 0xe70e, 0xf78f};
57#endif
58
59#ifndef USE_ESP32
60static const uint16_t CRC16_1021_BE_LUT_L[] = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
61 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef};
62static const uint16_t CRC16_1021_BE_LUT_H[] = {0x0000, 0x1231, 0x2462, 0x3653, 0x48c4, 0x5af5, 0x6ca6, 0x7e97,
63 0x9188, 0x83b9, 0xb5ea, 0xa7db, 0xd94c, 0xcb7d, 0xfd2e, 0xef1f};
64#endif
65
66// Mathematics
67
68uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc, uint8_t poly, bool msb_first) {
69 while ((len--) != 0u) {
70 uint8_t inbyte = *data++;
71 if (msb_first) {
72 // MSB first processing (for polynomials like 0x31, 0x07)
73 crc ^= inbyte;
74 for (uint8_t i = 8; i != 0u; i--) {
75 if (crc & 0x80) {
76 crc = (crc << 1) ^ poly;
77 } else {
78 crc <<= 1;
79 }
80 }
81 } else {
82 // LSB first processing (default for Dallas/Maxim 0x8C)
83 for (uint8_t i = 8; i != 0u; i--) {
84 bool mix = (crc ^ inbyte) & 0x01;
85 crc >>= 1;
86 if (mix)
87 crc ^= poly;
88 inbyte >>= 1;
89 }
90 }
91 }
92 return crc;
93}
94
95uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t reverse_poly, bool refin, bool refout) {
96#ifdef USE_ESP32
97 if (reverse_poly == 0x8408) {
98 crc = esp_rom_crc16_le(refin ? crc : (crc ^ 0xffff), data, len);
99 return refout ? crc : (crc ^ 0xffff);
100 }
101#endif
102 if (refin) {
103 crc ^= 0xffff;
104 }
105#ifndef USE_ESP32
106 if (reverse_poly == 0x8408) {
107 while (len--) {
108 uint8_t combo = crc ^ (uint8_t) *data++;
109 crc = (crc >> 8) ^ CRC16_8408_LE_LUT_L[combo & 0x0F] ^ CRC16_8408_LE_LUT_H[combo >> 4];
110 }
111 } else
112#endif
113 {
114 if (reverse_poly == 0xa001) {
115 while (len--) {
116 uint8_t combo = crc ^ (uint8_t) *data++;
117 crc = (crc >> 8) ^ CRC16_A001_LE_LUT_L[combo & 0x0F] ^ CRC16_A001_LE_LUT_H[combo >> 4];
118 }
119 } else {
120 while (len--) {
121 crc ^= *data++;
122 for (uint8_t i = 0; i < 8; i++) {
123 if (crc & 0x0001) {
124 crc = (crc >> 1) ^ reverse_poly;
125 } else {
126 crc >>= 1;
127 }
128 }
129 }
130 }
131 }
132 return refout ? (crc ^ 0xffff) : crc;
133}
134
135uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t poly, bool refin, bool refout) {
136#ifdef USE_ESP32
137 if (poly == 0x1021) {
138 crc = esp_rom_crc16_be(refin ? crc : (crc ^ 0xffff), data, len);
139 return refout ? crc : (crc ^ 0xffff);
140 }
141#endif
142 if (refin) {
143 crc ^= 0xffff;
144 }
145#ifndef USE_ESP32
146 if (poly == 0x1021) {
147 while (len--) {
148 uint8_t combo = (crc >> 8) ^ *data++;
149 crc = (crc << 8) ^ CRC16_1021_BE_LUT_L[combo & 0x0F] ^ CRC16_1021_BE_LUT_H[combo >> 4];
150 }
151 } else
152#endif
153 {
154 while (len--) {
155 crc ^= (((uint16_t) *data++) << 8);
156 for (uint8_t i = 0; i < 8; i++) {
157 if (crc & 0x8000) {
158 crc = (crc << 1) ^ poly;
159 } else {
160 crc <<= 1;
161 }
162 }
163 }
164 }
165 return refout ? (crc ^ 0xffff) : crc;
166}
167
168// FNV-1 hash - deprecated, use fnv1a_hash() for new code
169uint32_t fnv1_hash(const char *str) {
171 if (str) {
172 while (*str) {
173 hash *= FNV1_PRIME;
174 hash ^= *str++;
175 }
176 }
177 return hash;
178}
179
180// SplitMix32 — a fast, non-cryptographic PRNG from the SplitMix family
181// (Steele et al., 2014). Uses a Weyl sequence with golden-ratio increment
182// and the MurmurHash3 32-bit finalizer as output mixing function.
183// Reference: https://doi.org/10.1145/2714064.2660195
184// Test results: https://lemire.me/blog/2017/08/22/testing-non-cryptographic-random-number-generators-my-results/
185// Seeded lazily from the platform's secure RNG via random_bytes().
186// ESP8266 uses os_random() instead (defined in esp8266/helpers.cpp).
187#ifndef USE_ESP8266
188static uint32_t splitmix32_state; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
189
191 // State of 0 means unseeded. The state will wrap back to 0 after 2^32 calls,
192 // triggering one extra random_bytes() call — an acceptable trade-off vs. adding
193 // a separate bool flag (4 bytes BSS + branch on every call).
194 if (splitmix32_state == 0) {
195 random_bytes(reinterpret_cast<uint8_t *>(&splitmix32_state), sizeof(splitmix32_state));
196 splitmix32_state |= 1; // ensure non-zero seed
197 }
198 splitmix32_state += 0x9e3779b9u;
199 uint32_t z = splitmix32_state;
200 z = (z ^ (z >> 16)) * 0x85ebca6bu;
201 z = (z ^ (z >> 13)) * 0xc2b2ae35u;
202 return z ^ (z >> 16);
203}
204#endif
205
206float random_float() { return static_cast<float>(random_uint32()) / static_cast<float>(UINT32_MAX); }
207
208// Strings
209
210bool str_equals_case_insensitive(const std::string &a, const std::string &b) {
211 return strcasecmp(a.c_str(), b.c_str()) == 0;
212}
214 return a.size() == b.size() && strncasecmp(a.c_str(), b.c_str(), a.size()) == 0;
215}
216#if __cplusplus >= 202002L
217bool str_startswith(const std::string &str, const std::string &start) { return str.starts_with(start); }
218bool str_endswith(const std::string &str, const std::string &end) { return str.ends_with(end); }
219#else
220bool str_startswith(const std::string &str, const std::string &start) { return str.rfind(start, 0) == 0; }
221bool str_endswith(const std::string &str, const std::string &end) {
222 return str.rfind(end) == (str.size() - end.size());
223}
224#endif
225
226bool str_endswith_ignore_case(const char *str, size_t str_len, const char *suffix, size_t suffix_len) {
227 if (suffix_len > str_len)
228 return false;
229 return strncasecmp(str + str_len - suffix_len, suffix, suffix_len) == 0;
230}
231
232bool str_contains_ignore_case_fallback(const char *haystack, const char *needle) {
233 const size_t needle_len = strlen(needle);
234 if (needle_len == 0) {
235 return true;
236 }
237 for (const char *p = haystack; *p != '\0'; p++) {
238 if (strncasecmp(p, needle, needle_len) == 0) {
239 return true;
240 }
241 }
242 return false;
243}
244
245#ifdef USE_ESP8266
246// _P mirror of str_contains_ignore_case_fallback above; host tests cover only the fallback,
247// so keep the two bodies in sync.
248bool str_contains_ignore_case_p(const char *haystack, PGM_P needle) {
249 if (haystack == nullptr || needle == nullptr) {
250 return false;
251 }
252 const size_t needle_len = strlen_P(needle);
253 if (needle_len == 0) {
254 return true;
255 }
256 for (const char *p = haystack; *p != '\0'; p++) {
257 if (strncasecmp_P(p, needle, needle_len) == 0) {
258 return true;
259 }
260 }
261 return false;
262}
263#endif // USE_ESP8266
264
265char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str) {
266 if (buffer_size == 0) {
267 return buffer;
268 }
269 size_t i = 0;
270 while (*str && i < buffer_size - 1) {
271 buffer[i++] = to_sanitized_char(*str++);
272 }
273 buffer[i] = '\0';
274 return buffer;
275}
276
277size_t make_name_with_suffix_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, char sep,
278 const char *suffix_ptr, size_t suffix_len) {
279 size_t total_len = name_len + 1 + suffix_len;
280
281 // Silently truncate if needed: prioritize keeping the full suffix
282 if (total_len >= buffer_size) {
283 // NOTE: This calculation could underflow if suffix_len >= buffer_size - 2,
284 // but this is safe because this helper is only called with small suffixes:
285 // MAC suffixes (6-12 bytes), ".local" (5 bytes), etc.
286 name_len = buffer_size - suffix_len - 2; // -2 for separator and null terminator
287 total_len = name_len + 1 + suffix_len;
288 }
289
290 memcpy(buffer, name, name_len);
291 buffer[name_len] = sep;
292 memcpy(buffer + name_len + 1, suffix_ptr, suffix_len);
293 buffer[total_len] = '\0';
294 return total_len;
295}
296
297size_t truncate_name_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, size_t keep_suffix_len) {
298 if (buffer_size == 0) {
299 return 0;
300 }
301 const size_t max_len = buffer_size - 1;
302 if (name_len <= max_len) {
303 memcpy(buffer, name, name_len);
304 buffer[name_len] = '\0';
305 return name_len;
306 }
307 if (keep_suffix_len > max_len) {
308 keep_suffix_len = max_len;
309 }
310 const size_t head_len = max_len - keep_suffix_len;
311 memcpy(buffer, name, head_len);
312 memcpy(buffer + head_len, name + name_len - keep_suffix_len, keep_suffix_len);
313 buffer[max_len] = '\0';
314 return max_len;
315}
316
317// Parsing & formatting
318
319// A plain decimal converts exactly with one division: the digits accumulate
320// into an integer that stays below 2^24 and the fraction's scale is a power of
321// ten up to 1e9 (2^9 * 5^9), so both convert to float exactly and IEEE division
322// rounds the quotient correctly, the same result strtof produces.
323static constexpr uint32_t PARSE_FLOAT_MANTISSA_LIMIT = 1000000; // one more digit stays below 2^24
324static constexpr uint32_t PARSE_FLOAT_MAX_FRACTION_DIGITS = 9;
325
326optional<float> parse_float(const char *str) {
327 const char *p = str;
328 const bool negative = *p == '-';
329 if (negative || *p == '+')
330 p++;
331 const char *digits = p;
332 const char *dot = nullptr;
333 uint32_t mantissa = 0;
334 for (;; p++) {
335 const uint32_t d = static_cast<unsigned char>(*p) - '0';
336 if (d > 9) {
337 if (*p == '.' && dot == nullptr) {
338 dot = p;
339 continue;
340 }
341 break;
342 }
343 if (mantissa >= PARSE_FLOAT_MANTISSA_LIMIT)
344 break;
345 mantissa = mantissa * 10 + d;
346 }
347 const uint32_t fraction_digits = dot != nullptr ? p - dot - 1 : 0;
348 const bool any_digit = p - digits > (dot != nullptr ? 1 : 0);
349 if (*p == '\0' && any_digit && fraction_digits <= PARSE_FLOAT_MAX_FRACTION_DIGITS) {
350 // The mantissa is exact in a float, so the sign goes on before the one rounding step
351 float value = static_cast<float>(mantissa);
352 if (negative)
353 value = -value;
354 if (fraction_digits != 0) {
355 uint32_t scale = 10;
356 for (uint32_t i = 1; i < fraction_digits; i++)
357 scale *= 10;
358 value /= static_cast<float>(scale);
359 }
360 return value;
361 }
362 // Exponents, hex, inf, nan, leading whitespace and longer inputs take the libc path.
363 char *end = nullptr;
364 const float value = ::strtof(str, &end);
365 if (end == str || *end != '\0' || value == HUGE_VALF)
366 return {};
367 return value;
368}
369
370size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
371 size_t chars = std::min(length, 2 * count);
372 for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) {
373 uint8_t val = parse_hex_char(*str);
374 if (val == INVALID_HEX_CHAR)
375 return 0;
376 data[i >> 1] = (i & 1) ? data[i >> 1] | val : val << 4;
377 }
378 return chars;
379}
380
381// Internal helper for hex formatting - base is 'a' for lowercase or 'A' for uppercase.
382// When separator is set, it is written unconditionally after each byte and the last
383// one is overwritten with '\0', eliminating the per-byte `i < length - 1` check.
384static char *format_hex_internal(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator,
385 char base) {
386 if (length == 0 || buffer_size == 0) {
387 if (buffer_size > 0)
388 buffer[0] = '\0';
389 return buffer;
390 }
391 uint8_t stride = separator ? 3 : 2;
392 size_t max_bytes = separator ? (buffer_size / 3) : ((buffer_size - 1) / 2);
393 if (max_bytes == 0) {
394 buffer[0] = '\0';
395 return buffer;
396 }
397 if (length > max_bytes) {
398 length = max_bytes;
399 }
400 for (size_t i = 0; i < length; i++) {
401 size_t pos = i * stride;
402 buffer[pos] = format_hex_char(data[i] >> 4, base);
403 buffer[pos + 1] = format_hex_char(data[i] & 0x0F, base);
404 if (separator) {
405 buffer[pos + 2] = separator;
406 }
407 }
408 // With separator: overwrite last separator with '\0'
409 // Without: write '\0' after last hex char
410 buffer[length * stride - (separator ? 1 : 0)] = '\0';
411 return buffer;
412}
413
415 if (val == 0) {
416 *buf++ = '0';
417 return buf;
418 }
419 char *start = buf;
420 while (val > 0) {
421 *buf++ = '0' + (val % 10);
422 val /= 10;
423 }
424 std::reverse(start, buf);
425 return buf;
426}
427
428char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) {
429 return format_hex_internal(buffer, buffer_size, data, length, 0, 'a');
430}
431
432const char *json_escape_into_buffer(std::span<char> buf, StringRef value, bool short_control_escapes) {
433 if (buf.empty())
434 return "";
435 // Reserve one byte for the null terminator.
436 const size_t limit = buf.size() - 1;
437 size_t pos = 0;
438 for (char ch : value) {
439 auto c = static_cast<unsigned char>(ch);
440 // Every short form is a backslash followed by a single character, so only that character is needed here. Keeping
441 // it a char rather than a string avoids putting the sequences in read only data, which is RAM on the ESP8266.
442 char escape = '\0';
443 switch (c) {
444 case '"':
445 escape = '"';
446 break;
447 case '\\':
448 escape = '\\';
449 break;
450 case '\n':
451 escape = 'n';
452 break;
453 case '\r':
454 escape = 'r';
455 break;
456 case '\t':
457 escape = 't';
458 break;
459 case '\b':
460 escape = 'b';
461 break;
462 case '\f':
463 escape = 'f';
464 break;
465 default:
466 break;
467 }
468 // " and \ are always written as two characters, but the control characters fall through to \u00XX when the
469 // caller did not ask for the short forms.
470 if (!short_control_escapes && c < 0x20)
471 escape = '\0';
472 if (escape != '\0') {
473 if (pos + 2 > limit)
474 break;
475 buf[pos++] = '\\';
476 buf[pos++] = escape;
477 } else if (c < 0x20) {
478 // Remaining control characters have no short form and must be written as \u00XX. The value is below 0x20, so
479 // the two high hex digits are always zero.
480 if (pos + JSON_ESCAPE_MAX_EXPANSION > limit)
481 break;
482 buf[pos++] = '\\';
483 buf[pos++] = 'u';
484 buf[pos++] = '0';
485 buf[pos++] = '0';
486 buf[pos++] = format_hex_char(static_cast<uint8_t>(c >> 4));
487 buf[pos++] = format_hex_char(static_cast<uint8_t>(c & 0x0F));
488 } else {
489 if (pos + 1 > limit)
490 break;
491 buf[pos++] = static_cast<char>(c);
492 }
493 }
494 buf[pos] = '\0';
495 return buf.data();
496}
497
498char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator) {
499 return format_hex_internal(buffer, buffer_size, data, length, separator, 'A');
500}
501
502char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint16_t *data, size_t length, char separator) {
503 if (length == 0 || buffer_size == 0) {
504 if (buffer_size > 0)
505 buffer[0] = '\0';
506 return buffer;
507 }
508 // With separator: each uint16_t needs 5 chars (4 hex + 1 sep), except last has no separator
509 // Without separator: each uint16_t needs 4 chars, plus null terminator
510 uint8_t stride = separator ? 5 : 4;
511 size_t max_values = separator ? (buffer_size / stride) : ((buffer_size - 1) / stride);
512 if (max_values == 0) {
513 buffer[0] = '\0';
514 return buffer;
515 }
516 if (length > max_values) {
517 length = max_values;
518 }
519 for (size_t i = 0; i < length; i++) {
520 size_t pos = i * stride;
521 buffer[pos] = format_hex_pretty_char((data[i] & 0xF000) >> 12);
522 buffer[pos + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8);
523 buffer[pos + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4);
524 buffer[pos + 3] = format_hex_pretty_char(data[i] & 0x000F);
525 if (separator && i < length - 1) {
526 buffer[pos + 4] = separator;
527 }
528 }
529 buffer[length * stride - (separator ? 1 : 0)] = '\0';
530 return buffer;
531}
532
533char *format_bin_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) {
534 if (buffer_size == 0) {
535 return buffer;
536 }
537 // Calculate max bytes we can format: each byte needs 8 chars
538 size_t max_bytes = (buffer_size - 1) / 8;
539 if (max_bytes == 0 || length == 0) {
540 buffer[0] = '\0';
541 return buffer;
542 }
543 size_t bytes_to_format = std::min(length, max_bytes);
544
545 for (size_t byte_idx = 0; byte_idx < bytes_to_format; byte_idx++) {
546 for (size_t bit_idx = 0; bit_idx < 8; bit_idx++) {
547 buffer[byte_idx * 8 + bit_idx] = ((data[byte_idx] >> (7 - bit_idx)) & 1) + '0';
548 }
549 }
550 buffer[bytes_to_format * 8] = '\0';
551 return buffer;
552}
553
554ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
555 if (on == nullptr && ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("on")) == 0)
556 return PARSE_ON;
557 if (on != nullptr && strcasecmp(str, on) == 0)
558 return PARSE_ON;
559 if (off == nullptr && ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("off")) == 0)
560 return PARSE_OFF;
561 if (off != nullptr && strcasecmp(str, off) == 0)
562 return PARSE_OFF;
563 if (ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("toggle")) == 0)
564 return PARSE_TOGGLE;
565
566 return PARSE_NONE;
567}
568
569int8_t ilog10(float value) {
570 float abs_val = fabsf(value);
571 int8_t exp = 0;
572 if (abs_val >= 10.0f) {
573 while (abs_val >= 10.0f) {
574 abs_val /= 10.0f;
575 exp++;
576 }
577 } else if (abs_val < 1.0f) {
578 while (abs_val < 1.0f) {
579 abs_val *= 10.0f;
580 exp--;
581 }
582 }
583 return exp;
584}
585
586static inline void normalize_accuracy_decimals(float &value, int8_t &accuracy_decimals) {
587 if (accuracy_decimals < 0) {
588 float divisor;
589 if (accuracy_decimals == -1) {
590 divisor = 10.0f;
591 } else if (accuracy_decimals == -2) {
592 divisor = 100.0f;
593 } else {
594 divisor = pow10_int(-accuracy_decimals);
595 }
596 value = roundf(value / divisor) * divisor;
597 accuracy_decimals = 0;
598 }
599}
600
601// Fast float-to-string for accuracy_decimals 0-4 (covers sensors and small PID coefficients).
602// Avoids snprintf("%.*f") which pulls in heavy float formatting machinery.
603// Caller must guarantee value is finite and |value| * mult fits in uint32_t.
604static size_t value_accuracy_to_buf_fast(char *buf, float value, int8_t accuracy_decimals, uint32_t mult) {
605 char *p = buf;
606 if (std::signbit(value)) {
607 *p++ = '-';
608 value = -value;
609 }
610 // Cast to double for the multiply to match snprintf's rounding precision.
611 // float*int loses bits at exact-half boundaries (e.g. 23.45f*10 = 234.5 in float,
612 // but snprintf sees 234.500007... via double promotion and rounds differently).
613 // llrint returns long long so the result fits even on 32-bit targets where
614 // long is 32-bit; caller has already bounded |value * mult| to UINT32_MAX.
615 uint32_t scaled = static_cast<uint32_t>(llrint(static_cast<double>(value) * mult));
616 p = uint32_to_str_unchecked(p, scaled / mult);
617 if (accuracy_decimals > 0) {
618 *p++ = '.';
619 p = frac_to_str_unchecked(p, scaled % mult, mult / 10);
620 }
621 *p = '\0';
622 return static_cast<size_t>(p - buf);
623}
624
625size_t value_accuracy_to_buf(std::span<char, VALUE_ACCURACY_MAX_LEN> buf, float value, int8_t accuracy_decimals) {
626 normalize_accuracy_decimals(value, accuracy_decimals);
627
628 // Fast path for accuracy 0-4, finite values whose scaled magnitude fits in uint32_t.
629 // For 4 decimals that's |value| < ~4.29e5; larger totals fall through to snprintf.
630 if (accuracy_decimals <= 4 && std::isfinite(value)) {
631 const uint32_t mult = small_pow10(accuracy_decimals);
632 if (std::fabs(value) < static_cast<float>(UINT32_MAX) / mult) {
633 return value_accuracy_to_buf_fast(buf.data(), value, accuracy_decimals, mult);
634 }
635 }
636
637 // Fallback for NaN/Inf/high accuracy/out-of-range
638 int len = snprintf(buf.data(), buf.size(), "%.*f", accuracy_decimals, static_cast<double>(value));
639 if (len < 0)
640 return 0;
641 return static_cast<size_t>(len) >= buf.size() ? buf.size() - 1 : static_cast<size_t>(len);
642}
643
644size_t value_accuracy_with_uom_to_buf(std::span<char, VALUE_ACCURACY_MAX_LEN> buf, float value,
645 int8_t accuracy_decimals, StringRef unit_of_measurement) {
646 size_t len = value_accuracy_to_buf(buf, value, accuracy_decimals);
647 if (len == 0 || unit_of_measurement.empty()) {
648 return len;
649 }
650 char *end = buf_append_sep_str(buf.data() + len, buf.size() - len, ' ', unit_of_measurement.c_str(),
651 unit_of_measurement.size());
652 return static_cast<size_t>(end - buf.data());
653}
654
655int8_t step_to_accuracy_decimals(float step) {
656 // Decimals needed to show the step at five significant digits, trailing zeros dropped.
657 if (!std::isfinite(step) || step == 0.0f)
658 return 0;
659 float mantissa = std::fabs(step);
660 int8_t decimals = 4; // decimals needed for five significant digits when mantissa is in [1, 10)
661 while (mantissa >= 10.0f) {
662 mantissa /= 10.0f;
663 decimals--;
664 }
665 while (mantissa < 1.0f) {
666 mantissa *= 10.0f;
667 decimals++;
668 }
669 if (decimals <= 0)
670 return 0;
671 float scaled = mantissa * 10000.0f;
672 auto digits = static_cast<uint32_t>(scaled);
673 if (scaled - static_cast<float>(digits) >= 0.5f)
674 digits++;
675 while (decimals > 0 && digits % 10 == 0) {
676 digits /= 10;
677 decimals--;
678 }
679 return decimals;
680}
681
682static constexpr uint8_t INVALID_BASE64_CHAR = 0xFF;
683
684// 6-bit value of a base64 or base64url char, or INVALID_BASE64_CHAR.
685// No lookup table: .rodata lives in DRAM on ESP8266.
686static constexpr uint8_t base64_char_value(uint8_t c) {
687 if (c >= 'A' && c <= 'Z')
688 return c - 'A';
689 if (c >= 'a' && c <= 'z')
690 return c - 'a' + 26;
691 if (c >= '0' && c <= '9')
692 return c - '0' + 52;
693 // base64url variants: '-' maps to '+' (index 62), '_' maps to '/' (index 63)
694 if (c == '+' || c == '-')
695 return 62;
696 if (c == '/' || c == '_')
697 return 63;
698 return INVALID_BASE64_CHAR;
699}
700
701size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len) {
702 return base64_decode(reinterpret_cast<const uint8_t *>(encoded_string.data()), encoded_string.size(), buf, buf_len);
703}
704
705size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len) {
706 size_t out = 0;
707 uint32_t accum = 0;
708 uint32_t bits = 0;
709 // Stops at '=' or any non-alphabet char; leftover bits of a partial group are dropped.
710 for (size_t in = 0; in < encoded_len; in++) {
711 uint8_t value = base64_char_value(encoded_data[in]);
712 if (value == INVALID_BASE64_CHAR)
713 break;
714 accum = (accum << 6) | value;
715 bits += 6;
716 if (bits >= 8) {
717 bits -= 8;
718 if (out == buf_len) {
719 ESP_LOGW(TAG, "Base64 decode: buffer too small, truncating");
720 return out;
721 }
722 buf[out++] = static_cast<uint8_t>(accum >> bits);
723 }
724 }
725 return out;
726}
727
732bool base64_decode_int32_vector(const std::string &base64, std::vector<int32_t> &out) {
733 // Decode in chunks to minimize stack usage
734 constexpr size_t chunk_bytes = 48; // 12 int32 values
735 constexpr size_t chunk_chars = 64; // 48 * 4/3 = 64 chars
736 uint8_t chunk[chunk_bytes];
737
738 out.clear();
739
740 const uint8_t *input = reinterpret_cast<const uint8_t *>(base64.data());
741 size_t remaining = base64.size();
742 size_t pos = 0;
743
744 while (remaining > 0) {
745 size_t chars_to_decode = std::min(remaining, chunk_chars);
746 size_t decoded_len = base64_decode(input + pos, chars_to_decode, chunk, chunk_bytes);
747
748 if (decoded_len == 0)
749 return false;
750
751 // Parse little-endian int32 values
752 for (size_t i = 0; i + 3 < decoded_len; i += 4) {
753 int32_t timing = static_cast<int32_t>(encode_uint32(chunk[i + 3], chunk[i + 2], chunk[i + 1], chunk[i]));
754 out.push_back(timing);
755 }
756
757 // Check for incomplete int32 in last chunk
758 if (remaining <= chunk_chars && (decoded_len % 4) != 0)
759 return false;
760
761 pos += chars_to_decode;
762 remaining -= chars_to_decode;
763 }
764
765 return !out.empty();
766}
767
768// Colors
769
770void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) {
771 float max_color_value = std::max({red, green, blue});
772 float min_color_value = std::min({red, green, blue});
773 float delta = max_color_value - min_color_value;
774
775 if (delta == 0) {
776 hue = 0;
777 } else if (max_color_value == red) {
778 hue = int(fmodf((60.0f * ((green - blue) / delta)) + 360.0f, 360.0f));
779 } else if (max_color_value == green) {
780 hue = int(fmodf((60.0f * ((blue - red) / delta)) + 120.0f, 360.0f));
781 } else if (max_color_value == blue) {
782 hue = int(fmodf((60.0f * ((red - green) / delta)) + 240.0f, 360.0f));
783 }
784
785 if (max_color_value == 0) {
786 saturation = 0;
787 } else {
788 saturation = delta / max_color_value;
789 }
790
791 value = max_color_value;
792}
793void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue) {
794 float chroma = value * saturation;
795 float hue_prime = fmodf(hue / 60.0f, 6.0f);
796 float intermediate = chroma * (1.0f - fabsf(fmodf(hue_prime, 2.0f) - 1.0f));
797 float delta = value - chroma;
798
799 if (0 <= hue_prime && hue_prime < 1) {
800 red = chroma;
801 green = intermediate;
802 blue = 0;
803 } else if (1 <= hue_prime && hue_prime < 2) {
804 red = intermediate;
805 green = chroma;
806 blue = 0;
807 } else if (2 <= hue_prime && hue_prime < 3) {
808 red = 0;
809 green = chroma;
810 blue = intermediate;
811 } else if (3 <= hue_prime && hue_prime < 4) {
812 red = 0;
813 green = intermediate;
814 blue = chroma;
815 } else if (4 <= hue_prime && hue_prime < 5) {
816 red = intermediate;
817 green = 0;
818 blue = chroma;
819 } else if (5 <= hue_prime && hue_prime < 6) {
820 red = chroma;
821 green = 0;
822 blue = intermediate;
823 } else {
824 red = 0;
825 green = 0;
826 blue = 0;
827 }
828
829 red += delta;
830 green += delta;
831 blue += delta;
832}
833
834uint8_t HighFrequencyLoopRequester::num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
836 if (this->started_)
837 return;
838 num_requests++;
839 this->started_ = true;
840}
842 if (!this->started_)
843 return;
844 num_requests--;
845 this->started_ = false;
846}
847
848void get_mac_address_into_buffer(std::span<char, MAC_ADDRESS_BUFFER_SIZE> buf) {
849 uint8_t mac[MAC_ADDRESS_SIZE];
851 format_mac_addr_lower_no_sep(mac, buf.data());
852}
853
854const char *get_mac_address_pretty_into_buffer(std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
855 uint8_t mac[MAC_ADDRESS_SIZE];
857 format_mac_addr_upper(mac, buf.data());
858 return buf.data();
859}
860
861#ifndef USE_ESP32
862bool has_custom_mac_address() { return false; }
863#endif
864
865bool mac_address_is_valid(const uint8_t *mac) {
866 bool is_all_zeros = true;
867 bool is_all_ones = true;
868
869 for (uint8_t i = 0; i < 6; i++) {
870 if (mac[i] != 0) {
871 is_all_zeros = false;
872 }
873 if (mac[i] != 0xFF) {
874 is_all_ones = false;
875 }
876 }
877 if (is_all_zeros || is_all_ones) {
878 return false;
879 }
880 // Reject multicast MACs (bit 0 of first byte set) - device MACs must be unicast.
881 // This catches garbage data from corrupted eFuse custom MAC areas, which often
882 // has random values that would otherwise pass the all-zeros/all-ones check.
883 if (mac[0] & 0x01) {
884 return false;
885 }
886 return true;
887}
888
889void IRAM_ATTR HOT delay_microseconds_safe(uint32_t us) {
890 // avoids CPU locks that could trigger WDT or affect WiFi/BT stability
891 uint32_t start = micros();
892
893 constexpr uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop.
894 // it must be larger than the worst-case duration of a delay(1) call (hardware tasks)
895 // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known
896 if (us > lag) {
897 delay((us - lag) / 1000UL); // note: in disabled-interrupt contexts delay() won't actually sleep
898 while (micros() - start < us - lag)
899 delay(1); // in those cases, this loop allows to yield for BT/WiFi stack tasks
900 }
901 while (micros() - start < us) // fine delay the remaining usecs
902 ;
903}
904
905} // namespace esphome
void stop()
Stop running the loop continuously.
Definition helpers.cpp:841
void start()
Start running the loop continuously.
Definition helpers.cpp:835
StringRef is a reference to a string owned by something else.
Definition string_ref.h:31
constexpr const char * c_str() const
Definition string_ref.h:78
constexpr bool empty() const
Definition string_ref.h:81
constexpr size_type size() const
Definition string_ref.h:79
struct @66::@67 __attribute__
Wake the main loop task from an ISR. ISR-safe.
Definition main_task.h:45
std::vector< uint8_t > & out
Definition gif.cpp:49
unsigned count
Definition gif.cpp:51
uint32_t buffer
Definition gif.cpp:50
mopeka_std_values val[3]
bool z
Definition msa3xx.h:1
ESPHOME_LOG_TAG(TAG, CRASH_LOG_TAG)
value[LAYOUT.day_index()]
std::string progmem_string(ProgmemStr str)
Copies a string stored with ESPHOME_F into a std::string.
Definition helpers.cpp:24
bool random_bytes(uint8_t *data, size_t len)
Generate len random bytes using the platform's secure RNG (hardware RNG or OS CSPRNG).
Definition helpers.cpp:20
float random_float()
Return a random float between 0 and 1.
Definition helpers.cpp:206
ESPHOME_ALWAYS_INLINE char format_hex_char(uint8_t v, char base)
Convert a nibble (0-15) to hex char with specified base ('a' for lowercase, 'A' for uppercase)
Definition helpers.h:1456
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t reverse_poly, bool refin, bool refout)
Calculate a CRC-16 checksum of data with size len.
Definition helpers.cpp:95
size_t make_name_with_suffix_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, char sep, const char *suffix_ptr, size_t suffix_len)
Format name + separator + suffix directly into buffer without heap allocation.
Definition helpers.cpp:277
size_t truncate_name_to(char *buffer, size_t buffer_size, const char *name, size_t name_len, size_t keep_suffix_len)
Copy a name into a buffer, cutting it when it does not fit.
Definition helpers.cpp:297
constexpr char to_sanitized_char(char c)
Sanitize a single char: keep alphanumerics, dashes, underscores; replace others with underscore.
Definition helpers.h:1186
bool mac_address_is_valid(const uint8_t *mac)
Check if the MAC address is not all zeros or all ones.
Definition helpers.cpp:865
ESPHOME_ALWAYS_INLINE char format_hex_pretty_char(uint8_t v)
Convert a nibble (0-15) to uppercase hex char (used for pretty printing)
Definition helpers.h:1462
void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output)
Format MAC address as xxxxxxxxxxxxxx (lowercase, no separators)
Definition helpers.h:1681
constexpr uint32_t FNV1_OFFSET_BASIS
FNV-1 32-bit offset basis.
Definition helpers.h:923
void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value)
Convert red, green and blue (all 0-1) values to hue (0-360), saturation (0-1) and value (0-1).
Definition helpers.cpp:770
uint16_t uint16_t size_t elem_size
Definition helpers.cpp:35
size_t value_accuracy_to_buf(std::span< char, VALUE_ACCURACY_MAX_LEN > buf, float value, int8_t accuracy_decimals)
Format value with accuracy to buffer, returns chars written (excluding null)
Definition helpers.cpp:625
ParseOnOffState parse_on_off(const char *str, const char *on, const char *off)
Parse a string that contains either on, off or toggle.
Definition helpers.cpp:554
bool base64_decode_int32_vector(const std::string &base64, std::vector< int32_t > &out)
Decode base64/base64url string directly into vector of little-endian int32 values.
Definition helpers.cpp:732
const void size_t len
Definition hal.h:68
uint32_t small_pow10(int8_t n)
Return 10^n for small non-negative n (0-4) as uint32_t, avoiding float.
Definition helpers.h:1525
std::vector< uint8_t > base64_decode(const std::string &encoded_string)
Decode a base64 string to a byte vector.
bool has_custom_mac_address()
Check if a custom MAC address is set (ESP32 & variants)
Definition helpers.cpp:119
size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count)
Parse bytes from a hex-encoded string into a byte array.
Definition helpers.cpp:370
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:498
uint16_t size
Definition helpers.cpp:34
bool str_contains_ignore_case_fallback(const char *haystack, const char *needle)
Fallback implementation for case insensitive substring comparison.
Definition helpers.cpp:232
int8_t ilog10(float value)
Compute floor(log10(fabs(value))) using iterative comparison.
Definition helpers.cpp:569
uint32_t fnv1_hash(const char *str)
Calculate a FNV-1 hash of str.
Definition helpers.cpp:169
char * frac_to_str_unchecked(char *buf, uint32_t frac, uint32_t divisor)
Write fractional digits with leading zeros to buffer (internal, no size check).
Definition helpers.h:1545
int8_t step_to_accuracy_decimals(float step)
Derive accuracy in decimals from an increment step.
Definition helpers.cpp:655
uint32_t IRAM_ATTR HOT micros()
Definition hal.cpp:43
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition helpers.cpp:12
size_t size_t pos
Definition helpers.h:1255
const char * get_mac_address_pretty_into_buffer(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf)
Get the device MAC address into the given buffer, in colon-separated uppercase hex notation.
Definition helpers.cpp:854
char * buf_append_sep_str(char *buf, size_t remaining, char separator, const char *str, size_t str_len)
Append a separator char and a string to a buffer, respecting remaining space.
Definition helpers.h:1508
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:889
uint16_t new_cap
Definition helpers.cpp:37
bool str_equals_case_insensitive(const std::string &a, const std::string &b)
Compare strings for equality in case-insensitive manner.
Definition helpers.cpp:210
bool str_endswith_ignore_case(const char *str, size_t str_len, const char *suffix, size_t suffix_len)
Case-insensitive check if string ends with suffix (no heap allocation).
Definition helpers.cpp:226
char * str_sanitize_to(char *buffer, size_t buffer_size, const char *str)
Sanitize a string to buffer, keeping only alphanumerics, dashes, and underscores.
Definition helpers.cpp:265
size_t value_accuracy_with_uom_to_buf(std::span< char, VALUE_ACCURACY_MAX_LEN > buf, float value, int8_t accuracy_decimals, StringRef unit_of_measurement)
Format value with accuracy and UOM to buffer, returns chars written (excluding null)
Definition helpers.cpp:644
const char * json_escape_into_buffer(std::span< char > buf, StringRef value, bool short_control_escapes)
Copy value into buf, escaping the characters that cannot appear raw inside a JSON string literal.
Definition helpers.cpp:432
char * uint32_to_str_unchecked(char *buf, uint32_t val)
Write unsigned 32-bit integer to buffer (internal, no size check).
Definition helpers.cpp:414
constexpr uint32_t FNV1_PRIME
FNV-1 32-bit prime.
Definition helpers.h:925
void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue)
Convert hue (0-360), saturation (0-1) and value (0-1) to red, green and blue (all 0-1).
Definition helpers.cpp:793
void get_mac_address_into_buffer(std::span< char, MAC_ADDRESS_BUFFER_SIZE > buf)
Get the device MAC address into the given buffer, in lowercase hex notation.
Definition helpers.cpp:848
uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t poly, bool refin, bool refout)
Definition helpers.cpp:135
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:1006
const void * src
Definition hal.h:68
uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc, uint8_t poly, bool msb_first)
Calculate a CRC-8 checksum of data with size len.
Definition helpers.cpp:68
void get_mac_address_raw(uint8_t *mac)
Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
Definition helpers.cpp:87
constexpr uint8_t parse_hex_char(char c)
Definition helpers.h:1445
auto * new_data
Definition helpers.cpp:38
bool str_startswith(const std::string &str, const std::string &start)
Check whether a string starts with a value.
Definition helpers.cpp:217
void HOT delay(uint32_t ms)
Definition hal.cpp:85
optional< float > parse_float(const char *str)
Parse a decimal floating-point number; plain decimals skip the libc parser.
Definition helpers.cpp:326
char * format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length)
Format byte array as lowercase hex to buffer (base implementation).
Definition helpers.cpp:428
void * callback_manager_grow(void *data, uint16_t size, uint16_t &capacity, size_t elem_size)
Grow a CallbackManager's backing array to exactly size+1. Defined in helpers.cpp.
bool str_endswith(const std::string &str, const std::string &end)
Check whether a string ends with a value.
Definition helpers.cpp:218
bool str_contains_ignore_case_p(const char *haystack, PGM_P needle)
ESP8266 internal implementation reading the needle from flash — prefer the str_contains_ignore_case m...
Definition helpers.cpp:248
uint16_t uint16_t & capacity
Definition helpers.cpp:34
ParseOnOffState
Return values for parse_on_off().
Definition helpers.h:1739
@ PARSE_ON
Definition helpers.h:1741
@ PARSE_TOGGLE
Definition helpers.h:1743
@ PARSE_OFF
Definition helpers.h:1742
@ PARSE_NONE
Definition helpers.h:1740
float pow10_int(int8_t exp)
Compute 10^exp using iterative multiplication/division.
Definition helpers.h:874
char * format_bin_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length)
Format byte array as binary string to buffer.
Definition helpers.cpp:533
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
Definition helpers.h:1676
const __FlashStringHelper * ProgmemStr
Definition progmem.h:31
static void uint32_t
uint8_t end[39]
Definition sun_gtil2.cpp:17
uint16_t length
Definition tt21100.cpp:0