Compare commits

..

1 Commits

Author SHA1 Message Date
pre-commit-ci-lite[bot]
21507c570d [pre-commit.ci lite] apply automatic fixes 2026-01-15 02:29:46 +00:00
89 changed files with 230 additions and 1228 deletions

View File

@@ -795,13 +795,6 @@ def command_vscode(args: ArgsProtocol) -> int | None:
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
# Set memory analysis options in config
if args.analyze_memory:
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
if args.memory_report:
config.setdefault(CONF_ESPHOME, {})["memory_report_file"] = args.memory_report
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
@@ -1286,17 +1279,6 @@ def parse_args(argv):
help="Only generate source code, do not compile.",
action="store_true",
)
parser_compile.add_argument(
"--analyze-memory",
help="Analyze and display memory usage by component after compilation.",
action="store_true",
)
parser_compile.add_argument(
"--memory-report",
help="Save memory analysis report to a file (supports .json or .txt).",
type=str,
metavar="FILE",
)
parser_upload = subparsers.add_parser(
"upload",

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
from collections import defaultdict
from collections.abc import Callable
import json
import sys
from typing import TYPE_CHECKING
@@ -439,28 +438,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
return "\n".join(lines)
def to_json(self) -> str:
"""Export analysis results as JSON."""
data = {
"components": {
name: {
"text": mem.text_size,
"rodata": mem.rodata_size,
"data": mem.data_size,
"bss": mem.bss_size,
"flash_total": mem.flash_total,
"ram_total": mem.ram_total,
"symbol_count": mem.symbol_count,
}
for name, mem in self.components.items()
},
"totals": {
"flash": sum(c.flash_total for c in self.components.values()),
"ram": sum(c.ram_total for c in self.components.values()),
},
}
return json.dumps(data, indent=2)
def dump_uncategorized_symbols(self, output_file: str | None = None) -> None:
"""Dump uncategorized symbols for analysis."""
# Sort by size descending

View File

@@ -9,44 +9,10 @@ ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::")
# Maps standard section names to their various platform-specific variants
# Note: Order matters! More specific patterns (.bss) must come before general ones (.dram)
# because ESP-IDF uses names like ".dram0.bss" which would match ".dram" otherwise
#
# Platform-specific sections:
# - ESP8266/ESP32: .iram*, .dram*
# - LibreTiny RTL87xx: .xip.code_* (flash), .ram.code_* (RAM)
# - LibreTiny BK7231: .itcm.code (fast RAM), .vectors (interrupt vectors)
# - LibreTiny LN882X: .flash_text, .flash_copy* (flash code)
SECTION_MAPPING = {
".text": frozenset(
[
".text",
".iram",
# LibreTiny RTL87xx XIP (eXecute In Place) flash code
".xip.code",
# LibreTiny RTL87xx RAM code
".ram.code_text",
# LibreTiny BK7231 fast RAM code and vectors
".itcm.code",
".vectors",
# LibreTiny LN882X flash code
".flash_text",
".flash_copy",
]
),
".rodata": frozenset(
[
".rodata",
# LibreTiny RTL87xx read-only data in RAM
".ram.code_rodata",
]
),
# .bss patterns - must be before .data to catch ".dram0.bss"
".bss": frozenset(
[
".bss",
# LibreTiny LN882X BSS
".bss_ram",
]
),
".text": frozenset([".text", ".iram"]),
".rodata": frozenset([".rodata"]),
".bss": frozenset([".bss"]), # Must be before .data to catch ".dram0.bss"
".data": frozenset([".data", ".dram"]),
}

View File

@@ -3,7 +3,6 @@
#include "bedjet_hub.h"
#include "bedjet_child.h"
#include "bedjet_const.h"
#include "esphome/components/esp32_ble/ble_uuid.h"
#include "esphome/core/application.h"
#include <cinttypes>

View File

@@ -2,7 +2,6 @@
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cstdarg>
namespace esphome {
namespace cse7766 {
@@ -10,32 +9,6 @@ namespace cse7766 {
static const char *const TAG = "cse7766";
static constexpr size_t CSE7766_RAW_DATA_SIZE = 24;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
/// @brief Safely append formatted string to buffer.
/// @param buf Destination buffer (must be non-null)
/// @param size Total buffer size in bytes
/// @param pos Current write position (0 to size-1 for valid positions, size means full)
/// @param fmt printf-style format string
/// @return New write position: pos + chars_written, capped at size when buffer is full.
/// Returns size (not size-1) when full because vsnprintf already wrote the null
/// terminator at buf[size-1]. Returning size signals "no room for more content".
/// On encoding error, returns pos unchanged (no write occurred).
__attribute__((format(printf, 4, 5))) static size_t buf_append(char *buf, size_t size, size_t pos, const char *fmt,
...) {
if (pos >= size) {
return size;
}
va_list args;
va_start(args, fmt);
int written = vsnprintf(buf + pos, size - pos, fmt, args);
va_end(args);
if (written < 0) {
return pos; // encoding error
}
return std::min(pos + static_cast<size_t>(written), size);
}
#endif
void CSE7766Component::loop() {
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_transmission_ >= 500) {
@@ -234,23 +207,20 @@ void CSE7766Component::parse_data_() {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
{
// Buffer: 7 + 15 + 33 + 15 + 25 = 95 chars max + null, rounded to 128 for safety margin.
// Float sizes with %.4f can be up to 11 chars for large values (e.g., 999999.9999).
char buf[128];
size_t pos = buf_append(buf, sizeof(buf), 0, "Parsed:");
std::string buf = "Parsed:";
if (have_voltage) {
pos = buf_append(buf, sizeof(buf), pos, " V=%.4fV", voltage);
buf += str_sprintf(" V=%fV", voltage);
}
if (have_current) {
pos = buf_append(buf, sizeof(buf), pos, " I=%.4fmA (~%.4fmA)", current * 1000.0f, calculated_current * 1000.0f);
buf += str_sprintf(" I=%fmA (~%fmA)", current * 1000.0f, calculated_current * 1000.0f);
}
if (have_power) {
pos = buf_append(buf, sizeof(buf), pos, " P=%.4fW", power);
buf += str_sprintf(" P=%fW", power);
}
if (energy != 0.0f) {
buf_append(buf, sizeof(buf), pos, " E=%.4fkWh (%u)", energy, cf_pulses);
buf += str_sprintf(" E=%fkWh (%u)", energy, cf_pulses);
}
ESP_LOGVV(TAG, "%s", buf);
ESP_LOGVV(TAG, "%s", buf.c_str());
}
#endif
}

View File

@@ -74,11 +74,8 @@ class DebugComponent : public PollingComponent {
#ifdef USE_SENSOR
void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; }
#if (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)) || defined(USE_ESP32)
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; }
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
void set_min_free_sensor(sensor::Sensor *min_free_sensor) { min_free_sensor_ = min_free_sensor; }
#endif
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
#ifdef USE_ESP32
@@ -100,11 +97,8 @@ class DebugComponent : public PollingComponent {
sensor::Sensor *free_sensor_{nullptr};
sensor::Sensor *block_sensor_{nullptr};
#if (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)) || defined(USE_ESP32)
#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
sensor::Sensor *fragmentation_sensor_{nullptr};
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
sensor::Sensor *min_free_sensor_{nullptr};
#endif
sensor::Sensor *loop_time_sensor_{nullptr};
#ifdef USE_ESP32

View File

@@ -234,19 +234,8 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
void DebugComponent::update_platform_() {
#ifdef USE_SENSOR
uint32_t max_alloc = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
if (this->block_sensor_ != nullptr) {
this->block_sensor_->publish_state(max_alloc);
}
if (this->min_free_sensor_ != nullptr) {
this->min_free_sensor_->publish_state(heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
}
if (this->fragmentation_sensor_ != nullptr) {
uint32_t free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
if (free_heap > 0) {
float fragmentation = 100.0f - (100.0f * max_alloc / free_heap);
this->fragmentation_sensor_->publish_state(fragmentation);
}
this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL));
}
if (this->psram_sensor_ != nullptr) {
this->psram_sensor_->publish_state(heap_caps_get_free_size(MALLOC_CAP_SPIRAM));

View File

@@ -51,9 +51,6 @@ void DebugComponent::update_platform_() {
if (this->block_sensor_ != nullptr) {
this->block_sensor_->publish_state(lt_heap_get_max_alloc());
}
if (this->min_free_sensor_ != nullptr) {
this->min_free_sensor_->publish_state(lt_heap_get_min_free());
}
#endif
}

View File

@@ -17,15 +17,10 @@ from esphome.const import (
UNIT_PERCENT,
)
from . import ( # noqa: F401 pylint: disable=unused-import
CONF_DEBUG_ID,
FILTER_SOURCE_FILES,
DebugComponent,
)
from . import CONF_DEBUG_ID, DebugComponent
DEPENDENCIES = ["debug"]
CONF_MIN_FREE = "min_free"
CONF_PSRAM = "psram"
CONFIG_SCHEMA = {
@@ -43,13 +38,8 @@ CONFIG_SCHEMA = {
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_FRAGMENTATION): cv.All(
cv.Any(
cv.All(
cv.only_on_esp8266,
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
),
cv.only_on_esp32,
),
cv.only_on_esp8266,
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_COUNTER,
@@ -57,15 +47,6 @@ CONFIG_SCHEMA = {
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
),
cv.Optional(CONF_MIN_FREE): cv.All(
cv.Any(cv.only_on_esp32, cv.only_on_libretiny),
sensor.sensor_schema(
unit_of_measurement=UNIT_BYTES,
icon=ICON_COUNTER,
accuracy_decimals=0,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
),
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(
unit_of_measurement=UNIT_MILLISECOND,
icon=ICON_TIMER,
@@ -108,10 +89,6 @@ async def to_code(config):
sens = await sensor.new_sensor(fragmentation_conf)
cg.add(debug_component.set_fragmentation_sensor(sens))
if min_free_conf := config.get(CONF_MIN_FREE):
sens = await sensor.new_sensor(min_free_conf)
cg.add(debug_component.set_min_free_sensor(sens))
if loop_time_conf := config.get(CONF_LOOP_TIME):
sens = await sensor.new_sensor(loop_time_conf)
cg.add(debug_component.set_loop_time_sensor(sens))

View File

@@ -8,11 +8,7 @@ from esphome.const import (
ICON_RESTART,
)
from . import ( # noqa: F401 pylint: disable=unused-import
CONF_DEBUG_ID,
FILTER_SOURCE_FILES,
DebugComponent,
)
from . import CONF_DEBUG_ID, DebugComponent
DEPENDENCIES = ["debug"]

View File

@@ -127,9 +127,7 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
this->max4_ = max4 = -1;
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
this->cmd_ = buf;
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
} else if (min3 < 0 || max3 < 0) {
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
@@ -137,10 +135,7 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
max2 / 0.15);
this->cmd_ = buf;
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15);
} else if (min4 < 0 || max4 < 0) {
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
@@ -150,10 +145,9 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
this->min4_ = min4 = this->max4_ = max4 = -1;
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15,
max2 / 0.15, min3 / 0.15, max3 / 0.15);
this->cmd_ = buf;
this->cmd_ = str_sprintf("detRangeCfg -1 "
"%.0f %.0f %.0f %.0f %.0f %.0f",
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15);
} else {
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
@@ -164,10 +158,10 @@ DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float
this->min4_ = min4 = round(min4 / 0.15) * 0.15;
this->max4_ = max4 = round(max4 / 0.15) * 0.15;
char buf[72]; // max 72: "detRangeCfg -1 "(15) + 8 * (float(5) + space(1)) + null
snprintf(buf, sizeof(buf), "detRangeCfg -1 %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15,
min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15, max4 / 0.15);
this->cmd_ = buf;
this->cmd_ = str_sprintf("detRangeCfg -1 "
"%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f",
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15,
max4 / 0.15);
}
this->min1_ = min1;
@@ -209,10 +203,7 @@ SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_af
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
// max 32: "setLatency "(11) + float(8) + " "(1) + float(8) + null, rounded to 32
char buf[32];
snprintf(buf, sizeof(buf), "setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
this->cmd_ = buf;
this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
};
uint8_t SetLatencyCommand::on_message(std::string &message) {

View File

@@ -63,13 +63,11 @@ def validate_auto_clear(value):
return cv.boolean(value)
def basic_display_schema(default_update_interval: str = "1s") -> cv.Schema:
"""Create a basic display schema with configurable default update interval."""
return cv.Schema(
{
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
}
).extend(cv.polling_component_schema(default_update_interval))
BASIC_DISPLAY_SCHEMA = cv.Schema(
{
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
}
).extend(cv.polling_component_schema("1s"))
def _validate_test_card(config):
@@ -83,41 +81,34 @@ def _validate_test_card(config):
return config
def full_display_schema(default_update_interval: str = "1s") -> cv.Schema:
"""Create a full display schema with configurable default update interval."""
schema = basic_display_schema(default_update_interval).extend(
{
cv.Optional(CONF_ROTATION): validate_rotation,
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
cv.ensure_list(
{
cv.GenerateID(): cv.declare_id(DisplayPage),
cv.Required(CONF_LAMBDA): cv.lambda_,
}
),
cv.Length(min=1),
),
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
{
cv.Optional(CONF_ROTATION): validate_rotation,
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
cv.ensure_list(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DisplayOnPageChangeTrigger
),
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
cv.GenerateID(): cv.declare_id(DisplayPage),
cv.Required(CONF_LAMBDA): cv.lambda_,
}
),
cv.Optional(
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
): validate_auto_clear,
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
}
)
schema.add_extra(_validate_test_card)
return schema
BASIC_DISPLAY_SCHEMA = basic_display_schema("1s")
FULL_DISPLAY_SCHEMA = full_display_schema("1s")
cv.Length(min=1),
),
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DisplayOnPageChangeTrigger
),
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
}
),
cv.Optional(
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
): validate_auto_clear,
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
}
)
FULL_DISPLAY_SCHEMA.add_extra(_validate_test_card)
async def setup_display_core_(var, config):

