diff --git a/esphome/core/datatypes.h b/esphome/core/datatypes.h new file mode 100644 index 0000000000..5356be6b52 --- /dev/null +++ b/esphome/core/datatypes.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include "esphome/core/helpers.h" + +namespace esphome { + +namespace internal { + +/// Wrapper class for memory using big endian data layout, transparently converting it to native order. +template class BigEndianLayout { + public: + constexpr14 operator T() { return convert_big_endian(val_); } + + private: + T val_; +} __attribute__((packed)); + +/// Wrapper class for memory using big endian data layout, transparently converting it to native order. +template class LittleEndianLayout { + public: + constexpr14 operator T() { return convert_little_endian(val_); } + + private: + T val_; +} __attribute__((packed)); + +} // namespace internal + +/// 24-bit unsigned integer type, transparently converting to 32-bit. +struct uint24_t { // NOLINT(readability-identifier-naming) + operator uint32_t() { return val; } + uint32_t val : 24; +} __attribute__((packed)); + +/// 24-bit signed integer type, transparently converting to 32-bit. +struct int24_t { // NOLINT(readability-identifier-naming) + operator int32_t() { return val; } + int32_t val : 24; +} __attribute__((packed)); + +// Integer types in big or little endian data layout. +using uint64_be_t = internal::BigEndianLayout; +using uint32_be_t = internal::BigEndianLayout; +using uint24_be_t = internal::BigEndianLayout; +using uint16_be_t = internal::BigEndianLayout; +using int64_be_t = internal::BigEndianLayout; +using int32_be_t = internal::BigEndianLayout; +using int24_be_t = internal::BigEndianLayout; +using int16_be_t = internal::BigEndianLayout; +using uint64_le_t = internal::LittleEndianLayout; +using uint32_le_t = internal::LittleEndianLayout; +using uint24_le_t = internal::LittleEndianLayout; +using uint16_le_t = internal::LittleEndianLayout; +using int64_le_t = internal::LittleEndianLayout; +using int32_le_t = internal::LittleEndianLayout; +using int24_le_t = internal::LittleEndianLayout; +using int16_le_t = internal::LittleEndianLayout; + +} // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index f4e814203f..f071b4a814 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -20,6 +20,14 @@ #define ALWAYS_INLINE __attribute__((always_inline)) #define PACKED __attribute__((packed)) +// Various functions can be constexpr in C++14, but not in C++11 (because their body isn't just a return statement). +// Define a substitute constexpr keyword for those functions, until we can drop C++11 support. +#if __cplusplus >= 201402L +#define constexpr14 constexpr +#else +#define constexpr14 inline // constexpr implies inline +#endif + namespace esphome { /// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). @@ -277,11 +285,21 @@ To bit_cast(const From &src) { } #endif -// std::byteswap is from C++23 and technically should be a template, but this will do for now. -constexpr uint8_t byteswap(uint8_t n) { return n; } -constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } -constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } -constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } +// std::byteswap from C++23 +template constexpr14 T byteswap(T n) { + T m; + for (size_t i = 0; i < sizeof(T); i++) + reinterpret_cast(&m)[i] = reinterpret_cast(&n)[sizeof(T) - 1 - i]; + return m; +} +template<> constexpr14 uint8_t byteswap(uint8_t n) { return n; } +template<> constexpr14 uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); } +template<> constexpr14 uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); } +template<> constexpr14 uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); } +template<> constexpr14 int8_t byteswap(int8_t n) { return n; } +template<> constexpr14 int16_t byteswap(int16_t n) { return __builtin_bswap16(n); } +template<> constexpr14 int32_t byteswap(int32_t n) { return __builtin_bswap32(n); } +template<> constexpr14 int64_t byteswap(int64_t n) { return __builtin_bswap64(n); } ///@} @@ -311,7 +329,8 @@ constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, ui } /// Encode a value from its constituent bytes (from most to least significant) in an array with length sizeof(T). -template::value, int> = 0> inline T encode_value(const uint8_t *bytes) { +template::value, int> = 0> +constexpr14 T encode_value(const uint8_t *bytes) { T val = 0; for (size_t i = 0; i < sizeof(T); i++) { val <<= 8; @@ -321,12 +340,12 @@ template::value, int> = 0> inline T } /// Encode a value from its constituent bytes (from most to least significant) in an std::array with length sizeof(T). template::value, int> = 0> -inline T encode_value(const std::array bytes) { +constexpr14 T encode_value(const std::array bytes) { return encode_value(bytes.data()); } /// Decode a value into its constituent bytes (from most to least significant). template::value, int> = 0> -inline std::array decode_value(T val) { +constexpr14 std::array decode_value(T val) { std::array ret{}; for (size_t i = sizeof(T); i > 0; i--) { ret[i - 1] = val & 0xFF; @@ -353,7 +372,7 @@ inline uint32_t reverse_bits(uint32_t x) { } /// Convert a value between host byte order and big endian (most significant byte first) order. -template::value, int> = 0> constexpr T convert_big_endian(T val) { +template constexpr14 T convert_big_endian(T val) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ return byteswap(val); #else @@ -361,6 +380,15 @@ template::value, int> = 0> constexpr #endif } +/// Convert a value between host byte order and little endian (least significant byte first) order. +template constexpr14 T convert_little_endian(T val) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return val; +#else + return byteswap(val); +#endif +} + ///@} /// @name Strings @@ -512,7 +540,7 @@ template::value, int> = 0> std::stri ///@{ /// Remap a number from one range to another. -template T remap(U value, U min, U max, T min_out, T max_out) { +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; }