From ea3112297916831478d94826a8042b0dc15eafb1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 May 2025 11:48:54 +1200 Subject: [PATCH 1/8] [media_player] Deprecate ``MEDIA_PLAYER_SCHEMA`` (#8784) --- .../i2s_audio/media_player/__init__.py | 19 ++++++----- esphome/components/media_player/__init__.py | 34 ++++++++++++++++++- .../speaker/media_player/__init__.py | 6 ++-- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/esphome/components/i2s_audio/media_player/__init__.py b/esphome/components/i2s_audio/media_player/__init__.py index bed25b011f..51001e9444 100644 --- a/esphome/components/i2s_audio/media_player/__init__.py +++ b/esphome/components/i2s_audio/media_player/__init__.py @@ -2,7 +2,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import esp32, media_player import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_MODE +from esphome.const import CONF_MODE from .. import ( CONF_I2S_AUDIO_ID, @@ -57,16 +57,17 @@ def validate_esp32_variant(config): CONFIG_SCHEMA = cv.All( cv.typed_schema( { - "internal": media_player.MEDIA_PLAYER_SCHEMA.extend( + "internal": media_player.media_player_schema(I2SAudioMediaPlayer) + .extend( { - cv.GenerateID(): cv.declare_id(I2SAudioMediaPlayer), cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True), } - ).extend(cv.COMPONENT_SCHEMA), - "external": media_player.MEDIA_PLAYER_SCHEMA.extend( + ) + .extend(cv.COMPONENT_SCHEMA), + "external": media_player.media_player_schema(I2SAudioMediaPlayer) + .extend( { - cv.GenerateID(): cv.declare_id(I2SAudioMediaPlayer), cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.Required( CONF_I2S_DOUT_PIN @@ -79,7 +80,8 @@ CONFIG_SCHEMA = cv.All( *I2C_COMM_FMT_OPTIONS, lower=True ), } - ).extend(cv.COMPONENT_SCHEMA), + ) + .extend(cv.COMPONENT_SCHEMA), }, key=CONF_DAC_TYPE, ), @@ -97,9 +99,8 @@ FINAL_VALIDATE_SCHEMA = _final_validate async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await media_player.new_media_player(config) await cg.register_component(var, config) - await media_player.register_media_player(var, config) await cg.register_parented(var, config[CONF_I2S_AUDIO_ID]) diff --git a/esphome/components/media_player/__init__.py b/esphome/components/media_player/__init__.py index 14fe1fdb6a..2f5fe0c03e 100644 --- a/esphome/components/media_player/__init__.py +++ b/esphome/components/media_player/__init__.py @@ -2,6 +2,8 @@ from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, CONF_ID, CONF_ON_IDLE, CONF_ON_STATE, @@ -10,6 +12,7 @@ from esphome.const import ( ) from esphome.core import CORE from esphome.coroutine import coroutine_with_priority +from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@jesserockz"] @@ -103,7 +106,13 @@ async def register_media_player(var, config): await setup_media_player_core_(var, config) -MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( +async def new_media_player(config, *args): + var = cg.new_Pvariable(config[CONF_ID], *args) + await register_media_player(var, config) + return var + + +_MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( { cv.Optional(CONF_ON_STATE): automation.validate_automation( { @@ -134,6 +143,29 @@ MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( ) +def media_player_schema( + class_: MockObjClass, + *, + entity_category: str = cv.UNDEFINED, + icon: str = cv.UNDEFINED, +) -> cv.Schema: + schema = {cv.GenerateID(CONF_ID): cv.declare_id(class_)} + + for key, default, validator in [ + (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), + (CONF_ICON, icon, cv.icon), + ]: + if default is not cv.UNDEFINED: + schema[cv.Optional(key, default=default)] = validator + + return _MEDIA_PLAYER_SCHEMA.extend(schema) + + +# Remove before 2025.11.0 +MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer) +MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player")) + + MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id( cv.Schema( { diff --git a/esphome/components/speaker/media_player/__init__.py b/esphome/components/speaker/media_player/__init__.py index 35d763b1f8..cedafe214d 100644 --- a/esphome/components/speaker/media_player/__init__.py +++ b/esphome/components/speaker/media_player/__init__.py @@ -271,9 +271,8 @@ PIPELINE_SCHEMA = cv.Schema( ) CONFIG_SCHEMA = cv.All( - media_player.MEDIA_PLAYER_SCHEMA.extend( + media_player.media_player_schema(SpeakerMediaPlayer).extend( { - cv.GenerateID(): cv.declare_id(SpeakerMediaPlayer), cv.Required(CONF_ANNOUNCEMENT_PIPELINE): PIPELINE_SCHEMA, cv.Optional(CONF_MEDIA_PIPELINE): PIPELINE_SCHEMA, cv.Optional(CONF_BUFFER_SIZE, default=1000000): cv.int_range( @@ -343,9 +342,8 @@ async def to_code(config): # Allocate wifi buffers in PSRAM esp32.add_idf_sdkconfig_option("CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP", True) - var = cg.new_Pvariable(config[CONF_ID]) + var = await media_player.new_media_player(config) await cg.register_component(var, config) - await media_player.register_media_player(var, config) cg.add_define("USE_OTA_STATE_CALLBACK") From c30ffd009876a1adaeee484b66784a8297dc9e08 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 May 2025 13:25:21 +1200 Subject: [PATCH 2/8] [schema] Get component name if available for deprecation warning (#8785) --- esphome/config_validation.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 88a805591d..2eabcc8568 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -2077,14 +2077,20 @@ def rename_key(old_key, new_key): # Remove before 2025.11.0 def deprecated_schema_constant(entity_type: str): def validator(config): + type: str = "unknown" + if (id := config.get(CONF_ID)) is not None and isinstance(id, core.ID): + type = str(id.type).split("::", maxsplit=1)[0] _LOGGER.warning( "Using `%s.%s_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. " "Please use `%s.%s_schema(...)` instead. " - "If you are seeing this, report an issue to the external_component author and ask them to update it.", + "If you are seeing this, report an issue to the external_component author and ask them to update it. " + "https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. " + "Component using this schema: %s", entity_type, entity_type.upper(), entity_type, entity_type, + type, ) return config From 7d0262dd1a8e52b663e31b055d9eab19218ddea1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 May 2025 13:30:11 +1200 Subject: [PATCH 3/8] [fan] Update components to use ``fan_schema(...)`` (#8786) --- esphome/components/bedjet/fan/__init__.py | 13 ++------ esphome/components/binary/fan/__init__.py | 29 ++++++++--------- esphome/components/copy/fan/__init__.py | 20 ++++++------ esphome/components/hbridge/fan/__init__.py | 36 +++++++++++---------- esphome/components/speed/fan/__init__.py | 33 ++++++++++--------- esphome/components/template/fan/__init__.py | 26 ++++++++------- 6 files changed, 76 insertions(+), 81 deletions(-) diff --git a/esphome/components/bedjet/fan/__init__.py b/esphome/components/bedjet/fan/__init__.py index fdf0636153..a4a611fefc 100644 --- a/esphome/components/bedjet/fan/__init__.py +++ b/esphome/components/bedjet/fan/__init__.py @@ -1,31 +1,22 @@ -import logging - import esphome.codegen as cg from esphome.components import fan import esphome.config_validation as cv -from esphome.const import CONF_ID from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child -_LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@jhansche"] DEPENDENCIES = ["bedjet"] BedJetFan = bedjet_ns.class_("BedJetFan", fan.Fan, cg.PollingComponent) CONFIG_SCHEMA = ( - fan.FAN_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(BedJetFan), - } - ) + fan.fan_schema(BedJetFan) .extend(cv.polling_component_schema("60s")) .extend(BEDJET_CLIENT_SCHEMA) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await fan.new_fan(config) await cg.register_component(var, config) - await fan.register_fan(var, config) await register_bedjet_child(var, config) diff --git a/esphome/components/binary/fan/__init__.py b/esphome/components/binary/fan/__init__.py index a504ef642c..dadcf52372 100644 --- a/esphome/components/binary/fan/__init__.py +++ b/esphome/components/binary/fan/__init__.py @@ -1,31 +1,28 @@ import esphome.codegen as cg from esphome.components import fan, output import esphome.config_validation as cv -from esphome.const import ( - CONF_DIRECTION_OUTPUT, - CONF_OSCILLATION_OUTPUT, - CONF_OUTPUT, - CONF_OUTPUT_ID, -) +from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, CONF_OUTPUT from .. import binary_ns BinaryFan = binary_ns.class_("BinaryFan", fan.Fan, cg.Component) -CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( - { - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan), - cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), - cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), - cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + fan.fan_schema(BinaryFan) + .extend( + { + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + var = await fan.new_fan(config) await cg.register_component(var, config) - await fan.register_fan(var, config) output_ = await cg.get_variable(config[CONF_OUTPUT]) cg.add(var.set_output(output_)) diff --git a/esphome/components/copy/fan/__init__.py b/esphome/components/copy/fan/__init__.py index 04872fb029..a208e5f80a 100644 --- a/esphome/components/copy/fan/__init__.py +++ b/esphome/components/copy/fan/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg from esphome.components import fan import esphome.config_validation as cv -from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_SOURCE_ID +from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_SOURCE_ID from esphome.core.entity_helpers import inherit_property_from from .. import copy_ns @@ -9,12 +9,15 @@ from .. import copy_ns CopyFan = copy_ns.class_("CopyFan", fan.Fan, cg.Component) -CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(CopyFan), - cv.Required(CONF_SOURCE_ID): cv.use_id(fan.Fan), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + fan.fan_schema(CopyFan) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(fan.Fan), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) FINAL_VALIDATE_SCHEMA = cv.All( inherit_property_from(CONF_ICON, CONF_SOURCE_ID), @@ -23,8 +26,7 @@ FINAL_VALIDATE_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await fan.register_fan(var, config) + var = await fan.new_fan(config) await cg.register_component(var, config) source = await cg.get_variable(config[CONF_SOURCE_ID]) diff --git a/esphome/components/hbridge/fan/__init__.py b/esphome/components/hbridge/fan/__init__.py index 4309a64359..31a20a8981 100644 --- a/esphome/components/hbridge/fan/__init__.py +++ b/esphome/components/hbridge/fan/__init__.py @@ -30,25 +30,28 @@ DECAY_MODE_OPTIONS = { # Actions BrakeAction = hbridge_ns.class_("BrakeAction", automation.Action) -CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( - { - cv.GenerateID(CONF_ID): cv.declare_id(HBridgeFan), - cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput), - cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput), - cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum( - DECAY_MODE_OPTIONS, upper=True - ), - cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1), - cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput), - cv.Optional(CONF_PRESET_MODES): validate_preset_modes, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + fan.fan_schema(HBridgeFan) + .extend( + { + cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput), + cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput), + cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum( + DECAY_MODE_OPTIONS, upper=True + ), + cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1), + cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput), + cv.Optional(CONF_PRESET_MODES): validate_preset_modes, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) @automation.register_action( "fan.hbridge.brake", BrakeAction, - maybe_simple_id({cv.Required(CONF_ID): cv.use_id(HBridgeFan)}), + maybe_simple_id({cv.GenerateID(): cv.use_id(HBridgeFan)}), ) async def fan_hbridge_brake_to_code(config, action_id, template_arg, args): paren = await cg.get_variable(config[CONF_ID]) @@ -56,13 +59,12 @@ async def fan_hbridge_brake_to_code(config, action_id, template_arg, args): async def to_code(config): - var = cg.new_Pvariable( - config[CONF_ID], + var = await fan.new_fan( + config, config[CONF_SPEED_COUNT], config[CONF_DECAY_MODE], ) await cg.register_component(var, config) - await fan.register_fan(var, config) pin_a_ = await cg.get_variable(config[CONF_PIN_A]) cg.add(var.set_pin_a(pin_a_)) pin_b_ = await cg.get_variable(config[CONF_PIN_B]) diff --git a/esphome/components/speed/fan/__init__.py b/esphome/components/speed/fan/__init__.py index fe43ac6a3a..3c495f3160 100644 --- a/esphome/components/speed/fan/__init__.py +++ b/esphome/components/speed/fan/__init__.py @@ -6,7 +6,6 @@ from esphome.const import ( CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, - CONF_OUTPUT_ID, CONF_PRESET_MODES, CONF_SPEED, CONF_SPEED_COUNT, @@ -16,25 +15,27 @@ from .. import speed_ns SpeedFan = speed_ns.class_("SpeedFan", cg.Component, fan.Fan) -CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( - { - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpeedFan), - cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput), - cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), - cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), - cv.Optional(CONF_SPEED): cv.invalid( - "Configuring individual speeds is deprecated." - ), - cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1), - cv.Optional(CONF_PRESET_MODES): validate_preset_modes, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + fan.fan_schema(SpeedFan) + .extend( + { + cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput), + cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Optional(CONF_SPEED): cv.invalid( + "Configuring individual speeds is deprecated." + ), + cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1), + cv.Optional(CONF_PRESET_MODES): validate_preset_modes, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_OUTPUT_ID], config[CONF_SPEED_COUNT]) + var = await fan.new_fan(config, config[CONF_SPEED_COUNT]) await cg.register_component(var, config) - await fan.register_fan(var, config) output_ = await cg.get_variable(config[CONF_OUTPUT]) cg.add(var.set_output(output_)) diff --git a/esphome/components/template/fan/__init__.py b/esphome/components/template/fan/__init__.py index c885866d40..72b20e1efe 100644 --- a/esphome/components/template/fan/__init__.py +++ b/esphome/components/template/fan/__init__.py @@ -2,7 +2,7 @@ import esphome.codegen as cg from esphome.components import fan from esphome.components.fan import validate_preset_modes import esphome.config_validation as cv -from esphome.const import CONF_OUTPUT_ID, CONF_PRESET_MODES, CONF_SPEED_COUNT +from esphome.const import CONF_PRESET_MODES, CONF_SPEED_COUNT from .. import template_ns @@ -13,21 +13,23 @@ TemplateFan = template_ns.class_("TemplateFan", cg.Component, fan.Fan) CONF_HAS_DIRECTION = "has_direction" CONF_HAS_OSCILLATING = "has_oscillating" -CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( - { - cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TemplateFan), - cv.Optional(CONF_HAS_DIRECTION, default=False): cv.boolean, - cv.Optional(CONF_HAS_OSCILLATING, default=False): cv.boolean, - cv.Optional(CONF_SPEED_COUNT): cv.int_range(min=1), - cv.Optional(CONF_PRESET_MODES): validate_preset_modes, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + fan.fan_schema(TemplateFan) + .extend( + { + cv.Optional(CONF_HAS_DIRECTION, default=False): cv.boolean, + cv.Optional(CONF_HAS_OSCILLATING, default=False): cv.boolean, + cv.Optional(CONF_SPEED_COUNT): cv.int_range(min=1), + cv.Optional(CONF_PRESET_MODES): validate_preset_modes, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + var = await fan.new_fan(config) await cg.register_component(var, config) - await fan.register_fan(var, config) cg.add(var.set_has_direction(config[CONF_HAS_DIRECTION])) cg.add(var.set_has_oscillating(config[CONF_HAS_OSCILLATING])) From 4f2643e6e9d4a34d8579f6211ab176af803fc648 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 13 May 2025 23:34:33 -0500 Subject: [PATCH 4/8] Improve batching of BLE advertisements for better airtime efficiency (#8778) --- .../bluetooth_proxy/bluetooth_proxy.cpp | 77 ++++++++++++++----- .../bluetooth_proxy/bluetooth_proxy.h | 1 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 4 +- .../esp32_ble_tracker/esp32_ble_tracker.h | 2 +- 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index 9c8bd4009f..915d2882d3 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -51,35 +51,60 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) return true; } +static constexpr size_t FLUSH_BATCH_SIZE = 8; +static std::vector &get_batch_buffer() { + static std::vector batch_buffer; + return batch_buffer; +} + bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) { if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_) return false; - api::BluetoothLERawAdvertisementsResponse resp; - // Pre-allocate the advertisements vector to avoid reallocations - resp.advertisements.reserve(count); + // Get the batch buffer reference + auto &batch_buffer = get_batch_buffer(); + // Reserve additional capacity if needed + size_t new_size = batch_buffer.size() + count; + if (batch_buffer.capacity() < new_size) { + batch_buffer.reserve(new_size); + } + + // Add new advertisements to the batch buffer for (size_t i = 0; i < count; i++) { auto &result = advertisements[i]; - api::BluetoothLERawAdvertisement adv; + uint8_t length = result.adv_data_len + result.scan_rsp_len; + + batch_buffer.emplace_back(); + auto &adv = batch_buffer.back(); adv.address = esp32_ble::ble_addr_to_uint64(result.bda); adv.rssi = result.rssi; adv.address_type = result.ble_addr_type; + adv.data.assign(&result.ble_adv[0], &result.ble_adv[length]); - uint8_t length = result.adv_data_len + result.scan_rsp_len; - adv.data.reserve(length); - // Use a bulk insert instead of individual push_backs - adv.data.insert(adv.data.end(), &result.ble_adv[0], &result.ble_adv[length]); - - resp.advertisements.push_back(std::move(adv)); - - ESP_LOGV(TAG, "Proxying raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0], + ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0], result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi); } - ESP_LOGV(TAG, "Proxying %d packets", count); - this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp); + + // Only send if we've accumulated a good batch size to maximize batching efficiency + // https://github.com/esphome/backlog/issues/21 + if (batch_buffer.size() >= FLUSH_BATCH_SIZE) { + this->flush_pending_advertisements(); + } + return true; } + +void BluetoothProxy::flush_pending_advertisements() { + auto &batch_buffer = get_batch_buffer(); + if (batch_buffer.empty() || !api::global_api_server->is_connected() || this->api_connection_ == nullptr) + return; + + api::BluetoothLERawAdvertisementsResponse resp; + resp.advertisements.swap(batch_buffer); + this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp); +} + void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) { api::BluetoothLEAdvertisementResponse resp; resp.address = device.address_uint64(); @@ -91,28 +116,28 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi // Pre-allocate vectors based on known sizes auto service_uuids = device.get_service_uuids(); resp.service_uuids.reserve(service_uuids.size()); - for (auto uuid : service_uuids) { - resp.service_uuids.push_back(uuid.to_string()); + for (auto &uuid : service_uuids) { + resp.service_uuids.emplace_back(uuid.to_string()); } // Pre-allocate service data vector auto service_datas = device.get_service_datas(); resp.service_data.reserve(service_datas.size()); for (auto &data : service_datas) { - api::BluetoothServiceData service_data; + resp.service_data.emplace_back(); + auto &service_data = resp.service_data.back(); service_data.uuid = data.uuid.to_string(); service_data.data.assign(data.data.begin(), data.data.end()); - resp.service_data.push_back(std::move(service_data)); } // Pre-allocate manufacturer data vector auto manufacturer_datas = device.get_manufacturer_datas(); resp.manufacturer_data.reserve(manufacturer_datas.size()); for (auto &data : manufacturer_datas) { - api::BluetoothServiceData manufacturer_data; + resp.manufacturer_data.emplace_back(); + auto &manufacturer_data = resp.manufacturer_data.back(); manufacturer_data.uuid = data.uuid.to_string(); manufacturer_data.data.assign(data.data.begin(), data.data.end()); - resp.manufacturer_data.push_back(std::move(manufacturer_data)); } this->api_connection_->send_bluetooth_le_advertisement(resp); @@ -148,6 +173,18 @@ void BluetoothProxy::loop() { } return; } + + // Flush any pending BLE advertisements that have been accumulated but not yet sent + if (this->raw_advertisements_) { + static uint32_t last_flush_time = 0; + uint32_t now = millis(); + + // Flush accumulated advertisements every 100ms + if (now - last_flush_time >= 100) { + this->flush_pending_advertisements(); + last_flush_time = now; + } + } for (auto *connection : this->connections_) { if (connection->send_service_ == connection->service_count_) { connection->send_service_ = DONE_SENDING_SERVICES; diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index de24165fe8..f75e73e796 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -56,6 +56,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com void dump_config() override; void setup() override; void loop() override; + void flush_pending_advertisements(); esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override; void register_connection(BluetoothConnection *connection) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index be45b177ff..1a6071c9fe 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -122,7 +122,7 @@ void ESP32BLETracker::loop() { if (this->scanner_state_ == ScannerState::RUNNING && this->scan_result_index_ && // if it looks like we have a scan result we will take the lock - xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { + xSemaphoreTake(this->scan_result_lock_, 0)) { uint32_t index = this->scan_result_index_; if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up."); @@ -447,7 +447,7 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_ void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { ESP_LOGV(TAG, "gap_scan_result - event %d", param.search_evt); if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { - if (xSemaphoreTake(this->scan_result_lock_, 0L)) { + if (xSemaphoreTake(this->scan_result_lock_, 0)) { if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { this->scan_result_buffer_[this->scan_result_index_++] = param; } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 2e45d9602c..eea73a7d26 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -290,7 +290,7 @@ class ESP32BLETracker : public Component, #ifdef USE_PSRAM const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 32; #else - const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 16; + const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 20; #endif // USE_PSRAM esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_; esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; From c050e8d0fbb05a4e8c06e3025d83ebc18606f7c0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 May 2025 16:35:30 +1200 Subject: [PATCH 5/8] Fix release to pypi (#8789) --- .github/workflows/release.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88704953ce..41e9186987 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -56,16 +56,14 @@ jobs: uses: actions/setup-python@v5.6.0 with: python-version: "3.x" - - name: Set up python environment - env: - ESPHOME_NO_VENV: 1 - run: script/setup - name: Build run: |- pip3 install build python3 -m build - name: Publish uses: pypa/gh-action-pypi-publish@v1.12.4 + with: + skip-existing: true deploy-docker: name: Build ESPHome ${{ matrix.platform.arch }} From 7cb01bf8426b1bdc28f691ab85eda8d9c5d02fbb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 May 2025 17:36:21 +1200 Subject: [PATCH 6/8] [climate] Update components to use ``climate_schema(...)`` (#8788) --- esphome/components/anova/climate.py | 9 +-- esphome/components/ballu/climate.py | 11 +-- esphome/components/bang_bang/climate.py | 11 ++- esphome/components/bedjet/climate/__init__.py | 11 +-- esphome/components/climate_ir/__init__.py | 74 ++++++++++++++----- esphome/components/climate_ir_lg/climate.py | 7 +- esphome/components/coolix/climate.py | 11 +-- esphome/components/daikin/climate.py | 11 +-- esphome/components/daikin_arc/climate.py | 9 +-- esphome/components/daikin_brc/climate.py | 8 +- esphome/components/delonghi/climate.py | 11 +-- esphome/components/emmeti/climate.py | 11 +-- esphome/components/fujitsu_general/climate.py | 11 +-- esphome/components/gree/climate.py | 9 +-- esphome/components/haier/climate.py | 71 +++++++++--------- esphome/components/heatpumpir/climate.py | 9 +-- esphome/components/hitachi_ac344/climate.py | 11 +-- esphome/components/hitachi_ac424/climate.py | 11 +-- esphome/components/midea/climate.py | 7 +- esphome/components/midea_ir/climate.py | 8 +- esphome/components/mitsubishi/climate.py | 7 +- esphome/components/noblex/climate.py | 11 +-- esphome/components/pid/climate.py | 6 +- esphome/components/tcl112/climate.py | 11 +-- esphome/components/thermostat/climate.py | 10 +-- esphome/components/toshiba/climate.py | 8 +- esphome/components/tuya/climate/__init__.py | 11 ++- .../uponor_smatrix/climate/__init__.py | 13 +--- esphome/components/whirlpool/climate.py | 8 +- esphome/components/whynter/climate.py | 8 +- esphome/components/yashima/climate.py | 12 +-- esphome/components/zhlt01/climate.py | 9 +-- 32 files changed, 180 insertions(+), 255 deletions(-) diff --git a/esphome/components/anova/climate.py b/esphome/components/anova/climate.py index 052296294b..e1fd38fddc 100644 --- a/esphome/components/anova/climate.py +++ b/esphome/components/anova/climate.py @@ -1,7 +1,7 @@ import esphome.codegen as cg from esphome.components import ble_client, climate import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT +from esphome.const import CONF_UNIT_OF_MEASUREMENT UNITS = { "f": "f", @@ -17,9 +17,9 @@ Anova = anova_ns.class_( ) CONFIG_SCHEMA = ( - climate.CLIMATE_SCHEMA.extend( + climate.climate_schema(Anova) + .extend( { - cv.GenerateID(): cv.declare_id(Anova), cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS), } ) @@ -29,8 +29,7 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate.new_climate(config) await cg.register_component(var, config) - await climate.register_climate(var, config) await ble_client.register_ble_node(var, config) cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT])) diff --git a/esphome/components/ballu/climate.py b/esphome/components/ballu/climate.py index 416fa250cc..e35a1d244d 100644 --- a/esphome/components/ballu/climate.py +++ b/esphome/components/ballu/climate.py @@ -1,7 +1,5 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] CODEOWNERS = ["@bazuchan"] @@ -9,13 +7,8 @@ CODEOWNERS = ["@bazuchan"] ballu_ns = cg.esphome_ns.namespace("ballu") BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(BalluClimate), - } -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(BalluClimate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/bang_bang/climate.py b/esphome/components/bang_bang/climate.py index 6511270f60..bfdb12278f 100644 --- a/esphome/components/bang_bang/climate.py +++ b/esphome/components/bang_bang/climate.py @@ -9,7 +9,6 @@ from esphome.const import ( CONF_DEFAULT_TARGET_TEMPERATURE_LOW, CONF_HEAT_ACTION, CONF_HUMIDITY_SENSOR, - CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR, ) @@ -19,9 +18,9 @@ BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Com BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig") CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( + climate.climate_schema(BangBangClimate) + .extend( { - cv.GenerateID(): cv.declare_id(BangBangClimate), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature, @@ -36,15 +35,15 @@ CONFIG_SCHEMA = cv.All( } ), } - ).extend(cv.COMPONENT_SCHEMA), + ) + .extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION), ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate.new_climate(config) await cg.register_component(var, config) - await climate.register_climate(var, config) sens = await cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_sensor(sens)) diff --git a/esphome/components/bedjet/climate/__init__.py b/esphome/components/bedjet/climate/__init__.py index 7ba3e439b2..e9c5510256 100644 --- a/esphome/components/bedjet/climate/__init__.py +++ b/esphome/components/bedjet/climate/__init__.py @@ -1,11 +1,8 @@ -import logging - import esphome.codegen as cg from esphome.components import ble_client, climate import esphome.config_validation as cv from esphome.const import ( CONF_HEAT_MODE, - CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_TEMPERATURE_SOURCE, CONF_TIME_ID, @@ -13,7 +10,6 @@ from esphome.const import ( from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child -_LOGGER = logging.getLogger(__name__) CODEOWNERS = ["@jhansche"] DEPENDENCIES = ["bedjet"] @@ -30,9 +26,9 @@ BEDJET_TEMPERATURE_SOURCES = { } CONFIG_SCHEMA = ( - climate.CLIMATE_SCHEMA.extend( + climate.climate_schema(BedJetClimate) + .extend( { - cv.GenerateID(): cv.declare_id(BedJetClimate), cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum( BEDJET_HEAT_MODES, lower=True ), @@ -63,9 +59,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate.new_climate(config) await cg.register_component(var, config) - await climate.register_climate(var, config) await register_bedjet_child(var, config) cg.add(var.set_heating_mode(config[CONF_HEAT_MODE])) diff --git a/esphome/components/climate_ir/__init__.py b/esphome/components/climate_ir/__init__.py index d8be61397e..32b614e933 100644 --- a/esphome/components/climate_ir/__init__.py +++ b/esphome/components/climate_ir/__init__.py @@ -1,7 +1,13 @@ +import logging + +from esphome import core import esphome.codegen as cg from esphome.components import climate, remote_base, sensor import esphome.config_validation as cv -from esphome.const import CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT +from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT +from esphome.cpp_generator import MockObjClass + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["remote_transmitter"] AUTO_LOAD = ["sensor", "remote_base"] @@ -16,30 +22,58 @@ ClimateIR = climate_ir_ns.class_( remote_base.RemoteTransmittable, ) -CLIMATE_IR_SCHEMA = ( - climate.CLIMATE_SCHEMA.extend( + +def climate_ir_schema( + class_: MockObjClass, +) -> cv.Schema: + return ( + climate.climate_schema(class_) + .extend( + { + cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, + cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, + cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(remote_base.REMOTE_TRANSMITTABLE_SCHEMA) + ) + + +def climare_ir_with_receiver_schema( + class_: MockObjClass, +) -> cv.Schema: + return climate_ir_schema(class_).extend( { - cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, - cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, - cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), + cv.Optional(remote_base.CONF_RECEIVER_ID): cv.use_id( + remote_base.RemoteReceiverBase + ), } ) - .extend(cv.COMPONENT_SCHEMA) - .extend(remote_base.REMOTE_TRANSMITTABLE_SCHEMA) -) -CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend( - { - cv.Optional(remote_base.CONF_RECEIVER_ID): cv.use_id( - remote_base.RemoteReceiverBase - ), - } -) + +# Remove before 2025.11.0 +def deprecated_schema_constant(config): + type: str = "unknown" + if (id := config.get(CONF_ID)) is not None and isinstance(id, core.ID): + type = str(id.type).split("::", maxsplit=1)[0] + _LOGGER.warning( + "Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. " + "Please use `climate_ir.climare_ir_with_receiver_schema(...)` instead. " + "If you are seeing this, report an issue to the external_component author and ask them to update it. " + "https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. " + "Component using this schema: %s", + type, + ) + return config + + +CLIMATE_IR_WITH_RECEIVER_SCHEMA = climare_ir_with_receiver_schema(ClimateIR) +CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant) async def register_climate_ir(var, config): await cg.register_component(var, config) - await climate.register_climate(var, config) await remote_base.register_transmittable(var, config) cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL])) cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT])) @@ -48,3 +82,9 @@ async def register_climate_ir(var, config): if sensor_id := config.get(CONF_SENSOR): sens = await cg.get_variable(sensor_id) cg.add(var.set_sensor(sens)) + + +async def new_climate_ir(config, *args): + var = await climate.new_climate(config, *args) + await register_climate_ir(var, config) + return var diff --git a/esphome/components/climate_ir_lg/climate.py b/esphome/components/climate_ir_lg/climate.py index 76d4c00baf..de824bfe5c 100644 --- a/esphome/components/climate_ir_lg/climate.py +++ b/esphome/components/climate_ir_lg/climate.py @@ -1,7 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] @@ -14,9 +13,8 @@ CONF_BIT_HIGH = "bit_high" CONF_BIT_ONE_LOW = "bit_one_low" CONF_BIT_ZERO_LOW = "bit_zero_low" -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(LgIrClimate).extend( { - cv.GenerateID(): cv.declare_id(LgIrClimate), cv.Optional( CONF_HEADER_HIGH, default="8000us" ): cv.positive_time_period_microseconds, @@ -37,8 +35,7 @@ CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + var = await climate_ir.new_climate_ir(config) cg.add(var.set_header_high(config[CONF_HEADER_HIGH])) cg.add(var.set_header_low(config[CONF_HEADER_LOW])) diff --git a/esphome/components/coolix/climate.py b/esphome/components/coolix/climate.py index 339e7de906..b280544a5c 100644 --- a/esphome/components/coolix/climate.py +++ b/esphome/components/coolix/climate.py @@ -1,7 +1,5 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] CODEOWNERS = ["@glmnet"] @@ -9,13 +7,8 @@ CODEOWNERS = ["@glmnet"] coolix_ns = cg.esphome_ns.namespace("coolix") CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(CoolixClimate), - } -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(CoolixClimate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/daikin/climate.py b/esphome/components/daikin/climate.py index 3946513191..2cd44969c1 100644 --- a/esphome/components/daikin/climate.py +++ b/esphome/components/daikin/climate.py @@ -1,20 +1,13 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] daikin_ns = cg.esphome_ns.namespace("daikin") DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(DaikinClimate), - } -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinClimate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/daikin_arc/climate.py b/esphome/components/daikin_arc/climate.py index 967d080c24..8f6b07315d 100644 --- a/esphome/components/daikin_arc/climate.py +++ b/esphome/components/daikin_arc/climate.py @@ -1,18 +1,13 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] daikin_arc_ns = cg.esphome_ns.namespace("daikin_arc") DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - {cv.GenerateID(): cv.declare_id(DaikinArcClimate)} -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinArcClimate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/daikin_brc/climate.py b/esphome/components/daikin_brc/climate.py index aacac408ca..1000784380 100644 --- a/esphome/components/daikin_brc/climate.py +++ b/esphome/components/daikin_brc/climate.py @@ -1,7 +1,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_USE_FAHRENHEIT +from esphome.const import CONF_USE_FAHRENHEIT AUTO_LOAD = ["climate_ir"] @@ -9,15 +9,13 @@ daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc") DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinBrcClimate).extend( { - cv.GenerateID(): cv.declare_id(DaikinBrcClimate), cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean, } ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + var = await climate_ir.new_climate_ir(config) cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT])) diff --git a/esphome/components/delonghi/climate.py b/esphome/components/delonghi/climate.py index 0d3bb76c98..ff878b4ff7 100644 --- a/esphome/components/delonghi/climate.py +++ b/esphome/components/delonghi/climate.py @@ -1,20 +1,13 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] delonghi_ns = cg.esphome_ns.namespace("delonghi") DelonghiClimate = delonghi_ns.class_("DelonghiClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(DelonghiClimate), - } -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DelonghiClimate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/emmeti/climate.py b/esphome/components/emmeti/climate.py index b925f4b61e..042f1af64b 100644 --- a/esphome/components/emmeti/climate.py +++ b/esphome/components/emmeti/climate.py @@ -1,7 +1,5 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID CODEOWNERS = ["@E440QF"] AUTO_LOAD = ["climate_ir"] @@ -9,13 +7,8 @@ AUTO_LOAD = ["climate_ir"] emmeti_ns = cg.esphome_ns.namespace("emmeti") EmmetiClimate = emmeti_ns.class_("EmmetiClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(EmmetiClimate), - } -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(EmmetiClimate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/fujitsu_general/climate.py b/esphome/components/fujitsu_general/climate.py index 6d2e46512e..0f028d0af3 100644 --- a/esphome/components/fujitsu_general/climate.py +++ b/esphome/components/fujitsu_general/climate.py @@ -1,7 +1,5 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] @@ -10,13 +8,8 @@ FujitsuGeneralClimate = fujitsu_general_ns.class_( "FujitsuGeneralClimate", climate_ir.ClimateIR ) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(FujitsuGeneralClimate), - } -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(FujitsuGeneralClimate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/gree/climate.py b/esphome/components/gree/climate.py index 389c9fb3c7..947ef9bb97 100644 --- a/esphome/components/gree/climate.py +++ b/esphome/components/gree/climate.py @@ -1,7 +1,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_MODEL +from esphome.const import CONF_MODEL CODEOWNERS = ["@orestismers"] @@ -21,16 +21,13 @@ MODELS = { "yag": Model.GREE_YAG, } -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(GreeClimate).extend( { - cv.GenerateID(): cv.declare_id(GreeClimate), cv.Required(CONF_MODEL): cv.enum(MODELS), } ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate_ir.new_climate_ir(config) cg.add(var.set_model(config[CONF_MODEL])) - - await climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index f77d624649..0393c263d4 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -30,6 +30,7 @@ from esphome.const import ( CONF_VISUAL, CONF_WIFI, ) +from esphome.cpp_generator import MockObjClass import esphome.final_validate as fv _LOGGER = logging.getLogger(__name__) @@ -185,42 +186,46 @@ def validate_visual(config): return config -BASE_CONFIG_SCHEMA = ( - climate.CLIMATE_SCHEMA.extend( - { - cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list( - cv.enum(SUPPORTED_CLIMATE_MODES_OPTIONS, upper=True) - ), - cv.Optional( - CONF_SUPPORTED_SWING_MODES, - default=[ - "VERTICAL", - "HORIZONTAL", - "BOTH", - ], - ): cv.ensure_list(cv.enum(SUPPORTED_SWING_MODES_OPTIONS, upper=True)), - cv.Optional(CONF_WIFI_SIGNAL, default=False): cv.boolean, - cv.Optional(CONF_DISPLAY): cv.boolean, - cv.Optional( - CONF_ANSWER_TIMEOUT, - ): cv.positive_time_period_milliseconds, - cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StatusMessageTrigger), - } - ), - } +def _base_config_schema(class_: MockObjClass) -> cv.Schema: + return ( + climate.climate_schema(class_) + .extend( + { + cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list( + cv.enum(SUPPORTED_CLIMATE_MODES_OPTIONS, upper=True) + ), + cv.Optional( + CONF_SUPPORTED_SWING_MODES, + default=[ + "VERTICAL", + "HORIZONTAL", + "BOTH", + ], + ): cv.ensure_list(cv.enum(SUPPORTED_SWING_MODES_OPTIONS, upper=True)), + cv.Optional(CONF_WIFI_SIGNAL, default=False): cv.boolean, + cv.Optional(CONF_DISPLAY): cv.boolean, + cv.Optional( + CONF_ANSWER_TIMEOUT, + ): cv.positive_time_period_milliseconds, + cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + StatusMessageTrigger + ), + } + ), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) ) - .extend(uart.UART_DEVICE_SCHEMA) - .extend(cv.COMPONENT_SCHEMA) -) + CONFIG_SCHEMA = cv.All( cv.typed_schema( { - PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend( + PROTOCOL_SMARTAIR2: _base_config_schema(Smartair2Climate).extend( { - cv.GenerateID(): cv.declare_id(Smartair2Climate), cv.Optional( CONF_ALTERNATIVE_SWING_CONTROL, default=False ): cv.boolean, @@ -232,9 +237,8 @@ CONFIG_SCHEMA = cv.All( ), } ), - PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend( + PROTOCOL_HON: _base_config_schema(HonClimate).extend( { - cv.GenerateID(): cv.declare_id(HonClimate), cv.Optional( CONF_CONTROL_METHOD, default="SET_GROUP_PARAMETERS" ): cv.ensure_list( @@ -464,10 +468,9 @@ FINAL_VALIDATE_SCHEMA = _final_validate async def to_code(config): cg.add(haier_ns.init_haier_protocol_logging()) - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate.new_climate(config) await cg.register_component(var, config) await uart.register_uart_device(var, config) - await climate.register_climate(var, config) cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL])) if CONF_CONTROL_METHOD in config: diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 612b0d6123..21b0168615 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -2,7 +2,6 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv from esphome.const import ( - CONF_ID, CONF_MAX_TEMPERATURE, CONF_MIN_TEMPERATURE, CONF_PROTOCOL, @@ -98,9 +97,8 @@ VERTICAL_DIRECTIONS = { } CONFIG_SCHEMA = cv.All( - climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( + climate_ir.climare_ir_with_receiver_schema(HeatpumpIRClimate).extend( { - cv.GenerateID(): cv.declare_id(HeatpumpIRClimate), cv.Required(CONF_PROTOCOL): cv.enum(PROTOCOLS), cv.Required(CONF_HORIZONTAL_DEFAULT): cv.enum(HORIZONTAL_DIRECTIONS), cv.Required(CONF_VERTICAL_DEFAULT): cv.enum(VERTICAL_DIRECTIONS), @@ -112,8 +110,8 @@ CONFIG_SCHEMA = cv.All( ) -def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) +async def to_code(config): + var = await climate_ir.new_climate_ir(config) if CONF_VISUAL not in config: config[CONF_VISUAL] = {} visual = config[CONF_VISUAL] @@ -121,7 +119,6 @@ def to_code(config): visual[CONF_MAX_TEMPERATURE] = config[CONF_MAX_TEMPERATURE] if CONF_MIN_TEMPERATURE not in visual: visual[CONF_MIN_TEMPERATURE] = config[CONF_MIN_TEMPERATURE] - yield climate_ir.register_climate_ir(var, config) cg.add(var.set_protocol(config[CONF_PROTOCOL])) cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT])) cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT])) diff --git a/esphome/components/hitachi_ac344/climate.py b/esphome/components/hitachi_ac344/climate.py index 0988d63995..4fa2d54fbb 100644 --- a/esphome/components/hitachi_ac344/climate.py +++ b/esphome/components/hitachi_ac344/climate.py @@ -1,20 +1,13 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] hitachi_ac344_ns = cg.esphome_ns.namespace("hitachi_ac344") HitachiClimate = hitachi_ac344_ns.class_("HitachiClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(HitachiClimate), - } -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/hitachi_ac424/climate.py b/esphome/components/hitachi_ac424/climate.py index 74f3c2fa14..4b20147922 100644 --- a/esphome/components/hitachi_ac424/climate.py +++ b/esphome/components/hitachi_ac424/climate.py @@ -1,20 +1,13 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] hitachi_ac424_ns = cg.esphome_ns.namespace("hitachi_ac424") HitachiClimate = hitachi_ac424_ns.class_("HitachiClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(HitachiClimate), - } -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/midea/climate.py b/esphome/components/midea/climate.py index 1d3cac66ba..b08a47afa9 100644 --- a/esphome/components/midea/climate.py +++ b/esphome/components/midea/climate.py @@ -104,9 +104,9 @@ validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True) validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True) CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( + climate.climate_schema(AirConditioner) + .extend( { - cv.GenerateID(): cv.declare_id(AirConditioner), cv.Optional(CONF_PERIOD, default="1s"): cv.time_period, cv.Optional(CONF_TIMEOUT, default="2s"): cv.time_period, cv.Optional(CONF_NUM_ATTEMPTS, default=3): cv.int_range(min=1, max=5), @@ -259,10 +259,9 @@ async def power_inv_to_code(var, config, args): async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate.new_climate(config) await cg.register_component(var, config) await uart.register_uart_device(var, config) - await climate.register_climate(var, config) cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds)) cg.add(var.set_response_timeout(config[CONF_TIMEOUT].total_milliseconds)) cg.add(var.set_request_attempts(config[CONF_NUM_ATTEMPTS])) diff --git a/esphome/components/midea_ir/climate.py b/esphome/components/midea_ir/climate.py index 21fa5f4f56..5c9256b5e4 100644 --- a/esphome/components/midea_ir/climate.py +++ b/esphome/components/midea_ir/climate.py @@ -1,7 +1,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_USE_FAHRENHEIT +from esphome.const import CONF_USE_FAHRENHEIT AUTO_LOAD = ["climate_ir", "coolix"] CODEOWNERS = ["@dudanov"] @@ -10,15 +10,13 @@ midea_ir_ns = cg.esphome_ns.namespace("midea_ir") MideaIR = midea_ir_ns.class_("MideaIR", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(MideaIR).extend( { - cv.GenerateID(): cv.declare_id(MideaIR), cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean, } ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + var = await climate_ir.new_climate_ir(config) cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT])) diff --git a/esphome/components/mitsubishi/climate.py b/esphome/components/mitsubishi/climate.py index 23f8ed21fa..5784d3ee8a 100644 --- a/esphome/components/mitsubishi/climate.py +++ b/esphome/components/mitsubishi/climate.py @@ -1,7 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv -from esphome.const import CONF_ID CODEOWNERS = ["@RubyBailey"] AUTO_LOAD = ["climate_ir"] @@ -44,9 +43,8 @@ VERTICAL_DIRECTIONS = { } -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(MitsubishiClimate).extend( { - cv.GenerateID(): cv.declare_id(MitsubishiClimate), cv.Optional(CONF_SET_FAN_MODE, default="3levels"): cv.enum(SETFANMODE), cv.Optional(CONF_SUPPORTS_DRY, default=False): cv.boolean, cv.Optional(CONF_SUPPORTS_FAN_ONLY, default=False): cv.boolean, @@ -61,8 +59,7 @@ CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + var = await climate_ir.new_climate_ir(config) cg.add(var.set_fan_mode(config[CONF_SET_FAN_MODE])) cg.add(var.set_supports_dry(config[CONF_SUPPORTS_DRY])) diff --git a/esphome/components/noblex/climate.py b/esphome/components/noblex/climate.py index 7f4e8e6488..d619265d01 100644 --- a/esphome/components/noblex/climate.py +++ b/esphome/components/noblex/climate.py @@ -1,20 +1,13 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] noblex_ns = cg.esphome_ns.namespace("noblex") NoblexClimate = noblex_ns.class_("NoblexClimate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(NoblexClimate), - } -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(NoblexClimate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/pid/climate.py b/esphome/components/pid/climate.py index aab7ee5c00..5919d2cac8 100644 --- a/esphome/components/pid/climate.py +++ b/esphome/components/pid/climate.py @@ -41,9 +41,8 @@ CONF_KI_MULTIPLIER = "ki_multiplier" CONF_KD_MULTIPLIER = "kd_multiplier" CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( + climate.climate_schema(PIDClimate).extend( { - cv.GenerateID(): cv.declare_id(PIDClimate), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE): cv.temperature, @@ -80,9 +79,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate.new_climate(config) await cg.register_component(var, config) - await climate.register_climate(var, config) sens = await cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_sensor(sens)) diff --git a/esphome/components/tcl112/climate.py b/esphome/components/tcl112/climate.py index 9cd193f5c7..9864113a52 100644 --- a/esphome/components/tcl112/climate.py +++ b/esphome/components/tcl112/climate.py @@ -1,7 +1,5 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] CODEOWNERS = ["@glmnet"] @@ -9,13 +7,8 @@ CODEOWNERS = ["@glmnet"] tcl112_ns = cg.esphome_ns.namespace("tcl112") Tcl112Climate = tcl112_ns.class_("Tcl112Climate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(Tcl112Climate), - } -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(Tcl112Climate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 638aad7c06..0314d877a3 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -516,9 +516,9 @@ def validate_thermostat(config): CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( + climate.climate_schema(ThermostatClimate) + .extend( { - cv.GenerateID(): cv.declare_id(ThermostatClimate), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_IDLE_ACTION): automation.validate_automation(single=True), @@ -631,7 +631,8 @@ CONFIG_SCHEMA = cv.All( single=True ), } - ).extend(cv.COMPONENT_SCHEMA), + ) + .extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key( CONF_COOL_ACTION, CONF_DRY_ACTION, CONF_FAN_ONLY_ACTION, CONF_HEAT_ACTION ), @@ -640,9 +641,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate.new_climate(config) await cg.register_component(var, config) - await climate.register_climate(var, config) heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config two_points_available = CONF_HEAT_ACTION in config and ( diff --git a/esphome/components/toshiba/climate.py b/esphome/components/toshiba/climate.py index 54582b78a9..40112fc460 100644 --- a/esphome/components/toshiba/climate.py +++ b/esphome/components/toshiba/climate.py @@ -1,7 +1,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_MODEL +from esphome.const import CONF_MODEL AUTO_LOAD = ["climate_ir"] CODEOWNERS = ["@kbx81"] @@ -16,15 +16,13 @@ MODELS = { "RAC-PT1411HWRU-F": Model.MODEL_RAC_PT1411HWRU_F, } -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(ToshibaClimate).extend( { - cv.GenerateID(): cv.declare_id(ToshibaClimate), cv.Optional(CONF_MODEL, default="generic"): cv.enum(MODELS, upper=True), } ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + var = await climate_ir.new_climate_ir(config) cg.add(var.set_model(config[CONF_MODEL])) diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 371c599ef7..4dbdf07651 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -4,7 +4,6 @@ from esphome.components import climate import esphome.config_validation as cv from esphome.const import ( CONF_FAN_MODE, - CONF_ID, CONF_PRESET, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, @@ -151,9 +150,9 @@ SWING_MODES = cv.Schema( ) CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( + climate.climate_schema(TuyaClimate) + .extend( { - cv.GenerateID(): cv.declare_id(TuyaClimate), cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, cv.Optional(CONF_SUPPORTS_COOL, default=False): cv.boolean, @@ -186,7 +185,8 @@ CONFIG_SCHEMA = cv.All( "'eco_temperature' has been moved inside of the 'eco' config block under 'preset' as 'temperature'" ), } - ).extend(cv.COMPONENT_SCHEMA), + ) + .extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT), validate_temperature_multipliers, validate_cooling_values, @@ -194,9 +194,8 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate.new_climate(config) await cg.register_component(var, config) - await climate.register_climate(var, config) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/uponor_smatrix/climate/__init__.py b/esphome/components/uponor_smatrix/climate/__init__.py index 5aeb521fb1..47495fde9a 100644 --- a/esphome/components/uponor_smatrix/climate/__init__.py +++ b/esphome/components/uponor_smatrix/climate/__init__.py @@ -1,7 +1,5 @@ import esphome.codegen as cg from esphome.components import climate -import esphome.config_validation as cv -from esphome.const import CONF_ID from .. import ( UPONOR_SMATRIX_DEVICE_SCHEMA, @@ -19,15 +17,12 @@ UponorSmatrixClimate = uponor_smatrix_ns.class_( UponorSmatrixDevice, ) -CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(UponorSmatrixClimate), - } -).extend(UPONOR_SMATRIX_DEVICE_SCHEMA) +CONFIG_SCHEMA = climate.climate_schema(UponorSmatrixClimate).extend( + UPONOR_SMATRIX_DEVICE_SCHEMA +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate.new_climate(config) await cg.register_component(var, config) - await climate.register_climate(var, config) await register_uponor_smatrix_device(var, config) diff --git a/esphome/components/whirlpool/climate.py b/esphome/components/whirlpool/climate.py index 40c6053349..daee9e7fb7 100644 --- a/esphome/components/whirlpool/climate.py +++ b/esphome/components/whirlpool/climate.py @@ -1,7 +1,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_MODEL +from esphome.const import CONF_MODEL AUTO_LOAD = ["climate_ir"] CODEOWNERS = ["@glmnet"] @@ -15,15 +15,13 @@ MODELS = { "DG11J1-91": Model.MODEL_DG11J1_91, } -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(WhirlpoolClimate).extend( { - cv.GenerateID(): cv.declare_id(WhirlpoolClimate), cv.Optional(CONF_MODEL, default="DG11J1-3A"): cv.enum(MODELS, upper=True), } ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + var = await climate_ir.new_climate_ir(config) cg.add(var.set_model(config[CONF_MODEL])) diff --git a/esphome/components/whynter/climate.py b/esphome/components/whynter/climate.py index ae21c64e9b..4a01c014c7 100644 --- a/esphome/components/whynter/climate.py +++ b/esphome/components/whynter/climate.py @@ -1,7 +1,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_USE_FAHRENHEIT +from esphome.const import CONF_USE_FAHRENHEIT AUTO_LOAD = ["climate_ir"] @@ -9,15 +9,13 @@ whynter_ns = cg.esphome_ns.namespace("whynter") Whynter = whynter_ns.class_("Whynter", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(Whynter).extend( { - cv.GenerateID(): cv.declare_id(Whynter), cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean, } ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + var = await climate_ir.new_climate_ir(config) cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT])) diff --git a/esphome/components/yashima/climate.py b/esphome/components/yashima/climate.py index eb68d3b6e7..d7386eb6a3 100644 --- a/esphome/components/yashima/climate.py +++ b/esphome/components/yashima/climate.py @@ -2,7 +2,7 @@ import esphome.codegen as cg from esphome.components import climate, remote_transmitter, sensor from esphome.components.remote_base import CONF_TRANSMITTER_ID import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT +from esphome.const import CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT AUTO_LOAD = ["sensor"] @@ -10,9 +10,9 @@ yashima_ns = cg.esphome_ns.namespace("yashima") YashimaClimate = yashima_ns.class_("YashimaClimate", climate.Climate, cg.Component) CONFIG_SCHEMA = cv.All( - climate.CLIMATE_SCHEMA.extend( + climate.climate_schema(YashimaClimate) + .extend( { - cv.GenerateID(): cv.declare_id(YashimaClimate), cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id( remote_transmitter.RemoteTransmitterComponent ), @@ -20,14 +20,14 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), } - ).extend(cv.COMPONENT_SCHEMA) + ) + .extend(cv.COMPONENT_SCHEMA) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await climate.new_climate(config) await cg.register_component(var, config) - await climate.register_climate(var, config) cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL])) cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT])) diff --git a/esphome/components/zhlt01/climate.py b/esphome/components/zhlt01/climate.py index fc01107e1d..d5098ab42c 100644 --- a/esphome/components/zhlt01/climate.py +++ b/esphome/components/zhlt01/climate.py @@ -1,7 +1,5 @@ import esphome.codegen as cg from esphome.components import climate_ir -import esphome.config_validation as cv -from esphome.const import CONF_ID AUTO_LOAD = ["climate_ir"] CODEOWNERS = ["@cfeenstra1024"] @@ -9,11 +7,8 @@ CODEOWNERS = ["@cfeenstra1024"] zhlt01_ns = cg.esphome_ns.namespace("zhlt01") ZHLT01Climate = zhlt01_ns.class_("ZHLT01Climate", climate_ir.ClimateIR) -CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend( - {cv.GenerateID(): cv.declare_id(ZHLT01Climate)} -) +CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(ZHLT01Climate) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await climate_ir.register_climate_ir(var, config) + await climate_ir.new_climate_ir(config) From 498e3904a9c5aa02315a1490d56ded68b89f8f7b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 May 2025 19:08:16 +1200 Subject: [PATCH 7/8] Bump esphome-dashboard to 20250514.0 (#8790) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9547cd0ef0..e3b3538d12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ pyserial==3.5 platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile esptool==4.8.1 click==8.1.7 -esphome-dashboard==20250415.0 +esphome-dashboard==20250514.0 aioesphomeapi==30.2.0 zeroconf==0.147.0 puremagic==1.29 From d9839f3a5cb6232c976cdb08b2aa33489a713417 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 14 May 2025 21:29:00 +1200 Subject: [PATCH 8/8] Bump version to 2025.5.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 0974a673ec..f48de581ea 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2025.5.0b1" +__version__ = "2025.5.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = (