1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 00:31:58 +00:00

[infrared, remote_base] Replace base85 with base64url for web server infrared transmissions (#13265)

This commit is contained in:
Keith Burzinski
2026-01-16 18:15:27 -06:00
committed by GitHub
parent 58a9e30017
commit f7ad324d81
4 changed files with 36 additions and 29 deletions

View File

@@ -19,12 +19,12 @@ InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) {
InfraredCall &InfraredCall::set_raw_timings(const std::vector<int32_t> &timings) {
this->raw_timings_ = &timings;
this->packed_data_ = nullptr;
this->base85_ptr_ = nullptr;
this->base64url_ptr_ = nullptr;
return *this;
}
InfraredCall &InfraredCall::set_raw_timings_base85(const std::string &base85) {
this->base85_ptr_ = &base85;
InfraredCall &InfraredCall::set_raw_timings_base64url(const std::string &base64url) {
this->base64url_ptr_ = &base64url;
this->raw_timings_ = nullptr;
this->packed_data_ = nullptr;
return *this;
@@ -35,7 +35,7 @@ InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t
this->packed_length_ = length;
this->packed_count_ = count;
this->raw_timings_ = nullptr;
this->base85_ptr_ = nullptr;
this->base64url_ptr_ = nullptr;
return *this;
}
@@ -101,13 +101,22 @@ 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");
} else if (call.is_base64url()) {
// Decode base64url (URL-safe) into transmit buffer
if (!transmit_data->set_data_from_base64url(call.get_base64url_data())) {
ESP_LOGE(TAG, "Invalid base64url data");
return;
}
ESP_LOGD(TAG, "Transmitting base85 raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(),
// Sanity check: validate timing values are within reasonable bounds
constexpr int32_t max_timing_us = 500000; // 500ms absolute max
for (int32_t timing : transmit_data->get_data()) {
int32_t abs_timing = timing < 0 ? -timing : timing;
if (abs_timing > max_timing_us) {
ESP_LOGE(TAG, "Invalid timing value: %d µs (max %d)", timing, max_timing_us);
return;
}
}
ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(),
call.get_repeat_count());
} else {
// From vector (lambdas/automations)

View File

@@ -40,11 +40,11 @@ class InfraredCall {
/// @note Usage: Primarily for lambdas/automations where the vector is in scope.
InfraredCall &set_raw_timings(const std::vector<int32_t> &timings);
/// Set the raw timings from base85-encoded int32 data
/// Set the raw timings from base64url-encoded little-endian int32 data
/// @note Lifetime: Stores a pointer to the string. The string must outlive perform().
/// @note Usage: For web_server where the encoded string is on the stack.
/// @note Usage: For web_server - base64url is fully URL-safe (uses '-' and '_').
/// @note Decoding happens at perform() time, directly into the transmit buffer.
InfraredCall &set_raw_timings_base85(const std::string &base85);
InfraredCall &set_raw_timings_base64url(const std::string &base64url);
/// Set the raw timings from packed protobuf sint32 data (zigzag + varint encoded)
/// @note Lifetime: Stores a pointer to the buffer. The buffer must outlive perform().
@@ -59,18 +59,18 @@ class InfraredCall {
/// Get the carrier frequency
const optional<uint32_t> &get_carrier_frequency() const { return this->carrier_frequency_; }
/// Get the raw timings (only valid if set via set_raw_timings, not packed or base85)
/// Get the raw timings (only valid if set via set_raw_timings)
const std::vector<int32_t> &get_raw_timings() const { return *this->raw_timings_; }
/// Check if raw timings have been set (vector, packed, or base85)
/// Check if raw timings have been set (any format)
bool has_raw_timings() const {
return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base85_ptr_ != nullptr;
return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base64url_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_; }
/// Check if using base64url data format
bool is_base64url() const { return this->base64url_ptr_ != nullptr; }
/// Get the base64url data string
const std::string &get_base64url_data() const { return *this->base64url_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_; }
@@ -84,8 +84,8 @@ class InfraredCall {
optional<uint32_t> carrier_frequency_;
// Pointer to vector-based timings (caller-owned, must outlive perform())
const std::vector<int32_t> *raw_timings_{nullptr};
// Pointer to base85-encoded string (caller-owned, must outlive perform())
const std::string *base85_ptr_{nullptr};
// Pointer to base64url-encoded string (caller-owned, must outlive perform())
const std::string *base64url_ptr_{nullptr};
// Pointer to packed protobuf buffer (caller-owned, must outlive perform())
const uint8_t *packed_data_{nullptr};
uint16_t packed_length_{0};

View File

@@ -2,8 +2,6 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace remote_base {
@@ -160,8 +158,8 @@ void RemoteTransmitData::set_data_from_packed_sint32(const uint8_t *data, size_t
}
}
bool RemoteTransmitData::set_data_from_base85(const std::string &base85) {
return base85_decode_int32_vector(base85, this->data_);
bool RemoteTransmitData::set_data_from_base64url(const std::string &base64url) {
return base64_decode_int32_vector(base64url, this->data_);
}
/* RemoteTransmitterBase */

View File

@@ -36,11 +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)
/// Set data from base64url-encoded little-endian int32 values
/// Base64url is URL-safe: uses '-' instead of '+', '_' instead of '/'
/// @param base64url Base64url-encoded string of little-endian int32 values
/// @return true if successful, false if decode failed or invalid size
bool set_data_from_base85(const std::string &base85);
bool set_data_from_base64url(const std::string &base64url);
void reset() {
this->data_.clear();
this->carrier_frequency_ = 0;