ESPHome 2025.12.0-dev
Loading...
Searching...
No Matches
remote_transmitter_esp32.cpp
Go to the documentation of this file.
2#include "esphome/core/log.h"
4
5#ifdef USE_ESP32
6#include <driver/gpio.h>
7
8namespace esphome {
9namespace remote_transmitter {
10
11static const char *const TAG = "remote_transmitter";
12
13// Maximum RMT symbol duration (15-bit field)
14static constexpr uint32_t RMT_SYMBOL_DURATION_MAX = 0x7FFF;
15
16#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
17static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t written, size_t free,
18 rmt_symbol_word_t *symbols, bool *done, void *arg) {
19 auto *store = static_cast<RemoteTransmitterComponentStore *>(arg);
20 const auto *encoded = static_cast<const rmt_symbol_half_t *>(data);
21 size_t length = size / sizeof(rmt_symbol_half_t);
22 size_t count = 0;
23
24 // copy symbols
25 for (size_t i = 0; i < free; i++) {
26 uint16_t sym_0 = encoded[store->index++].val;
27 if (store->index >= length) {
28 store->index = 0;
29 store->times--;
30 if (store->times == 0) {
31 *done = true;
32 symbols[count++].val = sym_0;
33 return count;
34 }
35 }
36 uint16_t sym_1 = encoded[store->index++].val;
37 if (store->index >= length) {
38 store->index = 0;
39 store->times--;
40 if (store->times == 0) {
41 *done = true;
42 symbols[count++].val = sym_0 | (sym_1 << 16);
43 return count;
44 }
45 }
46 symbols[count++].val = sym_0 | (sym_1 << 16);
47 }
48 *done = false;
49 return count;
50}
51#endif
52
54 this->inverted_ = this->pin_->is_inverted();
55 this->configure_rmt_();
56}
57
59 ESP_LOGCONFIG(TAG, "Remote Transmitter:");
60 ESP_LOGCONFIG(TAG,
61 " Clock resolution: %" PRIu32 " hz\n"
62 " RMT symbols: %" PRIu32,
63 this->clock_resolution_, this->rmt_symbols_);
64 LOG_PIN(" Pin: ", this->pin_);
65
66 if (this->current_carrier_frequency_ != 0 && this->carrier_duty_percent_ != 100) {
67 ESP_LOGCONFIG(TAG, " Carrier Duty: %u%%", this->carrier_duty_percent_);
68 }
69
70 if (this->is_failed()) {
71 ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_),
72 this->error_string_.c_str());
73 }
74}
75
77#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
78 rmt_symbol_half_t symbol = {
79 .duration = 1,
80 .level = value,
81 };
82 rmt_transmit_config_t config;
83 memset(&config, 0, sizeof(config));
84 config.flags.eot_level = value;
85 this->store_.times = 1;
86 this->store_.index = 0;
87#else
88 rmt_symbol_word_t symbol = {
89 .duration0 = 1,
90 .level0 = value,
91 .duration1 = 0,
92 .level1 = value,
93 };
94 rmt_transmit_config_t config;
95 memset(&config, 0, sizeof(config));
96 config.flags.eot_level = value;
97#endif
98 esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config);
99 if (error != ESP_OK) {
100 ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
101 this->status_set_warning();
102 }
103 error = rmt_tx_wait_all_done(this->channel_, -1);
104 if (error != ESP_OK) {
105 ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
106 this->status_set_warning();
107 }
108}
109
111 esp_err_t error;
112
113 if (!this->initialized_) {
114 bool open_drain = (this->pin_->get_flags() & gpio::FLAG_OPEN_DRAIN) != 0;
115 rmt_tx_channel_config_t channel;
116 memset(&channel, 0, sizeof(channel));
117 channel.clk_src = RMT_CLK_SRC_DEFAULT;
118 channel.resolution_hz = this->clock_resolution_;
119 channel.gpio_num = gpio_num_t(this->pin_->get_pin());
120 channel.mem_block_symbols = this->rmt_symbols_;
121 channel.trans_queue_depth = 1;
122 channel.flags.io_loop_back = open_drain;
123 channel.flags.io_od_mode = open_drain;
124 channel.flags.invert_out = 0;
125 channel.flags.with_dma = this->with_dma_;
126 channel.intr_priority = 0;
127 error = rmt_new_tx_channel(&channel, &this->channel_);
128 if (error != ESP_OK) {
129 this->error_code_ = error;
130 if (error == ESP_ERR_NOT_FOUND) {
131 this->error_string_ = "out of RMT symbol memory";
132 } else {
133 this->error_string_ = "in rmt_new_tx_channel";
134 }
135 this->mark_failed();
136 return;
137 }
138 if (this->pin_->get_flags() & gpio::FLAG_PULLUP) {
139 gpio_pullup_en(gpio_num_t(this->pin_->get_pin()));
140 } else {
141 gpio_pullup_dis(gpio_num_t(this->pin_->get_pin()));
142 }
143
144#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
145 rmt_simple_encoder_config_t encoder;
146 memset(&encoder, 0, sizeof(encoder));
147 encoder.callback = encoder_callback;
148 encoder.arg = &this->store_;
149 encoder.min_chunk_size = 1;
150 error = rmt_new_simple_encoder(&encoder, &this->encoder_);
151 if (error != ESP_OK) {
152 this->error_code_ = error;
153 this->error_string_ = "in rmt_new_simple_encoder";
154 this->mark_failed();
155 return;
156 }
157#else
158 rmt_copy_encoder_config_t encoder;
159 memset(&encoder, 0, sizeof(encoder));
160 error = rmt_new_copy_encoder(&encoder, &this->encoder_);
161 if (error != ESP_OK) {
162 this->error_code_ = error;
163 this->error_string_ = "in rmt_new_copy_encoder";
164 this->mark_failed();
165 return;
166 }
167#endif
168
169 error = rmt_enable(this->channel_);
170 if (error != ESP_OK) {
171 this->error_code_ = error;
172 this->error_string_ = "in rmt_enable";
173 this->mark_failed();
174 return;
175 }
176 this->digital_write(open_drain || this->inverted_);
177 this->initialized_ = true;
178 }
179
180 if (this->current_carrier_frequency_ == 0 || this->carrier_duty_percent_ == 100) {
181 error = rmt_apply_carrier(this->channel_, nullptr);
182 } else {
183 rmt_carrier_config_t carrier;
184 memset(&carrier, 0, sizeof(carrier));
185 carrier.frequency_hz = this->current_carrier_frequency_;
186 carrier.duty_cycle = (float) this->carrier_duty_percent_ / 100.0f;
187 carrier.flags.polarity_active_low = this->inverted_;
188 carrier.flags.always_on = 1;
189 error = rmt_apply_carrier(this->channel_, &carrier);
190 }
191 if (error != ESP_OK) {
192 this->error_code_ = error;
193 this->error_string_ = "in rmt_apply_carrier";
194 this->mark_failed();
195 return;
196 }
197}
198
200 esp_err_t error = rmt_tx_wait_all_done(this->channel_, -1);
201 if (error != ESP_OK) {
202 ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
203 this->status_set_warning();
204 }
205
206 this->complete_trigger_->trigger();
207}
208
209#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
210void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
211 uint64_t total_duration = 0;
212
213 if (this->is_failed()) {
214 return;
215 }
216
217 // if the timeout was cancelled, block until the tx is complete
218 if (this->non_blocking_ && this->cancel_timeout("complete")) {
219 this->wait_for_rmt_();
220 }
221
224 this->configure_rmt_();
225 }
226
227 this->rmt_temp_.clear();
228 this->rmt_temp_.reserve(this->temp_.get_data().size() + 1);
229
230 // encode any delay at the start of the buffer to simplify the encoder callback
231 // this will be skipped the first time around
232 total_duration += send_wait * (send_times - 1);
233 send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait));
234 while (send_wait > 0) {
235 int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX));
236 this->rmt_temp_.push_back({
237 .duration = static_cast<uint16_t>(duration),
238 .level = static_cast<uint16_t>(this->eot_level_),
239 });
240 send_wait -= duration;
241 }
242
243 // encode data
244 size_t offset = this->rmt_temp_.size();
245 for (int32_t value : this->temp_.get_data()) {
246 bool level = value >= 0;
247 if (!level) {
248 value = -value;
249 }
250 total_duration += value * send_times;
251 value = this->from_microseconds_(static_cast<uint32_t>(value));
252 while (value > 0) {
253 int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX));
254 this->rmt_temp_.push_back({
255 .duration = static_cast<uint16_t>(duration),
256 .level = static_cast<uint16_t>(level ^ this->inverted_),
257 });
258 value -= duration;
259 }
260 }
261
262 if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.size() <= offset) {
263 ESP_LOGE(TAG, "Empty data");
264 return;
265 }
266
267 this->transmit_trigger_->trigger();
268
269 rmt_transmit_config_t config;
270 memset(&config, 0, sizeof(config));
271 config.flags.eot_level = this->eot_level_;
272 this->store_.times = send_times;
273 this->store_.index = offset;
274 esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
275 this->rmt_temp_.size() * sizeof(rmt_symbol_half_t), &config);
276 if (error != ESP_OK) {
277 ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
278 this->status_set_warning();
279 } else {
280 this->status_clear_warning();
281 }
282
283 if (this->non_blocking_) {
284 this->set_timeout("complete", total_duration / 1000, [this]() { this->wait_for_rmt_(); });
285 } else {
286 this->wait_for_rmt_();
287 }
288}
289#else
290void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
291 if (this->is_failed())
292 return;
293
296 this->configure_rmt_();
297 }
298
299 this->rmt_temp_.clear();
300 this->rmt_temp_.reserve((this->temp_.get_data().size() + 1) / 2);
301 uint32_t rmt_i = 0;
302 rmt_symbol_word_t rmt_item;
303
304 for (int32_t val : this->temp_.get_data()) {
305 bool level = val >= 0;
306 if (!level)
307 val = -val;
308 val = this->from_microseconds_(static_cast<uint32_t>(val));
309
310 do {
311 int32_t item = std::min(val, int32_t(RMT_SYMBOL_DURATION_MAX));
312 val -= item;
313
314 if (rmt_i % 2 == 0) {
315 rmt_item.level0 = static_cast<uint32_t>(level ^ this->inverted_);
316 rmt_item.duration0 = static_cast<uint32_t>(item);
317 } else {
318 rmt_item.level1 = static_cast<uint32_t>(level ^ this->inverted_);
319 rmt_item.duration1 = static_cast<uint32_t>(item);
320 this->rmt_temp_.push_back(rmt_item);
321 }
322 rmt_i++;
323 } while (val != 0);
324 }
325
326 if (rmt_i % 2 == 1) {
327 rmt_item.level1 = 0;
328 rmt_item.duration1 = 0;
329 this->rmt_temp_.push_back(rmt_item);
330 }
331
332 if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.empty()) {
333 ESP_LOGE(TAG, "Empty data");
334 return;
335 }
336 this->transmit_trigger_->trigger();
337 for (uint32_t i = 0; i < send_times; i++) {
338 rmt_transmit_config_t config;
339 memset(&config, 0, sizeof(config));
340 config.flags.eot_level = this->eot_level_;
341 esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
342 this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config);
343 if (error != ESP_OK) {
344 ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
345 this->status_set_warning();
346 } else {
347 this->status_clear_warning();
348 }
349 error = rmt_tx_wait_all_done(this->channel_, -1);
350 if (error != ESP_OK) {
351 ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
352 this->status_set_warning();
353 }
354 if (i + 1 < send_times)
355 delayMicroseconds(send_wait);
356 }
357 this->complete_trigger_->trigger();
358}
359#endif
360
361} // namespace remote_transmitter
362} // namespace esphome
363
364#endif
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_set_warning(const char *message=nullptr)
bool cancel_timeout(const std::string &name)
Cancel a timeout function.
void status_clear_warning()
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
virtual gpio::Flags get_flags() const =0
Retrieve GPIO pin flags.
virtual uint8_t get_pin() const =0
virtual bool is_inverted() const =0
void trigger(const Ts &...x)
Inform the parent automation that the event has triggered.
Definition automation.h:169
uint32_t from_microseconds_(uint32_t us)
const RawTimings & get_data() const
Definition remote_base.h:32
RemoteTransmitData temp_
Use same vector for all transmits, avoids many allocations.
void send_internal(uint32_t send_times, uint32_t send_wait) override
mopeka_std_values val[4]
uint8_t duration
Definition msa3xx.h:0
@ FLAG_OPEN_DRAIN
Definition gpio.h:20
@ FLAG_PULLUP
Definition gpio.h:21
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition core.cpp:33
uint16_t length
Definition tt21100.cpp:0