ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
rc5x_protocol.cpp
Go to the documentation of this file.
1#include "rc5x_protocol.h"
2#include "esphome/core/log.h"
3
4namespace esphome::remote_base {
5
6ESPHOME_LOG_TAG(TAG, "remote.rc5x");
7
8static constexpr uint32_t HALF_BIT_US = 889; // a Manchester bit is two half-bits
9// Start, field and toggle bits plus the address, then a pause of 4 half-bits, then command and extension
10static constexpr uint8_t RC5_TOGGLE_BIT = 1;
11static constexpr uint8_t RC5_ADDRESS_BITS = 5;
12static constexpr uint8_t FIRST_PART_NBITS = 8;
13static constexpr uint8_t RC5_COMMAND_BITS = 6;
14static constexpr uint8_t RC5_EXT_BITS = 6;
15static constexpr uint8_t NBITS = 20;
16static constexpr uint8_t PAUSE_HALFBITS = 4;
17// Half-bits in a frame, counting the pause; the pause starts after the first part's half-bits
18static constexpr uint8_t NFRAMEHALFBITS = NBITS * 2 + PAUSE_HALFBITS;
19static constexpr uint8_t PAUSE_START = FIRST_PART_NBITS * 2;
20
22 static bool toggle = false;
23 dst->set_carrier_frequency(36000);
24 dst->reserve(NFRAMEHALFBITS);
25 // Start bit, then the field bit, which is clear for commands 64 to 127
26 const uint8_t command = data.command & 0x7F; // bit 6 travels in the field bit, higher bits have no place
27 const uint32_t start_field = command >= 64 ? 0b10 : 0b11;
28 const uint32_t out_data = start_field << (RC5_TOGGLE_BIT + RC5_ADDRESS_BITS + RC5_COMMAND_BITS + RC5_EXT_BITS) |
29 uint32_t(toggle) << (RC5_ADDRESS_BITS + RC5_COMMAND_BITS + RC5_EXT_BITS) |
30 uint32_t(data.address & 0x1F) << (RC5_COMMAND_BITS + RC5_EXT_BITS) |
31 uint32_t(command & 0x3F) << RC5_EXT_BITS | (data.extension & 0x3F);
32
33 for (uint8_t bit = NBITS; bit-- > 0;) {
34 if (bit == NBITS - 1 - FIRST_PART_NBITS) {
35 for (uint8_t i = 0; i < PAUSE_HALFBITS; i++)
36 dst->space(HALF_BIT_US);
37 }
38 if (out_data & (1UL << bit)) {
39 dst->space(HALF_BIT_US);
40 dst->mark(HALF_BIT_US);
41 } else {
42 dst->mark(HALF_BIT_US);
43 dst->space(HALF_BIT_US);
44 }
45 }
46 toggle = !toggle;
47}
48
50 // Expand the runs into half-bit levels (true = mark), as the RC5 decoder does. Each run is one
51 // half-bit (HALF_BIT_US) or two, except the pause, which is PAUSE_HALFBITS half-bits at the idle
52 // level plus up to one merged half on each side (the end of bit 12 and the start of bit 11).
53 //
54 // halfbits[0] is reserved for the leading half-bit, which is always dropped: S1 is 1, so its
55 // first half sits at the idle level and merges into the pre-frame idle.
56 bool halfbits[NFRAMEHALFBITS + 2];
57 uint8_t n = 1;
58 bool pause_seen = false;
59 for (uint32_t i = 0; n <= NFRAMEHALFBITS && src.is_valid(i); i++) {
60 if (src.peek_mark(HALF_BIT_US, i)) {
61 halfbits[n++] = true;
62 } else if (src.peek_space(HALF_BIT_US, i)) {
63 halfbits[n++] = false;
64 } else if (src.peek_mark(2 * HALF_BIT_US, i)) {
65 halfbits[n++] = true;
66 halfbits[n++] = true;
67 } else if (src.peek_space(2 * HALF_BIT_US, i)) {
68 halfbits[n++] = false;
69 halfbits[n++] = false;
70 } else {
71 // The pause must start right after bit 12, or one half earlier when bit 12 ends idle
72 if (pause_seen || (n != PAUSE_START && n != PAUSE_START - 1))
73 break;
74 const int32_t value = src.peek(i);
75 const bool level = value > 0;
76 const uint32_t halves = (static_cast<uint32_t>(value > 0 ? value : -value) + HALF_BIT_US / 2) / HALF_BIT_US;
77 if (level == halfbits[1] || halves < PAUSE_HALFBITS || halves > PAUSE_HALFBITS + 2)
78 break;
79 for (uint32_t h = 0; h < halves; h++)
80 halfbits[n++] = level;
81 // It may also run one half into bit 11, but must cover the whole pause
82 if (n != PAUSE_START + PAUSE_HALFBITS && n != PAUSE_START + PAUSE_HALFBITS + 1)
83 return {};
84 pause_seen = true;
85 }
86 }
87
88 // A full frame, or one half short when the last bit ends idle and its trailing half is dropped.
89 // A dropped edge half is the inverse of its partner, since a Manchester bit always changes mid-bit.
90 if (!pause_seen || (n != NFRAMEHALFBITS && n != NFRAMEHALFBITS - 1)) {
91 return {};
92 }
93 halfbits[0] = !halfbits[1];
94 if (n == NFRAMEHALFBITS - 1) {
95 halfbits[n] = !halfbits[n - 1];
96 }
97
98 const bool carrier = halfbits[1];
99 uint32_t bits = 0;
100 for (uint8_t i = 0; i < NBITS; i++) {
101 const uint8_t h = 2 * i + (i < FIRST_PART_NBITS ? 0 : PAUSE_HALFBITS);
102 const bool first = halfbits[h];
103 const bool second = halfbits[h + 1];
104 if (first == second) {
105 return {}; // no midpoint transition -> not a valid Manchester bit
106 }
107 bits = (bits << 1) | (second == carrier ? 1 : 0);
108 }
109
110 // [18 field] [17 toggle] [16-12 address] [11-6 command] [5-0 extension]
111 const bool field_bit = bits & (1UL << (NBITS - 2)); // S2: the inverted 7th command bit
112 return RC5XData{
113 .address = static_cast<uint8_t>((bits >> (RC5_COMMAND_BITS + RC5_EXT_BITS)) & 0x1F),
114 .command = static_cast<uint8_t>(((bits >> RC5_EXT_BITS) & 0x3F) | (field_bit ? 0 : 0x40)),
115 .extension = static_cast<uint8_t>(bits & 0x3F),
116 };
117}
118
119void RC5XProtocol::dump(const RC5XData &data) {
120 ESP_LOGI(TAG, "Received RC5X (Marantz): address=0x%02X, command=0x%02X, extension=0x%02X", data.address, data.command,
121 data.extension);
122}
123
124} // namespace esphome::remote_base
uint8_t h
Definition bl0906.h:2
optional< RC5XData > decode(RemoteReceiveData src)
void dump(const RC5XData &data)
void encode(RemoteTransmitData *dst, const RC5XData &data)
void set_carrier_frequency(uint32_t carrier_frequency)
Definition remote_base.h:31
uint8_t second
ESPHOME_LOG_TAG(TAG, "remote.abbwelcome")
const void * src
Definition hal.h:68
static void uint32_t