ESPHome 2026.10.2
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
23 public:
24 void mark(uint32_t length) { this->data_.push_back(length); }
25 void space(uint32_t length) { this->data_.push_back(-length); }
27 this->mark(mark);
28 this->space(space);
29 }
30 void reserve(uint32_t len) { this->data_.reserve(len); }
31 void set_carrier_frequency(uint32_t carrier_frequency) { this->carrier_frequency_ = carrier_frequency; }
33 const RawTimings &get_data() const { return this->data_; }
34 void set_data(const RawTimings &data) { this->data_ = data; }
39 void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count);
44 bool set_data_from_base64url(const std::string &base64url);
45 void reset() {
46 this->data_.clear();
47 this->carrier_frequency_ = 0;
48 }
49
50 protected:
53};
54
56 public:
57 explicit RemoteReceiveData(const RawTimings &data, uint32_t tolerance, ToleranceMode tolerance_mode)
58 : data_(data), index_(0), tolerance_(tolerance), tolerance_mode_(tolerance_mode) {}
59
60 const RawTimings &get_raw_data() const { return this->data_; }
61 uint32_t get_index() const { return index_; }
62 int32_t operator[](uint32_t index) const { return this->data_[index]; }
63 int32_t size() const { return this->data_.size(); }
64 bool is_valid(uint32_t offset = 0) const { return this->index_ + offset < this->data_.size(); }
65 int32_t peek(uint32_t offset = 0) const { return this->data_[this->index_ + offset]; }
66 bool peek_mark(uint32_t length, uint32_t offset = 0) const;
67 bool peek_mark_at_least(uint32_t length, uint32_t offset = 0) const;
68 bool peek_mark_at_most(uint32_t length, uint32_t offset = 0) const;
69 bool peek_space(uint32_t length, uint32_t offset = 0) const;
70 bool peek_space_at_least(uint32_t length, uint32_t offset = 0) const;
71 bool peek_space_at_most(uint32_t length, uint32_t offset = 0) const;
72 bool peek_item(uint32_t mark, uint32_t space, uint32_t offset = 0) const {
73 return this->peek_space(space, offset + 1) && this->peek_mark(mark, offset);
74 }
75
78 bool expect_item(uint32_t mark, uint32_t space);
79 bool expect_pulse_with_gap(uint32_t mark, uint32_t space);
80 void advance(uint32_t amount = 1) { this->index_ += amount; }
81 void reset() { this->index_ = 0; }
82
83 void set_tolerance(uint32_t tolerance, ToleranceMode tolerance_mode) {
84 this->tolerance_ = tolerance;
85 this->tolerance_mode_ = tolerance_mode;
86 }
89
90 protected:
91 int32_t lower_bound_(uint32_t length) const {
93 return int32_t(length - this->tolerance_);
94 } else if (this->tolerance_mode_ == TOLERANCE_MODE_PERCENTAGE) {
95 return int32_t(100 - this->tolerance_) * length / 100U;
96 }
97 return 0;
98 }
99 int32_t upper_bound_(uint32_t length) const {
101 return int32_t(length + this->tolerance_);
102 } else if (this->tolerance_mode_ == TOLERANCE_MODE_PERCENTAGE) {
103 return int32_t(100 + this->tolerance_) * length / 100U;
104 }
105 return 0;
106 }
107
112};
113
115 public:
116 explicit RemoteComponentBase(InternalGPIOPin *pin) : pin_(pin){};
117
118 protected:
120};
121
122#ifdef USE_ESP32
123#include <soc/soc_caps.h>
124#if SOC_RMT_SUPPORTED
126 public:
127 void set_clock_resolution(uint32_t clock_resolution) { this->clock_resolution_ = clock_resolution; }
128 void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; }
129
130 protected:
132 const uint32_t ticks_per_ten_us = this->clock_resolution_ / 100000u;
133 return us * ticks_per_ten_us / 10;
134 }
136 const uint32_t ticks_per_ten_us = this->clock_resolution_ / 100000u;
137 return (ticks * 10) / ticks_per_ten_us;
138 }
142};
143#endif // SOC_RMT_SUPPORTED
144#endif // USE_ESP32
145
147
148#ifdef USE_IR_RF_TRANSMIT_COMPLETE
152void ir_rf_transmit_complete(RemoteTransmitterBase *transmitter, uint16_t seq, bool sent);
153#endif
154// Protocol shapes, checked where a protocol is used so a missing method fails at the use site
155// instead of deep inside a template body. Receive-only protocols such as RCSwitchBase decode
156// without encoding.
157template<typename T>
158concept RemoteProtocolDecoder = requires(T proto, RemoteReceiveData src) {
159 { proto.decode(src) } -> std::same_as<optional<typename T::ProtocolData>>;
160};
161template<typename T>
162concept RemoteProtocolDumper = RemoteProtocolDecoder<T> && requires(T proto, const typename T::ProtocolData &data) {
163 proto.dump(data);
164};
165template<typename T>
166concept RemoteProtocolEncoder = requires(T proto, RemoteTransmitData *dst, const typename T::ProtocolData &data) {
167 proto.encode(dst, data);
168};
169
171 public:
174 public:
175 TransmitCall(RemoteTransmitterBase *parent, uint16_t seq) : parent_(parent), seq_(seq) {}
177 void set_send_times(uint32_t send_times) { send_times_ = send_times; }
178 void set_send_wait(uint32_t send_wait) { send_wait_ = send_wait; }
180 uint16_t get_seq() const { return this->seq_; }
181 void perform() { this->parent_->send_(this->send_times_, this->send_wait_, this->seq_); }
182
183 protected:
187 uint16_t seq_;
188 };
189
191 this->temp_.reset();
192 return TransmitCall(this, this->take_seq_());
193 }
194 template<RemoteProtocolEncoder Protocol>
195 void transmit(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
196 auto call = this->transmit();
197 Protocol().encode(call.get_data(), data);
198 call.set_send_times(send_times);
199 call.set_send_wait(send_wait);
200 call.perform();
201 }
202
203 protected:
204 void send_(uint32_t send_times, uint32_t send_wait, uint16_t seq);
205 virtual void send_internal(uint32_t send_times, uint32_t send_wait) = 0;
208 virtual void flush_pending_completion() {}
209 void send_single_() { this->send_(1, 0, this->take_seq_()); }
210#ifdef USE_IR_RF_TRANSMIT_COMPLETE
213 void notify_complete_(bool sent) { ir_rf_transmit_complete(this, this->current_seq_, sent); }
214 uint16_t take_seq_() { return ++this->next_seq_; }
215
216 uint16_t next_seq_{0};
217 uint16_t current_seq_{0};
218#else
219 // seq tracking only exists for the API completion reply
220 void notify_complete_(bool /*sent*/) {}
221 static uint16_t take_seq_() { return 0; }
222#endif
223
226};
227
229 public:
230 virtual bool on_receive(RemoteReceiveData data) = 0;
231};
232
234 public:
235 virtual bool dump(RemoteReceiveData src) = 0;
236 virtual bool is_secondary() { return false; }
237};
238
240 public:
242 // Slots are counted at code generation; without one the call fails at compile time with the same message
243 // the runtime check logs
244#ifdef REMOTE_BASE_LISTENER_COUNT
246#else
247 template<typename T> void register_listener(T *) {
248 static_assert(sizeof(T) == 0, "No listener slot: register it from to_code() with remote_base.add_listener");
249 }
250#endif
251#ifdef REMOTE_BASE_DUMPER_COUNT
253#else
254 template<typename T> void register_dumper(T *) {
255 static_assert(sizeof(T) == 0, "No dumper slot: register it from to_code() with remote_base.add_dumper");
256 }
257#endif
258 void set_tolerance(uint32_t tolerance, ToleranceMode tolerance_mode) {
259 this->tolerance_ = tolerance;
260 this->tolerance_mode_ = tolerance_mode;
261 }
262
263 protected:
265
266#ifdef REMOTE_BASE_LISTENER_COUNT
268#endif
269#ifdef REMOTE_BASE_DUMPER_COUNT
271 RemoteReceiverDumperBase *secondary_dumper_{nullptr}; // runs only when no primary dumper matched
272#endif
276};
277
279 public Component,
281 public:
283 void dump_config() override;
284 virtual bool matches(RemoteReceiveData src) = 0;
285 bool on_receive(RemoteReceiveData src) override;
286};
287
288/* TEMPLATES */
289
290// Protocols are used only through their concrete type (see the RemoteProtocol* concepts); encode/decode/dump
291// stay non-virtual so unused ones link out
292template<typename T> class RemoteProtocol {
293 public:
294 using ProtocolData = T;
295};
296
297template<RemoteProtocolDecoder T> class RemoteReceiverBinarySensor : public RemoteReceiverBinarySensorBase {
298 public:
300
301 protected:
303 auto proto = T();
304 auto res = proto.decode(src);
305 return res.has_value() && *res == this->data_;
306 }
307
308 public:
309 void set_data(T::ProtocolData data) { data_ = data; }
310
311 protected:
312 T::ProtocolData data_;
313};
314
315template<RemoteProtocolDecoder T>
316class RemoteReceiverTrigger final : public Trigger<typename T::ProtocolData>, public RemoteReceiverListener {
317 protected:
319 auto proto = T();
320 auto res = proto.decode(src);
321 if (res.has_value()) {
322 this->trigger(*res);
323 return true;
324 }
325 return false;
326 }
327};
328
330 public:
333 void set_transmitter(RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
334
335 protected:
336 template<RemoteProtocolEncoder Protocol>
337 void transmit_(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
338 this->transmitter_->transmit<Protocol>(data, send_times, send_wait);
339 }
341};
342
343template<typename... Ts> class RemoteTransmitterActionBase : public RemoteTransmittable, public Action<Ts...> {
344 TEMPLATABLE_VALUE(uint32_t, send_times)
345 TEMPLATABLE_VALUE(uint32_t, send_wait)
346
347 protected:
348 void play(const Ts &...x) override {
349 auto call = this->transmitter_->transmit();
350 this->encode(call.get_data(), x...);
351 call.set_send_times(this->send_times_.value_or(x..., 1));
352 call.set_send_wait(this->send_wait_.value_or(x..., 0));
353 call.perform();
354 }
355 virtual void encode(RemoteTransmitData *dst, Ts... x) = 0;
356};
357
358template<RemoteProtocolDumper T> class RemoteReceiverDumper : public RemoteReceiverDumperBase {
359 public:
360 bool dump(RemoteReceiveData src) override {
361 auto proto = T();
362 auto decoded = proto.decode(src);
363 if (!decoded.has_value())
364 return false;
365 proto.dump(*decoded);
366 return true;
367 }
368};
369
370#define DECLARE_REMOTE_PROTOCOL_(prefix) \
371 using prefix##BinarySensor = RemoteReceiverBinarySensor<prefix##Protocol>; \
372 using prefix##Trigger = RemoteReceiverTrigger<prefix##Protocol>; \
373 using prefix##Dumper = RemoteReceiverDumper<prefix##Protocol>;
374#define DECLARE_REMOTE_PROTOCOL(prefix) DECLARE_REMOTE_PROTOCOL_(prefix)
375
376} // namespace esphome::remote_base
Minimal static vector - saves memory by avoiding std::vector overhead.
Definition helpers.h:291
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Definition automation.h:528
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:62
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:65
bool peek_item(uint32_t mark, uint32_t space, uint32_t offset=0) const
Definition remote_base.h:72
int32_t upper_bound_(uint32_t length) const
Definition remote_base.h:99
bool is_valid(uint32_t offset=0) const
Definition remote_base.h:64
const RawTimings & get_raw_data() const
Definition remote_base.h:60
RemoteReceiveData(const RawTimings &data, uint32_t tolerance, ToleranceMode tolerance_mode)
Definition remote_base.h:57
void set_tolerance(uint32_t tolerance, ToleranceMode tolerance_mode)
Definition remote_base.h:83
bool peek_space_at_least(uint32_t length, uint32_t offset=0) const
int32_t lower_bound_(uint32_t length) const
Definition remote_base.h:91
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:31
void item(uint32_t mark, uint32_t space)
Definition remote_base.h:26
const RawTimings & get_data() const
Definition remote_base.h:33
void set_data(const RawTimings &data)
Definition remote_base.h:34
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
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 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 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...
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:64
const void * src
Definition hal.h:64
static void uint32_t
uint16_t seq
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5