1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-06 13:22:19 +01:00

Merge remote-tracking branch 'upstream/dev' into ruff_ret

This commit is contained in:
J. Nick Koston
2025-07-29 22:48:15 -10:00
83 changed files with 2942 additions and 1320 deletions

View File

@@ -1 +1 @@
32b0db73b3ae01ba18c9cbb1dabbd8156bc14dded500471919bd0a3dc33916e0 f84518ea4140c194b21cc516aae05aaa0cf876ab866f89e22e91842df46333ed

View File

@@ -2,6 +2,7 @@
import argparse import argparse
from datetime import datetime from datetime import datetime
import functools import functools
import getpass
import importlib import importlib
import logging import logging
import os import os
@@ -335,7 +336,7 @@ def check_permissions(port):
raise EsphomeError( raise EsphomeError(
"You do not have read or write permission on the selected serial port. " "You do not have read or write permission on the selected serial port. "
"To resolve this issue, you can add your user to the dialout group " "To resolve this issue, you can add your user to the dialout group "
f"by running the following command: sudo usermod -a -G dialout {os.getlogin()}. " f"by running the following command: sudo usermod -a -G dialout {getpass.getuser()}. "
"You will need to log out & back in or reboot to activate the new group access." "You will need to log out & back in or reboot to activate the new group access."
) )

View File

@@ -1,6 +1,6 @@
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import VARIANT_ESP32P4, get_esp32_variant
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
VARIANT_ESP32, VARIANT_ESP32,
VARIANT_ESP32C2, VARIANT_ESP32C2,
@@ -140,6 +140,16 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
9: adc_channel_t.ADC_CHANNEL_8, 9: adc_channel_t.ADC_CHANNEL_8,
10: adc_channel_t.ADC_CHANNEL_9, 10: adc_channel_t.ADC_CHANNEL_9,
}, },
VARIANT_ESP32P4: {
16: adc_channel_t.ADC_CHANNEL_0,
17: adc_channel_t.ADC_CHANNEL_1,
18: adc_channel_t.ADC_CHANNEL_2,
19: adc_channel_t.ADC_CHANNEL_3,
20: adc_channel_t.ADC_CHANNEL_4,
21: adc_channel_t.ADC_CHANNEL_5,
22: adc_channel_t.ADC_CHANNEL_6,
23: adc_channel_t.ADC_CHANNEL_7,
},
} }
# pin to adc2 channel mapping # pin to adc2 channel mapping
@@ -198,6 +208,14 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
19: adc_channel_t.ADC_CHANNEL_8, 19: adc_channel_t.ADC_CHANNEL_8,
20: adc_channel_t.ADC_CHANNEL_9, 20: adc_channel_t.ADC_CHANNEL_9,
}, },
VARIANT_ESP32P4: {
49: adc_channel_t.ADC_CHANNEL_0,
50: adc_channel_t.ADC_CHANNEL_1,
51: adc_channel_t.ADC_CHANNEL_2,
52: adc_channel_t.ADC_CHANNEL_3,
53: adc_channel_t.ADC_CHANNEL_4,
54: adc_channel_t.ADC_CHANNEL_5,
},
} }

View File

@@ -136,8 +136,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
adc_oneshot_unit_handle_t adc_handle_{nullptr}; adc_oneshot_unit_handle_t adc_handle_{nullptr};
adc_cali_handle_t calibration_handle_{nullptr}; adc_cali_handle_t calibration_handle_{nullptr};
adc_atten_t attenuation_{ADC_ATTEN_DB_0}; adc_atten_t attenuation_{ADC_ATTEN_DB_0};
adc_channel_t channel_; adc_channel_t channel_{};
adc_unit_t adc_unit_; adc_unit_t adc_unit_{};
struct SetupFlags { struct SetupFlags {
uint8_t init_complete : 1; uint8_t init_complete : 1;
uint8_t config_complete : 1; uint8_t config_complete : 1;

View File

@@ -72,10 +72,9 @@ void ADCSensor::setup() {
// Initialize ADC calibration // Initialize ADC calibration
if (this->calibration_handle_ == nullptr) { if (this->calibration_handle_ == nullptr) {
adc_cali_handle_t handle = nullptr; adc_cali_handle_t handle = nullptr;
esp_err_t err;
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
// RISC-V variants and S3 use curve fitting calibration // RISC-V variants and S3 use curve fitting calibration
adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
@@ -187,7 +186,7 @@ float ADCSensor::sample_fixed_attenuation_() {
ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err); ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err);
if (this->calibration_handle_ != nullptr) { if (this->calibration_handle_ != nullptr) {
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
#else // Other ESP32 variants use line fitting calibration #else // Other ESP32 variants use line fitting calibration
adc_cali_delete_scheme_line_fitting(this->calibration_handle_); adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
@@ -220,7 +219,7 @@ float ADCSensor::sample_autorange_() {
if (this->calibration_handle_ != nullptr) { if (this->calibration_handle_ != nullptr) {
// Delete old calibration handle // Delete old calibration handle
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); adc_cali_delete_scheme_curve_fitting(this->calibration_handle_);
#else #else
adc_cali_delete_scheme_line_fitting(this->calibration_handle_); adc_cali_delete_scheme_line_fitting(this->calibration_handle_);
@@ -232,7 +231,7 @@ float ADCSensor::sample_autorange_() {
adc_cali_handle_t handle = nullptr; adc_cali_handle_t handle = nullptr;
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
adc_cali_curve_fitting_config_t cali_config = {}; adc_cali_curve_fitting_config_t cali_config = {};
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
cali_config.chan = this->channel_; cali_config.chan = this->channel_;
@@ -261,7 +260,7 @@ float ADCSensor::sample_autorange_() {
ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err); ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err);
if (handle != nullptr) { if (handle != nullptr) {
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
adc_cali_delete_scheme_curve_fitting(handle); adc_cali_delete_scheme_curve_fitting(handle);
#else #else
adc_cali_delete_scheme_line_fitting(handle); adc_cali_delete_scheme_line_fitting(handle);
@@ -281,7 +280,7 @@ float ADCSensor::sample_autorange_() {
} }
// Clean up calibration handle // Clean up calibration handle
#if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \
USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4
adc_cali_delete_scheme_curve_fitting(handle); adc_cali_delete_scheme_curve_fitting(handle);
#else #else
adc_cali_delete_scheme_line_fitting(handle); adc_cali_delete_scheme_line_fitting(handle);

View File

@@ -53,6 +53,7 @@ SERVICE_ARG_NATIVE_TYPES = {
CONF_ENCRYPTION = "encryption" CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay" CONF_BATCH_DELAY = "batch_delay"
CONF_CUSTOM_SERVICES = "custom_services" CONF_CUSTOM_SERVICES = "custom_services"
CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
CONF_HOMEASSISTANT_STATES = "homeassistant_states" CONF_HOMEASSISTANT_STATES = "homeassistant_states"
@@ -119,6 +120,7 @@ CONFIG_SCHEMA = cv.All(
cv.Range(max=cv.TimePeriod(milliseconds=65535)), cv.Range(max=cv.TimePeriod(milliseconds=65535)),
), ),
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean, cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
cv.Optional(CONF_HOMEASSISTANT_SERVICES, default=False): cv.boolean,
cv.Optional(CONF_HOMEASSISTANT_STATES, default=False): cv.boolean, cv.Optional(CONF_HOMEASSISTANT_STATES, default=False): cv.boolean,
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True single=True
@@ -148,6 +150,9 @@ async def to_code(config):
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]: if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
cg.add_define("USE_API_SERVICES") cg.add_define("USE_API_SERVICES")
if config[CONF_HOMEASSISTANT_SERVICES]:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
if config[CONF_HOMEASSISTANT_STATES]: if config[CONF_HOMEASSISTANT_STATES]:
cg.add_define("USE_API_HOMEASSISTANT_STATES") cg.add_define("USE_API_HOMEASSISTANT_STATES")
@@ -240,6 +245,7 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
HOMEASSISTANT_ACTION_ACTION_SCHEMA, HOMEASSISTANT_ACTION_ACTION_SCHEMA,
) )
async def homeassistant_service_to_code(config, action_id, template_arg, args): async def homeassistant_service_to_code(config, action_id, template_arg, args):
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
serv = await cg.get_variable(config[CONF_ID]) serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, False) var = cg.new_Pvariable(action_id, template_arg, serv, False)
templ = await cg.templatable(config[CONF_ACTION], args, None) templ = await cg.templatable(config[CONF_ACTION], args, None)
@@ -283,6 +289,7 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
HOMEASSISTANT_EVENT_ACTION_SCHEMA, HOMEASSISTANT_EVENT_ACTION_SCHEMA,
) )
async def homeassistant_event_to_code(config, action_id, template_arg, args): async def homeassistant_event_to_code(config, action_id, template_arg, args):
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
serv = await cg.get_variable(config[CONF_ID]) serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, True) var = cg.new_Pvariable(action_id, template_arg, serv, True)
templ = await cg.templatable(config[CONF_EVENT], args, None) templ = await cg.templatable(config[CONF_EVENT], args, None)

View File

@@ -419,7 +419,7 @@ message ListEntitiesFanResponse {
bool disabled_by_default = 9; bool disabled_by_default = 9;
string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"]; string icon = 10 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 11; EntityCategory entity_category = 11;
repeated string supported_preset_modes = 12; repeated string supported_preset_modes = 12 [(container_pointer) = "std::set"];
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"]; uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
} }
// Deprecated in API version 1.6 - only used in deprecated fields // Deprecated in API version 1.6 - only used in deprecated fields
@@ -500,7 +500,7 @@ message ListEntitiesLightResponse {
string name = 3; string name = 3;
reserved 4; // Deprecated: was string unique_id reserved 4; // Deprecated: was string unique_id
repeated ColorMode supported_color_modes = 12; repeated ColorMode supported_color_modes = 12 [(container_pointer) = "std::set<light::ColorMode>"];
// next four supports_* are for legacy clients, newer clients should use color modes // next four supports_* are for legacy clients, newer clients should use color modes
// Deprecated in API version 1.6 // Deprecated in API version 1.6
bool legacy_supports_brightness = 5 [deprecated=true]; bool legacy_supports_brightness = 5 [deprecated=true];
@@ -755,17 +755,19 @@ message NoiseEncryptionSetKeyResponse {
message SubscribeHomeassistantServicesRequest { message SubscribeHomeassistantServicesRequest {
option (id) = 34; option (id) = 34;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_API_HOMEASSISTANT_SERVICES";
} }
message HomeassistantServiceMap { message HomeassistantServiceMap {
string key = 1; string key = 1;
string value = 2; string value = 2 [(no_zero_copy) = true];
} }
message HomeassistantServiceResponse { message HomeassistantServiceResponse {
option (id) = 35; option (id) = 35;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (no_delay) = true; option (no_delay) = true;
option (ifdef) = "USE_API_HOMEASSISTANT_SERVICES";
string service = 1; string service = 1;
repeated HomeassistantServiceMap data = 2; repeated HomeassistantServiceMap data = 2;
@@ -964,7 +966,7 @@ message ListEntitiesClimateResponse {
bool supports_current_temperature = 5; bool supports_current_temperature = 5;
bool supports_two_point_target_temperature = 6; bool supports_two_point_target_temperature = 6;
repeated ClimateMode supported_modes = 7; repeated ClimateMode supported_modes = 7 [(container_pointer) = "std::set<climate::ClimateMode>"];
float visual_min_temperature = 8; float visual_min_temperature = 8;
float visual_max_temperature = 9; float visual_max_temperature = 9;
float visual_target_temperature_step = 10; float visual_target_temperature_step = 10;
@@ -973,11 +975,11 @@ message ListEntitiesClimateResponse {
// Deprecated in API version 1.5 // Deprecated in API version 1.5
bool legacy_supports_away = 11 [deprecated=true]; bool legacy_supports_away = 11 [deprecated=true];
bool supports_action = 12; bool supports_action = 12;
repeated ClimateFanMode supported_fan_modes = 13; repeated ClimateFanMode supported_fan_modes = 13 [(container_pointer) = "std::set<climate::ClimateFanMode>"];
repeated ClimateSwingMode supported_swing_modes = 14; repeated ClimateSwingMode supported_swing_modes = 14 [(container_pointer) = "std::set<climate::ClimateSwingMode>"];
repeated string supported_custom_fan_modes = 15; repeated string supported_custom_fan_modes = 15 [(container_pointer) = "std::set"];
repeated ClimatePreset supported_presets = 16; repeated ClimatePreset supported_presets = 16 [(container_pointer) = "std::set<climate::ClimatePreset>"];
repeated string supported_custom_presets = 17; repeated string supported_custom_presets = 17 [(container_pointer) = "std::set"];
bool disabled_by_default = 18; bool disabled_by_default = 18;
string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"]; string icon = 19 [(field_ifdef) = "USE_ENTITY_ICON"];
EntityCategory entity_category = 20; EntityCategory entity_category = 20;
@@ -1117,7 +1119,7 @@ message ListEntitiesSelectResponse {
reserved 4; // Deprecated: was string unique_id reserved 4; // Deprecated: was string unique_id
string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"]; string icon = 5 [(field_ifdef) = "USE_ENTITY_ICON"];
repeated string options = 6; repeated string options = 6 [(container_pointer) = "std::vector"];
bool disabled_by_default = 7; bool disabled_by_default = 7;
EntityCategory entity_category = 8; EntityCategory entity_category = 8;
uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"]; uint32 device_id = 9 [(field_ifdef) = "USE_DEVICES"];
@@ -1295,6 +1297,7 @@ enum MediaPlayerState {
MEDIA_PLAYER_STATE_IDLE = 1; MEDIA_PLAYER_STATE_IDLE = 1;
MEDIA_PLAYER_STATE_PLAYING = 2; MEDIA_PLAYER_STATE_PLAYING = 2;
MEDIA_PLAYER_STATE_PAUSED = 3; MEDIA_PLAYER_STATE_PAUSED = 3;
MEDIA_PLAYER_STATE_ANNOUNCING = 4;
} }
enum MediaPlayerCommand { enum MediaPlayerCommand {
MEDIA_PLAYER_COMMAND_PLAY = 0; MEDIA_PLAYER_COMMAND_PLAY = 0;
@@ -1302,6 +1305,13 @@ enum MediaPlayerCommand {
MEDIA_PLAYER_COMMAND_STOP = 2; MEDIA_PLAYER_COMMAND_STOP = 2;
MEDIA_PLAYER_COMMAND_MUTE = 3; MEDIA_PLAYER_COMMAND_MUTE = 3;
MEDIA_PLAYER_COMMAND_UNMUTE = 4; MEDIA_PLAYER_COMMAND_UNMUTE = 4;
MEDIA_PLAYER_COMMAND_TOGGLE = 5;
MEDIA_PLAYER_COMMAND_VOLUME_UP = 6;
MEDIA_PLAYER_COMMAND_VOLUME_DOWN = 7;
MEDIA_PLAYER_COMMAND_ENQUEUE = 8;
MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9;
MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10;
MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11;
} }
enum MediaPlayerFormatPurpose { enum MediaPlayerFormatPurpose {
MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0; MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0;
@@ -1336,6 +1346,8 @@ message ListEntitiesMediaPlayerResponse {
repeated MediaPlayerSupportedFormat supported_formats = 9; repeated MediaPlayerSupportedFormat supported_formats = 9;
uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"]; uint32 device_id = 10 [(field_ifdef) = "USE_DEVICES"];
uint32 feature_flags = 11;
} }
message MediaPlayerStateResponse { message MediaPlayerStateResponse {
option (id) = 64; option (id) = 64;
@@ -1832,7 +1844,7 @@ message VoiceAssistantConfigurationResponse {
option (ifdef) = "USE_VOICE_ASSISTANT"; option (ifdef) = "USE_VOICE_ASSISTANT";
repeated VoiceAssistantWakeWord available_wake_words = 1; repeated VoiceAssistantWakeWord available_wake_words = 1;
repeated string active_wake_words = 2; repeated string active_wake_words = 2 [(container_pointer) = "std::vector"];
uint32 max_active_wake_words = 3; uint32 max_active_wake_words = 3;
} }

View File

@@ -244,21 +244,7 @@ void APIConnection::loop() {
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
if (state_subs_at_ >= 0) { if (state_subs_at_ >= 0) {
const auto &subs = this->parent_->get_state_subs(); this->process_state_subscriptions_();
if (state_subs_at_ < static_cast<int>(subs.size())) {
auto &it = subs[state_subs_at_];
SubscribeHomeAssistantStateResponse resp;
resp.set_entity_id(StringRef(it.entity_id));
// attribute.value() returns temporary - must store it
std::string attribute_value = it.attribute.value();
resp.set_attribute(StringRef(attribute_value));
resp.once = it.once;
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
state_subs_at_++;
}
} else {
state_subs_at_ = -1;
}
} }
#endif #endif
} }
@@ -290,8 +276,9 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
#endif #endif
// Calculate size // Calculate size
uint32_t calculated_size = 0; ProtoSize size_calc;
msg.calculate_size(calculated_size); msg.calculate_size(size_calc);
uint32_t calculated_size = size_calc.get_size();
// Cache frame sizes to avoid repeated virtual calls // Cache frame sizes to avoid repeated virtual calls
const uint8_t header_padding = conn->helper_->frame_header_padding(); const uint8_t header_padding = conn->helper_->frame_header_padding();
@@ -426,8 +413,7 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
msg.supports_speed = traits.supports_speed(); msg.supports_speed = traits.supports_speed();
msg.supports_direction = traits.supports_direction(); msg.supports_direction = traits.supports_direction();
msg.supported_speed_count = traits.supported_speed_count(); msg.supported_speed_count = traits.supported_speed_count();
for (auto const &preset : traits.supported_preset_modes()) msg.supported_preset_modes = &traits.supported_preset_modes_for_api_();
msg.supported_preset_modes.push_back(preset);
return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return fill_and_encode_entity_info(fan, msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::fan_command(const FanCommandRequest &msg) { void APIConnection::fan_command(const FanCommandRequest &msg) {
@@ -483,8 +469,7 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
auto *light = static_cast<light::LightState *>(entity); auto *light = static_cast<light::LightState *>(entity);
ListEntitiesLightResponse msg; ListEntitiesLightResponse msg;
auto traits = light->get_traits(); auto traits = light->get_traits();
for (auto mode : traits.get_supported_color_modes()) msg.supported_color_modes = &traits.get_supported_color_modes_for_api_();
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) || if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) { traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
msg.min_mireds = traits.get_min_mireds(); msg.min_mireds = traits.get_min_mireds();
@@ -644,17 +629,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value()); resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) { if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) {
// custom_fan_mode.value() returns temporary - must store it resp.set_custom_fan_mode(StringRef(climate->custom_fan_mode.value()));
std::string custom_fan_mode = climate->custom_fan_mode.value();
resp.set_custom_fan_mode(StringRef(custom_fan_mode));
} }
if (traits.get_supports_presets() && climate->preset.has_value()) { if (traits.get_supports_presets() && climate->preset.has_value()) {
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value()); resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
} }
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) { if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) {
// custom_preset.value() returns temporary - must store it resp.set_custom_preset(StringRef(climate->custom_preset.value()));
std::string custom_preset = climate->custom_preset.value();
resp.set_custom_preset(StringRef(custom_preset));
} }
if (traits.get_supports_swing_modes()) if (traits.get_supports_swing_modes())
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode); resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
@@ -674,8 +655,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.supports_current_humidity = traits.get_supports_current_humidity(); msg.supports_current_humidity = traits.get_supports_current_humidity();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
msg.supports_target_humidity = traits.get_supports_target_humidity(); msg.supports_target_humidity = traits.get_supports_target_humidity();
for (auto mode : traits.get_supported_modes()) msg.supported_modes = &traits.get_supported_modes_for_api_();
msg.supported_modes.push_back(static_cast<enums::ClimateMode>(mode));
msg.visual_min_temperature = traits.get_visual_min_temperature(); msg.visual_min_temperature = traits.get_visual_min_temperature();
msg.visual_max_temperature = traits.get_visual_max_temperature(); msg.visual_max_temperature = traits.get_visual_max_temperature();
msg.visual_target_temperature_step = traits.get_visual_target_temperature_step(); msg.visual_target_temperature_step = traits.get_visual_target_temperature_step();
@@ -683,16 +663,11 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
msg.visual_min_humidity = traits.get_visual_min_humidity(); msg.visual_min_humidity = traits.get_visual_min_humidity();
msg.visual_max_humidity = traits.get_visual_max_humidity(); msg.visual_max_humidity = traits.get_visual_max_humidity();
msg.supports_action = traits.get_supports_action(); msg.supports_action = traits.get_supports_action();
for (auto fan_mode : traits.get_supported_fan_modes()) msg.supported_fan_modes = &traits.get_supported_fan_modes_for_api_();
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode)); msg.supported_custom_fan_modes = &traits.get_supported_custom_fan_modes_for_api_();
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) msg.supported_presets = &traits.get_supported_presets_for_api_();
msg.supported_custom_fan_modes.push_back(custom_fan_mode); msg.supported_custom_presets = &traits.get_supported_custom_presets_for_api_();
for (auto preset : traits.get_supported_presets()) msg.supported_swing_modes = &traits.get_supported_swing_modes_for_api_();
msg.supported_presets.push_back(static_cast<enums::ClimatePreset>(preset));
for (auto const &custom_preset : traits.get_supported_custom_presets())
msg.supported_custom_presets.push_back(custom_preset);
for (auto swing_mode : traits.get_supported_swing_modes())
msg.supported_swing_modes.push_back(static_cast<enums::ClimateSwingMode>(swing_mode));
return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(climate, msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size,
is_single); is_single);
} }
@@ -898,8 +873,7 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
bool is_single) { bool is_single) {
auto *select = static_cast<select::Select *>(entity); auto *select = static_cast<select::Select *>(entity);
ListEntitiesSelectResponse msg; ListEntitiesSelectResponse msg;
for (const auto &option : select->traits.get_options()) msg.options = &select->traits.get_options();
msg.options.push_back(option);
return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, return fill_and_encode_entity_info(select, msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size,
is_single); is_single);
} }
@@ -1025,6 +999,7 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
ListEntitiesMediaPlayerResponse msg; ListEntitiesMediaPlayerResponse msg;
auto traits = media_player->get_traits(); auto traits = media_player->get_traits();
msg.supports_pause = traits.get_supports_pause(); msg.supports_pause = traits.get_supports_pause();
msg.feature_flags = traits.get_feature_flags();
for (auto &supported_format : traits.get_supported_formats()) { for (auto &supported_format : traits.get_supported_formats()) {
msg.supported_formats.emplace_back(); msg.supported_formats.emplace_back();
auto &media_format = msg.supported_formats.back(); auto &media_format = msg.supported_formats.back();
@@ -1213,9 +1188,7 @@ bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceA
resp_wake_word.trained_languages.push_back(lang); resp_wake_word.trained_languages.push_back(lang);
} }
} }
for (auto &wake_word_id : config.active_wake_words) { resp.active_wake_words = &config.active_wake_words;
resp.active_wake_words.push_back(wake_word_id);
}
resp.max_active_wake_words = config.max_active_wake_words; resp.max_active_wake_words = config.max_active_wake_words;
return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE); return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
} }
@@ -1538,19 +1511,18 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
#endif #endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) { bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
psk_t psk{};
NoiseEncryptionSetKeyResponse resp; NoiseEncryptionSetKeyResponse resp;
resp.success = false;
psk_t psk{};
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) { if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
ESP_LOGW(TAG, "Invalid encryption key length"); ESP_LOGW(TAG, "Invalid encryption key length");
resp.success = false; } else if (!this->parent_->save_noise_psk(psk, true)) {
return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
}
if (!this->parent_->save_noise_psk(psk, true)) {
ESP_LOGW(TAG, "Failed to save encryption key"); ESP_LOGW(TAG, "Failed to save encryption key");
resp.success = false; } else {
return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE); resp.success = true;
} }
resp.success = true;
return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE); return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
} }
#endif #endif
@@ -1844,5 +1816,27 @@ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
#ifdef USE_API_HOMEASSISTANT_STATES
void APIConnection::process_state_subscriptions_() {
const auto &subs = this->parent_->get_state_subs();
if (this->state_subs_at_ >= static_cast<int>(subs.size())) {
this->state_subs_at_ = -1;
return;
}
const auto &it = subs[this->state_subs_at_];
SubscribeHomeAssistantStateResponse resp;
resp.set_entity_id(StringRef(it.entity_id));
// Avoid string copy by directly using the optional's value if it exists
resp.set_attribute(it.attribute.has_value() ? StringRef(it.attribute.value()) : StringRef(""));
resp.once = it.once;
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
this->state_subs_at_++;
}
}
#endif // USE_API_HOMEASSISTANT_STATES
} // namespace esphome::api } // namespace esphome::api
#endif #endif

View File

@@ -131,11 +131,13 @@ class APIConnection : public APIServerConnection {
void media_player_command(const MediaPlayerCommandRequest &msg) override; void media_player_command(const MediaPlayerCommandRequest &msg) override;
#endif #endif
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len); bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
#ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
if (!this->flags_.service_call_subscription) if (!this->flags_.service_call_subscription)
return; return;
this->send_message(call, HomeassistantServiceResponse::MESSAGE_TYPE); this->send_message(call, HomeassistantServiceResponse::MESSAGE_TYPE);
} }
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override; void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
@@ -209,9 +211,11 @@ class APIConnection : public APIServerConnection {
if (msg.dump_config) if (msg.dump_config)
App.schedule_dump_config(); App.schedule_dump_config();
} }
#ifdef USE_API_HOMEASSISTANT_SERVICES
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override { void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
this->flags_.service_call_subscription = true; this->flags_.service_call_subscription = true;
} }
#endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override; void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif #endif
@@ -294,6 +298,10 @@ class APIConnection : public APIServerConnection {
// Helper function to handle authentication completion // Helper function to handle authentication completion
void complete_authentication_(); void complete_authentication_();
#ifdef USE_API_HOMEASSISTANT_STATES
void process_state_subscriptions_();
#endif
// Non-template helper to encode any ProtoMessage // Non-template helper to encode any ProtoMessage
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn, static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
uint32_t remaining_size, bool is_single); uint32_t remaining_size, bool is_single);

View File

