diff --git a/esphome/components/infrared/infrared.cpp b/esphome/components/infrared/infrared.cpp index 5f8d63926a..294d69e523 100644 --- a/esphome/components/infrared/infrared.cpp +++ b/esphome/components/infrared/infrared.cpp @@ -18,7 +18,15 @@ InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) { InfraredCall &InfraredCall::set_raw_timings(const std::vector &timings) { this->raw_timings_ = &timings; - this->packed_data_ = nullptr; // Clear packed if vector is set + this->packed_data_ = nullptr; + this->base85_ptr_ = nullptr; + return *this; +} + +InfraredCall &InfraredCall::set_raw_timings_base85(const std::string &base85) { + this->base85_ptr_ = &base85; + this->raw_timings_ = nullptr; + this->packed_data_ = nullptr; return *this; } @@ -26,7 +34,8 @@ InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t this->packed_data_ = data; this->packed_length_ = length; this->packed_count_ = count; - this->raw_timings_ = nullptr; // Clear vector if packed is set + this->raw_timings_ = nullptr; + this->base85_ptr_ = nullptr; return *this; } @@ -92,6 +101,14 @@ void Infrared::control(const InfraredCall &call) { call.get_packed_count()); ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(), call.get_repeat_count()); + } else if (call.is_base85()) { + // Decode base85 directly into transmit buffer (zero heap allocations) + if (!transmit_data->set_data_from_base85(call.get_base85_data())) { + ESP_LOGE(TAG, "Invalid base85 data"); + return; + } + ESP_LOGD(TAG, "Transmitting base85 raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(), + call.get_repeat_count()); } else { // From vector (lambdas/automations) transmit_data->set_data(call.get_raw_timings()); diff --git a/esphome/components/infrared/infrared.h b/esphome/components/infrared/infrared.h index 3a891301f4..12feb2c692 100644 --- a/esphome/components/infrared/infrared.h +++ b/esphome/components/infrared/infrared.h @@ -31,6 +31,10 @@ class InfraredCall { /// Set the raw timings (positive = mark, negative = space) /// Note: The timings vector must outlive the InfraredCall (zero-copy reference) InfraredCall &set_raw_timings(const std::vector &timings); + /// Set the raw timings from base85-encoded int32 data + /// Note: The string must outlive the InfraredCall (zero-copy pointer) + /// Decoded directly into transmit buffer at perform() time + InfraredCall &set_raw_timings_base85(const std::string &base85); /// Set the raw timings from packed protobuf sint32 data (zero-copy from wire) /// Note: The data must outlive the InfraredCall InfraredCall &set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count); @@ -42,12 +46,18 @@ class InfraredCall { /// Get the carrier frequency const optional &get_carrier_frequency() const { return this->carrier_frequency_; } - /// Get the raw timings (only valid if set via set_raw_timings, not packed) + /// Get the raw timings (only valid if set via set_raw_timings, not packed or base85) const std::vector &get_raw_timings() const { return *this->raw_timings_; } - /// Check if raw timings have been set (either vector or packed) - bool has_raw_timings() const { return this->raw_timings_ != nullptr || this->packed_data_ != nullptr; } + /// Check if raw timings have been set (vector, packed, or base85) + bool has_raw_timings() const { + return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base85_ptr_ != nullptr; + } /// Check if using packed data format bool is_packed() const { return this->packed_data_ != nullptr; } + /// Check if using base85 data format + bool is_base85() const { return this->base85_ptr_ != nullptr; } + /// Get the base85 data string + const std::string &get_base85_data() const { return *this->base85_ptr_; } /// Get packed data (only valid if set via set_raw_timings_packed) const uint8_t *get_packed_data() const { return this->packed_data_; } uint16_t get_packed_length() const { return this->packed_length_; } @@ -61,6 +71,8 @@ class InfraredCall { optional carrier_frequency_; // Vector-based timings (for lambdas/automations) const std::vector *raw_timings_{nullptr}; + // Base85-encoded data pointer (for web_server - decoded directly into transmit buffer) + const std::string *base85_ptr_{nullptr}; // Packed protobuf timings (for API zero-copy) const uint8_t *packed_data_{nullptr}; uint16_t packed_length_{0}; diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index 2f1c107bf4..b721cfd39a 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -159,6 +159,25 @@ void RemoteTransmitData::set_data_from_packed_sint32(const uint8_t *data, size_t } } +bool RemoteTransmitData::set_data_from_base85(const std::string &base85) { + size_t len = base85.size(); + if (len % 5 != 0) + return false; + + this->data_.clear(); // Retains capacity! + const char *ptr = base85.data(); + const char *end = ptr + len; + + while (ptr < end) { + int32_t value; + if (!base85_decode_int32(ptr, value)) + return false; + this->data_.push_back(value); + ptr += 5; + } + return true; +} + /* RemoteTransmitterBase */ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) { diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index a11e0271af..2d7642cc31 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -36,6 +36,11 @@ class RemoteTransmitData { /// @param len Length of the buffer in bytes /// @param count Number of values (for reserve optimization) void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count); + /// Set data from base85-encoded int32 values + /// Decodes directly into internal buffer (zero heap allocations) + /// @param base85 Base85-encoded string (5 chars per int32 value) + /// @return true if successful, false if decode failed or invalid size + bool set_data_from_base85(const std::string &base85); void reset() { this->data_.clear(); this->carrier_frequency_ = 0;