diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index c51ef4a652d..94921e1d088 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -990,6 +990,12 @@ constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, ui (static_cast(byte3) << 8) | (static_cast(byte4)); } +/// Sign-extend the low \p BITS bits of \p value, e.g. sign_extend<24>(0xFFFFFE) == -2. Higher bits are ignored. +template constexpr int32_t sign_extend(uint32_t value) { + static_assert(BITS > 0 && BITS <= 32, "BITS must be 1 to 32"); + return static_cast(value << (32 - BITS)) >> (32 - BITS); +} + /// Encode a value from its constituent bytes (from most to least significant) in an array with length sizeof(T). template::value, int> = 0> constexpr T encode_value(const uint8_t *bytes) { T val = 0; diff --git a/tests/components/core/helpers_test.cpp b/tests/components/core/helpers_test.cpp index d0058a634b2..92a7f841830 100644 --- a/tests/components/core/helpers_test.cpp +++ b/tests/components/core/helpers_test.cpp @@ -55,6 +55,21 @@ TEST(HelpersTest, Ilog10RoundTripMatchesLog10) { } } +TEST(HelpersTest, SignExtend) { + EXPECT_EQ(sign_extend<24>(0x000000), 0); + EXPECT_EQ(sign_extend<24>(0x7FFFFF), 8388607); + EXPECT_EQ(sign_extend<24>(0x800000), -8388608); + EXPECT_EQ(sign_extend<24>(0xFFFFFE), -2); + EXPECT_EQ(sign_extend<24>(0xAB000001), 1); // bits above BITS are ignored + EXPECT_EQ(sign_extend<12>(0x7FF), 2047); + EXPECT_EQ(sign_extend<12>(0x800), -2048); + EXPECT_EQ(sign_extend<16>(0xFF38), -200); + EXPECT_EQ(sign_extend<1>(1), -1); + EXPECT_EQ(sign_extend<32>(0xFFFFFFFF), -1); + EXPECT_EQ(sign_extend<32>(0x7FFFFFFF), INT32_MAX); + static_assert(sign_extend<12>(0xFFF) == -1, "usable in constant expressions"); +} + TEST(HelpersTest, SumBytes) { static constexpr uint8_t FRAME[] = {0x11, 0xDA, 0x27, 0x00, 0xC5, 0x00, 0x10}; EXPECT_EQ(sum_bytes(FRAME, sizeof(FRAME)), 0xE7); // the Daikin comfort frame checksum