@@ -27,4 +27,31 @@ extend google.protobuf.MessageOptions {
extend google.protobuf.FieldOptions { extend google.protobuf.FieldOptions {
optional string field_ifdef = 1042; optional string field_ifdef = 1042;
optional uint32 fixed_array_size = 50007; optional uint32 fixed_array_size = 50007;
optional bool no_zero_copy = 50008 [default=false];
// container_pointer: Zero-copy optimization for repeated fields.
//
// When container_pointer is set on a repeated field, the generated message will
// store a pointer to an existing container instead of copying the data into the
// message's own repeated field. This eliminates heap allocations and improves performance.
//
// Requirements for safe usage:
// 1. The source container must remain valid until the message is encoded
// 2. Messages must be encoded immediately (which ESPHome does by default)
// 3. The container type must match the field type exactly
//
// Supported container types:
// - "std::vector<T>" for most repeated fields
// - "std::set<T>" for unique/sorted data
// - Full type specification required for enums (e.g., "std::set<climate::ClimateMode>")
//
// Example usage in .proto file:
// repeated string supported_modes = 12 [(container_pointer) = "std::set"];
// repeated ColorMode color_modes = 13 [(container_pointer) = "std::set<light::ColorMode>"];
//
// The corresponding C++ code must provide const reference access to a container
// that matches the specified type and remains valid during message encoding.
// This is typically done through methods returning const T& or special accessor
// methods like get_options() or supported_modes_for_api_().
optional string container_pointer = 50001;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@
#include "esphome/core/string_ref.h" #include "esphome/core/string_ref.h"
#include "proto.h" #include "proto.h"
#include "api_pb2_includes.h"
namespace esphome::api { namespace esphome::api {
@@ -149,6 +150,7 @@ enum MediaPlayerState : uint32_t {
MEDIA_PLAYER_STATE_IDLE = 1, MEDIA_PLAYER_STATE_IDLE = 1,
MEDIA_PLAYER_STATE_PLAYING = 2, MEDIA_PLAYER_STATE_PLAYING = 2,
MEDIA_PLAYER_STATE_PAUSED = 3, MEDIA_PLAYER_STATE_PAUSED = 3,
MEDIA_PLAYER_STATE_ANNOUNCING = 4,
}; };
enum MediaPlayerCommand : uint32_t { enum MediaPlayerCommand : uint32_t {
MEDIA_PLAYER_COMMAND_PLAY = 0, MEDIA_PLAYER_COMMAND_PLAY = 0,
@@ -156,6 +158,13 @@ enum MediaPlayerCommand : uint32_t {
MEDIA_PLAYER_COMMAND_STOP = 2, MEDIA_PLAYER_COMMAND_STOP = 2,
MEDIA_PLAYER_COMMAND_MUTE = 3, MEDIA_PLAYER_COMMAND_MUTE = 3,
MEDIA_PLAYER_COMMAND_UNMUTE = 4, MEDIA_PLAYER_COMMAND_UNMUTE = 4,
MEDIA_PLAYER_COMMAND_TOGGLE = 5,
MEDIA_PLAYER_COMMAND_VOLUME_UP = 6,
MEDIA_PLAYER_COMMAND_VOLUME_DOWN = 7,
MEDIA_PLAYER_COMMAND_ENQUEUE = 8,
MEDIA_PLAYER_COMMAND_REPEAT_ONE = 9,
MEDIA_PLAYER_COMMAND_REPEAT_OFF = 10,
MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST = 11,
}; };
enum MediaPlayerFormatPurpose : uint32_t { enum MediaPlayerFormatPurpose : uint32_t {
MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0, MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0,
@@ -340,7 +349,7 @@ class HelloResponse : public ProtoMessage {
StringRef name_ref_{}; StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; } void set_name(const StringRef &ref) { this->name_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -371,14 +380,14 @@ class ConnectResponse : public ProtoMessage {
#endif #endif
bool invalid_password{false}; bool invalid_password{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
protected: protected:
}; };
class DisconnectRequest : public ProtoDecodableMessage { class DisconnectRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 5; static constexpr uint8_t MESSAGE_TYPE = 5;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -391,7 +400,7 @@ class DisconnectRequest : public ProtoDecodableMessage {
protected: protected:
}; };
class DisconnectResponse : public ProtoDecodableMessage { class DisconnectResponse : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 6; static constexpr uint8_t MESSAGE_TYPE = 6;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -404,7 +413,7 @@ class DisconnectResponse : public ProtoDecodableMessage {
protected: protected:
}; };
class PingRequest : public ProtoDecodableMessage { class PingRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 7; static constexpr uint8_t MESSAGE_TYPE = 7;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -417,7 +426,7 @@ class PingRequest : public ProtoDecodableMessage {
protected: protected:
}; };
class PingResponse : public ProtoDecodableMessage { class PingResponse : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 8; static constexpr uint8_t MESSAGE_TYPE = 8;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -430,7 +439,7 @@ class PingResponse : public ProtoDecodableMessage {
protected: protected:
}; };
class DeviceInfoRequest : public ProtoDecodableMessage { class DeviceInfoRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 9; static constexpr uint8_t MESSAGE_TYPE = 9;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -450,7 +459,7 @@ class AreaInfo : public ProtoMessage {
StringRef name_ref_{}; StringRef name_ref_{};
void set_name(const StringRef &ref) { this->name_ref_ = ref; } void set_name(const StringRef &ref) { this->name_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -466,7 +475,7 @@ class DeviceInfo : public ProtoMessage {
void set_name(const StringRef &ref) { this->name_ref_ = ref; } void set_name(const StringRef &ref) { this->name_ref_ = ref; }
uint32_t area_id{0}; uint32_t area_id{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -539,14 +548,14 @@ class DeviceInfoResponse : public ProtoMessage {
AreaInfo area{}; AreaInfo area{};
#endif #endif
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
protected: protected:
}; };
class ListEntitiesRequest : public ProtoDecodableMessage { class ListEntitiesRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 11; static constexpr uint8_t MESSAGE_TYPE = 11;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -572,7 +581,7 @@ class ListEntitiesDoneResponse : public ProtoMessage {
protected: protected:
}; };
class SubscribeStatesRequest : public ProtoDecodableMessage { class SubscribeStatesRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 20; static constexpr uint8_t MESSAGE_TYPE = 20;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -597,7 +606,7 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
bool is_status_binary_sensor{false}; bool is_status_binary_sensor{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -614,7 +623,7 @@ class BinarySensorStateResponse : public StateResponseProtoMessage {
bool state{false}; bool state{false};
bool missing_state{false}; bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -637,7 +646,7 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
bool supports_stop{false}; bool supports_stop{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -655,7 +664,7 @@ class CoverStateResponse : public StateResponseProtoMessage {
float tilt{0.0f}; float tilt{0.0f};
enums::CoverOperation current_operation{}; enums::CoverOperation current_operation{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -695,9 +704,9 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage {
bool supports_speed{false}; bool supports_speed{false};
bool supports_direction{false}; bool supports_direction{false};
int32_t supported_speed_count{0}; int32_t supported_speed_count{0};
std::vector<std::string> supported_preset_modes{}; const std::set<std::string> *supported_preset_modes{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -718,7 +727,7 @@ class FanStateResponse : public StateResponseProtoMessage {
StringRef preset_mode_ref_{}; StringRef preset_mode_ref_{};
void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; } void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -760,12 +769,12 @@ class ListEntitiesLightResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_light_response"; } const char *message_name() const override { return "list_entities_light_response"; }
#endif #endif
std::vector<enums::ColorMode> supported_color_modes{}; const std::set<light::ColorMode> *supported_color_modes{};
float min_mireds{0.0f}; float min_mireds{0.0f};
float max_mireds{0.0f}; float max_mireds{0.0f};
std::vector<std::string> effects{}; std::vector<std::string> effects{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -793,7 +802,7 @@ class LightStateResponse : public StateResponseProtoMessage {
StringRef effect_ref_{}; StringRef effect_ref_{};
void set_effect(const StringRef &ref) { this->effect_ref_ = ref; } void set_effect(const StringRef &ref) { this->effect_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -859,7 +868,7 @@ class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
enums::SensorStateClass state_class{}; enums::SensorStateClass state_class{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -876,7 +885,7 @@ class SensorStateResponse : public StateResponseProtoMessage {
float state{0.0f}; float state{0.0f};
bool missing_state{false}; bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -896,7 +905,7 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
StringRef device_class_ref_{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -912,7 +921,7 @@ class SwitchStateResponse : public StateResponseProtoMessage {
#endif #endif
bool state{false}; bool state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -947,7 +956,7 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
StringRef device_class_ref_{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -965,7 +974,7 @@ class TextSensorStateResponse : public StateResponseProtoMessage {
void set_state(const StringRef &ref) { this->state_ref_ = ref; } void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false}; bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1004,7 +1013,7 @@ class SubscribeLogsResponse : public ProtoMessage {
this->message_len_ = len; this->message_len_ = len;
} }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1036,7 +1045,7 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage {
#endif #endif
bool success{false}; bool success{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1044,7 +1053,8 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage {
protected: protected:
}; };
#endif #endif
class SubscribeHomeassistantServicesRequest : public ProtoDecodableMessage { #ifdef USE_API_HOMEASSISTANT_SERVICES
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 34; static constexpr uint8_t MESSAGE_TYPE = 34;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -1061,10 +1071,9 @@ class HomeassistantServiceMap : public ProtoMessage {
public: public:
StringRef key_ref_{}; StringRef key_ref_{};
void set_key(const StringRef &ref) { this->key_ref_ = ref; } void set_key(const StringRef &ref) { this->key_ref_ = ref; }
StringRef value_ref_{}; std::string value{};
void set_value(const StringRef &ref) { this->value_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1085,15 +1094,16 @@ class HomeassistantServiceResponse : public ProtoMessage {
std::vector<HomeassistantServiceMap> variables{}; std::vector<HomeassistantServiceMap> variables{};
bool is_event{false}; bool is_event{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
protected: protected:
}; };
#endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
class SubscribeHomeAssistantStatesRequest : public ProtoDecodableMessage { class SubscribeHomeAssistantStatesRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 38; static constexpr uint8_t MESSAGE_TYPE = 38;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -1119,7 +1129,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; } void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; }
bool once{false}; bool once{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1144,7 +1154,7 @@ class HomeAssistantStateResponse : public ProtoDecodableMessage {
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
}; };
#endif #endif
class GetTimeRequest : public ProtoDecodableMessage { class GetTimeRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 36; static constexpr uint8_t MESSAGE_TYPE = 36;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -1166,7 +1176,7 @@ class GetTimeResponse : public ProtoDecodableMessage {
#endif #endif
uint32_t epoch_seconds{0}; uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1181,7 +1191,7 @@ class ListEntitiesServicesArgument : public ProtoMessage {
void set_name(const StringRef &ref) { this->name_ref_ = ref; } void set_name(const StringRef &ref) { this->name_ref_ = ref; }
enums::ServiceArgType type{}; enums::ServiceArgType type{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1200,7 +1210,7 @@ class ListEntitiesServicesResponse : public ProtoMessage {
uint32_t key{0}; uint32_t key{0};
std::vector<ListEntitiesServicesArgument> args{}; std::vector<ListEntitiesServicesArgument> args{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1254,7 +1264,7 @@ class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_camera_response"; } const char *message_name() const override { return "list_entities_camera_response"; }
#endif #endif
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1276,7 +1286,7 @@ class CameraImageResponse : public StateResponseProtoMessage {
} }
bool done{false}; bool done{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1310,23 +1320,23 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
#endif #endif
bool supports_current_temperature{false}; bool supports_current_temperature{false};
bool supports_two_point_target_temperature{false}; bool supports_two_point_target_temperature{false};
std::vector<enums::ClimateMode> supported_modes{}; const std::set<climate::ClimateMode> *supported_modes{};
float visual_min_temperature{0.0f}; float visual_min_temperature{0.0f};
float visual_max_temperature{0.0f}; float visual_max_temperature{0.0f};
float visual_target_temperature_step{0.0f}; float visual_target_temperature_step{0.0f};
bool supports_action{false}; bool supports_action{false};
std::vector<enums::ClimateFanMode> supported_fan_modes{}; const std::set<climate::ClimateFanMode> *supported_fan_modes{};
std::vector<enums::ClimateSwingMode> supported_swing_modes{}; const std::set<climate::ClimateSwingMode> *supported_swing_modes{};
std::vector<std::string> supported_custom_fan_modes{}; const std::set<std::string> *supported_custom_fan_modes{};
std::vector<enums::ClimatePreset> supported_presets{}; const std::set<climate::ClimatePreset> *supported_presets{};
std::vector<std::string> supported_custom_presets{}; const std::set<std::string> *supported_custom_presets{};
float visual_current_temperature_step{0.0f}; float visual_current_temperature_step{0.0f};
bool supports_current_humidity{false}; bool supports_current_humidity{false};
bool supports_target_humidity{false}; bool supports_target_humidity{false};
float visual_min_humidity{0.0f}; float visual_min_humidity{0.0f};
float visual_max_humidity{0.0f}; float visual_max_humidity{0.0f};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1356,7 +1366,7 @@ class ClimateStateResponse : public StateResponseProtoMessage {
float current_humidity{0.0f}; float current_humidity{0.0f};
float target_humidity{0.0f}; float target_humidity{0.0f};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1417,7 +1427,7 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
StringRef device_class_ref_{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1434,7 +1444,7 @@ class NumberStateResponse : public StateResponseProtoMessage {
float state{0.0f}; float state{0.0f};
bool missing_state{false}; bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1466,9 +1476,9 @@ class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_select_response"; } const char *message_name() const override { return "list_entities_select_response"; }
#endif #endif
std::vector<std::string> options{}; const std::vector<std::string> *options{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1486,7 +1496,7 @@ class SelectStateResponse : public StateResponseProtoMessage {
void set_state(const StringRef &ref) { this->state_ref_ = ref; } void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false}; bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1523,7 +1533,7 @@ class ListEntitiesSirenResponse : public InfoResponseProtoMessage {
bool supports_duration{false}; bool supports_duration{false};
bool supports_volume{false}; bool supports_volume{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1539,7 +1549,7 @@ class SirenStateResponse : public StateResponseProtoMessage {
#endif #endif
bool state{false}; bool state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1585,7 +1595,7 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage {
StringRef code_format_ref_{}; StringRef code_format_ref_{};
void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; } void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1601,7 +1611,7 @@ class LockStateResponse : public StateResponseProtoMessage {
#endif #endif
enums::LockState state{}; enums::LockState state{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1639,7 +1649,7 @@ class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
StringRef device_class_ref_{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1672,7 +1682,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage {
enums::MediaPlayerFormatPurpose purpose{}; enums::MediaPlayerFormatPurpose purpose{};
uint32_t sample_bytes{0}; uint32_t sample_bytes{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1682,14 +1692,15 @@ class MediaPlayerSupportedFormat : public ProtoMessage {
class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage { class ListEntitiesMediaPlayerResponse : public InfoResponseProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 63; static constexpr uint8_t MESSAGE_TYPE = 63;
static constexpr uint8_t ESTIMATED_SIZE = 76; static constexpr uint8_t ESTIMATED_SIZE = 80;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "list_entities_media_player_response"; } const char *message_name() const override { return "list_entities_media_player_response"; }
#endif #endif
bool supports_pause{false}; bool supports_pause{false};
std::vector<MediaPlayerSupportedFormat> supported_formats{}; std::vector<MediaPlayerSupportedFormat> supported_formats{};
uint32_t feature_flags{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1707,7 +1718,7 @@ class MediaPlayerStateResponse : public StateResponseProtoMessage {
float volume{0.0f}; float volume{0.0f};
bool muted{false}; bool muted{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1763,7 +1774,7 @@ class BluetoothLERawAdvertisement : public ProtoMessage {
uint8_t data[62]{}; uint8_t data[62]{};
uint8_t data_len{0}; uint8_t data_len{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1779,7 +1790,7 @@ class BluetoothLERawAdvertisementsResponse : public ProtoMessage {
#endif #endif
std::vector<BluetoothLERawAdvertisement> advertisements{}; std::vector<BluetoothLERawAdvertisement> advertisements{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1816,7 +1827,7 @@ class BluetoothDeviceConnectionResponse : public ProtoMessage {
uint32_t mtu{0}; uint32_t mtu{0};
int32_t error{0}; int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1843,7 +1854,7 @@ class BluetoothGATTDescriptor : public ProtoMessage {
std::array<uint64_t, 2> uuid{}; std::array<uint64_t, 2> uuid{};
uint32_t handle{0}; uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1857,7 +1868,7 @@ class BluetoothGATTCharacteristic : public ProtoMessage {
uint32_t properties{0}; uint32_t properties{0};
std::vector<BluetoothGATTDescriptor> descriptors{}; std::vector<BluetoothGATTDescriptor> descriptors{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1870,7 +1881,7 @@ class BluetoothGATTService : public ProtoMessage {
uint32_t handle{0}; uint32_t handle{0};
std::vector<BluetoothGATTCharacteristic> characteristics{}; std::vector<BluetoothGATTCharacteristic> characteristics{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1887,7 +1898,7 @@ class BluetoothGATTGetServicesResponse : public ProtoMessage {
uint64_t address{0}; uint64_t address{0};
std::array<BluetoothGATTService, 1> services{}; std::array<BluetoothGATTService, 1> services{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1903,7 +1914,7 @@ class BluetoothGATTGetServicesDoneResponse : public ProtoMessage {
#endif #endif
uint64_t address{0}; uint64_t address{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -1942,7 +1953,7 @@ class BluetoothGATTReadResponse : public ProtoMessage {
this->data_len_ = len; this->data_len_ = len;
} }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2035,14 +2046,14 @@ class BluetoothGATTNotifyDataResponse : public ProtoMessage {
this->data_len_ = len; this->data_len_ = len;
} }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
protected: protected:
}; };
class SubscribeBluetoothConnectionsFreeRequest : public ProtoDecodableMessage { class SubscribeBluetoothConnectionsFreeRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 80; static constexpr uint8_t MESSAGE_TYPE = 80;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -2066,7 +2077,7 @@ class BluetoothConnectionsFreeResponse : public ProtoMessage {
uint32_t limit{0}; uint32_t limit{0};
std::vector<uint64_t> allocated{}; std::vector<uint64_t> allocated{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2084,7 +2095,7 @@ class BluetoothGATTErrorResponse : public ProtoMessage {
uint32_t handle{0}; uint32_t handle{0};
int32_t error{0}; int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2101,7 +2112,7 @@ class BluetoothGATTWriteResponse : public ProtoMessage {
uint64_t address{0}; uint64_t address{0};
uint32_t handle{0}; uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2118,7 +2129,7 @@ class BluetoothGATTNotifyResponse : public ProtoMessage {
uint64_t address{0}; uint64_t address{0};
uint32_t handle{0}; uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2136,7 +2147,7 @@ class BluetoothDevicePairingResponse : public ProtoMessage {
bool paired{false}; bool paired{false};
int32_t error{0}; int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2154,14 +2165,14 @@ class BluetoothDeviceUnpairingResponse : public ProtoMessage {
bool success{false}; bool success{false};
int32_t error{0}; int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
protected: protected:
}; };
class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoDecodableMessage { class UnsubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 87; static constexpr uint8_t MESSAGE_TYPE = 87;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -2185,7 +2196,7 @@ class BluetoothDeviceClearCacheResponse : public ProtoMessage {
bool success{false}; bool success{false};
int32_t error{0}; int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2202,7 +2213,7 @@ class BluetoothScannerStateResponse : public ProtoMessage {
enums::BluetoothScannerState state{}; enums::BluetoothScannerState state{};
enums::BluetoothScannerMode mode{}; enums::BluetoothScannerMode mode{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2248,7 +2259,7 @@ class VoiceAssistantAudioSettings : public ProtoMessage {
uint32_t auto_gain{0}; uint32_t auto_gain{0};
float volume_multiplier{0.0f}; float volume_multiplier{0.0f};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2270,7 +2281,7 @@ class VoiceAssistantRequest : public ProtoMessage {
StringRef wake_word_phrase_ref_{}; StringRef wake_word_phrase_ref_{};
void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; } void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2337,7 +2348,7 @@ class VoiceAssistantAudio : public ProtoDecodableMessage {
} }
bool end{false}; bool end{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2395,7 +2406,7 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage {
#endif #endif
bool success{false}; bool success{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2410,14 +2421,14 @@ class VoiceAssistantWakeWord : public ProtoMessage {
void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; } void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; }
std::vector<std::string> trained_languages{}; std::vector<std::string> trained_languages{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
protected: protected:
}; };
class VoiceAssistantConfigurationRequest : public ProtoDecodableMessage { class VoiceAssistantConfigurationRequest : public ProtoMessage {
public: public:
static constexpr uint8_t MESSAGE_TYPE = 121; static constexpr uint8_t MESSAGE_TYPE = 121;
static constexpr uint8_t ESTIMATED_SIZE = 0; static constexpr uint8_t ESTIMATED_SIZE = 0;
@@ -2438,10 +2449,10 @@ class VoiceAssistantConfigurationResponse : public ProtoMessage {
const char *message_name() const override { return "voice_assistant_configuration_response"; } const char *message_name() const override { return "voice_assistant_configuration_response"; }
#endif #endif
std::vector<VoiceAssistantWakeWord> available_wake_words{}; std::vector<VoiceAssistantWakeWord> available_wake_words{};
std::vector<std::string> active_wake_words{}; const std::vector<std::string> *active_wake_words{};
uint32_t max_active_wake_words{0}; uint32_t max_active_wake_words{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2476,7 +2487,7 @@ class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage {
bool requires_code{false}; bool requires_code{false};
bool requires_code_to_arm{false}; bool requires_code_to_arm{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2492,7 +2503,7 @@ class AlarmControlPanelStateResponse : public StateResponseProtoMessage {
#endif #endif
enums::AlarmControlPanelState state{}; enums::AlarmControlPanelState state{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2532,7 +2543,7 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage {
void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; } void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; }
enums::TextMode mode{}; enums::TextMode mode{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2550,7 +2561,7 @@ class TextStateResponse : public StateResponseProtoMessage {
void set_state(const StringRef &ref) { this->state_ref_ = ref; } void set_state(const StringRef &ref) { this->state_ref_ = ref; }
bool missing_state{false}; bool missing_state{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2584,7 +2595,7 @@ class ListEntitiesDateResponse : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_date_response"; } const char *message_name() const override { return "list_entities_date_response"; }
#endif #endif
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2603,7 +2614,7 @@ class DateStateResponse : public StateResponseProtoMessage {
uint32_t month{0}; uint32_t month{0};
uint32_t day{0}; uint32_t day{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2638,7 +2649,7 @@ class ListEntitiesTimeResponse : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_time_response"; } const char *message_name() const override { return "list_entities_time_response"; }
#endif #endif
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2657,7 +2668,7 @@ class TimeStateResponse : public StateResponseProtoMessage {
uint32_t minute{0}; uint32_t minute{0};
uint32_t second{0}; uint32_t second{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2695,7 +2706,7 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage {
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
std::vector<std::string> event_types{}; std::vector<std::string> event_types{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2712,7 +2723,7 @@ class EventResponse : public StateResponseProtoMessage {
StringRef event_type_ref_{}; StringRef event_type_ref_{};
void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; } void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2734,7 +2745,7 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage {
bool supports_position{false}; bool supports_position{false};
bool supports_stop{false}; bool supports_stop{false};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2751,7 +2762,7 @@ class ValveStateResponse : public StateResponseProtoMessage {
float position{0.0f}; float position{0.0f};
enums::ValveOperation current_operation{}; enums::ValveOperation current_operation{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2786,7 +2797,7 @@ class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage {
const char *message_name() const override { return "list_entities_date_time_response"; } const char *message_name() const override { return "list_entities_date_time_response"; }
#endif #endif
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2803,7 +2814,7 @@ class DateTimeStateResponse : public StateResponseProtoMessage {
bool missing_state{false}; bool missing_state{false};
uint32_t epoch_seconds{0}; uint32_t epoch_seconds{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2838,7 +2849,7 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
StringRef device_class_ref_{}; StringRef device_class_ref_{};
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; } void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif
@@ -2867,7 +2878,7 @@ class UpdateStateResponse : public StateResponseProtoMessage {
StringRef release_url_ref_{}; StringRef release_url_ref_{};
void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; } void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; }
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override; void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
#endif #endif

View File

@@ -383,6 +383,8 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerState>(enums::Medi
return "MEDIA_PLAYER_STATE_PLAYING"; return "MEDIA_PLAYER_STATE_PLAYING";
case enums::MEDIA_PLAYER_STATE_PAUSED: case enums::MEDIA_PLAYER_STATE_PAUSED:
return "MEDIA_PLAYER_STATE_PAUSED"; return "MEDIA_PLAYER_STATE_PAUSED";
case enums::MEDIA_PLAYER_STATE_ANNOUNCING:
return "MEDIA_PLAYER_STATE_ANNOUNCING";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }
@@ -399,6 +401,20 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me
return "MEDIA_PLAYER_COMMAND_MUTE"; return "MEDIA_PLAYER_COMMAND_MUTE";
case enums::MEDIA_PLAYER_COMMAND_UNMUTE: case enums::MEDIA_PLAYER_COMMAND_UNMUTE:
return "MEDIA_PLAYER_COMMAND_UNMUTE"; return "MEDIA_PLAYER_COMMAND_UNMUTE";
case enums::MEDIA_PLAYER_COMMAND_TOGGLE:
return "MEDIA_PLAYER_COMMAND_TOGGLE";
case enums::MEDIA_PLAYER_COMMAND_VOLUME_UP:
return "MEDIA_PLAYER_COMMAND_VOLUME_UP";
case enums::MEDIA_PLAYER_COMMAND_VOLUME_DOWN:
return "MEDIA_PLAYER_COMMAND_VOLUME_DOWN";
case enums::MEDIA_PLAYER_COMMAND_ENQUEUE:
return "MEDIA_PLAYER_COMMAND_ENQUEUE";
case enums::MEDIA_PLAYER_COMMAND_REPEAT_ONE:
return "MEDIA_PLAYER_COMMAND_REPEAT_ONE";
case enums::MEDIA_PLAYER_COMMAND_REPEAT_OFF:
return "MEDIA_PLAYER_COMMAND_REPEAT_OFF";
case enums::MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST:
return "MEDIA_PLAYER_COMMAND_CLEAR_PLAYLIST";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }
@@ -814,7 +830,7 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
dump_field(out, "icon", this->icon_ref_); dump_field(out, "icon", this->icon_ref_);
#endif #endif
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category)); dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
for (const auto &it : this->supported_preset_modes) { for (const auto &it : *this->supported_preset_modes) {
dump_field(out, "supported_preset_modes", it, 4); dump_field(out, "supported_preset_modes", it, 4);
} }
#ifdef USE_DEVICES #ifdef USE_DEVICES
@@ -857,7 +873,7 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
dump_field(out, "object_id", this->object_id_ref_); dump_field(out, "object_id", this->object_id_ref_);
dump_field(out, "key", this->key); dump_field(out, "key", this->key);
dump_field(out, "name", this->name_ref_); dump_field(out, "name", this->name_ref_);
for (const auto &it : this->supported_color_modes) { for (const auto &it : *this->supported_color_modes) {
dump_field(out, "supported_color_modes", static_cast<enums::ColorMode>(it), 4); dump_field(out, "supported_color_modes", static_cast<enums::ColorMode>(it), 4);
} }
dump_field(out, "min_mireds", this->min_mireds); dump_field(out, "min_mireds", this->min_mireds);
@@ -1038,13 +1054,14 @@ void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
} }
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); } void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const { dump_field(out, "success", this->success); }
#endif #endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const { void SubscribeHomeassistantServicesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeassistantServicesRequest {}"); out.append("SubscribeHomeassistantServicesRequest {}");
} }
void HomeassistantServiceMap::dump_to(std::string &out) const { void HomeassistantServiceMap::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantServiceMap"); MessageDumpHelper helper(out, "HomeassistantServiceMap");
dump_field(out, "key", this->key_ref_); dump_field(out, "key", this->key_ref_);
dump_field(out, "value", this->value_ref_); dump_field(out, "value", this->value);
} }
void HomeassistantServiceResponse::dump_to(std::string &out) const { void HomeassistantServiceResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "HomeassistantServiceResponse"); MessageDumpHelper helper(out, "HomeassistantServiceResponse");
@@ -1066,6 +1083,7 @@ void HomeassistantServiceResponse::dump_to(std::string &out) const {
} }
dump_field(out, "is_event", this->is_event); dump_field(out, "is_event", this->is_event);
} }
#endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const { void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeAssistantStatesRequest {}"); out.append("SubscribeHomeAssistantStatesRequest {}");
@@ -1171,26 +1189,26 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
dump_field(out, "name", this->name_ref_); dump_field(out, "name", this->name_ref_);
dump_field(out, "supports_current_temperature", this->supports_current_temperature); dump_field(out, "supports_current_temperature", this->supports_current_temperature);
dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature); dump_field(out, "supports_two_point_target_temperature", this->supports_two_point_target_temperature);
for (const auto &it : this->supported_modes) { for (const auto &it : *this->supported_modes) {
dump_field(out, "supported_modes", static_cast<enums::ClimateMode>(it), 4); dump_field(out, "supported_modes", static_cast<enums::ClimateMode>(it), 4);
} }
dump_field(out, "visual_min_temperature", this->visual_min_temperature); dump_field(out, "visual_min_temperature", this->visual_min_temperature);
dump_field(out, "visual_max_temperature", this->visual_max_temperature); dump_field(out, "visual_max_temperature", this->visual_max_temperature);
dump_field(out, "visual_target_temperature_step", this->visual_target_temperature_step); dump_field(out, "visual_target_temperature_step", this->visual_target_temperature_step);
dump_field(out, "supports_action", this->supports_action); dump_field(out, "supports_action", this->supports_action);
for (const auto &it : this->supported_fan_modes) { for (const auto &it : *this->supported_fan_modes) {
dump_field(out, "supported_fan_modes", static_cast<enums::ClimateFanMode>(it), 4); dump_field(out, "supported_fan_modes", static_cast<enums::ClimateFanMode>(it), 4);
} }
for (const auto &it : this->supported_swing_modes) { for (const auto &it : *this->supported_swing_modes) {
dump_field(out, "supported_swing_modes", static_cast<enums::ClimateSwingMode>(it), 4); dump_field(out, "supported_swing_modes", static_cast<enums::ClimateSwingMode>(it), 4);
} }
for (const auto &it : this->supported_custom_fan_modes) { for (const auto &it : *this->supported_custom_fan_modes) {
dump_field(out, "supported_custom_fan_modes", it, 4); dump_field(out, "supported_custom_fan_modes", it, 4);
} }
for (const auto &it : this->supported_presets) { for (const auto &it : *this->supported_presets) {
dump_field(out, "supported_presets", static_cast<enums::ClimatePreset>(it), 4); dump_field(out, "supported_presets", static_cast<enums::ClimatePreset>(it), 4);
} }
for (const auto &it : this->supported_custom_presets) { for (const auto &it : *this->supported_custom_presets) {
dump_field(out, "supported_custom_presets", it, 4); dump_field(out, "supported_custom_presets", it, 4);
} }
dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "disabled_by_default", this->disabled_by_default);
@@ -1303,7 +1321,7 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
#ifdef USE_ENTITY_ICON #ifdef USE_ENTITY_ICON
dump_field(out, "icon", this->icon_ref_); dump_field(out, "icon", this->icon_ref_);
#endif #endif
for (const auto &it : this->options) { for (const auto &it : *this->options) {
dump_field(out, "options", it, 4); dump_field(out, "options", it, 4);
} }
dump_field(out, "disabled_by_default", this->disabled_by_default); dump_field(out, "disabled_by_default", this->disabled_by_default);
@@ -1464,6 +1482,7 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
#ifdef USE_DEVICES #ifdef USE_DEVICES
dump_field(out, "device_id", this->device_id); dump_field(out, "device_id", this->device_id);
#endif #endif
dump_field(out, "feature_flags", this->feature_flags);
} }
void MediaPlayerStateResponse::dump_to(std::string &out) const { void MediaPlayerStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "MediaPlayerStateResponse"); MessageDumpHelper helper(out, "MediaPlayerStateResponse");
@@ -1767,7 +1786,7 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const {
it.dump_to(out); it.dump_to(out);
out.append("\n"); out.append("\n");
} }
for (const auto &it : this->active_wake_words) { for (const auto &it : *this->active_wake_words) {
dump_field(out, "active_wake_words", it, 4); dump_field(out, "active_wake_words", it, 4);
} }
dump_field(out, "max_active_wake_words", this->max_active_wake_words); dump_field(out, "max_active_wake_words", this->max_active_wake_words);

View File

@@ -0,0 +1,34 @@
#pragma once
#include "esphome/core/defines.h"
// This file provides includes needed by the generated protobuf code
// when using pointer optimizations for component-specific types
#ifdef USE_CLIMATE
#include "esphome/components/climate/climate_mode.h"
#include "esphome/components/climate/climate_traits.h"
#endif
#ifdef USE_LIGHT
#include "esphome/components/light/light_traits.h"
#endif
#ifdef USE_FAN
#include "esphome/components/fan/fan_traits.h"
#endif
#ifdef USE_SELECT
#include "esphome/components/select/select_traits.h"
#endif
// Standard library includes that might be needed
#include <set>
#include <vector>
#include <string>
namespace esphome::api {
// This file only provides includes, no actual code
} // namespace esphome::api

View File

@@ -35,7 +35,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
} }
case DisconnectRequest::MESSAGE_TYPE: { case DisconnectRequest::MESSAGE_TYPE: {
DisconnectRequest msg; DisconnectRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
#endif #endif
@@ -44,7 +44,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
} }
case DisconnectResponse::MESSAGE_TYPE: { case DisconnectResponse::MESSAGE_TYPE: {
DisconnectResponse msg; DisconnectResponse msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
#endif #endif
@@ -53,7 +53,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
} }
case PingRequest::MESSAGE_TYPE: { case PingRequest::MESSAGE_TYPE: {
PingRequest msg; PingRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
#endif #endif
@@ -62,7 +62,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
} }
case PingResponse::MESSAGE_TYPE: { case PingResponse::MESSAGE_TYPE: {
PingResponse msg; PingResponse msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
#endif #endif
@@ -71,7 +71,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
} }
case DeviceInfoRequest::MESSAGE_TYPE: { case DeviceInfoRequest::MESSAGE_TYPE: {
DeviceInfoRequest msg; DeviceInfoRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
#endif #endif
@@ -80,7 +80,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
} }
case ListEntitiesRequest::MESSAGE_TYPE: { case ListEntitiesRequest::MESSAGE_TYPE: {
ListEntitiesRequest msg; ListEntitiesRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
#endif #endif
@@ -89,7 +89,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
} }
case SubscribeStatesRequest::MESSAGE_TYPE: { case SubscribeStatesRequest::MESSAGE_TYPE: {
SubscribeStatesRequest msg; SubscribeStatesRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
#endif #endif
@@ -149,18 +149,20 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
break; break;
} }
#endif #endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
case SubscribeHomeassistantServicesRequest::MESSAGE_TYPE: { case SubscribeHomeassistantServicesRequest::MESSAGE_TYPE: {
SubscribeHomeassistantServicesRequest msg; SubscribeHomeassistantServicesRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
#endif #endif
this->on_subscribe_homeassistant_services_request(msg); this->on_subscribe_homeassistant_services_request(msg);
break; break;
} }
#endif
case GetTimeRequest::MESSAGE_TYPE: { case GetTimeRequest::MESSAGE_TYPE: {
GetTimeRequest msg; GetTimeRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_get_time_request: %s", msg.dump().c_str());
#endif #endif
@@ -179,7 +181,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
case SubscribeHomeAssistantStatesRequest::MESSAGE_TYPE: { case SubscribeHomeAssistantStatesRequest::MESSAGE_TYPE: {
SubscribeHomeAssistantStatesRequest msg; SubscribeHomeAssistantStatesRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
#endif #endif
@@ -388,7 +390,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
case SubscribeBluetoothConnectionsFreeRequest::MESSAGE_TYPE: { case SubscribeBluetoothConnectionsFreeRequest::MESSAGE_TYPE: {
SubscribeBluetoothConnectionsFreeRequest msg; SubscribeBluetoothConnectionsFreeRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str());
#endif #endif
@@ -399,7 +401,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
case UnsubscribeBluetoothLEAdvertisementsRequest::MESSAGE_TYPE: { case UnsubscribeBluetoothLEAdvertisementsRequest::MESSAGE_TYPE: {
UnsubscribeBluetoothLEAdvertisementsRequest msg; UnsubscribeBluetoothLEAdvertisementsRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str());
#endif #endif
@@ -553,7 +555,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: { case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: {
VoiceAssistantConfigurationRequest msg; VoiceAssistantConfigurationRequest msg;
msg.decode(msg_data, msg_size); // Empty message: no decode needed
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
#endif #endif
@@ -639,12 +641,14 @@ void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &
this->subscribe_logs(msg); this->subscribe_logs(msg);
} }
} }
#ifdef USE_API_HOMEASSISTANT_SERVICES
void APIServerConnection::on_subscribe_homeassistant_services_request( void APIServerConnection::on_subscribe_homeassistant_services_request(
const SubscribeHomeassistantServicesRequest &msg) { const SubscribeHomeassistantServicesRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {
this->subscribe_homeassistant_services(msg); this->subscribe_homeassistant_services(msg);
} }
} }
#endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) { void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {

View File

@@ -60,7 +60,9 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){}; virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
#endif #endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){}; virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
#endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){}; virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
@@ -218,7 +220,9 @@ class APIServerConnection : public APIServerConnectionBase {
virtual void list_entities(const ListEntitiesRequest &msg) = 0; virtual void list_entities(const ListEntitiesRequest &msg) = 0;
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0; virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0; virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
#ifdef USE_API_HOMEASSISTANT_SERVICES
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
#endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
#endif #endif
@@ -338,7 +342,9 @@ class APIServerConnection : public APIServerConnectionBase {
void on_list_entities_request(const ListEntitiesRequest &msg) override; void on_list_entities_request(const ListEntitiesRequest &msg) override;
void on_subscribe_states_request(const SubscribeStatesRequest &msg) override; void on_subscribe_states_request(const SubscribeStatesRequest &msg) override;
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override; void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
#ifdef USE_API_HOMEASSISTANT_SERVICES
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
#endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
#endif #endif

View File

@@ -369,11 +369,13 @@ void APIServer::set_password(const std::string &password) { this->password_ = pa
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; } void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
#ifdef USE_API_HOMEASSISTANT_SERVICES
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) { void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
for (auto &client : this->clients_) { for (auto &client : this->clients_) {
client->send_homeassistant_service_call(call); client->send_homeassistant_service_call(call);
} }
} }
#endif
#ifdef USE_API_HOMEASSISTANT_STATES #ifdef USE_API_HOMEASSISTANT_STATES
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute, void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,

View File

@@ -106,7 +106,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
void on_media_player_update(media_player::MediaPlayer *obj) override; void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif #endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
#endif
#ifdef USE_API_SERVICES #ifdef USE_API_SERVICES
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#endif #endif

View File

@@ -137,6 +137,7 @@ class CustomAPIDevice {
} }
#endif #endif
#ifdef USE_API_HOMEASSISTANT_SERVICES
/** Call a Home Assistant service from ESPHome. /** Call a Home Assistant service from ESPHome.
* *
* Usage: * Usage:
@@ -174,7 +175,7 @@ class CustomAPIDevice {
resp.data.emplace_back(); resp.data.emplace_back();
auto &kv = resp.data.back(); auto &kv = resp.data.back();
kv.set_key(StringRef(it.first)); kv.set_key(StringRef(it.first));
kv.set_value(StringRef(it.second)); kv.value = it.second;
} }
global_api_server->send_homeassistant_service_call(resp); global_api_server->send_homeassistant_service_call(resp);
} }
@@ -217,10 +218,11 @@ class CustomAPIDevice {
resp.data.emplace_back(); resp.data.emplace_back();
auto &kv = resp.data.back(); auto &kv = resp.data.back();
kv.set_key(StringRef(it.first)); kv.set_key(StringRef(it.first));
kv.set_value(StringRef(it.second)); kv.value = it.second;
} }
global_api_server->send_homeassistant_service_call(resp); global_api_server->send_homeassistant_service_call(resp);
} }
#endif
}; };
} // namespace esphome::api } // namespace esphome::api

View File

@@ -2,6 +2,7 @@
#include "api_server.h" #include "api_server.h"
#ifdef USE_API #ifdef USE_API
#ifdef USE_API_HOMEASSISTANT_SERVICES
#include "api_pb2.h" #include "api_pb2.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
@@ -69,22 +70,19 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
resp.data.emplace_back(); resp.data.emplace_back();
auto &kv = resp.data.back(); auto &kv = resp.data.back();
kv.set_key(StringRef(it.key)); kv.set_key(StringRef(it.key));
std::string value = it.value.value(x...); kv.value = it.value.value(x...);
kv.set_value(StringRef(value));
} }
for (auto &it : this->data_template_) { for (auto &it : this->data_template_) {
resp.data_template.emplace_back(); resp.data_template.emplace_back();
auto &kv = resp.data_template.back(); auto &kv = resp.data_template.back();
kv.set_key(StringRef(it.key)); kv.set_key(StringRef(it.key));
std::string value = it.value.value(x...); kv.value = it.value.value(x...);
kv.set_value(StringRef(value));
} }
for (auto &it : this->variables_) { for (auto &it : this->variables_) {
resp.variables.emplace_back(); resp.variables.emplace_back();
auto &kv = resp.variables.back(); auto &kv = resp.variables.back();
kv.set_key(StringRef(it.key)); kv.set_key(StringRef(it.key));
std::string value = it.value.value(x...); kv.value = it.value.value(x...);
kv.set_value(StringRef(value));
} }
this->parent_->send_homeassistant_service_call(resp); this->parent_->send_homeassistant_service_call(resp);
} }
@@ -100,3 +98,4 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
} // namespace esphome::api } // namespace esphome::api
#endif #endif
#endif

View File

@@ -35,11 +35,10 @@ namespace esphome::api {
* *
* Unsafe Patterns (WILL cause crashes/corruption): * Unsafe Patterns (WILL cause crashes/corruption):
* 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value * 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value
* 2. Optional values: msg.set_field(StringRef(optional.value())) // value() returns a copy * 2. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary
* 3. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary
* *
* For unsafe patterns, store in a local variable first: * For unsafe patterns, store in a local variable first:
* std::string temp = optional.value(); // or get_string() or str1 + str2 * std::string temp = get_string(); // or str1 + str2
* msg.set_field(StringRef(temp)); * msg.set_field(StringRef(temp));
* *
* The send_*_response pattern ensures proper lifetime management by encoding * The send_*_response pattern ensures proper lifetime management by encoding
@@ -334,13 +333,16 @@ class ProtoWriteBuffer {
std::vector<uint8_t> *buffer_; std::vector<uint8_t> *buffer_;
}; };
// Forward declaration
class ProtoSize;
class ProtoMessage { class ProtoMessage {
public: public:
virtual ~ProtoMessage() = default; virtual ~ProtoMessage() = default;
// Default implementation for messages with no fields // Default implementation for messages with no fields
virtual void encode(ProtoWriteBuffer buffer) const {} virtual void encode(ProtoWriteBuffer buffer) const {}
// Default implementation for messages with no fields // Default implementation for messages with no fields
virtual void calculate_size(uint32_t &total_size) const {} virtual void calculate_size(ProtoSize &size) const {}
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
std::string dump() const; std::string dump() const;
virtual void dump_to(std::string &out) const = 0; virtual void dump_to(std::string &out) const = 0;
@@ -361,24 +363,32 @@ class ProtoDecodableMessage : public ProtoMessage {
}; };
class ProtoSize { class ProtoSize {
private:
uint32_t total_size_ = 0;
public: public:
/** /**
* @brief ProtoSize class for Protocol Buffer serialization size calculation * @brief ProtoSize class for Protocol Buffer serialization size calculation
* *
* This class provides static methods to calculate the exact byte counts needed * This class provides methods to calculate the exact byte counts needed
* for encoding various Protocol Buffer field types. All methods are designed to be * for encoding various Protocol Buffer field types. The class now uses an
* efficient for the common case where many fields have default values. * object-based approach to reduce parameter passing overhead while keeping
* varint calculation methods static for external use.
* *
* Implements Protocol Buffer encoding size calculation according to: * Implements Protocol Buffer encoding size calculation according to:
* https://protobuf.dev/programming-guides/encoding/ * https://protobuf.dev/programming-guides/encoding/
* *
* Key features: * Key features:
* - Object-based approach reduces flash usage by eliminating parameter passing
* - Early-return optimization for zero/default values * - Early-return optimization for zero/default values
* - Direct total_size updates to avoid unnecessary additions * - Static varint methods for external callers
* - Specialized handling for different field types according to protobuf spec * - Specialized handling for different field types according to protobuf spec
* - Templated helpers for repeated fields and messages
*/ */
ProtoSize() = default;
uint32_t get_size() const { return total_size_; }
/** /**
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint * @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
* *
@@ -479,9 +489,7 @@ class ProtoSize {
* @brief Common parameters for all add_*_field methods * @brief Common parameters for all add_*_field methods
* *
* All add_*_field methods follow these common patterns: * All add_*_field methods follow these common patterns:
* * * @param field_id_size Pre-calculated size of the field ID in bytes
* @param total_size Reference to the total message size to update
* @param field_id_size Pre-calculated size of the field ID in bytes
* @param value The value to calculate size for (type varies) * @param value The value to calculate size for (type varies)
* @param force Whether to calculate size even if the value is default/zero/empty * @param force Whether to calculate size even if the value is default/zero/empty
* *
@@ -494,85 +502,63 @@ class ProtoSize {
/** /**
* @brief Calculates and adds the size of an int32 field to the total message size * @brief Calculates and adds the size of an int32 field to the total message size
*/ */
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) { inline void add_int32(uint32_t field_id_size, int32_t value) {
// Skip calculation if value is zero if (value != 0) {
if (value == 0) { add_int32_force(field_id_size, value);
return; // No need to update total_size
}
// Calculate and directly add to total_size
if (value < 0) {
// Negative values are encoded as 10-byte varints in protobuf
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
} }
} }
/** /**
* @brief Calculates and adds the size of an int32 field to the total message size (repeated field version) * @brief Calculates and adds the size of an int32 field to the total message size (force version)
*/ */
static inline void add_int32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) { inline void add_int32_force(uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields // Always calculate size when forced
if (value < 0) { // Negative values are encoded as 10-byte varints in protobuf
// Negative values are encoded as 10-byte varints in protobuf total_size_ += field_id_size + (value < 0 ? 10 : varint(static_cast<uint32_t>(value)));
total_size += field_id_size + 10;
} else {
// For non-negative values, use the standard varint size
total_size += field_id_size + varint(static_cast<uint32_t>(value));
}
} }
/** /**
* @brief Calculates and adds the size of a uint32 field to the total message size * @brief Calculates and adds the size of a uint32 field to the total message size
*/ */
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { inline void add_uint32(uint32_t field_id_size, uint32_t value) {
// Skip calculation if value is zero if (value != 0) {
if (value == 0) { add_uint32_force(field_id_size, value);
return; // No need to update total_size
} }
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
} }
/** /**
* @brief Calculates and adds the size of a uint32 field to the total message size (repeated field version) * @brief Calculates and adds the size of a uint32 field to the total message size (force version)
*/ */
static inline void add_uint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { inline void add_uint32_force(uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields // Always calculate size when force is true
total_size += field_id_size + varint(value); total_size_ += field_id_size + varint(value);
} }
/** /**
* @brief Calculates and adds the size of a boolean field to the total message size * @brief Calculates and adds the size of a boolean field to the total message size
*/ */
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value) { inline void add_bool(uint32_t field_id_size, bool value) {
// Skip calculation if value is false if (value) {
if (!value) { // Boolean fields always use 1 byte when true
return; // No need to update total_size total_size_ += field_id_size + 1;
} }
// Boolean fields always use 1 byte when true
total_size += field_id_size + 1;
} }
/** /**
* @brief Calculates and adds the size of a boolean field to the total message size (repeated field version) * @brief Calculates and adds the size of a boolean field to the total message size (force version)
*/ */
static inline void add_bool_field_repeated(uint32_t &total_size, uint32_t field_id_size, bool value) { inline void add_bool_force(uint32_t field_id_size, bool value) {
// Always calculate size for repeated fields // Always calculate size when force is true
// Boolean fields always use 1 byte // Boolean fields always use 1 byte
total_size += field_id_size + 1; total_size_ += field_id_size + 1;
} }
/** /**
* @brief Calculates and adds the size of a float field to the total message size * @brief Calculates and adds the size of a float field to the total message size
*/ */
static inline void add_float_field(uint32_t &total_size, uint32_t field_id_size, float value) { inline void add_float(uint32_t field_id_size, float value) {
if (value != 0.0f) { if (value != 0.0f) {
total_size += field_id_size + 4; total_size_ += field_id_size + 4;
} }
} }
@@ -582,9 +568,9 @@ class ProtoSize {
/** /**
* @brief Calculates and adds the size of a fixed32 field to the total message size * @brief Calculates and adds the size of a fixed32 field to the total message size
*/ */
static inline void add_fixed32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) { inline void add_fixed32(uint32_t field_id_size, uint32_t value) {
if (value != 0) { if (value != 0) {
total_size += field_id_size + 4; total_size_ += field_id_size + 4;
} }
} }
@@ -594,149 +580,104 @@ class ProtoSize {
/** /**
* @brief Calculates and adds the size of a sfixed32 field to the total message size * @brief Calculates and adds the size of a sfixed32 field to the total message size
*/ */
static inline void add_sfixed32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) { inline void add_sfixed32(uint32_t field_id_size, int32_t value) {
if (value != 0) { if (value != 0) {
total_size += field_id_size + 4; total_size_ += field_id_size + 4;
} }
} }
// NOTE: add_sfixed64_field removed - wire type 1 (64-bit: sfixed64) not supported // NOTE: add_sfixed64_field removed - wire type 1 (64-bit: sfixed64) not supported
// to reduce overhead on embedded systems // to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of an enum field to the total message size
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Skip calculation if value is zero
if (value == 0) {
return; // No need to update total_size
}
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/**
* @brief Calculates and adds the size of an enum field to the total message size (repeated field version)
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
// Enums are encoded as uint32
total_size += field_id_size + varint(value);
}
/** /**
* @brief Calculates and adds the size of a sint32 field to the total message size * @brief Calculates and adds the size of a sint32 field to the total message size
* *
* Sint32 fields use ZigZag encoding, which is more efficient for negative values. * Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/ */
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) { inline void add_sint32(uint32_t field_id_size, int32_t value) {
// Skip calculation if value is zero if (value != 0) {
if (value == 0) { add_sint32_force(field_id_size, value);
return; // No need to update total_size
} }
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag);
} }
/** /**
* @brief Calculates and adds the size of a sint32 field to the total message size (repeated field version) * @brief Calculates and adds the size of a sint32 field to the total message size (force version)
* *
* Sint32 fields use ZigZag encoding, which is more efficient for negative values. * Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/ */
static inline void add_sint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) { inline void add_sint32_force(uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields // Always calculate size when force is true
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31) // ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31)); uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
total_size += field_id_size + varint(zigzag); total_size_ += field_id_size + varint(zigzag);
} }
/** /**
* @brief Calculates and adds the size of an int64 field to the total message size * @brief Calculates and adds the size of an int64 field to the total message size
*/ */
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value) { inline void add_int64(uint32_t field_id_size, int64_t value) {
// Skip calculation if value is zero if (value != 0) {
if (value == 0) { add_int64_force(field_id_size, value);
return; // No need to update total_size
} }
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
} }
/** /**
* @brief Calculates and adds the size of an int64 field to the total message size (repeated field version) * @brief Calculates and adds the size of an int64 field to the total message size (force version)
*/ */
static inline void add_int64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) { inline void add_int64_force(uint32_t field_id_size, int64_t value) {
// Always calculate size for repeated fields // Always calculate size when force is true
total_size += field_id_size + varint(value); total_size_ += field_id_size + varint(value);
} }
/** /**
* @brief Calculates and adds the size of a uint64 field to the total message size * @brief Calculates and adds the size of a uint64 field to the total message size
*/ */
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value) { inline void add_uint64(uint32_t field_id_size, uint64_t value) {
// Skip calculation if value is zero if (value != 0) {
if (value == 0) { add_uint64_force(field_id_size, value);
return; // No need to update total_size
} }
// Calculate and directly add to total_size
total_size += field_id_size + varint(value);
} }
/** /**
* @brief Calculates and adds the size of a uint64 field to the total message size (repeated field version) * @brief Calculates and adds the size of a uint64 field to the total message size (force version)
*/ */
static inline void add_uint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint64_t value) { inline void add_uint64_force(uint32_t field_id_size, uint64_t value) {
// Always calculate size for repeated fields // Always calculate size when force is true
total_size += field_id_size + varint(value); total_size_ += field_id_size + varint(value);
} }
// NOTE: sint64 support functions (add_sint64_field, add_sint64_field_repeated) removed // NOTE: sint64 support functions (add_sint64_field, add_sint64_field_force) removed
// sint64 type is not supported by ESPHome API to reduce overhead on embedded systems // sint64 type is not supported by ESPHome API to reduce overhead on embedded systems
/** /**
* @brief Calculates and adds the size of a string field using length * @brief Calculates and adds the size of a length-delimited field (string/bytes) to the total message size
*/ */
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, size_t len) { inline void add_length(uint32_t field_id_size, size_t len) {
// Skip calculation if string is empty if (len != 0) {
if (len == 0) { add_length_force(field_id_size, len);
return; // No need to update total_size
} }
// Field ID + length varint + string bytes
total_size += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
} }
/** /**
* @brief Calculates and adds the size of a string/bytes field to the total message size (repeated field version) * @brief Calculates and adds the size of a length-delimited field (string/bytes) to the total message size (repeated
* field version)
*/ */
static inline void add_string_field_repeated(uint32_t &total_size, uint32_t field_id_size, const std::string &str) { inline void add_length_force(uint32_t field_id_size, size_t len) {
// Always calculate size for repeated fields // Always calculate size when force is true
const uint32_t str_size = static_cast<uint32_t>(str.size());
total_size += field_id_size + varint(str_size) + str_size;
}
/**
* @brief Calculates and adds the size of a bytes field to the total message size
*/
static inline void add_bytes_field(uint32_t &total_size, uint32_t field_id_size, size_t len) {
// Skip calculation if bytes is empty
if (len == 0) {
return; // No need to update total_size
}
// Field ID + length varint + data bytes // Field ID + length varint + data bytes
total_size += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len); total_size_ += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
} }
/**
* @brief Adds a pre-calculated size directly to the total
*
* This is used when we can calculate the total size by multiplying the number
* of elements by the bytes per element (for repeated fixed-size types like float, fixed32, etc.)
*
* @param size The pre-calculated total size to add
*/
inline void add_precalculated_size(uint32_t size) { total_size_ += size; }
/** /**
* @brief Calculates and adds the size of a nested message field to the total message size * @brief Calculates and adds the size of a nested message field to the total message size
* *
@@ -745,26 +686,21 @@ class ProtoSize {
* *
* @param nested_size The pre-calculated size of the nested message * @param nested_size The pre-calculated size of the nested message
*/ */
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) { inline void add_message_field(uint32_t field_id_size, uint32_t nested_size) {
// Skip calculation if nested message is empty if (nested_size != 0) {
if (nested_size == 0) { add_message_field_force(field_id_size, nested_size);
return; // No need to update total_size
} }
// Calculate and directly add to total_size
// Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size;
} }
/** /**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version) * @brief Calculates and adds the size of a nested message field to the total message size (force version)
* *
* @param nested_size The pre-calculated size of the nested message * @param nested_size The pre-calculated size of the nested message
*/ */
static inline void add_message_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) { inline void add_message_field_force(uint32_t field_id_size, uint32_t nested_size) {
// Always calculate size for repeated fields // Always calculate size when force is true
// Field ID + length varint + nested message content // Field ID + length varint + nested message content
total_size += field_id_size + varint(nested_size) + nested_size; total_size_ += field_id_size + varint(nested_size) + nested_size;
} }
/** /**
@@ -776,26 +712,29 @@ class ProtoSize {
* *
* @param message The nested message object * @param message The nested message object
*/ */
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message) { inline void add_message_object(uint32_t field_id_size, const ProtoMessage &message) {
uint32_t nested_size = 0; // Calculate nested message size by creating a temporary ProtoSize
message.calculate_size(nested_size); ProtoSize nested_calc;
message.calculate_size(nested_calc);
uint32_t nested_size = nested_calc.get_size();
// Use the base implementation with the calculated nested_size // Use the base implementation with the calculated nested_size
add_message_field(total_size, field_id_size, nested_size); add_message_field(field_id_size, nested_size);
} }
/** /**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version) * @brief Calculates and adds the size of a nested message field to the total message size (force version)
* *
* @param message The nested message object * @param message The nested message object
*/ */
static inline void add_message_object_repeated(uint32_t &total_size, uint32_t field_id_size, inline void add_message_object_force(uint32_t field_id_size, const ProtoMessage &message) {
const ProtoMessage &message) { // Calculate nested message size by creating a temporary ProtoSize
uint32_t nested_size = 0; ProtoSize nested_calc;
message.calculate_size(nested_size); message.calculate_size(nested_calc);
uint32_t nested_size = nested_calc.get_size();
// Use the base implementation with the calculated nested_size // Use the base implementation with the calculated nested_size
add_message_field_repeated(total_size, field_id_size, nested_size); add_message_field_force(field_id_size, nested_size);
} }
/** /**
@@ -808,16 +747,15 @@ class ProtoSize {
* @param messages Vector of message objects * @param messages Vector of message objects
*/ */
template<typename MessageType> template<typename MessageType>
static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size, inline void add_repeated_message(uint32_t field_id_size, const std::vector<MessageType> &messages) {
const std::vector<MessageType> &messages) {
// Skip if the vector is empty // Skip if the vector is empty
if (messages.empty()) { if (messages.empty()) {
return; return;
} }
// Use the repeated field version for all messages // Use the force version for all messages in the repeated field
for (const auto &message : messages) { for (const auto &message : messages) {
add_message_object_repeated(total_size, field_id_size, message); add_message_object_force(field_id_size, message);
} }
} }
}; };
@@ -827,8 +765,9 @@ inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessa
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
// Calculate the message size first // Calculate the message size first
uint32_t msg_length_bytes = 0; ProtoSize msg_size;
value.calculate_size(msg_length_bytes); value.calculate_size(msg_size);
uint32_t msg_length_bytes = msg_size.get_size();
// Calculate how many bytes the length varint needs // Calculate how many bytes the length varint needs
uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes); uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes);
@@ -877,8 +816,9 @@ class ProtoService {
// Optimized method that pre-allocates buffer based on message size // Optimized method that pre-allocates buffer based on message size
bool send_message_(const ProtoMessage &msg, uint8_t message_type) { bool send_message_(const ProtoMessage &msg, uint8_t message_type) {
uint32_t msg_size = 0; ProtoSize size;
msg.calculate_size(msg_size); msg.calculate_size(size);
uint32_t msg_size = size.get_size();
// Create a pre-sized buffer // Create a pre-sized buffer
auto buffer = this->create_buffer(msg_size); auto buffer = this->create_buffer(msg_size);

View File

@@ -516,6 +516,7 @@ def binary_sensor_schema(
icon: str = cv.UNDEFINED, icon: str = cv.UNDEFINED,
entity_category: str = cv.UNDEFINED, entity_category: str = cv.UNDEFINED,
device_class: str = cv.UNDEFINED, device_class: str = cv.UNDEFINED,
filters: list = cv.UNDEFINED,
) -> cv.Schema: ) -> cv.Schema:
schema = {} schema = {}
@@ -527,6 +528,7 @@ def binary_sensor_schema(
(CONF_ICON, icon, cv.icon), (CONF_ICON, icon, cv.icon),
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_DEVICE_CLASS, device_class, validate_device_class), (CONF_DEVICE_CLASS, device_class, validate_device_class),
(CONF_FILTERS, filters, validate_filters),
]: ]:
if default is not cv.UNDEFINED: if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator schema[cv.Optional(key, default=default)] = validator

View File

@@ -80,15 +80,10 @@ void BluetoothConnection::send_service_for_discovery_() {
&service_result, &service_count, this->send_service_); &service_result, &service_count, this->send_service_);
this->send_service_++; this->send_service_++;
if (service_status != ESP_GATT_OK) { if (service_status != ESP_GATT_OK || service_count == 0) {
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d", this->connection_index_, ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service %s, status=%d, service_count=%d, offset=%d",
this->address_str().c_str(), this->send_service_ - 1, service_status); this->connection_index_, this->address_str().c_str(), service_status != ESP_GATT_OK ? "error" : "missing",
return; service_status, service_count, this->send_service_ - 1);
}
if (service_count == 0) {
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d", this->connection_index_,
this->address_str().c_str(), service_count);
return; return;
} }
@@ -104,15 +99,20 @@ void BluetoothConnection::send_service_for_discovery_() {
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC, esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
service_result.start_handle, service_result.end_handle, 0, &total_char_count); service_result.start_handle, service_result.end_handle, 0, &total_char_count);
if (char_count_status == ESP_GATT_OK && total_char_count > 0) { if (char_count_status != ESP_GATT_OK) {
// Only reserve if we successfully got a count
service_resp.characteristics.reserve(total_char_count);
} else if (char_count_status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_, ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_,
this->address_str().c_str(), char_count_status); this->address_str().c_str(), char_count_status);
return;
} }
// Now process characteristics if (total_char_count == 0) {
// No characteristics, just send the service response
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
return;
}
// Reserve space and process characteristics
service_resp.characteristics.reserve(total_char_count);
uint16_t char_offset = 0; uint16_t char_offset = 0;
esp_gattc_char_elem_t char_result; esp_gattc_char_elem_t char_result;
while (true) { // characteristics while (true) { // characteristics
@@ -126,7 +126,7 @@ void BluetoothConnection::send_service_for_discovery_() {
if (char_status != ESP_GATT_OK) { if (char_status != ESP_GATT_OK) {
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_, ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
this->address_str().c_str(), char_status); this->address_str().c_str(), char_status);
break; return;
} }
if (char_count == 0) { if (char_count == 0) {
break; break;
@@ -141,19 +141,21 @@ void BluetoothConnection::send_service_for_discovery_() {
// Get the number of descriptors directly with one call // Get the number of descriptors directly with one call
uint16_t total_desc_count = 0; uint16_t total_desc_count = 0;
esp_gatt_status_t desc_count_status = esp_gatt_status_t desc_count_status = esp_ble_gattc_get_attr_count(
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, char_result.char_handle, this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
service_result.end_handle, 0, &total_desc_count);
if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) { if (desc_count_status != ESP_GATT_OK) {
// Only reserve if we successfully got a count
characteristic_resp.descriptors.reserve(total_desc_count);
} else if (desc_count_status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_, ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_,
this->address_str().c_str(), char_result.char_handle, desc_count_status); this->address_str().c_str(), char_result.char_handle, desc_count_status);
return;
}
if (total_desc_count == 0) {
// No descriptors, continue to next characteristic
continue;
} }
// Now process descriptors // Reserve space and process descriptors
characteristic_resp.descriptors.reserve(total_desc_count);
uint16_t desc_offset = 0; uint16_t desc_offset = 0;
esp_gattc_descr_elem_t desc_result; esp_gattc_descr_elem_t desc_result;
while (true) { // descriptors while (true) { // descriptors
@@ -166,10 +168,10 @@ void BluetoothConnection::send_service_for_discovery_() {
if (desc_status != ESP_GATT_OK) { if (desc_status != ESP_GATT_OK) {
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_, ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_,
this->address_str().c_str(), desc_status); this->address_str().c_str(), desc_status);
break; return;
} }
if (desc_count == 0) { if (desc_count == 0) {
break; break; // No more descriptors
} }
characteristic_resp.descriptors.emplace_back(); characteristic_resp.descriptors.emplace_back();

View File

@@ -5,6 +5,13 @@
#include <set> #include <set>
namespace esphome { namespace esphome {
#ifdef USE_API
namespace api {
class APIConnection;
} // namespace api
#endif
namespace climate { namespace climate {
/** This class contains all static data for climate devices. /** This class contains all static data for climate devices.
@@ -173,6 +180,23 @@ class ClimateTraits {
void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; } void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; }
protected: protected:
#ifdef USE_API
// The API connection is a friend class to access internal methods
friend class api::APIConnection;
// These methods return references to internal data structures.
// They are used by the API to avoid copying data when encoding messages.
// Warning: Do not use these methods outside of the API connection code.
// They return references to internal data that can be invalidated.
const std::set<ClimateMode> &get_supported_modes_for_api_() const { return this->supported_modes_; }
const std::set<ClimateFanMode> &get_supported_fan_modes_for_api_() const { return this->supported_fan_modes_; }
const std::set<std::string> &get_supported_custom_fan_modes_for_api_() const {
return this->supported_custom_fan_modes_;
}
const std::set<climate::ClimatePreset> &get_supported_presets_for_api_() const { return this->supported_presets_; }
const std::set<std::string> &get_supported_custom_presets_for_api_() const { return this->supported_custom_presets_; }
const std::set<ClimateSwingMode> &get_supported_swing_modes_for_api_() const { return this->supported_swing_modes_; }
#endif
void set_mode_support_(climate::ClimateMode mode, bool supported) { void set_mode_support_(climate::ClimateMode mode, bool supported) {
if (supported) { if (supported) {
this->supported_modes_.insert(mode); this->supported_modes_.insert(mode);

View File

@@ -313,7 +313,7 @@ def _format_framework_espidf_version(
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 2, 1) RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 2, 1)
# The platform-espressif32 version to use for arduino frameworks # The platform-espressif32 version to use for arduino frameworks
# - https://github.com/pioarduino/platform-espressif32/releases # - https://github.com/pioarduino/platform-espressif32/releases
ARDUINO_PLATFORM_VERSION = cv.Version(54, 3, 21) ARDUINO_PLATFORM_VERSION = cv.Version(54, 3, 21, "1")
# The default/recommended esp-idf framework version # The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases # - https://github.com/espressif/esp-idf/releases
@@ -322,7 +322,7 @@ RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 4, 2)
# The platformio/espressif32 version to use for esp-idf frameworks # The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases # - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ESP_IDF_PLATFORM_VERSION = cv.Version(54, 3, 21) ESP_IDF_PLATFORM_VERSION = cv.Version(54, 3, 21, "1")
# List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions # List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions
SUPPORTED_PLATFORMIO_ESP_IDF_5X = [ SUPPORTED_PLATFORMIO_ESP_IDF_5X = [
@@ -468,10 +468,10 @@ def _parse_platform_version(value):
try: try:
ver = cv.Version.parse(cv.version_number(value)) ver = cv.Version.parse(cv.version_number(value))
if ver.major >= 50: # a pioarduino version if ver.major >= 50: # a pioarduino version
if "-" in value: release = f"{ver.major}.{ver.minor:02d}.{ver.patch:02d}"
# maybe a release candidate?...definitely not our default, just use it as-is... if ver.extra:
return f"https://github.com/pioarduino/platform-espressif32/releases/download/{value}/platform-espressif32.zip" release += f"-{ver.extra}"
return f"https://github.com/pioarduino/platform-espressif32/releases/download/{ver.major}.{ver.minor:02d}.{ver.patch:02d}/platform-espressif32.zip" return f"https://github.com/pioarduino/platform-espressif32/releases/download/{release}/platform-espressif32.zip"
# if platform version is a valid version constraint, prefix the default package # if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value) cv.platformio_version_constraint(value)
return f"platformio/espressif32@{value}" return f"platformio/espressif32@{value}"
@@ -571,6 +571,8 @@ CONF_SDKCONFIG_OPTIONS = "sdkconfig_options"
CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server" CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server"
CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries" CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries"
CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface" CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface"
CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING = "enable_lwip_tcpip_core_locking"
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY = "enable_lwip_check_thread_safety"
def _validate_idf_component(config: ConfigType) -> ConfigType: def _validate_idf_component(config: ConfigType) -> ConfigType:
@@ -619,6 +621,12 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
cv.Optional( cv.Optional(
CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False CONF_ENABLE_LWIP_BRIDGE_INTERFACE, default=False
): cv.boolean, ): cv.boolean,
cv.Optional(
CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, default=True
): cv.boolean,
cv.Optional(
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True
): cv.boolean,
} }
), ),
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
@@ -785,6 +793,18 @@ async def to_code(config):
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False): if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0) add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0)
# Apply LWIP core locking for better socket performance
# This is already enabled by default in Arduino framework, where it provides
# significant performance benefits. Our benchmarks show socket operations are
# 24-200% faster with core locking enabled:
# - select() on 4 sockets: ~190μs (Arduino/core locking) vs ~235μs (ESP-IDF default)
# - Up to 200% slower under load when all operations queue through tcpip_thread
# Enabling this makes ESP-IDF socket performance match Arduino framework.
if advanced.get(CONF_ENABLE_LWIP_TCPIP_CORE_LOCKING, True):
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_CORE_LOCKING", True)
if advanced.get(CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, True):
add_idf_sdkconfig_option("CONFIG_LWIP_CHECK_THREAD_SAFETY", True)
cg.add_platformio_option("board_build.partitions", "partitions.csv") cg.add_platformio_option("board_build.partitions", "partitions.csv")
if CONF_PARTITIONS in config: if CONF_PARTITIONS in config:
add_extra_build_file( add_extra_build_file(

View File

@@ -1,10 +1,11 @@
Import("env") Import("env") # noqa: F821
import itertools # noqa: E402
import json # noqa: E402
import os # noqa: E402
import pathlib # noqa: E402
import shutil # noqa: E402
import os
import json
import shutil
import pathlib
import itertools
def merge_factory_bin(source, target, env): def merge_factory_bin(source, target, env):
""" """
@@ -25,7 +26,9 @@ def merge_factory_bin(source, target, env):
try: try:
with flasher_args_path.open() as f: with flasher_args_path.open() as f:
flash_data = json.load(f) flash_data = json.load(f)
for addr, fname in sorted(flash_data["flash_files"].items(), key=lambda kv: int(kv[0], 16)): for addr, fname in sorted(
flash_data["flash_files"].items(), key=lambda kv: int(kv[0], 16)
):
file_path = pathlib.Path(fname) file_path = pathlib.Path(fname)
if file_path.exists(): if file_path.exists():
sections.append((addr, str(file_path))) sections.append((addr, str(file_path)))
@@ -40,20 +43,27 @@ def merge_factory_bin(source, target, env):
if flash_images: if flash_images:
print("Using FLASH_EXTRA_IMAGES from PlatformIO environment") print("Using FLASH_EXTRA_IMAGES from PlatformIO environment")
# flatten any nested lists # flatten any nested lists
flat = list(itertools.chain.from_iterable( flat = list(
x if isinstance(x, (list, tuple)) else [x] for x in flash_images itertools.chain.from_iterable(
)) x if isinstance(x, (list, tuple)) else [x] for x in flash_images
)
)
entries = [env.subst(x) for x in flat] entries = [env.subst(x) for x in flat]
for i in range(0, len(entries) - 1, 2): for i in range(0, len(entries) - 1, 2):
addr, fname = entries[i], entries[i + 1] addr, fname = entries[i], entries[i + 1]
if isinstance(fname, (list, tuple)): if isinstance(fname, (list, tuple)):
print(f"Warning: Skipping malformed FLASH_EXTRA_IMAGES entry: {fname}") print(
f"Warning: Skipping malformed FLASH_EXTRA_IMAGES entry: {fname}"
)
continue continue
file_path = pathlib.Path(str(fname)) file_path = pathlib.Path(str(fname))
if file_path.exists(): if file_path.exists():
sections.append((addr, str(file_path))) sections.append((addr, file_path))
else: else:
print(f"Info: {file_path.name} not found — skipping") print(f"Info: {file_path.name} not found — skipping")
if sections:
# Append main firmware to sections
sections.append(("0x10000", firmware_path))
# 3. Final fallback: guess standard image locations # 3. Final fallback: guess standard image locations
if not sections: if not sections:
@@ -62,11 +72,11 @@ def merge_factory_bin(source, target, env):
("0x0", build_dir / "bootloader" / "bootloader.bin"), ("0x0", build_dir / "bootloader" / "bootloader.bin"),
("0x8000", build_dir / "partition_table" / "partition-table.bin"), ("0x8000", build_dir / "partition_table" / "partition-table.bin"),
("0xe000", build_dir / "ota_data_initial.bin"), ("0xe000", build_dir / "ota_data_initial.bin"),
("0x10000", firmware_path) ("0x10000", firmware_path),
] ]
for addr, file_path in guesses: for addr, file_path in guesses:
if file_path.exists(): if file_path.exists():
sections.append((addr, str(file_path))) sections.append((addr, file_path))
else: else:
print(f"Info: {file_path.name} not found — skipping") print(f"Info: {file_path.name} not found — skipping")
@@ -76,21 +86,25 @@ def merge_factory_bin(source, target, env):
return return
output_path = firmware_path.with_suffix(".factory.bin") output_path = firmware_path.with_suffix(".factory.bin")
python_exe = f'"{env.subst("$PYTHONEXE")}"'
cmd = [ cmd = [
"--chip", chip, python_exe,
"-m",
"esptool",
"--chip",
chip,
"merge_bin", "merge_bin",
"--flash_size", flash_size, "--flash_size",
"--output", str(output_path) flash_size,
"--output",
str(output_path),
] ]
for addr, file_path in sections: for addr, file_path in sections:
cmd += [addr, file_path] cmd += [addr, str(file_path)]
print(f"Merging binaries into {output_path}") print(f"Merging binaries into {output_path}")
result = env.Execute( result = env.Execute(
env.VerboseAction( env.VerboseAction(" ".join(cmd), "Merging binaries with esptool")
f"{env.subst('$PYTHONEXE')} -m esptool " + " ".join(cmd),
"Merging binaries with esptool"
)
) )
if result == 0: if result == 0:
@@ -98,6 +112,7 @@ def merge_factory_bin(source, target, env):
else: else:
print(f"Error: esptool merge_bin failed with code {result}") print(f"Error: esptool merge_bin failed with code {result}")
def esp32_copy_ota_bin(source, target, env): def esp32_copy_ota_bin(source, target, env):
""" """
Copy the main firmware to a .ota.bin file for compatibility with ESPHome OTA tools. Copy the main firmware to a .ota.bin file for compatibility with ESPHome OTA tools.
@@ -107,6 +122,7 @@ def esp32_copy_ota_bin(source, target, env):
shutil.copyfile(firmware_name, new_file_name) shutil.copyfile(firmware_name, new_file_name)
print(f"Copied firmware to {new_file_name}") print(f"Copied firmware to {new_file_name}")
# Run merge first, then ota copy second # Run merge first, then ota copy second
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821

View File

@@ -5,10 +5,23 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <esp_gap_ble_api.h>
#include <esp_gatt_defs.h>
namespace esphome { namespace esphome {
namespace esp32_ble_client { namespace esp32_ble_client {
static const char *const TAG = "esp32_ble_client"; static const char *const TAG = "esp32_ble_client";
// Connection interval defaults matching ESP-IDF's BTM_BLE_CONN_INT_*_DEF
static const uint16_t DEFAULT_MIN_CONN_INTERVAL = 0x0A; // 10 * 1.25ms = 12.5ms
static const uint16_t DEFAULT_MAX_CONN_INTERVAL = 0x0C; // 12 * 1.25ms = 15ms
static const uint16_t DEFAULT_CONN_TIMEOUT = 600; // 600 * 10ms = 6s
// Fastest connection parameters for devices with short discovery timeouts
static const uint16_t FAST_MIN_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms (BLE minimum)
static const uint16_t FAST_MAX_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms
static const uint16_t FAST_CONN_TIMEOUT = 1000; // 1000 * 10ms = 10s
static const esp_bt_uuid_t NOTIFY_DESC_UUID = { static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
.len = ESP_UUID_LEN_16, .len = ESP_UUID_LEN_16,
.uuid = .uuid =
@@ -129,6 +142,7 @@ void BLEClientBase::connect() {
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(), ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),
this->remote_addr_type_); this->remote_addr_type_);
this->paired_ = false; this->paired_ = false;
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
if (ret) { if (ret) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(), ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
@@ -136,6 +150,22 @@ void BLEClientBase::connect() {
this->set_state(espbt::ClientState::IDLE); this->set_state(espbt::ClientState::IDLE);
} else { } else {
this->set_state(espbt::ClientState::CONNECTING); this->set_state(espbt::ClientState::CONNECTING);
// For connections without cache, set fast connection parameters after initiating connection
// This ensures service discovery completes within the 10-second timeout that
// some devices like HomeKit BLE sensors enforce
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
auto param_ret =
esp_ble_gap_set_prefer_conn_params(this->remote_bda_, FAST_MIN_CONN_INTERVAL, FAST_MAX_CONN_INTERVAL,
0, // latency: 0
FAST_CONN_TIMEOUT);
if (param_ret != ESP_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_,
this->address_str_.c_str(), param_ret);
} else {
ESP_LOGD(TAG, "[%d] [%s] Set fast conn params", this->connection_index_, this->address_str_.c_str());
}
}
} }
} }
@@ -278,12 +308,14 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->address_str_.c_str(), ret); this->address_str_.c_str(), ret);
} }
this->set_state(espbt::ClientState::CONNECTED); this->set_state(espbt::ClientState::CONNECTED);
ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) { if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); ESP_LOGI(TAG, "[%d] [%s] Using cached services", this->connection_index_, this->address_str_.c_str());
// only set our state, subclients might have more stuff to do yet. // only set our state, subclients might have more stuff to do yet.
this->state_ = espbt::ClientState::ESTABLISHED; this->state_ = espbt::ClientState::ESTABLISHED;
break; break;
} }
ESP_LOGD(TAG, "[%d] [%s] Searching for services", this->connection_index_, this->address_str_.c_str());
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr); esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
break; break;
} }
@@ -296,8 +328,15 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
case ESP_GATTC_DISCONNECT_EVT: { case ESP_GATTC_DISCONNECT_EVT: {
if (!this->check_addr(param->disconnect.remote_bda)) if (!this->check_addr(param->disconnect.remote_bda))
return false; return false;
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_, // Check if we were disconnected while waiting for service discovery
this->address_str_.c_str(), param->disconnect.reason); if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
this->state_ == espbt::ClientState::CONNECTED) {
ESP_LOGW(TAG, "[%d] [%s] Disconnected by remote during service discovery", this->connection_index_,
this->address_str_.c_str());
} else {
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_,
this->address_str_.c_str(), param->disconnect.reason);
}
this->release_services(); this->release_services();
this->set_state(espbt::ClientState::IDLE); this->set_state(espbt::ClientState::IDLE);
break; break;
@@ -353,7 +392,22 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_, ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_,
this->address_str_.c_str(), svc->start_handle, svc->end_handle); this->address_str_.c_str(), svc->start_handle, svc->end_handle);
} }
ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str()); ESP_LOGI(TAG, "[%d] [%s] Service discovery complete", this->connection_index_, this->address_str_.c_str());
// For non-cached connections, restore default connection parameters after service discovery
// Now that we've discovered all services, we can use more balanced parameters
// that save power and reduce interference
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
esp_ble_conn_update_params_t conn_params = {{0}};
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
conn_params.min_int = DEFAULT_MIN_CONN_INTERVAL;
conn_params.max_int = DEFAULT_MAX_CONN_INTERVAL;
conn_params.latency = 0;
conn_params.timeout = DEFAULT_CONN_TIMEOUT;
ESP_LOGD(TAG, "[%d] [%s] Restored default conn params", this->connection_index_, this->address_str_.c_str());
esp_ble_gap_update_conn_params(&conn_params);
}
this->state_ = espbt::ClientState::ESTABLISHED; this->state_ = espbt::ClientState::ESTABLISHED;
break; break;
} }

View File

@@ -4,6 +4,13 @@
#pragma once #pragma once
namespace esphome { namespace esphome {
#ifdef USE_API
namespace api {
class APIConnection;
} // namespace api
#endif
namespace fan { namespace fan {
class FanTraits { class FanTraits {
@@ -36,6 +43,15 @@ class FanTraits {
bool supports_preset_modes() const { return !this->preset_modes_.empty(); } bool supports_preset_modes() const { return !this->preset_modes_.empty(); }
protected: protected:
#ifdef USE_API
// The API connection is a friend class to access internal methods
friend class api::APIConnection;
// This method returns a reference to the internal preset modes set.
// It is used by the API to avoid copying data when encoding messages.
// Warning: Do not use this method outside of the API connection code.
// It returns a reference to internal data that can be invalidated.
const std::set<std::string> &supported_preset_modes_for_api_() const { return this->preset_modes_; }
#endif
bool oscillation_{false}; bool oscillation_{false};
bool speed_{false}; bool speed_{false};
bool direction_{false}; bool direction_{false};

View File

@@ -52,7 +52,7 @@ void GPS::update() {
void GPS::loop() { void GPS::loop() {
while (this->available() > 0 && !this->has_time_) { while (this->available() > 0 && !this->has_time_) {
if (!this->tiny_gps_.encode(this->read())) { if (!this->tiny_gps_.encode(this->read())) {
return; continue;
} }
if (this->tiny_gps_.location.isUpdated()) { if (this->tiny_gps_.location.isUpdated()) {
this->latitude_ = this->tiny_gps_.location.lat(); this->latitude_ = this->tiny_gps_.location.lat();

View File

@@ -126,6 +126,6 @@ async def to_code(config):
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE])) cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
cg.add_library("tonia/HeatpumpIR", "1.0.35") cg.add_library("tonia/HeatpumpIR", "1.0.37")
if CORE.is_libretiny: if CORE.is_libretiny or CORE.is_esp32:
CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") CORE.add_platformio_option("lib_ignore", "IRremoteESP8266")

View File

@@ -23,6 +23,7 @@ CONFIG_SCHEMA = (
async def to_code(config): async def to_code(config):
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
var = await number.new_number( var = await number.new_number(
config, config,
min_value=0, min_value=0,

View File

@@ -93,14 +93,12 @@ void HomeassistantNumber::control(float value) {
resp.data.emplace_back(); resp.data.emplace_back();
auto &entity_id = resp.data.back(); auto &entity_id = resp.data.back();
entity_id.set_key(ENTITY_ID_KEY); entity_id.set_key(ENTITY_ID_KEY);
entity_id.set_value(StringRef(this->entity_id_)); entity_id.value = this->entity_id_;
resp.data.emplace_back(); resp.data.emplace_back();
auto &entity_value = resp.data.back(); auto &entity_value = resp.data.back();
entity_value.set_key(VALUE_KEY); entity_value.set_key(VALUE_KEY);
// to_string() returns a temporary - must store it to avoid dangling reference entity_value.value = to_string(value);
std::string value_str = to_string(value);
entity_value.set_value(StringRef(value_str));
api::global_api_server->send_homeassistant_service_call(resp); api::global_api_server->send_homeassistant_service_call(resp);
} }

View File

@@ -37,6 +37,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
await switch.register_switch(var, config) await switch.register_switch(var, config)

View File

@@ -54,7 +54,7 @@ void HomeassistantSwitch::write_state(bool state) {
resp.data.emplace_back(); resp.data.emplace_back();
auto &entity_id_kv = resp.data.back(); auto &entity_id_kv = resp.data.back();
entity_id_kv.set_key(ENTITY_ID_KEY); entity_id_kv.set_key(ENTITY_ID_KEY);
entity_id_kv.set_value(StringRef(this->entity_id_)); entity_id_kv.value = this->entity_id_;
api::global_api_server->send_homeassistant_service_call(resp); api::global_api_server->send_homeassistant_service_call(resp);
} }

View File

@@ -9,11 +9,28 @@ namespace light {
static const char *const TAG = "light"; static const char *const TAG = "light";
// Helper function to reduce code size for validation warnings // Helper functions to reduce code size for logging
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
static void log_validation_warning(const char *name, const char *param_name, float val, float min, float max) { static void log_validation_warning(const char *name, const char *param_name, float val, float min, float max) {
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, param_name, val, min, max); ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, param_name, val, min, max);
} }
static void log_feature_not_supported(const char *name, const char *feature) {
ESP_LOGW(TAG, "'%s': %s not supported", name, feature);
}
static void log_color_mode_not_supported(const char *name, const char *feature) {
ESP_LOGW(TAG, "'%s': color mode does not support setting %s", name, feature);
}
static void log_invalid_parameter(const char *name, const char *message) { ESP_LOGW(TAG, "'%s': %s", name, message); }
#else
#define log_validation_warning(name, param_name, val, min, max)
#define log_feature_not_supported(name, feature)
#define log_color_mode_not_supported(name, feature)
#define log_invalid_parameter(name, message)
#endif
// Macro to reduce repetitive setter code // Macro to reduce repetitive setter code
#define IMPLEMENT_LIGHT_CALL_SETTER(name, type, flag) \ #define IMPLEMENT_LIGHT_CALL_SETTER(name, type, flag) \
LightCall &LightCall::set_##name(optional<type>(name)) { \ LightCall &LightCall::set_##name(optional<type>(name)) { \
@@ -49,11 +66,21 @@ static const LogString *color_mode_to_human(ColorMode color_mode) {
return LOG_STR(""); return LOG_STR("");
} }
// Helper to log percentage values
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
static void log_percent(const char *name, const char *param, float value) {
ESP_LOGD(TAG, " %s: %.0f%%", param, value * 100.0f);
}
#else
#define log_percent(name, param, value)
#endif
void LightCall::perform() { void LightCall::perform() {
const char *name = this->parent_->get_name().c_str(); const char *name = this->parent_->get_name().c_str();
LightColorValues v = this->validate_(); LightColorValues v = this->validate_();
const bool publish = this->get_publish_();
if (this->get_publish_()) { if (publish) {
ESP_LOGD(TAG, "'%s' Setting:", name); ESP_LOGD(TAG, "'%s' Setting:", name);
// Only print color mode when it's being changed // Only print color mode when it's being changed
@@ -71,11 +98,11 @@ void LightCall::perform() {
} }
if (this->has_brightness()) { if (this->has_brightness()) {
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f); log_percent(name, "Brightness", v.get_brightness());
} }
if (this->has_color_brightness()) { if (this->has_color_brightness()) {
ESP_LOGD(TAG, " Color brightness: %.0f%%", v.get_color_brightness() * 100.0f); log_percent(name, "Color brightness", v.get_color_brightness());
} }
if (this->has_red() || this->has_green() || this->has_blue()) { if (this->has_red() || this->has_green() || this->has_blue()) {
ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f, ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
@@ -83,7 +110,7 @@ void LightCall::perform() {
} }
if (this->has_white()) { if (this->has_white()) {
ESP_LOGD(TAG, " White: %.0f%%", v.get_white() * 100.0f); log_percent(name, "White", v.get_white());
} }
if (this->has_color_temperature()) { if (this->has_color_temperature()) {
ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature()); ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature());
@@ -97,26 +124,26 @@ void LightCall::perform() {
if (this->has_flash_()) { if (this->has_flash_()) {
// FLASH // FLASH
if (this->get_publish_()) { if (publish) {
ESP_LOGD(TAG, " Flash length: %.1fs", this->flash_length_ / 1e3f); ESP_LOGD(TAG, " Flash length: %.1fs", this->flash_length_ / 1e3f);
} }
this->parent_->start_flash_(v, this->flash_length_, this->get_publish_()); this->parent_->start_flash_(v, this->flash_length_, publish);
} else if (this->has_transition_()) { } else if (this->has_transition_()) {
// TRANSITION // TRANSITION
if (this->get_publish_()) { if (publish) {
ESP_LOGD(TAG, " Transition length: %.1fs", this->transition_length_ / 1e3f); ESP_LOGD(TAG, " Transition length: %.1fs", this->transition_length_ / 1e3f);
} }
// Special case: Transition and effect can be set when turning off // Special case: Transition and effect can be set when turning off
if (this->has_effect_()) { if (this->has_effect_()) {
if (this->get_publish_()) { if (publish) {
ESP_LOGD(TAG, " Effect: 'None'"); ESP_LOGD(TAG, " Effect: 'None'");
} }
this->parent_->stop_effect_(); this->parent_->stop_effect_();
} }
this->parent_->start_transition_(v, this->transition_length_, this->get_publish_()); this->parent_->start_transition_(v, this->transition_length_, publish);
} else if (this->has_effect_()) { } else if (this->has_effect_()) {
// EFFECT // EFFECT
@@ -127,7 +154,7 @@ void LightCall::perform() {
effect_s = this->parent_->effects_[this->effect_ - 1]->get_name().c_str(); effect_s = this->parent_->effects_[this->effect_ - 1]->get_name().c_str();
} }
if (this->get_publish_()) { if (publish) {
ESP_LOGD(TAG, " Effect: '%s'", effect_s); ESP_LOGD(TAG, " Effect: '%s'", effect_s);
} }
@@ -138,13 +165,13 @@ void LightCall::perform() {
this->parent_->set_immediately_(v, true); this->parent_->set_immediately_(v, true);
} else { } else {
// INSTANT CHANGE // INSTANT CHANGE
this->parent_->set_immediately_(v, this->get_publish_()); this->parent_->set_immediately_(v, publish);
} }
if (!this->has_transition_()) { if (!this->has_transition_()) {
this->parent_->target_state_reached_callback_.call(); this->parent_->target_state_reached_callback_.call();
} }
if (this->get_publish_()) { if (publish) {
this->parent_->publish_state(); this->parent_->publish_state();
} }
if (this->get_save_()) { if (this->get_save_()) {
@@ -174,19 +201,19 @@ LightColorValues LightCall::validate_() {
// Brightness exists check // Brightness exists check
if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) { if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
ESP_LOGW(TAG, "'%s': setting brightness not supported", name); log_feature_not_supported(name, "brightness");
this->set_flag_(FLAG_HAS_BRIGHTNESS, false); this->set_flag_(FLAG_HAS_BRIGHTNESS, false);
} }
// Transition length possible check // Transition length possible check
if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) { if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) {
ESP_LOGW(TAG, "'%s': transitions not supported", name); log_feature_not_supported(name, "transitions");
this->set_flag_(FLAG_HAS_TRANSITION, false); this->set_flag_(FLAG_HAS_TRANSITION, false);
} }
// Color brightness exists check // Color brightness exists check
if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) { if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name); log_color_mode_not_supported(name, "RGB brightness");
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, false); this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, false);
} }
@@ -194,7 +221,7 @@ LightColorValues LightCall::validate_() {
if ((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) || if ((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) ||
(this->has_blue() && this->blue_ > 0.0f)) { (this->has_blue() && this->blue_ > 0.0f)) {
if (!(color_mode & ColorCapability::RGB)) { if (!(color_mode & ColorCapability::RGB)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name); log_color_mode_not_supported(name, "RGB color");
this->set_flag_(FLAG_HAS_RED, false); this->set_flag_(FLAG_HAS_RED, false);
this->set_flag_(FLAG_HAS_GREEN, false); this->set_flag_(FLAG_HAS_GREEN, false);
this->set_flag_(FLAG_HAS_BLUE, false); this->set_flag_(FLAG_HAS_BLUE, false);
@@ -204,21 +231,21 @@ LightColorValues LightCall::validate_() {
// White value exists check // White value exists check
if (this->has_white() && this->white_ > 0.0f && if (this->has_white() && this->white_ > 0.0f &&
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) { !(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name); log_color_mode_not_supported(name, "white value");
this->set_flag_(FLAG_HAS_WHITE, false); this->set_flag_(FLAG_HAS_WHITE, false);
} }
// Color temperature exists check // Color temperature exists check
if (this->has_color_temperature() && if (this->has_color_temperature() &&
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) { !(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name); log_color_mode_not_supported(name, "color temperature");
this->set_flag_(FLAG_HAS_COLOR_TEMPERATURE, false); this->set_flag_(FLAG_HAS_COLOR_TEMPERATURE, false);
} }
// Cold/warm white value exists check // Cold/warm white value exists check
if ((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) { if ((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) {
if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) { if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name); log_color_mode_not_supported(name, "cold/warm white value");
this->set_flag_(FLAG_HAS_COLD_WHITE, false); this->set_flag_(FLAG_HAS_COLD_WHITE, false);
this->set_flag_(FLAG_HAS_WARM_WHITE, false); this->set_flag_(FLAG_HAS_WARM_WHITE, false);
} }
@@ -292,7 +319,7 @@ LightColorValues LightCall::validate_() {
// Flash length check // Flash length check
if (this->has_flash_() && this->flash_length_ == 0) { if (this->has_flash_() && this->flash_length_ == 0) {
ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name); log_invalid_parameter(name, "flash length must be greater than zero");
this->set_flag_(FLAG_HAS_FLASH, false); this->set_flag_(FLAG_HAS_FLASH, false);
} }
@@ -311,13 +338,13 @@ LightColorValues LightCall::validate_() {
} }
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) { if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name); log_invalid_parameter(name, "effect cannot be used with transition/flash");
this->set_flag_(FLAG_HAS_TRANSITION, false); this->set_flag_(FLAG_HAS_TRANSITION, false);
this->set_flag_(FLAG_HAS_FLASH, false); this->set_flag_(FLAG_HAS_FLASH, false);
} }
if (this->has_flash_() && this->has_transition_()) { if (this->has_flash_() && this->has_transition_()) {
ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name); log_invalid_parameter(name, "flash cannot be used with transition");
this->set_flag_(FLAG_HAS_TRANSITION, false); this->set_flag_(FLAG_HAS_TRANSITION, false);
} }
@@ -334,7 +361,7 @@ LightColorValues LightCall::validate_() {
} }
if (this->has_transition_() && !supports_transition) { if (this->has_transition_() && !supports_transition) {
ESP_LOGW(TAG, "'%s': transitions not supported", name); log_feature_not_supported(name, "transitions");
this->set_flag_(FLAG_HAS_TRANSITION, false); this->set_flag_(FLAG_HAS_TRANSITION, false);
} }
@@ -344,7 +371,7 @@ LightColorValues LightCall::validate_() {
bool target_state = this->has_state() ? this->state_ : v.is_on(); bool target_state = this->has_state() ? this->state_ : v.is_on();
if (!this->has_flash_() && !target_state) { if (!this->has_flash_() && !target_state) {
if (this->has_effect_()) { if (this->has_effect_()) {
ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name); log_invalid_parameter(name, "cannot start effect when turning off");
this->set_flag_(FLAG_HAS_EFFECT, false); this->set_flag_(FLAG_HAS_EFFECT, false);
} else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) { } else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) {
// Auto turn off effect // Auto turn off effect
@@ -368,21 +395,27 @@ void LightCall::transform_parameters_() {
// - RGBWW lights with color_interlock=true, which also sets "brightness" and // - RGBWW lights with color_interlock=true, which also sets "brightness" and
// "color_temperature" (without color_interlock, CW/WW are set directly) // "color_temperature" (without color_interlock, CW/WW are set directly)
// - Legacy Home Assistant (pre-colormode), which sets "white" and "color_temperature" // - Legacy Home Assistant (pre-colormode), which sets "white" and "color_temperature"
// Cache min/max mireds to avoid repeated calls
const float min_mireds = traits.get_min_mireds();
const float max_mireds = traits.get_max_mireds();
if (((this->has_white() && this->white_ > 0.0f) || this->has_color_temperature()) && // if (((this->has_white() && this->white_ > 0.0f) || this->has_color_temperature()) && //
(this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && // (this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && //
!(this->color_mode_ & ColorCapability::WHITE) && // !(this->color_mode_ & ColorCapability::WHITE) && //
!(this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && // !(this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && //
traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) { min_mireds > 0.0f && max_mireds > 0.0f) {
ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values", ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
this->parent_->get_name().c_str()); this->parent_->get_name().c_str());
if (this->has_color_temperature()) { if (this->has_color_temperature()) {
const float color_temp = clamp(this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds()); const float color_temp = clamp(this->color_temperature_, min_mireds, max_mireds);
const float ww_fraction = const float range = max_mireds - min_mireds;
(color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds()); const float ww_fraction = (color_temp - min_mireds) / range;
const float cw_fraction = 1.0f - ww_fraction; const float cw_fraction = 1.0f - ww_fraction;
const float max_cw_ww = std::max(ww_fraction, cw_fraction); const float max_cw_ww = std::max(ww_fraction, cw_fraction);
this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct()); const float gamma = this->parent_->get_gamma_correct();
this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct()); this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, gamma);
this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, gamma);
this->set_flag_(FLAG_HAS_COLD_WHITE, true); this->set_flag_(FLAG_HAS_COLD_WHITE, true);
this->set_flag_(FLAG_HAS_WARM_WHITE, true); this->set_flag_(FLAG_HAS_WARM_WHITE, true);
} }

View File

@@ -84,18 +84,23 @@ class LightColorValues {
* @return The linearly interpolated LightColorValues. * @return The linearly interpolated LightColorValues.
*/ */
static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion) { static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion) {
// Directly interpolate the raw values to avoid getter/setter overhead.
// This is safe because:
// - All LightColorValues have their values clamped when set via the setters
// - std::lerp guarantees output is in the same range as inputs
// - Therefore the output doesn't need clamping, so we can skip the setters
LightColorValues v; LightColorValues v;
v.set_color_mode(end.color_mode_); v.color_mode_ = end.color_mode_;
v.set_state(std::lerp(start.get_state(), end.get_state(), completion)); v.state_ = std::lerp(start.state_, end.state_, completion);
v.set_brightness(std::lerp(start.get_brightness(), end.get_brightness(), completion)); v.brightness_ = std::lerp(start.brightness_, end.brightness_, completion);
v.set_color_brightness(std::lerp(start.get_color_brightness(), end.get_color_brightness(), completion)); v.color_brightness_ = std::lerp(start.color_brightness_, end.color_brightness_, completion);
v.set_red(std::lerp(start.get_red(), end.get_red(), completion)); v.red_ = std::lerp(start.red_, end.red_, completion);
v.set_green(std::lerp(start.get_green(), end.get_green(), completion)); v.green_ = std::lerp(start.green_, end.green_, completion);
v.set_blue(std::lerp(start.get_blue(), end.get_blue(), completion)); v.blue_ = std::lerp(start.blue_, end.blue_, completion);
v.set_white(std::lerp(start.get_white(), end.get_white(), completion)); v.white_ = std::lerp(start.white_, end.white_, completion);
v.set_color_temperature(std::lerp(start.get_color_temperature(), end.get_color_temperature(), completion)); v.color_temperature_ = std::lerp(start.color_temperature_, end.color_temperature_, completion);
v.set_cold_white(std::lerp(start.get_cold_white(), end.get_cold_white(), completion)); v.cold_white_ = std::lerp(start.cold_white_, end.cold_white_, completion);
v.set_warm_white(std::lerp(start.get_warm_white(), end.get_warm_white(), completion)); v.warm_white_ = std::lerp(start.warm_white_, end.warm_white_, completion);
return v; return v;
} }

View File

@@ -8,6 +8,32 @@ namespace light {
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema // See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
// Lookup table for color mode strings
static constexpr const char *get_color_mode_json_str(ColorMode mode) {
switch (mode) {
case ColorMode::ON_OFF:
return "onoff";
case ColorMode::BRIGHTNESS:
return "brightness";
case ColorMode::WHITE:
return "white"; // not supported by HA in MQTT
case ColorMode::COLOR_TEMPERATURE:
return "color_temp";
case ColorMode::COLD_WARM_WHITE:
return "cwww"; // not supported by HA
case ColorMode::RGB:
return "rgb";
case ColorMode::RGB_WHITE:
return "rgbw";
case ColorMode::RGB_COLOR_TEMPERATURE:
return "rgbct"; // not supported by HA
case ColorMode::RGB_COLD_WARM_WHITE:
return "rgbww";
default:
return nullptr;
}
}
void LightJSONSchema::dump_json(LightState &state, JsonObject root) { void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (state.supports_effects()) if (state.supports_effects())
@@ -16,60 +42,36 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
auto values = state.remote_values; auto values = state.remote_values;
auto traits = state.get_output()->get_traits(); auto traits = state.get_output()->get_traits();
switch (values.get_color_mode()) { const auto color_mode = values.get_color_mode();
case ColorMode::UNKNOWN: // don't need to set color mode if we don't know it const char *mode_str = get_color_mode_json_str(color_mode);
break; if (mode_str != nullptr) {
case ColorMode::ON_OFF: root["color_mode"] = mode_str;
root["color_mode"] = "onoff";
break;
case ColorMode::BRIGHTNESS:
root["color_mode"] = "brightness";
break;
case ColorMode::WHITE: // not supported by HA in MQTT
root["color_mode"] = "white";
break;
case ColorMode::COLOR_TEMPERATURE:
root["color_mode"] = "color_temp";
break;
case ColorMode::COLD_WARM_WHITE: // not supported by HA
root["color_mode"] = "cwww";
break;
case ColorMode::RGB:
root["color_mode"] = "rgb";
break;
case ColorMode::RGB_WHITE:
root["color_mode"] = "rgbw";
break;
case ColorMode::RGB_COLOR_TEMPERATURE: // not supported by HA
root["color_mode"] = "rgbct";
break;
case ColorMode::RGB_COLD_WARM_WHITE:
root["color_mode"] = "rgbww";
break;
} }
if (values.get_color_mode() & ColorCapability::ON_OFF) if (color_mode & ColorCapability::ON_OFF)
root["state"] = (values.get_state() != 0.0f) ? "ON" : "OFF"; root["state"] = (values.get_state() != 0.0f) ? "ON" : "OFF";
if (values.get_color_mode() & ColorCapability::BRIGHTNESS) if (color_mode & ColorCapability::BRIGHTNESS)
root["brightness"] = uint8_t(values.get_brightness() * 255); root["brightness"] = to_uint8_scale(values.get_brightness());
JsonObject color = root["color"].to<JsonObject>(); JsonObject color = root["color"].to<JsonObject>();
if (values.get_color_mode() & ColorCapability::RGB) { if (color_mode & ColorCapability::RGB) {
color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255); float color_brightness = values.get_color_brightness();
color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255); color["r"] = to_uint8_scale(color_brightness * values.get_red());
color["b"] = uint8_t(values.get_color_brightness() * values.get_blue() * 255); color["g"] = to_uint8_scale(color_brightness * values.get_green());
color["b"] = to_uint8_scale(color_brightness * values.get_blue());
} }
if (values.get_color_mode() & ColorCapability::WHITE) { if (color_mode & ColorCapability::WHITE) {
color["w"] = uint8_t(values.get_white() * 255); uint8_t white_val = to_uint8_scale(values.get_white());
root["white_value"] = uint8_t(values.get_white() * 255); // legacy API color["w"] = white_val;
root["white_value"] = white_val; // legacy API
} }
if (values.get_color_mode() & ColorCapability::COLOR_TEMPERATURE) { if (color_mode & ColorCapability::COLOR_TEMPERATURE) {
// this one isn't under the color subkey for some reason // this one isn't under the color subkey for some reason
root["color_temp"] = uint32_t(values.get_color_temperature()); root["color_temp"] = uint32_t(values.get_color_temperature());
} }
if (values.get_color_mode() & ColorCapability::COLD_WARM_WHITE) { if (color_mode & ColorCapability::COLD_WARM_WHITE) {
color["c"] = uint8_t(values.get_cold_white() * 255); color["c"] = to_uint8_scale(values.get_cold_white());
color["w"] = uint8_t(values.get_warm_white() * 255); color["w"] = to_uint8_scale(values.get_warm_white());
} }
} }

View File

@@ -24,7 +24,8 @@ void LightState::setup() {
} }
// When supported color temperature range is known, initialize color temperature setting within bounds. // When supported color temperature range is known, initialize color temperature setting within bounds.
float min_mireds = this->get_traits().get_min_mireds(); auto traits = this->get_traits();
float min_mireds = traits.get_min_mireds();
if (min_mireds > 0) { if (min_mireds > 0) {
this->remote_values.set_color_temperature(min_mireds); this->remote_values.set_color_temperature(min_mireds);
this->current_values.set_color_temperature(min_mireds); this->current_values.set_color_temperature(min_mireds);
@@ -43,11 +44,8 @@ void LightState::setup() {
this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_object_id_hash()); this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_object_id_hash());
// Attempt to load from preferences, else fall back to default values // Attempt to load from preferences, else fall back to default values
if (!this->rtc_.load(&recovered)) { if (!this->rtc_.load(&recovered)) {
recovered.state = false; recovered.state = (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON ||
if (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON || this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_ON);
this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_ON) {
recovered.state = true;
}
} else if (this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_OFF || } else if (this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_OFF ||
this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_ON) { this->restore_mode_ == LIGHT_RESTORE_INVERTED_DEFAULT_ON) {
// Inverted restore state // Inverted restore state
@@ -88,17 +86,18 @@ void LightState::setup() {
} }
void LightState::dump_config() { void LightState::dump_config() {
ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str());
if (this->get_traits().supports_color_capability(ColorCapability::BRIGHTNESS)) { auto traits = this->get_traits();
if (traits.supports_color_capability(ColorCapability::BRIGHTNESS)) {
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Default Transition Length: %.1fs\n" " Default Transition Length: %.1fs\n"
" Gamma Correct: %.2f", " Gamma Correct: %.2f",
this->default_transition_length_ / 1e3f, this->gamma_correct_); this->default_transition_length_ / 1e3f, this->gamma_correct_);
} }
if (this->get_traits().supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) { if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) {
ESP_LOGCONFIG(TAG, ESP_LOGCONFIG(TAG,
" Min Mireds: %.1f\n" " Min Mireds: %.1f\n"
" Max Mireds: %.1f", " Max Mireds: %.1f",
this->get_traits().get_min_mireds(), this->get_traits().get_max_mireds()); traits.get_min_mireds(), traits.get_max_mireds());
} }
} }
void LightState::loop() { void LightState::loop() {

View File

@@ -5,6 +5,13 @@
#include <set> #include <set>
namespace esphome { namespace esphome {
#ifdef USE_API
namespace api {
class APIConnection;
} // namespace api
#endif
namespace light { namespace light {
/// This class is used to represent the capabilities of a light. /// This class is used to represent the capabilities of a light.
@@ -52,6 +59,16 @@ class LightTraits {
void set_max_mireds(float max_mireds) { this->max_mireds_ = max_mireds; } void set_max_mireds(float max_mireds) { this->max_mireds_ = max_mireds; }
protected: protected:
#ifdef USE_API
// The API connection is a friend class to access internal methods
friend class api::APIConnection;
// This method returns a reference to the internal color modes set.
// It is used by the API to avoid copying data when encoding messages.
// Warning: Do not use this method outside of the API connection code.
// It returns a reference to internal data that can be invalidated.
const std::set<ColorMode> &get_supported_color_modes_for_api_() const { return this->supported_color_modes_; }
#endif
std::set<ColorMode> supported_color_modes_{}; std::set<ColorMode> supported_color_modes_{};
float min_mireds_{0}; float min_mireds_{0};
float max_mireds_{0}; float max_mireds_{0};

View File

@@ -15,7 +15,7 @@ from ..defines import (
TILE_DIRECTIONS, TILE_DIRECTIONS,
literal, literal,
) )
from ..lv_validation import animated, lv_int from ..lv_validation import animated, lv_int, lv_pct
from ..lvcode import lv, lv_assign, lv_expr, lv_obj, lv_Pvariable from ..lvcode import lv, lv_assign, lv_expr, lv_obj, lv_Pvariable
from ..schemas import container_schema from ..schemas import container_schema
from ..types import LV_EVENT, LvType, ObjUpdateAction, lv_obj_t, lv_obj_t_ptr from ..types import LV_EVENT, LvType, ObjUpdateAction, lv_obj_t, lv_obj_t_ptr
@@ -41,8 +41,8 @@ TILEVIEW_SCHEMA = cv.Schema(
container_schema( container_schema(
obj_spec, obj_spec,
{ {
cv.Required(CONF_ROW): lv_int, cv.Required(CONF_ROW): cv.positive_int,
cv.Required(CONF_COLUMN): lv_int, cv.Required(CONF_COLUMN): cv.positive_int,
cv.GenerateID(): cv.declare_id(lv_tile_t), cv.GenerateID(): cv.declare_id(lv_tile_t),
cv.Optional(CONF_DIR, default="ALL"): TILE_DIRECTIONS.several_of, cv.Optional(CONF_DIR, default="ALL"): TILE_DIRECTIONS.several_of,
}, },
@@ -63,21 +63,29 @@ class TileviewType(WidgetType):
) )
async def to_code(self, w: Widget, config: dict): async def to_code(self, w: Widget, config: dict):
for tile_conf in config.get(CONF_TILES, ()): tiles = config[CONF_TILES]
for tile_conf in tiles:
w_id = tile_conf[CONF_ID] w_id = tile_conf[CONF_ID]
tile_obj = lv_Pvariable(lv_obj_t, w_id) tile_obj = lv_Pvariable(lv_obj_t, w_id)
tile = Widget.create(w_id, tile_obj, tile_spec, tile_conf) tile = Widget.create(w_id, tile_obj, tile_spec, tile_conf)
dirs = tile_conf[CONF_DIR] dirs = tile_conf[CONF_DIR]
if isinstance(dirs, list): if isinstance(dirs, list):
dirs = "|".join(dirs) dirs = "|".join(dirs)
row_pos = tile_conf[CONF_ROW]
col_pos = tile_conf[CONF_COLUMN]
lv_assign( lv_assign(
tile_obj, tile_obj,
lv_expr.tileview_add_tile( lv_expr.tileview_add_tile(w.obj, col_pos, row_pos, literal(dirs)),
w.obj, tile_conf[CONF_COLUMN], tile_conf[CONF_ROW], literal(dirs)
),
) )
# Bugfix for LVGL 8.x
lv_obj.set_pos(tile_obj, lv_pct(col_pos * 100), lv_pct(row_pos * 100))
await set_obj_properties(tile, tile_conf) await set_obj_properties(tile, tile_conf)
await add_widgets(tile, tile_conf) await add_widgets(tile, tile_conf)
if tiles:
# Set the first tile as active
lv_obj.set_tile_id(
w.obj, tiles[0][CONF_COLUMN], tiles[0][CONF_ROW], literal("LV_ANIM_OFF")
)
tileview_spec = TileviewType() tileview_spec = TileviewType()

View File

@@ -6,12 +6,38 @@
namespace esphome { namespace esphome {
namespace media_player { namespace media_player {
enum MediaPlayerEntityFeature : uint32_t {
PAUSE = 1 << 0,
SEEK = 1 << 1,
VOLUME_SET = 1 << 2,
VOLUME_MUTE = 1 << 3,
PREVIOUS_TRACK = 1 << 4,
NEXT_TRACK = 1 << 5,
TURN_ON = 1 << 7,
TURN_OFF = 1 << 8,
PLAY_MEDIA = 1 << 9,
VOLUME_STEP = 1 << 10,
SELECT_SOURCE = 1 << 11,
STOP = 1 << 12,
CLEAR_PLAYLIST = 1 << 13,
PLAY = 1 << 14,
SHUFFLE_SET = 1 << 15,
SELECT_SOUND_MODE = 1 << 16,
BROWSE_MEDIA = 1 << 17,
REPEAT_SET = 1 << 18,
GROUPING = 1 << 19,
MEDIA_ANNOUNCE = 1 << 20,
MEDIA_ENQUEUE = 1 << 21,
SEARCH_MEDIA = 1 << 22,
};
enum MediaPlayerState : uint8_t { enum MediaPlayerState : uint8_t {
MEDIA_PLAYER_STATE_NONE = 0, MEDIA_PLAYER_STATE_NONE = 0,
MEDIA_PLAYER_STATE_IDLE = 1, MEDIA_PLAYER_STATE_IDLE = 1,
MEDIA_PLAYER_STATE_PLAYING = 2, MEDIA_PLAYER_STATE_PLAYING = 2,
MEDIA_PLAYER_STATE_PAUSED = 3, MEDIA_PLAYER_STATE_PAUSED = 3,
MEDIA_PLAYER_STATE_ANNOUNCING = 4 MEDIA_PLAYER_STATE_ANNOUNCING = 4,
}; };
const char *media_player_state_to_string(MediaPlayerState state); const char *media_player_state_to_string(MediaPlayerState state);
@@ -56,6 +82,17 @@ class MediaPlayerTraits {
std::vector<MediaPlayerSupportedFormat> &get_supported_formats() { return this->supported_formats_; } std::vector<MediaPlayerSupportedFormat> &get_supported_formats() { return this->supported_formats_; }
uint32_t get_feature_flags() const {
uint32_t flags = 0;
flags |= MediaPlayerEntityFeature::PLAY_MEDIA | MediaPlayerEntityFeature::BROWSE_MEDIA |
MediaPlayerEntityFeature::STOP | MediaPlayerEntityFeature::VOLUME_SET |
MediaPlayerEntityFeature::VOLUME_MUTE | MediaPlayerEntityFeature::MEDIA_ANNOUNCE;
if (this->get_supports_pause()) {
flags |= MediaPlayerEntityFeature::PAUSE | MediaPlayerEntityFeature::PLAY;
}
return flags;
}
protected: protected:
bool supports_pause_{false}; bool supports_pause_{false};
std::vector<MediaPlayerSupportedFormat> supported_formats_{}; std::vector<MediaPlayerSupportedFormat> supported_formats_{};

View File

@@ -57,7 +57,8 @@ from esphome.final_validate import full_config
from . import mipi_dsi_ns, models from . import mipi_dsi_ns, models
DEPENDENCIES = ["esp32"] # Currently only ESP32-P4 is supported, so esp_ldo and psram are required
DEPENDENCIES = ["esp32", "esp_ldo", "psram"]
DOMAIN = "mipi_dsi" DOMAIN = "mipi_dsi"
LOGGER = logging.getLogger(DOMAIN) LOGGER = logging.getLogger(DOMAIN)

View File

@@ -1,6 +1,9 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#ifdef USE_OPENTHREAD #ifdef USE_OPENTHREAD
#include "openthread.h" #include "openthread.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
#include "esp_openthread.h"
#endif
#include <freertos/portmacro.h> #include <freertos/portmacro.h>
@@ -28,18 +31,6 @@ OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines-
OpenThreadComponent::OpenThreadComponent() { global_openthread_component = this; } OpenThreadComponent::OpenThreadComponent() { global_openthread_component = this; }
OpenThreadComponent::~OpenThreadComponent() {
auto lock = InstanceLock::try_acquire(100);
if (!lock) {
ESP_LOGW(TAG, "Failed to acquire OpenThread lock in destructor, leaking memory");
return;
}
otInstance *instance = lock->get_instance();
otSrpClientClearHostAndServices(instance);
otSrpClientBuffersFreeAllServices(instance);
global_openthread_component = nullptr;
}
bool OpenThreadComponent::is_connected() { bool OpenThreadComponent::is_connected() {
auto lock = InstanceLock::try_acquire(100); auto lock = InstanceLock::try_acquire(100);
if (!lock) { if (!lock) {
@@ -199,6 +190,33 @@ void *OpenThreadSrpComponent::pool_alloc_(size_t size) {
void OpenThreadSrpComponent::set_mdns(esphome::mdns::MDNSComponent *mdns) { this->mdns_ = mdns; } void OpenThreadSrpComponent::set_mdns(esphome::mdns::MDNSComponent *mdns) { this->mdns_ = mdns; }
bool OpenThreadComponent::teardown() {
if (!this->teardown_started_) {
this->teardown_started_ = true;
ESP_LOGD(TAG, "Clear Srp");
auto lock = InstanceLock::try_acquire(100);
if (!lock) {
ESP_LOGW(TAG, "Failed to acquire OpenThread lock during teardown, leaking memory");
return true;
}
otInstance *instance = lock->get_instance();
otSrpClientClearHostAndServices(instance);
otSrpClientBuffersFreeAllServices(instance);
global_openthread_component = nullptr;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
ESP_LOGD(TAG, "Exit main loop ");
int error = esp_openthread_mainloop_exit();
if (error != ESP_OK) {
ESP_LOGW(TAG, "Failed attempt to stop main loop %d", error);
this->teardown_complete_ = true;
}
#else
this->teardown_complete_ = true;
#endif
}
return this->teardown_complete_;
}
} // namespace openthread } // namespace openthread
} // namespace esphome } // namespace esphome

View File

@@ -21,6 +21,7 @@ class OpenThreadComponent : public Component {
OpenThreadComponent(); OpenThreadComponent();
~OpenThreadComponent(); ~OpenThreadComponent();
void setup() override; void setup() override;
bool teardown() override;
float get_setup_priority() const override { return setup_priority::WIFI; } float get_setup_priority() const override { return setup_priority::WIFI; }
bool is_connected(); bool is_connected();
@@ -30,6 +31,8 @@ class OpenThreadComponent : public Component {
protected: protected:
std::optional<otIp6Address> get_omr_address_(InstanceLock &lock); std::optional<otIp6Address> get_omr_address_(InstanceLock &lock);
bool teardown_started_{false};
bool teardown_complete_{false};
}; };
extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) extern OpenThreadComponent *global_openthread_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -143,10 +143,13 @@ void OpenThreadComponent::ot_main() {
esp_openthread_launch_mainloop(); esp_openthread_launch_mainloop();
// Clean up // Clean up
esp_openthread_deinit();
esp_openthread_netif_glue_deinit(); esp_openthread_netif_glue_deinit();
esp_netif_destroy(openthread_netif); esp_netif_destroy(openthread_netif);
esp_vfs_eventfd_unregister(); esp_vfs_eventfd_unregister();
this->teardown_complete_ = true;
vTaskDelete(NULL);
} }
network::IPAddresses OpenThreadComponent::get_ip_addresses() { network::IPAddresses OpenThreadComponent::get_ip_addresses() {

View File

@@ -43,6 +43,8 @@ FloatOutputPtr = FloatOutput.operator("ptr")
TurnOffAction = output_ns.class_("TurnOffAction", automation.Action) TurnOffAction = output_ns.class_("TurnOffAction", automation.Action)
TurnOnAction = output_ns.class_("TurnOnAction", automation.Action) TurnOnAction = output_ns.class_("TurnOnAction", automation.Action)
SetLevelAction = output_ns.class_("SetLevelAction", automation.Action) SetLevelAction = output_ns.class_("SetLevelAction", automation.Action)
SetMinPowerAction = output_ns.class_("SetMinPowerAction", automation.Action)
SetMaxPowerAction = output_ns.class_("SetMaxPowerAction", automation.Action)
async def setup_output_platform_(obj, config): async def setup_output_platform_(obj, config):
@@ -104,6 +106,42 @@ async def output_set_level_to_code(config, action_id, template_arg, args):
return var return var
@automation.register_action(
"output.set_min_power",
SetMinPowerAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(FloatOutput),
cv.Required(CONF_MIN_POWER): cv.templatable(cv.percentage),
}
),
)
async def output_set_min_power_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_MIN_POWER], args, float)
cg.add(var.set_min_power(template_))
return var
@automation.register_action(
"output.set_max_power",
SetMaxPowerAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(FloatOutput),
cv.Required(CONF_MAX_POWER): cv.templatable(cv.percentage),
}
),
)
async def output_set_max_power_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_MAX_POWER], args, float)
cg.add(var.set_max_power(template_))
return var
async def to_code(config): async def to_code(config):
cg.add_define("USE_OUTPUT") cg.add_define("USE_OUTPUT")
cg.add_global(output_ns.using) cg.add_global(output_ns.using)

View File

@@ -40,5 +40,29 @@ template<typename... Ts> class SetLevelAction : public Action<Ts...> {
FloatOutput *output_; FloatOutput *output_;
}; };
template<typename... Ts> class SetMinPowerAction : public Action<Ts...> {
public:
SetMinPowerAction(FloatOutput *output) : output_(output) {}
TEMPLATABLE_VALUE(float, min_power)
void play(Ts... x) override { this->output_->set_min_power(this->min_power_.value(x...)); }
protected:
FloatOutput *output_;
};
template<typename... Ts> class SetMaxPowerAction : public Action<Ts...> {
public:
SetMaxPowerAction(FloatOutput *output) : output_(output) {}
TEMPLATABLE_VALUE(float, max_power)
void play(Ts... x) override { this->output_->set_max_power(this->max_power_.value(x...)); }
protected:
FloatOutput *output_;
};
} // namespace output } // namespace output
} // namespace esphome } // namespace esphome

View File

@@ -5,7 +5,7 @@ namespace select {
void SelectTraits::set_options(std::vector<std::string> options) { this->options_ = std::move(options); } void SelectTraits::set_options(std::vector<std::string> options) { this->options_ = std::move(options); }
std::vector<std::string> SelectTraits::get_options() const { return this->options_; } const std::vector<std::string> &SelectTraits::get_options() const { return this->options_; }
} // namespace select } // namespace select
} // namespace esphome } // namespace esphome

View File

@@ -9,7 +9,7 @@ namespace select {
class SelectTraits { class SelectTraits {
public: public:
void set_options(std::vector<std::string> options); void set_options(std::vector<std::string> options);
std::vector<std::string> get_options() const; const std::vector<std::string> &get_options() const;
protected: protected:
std::vector<std::string> options_; std::vector<std::string> options_;

View File

@@ -256,6 +256,7 @@ OffsetFilter = sensor_ns.class_("OffsetFilter", Filter)
MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter) MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter)
FilterOutValueFilter = sensor_ns.class_("FilterOutValueFilter", Filter) FilterOutValueFilter = sensor_ns.class_("FilterOutValueFilter", Filter)
ThrottleFilter = sensor_ns.class_("ThrottleFilter", Filter) ThrottleFilter = sensor_ns.class_("ThrottleFilter", Filter)
ThrottleWithPriorityFilter = sensor_ns.class_("ThrottleWithPriorityFilter", Filter)
TimeoutFilter = sensor_ns.class_("TimeoutFilter", Filter, cg.Component) TimeoutFilter = sensor_ns.class_("TimeoutFilter", Filter, cg.Component)
DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component) DebounceFilter = sensor_ns.class_("DebounceFilter", Filter, cg.Component)
HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter, cg.Component) HeartbeatFilter = sensor_ns.class_("HeartbeatFilter", Filter, cg.Component)
@@ -332,6 +333,7 @@ def sensor_schema(
device_class: str = cv.UNDEFINED, device_class: str = cv.UNDEFINED,
state_class: str = cv.UNDEFINED, state_class: str = cv.UNDEFINED,
entity_category: str = cv.UNDEFINED, entity_category: str = cv.UNDEFINED,
filters: list = cv.UNDEFINED,
) -> cv.Schema: ) -> cv.Schema:
schema = {} schema = {}
@@ -346,6 +348,7 @@ def sensor_schema(
(CONF_DEVICE_CLASS, device_class, validate_device_class), (CONF_DEVICE_CLASS, device_class, validate_device_class),
(CONF_STATE_CLASS, state_class, validate_state_class), (CONF_STATE_CLASS, state_class, validate_state_class),
(CONF_ENTITY_CATEGORY, entity_category, sensor_entity_category), (CONF_ENTITY_CATEGORY, entity_category, sensor_entity_category),
(CONF_FILTERS, filters, validate_filters),
]: ]:
if default is not cv.UNDEFINED: if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator schema[cv.Optional(key, default=default)] = validator
@@ -593,6 +596,25 @@ async def throttle_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id, config) return cg.new_Pvariable(filter_id, config)
TIMEOUT_WITH_PRIORITY_SCHEMA = cv.maybe_simple_value(
{
cv.Required(CONF_TIMEOUT): cv.positive_time_period_milliseconds,
cv.Optional(CONF_VALUE, default="nan"): cv.ensure_list(cv.float_),
},
key=CONF_TIMEOUT,
)
@FILTER_REGISTRY.register(
"throttle_with_priority",
ThrottleWithPriorityFilter,
TIMEOUT_WITH_PRIORITY_SCHEMA,
)
async def throttle_with_priority_filter_to_code(config, filter_id):
template_ = [await cg.templatable(x, [], float) for x in config[CONF_VALUE]]
return cg.new_Pvariable(filter_id, config[CONF_TIMEOUT], template_)
@FILTER_REGISTRY.register( @FILTER_REGISTRY.register(
"heartbeat", HeartbeatFilter, cv.positive_time_period_milliseconds "heartbeat", HeartbeatFilter, cv.positive_time_period_milliseconds
) )

View File

@@ -1,5 +1,6 @@
#include "filter.h" #include "filter.h"
#include <cmath> #include <cmath>
#include "esphome/core/application.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "sensor.h" #include "sensor.h"
@@ -332,6 +333,40 @@ optional<float> ThrottleFilter::new_value(float value) {
return {}; return {};
} }
// ThrottleWithPriorityFilter
ThrottleWithPriorityFilter::ThrottleWithPriorityFilter(uint32_t min_time_between_inputs,
std::vector<TemplatableValue<float>> prioritized_values)
: min_time_between_inputs_(min_time_between_inputs), prioritized_values_(std::move(prioritized_values)) {}
optional<float> ThrottleWithPriorityFilter::new_value(float value) {
bool is_prioritized_value = false;
int8_t accuracy = this->parent_->get_accuracy_decimals();
float accuracy_mult = powf(10.0f, accuracy);
const uint32_t now = App.get_loop_component_start_time();
// First, determine if the new value is one of the prioritized values
for (auto prioritized_value : this->prioritized_values_) {
if (std::isnan(prioritized_value.value())) {
if (std::isnan(value)) {
is_prioritized_value = true;
break;
}
continue;
}
float rounded_prioritized_value = roundf(accuracy_mult * prioritized_value.value());
float rounded_value = roundf(accuracy_mult * value);
if (rounded_prioritized_value == rounded_value) {
is_prioritized_value = true;
break;
}
}
// Finally, determine if the new value should be throttled and pass it through if not
if (this->last_input_ == 0 || now - this->last_input_ >= min_time_between_inputs_ || is_prioritized_value) {
this->last_input_ = now;
return value;
}
return {};
}
// DeltaFilter // DeltaFilter
DeltaFilter::DeltaFilter(float delta, bool percentage_mode) DeltaFilter::DeltaFilter(float delta, bool percentage_mode)
: delta_(delta), current_delta_(delta), percentage_mode_(percentage_mode), last_value_(NAN) {} : delta_(delta), current_delta_(delta), percentage_mode_(percentage_mode), last_value_(NAN) {}

View File

@@ -314,6 +314,20 @@ class ThrottleFilter : public Filter {
uint32_t min_time_between_inputs_; uint32_t min_time_between_inputs_;
}; };
/// Same as 'throttle' but will immediately publish values contained in `value_to_prioritize`.
class ThrottleWithPriorityFilter : public Filter {
public:
explicit ThrottleWithPriorityFilter(uint32_t min_time_between_inputs,
std::vector<TemplatableValue<float>> prioritized_values);
optional<float> new_value(float value) override;
protected:
uint32_t last_input_{0};
uint32_t min_time_between_inputs_;
std::vector<TemplatableValue<float>> prioritized_values_;
};
class TimeoutFilter : public Filter, public Component { class TimeoutFilter : public Filter, public Component {
public: public:
explicit TimeoutFilter(uint32_t time_period, TemplatableValue<float> new_value); explicit TimeoutFilter(uint32_t time_period, TemplatableValue<float> new_value);

View File

@@ -162,6 +162,7 @@ def text_sensor_schema(
device_class: str = cv.UNDEFINED, device_class: str = cv.UNDEFINED,
entity_category: str = cv.UNDEFINED, entity_category: str = cv.UNDEFINED,
icon: str = cv.UNDEFINED, icon: str = cv.UNDEFINED,
filters: list = cv.UNDEFINED,
) -> cv.Schema: ) -> cv.Schema:
schema = {} schema = {}
@@ -172,6 +173,7 @@ def text_sensor_schema(
(CONF_ICON, icon, cv.icon), (CONF_ICON, icon, cv.icon),
(CONF_DEVICE_CLASS, device_class, validate_device_class), (CONF_DEVICE_CLASS, device_class, validate_device_class),
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_FILTERS, filters, validate_filters),
]: ]:
if default is not cv.UNDEFINED: if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator schema[cv.Optional(key, default=default)] = validator

View File

@@ -6,6 +6,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_
from esphome.components.network import IPAddress from esphome.components.network import IPAddress
from esphome.config_helpers import filter_source_files_from_platform from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.config_validation import only_with_esp_idf
from esphome.const import ( from esphome.const import (
CONF_AP, CONF_AP,
CONF_BSSID, CONF_BSSID,
@@ -336,7 +337,7 @@ CONFIG_SCHEMA = cv.All(
single=True single=True
), ),
cv.Optional(CONF_USE_PSRAM): cv.All( cv.Optional(CONF_USE_PSRAM): cv.All(
cv.requires_component("psram"), cv.boolean only_with_esp_idf, cv.requires_component("psram"), cv.boolean
), ),
} }
), ),

View File

@@ -533,9 +533,17 @@ void WiFiComponent::check_scanning_finished() {
return false; return false;
if (a.get_matches() && b.get_matches()) { if (a.get_matches() && b.get_matches()) {
// if both match, check priority // For APs with the same SSID, always prefer stronger signal
// This helps with mesh networks and multiple APs
if (a.get_ssid() == b.get_ssid()) {
return a.get_rssi() > b.get_rssi();
}
// For different SSIDs, check priority first
if (a.get_priority() != b.get_priority()) if (a.get_priority() != b.get_priority())
return a.get_priority() > b.get_priority(); return a.get_priority() > b.get_priority();
// If priorities are equal, prefer stronger signal
return a.get_rssi() > b.get_rssi();
} }
return a.get_rssi() > b.get_rssi(); return a.get_rssi() > b.get_rssi();

View File

@@ -474,8 +474,20 @@ const char *get_disconnect_reason_str(uint8_t reason) {
return "Handshake Failed"; return "Handshake Failed";
case WIFI_REASON_CONNECTION_FAIL: case WIFI_REASON_CONNECTION_FAIL:
return "Connection Failed"; return "Connection Failed";
case WIFI_REASON_AP_TSF_RESET:
return "AP TSF reset";
case WIFI_REASON_ROAMING: case WIFI_REASON_ROAMING:
return "Station Roaming"; return "Station Roaming";
case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG:
return "Association comeback time too long";
case WIFI_REASON_SA_QUERY_TIMEOUT:
return "SA query timeout";
case WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY:
return "No AP found with compatible security";
case WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD:
return "No AP found in auth mode threshold";
case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD:
return "No AP found in RSSI threshold";
case WIFI_REASON_UNSPECIFIED: case WIFI_REASON_UNSPECIFIED:
default: default:
return "Unspecified"; return "Unspecified";

View File

@@ -640,8 +640,20 @@ const char *get_disconnect_reason_str(uint8_t reason) {
return "Handshake Failed"; return "Handshake Failed";
case WIFI_REASON_CONNECTION_FAIL: case WIFI_REASON_CONNECTION_FAIL:
return "Connection Failed"; return "Connection Failed";
case WIFI_REASON_AP_TSF_RESET:
return "AP TSF reset";
case WIFI_REASON_ROAMING: case WIFI_REASON_ROAMING:
return "Station Roaming"; return "Station Roaming";
case WIFI_REASON_ASSOC_COMEBACK_TIME_TOO_LONG:
return "Association comeback time too long";
case WIFI_REASON_SA_QUERY_TIMEOUT:
return "SA query timeout";
case WIFI_REASON_NO_AP_FOUND_W_COMPATIBLE_SECURITY:
return "No AP found with compatible security";
case WIFI_REASON_NO_AP_FOUND_IN_AUTHMODE_THRESHOLD:
return "No AP found in auth mode threshold";
case WIFI_REASON_NO_AP_FOUND_IN_RSSI_THRESHOLD:
return "No AP found in RSSI threshold";
case WIFI_REASON_UNSPECIFIED: case WIFI_REASON_UNSPECIFIED:
default: default:
return "Unspecified"; return "Unspecified";

View File

@@ -291,6 +291,8 @@ class Version:
extra: str = "" extra: str = ""
def __str__(self): def __str__(self):
if self.extra:
return f"{self.major}.{self.minor}.{self.patch}-{self.extra}"
return f"{self.major}.{self.minor}.{self.patch}" return f"{self.major}.{self.minor}.{self.patch}"
@classmethod @classmethod

View File

@@ -16,6 +16,7 @@
namespace esphome { namespace esphome {
static const char *const TAG = "component"; static const char *const TAG = "component";
static const char *const UNSPECIFIED_MESSAGE = "unspecified";
// Global vectors for component data that doesn't belong in every instance. // Global vectors for component data that doesn't belong in every instance.
// Using vector instead of unordered_map for both because: // Using vector instead of unordered_map for both because:
@@ -132,7 +133,7 @@ void Component::call_dump_config() {
this->dump_config(); this->dump_config();
if (this->is_failed()) { if (this->is_failed()) {
// Look up error message from global vector // Look up error message from global vector
const char *error_msg = "unspecified"; const char *error_msg = nullptr;
if (component_error_messages) { if (component_error_messages) {
for (const auto &pair : *component_error_messages) { for (const auto &pair : *component_error_messages) {
if (pair.first == this) { if (pair.first == this) {
@@ -141,7 +142,8 @@ void Component::call_dump_config() {
} }
} }
} }
ESP_LOGE(TAG, " %s is marked FAILED: %s", this->get_component_source(), error_msg); ESP_LOGE(TAG, " %s is marked FAILED: %s", this->get_component_source(),
error_msg ? error_msg : UNSPECIFIED_MESSAGE);
} }
} }
@@ -159,7 +161,7 @@ void Component::call() {
this->call_setup(); this->call_setup();
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
uint32_t setup_time = millis() - start_time; uint32_t setup_time = millis() - start_time;
ESP_LOGD(TAG, "Setup %s took %ums", this->get_component_source(), (unsigned) setup_time); ESP_LOGCONFIG(TAG, "Setup %s took %ums", this->get_component_source(), (unsigned) setup_time);
#endif #endif
break; break;
} }
@@ -284,15 +286,15 @@ void Component::status_set_warning(const char *message) {
return; return;
this->component_state_ |= STATUS_LED_WARNING; this->component_state_ |= STATUS_LED_WARNING;
App.app_state_ |= STATUS_LED_WARNING; App.app_state_ |= STATUS_LED_WARNING;
ESP_LOGW(TAG, "%s set Warning flag: %s", this->get_component_source(), message); ESP_LOGW(TAG, "%s set Warning flag: %s", this->get_component_source(), message ? message : UNSPECIFIED_MESSAGE);
} }
void Component::status_set_error(const char *message) { void Component::status_set_error(const char *message) {
if ((this->component_state_ & STATUS_LED_ERROR) != 0) if ((this->component_state_ & STATUS_LED_ERROR) != 0)
return; return;
this->component_state_ |= STATUS_LED_ERROR; this->component_state_ |= STATUS_LED_ERROR;
App.app_state_ |= STATUS_LED_ERROR; App.app_state_ |= STATUS_LED_ERROR;
ESP_LOGE(TAG, "%s set Error flag: %s", this->get_component_source(), message); ESP_LOGE(TAG, "%s set Error flag: %s", this->get_component_source(), message ? message : UNSPECIFIED_MESSAGE);
if (strcmp(message, "unspecified") != 0) { if (message != nullptr) {
// Lazy allocate the error messages vector if needed // Lazy allocate the error messages vector if needed
if (!component_error_messages) { if (!component_error_messages) {
component_error_messages = std::make_unique<std::vector<std::pair<const Component *, const char *>>>(); component_error_messages = std::make_unique<std::vector<std::pair<const Component *, const char *>>>();

View File

@@ -202,9 +202,9 @@ class Component {
bool status_has_error() const; bool status_has_error() const;
void status_set_warning(const char *message = "unspecified"); void status_set_warning(const char *message = nullptr);
void status_set_error(const char *message = "unspecified"); void status_set_error(const char *message = nullptr);
void status_clear_warning(); void status_clear_warning();

View File

@@ -109,6 +109,7 @@
#define USE_API #define USE_API
#define USE_API_CLIENT_CONNECTED_TRIGGER #define USE_API_CLIENT_CONNECTED_TRIGGER
#define USE_API_CLIENT_DISCONNECTED_TRIGGER #define USE_API_CLIENT_DISCONNECTED_TRIGGER
#define USE_API_HOMEASSISTANT_SERVICES
#define USE_API_HOMEASSISTANT_STATES #define USE_API_HOMEASSISTANT_STATES
#define USE_API_NOISE #define USE_API_NOISE
#define USE_API_PLAINTEXT #define USE_API_PLAINTEXT

View File

@@ -68,7 +68,10 @@ To bit_cast(const From &src) {
return dst; return dst;
} }
#endif #endif
using std::lerp;
// clang-format off
inline float lerp(float completion, float start, float end) = delete; // Please use std::lerp. Notice that it has different order on arguments!
// clang-format on
// std::byteswap from C++23 // std::byteswap from C++23
template<typename T> constexpr T byteswap(T n) { template<typename T> constexpr T byteswap(T n) {

View File

@@ -83,6 +83,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
item->type = type; item->type = type;
item->callback = std::move(func); item->callback = std::move(func);
item->remove = false; item->remove = false;
item->is_retry = is_retry;
#ifndef ESPHOME_THREAD_SINGLE #ifndef ESPHOME_THREAD_SINGLE
// Special handling for defer() (delay = 0, type = TIMEOUT) // Special handling for defer() (delay = 0, type = TIMEOUT)
@@ -134,8 +135,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
// For retries, check if there's a cancelled timeout first // For retries, check if there's a cancelled timeout first
if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT && if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT &&
(has_cancelled_timeout_in_container_(this->items_, component, name_cstr) || (has_cancelled_timeout_in_container_(this->items_, component, name_cstr, /* match_retry= */ true) ||
has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr))) { has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr, /* match_retry= */ true))) {
// Skip scheduling - the retry was cancelled // Skip scheduling - the retry was cancelled
#ifdef ESPHOME_DEBUG_SCHEDULER #ifdef ESPHOME_DEBUG_SCHEDULER
ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", name_cstr); ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", name_cstr);
@@ -198,25 +199,27 @@ void retry_handler(const std::shared_ptr<RetryArgs> &args) {
// second execution of `func` happens after `initial_wait_time` // second execution of `func` happens after `initial_wait_time`
args->scheduler->set_timer_common_( args->scheduler->set_timer_common_(
args->component, Scheduler::SchedulerItem::TIMEOUT, false, &args->name, args->current_interval, args->component, Scheduler::SchedulerItem::TIMEOUT, false, &args->name, args->current_interval,
[args]() { retry_handler(args); }, true); [args]() { retry_handler(args); }, /* is_retry= */ true);
// backoff_increase_factor applied to third & later executions // backoff_increase_factor applied to third & later executions
args->current_interval *= args->backoff_increase_factor; args->current_interval *= args->backoff_increase_factor;
} }
void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, void HOT Scheduler::set_retry_common_(Component *component, bool is_static_string, const void *name_ptr,
uint8_t max_attempts, std::function<RetryResult(uint8_t)> func, uint32_t initial_wait_time, uint8_t max_attempts,
float backoff_increase_factor) { std::function<RetryResult(uint8_t)> func, float backoff_increase_factor) {
if (!name.empty()) const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr);
this->cancel_retry(component, name);
if (name_cstr != nullptr)
this->cancel_retry(component, name_cstr);
if (initial_wait_time == SCHEDULER_DONT_RUN) if (initial_wait_time == SCHEDULER_DONT_RUN)
return; return;
ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)", ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)",
name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor); name_cstr ? name_cstr : "", initial_wait_time, max_attempts, backoff_increase_factor);
if (backoff_increase_factor < 0.0001) { if (backoff_increase_factor < 0.0001) {
ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name.c_str()); ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name_cstr ? name_cstr : "");
backoff_increase_factor = 1; backoff_increase_factor = 1;
} }
@@ -225,15 +228,36 @@ void HOT Scheduler::set_retry(Component *component, const std::string &name, uin
args->retry_countdown = max_attempts; args->retry_countdown = max_attempts;
args->current_interval = initial_wait_time; args->current_interval = initial_wait_time;
args->component = component; args->component = component;
args->name = "retry$" + name; args->name = name_cstr ? name_cstr : ""; // Convert to std::string for RetryArgs
args->backoff_increase_factor = backoff_increase_factor; args->backoff_increase_factor = backoff_increase_factor;
args->scheduler = this; args->scheduler = this;
// First execution of `func` immediately // First execution of `func` immediately - use set_timer_common_ with is_retry=true
this->set_timeout(component, args->name, 0, [args]() { retry_handler(args); }); this->set_timer_common_(
component, SchedulerItem::TIMEOUT, false, &args->name, 0, [args]() { retry_handler(args); },
/* is_retry= */ true);
}
void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time,
uint8_t max_attempts, std::function<RetryResult(uint8_t)> func,
float backoff_increase_factor) {
this->set_retry_common_(component, false, &name, initial_wait_time, max_attempts, std::move(func),
backoff_increase_factor);
}
void HOT Scheduler::set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor) {
this->set_retry_common_(component, true, name, initial_wait_time, max_attempts, std::move(func),
backoff_increase_factor);
} }
bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) { bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) {
return this->cancel_timeout(component, "retry$" + name); return this->cancel_retry(component, name.c_str());
}
bool HOT Scheduler::cancel_retry(Component *component, const char *name) {
// Cancel timeouts that have is_retry flag set
LockGuard guard{this->lock_};
return this->cancel_item_locked_(component, name, SchedulerItem::TIMEOUT, /* match_retry= */ true);
} }
optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) { optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) {
@@ -479,7 +503,8 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co
} }
// Helper to cancel items by name - must be called with lock held // Helper to cancel items by name - must be called with lock held
bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type) { bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type,
bool match_retry) {
// Early return if name is invalid - no items to cancel // Early return if name is invalid - no items to cancel
if (name_cstr == nullptr) { if (name_cstr == nullptr) {
return false; return false;
@@ -492,7 +517,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
// Only check defer queue for timeouts (intervals never go there) // Only check defer queue for timeouts (intervals never go there)
if (type == SchedulerItem::TIMEOUT) { if (type == SchedulerItem::TIMEOUT) {
for (auto &item : this->defer_queue_) { for (auto &item : this->defer_queue_) {
if (this->matches_item_(item, component, name_cstr, type)) { if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
item->remove = true; item->remove = true;
total_cancelled++; total_cancelled++;
} }
@@ -502,7 +527,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
// Cancel items in the main heap // Cancel items in the main heap
for (auto &item : this->items_) { for (auto &item : this->items_) {
if (this->matches_item_(item, component, name_cstr, type)) { if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
item->remove = true; item->remove = true;
total_cancelled++; total_cancelled++;
this->to_remove_++; // Track removals for heap items this->to_remove_++; // Track removals for heap items
@@ -511,7 +536,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
// Cancel items in to_add_ // Cancel items in to_add_
for (auto &item : this->to_add_) { for (auto &item : this->to_add_) {
if (this->matches_item_(item, component, name_cstr, type)) { if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
item->remove = true; item->remove = true;
total_cancelled++; total_cancelled++;
// Don't track removals for to_add_ items // Don't track removals for to_add_ items

View File

@@ -61,7 +61,10 @@ class Scheduler {
bool cancel_interval(Component *component, const char *name); bool cancel_interval(Component *component, const char *name);
void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f); std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
void set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
bool cancel_retry(Component *component, const std::string &name); bool cancel_retry(Component *component, const std::string &name);
bool cancel_retry(Component *component, const char *name);
// Calculate when the next scheduled item should run // Calculate when the next scheduled item should run
// @param now Fresh timestamp from millis() - must not be stale/cached // @param now Fresh timestamp from millis() - must not be stale/cached
@@ -98,11 +101,18 @@ class Scheduler {
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1; enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
bool remove : 1; bool remove : 1;
bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[]) bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[])
// 5 bits padding bool is_retry : 1; // True if this is a retry timeout
// 4 bits padding
// Constructor // Constructor
SchedulerItem() SchedulerItem()
: component(nullptr), interval(0), next_execution_(0), type(TIMEOUT), remove(false), name_is_dynamic(false) { : component(nullptr),
interval(0),
next_execution_(0),
type(TIMEOUT),
remove(false),
name_is_dynamic(false),
is_retry(false) {
name_.static_name = nullptr; name_.static_name = nullptr;
} }
@@ -156,6 +166,10 @@ class Scheduler {
void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr, void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr,
uint32_t delay, std::function<void()> func, bool is_retry = false); uint32_t delay, std::function<void()> func, bool is_retry = false);
// Common implementation for retry
void set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, uint32_t initial_wait_time,
uint8_t max_attempts, std::function<RetryResult(uint8_t)> func, float backoff_increase_factor);
uint64_t millis_64_(uint32_t now); uint64_t millis_64_(uint32_t now);
// Cleanup logically deleted items from the scheduler // Cleanup logically deleted items from the scheduler
// Returns the number of items remaining after cleanup // Returns the number of items remaining after cleanup
@@ -165,7 +179,7 @@ class Scheduler {
private: private:
// Helper to cancel items by name - must be called with lock held // Helper to cancel items by name - must be called with lock held
bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type); bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type, bool match_retry = false);
// Helper to extract name as const char* from either static string or std::string // Helper to extract name as const char* from either static string or std::string
inline const char *get_name_cstr_(bool is_static_string, const void *name_ptr) { inline const char *get_name_cstr_(bool is_static_string, const void *name_ptr) {
@@ -177,8 +191,9 @@ class Scheduler {
// Helper function to check if item matches criteria for cancellation // Helper function to check if item matches criteria for cancellation
inline bool HOT matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr, inline bool HOT matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr,
SchedulerItem::Type type, bool skip_removed = true) const { SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const {
if (item->component != component || item->type != type || (skip_removed && item->remove)) { if (item->component != component || item->type != type || (skip_removed && item->remove) ||
(match_retry && !item->is_retry)) {
return false; return false;
} }
const char *item_name = item->get_name(); const char *item_name = item->get_name();
@@ -206,10 +221,11 @@ class Scheduler {
// Template helper to check if any item in a container matches our criteria // Template helper to check if any item in a container matches our criteria
template<typename Container> template<typename Container>
bool has_cancelled_timeout_in_container_(const Container &container, Component *component, bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr,
const char *name_cstr) const { bool match_retry) const {
for (const auto &item : container) { for (const auto &item : container) {
if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, false)) { if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry,
/* skip_removed= */ false)) {
return true; return true;
} }
} }

View File

@@ -225,9 +225,10 @@ class _Schema(vol.Schema):
return ret return ret
schema = schemas[0] schema = schemas[0]
extra_schemas = self._extra_schemas.copy()
if isinstance(schema, _Schema):
extra_schemas.extend(schema._extra_schemas)
if isinstance(schema, vol.Schema): if isinstance(schema, vol.Schema):
schema = schema.schema schema = schema.schema
ret = super().extend(schema, extra=extra) ret = super().extend(schema, extra=extra)
return _Schema( return _Schema(ret.schema, extra=ret.extra, extra_schemas=extra_schemas)
ret.schema, extra=ret.extra, extra_schemas=self._extra_schemas.copy()
)

View File

@@ -78,7 +78,7 @@ lib_deps =
glmnet/Dsmr@0.7 ; dsmr glmnet/Dsmr@0.7 ; dsmr
rweather/Crypto@0.4.0 ; dsmr rweather/Crypto@0.4.0 ; dsmr
dudanov/MideaUART@1.1.9 ; midea dudanov/MideaUART@1.1.9 ; midea
tonia/HeatpumpIR@1.0.35 ; heatpumpir tonia/HeatpumpIR@1.0.37 ; heatpumpir
build_flags = build_flags =
${common.build_flags} ${common.build_flags}
-DUSE_ARDUINO -DUSE_ARDUINO
@@ -125,7 +125,7 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script
; This are common settings for the ESP32 (all variants) using Arduino. ; This are common settings for the ESP32 (all variants) using Arduino.
[common:esp32-arduino] [common:esp32-arduino]
extends = common:arduino extends = common:arduino
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21/platform-espressif32.zip platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21-1/platform-espressif32.zip
platform_packages = platform_packages =
pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.2.1/esp32-3.2.1.zip pioarduino/framework-arduinoespressif32@https://github.com/espressif/arduino-esp32/releases/download/3.2.1/esp32-3.2.1.zip
@@ -161,7 +161,7 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script
; This are common settings for the ESP32 (all variants) using IDF. ; This are common settings for the ESP32 (all variants) using IDF.
[common:esp32-idf] [common:esp32-idf]
extends = common:idf extends = common:idf
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21/platform-espressif32.zip platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.21-1/platform-espressif32.zip
platform_packages = platform_packages =
pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.4.2/esp-idf-v5.4.2.zip pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v5.4.2/esp-idf-v5.4.2.zip

View File

@@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
esptool==4.9.0 esptool==4.9.0
click==8.1.7 click==8.1.7
esphome-dashboard==20250514.0 esphome-dashboard==20250514.0
aioesphomeapi==37.1.2 aioesphomeapi==37.1.4
zeroconf==0.147.0 zeroconf==0.147.0
puremagic==1.30 puremagic==1.30
ruamel.yaml==0.18.14 # dashboard_import ruamel.yaml==0.18.14 # dashboard_import

View File

@@ -275,13 +275,13 @@ class TypeInfo(ABC):
Args: Args:
name: Field name name: Field name
force: Whether this is for a repeated field force: Whether this is for a repeated field
base_method: Base method name (e.g., "add_int32_field") base_method: Base method name (e.g., "add_int32")
value_expr: Optional value expression (defaults to name) value_expr: Optional value expression (defaults to name)
""" """
field_id_size = self.calculate_field_id_size() field_id_size = self.calculate_field_id_size()
method = f"{base_method}_repeated" if force else base_method method = f"{base_method}_force" if force else base_method
value = value_expr if value_expr else name value = value_expr if value_expr else name
return f"ProtoSize::{method}(total_size, {field_id_size}, {value});" return f"size.{method}({field_id_size}, {value});"
@abstractmethod @abstractmethod
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
@@ -389,7 +389,7 @@ class DoubleType(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size() field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_double_field(total_size, {field_id_size}, {name});" return f"size.add_double({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int: def get_fixed_size_bytes(self) -> int:
return 8 return 8
@@ -413,7 +413,7 @@ class FloatType(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size() field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_float_field(total_size, {field_id_size}, {name});" return f"size.add_float({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int: def get_fixed_size_bytes(self) -> int:
return 4 return 4
@@ -436,7 +436,7 @@ class Int64Type(TypeInfo):
return o return o
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_int64_field") return self._get_simple_size_calculation(name, force, "add_int64")
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -456,7 +456,7 @@ class UInt64Type(TypeInfo):
return o return o
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_uint64_field") return self._get_simple_size_calculation(name, force, "add_uint64")
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -476,7 +476,7 @@ class Int32Type(TypeInfo):
return o return o
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_int32_field") return self._get_simple_size_calculation(name, force, "add_int32")
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -497,7 +497,7 @@ class Fixed64Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size() field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_fixed64_field(total_size, {field_id_size}, {name});" return f"size.add_fixed64({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int: def get_fixed_size_bytes(self) -> int:
return 8 return 8
@@ -521,7 +521,7 @@ class Fixed32Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size() field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_fixed32_field(total_size, {field_id_size}, {name});" return f"size.add_fixed32({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int: def get_fixed_size_bytes(self) -> int:
return 4 return 4
@@ -542,7 +542,7 @@ class BoolType(TypeInfo):
return f"out.append(YESNO({name}));" return f"out.append(YESNO({name}));"
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_bool_field") return self._get_simple_size_calculation(name, force, "add_bool")
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 1 # field ID + 1 byte return self.calculate_field_id_size() + 1 # field ID + 1 byte
@@ -561,11 +561,16 @@ class StringType(TypeInfo):
@property @property
def public_content(self) -> list[str]: def public_content(self) -> list[str]:
content: list[str] = [] content: list[str] = []
# Add std::string storage if message needs decoding
if self._needs_decode: # Check if no_zero_copy option is set
no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False)
# Add std::string storage if message needs decoding OR if no_zero_copy is set
if self._needs_decode or no_zero_copy:
content.append(f"std::string {self.field_name}{{}};") content.append(f"std::string {self.field_name}{{}};")
if self._needs_encode: # Only add StringRef if encoding is needed AND no_zero_copy is not set
if self._needs_encode and not no_zero_copy:
content.extend( content.extend(
[ [
# Add StringRef field if message needs encoding # Add StringRef field if message needs encoding
@@ -580,13 +585,27 @@ class StringType(TypeInfo):
@property @property
def encode_content(self) -> str: def encode_content(self) -> str:
# Check if no_zero_copy option is set
no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False)
if no_zero_copy:
# Use the std::string directly
return f"buffer.encode_string({self.number}, this->{self.field_name});"
# Use the StringRef
return f"buffer.encode_string({self.number}, this->{self.field_name}_ref_);" return f"buffer.encode_string({self.number}, this->{self.field_name}_ref_);"
def dump(self, name): def dump(self, name):
# Check if no_zero_copy option is set
no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False)
# If name is 'it', this is a repeated field element - always use string # If name is 'it', this is a repeated field element - always use string
if name == "it": if name == "it":
return "append_quoted_string(out, StringRef(it));" return "append_quoted_string(out, StringRef(it));"
# If no_zero_copy is set, always use std::string
if no_zero_copy:
return f'out.append("\'").append(this->{self.field_name}).append("\'");'
# For SOURCE_CLIENT only, always use std::string # For SOURCE_CLIENT only, always use std::string
if not self._needs_encode: if not self._needs_encode:
return f'out.append("\'").append(this->{self.field_name}).append("\'");' return f'out.append("\'").append(this->{self.field_name}).append("\'");'
@@ -606,6 +625,13 @@ class StringType(TypeInfo):
@property @property
def dump_content(self) -> str: def dump_content(self) -> str:
# Check if no_zero_copy option is set
no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False)
# If no_zero_copy is set, always use std::string
if no_zero_copy:
return f'dump_field(out, "{self.name}", this->{self.field_name});'
# For SOURCE_CLIENT only, use std::string # For SOURCE_CLIENT only, use std::string
if not self._needs_encode: if not self._needs_encode:
return f'dump_field(out, "{self.name}", this->{self.field_name});' return f'dump_field(out, "{self.name}", this->{self.field_name});'
@@ -621,20 +647,29 @@ class StringType(TypeInfo):
return o return o
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
# For SOURCE_CLIENT only messages, use the string field directly # Check if no_zero_copy option is set
if not self._needs_encode: no_zero_copy = get_field_opt(self._field, pb.no_zero_copy, False)
return self._get_simple_size_calculation(name, force, "add_string_field")
# For SOURCE_CLIENT only messages or no_zero_copy, use the string field directly
if not self._needs_encode or no_zero_copy:
# For no_zero_copy, we need to use .size() on the string
if no_zero_copy and name != "it":
field_id_size = self.calculate_field_id_size()
return (
f"size.add_length({field_id_size}, this->{self.field_name}.size());"
)
return self._get_simple_size_calculation(name, force, "add_length")
# Check if this is being called from a repeated field context # Check if this is being called from a repeated field context
# In that case, 'name' will be 'it' and we need to use the repeated version # In that case, 'name' will be 'it' and we need to use the repeated version
if name == "it": if name == "it":
# For repeated fields, we need to use add_string_field_repeated which includes field ID # For repeated fields, we need to use add_length_force which includes field ID
field_id_size = self.calculate_field_id_size() field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_string_field_repeated(total_size, {field_id_size}, it);" return f"size.add_length_force({field_id_size}, it.size());"
# For messages that need encoding, use the StringRef size # For messages that need encoding, use the StringRef size
field_id_size = self.calculate_field_id_size() field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_string_field(total_size, {field_id_size}, this->{self.field_name}_ref_.size());" return f"size.add_length({field_id_size}, this->{self.field_name}_ref_.size());"
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical string
@@ -768,7 +803,7 @@ class BytesType(TypeInfo):
return o return o
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
return f"ProtoSize::add_bytes_field(total_size, {self.calculate_field_id_size()}, this->{self.field_name}_len_);" return f"size.add_length({self.calculate_field_id_size()}, this->{self.field_name}_len_);"
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes return self.calculate_field_id_size() + 8 # field ID + 8 bytes typical bytes
@@ -842,14 +877,10 @@ class FixedArrayBytesType(TypeInfo):
field_id_size = self.calculate_field_id_size() field_id_size = self.calculate_field_id_size()
if force: if force:
# For repeated fields, always calculate size # For repeated fields, always calculate size (no zero check)
return f"total_size += {field_id_size} + ProtoSize::varint(static_cast<uint32_t>({length_field})) + {length_field};" return f"size.add_length_force({field_id_size}, {length_field});"
# For non-repeated fields, skip if length is 0 (matching encode_string behavior) # For non-repeated fields, add_length already checks for zero
return ( return f"size.add_length({field_id_size}, {length_field});"
f"if ({length_field} != 0) {{\n"
f" total_size += {field_id_size} + ProtoSize::varint(static_cast<uint32_t>({length_field})) + {length_field};\n"
f"}}"
)
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
# Estimate based on typical BLE advertisement size # Estimate based on typical BLE advertisement size
@@ -876,7 +907,7 @@ class UInt32Type(TypeInfo):
return o return o
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_uint32_field") return self._get_simple_size_calculation(name, force, "add_uint32")
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -912,7 +943,7 @@ class EnumType(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation( return self._get_simple_size_calculation(
name, force, "add_enum_field", f"static_cast<uint32_t>({name})" name, force, "add_uint32", f"static_cast<uint32_t>({name})"
) )
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
@@ -934,7 +965,7 @@ class SFixed32Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size() field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_sfixed32_field(total_size, {field_id_size}, {name});" return f"size.add_sfixed32({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int: def get_fixed_size_bytes(self) -> int:
return 4 return 4
@@ -958,7 +989,7 @@ class SFixed64Type(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
field_id_size = self.calculate_field_id_size() field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_sfixed64_field(total_size, {field_id_size}, {name});" return f"size.add_sfixed64({field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int: def get_fixed_size_bytes(self) -> int:
return 8 return 8
@@ -981,7 +1012,7 @@ class SInt32Type(TypeInfo):
return o return o
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_sint32_field") return self._get_simple_size_calculation(name, force, "add_sint32")
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -1001,7 +1032,7 @@ class SInt64Type(TypeInfo):
return o return o
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_simple_size_calculation(name, force, "add_sint64_field") return self._get_simple_size_calculation(name, force, "add_sint64")
def get_estimated_size(self) -> int: def get_estimated_size(self) -> int:
return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint return self.calculate_field_id_size() + 3 # field ID + 3 bytes typical varint
@@ -1132,6 +1163,10 @@ class FixedArrayRepeatedType(TypeInfo):
class RepeatedTypeInfo(TypeInfo): class RepeatedTypeInfo(TypeInfo):
def __init__(self, field: descriptor.FieldDescriptorProto) -> None: def __init__(self, field: descriptor.FieldDescriptorProto) -> None:
super().__init__(field) super().__init__(field)
# Check if this is a pointer field by looking for container_pointer option
self._container_type = get_field_opt(field, pb.container_pointer, "")
self._use_pointer = bool(self._container_type)
# For repeated fields, we need to get the base type info # For repeated fields, we need to get the base type info
# but we can't call create_field_type_info as it would cause recursion # but we can't call create_field_type_info as it would cause recursion
# So we extract just the type creation logic # So we extract just the type creation logic
@@ -1147,6 +1182,13 @@ class RepeatedTypeInfo(TypeInfo):
@property @property
def cpp_type(self) -> str: def cpp_type(self) -> str:
if self._use_pointer and self._container_type:
# For pointer fields, use the specified container type
# If the container type already includes the element type (e.g., std::set<climate::ClimateMode>)
# use it as-is, otherwise append the element type
if "<" in self._container_type and ">" in self._container_type:
return f"const {self._container_type}*"
return f"const {self._container_type}<{self._ti.cpp_type}>*"
return f"std::vector<{self._ti.cpp_type}>" return f"std::vector<{self._ti.cpp_type}>"
@property @property
@@ -1167,6 +1209,9 @@ class RepeatedTypeInfo(TypeInfo):
@property @property
def decode_varint_content(self) -> str: def decode_varint_content(self) -> str:
# Pointer fields don't support decoding
if self._use_pointer:
return None
content = self._ti.decode_varint content = self._ti.decode_varint
if content is None: if content is None:
return None return None
@@ -1176,6 +1221,9 @@ class RepeatedTypeInfo(TypeInfo):
@property @property
def decode_length_content(self) -> str: def decode_length_content(self) -> str:
# Pointer fields don't support decoding
if self._use_pointer:
return None
content = self._ti.decode_length content = self._ti.decode_length
if content is None and isinstance(self._ti, MessageType): if content is None and isinstance(self._ti, MessageType):
# Special handling for non-template message decoding # Special handling for non-template message decoding
@@ -1188,6 +1236,9 @@ class RepeatedTypeInfo(TypeInfo):
@property @property
def decode_32bit_content(self) -> str: def decode_32bit_content(self) -> str:
# Pointer fields don't support decoding
if self._use_pointer:
return None
content = self._ti.decode_32bit content = self._ti.decode_32bit
if content is None: if content is None:
return None return None
@@ -1197,6 +1248,9 @@ class RepeatedTypeInfo(TypeInfo):
@property @property
def decode_64bit_content(self) -> str: def decode_64bit_content(self) -> str:
# Pointer fields don't support decoding
if self._use_pointer:
return None
content = self._ti.decode_64bit content = self._ti.decode_64bit
if content is None: if content is None:
return None return None
@@ -1211,6 +1265,15 @@ class RepeatedTypeInfo(TypeInfo):
@property @property
def encode_content(self) -> str: def encode_content(self) -> str:
if self._use_pointer:
# For pointer fields, just dereference (pointer should never be null in our use case)
o = f"for (const auto &it : *this->{self.field_name}) {{\n"
if isinstance(self._ti, EnumType):
o += f" buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n"
else:
o += f" buffer.{self._ti.encode_func}({self.number}, it, true);\n"
o += "}"
return o
o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n" o = f"for (auto {'' if self._ti_is_bool else '&'}it : this->{self.field_name}) {{\n"
if isinstance(self._ti, EnumType): if isinstance(self._ti, EnumType):
o += f" buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n" o += f" buffer.{self._ti.encode_func}({self.number}, static_cast<uint32_t>(it), true);\n"
@@ -1221,6 +1284,11 @@ class RepeatedTypeInfo(TypeInfo):
@property @property
def dump_content(self) -> str: def dump_content(self) -> str:
if self._use_pointer:
# For pointer fields, dereference and use the existing helper
return _generate_array_dump_content(
self._ti, f"*this->{self.field_name}", self.name, is_bool=False
)
return _generate_array_dump_content( return _generate_array_dump_content(
self._ti, f"this->{self.field_name}", self.name, is_bool=self._ti_is_bool self._ti, f"this->{self.field_name}", self.name, is_bool=self._ti_is_bool
) )
@@ -1231,29 +1299,34 @@ class RepeatedTypeInfo(TypeInfo):
def get_size_calculation(self, name: str, force: bool = False) -> str: def get_size_calculation(self, name: str, force: bool = False) -> str:
# For repeated fields, we always need to pass force=True to the underlying type's calculation # For repeated fields, we always need to pass force=True to the underlying type's calculation
# This is because the encode method always sets force=true for repeated fields # This is because the encode method always sets force=true for repeated fields
# Handle message types separately as they use a dedicated helper
if isinstance(self._ti, MessageType): if isinstance(self._ti, MessageType):
# For repeated messages, use the dedicated helper that handles iteration internally
field_id_size = self._ti.calculate_field_id_size() field_id_size = self._ti.calculate_field_id_size()
return ( container = f"*{name}" if self._use_pointer else name
f"ProtoSize::add_repeated_message(total_size, {field_id_size}, {name});" return f"size.add_repeated_message({field_id_size}, {container});"
)
# For other repeated types, use the underlying type's size calculation with force=True # For non-message types, generate size calculation with iteration
o = f"if (!{name}.empty()) {{\n" container_ref = f"*{name}" if self._use_pointer else name
empty_check = f"{name}->empty()" if self._use_pointer else f"{name}.empty()"
# Check if this is a fixed-size type by seeing if it has a fixed byte count o = f"if (!{empty_check}) {{\n"
# Check if this is a fixed-size type
num_bytes = self._ti.get_fixed_size_bytes() num_bytes = self._ti.get_fixed_size_bytes()
if num_bytes is not None: if num_bytes is not None:
# Fixed types have constant size per element, so we can multiply # Fixed types have constant size per element
field_id_size = self._ti.calculate_field_id_size() field_id_size = self._ti.calculate_field_id_size()
# Pre-calculate the total bytes per element
bytes_per_element = field_id_size + num_bytes bytes_per_element = field_id_size + num_bytes
o += f" total_size += {name}.size() * {bytes_per_element};\n" size_expr = f"{name}->size()" if self._use_pointer else f"{name}.size()"
o += f" size.add_precalculated_size({size_expr} * {bytes_per_element});\n"
else: else:
# Other types need the actual value # Other types need the actual value
o += f" for (const auto {'' if self._ti_is_bool else '&'}it : {name}) {{\n" auto_ref = "" if self._ti_is_bool else "&"
o += f" for (const auto {auto_ref}it : {container_ref}) {{\n"
o += f" {self._ti.get_size_calculation('it', True)}\n" o += f" {self._ti.get_size_calculation('it', True)}\n"
o += " }\n" o += " }\n"
o += "}" o += "}"
return o return o
@@ -1680,11 +1753,11 @@ def build_message_type(
if needs_encode and encode: if needs_encode and encode:
o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{" o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{"
if len(encode) == 1 and len(encode[0]) + len(o) + 3 < 120: if len(encode) == 1 and len(encode[0]) + len(o) + 3 < 120:
o += f" {encode[0]} " o += f" {encode[0]} }}\n"
else: else:
o += "\n" o += "\n"
o += indent("\n".join(encode)) + "\n" o += indent("\n".join(encode)) + "\n"
o += "}\n" o += "}\n"
cpp += o cpp += o
prot = "void encode(ProtoWriteBuffer buffer) const override;" prot = "void encode(ProtoWriteBuffer buffer) const override;"
public_content.append(prot) public_content.append(prot)
@@ -1692,17 +1765,17 @@ def build_message_type(
# Add calculate_size method only if this message needs encoding and has fields # Add calculate_size method only if this message needs encoding and has fields
if needs_encode and size_calc: if needs_encode and size_calc:
o = f"void {desc.name}::calculate_size(uint32_t &total_size) const {{" o = f"void {desc.name}::calculate_size(ProtoSize &size) const {{"
# For a single field, just inline it for simplicity # For a single field, just inline it for simplicity
if len(size_calc) == 1 and len(size_calc[0]) + len(o) + 3 < 120: if len(size_calc) == 1 and len(size_calc[0]) + len(o) + 3 < 120:
o += f" {size_calc[0]} " o += f" {size_calc[0]} }}\n"
else: else:
# For multiple fields # For multiple fields
o += "\n" o += "\n"
o += indent("\n".join(size_calc)) + "\n" o += indent("\n".join(size_calc)) + "\n"
o += "}\n" o += "}\n"
cpp += o cpp += o
prot = "void calculate_size(uint32_t &total_size) const override;" prot = "void calculate_size(ProtoSize &size) const override;"
public_content.append(prot) public_content.append(prot)
# If no fields to calculate size for or message doesn't need encoding, the default implementation in ProtoMessage will be used # If no fields to calculate size for or message doesn't need encoding, the default implementation in ProtoMessage will be used
@@ -1733,8 +1806,13 @@ def build_message_type(
if base_class: if base_class:
out = f"class {desc.name} : public {base_class} {{\n" out = f"class {desc.name} : public {base_class} {{\n"
else: else:
# Determine inheritance based on whether the message needs decoding # Check if message has any non-deprecated fields
base_class = "ProtoDecodableMessage" if needs_decode else "ProtoMessage" has_fields = any(not field.options.deprecated for field in desc.field)
# Determine inheritance based on whether the message needs decoding and has fields
if needs_decode and has_fields:
base_class = "ProtoDecodableMessage"
else:
base_class = "ProtoMessage"
out = f"class {desc.name} : public {base_class} {{\n" out = f"class {desc.name} : public {base_class} {{\n"
out += " public:\n" out += " public:\n"
out += indent("\n".join(public_content)) + "\n" out += indent("\n".join(public_content)) + "\n"
@@ -2000,7 +2078,14 @@ def build_service_message_type(
hout += f"virtual void {func}(const {mt.name} &value){{}};\n" hout += f"virtual void {func}(const {mt.name} &value){{}};\n"
case = "" case = ""
case += f"{mt.name} msg;\n" case += f"{mt.name} msg;\n"
case += "msg.decode(msg_data, msg_size);\n" # Check if this message has any fields (excluding deprecated ones)
has_fields = any(not field.options.deprecated for field in mt.field)
if has_fields:
# Normal case: decode the message
case += "msg.decode(msg_data, msg_size);\n"
else:
# Empty message optimization: skip decode since there are no fields
case += "// Empty message: no decode needed\n"
if log: if log:
case += "#ifdef HAS_PROTO_MESSAGE_DUMP\n" case += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n' case += f'ESP_LOGVV(TAG, "{func}: %s", msg.dump().c_str());\n'
@@ -2029,6 +2114,7 @@ def main() -> None:
d = descriptor.FileDescriptorSet.FromString(proto_content) d = descriptor.FileDescriptorSet.FromString(proto_content)
file = d.file[0] file = d.file[0]
content = FILE_HEADER content = FILE_HEADER
content += """\ content += """\
#pragma once #pragma once
@@ -2037,7 +2123,10 @@ def main() -> None:
#include "esphome/core/string_ref.h" #include "esphome/core/string_ref.h"
#include "proto.h" #include "proto.h"
#include "api_pb2_includes.h"
"""
content += """
namespace esphome::api { namespace esphome::api {
""" """

View File

@@ -6,7 +6,7 @@ set -e
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
if [ ! -n "$VIRTUAL_ENV" ]; then if [ ! -n "$VIRTUAL_ENV" ]; then
if [ -x "$(command -v uv)" ]; then if [ -x "$(command -v uv)" ]; then
uv venv venv uv venv --seed venv
else else
python3 -m venv venv python3 -m venv venv
fi fi

View File

@@ -0,0 +1,51 @@
"""
Test schema.extend functionality in esphome.config_validation.
"""
from typing import Any
import esphome.config_validation as cv
def test_config_extend() -> None:
"""Test that schema.extend correctly merges schemas with extras."""
def func1(data: dict[str, Any]) -> dict[str, Any]:
data["extra_1"] = "value1"
return data
def func2(data: dict[str, Any]) -> dict[str, Any]:
data["extra_2"] = "value2"
return data
schema1 = cv.Schema(
{
cv.Required("key1"): cv.string,
}
)
schema1.add_extra(func1)
schema2 = cv.Schema(
{
cv.Required("key2"): cv.string,
}
)
schema2.add_extra(func2)
extended_schema = schema1.extend(schema2)
config = {
"key1": "initial_value1",
"key2": "initial_value2",
}
validated = extended_schema(config)
assert validated["key1"] == "initial_value1"
assert validated["key2"] == "initial_value2"
assert validated["extra_1"] == "value1"
assert validated["extra_2"] == "value2"
# Check the opposite order of extension
extended_schema = schema2.extend(schema1)
validated = extended_schema(config)
assert validated["key1"] == "initial_value1"
assert validated["key2"] == "initial_value2"
assert validated["extra_1"] == "value1"
assert validated["extra_2"] == "value2"

View File

@@ -0,0 +1,6 @@
packages:
base: !include common.yaml
sensor:
- id: !extend my_sensor
pin: GPIO50

View File

@@ -738,7 +738,7 @@ lvgl:
id: bar_id id: bar_id
value: !lambda return (int)((float)rand() / RAND_MAX * 100); value: !lambda return (int)((float)rand() / RAND_MAX * 100);
start_value: !lambda return (int)((float)rand() / RAND_MAX * 100); start_value: !lambda return (int)((float)rand() / RAND_MAX * 100);
mode: symmetrical mode: range
- logger.log: - logger.log:
format: "bar value %f" format: "bar value %f"
args: [x] args: [x]

View File

@@ -12,6 +12,8 @@ display:
#- platform: mipi_dsi #- platform: mipi_dsi
#id: backlight_id #id: backlight_id
psram:
i2c: i2c:
sda: GPIO7 sda: GPIO7
scl: GPIO8 scl: GPIO8

View File

@@ -6,6 +6,12 @@ esphome:
- output.set_level: - output.set_level:
id: light_output_1 id: light_output_1
level: 50% level: 50%
- output.set_min_power:
id: light_output_1
min_power: 20%
- output.set_max_power:
id: light_output_1
max_power: 80%
output: output:
- platform: ${output_platform} - platform: ${output_platform}

View File

@@ -0,0 +1,5 @@
switch:
- name: ${door_name} Garage Door Switch
platform: gpio
pin: ${door_pin}
id: ${door_id}

View File

@@ -0,0 +1,19 @@
packages:
left_garage_door: !include
file: garage-door.yaml
vars:
door_name: Left
door_pin: 1
door_id: left_garage_door
middle_garage_door: !include
file: garage-door.yaml
vars:
door_name: Middle
door_pin: 2
door_id: middle_garage_door
right_garage_door: !include
file: garage-door.yaml
vars:
door_name: Right
door_pin: 3
door_id: right_garage_door

View File

@@ -1,44 +1,3 @@
sensor:
- platform: template
name: "Template Sensor"
id: template_sens
lambda: |-
if (id(some_binary_sensor).state) {
return 42.0;
} else {
return 0.0;
}
update_interval: 60s
filters:
- offset: 10
- multiply: 1
- offset: !lambda return 10;
- multiply: !lambda return 2;
- filter_out:
- 10
- 20
- !lambda return 10;
- filter_out: 10
- filter_out: !lambda return NAN;
- timeout:
timeout: 10s
value: !lambda return 10;
- timeout:
timeout: 1h
value: 20.0
- timeout:
timeout: 1d
- to_ntc_resistance:
calibration:
- 10.0kOhm -> 25°C
- 27.219kOhm -> 0°C
- 14.674kOhm -> 15°C
- to_ntc_temperature:
calibration:
- 10.0kOhm -> 25°C
- 27.219kOhm -> 0°C
- 14.674kOhm -> 15°C
esphome: esphome:
on_boot: on_boot:
- sensor.template.publish: - sensor.template.publish:
@@ -82,6 +41,123 @@ binary_sensor:
sensor.in_range: sensor.in_range:
id: template_sens id: template_sens
below: 30.0 below: 30.0
filters:
- invert:
- delayed_on: 100ms
- delayed_off: 100ms
- delayed_on_off: !lambda "if (id(test_switch).state) return 1000; else return 0;"
- delayed_on_off:
time_on: 10s
time_off: !lambda "if (id(test_switch).state) return 1000; else return 0;"
- autorepeat:
- delay: 1s
time_off: 100ms
time_on: 900ms
- delay: 5s
time_off: 100ms
time_on: 400ms
- lambda: |-
if (id(other_binary_sensor).state) {
return x;
} else {
return {};
}
- settle: 500ms
- timeout: 5s
sensor:
- platform: template
name: "Template Sensor"
id: template_sens
lambda: |-
if (id(some_binary_sensor).state) {
return 42.0;
} else {
return 0.0;
}
update_interval: 60s
filters:
- calibrate_linear:
- 0.0 -> 0.0
- 40.0 -> 45.0
- 100.0 -> 102.5
- calibrate_polynomial:
degree: 2
datapoints:
# Map 0.0 (from sensor) to 0.0 (true value)
- 0.0 -> 0.0
- 10.0 -> 12.1
- 13.0 -> 14.0
- clamp:
max_value: 10.0
min_value: -10.0
- debounce: 0.1s
- delta: 5.0
- exponential_moving_average:
alpha: 0.1
send_every: 15
- filter_out:
- 10
- 20
- !lambda return 10;
- filter_out: 10
- filter_out: !lambda return NAN;
- heartbeat: 5s
- lambda: return x * (9.0/5.0) + 32.0;
- max:
window_size: 10
send_every: 2
send_first_at: 1
- median:
window_size: 7
send_every: 4
send_first_at: 3
- min:
window_size: 10
send_every: 2
send_first_at: 1
- multiply: 1
- multiply: !lambda return 2;
- offset: 10
- offset: !lambda return 10;
- or:
- quantile:
window_size: 7
send_every: 4
send_first_at: 3
quantile: .9
- round: 1
- round_to_multiple_of: 0.25
- skip_initial: 3
- sliding_window_moving_average:
window_size: 15
send_every: 15
- throttle: 1s
- throttle_average: 2s
- throttle_with_priority: 5s
- throttle_with_priority:
timeout: 3s
value:
- 42.0
- nan
- timeout:
timeout: 10s
value: !lambda return 10;
- timeout:
timeout: 1h
value: 20.0
- timeout:
timeout: 1d
- to_ntc_resistance:
calibration:
- 10.0kOhm -> 25°C
- 27.219kOhm -> 0°C
- 14.674kOhm -> 15°C
- to_ntc_temperature:
calibration:
- 10.0kOhm -> 25°C
- 27.219kOhm -> 0°C
- 14.674kOhm -> 15°C
output: output:
- platform: template - platform: template
@@ -92,6 +168,7 @@ output:
switch: switch:
- platform: template - platform: template
id: test_switch
name: "Template Switch" name: "Template Switch"
lambda: |- lambda: |-
if (id(some_binary_sensor).state) { if (id(some_binary_sensor).state) {

View File

@@ -0,0 +1,311 @@
esphome:
name: test-ha-api
friendly_name: Home Assistant API Test
host:
api:
services:
- service: trigger_all_tests
then:
- logger.log: "=== Starting Home Assistant API Tests ==="
- button.press: test_basic_service
- button.press: test_templated_service
- button.press: test_empty_string_service
- button.press: test_multiple_fields_service
- button.press: test_complex_lambda_service
- button.press: test_all_empty_service
- button.press: test_rapid_service_calls
- button.press: test_read_ha_states
- number.set:
id: ha_number
value: 42.5
- switch.turn_on: ha_switch
- switch.turn_off: ha_switch
- logger.log: "=== All tests completed ==="
logger:
level: DEBUG
# Time component for templated values
time:
- platform: homeassistant
id: homeassistant_time
# Global variables for testing
globals:
- id: test_brightness
type: int
initial_value: '75'
- id: test_string
type: std::string
initial_value: '"test_value"'
# Sensors for testing state reading
sensor:
- platform: template
name: "Test Sensor"
id: test_sensor
lambda: return 42.0;
update_interval: 0.1s
# Home Assistant sensor that reads external state
- platform: homeassistant
name: "HA Temperature"
entity_id: sensor.external_temperature
id: ha_temperature
on_value:
then:
- logger.log:
format: "HA Temperature state updated: %.1f"
args: ['x']
# Test multiple HA state sensors
- platform: homeassistant
name: "HA Humidity"
entity_id: sensor.external_humidity
id: ha_humidity
on_value:
then:
- logger.log:
format: "HA Humidity state updated: %.1f"
args: ['x']
# Binary sensor from Home Assistant
binary_sensor:
- platform: homeassistant
name: "HA Motion"
entity_id: binary_sensor.external_motion
id: ha_motion
on_state:
then:
- logger.log:
format: "HA Motion state changed: %s"
args: ['x ? "ON" : "OFF"']
# Text sensor from Home Assistant
text_sensor:
- platform: homeassistant
name: "HA Weather"
entity_id: weather.home
attribute: condition
id: ha_weather
on_value:
then:
- logger.log:
format: "HA Weather condition updated: %s"
args: ['x.c_str()']
# Test empty state handling
- platform: homeassistant
name: "HA Empty State"
entity_id: sensor.nonexistent_sensor
id: ha_empty_state
on_value:
then:
- logger.log:
format: "HA Empty state updated: %s"
args: ['x.c_str()']
# Number component for testing HA number control
number:
- platform: template
name: "HA Controlled Number"
id: ha_number
min_value: 0
max_value: 100
step: 1
optimistic: true
set_action:
- logger.log:
format: "Setting HA number to: %.1f"
args: ['x']
- homeassistant.action:
action: input_number.set_value
data:
entity_id: input_number.test_number
value: !lambda 'return to_string(x);'
# Switch component for testing HA switch control
switch:
- platform: template
name: "HA Controlled Switch"
id: ha_switch
optimistic: true
turn_on_action:
- logger.log: "Toggling HA switch: switch.test_switch ON"
- homeassistant.action:
action: switch.turn_on
data:
entity_id: switch.test_switch
turn_off_action:
- logger.log: "Toggling HA switch: switch.test_switch OFF"
- homeassistant.action:
action: switch.turn_off
data:
entity_id: switch.test_switch
# Buttons for testing various service call scenarios
button:
# Test 1: Basic service call with static values
- platform: template
name: "Test Basic Service"
id: test_basic_service
on_press:
- logger.log: "Sending HomeAssistant service call: light.turn_off"
- homeassistant.action:
action: light.turn_off
data:
entity_id: light.test_light
- logger.log: "Service data: entity_id=light.test_light"
# Test 2: Service call with templated/lambda values (main bug fix test)
- platform: template
name: "Test Templated Service"
id: test_templated_service
on_press:
- logger.log: "Testing templated service call"
- lambda: |-
int brightness_percent = id(test_brightness);
std::string computed = to_string(brightness_percent * 255 / 100);
ESP_LOGI("test", "Lambda computed value: %s", computed.c_str());
- homeassistant.action:
action: light.turn_on
data:
entity_id: light.test_light
# This creates a temporary string - the main test case
brightness: !lambda 'return to_string(id(test_brightness) * 255 / 100);'
data_template:
color_name: !lambda 'return id(test_string);'
variables:
transition: !lambda 'return "2.5";'
# Test 3: Service call with empty string values
- platform: template
name: "Test Empty String Service"
id: test_empty_string_service
on_press:
- logger.log: "Testing empty string values"
- homeassistant.action:
action: notify.test
data:
message: "Test message"
title: ""
data_template:
target: !lambda 'return "";'
variables:
sound: !lambda 'return "";'
- logger.log: "Empty value for key: title"
- logger.log: "Empty value for key: target"
- logger.log: "Empty value for key: sound"
# Test 4: Service call with multiple data fields
- platform: template
name: "Test Multiple Fields Service"
id: test_multiple_fields_service
on_press:
- logger.log: "Testing multiple data fields"
- homeassistant.action:
action: climate.set_temperature
data:
entity_id: climate.test_climate
temperature: "22"
hvac_mode: "heat"
data_template:
target_temp_high: !lambda 'return "24";'
target_temp_low: !lambda 'return "20";'
variables:
preset_mode: !lambda 'return "comfort";'
# Test 5: Complex lambda with string operations
- platform: template
name: "Test Complex Lambda Service"
id: test_complex_lambda_service
on_press:
- logger.log: "Testing complex lambda expressions"
- homeassistant.action:
action: script.test_script
data:
entity_id: !lambda |-
std::string base = "light.";
std::string room = "living_room";
return base + room;
brightness_pct: !lambda |-
float sensor_val = id(test_sensor).state;
int pct = (int)(sensor_val * 2.38); // 42 * 2.38 ≈ 100
return to_string(pct);
data_template:
message: !lambda |-
char buffer[50];
snprintf(buffer, sizeof(buffer), "Sensor: %.1f, Time: %02d:%02d",
id(test_sensor).state,
id(homeassistant_time).now().hour,
id(homeassistant_time).now().minute);
return std::string(buffer);
# Test 6: Service with only empty strings to verify size calculation
- platform: template
name: "Test All Empty Service"
id: test_all_empty_service
on_press:
- logger.log: "Testing all empty string values"
- homeassistant.action:
action: test.empty
data:
field1: ""
field2: ""
data_template:
field3: !lambda 'return "";'
variables:
field4: !lambda 'return "";'
- logger.log: "All empty service call completed"
# Test 7: Rapid successive service calls
- platform: template
name: "Test Rapid Service Calls"
id: test_rapid_service_calls
on_press:
- logger.log: "Testing rapid service calls"
- repeat:
count: 5
then:
- homeassistant.action:
action: counter.increment
data:
entity_id: counter.test_counter
- delay: 10ms
- logger.log: "Rapid service calls completed"
# Test 8: Log current HA states
- platform: template
name: "Test Read HA States"
id: test_read_ha_states
on_press:
- logger.log: "Reading current HA states"
- lambda: |-
if (id(ha_temperature).has_state()) {
ESP_LOGI("test", "Current HA Temperature: %.1f", id(ha_temperature).state);
} else {
ESP_LOGI("test", "HA Temperature has no state");
}
if (id(ha_humidity).has_state()) {
ESP_LOGI("test", "Current HA Humidity: %.1f", id(ha_humidity).state);
} else {
ESP_LOGI("test", "HA Humidity has no state");
}
ESP_LOGI("test", "Current HA Motion: %s", id(ha_motion).state ? "ON" : "OFF");
if (id(ha_weather).has_state()) {
ESP_LOGI("test", "Current HA Weather: %s", id(ha_weather).state.c_str());
} else {
ESP_LOGI("test", "HA Weather has no state");
}
if (id(ha_empty_state).has_state()) {
ESP_LOGI("test", "HA Empty State value: %s", id(ha_empty_state).state.c_str());
} else {
ESP_LOGI("test", "HA Empty State has no value (expected)");
}

View File

@@ -210,6 +210,15 @@ sensor:
name: "Test Sensor 50" name: "Test Sensor 50"
lambda: return 50.0; lambda: return 50.0;
update_interval: 0.1s update_interval: 0.1s
# Temperature sensor for the thermostat
- platform: template
name: "Temperature Sensor"
id: temp_sensor
lambda: return 22.5;
unit_of_measurement: "°C"
device_class: temperature
state_class: measurement
update_interval: 5s
# Mixed entity types for comprehensive batching test # Mixed entity types for comprehensive batching test
binary_sensor: binary_sensor:
@@ -285,6 +294,50 @@ valve:
stop_action: stop_action:
- logger.log: "Valve stopping" - logger.log: "Valve stopping"
output:
- platform: template
id: heater_output
type: binary
write_action:
- logger.log: "Heater output changed"
- platform: template
id: cooler_output
type: binary
write_action:
- logger.log: "Cooler output changed"
climate:
- platform: thermostat
name: "Test Thermostat"
sensor: temp_sensor
default_preset: Home
on_boot_restore_from: default_preset
min_heating_off_time: 1s
min_heating_run_time: 1s
min_cooling_off_time: 1s
min_cooling_run_time: 1s
min_idle_time: 1s
heat_action:
- output.turn_on: heater_output
cool_action:
- output.turn_on: cooler_output
idle_action:
- output.turn_off: heater_output
- output.turn_off: cooler_output
preset:
- name: Home
default_target_temperature_low: 20
default_target_temperature_high: 24
mode: heat_cool
- name: Away
default_target_temperature_low: 16
default_target_temperature_high: 26
mode: heat_cool
- name: Sleep
default_target_temperature_low: 18
default_target_temperature_high: 22
mode: heat_cool
alarm_control_panel: alarm_control_panel:
- platform: template - platform: template
name: "Test Alarm" name: "Test Alarm"

View File

@@ -37,6 +37,15 @@ globals:
- id: multiple_same_name_counter - id: multiple_same_name_counter
type: int type: int
initial_value: '0' initial_value: '0'
- id: const_char_retry_counter
type: int
initial_value: '0'
- id: static_char_retry_counter
type: int
initial_value: '0'
- id: mixed_cancel_result
type: bool
initial_value: 'false'
# Using different component types for each test to ensure isolation # Using different component types for each test to ensure isolation
sensor: sensor:
@@ -229,6 +238,56 @@ script:
return RetryResult::RETRY; return RetryResult::RETRY;
}); });
# Test 8: Const char* overloads
- logger.log: "=== Test 8: Const char* overloads ==="
- lambda: |-
auto *component = id(simple_retry_sensor);
// Test 8a: Direct string literal
App.scheduler.set_retry(component, "const_char_test", 30, 2,
[](uint8_t retry_countdown) {
id(const_char_retry_counter)++;
ESP_LOGI("test", "Const char retry %d", id(const_char_retry_counter));
return RetryResult::DONE;
});
# Test 9: Static const char* variable
- logger.log: "=== Test 9: Static const char* ==="
- lambda: |-
auto *component = id(backoff_retry_sensor);
static const char* STATIC_NAME = "static_retry_test";
App.scheduler.set_retry(component, STATIC_NAME, 20, 1,
[](uint8_t retry_countdown) {
id(static_char_retry_counter)++;
ESP_LOGI("test", "Static const char retry %d", id(static_char_retry_counter));
return RetryResult::DONE;
});
// Cancel with same static const char*
App.scheduler.set_timeout(component, "static_cancel", 10, []() {
static const char* STATIC_NAME = "static_retry_test";
bool result = App.scheduler.cancel_retry(id(backoff_retry_sensor), STATIC_NAME);
ESP_LOGI("test", "Static cancel result: %s", result ? "true" : "false");
});
# Test 10: Mix string and const char* cancel
- logger.log: "=== Test 10: Mixed string/const char* ==="
- lambda: |-
auto *component = id(immediate_done_sensor);
// Set with std::string
std::string str_name = "mixed_retry";
App.scheduler.set_retry(component, str_name, 40, 3,
[](uint8_t retry_countdown) {
ESP_LOGI("test", "Mixed retry - should be cancelled");
return RetryResult::RETRY;
});
// Cancel with const char*
id(mixed_cancel_result) = App.scheduler.cancel_retry(component, "mixed_retry");
ESP_LOGI("test", "Mixed cancel result: %s", id(mixed_cancel_result) ? "true" : "false");
# Wait for all tests to complete before reporting # Wait for all tests to complete before reporting
- delay: 500ms - delay: 500ms
@@ -242,4 +301,7 @@ script:
ESP_LOGI("test", "Empty name retry counter: %d (expected 1-2)", id(empty_name_retry_counter)); ESP_LOGI("test", "Empty name retry counter: %d (expected 1-2)", id(empty_name_retry_counter));
ESP_LOGI("test", "Component retry counter: %d (expected 2)", id(script_retry_counter)); ESP_LOGI("test", "Component retry counter: %d (expected 2)", id(script_retry_counter));
ESP_LOGI("test", "Multiple same name counter: %d (expected 20+)", id(multiple_same_name_counter)); ESP_LOGI("test", "Multiple same name counter: %d (expected 20+)", id(multiple_same_name_counter));
ESP_LOGI("test", "Const char retry counter: %d (expected 1)", id(const_char_retry_counter));
ESP_LOGI("test", "Static char retry counter: %d (expected 1)", id(static_char_retry_counter));
ESP_LOGI("test", "Mixed cancel result: %s (expected true)", id(mixed_cancel_result) ? "true" : "false");
ESP_LOGI("test", "All retry tests completed"); ESP_LOGI("test", "All retry tests completed");

View File

@@ -0,0 +1,305 @@
"""Integration test for Home Assistant API functionality.
Tests:
- Home Assistant service calls with templated values (main bug fix)
- Service calls with empty string values
- Home Assistant state reading (sensors, binary sensors, text sensors)
- Home Assistant number and switch component control
- Complex lambda expressions and string handling
"""
from __future__ import annotations
import asyncio
import re
from aioesphomeapi import HomeassistantServiceCall
import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction
@pytest.mark.asyncio
async def test_api_homeassistant(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Comprehensive test for Home Assistant API functionality."""
loop = asyncio.get_running_loop()
# Create futures for patterns that capture values
lambda_computed_future = loop.create_future()
ha_temp_state_future = loop.create_future()
ha_humidity_state_future = loop.create_future()
ha_motion_state_future = loop.create_future()
ha_weather_state_future = loop.create_future()
# State update futures
temp_update_future = loop.create_future()
humidity_update_future = loop.create_future()
motion_update_future = loop.create_future()
weather_update_future = loop.create_future()
# Number future
ha_number_future = loop.create_future()
tests_complete_future = loop.create_future()
# Patterns to match in logs - only keeping patterns that capture values
lambda_computed_pattern = re.compile(r"Lambda computed value: (\d+)")
ha_temp_state_pattern = re.compile(r"Current HA Temperature: ([\d.]+)")
ha_humidity_state_pattern = re.compile(r"Current HA Humidity: ([\d.]+)")
ha_motion_state_pattern = re.compile(r"Current HA Motion: (ON|OFF)")
ha_weather_state_pattern = re.compile(r"Current HA Weather: (\w+)")
# State update patterns
temp_update_pattern = re.compile(r"HA Temperature state updated: ([\d.]+)")
humidity_update_pattern = re.compile(r"HA Humidity state updated: ([\d.]+)")
motion_update_pattern = re.compile(r"HA Motion state changed: (ON|OFF)")
weather_update_pattern = re.compile(r"HA Weather condition updated: (\w+)")
# Number pattern
ha_number_pattern = re.compile(r"Setting HA number to: ([\d.]+)")
tests_complete_pattern = re.compile(r"=== All tests completed ===")
# Track all log lines for debugging
log_lines: list[str] = []
# Track HomeAssistant service calls
ha_service_calls: list[HomeassistantServiceCall] = []
# Service call futures organized by service name
service_call_futures = {
"light.turn_off": loop.create_future(), # basic_service_call
"light.turn_on": loop.create_future(), # templated_service_call
"notify.test": loop.create_future(), # empty_string_service_call
"climate.set_temperature": loop.create_future(), # multiple_fields_service_call
"script.test_script": loop.create_future(), # complex_lambda_service_call
"test.empty": loop.create_future(), # all_empty_service_call
"input_number.set_value": loop.create_future(), # ha_number_service_call
"switch.turn_on": loop.create_future(), # ha_switch_on_service_call
"switch.turn_off": loop.create_future(), # ha_switch_off_service_call
}
def on_service_call(service_call: HomeassistantServiceCall) -> None:
"""Capture HomeAssistant service calls."""
ha_service_calls.append(service_call)
# Check if this service call is one we're waiting for
if service_call.service in service_call_futures:
future = service_call_futures[service_call.service]
if not future.done():
future.set_result(service_call)
def check_output(line: str) -> None:
"""Check log output for expected messages."""
log_lines.append(line)
# Check for patterns that capture values
if not lambda_computed_future.done():
match = lambda_computed_pattern.search(line)
if match:
lambda_computed_future.set_result(match.group(1))
elif not ha_temp_state_future.done() and ha_temp_state_pattern.search(line):
ha_temp_state_future.set_result(line)
elif not ha_humidity_state_future.done() and ha_humidity_state_pattern.search(
line
):
ha_humidity_state_future.set_result(line)
elif not ha_motion_state_future.done() and ha_motion_state_pattern.search(line):
ha_motion_state_future.set_result(line)
elif not ha_weather_state_future.done() and ha_weather_state_pattern.search(
line
):
ha_weather_state_future.set_result(line)
# Check state update patterns
elif not temp_update_future.done() and temp_update_pattern.search(line):
temp_update_future.set_result(line)
elif not humidity_update_future.done() and humidity_update_pattern.search(line):
humidity_update_future.set_result(line)
elif not motion_update_future.done() and motion_update_pattern.search(line):
motion_update_future.set_result(line)
elif not weather_update_future.done() and weather_update_pattern.search(line):
weather_update_future.set_result(line)
# Check number pattern
elif not ha_number_future.done() and ha_number_pattern.search(line):
match = ha_number_pattern.search(line)
if match:
ha_number_future.set_result(match.group(1))
elif not tests_complete_future.done() and tests_complete_pattern.search(line):
tests_complete_future.set_result(True)
# Run with log monitoring
async with (
run_compiled(yaml_config, line_callback=check_output),
api_client_connected() as client,
):
# Verify device info
device_info = await client.device_info()
assert device_info is not None
assert device_info.name == "test-ha-api"
# Subscribe to HomeAssistant service calls
client.subscribe_service_calls(on_service_call)
# Send some Home Assistant states for our sensors to read
client.send_home_assistant_state("sensor.external_temperature", "", "22.5")
client.send_home_assistant_state("sensor.external_humidity", "", "65.0")
client.send_home_assistant_state("binary_sensor.external_motion", "", "ON")
client.send_home_assistant_state("weather.home", "condition", "sunny")
# List entities and services
_, services = await client.list_entities_services()
# Find the trigger service
trigger_service = next(
(s for s in services if s.name == "trigger_all_tests"), None
)
assert trigger_service is not None, "trigger_all_tests service not found"
# Execute all tests
client.execute_service(trigger_service, {})
# Wait for all tests to complete with appropriate timeouts
try:
# Templated service test - the main bug fix
computed_value = await asyncio.wait_for(lambda_computed_future, timeout=5.0)
# Verify the computed value is reasonable (75 * 255 / 100 = 191.25 -> 191)
assert computed_value in ["191", "192"], (
f"Unexpected computed value: {computed_value}"
)
# Check state reads - verify we received the mocked values
temp_line = await asyncio.wait_for(ha_temp_state_future, timeout=5.0)
assert "Current HA Temperature: 22.5" in temp_line
humidity_line = await asyncio.wait_for(
ha_humidity_state_future, timeout=5.0
)
assert "Current HA Humidity: 65.0" in humidity_line
motion_line = await asyncio.wait_for(ha_motion_state_future, timeout=5.0)
assert "Current HA Motion: ON" in motion_line
weather_line = await asyncio.wait_for(ha_weather_state_future, timeout=5.0)
assert "Current HA Weather: sunny" in weather_line
# Number test
number_value = await asyncio.wait_for(ha_number_future, timeout=5.0)
assert number_value == "42.5", f"Unexpected number value: {number_value}"
# Wait for completion
await asyncio.wait_for(tests_complete_future, timeout=5.0)
# Now verify the protobuf messages
# 1. Basic service call
basic_call = await asyncio.wait_for(
service_call_futures["light.turn_off"], timeout=2.0
)
assert basic_call.service == "light.turn_off"
assert "entity_id" in basic_call.data, (
f"entity_id not found in data: {basic_call.data}"
)
assert basic_call.data["entity_id"] == "light.test_light", (
f"Wrong entity_id: {basic_call.data['entity_id']}"
)
# 2. Templated service call - verify the temporary string issue is fixed
templated_call = await asyncio.wait_for(
service_call_futures["light.turn_on"], timeout=2.0
)
assert templated_call.service == "light.turn_on"
# Check the computed brightness value
assert "brightness" in templated_call.data
assert templated_call.data["brightness"] in ["191", "192"] # 75 * 255 / 100
# Check data_template
assert "color_name" in templated_call.data_template
assert templated_call.data_template["color_name"] == "test_value"
# Check variables
assert "transition" in templated_call.variables
assert templated_call.variables["transition"] == "2.5"
# 3. Empty string service call
empty_call = await asyncio.wait_for(
service_call_futures["notify.test"], timeout=2.0
)
assert empty_call.service == "notify.test"
# Verify empty strings are properly handled
assert "title" in empty_call.data and empty_call.data["title"] == ""
assert (
"target" in empty_call.data_template
and empty_call.data_template["target"] == ""
)
assert (
"sound" in empty_call.variables and empty_call.variables["sound"] == ""
)
# 4. Multiple fields service call
multi_call = await asyncio.wait_for(
service_call_futures["climate.set_temperature"], timeout=2.0
)
assert multi_call.service == "climate.set_temperature"
assert multi_call.data["temperature"] == "22"
assert multi_call.data["hvac_mode"] == "heat"
assert multi_call.data_template["target_temp_high"] == "24"
assert multi_call.variables["preset_mode"] == "comfort"
# 5. Complex lambda service call
complex_call = await asyncio.wait_for(
service_call_futures["script.test_script"], timeout=2.0
)
assert complex_call.service == "script.test_script"
assert complex_call.data["entity_id"] == "light.living_room"
assert complex_call.data["brightness_pct"] == "99" # 42 * 2.38 ≈ 99
# Check message includes sensor value
assert "message" in complex_call.data_template
assert "Sensor: 42.0" in complex_call.data_template["message"]
# 6. All empty service call
all_empty_call = await asyncio.wait_for(
service_call_futures["test.empty"], timeout=2.0
)
assert all_empty_call.service == "test.empty"
# All fields should be empty strings
assert all(v == "" for v in all_empty_call.data.values())
assert all(v == "" for v in all_empty_call.data_template.values())
assert all(v == "" for v in all_empty_call.variables.values())
# 7. HA Number service call
number_call = await asyncio.wait_for(
service_call_futures["input_number.set_value"], timeout=2.0
)
assert number_call.service == "input_number.set_value"
assert number_call.data["entity_id"] == "input_number.test_number"
# The value might be formatted with trailing zeros
assert float(number_call.data["value"]) == 42.5
# 8. HA Switch service calls
switch_on_call = await asyncio.wait_for(
service_call_futures["switch.turn_on"], timeout=2.0
)
assert switch_on_call.service == "switch.turn_on"
assert switch_on_call.data["entity_id"] == "switch.test_switch"
switch_off_call = await asyncio.wait_for(
service_call_futures["switch.turn_off"], timeout=2.0
)
assert switch_off_call.service == "switch.turn_off"
assert switch_off_call.data["entity_id"] == "switch.test_switch"
except TimeoutError as e:
# Show recent log lines for debugging
recent_logs = "\n".join(log_lines[-20:])
service_calls_summary = "\n".join(
f"- {call.service}" for call in ha_service_calls
)
pytest.fail(
f"Test timed out waiting for expected log pattern or service call. Error: {e}\n\n"
f"Recent log lines:\n{recent_logs}\n\n"
f"Received service calls:\n{service_calls_summary}"
)

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import asyncio import asyncio
from aioesphomeapi import EntityState, SensorState from aioesphomeapi import ClimateInfo, EntityState, SensorState
import pytest import pytest
from .types import APIClientConnectedFactory, RunCompiledFunction from .types import APIClientConnectedFactory, RunCompiledFunction
@@ -70,3 +70,22 @@ async def test_host_mode_many_entities(
assert len(sensor_states) >= 50, ( assert len(sensor_states) >= 50, (
f"Expected at least 50 sensor states, got {len(sensor_states)}" f"Expected at least 50 sensor states, got {len(sensor_states)}"
) )
# Get entity info to verify climate entity details
entities = await client.list_entities_services()
climate_infos = [e for e in entities[0] if isinstance(e, ClimateInfo)]
assert len(climate_infos) >= 1, "Expected at least 1 climate entity"
climate_info = climate_infos[0]
# Verify the thermostat has presets
assert len(climate_info.supported_presets) > 0, (
"Expected climate to have presets"
)
# The thermostat platform uses standard presets (Home, Away, Sleep)
# which should be transmitted properly without string copies
# Verify specific presets exist
preset_names = [p.name for p in climate_info.supported_presets]
assert "HOME" in preset_names, f"Expected 'HOME' preset, got {preset_names}"
assert "AWAY" in preset_names, f"Expected 'AWAY' preset, got {preset_names}"
assert "SLEEP" in preset_names, f"Expected 'SLEEP' preset, got {preset_names}"

View File

@@ -23,6 +23,9 @@ async def test_scheduler_retry_test(
empty_name_retry_done = asyncio.Event() empty_name_retry_done = asyncio.Event()
component_retry_done = asyncio.Event() component_retry_done = asyncio.Event()
multiple_name_done = asyncio.Event() multiple_name_done = asyncio.Event()
const_char_done = asyncio.Event()
static_char_done = asyncio.Event()
mixed_cancel_done = asyncio.Event()
test_complete = asyncio.Event() test_complete = asyncio.Event()
# Track retry counts # Track retry counts
@@ -33,16 +36,20 @@ async def test_scheduler_retry_test(
empty_name_retry_count = 0 empty_name_retry_count = 0
component_retry_count = 0 component_retry_count = 0
multiple_name_count = 0 multiple_name_count = 0
const_char_retry_count = 0
static_char_retry_count = 0
# Track specific test results # Track specific test results
cancel_result = None cancel_result = None
empty_cancel_result = None empty_cancel_result = None
mixed_cancel_result = None
backoff_intervals = [] backoff_intervals = []
def on_log_line(line: str) -> None: def on_log_line(line: str) -> None:
nonlocal simple_retry_count, backoff_retry_count, immediate_done_count nonlocal simple_retry_count, backoff_retry_count, immediate_done_count
nonlocal cancel_retry_count, empty_name_retry_count, component_retry_count nonlocal cancel_retry_count, empty_name_retry_count, component_retry_count
nonlocal multiple_name_count, cancel_result, empty_cancel_result nonlocal multiple_name_count, const_char_retry_count, static_char_retry_count
nonlocal cancel_result, empty_cancel_result, mixed_cancel_result
# Strip ANSI color codes # Strip ANSI color codes
clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line) clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line)
@@ -106,6 +113,27 @@ async def test_scheduler_retry_test(
if multiple_name_count >= 20: if multiple_name_count >= 20:
multiple_name_done.set() multiple_name_done.set()
# Const char retry test
elif "Const char retry" in clean_line:
if match := re.search(r"Const char retry (\d+)", clean_line):
const_char_retry_count = int(match.group(1))
const_char_done.set()
# Static const char retry test
elif "Static const char retry" in clean_line:
if match := re.search(r"Static const char retry (\d+)", clean_line):
static_char_retry_count = int(match.group(1))
static_char_done.set()
elif "Static cancel result:" in clean_line:
# This is part of test 9, but we don't track it separately
pass
# Mixed cancel test
elif "Mixed cancel result:" in clean_line:
mixed_cancel_result = "true" in clean_line
mixed_cancel_done.set()
# Test completion # Test completion
elif "All retry tests completed" in clean_line: elif "All retry tests completed" in clean_line:
test_complete.set() test_complete.set()
@@ -227,6 +255,40 @@ async def test_scheduler_retry_test(
f"Expected multiple name count >= 20 (second retry only), got {multiple_name_count}" f"Expected multiple name count >= 20 (second retry only), got {multiple_name_count}"
) )
# Wait for const char retry test
try:
await asyncio.wait_for(const_char_done.wait(), timeout=1.0)
except TimeoutError:
pytest.fail(
f"Const char retry test did not complete. Count: {const_char_retry_count}"
)
assert const_char_retry_count == 1, (
f"Expected 1 const char retry call, got {const_char_retry_count}"
)
# Wait for static char retry test
try:
await asyncio.wait_for(static_char_done.wait(), timeout=1.0)
except TimeoutError:
pytest.fail(
f"Static char retry test did not complete. Count: {static_char_retry_count}"
)
assert static_char_retry_count == 1, (
f"Expected 1 static char retry call, got {static_char_retry_count}"
)
# Wait for mixed cancel test
try:
await asyncio.wait_for(mixed_cancel_done.wait(), timeout=1.0)
except TimeoutError:
pytest.fail("Mixed cancel test did not complete")
assert mixed_cancel_result is True, (
"Mixed string/const char cancel should have succeeded"
)
# Wait for test completion # Wait for test completion
try: try:
await asyncio.wait_for(test_complete.wait(), timeout=1.0) await asyncio.wait_for(test_complete.wait(), timeout=1.0)