mirror of
https://github.com/esphome/esphome.git
synced 2026-09-15 17:18:40 +00:00
Merge remote-tracking branch 'upstream/dev' into integration
This commit is contained in:
@@ -80,4 +80,15 @@ extend google.protobuf.FieldOptions {
|
||||
// Example: [(container_pointer_no_template) = "light::ColorModeMask"]
|
||||
// generates: const light::ColorModeMask *supported_color_modes{};
|
||||
optional string container_pointer_no_template = 50014;
|
||||
|
||||
// packed_buffer: Expose raw packed buffer instead of decoding into container
|
||||
// When set on a packed repeated field, the generated code stores a pointer
|
||||
// to the raw protobuf buffer instead of decoding values. This enables
|
||||
// zero-copy passthrough when the consumer can decode on-demand.
|
||||
// The field must be a packed repeated field (packed=true).
|
||||
// Generates three fields:
|
||||
// - const uint8_t *<field>_data_{nullptr};
|
||||
// - uint16_t <field>_length_{0};
|
||||
// - uint16_t <field>_count_{0};
|
||||
optional bool packed_buffer = 50015 [default=false];
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ from .const import (
|
||||
CONF_SDK_SILENT,
|
||||
CONF_UART_PORT,
|
||||
FAMILIES,
|
||||
FAMILY_BK7231N,
|
||||
FAMILY_COMPONENT,
|
||||
FAMILY_FRIENDLY,
|
||||
KEY_BOARD,
|
||||
@@ -50,6 +51,22 @@ CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = ["preferences"]
|
||||
IS_TARGET_PLATFORM = True
|
||||
|
||||
# BK7231N SDK options to disable unused features.
|
||||
# Disabling BLE saves ~21KB RAM and ~200KB Flash because BLE init code is
|
||||
# called unconditionally by the SDK. ESPHome doesn't use BLE on LibreTiny.
|
||||
#
|
||||
# This only works on BK7231N (BLE 5.x). Other BK72XX chips using BLE 4.2
|
||||
# (BK7231T, BK7231Q, BK7251; BK7252 boards use the BK7251 family) have a bug
|
||||
# where the BLE library still links and references undefined symbols when
|
||||
# CFG_SUPPORT_BLE=0.
|
||||
#
|
||||
# Other options like CFG_TX_EVM_TEST, CFG_RX_SENSITIVITY_TEST, CFG_SUPPORT_BKREG,
|
||||
# CFG_SUPPORT_OTA_HTTP, and CFG_USE_SPI_SLAVE were evaluated but provide no # NOLINT
|
||||
# measurable benefit - the linker already strips unreferenced code via -gc-sections.
|
||||
_BK7231N_SYS_CONFIG_OPTIONS = [
|
||||
"CFG_SUPPORT_BLE=0",
|
||||
]
|
||||
|
||||
|
||||
def _detect_variant(value):
|
||||
if KEY_LIBRETINY not in CORE.data:
|
||||
@@ -346,4 +363,10 @@ async def component_to_code(config):
|
||||
cg.add_platformio_option("custom_fw_name", "esphome")
|
||||
cg.add_platformio_option("custom_fw_version", __version__)
|
||||
|
||||
# Apply chip-specific SDK options to save RAM/Flash
|
||||
if config[CONF_FAMILY] == FAMILY_BK7231N:
|
||||
cg.add_platformio_option(
|
||||
"custom_options.sys_config#h", _BK7231N_SYS_CONFIG_OPTIONS
|
||||
)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -6,8 +6,10 @@ namespace one_wire {
|
||||
static const char *const TAG = "one_wire";
|
||||
|
||||
const std::string &OneWireDevice::get_address_name() {
|
||||
if (this->address_name_.empty())
|
||||
this->address_name_ = std::string("0x") + format_hex(this->address_);
|
||||
if (this->address_name_.empty()) {
|
||||
char hex_buf[19]; // "0x" + 16 hex chars + null
|
||||
this->address_name_ = format_hex_prefixed_to(hex_buf, this->address_);
|
||||
}
|
||||
return this->address_name_;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,11 +24,9 @@ void SmlTextSensor::publish_val(const ObisInfo &obis_info) {
|
||||
case SML_HEX: {
|
||||
// Buffer for "0x" + up to 32 bytes as hex + null
|
||||
char buf[67];
|
||||
buf[0] = '0';
|
||||
buf[1] = 'x';
|
||||
// Max 32 bytes of data fit in remaining buffer ((65-1)/2)
|
||||
// Max 32 bytes of data fit in buffer ((67-3)/2)
|
||||
size_t hex_bytes = std::min(obis_info.value.size(), size_t(32));
|
||||
format_hex_to(buf + 2, sizeof(buf) - 2, obis_info.value.begin(), hex_bytes);
|
||||
format_hex_prefixed_to(buf, obis_info.value.begin(), hex_bytes);
|
||||
publish_state(buf, 2 + hex_bytes * 2);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2083,8 +2083,8 @@ void WiFiComponent::release_scan_results_() {
|
||||
// std::vector - use swap trick since shrink_to_fit is non-binding
|
||||
decltype(this->scan_result_)().swap(this->scan_result_);
|
||||
#else
|
||||
// FixedVector::shrink_to_fit() actually frees all memory
|
||||
this->scan_result_.shrink_to_fit();
|
||||
// FixedVector::release() frees all memory
|
||||
this->scan_result_.release();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
+25
-2
@@ -296,8 +296,8 @@ template<typename T> class FixedVector {
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
// Shrink capacity to fit current size (frees all memory)
|
||||
void shrink_to_fit() {
|
||||
// Release all memory (destroys elements and frees memory)
|
||||
void release() {
|
||||
cleanup_();
|
||||
reset_();
|
||||
}
|
||||
@@ -773,6 +773,29 @@ inline char *format_hex_to(char (&buffer)[N], T val) {
|
||||
/// Calculate buffer size needed for format_hex_to: "XXXXXXXX...\0" = bytes * 2 + 1
|
||||
constexpr size_t format_hex_size(size_t byte_count) { return byte_count * 2 + 1; }
|
||||
|
||||
/// Calculate buffer size needed for format_hex_prefixed_to: "0xXXXXXXXX...\0" = bytes * 2 + 3
|
||||
constexpr size_t format_hex_prefixed_size(size_t byte_count) { return byte_count * 2 + 3; }
|
||||
|
||||
/// Format an unsigned integer as "0x" prefixed lowercase hex to buffer.
|
||||
template<size_t N, typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
|
||||
inline char *format_hex_prefixed_to(char (&buffer)[N], T val) {
|
||||
static_assert(N >= sizeof(T) * 2 + 3, "Buffer too small for prefixed hex");
|
||||
buffer[0] = '0';
|
||||
buffer[1] = 'x';
|
||||
val = convert_big_endian(val);
|
||||
format_hex_to(buffer + 2, N - 2, reinterpret_cast<const uint8_t *>(&val), sizeof(T));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// Format byte array as "0x" prefixed lowercase hex to buffer.
|
||||
template<size_t N> inline char *format_hex_prefixed_to(char (&buffer)[N], const uint8_t *data, size_t length) {
|
||||
static_assert(N >= 5, "Buffer must hold at least '0x' + one hex byte + null");
|
||||
buffer[0] = '0';
|
||||
buffer[1] = 'x';
|
||||
format_hex_to(buffer + 2, N - 2, data, length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0"
|
||||
constexpr size_t format_hex_pretty_size(size_t byte_count) { return byte_count * 3; }
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
||||
esptool==5.1.0
|
||||
click==8.1.7
|
||||
esphome-dashboard==20260110.0
|
||||
aioesphomeapi==43.10.1
|
||||
aioesphomeapi==43.11.0
|
||||
zeroconf==0.148.0
|
||||
puremagic==1.30
|
||||
ruamel.yaml==0.19.1 # dashboard_import
|
||||
|
||||
@@ -339,6 +339,9 @@ def create_field_type_info(
|
||||
) -> TypeInfo:
|
||||
"""Create the appropriate TypeInfo instance for a field, handling repeated fields and custom options."""
|
||||
if field.label == FieldDescriptorProto.LABEL_REPEATED:
|
||||
# Check if this is a packed_buffer field (zero-copy packed repeated)
|
||||
if get_field_opt(field, pb.packed_buffer, False):
|
||||
return PackedBufferTypeInfo(field)
|
||||
# Check if this repeated field has fixed_array_with_length_define option
|
||||
if (
|
||||
fixed_size := get_field_opt(field, pb.fixed_array_with_length_define)
|
||||
@@ -947,6 +950,88 @@ class PointerToStringBufferType(PointerToBufferTypeBase):
|
||||
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string
|
||||
|
||||
|
||||
class PackedBufferTypeInfo(TypeInfo):
|
||||
"""Type for packed repeated fields that expose raw buffer instead of decoding.
|
||||
|
||||
When a repeated field is marked with [(packed_buffer) = true], this type
|
||||
generates code that stores a pointer to the raw protobuf buffer along with
|
||||
its length and the count of values. This enables zero-copy passthrough when
|
||||
the consumer can decode the packed varints on-demand.
|
||||
"""
|
||||
|
||||
def __init__(self, field: descriptor.FieldDescriptorProto) -> None:
|
||||
# packed_buffer is decode-only (SOURCE_CLIENT messages)
|
||||
super().__init__(field, needs_decode=True, needs_encode=False)
|
||||
|
||||
@property
|
||||
def cpp_type(self) -> str:
|
||||
# Not used - we have multiple fields
|
||||
return "const uint8_t*"
|
||||
|
||||
@property
|
||||
def wire_type(self) -> WireType:
|
||||
"""Packed fields use LENGTH_DELIMITED wire type."""
|
||||
return WireType.LENGTH_DELIMITED
|
||||
|
||||
@property
|
||||
def public_content(self) -> list[str]:
|
||||
"""Generate three fields: data pointer, length, and count."""
|
||||
return [
|
||||
f"const uint8_t *{self.field_name}_data_{{nullptr}};",
|
||||
f"uint16_t {self.field_name}_length_{{0}};",
|
||||
f"uint16_t {self.field_name}_count_{{0}};",
|
||||
]
|
||||
|
||||
@property
|
||||
def decode_length_content(self) -> str:
|
||||
"""Store pointer to buffer and calculate count of packed varints."""
|
||||
return f"""case {self.number}: {{
|
||||
this->{self.field_name}_data_ = value.data();
|
||||
this->{self.field_name}_length_ = value.size();
|
||||
this->{self.field_name}_count_ = count_packed_varints(value.data(), value.size());
|
||||
break;
|
||||
}}"""
|
||||
|
||||
@property
|
||||
def encode_content(self) -> str:
|
||||
"""No encoding - this is decode-only for SOURCE_CLIENT messages."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def dump_content(self) -> str:
|
||||
"""Dump shows buffer info but not decoded values."""
|
||||
return (
|
||||
f'out.append(" {self.name}: ");\n'
|
||||
+ 'out.append("packed buffer [");\n'
|
||||
+ f"out.append(std::to_string(this->{self.field_name}_count_));\n"
|
||||
+ 'out.append(" values, ");\n'
|
||||
+ f"out.append(std::to_string(this->{self.field_name}_length_));\n"
|
||||
+ 'out.append(" bytes]\\n");'
|
||||
)
|
||||
|
||||
def dump(self, name: str) -> str:
|
||||
"""Dump method for packed buffer - not typically used but required by abstract base."""
|
||||
return 'out.append("packed buffer");'
|
||||
|
||||
def get_size_calculation(self, name: str, force: bool = False) -> str:
|
||||
"""No size calculation needed - decode-only."""
|
||||
return ""
|
||||
|
||||
def get_estimated_size(self) -> int:
|
||||
"""Estimate size for packed buffer field.
|
||||
|
||||
Typical IR/RF timing array has ~50-200 values, each encoded as 1-3 bytes.
|
||||
Estimate 100 values * 2 bytes = 200 bytes typical.
|
||||
"""
|
||||
return (
|
||||
self.calculate_field_id_size() + 2 + 200
|
||||
) # field ID + length varint + data
|
||||
|
||||
@classmethod
|
||||
def can_use_dump_field(cls) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class FixedArrayBytesType(TypeInfo):
|
||||
"""Special type for fixed-size byte arrays."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user