Merge branch 'dev' into api/peel-first-write-iteration

This commit is contained in:
J. Nick Koston
2026-03-23 08:57:13 -10:00
committed by GitHub
146 changed files with 2022 additions and 903 deletions
+1 -1
View File
@@ -1 +1 @@
9f5d763f95ff720024f3fdddba2fad3801e2bfe00b7cc2124e6d68c17d3504c6
f31f13994768b5b07e29624406c9b053bf4bb26e1623ac2bc1e9d4a9477502d6
+29
View File
@@ -56,6 +56,10 @@ _COMPONENT_PREFIX_LIB = "[lib]"
_COMPONENT_CORE = f"{_COMPONENT_PREFIX_ESPHOME}core"
_COMPONENT_API = f"{_COMPONENT_PREFIX_ESPHOME}api"
# Placement new storage suffix (generated by codegen Pvariable)
_PSTORAGE_SUFFIX = "__pstorage"
# C++ namespace prefixes
_NAMESPACE_ESPHOME = "esphome::"
_NAMESPACE_STD = "std::"
@@ -332,6 +336,13 @@ class MemoryAnalyzer:
# Demangle C++ names if needed
demangled = self._demangle_symbol(symbol_name)
# Check for placement new storage symbols (generated by codegen)
# Format: {component}__{id}__pstorage
if demangled.endswith(_PSTORAGE_SUFFIX) and (
component := self._match_pstorage_component(demangled)
):
return component
# Check for special component classes first (before namespace pattern)
# This handles cases like esphome::ESPHomeOTAComponent which should map to ota
if _NAMESPACE_ESPHOME in demangled:
@@ -399,6 +410,24 @@ class MemoryAnalyzer:
# Track uncategorized symbols for analysis
return "other"
def _match_pstorage_component(self, symbol_name: str) -> str | None:
"""Match a __pstorage symbol to its ESPHome component.
Symbol format: {component}__{id}__pstorage
The component namespace is embedded by codegen before the double underscore.
"""
prefix = symbol_name[: -len(_PSTORAGE_SUFFIX)]
# Extract component namespace before the first double underscore
dunder_pos = prefix.find("__")
if dunder_pos == -1:
return None
component_name = prefix[:dunder_pos]
if component_name in get_esphome_components():
return f"{_COMPONENT_PREFIX_ESPHOME}{component_name}"
if component_name in self.external_components:
return f"{_COMPONENT_PREFIX_EXTERNAL}{component_name}"
return None
def _batch_demangle_symbols(self, symbols: list[str]) -> None:
"""Batch demangle C++ symbol names for efficiency."""
if not symbols:
+35 -13
View File
@@ -15,6 +15,7 @@ from . import (
_COMPONENT_PREFIX_ESPHOME,
_COMPONENT_PREFIX_EXTERNAL,
_COMPONENT_PREFIX_LIB,
_PSTORAGE_SUFFIX,
RAM_SECTIONS,
MemoryAnalyzer,
)
@@ -23,6 +24,17 @@ if TYPE_CHECKING:
from . import ComponentMemory
def _format_pstorage_name(name: str) -> str:
"""Format a __pstorage symbol as 'storage for {id}'."""
if not name.endswith(_PSTORAGE_SUFFIX):
return name
prefix = name[: -len(_PSTORAGE_SUFFIX)]
# Strip component namespace prefix: {component}__{id} -> {id}
dunder_pos = prefix.find("__")
var_id = prefix[dunder_pos + 2 :] if dunder_pos != -1 else prefix
return f"storage for {var_id}"
class MemoryAnalyzerCLI(MemoryAnalyzer):
"""Memory analyzer with CLI-specific report generation."""
@@ -148,11 +160,14 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
If section is one of the RAM sections (.data or .bss), a label like
" [data]" or " [bss]" is appended. For non-RAM sections or when
section is None, no section label is added.
Placement new storage symbols are formatted as "storage for {id}".
"""
display_name = _format_pstorage_name(demangled)
section_label = ""
if section in RAM_SECTIONS:
section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss]
return f"{demangled} ({size:,} B){section_label}"
return f"{display_name} ({size:,} B){section_label}"
def _add_top_symbols(self, lines: list[str]) -> None:
"""Add a section showing the top largest symbols in the binary."""
@@ -175,11 +190,13 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
for i, (_, demangled, size, section, component) in enumerate(top_symbols):
# Format section label
section_label = f"[{section[1:]}]" if section else ""
# Truncate demangled name if too long
# Format storage symbols readably
display_name = _format_pstorage_name(demangled)
# Truncate if too long
demangled_display = (
f"{demangled[:truncate_limit]}..."
if len(demangled) > self.COL_TOP_SYMBOL_NAME
else demangled
f"{display_name[:truncate_limit]}..."
if len(display_name) > self.COL_TOP_SYMBOL_NAME
else display_name
)
lines.append(
f"{i + 1:>2}. {size:>7,} B {section_label:<8} {demangled_display:<{self.COL_TOP_SYMBOL_NAME}} {component}"
@@ -573,15 +590,16 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
lines.append(f"Total size: {comp_mem.flash_total:,} B")
lines.append("")
# Show all symbols above threshold for better visibility
# Show symbols above threshold, always include storage symbols
large_symbols = [
(sym, dem, size, sec)
for sym, dem, size, sec in sorted_symbols
if size > self.SYMBOL_SIZE_THRESHOLD
or dem.endswith(_PSTORAGE_SUFFIX)
]
lines.append(
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B ({len(large_symbols)} symbols):"
f"{comp_name} Symbols > {self.SYMBOL_SIZE_THRESHOLD} B & storage ({len(large_symbols)} symbols):"
)
for i, (symbol, demangled, size, section) in enumerate(large_symbols):
lines.append(
@@ -604,7 +622,10 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
# Sort by size descending
sorted_ram_syms = sorted(ram_syms, key=lambda x: x[2], reverse=True)
large_ram_syms = [
s for s in sorted_ram_syms if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD
s
for s in sorted_ram_syms
if s[2] > self.RAM_SYMBOL_SIZE_THRESHOLD
or s[1].endswith(_PSTORAGE_SUFFIX)
]
lines.append(f"{name} ({mem.ram_total:,} B total RAM):")
@@ -622,13 +643,14 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
for symbol, demangled, size, section in large_ram_syms[:10]:
# Format section label consistently by stripping leading dot
section_label = section.lstrip(".") if section else ""
display_name = _format_pstorage_name(demangled)
# Add ellipsis if name is truncated
demangled_display = (
f"{demangled[:70]}..." if len(demangled) > 70 else demangled
)
lines.append(
f" {size:>6,} B [{section_label}] {demangled_display}"
display_name = (
f"{display_name[:70]}..."
if len(display_name) > 70
else display_name
)
lines.append(f" {size:>6,} B [{section_label}] {display_name}")
if len(large_ram_syms) > 10:
lines.append(f" ... and {len(large_ram_syms) - 10} more")
lines.append("")
+71 -71
View File
@@ -316,7 +316,7 @@ message ListEntitiesBinarySensorResponse {
option (ifdef) = "USE_BINARY_SENSOR";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -334,7 +334,7 @@ message BinarySensorStateResponse {
option (ifdef) = "USE_BINARY_SENSOR";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool state = 2;
// If the binary sensor does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
@@ -350,7 +350,7 @@ message ListEntitiesCoverResponse {
option (ifdef) = "USE_COVER";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -383,7 +383,7 @@ message CoverStateResponse {
option (ifdef) = "USE_COVER";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
// legacy: state has been removed in 1.13
// clients/servers must still send/accept it until the next protocol change
// Deprecated in API version 1.1
@@ -409,7 +409,7 @@ message CoverCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
// legacy: command has been removed in 1.13
// clients/servers must still send/accept it until the next protocol change
@@ -434,7 +434,7 @@ message ListEntitiesFanResponse {
option (ifdef) = "USE_FAN";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -466,7 +466,7 @@ message FanStateResponse {
option (ifdef) = "USE_FAN";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool state = 2;
bool oscillating = 3;
// Deprecated in API version 1.6
@@ -483,7 +483,7 @@ message FanCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool has_state = 2;
bool state = 3;
// Deprecated in API version 1.6
@@ -522,7 +522,7 @@ message ListEntitiesLightResponse {
option (ifdef) = "USE_LIGHT";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -551,7 +551,7 @@ message LightStateResponse {
option (ifdef) = "USE_LIGHT";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool state = 2;
float brightness = 3;
ColorMode color_mode = 11;
@@ -573,7 +573,7 @@ message LightCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool has_state = 2;
bool state = 3;
bool has_brightness = 4;
@@ -627,7 +627,7 @@ message ListEntitiesSensorResponse {
option (ifdef) = "USE_SENSOR";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -651,7 +651,7 @@ message SensorStateResponse {
option (ifdef) = "USE_SENSOR";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
float state = 2;
// If the sensor does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
@@ -667,7 +667,7 @@ message ListEntitiesSwitchResponse {
option (ifdef) = "USE_SWITCH";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -685,7 +685,7 @@ message SwitchStateResponse {
option (ifdef) = "USE_SWITCH";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool state = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -696,7 +696,7 @@ message SwitchCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool state = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -709,7 +709,7 @@ message ListEntitiesTextSensorResponse {
option (ifdef) = "USE_TEXT_SENSOR";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -726,7 +726,7 @@ message TextSensorStateResponse {
option (ifdef) = "USE_TEXT_SENSOR";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
string state = 2;
// If the text sensor does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
@@ -922,7 +922,7 @@ message ListEntitiesServicesResponse {
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
string name = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
repeated ListEntitiesServicesArgument args = 3 [(fixed_vector) = true];
SupportsResponseType supports_response = 4;
}
@@ -945,7 +945,7 @@ message ExecuteServiceRequest {
option (no_delay) = true;
option (ifdef) = "USE_API_USER_DEFINED_ACTIONS";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
repeated ExecuteServiceArgument args = 2 [(fixed_vector) = true];
uint32 call_id = 3 [(field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"];
bool return_response = 4 [(field_ifdef) = "USE_API_USER_DEFINED_ACTION_RESPONSES"];
@@ -972,7 +972,7 @@ message ListEntitiesCameraResponse {
option (ifdef) = "USE_CAMERA";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
bool disabled_by_default = 5;
@@ -987,7 +987,7 @@ message CameraImageResponse {
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_CAMERA";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bytes data = 2;
bool done = 3;
uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"];
@@ -1057,7 +1057,7 @@ message ListEntitiesClimateResponse {
option (ifdef) = "USE_CLIMATE";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -1095,7 +1095,7 @@ message ClimateStateResponse {
option (ifdef) = "USE_CLIMATE";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
ClimateMode mode = 2;
float current_temperature = 3;
float target_temperature = 4;
@@ -1121,7 +1121,7 @@ message ClimateCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool has_mode = 2;
ClimateMode mode = 3;
bool has_target_temperature = 4;
@@ -1168,7 +1168,7 @@ message ListEntitiesWaterHeaterResponse {
option (ifdef) = "USE_WATER_HEATER";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
bool disabled_by_default = 5;
@@ -1189,7 +1189,7 @@ message WaterHeaterStateResponse {
option (ifdef) = "USE_WATER_HEATER";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
float current_temperature = 2;
float target_temperature = 3;
WaterHeaterMode mode = 4;
@@ -1219,7 +1219,7 @@ message WaterHeaterCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
// Bitmask of which fields are set (see WaterHeaterCommandHasField)
uint32 has_fields = 2;
WaterHeaterMode mode = 3;
@@ -1244,7 +1244,7 @@ message ListEntitiesNumberResponse {
option (ifdef) = "USE_NUMBER";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -1266,7 +1266,7 @@ message NumberStateResponse {
option (ifdef) = "USE_NUMBER";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
float state = 2;
// If the number does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
@@ -1280,7 +1280,7 @@ message NumberCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
float state = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -1293,7 +1293,7 @@ message ListEntitiesSelectResponse {
option (ifdef) = "USE_SELECT";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -1310,7 +1310,7 @@ message SelectStateResponse {
option (ifdef) = "USE_SELECT";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
string state = 2;
// If the select does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
@@ -1324,7 +1324,7 @@ message SelectCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
string state = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -1337,7 +1337,7 @@ message ListEntitiesSirenResponse {
option (ifdef) = "USE_SIREN";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -1356,7 +1356,7 @@ message SirenStateResponse {
option (ifdef) = "USE_SIREN";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool state = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -1367,7 +1367,7 @@ message SirenCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool has_state = 2;
bool state = 3;
bool has_tone = 4;
@@ -1400,7 +1400,7 @@ message ListEntitiesLockResponse {
option (ifdef) = "USE_LOCK";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -1422,7 +1422,7 @@ message LockStateResponse {
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_LOCK";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
LockState state = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -1432,7 +1432,7 @@ message LockCommandRequest {
option (ifdef) = "USE_LOCK";
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
LockCommand command = 2;
// Not yet implemented:
@@ -1449,7 +1449,7 @@ message ListEntitiesButtonResponse {
option (ifdef) = "USE_BUTTON";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -1466,7 +1466,7 @@ message ButtonCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
uint32 device_id = 2 [(field_ifdef) = "USE_DEVICES"];
}
@@ -1516,7 +1516,7 @@ message ListEntitiesMediaPlayerResponse {
option (ifdef) = "USE_MEDIA_PLAYER";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -1538,7 +1538,7 @@ message MediaPlayerStateResponse {
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_MEDIA_PLAYER";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
MediaPlayerState state = 2;
float volume = 3;
bool muted = 4;
@@ -1551,7 +1551,7 @@ message MediaPlayerCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool has_command = 2;
MediaPlayerCommand command = 3;
@@ -2104,7 +2104,7 @@ message ListEntitiesAlarmControlPanelResponse {
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
@@ -2122,7 +2122,7 @@ message AlarmControlPanelStateResponse {
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
AlarmControlPanelState state = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -2133,7 +2133,7 @@ message AlarmControlPanelCommandRequest {
option (ifdef) = "USE_ALARM_CONTROL_PANEL";
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
AlarmControlPanelStateCommand command = 2;
string code = 3;
uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"];
@@ -2151,7 +2151,7 @@ message ListEntitiesTextResponse {
option (ifdef) = "USE_TEXT";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
@@ -2171,7 +2171,7 @@ message TextStateResponse {
option (ifdef) = "USE_TEXT";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
string state = 2;
// If the Text does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
@@ -2185,7 +2185,7 @@ message TextCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
string state = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -2199,7 +2199,7 @@ message ListEntitiesDateResponse {
option (ifdef) = "USE_DATETIME_DATE";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -2215,7 +2215,7 @@ message DateStateResponse {
option (ifdef) = "USE_DATETIME_DATE";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
// If the date does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
bool missing_state = 2;
@@ -2231,7 +2231,7 @@ message DateCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
uint32 year = 2;
uint32 month = 3;
uint32 day = 4;
@@ -2246,7 +2246,7 @@ message ListEntitiesTimeResponse {
option (ifdef) = "USE_DATETIME_TIME";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -2262,7 +2262,7 @@ message TimeStateResponse {
option (ifdef) = "USE_DATETIME_TIME";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
// If the time does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
bool missing_state = 2;
@@ -2278,7 +2278,7 @@ message TimeCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
uint32 hour = 2;
uint32 minute = 3;
uint32 second = 4;
@@ -2293,7 +2293,7 @@ message ListEntitiesEventResponse {
option (ifdef) = "USE_EVENT";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -2311,7 +2311,7 @@ message EventResponse {
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_EVENT";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
string event_type = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -2324,7 +2324,7 @@ message ListEntitiesValveResponse {
option (ifdef) = "USE_VALVE";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -2351,7 +2351,7 @@ message ValveStateResponse {
option (ifdef) = "USE_VALVE";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
float position = 2;
ValveOperation current_operation = 3;
uint32 device_id = 4 [(field_ifdef) = "USE_DEVICES"];
@@ -2364,7 +2364,7 @@ message ValveCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool has_position = 2;
float position = 3;
bool stop = 4;
@@ -2379,7 +2379,7 @@ message ListEntitiesDateTimeResponse {
option (ifdef) = "USE_DATETIME_DATETIME";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -2395,7 +2395,7 @@ message DateTimeStateResponse {
option (ifdef) = "USE_DATETIME_DATETIME";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
// If the datetime does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
bool missing_state = 2;
@@ -2409,7 +2409,7 @@ message DateTimeCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
fixed32 epoch_seconds = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -2422,7 +2422,7 @@ message ListEntitiesUpdateResponse {
option (ifdef) = "USE_UPDATE";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
reserved 4; // Deprecated: was string unique_id
@@ -2439,7 +2439,7 @@ message UpdateStateResponse {
option (ifdef) = "USE_UPDATE";
option (no_delay) = true;
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
bool missing_state = 2;
bool in_progress = 3;
bool has_progress = 4;
@@ -2463,7 +2463,7 @@ message UpdateCommandRequest {
option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1;
fixed32 key = 1 [(force) = true];
UpdateCommand command = 2;
uint32 device_id = 3 [(field_ifdef) = "USE_DEVICES"];
}
@@ -2505,7 +2505,7 @@ message ListEntitiesInfraredResponse {
option (ifdef) = "USE_INFRARED";
string object_id = 1;
fixed32 key = 2;
fixed32 key = 2 [(force) = true];
string name = 3;
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
bool disabled_by_default = 5;
@@ -2521,7 +2521,7 @@ message InfraredRFTransmitRawTimingsRequest {
option (ifdef) = "USE_IR_RF";
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
fixed32 key = 2; // Key identifying the transmitter instance
fixed32 key = 2 [(force) = true]; // Key identifying the transmitter instance
uint32 carrier_frequency = 3; // Carrier frequency in Hz
uint32 repeat_count = 4; // Number of times to transmit (1 = once, 2 = twice, etc.)
repeated sint32 timings = 5 [packed = true, (packed_buffer) = true]; // Raw timings in microseconds (zigzag-encoded): positive = mark (LED/TX on), negative = space (LED/TX off)
@@ -2535,7 +2535,7 @@ message InfraredRFReceiveEvent {
option (no_delay) = true;
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
fixed32 key = 2; // Key identifying the receiver instance
fixed32 key = 2 [(force) = true]; // Key identifying the receiver instance
repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector<int32_t>"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods
}
+5 -5
View File
@@ -234,7 +234,7 @@ void APIConnection::loop() {
this->last_traffic_ = now;
}
// read a packet
this->read_message(buffer.data_len, buffer.type, buffer.data);
this->read_message_(buffer.data_len, buffer.type, buffer.data);
if (this->flags_.remove)
return;
}
@@ -1519,16 +1519,16 @@ void APIConnection::on_serial_proxy_request(const SerialProxyRequest &msg) {
resp.instance = msg.instance;
resp.type = enums::SERIAL_PROXY_REQUEST_TYPE_FLUSH;
switch (proxies[msg.instance]->flush_port()) {
case uart::FlushResult::SUCCESS:
case uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS:
resp.status = enums::SERIAL_PROXY_STATUS_OK;
break;
case uart::FlushResult::ASSUMED_SUCCESS:
case uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS:
resp.status = enums::SERIAL_PROXY_STATUS_ASSUMED_SUCCESS;
break;
case uart::FlushResult::TIMEOUT:
case uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT:
resp.status = enums::SERIAL_PROXY_STATUS_TIMEOUT;
break;
case uart::FlushResult::FAILED:
case uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED:
resp.status = enums::SERIAL_PROXY_STATUS_ERROR;
break;
}
+87 -69
View File
@@ -49,11 +49,29 @@ class APIConnection final : public APIServerConnectionBase {
friend class APIServer;
friend class ListEntitiesIterator;
APIConnection(std::unique_ptr<socket::Socket> socket, APIServer *parent);
virtual ~APIConnection();
~APIConnection();
void start();
void loop();
protected:
// read_message_ is defined here (instead of in APIServerConnectionBase) so the
// compiler can devirtualize and inline on_* handler calls within this final class.
void read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data);
// Auth helpers defined here (not in ProtoService) so the compiler can
// devirtualize is_connection_setup()/on_no_setup_connection() calls
// within this final class.
inline bool check_connection_setup_() {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return false;
}
return true;
}
inline bool check_authenticated_() { return this->check_connection_setup_(); }
public:
bool send_list_info_done() {
return this->schedule_message_(nullptr, ListEntitiesDoneResponse::MESSAGE_TYPE,
ListEntitiesDoneResponse::ESTIMATED_SIZE);
@@ -63,72 +81,72 @@ class APIConnection final : public APIServerConnectionBase {
#endif
#ifdef USE_COVER
bool send_cover_state(cover::Cover *cover);
void on_cover_command_request(const CoverCommandRequest &msg) override;
void on_cover_command_request(const CoverCommandRequest &msg);
#endif
#ifdef USE_FAN
bool send_fan_state(fan::Fan *fan);
void on_fan_command_request(const FanCommandRequest &msg) override;
void on_fan_command_request(const FanCommandRequest &msg);
#endif
#ifdef USE_LIGHT
bool send_light_state(light::LightState *light);
void on_light_command_request(const LightCommandRequest &msg) override;
void on_light_command_request(const LightCommandRequest &msg);
#endif
#ifdef USE_SENSOR
bool send_sensor_state(sensor::Sensor *sensor);
#endif
#ifdef USE_SWITCH
bool send_switch_state(switch_::Switch *a_switch);
void on_switch_command_request(const SwitchCommandRequest &msg) override;
void on_switch_command_request(const SwitchCommandRequest &msg);
#endif
#ifdef USE_TEXT_SENSOR
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor);
#endif
#ifdef USE_CAMERA
void set_camera_state(std::shared_ptr<camera::CameraImage> image);
void on_camera_image_request(const CameraImageRequest &msg) override;
void on_camera_image_request(const CameraImageRequest &msg);
#endif
#ifdef USE_CLIMATE
bool send_climate_state(climate::Climate *climate);
void on_climate_command_request(const ClimateCommandRequest &msg) override;
void on_climate_command_request(const ClimateCommandRequest &msg);
#endif
#ifdef USE_NUMBER
bool send_number_state(number::Number *number);
void on_number_command_request(const NumberCommandRequest &msg) override;
void on_number_command_request(const NumberCommandRequest &msg);
#endif
#ifdef USE_DATETIME_DATE
bool send_date_state(datetime::DateEntity *date);
void on_date_command_request(const DateCommandRequest &msg) override;
void on_date_command_request(const DateCommandRequest &msg);
#endif
#ifdef USE_DATETIME_TIME
bool send_time_state(datetime::TimeEntity *time);
void on_time_command_request(const TimeCommandRequest &msg) override;
void on_time_command_request(const TimeCommandRequest &msg);
#endif
#ifdef USE_DATETIME_DATETIME
bool send_datetime_state(datetime::DateTimeEntity *datetime);
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
void on_date_time_command_request(const DateTimeCommandRequest &msg);
#endif
#ifdef USE_TEXT
bool send_text_state(text::Text *text);
void on_text_command_request(const TextCommandRequest &msg) override;
void on_text_command_request(const TextCommandRequest &msg);
#endif
#ifdef USE_SELECT
bool send_select_state(select::Select *select);
void on_select_command_request(const SelectCommandRequest &msg) override;
void on_select_command_request(const SelectCommandRequest &msg);
#endif
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
void on_button_command_request(const ButtonCommandRequest &msg);
#endif
#ifdef USE_LOCK
bool send_lock_state(lock::Lock *a_lock);
void on_lock_command_request(const LockCommandRequest &msg) override;
void on_lock_command_request(const LockCommandRequest &msg);
#endif
#ifdef USE_VALVE
bool send_valve_state(valve::Valve *valve);
void on_valve_command_request(const ValveCommandRequest &msg) override;
void on_valve_command_request(const ValveCommandRequest &msg);
#endif
#ifdef USE_MEDIA_PLAYER
bool send_media_player_state(media_player::MediaPlayer *media_player);
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
void on_media_player_command_request(const MediaPlayerCommandRequest &msg);
#endif
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
#ifdef USE_API_HOMEASSISTANT_SERVICES
@@ -138,23 +156,23 @@ class APIConnection final : public APIServerConnectionBase {
this->send_message(call);
}
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
void on_homeassistant_action_response(const HomeassistantActionResponse &msg) override;
void on_homeassistant_action_response(const HomeassistantActionResponse &msg);
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
#endif // USE_API_HOMEASSISTANT_SERVICES
#ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
void on_unsubscribe_bluetooth_le_advertisements_request() override;
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg);
void on_unsubscribe_bluetooth_le_advertisements_request();
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg) override;
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg) override;
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg) override;
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg) override;
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg) override;
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg) override;
void on_subscribe_bluetooth_connections_free_request() override;
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg) override;
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg) override;
void on_bluetooth_device_request(const BluetoothDeviceRequest &msg);
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &msg);
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &msg);
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &msg);
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &msg);
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &msg);
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &msg);
void on_subscribe_bluetooth_connections_free_request();
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &msg);
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &msg);
#endif
#ifdef USE_HOMEASSISTANT_TIME
@@ -165,42 +183,42 @@ class APIConnection final : public APIServerConnectionBase {
#endif
#ifdef USE_VOICE_ASSISTANT
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override;
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override;
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override;
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg);
void on_voice_assistant_response(const VoiceAssistantResponse &msg);
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg);
void on_voice_assistant_audio(const VoiceAssistantAudio &msg);
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg);
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg);
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg);
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg);
#endif
#ifdef USE_ZWAVE_PROXY
void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) override;
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
void on_z_wave_proxy_frame(const ZWaveProxyFrame &msg);
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel);
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override;
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg);
#endif
#ifdef USE_WATER_HEATER
bool send_water_heater_state(water_heater::WaterHeater *water_heater);
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg);
#endif
#ifdef USE_IR_RF
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override;
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg);
void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg);
#endif
#ifdef USE_SERIAL_PROXY
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg) override;
void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg) override;
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg) override;
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg) override;
void on_serial_proxy_request(const SerialProxyRequest &msg) override;
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &msg);
void on_serial_proxy_write_request(const SerialProxyWriteRequest &msg);
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &msg);
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &msg);
void on_serial_proxy_request(const SerialProxyRequest &msg);
void send_serial_proxy_data(const SerialProxyDataReceived &msg);
#endif
@@ -210,26 +228,26 @@ class APIConnection final : public APIServerConnectionBase {
#ifdef USE_UPDATE
bool send_update_state(update::UpdateEntity *update);
void on_update_command_request(const UpdateCommandRequest &msg) override;
void on_update_command_request(const UpdateCommandRequest &msg);
#endif
void on_disconnect_response() override;
void on_ping_response() override {
void on_disconnect_response();
void on_ping_response() {
// we initiated ping
this->flags_.sent_ping = false;
}
#ifdef USE_API_HOMEASSISTANT_STATES
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg);
#endif
#ifdef USE_HOMEASSISTANT_TIME
void on_get_time_response(const GetTimeResponse &value) override;
void on_get_time_response(const GetTimeResponse &value);
#endif
void on_hello_request(const HelloRequest &msg) override;
void on_disconnect_request() override;
void on_ping_request() override;
void on_device_info_request() override;
void on_list_entities_request() override { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
void on_subscribe_states_request() override {
void on_hello_request(const HelloRequest &msg);
void on_disconnect_request();
void on_ping_request();
void on_device_info_request();
void on_list_entities_request() { this->begin_iterator_(ActiveIterator::LIST_ENTITIES); }
void on_subscribe_states_request() {
this->flags_.state_subscription = true;
// Start initial state iterator only if no iterator is active
// If list_entities is running, we'll start initial_state when it completes
@@ -237,7 +255,7 @@ class APIConnection final : public APIServerConnectionBase {
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
}
}
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override {
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) {
this->flags_.log_subscription = msg.level;
if (msg.dump_config)
App.schedule_dump_config();
@@ -249,13 +267,13 @@ class APIConnection final : public APIServerConnectionBase {
#endif
}
#ifdef USE_API_HOMEASSISTANT_SERVICES
void on_subscribe_homeassistant_services_request() override { this->flags_.service_call_subscription = true; }
void on_subscribe_homeassistant_services_request() { this->flags_.service_call_subscription = true; }
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
void on_subscribe_home_assistant_states_request() override;
void on_subscribe_home_assistant_states_request();
#endif
#ifdef USE_API_USER_DEFINED_ACTIONS
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
void on_execute_service_request(const ExecuteServiceRequest &msg);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
void send_execute_service_response(uint32_t call_id, bool success, StringRef error_message);
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
@@ -265,13 +283,13 @@ class APIConnection final : public APIServerConnectionBase {
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
#endif
#ifdef USE_API_NOISE
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg);
#endif
bool is_authenticated() override {
bool is_authenticated() {
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::AUTHENTICATED;
}
bool is_connection_setup() override {
bool is_connection_setup() {
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::CONNECTED ||
this->is_authenticated();
}
@@ -284,8 +302,8 @@ class APIConnection final : public APIServerConnectionBase {
(this->client_api_version_major_ == major && this->client_api_version_minor_ >= minor);
}
void on_fatal_error() override;
void on_no_setup_connection() override;
void on_fatal_error();
void on_no_setup_connection();
// Function pointer type for type-erased message encoding
using MessageEncodeFn = void (*)(const void *, ProtoWriteBuffer &);
@@ -324,7 +342,7 @@ class APIConnection final : public APIServerConnectionBase {
return true;
return this->try_to_clear_buffer_slow_(log_out_of_space);
}
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type);
const char *get_name() const { return this->helper_->get_client_name(); }
/// Get peer name (IP address) into caller-provided buffer, returns buf for convenience
+100 -100
View File
@@ -208,7 +208,7 @@ uint32_t DeviceInfoResponse::calculate_size() const {
#ifdef USE_BINARY_SENSOR
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(5, this->device_class);
buffer.encode_bool(6, this->is_status_binary_sensor);
@@ -224,7 +224,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesBinarySensorResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
size += ProtoSize::calc_length(1, this->device_class.size());
size += ProtoSize::calc_bool(1, this->is_status_binary_sensor);
@@ -239,7 +239,7 @@ uint32_t ListEntitiesBinarySensorResponse::calculate_size() const {
return size;
}
void BinarySensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_bool(2, this->state);
buffer.encode_bool(3, this->missing_state);
#ifdef USE_DEVICES
@@ -248,7 +248,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t BinarySensorStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_bool(1, this->state);
size += ProtoSize::calc_bool(1, this->missing_state);
#ifdef USE_DEVICES
@@ -260,7 +260,7 @@ uint32_t BinarySensorStateResponse::calculate_size() const {
#ifdef USE_COVER
void ListEntitiesCoverResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
buffer.encode_bool(5, this->assumed_state);
buffer.encode_bool(6, this->supports_position);
@@ -279,7 +279,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesCoverResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
size += ProtoSize::calc_bool(1, this->assumed_state);
size += ProtoSize::calc_bool(1, this->supports_position);
@@ -297,7 +297,7 @@ uint32_t ListEntitiesCoverResponse::calculate_size() const {
return size;
}
void CoverStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_float(3, this->position);
buffer.encode_float(4, this->tilt);
buffer.encode_uint32(5, static_cast<uint32_t>(this->current_operation));
@@ -307,7 +307,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t CoverStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_float(1, this->position);
size += ProtoSize::calc_float(1, this->tilt);
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->current_operation));
@@ -357,7 +357,7 @@ bool CoverCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_FAN
void ListEntitiesFanResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
buffer.encode_bool(5, this->supports_oscillation);
buffer.encode_bool(6, this->supports_speed);
@@ -378,7 +378,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesFanResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
size += ProtoSize::calc_bool(1, this->supports_oscillation);
size += ProtoSize::calc_bool(1, this->supports_speed);
@@ -400,7 +400,7 @@ uint32_t ListEntitiesFanResponse::calculate_size() const {
return size;
}
void FanStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_bool(2, this->state);
buffer.encode_bool(3, this->oscillating);
buffer.encode_uint32(5, static_cast<uint32_t>(this->direction));
@@ -412,7 +412,7 @@ void FanStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t FanStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_bool(1, this->state);
size += ProtoSize::calc_bool(1, this->oscillating);
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->direction));
@@ -487,7 +487,7 @@ bool FanCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_LIGHT
void ListEntitiesLightResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
for (const auto &it : *this->supported_color_modes) {
buffer.encode_uint32(12, static_cast<uint32_t>(it), true);
@@ -509,7 +509,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesLightResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
if (!this->supported_color_modes->empty()) {
for (const auto &it : *this->supported_color_modes) {
@@ -534,7 +534,7 @@ uint32_t ListEntitiesLightResponse::calculate_size() const {
return size;
}
void LightStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_bool(2, this->state);
buffer.encode_float(3, this->brightness);
buffer.encode_uint32(11, static_cast<uint32_t>(this->color_mode));
@@ -553,7 +553,7 @@ void LightStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t LightStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_bool(1, this->state);
size += ProtoSize::calc_float(1, this->brightness);
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->color_mode));
@@ -683,7 +683,7 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_SENSOR
void ListEntitiesSensorResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -702,7 +702,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesSensorResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -720,7 +720,7 @@ uint32_t ListEntitiesSensorResponse::calculate_size() const {
return size;
}
void SensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_float(2, this->state);
buffer.encode_bool(3, this->missing_state);
#ifdef USE_DEVICES
@@ -729,7 +729,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t SensorStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_float(1, this->state);
size += ProtoSize::calc_bool(1, this->missing_state);
#ifdef USE_DEVICES
@@ -741,7 +741,7 @@ uint32_t SensorStateResponse::calculate_size() const {
#ifdef USE_SWITCH
void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -757,7 +757,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesSwitchResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -772,7 +772,7 @@ uint32_t ListEntitiesSwitchResponse::calculate_size() const {
return size;
}
void SwitchStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_bool(2, this->state);
#ifdef USE_DEVICES
buffer.encode_uint32(3, this->device_id);
@@ -780,7 +780,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t SwitchStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_bool(1, this->state);
#ifdef USE_DEVICES
size += ProtoSize::calc_uint32(1, this->device_id);
@@ -816,7 +816,7 @@ bool SwitchCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_TEXT_SENSOR
void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -831,7 +831,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesTextSensorResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -845,7 +845,7 @@ uint32_t ListEntitiesTextSensorResponse::calculate_size() const {
return size;
}
void TextSensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_string(2, this->state);
buffer.encode_bool(3, this->missing_state);
#ifdef USE_DEVICES
@@ -854,7 +854,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t TextSensorStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->state.size());
size += ProtoSize::calc_bool(1, this->missing_state);
#ifdef USE_DEVICES
@@ -1124,7 +1124,7 @@ uint32_t ListEntitiesServicesArgument::calculate_size() const {
}
void ListEntitiesServicesResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->name);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
for (auto &it : this->args) {
buffer.encode_sub_message(3, it);
}
@@ -1133,7 +1133,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesServicesResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->name.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
if (!this->args.empty()) {
for (const auto &it : this->args) {
size += ProtoSize::calc_message_force(1, it.calculate_size());
@@ -1269,7 +1269,7 @@ uint32_t ExecuteServiceResponse::calculate_size() const {
#ifdef USE_CAMERA
void ListEntitiesCameraResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
buffer.encode_bool(5, this->disabled_by_default);
#ifdef USE_ENTITY_ICON
@@ -1283,7 +1283,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesCameraResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
size += ProtoSize::calc_bool(1, this->disabled_by_default);
#ifdef USE_ENTITY_ICON
@@ -1296,7 +1296,7 @@ uint32_t ListEntitiesCameraResponse::calculate_size() const {
return size;
}
void CameraImageResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_bytes(2, this->data_ptr_, this->data_len_);
buffer.encode_bool(3, this->done);
#ifdef USE_DEVICES
@@ -1305,7 +1305,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t CameraImageResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->data_len_);
size += ProtoSize::calc_bool(1, this->done);
#ifdef USE_DEVICES
@@ -1330,7 +1330,7 @@ bool CameraImageRequest::decode_varint(uint32_t field_id, proto_varint_value_t v
#ifdef USE_CLIMATE
void ListEntitiesClimateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
buffer.encode_bool(5, this->supports_current_temperature);
buffer.encode_bool(6, this->supports_two_point_target_temperature);
@@ -1374,7 +1374,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesClimateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
size += ProtoSize::calc_bool(1, this->supports_current_temperature);
size += ProtoSize::calc_bool(1, this->supports_two_point_target_temperature);
@@ -1429,7 +1429,7 @@ uint32_t ListEntitiesClimateResponse::calculate_size() const {
return size;
}
void ClimateStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
buffer.encode_float(3, this->current_temperature);
buffer.encode_float(4, this->target_temperature);
@@ -1449,7 +1449,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t ClimateStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->mode));
size += ProtoSize::calc_float(1, this->current_temperature);
size += ProtoSize::calc_float(1, this->target_temperature);
@@ -1563,7 +1563,7 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_WATER_HEATER
void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(4, this->icon);
@@ -1584,7 +1584,7 @@ void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesWaterHeaterResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -1606,7 +1606,7 @@ uint32_t ListEntitiesWaterHeaterResponse::calculate_size() const {
return size;
}
void WaterHeaterStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_float(2, this->current_temperature);
buffer.encode_float(3, this->target_temperature);
buffer.encode_uint32(4, static_cast<uint32_t>(this->mode));
@@ -1619,7 +1619,7 @@ void WaterHeaterStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t WaterHeaterStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_float(1, this->current_temperature);
size += ProtoSize::calc_float(1, this->target_temperature);
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->mode));
@@ -1675,7 +1675,7 @@ bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value
#ifdef USE_NUMBER
void ListEntitiesNumberResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -1695,7 +1695,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesNumberResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -1714,7 +1714,7 @@ uint32_t ListEntitiesNumberResponse::calculate_size() const {
return size;
}
void NumberStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_float(2, this->state);
buffer.encode_bool(3, this->missing_state);
#ifdef USE_DEVICES
@@ -1723,7 +1723,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t NumberStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_float(1, this->state);
size += ProtoSize::calc_bool(1, this->missing_state);
#ifdef USE_DEVICES
@@ -1760,7 +1760,7 @@ bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_SELECT
void ListEntitiesSelectResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -1777,7 +1777,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesSelectResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -1795,7 +1795,7 @@ uint32_t ListEntitiesSelectResponse::calculate_size() const {
return size;
}
void SelectStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_string(2, this->state);
buffer.encode_bool(3, this->missing_state);
#ifdef USE_DEVICES
@@ -1804,7 +1804,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t SelectStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->state.size());
size += ProtoSize::calc_bool(1, this->missing_state);
#ifdef USE_DEVICES
@@ -1849,7 +1849,7 @@ bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_SIREN
void ListEntitiesSirenResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -1868,7 +1868,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesSirenResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -1888,7 +1888,7 @@ uint32_t ListEntitiesSirenResponse::calculate_size() const {
return size;
}
void SirenStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_bool(2, this->state);
#ifdef USE_DEVICES
buffer.encode_uint32(3, this->device_id);
@@ -1896,7 +1896,7 @@ void SirenStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t SirenStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_bool(1, this->state);
#ifdef USE_DEVICES
size += ProtoSize::calc_uint32(1, this->device_id);
@@ -1961,7 +1961,7 @@ bool SirenCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_LOCK
void ListEntitiesLockResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -1979,7 +1979,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesLockResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -1996,7 +1996,7 @@ uint32_t ListEntitiesLockResponse::calculate_size() const {
return size;
}
void LockStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
#ifdef USE_DEVICES
buffer.encode_uint32(3, this->device_id);
@@ -2004,7 +2004,7 @@ void LockStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t LockStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->state));
#ifdef USE_DEVICES
size += ProtoSize::calc_uint32(1, this->device_id);
@@ -2054,7 +2054,7 @@ bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_BUTTON
void ListEntitiesButtonResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -2069,7 +2069,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesButtonResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -2124,7 +2124,7 @@ uint32_t MediaPlayerSupportedFormat::calculate_size() const {
}
void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -2143,7 +2143,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesMediaPlayerResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -2163,7 +2163,7 @@ uint32_t ListEntitiesMediaPlayerResponse::calculate_size() const {
return size;
}
void MediaPlayerStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
buffer.encode_float(3, this->volume);
buffer.encode_bool(4, this->muted);
@@ -2173,7 +2173,7 @@ void MediaPlayerStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t MediaPlayerStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->state));
size += ProtoSize::calc_float(1, this->volume);
size += ProtoSize::calc_bool(1, this->muted);
@@ -2942,7 +2942,7 @@ bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengt
#ifdef USE_ALARM_CONTROL_PANEL
void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -2959,7 +2959,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer &buffer) con
uint32_t ListEntitiesAlarmControlPanelResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -2975,7 +2975,7 @@ uint32_t ListEntitiesAlarmControlPanelResponse::calculate_size() const {
return size;
}
void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
#ifdef USE_DEVICES
buffer.encode_uint32(3, this->device_id);
@@ -2983,7 +2983,7 @@ void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t AlarmControlPanelStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->state));
#ifdef USE_DEVICES
size += ProtoSize::calc_uint32(1, this->device_id);
@@ -3030,7 +3030,7 @@ bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit
#ifdef USE_TEXT
void ListEntitiesTextResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -3048,7 +3048,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesTextResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -3065,7 +3065,7 @@ uint32_t ListEntitiesTextResponse::calculate_size() const {
return size;
}
void TextStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_string(2, this->state);
buffer.encode_bool(3, this->missing_state);
#ifdef USE_DEVICES
@@ -3074,7 +3074,7 @@ void TextStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t TextStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->state.size());
size += ProtoSize::calc_bool(1, this->missing_state);
#ifdef USE_DEVICES
@@ -3119,7 +3119,7 @@ bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_DATETIME_DATE
void ListEntitiesDateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -3133,7 +3133,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesDateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -3146,7 +3146,7 @@ uint32_t ListEntitiesDateResponse::calculate_size() const {
return size;
}
void DateStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_bool(2, this->missing_state);
buffer.encode_uint32(3, this->year);
buffer.encode_uint32(4, this->month);
@@ -3157,7 +3157,7 @@ void DateStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t DateStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_bool(1, this->missing_state);
size += ProtoSize::calc_uint32(1, this->year);
size += ProtoSize::calc_uint32(1, this->month);
@@ -3202,7 +3202,7 @@ bool DateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_DATETIME_TIME
void ListEntitiesTimeResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -3216,7 +3216,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesTimeResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -3229,7 +3229,7 @@ uint32_t ListEntitiesTimeResponse::calculate_size() const {
return size;
}
void TimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_bool(2, this->missing_state);
buffer.encode_uint32(3, this->hour);
buffer.encode_uint32(4, this->minute);
@@ -3240,7 +3240,7 @@ void TimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t TimeStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_bool(1, this->missing_state);
size += ProtoSize::calc_uint32(1, this->hour);
size += ProtoSize::calc_uint32(1, this->minute);
@@ -3285,7 +3285,7 @@ bool TimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_EVENT
void ListEntitiesEventResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -3303,7 +3303,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesEventResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -3322,7 +3322,7 @@ uint32_t ListEntitiesEventResponse::calculate_size() const {
return size;
}
void EventResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_string(2, this->event_type);
#ifdef USE_DEVICES
buffer.encode_uint32(3, this->device_id);
@@ -3330,7 +3330,7 @@ void EventResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t EventResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->event_type.size());
#ifdef USE_DEVICES
size += ProtoSize::calc_uint32(1, this->device_id);
@@ -3341,7 +3341,7 @@ uint32_t EventResponse::calculate_size() const {
#ifdef USE_VALVE
void ListEntitiesValveResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -3359,7 +3359,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesValveResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -3376,7 +3376,7 @@ uint32_t ListEntitiesValveResponse::calculate_size() const {
return size;
}
void ValveStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_float(2, this->position);
buffer.encode_uint32(3, static_cast<uint32_t>(this->current_operation));
#ifdef USE_DEVICES
@@ -3385,7 +3385,7 @@ void ValveStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t ValveStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_float(1, this->position);
size += ProtoSize::calc_uint32(1, static_cast<uint32_t>(this->current_operation));
#ifdef USE_DEVICES
@@ -3428,7 +3428,7 @@ bool ValveCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_DATETIME_DATETIME
void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -3442,7 +3442,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesDateTimeResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -3455,7 +3455,7 @@ uint32_t ListEntitiesDateTimeResponse::calculate_size() const {
return size;
}
void DateTimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_bool(2, this->missing_state);
buffer.encode_fixed32(3, this->epoch_seconds);
#ifdef USE_DEVICES
@@ -3464,7 +3464,7 @@ void DateTimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t DateTimeStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_bool(1, this->missing_state);
size += ProtoSize::calc_fixed32(1, this->epoch_seconds);
#ifdef USE_DEVICES
@@ -3501,7 +3501,7 @@ bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
#ifdef USE_UPDATE
void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(5, this->icon);
@@ -3516,7 +3516,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesUpdateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -3530,7 +3530,7 @@ uint32_t ListEntitiesUpdateResponse::calculate_size() const {
return size;
}
void UpdateStateResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.write_tag_and_fixed32(13, this->key);
buffer.encode_bool(2, this->missing_state);
buffer.encode_bool(3, this->in_progress);
buffer.encode_bool(4, this->has_progress);
@@ -3546,7 +3546,7 @@ void UpdateStateResponse::encode(ProtoWriteBuffer &buffer) const {
}
uint32_t UpdateStateResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_bool(1, this->missing_state);
size += ProtoSize::calc_bool(1, this->in_progress);
size += ProtoSize::calc_bool(1, this->has_progress);
@@ -3642,7 +3642,7 @@ uint32_t ZWaveProxyRequest::calculate_size() const {
#ifdef USE_INFRARED
void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
buffer.encode_string(3, this->name);
#ifdef USE_ENTITY_ICON
buffer.encode_string(4, this->icon);
@@ -3657,7 +3657,7 @@ void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer) const {
uint32_t ListEntitiesInfraredResponse::calculate_size() const {
uint32_t size = 0;
size += ProtoSize::calc_length(1, this->object_id.size());
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
size += ProtoSize::calc_length(1, this->name.size());
#ifdef USE_ENTITY_ICON
size += ProtoSize::calc_length(1, this->icon.size());
@@ -3717,7 +3717,7 @@ void InfraredRFReceiveEvent::encode(ProtoWriteBuffer &buffer) const {
#ifdef USE_DEVICES
buffer.encode_uint32(1, this->device_id);
#endif
buffer.encode_fixed32(2, this->key);
buffer.write_tag_and_fixed32(21, this->key);
for (const auto &it : *this->timings) {
buffer.encode_sint32(3, it, true);
}
@@ -3727,7 +3727,7 @@ uint32_t InfraredRFReceiveEvent::calculate_size() const {
#ifdef USE_DEVICES
size += ProtoSize::calc_uint32(1, this->device_id);
#endif
size += ProtoSize::calc_fixed32(1, this->key);
size += 5;
if (!this->timings->empty()) {
for (const auto &it : *this->timings) {
size += ProtoSize::calc_sint32_force(1, it);
+2 -2
View File
@@ -1253,7 +1253,7 @@ class ExecuteServiceArgument final : public ProtoDecodableMessage {
FixedVector<int32_t> int_array{};
FixedVector<float> float_array{};
FixedVector<std::string> string_array{};
void decode(const uint8_t *buffer, size_t length) override;
void decode(const uint8_t *buffer, size_t length);
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
@@ -1278,7 +1278,7 @@ class ExecuteServiceRequest final : public ProtoDecodableMessage {
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
bool return_response{false};
#endif
void decode(const uint8_t *buffer, size_t length) override;
void decode(const uint8_t *buffer, size_t length);
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *dump_to(DumpBuffer &out) const override;
#endif
+2 -1
View File
@@ -1,6 +1,7 @@
// This file was automatically generated with a tool.
// See script/api_protobuf/api_protobuf.py
#include "api_pb2_service.h"
#include "api_connection.h"
#include "esphome/core/log.h"
namespace esphome::api {
@@ -20,7 +21,7 @@ void APIServerConnectionBase::log_receive_message_(const LogString *name) {
}
#endif
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
// Check authentication/connection requirements
switch (msg_type) {
case HelloRequest::MESSAGE_TYPE: // No setup required
+65 -69
View File
@@ -8,7 +8,7 @@
namespace esphome::api {
class APIServerConnectionBase : public ProtoService {
class APIServerConnectionBase {
public:
#ifdef HAS_PROTO_MESSAGE_DUMP
protected:
@@ -19,227 +19,223 @@ class APIServerConnectionBase : public ProtoService {
public:
#endif
virtual void on_hello_request(const HelloRequest &value){};
void on_hello_request(const HelloRequest &value){};
virtual void on_disconnect_request(){};
virtual void on_disconnect_response(){};
virtual void on_ping_request(){};
virtual void on_ping_response(){};
virtual void on_device_info_request(){};
void on_disconnect_request(){};
void on_disconnect_response(){};
void on_ping_request(){};
void on_ping_response(){};
void on_device_info_request(){};
virtual void on_list_entities_request(){};
void on_list_entities_request(){};
virtual void on_subscribe_states_request(){};
void on_subscribe_states_request(){};
#ifdef USE_COVER
virtual void on_cover_command_request(const CoverCommandRequest &value){};
void on_cover_command_request(const CoverCommandRequest &value){};
#endif
#ifdef USE_FAN
virtual void on_fan_command_request(const FanCommandRequest &value){};
void on_fan_command_request(const FanCommandRequest &value){};
#endif
#ifdef USE_LIGHT
virtual void on_light_command_request(const LightCommandRequest &value){};
void on_light_command_request(const LightCommandRequest &value){};
#endif
#ifdef USE_SWITCH
virtual void on_switch_command_request(const SwitchCommandRequest &value){};
void on_switch_command_request(const SwitchCommandRequest &value){};
#endif
virtual void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
void on_subscribe_logs_request(const SubscribeLogsRequest &value){};
#ifdef USE_API_NOISE
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
#endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
virtual void on_subscribe_homeassistant_services_request(){};
void on_subscribe_homeassistant_services_request(){};
#endif
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
virtual void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
void on_homeassistant_action_response(const HomeassistantActionResponse &value){};
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void on_subscribe_home_assistant_states_request(){};
void on_subscribe_home_assistant_states_request(){};
#endif
#ifdef USE_API_HOMEASSISTANT_STATES
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
#endif
virtual void on_get_time_response(const GetTimeResponse &value){};
void on_get_time_response(const GetTimeResponse &value){};
#ifdef USE_API_USER_DEFINED_ACTIONS
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
void on_execute_service_request(const ExecuteServiceRequest &value){};
#endif
#ifdef USE_CAMERA
virtual void on_camera_image_request(const CameraImageRequest &value){};
void on_camera_image_request(const CameraImageRequest &value){};
#endif
#ifdef USE_CLIMATE
virtual void on_climate_command_request(const ClimateCommandRequest &value){};
void on_climate_command_request(const ClimateCommandRequest &value){};
#endif
#ifdef USE_WATER_HEATER
virtual void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
void on_water_heater_command_request(const WaterHeaterCommandRequest &value){};
#endif
#ifdef USE_NUMBER
virtual void on_number_command_request(const NumberCommandRequest &value){};
void on_number_command_request(const NumberCommandRequest &value){};
#endif
#ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){};
void on_select_command_request(const SelectCommandRequest &value){};
#endif
#ifdef USE_SIREN
virtual void on_siren_command_request(const SirenCommandRequest &value){};
void on_siren_command_request(const SirenCommandRequest &value){};
#endif
#ifdef USE_LOCK
virtual void on_lock_command_request(const LockCommandRequest &value){};
void on_lock_command_request(const LockCommandRequest &value){};
#endif
#ifdef USE_BUTTON
virtual void on_button_command_request(const ButtonCommandRequest &value){};
void on_button_command_request(const ButtonCommandRequest &value){};
#endif
#ifdef USE_MEDIA_PLAYER
virtual void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
void on_media_player_command_request(const MediaPlayerCommandRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &value){};
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
void on_bluetooth_device_request(const BluetoothDeviceRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
void on_bluetooth_gatt_get_services_request(const BluetoothGATTGetServicesRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
void on_bluetooth_gatt_read_request(const BluetoothGATTReadRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
void on_bluetooth_gatt_write_request(const BluetoothGATTWriteRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
void on_bluetooth_gatt_read_descriptor_request(const BluetoothGATTReadDescriptorRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
void on_bluetooth_gatt_write_descriptor_request(const BluetoothGATTWriteDescriptorRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
void on_bluetooth_gatt_notify_request(const BluetoothGATTNotifyRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_subscribe_bluetooth_connections_free_request(){};
void on_subscribe_bluetooth_connections_free_request(){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_unsubscribe_bluetooth_le_advertisements_request(){};
void on_unsubscribe_bluetooth_le_advertisements_request(){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
void on_bluetooth_scanner_set_mode_request(const BluetoothScannerSetModeRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_response(const VoiceAssistantResponse &value){};
void on_voice_assistant_response(const VoiceAssistantResponse &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){};
#endif
#ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &value){};
#endif
#ifdef USE_TEXT
virtual void on_text_command_request(const TextCommandRequest &value){};
void on_text_command_request(const TextCommandRequest &value){};
#endif
#ifdef USE_DATETIME_DATE
virtual void on_date_command_request(const DateCommandRequest &value){};
void on_date_command_request(const DateCommandRequest &value){};
#endif
#ifdef USE_DATETIME_TIME
virtual void on_time_command_request(const TimeCommandRequest &value){};
void on_time_command_request(const TimeCommandRequest &value){};
#endif
#ifdef USE_VALVE
virtual void on_valve_command_request(const ValveCommandRequest &value){};
void on_valve_command_request(const ValveCommandRequest &value){};
#endif
#ifdef USE_DATETIME_DATETIME
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
void on_date_time_command_request(const DateTimeCommandRequest &value){};
#endif
#ifdef USE_UPDATE
virtual void on_update_command_request(const UpdateCommandRequest &value){};
void on_update_command_request(const UpdateCommandRequest &value){};
#endif
#ifdef USE_ZWAVE_PROXY
virtual void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){};
void on_z_wave_proxy_frame(const ZWaveProxyFrame &value){};
#endif
#ifdef USE_ZWAVE_PROXY
virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
#endif
#ifdef USE_IR_RF
virtual void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
virtual void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){};
void on_serial_proxy_configure_request(const SerialProxyConfigureRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
virtual void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){};
void on_serial_proxy_write_request(const SerialProxyWriteRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
virtual void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){};
void on_serial_proxy_set_modem_pins_request(const SerialProxySetModemPinsRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
virtual void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){};
void on_serial_proxy_get_modem_pins_request(const SerialProxyGetModemPinsRequest &value){};
#endif
#ifdef USE_SERIAL_PROXY
virtual void on_serial_proxy_request(const SerialProxyRequest &value){};
void on_serial_proxy_request(const SerialProxyRequest &value){};
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){};
void on_bluetooth_set_connection_params_request(const BluetoothSetConnectionParamsRequest &value){};
#endif
protected:
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
};
} // namespace esphome::api
+25 -38
View File
@@ -152,8 +152,7 @@ class ProtoVarInt {
#endif
};
// Forward declarations for decode_to_message and related encoding helpers
class ProtoDecodableMessage;
// Forward declarations for encoding helpers
class ProtoMessage;
class ProtoSize;
@@ -166,16 +165,9 @@ class ProtoLengthDelimited {
const uint8_t *data() const { return this->value_; }
size_t size() const { return this->length_; }
/**
* Decode the length-delimited data into an existing ProtoDecodableMessage instance.
*
* This method allows decoding without templates, enabling use in contexts
* where the message type is not known at compile time. The ProtoDecodableMessage's
* decode() method will be called with the raw data and length.
*
* @param msg The ProtoDecodableMessage instance to decode into
*/
void decode_to_message(ProtoDecodableMessage &msg) const;
/// Decode the length-delimited data into a message instance.
/// Template preserves concrete type so decode() resolves statically.
template<typename T> void decode_to_message(T &msg) const;
protected:
const uint8_t *const value_;
@@ -236,6 +228,21 @@ class ProtoWriteBuffer {
* Following https://protobuf.dev/programming-guides/encoding/#structure
*/
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); }
/// Write a precomputed tag byte + 32-bit value in one operation.
/// Tag must be a single-byte varint (< 128). No zero check.
inline void write_tag_and_fixed32(uint8_t tag, uint32_t value) ESPHOME_ALWAYS_INLINE {
this->debug_check_bounds_(5);
this->pos_[0] = tag;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
std::memcpy(this->pos_ + 1, &value, 4);
#else
this->pos_[1] = static_cast<uint8_t>(value & 0xFF);
this->pos_[2] = static_cast<uint8_t>((value >> 8) & 0xFF);
this->pos_[3] = static_cast<uint8_t>((value >> 16) & 0xFF);
this->pos_[4] = static_cast<uint8_t>((value >> 24) & 0xFF);
#endif
this->pos_ += 5;
}
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
if (len == 0 && !force)
return;
@@ -276,8 +283,7 @@ class ProtoWriteBuffer {
this->debug_check_bounds_(1);
*this->pos_++ = value ? 0x01 : 0x00;
}
// noinline: 51 call sites; inlining causes net code growth vs a single out-of-line copy
__attribute__((noinline)) void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
if (value == 0 && !force)
return;
@@ -454,7 +460,7 @@ class ProtoMessage {
// Base class for messages that support decoding
class ProtoDecodableMessage : public ProtoMessage {
public:
virtual void decode(const uint8_t *buffer, size_t length);
void decode(const uint8_t *buffer, size_t length);
/**
* Count occurrences of a repeated field in a protobuf buffer.
@@ -690,33 +696,14 @@ template<typename T> inline void ProtoWriteBuffer::encode_optional_sub_message(u
this->encode_optional_sub_message(field_id, value.calculate_size(), &value, &proto_encode_msg<T>);
}
// Implementation of decode_to_message - must be after ProtoDecodableMessage is defined
inline void ProtoLengthDelimited::decode_to_message(ProtoDecodableMessage &msg) const {
// Template decode_to_message - preserves concrete type so decode() resolves statically
template<typename T> void ProtoLengthDelimited::decode_to_message(T &msg) const {
msg.decode(this->value_, this->length_);
}
template<typename T> const char *proto_enum_to_string(T value);
class ProtoService {
public:
protected:
virtual bool is_authenticated() = 0;
virtual bool is_connection_setup() = 0;
virtual void on_fatal_error() = 0;
virtual void on_no_setup_connection() = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0;
virtual void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) = 0;
// Authentication helper methods
inline bool check_connection_setup_() {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return false;
}
return true;
}
inline bool check_authenticated_() { return this->check_connection_setup_(); }
};
// ProtoService removed — its methods were inlined into APIConnection.
// APIConnection is the concrete server-side implementation; the extra virtual layer was unnecessary.
} // namespace esphome::api
+1 -1
View File
@@ -214,4 +214,4 @@ async def to_code(config):
cg.add_define("USE_AUDIO_MP3_SUPPORT")
if data.opus_support:
cg.add_define("USE_AUDIO_OPUS_SUPPORT")
add_idf_component(name="esphome/micro-opus", ref="0.3.5")
add_idf_component(name="esphome/micro-opus", ref="0.3.6")
+3 -3
View File
@@ -103,17 +103,17 @@ size_t BLENUS::available() {
#endif
}
uart::FlushResult BLENUS::flush() {
uart::UARTFlushResult BLENUS::flush() {
constexpr uint32_t timeout_500ms = 500;
uint32_t start = millis();
while (atomic_get(&this->tx_status_) != TX_DISABLED && !ring_buf_is_empty(&global_ble_tx_ring_buf)) {
if (millis() - start > timeout_500ms) {
ESP_LOGW(TAG, "Flush timeout");
return uart::FlushResult::TIMEOUT;
return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT;
}
delay(1);
}
return uart::FlushResult::SUCCESS;
return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS;
}
void BLENUS::connected(bt_conn *conn, uint8_t err) {
+1 -1
View File
@@ -26,7 +26,7 @@ class BLENUS : public uart::UARTComponent, public Component {
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
uart::FlushResult flush() override;
uart::UARTFlushResult flush() override;
void check_logger_conflict() override {}
void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; }
#ifdef USE_LOGGER
+17 -17
View File
@@ -4,8 +4,6 @@
#include "esphome/core/hal.h"
#include <cmath>
#include <functional>
#include <vector>
namespace esphome {
namespace combination {
@@ -20,12 +18,12 @@ void CombinationComponent::log_config_(const LogString *combo_type) {
void CombinationNoParameterComponent::add_source(Sensor *sensor) { this->sensors_.emplace_back(sensor); }
void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
this->sensor_pairs_.emplace_back(sensor, stddev);
void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function<float(float)> const &compute) {
this->sensor_sources_.push_back({sensor, compute, this});
}
void CombinationOneParameterComponent::add_source(Sensor *sensor, float stddev) {
this->add_source(sensor, std::function<float(float)>{[stddev](float x) -> float { return stddev; }});
void CombinationOneParameterComponent::add_source(Sensor *sensor, float value) {
this->add_source(sensor, std::function<float(float)>{[value](float x) -> float { return value; }});
}
void CombinationNoParameterComponent::log_source_sensors() {
@@ -37,9 +35,8 @@ void CombinationNoParameterComponent::log_source_sensors() {
void CombinationOneParameterComponent::log_source_sensors() {
ESP_LOGCONFIG(TAG, " Source Sensors:");
for (const auto &sensor : this->sensor_pairs_) {
auto &entity = *sensor.first;
ESP_LOGCONFIG(TAG, " - %s", entity.get_name().c_str());
for (const auto &source : this->sensor_sources_) {
ESP_LOGCONFIG(TAG, " - %s", source.sensor->get_name().c_str());
}
}
@@ -62,9 +59,12 @@ void KalmanCombinationComponent::dump_config() {
}
void KalmanCombinationComponent::setup() {
for (const auto &sensor : this->sensor_pairs_) {
const auto stddev = sensor.second;
sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); });
for (auto &source : this->sensor_sources_) {
// [&source] is safe: source refers to a FixedVector element that never reallocates,
// so the reference remains valid for the component's lifetime.
source.sensor->add_on_state_callback([&source](float x) -> void {
static_cast<KalmanCombinationComponent *>(source.parent)->correct_(x, source.compute(x));
});
}
}
@@ -117,10 +117,10 @@ void KalmanCombinationComponent::correct_(float value, float stddev) {
}
void LinearCombinationComponent::setup() {
for (const auto &sensor : this->sensor_pairs_) {
for (auto &source : this->sensor_sources_) {
// All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result
// repeatedly in the same loop if multiple source senors update.
sensor.first->add_on_state_callback(
source.sensor->add_on_state_callback(
[this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); });
}
}
@@ -133,10 +133,10 @@ void LinearCombinationComponent::handle_new_value(float value) {
float sum = 0.0;
for (const auto &sensor : this->sensor_pairs_) {
const float sensor_state = sensor.first->state;
for (const auto &source : this->sensor_sources_) {
const float sensor_state = source.sensor->state;
if (std::isfinite(sensor_state)) {
sum += sensor_state * sensor.second(sensor_state);
sum += sensor_state * source.compute(sensor_state);
}
}
+13 -5
View File
@@ -1,9 +1,10 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/sensor/sensor.h"
#include <vector>
#include <functional>
namespace esphome {
namespace combination {
@@ -41,14 +42,21 @@ class CombinationNoParameterComponent : public CombinationComponent {
// Base class for opertions that require one parameter to compute the combination
class CombinationOneParameterComponent : public CombinationComponent {
public:
void add_source(Sensor *sensor, std::function<float(float)> const &stddev);
void add_source(Sensor *sensor, float stddev);
void set_source_count(size_t count) { this->sensor_sources_.init(count); }
void add_source(Sensor *sensor, std::function<float(float)> const &compute);
void add_source(Sensor *sensor, float value);
/// @brief Logs all source sensor's names in sensor_pairs_
/// @brief Logs all source sensors' names in sensor_sources_
void log_source_sensors() override;
protected:
std::vector<std::pair<Sensor *, std::function<float(float)>>> sensor_pairs_;
struct SensorSource {
sensor::Sensor *sensor;
std::function<float(float)> compute;
CombinationOneParameterComponent *parent;
};
FixedVector<SensorSource> sensor_sources_;
};
class KalmanCombinationComponent : public CombinationOneParameterComponent {
+3
View File
@@ -180,6 +180,9 @@ async def to_code(config):
if proces_std_dev := config.get(CONF_PROCESS_STD_DEV):
cg.add(var.set_process_std_dev(proces_std_dev))
if config[CONF_TYPE] in (CONF_KALMAN, CONF_LINEAR):
cg.add(var.set_source_count(len(config[CONF_SOURCES])))
for source_conf in config[CONF_SOURCES]:
source = await cg.get_variable(source_conf[CONF_SOURCE])
if config[CONF_TYPE] == CONF_KALMAN:
+2 -2
View File
@@ -2,6 +2,7 @@
#include "esphome/core/defines.h"
#include "crash_handler.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "preferences.h"
@@ -15,7 +16,6 @@
#include <freertos/task.h>
void setup(); // NOLINT(readability-redundant-declaration)
void loop(); // NOLINT(readability-redundant-declaration)
// Weak stub for initArduino - overridden when the Arduino component is present
extern "C" __attribute__((weak)) void initArduino() {}
@@ -65,7 +65,7 @@ TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-
void loop_task(void *pv_params) {
setup();
while (true) {
loop();
App.loop();
}
}
+5 -1
View File
@@ -28,7 +28,11 @@ def esp32_validate_gpio_pin(value: int) -> int:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
if value in _ESP_SDIO_PINS:
raise cv.Invalid(
f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
f"This pin cannot be used on ESP32s and is already used by the flash interface"
f" (function: {_ESP_SDIO_PINS[value]})."
f" If you are using an ESP32 module that uses a different flash pin"
f" configuration (e.g. ESP32-PICO-V3-02), you can set"
f" 'ignore_pin_validation_error: true' to bypass this check."
)
if 9 <= value <= 10:
_LOGGER.warning(
+36 -10
View File
@@ -9,11 +9,14 @@
#include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h>
#include "esphome/core/log.h"
#include "esp_random.h"
#include "esp_system.h"
namespace esphome {
static const char *const TAG = "esp32";
bool random_bytes(uint8_t *data, size_t len) {
esp_fill_random(data, len);
return true;
@@ -63,22 +66,43 @@ LwIPLock::~LwIPLock() {
#endif
}
/// Read MAC and validate both the return code and content.
static bool read_valid_mac(uint8_t *mac, esp_err_t err) { return err == ESP_OK && mac_address_is_valid(mac); }
static constexpr size_t MAC_ADDRESS_SIZE_BITS = MAC_ADDRESS_SIZE * 8; // 48 bits
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED)
// When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
// returns the 802.15.4 EUI-64 address, so we read directly from eFuse instead.
if (has_custom_mac_address()) {
esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48);
} else {
esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48);
// Both paths already read raw eFuse bytes, so there is no CRC-bypass fallback
// (unlike the non-IEEE802154 path where esp_efuse_mac_get_default does CRC checks).
if (has_custom_mac_address() &&
read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS))) {
return;
}
if (read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, MAC_ADDRESS_SIZE_BITS))) {
return;
}
#else
if (has_custom_mac_address()) {
esp_efuse_mac_get_custom(mac);
} else {
esp_efuse_mac_get_default(mac);
if (has_custom_mac_address() && read_valid_mac(mac, esp_efuse_mac_get_custom(mac))) {
return;
}
if (read_valid_mac(mac, esp_efuse_mac_get_default(mac))) {
return;
}
// Default MAC read failed (e.g., eFuse CRC error) - try reading raw eFuse bytes
// directly, bypassing CRC validation. A MAC that passes mac_address_is_valid()
// (non-zero, non-broadcast, unicast) is almost certainly the real factory MAC
// with a corrupted CRC byte, which is far better than returning garbage or zeros.
if (read_valid_mac(mac, esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, MAC_ADDRESS_SIZE_BITS))) {
ESP_LOGW(TAG, "eFuse MAC CRC failed but raw bytes appear valid - using raw eFuse MAC");
return;
}
#endif
// All methods failed - zero the MAC rather than returning garbage
ESP_LOGE(TAG, "Failed to read a valid MAC address from eFuse");
memset(mac, 0, MAC_ADDRESS_SIZE);
}
void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
@@ -88,9 +112,11 @@ bool has_custom_mac_address() {
uint8_t mac[6];
// do not use 'esp_efuse_mac_get_custom(mac)' because it drops an error in the logs whenever it fails
#ifndef USE_ESP32_VARIANT_ESP32
return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS) == ESP_OK) &&
mac_address_is_valid(mac);
#else
return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, MAC_ADDRESS_SIZE_BITS) == ESP_OK) &&
mac_address_is_valid(mac);
#endif
#else
return false;
+42 -28
View File
@@ -4,12 +4,40 @@ import re
# pylint: disable=E0602
Import("env") # noqa
# IRAM size for testing mode (2MB - large enough to accommodate grouped tests)
TESTING_IRAM_SIZE = 0x200000
# Memory sizes for testing mode (large enough to accommodate grouped tests)
TESTING_IRAM_SIZE = 0x200000 # 2MB
TESTING_DRAM_SIZE = 0x200000 # 2MB
def patch_segment(content, segment_name, new_size):
"""Patch a memory segment's length in linker script content.
Handles both single-line and multi-line segment definitions, e.g.:
iram0_0_seg (RX) : org = 0x40080000, len = 0x20000 + 0x0
or split across lines:
dram0_0_seg (RW) : org = 0x3FFB0000 + 0xdb5c,
len = 0x2c200 - 0xdb5c
Args:
content: Full linker script content as string
segment_name: Name of the segment (e.g., 'iram0_0_seg')
new_size: New size as integer
Returns:
Tuple of (new_content, was_patched)
"""
# Match segment name through to "len = <value>" allowing newlines between org and len
pattern = rf'({re.escape(segment_name)}\s*\([^)]*\)\s*:\s*org\s*=\s*.+?,\s*len\s*=\s*)(\S+[^\n]*)'
if match := re.search(pattern, content, re.DOTALL):
replacement = f"{match.group(1)}{new_size:#x}"
new_content = content[:match.start()] + replacement + content[match.end():]
if new_content != content:
return new_content, True
return content, False
def patch_idf_linker_script(source, target, env):
"""Patch ESP-IDF linker script to increase IRAM size for testing mode."""
"""Patch ESP-IDF linker script to increase IRAM and DRAM size for testing mode."""
# Check if we're in testing mode by looking for the define
build_flags = env.get("BUILD_FLAGS", [])
testing_mode = any("-DESPHOME_TESTING_MODE" in flag for flag in build_flags)
@@ -34,36 +62,22 @@ def patch_idf_linker_script(source, target, env):
print(f"ESPHome: Error reading linker script: {e}")
return
# Check if this file contains iram0_0_seg
if 'iram0_0_seg' not in content:
print(f"ESPHome: Warning - iram0_0_seg not found in {memory_ld}")
return
patches = []
# Look for iram0_0_seg definition and increase its length
# ESP-IDF format can be:
# iram0_0_seg (RX) : org = 0x40080000, len = 0x20000 + 0x0
# or more complex with nested parentheses:
# iram0_0_seg (RX) : org = (0x40370000 + 0x4000), len = (((0x403CB700 - (0x40378000 - 0x3FC88000)) - 0x3FC88000) + 0x8000 - 0x4000)
# We want to change len to TESTING_IRAM_SIZE for testing
content, patched = patch_segment(content, 'iram0_0_seg', TESTING_IRAM_SIZE)
if patched:
patches.append(f"IRAM={TESTING_IRAM_SIZE:#x}")
# Use a more robust approach: find the line and manually parse it
lines = content.split('\n')
for i, line in enumerate(lines):
if 'iram0_0_seg' in line and 'len' in line:
# Find the position of "len = " and replace everything after it until the end of the statement
match = re.search(r'(iram0_0_seg\s*\([^)]*\)\s*:\s*org\s*=\s*(?:\([^)]+\)|0x[0-9a-fA-F]+)\s*,\s*len\s*=\s*)(.+?)(\s*)$', line)
if match:
lines[i] = f"{match.group(1)}{TESTING_IRAM_SIZE:#x}{match.group(3)}"
break
content, patched = patch_segment(content, 'dram0_0_seg', TESTING_DRAM_SIZE)
if patched:
patches.append(f"DRAM={TESTING_DRAM_SIZE:#x}")
updated = '\n'.join(lines)
if updated != content:
if patches:
with open(memory_ld, "w") as f:
f.write(updated)
print(f"ESPHome: Patched IRAM size to {TESTING_IRAM_SIZE:#x} in {memory_ld} for testing mode")
f.write(content)
print(f"ESPHome: Patched {', '.join(patches)} in {memory_ld} for testing mode")
else:
print(f"ESPHome: Warning - could not patch iram0_0_seg in {memory_ld}")
print(f"ESPHome: Warning - could not patch memory segments in {memory_ld}")
# Hook into the build process before linking
@@ -18,12 +18,6 @@ void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
#endif
// set_use_address() is guaranteed to be called during component setup by Python code generation,
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
const char *EthernetComponent::get_use_address() const { return this->use_address_; }
void EthernetComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; }
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
void EthernetComponent::notify_ip_state_listeners_() {
auto ips = this->get_ip_addresses();
@@ -103,8 +103,8 @@ class EthernetComponent final : public Component {
network::IPAddresses get_ip_addresses();
network::IPAddress get_dns_address(uint8_t num);
const char *get_use_address() const;
void set_use_address(const char *use_address);
const char *get_use_address() const { return this->use_address_; }
void set_use_address(const char *use_address) { this->use_address_ = use_address; }
void get_eth_mac_address_raw(uint8_t *mac);
// Remove before 2026.9.0
ESPDEPRECATED("Use get_eth_mac_address_pretty_into_buffer() instead. Removed in 2026.9.0", "2026.3.0")
@@ -32,6 +32,7 @@ async def to_code(config):
cg.add(var.set_pin(pin))
if CONF_INTERLOCK in config:
cg.add_define("USE_GPIO_SWITCH_INTERLOCK")
interlock = []
for it in config[CONF_INTERLOCK]:
lock = await cg.get_variable(it)
@@ -5,7 +5,9 @@ namespace esphome {
namespace gpio {
static const char *const TAG = "switch.gpio";
#ifdef USE_GPIO_SWITCH_INTERLOCK
static constexpr uint32_t INTERLOCK_TIMEOUT_ID = 0;
#endif
float GPIOSwitch::get_setup_priority() const { return setup_priority::HARDWARE; }
void GPIOSwitch::setup() {
@@ -28,6 +30,7 @@ void GPIOSwitch::setup() {
void GPIOSwitch::dump_config() {
LOG_SWITCH("", "GPIO Switch", this);
LOG_PIN(" Pin: ", this->pin_);
#ifdef USE_GPIO_SWITCH_INTERLOCK
if (!this->interlock_.empty()) {
ESP_LOGCONFIG(TAG, " Interlocks:");
for (auto *lock : this->interlock_) {
@@ -36,8 +39,10 @@ void GPIOSwitch::dump_config() {
ESP_LOGCONFIG(TAG, " %s", lock->get_name().c_str());
}
}
#endif
}
void GPIOSwitch::write_state(bool state) {
#ifdef USE_GPIO_SWITCH_INTERLOCK
if (state != this->inverted_) {
// Turning ON, check interlocking
@@ -64,11 +69,15 @@ void GPIOSwitch::write_state(bool state) {
// re-activations
this->cancel_timeout(INTERLOCK_TIMEOUT_ID);
}
#endif
this->pin_->digital_write(state);
this->publish_state(state);
}
#ifdef USE_GPIO_SWITCH_INTERLOCK
void GPIOSwitch::set_interlock(const std::initializer_list<Switch *> &interlock) { this->interlock_ = interlock; }
#endif
} // namespace gpio
} // namespace esphome
@@ -18,15 +18,19 @@ class GPIOSwitch final : public switch_::Switch, public Component {
void setup() override;
void dump_config() override;
#ifdef USE_GPIO_SWITCH_INTERLOCK
void set_interlock(const std::initializer_list<Switch *> &interlock);
void set_interlock_wait_time(uint32_t interlock_wait_time) { interlock_wait_time_ = interlock_wait_time; }
#endif
protected:
void write_state(bool state) override;
GPIOPin *pin_;
#ifdef USE_GPIO_SWITCH_INTERLOCK
FixedVector<Switch *> interlock_;
uint32_t interlock_wait_time_{0};
#endif
};
} // namespace gpio
+21 -7
View File
@@ -81,18 +81,32 @@ def _get_data() -> LightData:
return CORE.data[DOMAIN]
def generate_gamma_table(gamma_correct: float) -> list[HexInt]:
"""Generate a 256-entry uint16 gamma lookup table.
For gamma > 0, non-zero indices are clamped to a minimum of 1 to preserve
the invariant that non-zero input always produces non-zero output. Without
this, small brightness values (e.g. 1%) get quantized to exactly 0.0,
which breaks zero_means_zero logic in FloatOutput.
"""
if gamma_correct > 0:
return [
HexInt(
max(1, min(65535, int(round((i / 255.0) ** gamma_correct * 65535))))
if i > 0
else HexInt(0)
)
for i in range(256)
]
return [HexInt(int(round(i / 255.0 * 65535))) for i in range(256)]
def _get_or_create_gamma_table(gamma_correct):
data = _get_data()
if gamma_correct in data.gamma_tables:
return data.gamma_tables[gamma_correct]
if gamma_correct > 0:
forward = [
HexInt(min(65535, int(round((i / 255.0) ** gamma_correct * 65535))))
for i in range(256)
]
else:
forward = [HexInt(int(round(i / 255.0 * 65535))) for i in range(256)]
forward = generate_gamma_table(gamma_correct)
gamma_str = f"{gamma_correct}".replace(".", "_")
fwd_id = ID(f"gamma_{gamma_str}_fwd", is_declaration=True, type=cg.uint16)
@@ -154,6 +154,16 @@ class LightColorValues {
}
/// Convert these light color values to an CWWW representation with the given parameters.
///
/// Note on gamma and constant_brightness: This method operates on the raw/internal channel
/// values stored in this object. For cold_white_ and warm_white_ specifically, these
/// may already be gamma-uncorrected when derived from a color_temperature value.
/// For constant_brightness=false, additional gamma for the output can be applied after
/// this method since gamma commutes with simple multiplication. For constant_brightness=true,
/// the caller (LightState::current_values_as_cwww) must apply gamma to the individual
/// channel values BEFORE the balancing formula, because the nonlinear max/sum ratio does
/// not commute with gamma. See LightState::current_values_as_cwww() for the correct
/// implementation.
void as_cwww(float *cold_white, float *warm_white, bool constant_brightness = false) const {
if (this->color_mode_ & ColorCapability::COLD_WARM_WHITE) {
const float cw_level = this->cold_white_;
+41 -6
View File
@@ -223,12 +223,11 @@ void LightState::current_values_as_rgbw(float *red, float *green, float *blue, f
}
void LightState::current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white,
bool constant_brightness) {
this->current_values.as_rgbww(red, green, blue, cold_white, warm_white, constant_brightness);
this->current_values.as_rgb(red, green, blue);
*red = this->gamma_correct_lut(*red);
*green = this->gamma_correct_lut(*green);
*blue = this->gamma_correct_lut(*blue);
*cold_white = this->gamma_correct_lut(*cold_white);
*warm_white = this->gamma_correct_lut(*warm_white);
this->current_values_as_cwww(cold_white, warm_white, constant_brightness);
}
void LightState::current_values_as_rgbct(float *red, float *green, float *blue, float *color_temperature,
float *white_brightness) {
@@ -241,9 +240,45 @@ void LightState::current_values_as_rgbct(float *red, float *green, float *blue,
*white_brightness = this->gamma_correct_lut(*white_brightness);
}
void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness) {
this->current_values.as_cwww(cold_white, warm_white, constant_brightness);
*cold_white = this->gamma_correct_lut(*cold_white);
*warm_white = this->gamma_correct_lut(*warm_white);
if (!constant_brightness) {
// Without constant_brightness, gamma commutes with simple multiplication:
// gamma(white_level * cw) = gamma(white_level) * gamma(cw)
// (since gamma(a*b) = (a*b)^g = a^g * b^g = gamma(a) * gamma(b))
// so applying gamma after is mathematically equivalent and simpler.
this->current_values.as_cwww(cold_white, warm_white, false);
*cold_white = this->gamma_correct_lut(*cold_white);
*warm_white = this->gamma_correct_lut(*warm_white);
return;
}
// For constant_brightness mode, gamma MUST be applied to the individual
// channel values BEFORE the balancing formula (max/sum ratio), not after.
//
// Why: The cold_white_ and warm_white_ values stored in LightColorValues
// are gamma-uncorrected (see transform_parameters_() which applies
// gamma_uncorrect to the linear CW/WW fractions derived from color
// temperature). Applying gamma_correct here recovers the original linear
// fractions, which the constant_brightness formula then uses to distribute
// power evenly. The max/sum formula ensures cold+warm PWM output sums to
// a constant, keeping total power (and perceived brightness) the same
// across all color temperatures.
//
// Applying gamma AFTER the formula would be incorrect because gamma is
// nonlinear: gamma(a/b) != gamma(a)/gamma(b), so the carefully balanced
// ratio would be distorted, causing a severe brightness dip at mid-range
// color temperatures.
const auto &v = this->current_values;
if (!(v.get_color_mode() & ColorCapability::COLD_WARM_WHITE)) {
*cold_white = *warm_white = 0;
return;
}
const float cw_level = this->gamma_correct_lut(v.get_cold_white());
const float ww_level = this->gamma_correct_lut(v.get_warm_white());
const float white_level = this->gamma_correct_lut(v.get_state() * v.get_brightness());
const float sum = cw_level > 0 || ww_level > 0 ? cw_level + ww_level : 1; // Don't divide by zero.
*cold_white = white_level * std::max(cw_level, ww_level) * cw_level / sum;
*warm_white = white_level * std::max(cw_level, ww_level) * ww_level / sum;
}
void LightState::current_values_as_ct(float *color_temperature, float *white_brightness) {
auto traits = this->get_traits();
+25 -15
View File
@@ -331,11 +331,27 @@ async def to_code(config: ConfigType) -> None:
CORE.data.setdefault(CONF_LOGGER, {})[CONF_LEVEL] = level
tx_buffer_size = config[CONF_TX_BUFFER_SIZE]
cg.add_define("ESPHOME_LOGGER_TX_BUFFER_SIZE", tx_buffer_size)
log = cg.new_Pvariable(
config[CONF_ID],
baud_rate,
)
if CORE.is_esp32:
# Determine task log buffer size and define USE_ESPHOME_TASK_LOG_BUFFER early
# so the constructor can allocate the buffer immediately, preventing a race
# where another task logs before the buffer is initialized.
task_log_buffer_size = 0
if CORE.is_esp32 or CORE.is_libretiny or CORE.is_nrf52:
task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
elif CORE.is_host:
task_log_buffer_size = 64 # Fixed 64 slots for host
if task_log_buffer_size > 0:
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
log = cg.new_Pvariable(
config[CONF_ID],
baud_rate,
task_log_buffer_size,
)
else:
log = cg.new_Pvariable(
config[CONF_ID],
baud_rate,
)
if CORE.is_esp32 or CORE.is_host:
cg.add(log.create_pthread_key())
# set_uart_selection() must be called before pre_setup() because
# pre_setup() switches on uart_ to decide which hardware to initialize
@@ -364,17 +380,10 @@ async def _late_logger_init(config: ConfigType) -> None:
log = await cg.get_variable(config[CONF_ID])
level = config[CONF_LEVEL]
baud_rate: int = config[CONF_BAUD_RATE]
if CORE.is_esp32 or CORE.is_libretiny or CORE.is_nrf52:
task_log_buffer_size = config[CONF_TASK_LOG_BUFFER_SIZE]
if CORE.using_zephyr:
task_log_buffer_size = config.get(CONF_TASK_LOG_BUFFER_SIZE, 0)
if task_log_buffer_size > 0:
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
cg.add(log.init_log_buffer(task_log_buffer_size))
if CORE.using_zephyr:
zephyr_add_prj_conf("MPSC_PBUF", True)
elif CORE.is_host:
cg.add(log.create_pthread_key())
cg.add_define("USE_ESPHOME_TASK_LOG_BUFFER")
cg.add(log.init_log_buffer(64)) # Fixed 64 slots for host
zephyr_add_prj_conf("MPSC_PBUF", True)
# Enable runtime tag levels if logs are configured or explicitly enabled
logs_config = config[CONF_LOGS]
@@ -605,6 +614,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"task_log_buffer_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
}
)
+6 -5
View File
@@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -8,8 +9,8 @@ namespace esphome::logger {
// Maximum header size: 35 bytes fixed + 32 bytes tag + 16 bytes thread name = 83 bytes (45 byte safety margin)
static constexpr uint16_t MAX_HEADER_SIZE = 128;
// ANSI color code last digit (30-38 range, store only last digit to save RAM)
static constexpr char LOG_LEVEL_COLOR_DIGIT[] = {
// ANSI color code last digit (30-38 range, store only last digit to save RAM on ESP8266)
static const char LOG_LEVEL_COLOR_DIGIT[] PROGMEM = {
'\0', // NONE
'1', // ERROR (31 = red)
'3', // WARNING (33 = yellow)
@@ -20,7 +21,7 @@ static constexpr char LOG_LEVEL_COLOR_DIGIT[] = {
'8', // VERY_VERBOSE (38 = white)
};
static constexpr char LOG_LEVEL_LETTER_CHARS[] = {
static const char LOG_LEVEL_LETTER_CHARS[] PROGMEM = {
'\0', // NONE
'E', // ERROR
'W', // WARNING
@@ -64,7 +65,7 @@ struct LogBuffer {
*p++ = 'V'; // VERY_VERBOSE = "VV"
*p++ = 'V';
} else {
*p++ = LOG_LEVEL_LETTER_CHARS[level];
*p++ = static_cast<char>(progmem_read_byte(reinterpret_cast<const uint8_t *>(&LOG_LEVEL_LETTER_CHARS[level])));
}
}
*p++ = ']';
@@ -184,7 +185,7 @@ struct LogBuffer {
*p++ = (level == 1) ? '1' : '0'; // Only ERROR is bold
*p++ = ';';
*p++ = '3';
*p++ = LOG_LEVEL_COLOR_DIGIT[level];
*p++ = static_cast<char>(progmem_read_byte(reinterpret_cast<const uint8_t *>(&LOG_LEVEL_COLOR_DIGIT[level])));
*p++ = 'm';
}
// Copy string without null terminator, updates pointer in place
+8 -12
View File
@@ -152,29 +152,25 @@ inline uint8_t Logger::level_for(const char *tag) {
return this->current_level_;
}
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
Logger::Logger(uint32_t baud_rate, size_t task_log_buffer_size) : baud_rate_(baud_rate) {
#else
Logger::Logger(uint32_t baud_rate) : baud_rate_(baud_rate) {
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
this->main_task_ = xTaskGetCurrentTaskHandle();
#elif defined(USE_ZEPHYR)
this->main_task_ = k_current_get();
#elif defined(USE_HOST)
this->main_thread_ = pthread_self();
this->main_thread_ = pthread_self();
#endif
}
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
void Logger::init_log_buffer(size_t total_buffer_size) {
// Host uses slot count instead of byte size
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed
this->log_buffer_ = new logger::TaskLogBuffer(total_buffer_size);
#if !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
// Start with loop disabled when using task buffer
// The loop will be enabled automatically when messages arrive
// Zephyr with USB CDC needs loop active to poll port readiness via cdc_loop_()
this->disable_loop_when_buffer_empty_();
this->log_buffer_ = new logger::TaskLogBuffer(task_log_buffer_size);
// Note: we don't disable loop here because the component isn't registered with App yet.
// The loop self-disables on its first iteration when it finds no messages to process.
#endif
}
#endif
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
void Logger::loop() {
+3 -2
View File
@@ -143,9 +143,10 @@ enum UARTSelection : uint8_t {
*/
class Logger final : public Component {
public:
explicit Logger(uint32_t baud_rate);
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
void init_log_buffer(size_t total_buffer_size);
explicit Logger(uint32_t baud_rate, size_t task_log_buffer_size);
#else
explicit Logger(uint32_t baud_rate);
#endif
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
void loop() override;
+7 -1
View File
@@ -226,6 +226,9 @@ async def to_code(configs):
config_0 = configs[0]
# Global configuration
if CORE.is_esp32:
# Skip compiling lvgl examples
add_idf_sdkconfig_option("CONFIG_LV_BUILD_EXAMPLES", False)
add_idf_sdkconfig_option("CONFIG_LV_BUILD_DEMOS", False)
if get_esp32_variant() == VARIANT_ESP32P4:
add_idf_sdkconfig_option("CONFIG_LV_DRAW_BUF_ALIGN", 64)
# disable use of PPA for fills until upstream bugs fixed
@@ -406,7 +409,10 @@ async def to_code(configs):
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)
write_file_if_changed(lv_conf_h_file, generate_lv_conf_h())
cg.add_build_flag("-DLV_CONF_H=1")
cg.add_build_flag(f'-DLV_CONF_PATH=\\"{LV_CONF_FILENAME}\\"')
# handle windows paths in a way that doesn't break the generated C++
lv_conf_h_path = Path(lv_conf_h_file).as_posix()
cg.add_build_flag(f'-DLV_CONF_PATH=\\"{lv_conf_h_path}\\"')
cg.add_build_flag("-DLV_KCONFIG_IGNORE")
for prop in df.get_remapped_uses():
df.LOGGER.warning(
+1 -1
View File
@@ -280,7 +280,7 @@ SWIPE_TRIGGERS = tuple(
LV_ANIM = LvConstant(
"LV_SCR_LOAD_ANIM_",
"LV_SCREEN_LOAD_ANIM_",
"NONE",
"OVER_LEFT",
"OVER_RIGHT",
+11 -5
View File
@@ -176,7 +176,11 @@ void LvglComponent::show_page(size_t index, lv_scr_load_anim_t anim, uint32_t ti
if (index >= this->pages_.size())
return;
this->current_page_ = index;
lv_scr_load_anim(this->pages_[this->current_page_]->obj, anim, time, 0, false);
if (anim == LV_SCREEN_LOAD_ANIM_NONE) {
lv_scr_load(this->pages_[this->current_page_]->obj);
} else {
lv_scr_load_anim(this->pages_[this->current_page_]->obj, anim, time, 0, false);
}
}
void LvglComponent::show_next_page(lv_scr_load_anim_t anim, uint32_t time) {
@@ -262,8 +266,8 @@ void LvglComponent::flush_cb_(lv_display_t *disp_drv, const lv_area_t *area, uin
if (!this->is_paused()) {
auto now = millis();
this->draw_buffer_(area, reinterpret_cast<lv_color_data *>(color_p));
ESP_LOGV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", area->x1, area->y1, lv_area_get_width(area),
lv_area_get_height(area), (int) (millis() - now));
ESP_LOGV(TAG, "flush_cb, area=%d/%d, %d/%d took %dms", (int) area->x1, (int) area->y1,
(int) lv_area_get_width(area), (int) lv_area_get_height(area), (int) (millis() - now));
}
lv_display_flush_ready(disp_drv);
}
@@ -619,7 +623,7 @@ void LvglComponent::setup() {
// Rotation will be handled by our drawing function, so reset the display rotation.
for (auto *disp : this->displays_)
disp->set_rotation(display::DISPLAY_ROTATION_0_DEGREES);
this->show_page(0, LV_SCR_LOAD_ANIM_NONE, 0);
this->show_page(0, LV_SCREEN_LOAD_ANIM_NONE, 0);
lv_display_trigger_activity(this->disp_);
}
@@ -667,9 +671,10 @@ void LvglComponent::static_flush_cb(lv_display_t *disp_drv, const lv_area_t *are
* @param e The event data
* @param color_start The color to apply to the first tick
* @param color_end The color to apply to the last tick
* @param width
*/
void lv_scale_draw_event_cb(lv_event_t *e, uint16_t range_start, uint16_t range_end, lv_color_t color_start,
lv_color_t color_end, bool local) {
lv_color_t color_end, int width, bool local) {
auto *scale = static_cast<lv_obj_t *>(lv_event_get_target(e));
lv_draw_task_t *task = lv_event_get_draw_task(e);
@@ -687,6 +692,7 @@ void lv_scale_draw_event_cb(lv_event_t *e, uint16_t range_start, uint16_t range_
range = 1;
auto ratio = (tick * 255) / range;
line_dsc->color = lv_color_mix(color_end, color_start, ratio);
line_dsc->width += width;
}
}
}
+2 -2
View File
@@ -53,7 +53,7 @@ extern std::string lv_event_code_name_for(lv_event_t *event);
lv_obj_t *lv_container_create(lv_obj_t *parent);
#ifdef USE_LVGL_SCALE
void lv_scale_draw_event_cb(lv_event_t *e, uint16_t range_start, uint16_t range_end, lv_color_t color_start,
lv_color_t color_end, bool local);
lv_color_t color_end, int width, bool local);
#endif
#if LV_COLOR_DEPTH == 16
static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_565;
@@ -71,7 +71,7 @@ inline void lv_style_set_text_font(lv_style_t *style, const font::Font *font) {
lv_style_set_text_font(style, font->get_lv_font());
}
#endif
#ifdef USE_IMAGE
#if defined(USE_LVGL_IMAGE) && defined(USE_IMAGE)
// Shortcut / overload, so that the source of an image can easily be updated
// from within a lambda.
inline void lv_image_set_src(lv_obj_t *obj, esphome::image::Image *image) {
+11 -10
View File
@@ -177,7 +177,7 @@ INDICATOR_ARC_SCHEMA = cv.Schema(
cv.Optional(CONF_VALUE): lv_float,
cv.Optional(CONF_START_VALUE): lv_float,
cv.Optional(CONF_END_VALUE): lv_float,
cv.Optional(CONF_OPA): opacity,
cv.Optional(CONF_OPA, default=1.0): opacity,
}
).add_extra(cv.has_at_most_one_key(CONF_VALUE, CONF_START_VALUE))
@@ -247,7 +247,7 @@ SCALE_SCHEMA = cv.Schema(
cv.Optional(CONF_RANGE_FROM, default=0.0): lv_int,
cv.Optional(CONF_RANGE_TO, default=100.0): lv_int,
cv.Optional(CONF_ANGLE_RANGE, default=270): lv_angle_degrees,
cv.Optional(CONF_ROTATION, default=0): lv_angle_degrees,
cv.Optional(CONF_ROTATION): lv_angle_degrees,
cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA),
cv.Optional(CONF_DRAW_TICKS_ON_TOP, default=True): bool,
}
@@ -329,7 +329,7 @@ class MeterType(WidgetType):
)
def get_uses(self):
return CONF_SCALE, CONF_LINE
return CONF_SCALE, CONF_LINE, CONF_IMAGE
def validate(self, value):
return cv.has_at_most_one_key(CONF_INDICATOR, CONF_PIVOT)(value)
@@ -366,16 +366,17 @@ class MeterType(WidgetType):
lv.scale_set_range(scale_var, range_from, range_to)
angle_range = await lv_angle_degrees.process(scale_conf[CONF_ANGLE_RANGE])
rotation = await lv_angle_degrees.process(scale_conf[CONF_ROTATION])
if (rotation := scale_conf.get(CONF_ROTATION)) is not None:
rotation = await lv_angle_degrees.process(rotation)
else:
rotation = 90 + (360 - angle_range) // 2
# Set angle range
lv.scale_set_angle_range(
scale_var,
angle_range,
)
# Set rotation if specified
if rotation:
lv.scale_set_rotation(scale_var, rotation)
lv.scale_set_rotation(scale_var, rotation)
# Handle indicators as sections
for indicator in scale_conf.get(CONF_INDICATORS, ()):
@@ -393,10 +394,9 @@ class MeterType(WidgetType):
props = {
"arc_width": v[CONF_WIDTH],
"arc_color": v[CONF_COLOR],
"arc_opa": v[CONF_OPA],
"arc_rounded": v.get("arc_rounded", False),
}
if (opa := v.get(CONF_OPA)) is not None:
props["arc_opa"] = opa
if CONF_R_MOD in v:
get_warnings().add(
"The 'r_mod' indicator property is not supported in LVGL 9.x and will be ignored."
@@ -424,6 +424,7 @@ class MeterType(WidgetType):
end_value,
color_start,
color_end,
v[CONF_WIDTH],
local,
)
lv_obj.add_event_cb(
@@ -16,6 +16,7 @@ from ..lv_validation import lv_bool, lv_int, lv_text
from ..schemas import TEXT_SCHEMA
from ..types import LvText
from . import Widget, WidgetType
from .label import CONF_LABEL
CONF_TEXTAREA = "textarea"
@@ -46,6 +47,9 @@ class TextareaType(WidgetType):
TEXTAREA_SCHEMA,
)
def get_uses(self):
return (CONF_LABEL,)
async def to_code(self, w: Widget, config: dict):
for prop in (CONF_TEXT, CONF_PLACEHOLDER_TEXT, CONF_ACCEPTED_CHARS):
if (value := config.get(prop)) is not None:
+14 -3
View File
@@ -28,6 +28,10 @@ namespace esphome::mqtt {
static const char *const TAG = "mqtt";
// Maximum number of MQTT component resends per loop iteration.
// Limits work to avoid triggering the task watchdog on reconnect.
static constexpr uint8_t MAX_RESENDS_PER_LOOP = 8;
// Disconnect reason strings indexed by MQTTClientDisconnectReason enum (0-8)
PROGMEM_STRING_TABLE(MQTTDisconnectReasonStrings, "TCP disconnected", "Unacceptable Protocol Version",
"Identifier Rejected", "Server Unavailable", "Malformed Credentials", "Not Authorized",
@@ -396,9 +400,16 @@ void MQTTClientComponent::loop() {
this->resubscribe_subscriptions_();
// Process pending resends for all MQTT components centrally
// This is more efficient than each component polling in its own loop
for (MQTTComponent *component : this->children_) {
component->process_resend();
// Limit work per loop iteration to avoid triggering task WDT on reconnect
{
uint8_t resend_count = 0;
for (MQTTComponent *component : this->children_) {
if (component->is_resend_pending()) {
component->process_resend();
if (++resend_count >= MAX_RESENDS_PER_LOOP)
break;
}
}
}
}
break;
+2 -2
View File
@@ -366,14 +366,14 @@ class MQTTJsonMessageTrigger : public Trigger<JsonObjectConst> {
class MQTTConnectTrigger : public Trigger<bool> {
public:
explicit MQTTConnectTrigger(MQTTClientComponent *&client) {
explicit MQTTConnectTrigger(MQTTClientComponent *client) {
client->set_on_connect([this](bool session_present) { this->trigger(session_present); });
}
};
class MQTTDisconnectTrigger : public Trigger<MQTTClientDisconnectReason> {
public:
explicit MQTTDisconnectTrigger(MQTTClientComponent *&client) {
explicit MQTTDisconnectTrigger(MQTTClientComponent *client) {
client->set_on_disconnect([this](MQTTClientDisconnectReason reason) { this->trigger(reason); });
}
};
+3
View File
@@ -147,6 +147,9 @@ class MQTTComponent : public Component {
/// Internal method for the MQTT client base to schedule a resend of the state on reconnect.
void schedule_resend_state();
/// Check if a resend is pending (called by MQTTClientComponent to rate-limit work)
bool is_resend_pending() const { return this->resend_state_; }
/// Process pending resend if needed (called by MQTTClientComponent)
void process_resend();
-24
View File
@@ -42,29 +42,5 @@ network::IPAddresses get_ip_addresses() {
return {};
}
const char *get_use_address() {
// Global component pointers are guaranteed to be set by component constructors when USE_* is defined
#ifdef USE_ETHERNET
return ethernet::global_eth_component->get_use_address();
#endif
#ifdef USE_MODEM
return modem::global_modem_component->get_use_address();
#endif
#ifdef USE_WIFI
return wifi::global_wifi_component->get_use_address();
#endif
#ifdef USE_OPENTHREAD
return openthread::global_openthread_component->get_use_address();
#endif
#if !defined(USE_ETHERNET) && !defined(USE_MODEM) && !defined(USE_WIFI) && !defined(USE_OPENTHREAD)
// Fallback when no network component is defined (e.g., host platform)
return "";
#endif
}
} // namespace esphome::network
#endif
+23 -1
View File
@@ -54,7 +54,29 @@ ESPHOME_ALWAYS_INLINE inline bool is_connected() {
/// Return whether the network is disabled (only wifi for now)
bool is_disabled();
/// Get the active network hostname
const char *get_use_address();
ESPHOME_ALWAYS_INLINE inline const char *get_use_address() {
// Global component pointers are guaranteed to be set by component constructors when USE_* is defined
#ifdef USE_ETHERNET
return ethernet::global_eth_component->get_use_address();
#endif
#ifdef USE_MODEM
return modem::global_modem_component->get_use_address();
#endif
#ifdef USE_WIFI
return wifi::global_wifi_component->get_use_address();
#endif
#ifdef USE_OPENTHREAD
return openthread::global_openthread_component->get_use_address();
#endif
#if !defined(USE_ETHERNET) && !defined(USE_MODEM) && !defined(USE_WIFI) && !defined(USE_OPENTHREAD)
// Fallback when no network component is defined (e.g., host platform)
return "";
#endif
}
IPAddresses get_ip_addresses();
} // namespace esphome::network
@@ -257,11 +257,5 @@ void OpenThreadComponent::on_factory_reset(std::function<void()> callback) {
ESP_LOGD(TAG, "Waiting on Confirmation Removal SRP Host and Services");
}
// set_use_address() is guaranteed to be called during component setup by Python code generation,
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
const char *OpenThreadComponent::get_use_address() const { return this->use_address_; }
void OpenThreadComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; }
} // namespace esphome::openthread
#endif
+2 -2
View File
@@ -37,8 +37,8 @@ class OpenThreadComponent : public Component {
void on_factory_reset(std::function<void()> callback);
void defer_factory_reset_external_callback();
const char *get_use_address() const;
void set_use_address(const char *use_address);
const char *get_use_address() const { return this->use_address_; }
void set_use_address(const char *use_address) { this->use_address_ = use_address; }
#if CONFIG_OPENTHREAD_MTD
void set_poll_period(uint32_t poll_period) { this->poll_period_ = poll_period; }
#endif
@@ -177,13 +177,19 @@ async def register_packet_transport(var, config):
cg.add(var.set_provider_encryption(name, hash_encryption_key(encryption)))
is_provider = False
for sens_conf in config.get(CONF_SENSORS, ()):
sensors = config.get(CONF_SENSORS, ())
binary_sensors = config.get(CONF_BINARY_SENSORS, ())
if sensors:
cg.add(var.set_sensor_count(len(sensors)))
if binary_sensors:
cg.add(var.set_binary_sensor_count(len(binary_sensors)))
for sens_conf in sensors:
is_provider = True
sens_id = sens_conf[CONF_ID]
sensor = await cg.get_variable(sens_id)
bcst_id = sens_conf.get(CONF_BROADCAST_ID, sens_id.id)
cg.add(var.add_sensor(bcst_id, sensor))
for sens_conf in config.get(CONF_BINARY_SENSORS, ()):
for sens_conf in binary_sensors:
is_provider = True
sens_id = sens_conf[CONF_ID]
sensor = await cg.get_variable(sens_id)
@@ -221,16 +221,20 @@ void PacketTransport::setup() {
}
#ifdef USE_SENSOR
for (auto &sensor : this->sensors_) {
sensor.sensor->add_on_state_callback([this, &sensor](float x) {
this->updated_ = true;
// [&sensor] is safe: sensor refers to a FixedVector element that never reallocates,
// so the reference remains valid for the component's lifetime.
sensor.sensor->add_on_state_callback([&sensor](float x) {
sensor.parent->updated_ = true;
sensor.updated = true;
});
}
#endif
#ifdef USE_BINARY_SENSOR
for (auto &sensor : this->binary_sensors_) {
sensor.sensor->add_on_state_callback([this, &sensor](bool value) {
this->updated_ = true;
// [&sensor] is safe: sensor refers to a FixedVector element that never reallocates,
// so the reference remains valid for the component's lifetime.
sensor.sensor->add_on_state_callback([&sensor](bool value) {
sensor.parent->updated_ = true;
sensor.updated = true;
});
}
@@ -548,11 +552,11 @@ void PacketTransport::dump_config() {
" Ping-pong: %s",
this->platform_name_, YESNO(this->is_encrypted_()), YESNO(this->ping_pong_enable_));
#ifdef USE_SENSOR
for (auto sensor : this->sensors_)
for (const auto &sensor : this->sensors_)
ESP_LOGCONFIG(TAG, " Sensor: %s", sensor.id);
#endif
#ifdef USE_BINARY_SENSOR
for (auto sensor : this->binary_sensors_)
for (const auto &sensor : this->binary_sensors_)
ESP_LOGCONFIG(TAG, " Binary Sensor: %s", sensor.id);
#endif
for (const auto &host : this->providers_) {
@@ -1,6 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
@@ -37,11 +38,14 @@ struct Provider {
#endif
};
class PacketTransport;
#ifdef USE_SENSOR
struct Sensor {
sensor::Sensor *sensor;
const char *id;
bool updated;
PacketTransport *parent;
};
#endif
#ifdef USE_BINARY_SENSOR
@@ -49,6 +53,7 @@ struct BinarySensor {
binary_sensor::BinarySensor *sensor;
const char *id;
bool updated;
PacketTransport *parent;
};
#endif
@@ -60,8 +65,9 @@ class PacketTransport : public PollingComponent {
void dump_config() override;
#ifdef USE_SENSOR
void set_sensor_count(size_t count) { this->sensors_.init(count); }
void add_sensor(const char *id, sensor::Sensor *sensor) {
Sensor st{sensor, id, true};
Sensor st{sensor, id, true, this};
this->sensors_.push_back(st);
}
void add_remote_sensor(const char *hostname, const char *remote_id, sensor::Sensor *sensor) {
@@ -70,8 +76,9 @@ class PacketTransport : public PollingComponent {
}
#endif
#ifdef USE_BINARY_SENSOR
void set_binary_sensor_count(size_t count) { this->binary_sensors_.init(count); }
void add_binary_sensor(const char *id, binary_sensor::BinarySensor *sensor) {
BinarySensor st{sensor, id, true};
BinarySensor st{sensor, id, true, this};
this->binary_sensors_.push_back(st);
}
@@ -141,11 +148,11 @@ class PacketTransport : public PollingComponent {
std::vector<uint8_t> encryption_key_{};
#ifdef USE_SENSOR
std::vector<Sensor> sensors_{};
FixedVector<Sensor> sensors_{};
string_map_t<string_map_t<sensor::Sensor *>> remote_sensors_{};
#endif
#ifdef USE_BINARY_SENSOR
std::vector<BinarySensor> binary_sensors_{};
FixedVector<BinarySensor> binary_sensors_{};
string_map_t<string_map_t<binary_sensor::BinarySensor *>> remote_binary_sensors_{};
#endif
+5 -8
View File
@@ -95,10 +95,6 @@ void PMSX003Component::loop() {
// Just go ahead and read stuff
break;
}
} else if (now - this->last_update_ < this->update_interval_) {
// Otherwise just leave the sensor powered up and come back when we hit the update
// time
return;
}
if (now - this->last_transmission_ >= 500) {
@@ -114,10 +110,11 @@ void PMSX003Component::loop() {
this->read_byte(&this->data_[this->data_index_]);
auto check = this->check_byte_();
if (!check.has_value()) {
// finished
this->parse_data_();
if (this->update_interval_ > STABILISING_MS || now - this->last_update_ >= this->update_interval_) {
this->parse_data_();
this->last_update_ = now;
}
this->data_index_ = 0;
this->last_update_ = now;
} else if (!*check) {
// wrong data
this->data_index_ = 0;
@@ -138,7 +135,7 @@ optional<bool> PMSX003Component::check_byte_() {
return true;
}
ESP_LOGW(TAG, "Start character %u mismatch: 0x%02X != 0x%02X", index + 1, byte, START_CHARACTER_1);
ESP_LOGW(TAG, "Start character %u mismatch: 0x%02X != 0x%02X", index + 1, byte, start_char);
return false;
}
@@ -165,7 +165,7 @@ uint32_t SerialProxy::get_modem_pins() const {
(this->dtr_state_ ? SERIAL_PROXY_LINE_STATE_FLAG_DTR : 0u);
}
uart::FlushResult SerialProxy::flush_port() {
uart::UARTFlushResult SerialProxy::flush_port() {
ESP_LOGV(TAG, "Flushing serial proxy [%u]", this->instance_index_);
return this->flush();
}
@@ -92,7 +92,7 @@ class SerialProxy : public uart::UARTDevice, public Component {
uint32_t get_modem_pins() const;
/// Flush the serial port (block until all TX data is sent)
uart::FlushResult flush_port();
uart::UARTFlushResult flush_port();
/// Set the RTS GPIO pin (from YAML configuration)
void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; }
+23 -18
View File
@@ -1,4 +1,5 @@
#include "sht4x.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -9,14 +10,12 @@ static const char *const TAG = "sht4x";
static const uint8_t MEASURECOMMANDS[] = {0xFD, 0xF6, 0xE0};
static const uint8_t SERIAL_NUMBER_COMMAND = 0x89;
void SHT4XComponent::start_heater_() {
uint8_t cmd[] = {this->heater_command_};
ESP_LOGD(TAG, "Heater turning on");
if (this->write(cmd, 1) != i2c::ERROR_OK) {
this->status_set_error(LOG_STR("Failed to turn on heater"));
}
}
// Conversion constants from SHT4x datasheet
static constexpr float TEMPERATURE_OFFSET = -45.0f;
static constexpr float TEMPERATURE_SPAN = 175.0f;
static constexpr float HUMIDITY_OFFSET = -6.0f;
static constexpr float HUMIDITY_SPAN = 125.0f;
static constexpr float RAW_MAX = 65535.0f;
void SHT4XComponent::read_serial_number_() {
uint16_t buffer[2];
@@ -39,8 +38,8 @@ void SHT4XComponent::setup() {
this->read_serial_number_();
if (std::isfinite(this->duty_cycle_) && this->duty_cycle_ > 0.0f) {
uint32_t heater_interval = static_cast<uint32_t>(static_cast<uint16_t>(this->heater_time_) / this->duty_cycle_);
ESP_LOGD(TAG, "Heater interval: %" PRIu32, heater_interval);
this->heater_interval_ = static_cast<uint32_t>(static_cast<uint16_t>(this->heater_time_) / this->duty_cycle_);
ESP_LOGD(TAG, "Heater interval: %" PRIu32, this->heater_interval_);
if (this->heater_power_ == SHT4X_HEATERPOWER_HIGH) {
if (this->heater_time_ == SHT4X_HEATERTIME_LONG) {
@@ -62,8 +61,6 @@ void SHT4XComponent::setup() {
}
}
ESP_LOGD(TAG, "Heater command: %x", this->heater_command_);
this->set_interval(heater_interval, [this]() { this->start_heater_(); });
}
}
@@ -106,19 +103,27 @@ void SHT4XComponent::update() {
// Evaluate and publish measurements
if (this->temp_sensor_ != nullptr) {
// Temp is contained in the first result word
float sensor_value_temp = buffer[0];
float temp = -45 + 175 * sensor_value_temp / 65535;
float temp = TEMPERATURE_OFFSET + TEMPERATURE_SPAN * static_cast<float>(buffer[0]) / RAW_MAX;
this->temp_sensor_->publish_state(temp);
}
if (this->humidity_sensor_ != nullptr) {
// Relative humidity is in the second result word
float sensor_value_rh = buffer[1];
float rh = -6 + 125 * sensor_value_rh / 65535;
float rh = HUMIDITY_OFFSET + HUMIDITY_SPAN * static_cast<float>(buffer[1]) / RAW_MAX;
this->humidity_sensor_->publish_state(rh);
}
// Fire heater after measurement to maximize cooldown time before the next reading.
// The heater command produces a measurement that we don't need (datasheet 4.9).
if (this->heater_interval_ > 0) {
uint32_t now = millis();
if (now - this->last_heater_millis_ >= this->heater_interval_) {
ESP_LOGD(TAG, "Heater turning on");
if (this->write_command(this->heater_command_)) {
this->last_heater_millis_ = now;
}
}
}
});
}
+2 -1
View File
@@ -35,9 +35,10 @@ class SHT4XComponent : public PollingComponent, public sensirion_common::Sensiri
SHT4XHEATERTIME heater_time_;
float duty_cycle_;
void start_heater_();
void read_serial_number_();
uint8_t heater_command_;
uint32_t heater_interval_{0};
uint32_t last_heater_millis_{0};
uint32_t serial_number_;
sensor::Sensor *temp_sensor_{nullptr};
+1 -1
View File
@@ -125,7 +125,7 @@ size_t format_sockaddr_to(const struct sockaddr *addr_ptr, socklen_t len, std::s
/// On ESP8266, uses esp_delay() with a callback that checks socket activity.
/// On RP2040, uses __wfe() (Wait For Event) to truly sleep until an interrupt
/// (for example, CYW43 GPIO or a timer alarm) fires and wakes the CPU.
void socket_delay(uint32_t ms);
void socket_delay(uint32_t ms); // NOLINT(readability-redundant-declaration)
/// Signal socket/IO activity and wake the main loop early.
/// On ESP8266: sets flag + esp_schedule().
@@ -31,7 +31,9 @@ void TextSensor::publish_state(const char *state, size_t len) {
if (len != this->state.size() || memcmp(state, this->state.data(), len) != 0) {
this->state.assign(state, len);
}
#ifdef USE_TEXT_SENSOR_FILTER
this->raw_callback_.call(this->state);
#endif
ESP_LOGV(TAG, "'%s': Received new state %s", this->name_.c_str(), this->state.c_str());
this->notify_frontend_();
#ifdef USE_TEXT_SENSOR_FILTER
+9 -1
View File
@@ -64,8 +64,14 @@ class TextSensor : public EntityBase {
template<typename F> void add_on_state_callback(F &&callback) { this->callback_.add(std::forward<F>(callback)); }
/// Add a callback that will be called every time the sensor sends a raw value.
/// When USE_TEXT_SENSOR_FILTER is not enabled, delegates to the regular callback
/// since raw state equals filtered state without filter support compiled in.
template<typename F> void add_on_raw_state_callback(F &&callback) {
#ifdef USE_TEXT_SENSOR_FILTER
this->raw_callback_.add(std::forward<F>(callback));
#else
this->callback_.add(std::forward<F>(callback));
#endif
}
// ========== INTERNAL METHODS ==========
@@ -77,8 +83,10 @@ class TextSensor : public EntityBase {
protected:
/// Notify frontend that state has changed (assumes this->state is already set)
void notify_frontend_();
#ifdef USE_TEXT_SENSOR_FILTER
LazyCallbackManager<void(const std::string &)> raw_callback_; ///< Storage for raw state callbacks.
LazyCallbackManager<void(const std::string &)> callback_; ///< Storage for filtered state callbacks.
#endif
LazyCallbackManager<void(const std::string &)> callback_; ///< Storage for filtered state callbacks.
#ifdef USE_TEXT_SENSOR_FILTER
Filter *filter_list_{nullptr}; ///< Store all active filters.
+11 -1
View File
@@ -284,13 +284,23 @@ def validate_tz(value: str) -> str:
tzfile = _load_tzdata(value)
if tzfile is not None:
value = _extract_tz_string(tzfile)
is_iana = True
else:
is_iana = False
# Validate that the POSIX TZ string is parseable (skip empty strings)
if value:
try:
parse_posix_tz_python(value)
except ValueError as e:
raise cv.Invalid(f"Invalid POSIX timezone string '{value}': {e}") from e
if is_iana:
raise cv.Invalid(f"Invalid POSIX timezone string '{value}': {e}") from e
raise cv.Invalid(
f"Invalid POSIX timezone string '{value}': {e}. "
f"If you meant to use an IANA timezone, check the list of valid "
f"timezones at "
f"https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"
) from e
return value
+1 -1
View File
@@ -45,7 +45,7 @@ class UARTDevice {
size_t available() { return this->parent_->available(); }
FlushResult flush() { return this->parent_->flush(); }
UARTFlushResult flush() { return this->parent_->flush(); }
// Compat APIs
int read() {
+7 -7
View File
@@ -30,11 +30,11 @@ enum UARTDirection {
const LogString *parity_to_str(UARTParityOptions parity);
/// Result of a flush() call.
enum class FlushResult {
SUCCESS, ///< Confirmed: all bytes left the TX FIFO.
TIMEOUT, ///< Confirmed: timed out before TX completed.
FAILED, ///< Confirmed: driver or hardware error.
ASSUMED_SUCCESS, ///< Platform cannot report result; success is assumed.
enum class UARTFlushResult {
UART_FLUSH_RESULT_SUCCESS, ///< Confirmed: all bytes left the TX FIFO.
UART_FLUSH_RESULT_TIMEOUT, ///< Confirmed: timed out before TX completed.
UART_FLUSH_RESULT_FAILED, ///< Confirmed: driver or hardware error.
UART_FLUSH_RESULT_ASSUMED_SUCCESS, ///< Platform cannot report result; success is assumed.
};
class UARTComponent {
@@ -82,8 +82,8 @@ class UARTComponent {
virtual size_t available() = 0;
// Pure virtual method to block until all bytes have been written to the UART bus.
// @return FlushResult indicating whether the flush was confirmed, timed out, failed, or assumed successful.
virtual FlushResult flush() = 0;
// @return UARTFlushResult indicating whether the flush was confirmed, timed out, failed, or assumed successful.
virtual UARTFlushResult flush() = 0;
// Sets the maximum time to wait for TX to drain during flush().
// Only meaningful on ESP32 (IDF). Other platforms ignore this value.
@@ -213,14 +213,14 @@ size_t ESP8266UartComponent::available() {
return this->sw_serial_->available();
}
}
FlushResult ESP8266UartComponent::flush() {
UARTFlushResult ESP8266UartComponent::flush() {
ESP_LOGVV(TAG, " Flushing");
if (this->hw_serial_ != nullptr) {
this->hw_serial_->flush();
} else {
this->sw_serial_->flush();
}
return FlushResult::ASSUMED_SUCCESS;
return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS;
}
void ESP8266SoftwareSerial::setup(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, uint32_t baud_rate,
uint8_t stop_bits, uint32_t data_bits, UARTParityOptions parity,
@@ -58,7 +58,7 @@ class ESP8266UartComponent : public UARTComponent, public Component {
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
FlushResult flush() override;
UARTFlushResult flush() override;
uint32_t get_config();
@@ -360,15 +360,15 @@ size_t IDFUARTComponent::available() {
return available;
}
FlushResult IDFUARTComponent::flush() {
UARTFlushResult IDFUARTComponent::flush() {
ESP_LOGVV(TAG, " Flushing");
TickType_t ticks = this->flush_timeout_ms_ == 0 ? portMAX_DELAY : pdMS_TO_TICKS(this->flush_timeout_ms_);
esp_err_t err = uart_wait_tx_done(this->uart_num_, ticks);
if (err == ESP_OK)
return FlushResult::SUCCESS;
return UARTFlushResult::UART_FLUSH_RESULT_SUCCESS;
if (err == ESP_ERR_TIMEOUT)
return FlushResult::TIMEOUT;
return FlushResult::FAILED;
return UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT;
return UARTFlushResult::UART_FLUSH_RESULT_FAILED;
}
void IDFUARTComponent::check_logger_conflict() {}
@@ -31,7 +31,7 @@ class IDFUARTComponent : public UARTComponent, public Component {
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
FlushResult flush() override;
UARTFlushResult flush() override;
void set_flush_timeout(uint32_t flush_timeout_ms) override { this->flush_timeout_ms_ = flush_timeout_ms; }
@@ -274,13 +274,13 @@ size_t HostUartComponent::available() {
return result;
};
FlushResult HostUartComponent::flush() {
UARTFlushResult HostUartComponent::flush() {
if (this->file_descriptor_ == -1) {
return FlushResult::ASSUMED_SUCCESS;
return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS;
}
tcflush(this->file_descriptor_, TCIOFLUSH);
ESP_LOGV(TAG, " Flushing");
return FlushResult::ASSUMED_SUCCESS;
return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS;
}
void HostUartComponent::update_error_(const std::string &error) {
@@ -18,7 +18,7 @@ class HostUartComponent : public UARTComponent, public Component {
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
FlushResult flush() override;
UARTFlushResult flush() override;
void set_name(std::string port_name) { port_name_ = port_name; };
protected:
@@ -170,10 +170,10 @@ bool LibreTinyUARTComponent::read_array(uint8_t *data, size_t len) {
}
size_t LibreTinyUARTComponent::available() { return this->serial_->available(); }
FlushResult LibreTinyUARTComponent::flush() {
UARTFlushResult LibreTinyUARTComponent::flush() {
ESP_LOGVV(TAG, " Flushing");
this->serial_->flush();
return FlushResult::ASSUMED_SUCCESS;
return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS;
}
void LibreTinyUARTComponent::check_logger_conflict() {
@@ -22,7 +22,7 @@ class LibreTinyUARTComponent : public UARTComponent, public Component {
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
FlushResult flush() override;
UARTFlushResult flush() override;
uint16_t get_config();
@@ -208,10 +208,10 @@ bool RP2040UartComponent::read_array(uint8_t *data, size_t len) {
return true;
}
size_t RP2040UartComponent::available() { return this->serial_->available(); }
FlushResult RP2040UartComponent::flush() {
UARTFlushResult RP2040UartComponent::flush() {
ESP_LOGVV(TAG, " Flushing");
this->serial_->flush();
return FlushResult::ASSUMED_SUCCESS;
return UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS;
}
} // namespace esphome::uart
@@ -25,7 +25,7 @@ class RP2040UartComponent : public UARTComponent, public Component {
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
FlushResult flush() override;
UARTFlushResult flush() override;
uint16_t get_config();
@@ -6,11 +6,17 @@ namespace esphome::ultrasonic {
static const char *const TAG = "ultrasonic.sensor";
static constexpr uint32_t DEBOUNCE_US = 50; // Ignore edges within 50us of each other (noise filtering)
static constexpr uint32_t START_DELAY_US = 100; // Ignore edges within 100us of trigger (filters bleed-through)
static constexpr uint32_t START_TIMEOUT_US = 40000; // Maximum time to wait for echo pulse to start
void IRAM_ATTR UltrasonicSensorStore::gpio_intr(UltrasonicSensorStore *arg) {
uint32_t now = micros();
if (arg->echo_pin_isr.digital_read()) {
// Ignore edges after measurement complete or too soon after trigger pulse
if (arg->echo_end || (now - arg->measurement_start_us) <= START_DELAY_US) {
return;
}
if (!arg->echo_start || (now - arg->echo_start_us) <= DEBOUNCE_US) {
arg->echo_start_us = now;
arg->echo_start = true;
} else {
@@ -21,15 +27,14 @@ void IRAM_ATTR UltrasonicSensorStore::gpio_intr(UltrasonicSensorStore *arg) {
void IRAM_ATTR UltrasonicSensorComponent::send_trigger_pulse_() {
InterruptLock lock;
this->store_.echo_start_us = 0;
this->store_.echo_end_us = 0;
this->store_.echo_start = false;
this->store_.echo_end = false;
this->store_.measurement_start_us = micros();
this->trigger_pin_isr_.digital_write(true);
delayMicroseconds(this->pulse_time_us_);
this->trigger_pin_isr_.digital_write(false);
this->measurement_pending_ = true;
this->measurement_start_us_ = micros();
this->measurement_start_us_ = this->store_.measurement_start_us;
}
void UltrasonicSensorComponent::setup() {
@@ -37,7 +42,6 @@ void UltrasonicSensorComponent::setup() {
this->trigger_pin_->digital_write(false);
this->trigger_pin_isr_ = this->trigger_pin_->to_isr();
this->echo_pin_->setup();
this->store_.echo_pin_isr = this->echo_pin_->to_isr();
this->echo_pin_->attach_interrupt(UltrasonicSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
}
@@ -77,17 +81,10 @@ void UltrasonicSensorComponent::loop() {
}
if (this->store_.echo_end) {
float result;
if (this->store_.echo_start) {
uint32_t pulse_duration = this->store_.echo_end_us - this->store_.echo_start_us;
ESP_LOGV(TAG, "pulse start took %" PRIu32 "us, echo took %" PRIu32 "us",
this->store_.echo_start_us - this->measurement_start_us_, pulse_duration);
result = UltrasonicSensorComponent::us_to_m(pulse_duration);
ESP_LOGD(TAG, "'%s' - Got distance: %.3f m", this->name_.c_str(), result);
} else {
ESP_LOGW(TAG, "'%s' - pulse end before pulse start, does the echo pin need to be inverted?", this->name_.c_str());
result = NAN;
}
uint32_t pulse_duration = this->store_.echo_end_us - this->store_.echo_start_us;
ESP_LOGV(TAG, "Echo took %" PRIu32 "us", pulse_duration);
float result = UltrasonicSensorComponent::us_to_m(pulse_duration);
ESP_LOGD(TAG, "'%s' - Got distance: %.3f m", this->name_.c_str(), result);
this->publish_state(result);
this->measurement_pending_ = false;
return;
@@ -11,8 +11,7 @@ namespace esphome::ultrasonic {
struct UltrasonicSensorStore {
static void gpio_intr(UltrasonicSensorStore *arg);
ISRInternalGPIOPin echo_pin_isr;
volatile uint32_t wait_start_us{0};
volatile uint32_t measurement_start_us{0};
volatile uint32_t echo_start_us{0};
volatile uint32_t echo_end_us{0};
volatile bool echo_start{false};
+1 -1
View File
@@ -82,7 +82,7 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMC
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
size_t available() override;
uart::FlushResult flush() override;
uart::UARTFlushResult flush() override;
protected:
void check_logger_conflict() override;
@@ -325,10 +325,10 @@ size_t USBCDCACMInstance::available() {
return waiting + (this->has_peek_ ? 1 : 0);
}
uart::FlushResult USBCDCACMInstance::flush() {
uart::UARTFlushResult USBCDCACMInstance::flush() {
// Wait for TX ring buffer to be empty
if (this->usb_tx_ringbuf_ == nullptr) {
return uart::FlushResult::ASSUMED_SUCCESS;
return uart::UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS;
}
UBaseType_t waiting = 1;
@@ -342,10 +342,10 @@ uart::FlushResult USBCDCACMInstance::flush() {
// Also wait for USB to finish transmitting
esp_err_t err = tinyusb_cdcacm_write_flush(static_cast<tinyusb_cdcacm_itf_t>(this->itf_), pdMS_TO_TICKS(100));
if (err == ESP_OK)
return uart::FlushResult::SUCCESS;
return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS;
if (err == ESP_ERR_TIMEOUT)
return uart::FlushResult::TIMEOUT;
return uart::FlushResult::FAILED;
return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT;
return uart::UARTFlushResult::UART_FLUSH_RESULT_FAILED;
}
void USBCDCACMInstance::check_logger_conflict() {}
+3 -3
View File
@@ -169,7 +169,7 @@ void USBUartChannel::write_array(const uint8_t *data, size_t len) {
this->parent_->start_output(this);
}
uart::FlushResult USBUartChannel::flush() {
uart::UARTFlushResult USBUartChannel::flush() {
// Spin until the output queue is drained and the last USB transfer completes.
// Safe to call from the main loop only.
// The flush_timeout_ms_ timeout guards against a device that stops responding mid-flush;
@@ -181,8 +181,8 @@ uart::FlushResult USBUartChannel::flush() {
yield();
}
if (!this->output_queue_.empty() || this->output_started_.load())
return uart::FlushResult::TIMEOUT;
return uart::FlushResult::SUCCESS;
return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT;
return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS;
}
bool USBUartChannel::peek_byte(uint8_t *data) {
+1 -1
View File
@@ -140,7 +140,7 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
bool peek_byte(uint8_t *data) override;
bool read_array(uint8_t *data, size_t len) override;
size_t available() override { return this->input_buffer_.get_available(); }
uart::FlushResult flush() override;
uart::UARTFlushResult flush() override;
void check_logger_conflict() override {}
void set_parity(UARTParityOptions parity) { this->parity_ = parity; }
void set_debug(bool debug) { this->debug_ = debug; }
@@ -262,19 +262,6 @@ StringRef AsyncWebServerRequest::url_to(std::span<char, URL_BUF_SIZE> buffer) co
return StringRef(buffer.data(), decoded_len);
}
void AsyncWebServerRequest::send(AsyncWebServerResponse *response) {
httpd_resp_send(*this, response->get_content_data(), response->get_content_size());
}
void AsyncWebServerRequest::send(int code, const char *content_type, const char *content) {
this->init_response_(nullptr, code, content_type);
if (content) {
httpd_resp_send(*this, content, HTTPD_RESP_USE_STRLEN);
} else {
httpd_resp_send(*this, nullptr, 0);
}
}
void AsyncWebServerRequest::redirect(const std::string &url) {
httpd_resp_set_status(*this, "302 Found");
httpd_resp_set_hdr(*this, "Location", url.c_str());
@@ -134,8 +134,17 @@ class AsyncWebServerRequest {
void redirect(const std::string &url);
void send(AsyncWebServerResponse *response);
void send(int code, const char *content_type = nullptr, const char *content = nullptr);
inline void ESPHOME_ALWAYS_INLINE send(AsyncWebServerResponse *response) {
httpd_resp_send(*this, response->get_content_data(), response->get_content_size());
}
inline void ESPHOME_ALWAYS_INLINE send(int code, const char *content_type = nullptr, const char *content = nullptr) {
this->init_response_(nullptr, code, content_type);
if (content) {
httpd_resp_send(*this, content, HTTPD_RESP_USE_STRLEN);
} else {
httpd_resp_send(*this, nullptr, 0);
}
}
// NOLINTNEXTLINE(readability-identifier-naming)
AsyncWebServerResponse *beginResponse(int code, const char *content_type) {
auto *res = new AsyncWebServerResponseEmpty(this); // NOLINT(cppcoreguidelines-owning-memory)
+3 -3
View File
@@ -433,16 +433,16 @@ void WeikaiChannel::write_array(const uint8_t *buffer, size_t length) {
this->reg(0).write_fifo(const_cast<uint8_t *>(buffer), length);
}
uart::FlushResult WeikaiChannel::flush() {
uart::UARTFlushResult WeikaiChannel::flush() {
uint32_t const start_time = millis();
while (this->tx_fifo_is_not_empty_()) { // wait until buffer empty
if (millis() - start_time > 200) {
ESP_LOGW(TAG, "WARNING flush timeout - still %d bytes not sent after 200 ms", this->tx_in_fifo_());
return uart::FlushResult::TIMEOUT;
return uart::UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT;
}
yield(); // reschedule our thread to avoid blocking
}
return uart::FlushResult::SUCCESS;
return uart::UARTFlushResult::UART_FLUSH_RESULT_SUCCESS;
}
size_t WeikaiChannel::xfer_fifo_to_buffer_() {
+1 -1
View File
@@ -380,7 +380,7 @@ class WeikaiChannel : public uart::UARTComponent {
/// @details If we refer to Serial.flush() in Arduino it says: ** Waits for the transmission of outgoing serial data
/// to complete. (Prior to Arduino 1.0, this the method was removing any buffered incoming serial data.). ** Therefore
/// we wait until all bytes are gone with a timeout of 100 ms
uart::FlushResult flush() override;
uart::UARTFlushResult flush() override;
protected:
friend class WeikaiComponent;
+7 -15
View File
@@ -269,11 +269,11 @@ bool CompactString::operator==(const StringRef &other) const {
/// │ │ │
/// │ ┌──────────────┼──────────────┐ │
/// │ ↓ ↓ ↓ │
/// │ scan error no better AP +10 dB better AP │
/// │ disconnect no better AP +10 dB better AP │
/// │ │ │ │ │
/// │ ↓ ↓ ↓ │
/// │ ┌──────────────────────────────┐ ┌──────────────────────────┐ │
/// │ │ → IDLE │ │ CONNECTING │ │
/// │ │ → RECONNECTING │ │ CONNECTING │ │
/// │ │ (counter preserved) │ │ (process_roaming_scan_) │ │
/// │ └──────────────────────────────┘ └────────────┬─────────────┘ │
/// │ │ │
@@ -296,7 +296,7 @@ bool CompactString::operator==(const StringRef &other) const {
/// │ Key behaviors: │
/// │ - After 3 checks: attempts >= 3, stop checking │
/// │ - Non-roaming disconnect: clear_roaming_state_() resets counter │
/// │ - Scan error (SCANNING→IDLE): counter preserved
/// │ - Disconnect during scan (SCANNING→RECONNECTING): counter preserved │
/// │ - Roaming success (CONNECTING→IDLE): counter reset (can roam again) │
/// │ - Roaming fail (RECONNECTING→IDLE): counter preserved (ping-pong) │
/// └──────────────────────────────────────────────────────────────────────┘
@@ -871,9 +871,6 @@ void WiFiComponent::loop() {
WiFiComponent::WiFiComponent() { global_wifi_component = this; }
bool WiFiComponent::has_ap() const { return this->has_ap_; }
bool WiFiComponent::is_ap_active() const { return this->ap_started_; }
bool WiFiComponent::has_sta() const { return !this->sta_.empty(); }
#ifdef USE_WIFI_11KV_SUPPORT
void WiFiComponent::set_btm(bool btm) { this->btm_ = btm; }
void WiFiComponent::set_rrm(bool rrm) { this->rrm_ = rrm; }
@@ -894,10 +891,6 @@ network::IPAddress WiFiComponent::get_dns_address(int num) {
return this->wifi_dns_ip_(num);
return {};
}
// set_use_address() is guaranteed to be called during component setup by Python code generation,
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.
const char *WiFiComponent::get_use_address() const { return this->use_address_; }
void WiFiComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; }
#ifdef USE_WIFI_AP
void WiFiComponent::setup_ap_config_() {
@@ -2075,9 +2068,10 @@ void WiFiComponent::retry_connect() {
ESP_LOGD(TAG, "Roam failed, reconnecting (attempt %u/%u)", this->roaming_attempts_, ROAMING_MAX_ATTEMPTS);
this->roaming_state_ = RoamingState::RECONNECTING;
} else if (this->roaming_state_ == RoamingState::SCANNING) {
// Roam scan failed (e.g., scan error on ESP8266) - go back to idle, keep counter
ESP_LOGD(TAG, "Roam scan failed (attempt %u/%u)", this->roaming_attempts_, ROAMING_MAX_ATTEMPTS);
this->roaming_state_ = RoamingState::IDLE;
// Disconnected during roam scan - transition to RECONNECTING so the attempts
// counter is preserved when reconnection succeeds (IDLE would reset it)
ESP_LOGD(TAG, "Disconnected during roam scan (attempt %u/%u)", this->roaming_attempts_, ROAMING_MAX_ATTEMPTS);
this->roaming_state_ = RoamingState::RECONNECTING;
} else if (this->roaming_state_ == RoamingState::IDLE) {
// Not a roaming-triggered reconnect, reset state
this->clear_roaming_state_();
@@ -2250,8 +2244,6 @@ bool WiFiAP::has_bssid() const { return this->bssid_ != bssid_t{}; }
#ifdef USE_WIFI_WPA2_EAP
const optional<EAPAuth> &WiFiAP::get_eap() const { return this->eap_; }
#endif
uint8_t WiFiAP::get_channel() const { return this->channel_; }
bool WiFiAP::has_channel() const { return this->channel_ != 0; }
#ifdef USE_WIFI_MANUAL_IP
const optional<ManualIP> &WiFiAP::get_manual_ip() const { return this->manual_ip_; }
#endif
+7 -7
View File
@@ -263,8 +263,8 @@ class WiFiAP {
#ifdef USE_WIFI_WPA2_EAP
const optional<EAPAuth> &get_eap() const;
#endif // USE_WIFI_WPA2_EAP
uint8_t get_channel() const;
bool has_channel() const;
uint8_t get_channel() const { return this->channel_; }
bool has_channel() const { return this->channel_ != 0; }
int8_t get_priority() const { return priority_; }
#ifdef USE_WIFI_MANUAL_IP
const optional<ManualIP> &get_manual_ip() const;
@@ -470,9 +470,9 @@ class WiFiComponent final : public Component {
/// Reconnect WiFi if required.
void loop() override;
bool has_sta() const;
bool has_ap() const;
bool is_ap_active() const;
bool has_sta() const { return !this->sta_.empty(); }
bool has_ap() const { return this->has_ap_; }
bool is_ap_active() const { return this->ap_started_; }
#ifdef USE_WIFI_11KV_SUPPORT
void set_btm(bool btm);
@@ -481,8 +481,8 @@ class WiFiComponent final : public Component {
network::IPAddress get_dns_address(int num);
network::IPAddresses get_ip_addresses();
const char *get_use_address() const;
void set_use_address(const char *use_address);
const char *get_use_address() const { return this->use_address_; }
void set_use_address(const char *use_address) { this->use_address_ = use_address; }
const wifi_scan_vector_t<WiFiScanResult> &get_scan_result() const { return scan_result_; }
+1 -1
View File
@@ -137,7 +137,7 @@ async def to_code(config):
# the '+1' modifier is relative to the device's own address that will
# be automatically added to the provided list.
cg.add_build_flag(f"-DCONFIG_WIREGUARD_MAX_SRC_IPS={len(allowed_ips) + 1}")
cg.add_library("droscy/esp_wireguard", "0.4.2")
cg.add_library("droscy/esp_wireguard", "0.4.4")
await cg.register_component(var, config)
+3 -142
View File
@@ -12,21 +12,11 @@
#endif
#ifdef USE_LWIP_FAST_SELECT
#include "esphome/core/lwip_fast_select.h"
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#else
#include <FreeRTOS.h>
#include <task.h>
#endif
#endif // USE_LWIP_FAST_SELECT
#include "esphome/core/version.h"
#include "esphome/core/hal.h"
#include <algorithm>
#include <ranges>
#ifdef USE_RUNTIME_STATS
#include "esphome/components/runtime_stats/runtime_stats.h"
#endif
#ifdef USE_STATUS_LED
#include "esphome/components/status_led/status_led.h"
@@ -163,66 +153,6 @@ void Application::setup() {
this->schedule_dump_config();
}
void Application::loop() {
uint8_t new_app_state = 0;
// Get the initial loop time at the start
uint32_t last_op_end_time = millis();
this->before_loop_tasks_(last_op_end_time);
for (this->current_loop_index_ = 0; this->current_loop_index_ < this->looping_components_active_end_;
this->current_loop_index_++) {
Component *component = this->looping_components_[this->current_loop_index_];
// Update the cached time before each component runs
this->loop_component_start_time_ = last_op_end_time;
{
this->set_current_component(component);
WarnIfComponentBlockingGuard guard{component, last_op_end_time};
component->loop();
// Use the finish method to get the current time as the end time
last_op_end_time = guard.finish();
}
new_app_state |= component->get_component_state();
this->app_state_ |= new_app_state;
this->feed_wdt(last_op_end_time);
}
this->after_loop_tasks_();
this->app_state_ = new_app_state;
#ifdef USE_RUNTIME_STATS
// Process any pending runtime stats printing after all components have run
// This ensures stats printing doesn't affect component timing measurements
if (global_runtime_stats != nullptr) {
global_runtime_stats->process_pending_stats(last_op_end_time);
}
#endif
// Use the last component's end time instead of calling millis() again
auto elapsed = last_op_end_time - this->last_loop_;
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
// Even if we overran the loop interval, we still need to select()
// to know if any sockets have data ready
this->yield_with_select_(0);
} else {
uint32_t delay_time = this->loop_interval_ - elapsed;
uint32_t next_schedule = this->scheduler.next_schedule_in(last_op_end_time).value_or(delay_time);
// next_schedule is max 0.5*delay_time
// otherwise interval=0 schedules result in constant looping with almost no sleep
next_schedule = std::max(next_schedule, delay_time / 2);
delay_time = std::min(next_schedule, delay_time);
this->yield_with_select_(delay_time);
}
this->last_loop_ = last_op_end_time;
if (this->dump_config_at_ < this->components_.size()) {
this->process_dump_config_();
}
}
void Application::process_dump_config_() {
if (this->dump_config_at_ == 0) {
@@ -509,41 +439,6 @@ void Application::enable_pending_loops_() {
}
}
void Application::before_loop_tasks_(uint32_t loop_start_time) {
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
// Drain wake notifications first to clear socket for next wake
this->drain_wake_notifications_();
#endif
// Process scheduled tasks
this->scheduler.call(loop_start_time);
// Feed the watchdog timer
this->feed_wdt(loop_start_time);
// Process any pending enable_loop requests from ISRs
// This must be done before marking in_loop_ = true to avoid race conditions
if (this->has_pending_enable_loop_requests_) {
// Clear flag BEFORE processing to avoid race condition
// If ISR sets it during processing, we'll catch it next loop iteration
// This is safe because:
// 1. Each component has its own pending_enable_loop_ flag that we check
// 2. If we can't process a component (wrong state), enable_pending_loops_()
// will set this flag back to true
// 3. Any new ISR requests during processing will set the flag again
this->has_pending_enable_loop_requests_ = false;
this->enable_pending_loops_();
}
// Mark that we're in the loop for safe reentrant modifications
this->in_loop_ = true;
}
void Application::after_loop_tasks_() {
// Clear the in_loop_ flag to indicate we're done processing components
this->in_loop_ = false;
}
#ifdef USE_LWIP_FAST_SELECT
bool Application::register_socket(struct lwip_sock *sock) {
// It modifies monitored_sockets_ without locking — must only be called from the main loop.
@@ -625,36 +520,10 @@ void Application::unregister_socket_fd(int fd) {
#endif
// Only the select() fallback path remains in the .cpp — all other paths are inlined in application.h
#if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT)
void Application::yield_with_select_(uint32_t delay_ms) {
// Delay while monitoring sockets. When delay_ms is 0, always yield() to ensure other tasks run.
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_LWIP_FAST_SELECT)
// Fast path (ESP32/LibreTiny): reads rcvevent directly from cached lwip_sock pointers.
// Safe because this runs on the main loop which owns socket lifetime (create, read, close).
if (delay_ms == 0) [[unlikely]] {
yield();
return;
}
// Check if any socket already has pending data before sleeping.
// If a socket still has unread data (rcvevent > 0) but the task notification was already
// consumed, ulTaskNotifyTake would block until timeout — adding up to delay_ms latency.
// This scan preserves select() semantics: return immediately when any fd is ready.
for (struct lwip_sock *sock : this->monitored_sockets_) {
if (esphome_lwip_socket_has_data(sock)) {
yield();
return;
}
}
// Sleep with instant wake via FreeRTOS task notification.
// Woken by: callback wrapper (socket data arrives), wake_loop_threadsafe() (other tasks), or timeout.
// Without USE_WAKE_LOOP_THREADSAFE, only hooked socket callbacks wake the task —
// background tasks won't call wake, so this degrades to a pure timeout (same as old select path).
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(delay_ms));
#elif defined(USE_SOCKET_SELECT_SUPPORT)
// Fallback select() path (host platform and any future platforms without fast select).
// ESP32 and LibreTiny are excluded by the #if above — they use the fast path.
if (!this->socket_fds_.empty()) [[likely]] {
// Update fd_set if socket list has changed
if (this->socket_fds_changed_) [[unlikely]] {
@@ -701,16 +570,8 @@ void Application::yield_with_select_(uint32_t delay_ms) {
}
// No sockets registered or select() failed - use regular delay
delay(delay_ms);
#elif (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_SOCKET_IMPL_LWIP_TCP)
// No select support but can wake on socket activity
// ESP8266: via esp_schedule()
// RP2040: via __sev()/__wfe() hardware sleep/wake
socket::socket_delay(delay_ms);
#else
// No select support, use regular delay
delay(delay_ms);
#endif
}
#endif // defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT)
// App storage — asm label shares the linker symbol with "extern Application App".
// char[] is trivially destructible, so no __cxa_atexit or destructor chain is emitted.
+150 -4
View File
@@ -27,6 +27,13 @@
#ifdef USE_SOCKET_SELECT_SUPPORT
#ifdef USE_LWIP_FAST_SELECT
#include "esphome/core/lwip_fast_select.h"
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#else
#include <FreeRTOS.h>
#include <task.h>
#endif
#else
#include <sys/select.h>
#ifdef USE_WAKE_LOOP_THREADSAFE
@@ -34,9 +41,13 @@
#endif
#endif
#endif // USE_SOCKET_SELECT_SUPPORT
#ifdef USE_RUNTIME_STATS
#include "esphome/components/runtime_stats/runtime_stats.h"
#endif
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_SOCKET_IMPL_LWIP_TCP)
namespace esphome::socket {
void socket_wake(); // NOLINT(readability-redundant-declaration)
void socket_wake(); // NOLINT(readability-redundant-declaration)
void socket_delay(uint32_t ms); // NOLINT(readability-redundant-declaration)
} // namespace esphome::socket
#endif
#ifdef USE_BINARY_SENSOR
@@ -293,7 +304,7 @@ class Application {
void setup();
/// Make a loop iteration. Call this in your loop() function.
void loop();
inline void ESPHOME_ALWAYS_INLINE loop();
/// Get the name of this Application set by pre_setup().
const StringRef &get_name() const { return this->name_; }
@@ -617,8 +628,8 @@ class Application {
void enable_component_loop_(Component *component);
void enable_pending_loops_();
void activate_looping_component_(uint16_t index);
void before_loop_tasks_(uint32_t loop_start_time);
void after_loop_tasks_();
inline void ESPHOME_ALWAYS_INLINE before_loop_tasks_(uint32_t loop_start_time);
inline void ESPHOME_ALWAYS_INLINE after_loop_tasks_() { this->in_loop_ = false; }
/// Process dump_config output one component per loop iteration.
/// Extracted from loop() to keep cold startup/reconnect logging out of the hot path.
@@ -628,7 +639,12 @@ class Application {
void feed_wdt_arch_();
/// Perform a delay while also monitoring socket file descriptors for readiness
#if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT)
// select() fallback path is too complex to inline (host platform)
void yield_with_select_(uint32_t delay_ms);
#else
inline void ESPHOME_ALWAYS_INLINE yield_with_select_(uint32_t delay_ms);
#endif
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
void setup_wake_loop_threadsafe_(); // Create wake notification socket
@@ -814,4 +830,134 @@ inline void Application::drain_wake_notifications_() {
}
#endif // defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
inline void ESPHOME_ALWAYS_INLINE Application::before_loop_tasks_(uint32_t loop_start_time) {
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) && !defined(USE_LWIP_FAST_SELECT)
// Drain wake notifications first to clear socket for next wake
this->drain_wake_notifications_();
#endif
// Process scheduled tasks
this->scheduler.call(loop_start_time);
// Feed the watchdog timer
this->feed_wdt(loop_start_time);
// Process any pending enable_loop requests from ISRs
// This must be done before marking in_loop_ = true to avoid race conditions
if (this->has_pending_enable_loop_requests_) {
// Clear flag BEFORE processing to avoid race condition
// If ISR sets it during processing, we'll catch it next loop iteration
// This is safe because:
// 1. Each component has its own pending_enable_loop_ flag that we check
// 2. If we can't process a component (wrong state), enable_pending_loops_()
// will set this flag back to true
// 3. Any new ISR requests during processing will set the flag again
this->has_pending_enable_loop_requests_ = false;
this->enable_pending_loops_();
}
// Mark that we're in the loop for safe reentrant modifications
this->in_loop_ = true;
}
inline void ESPHOME_ALWAYS_INLINE Application::loop() {
uint8_t new_app_state = 0;
// Get the initial loop time at the start
uint32_t last_op_end_time = millis();
this->before_loop_tasks_(last_op_end_time);
for (this->current_loop_index_ = 0; this->current_loop_index_ < this->looping_components_active_end_;
this->current_loop_index_++) {
Component *component = this->looping_components_[this->current_loop_index_];
// Update the cached time before each component runs
this->loop_component_start_time_ = last_op_end_time;
{
this->set_current_component(component);
WarnIfComponentBlockingGuard guard{component, last_op_end_time};
component->loop();
// Use the finish method to get the current time as the end time
last_op_end_time = guard.finish();
}
new_app_state |= component->get_component_state();
this->app_state_ |= new_app_state;
this->feed_wdt(last_op_end_time);
}
this->after_loop_tasks_();
this->app_state_ = new_app_state;
#ifdef USE_RUNTIME_STATS
// Process any pending runtime stats printing after all components have run
// This ensures stats printing doesn't affect component timing measurements
if (global_runtime_stats != nullptr) {
global_runtime_stats->process_pending_stats(last_op_end_time);
}
#endif
// Use the last component's end time instead of calling millis() again
auto elapsed = last_op_end_time - this->last_loop_;
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
// Even if we overran the loop interval, we still need to select()
// to know if any sockets have data ready
this->yield_with_select_(0);
} else {
uint32_t delay_time = this->loop_interval_ - elapsed;
uint32_t next_schedule = this->scheduler.next_schedule_in(last_op_end_time).value_or(delay_time);
// next_schedule is max 0.5*delay_time
// otherwise interval=0 schedules result in constant looping with almost no sleep
next_schedule = std::max(next_schedule, delay_time / 2);
delay_time = std::min(next_schedule, delay_time);
this->yield_with_select_(delay_time);
}
this->last_loop_ = last_op_end_time;
if (this->dump_config_at_ < this->components_.size()) {
this->process_dump_config_();
}
}
// Inline yield_with_select_ for all paths except the select() fallback
#if !defined(USE_SOCKET_SELECT_SUPPORT) || defined(USE_LWIP_FAST_SELECT)
inline void ESPHOME_ALWAYS_INLINE Application::yield_with_select_(uint32_t delay_ms) {
#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_LWIP_FAST_SELECT)
// Fast path (ESP32/LibreTiny): reads rcvevent directly from cached lwip_sock pointers.
// Safe because this runs on the main loop which owns socket lifetime (create, read, close).
if (delay_ms == 0) [[unlikely]] {
yield();
return;
}
// Check if any socket already has pending data before sleeping.
// If a socket still has unread data (rcvevent > 0) but the task notification was already
// consumed, ulTaskNotifyTake would block until timeout — adding up to delay_ms latency.
// This scan preserves select() semantics: return immediately when any fd is ready.
for (struct lwip_sock *sock : this->monitored_sockets_) {
if (esphome_lwip_socket_has_data(sock)) {
yield();
return;
}
}
// Sleep with instant wake via FreeRTOS task notification.
// Woken by: callback wrapper (socket data arrives), wake_loop_threadsafe() (other tasks), or timeout.
// Without USE_WAKE_LOOP_THREADSAFE, only hooked socket callbacks wake the task —
// background tasks won't call wake, so this degrades to a pure timeout (same as old select path).
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(delay_ms));
#elif (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_SOCKET_IMPL_LWIP_TCP)
// No select support but can wake on socket activity
// ESP8266: via esp_schedule()
// RP2040: via __sev()/__wfe() hardware sleep/wake
socket::socket_delay(delay_ms);
#else
// No select support, use regular delay
delay(delay_ms);
#endif
}
#endif // !defined(USE_SOCKET_SELECT_SUPPORT) || defined(USE_LWIP_FAST_SELECT)
} // namespace esphome
+9 -3
View File
@@ -322,7 +322,9 @@ template<typename... Ts> class Automation;
template<typename... Ts> class Trigger {
public:
/// Inform the parent automation that the event has triggered.
void trigger(const Ts &...x) {
// Force-inline: collapses the Trigger→Automation→ActionList forwarding
// chain into a single frame, reducing automation call stack depth.
inline void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE {
if (this->automation_parent_ == nullptr)
return;
this->automation_parent_->trigger(x...);
@@ -429,7 +431,9 @@ template<typename... Ts> class ActionList {
this->add_action(action);
}
}
void play(const Ts &...x) {
// Force-inline: part of the Trigger→Automation→ActionList forwarding
// chain collapsed to reduce automation call stack depth.
inline void play(const Ts &...x) ESPHOME_ALWAYS_INLINE {
if (this->actions_begin_ != nullptr)
this->actions_begin_->play_complex(x...);
}
@@ -473,7 +477,9 @@ template<typename... Ts> class Automation {
void stop() { this->actions_.stop(); }
void trigger(const Ts &...x) { this->actions_.play(x...); }
// Force-inline: part of the Trigger→Automation→ActionList forwarding
// chain collapsed to reduce automation call stack depth.
inline void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE { this->actions_.play(x...); }
bool is_running() { return this->actions_.is_running(); }
-3
View File
@@ -272,9 +272,6 @@ void Component::call() {
break;
}
}
const LogString *Component::get_component_log_str() const {
return this->component_source_ == nullptr ? LOG_STR("<unknown>") : this->component_source_;
}
bool Component::should_warn_of_blocking(uint32_t blocking_time) {
if (blocking_time > this->warn_if_blocking_over_) {
// Prevent overflow when adding increment - if we're about to overflow, just max out
+3 -1
View File
@@ -294,7 +294,9 @@ class Component {
*
* Returns LOG_STR("<unknown>") if source not set
*/
const LogString *get_component_log_str() const;
const LogString *get_component_log_str() const {
return this->component_source_ == nullptr ? LOG_STR("<unknown>") : this->component_source_;
}
bool should_warn_of_blocking(uint32_t blocking_time);
+1
View File
@@ -53,6 +53,7 @@
#define USE_ESP32_IMPROV_STATE_CALLBACK
#define USE_EVENT
#define USE_FAN
#define USE_GPIO_SWITCH_INTERLOCK
#define USE_GRAPH
#define USE_GRAPHICAL_DISPLAY_MENU
#define USE_HOMEASSISTANT_TIME
+10 -1
View File
@@ -863,7 +863,16 @@ bool mac_address_is_valid(const uint8_t *mac) {
is_all_ones = false;
}
}
return !(is_all_zeros || is_all_ones);
if (is_all_zeros || is_all_ones) {
return false;
}
// Reject multicast MACs (bit 0 of first byte set) - device MACs must be unicast.
// This catches garbage data from corrupted eFuse custom MAC areas, which often
// has random values that would otherwise pass the all-zeros/all-ones check.
if (mac[0] & 0x01) {
return false;
}
return true;
}
void IRAM_ATTR HOT delay_microseconds_safe(uint32_t us) {
+4 -1
View File
@@ -1762,7 +1762,10 @@ template<typename... Ts> struct Callback<void(Ts...)> {
// Safe under C++20 (P0593R6): byte copy into aligned storage implicitly
// creates objects of implicit-lifetime types (trivially copyable qualifies).
Callback cb; // fn and ctx are zero-initialized by default
__builtin_memcpy(&cb.ctx_, &callable, sizeof(DecayF));
// Decay callable to a local variable first. When F is a function reference
// (e.g. void(&)(int)), &callable would point at machine code, not a pointer variable.
DecayF decayed = std::forward<F>(callable);
__builtin_memcpy(&cb.ctx_, &decayed, sizeof(DecayF));
cb.fn_ = [](void *c, Ts... args) {
alignas(DecayF) char buf[sizeof(DecayF)];
__builtin_memcpy(buf, &c, sizeof(DecayF));
+7 -1
View File
@@ -166,7 +166,13 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
item->component = component;
item->set_name(name_type, static_name, hash_or_id);
item->type = type;
item->callback = std::move(func);
// Use destroy + placement-new instead of move-assignment.
// GCC's std::function::operator=(function&&) does a full swap dance even when the
// target is empty. Since recycled/new items always have an empty callback, we can
// destroy the empty one (no-op) and move-construct directly, saving ~40 bytes of
// swap/destructor code on Xtensa.
item->callback.~function();
new (&item->callback) std::function<void()>(std::move(func));
// Reset remove flag - recycled items may have been cancelled (remove=true) in previous use
this->set_item_removed_(item, false);
item->is_retry = is_retry;
+65 -8
View File
@@ -565,6 +565,29 @@ def new_variable(
return obj
def _extract_component_ns(type_str: str) -> str:
"""Extract the component namespace from a fully-qualified C++ type string.
Strips leading ``esphome::`` and template arguments, then returns
the first namespace segment. Falls back to ``"esphome"`` when the
type has no namespace qualifier (after stripping templates).
Examples::
esphome::dsmr::Dsmr -> dsmr
esphome::logger::Logger -> logger
esphome::Automation<std::optional<bool>, std::optional<bool>> -> esphome
Logger -> esphome
"""
bare = type_str.removeprefix("esphome::")
# Strip template arguments before namespace extraction to avoid
# matching :: inside template params (e.g. Automation<std::optional<bool>>)
bare_no_template = bare.split("<", maxsplit=1)[0]
if "::" in bare_no_template:
return bare_no_template.split("::", maxsplit=1)[0].rstrip("_")
return "esphome"
def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
"""Declare a new pointer variable in the code generation.
@@ -579,10 +602,43 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
obj = MockObj(id_, "->")
if type_ is not None:
id_.type = type_
decl = VariableDeclarationExpression(id_.type, "*", id_, static=True)
CORE.add_global(decl)
assignment = AssignmentExpression(None, None, id_, rhs)
CORE.add(assignment)
if isinstance(rhs, MockObj) and rhs.is_new_expr:
# For 'new' allocations, use placement new into static storage
# to avoid heap fragmentation on embedded devices.
the_type = id_.type
# Extract component namespace from type for memory analysis attribution
component_ns = _extract_component_ns(str(the_type))
storage_name = f"{component_ns}__{id_.id}__pstorage"
# Declare aligned byte array for the object storage
CORE.add_global(
RawStatement(
f"alignas({the_type}) static unsigned char {storage_name}[sizeof({the_type})];"
)
)
CORE.add_global(
AssignmentExpression(
f"static {the_type}",
"*const ",
id_,
MockObj(f"reinterpret_cast<{the_type} *>({storage_name})"),
)
)
# Extract args from the CallExpression and rebuild as placement new.
# Template args are already encoded in the_type (e.g. GlobalsComponent<int>),
# so we only pass the constructor args, not template_args.
call_expr = rhs.base
assert isinstance(call_expr, CallExpression), (
f"Expected CallExpression for placement new, got {type(call_expr)}"
)
placement_new = CallExpression(f"new({id_.id}) {the_type}", *call_expr.args)
CORE.add(ExpressionStatement(placement_new))
else:
decl = VariableDeclarationExpression(id_.type, "*", id_, static=True)
CORE.add_global(decl)
CORE.add(AssignmentExpression(None, None, id_, rhs))
CORE.register_variable(id_, obj)
return obj
@@ -799,11 +855,12 @@ class MockObj(Expression):
Mostly consists of magic methods that allow ESPHome's codegen syntax.
"""
__slots__ = ("base", "op")
__slots__ = ("base", "op", "is_new_expr")
def __init__(self, base, op="."):
def __init__(self, base, op=".", is_new_expr=False) -> None:
self.base = base
self.op = op
self.is_new_expr = is_new_expr
def __getattr__(self, attr: str) -> "MockObj":
# prevent python dunder methods being replaced by mock objects
@@ -818,7 +875,7 @@ class MockObj(Expression):
def __call__(self, *args: SafeExpType) -> "MockObj":
call = CallExpression(self.base, *args)
return MockObj(call, self.op)
return MockObj(call, self.op, is_new_expr=self.is_new_expr)
def __str__(self):
return str(self.base)
@@ -832,7 +889,7 @@ class MockObj(Expression):
@property
def new(self) -> "MockObj":
return MockObj(f"new {self.base}", "->")
return MockObj(f"new {self.base}", "->", is_new_expr=True)
def template(self, *args: SafeExpType) -> "MockObj":
"""Apply template parameters to this object."""
+1 -1
View File
@@ -4,7 +4,7 @@ dependencies:
esphome/esp-audio-libs:
version: 2.0.3
esphome/micro-opus:
version: 0.3.5
version: 0.3.6
espressif/esp-dsp:
version: "1.7.1"
espressif/esp-tflite-micro:
+4 -4
View File
@@ -118,7 +118,7 @@ lib_deps =
ESP8266HTTPClient ; http_request (Arduino built-in)
ESP8266mDNS ; mdns (Arduino built-in)
DNSServer ; captive_portal (Arduino built-in)
droscy/esp_wireguard@0.4.2 ; wireguard
droscy/esp_wireguard@0.4.4 ; wireguard
lvgl/lvgl@9.5.0 ; lvgl
build_flags =
@@ -154,7 +154,7 @@ lib_deps =
DNSServer ; captive_portal (Arduino built-in)
makuna/NeoPixelBus@2.8.0 ; neopixelbus
esphome/ESP32-audioI2S@2.3.0 ; i2s_audio
droscy/esp_wireguard@0.4.2 ; wireguard
droscy/esp_wireguard@0.4.4 ; wireguard
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
build_flags =
@@ -176,7 +176,7 @@ platform_packages =
framework = espidf
lib_deps =
${common:idf.lib_deps}
droscy/esp_wireguard@0.4.2 ; wireguard
droscy/esp_wireguard@0.4.4 ; wireguard
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
tonia/HeatpumpIR@1.0.40 ; heatpumpir
build_flags =
@@ -221,7 +221,7 @@ lib_compat_mode = soft
lib_deps =
bblanchon/ArduinoJson@7.4.2 ; json
ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base
droscy/esp_wireguard@0.4.2 ; wireguard
droscy/esp_wireguard@0.4.4 ; wireguard
lvgl/lvgl@9.5.0 ; lvgl
build_flags =
${common:arduino.build_flags}
+1
View File
@@ -83,6 +83,7 @@ ISOLATED_COMPONENTS = {
"openthread": "Conflicts with wifi: used by most components",
"openthread_info": "Conflicts with wifi: used by most components",
"matrix_keypad": "Needs isolation due to keypad",
"microphone": "Defines PDM microphone requiring I2S port 0 - conflicts with micro_wake_word PDM mic when merged",
"modbus_controller": "Defines multiple modbus buses for testing client/server functionality - conflicts with package modbus bus",
"neopixelbus": "RMT type conflict with ESP32 Arduino/ESP-IDF headers (enum vs struct rmt_channel_t)",
"packages": "cannot merge packages",
+23 -9
View File
@@ -254,14 +254,17 @@ class TypeInfo(ABC):
def dump(self, name: str) -> str:
"""Dump the value to the output."""
def calculate_tag(self) -> int:
"""Calculate the protobuf tag (field_id << 3 | wire_type)."""
return (self.number << 3) | (self.wire_type & 0b111)
def calculate_field_id_size(self) -> int:
"""Calculates the size of a field ID in bytes.
Returns:
The number of bytes needed to encode the field ID
"""
# Calculate the tag by combining field_id and wire_type
tag = (self.number << 3) | (self.wire_type & 0b111)
tag = self.calculate_tag()
# Calculate the varint size
if tag < 128:
@@ -556,6 +559,16 @@ class Fixed32Type(TypeInfo):
o += "out.append(buffer);"
return o
@property
def encode_content(self) -> str:
tag = self.calculate_tag()
if self.force and tag < 128:
# Emit combined tag+value write: precomputed tag + direct memcpy
return f"buffer.write_tag_and_fixed32({tag}, this->{self.field_name});"
if self.force:
return f"buffer.{self.encode_func}({self.number}, this->{self.field_name}, true);"
return f"buffer.{self.encode_func}({self.number}, this->{self.field_name});"
def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size()
if force:
@@ -2262,7 +2275,7 @@ def build_message_type(
o += "}\n"
cpp += o
# Generate the decode() declaration in header (public method)
prot = "void decode(const uint8_t *buffer, size_t length) override;"
prot = "void decode(const uint8_t *buffer, size_t length);"
public_content.append(prot)
# Only generate encode method if this message needs encoding and has fields
@@ -2595,7 +2608,7 @@ def build_service_message_type(
is_empty = not has_fields
if is_empty:
EMPTY_MESSAGES.add(mt.name)
hout += f"virtual void {func}({'' if is_empty else f'const {mt.name} &value'}){{}};\n"
hout += f"void {func}({'' if is_empty else f'const {mt.name} &value'}){{}};\n"
case = ""
if not is_empty:
case += f"{mt.name} msg;\n"
@@ -2947,6 +2960,7 @@ namespace esphome::api {
cpp = FILE_HEADER
cpp += """\
#include "api_pb2_service.h"
#include "api_connection.h"
#include "esphome/core/log.h"
namespace esphome::api {
@@ -2957,7 +2971,7 @@ static const char *const TAG = "api.service";
class_name = "APIServerConnectionBase"
hpp += f"class {class_name} : public ProtoService {{\n"
hpp += f"class {class_name} {{\n"
hpp += " public:\n"
# Add logging helper method declarations
@@ -3050,11 +3064,11 @@ static const char *const TAG = "api.service";
result += "#endif\n"
return result
# Generate read_message with auth check before dispatch
hpp += " protected:\n"
hpp += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n"
# Generate read_message_ as APIConnection method (not base class) so the compiler
# can devirtualize and inline the on_* handler calls within the same class.
# APIConnection declares this method in api_connection.h.
out = f"void {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n"
out = "void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {\n"
# Auth check block before dispatch switch
out += " // Check authentication/connection requirements\n"
+16
View File
@@ -890,6 +890,22 @@ def lint_no_powf_in_core(fname, match):
)
@lint_re_check(
r"[^\w]std\s*::\s*bind\s*\(" + CPP_RE_EOL,
include=cpp_include,
)
def lint_no_std_bind(fname, match):
return (
f"{highlight('std::bind()')} is not allowed in new ESPHome code. "
f"Lambdas are clearer, produce smaller binaries, and are more likely to fit within "
f"the {highlight('std::function')} small-buffer optimization (avoiding heap allocation).\n"
f"Please use a lambda instead.\n"
f" Before: {highlight('std::bind(&Class::method, this, std::placeholders::_1)')}\n"
f" After: {highlight('[this](auto arg) { this->method(arg); }')}\n"
f"(If strictly necessary, add `// NOLINT` to the end of the line)"
)
LOG_MULTILINE_RE = re.compile(r"ESP_LOG\w+\s*\(.*?;", re.DOTALL)
LOG_BAD_CONTINUATION_RE = re.compile(r'\\n(?:[^ \\"\r\n\t]|"\s*\n\s*"[^ \\])')
LOG_PERCENT_S_CONTINUATION_RE = re.compile(r'\\n(?:%s|"\s*\n\s*"%s)')
@@ -0,0 +1,5 @@
from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
manifest.enable_codegen()
@@ -0,0 +1,61 @@
#include <benchmark/benchmark.h>
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome::binary_sensor::benchmarks {
static constexpr int kInnerIterations = 2000;
// Benchmark: publish_state with alternating values (forces state change every time)
static void BinarySensorPublish_Alternating(benchmark::State &state) {
BinarySensor sensor;
// First publish to establish initial state
sensor.publish_initial_state(false);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
sensor.publish_state(i % 2 == 0);
}
benchmark::DoNotOptimize(sensor.state);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(BinarySensorPublish_Alternating);
// Benchmark: publish_state with same value (tests dedup fast path)
static void BinarySensorPublish_NoChange(benchmark::State &state) {
BinarySensor sensor;
sensor.publish_initial_state(true);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
sensor.publish_state(true);
}
benchmark::DoNotOptimize(sensor.state);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(BinarySensorPublish_NoChange);
// Benchmark: publish_state with a callback registered
static void BinarySensorPublish_WithCallback(benchmark::State &state) {
BinarySensor sensor;
int callback_count = 0;
sensor.add_on_state_callback([&callback_count](bool) { callback_count++; });
sensor.publish_initial_state(false);
for (auto _ : state) {
for (int i = 0; i < kInnerIterations; i++) {
sensor.publish_state(i % 2 == 0);
}
benchmark::DoNotOptimize(callback_count);
}
state.SetItemsProcessed(state.iterations() * kInnerIterations);
}
BENCHMARK(BinarySensorPublish_WithCallback);
} // namespace esphome::binary_sensor::benchmarks

Some files were not shown because too many files have changed in this diff Show More