View File

@@ -31,7 +31,6 @@ from esphome.const import (
CONF_TRANSFORM,
CONF_UPDATE_INTERVAL,
CONF_WIDTH,
SCHEDULER_DONT_RUN,
)
from esphome.cpp_generator import RawExpression
from esphome.final_validate import full_config
@@ -73,10 +72,12 @@ TRANSFORM_OPTIONS = {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY}
def model_schema(config):
model = MODELS[config[CONF_MODEL]]
class_name = epaper_spi_ns.class_(model.class_name, EPaperBase)
minimum_update_interval = update_interval(
model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s")
)
cv_dimensions = cv.Optional if model.get_default(CONF_WIDTH) else cv.Required
return (
display.full_display_schema("60s")
.extend(
display.FULL_DISPLAY_SCHEMA.extend(
spi.spi_device_schema(
cs_pin_required=False,
default_mode="MODE0",
@@ -93,6 +94,9 @@ def model_schema(config):
{
cv.Optional(CONF_ROTATION, default=0): validate_rotation,
cv.Required(CONF_MODEL): cv.one_of(model.name, upper=True),
cv.Optional(CONF_UPDATE_INTERVAL, default=cv.UNDEFINED): cv.All(
update_interval, cv.Range(min=minimum_update_interval)
),
cv.Optional(CONF_TRANSFORM): cv.Schema(
{
cv.Required(CONF_MIRROR_X): cv.boolean,
@@ -146,22 +150,15 @@ def _final_validate(config):
global_config = full_config.get()
from esphome.components.lvgl import DOMAIN as LVGL_DOMAIN
# If no drawing methods are configured, and LVGL is not enabled, show a test card
if (
CONF_LAMBDA not in config
and CONF_PAGES not in config
and LVGL_DOMAIN not in global_config
):
config[CONF_SHOW_TEST_CARD] = True
interval = config[CONF_UPDATE_INTERVAL]
if interval != SCHEDULER_DONT_RUN:
model = MODELS[config[CONF_MODEL]]
minimum = update_interval(model.get_default(CONF_MINIMUM_UPDATE_INTERVAL, "1s"))
if interval < minimum:
raise cv.Invalid(
f"update_interval must be at least {minimum} for {model.name}, got {interval}"
)
if CONF_LAMBDA not in config and CONF_PAGES not in config:
if LVGL_DOMAIN in global_config:
if CONF_UPDATE_INTERVAL not in config:
config[CONF_UPDATE_INTERVAL] = update_interval("never")
else:
# If no drawing methods are configured, and LVGL is not enabled, show a test card
config[CONF_SHOW_TEST_CARD] = True
elif CONF_UPDATE_INTERVAL not in config:
config[CONF_UPDATE_INTERVAL] = update_interval("1min")
return config

View File

@@ -660,9 +660,6 @@ CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
KEY_VFS_SELECT_REQUIRED = "vfs_select_required"
KEY_VFS_DIR_REQUIRED = "vfs_dir_required"
# Ring buffer IRAM requirement tracking
KEY_RINGBUF_IN_IRAM = "ringbuf_in_iram"
def require_vfs_select() -> None:
"""Mark that VFS select support is required by a component.
@@ -682,17 +679,6 @@ def require_vfs_dir() -> None:
CORE.data[KEY_VFS_DIR_REQUIRED] = True
def enable_ringbuf_in_iram() -> None:
"""Keep ring buffer functions in IRAM instead of moving them to flash.
Call this from components that use esphome/core/ring_buffer.cpp and need
the ring buffer functions to remain in IRAM for performance reasons
(e.g., voice assistants, audio components).
This prevents CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH from being enabled.
"""
CORE.data[KEY_RINGBUF_IN_IRAM] = True
def _parse_idf_component(value: str) -> ConfigType:
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
# Match operator followed by version-like string (digit or *)
@@ -1104,18 +1090,14 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH", True)
# Place ring buffer functions into flash instead of IRAM by default
# This saves IRAM but may impact performance for audio/voice components.
# Components that need ring buffer in IRAM call enable_ringbuf_in_iram().
# Users can also set ringbuf_in_iram: true to force IRAM placement.
# In ESP-IDF 6.0 flash placement becomes the default.
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM] or CORE.data.get(
KEY_RINGBUF_IN_IRAM, False
):
# User config or component requires ring buffer in IRAM for performance
# This saves IRAM. In ESP-IDF 6.0 flash placement becomes the default.
# Users can set ringbuf_in_iram: true as an escape hatch if they encounter issues.
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM]:
# User requests ring buffer in IRAM
# IDF 6.0+: will need CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=n
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH", False)
else:
# No component needs it - place in flash to save IRAM
# Place in flash to save IRAM (default)
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True)
# Place heap functions into flash to save IRAM (~4-6KB savings)

View File

@@ -85,6 +85,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
break;
}
gpio_set_intr_type(this->get_pin_num(), idf_type);
gpio_intr_enable(this->get_pin_num());
if (!isr_service_installed) {
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
if (res != ESP_OK) {
@@ -94,7 +95,6 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
isr_service_installed = true;
}
gpio_isr_handler_add(this->get_pin_num(), func, arg);
gpio_intr_enable(this->get_pin_num());
}
size_t ESP32InternalGPIOPin::dump_summary(char *buffer, size_t len) const {

View File

@@ -98,10 +98,6 @@ void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
}
void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
this->advertising_set_manufacturer_data(std::span<const uint8_t>(data));
}
void ESP32BLE::advertising_set_manufacturer_data(std::span<const uint8_t> data) {
this->advertising_init_();
this->advertising_->set_manufacturer_data(data);
this->advertising_start();

View File

@@ -118,7 +118,6 @@ class ESP32BLE : public Component {
void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(std::span<const uint8_t> data);
void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; }
void advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name);
void advertising_add_service_uuid(ESPBTUUID uuid);

View File

@@ -59,10 +59,6 @@ void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) {
}
void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) {
this->set_manufacturer_data(std::span<const uint8_t>(data));
}
void BLEAdvertising::set_manufacturer_data(std::span<const uint8_t> data) {
delete[] this->advertising_data_.p_manufacturer_data;
this->advertising_data_.p_manufacturer_data = nullptr;
this->advertising_data_.manufacturer_len = data.size();

View File

@@ -37,7 +37,6 @@ class BLEAdvertising {
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(const std::vector<uint8_t> &data);
void set_manufacturer_data(std::span<const uint8_t> data);
void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; }
void set_service_data(const std::vector<uint8_t> &data);
void set_service_data(std::span<const uint8_t> data);

View File

@@ -1,6 +1,5 @@
#include "esp32_ble_beacon.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32

View File

@@ -15,10 +15,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
new Trigger<std::vector<uint8_t>, uint16_t>();
characteristic->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
// Convert span to vector for trigger - copy is necessary because:
// 1. Trigger stores the data for use in automation actions that execute later
// 2. The span is only valid during this callback (points to temporary BLE stack data)
// 3. User lambdas in automations need persistent data they can access asynchronously
// Convert span to vector for trigger
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
});
return on_write_trigger;
@@ -30,10 +27,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
new Trigger<std::vector<uint8_t>, uint16_t>();
descriptor->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
// Convert span to vector for trigger - copy is necessary because:
// 1. Trigger stores the data for use in automation actions that execute later
// 2. The span is only valid during this callback (points to temporary BLE stack data)
// 3. User lambdas in automations need persistent data they can access asynchronously
// Convert span to vector for trigger
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
});
return on_write_trigger;

View File

@@ -69,10 +69,7 @@ void Esp32HostedUpdate::setup() {
// Get coprocessor version
esp_hosted_coprocessor_fwver_t ver_info;
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
// 16 bytes: "255.255.255" (11 chars) + null + safety margin
char buf[16];
snprintf(buf, sizeof(buf), "%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
this->update_info_.current_version = buf;
this->update_info_.current_version = str_sprintf("%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
} else {
this->update_info_.current_version = "unknown";
}
@@ -297,7 +294,8 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
}
// Stream firmware to coprocessor while computing SHA256
sha256::SHA256 hasher;
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
alignas(32) sha256::SHA256 hasher;
hasher.init();
uint8_t buffer[CHUNK_SIZE];
@@ -354,7 +352,8 @@ bool Esp32HostedUpdate::write_embedded_firmware_to_coprocessor_() {
}
// Verify SHA256 before writing
sha256::SHA256 hasher;
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
alignas(32) sha256::SHA256 hasher;
hasher.init();
hasher.add(this->firmware_data_, this->firmware_size_);
hasher.calculate();

View File

@@ -563,9 +563,11 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
// [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
// [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
// CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// (no passing to other functions). All hash operations must happen in this function.
sha256::SHA256 hasher;
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for
// hardware SHA acceleration DMA operations.
alignas(32) sha256::SHA256 hasher;
const size_t hex_size = hasher.get_size() * 2;
const size_t nonce_len = hasher.get_size() / 4;
@@ -637,9 +639,11 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
const char *cnonce = nonce + hex_size;
const char *response = cnonce + hex_size;
// CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
// (no passing to other functions). All hash operations must happen in this function.
sha256::SHA256 hasher;
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for
// hardware SHA acceleration DMA operations.
alignas(32) sha256::SHA256 hasher;
hasher.init();
hasher.add(this->password_.c_str(), this->password_.length());

View File

@@ -802,8 +802,8 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)];
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
#endif
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
/*
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
@@ -820,10 +820,8 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed");
err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s",
format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
#endif
}
}
#endif // USE_ETHERNET_KSZ8081

View File

@@ -160,7 +160,7 @@ void EZOSensor::loop() {
this->commands_.pop_front();
}
void EZOSensor::add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms) {
void EZOSensor::add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms) {
std::unique_ptr<EzoCommand> ezo_command(new EzoCommand);
ezo_command->command = command;
ezo_command->command_type = command_type;
@@ -169,17 +169,13 @@ void EZOSensor::add_command_(const char *command, EzoCommandType command_type, u
}
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
// max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
char payload[24];
snprintf(payload, sizeof(payload), "Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
}
void EZOSensor::set_address(uint8_t address) {
if (address > 0 && address < 128) {
// max 8: "I2C,"(4) + uint8(3) + null
char payload[8];
snprintf(payload, sizeof(payload), "I2C,%u", address);
std::string payload = str_sprintf("I2C,%u", address);
this->new_address_ = address;
this->add_command_(payload, EzoCommandType::EZO_I2C);
} else {
@@ -198,9 +194,7 @@ void EZOSensor::get_slope() { this->add_command_("Slope,?", EzoCommandType::EZO_
void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
void EZOSensor::set_t(float value) {
// max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
char payload[16];
snprintf(payload, sizeof(payload), "T,%0.2f", value);
std::string payload = str_sprintf("T,%0.2f", value);
this->add_command_(payload, EzoCommandType::EZO_T);
}
@@ -221,9 +215,7 @@ void EZOSensor::set_calibration_point_high(float value) {
}
void EZOSensor::set_calibration_generic(float value) {
// exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
char payload[16];
snprintf(payload, sizeof(payload), "Cal,%0.2f", value);
std::string payload = str_sprintf("Cal,%0.2f", value);
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
}
@@ -231,11 +223,13 @@ void EZOSensor::clear_calibration() { this->add_command_("Cal,clear", EzoCommand
void EZOSensor::get_led_state() { this->add_command_("L,?", EzoCommandType::EZO_LED); }
void EZOSensor::set_led_state(bool on) { this->add_command_(on ? "L,1" : "L,0", EzoCommandType::EZO_LED); }
void EZOSensor::send_custom(const std::string &to_send) {
this->add_command_(to_send.c_str(), EzoCommandType::EZO_CUSTOM);
void EZOSensor::set_led_state(bool on) {
std::string to_send = "L,";
to_send += on ? "1" : "0";
this->add_command_(to_send, EzoCommandType::EZO_LED);
}
void EZOSensor::send_custom(const std::string &to_send) { this->add_command_(to_send, EzoCommandType::EZO_CUSTOM); }
} // namespace ezo
} // namespace esphome

View File

@@ -92,7 +92,7 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2
std::deque<std::unique_ptr<EzoCommand>> commands_;
int new_address_;
void add_command_(const char *command, EzoCommandType command_type, uint16_t delay_ms = 300);
void add_command_(const std::string &command, EzoCommandType command_type, uint16_t delay_ms = 300);
void set_calibration_point_(EzoCalibrationType type, float value);

View File

@@ -71,7 +71,7 @@ void FanCall::validate_() {
auto traits = this->parent_.get_traits();
if (this->speed_.has_value()) {
this->speed_ = clamp(*this->speed_, 1, static_cast<int>(traits.supported_speed_count()));
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
// https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
// "Manually setting a speed must disable any set preset mode"

View File

@@ -11,7 +11,7 @@ namespace fan {
class FanTraits {
public:
FanTraits() = default;
FanTraits(bool oscillation, bool speed, bool direction, uint8_t speed_count)
FanTraits(bool oscillation, bool speed, bool direction, int speed_count)
: oscillation_(oscillation), speed_(speed), direction_(direction), speed_count_(speed_count) {}
/// Return if this fan supports oscillation.
@@ -23,9 +23,9 @@ class FanTraits {
/// Set whether this fan supports speed levels.
void set_speed(bool speed) { this->speed_ = speed; }
/// Return how many speed levels the fan has
uint8_t supported_speed_count() const { return this->speed_count_; }
int supported_speed_count() const { return this->speed_count_; }
/// Set how many speed levels this fan has.
void set_supported_speed_count(uint8_t speed_count) { this->speed_count_ = speed_count; }
void set_supported_speed_count(int speed_count) { this->speed_count_ = speed_count; }
/// Return if this fan supports changing direction
bool supports_direction() const { return this->direction_; }
/// Set whether this fan supports changing direction
@@ -64,7 +64,7 @@ class FanTraits {
bool oscillation_{false};
bool speed_{false};
bool direction_{false};
uint8_t speed_count_{};
int speed_count_{};
std::vector<const char *> preset_modes_{};
};

View File

@@ -163,10 +163,9 @@ bool GDK101Component::read_fw_version_(uint8_t *data) {
return false;
}
// max 8: "255.255" (7 chars) + null
char buf[8];
snprintf(buf, sizeof(buf), "%d.%d", data[0], data[1]);
this->fw_version_text_sensor_->publish_state(buf);
const std::string fw_version_str = str_sprintf("%d.%d", data[0], data[1]);
this->fw_version_text_sensor_->publish_state(fw_version_str);
}
#endif // USE_TEXT_SENSOR
return true;

View File

@@ -39,7 +39,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum(
DECAY_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1, max=255),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
}

View File

@@ -15,7 +15,7 @@ enum DecayMode {
class HBridgeFan : public Component, public fan::Fan {
public:
HBridgeFan(uint8_t speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; }
void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; }
@@ -33,7 +33,7 @@ class HBridgeFan : public Component, public fan::Fan {
output::FloatOutput *pin_b_;
output::FloatOutput *enable_{nullptr};
output::BinaryOutput *oscillating_{nullptr};
uint8_t speed_count_{};
int speed_count_{};
DecayMode decay_mode_{DECAY_MODE_SLOW};
fan::FanTraits traits_;
std::vector<const char *> preset_modes_{};

View File

@@ -119,7 +119,7 @@ void IDFI2CBus::dump_config() {
if (s.second) {
ESP_LOGCONFIG(TAG, "Found device at address 0x%02X", s.first);
} else {
ESP_LOGCONFIG(TAG, "Unknown error at address 0x%02X", s.first);
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}

View File

@@ -1,11 +1,6 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components.esp32 import (
add_idf_sdkconfig_option,
enable_ringbuf_in_iram,
get_esp32_variant,
)
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
@@ -15,6 +10,8 @@ from esphome.components.esp32.const import (
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
add_idf_sdkconfig_option,
get_esp32_variant,
)
import esphome.config_validation as cv
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
@@ -281,9 +278,6 @@ async def to_code(config):
# Helps avoid callbacks being skipped due to processor load
add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True)
# Keep ring buffer functions in IRAM for audio performance
enable_ringbuf_in_iram()
cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN]))
if CONF_I2S_BCLK_PIN in config:
cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN]))

View File

@@ -665,10 +665,15 @@ async def write_image(config, all_frames=False):
if is_svg_file(path):
import resvg_py
resize = resize or (None, None)
image_data = resvg_py.svg_to_bytes(
svg_path=str(path), width=resize[0], height=resize[1], dpi=100
)
if resize:
width, height = resize
# resvg-py allows rendering by width/height directly
image_data = resvg_py.svg_to_bytes(
svg_path=str(path), width=int(width), height=int(height)
)
else:
# Default size
image_data = resvg_py.svg_to_bytes(svg_path=str(path))
# Convert bytes to Pillow Image
image = Image.open(io.BytesIO(image_data))

View File

@@ -11,7 +11,7 @@ static const char *const TAG = "kuntze";
static const uint8_t CMD_READ_REG = 0x03;
static const uint16_t REGISTER[] = {4136, 4160, 4680, 6000, 4688, 4728, 5832};
// Maximum bytes to log for Modbus responses (2 registers = 4 bytes, plus byte count = 5 bytes)
// Maximum bytes to log for Modbus responses (2 registers = 4, plus count = 5)
static constexpr size_t KUNTZE_MAX_LOG_BYTES = 8;
void Kuntze::on_modbus_data(const std::vector<uint8_t> &data) {

View File

@@ -1,51 +0,0 @@
#ifdef USE_ESP8266
#include "logger.h"
#include "esphome/core/log.h"
namespace esphome::logger {
static const char *const TAG = "logger";
void Logger::pre_setup() {
if (this->baud_rate_ > 0) {
switch (this->uart_) {
case UART_SELECTION_UART0:
case UART_SELECTION_UART0_SWAP:
this->hw_serial_ = &Serial;
Serial.begin(this->baud_rate_);
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
Serial.swap();
}
Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
break;
case UART_SELECTION_UART1:
this->hw_serial_ = &Serial1;
Serial1.begin(this->baud_rate_);
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
break;
}
} else {
uart_set_debug(UART_NO);
}
global_logger = this;
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {
case UART_SELECTION_UART0:
return LOG_STR("UART0");
case UART_SELECTION_UART1:
return LOG_STR("UART1");
case UART_SELECTION_UART0_SWAP:
default:
return LOG_STR("UART0_SWAP");
}
}
} // namespace esphome::logger
#endif

View File

@@ -1,22 +0,0 @@
#if defined(USE_HOST)
#include "logger.h"
namespace esphome::logger {
void HOT Logger::write_msg_(const char *msg) {
time_t rawtime;
struct tm *timeinfo;
char buffer[80];
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo);
fputs(buffer, stdout);
puts(msg);
}
void Logger::pre_setup() { global_logger = this; }
} // namespace esphome::logger
#endif

View File

@@ -1,70 +0,0 @@
#ifdef USE_LIBRETINY
#include "logger.h"
namespace esphome::logger {
static const char *const TAG = "logger";
void Logger::pre_setup() {
if (this->baud_rate_ > 0) {
switch (this->uart_) {
#if LT_HW_UART0
case UART_SELECTION_UART0:
this->hw_serial_ = &Serial0;
Serial0.begin(this->baud_rate_);
break;
#endif
#if LT_HW_UART1
case UART_SELECTION_UART1:
this->hw_serial_ = &Serial1;
Serial1.begin(this->baud_rate_);
break;
#endif
#if LT_HW_UART2
case UART_SELECTION_UART2:
this->hw_serial_ = &Serial2;
Serial2.begin(this->baud_rate_);
break;
#endif
default:
this->hw_serial_ = &Serial;
Serial.begin(this->baud_rate_);
if (this->uart_ != UART_SELECTION_DEFAULT) {
ESP_LOGW(TAG, " The chosen logger UART port is not available on this board."
"The default port was used instead.");
}
break;
}
// change lt_log() port to match default Serial
if (this->uart_ == UART_SELECTION_DEFAULT) {
this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1);
lt_log_set_port(LT_UART_DEFAULT_SERIAL);
} else {
lt_log_set_port(this->uart_ - 1);
}
}
global_logger = this;
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {
case UART_SELECTION_DEFAULT:
return LOG_STR("DEFAULT");
case UART_SELECTION_UART0:
return LOG_STR("UART0");
case UART_SELECTION_UART1:
return LOG_STR("UART1");
case UART_SELECTION_UART2:
default:
return LOG_STR("UART2");
}
}
} // namespace esphome::logger
#endif // USE_LIBRETINY

View File

@@ -1,48 +0,0 @@
#ifdef USE_RP2040
#include "logger.h"
#include "esphome/core/log.h"
namespace esphome::logger {
static const char *const TAG = "logger";
void Logger::pre_setup() {
if (this->baud_rate_ > 0) {
switch (this->uart_) {
case UART_SELECTION_UART0:
this->hw_serial_ = &Serial1;
Serial1.begin(this->baud_rate_);
break;
case UART_SELECTION_UART1:
this->hw_serial_ = &Serial2;
Serial2.begin(this->baud_rate_);
break;
case UART_SELECTION_USB_CDC:
this->hw_serial_ = &Serial;
Serial.begin(this->baud_rate_);
break;
}
}
global_logger = this;
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {
case UART_SELECTION_UART0:
return LOG_STR("UART0");
case UART_SELECTION_UART1:
return LOG_STR("UART1");
#ifdef USE_LOGGER_USB_CDC
case UART_SELECTION_USB_CDC:
return LOG_STR("USB_CDC");
#endif
default:
return LOG_STR("UNKNOWN");
}
}
} // namespace esphome::logger
#endif // USE_RP2040

View File

@@ -1,96 +0,0 @@
#ifdef USE_ZEPHYR
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "logger.h"
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/usb/usb_device.h>
namespace esphome::logger {
static const char *const TAG = "logger";
#ifdef USE_LOGGER_USB_CDC
void Logger::loop() {
if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) {
return;
}
static bool opened = false;
uint32_t dtr = 0;
uart_line_ctrl_get(this->uart_dev_, UART_LINE_CTRL_DTR, &dtr);
/* Poll if the DTR flag was set, optional */
if (opened == dtr) {
return;
}
if (!opened) {
App.schedule_dump_config();
}
opened = !opened;
}
#endif
void Logger::pre_setup() {
if (this->baud_rate_ > 0) {
static const struct device *uart_dev = nullptr;
switch (this->uart_) {
case UART_SELECTION_UART0:
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart0));
break;
case UART_SELECTION_UART1:
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart1));
break;
#ifdef USE_LOGGER_USB_CDC
case UART_SELECTION_USB_CDC:
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(cdc_acm_uart0));
if (device_is_ready(uart_dev)) {
usb_enable(nullptr);
}
break;
#endif
}
if (!device_is_ready(uart_dev)) {
ESP_LOGE(TAG, "%s is not ready.", LOG_STR_ARG(get_uart_selection_()));
} else {
this->uart_dev_ = uart_dev;
}
}
global_logger = this;
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) {
#ifdef CONFIG_PRINTK
printk("%s\n", msg);
#endif
if (nullptr == this->uart_dev_) {
return;
}
while (*msg) {
uart_poll_out(this->uart_dev_, *msg);
++msg;
}
uart_poll_out(this->uart_dev_, '\n');
}
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {
case UART_SELECTION_UART0:
return LOG_STR("UART0");
case UART_SELECTION_UART1:
return LOG_STR("UART1");
#ifdef USE_LOGGER_USB_CDC
case UART_SELECTION_USB_CDC:
return LOG_STR("USB_CDC");
#endif
default:
return LOG_STR("UNKNOWN");
}
}
} // namespace esphome::logger
#endif

View File

@@ -413,7 +413,6 @@ class TextValidator(LValidator):
str_args = [str(x) for x in value[CONF_ARGS]]
arg_expr = cg.RawExpression(",".join(str_args))
format_str = cpp_string_escape(format_str)
# str_sprintf justified: user-defined format, can't optimize without permanent RAM cost
sprintf_str = f"str_sprintf({format_str}, {arg_expr}).c_str()"
if nanval := value.get(CONF_IF_NAN):
nanval = cpp_string_escape(nanval)

View File

@@ -65,10 +65,7 @@ std::string lv_event_code_name_for(uint8_t event_code) {
if (event_code < sizeof(EVENT_NAMES) / sizeof(EVENT_NAMES[0])) {
return EVENT_NAMES[event_code];
}
// max 4 bytes: "%u" with uint8_t (max 255, 3 digits) + null
char buf[4];
snprintf(buf, sizeof(buf), "%u", event_code);
return buf;
return str_sprintf("%2d", event_code);
}
static void rounder_cb(lv_disp_drv_t *disp_drv, lv_area_t *area) {

View File

@@ -56,7 +56,7 @@ void MCP23016::pin_mode(uint8_t pin, gpio::Flags flags) {
this->update_reg_(pin, false, iodir);
}
}
float MCP23016::get_setup_priority() const { return setup_priority::IO; }
float MCP23016::get_setup_priority() const { return setup_priority::HARDWARE; }
bool MCP23016::read_reg_(uint8_t reg, uint8_t *value) {
if (this->is_failed())
return false;

View File

@@ -448,9 +448,6 @@ async def to_code(config):
# The inference task queues detection events that need immediate processing
socket.require_wake_loop_threadsafe()
# Keep ring buffer functions in IRAM for audio performance
esp32.enable_ringbuf_in_iram()
mic_source = await microphone.microphone_source_to_code(config[CONF_MICROPHONE])
cg.add(var.set_microphone_source(mic_source))

View File

@@ -286,7 +286,8 @@ class ServerRegister {
return std::to_string(value);
case SensorValueType::FP32_R:
case SensorValueType::FP32: {
// max 48: float with %.1f can be up to 42 chars incl. null (3.4e38 → 38 integer digits + decimal point + 1 decimal digit + optional sign)
// max 48: float with %.1f can be up to 42 chars incl. null (3.4e38 → 38 integer digits + decimal point + 1
// decimal digit + optional sign)
char buf[48];
snprintf(buf, sizeof(buf), "%.1f", bit_cast<float>(static_cast<uint32_t>(value)));
return buf;

View File

@@ -91,15 +91,7 @@ void MQTTClientComponent::send_device_info_() {
uint8_t index = 0;
for (auto &ip : network::get_ip_addresses()) {
if (ip.is_set()) {
char key[8]; // "ip" + up to 3 digits + null
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
if (index == 0) {
strcpy(key, "ip");
} else {
snprintf(key, sizeof(key), "ip%u", index);
}
ip.str_to(ip_buf);
root[key] = ip_buf;
root["ip" + (index == 0 ? "" : esphome::to_string(index))] = ip.str();
index++;
}
}
@@ -643,8 +635,7 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this
const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; }
void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix, const std::string &check_topic_prefix) {
if (App.is_name_add_mac_suffix_enabled() && (topic_prefix == check_topic_prefix)) {
char buf[ESPHOME_DEVICE_NAME_MAX_LEN + 1];
this->topic_prefix_ = str_sanitize_to(buf, App.get_name().c_str());
this->topic_prefix_ = str_sanitize(App.get_name());
} else {
this->topic_prefix_ = topic_prefix;
}

View File

@@ -48,8 +48,7 @@ void MQTTComponent::set_subscribe_qos(uint8_t qos) { this->subscribe_qos_ = qos;
void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; }
std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const {
char sanitized_name[ESPHOME_DEVICE_NAME_MAX_LEN + 1];
str_sanitize_to(sanitized_name, App.get_name().c_str());
std::string sanitized_name = str_sanitize(App.get_name());
const char *comp_type = this->component_type();
char object_id_buf[OBJECT_ID_MAX_LEN];
StringRef object_id = this->get_default_object_id_to_(object_id_buf);
@@ -61,7 +60,7 @@ std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discove
p = append_char(p, '/');
p = append_str(p, comp_type, strlen(comp_type));
p = append_char(p, '/');
p = append_str(p, sanitized_name, strlen(sanitized_name));
p = append_str(p, sanitized_name.data(), sanitized_name.size());
p = append_char(p, '/');
p = append_str(p, object_id.c_str(), object_id.size());
p = append_str(p, "/config", 7);
@@ -233,10 +232,7 @@ bool MQTTComponent::send_discovery_() {
#else
const char *fmt = ver_fmt;
#endif
// sizeof(ver_fmt) + 8: format specifier expands to 8 hex digits, plus safety margin
char version_buf[sizeof(ver_fmt) + 8];
snprintf(version_buf, sizeof(version_buf), fmt, App.get_config_hash());
device_info[MQTT_DEVICE_SW_VERSION] = version_buf;
device_info[MQTT_DEVICE_SW_VERSION] = str_sprintf(fmt, App.get_config_hash());
device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
#if defined(USE_ESP8266) || defined(USE_ESP32)
device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif";

View File

@@ -11,12 +11,7 @@ from esphome.const import (
)
from esphome.core import CORE, TimePeriod
from . import ( # noqa: F401 pylint: disable=unused-import
FILTER_SOURCE_FILES,
Nextion,
nextion_ns,
nextion_ref,
)
from . import Nextion, nextion_ns, nextion_ref
from .base_component import (
CONF_AUTO_WAKE_ON_TOUCH,
CONF_COMMAND_SPACING,

View File

@@ -561,9 +561,8 @@ const char *OpenTherm::message_id_to_str(MessageId id) {
}
void OpenTherm::debug_data(OpenthermData &data) {
char type_buf[9], id_buf[9], hb_buf[9], lb_buf[9];
ESP_LOGD(TAG, "%s %s %s %s", format_bin_to(type_buf, data.type), format_bin_to(id_buf, data.id),
format_bin_to(hb_buf, data.valueHB), format_bin_to(lb_buf, data.valueLB));
ESP_LOGD(TAG, "%s %s %s %s", format_bin(data.type).c_str(), format_bin(data.id).c_str(),
format_bin(data.valueHB).c_str(), format_bin(data.valueLB).c_str());
ESP_LOGD(TAG, "type: %s; id: %u; HB: %u; LB: %u; uint_16: %u; float: %f",
this->message_type_to_str((MessageType) data.type), data.id, data.valueHB, data.valueLB, data.u16(),
data.f88());

View File

@@ -1,7 +1,5 @@
from esphome.components import binary_sensor, remote_base
from . import FILTER_SOURCE_FILES # noqa: F401 pylint: disable=unused-import
DEPENDENCIES = ["remote_receiver"]
CONFIG_SCHEMA = remote_base.validate_binary_sensor

View File

@@ -80,21 +80,23 @@ class Select : public EntityBase {
void add_on_state_callback(std::function<void(size_t)> &&callback);
/** Set the value of the select by index, this is an optional virtual method.
*
* This method is called by the SelectCall when the index is already known.
* Default implementation converts to string and calls control().
* Override this to work directly with indices and avoid string conversions.
*
* @param index The index as validated by the SelectCall.
*/
virtual void control(size_t index) { this->control(this->option_at(index)); }
protected:
friend class SelectCall;
size_t active_index_{0};
/** Set the value of the select by index, this is an optional virtual method.
*
* IMPORTANT: At least ONE of the two control() methods must be overridden by derived classes.
* Overriding this index-based version is PREFERRED as it avoids string conversions.
*
* This method is called by the SelectCall when the index is already known.
* Default implementation converts to string and calls control(const std::string&).
*
* @param index The index as validated by the SelectCall.
*/
virtual void control(size_t index) { this->control(this->option_at(index)); }
/** Set the value of the select, this is a virtual method that each select integration can implement.
*
* IMPORTANT: At least ONE of the two control() methods must be overridden by derived classes.

View File

@@ -10,24 +10,26 @@ namespace esphome::sha256 {
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
// CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x):
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x):
//
// The ESP32-S2/S3 uses hardware DMA for SHA acceleration. The DMA engine requires proper
// alignment of the digest output buffer. This is handled automatically via HashBase::digest_
// which has alignas(32). This imposes two critical constraints:
// The ESP32-S3 uses hardware DMA for SHA acceleration. The mbedtls_sha256_context structure contains
// internal state that the DMA engine references. This imposes three critical constraints:
//
// 1. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to
// 1. ALIGNMENT: The SHA256 object MUST be declared with `alignas(32)` for proper DMA alignment.
// Without this, the DMA engine may crash with an abort in sha_hal_read_digest().
//
// 2. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to
// write to incorrect memory locations. This results in null pointer dereferences and crashes.
// ALWAYS use fixed-size arrays (e.g., char buf[65], not char buf[size+1]).
//
// 2. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same
// 3. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same
// function. NEVER pass the SHA256 object or HashBase pointer to another function. When the stack
// frame changes (function call/return), the DMA references become invalid and will produce
// truncated hash output (20 bytes instead of 32) or corrupt memory.
//
// CORRECT USAGE:
// void my_function() {
// sha256::SHA256 hasher;
// alignas(32) sha256::SHA256 hasher; // Created locally with proper alignment
// hasher.init();
// hasher.add(data, len); // Any size, no chunking needed
// hasher.calculate();
@@ -35,9 +37,9 @@ namespace esphome::sha256 {
// // hasher destroyed when function returns
// }
//
// INCORRECT USAGE (WILL FAIL ON ESP32-S2/S3):
// INCORRECT USAGE (WILL FAIL ON ESP32-S3):
// void my_function() {
// sha256::SHA256 hasher;
// sha256::SHA256 hasher; // WRONG: Missing alignas(32)
// helper(&hasher); // WRONG: Passed to different stack frame
// }
// void helper(HashBase *h) {

View File

@@ -24,14 +24,13 @@ namespace esphome::sha256 {
/// SHA256 hash implementation.
///
/// CRITICAL for ESP32-S2/S3 with IDF 5.5.x hardware SHA acceleration:
/// 1. The object MUST stay in the same stack frame (no passing to other functions)
/// 2. NO Variable Length Arrays (VLAs) in the same function
///
/// Note: Alignment is handled automatically via the HashBase::digest_ member.
/// CRITICAL for ESP32-S3 with IDF 5.5.x hardware SHA acceleration:
/// 1. SHA256 objects MUST be declared with `alignas(32)` for proper DMA alignment
/// 2. The object MUST stay in the same stack frame (no passing to other functions)
/// 3. NO Variable Length Arrays (VLAs) in the same function
///
/// Example usage:
/// sha256::SHA256 hasher;
/// alignas(32) sha256::SHA256 hasher;
/// hasher.init();
/// hasher.add(data, len);
/// hasher.calculate();

View File

@@ -104,10 +104,7 @@ std::vector<ObisInfo> SmlFile::get_obis_info() {
std::string bytes_repr(const BytesView &buffer) {
std::string repr;
for (auto const value : buffer) {
// max 3: 2 hex digits + null
char hex_buf[3];
snprintf(hex_buf, sizeof(hex_buf), "%02x", value & 0xff);
repr += hex_buf;
repr += str_sprintf("%02x", value & 0xff);
}
return repr;
}
@@ -149,11 +146,7 @@ ObisInfo::ObisInfo(const BytesView &server_id, const SmlNode &val_list_entry) :
}
std::string ObisInfo::code_repr() const {
// max 20: "255-255:255.255.255" (19 chars) + null
char buf[20];
snprintf(buf, sizeof(buf), "%d-%d:%d.%d.%d", this->code[0], this->code[1], this->code[2], this->code[3],
this->code[4]);
return buf;
return str_sprintf("%d-%d:%d.%d.%d", this->code[0], this->code[1], this->code[2], this->code[3], this->code[4]);
}
} // namespace sml

View File

@@ -107,9 +107,9 @@ std::unique_ptr<Socket> socket_ip_loop_monitored(int type, int protocol) {
#endif /* USE_NETWORK_IPV6 */
}
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port) {
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port) {
#if USE_NETWORK_IPV6
if (strchr(ip_address, ':') != nullptr) {
if (ip_address.find(':') != std::string::npos) {
if (addrlen < sizeof(sockaddr_in6)) {
errno = EINVAL;
return 0;
@@ -121,14 +121,14 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
#ifdef USE_SOCKET_IMPL_BSD_SOCKETS
// Use standard inet_pton for BSD sockets
if (inet_pton(AF_INET6, ip_address, &server->sin6_addr) != 1) {
if (inet_pton(AF_INET6, ip_address.c_str(), &server->sin6_addr) != 1) {
errno = EINVAL;
return 0;
}
#else
// Use LWIP-specific functions
ip6_addr_t ip6;
inet6_aton(ip_address, &ip6);
inet6_aton(ip_address.c_str(), &ip6);
memcpy(server->sin6_addr.un.u32_addr, ip6.addr, sizeof(ip6.addr));
#endif
return sizeof(sockaddr_in6);
@@ -141,7 +141,7 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_
auto *server = reinterpret_cast<sockaddr_in *>(addr);
memset(server, 0, sizeof(sockaddr_in));
server->sin_family = AF_INET;
server->sin_addr.s_addr = inet_addr(ip_address);
server->sin_addr.s_addr = inet_addr(ip_address.c_str());
server->sin_port = htons(port);
return sizeof(sockaddr_in);
}

View File

@@ -87,17 +87,7 @@ std::unique_ptr<Socket> socket_loop_monitored(int domain, int type, int protocol
std::unique_ptr<Socket> socket_ip_loop_monitored(int type, int protocol);
/// Set a sockaddr to the specified address and port for the IP version used by socket_ip().
/// @param addr Destination sockaddr structure
/// @param addrlen Size of the addr buffer
/// @param ip_address Null-terminated IP address string (IPv4 or IPv6)
/// @param port Port number in host byte order
/// @return Size of the sockaddr structure used, or 0 on error
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const char *ip_address, uint16_t port);
/// Convenience overload for std::string (backward compatible).
inline socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port) {
return set_sockaddr(addr, addrlen, ip_address.c_str(), port);
}
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port);
/// Set a sockaddr to the any address and specified port for the IP version used by socket_ip().
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port);

View File

@@ -25,7 +25,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_SPEED): cv.invalid(
"Configuring individual speeds is deprecated."
),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1, max=255),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
}
)

View File

@@ -10,7 +10,7 @@ namespace speed {
class SpeedFan : public Component, public fan::Fan {
public:
SpeedFan(uint8_t speed_count) : speed_count_(speed_count) {}
SpeedFan(int speed_count) : speed_count_(speed_count) {}
void setup() override;
void dump_config() override;
void set_output(output::FloatOutput *output) { this->output_ = output; }
@@ -26,7 +26,7 @@ class SpeedFan : public Component, public fan::Fan {
output::FloatOutput *output_;
output::BinaryOutput *oscillating_{nullptr};
output::BinaryOutput *direction_{nullptr};
uint8_t speed_count_{};
int speed_count_{};
fan::FanTraits traits_;
std::vector<const char *> preset_modes_{};
};

View File

@@ -114,23 +114,14 @@ void StatsdComponent::update() {
// This implies you can't explicitly set a gauge to a negative number without first setting it to zero.
if (val < 0) {
if (this->prefix_) {
out.append(this->prefix_);
out.append(".");
out.append(str_sprintf("%s.", this->prefix_));
}
out.append(s.name);
out.append(":0|g\n");
out.append(str_sprintf("%s:0|g\n", s.name));
}
if (this->prefix_) {
out.append(this->prefix_);
out.append(".");
out.append(str_sprintf("%s.", this->prefix_));
}
out.append(s.name);
// Buffer for ":" + value + "|g\n".
// %g uses max 13 chars for value (sign + 6 significant digits + e+xxx)
// Total: 1 + 13 + 4 = 18 chars + null, use 24 for safety
char val_buf[24];
snprintf(val_buf, sizeof(val_buf), ":%g|g\n", val);
out.append(val_buf);
out.append(str_sprintf("%s:%f|g\n", s.name, val));
if (out.length() > SEND_THRESHOLD) {
this->send_(&out);

View File

@@ -19,7 +19,7 @@ CONFIG_SCHEMA = (
{
cv.Optional(CONF_HAS_DIRECTION, default=False): cv.boolean,
cv.Optional(CONF_HAS_OSCILLATING, default=False): cv.boolean,
cv.Optional(CONF_SPEED_COUNT): cv.int_range(min=1, max=255),
cv.Optional(CONF_SPEED_COUNT): cv.int_range(min=1),
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
}
)

View File

@@ -12,7 +12,7 @@ class TemplateFan final : public Component, public fan::Fan {
void dump_config() override;
void set_has_direction(bool has_direction) { this->has_direction_ = has_direction; }
void set_has_oscillating(bool has_oscillating) { this->has_oscillating_ = has_oscillating; }
void set_speed_count(uint8_t count) { this->speed_count_ = count; }
void set_speed_count(int count) { this->speed_count_ = count; }
void set_preset_modes(std::initializer_list<const char *> presets) { this->preset_modes_ = presets; }
fan::FanTraits get_traits() override { return this->traits_; }
@@ -21,7 +21,7 @@ class TemplateFan final : public Component, public fan::Fan {
bool has_oscillating_{false};
bool has_direction_{false};
uint8_t speed_count_{0};
int speed_count_{0};
fan::FanTraits traits_;
std::vector<const char *> preset_modes_{};
};

View File

@@ -55,7 +55,6 @@ enum MessageType : uint16_t {
COMMAND = 0x0106,
};
// Max string length: 7 ("Unknown"/"Command"). Update print() buffer sizes if adding longer strings.
inline const char *message_type_to_str(MessageType t) {
switch (t) {
case STATUS:
@@ -84,11 +83,7 @@ struct MessageHeader {
}
std::string print() {
// 64 bytes: "MessageHeader: seq " + uint16 + ", len " + uint32 + ", type " + type + safety margin
char buf[64];
snprintf(buf, sizeof(buf), "MessageHeader: seq %d, len %d, type %s", this->seq, this->len,
message_type_to_str(this->type));
return buf;
return str_sprintf("MessageHeader: seq %d, len %d, type %s", this->seq, this->len, message_type_to_str(this->type));
}
void byteswap() {
@@ -136,7 +131,6 @@ inline CoverOperation gate_status_to_cover_operation(GateStatus s) {
return COVER_OPERATION_IDLE;
}
// Max string length: 11 ("Ventilating"). Update print() buffer sizes if adding longer strings.
inline const char *gate_status_to_str(GateStatus s) {
switch (s) {
case PAUSED:
@@ -176,12 +170,7 @@ struct StatusReply {
GateStatus state;
uint8_t trailer = 0x0;
std::string print() {
// 48 bytes: "StatusReply: state " (19) + state (11) + safety margin
char buf[48];
snprintf(buf, sizeof(buf), "StatusReply: state %s", gate_status_to_str(this->state));
return buf;
}
std::string print() { return str_sprintf("StatusReply: state %s", gate_status_to_str(this->state)); }
void byteswap(){};
} __attribute__((packed));
@@ -213,12 +202,7 @@ struct CommandRequestReply {
CommandRequestReply() = default;
CommandRequestReply(GateStatus state) { this->state = state; }
std::string print() {
// 56 bytes: "CommandRequestReply: state " (27) + state (11) + safety margin
char buf[56];
snprintf(buf, sizeof(buf), "CommandRequestReply: state %s", gate_status_to_str(this->state));
return buf;
}
std::string print() { return str_sprintf("CommandRequestReply: state %s", gate_status_to_str(this->state)); }
void byteswap() { this->type = convert_big_endian(this->type); }
} __attribute__((packed));

View File

@@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_SPEED_DATAPOINT): cv.uint8_t,
cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t,
cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t,
cv.Optional(CONF_SPEED_COUNT, default=3): cv.int_range(min=1, max=255),
cv.Optional(CONF_SPEED_COUNT, default=3): cv.int_range(min=1, max=256),
}
)
.extend(cv.COMPONENT_SCHEMA),

View File

@@ -9,7 +9,7 @@ namespace tuya {
class TuyaFan : public Component, public fan::Fan {
public:
TuyaFan(Tuya *parent, uint8_t speed_count) : parent_(parent), speed_count_(speed_count) {}
TuyaFan(Tuya *parent, int speed_count) : parent_(parent), speed_count_(speed_count) {}
void setup() override;
void dump_config() override;
void set_speed_id(uint8_t speed_id) { this->speed_id_ = speed_id; }
@@ -27,7 +27,7 @@ class TuyaFan : public Component, public fan::Fan {
optional<uint8_t> switch_id_{};
optional<uint8_t> oscillation_id_{};
optional<uint8_t> direction_id_{};
uint8_t speed_count_{};
int speed_count_{};
TuyaDatapointType speed_type_{};
TuyaDatapointType oscillation_type_{};
};

View File

@@ -406,9 +406,8 @@ void AsyncWebServerResponse::addHeader(const char *name, const char *value) {
void AsyncResponseStream::print(float value) {
// Use stack buffer to avoid temporary string allocation
// Size: sign (1) + digits (10) + decimal (1) + precision (6) + exponent (5) + null (1) = 24, use 32 for safety
constexpr size_t float_buf_size = 32;
char buf[float_buf_size];
int len = snprintf(buf, float_buf_size, "%f", value);
char buf[32];
int len = snprintf(buf, sizeof(buf), "%f", value);
this->content_.append(buf, len);
}

View File

@@ -1735,7 +1735,6 @@ void WiFiComponent::log_and_adjust_priority_for_failed_connect_() {
(old_priority > std::numeric_limits<int8_t>::min()) ? (old_priority - 1) : std::numeric_limits<int8_t>::min();
this->set_sta_priority(failed_bssid.value(), new_priority);
}
char bssid_s[18];
format_mac_addr_upper(failed_bssid.value().data(), bssid_s);
ESP_LOGD(TAG, "Failed " LOG_SECRET("'%s'") " " LOG_SECRET("(%s)") ", priority %d → %d",

View File

@@ -5,8 +5,6 @@
#include "esphome/core/preferences.h"
#include "esphome/core/log.h"
#include <zephyr/settings/settings.h>
#include <cinttypes>
#include <cstring>
namespace esphome {
namespace zephyr {
@@ -15,9 +13,6 @@ static const char *const TAG = "zephyr.preferences";
#define ESPHOME_SETTINGS_KEY "esphome"
// Buffer size for key: "esphome/" (8) + max hex uint32 (8) + null terminator (1) = 17; use 20 for safety margin
static constexpr size_t KEY_BUFFER_SIZE = 20;
class ZephyrPreferenceBackend : public ESPPreferenceBackend {
public:
ZephyrPreferenceBackend(uint32_t type) { this->type_ = type; }
@@ -32,9 +27,7 @@ class ZephyrPreferenceBackend : public ESPPreferenceBackend {
bool load(uint8_t *data, size_t len) override {
if (len != this->data.size()) {
char key_buf[KEY_BUFFER_SIZE];
this->format_key(key_buf, sizeof(key_buf));
ESP_LOGE(TAG, "size of setting key %s changed, from: %u, to: %u", key_buf, this->data.size(), len);
ESP_LOGE(TAG, "size of setting key %s changed, from: %u, to: %u", get_key().c_str(), this->data.size(), len);
return false;
}
std::memcpy(data, this->data.data(), len);
@@ -43,7 +36,7 @@ class ZephyrPreferenceBackend : public ESPPreferenceBackend {
}
uint32_t get_type() const { return this->type_; }
void format_key(char *buf, size_t size) const { snprintf(buf, size, ESPHOME_SETTINGS_KEY "/%" PRIx32, this->type_); }
std::string get_key() const { return str_sprintf(ESPHOME_SETTINGS_KEY "/%" PRIx32, this->type_); }
std::vector<uint8_t> data;
@@ -92,9 +85,7 @@ class ZephyrPreferences : public ESPPreferences {
}
printf("type %u size %u\n", type, this->backends_.size());
auto *pref = new ZephyrPreferenceBackend(type); // NOLINT(cppcoreguidelines-owning-memory)
char key_buf[KEY_BUFFER_SIZE];
pref->format_key(key_buf, sizeof(key_buf));
ESP_LOGD(TAG, "Add new setting %s.", key_buf);
ESP_LOGD(TAG, "Add new setting %s.", pref->get_key().c_str());
this->backends_.push_back(pref);
return ESPPreferenceObject(pref);
}
@@ -143,10 +134,9 @@ class ZephyrPreferences : public ESPPreferences {
static int export_settings(int (*cb)(const char *name, const void *value, size_t val_len)) {
for (auto *backend : static_cast<ZephyrPreferences *>(global_preferences)->backends_) {
char name[KEY_BUFFER_SIZE];
backend->format_key(name, sizeof(name));
int err = cb(name, backend->data.data(), backend->data.size());
ESP_LOGD(TAG, "save in flash, name %s, len %u, err %d", name, backend->data.size(), err);
auto name = backend->get_key();
int err = cb(name.c_str(), backend->data.data(), backend->data.size());
ESP_LOGD(TAG, "save in flash, name %s, len %u, err %d", name.c_str(), backend->data.size(), err);
}
return 0;
}

View File

@@ -68,12 +68,9 @@ from esphome.const import (
KEY_CORE,
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_RP2040,
PLATFORM_RTL87XX,
SCHEDULER_DONT_RUN,
TYPE_GIT,
TYPE_LOCAL,
@@ -699,7 +696,6 @@ def only_with_framework(
only_on_esp32 = only_on(PLATFORM_ESP32)
only_on_esp8266 = only_on(PLATFORM_ESP8266)
only_on_rp2040 = only_on(PLATFORM_RP2040)
only_on_libretiny = only_on([PLATFORM_BK72XX, PLATFORM_RTL87XX, PLATFORM_LN882X])
only_with_arduino = only_with_framework(Framework.ARDUINO)

View File

@@ -761,15 +761,6 @@ class EsphomeCore:
def relative_piolibdeps_path(self, *path: str | Path) -> Path:
return self.relative_build_path(".piolibdeps", *path)
@property
def platformio_cache_dir(self) -> str:
"""Get the PlatformIO cache directory path."""
# Check if running in Docker/HA addon with custom cache dir
if (cache_dir := os.environ.get("PLATFORMIO_CACHE_DIR")) and cache_dir.strip():
return cache_dir
# Default PlatformIO cache location
return os.path.expanduser("~/.platformio/.cache")
@property
def firmware_bin(self) -> Path:
if self.is_libretiny:

View File

@@ -44,9 +44,7 @@ class HashBase {
virtual size_t get_size() const = 0;
protected:
// 32-byte alignment required for ESP32-S2/S3 hardware SHA DMA operations.
// This also sets the class alignment to 32, ensuring derived objects are properly aligned.
alignas(32) uint8_t digest_[32]; // Storage sized for max(MD5=16, SHA256=32) bytes
uint8_t digest_[32]; // Storage sized for max(MD5=16, SHA256=32) bytes
};
} // namespace esphome

View File

@@ -199,22 +199,11 @@ std::string str_snake_case(const std::string &str) {
}
return result;
}
char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str) {
if (buffer_size == 0) {
return buffer;
}
size_t i = 0;
while (*str && i < buffer_size - 1) {
buffer[i++] = to_sanitized_char(*str++);
}
buffer[i] = '\0';
return buffer;
}
std::string str_sanitize(const std::string &str) {
std::string result;
result.resize(str.size());
str_sanitize_to(&result[0], str.size() + 1, str.c_str());
std::string result = str;
for (char &c : result) {
c = to_sanitized_char(c);
}
return result;
}
std::string str_snprintf(const char *fmt, size_t len, ...) {
@@ -340,10 +329,7 @@ std::string format_hex(const uint8_t *data, size_t length) {
format_hex_to(&ret[0], length * 2 + 1, data, length);
return ret;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
#pragma GCC diagnostic pop
char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator) {
return format_hex_internal(buffer, buffer_size, data, length, separator, 'A');
@@ -418,31 +404,15 @@ std::string format_hex_pretty(const std::string &data, char separator, bool show
return format_hex_pretty_uint8(reinterpret_cast<const uint8_t *>(data.data()), data.length(), separator, show_length);
}
char *format_bin_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) {
if (buffer_size == 0) {
return buffer;
}
// Calculate max bytes we can format: each byte needs 8 chars
size_t max_bytes = (buffer_size - 1) / 8;
if (max_bytes == 0 || length == 0) {
buffer[0] = '\0';
return buffer;
}
size_t bytes_to_format = std::min(length, max_bytes);
for (size_t byte_idx = 0; byte_idx < bytes_to_format; byte_idx++) {
for (size_t bit_idx = 0; bit_idx < 8; bit_idx++) {
buffer[byte_idx * 8 + bit_idx] = ((data[byte_idx] >> (7 - bit_idx)) & 1) + '0';
}
}
buffer[bytes_to_format * 8] = '\0';
return buffer;
}
std::string format_bin(const uint8_t *data, size_t length) {
std::string result;
result.resize(length * 8);
format_bin_to(&result[0], length * 8 + 1, data, length);
for (size_t byte_idx = 0; byte_idx < length; byte_idx++) {
for (size_t bit_idx = 0; bit_idx < 8; bit_idx++) {
result[byte_idx * 8 + bit_idx] = ((data[byte_idx] >> (7 - bit_idx)) & 1) + '0';
}
}
return result;
}
@@ -512,8 +482,10 @@ int8_t step_to_accuracy_decimals(float step) {
return str.length() - dot_pos - 1;
}
// Store BASE64 characters as array - automatically placed in flash/ROM on embedded platforms
static const char BASE64_CHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Use C-style string constant to store in ROM instead of RAM (saves 24 bytes)
static constexpr const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
// Helper function to find the index of a base64 character in the lookup table.
// Returns the character's position (0-63) if found, or 0 if not found.
@@ -523,8 +495,8 @@ static const char BASE64_CHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr
// stops processing at the first invalid character due to the is_base64() check in its
// while loop condition, making this edge case harmless in practice.
static inline uint8_t base64_find_char(char c) {
const void *ptr = memchr(BASE64_CHARS, c, sizeof(BASE64_CHARS));
return ptr ? (static_cast<const char *>(ptr) - BASE64_CHARS) : 0;
const char *pos = strchr(BASE64_CHARS, c);
return pos ? (pos - BASE64_CHARS) : 0;
}
static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); }

View File

@@ -162,9 +162,6 @@ template<typename T, size_t N> class StaticVector {
size_t size() const { return count_; }
bool empty() const { return count_ == 0; }
// Direct access to size counter for efficient in-place construction
size_t &count() { return count_; }
// Direct access to underlying data
T *data() { return data_.data(); }
const T *data() const { return data_.data(); }
@@ -394,15 +391,6 @@ constexpr uint32_t FNV1_OFFSET_BASIS = 2166136261UL;
/// FNV-1 32-bit prime
constexpr uint32_t FNV1_PRIME = 16777619UL;
/// Extend a FNV-1 hash with an integer (hashes each byte).
template<std::integral T> constexpr uint32_t fnv1_hash_extend(uint32_t hash, T value) {
for (size_t i = 0; i < sizeof(T); i++) {
hash *= FNV1_PRIME;
hash ^= (value >> (i * 8)) & 0xFF;
}
return hash;
}
/// Extend a FNV-1a hash with additional string data.
constexpr uint32_t fnv1a_hash_extend(uint32_t hash, const char *str) {
if (str) {
@@ -557,25 +545,7 @@ std::string str_snake_case(const std::string &str);
constexpr char to_sanitized_char(char c) {
return (c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ? c : '_';
}
/** Sanitize a string to buffer, keeping only alphanumerics, dashes, and underscores.
*
* @param buffer Output buffer to write to.
* @param buffer_size Size of the output buffer.
* @param str Input string to sanitize.
* @return Pointer to buffer.
*
* Buffer size needed: strlen(str) + 1.
*/
char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str);
/// Sanitize a string to buffer. Automatically deduces buffer size.
template<size_t N> inline char *str_sanitize_to(char (&buffer)[N], const char *str) {
return str_sanitize_to(buffer, N, str);
}
/// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores.
/// @warning Allocates heap memory. Use str_sanitize_to() with a stack buffer instead.
std::string str_sanitize(const std::string &str);
/// Calculate FNV-1 hash of a string while applying snake_case + sanitize transformations.
@@ -593,11 +563,9 @@ inline uint32_t fnv1_hash_object_id(const char *str, size_t len) {
}
/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator).
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...);
/// sprintf-like function returning std::string.
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
/// Concatenate a name with a separator and suffix using an efficient stack-based approach.
@@ -923,17 +891,13 @@ std::string format_hex(const std::vector<uint8_t> &data);
/// Causes heap fragmentation on long-running devices.
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex(T val) {
val = convert_big_endian(val);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return format_hex(reinterpret_cast<uint8_t *>(&val), sizeof(T));
#pragma GCC diagnostic pop
}
/// Format the std::array \p data in lowercased hex.
/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead.
/// Causes heap fragmentation on long-running devices.
template<std::size_t N> std::string format_hex(const std::array<uint8_t, N> &data) {
return format_hex(data.data(), data.size());
#pragma GCC diagnostic pop
}
/** Format a byte array in pretty-printed, human-readable hex format.
@@ -1081,66 +1045,9 @@ std::string format_hex_pretty(T val, char separator = '.', bool show_length = tr
return format_hex_pretty(reinterpret_cast<uint8_t *>(&val), sizeof(T), separator, show_length);
}
/// Calculate buffer size needed for format_bin_to: "01234567...\0" = bytes * 8 + 1
constexpr size_t format_bin_size(size_t byte_count) { return byte_count * 8 + 1; }
/** Format byte array as binary string to buffer.
*
* Each byte is formatted as 8 binary digits (MSB first).
* Truncates output if data exceeds buffer capacity.
*
* @param buffer Output buffer to write to.
* @param buffer_size Size of the output buffer.
* @param data Pointer to the byte array to format.
* @param length Number of bytes in the array.
* @return Pointer to buffer.
*
* Buffer size needed: length * 8 + 1 (use format_bin_size()).
*
* Example:
* @code
* char buf[9]; // format_bin_size(1)
* format_bin_to(buf, sizeof(buf), data, 1); // "10101011"
* @endcode
*/
char *format_bin_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length);
/// Format byte array as binary to buffer. Automatically deduces buffer size.
template<size_t N> inline char *format_bin_to(char (&buffer)[N], const uint8_t *data, size_t length) {
static_assert(N >= 9, "Buffer must hold at least one binary byte (9 chars)");
return format_bin_to(buffer, N, data, length);
}
/** Format an unsigned integer in binary to buffer, MSB first.
*
* @tparam N Buffer size (must be >= sizeof(T) * 8 + 1).
* @tparam T Unsigned integer type.
* @param buffer Output buffer to write to.
* @param val The unsigned integer value to format.
* @return Pointer to buffer.
*
* Example:
* @code
* char buf[9]; // format_bin_size(sizeof(uint8_t))
* format_bin_to(buf, uint8_t{0xAA}); // "10101010"
* char buf16[17]; // format_bin_size(sizeof(uint16_t))
* format_bin_to(buf16, uint16_t{0x1234}); // "0001001000110100"
* @endcode
*/
template<size_t N, typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
inline char *format_bin_to(char (&buffer)[N], T val) {
static_assert(N >= sizeof(T) * 8 + 1, "Buffer too small for type");
val = convert_big_endian(val);
return format_bin_to(buffer, reinterpret_cast<const uint8_t *>(&val), sizeof(T));
}
/// Format the byte array \p data of length \p len in binary.
/// @warning Allocates heap memory. Use format_bin_to() with a stack buffer instead.
/// Causes heap fragmentation on long-running devices.
std::string format_bin(const uint8_t *data, size_t length);
/// Format an unsigned integer in binary, starting with the most significant byte.
/// @warning Allocates heap memory. Use format_bin_to() with a stack buffer instead.
/// Causes heap fragmentation on long-running devices.
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_bin(T val) {
val = convert_big_endian(val);
return format_bin(reinterpret_cast<uint8_t *>(&val), sizeof(T));

View File

@@ -118,9 +118,10 @@ class Scheduler {
} name_;
uint32_t interval;
// Split time to handle millis() rollover. The scheduler combines the 32-bit millis()
// with a 16-bit rollover counter to create a 48-bit time space (stored as 64-bit
// for compatibility). With 49.7 days per 32-bit rollover, the 16-bit counter
// supports 49.7 days × 65536 = ~8900 years. This ensures correct scheduling
// with a 16-bit rollover counter to create a 48-bit time space (using 32+16 bits).
// This is intentionally limited to 48 bits, not stored as a full 64-bit value.
// With 49.7 days per 32-bit rollover, the 16-bit counter supports
// 49.7 days × 65536 = ~8900 years. This ensures correct scheduling
// even when devices run for months. Split into two fields for better memory
// alignment on 32-bit systems.
uint32_t next_execution_low_; // Lower 32 bits of execution time (millis value)
@@ -402,9 +403,7 @@ class Scheduler {
for (size_t i = 0; i < remaining; i++) {
this->defer_queue_[i] = std::move(this->defer_queue_[this->defer_queue_front_ + i]);
}
// Use erase() instead of resize() to avoid instantiating _M_default_append
// (saves ~156 bytes flash). Erasing from the end is O(1) - no shifting needed.
this->defer_queue_.erase(this->defer_queue_.begin() + remaining, this->defer_queue_.end());
this->defer_queue_.resize(remaining);
}
this->defer_queue_front_ = 0;
}

View File

@@ -171,16 +171,7 @@ def run_compile(config, verbose):
args = []
if CONF_COMPILE_PROCESS_LIMIT in config[CONF_ESPHOME]:
args += [f"-j{config[CONF_ESPHOME][CONF_COMPILE_PROCESS_LIMIT]}"]
result = run_platformio_cli_run(config, verbose, *args)
# Run memory analysis if enabled
if config.get(CONF_ESPHOME, {}).get("analyze_memory", False):
try:
analyze_memory_usage(config)
except Exception as e:
_LOGGER.warning("Failed to analyze memory usage: %s", e)
return result
return run_platformio_cli_run(config, verbose, *args)
def _run_idedata(config):
@@ -434,74 +425,3 @@ class IDEData:
def defines(self) -> list[str]:
"""Return the list of preprocessor defines from idedata."""
return self.raw.get("defines", [])
def analyze_memory_usage(config: dict[str, Any]) -> None:
"""Analyze memory usage by component after compilation."""
# Lazy import to avoid overhead when not needed
from esphome.analyze_memory.cli import MemoryAnalyzerCLI
from esphome.analyze_memory.helpers import get_esphome_components
idedata = get_idedata(config)
# Get paths to tools
elf_path = idedata.firmware_elf_path
objdump_path = idedata.objdump_path
readelf_path = idedata.readelf_path
# Debug logging
_LOGGER.debug("ELF path from idedata: %s", elf_path)
# Check if file exists
if not Path(elf_path).exists():
# Try alternate path
alt_path = Path(CORE.relative_build_path(".pioenvs", CORE.name, "firmware.elf"))
if alt_path.exists():
elf_path = str(alt_path)
_LOGGER.debug("Using alternate ELF path: %s", elf_path)
else:
_LOGGER.warning("ELF file not found at %s or %s", elf_path, alt_path)
return
# Extract external components from config
external_components = set()
# Get the list of built-in ESPHome components
builtin_components = get_esphome_components()
# Special non-component keys that appear in configs
NON_COMPONENT_KEYS = {
CONF_ESPHOME,
"substitutions",
"packages",
"globals",
"<<",
}
# Check all top-level keys in config
for key in config:
if key not in builtin_components and key not in NON_COMPONENT_KEYS:
# This is an external component
external_components.add(key)
_LOGGER.debug("Detected external components: %s", external_components)
# Create analyzer and run analysis
analyzer = MemoryAnalyzerCLI(
elf_path, objdump_path, readelf_path, external_components
)
analyzer.analyze()
# Generate and print report
report = analyzer.generate_report()
_LOGGER.info("\n%s", report)
# Optionally save to file
if config.get(CONF_ESPHOME, {}).get("memory_report_file"):
report_file = Path(config[CONF_ESPHOME]["memory_report_file"])
if report_file.suffix == ".json":
report_file.write_text(analyzer.to_json())
_LOGGER.info("Memory report saved to %s", report_file)
else:
report_file.write_text(report)
_LOGGER.info("Memory report saved to %s", report_file)

View File

@@ -682,18 +682,14 @@ def lint_trailing_whitespace(fname, match):
# Heap-allocating helpers that cause fragmentation on long-running embedded devices.
# These return std::string and should be replaced with stack-based alternatives.
HEAP_ALLOCATING_HELPERS = {
"format_bin": "format_bin_to() with a stack buffer",
"format_hex": "format_hex_to() with a stack buffer",
"format_hex_pretty": "format_hex_pretty_to() with a stack buffer",
"format_mac_address_pretty": "format_mac_addr_upper() with a stack buffer",
"get_mac_address": "get_mac_address_into_buffer() with a stack buffer",
"get_mac_address_pretty": "get_mac_address_pretty_into_buffer() with a stack buffer",
"str_sanitize": "str_sanitize_to() with a stack buffer",
"str_truncate": "removal (function is unused)",
"str_upper_case": "removal (function is unused)",
"str_snake_case": "removal (function is unused)",
"str_sprintf": "snprintf() with a stack buffer",
"str_snprintf": "snprintf() with a stack buffer",
}
@@ -703,18 +699,14 @@ HEAP_ALLOCATING_HELPERS = {
# get_mac_address(?!_) ensures we don't match get_mac_address_into_buffer, etc.
# CPP_RE_EOL captures rest of line so NOLINT comments are detected
r"[^\w]("
r"format_bin(?!_)|"
r"format_hex(?!_)|"
r"format_hex_pretty(?!_)|"
r"format_mac_address_pretty|"
r"get_mac_address_pretty(?!_)|"
r"get_mac_address(?!_)|"
r"str_sanitize(?!_)|"
r"str_truncate|"
r"str_upper_case|"
r"str_snake_case|"
r"str_sprintf|"
r"str_snprintf"
r"str_snake_case"
r")\s*\(" + CPP_RE_EOL,
include=cpp_include,
exclude=[

View File

@@ -90,8 +90,6 @@ class Platform(StrEnum):
ESP32_S2_IDF = "esp32-s2-idf"
ESP32_S3_IDF = "esp32-s3-idf"
BK72XX_ARD = "bk72xx-ard" # LibreTiny BK7231N
RTL87XX_ARD = "rtl87xx-ard" # LibreTiny RTL8720x
LN882X_ARD = "ln882x-ard" # LibreTiny LN882x
RP2040_ARD = "rp2040-ard" # Raspberry Pi Pico
@@ -124,8 +122,8 @@ PLATFORM_SPECIFIC_COMPONENTS = frozenset(
# fastest build times, most sensitive to code size changes
# 3. ESP32 IDF - Primary ESP32 platform, most representative of modern ESPHome
# 4-6. Other ESP32 variants - Less commonly used but still supported
# 7-9. LibreTiny platforms (BK72XX, RTL87XX, LN882X) - good for detecting LibreTiny-specific changes
# 10. RP2040 - Raspberry Pi Pico platform
# 7. BK72XX - LibreTiny platform (good for detecting LibreTiny-specific changes)
# 8. RP2040 - Raspberry Pi Pico platform
MEMORY_IMPACT_PLATFORM_PREFERENCE = [
Platform.ESP32_C6_IDF, # ESP32-C6 IDF (newest, supports Thread/Zigbee)
Platform.ESP8266_ARD, # ESP8266 Arduino (most memory constrained, fastest builds)
@@ -134,8 +132,6 @@ MEMORY_IMPACT_PLATFORM_PREFERENCE = [
Platform.ESP32_S2_IDF, # ESP32-S2 IDF
Platform.ESP32_S3_IDF, # ESP32-S3 IDF
Platform.BK72XX_ARD, # LibreTiny BK7231N
Platform.RTL87XX_ARD, # LibreTiny RTL8720x
Platform.LN882X_ARD, # LibreTiny LN882x
Platform.RP2040_ARD, # Raspberry Pi Pico
]
@@ -415,8 +411,6 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None:
- wifi_component_esp8266.cpp, *_esp8266.h -> ESP8266_ARD
- *_esp32*.cpp -> ESP32 IDF (generic)
- *_libretiny.cpp, *_bk72*.* -> BK72XX (LibreTiny)
- *_rtl87*.* -> RTL87XX (LibreTiny Realtek)
- *_ln882*.* -> LN882X (LibreTiny Lightning)
- *_pico.cpp, *_rp2040.* -> RP2040_ARD
Args:
@@ -450,12 +444,7 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None:
if "esp32" in filename_lower:
return Platform.ESP32_IDF
# LibreTiny platforms (check specific variants before generic libretiny)
# Check specific variants first to handle paths like libretiny/wifi_rtl87xx.cpp
if "rtl87" in filename_lower:
return Platform.RTL87XX_ARD
if "ln882" in filename_lower:
return Platform.LN882X_ARD
# LibreTiny (via 'libretiny' pattern or BK72xx-specific files)
if "libretiny" in filename_lower or "bk72" in filename_lower:
return Platform.BK72XX_ARD

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="10mm" height="10mm" viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" fill="#00FF00"/>
<circle cx="50" cy="50" r="30" fill="#0000FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 248 B

View File

@@ -5,21 +5,17 @@ from __future__ import annotations
from collections.abc import Callable
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch
import pytest
from esphome import config_validation as cv
from esphome.components.image import (
CONF_INVERT_ALPHA,
CONF_OPAQUE,
CONF_TRANSPARENCY,
CONFIG_SCHEMA,
get_all_image_metadata,
get_image_metadata,
write_image,
)
from esphome.const import CONF_DITHER, CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_TYPE
from esphome.const import CONF_ID, CONF_RAW_DATA_ID, CONF_TYPE
from esphome.core import CORE
@@ -354,52 +350,3 @@ def test_get_all_image_metadata_empty() -> None:
"get_all_image_metadata should always return a dict"
)
# Length could be 0 or more depending on what's in CORE at test time
@pytest.fixture
def mock_progmem_array():
"""Mock progmem_array to avoid needing a proper ID object in tests."""
with patch("esphome.components.image.cg.progmem_array") as mock_progmem:
mock_progmem.return_value = MagicMock()
yield mock_progmem
@pytest.mark.asyncio
async def test_svg_with_mm_dimensions_succeeds(
component_config_path: Callable[[str], Path],
mock_progmem_array: MagicMock,
) -> None:
"""Test that SVG files with dimensions in mm are successfully processed."""
# Create a config for write_image without CONF_RESIZE
config = {
CONF_FILE: component_config_path("mm_dimensions.svg"),
CONF_TYPE: "BINARY",
CONF_TRANSPARENCY: CONF_OPAQUE,
CONF_DITHER: "NONE",
CONF_INVERT_ALPHA: False,
CONF_RAW_DATA_ID: "test_raw_data_id",
}
# This should succeed without raising an error
result = await write_image(config)
# Verify that write_image returns the expected tuple
assert isinstance(result, tuple), "write_image should return a tuple"
assert len(result) == 6, "write_image should return 6 values"
prog_arr, width, height, image_type, trans_value, frame_count = result
# Verify the dimensions are positive integers
# At 100 DPI, 10mm = ~39 pixels (10mm * 100dpi / 25.4mm_per_inch)
assert isinstance(width, int), "Width should be an integer"
assert isinstance(height, int), "Height should be an integer"
assert width > 0, "Width should be positive"
assert height > 0, "Height should be positive"
assert frame_count == 1, "Single image should have frame_count of 1"
# Verify we got reasonable dimensions from the mm-based SVG
assert 30 < width < 50, (
f"Width should be around 39 pixels for 10mm at 100dpi, got {width}"
)
assert 30 < height < 50, (
f"Height should be around 39 pixels for 10mm at 100dpi, got {height}"
)

View File

@@ -11,8 +11,6 @@ sensor:
- platform: debug
free:
name: "Heap Free"
block:
name: "Heap Block"
loop_time:
name: "Loop Time"
cpu_frequency:

View File

@@ -1,6 +1 @@
<<: !include common.yaml
sensor:
- platform: debug
min_free:
name: "Heap Min Free"

View File

@@ -2,10 +2,3 @@
esp32:
cpu_frequency: 240MHz
sensor:
- platform: debug
fragmentation:
name: "Heap Fragmentation"
min_free:
name: "Heap Min Free"

View File

@@ -9,9 +9,5 @@ sensor:
name: "Heap Free"
psram:
name: "Free PSRAM"
fragmentation:
name: "Heap Fragmentation"
min_free:
name: "Heap Min Free"
psram:

View File

@@ -1,8 +1 @@
<<: !include common.yaml
sensor:
- platform: debug
fragmentation:
name: "Heap Fragmentation"
min_free:
name: "Heap Min Free"

View File

@@ -1,6 +1 @@
<<: !include common.yaml
sensor:
- platform: debug
fragmentation:
name: "Heap Fragmentation"

View File

@@ -1,6 +1 @@
<<: !include common.yaml
sensor:
- platform: debug
min_free:
name: "Heap Min Free"

View File

@@ -1,6 +0,0 @@
<<: !include common.yaml
sensor:
- platform: debug
min_free:
name: "Heap Min Free"

View File

@@ -1,59 +0,0 @@
esphome:
name: test-user-services-union
friendly_name: Test User Services Union Storage
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
level: DEBUG
wifi:
ssid: "test"
password: "password"
api:
actions:
# Test service with no arguments
- action: test_no_args
then:
- logger.log: "No args service called"
# Test service with one argument
- action: test_one_arg
variables:
value: int
then:
- logger.log:
format: "One arg service: %d"
args: [value]
# Test service with multiple arguments of different types
- action: test_multi_args
variables:
int_val: int
float_val: float
str_val: string
bool_val: bool
then:
- logger.log:
format: "Multi args: %d, %.2f, %s, %d"
args: [int_val, float_val, str_val.c_str(), bool_val]
# Test service with max typical arguments
- action: test_many_args
variables:
arg1: int
arg2: int
arg3: int
arg4: string
arg5: float
then:
- logger.log: "Many args service called"
binary_sensor:
- platform: template
name: "Test Binary Sensor"
id: test_sensor

View File

@@ -1472,24 +1472,6 @@ def test_detect_memory_impact_config_runs_at_component_limit(tmp_path: Path) ->
determine_jobs.Platform.BK72XX_ARD,
),
("esphome/components/ble/ble_bk72xx.cpp", determine_jobs.Platform.BK72XX_ARD),
# RTL87xx (LibreTiny Realtek) detection
(
"tests/components/logger/test.rtl87xx-ard.yaml",
determine_jobs.Platform.RTL87XX_ARD,
),
(
"esphome/components/libretiny/wifi_rtl87xx.cpp",
determine_jobs.Platform.RTL87XX_ARD,
),
# LN882x (LibreTiny Lightning) detection
(
"tests/components/logger/test.ln882x-ard.yaml",
determine_jobs.Platform.LN882X_ARD,
),
(
"esphome/components/libretiny/wifi_ln882x.cpp",
determine_jobs.Platform.LN882X_ARD,
),
# RP2040 / Raspberry Pi Pico detection
("esphome/components/gpio/gpio_rp2040.cpp", determine_jobs.Platform.RP2040_ARD),
("esphome/components/wifi/wifi_rp2040.cpp", determine_jobs.Platform.RP2040_ARD),
@@ -1519,10 +1501,6 @@ def test_detect_memory_impact_config_runs_at_component_limit(tmp_path: Path) ->
"esp32_in_name",
"libretiny",
"bk72xx",
"rtl87xx_test_yaml",
"rtl87xx_wifi",
"ln882x_test_yaml",
"ln882x_wifi",
"rp2040_gpio",
"rp2040_wifi",
"pico_i2c",

View File

@@ -453,7 +453,6 @@ def test_clean_build(
mock_core.relative_pioenvs_path.return_value = pioenvs_dir
mock_core.relative_piolibdeps_path.return_value = piolibdeps_dir
mock_core.relative_build_path.return_value = dependencies_lock
mock_core.platformio_cache_dir = str(platformio_cache_dir)
# Verify all exist before
assert pioenvs_dir.exists()