ESPHome 2026.5.1
Loading...
Searching...
No Matches
symphony_protocol.cpp
Go to the documentation of this file.
1#include "symphony_protocol.h"
2#include "esphome/core/log.h"
3
4#include <cinttypes>
5
6namespace esphome::remote_base {
7
8static const char *const TAG = "remote.symphony";
9
10// Reference implementation and timing details:
11// IRremoteESP8266 ir_Symphony.cpp
12// https://github.com/crankyoldgit/IRremoteESP8266/blob/master/src/ir_Symphony.cpp
13// The implementation below mirrors the constant bit-time mapping and
14// footer-gap handling used there.
15
16// Symphony protocol timing specifications (tuned to handset captures)
17static constexpr uint32_t BIT_ZERO_HIGH_US = 460; // short
18static constexpr uint32_t BIT_ZERO_LOW_US = 1260; // long
19static constexpr uint32_t BIT_ONE_HIGH_US = 1260; // long
20static constexpr uint32_t BIT_ONE_LOW_US = 460; // short
21static constexpr uint32_t CARRIER_FREQUENCY = 38000;
22
23// IRremoteESP8266 reference: kSymphonyFooterGap = 4 * (mark + space)
24static constexpr uint32_t FOOTER_GAP_US = 4 * (BIT_ZERO_HIGH_US + BIT_ZERO_LOW_US);
25// Typical inter-frame gap (~34.8 ms observed)
26static constexpr uint32_t INTER_FRAME_GAP_US = 34760;
27
29 dst->set_carrier_frequency(CARRIER_FREQUENCY);
30 ESP_LOGD(TAG, "Sending Symphony: data=0x%0*" PRIX32 " nbits=%" PRIu8 " repeats=%" PRIu8, (data.nbits + 3) / 4,
31 (uint32_t) data.data, data.nbits, data.repeats);
32 // Each bit produces a mark+space (2 entries). We fold the inter-frame/footer gap
33 // into the last bit's space of each frame to avoid over-length gaps.
34 dst->reserve(data.nbits * 2u * data.repeats);
35
36 for (uint8_t repeats = 0; repeats < data.repeats; repeats++) {
37 // Data bits (MSB first)
38 for (uint32_t mask = 1UL << (data.nbits - 1); mask != 0; mask >>= 1) {
39 const bool is_last_bit = (mask == 1);
40 const bool is_last_frame = (repeats == (data.repeats - 1));
41 if (is_last_bit) {
42 // Emit last bit's mark; replace its space with the proper gap
43 if (data.data & mask) {
44 dst->mark(BIT_ONE_HIGH_US);
45 } else {
46 dst->mark(BIT_ZERO_HIGH_US);
47 }
48 dst->space(is_last_frame ? FOOTER_GAP_US : INTER_FRAME_GAP_US);
49 } else {
50 if (data.data & mask) {
51 dst->item(BIT_ONE_HIGH_US, BIT_ONE_LOW_US);
52 } else {
53 dst->item(BIT_ZERO_HIGH_US, BIT_ZERO_LOW_US);
54 }
55 }
56 }
57 }
58}
59
60optional<SymphonyData> SymphonyProtocol::decode(RemoteReceiveData src) {
61 auto is_valid_len = [](uint8_t nbits) -> bool { return nbits == 8 || nbits == 12 || nbits == 16; };
62
63 RemoteReceiveData s = src; // copy
64 SymphonyData out{0, 0, 1};
65
66 for (; out.nbits < 32; out.nbits++) {
67 if (s.expect_mark(BIT_ONE_HIGH_US)) {
68 if (!s.expect_space(BIT_ONE_LOW_US)) {
69 // Allow footer gap immediately after the last mark
70 if (s.peek_space_at_least(FOOTER_GAP_US)) {
71 uint8_t bits_with_this = out.nbits + 1;
72 if (is_valid_len(bits_with_this)) {
73 out.data = (out.data << 1UL) | 1UL;
74 out.nbits = bits_with_this;
75 return out;
76 }
77 }
78 return {};
79 }
80 // Successfully consumed a '1' bit (mark + space)
81 out.data = (out.data << 1UL) | 1UL;
82 continue;
83 } else if (s.expect_mark(BIT_ZERO_HIGH_US)) {
84 if (!s.expect_space(BIT_ZERO_LOW_US)) {
85 // Allow footer gap immediately after the last mark
86 if (s.peek_space_at_least(FOOTER_GAP_US)) {
87 uint8_t bits_with_this = out.nbits + 1;
88 if (is_valid_len(bits_with_this)) {
89 out.data = (out.data << 1UL) | 0UL;
90 out.nbits = bits_with_this;
91 return out;
92 }
93 }
94 return {};
95 }
96 // Successfully consumed a '0' bit (mark + space)
97 out.data = (out.data << 1UL) | 0UL;
98 continue;
99 } else {
100 // Completed a valid-length frame followed by a footer gap
101 if (is_valid_len(out.nbits) && s.peek_space_at_least(FOOTER_GAP_US)) {
102 return out;
103 }
104 return {};
105 }
106 }
107
108 if (is_valid_len(out.nbits) && s.peek_space_at_least(FOOTER_GAP_US)) {
109 return out;
110 }
111
112 return {};
113}
114
116 const int hex_width = (data.nbits + 3) / 4; // pad to nibble width
117 ESP_LOGI(TAG, "Received Symphony: data=0x%0*" PRIX32 ", nbits=%" PRIu8, hex_width, (uint32_t) data.data, data.nbits);
118}
119
120} // namespace esphome::remote_base
bool peek_space_at_least(uint32_t length, uint32_t offset=0) const
void set_carrier_frequency(uint32_t carrier_frequency)
Definition remote_base.h:29
void item(uint32_t mark, uint32_t space)
Definition remote_base.h:24
void encode(RemoteTransmitData *dst, const SymphonyData &data) override
optional< SymphonyData > decode(RemoteReceiveData src) override
void dump(const SymphonyData &data) override
static void uint32_t