ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
remote_base.h
Go to the documentation of this file.
1#pragma once
2
3#include <concepts>
4#include <utility>
5#include <vector>
6
10#include "esphome/core/hal.h"
12
13namespace esphome::remote_base {
14
19
20using RawTimings = std::vector<int32_t>;
21
22enum class BitOrder : uint8_t {
25};
26
38
40 public:
41 void mark(uint32_t length) { this->data_.push_back(length); }
42 void space(uint32_t length) { this->data_.push_back(-length); }
44 this->mark(mark);
45 this->space(space);
46 }
47 void reserve(uint32_t len) { this->data_.reserve(len); }
49 template<BitTimings T> void item_bytes(const uint8_t *bytes, size_t len) {
50 static_assert(T.one_mark != 0 && T.zero_mark != 0, "set bit_mark, or one_mark and zero_mark");
51 for (size_t i = 0; i < len; i++) {
52 for (uint8_t bit = 0; bit < 8; bit++) {
53 const uint8_t mask = T.bit_order == BitOrder::BIT_ORDER_MSB_FIRST ? (0x80 >> bit) : (1 << bit);
54 const bool one = (bytes[i] & mask) != 0;
55 if constexpr (T.space_first) {
56 this->space(one ? T.one_space : T.zero_space);
57 this->mark(one ? T.one_mark : T.zero_mark);
58 } else {
59 this->item(one ? T.one_mark : T.zero_mark, one ? T.one_space : T.zero_space);
60 }
61 }
62 }
63 }
64 void set_carrier_frequency(uint32_t carrier_frequency) { this->carrier_frequency_ = carrier_frequency; }
66 const RawTimings &get_data() const { return this->data_; }
67 void set_data(const RawTimings &data) { this->data_ = data; }
72 void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count);
77 bool set_data_from_base64url(const std::string &base64url);
78 void reset() {
79 this->data_.clear();
80 this->carrier_frequency_ = 0;
81 }
82
83 protected:
86};
87
89 public:
90 explicit RemoteReceiveData(const RawTimings &data, uint32_t tolerance, ToleranceMode tolerance_mode)
91 : data_(data), index_(0), tolerance_(tolerance), tolerance_mode_(tolerance_mode) {}
92
93 const RawTimings &get_raw_data() const { return this->data_; }
94 uint32_t get_index() const { return index_; }
95 int32_t operator[](uint32_t index) const { return this->data_[index]; }
96 int32_t size() const { return this->data_.size(); }
97 bool is_valid(uint32_t offset = 0) const { return this->index_ + offset < this->data_.size(); }
98 int32_t peek(uint32_t offset = 0) const { return this->data_[this->index_ + offset]; }
99 bool peek_mark(uint32_t length, uint32_t offset = 0) const;
100 bool peek_mark_at_least(uint32_t length, uint32_t offset = 0) const;
101 bool peek_mark_at_most(uint32_t length, uint32_t offset = 0) const;
102 bool peek_space(uint32_t length, uint32_t offset = 0) const;
103 bool peek_space_at_least(uint32_t length, uint32_t offset = 0) const;
104 bool peek_space_at_most(uint32_t length, uint32_t offset = 0) const;
105 bool peek_item(uint32_t mark, uint32_t space, uint32_t offset = 0) const {
106 return this->peek_space(space, offset + 1) && this->peek_mark(mark, offset);
107 }
108
111 bool expect_item(uint32_t mark, uint32_t space);
112 bool expect_pulse_with_gap(uint32_t mark, uint32_t space);
114 template<BitTimings T> bool expect_bytes(uint8_t *out, size_t len) { return this->expect_bytes_<T, false>(out, len); }
116 template<BitTimings T> bool expect_bytes_last_mark(uint8_t *out, size_t len) {
117 static_assert(!T.space_first && T.one_mark != T.zero_mark, "the last bit must be told apart by its mark");
118 return this->expect_bytes_<T, true>(out, len);
119 }
120 void advance(uint32_t amount = 1) { this->index_ += amount; }
121 void reset() { this->index_ = 0; }
122
123 void set_tolerance(uint32_t tolerance, ToleranceMode tolerance_mode) {
124 this->tolerance_ = tolerance;
125 this->tolerance_mode_ = tolerance_mode;
126 }
129
130 protected:
131 template<BitTimings T, bool LAST_MARK> bool expect_bytes_(uint8_t *out, size_t len) {
132 static_assert(T.one_mark != 0 && T.zero_mark != 0, "set bit_mark, or one_mark and zero_mark");
133 for (size_t i = 0; i < len; i++) {
134 uint8_t byte = 0;
135 for (uint8_t bit = 0; bit < 8; bit++) {
136 const uint8_t mask = T.bit_order == BitOrder::BIT_ORDER_MSB_FIRST ? (0x80 >> bit) : (1 << bit);
137 if (LAST_MARK && i + 1 == len && bit == 7) {
138 if (this->expect_mark(T.one_mark)) {
139 byte |= mask;
140 } else if (!this->expect_mark(T.zero_mark)) {
141 return false;
142 }
143 } else if (this->expect_bit_<T>(T.one_mark, T.one_space)) {
144 byte |= mask;
145 } else if (!this->expect_bit_<T>(T.zero_mark, T.zero_space)) {
146 return false;
147 }
148 }
149 out[i] = byte;
150 }
151 return true;
152 }
154 template<BitTimings T> bool expect_bit_(uint32_t mark, uint32_t space) {
155 if constexpr (T.space_first) {
156 if (!this->peek_space(space) || !this->peek_mark(mark, 1))
157 return false;
158 this->advance(2);
159 return true;
160 } else {
161 return this->expect_item(mark, space);
162 }
163 }
164 int32_t lower_bound_(uint32_t length) const {
166 return int32_t(length - this->tolerance_);
167 } else if (this->tolerance_mode_ == TOLERANCE_MODE_PERCENTAGE) {
168 return int32_t(100 - this->tolerance_) * length / 100U;
169 }
170 return 0;
171 }
172 int32_t upper_bound_(uint32_t length) const {
174 return int32_t(length + this->tolerance_);
175 } else if (this->tolerance_mode_ == TOLERANCE_MODE_PERCENTAGE) {
176 return int32_t(100 + this->tolerance_) * length / 100U;
177 }
178 return 0;
179 }
180
185};
186
188 public:
189 explicit RemoteComponentBase(InternalGPIOPin *pin) : pin_(pin) {};
190
191 protected:
193};
194
195#ifdef USE_ESP32
196#include <soc/soc_caps.h>
197#if SOC_RMT_SUPPORTED
199 public:
200 void set_clock_resolution(uint32_t clock_resolution) { this->clock_resolution_ = clock_resolution; }
201 void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; }
202
203 protected:
205 const uint32_t ticks_per_ten_us = this->clock_resolution_ / 100000u;
206 return us * ticks_per_ten_us / 10;
207 }
209 const uint32_t ticks_per_ten_us = this->clock_resolution_ / 100000u;
210 return (ticks * 10) / ticks_per_ten_us;
211 }
215};
216#endif // SOC_RMT_SUPPORTED
217#endif // USE_ESP32
218
220
221#ifdef USE_IR_RF_TRANSMIT_COMPLETE
225void ir_rf_transmit_complete(RemoteTransmitterBase *transmitter, uint16_t seq, bool sent);
226#endif
227// Protocol shapes, checked where a protocol is used so a missing method fails at the use site
228// instead of deep inside a template body. Receive-only protocols such as RCSwitchBase decode
229// without encoding.
230template<typename T>
231concept RemoteProtocolDecoder = requires(T proto, RemoteReceiveData src) {
232 { proto.decode(src) } -> std::same_as<optional<typename T::ProtocolData>>;
233};
234template<typename T>
236 RemoteProtocolDecoder<T> && requires(T proto, const typename T::ProtocolData &data) { proto.dump(data); };
237template<typename T>
239 requires(T proto, RemoteTransmitData *dst, const typename T::ProtocolData &data) { proto.encode(dst, data); };
240
242 public:
245 public:
246 TransmitCall(RemoteTransmitterBase *parent, uint16_t seq) : parent_(parent), seq_(seq) {}
248 void set_send_times(uint32_t send_times) { send_times_ = send_times; }
249 void set_send_wait(uint32_t send_wait) { send_wait_ = send_wait; }
251 uint16_t get_seq() const { return this->seq_; }
252 void perform() { this->parent_->send_(this->send_times_, this->send_wait_, this->seq_); }
253
254 protected:
258 uint16_t seq_;
259 };
260
262 template<RemoteProtocolEncoder Protocol>
263 void transmit(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
264 this->transmit_encoded_(&encode_thunk<Protocol>, &data, send_times, send_wait);
265 }
267 template<RemoteProtocolEncoder Protocol> class ProtocolCall {
268 public:
269 explicit ProtocolCall(RemoteTransmitterBase *parent) : parent_(parent) {}
270 void set_send_times(uint32_t send_times) { this->send_times_ = send_times; }
271 void set_send_wait(uint32_t send_wait) { this->send_wait_ = send_wait; }
272 void perform() { this->parent_->template transmit<Protocol>(this->data, this->send_times_, this->send_wait_); }
273
274 typename Protocol::ProtocolData data{};
275
276 protected:
280 };
281 template<RemoteProtocolEncoder Protocol> ProtocolCall<Protocol> protocol_call() {
282 return ProtocolCall<Protocol>(this);
283 }
286 template<RemoteProtocolEncoder Protocol> class EncodeCall : public TransmitCall {
287 public:
288 explicit EncodeCall(TransmitCall call) : TransmitCall(call) {}
289 template<typename... Args> void encode(const Args &...args) { Protocol().encode(this->get_data(), args...); }
290 };
291 template<RemoteProtocolEncoder Protocol> EncodeCall<Protocol> encode_call() {
292 return EncodeCall<Protocol>(this->transmit());
293 }
294
295 protected:
296 using EncodeFn = void (*)(RemoteTransmitData *dst, const void *data);
297 // One out-of-line send path, so each transmit<P>() call site only fills its data.
298 void transmit_encoded_(EncodeFn encode, const void *data, uint32_t send_times, uint32_t send_wait);
299 template<RemoteProtocolEncoder Protocol> static void encode_thunk(RemoteTransmitData *dst, const void *data) {
300 Protocol().encode(dst, *static_cast<const typename Protocol::ProtocolData *>(data));
301 }
302 void send_(uint32_t send_times, uint32_t send_wait, uint16_t seq);
303 virtual void send_internal(uint32_t send_times, uint32_t send_wait) = 0;
306 virtual void flush_pending_completion() {}
307 void send_single_() { this->send_(1, 0, this->take_seq_()); }
308#ifdef USE_IR_RF_TRANSMIT_COMPLETE
311 void notify_complete_(bool sent) { ir_rf_transmit_complete(this, this->current_seq_, sent); }
312 uint16_t take_seq_() { return ++this->next_seq_; }
313
314 uint16_t next_seq_{0};
315 uint16_t current_seq_{0};
316#else
317 // seq tracking only exists for the API completion reply
318 void notify_complete_(bool /*sent*/) {}
319 static uint16_t take_seq_() { return 0; }
320#endif
321
324};
325
327 public:
328 virtual bool on_receive(RemoteReceiveData data) = 0;
329};
330
332 public:
333 virtual bool dump(RemoteReceiveData src) = 0;
334 virtual bool is_secondary() { return false; }
335};
336
338 public:
340 // Slots are counted at code generation; without one the call fails at compile time with the same message
341 // the runtime check logs
342#ifdef REMOTE_BASE_LISTENER_COUNT
344#else
345 template<typename T> void register_listener(T *) {
346 static_assert(sizeof(T) == 0, "No listener slot: register it from to_code() with remote_base.add_listener");
347 }
348#endif
349#ifdef REMOTE_BASE_DUMPER_COUNT
351#else
352 template<typename T> void register_dumper(T *) {
353 static_assert(sizeof(T) == 0, "No dumper slot: register it from to_code() with remote_base.add_dumper");
354 }
355#endif
356 void set_tolerance(uint32_t tolerance, ToleranceMode tolerance_mode) {
357 this->tolerance_ = tolerance;
358 this->tolerance_mode_ = tolerance_mode;
359 }
360
361 protected:
363
364#ifdef REMOTE_BASE_LISTENER_COUNT
366#endif
367#ifdef REMOTE_BASE_DUMPER_COUNT
369 RemoteReceiverDumperBase *secondary_dumper_{nullptr}; // runs only when no primary dumper matched
370#endif
374};
375
377 public Component,
379 public:
381 void dump_config() override;
382 virtual bool matches(RemoteReceiveData src) = 0;
383 bool on_receive(RemoteReceiveData src) override;
384};
385
386/* TEMPLATES */
387
388// Protocols are used only through their concrete type (see the RemoteProtocol* concepts); encode/decode/dump
389// stay non-virtual so unused ones link out
390template<typename T> class RemoteProtocol {
391 public:
392 using ProtocolData = T;
393};
394
395template<RemoteProtocolDecoder T> class RemoteReceiverBinarySensor : public RemoteReceiverBinarySensorBase {
396 public:
398
399 protected:
401 auto proto = T();
402 auto res = proto.decode(src);
403 return res.has_value() && *res == this->data_;
404 }
405
406 public:
407 void set_data(T::ProtocolData data) { data_ = data; }
408
409 protected:
410 T::ProtocolData data_;
411};
412
413template<RemoteProtocolDecoder T>
414class RemoteReceiverTrigger final : public Trigger<typename T::ProtocolData>, public RemoteReceiverListener {
415 protected:
417 auto proto = T();
418 auto res = proto.decode(src);
419 if (res.has_value()) {
420 this->trigger(*res);
421 return true;
422 }
423 return false;
424 }
425};
426
428 public:
431 void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
432
433 protected:
434 template<RemoteProtocolEncoder Protocol>
435 void transmit_(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
436 this->transmitter_->transmit<Protocol>(data, send_times, send_wait);
437 }
439};
440
441template<typename... Ts> class RemoteTransmitterActionBase : public RemoteTransmittable, public Action<Ts...> {
442 TEMPLATABLE_VALUE(uint32_t, send_times)
443 TEMPLATABLE_VALUE(uint32_t, send_wait)
444
445 protected:
446 void play(const Ts &...x) override {
447 auto call = this->transmitter_->transmit();
448 this->encode(call.get_data(), x...);
449 call.set_send_times(this->send_times_.value_or(x..., 1));
450 call.set_send_wait(this->send_wait_.value_or(x..., 0));
451 call.perform();
452 }
453 virtual void encode(RemoteTransmitData *dst, Ts... x) = 0;
454};
455
456template<RemoteProtocolDumper T> class RemoteReceiverDumper : public RemoteReceiverDumperBase {
457 public:
458 bool dump(RemoteReceiveData src) override {
459 auto proto = T();
460 auto decoded = proto.decode(src);
461 if (!decoded.has_value())
462 return false;
463 proto.dump(*decoded);
464 return true;
465 }
466};
467
468#define DECLARE_REMOTE_PROTOCOL_(prefix) \
469 using prefix##BinarySensor = RemoteReceiverBinarySensor<prefix##Protocol>; \
470 using prefix##Trigger = RemoteReceiverTrigger<prefix##Protocol>; \
471 using prefix##Dumper = RemoteReceiverDumper<prefix##Protocol>;
472#define DECLARE_REMOTE_PROTOCOL(prefix) DECLARE_REMOTE_PROTOCOL_(prefix)
473
474} // namespace esphome::remote_base
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:286
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Definition automation.h:556
void set_rmt_symbols(uint32_t rmt_symbols)
uint32_t to_microseconds_(uint32_t ticks)
uint32_t from_microseconds_(uint32_t us)
void set_clock_resolution(uint32_t clock_resolution)
int32_t operator[](uint32_t index) const
Definition remote_base.h:95
bool peek_mark_at_most(uint32_t length, uint32_t offset=0) const
bool expect_item(uint32_t mark, uint32_t space)
bool peek_space(uint32_t length, uint32_t offset=0) const
bool peek_space_at_most(uint32_t length, uint32_t offset=0) const
int32_t peek(uint32_t offset=0) const
Definition remote_base.h:98
bool peek_item(uint32_t mark, uint32_t space, uint32_t offset=0) const
bool expect_bytes_last_mark(uint8_t *out, size_t len)
As expect_bytes, but the last bit is only its mark: its space runs into the gap after the frame.
bool expect_bytes_(uint8_t *out, size_t len)
bool expect_bit_(uint32_t mark, uint32_t space)
One bit in T's order, consumed only when both its parts match.
int32_t upper_bound_(uint32_t length) const
bool is_valid(uint32_t offset=0) const
Definition remote_base.h:97
const RawTimings & get_raw_data() const
Definition remote_base.h:93
RemoteReceiveData(const RawTimings &data, uint32_t tolerance, ToleranceMode tolerance_mode)
Definition remote_base.h:90
bool expect_bytes(uint8_t *out, size_t len)
False at the first bit matching neither the one nor the zero timings, leaving it unread.
void set_tolerance(uint32_t tolerance, ToleranceMode tolerance_mode)
bool peek_space_at_least(uint32_t length, uint32_t offset=0) const
int32_t lower_bound_(uint32_t length) const
bool peek_mark(uint32_t length, uint32_t offset=0) const
bool expect_pulse_with_gap(uint32_t mark, uint32_t space)
bool peek_mark_at_least(uint32_t length, uint32_t offset=0) const
void register_dumper(RemoteReceiverDumperBase *dumper)
StaticVector< RemoteReceiverDumperBase *, REMOTE_BASE_DUMPER_COUNT > dumpers_
void set_tolerance(uint32_t tolerance, ToleranceMode tolerance_mode)
void register_listener(RemoteReceiverListener *listener)
RemoteReceiverDumperBase * secondary_dumper_
StaticVector< RemoteReceiverListener *, REMOTE_BASE_LISTENER_COUNT > listeners_
virtual bool matches(RemoteReceiveData src)=0
bool on_receive(RemoteReceiveData src) override
bool matches(RemoteReceiveData src) override
virtual bool dump(RemoteReceiveData src)=0
bool dump(RemoteReceiveData src) override
virtual bool on_receive(RemoteReceiveData data)=0
bool on_receive(RemoteReceiveData src) override
bool set_data_from_base64url(const std::string &base64url)
Set data from base64url-encoded little-endian int32 values Base64url is URL-safe: uses '-' instead of...
void set_carrier_frequency(uint32_t carrier_frequency)
Definition remote_base.h:64
void item(uint32_t mark, uint32_t space)
Definition remote_base.h:43
void item_bytes(const uint8_t *bytes, size_t len)
One item per bit of each byte.
Definition remote_base.h:49
const RawTimings & get_data() const
Definition remote_base.h:66
void set_data(const RawTimings &data)
Definition remote_base.h:67
void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count)
Set data from packed protobuf sint32 buffer (zigzag + varint encoded)
void set_transmitter(RemoteTransmitterBase *transmitter)
void transmit_(const typename Protocol::ProtocolData &data, uint32_t send_times=1, uint32_t send_wait=0)
RemoteTransmittable(RemoteTransmitterBase *transmitter)
virtual void encode(RemoteTransmitData *dst, Ts... x)=0
A transmit call whose encode() runs one of Protocol's encoders on its data, for a payload the protoco...
Protocol data filled field by field, then sent by perform(); the call a generated transmit action bui...
uint16_t get_seq() const
Identifies this transmission in the completion hook.
TransmitCall(RemoteTransmitterBase *parent, uint16_t seq)
void send_(uint32_t send_times, uint32_t send_wait, uint16_t seq)
void(*)(RemoteTransmitData *dst, const void *data) EncodeFn
ProtocolCall< Protocol > protocol_call()
static void encode_thunk(RemoteTransmitData *dst, const void *data)
void transmit(const typename Protocol::ProtocolData &data, uint32_t send_times=1, uint32_t send_wait=0)
virtual void send_internal(uint32_t send_times, uint32_t send_wait)=0
void transmit_encoded_(EncodeFn encode, const void *data, uint32_t send_times, uint32_t send_wait)
void notify_complete_(bool sent)
Reports the frame handed to the platform last, after its final repeat and before the on_complete trig...
RemoteTransmitData temp_
Use same vector for all transmits, avoids many allocations.
virtual void flush_pending_completion()
Platforms that report completion later wait out the previous frame here, before send_() assigns the n...
std::vector< uint8_t > & out
Definition gif.cpp:49
unsigned count
Definition gif.cpp:51
void ir_rf_transmit_complete(RemoteTransmitterBase *transmitter, uint16_t seq, bool sent)
Defined by ir_rf_base: answers the API request waiting on the entity that submitted seq; sent is fals...
std::vector< int32_t > RawTimings
Definition remote_base.h:20
const void size_t len
Definition hal.h:68
const void * src
Definition hal.h:68
static void uint32_t
A bit is a mark (one_mark or zero_mark, both bit_mark unless set) and a space (one_space or zero_spac...
Definition remote_base.h:29
uint16_t seq
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5