ESPHome  2022.11.3
helpers.cpp
Go to the documentation of this file.
1 #include "esphome/core/helpers.h"
2 
3 #include "esphome/core/defines.h"
4 #include "esphome/core/hal.h"
5 
6 #include <cstdio>
7 #include <algorithm>
8 #include <cctype>
9 #include <cmath>
10 #include <cstring>
11 #include <cstdarg>
12 
13 #if defined(USE_ESP8266)
14 #include <osapi.h>
15 #include <user_interface.h>
16 // for xt_rsil()/xt_wsr_ps()
17 #include <Arduino.h>
18 #elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
19 #include <Esp.h>
20 #elif defined(USE_ESP_IDF)
21 #include "esp_system.h"
22 #include <freertos/FreeRTOS.h>
23 #include <freertos/portmacro.h>
24 #elif defined(USE_RP2040)
25 #if defined(USE_WIFI)
26 #include <WiFi.h>
27 #endif
28 #include <hardware/structs/rosc.h>
29 #include <hardware/sync.h>
30 #endif
31 
32 #ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
33 #include "esp_efuse.h"
34 #include "esp_efuse_table.h"
35 #endif
36 
37 namespace esphome {
38 
39 // STL backports
40 
41 #if _GLIBCXX_RELEASE < 7
42 std::string to_string(int value) { return str_snprintf("%d", 32, value); } // NOLINT
43 std::string to_string(long value) { return str_snprintf("%ld", 32, value); } // NOLINT
44 std::string to_string(long long value) { return str_snprintf("%lld", 32, value); } // NOLINT
45 std::string to_string(unsigned value) { return str_snprintf("%u", 32, value); } // NOLINT
46 std::string to_string(unsigned long value) { return str_snprintf("%lu", 32, value); } // NOLINT
47 std::string to_string(unsigned long long value) { return str_snprintf("%llu", 32, value); } // NOLINT
48 std::string to_string(float value) { return str_snprintf("%f", 32, value); }
49 std::string to_string(double value) { return str_snprintf("%f", 32, value); }
50 std::string to_string(long double value) { return str_snprintf("%Lf", 32, value); }
51 #endif
52 
53 // Mathematics
54 
55 float lerp(float completion, float start, float end) { return start + (end - start) * completion; }
56 uint8_t crc8(uint8_t *data, uint8_t len) {
57  uint8_t crc = 0;
58 
59  while ((len--) != 0u) {
60  uint8_t inbyte = *data++;
61  for (uint8_t i = 8; i != 0u; i--) {
62  bool mix = (crc ^ inbyte) & 0x01;
63  crc >>= 1;
64  if (mix)
65  crc ^= 0x8C;
66  inbyte >>= 1;
67  }
68  }
69  return crc;
70 }
71 uint16_t crc16(const uint8_t *data, uint8_t len) {
72  uint16_t crc = 0xFFFF;
73  while (len--) {
74  crc ^= *data++;
75  for (uint8_t i = 0; i < 8; i++) {
76  if ((crc & 0x01) != 0) {
77  crc >>= 1;
78  crc ^= 0xA001;
79  } else {
80  crc >>= 1;
81  }
82  }
83  }
84  return crc;
85 }
86 uint32_t fnv1_hash(const std::string &str) {
87  uint32_t hash = 2166136261UL;
88  for (char c : str) {
89  hash *= 16777619UL;
90  hash ^= c;
91  }
92  return hash;
93 }
94 
95 uint32_t random_uint32() {
96 #ifdef USE_ESP32
97  return esp_random();
98 #elif defined(USE_ESP8266)
99  return os_random();
100 #elif defined(USE_RP2040)
101  uint32_t result = 0;
102  for (uint8_t i = 0; i < 32; i++) {
103  result <<= 1;
104  result |= rosc_hw->randombit;
105  }
106  return result;
107 #else
108 #error "No random source available for this configuration."
109 #endif
110 }
111 float random_float() { return static_cast<float>(random_uint32()) / static_cast<float>(UINT32_MAX); }
112 bool random_bytes(uint8_t *data, size_t len) {
113 #ifdef USE_ESP32
114  esp_fill_random(data, len);
115  return true;
116 #elif defined(USE_ESP8266)
117  return os_get_random(data, len) == 0;
118 #elif defined(USE_RP2040)
119  while (len-- != 0) {
120  uint8_t result = 0;
121  for (uint8_t i = 0; i < 8; i++) {
122  result <<= 1;
123  result |= rosc_hw->randombit;
124  }
125  *data++ = result;
126  }
127  return true;
128 #else
129 #error "No random source available for this configuration."
130 #endif
131 }
132 
133 // Strings
134 
135 bool str_equals_case_insensitive(const std::string &a, const std::string &b) {
136  return strcasecmp(a.c_str(), b.c_str()) == 0;
137 }
138 bool str_startswith(const std::string &str, const std::string &start) { return str.rfind(start, 0) == 0; }
139 bool str_endswith(const std::string &str, const std::string &end) {
140  return str.rfind(end) == (str.size() - end.size());
141 }
142 std::string str_truncate(const std::string &str, size_t length) {
143  return str.length() > length ? str.substr(0, length) : str;
144 }
145 std::string str_until(const char *str, char ch) {
146  char *pos = strchr(str, ch);
147  return pos == nullptr ? std::string(str) : std::string(str, pos - str);
148 }
149 std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); }
150 // wrapper around std::transform to run safely on functions from the ctype.h header
151 // see https://en.cppreference.com/w/cpp/string/byte/toupper#Notes
152 template<int (*fn)(int)> std::string str_ctype_transform(const std::string &str) {
153  std::string result;
154  result.resize(str.length());
155  std::transform(str.begin(), str.end(), result.begin(), [](unsigned char ch) { return fn(ch); });
156  return result;
157 }
158 std::string str_lower_case(const std::string &str) { return str_ctype_transform<std::tolower>(str); }
159 std::string str_upper_case(const std::string &str) { return str_ctype_transform<std::toupper>(str); }
160 std::string str_snake_case(const std::string &str) {
161  std::string result;
162  result.resize(str.length());
163  std::transform(str.begin(), str.end(), result.begin(), ::tolower);
164  std::replace(result.begin(), result.end(), ' ', '_');
165  return result;
166 }
167 std::string str_sanitize(const std::string &str) {
168  std::string out;
169  std::copy_if(str.begin(), str.end(), std::back_inserter(out), [](const char &c) {
170  return c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
171  });
172  return out;
173 }
174 std::string str_snprintf(const char *fmt, size_t len, ...) {
175  std::string str;
176  va_list args;
177 
178  str.resize(len);
179  va_start(args, len);
180  size_t out_length = vsnprintf(&str[0], len + 1, fmt, args);
181  va_end(args);
182 
183  if (out_length < len)
184  str.resize(out_length);
185 
186  return str;
187 }
188 std::string str_sprintf(const char *fmt, ...) {
189  std::string str;
190  va_list args;
191 
192  va_start(args, fmt);
193  size_t length = vsnprintf(nullptr, 0, fmt, args);
194  va_end(args);
195 
196  str.resize(length);
197  va_start(args, fmt);
198  vsnprintf(&str[0], length + 1, fmt, args);
199  va_end(args);
200 
201  return str;
202 }
203 
204 // Parsing & formatting
205 
206 size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
207  uint8_t val;
208  size_t chars = std::min(length, 2 * count);
209  for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) {
210  if (*str >= '0' && *str <= '9') {
211  val = *str - '0';
212  } else if (*str >= 'A' && *str <= 'F') {
213  val = 10 + (*str - 'A');
214  } else if (*str >= 'a' && *str <= 'f') {
215  val = 10 + (*str - 'a');
216  } else {
217  return 0;
218  }
219  data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val;
220  }
221  return chars;
222 }
223 
224 static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; }
225 std::string format_hex(const uint8_t *data, size_t length) {
226  std::string ret;
227  ret.resize(length * 2);
228  for (size_t i = 0; i < length; i++) {
229  ret[2 * i] = format_hex_char((data[i] & 0xF0) >> 4);
230  ret[2 * i + 1] = format_hex_char(data[i] & 0x0F);
231  }
232  return ret;
233 }
234 std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
235 
236 static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; }
237 std::string format_hex_pretty(const uint8_t *data, size_t length) {
238  if (length == 0)
239  return "";
240  std::string ret;
241  ret.resize(3 * length - 1);
242  for (size_t i = 0; i < length; i++) {
243  ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4);
244  ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F);
245  if (i != length - 1)
246  ret[3 * i + 2] = '.';
247  }
248  if (length > 4)
249  return ret + " (" + to_string(length) + ")";
250  return ret;
251 }
252 std::string format_hex_pretty(const std::vector<uint8_t> &data) { return format_hex_pretty(data.data(), data.size()); }
253 
254 std::string format_hex_pretty(const uint16_t *data, size_t length) {
255  if (length == 0)
256  return "";
257  std::string ret;
258  ret.resize(5 * length - 1);
259  for (size_t i = 0; i < length; i++) {
260  ret[5 * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12);
261  ret[5 * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8);
262  ret[5 * i + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4);
263  ret[5 * i + 3] = format_hex_pretty_char(data[i] & 0x000F);
264  if (i != length - 1)
265  ret[5 * i + 2] = '.';
266  }
267  if (length > 4)
268  return ret + " (" + to_string(length) + ")";
269  return ret;
270 }
271 std::string format_hex_pretty(const std::vector<uint16_t> &data) { return format_hex_pretty(data.data(), data.size()); }
272 
273 ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
274  if (on == nullptr && strcasecmp(str, "on") == 0)
275  return PARSE_ON;
276  if (on != nullptr && strcasecmp(str, on) == 0)
277  return PARSE_ON;
278  if (off == nullptr && strcasecmp(str, "off") == 0)
279  return PARSE_OFF;
280  if (off != nullptr && strcasecmp(str, off) == 0)
281  return PARSE_OFF;
282  if (strcasecmp(str, "toggle") == 0)
283  return PARSE_TOGGLE;
284 
285  return PARSE_NONE;
286 }
287 
288 std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
289  if (accuracy_decimals < 0) {
290  auto multiplier = powf(10.0f, accuracy_decimals);
291  value = roundf(value * multiplier) / multiplier;
292  accuracy_decimals = 0;
293  }
294  char tmp[32]; // should be enough, but we should maybe improve this at some point.
295  snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value);
296  return std::string(tmp);
297 }
298 
299 int8_t step_to_accuracy_decimals(float step) {
300  // use printf %g to find number of digits based on temperature step
301  char buf[32];
302  sprintf(buf, "%.5g", step);
303 
304  std::string str{buf};
305  size_t dot_pos = str.find('.');
306  if (dot_pos == std::string::npos)
307  return 0;
308 
309  return str.length() - dot_pos - 1;
310 }
311 
312 // Colors
313 
314 float gamma_correct(float value, float gamma) {
315  if (value <= 0.0f)
316  return 0.0f;
317  if (gamma <= 0.0f)
318  return value;
319 
320  return powf(value, gamma);
321 }
322 float gamma_uncorrect(float value, float gamma) {
323  if (value <= 0.0f)
324  return 0.0f;
325  if (gamma <= 0.0f)
326  return value;
327 
328  return powf(value, 1 / gamma);
329 }
330 
331 void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) {
332  float max_color_value = std::max(std::max(red, green), blue);
333  float min_color_value = std::min(std::min(red, green), blue);
334  float delta = max_color_value - min_color_value;
335 
336  if (delta == 0) {
337  hue = 0;
338  } else if (max_color_value == red) {
339  hue = int(fmod(((60 * ((green - blue) / delta)) + 360), 360));
340  } else if (max_color_value == green) {
341  hue = int(fmod(((60 * ((blue - red) / delta)) + 120), 360));
342  } else if (max_color_value == blue) {
343  hue = int(fmod(((60 * ((red - green) / delta)) + 240), 360));
344  }
345 
346  if (max_color_value == 0) {
347  saturation = 0;
348  } else {
349  saturation = delta / max_color_value;
350  }
351 
352  value = max_color_value;
353 }
354 void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue) {
355  float chroma = value * saturation;
356  float hue_prime = fmod(hue / 60.0, 6);
357  float intermediate = chroma * (1 - fabs(fmod(hue_prime, 2) - 1));
358  float delta = value - chroma;
359 
360  if (0 <= hue_prime && hue_prime < 1) {
361  red = chroma;
362  green = intermediate;
363  blue = 0;
364  } else if (1 <= hue_prime && hue_prime < 2) {
365  red = intermediate;
366  green = chroma;
367  blue = 0;
368  } else if (2 <= hue_prime && hue_prime < 3) {
369  red = 0;
370  green = chroma;
371  blue = intermediate;
372  } else if (3 <= hue_prime && hue_prime < 4) {
373  red = 0;
374  green = intermediate;
375  blue = chroma;
376  } else if (4 <= hue_prime && hue_prime < 5) {
377  red = intermediate;
378  green = 0;
379  blue = chroma;
380  } else if (5 <= hue_prime && hue_prime < 6) {
381  red = chroma;
382  green = 0;
383  blue = intermediate;
384  } else {
385  red = 0;
386  green = 0;
387  blue = 0;
388  }
389 
390  red += delta;
391  green += delta;
392  blue += delta;
393 }
394 
395 // System APIs
396 
397 #if defined(USE_ESP8266)
398 IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); }
399 IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); }
400 #elif defined(USE_ESP32)
401 // only affects the executing core
402 // so should not be used as a mutex lock, only to get accurate timing
403 IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
404 IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
405 #elif defined(USE_RP2040)
406 IRAM_ATTR InterruptLock::InterruptLock() { state_ = save_and_disable_interrupts(); }
407 IRAM_ATTR InterruptLock::~InterruptLock() { restore_interrupts(state_); }
408 #endif
409 
410 uint8_t HighFrequencyLoopRequester::num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
412  if (this->started_)
413  return;
414  num_requests++;
415  this->started_ = true;
416 }
418  if (!this->started_)
419  return;
420  num_requests--;
421  this->started_ = false;
422 }
423 bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0; }
424 
425 void get_mac_address_raw(uint8_t *mac) {
426 #if defined(USE_ESP32)
427 #if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC)
428  // On some devices, the MAC address that is burnt into EFuse does not
429  // match the CRC that goes along with it. For those devices, this
430  // work-around reads and uses the MAC address as-is from EFuse,
431  // without doing the CRC check.
432  esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48);
433 #else
434  esp_efuse_mac_get_default(mac);
435 #endif
436 #elif defined(USE_ESP8266)
437  wifi_get_macaddr(STATION_IF, mac);
438 #elif defined(USE_RP2040) && defined(USE_WIFI)
439  WiFi.macAddress(mac);
440 #endif
441 }
442 std::string get_mac_address() {
443  uint8_t mac[6];
444  get_mac_address_raw(mac);
445  return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
446 }
447 std::string get_mac_address_pretty() {
448  uint8_t mac[6];
449  get_mac_address_raw(mac);
450  return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
451 }
452 #ifdef USE_ESP32
453 void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
454 #endif
455 
456 void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability
457  uint32_t start = micros();
458  const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop.
459  // it must be larger than the worst-case duration of a delay(1) call (hardware tasks)
460  // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known
461  if (us > lag) {
462  delay((us - lag) / 1000UL); // note: in disabled-interrupt contexts delay() won't actually sleep
463  while (micros() - start < us - lag)
464  delay(1); // in those cases, this loop allows to yield for BT/WiFi stack tasks
465  }
466  while (micros() - start < us) // fine delay the remaining usecs
467  ;
468 }
469 
470 } // namespace esphome
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:354
std::string str_snake_case(const std::string &str)
Convert the string to snake case (lowercase with underscores).
Definition: helpers.cpp:160
std::string str_truncate(const std::string &str, size_t length)
Truncate a string to a specific length.
Definition: helpers.cpp:142
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals)
Create a string from a value and an accuracy in decimals.
Definition: helpers.cpp:288
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
Definition: helpers.cpp:237
static bool is_high_frequency()
Check whether the loop is running continuously.
Definition: helpers.cpp:423
std::string str_upper_case(const std::string &str)
Convert the string to upper case.
Definition: helpers.cpp:159
std::string format_hex(const uint8_t *data, size_t length)
Format the byte array data of length len in lowercased hex.
Definition: helpers.cpp:225
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:206
uint32_t random_uint32()
Return a random 32-bit unsigned integer.
Definition: helpers.cpp:95
std::string str_until(const char *str, char ch)
Extract the part of the string until either the first occurrence of the specified character...
Definition: helpers.cpp:145
uint8_t crc8(uint8_t *data, uint8_t len)
Calculate a CRC-8 checksum of data with size len.
Definition: helpers.cpp:56
std::string str_ctype_transform(const std::string &str)
Definition: helpers.cpp:152
float lerp(float completion, float start, float end)
Linearly interpolate between start and end by completion (between 0 and 1).
Definition: helpers.cpp:55
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:456
uint32_t IRAM_ATTR HOT micros()
Definition: core.cpp:28
bool random_bytes(uint8_t *data, size_t len)
Generate len number of random bytes.
Definition: helpers.cpp:112
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:273
uint16_t crc16(const uint8_t *data, uint8_t len)
Calculate a CRC-16 checksum of data with size len.
Definition: helpers.cpp:71
ParseOnOffState
Return values for parse_on_off().
Definition: helpers.h:409
float gamma_correct(float value, float gamma)
Applies gamma correction of gamma to value.
Definition: helpers.cpp:314
bool str_startswith(const std::string &str, const std::string &start)
Check whether a string starts with a value.
Definition: helpers.cpp:138
void start()
Start running the loop continuously.
Definition: helpers.cpp:411
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:331
std::string str_lower_case(const std::string &str)
Convert the string to lower case.
Definition: helpers.cpp:158
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:188
std::string get_mac_address()
Get the device MAC address as a string, in lowercase hex notation.
Definition: helpers.cpp:442
bool str_endswith(const std::string &str, const std::string &end)
Check whether a string ends with a value.
Definition: helpers.cpp:139
void stop()
Stop running the loop continuously.
Definition: helpers.cpp:417
void set_mac_address(uint8_t *mac)
Set the MAC address to use from the provided byte array (6 bytes).
Definition: helpers.cpp:453
int8_t step_to_accuracy_decimals(float step)
Derive accuracy in decimals from an increment step.
Definition: helpers.cpp:299
std::string str_sanitize(const std::string &str)
Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores...
Definition: helpers.cpp:167
std::string to_string(int value)
Definition: helpers.cpp:42
std::string size_t len
Definition: helpers.h:281
uint32_t fnv1_hash(const std::string &str)
Calculate a FNV-1 hash of str.
Definition: helpers.cpp:86
Definition: a4988.cpp:4
uint32_t val
Definition: datatypes.h:85
std::string get_mac_address_pretty()
Get the device MAC address as a string, in colon-separated uppercase hex notation.
Definition: helpers.cpp:447
std::string str_snprintf(const char *fmt, size_t len,...)
Definition: helpers.cpp:174
float random_float()
Return a random float between 0 and 1.
Definition: helpers.cpp:111
bool str_equals_case_insensitive(const std::string &a, const std::string &b)
Compare strings for equality in case-insensitive manner.
Definition: helpers.cpp:135
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:27
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:425
float gamma_uncorrect(float value, float gamma)
Reverts gamma correction of gamma to value.
Definition: helpers.cpp:322