diff --git a/esphome/core/automation.h b/esphome/core/automation.h index f43fb98f20..92bc32247b 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -8,6 +8,11 @@ namespace esphome { +// https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971 +template struct seq {}; // NOLINT +template struct gens : gens {}; // NOLINT +template struct gens<0, S...> { using type = seq; }; // NOLINT + #define TEMPLATABLE_VALUE_(type, name) \ protected: \ TemplatableValue name##_{}; \ diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index dc85bdb17d..c3383489b1 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -1,5 +1,8 @@ #include "esphome/core/helpers.h" + #include "esphome/core/defines.h" +#include "esphome/core/hal.h" + #include #include #include @@ -18,95 +21,31 @@ #include #include #endif + #ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC #include "esp_efuse.h" #include "esp_efuse_table.h" #endif -#include "esphome/core/log.h" -#include "esphome/core/hal.h" - namespace esphome { -static const char *const TAG = "helpers"; +// STL backports -void get_mac_address_raw(uint8_t *mac) { -#if defined(USE_ESP32) -#if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC) - // On some devices, the MAC address that is burnt into EFuse does not - // match the CRC that goes along with it. For those devices, this - // work-around reads and uses the MAC address as-is from EFuse, - // without doing the CRC check. - esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48); -#else - esp_efuse_mac_get_default(mac); -#endif -#elif defined(USE_ESP8266) - wifi_get_macaddr(STATION_IF, mac); -#endif -} - -std::string get_mac_address() { - uint8_t mac[6]; - get_mac_address_raw(mac); - return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); -} - -std::string get_mac_address_pretty() { - uint8_t mac[6]; - get_mac_address_raw(mac); - return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); -} - -#ifdef USE_ESP32 -void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } +#if _GLIBCXX_RELEASE < 7 +std::string to_string(int value) { return str_snprintf("%d", 32, value); } // NOLINT +std::string to_string(long value) { return str_snprintf("%ld", 32, value); } // NOLINT +std::string to_string(long long value) { return str_snprintf("%lld", 32, value); } // NOLINT +std::string to_string(unsigned value) { return str_snprintf("%u", 32, value); } // NOLINT +std::string to_string(unsigned long value) { return str_snprintf("%lu", 32, value); } // NOLINT +std::string to_string(unsigned long long value) { return str_snprintf("%llu", 32, value); } // NOLINT +std::string to_string(float value) { return str_snprintf("%f", 32, value); } +std::string to_string(double value) { return str_snprintf("%f", 32, value); } +std::string to_string(long double value) { return str_snprintf("%Lf", 32, value); } #endif -std::string generate_hostname(const std::string &base) { return base + std::string("-") + get_mac_address(); } - -float gamma_correct(float value, float gamma) { - if (value <= 0.0f) - return 0.0f; - if (gamma <= 0.0f) - return value; - - return powf(value, gamma); -} -float gamma_uncorrect(float value, float gamma) { - if (value <= 0.0f) - return 0.0f; - if (gamma <= 0.0f) - return value; - - return powf(value, 1 / gamma); -} - -std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { - if (accuracy_decimals < 0) { - auto multiplier = powf(10.0f, accuracy_decimals); - value = roundf(value * multiplier) / multiplier; - accuracy_decimals = 0; - } - char tmp[32]; // should be enough, but we should maybe improve this at some point. - snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); - return std::string(tmp); -} - -ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { - if (on == nullptr && strcasecmp(str, "on") == 0) - return PARSE_ON; - if (on != nullptr && strcasecmp(str, on) == 0) - return PARSE_ON; - if (off == nullptr && strcasecmp(str, "off") == 0) - return PARSE_OFF; - if (off != nullptr && strcasecmp(str, off) == 0) - return PARSE_OFF; - if (strcasecmp(str, "toggle") == 0) - return PARSE_TOGGLE; - - return PARSE_NONE; -} +// Mathematics +float lerp(float completion, float start, float end) { return start + (end - start) * completion; } uint8_t crc8(uint8_t *data, uint8_t len) { uint8_t crc = 0; @@ -122,21 +61,6 @@ uint8_t crc8(uint8_t *data, uint8_t len) { } return crc; } - -void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability - auto start = micros(); - const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop. - // it must be larger than the worst-case duration of a delay(1) call (hardware tasks) - // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known - if (us > lag) { - delay((us - lag) / 1000UL); // note: in disabled-interrupt contexts delay() won't actually sleep - while (micros() - start < us - lag) - delay(1); // in those cases, this loop allows to yield for BT/WiFi stack tasks - } - while (micros() - start < us) // fine delay the remaining usecs - ; -} - uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { @@ -145,139 +69,6 @@ uint32_t fnv1_hash(const std::string &str) { } return hash; } -bool str_equals_case_insensitive(const std::string &a, const std::string &b) { - return strcasecmp(a.c_str(), b.c_str()) == 0; -} - -static int high_freq_num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - -void HighFrequencyLoopRequester::start() { - if (this->started_) - return; - high_freq_num_requests++; - this->started_ = true; -} -void HighFrequencyLoopRequester::stop() { - if (!this->started_) - return; - high_freq_num_requests--; - this->started_ = false; -} -bool HighFrequencyLoopRequester::is_high_frequency() { return high_freq_num_requests > 0; } - -float lerp(float completion, float start, float end) { return start + (end - start) * completion; } - -bool str_startswith(const std::string &full, const std::string &start) { return full.rfind(start, 0) == 0; } -bool str_endswith(const std::string &full, const std::string &ending) { - return full.rfind(ending) == (full.size() - ending.size()); -} -std::string str_snprintf(const char *fmt, size_t length, ...) { - std::string str; - va_list args; - - str.resize(length); - va_start(args, length); - size_t out_length = vsnprintf(&str[0], length + 1, fmt, args); - va_end(args); - - if (out_length < length) - str.resize(out_length); - - return str; -} -std::string str_sprintf(const char *fmt, ...) { - std::string str; - va_list args; - - va_start(args, fmt); - size_t length = vsnprintf(nullptr, 0, fmt, args); - va_end(args); - - str.resize(length); - va_start(args, fmt); - vsnprintf(&str[0], length + 1, fmt, args); - va_end(args); - - return str; -} - -void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) { - float max_color_value = std::max(std::max(red, green), blue); - float min_color_value = std::min(std::min(red, green), blue); - float delta = max_color_value - min_color_value; - - if (delta == 0) { - hue = 0; - } else if (max_color_value == red) { - hue = int(fmod(((60 * ((green - blue) / delta)) + 360), 360)); - } else if (max_color_value == green) { - hue = int(fmod(((60 * ((blue - red) / delta)) + 120), 360)); - } else if (max_color_value == blue) { - hue = int(fmod(((60 * ((red - green) / delta)) + 240), 360)); - } - - if (max_color_value == 0) { - saturation = 0; - } else { - saturation = delta / max_color_value; - } - - value = max_color_value; -} - -void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue) { - float chroma = value * saturation; - float hue_prime = fmod(hue / 60.0, 6); - float intermediate = chroma * (1 - fabs(fmod(hue_prime, 2) - 1)); - float delta = value - chroma; - - if (0 <= hue_prime && hue_prime < 1) { - red = chroma; - green = intermediate; - blue = 0; - } else if (1 <= hue_prime && hue_prime < 2) { - red = intermediate; - green = chroma; - blue = 0; - } else if (2 <= hue_prime && hue_prime < 3) { - red = 0; - green = chroma; - blue = intermediate; - } else if (3 <= hue_prime && hue_prime < 4) { - red = 0; - green = intermediate; - blue = chroma; - } else if (4 <= hue_prime && hue_prime < 5) { - red = intermediate; - green = 0; - blue = chroma; - } else if (5 <= hue_prime && hue_prime < 6) { - red = chroma; - green = 0; - blue = intermediate; - } else { - red = 0; - green = 0; - blue = 0; - } - - red += delta; - green += delta; - blue += delta; -} - -#ifdef USE_ESP8266 -IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } -IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } -#endif -#ifdef USE_ESP32 -IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } -IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } -#endif - -// --------------------------------------------------------------------------------------------------------------------- - -// Mathematics uint32_t random_uint32() { #ifdef USE_ESP32 @@ -302,6 +93,13 @@ bool random_bytes(uint8_t *data, size_t len) { // Strings +bool str_equals_case_insensitive(const std::string &a, const std::string &b) { + return strcasecmp(a.c_str(), b.c_str()) == 0; +} +bool str_startswith(const std::string &str, const std::string &start) { return str.rfind(start, 0) == 0; } +bool str_endswith(const std::string &str, const std::string &end) { + return str.rfind(end) == (str.size() - end.size()); +} std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } @@ -334,6 +132,35 @@ std::string str_sanitize(const std::string &str) { }); return out; } +std::string str_snprintf(const char *fmt, size_t len, ...) { + std::string str; + va_list args; + + str.resize(len); + va_start(args, len); + size_t out_length = vsnprintf(&str[0], len + 1, fmt, args); + va_end(args); + + if (out_length < len) + str.resize(out_length); + + return str; +} +std::string str_sprintf(const char *fmt, ...) { + std::string str; + va_list args; + + va_start(args, fmt); + size_t length = vsnprintf(nullptr, 0, fmt, args); + va_end(args); + + str.resize(length); + va_start(args, fmt); + vsnprintf(&str[0], length + 1, fmt, args); + va_end(args); + + return str; +} // Parsing & formatting @@ -385,4 +212,181 @@ std::string format_hex_pretty(const uint8_t *data, size_t length) { } std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } +ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { + if (on == nullptr && strcasecmp(str, "on") == 0) + return PARSE_ON; + if (on != nullptr && strcasecmp(str, on) == 0) + return PARSE_ON; + if (off == nullptr && strcasecmp(str, "off") == 0) + return PARSE_OFF; + if (off != nullptr && strcasecmp(str, off) == 0) + return PARSE_OFF; + if (strcasecmp(str, "toggle") == 0) + return PARSE_TOGGLE; + + return PARSE_NONE; +} + +std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { + if (accuracy_decimals < 0) { + auto multiplier = powf(10.0f, accuracy_decimals); + value = roundf(value * multiplier) / multiplier; + accuracy_decimals = 0; + } + char tmp[32]; // should be enough, but we should maybe improve this at some point. + snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); + return std::string(tmp); +} + +// Colors + +float gamma_correct(float value, float gamma) { + if (value <= 0.0f) + return 0.0f; + if (gamma <= 0.0f) + return value; + + return powf(value, gamma); +} +float gamma_uncorrect(float value, float gamma) { + if (value <= 0.0f) + return 0.0f; + if (gamma <= 0.0f) + return value; + + return powf(value, 1 / gamma); +} + +void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) { + float max_color_value = std::max(std::max(red, green), blue); + float min_color_value = std::min(std::min(red, green), blue); + float delta = max_color_value - min_color_value; + + if (delta == 0) { + hue = 0; + } else if (max_color_value == red) { + hue = int(fmod(((60 * ((green - blue) / delta)) + 360), 360)); + } else if (max_color_value == green) { + hue = int(fmod(((60 * ((blue - red) / delta)) + 120), 360)); + } else if (max_color_value == blue) { + hue = int(fmod(((60 * ((red - green) / delta)) + 240), 360)); + } + + if (max_color_value == 0) { + saturation = 0; + } else { + saturation = delta / max_color_value; + } + + value = max_color_value; +} +void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue) { + float chroma = value * saturation; + float hue_prime = fmod(hue / 60.0, 6); + float intermediate = chroma * (1 - fabs(fmod(hue_prime, 2) - 1)); + float delta = value - chroma; + + if (0 <= hue_prime && hue_prime < 1) { + red = chroma; + green = intermediate; + blue = 0; + } else if (1 <= hue_prime && hue_prime < 2) { + red = intermediate; + green = chroma; + blue = 0; + } else if (2 <= hue_prime && hue_prime < 3) { + red = 0; + green = chroma; + blue = intermediate; + } else if (3 <= hue_prime && hue_prime < 4) { + red = 0; + green = intermediate; + blue = chroma; + } else if (4 <= hue_prime && hue_prime < 5) { + red = intermediate; + green = 0; + blue = chroma; + } else if (5 <= hue_prime && hue_prime < 6) { + red = chroma; + green = 0; + blue = intermediate; + } else { + red = 0; + green = 0; + blue = 0; + } + + red += delta; + green += delta; + blue += delta; +} + +// System APIs + +#if defined(USE_ESP8266) +IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } +IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } +#elif defined(USE_ESP32) +IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } +IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } +#endif + +uint8_t HighFrequencyLoopRequester::num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +void HighFrequencyLoopRequester::start() { + if (this->started_) + return; + num_requests++; + this->started_ = true; +} +void HighFrequencyLoopRequester::stop() { + if (!this->started_) + return; + num_requests--; + this->started_ = false; +} +bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0; } + +void get_mac_address_raw(uint8_t *mac) { +#if defined(USE_ESP32) +#if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC) + // On some devices, the MAC address that is burnt into EFuse does not + // match the CRC that goes along with it. For those devices, this + // work-around reads and uses the MAC address as-is from EFuse, + // without doing the CRC check. + esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48); +#else + esp_efuse_mac_get_default(mac); +#endif +#elif defined(USE_ESP8266) + wifi_get_macaddr(STATION_IF, mac); +#endif +} +std::string get_mac_address() { + uint8_t mac[6]; + get_mac_address_raw(mac); + return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} +std::string get_mac_address_pretty() { + uint8_t mac[6]; + get_mac_address_raw(mac); + return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} +#ifdef USE_ESP32 +void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } +#endif + +void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability + uint32_t start = micros(); + const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop. + // it must be larger than the worst-case duration of a delay(1) call (hardware tasks) + // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known + if (us > lag) { + delay((us - lag) / 1000UL); // note: in disabled-interrupt contexts delay() won't actually sleep + while (micros() - start < us - lag) + delay(1); // in those cases, this loop allows to yield for BT/WiFi stack tasks + } + while (micros() - start < us) // fine delay the remaining usecs + ; +} + } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 95bf10abc3..e0763d2c71 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -2,19 +2,18 @@ #include #include - -#include #include -#include #include +#include #include +#include + +#include "esphome/core/optional.h" #ifdef USE_ESP32 #include #endif -#include "esphome/core/optional.h" - #define HOT __attribute__((hot)) #define ESPDEPRECATED(msg, when) __attribute__((deprecated(msg))) #define ALWAYS_INLINE __attribute__((always_inline)) @@ -30,212 +29,26 @@ namespace esphome { -/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). -void get_mac_address_raw(uint8_t *mac); - -/// Get the device MAC address as a string, in lowercase hex notation. -std::string get_mac_address(); - -/// Get the device MAC address as a string, in colon-separated uppercase hex notation. -std::string get_mac_address_pretty(); - -#ifdef USE_ESP32 -/// Set the MAC address to use from the provided byte array (6 bytes). -void set_mac_address(uint8_t *mac); -#endif - -/// Compare string a to string b (ignoring case) and return whether they are equal. -bool str_equals_case_insensitive(const std::string &a, const std::string &b); -bool str_startswith(const std::string &full, const std::string &start); -bool str_endswith(const std::string &full, const std::string &ending); - -/// snprintf-like function returning std::string with a given maximum length. -std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t length, ...); - -/// sprintf-like function returning std::string. -std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); - -class HighFrequencyLoopRequester { - public: - void start(); - void stop(); - - static bool is_high_frequency(); - - protected: - bool started_{false}; -}; - -/** Linearly interpolate between end start and end by completion. - * - * @tparam T The input/output typename. - * @param start The start value. - * @param end The end value. - * @param completion The completion. 0 is start value, 1 is end value. - * @return The linearly interpolated value. - */ -float lerp(float completion, float start, float end); - -// Not all platforms we support target C++14 yet, so we can't unconditionally use std::make_unique. Provide our own -// implementation if needed, and otherwise pull std::make_unique into scope so that we have a uniform API. -#if __cplusplus >= 201402L -using std::make_unique; -#else -template std::unique_ptr make_unique(Args &&...args) { - return std::unique_ptr(new T(std::forward(args)...)); -} -#endif - -/// Applies gamma correction with the provided gamma to value. -float gamma_correct(float value, float gamma); -/// Reverts gamma correction with the provided gamma to value. -float gamma_uncorrect(float value, float gamma); - -/// Create a string from a value and an accuracy in decimals. -std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); - -/// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1) -void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); -/// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1) -void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue); - -/// Convert degrees Celsius to degrees Fahrenheit. -static inline float celsius_to_fahrenheit(float value) { return value * 1.8f + 32.0f; } -/// Convert degrees Fahrenheit to degrees Celsius. -static inline float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1.8f; } - -/*** - * An interrupt helper class. - * - * This behaves like std::lock_guard. As long as the value is visible in the current stack, all interrupts - * (including flash reads) will be disabled. - * - * Please note all functions called when the interrupt lock must be marked IRAM_ATTR (loading code into - * instruction cache is done via interrupts; disabling interrupts prevents data not already in cache from being - * pulled from flash). - * - * Example: - * - * ```cpp - * // interrupts are enabled - * { - * InterruptLock lock; - * // do something - * // interrupts are disabled - * } - * // interrupts are enabled - * ``` - */ -class InterruptLock { - public: - InterruptLock(); - ~InterruptLock(); - - protected: -#ifdef USE_ESP8266 - uint32_t xt_state_; -#endif -}; - -/// Calculate a crc8 of data with the provided data length. -uint8_t crc8(uint8_t *data, uint8_t len); - -enum ParseOnOffState { - PARSE_NONE = 0, - PARSE_ON, - PARSE_OFF, - PARSE_TOGGLE, -}; - -ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); - -// https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971 -template struct seq {}; // NOLINT -template struct gens : gens {}; // NOLINT -template struct gens<0, S...> { using type = seq; }; // NOLINT - -template using enable_if_t = typename std::enable_if::type; - -template::value, int> = 0> T id(T value) { return value; } -template::value, int> = 0> T &id(T *value) { return *value; } - -template class CallbackManager; - -/** Simple helper class to allow having multiple subscribers to a signal. - * - * @tparam Ts The arguments for the callback, wrapped in void(). - */ -template class CallbackManager { - public: - /// Add a callback to the internal callback list. - void add(std::function &&callback) { this->callbacks_.push_back(std::move(callback)); } - - /// Call all callbacks in this manager. - void call(Ts... args) { - for (auto &cb : this->callbacks_) - cb(args...); - } - - /// Call all callbacks in this manager. - void operator()(Ts... args) { call(args...); } - - protected: - std::vector> callbacks_; -}; - -void delay_microseconds_safe(uint32_t us); - -template class Deduplicator { - public: - bool next(T value) { - if (this->has_value_) { - if (this->last_value_ == value) - return false; - } - this->has_value_ = true; - this->last_value_ = value; - return true; - } - bool has_value() const { return this->has_value_; } - - protected: - bool has_value_{false}; - T last_value_{}; -}; - -template class Parented { - public: - Parented() {} - Parented(T *parent) : parent_(parent) {} - - T *get_parent() const { return parent_; } - void set_parent(T *parent) { parent_ = parent; } - - protected: - T *parent_{nullptr}; -}; - -uint32_t fnv1_hash(const std::string &str); - -// --------------------------------------------------------------------------------------------------------------------- - /// @name STL backports ///@{ +// Backports for various STL features we like to use. Pull in the STL implementation wherever available, to avoid +// ambiguity and to provide a uniform API. + // std::to_string() from C++11, available from libstdc++/g++ 8 // See https://github.com/espressif/esp-idf/issues/1445 #if _GLIBCXX_RELEASE >= 8 using std::to_string; #else -inline std::string to_string(int value) { return str_snprintf("%d", 32, value); } // NOLINT -inline std::string to_string(long value) { return str_snprintf("%ld", 32, value); } // NOLINT -inline std::string to_string(long long value) { return str_snprintf("%lld", 32, value); } // NOLINT -inline std::string to_string(unsigned value) { return str_snprintf("%u", 32, value); } // NOLINT -inline std::string to_string(unsigned long value) { return str_snprintf("%lu", 32, value); } // NOLINT -inline std::string to_string(unsigned long long value) { return str_snprintf("%llu", 32, value); } // NOLINT -inline std::string to_string(float value) { return str_snprintf("%f", 32, value); } -inline std::string to_string(double value) { return str_snprintf("%f", 32, value); } -inline std::string to_string(long double value) { return str_snprintf("%Lf", 32, value); } +std::string to_string(int value); // NOLINT +std::string to_string(long value); // NOLINT +std::string to_string(long long value); // NOLINT +std::string to_string(unsigned value); // NOLINT +std::string to_string(unsigned long value); // NOLINT +std::string to_string(unsigned long long value); // NOLINT +std::string to_string(float value); +std::string to_string(double value); +std::string to_string(long double value); #endif // std::is_trivially_copyable from C++11, implemented in libstdc++/g++ 5.1 (but minor releases can't be detected) @@ -248,6 +61,22 @@ using std::is_trivially_copyable; template struct is_trivially_copyable : public std::integral_constant {}; #endif +// std::make_unique() from C++14 +#if __cpp_lib_make_unique >= 201304 +using std::make_unique; +#else +template std::unique_ptr make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} +#endif + +// std::enable_if_t from C++14 +#if __cplusplus >= 201402L +using std::enable_if_t; +#else +template using enable_if_t = typename std::enable_if::type; +#endif + // std::clamp from C++17 #if __cpp_lib_clamp >= 201603 using std::clamp; @@ -309,6 +138,20 @@ template<> constexpr14 int64_t byteswap(int64_t n) { return __builtin_bswap64(n) /// @name Mathematics ///@{ +/// Linearly interpolate between \p start and \p end by \p completion (between 0 and 1). +float lerp(float completion, float start, float end); + +/// Remap \p value from the range (\p min, \p max) to (\p min_out, \p max_out). +template T remap(U value, U min, U max, T min_out, T max_out) { + return (value - min) * (max_out - min_out) / (max - min) + min_out; +} + +/// Calculate a CRC-8 checksum of \p data with size \p len. +uint8_t crc8(uint8_t *data, uint8_t len); + +/// Calculate a FNV-1 hash of \p str. +uint32_t fnv1_hash(const std::string &str); + /// Return a random 32-bit unsigned integer. uint32_t random_uint32(); /// Return a random float between 0 and 1. @@ -397,6 +240,14 @@ template constexpr14 T convert_little_endian(T val) { /// @name Strings ///@{ +/// Compare strings for equality in case-insensitive manner. +bool str_equals_case_insensitive(const std::string &a, const std::string &b); + +/// Check whether a string starts with a value. +bool str_startswith(const std::string &str, const std::string &start); +/// Check whether a string ends with a value. +bool str_endswith(const std::string &str, const std::string &end); + /// Convert the value to a string (added as extra overload so that to_string() can be used on all stringifiable types). inline std::string to_string(const std::string &val) { return val; } @@ -419,6 +270,12 @@ std::string str_snake_case(const std::string &str); /// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores. std::string str_sanitize(const std::string &str); +/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator). +std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...); + +/// sprintf-like function returning std::string. +std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); + ///@} /// @name Parsing & formatting @@ -537,15 +394,181 @@ template::value, int> = 0> std::stri return format_hex_pretty(reinterpret_cast(&val), sizeof(T)); } +/// Return values for parse_on_off(). +enum ParseOnOffState { + PARSE_NONE = 0, + PARSE_ON, + PARSE_OFF, + PARSE_TOGGLE, +}; +/// Parse a string that contains either on, off or toggle. +ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); + +/// Create a string from a value and an accuracy in decimals. +std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); + ///@} -/// @name Number manipulation +/// @name Colors ///@{ -/// Remap a number from one range to another. -template constexpr T remap(U value, U min, U max, T min_out, T max_out) { - return (value - min) * (max_out - min_out) / (max - min) + min_out; -} +/// Applies gamma correction of \p gamma to \p value. +float gamma_correct(float value, float gamma); +/// Reverts gamma correction of \p gamma to \p value. +float gamma_uncorrect(float value, float gamma); + +/// Convert \p red, \p green and \p blue (all 0-1) values to \p hue (0-360), \p saturation (0-1) and \p value (0-1). +void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); +/// Convert \p hue (0-360), \p saturation (0-1) and \p value (0-1) to \p red, \p green and \p blue (all 0-1). +void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue); + +///@} + +/// @name Units +///@{ + +/// Convert degrees Celsius to degrees Fahrenheit. +constexpr float celsius_to_fahrenheit(float value) { return value * 1.8f + 32.0f; } +/// Convert degrees Fahrenheit to degrees Celsius. +constexpr float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1.8f; } + +///@} + +/// @name Utilities +/// @{ + +template class CallbackManager; + +/** Helper class to allow having multiple subscribers to a callback. + * + * @tparam Ts The arguments for the callbacks, wrapped in void(). + */ +template class CallbackManager { + public: + /// Add a callback to the list. + void add(std::function &&callback) { this->callbacks_.push_back(std::move(callback)); } + + /// Call all callbacks in this manager. + void call(Ts... args) { + for (auto &cb : this->callbacks_) + cb(args...); + } + + /// Call all callbacks in this manager. + void operator()(Ts... args) { call(args...); } + + protected: + std::vector> callbacks_; +}; + +/// Helper class to deduplicate items in a series of values. +template class Deduplicator { + public: + /// Feeds the next item in the series to the deduplicator and returns whether this is a duplicate. + bool next(T value) { + if (this->has_value_) { + if (this->last_value_ == value) + return false; + } + this->has_value_ = true; + this->last_value_ = value; + return true; + } + /// Returns whether this deduplicator has processed any items so far. + bool has_value() const { return this->has_value_; } + + protected: + bool has_value_{false}; + T last_value_{}; +}; + +/// Helper class to easily give an object a parent of type \p T. +template class Parented { + public: + Parented() {} + Parented(T *parent) : parent_(parent) {} + + /// Get the parent of this object. + T *get_parent() const { return parent_; } + /// Set the parent of this object. + void set_parent(T *parent) { parent_ = parent; } + + protected: + T *parent_{nullptr}; +}; + +/// @} + +/// @name System APIs +///@{ + +/** Helper class to disable interrupts. + * + * This behaves like std::lock_guard: as long as the object is alive, all interrupts are disabled. + * + * Please note all functions called when the interrupt lock must be marked IRAM_ATTR (loading code into + * instruction cache is done via interrupts; disabling interrupts prevents data not already in cache from being + * pulled from flash). + * + * Example usage: + * + * \code{.cpp} + * // interrupts are enabled + * { + * InterruptLock lock; + * // do something + * // interrupts are disabled + * } + * // interrupts are enabled + * \endcode + */ +class InterruptLock { + public: + InterruptLock(); + ~InterruptLock(); + + protected: +#ifdef USE_ESP8266 + uint32_t xt_state_; +#endif +}; + +/** Helper class to request `loop()` to be called as fast as possible. + * + * Usually the ESPHome main loop runs at 60 Hz, sleeping in between invocations of `loop()` if necessary. When a higher + * execution frequency is necessary, you can use this class to make the loop run continuously without waiting. + */ +class HighFrequencyLoopRequester { + public: + /// Start running the loop continuously. + void start(); + /// Stop running the loop continuously. + void stop(); + + /// Check whether the loop is running continuously. + static bool is_high_frequency(); + + protected: + bool started_{false}; + static uint8_t num_requests; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +}; + +/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). +void get_mac_address_raw(uint8_t *mac); + +/// Get the device MAC address as a string, in lowercase hex notation. +std::string get_mac_address(); + +/// Get the device MAC address as a string, in colon-separated uppercase hex notation. +std::string get_mac_address_pretty(); + +#ifdef USE_ESP32 +/// Set the MAC address to use from the provided byte array (6 bytes). +void set_mac_address(uint8_t *mac); +#endif + +/// Delay for the given amount of microseconds, possibly yielding to other processes during the wait. +void delay_microseconds_safe(uint32_t us); ///@} @@ -594,6 +617,22 @@ template class ExternalRAMAllocator { /// @} +/// @name Internal functions +///@{ + +/** Helper function to make `id(var)` known from lambdas work in custom components. + * + * This function is not called from lambdas, the code generator replaces calls to it with the appropriate variable. + */ +template::value, int> = 0> T id(T value) { return value; } +/** Helper function to make `id(var)` known from lambdas work in custom components. + * + * This function is not called from lambdas, the code generator replaces calls to it with the appropriate variable. + */ +template::value, int> = 0> T &id(T *value) { return *value; } + +///@} + /// @name Deprecated functions ///@{