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