mirror of
https://github.com/esphome/esphome.git
synced 2026-10-21 13:34:12 +00:00
[remote_base] Add helpers to send and read IR frame bytes bit by bit (#21167)
This commit is contained in:
@@ -19,6 +19,19 @@ enum ToleranceMode : uint8_t {
|
||||
|
||||
using RawTimings = std::vector<int32_t>;
|
||||
|
||||
enum class BitOrder : uint8_t {
|
||||
BIT_ORDER_LSB_FIRST,
|
||||
BIT_ORDER_MSB_FIRST,
|
||||
};
|
||||
|
||||
/// A bit is bit_mark followed by one_space or zero_space.
|
||||
struct BitTimings {
|
||||
uint32_t bit_mark;
|
||||
uint32_t one_space;
|
||||
uint32_t zero_space;
|
||||
BitOrder bit_order{BitOrder::BIT_ORDER_LSB_FIRST};
|
||||
};
|
||||
|
||||
class RemoteTransmitData {
|
||||
public:
|
||||
void mark(uint32_t length) { this->data_.push_back(length); }
|
||||
@@ -28,6 +41,15 @@ class RemoteTransmitData {
|
||||
this->space(space);
|
||||
}
|
||||
void reserve(uint32_t len) { this->data_.reserve(len); }
|
||||
/// One item per bit of each byte.
|
||||
template<BitTimings T> void item_bytes(const uint8_t *bytes, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
const uint8_t mask = T.bit_order == BitOrder::BIT_ORDER_MSB_FIRST ? (0x80 >> bit) : (1 << bit);
|
||||
this->item(T.bit_mark, (bytes[i] & mask) ? T.one_space : T.zero_space);
|
||||
}
|
||||
}
|
||||
}
|
||||
void set_carrier_frequency(uint32_t carrier_frequency) { this->carrier_frequency_ = carrier_frequency; }
|
||||
uint32_t get_carrier_frequency() const { return this->carrier_frequency_; }
|
||||
const RawTimings &get_data() const { return this->data_; }
|
||||
@@ -77,6 +99,22 @@ class RemoteReceiveData {
|
||||
bool expect_space(uint32_t length);
|
||||
bool expect_item(uint32_t mark, uint32_t space);
|
||||
bool expect_pulse_with_gap(uint32_t mark, uint32_t space);
|
||||
/// False at the first item matching neither space, leaving it unread.
|
||||
template<BitTimings T> bool expect_bytes(uint8_t *out, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint8_t byte = 0;
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
const uint8_t mask = T.bit_order == BitOrder::BIT_ORDER_MSB_FIRST ? (0x80 >> bit) : (1 << bit);
|
||||
if (this->expect_item(T.bit_mark, T.one_space)) {
|
||||
byte |= mask;
|
||||
} else if (!this->expect_item(T.bit_mark, T.zero_space)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
out[i] = byte;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void advance(uint32_t amount = 1) { this->index_ += amount; }
|
||||
void reset() { this->index_ = 0; }
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <array>
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
|
||||
namespace esphome::remote_base::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t BIT_MARK = 500;
|
||||
constexpr uint32_t ONE_SPACE = 1500;
|
||||
constexpr uint32_t ZERO_SPACE = 400;
|
||||
constexpr BitTimings TIMINGS{.bit_mark = BIT_MARK, .one_space = ONE_SPACE, .zero_space = ZERO_SPACE};
|
||||
constexpr BitTimings MSB_TIMINGS{
|
||||
.bit_mark = BIT_MARK, .one_space = ONE_SPACE, .zero_space = ZERO_SPACE, .bit_order = BitOrder::BIT_ORDER_MSB_FIRST};
|
||||
constexpr std::array<uint8_t, 4> FRAME = {0xA5, 0x01, 0x80, 0xFF};
|
||||
|
||||
RemoteReceiveData receive(const RawTimings &timings) {
|
||||
return RemoteReceiveData(timings, 25, TOLERANCE_MODE_PERCENTAGE);
|
||||
}
|
||||
|
||||
RawTimings expected_items(uint8_t byte, bool msb_first) {
|
||||
RawTimings out;
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
const uint8_t mask = msb_first ? (0x80 >> bit) : (1 << bit);
|
||||
out.push_back(BIT_MARK);
|
||||
out.push_back(-static_cast<int32_t>((byte & mask) ? ONE_SPACE : ZERO_SPACE));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(RemoteBaseByteItemsTest, LsbItemSequence) {
|
||||
RemoteTransmitData tx;
|
||||
const uint8_t byte = 0x4B;
|
||||
tx.item_bytes<TIMINGS>(&byte, 1);
|
||||
EXPECT_EQ(tx.get_data(), expected_items(byte, false));
|
||||
}
|
||||
|
||||
TEST(RemoteBaseByteItemsTest, MsbItemSequence) {
|
||||
RemoteTransmitData tx;
|
||||
const uint8_t byte = 0x4B;
|
||||
tx.item_bytes<MSB_TIMINGS>(&byte, 1);
|
||||
EXPECT_EQ(tx.get_data(), expected_items(byte, true));
|
||||
}
|
||||
|
||||
TEST(RemoteBaseByteItemsTest, AppendsAfterExistingItems) {
|
||||
RemoteTransmitData tx;
|
||||
tx.item(9000, 4500);
|
||||
tx.item_bytes<TIMINGS>(FRAME.data(), FRAME.size());
|
||||
tx.mark(BIT_MARK);
|
||||
ASSERT_EQ(tx.get_data().size(), 2 + FRAME.size() * 16 + 1);
|
||||
EXPECT_EQ(tx.get_data()[0], 9000);
|
||||
EXPECT_EQ(tx.get_data()[1], -4500);
|
||||
EXPECT_EQ(tx.get_data().back(), static_cast<int32_t>(BIT_MARK));
|
||||
}
|
||||
|
||||
TEST(RemoteBaseByteItemsTest, LsbRoundTrip) {
|
||||
RemoteTransmitData tx;
|
||||
tx.item_bytes<TIMINGS>(FRAME.data(), FRAME.size());
|
||||
auto rx = receive(tx.get_data());
|
||||
std::array<uint8_t, FRAME.size()> out{};
|
||||
ASSERT_TRUE(rx.expect_bytes<TIMINGS>(out.data(), out.size()));
|
||||
EXPECT_EQ(out, FRAME);
|
||||
EXPECT_FALSE(rx.is_valid());
|
||||
}
|
||||
|
||||
TEST(RemoteBaseByteItemsTest, MsbRoundTrip) {
|
||||
RemoteTransmitData tx;
|
||||
tx.item_bytes<MSB_TIMINGS>(FRAME.data(), FRAME.size());
|
||||
auto rx = receive(tx.get_data());
|
||||
std::array<uint8_t, FRAME.size()> out{};
|
||||
ASSERT_TRUE(rx.expect_bytes<MSB_TIMINGS>(out.data(), out.size()));
|
||||
EXPECT_EQ(out, FRAME);
|
||||
}
|
||||
|
||||
TEST(RemoteBaseByteItemsTest, BitOrderMismatchReversesBits) {
|
||||
RemoteTransmitData tx;
|
||||
const uint8_t byte = 0x01;
|
||||
tx.item_bytes<TIMINGS>(&byte, 1);
|
||||
auto rx = receive(tx.get_data());
|
||||
uint8_t out = 0;
|
||||
ASSERT_TRUE(rx.expect_bytes<MSB_TIMINGS>(&out, 1));
|
||||
EXPECT_EQ(out, 0x80);
|
||||
}
|
||||
|
||||
TEST(RemoteBaseByteItemsTest, FailsMidByteAndStopsThere) {
|
||||
RemoteTransmitData tx;
|
||||
tx.item_bytes<TIMINGS>(FRAME.data(), FRAME.size());
|
||||
RawTimings timings = tx.get_data();
|
||||
// Bit 3 of byte 1 gets a space that matches neither length
|
||||
const size_t bad = (8 + 3) * 2 + 1;
|
||||
timings[bad] = -3000;
|
||||
auto rx = receive(timings);
|
||||
std::array<uint8_t, FRAME.size()> out = {0x11, 0x22, 0x33, 0x44};
|
||||
EXPECT_FALSE(rx.expect_bytes<TIMINGS>(out.data(), out.size()));
|
||||
EXPECT_EQ(rx.get_index(), bad - 1);
|
||||
// Byte 0 was read in full, the partial byte and the rest are untouched
|
||||
EXPECT_EQ(out[0], FRAME[0]);
|
||||
EXPECT_EQ(out[1], 0x22);
|
||||
EXPECT_EQ(out[2], 0x33);
|
||||
}
|
||||
|
||||
TEST(RemoteBaseByteItemsTest, FailsWhenDataRunsOut) {
|
||||
RemoteTransmitData tx;
|
||||
tx.item_bytes<MSB_TIMINGS>(FRAME.data(), 1);
|
||||
auto rx = receive(tx.get_data());
|
||||
std::array<uint8_t, 2> out{};
|
||||
EXPECT_FALSE(rx.expect_bytes<MSB_TIMINGS>(out.data(), out.size()));
|
||||
EXPECT_EQ(out[0], FRAME[0]);
|
||||
}
|
||||
|
||||
TEST(RemoteBaseByteItemsTest, ZeroLength) {
|
||||
RemoteTransmitData tx;
|
||||
tx.item_bytes<TIMINGS>(nullptr, 0);
|
||||
tx.item_bytes<MSB_TIMINGS>(nullptr, 0);
|
||||
EXPECT_TRUE(tx.get_data().empty());
|
||||
|
||||
const RawTimings empty;
|
||||
auto rx = receive(empty);
|
||||
EXPECT_TRUE(rx.expect_bytes<TIMINGS>(nullptr, 0));
|
||||
EXPECT_TRUE(rx.expect_bytes<MSB_TIMINGS>(nullptr, 0));
|
||||
EXPECT_EQ(rx.get_index(), 0u);
|
||||
}
|
||||
|
||||
} // namespace esphome::remote_base::testing
|
||||
Reference in New Issue
Block a user