ESPHome 2026.4.0-dev
Loading...
Searching...
No Matches
brennenstuhl_protocol.cpp
Go to the documentation of this file.
2#include "esphome/core/log.h"
3
4#include <cinttypes>
5
6namespace esphome::remote_base {
7
8static const char *const TAG = "remote.brennenstuhl";
9
10// receiver timing ranges [µs]
11constexpr uint32_t START_PULSE_MIN = 200;
12constexpr uint32_t START_PULSE_MAX = 500;
13constexpr uint32_t START_SYMBOL_MIN = 2600;
14constexpr uint32_t START_SYMBOL_MAX = 2700;
15constexpr uint32_t DATA_SYMBOL_MIN = 1500;
16constexpr uint32_t DATA_SYMBOL_MAX = 1600;
17
18// transmitter timings [µs]
19constexpr uint32_t PW_SHORT_US = 390;
20constexpr uint32_t PW_LONG_US = 1160;
21constexpr uint32_t PW_START_US = 2300;
22
23// number of data bits
24constexpr uint32_t N_BITS = 24;
25
26// number of required symbols = 2 x (start + N_BITS) = 50
27constexpr uint32_t N_SYMBOLS_REQ = 2u * (N_BITS + 1);
28
29// number of bs codes within received frame
30constexpr int32_t N_FRAME_CODES = 4;
31
32// decoder finite-state-machine
34
35// The encode() member function reserves and fills a complete frame, to be send. The Brennenstuhl
36// RC receivers demand a frame with a start-symbol followed by 4 repeated codes.
38 uint32_t code = data.code;
40 for (int32_t kc = 0; kc != N_FRAME_CODES; kc++) {
42 for (int32_t ic = (N_BITS - 1); ic != -1; ic--) {
43 if ((code >> ic) & 1) {
45 } else {
47 }
48 }
49 }
50}
51
52// The decode() member function extracts Brennenstuhl codes from the received frame. Instead
53// of validating the pulse width of the carriers and pauses individually, it is more accurate
54// to validate the symbols (symbol=carrier+pause) The symbol pulsewidth is around 1550µs, but
55// the pulse with of the carrier and the pauses vary greatly. Once the symbol pulsewidth is
56// valid, a code bit becomes "1" if the carrier is longer then the pause and "0" else. A total
57// frame consists of a start symbol and up to four codes. The decoder decodes all codes and
58// returns the best code (the one with the most identical codes)
59optional<BrennenstuhlData> BrennenstuhlProtocol::decode(RemoteReceiveData src) {
60 uint32_t n_received = static_cast<uint32_t>(src.size());
62 .code = 0,
63 };
64 // suppress noisy frames, at least a complete bs_code should be available
65 if (n_received > N_SYMBOLS_REQ) {
66 uint32_t bs_codes[4] = {0, 0, 0, 0}; // internal codes
67 int32_t bs_cnt = 0; // number of bs codes found within frame
68 int32_t bs_idx = -1; // index to best bs code
69 uint32_t bit_cnt = 0; // bit counter [0..23]
70 uint32_t pw_pre = 0; // pulsewidth of previous carrier (abs value)
72 for (uint32_t ic = 0; (ic != n_received) && (bs_cnt != N_FRAME_CODES); ic++) {
73 uint32_t pw_cur = (uint32_t) (src[ic] < 0 ? -src[ic] : src[ic]); // current pulsewidth
74 uint32_t pw_sym = pw_cur + pw_pre; // symbol=pulse+pause
75 switch (fsm) {
76 case RxSt::START_PULSE: { // check if start pulse is valid
77 if ((src[ic] > 0) && (pw_cur >= START_PULSE_MIN) && (pw_cur <= START_PULSE_MAX)) {
78 bs_codes[bs_cnt] = 0;
79 bit_cnt = 0;
80 pw_pre = pw_cur;
82 }
83 break;
84 }
85 case RxSt::START_SYMBOL: { // check if start symbol is valid
86 if ((src[ic] < 0) && (pw_sym >= START_SYMBOL_MIN) && (pw_sym <= START_SYMBOL_MAX)) {
87 fsm = RxSt::PULSE;
88 } else {
90 }
91 break;
92 }
93 case RxSt::PULSE: { // just grab pulse, validation is done in DATA_SYMBOL state
94 if (src[ic] > 0) {
95 pw_pre = pw_cur;
97 } else {
99 }
100 break;
101 }
102 case RxSt::DATA_SYMBOL: { // check if data symbol is valid and append bit to data
103 if ((src[ic] < 0) && (pw_sym >= DATA_SYMBOL_MIN) && (pw_sym <= DATA_SYMBOL_MAX)) {
104 bs_codes[bs_cnt] <<= 1;
105 bs_codes[bs_cnt] += (pw_cur < pw_pre) ? 1 : 0;
106 if (++bit_cnt < N_BITS) {
107 fsm = RxSt::PULSE;
108 } else {
109 bs_cnt++; // complete code found
110 fsm = RxSt::START_PULSE; // start over for further codes in frame
111 }
112 } else {
113 fsm = RxSt::START_PULSE; // decoding failed, start over for further codes
114 }
115 break;
116 }
117 }
118 }
119 if (bs_cnt > 0) { // complete codes found, find best code in list now
120 int32_t identical_max = 0;
121 for (int32_t ic = 0; ic != bs_cnt; ic++) {
122 int32_t identical_cnt = 0;
123 for (int32_t jc = 0; jc != bs_cnt; jc++) {
124 identical_cnt += (bs_codes[ic] == bs_codes[jc]) ? 1 : 0;
125 }
126 if (identical_cnt > identical_max) {
127 identical_max = identical_cnt;
128 bs_idx = ic; // save index to best code
129 }
130 }
131 if (bs_idx > -1) {
132 data.code = bs_codes[bs_idx];
133 return data; // return best bs code of list
134 }
135 }
136 }
137 return {};
138}
139
141 ESP_LOGI(TAG, "Brennenstuhl: code=0x%06" PRIx32, data.code);
142}
143
144} // namespace esphome::remote_base
void dump(const BrennenstuhlData &data) override
optional< BrennenstuhlData > decode(RemoteReceiveData src) override
void encode(RemoteTransmitData *dst, const BrennenstuhlData &data) override
void item(uint32_t mark, uint32_t space)
Definition remote_base.h:25
constexpr uint32_t PW_SHORT_US
constexpr uint32_t N_BITS
constexpr uint32_t DATA_SYMBOL_MAX
constexpr uint32_t DATA_SYMBOL_MIN
constexpr uint32_t START_SYMBOL_MIN
constexpr int32_t N_FRAME_CODES
constexpr uint32_t PW_LONG_US
constexpr uint32_t N_SYMBOLS_REQ
constexpr uint32_t START_PULSE_MIN
constexpr uint32_t START_PULSE_MAX
constexpr uint32_t START_SYMBOL_MAX
constexpr uint16_t PW_START_US
static void uint32_t