diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 189cee9e0eb..f8e9dae09a6 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -410,7 +410,7 @@ class ProtoEncode { [[nodiscard]] static inline uint8_t *encode_short_string_force(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag, const StringRef &ref) { #ifdef USE_ESP8266 - // memcpy_P reads RAM too, so RAM strings share the progmem copy + // memcpy_P reads RAM too, and sharing the progmem copy is smaller than an inline memcpy return encode_short_progmem_string_force(pos PROTO_ENCODE_DEBUG_ARG, tag, ProgmemStringRef(ref)); #else #ifdef ESPHOME_DEBUG_API @@ -424,7 +424,6 @@ class ProtoEncode { #endif } /// encode_short_string_force for a string that may be in flash (PROGMEM). -#ifdef USE_ESP8266 [[nodiscard]] static inline uint8_t *encode_short_progmem_string_force( uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag, const ProgmemStringRef &ref) { #ifdef ESPHOME_DEBUG_API @@ -436,12 +435,6 @@ class ProtoEncode { progmem_memcpy(pos + 2, ref.progmem_ptr(), ref.size()); return pos + 2 + ref.size(); } -#else - [[nodiscard]] static inline uint8_t *encode_short_progmem_string_force( - uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag, const ProgmemStringRef &ref) { - return encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, tag, ref.ram_ref()); - } -#endif /// Write a precomputed tag byte + 32-bit value. Outlined on embedded: one copy beats inline stores per field. [[nodiscard]] static PROTO_OUTLINE_FOR_SIZE uint8_t *write_tag_and_fixed32( uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t tag, uint32_t value) { diff --git a/esphome/core/string_ref.h b/esphome/core/string_ref.h index 4e388a4d091..d8c68d7e7fa 100644 --- a/esphome/core/string_ref.h +++ b/esphome/core/string_ref.h @@ -190,24 +190,6 @@ inline bool operator!=(const StringRef &lhs, const __FlashStringHelper *rhs) { r inline bool operator!=(const __FlashStringHelper *lhs, const StringRef &rhs) { return !(rhs == lhs); } -/// Codegen use only: a flash string literal with its byte length, compared without a runtime strlen. -struct ProgmemStringRef { - PGM_P str; - size_t len; -}; - -inline bool operator==(const std::string &lhs, ProgmemStringRef rhs) { - return lhs.size() == rhs.len && (rhs.len == 0 || memcmp_P(lhs.data(), rhs.str, rhs.len) == 0); -} - -inline bool operator!=(const std::string &lhs, ProgmemStringRef rhs) { return !(lhs == rhs); } - -inline bool operator==(const StringRef &lhs, ProgmemStringRef rhs) { - // A StringRef may be {nullptr, 0}, and memcmp_P with a null pointer is undefined even for 0 bytes - return lhs.size() == rhs.len && (rhs.len == 0 || memcmp_P(lhs.c_str(), rhs.str, rhs.len) == 0); -} - -inline bool operator!=(const StringRef &lhs, ProgmemStringRef rhs) { return !(lhs == rhs); } #endif // USE_ESP8266 inline bool operator<(const StringRef &lhs, const StringRef &rhs) { @@ -271,7 +253,9 @@ class ProgmemStringRef { bool equals(const StringRef &other) const { #ifdef USE_ESP8266 - return other.size() == this->ref_.size() && memcmp_P(other.c_str(), this->ref_.c_str(), other.size()) == 0; + // A StringRef may be {nullptr, 0}, and memcmp_P with a null pointer is undefined even for 0 bytes + return other.size() == this->ref_.size() && + (other.empty() || memcmp_P(other.c_str(), this->ref_.c_str(), other.size()) == 0); #else return other == this->ref_; #endif @@ -293,6 +277,13 @@ class ProgmemStringRef { StringRef ref_; }; +#ifdef USE_ESP8266 +inline bool operator==(const StringRef &lhs, const ProgmemStringRef &rhs) { return rhs.equals(lhs); } +inline bool operator!=(const StringRef &lhs, const ProgmemStringRef &rhs) { return !rhs.equals(lhs); } +inline bool operator==(const std::string &lhs, const ProgmemStringRef &rhs) { return rhs.equals(StringRef(lhs)); } +inline bool operator!=(const std::string &lhs, const ProgmemStringRef &rhs) { return !rhs.equals(StringRef(lhs)); } +#endif + // String conversion functions for ADL compatibility (allows stoi(x) where x is StringRef) // Must be in esphome namespace for ADL to find them. Uses strtol/strtod directly to avoid heap allocation. namespace internal { diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py index 4760890aab6..2f601f5c947 100755 --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1158,6 +1158,11 @@ class PointerToBufferTypeBase(TypeInfo): """Get the wire type for this field.""" return WireType.LENGTH_DELIMITED # Uses wire type 2 + @property + def progmem(self) -> bool: + """Whether the data is in flash, so encode and dump copy it with progmem_memcpy.""" + return get_field_opt(self._field, pb.progmem, False) + def get_estimated_size(self) -> int: # field ID + length varint + typical data (assume small for pointer fields) return self.calculate_field_id_size() + 2 + 16 @@ -1171,11 +1176,6 @@ class PointerToBytesBufferType(PointerToBufferTypeBase): reference_type = "const uint8_t*" const_reference_type = "const uint8_t*" - @property - def progmem(self) -> bool: - """Whether the data is in flash, so encode and dump copy it with progmem_memcpy.""" - return get_field_opt(self._field, pb.progmem, False) - @property def public_content(self) -> list[str]: # Use uint16_t for length - max packet size is well below 65535 @@ -1253,11 +1253,6 @@ class PointerToStringBufferType(PointerToBufferTypeBase): one zero fill. Any encode path that copies unconditionally must check this.""" return not self._needs_decode and not self.force - @property - def progmem(self) -> bool: - """Whether the string is in flash, so encode and dump copy it with progmem_memcpy.""" - return get_field_opt(self._field, pb.progmem, False) - @property def public_content(self) -> list[str]: if self.progmem: