diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index b51e1bf21a..a85cbadb6d 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1699,13 +1699,13 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c for (auto &item : items) { if (item.entity == entity && item.message_type == message_type) { // Replace with new creator - item.creator = std::move(creator); + item.creator = creator; return; } } // No existing item found, add new one - items.emplace_back(entity, std::move(creator), message_type, estimated_size); + items.emplace_back(entity, creator, message_type, estimated_size); } void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, @@ -1714,7 +1714,7 @@ void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCre // This avoids expensive vector::insert which shifts all elements // Note: We only ever have one high-priority message at a time (ping OR disconnect) // If we're disconnecting, pings are blocked, so this simple swap is sufficient - items.emplace_back(entity, std::move(creator), message_type, estimated_size); + items.emplace_back(entity, creator, message_type, estimated_size); if (items.size() > 1) { // Swap the new high-priority item to the front std::swap(items.front(), items.back()); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 05af0ccde7..4dad222ab4 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -505,27 +505,8 @@ class APIConnection final : public APIServerConnection { class MessageCreator { public: - // Constructor for function pointer MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; } - - // Constructor for const char * (Event types - no allocation needed) - explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; } - - // Delete copy operations - MessageCreator should only be moved - MessageCreator(const MessageCreator &other) = delete; - MessageCreator &operator=(const MessageCreator &other) = delete; - - // Move constructor - MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.function_ptr = nullptr; } - - // Move assignment - MessageCreator &operator=(MessageCreator &&other) noexcept { - if (this != &other) { - data_ = other.data_; - other.data_.function_ptr = nullptr; - } - return *this; - } + MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; } // Call operator - uses message_type to determine union type uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, @@ -535,7 +516,7 @@ class APIConnection final : public APIServerConnection { union Data { MessageCreatorPtr function_ptr; const char *const_char_ptr; - } data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before + } data_; // 4 bytes on 32-bit, 8 bytes on 64-bit }; // Generic batching mechanism for both state updates and entity info @@ -548,7 +529,7 @@ class APIConnection final : public APIServerConnection { // Constructor for creating BatchItem BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) - : entity(entity), creator(std::move(creator)), message_type(message_type), estimated_size(estimated_size) {} + : entity(entity), creator(creator), message_type(message_type), estimated_size(estimated_size) {} }; std::vector items; @@ -716,12 +697,12 @@ class APIConnection final : public APIServerConnection { } // Fall back to scheduled batching - return this->schedule_message_(entity, std::move(creator), message_type, estimated_size); + return this->schedule_message_(entity, creator, message_type, estimated_size); } // Helper function to schedule a deferred message with known message type bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { - this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size); + this->deferred_batch_.add_item(entity, creator, message_type, estimated_size); return this->schedule_batch_(); } diff --git a/esphome/components/remote_receiver/remote_receiver.cpp b/esphome/components/remote_receiver/remote_receiver.cpp index 53bfb0890f..a7ac74199d 100644 --- a/esphome/components/remote_receiver/remote_receiver.cpp +++ b/esphome/components/remote_receiver/remote_receiver.cpp @@ -5,63 +5,79 @@ #if defined(USE_LIBRETINY) || defined(USE_ESP8266) || defined(USE_RP2040) -namespace esphome { -namespace remote_receiver { +namespace esphome::remote_receiver { static const char *const TAG = "remote_receiver"; +static void IRAM_ATTR HOT write_value(RemoteReceiverComponentStore *arg, uint32_t delta, bool level) { + // convert level to -1 or +1 and write the delta to the buffer + int32_t multiplier = ((int32_t) level << 1) - 1; + uint32_t buffer_write = arg->buffer_write; + arg->buffer[buffer_write++] = (int32_t) delta * multiplier; + if (buffer_write >= arg->buffer_size) { + buffer_write = 0; + } + + // detect overflow and reset the write pointer + if (buffer_write == arg->buffer_read) { + buffer_write = arg->buffer_start; + arg->overflow = true; + } + + // detect idle and start a new sequence unless there is only idle in + // which case reset the write pointer instead + if (delta >= arg->idle_us) { + if (arg->buffer_write == arg->buffer_start) { + buffer_write = arg->buffer_start; + } else { + arg->buffer_start = buffer_write; + } + } + arg->buffer_write = buffer_write; +} + +static void IRAM_ATTR HOT commit_value(RemoteReceiverComponentStore *arg, uint32_t micros, bool level) { + // commit value if the level is different from the last commit level + if (level != arg->commit_level) { + write_value(arg, micros - arg->commit_micros, level); + arg->commit_micros = micros; + arg->commit_level = level; + } +} + void IRAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) { - const uint32_t now = micros(); - // If the lhs is 1 (rising edge) we should write to an uneven index and vice versa - const uint32_t next = (arg->buffer_write_at + 1) % arg->buffer_size; - const bool level = arg->pin.digital_read(); - if (level != next % 2) - return; + // invert the level so it matches the level of the signal before the edge + const bool curr_level = !arg->pin.digital_read(); + const uint32_t curr_micros = micros(); + const bool prev_level = arg->prev_level; + const uint32_t prev_micros = arg->prev_micros; - // If next is buffer_read, we have hit an overflow - if (next == arg->buffer_read_at) - return; - - const uint32_t last_change = arg->buffer[arg->buffer_write_at]; - const uint32_t time_since_change = now - last_change; - if (time_since_change <= arg->filter_us) - return; - - arg->buffer[arg->buffer_write_at = next] = now; // NOLINT(clang-diagnostic-deprecated-volatile) + // commit the previous value if the pulse is not filtered and the level is different + if (curr_micros - prev_micros >= arg->filter_us && prev_level != curr_level) { + commit_value(arg, prev_micros, prev_level); + } + arg->prev_micros = curr_micros; + arg->prev_level = curr_level; } void RemoteReceiverComponent::setup() { this->pin_->setup(); - auto &s = this->store_; - s.filter_us = this->filter_us_; - s.pin = this->pin_->to_isr(); - s.buffer_size = this->buffer_size_; - - this->high_freq_.start(); - if (s.buffer_size % 2 != 0) { - // Make sure divisible by two. This way, we know that every 0bxxx0 index is a space and every 0bxxx1 index is a mark - s.buffer_size++; - } - - s.buffer = new uint32_t[s.buffer_size]; - void *buf = (void *) s.buffer; - memset(buf, 0, s.buffer_size * sizeof(uint32_t)); - - // First index is a space. - if (this->pin_->digital_read()) { - s.buffer_write_at = s.buffer_read_at = 1; - } else { - s.buffer_write_at = s.buffer_read_at = 0; - } + this->store_.idle_us = this->idle_us_; + this->store_.filter_us = this->filter_us_; + this->store_.pin = this->pin_->to_isr(); + this->store_.buffer = new int32_t[this->buffer_size_]; + this->store_.buffer_size = this->buffer_size_; + this->store_.prev_micros = micros(); + this->store_.commit_micros = this->store_.prev_micros; + this->store_.prev_level = this->pin_->digital_read(); + this->store_.commit_level = this->store_.prev_level; this->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); + this->high_freq_.start(); } + void RemoteReceiverComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Receiver:"); LOG_PIN(" Pin: ", this->pin_); - if (this->pin_->digital_read()) { - ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to " - "invert the signal using 'inverted: True' in the pin schema!"); - } ESP_LOGCONFIG(TAG, " Buffer Size: %u\n" " Tolerance: %u%s\n" @@ -73,53 +89,54 @@ void RemoteReceiverComponent::dump_config() { } void RemoteReceiverComponent::loop() { + // check for overflow auto &s = this->store_; - - // copy write at to local variables, as it's volatile - const uint32_t write_at = s.buffer_write_at; - const uint32_t dist = (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size; - // signals must at least one rising and one leading edge - if (dist <= 1) - return; - const uint32_t now = micros(); - if (now - s.buffer[write_at] < this->idle_us_) { - // The last change was fewer than the configured idle time ago. - return; + if (s.overflow) { + ESP_LOGW(TAG, "Buffer overflow"); + s.overflow = false; } - ESP_LOGVV(TAG, "read_at=%u write_at=%u dist=%u now=%u end=%u", s.buffer_read_at, write_at, dist, now, - s.buffer[write_at]); - - // Skip first value, it's from the previous idle level - s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; - uint32_t prev = s.buffer_read_at; - s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; - const uint32_t reserve_size = 1 + (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size; - this->temp_.clear(); - this->temp_.reserve(reserve_size); - int32_t multiplier = s.buffer_read_at % 2 == 0 ? 1 : -1; - - for (uint32_t i = 0; prev != write_at; i++) { - int32_t delta = s.buffer[s.buffer_read_at] - s.buffer[prev]; - if (uint32_t(delta) >= this->idle_us_) { - // already found a space longer than idle. There must have been two pulses - break; + // if no data is available check for uncommitted data stuck in the buffer and commit + // the previous value if needed + uint32_t last_index = s.buffer_start; + if (last_index == s.buffer_read) { + InterruptLock lock; + if (s.buffer_read == s.buffer_start && s.buffer_write != s.buffer_start && + micros() - s.prev_micros >= this->idle_us_) { + commit_value(&s, s.prev_micros, s.prev_level); + write_value(&s, s.idle_us, !s.commit_level); + last_index = s.buffer_start; } - - ESP_LOGVV(TAG, " i=%u buffer[%u]=%u - buffer[%u]=%u -> %d", i, s.buffer_read_at, s.buffer[s.buffer_read_at], prev, - s.buffer[prev], multiplier * delta); - this->temp_.push_back(multiplier * delta); - prev = s.buffer_read_at; - s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size; - multiplier *= -1; } - s.buffer_read_at = (s.buffer_size + s.buffer_read_at - 1) % s.buffer_size; - this->temp_.push_back(this->idle_us_ * multiplier); + if (last_index == s.buffer_read) { + return; + } + // find the size of the packet and reserve the memory + uint32_t temp_read = s.buffer_read; + uint32_t reserve_size = 0; + while (temp_read != last_index && (uint32_t) std::abs(s.buffer[temp_read]) < this->idle_us_) { + reserve_size++; + temp_read++; + if (temp_read >= s.buffer_size) { + temp_read = 0; + } + } + this->temp_.clear(); + this->temp_.reserve(reserve_size + 1); + + // read the buffer + for (uint32_t i = 0; i < reserve_size + 1; i++) { + this->temp_.push_back((int32_t) s.buffer[s.buffer_read++]); + if (s.buffer_read >= s.buffer_size) { + s.buffer_read = 0; + } + } + + // call the listeners and dumpers this->call_listeners_dumpers_(); } -} // namespace remote_receiver -} // namespace esphome +} // namespace esphome::remote_receiver #endif diff --git a/esphome/components/remote_receiver/remote_receiver.h b/esphome/components/remote_receiver/remote_receiver.h index 3d2f7f0ef9..fabf0a481a 100644 --- a/esphome/components/remote_receiver/remote_receiver.h +++ b/esphome/components/remote_receiver/remote_receiver.h @@ -9,25 +9,31 @@ #include #endif -namespace esphome { -namespace remote_receiver { +namespace esphome::remote_receiver { #if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) struct RemoteReceiverComponentStore { static void gpio_intr(RemoteReceiverComponentStore *arg); - /// Stores the time (in micros) that the leading/falling edge happened at - /// * An even index means a falling edge appeared at the time stored at the index - /// * An uneven index means a rising edge appeared at the time stored at the index - volatile uint32_t *buffer{nullptr}; + /// Stores pulse durations in microseconds as signed integers + /// * Positive values indicate high pulses (marks) + /// * Negative values indicate low pulses (spaces) + volatile int32_t *buffer{nullptr}; /// The position last written to - volatile uint32_t buffer_write_at; + volatile uint32_t buffer_write{0}; + /// The start position of the last sequence + volatile uint32_t buffer_start{0}; /// The position last read from - uint32_t buffer_read_at{0}; - bool overflow{false}; + uint32_t buffer_read{0}; + volatile uint32_t commit_micros{0}; + volatile uint32_t prev_micros{0}; uint32_t buffer_size{1000}; uint32_t filter_us{10}; + uint32_t idle_us{10000}; ISRInternalGPIOPin pin; + volatile bool commit_level{false}; + volatile bool prev_level{false}; + volatile bool overflow{false}; }; #elif defined(USE_ESP32) struct RemoteReceiverComponentStore { @@ -84,15 +90,15 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase, std::string error_string_{""}; #endif -#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_ESP32) || defined(USE_RP2040) - RemoteReceiverComponentStore store_; +#if defined(USE_ESP8266) || defined(USE_LIBRETINY) || defined(USE_RP2040) HighFrequencyLoopRequester high_freq_; #endif + RemoteReceiverComponentStore store_; + uint32_t buffer_size_{}; uint32_t filter_us_{10}; uint32_t idle_us_{10000}; }; -} // namespace remote_receiver -} // namespace esphome +} // namespace esphome::remote_receiver diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index 49358eef3f..bd0bc8e57b 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -4,8 +4,7 @@ #ifdef USE_ESP32 #include -namespace esphome { -namespace remote_receiver { +namespace esphome::remote_receiver { static const char *const TAG = "remote_receiver.esp32"; #ifdef USE_ESP32_VARIANT_ESP32H2 @@ -248,7 +247,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_symbol_word_t *item, size_t item_c } } -} // namespace remote_receiver -} // namespace esphome +} // namespace esphome::remote_receiver #endif