diff --git a/CODEOWNERS b/CODEOWNERS index 9ddbca5c71..3047072ea2 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -381,6 +381,7 @@ esphome/components/nextion/switch/* @senexcrenshaw esphome/components/nextion/text_sensor/* @senexcrenshaw esphome/components/nfc/* @jesserockz @kbx81 esphome/components/noblex/* @AGalfra +esphome/components/noise/* @esphome/core esphome/components/npi19/* @bakerkj esphome/components/nrf52/* @tomaszduda23 esphome/components/number/* @esphome/core diff --git a/esphome/components/a01nyub/sensor.py b/esphome/components/a01nyub/sensor.py index e5f4f7ef30..f84091d688 100644 --- a/esphome/components/a01nyub/sensor.py +++ b/esphome/components/a01nyub/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_METER, ) +from esphome.types import ConfigType CODEOWNERS = ["@MrSuicideParrot"] DEPENDENCIES = ["uart"] @@ -35,7 +36,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/a02yyuw/sensor.py b/esphome/components/a02yyuw/sensor.py index f0bc59ae6c..7372f8f760 100644 --- a/esphome/components/a02yyuw/sensor.py +++ b/esphome/components/a02yyuw/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_MILLIMETER, ) +from esphome.types import ConfigType CODEOWNERS = ["@TH-Braemer"] DEPENDENCIES = ["uart"] @@ -35,7 +36,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/a4988/stepper.py b/esphome/components/a4988/stepper.py index 97f5a6fe0f..7a19bd550d 100644 --- a/esphome/components/a4988/stepper.py +++ b/esphome/components/a4988/stepper.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import stepper import esphome.config_validation as cv from esphome.const import CONF_DIR_PIN, CONF_ID, CONF_SLEEP_PIN, CONF_STEP_PIN +from esphome.types import ConfigType a4988_ns = cg.esphome_ns.namespace("a4988") A4988 = a4988_ns.class_("A4988", stepper.Stepper, cg.Component) @@ -17,7 +18,7 @@ CONFIG_SCHEMA = stepper.STEPPER_SCHEMA.extend( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await stepper.register_stepper(var, config) diff --git a/esphome/components/absolute_humidity/sensor.py b/esphome/components/absolute_humidity/sensor.py index caaa546e25..84a69dfa23 100644 --- a/esphome/components/absolute_humidity/sensor.py +++ b/esphome/components/absolute_humidity/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_GRAMS_PER_CUBIC_METER, ) +from esphome.types import ConfigType absolute_humidity_ns = cg.esphome_ns.namespace("absolute_humidity") AbsoluteHumidityComponent = absolute_humidity_ns.class_( @@ -43,7 +44,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/ac_dimmer/output.py b/esphome/components/ac_dimmer/output.py index 48bef2c317..498565b0ea 100644 --- a/esphome/components/ac_dimmer/output.py +++ b/esphome/components/ac_dimmer/output.py @@ -4,6 +4,7 @@ from esphome.components import output import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_METHOD, CONF_MIN_POWER from esphome.core import CORE +from esphome.types import ConfigType CODEOWNERS = ["@glmnet"] @@ -48,7 +49,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: if CORE.is_esp32: from esphome.components.esp32 import include_builtin_idf_component diff --git a/esphome/components/adalight/__init__.py b/esphome/components/adalight/__init__.py index 5e122676cd..afdfefaba6 100644 --- a/esphome/components/adalight/__init__.py +++ b/esphome/components/adalight/__init__.py @@ -4,6 +4,9 @@ from esphome.components.light.effects import register_addressable_effect from esphome.components.light.types import AddressableLightEffect import esphome.config_validation as cv from esphome.const import CONF_NAME, CONF_UART_ID +from esphome.core import ID +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType DEPENDENCIES = ["uart"] @@ -21,7 +24,7 @@ CONFIG_SCHEMA = cv.Schema({}) "Adalight", {cv.GenerateID(CONF_UART_ID): cv.use_id(uart.UARTComponent)}, ) -async def adalight_light_effect_to_code(config, effect_id): +async def adalight_light_effect_to_code(config: ConfigType, effect_id: ID) -> MockObj: effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) await uart.register_uart_device(effect, config) return effect diff --git a/esphome/components/adc128s102/__init__.py b/esphome/components/adc128s102/__init__.py index a5281aacc7..684147752d 100644 --- a/esphome/components/adc128s102/__init__.py +++ b/esphome/components/adc128s102/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import spi import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType DEPENDENCIES = ["spi"] MULTI_CONF = True @@ -17,7 +18,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(spi.spi_device_schema(cs_pin_required=True)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/adc128s102/sensor/__init__.py b/esphome/components/adc128s102/sensor/__init__.py index a65ae9d537..04589a7ce2 100644 --- a/esphome/components/adc128s102/sensor/__init__.py +++ b/esphome/components/adc128s102/sensor/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import sensor, voltage_sampler import esphome.config_validation as cv from esphome.const import CONF_CHANNEL, CONF_ID +from esphome.types import ConfigType from .. import ADC128S102, adc128s102_ns @@ -28,7 +29,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable( config[CONF_ID], config[CONF_CHANNEL], diff --git a/esphome/components/addressable_light/display.py b/esphome/components/addressable_light/display.py index 929d45121c..1db01b40f9 100644 --- a/esphome/components/addressable_light/display.py +++ b/esphome/components/addressable_light/display.py @@ -11,6 +11,7 @@ from esphome.const import ( CONF_UPDATE_INTERVAL, CONF_WIDTH, ) +from esphome.types import ConfigType CODEOWNERS = ["@justfalter"] @@ -38,7 +39,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) wrapped_light = await cg.get_variable(config[CONF_ADDRESSABLE_LIGHT_ID]) cg.add(var.set_width(config[CONF_WIDTH])) diff --git a/esphome/components/ade7880/sensor.py b/esphome/components/ade7880/sensor.py index beb74d7310..93c279e235 100644 --- a/esphome/components/ade7880/sensor.py +++ b/esphome/components/ade7880/sensor.py @@ -36,6 +36,7 @@ from esphome.const import ( UNIT_WATT, UNIT_WATT_HOURS, ) +from esphome.cpp_generator import MockObj from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -243,7 +244,7 @@ CONFIG_SCHEMA = cv.All( ) -async def neutral_channel(config): +async def neutral_channel(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) current = config[CONF_CURRENT] @@ -257,7 +258,7 @@ async def neutral_channel(config): return var -async def power_channel(config): +async def power_channel(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) for sensor_type in POWER_SENSOR_TYPES: @@ -280,7 +281,7 @@ async def power_channel(config): return var -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ade7953_base/__init__.py b/esphome/components/ade7953_base/__init__.py index 4fc35352f9..71250ac94e 100644 --- a/esphome/components/ade7953_base/__init__.py +++ b/esphome/components/ade7953_base/__init__.py @@ -23,6 +23,8 @@ from esphome.const import ( UNIT_VOLT_AMPS_REACTIVE, UNIT_WATT, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@angelnu"] @@ -163,7 +165,7 @@ ADE7953_CONFIG_SCHEMA = cv.Schema( ).extend(cv.polling_component_schema("60s")) -async def register_ade7953(var, config): +async def register_ade7953(var: MockObj, config: ConfigType) -> None: await cg.register_component(var, config) if irq_pin_config := config.get(CONF_IRQ_PIN): diff --git a/esphome/components/ade7953_i2c/sensor.py b/esphome/components/ade7953_i2c/sensor.py index 4b55acdafa..8447042d30 100644 --- a/esphome/components/ade7953_i2c/sensor.py +++ b/esphome/components/ade7953_i2c/sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import ade7953_base, i2c import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] AUTO_LOAD = ["ade7953_base"] @@ -20,7 +21,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await i2c.register_i2c_device(var, config) await ade7953_base.register_ade7953(var, config) diff --git a/esphome/components/ade7953_spi/sensor.py b/esphome/components/ade7953_spi/sensor.py index dce021daad..6fdf2147f3 100644 --- a/esphome/components/ade7953_spi/sensor.py +++ b/esphome/components/ade7953_spi/sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import ade7953_base, spi import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType DEPENDENCIES = ["spi"] AUTO_LOAD = ["ade7953_base"] @@ -20,7 +21,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await spi.register_spi_device(var, config) await ade7953_base.register_ade7953(var, config) diff --git a/esphome/components/ads1115/__init__.py b/esphome/components/ads1115/__init__.py index 6d52fc83fd..b42ee918c5 100644 --- a/esphome/components/ads1115/__init__.py +++ b/esphome/components/ads1115/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] MULTI_CONF = True @@ -24,7 +25,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ads1115/sensor/__init__.py b/esphome/components/ads1115/sensor/__init__.py index afb70d07c8..742f82d302 100644 --- a/esphome/components/ads1115/sensor/__init__.py +++ b/esphome/components/ads1115/sensor/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) +from esphome.types import ConfigType from .. import CONF_ADS1115_ID, ADS1115Component, ads1115_ns @@ -86,7 +87,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await sensor.register_sensor(var, config) await cg.register_component(var, config) diff --git a/esphome/components/ads1118/__init__.py b/esphome/components/ads1118/__init__.py index 45d47a329e..956b9a0c1f 100644 --- a/esphome/components/ads1118/__init__.py +++ b/esphome/components/ads1118/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import spi import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType CODEOWNERS = ["@solomondg1"] DEPENDENCIES = ["spi"] @@ -23,7 +24,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/ads1118/sensor/__init__.py b/esphome/components/ads1118/sensor/__init__.py index 33bfe97789..6bc3baa2e4 100644 --- a/esphome/components/ads1118/sensor/__init__.py +++ b/esphome/components/ads1118/sensor/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_VOLT, ) +from esphome.types import ConfigType from .. import ADS1118, CONF_ADS1118_ID, ads1118_ns @@ -86,7 +87,7 @@ CONFIG_SCHEMA = cv.typed_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await cg.register_parented(var, config[CONF_ADS1118_ID]) diff --git a/esphome/components/aht10/sensor.py b/esphome/components/aht10/sensor.py index a5b1cf0ffb..ae669d0000 100644 --- a/esphome/components/aht10/sensor.py +++ b/esphome/components/aht10/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -50,7 +51,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/airthings_ble/__init__.py b/esphome/components/airthings_ble/__init__.py index d0cb7631d2..44534b80e9 100644 --- a/esphome/components/airthings_ble/__init__.py +++ b/esphome/components/airthings_ble/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import ble_device_base import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType AUTO_LOAD = ["ble_device_base"] CODEOWNERS = ["@jeromelaban"] @@ -21,6 +22,6 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await ble_device_base.register_ble_device(var, config) diff --git a/esphome/components/airthings_wave_base/__init__.py b/esphome/components/airthings_wave_base/__init__.py index dee26b524a..58fde11a3d 100644 --- a/esphome/components/airthings_wave_base/__init__.py +++ b/esphome/components/airthings_wave_base/__init__.py @@ -20,6 +20,8 @@ from esphome.const import ( UNIT_PERCENT, UNIT_VOLT, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@ncareau", "@jeromelaban"] @@ -78,7 +80,7 @@ BASE_SCHEMA = ( ) -async def wave_base_to_code(var, config): +async def wave_base_to_code(var: MockObj, config: ConfigType) -> None: await cg.register_component(var, config) await ble_client.register_ble_node(var, config) diff --git a/esphome/components/airthings_wave_mini/sensor.py b/esphome/components/airthings_wave_mini/sensor.py index f231be6670..9136b333e2 100644 --- a/esphome/components/airthings_wave_mini/sensor.py +++ b/esphome/components/airthings_wave_mini/sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import airthings_wave_base import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType DEPENDENCIES = airthings_wave_base.DEPENDENCIES @@ -20,6 +21,6 @@ CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await airthings_wave_base.wave_base_to_code(var, config) diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index a12c70f04c..8ea79e644f 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -83,7 +83,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await airthings_wave_base.wave_base_to_code(var, config) diff --git a/esphome/components/alpha3/sensor.py b/esphome/components/alpha3/sensor.py index 279ab214cf..2c1a04ef27 100644 --- a/esphome/components/alpha3/sensor.py +++ b/esphome/components/alpha3/sensor.py @@ -20,6 +20,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, ) +from esphome.types import ConfigType alpha3_ns = cg.esphome_ns.namespace("alpha3") Alpha3 = alpha3_ns.class_("Alpha3", ble_client.BLEClientNode, cg.PollingComponent) @@ -68,7 +69,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await ble_client.register_ble_node(var, config) diff --git a/esphome/components/am2315c/sensor.py b/esphome/components/am2315c/sensor.py index ec12ab717e..febb11409c 100644 --- a/esphome/components/am2315c/sensor.py +++ b/esphome/components/am2315c/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -40,7 +41,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/am2320/sensor.py b/esphome/components/am2320/sensor.py index ed4a5fd922..ffac0e6407 100644 --- a/esphome/components/am2320/sensor.py +++ b/esphome/components/am2320/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/am43/cover/__init__.py b/esphome/components/am43/cover/__init__.py index e4ecf1444f..d1783b77df 100644 --- a/esphome/components/am43/cover/__init__.py +++ b/esphome/components/am43/cover/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import ble_client, cover import esphome.config_validation as cv from esphome.const import CONF_PIN +from esphome.types import ConfigType CODEOWNERS = ["@buxtronix"] DEPENDENCIES = ["ble_client"] @@ -27,7 +28,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await cover.new_cover(config) cg.add(var.set_pin(config[CONF_PIN])) cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) diff --git a/esphome/components/am43/sensor/__init__.py b/esphome/components/am43/sensor/__init__.py index 2697d364ad..80341972a9 100644 --- a/esphome/components/am43/sensor/__init__.py +++ b/esphome/components/am43/sensor/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ) +from esphome.types import ConfigType AUTO_LOAD = ["am43"] CODEOWNERS = ["@buxtronix"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await ble_client.register_ble_node(var, config) diff --git a/esphome/components/analog_threshold/binary_sensor.py b/esphome/components/analog_threshold/binary_sensor.py index 8c13727755..b2de1d6184 100644 --- a/esphome/components/analog_threshold/binary_sensor.py +++ b/esphome/components/analog_threshold/binary_sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import binary_sensor, sensor import esphome.config_validation as cv from esphome.const import CONF_SENSOR_ID, CONF_THRESHOLD +from esphome.types import ConfigType analog_threshold_ns = cg.esphome_ns.namespace("analog_threshold") @@ -32,7 +33,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/anova/climate.py b/esphome/components/anova/climate.py index e1fd38fddc..5590b18a83 100644 --- a/esphome/components/anova/climate.py +++ b/esphome/components/anova/climate.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import ble_client, climate import esphome.config_validation as cv from esphome.const import CONF_UNIT_OF_MEASUREMENT +from esphome.types import ConfigType UNITS = { "f": "f", @@ -28,7 +29,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await climate.new_climate(config) await cg.register_component(var, config) await ble_client.register_ble_node(var, config) diff --git a/esphome/components/apds9306/sensor.py b/esphome/components/apds9306/sensor.py index c3cba96fbf..4f165eec0b 100644 --- a/esphome/components/apds9306/sensor.py +++ b/esphome/components/apds9306/sensor.py @@ -1,6 +1,8 @@ # Based on this datasheet: # https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf +from typing import Any + import esphome.codegen as cg from esphome.components import i2c, sensor import esphome.config_validation as cv @@ -11,6 +13,8 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_LUX, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -55,7 +59,7 @@ AMBIENT_LIGHT_GAINS = { } -def _validate_measurement_rate(value): +def _validate_measurement_rate(value: Any) -> MockObj: value = cv.positive_time_period_milliseconds(value) return cv.enum(MEASUREMENT_RATES, int=True)(value.total_milliseconds) @@ -85,7 +89,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 0dc4b905bf..a10bfd3418 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -1,4 +1,3 @@ -import base64 import logging from typing import Any @@ -6,7 +5,16 @@ from esphome import automation from esphome.automation import Condition import esphome.codegen as cg from esphome.components.logger import request_log_listener -from esphome.config_helpers import get_logger_level + +# ENCRYPTION_SCHEMA and validate_encryption_key are re-exported for external +# components and downstream consumers that import them from api +from esphome.components.noise import ( # noqa: F401 + ENCRYPTION_SCHEMA, + decode_encryption_key, + encryption_schema, + validate_encryption_key, +) +from esphome.config_helpers import filter_source_files_from_defines, get_logger_level import esphome.config_validation as cv from esphome.const import ( CONF_ACTION, @@ -38,6 +46,10 @@ from esphome.core import CORE, ID, CoroPriority, EsphomeError, coroutine_with_pr from esphome.cpp_generator import MockObj, TemplateArgsType from esphome.types import ConfigFragmentType, ConfigType +# Compat alias: downstream consumers (e.g. device-builder) referenced the +# schema by its old private name before it moved to the noise component +_encryption_schema = encryption_schema + _LOGGER = logging.getLogger(__name__) DOMAIN = "api" @@ -46,9 +58,15 @@ CODEOWNERS = ["@esphome/core"] def AUTO_LOAD(config: ConfigType) -> list[str]: - """Conditionally auto-load json only when capture_response is used.""" + """Conditionally auto-load noise (encryption) and json (capture_response).""" base = ["socket"] + # A falsy config is a tooling probe for the maximal set (None from + # dependency resolution, {} from the components-graph platform probe); + # a validated config always carries defaults, never empty + if not config or CONF_ENCRYPTION in config: + base = base + ["noise"] + # Check if any homeassistant.action/homeassistant.service has capture_response: true # This flag is set during config validation in _validate_response_config if not config or CORE.data.get(DOMAIN, {}).get(CONF_CAPTURE_RESPONSE, False): @@ -130,20 +148,6 @@ def _register_provisioning_source(config: ConfigType) -> ConfigType: return config -def validate_encryption_key(value: Any) -> str: - value = cv.string_strict(value) - try: - decoded = base64.b64decode(value, validate=True) - except ValueError as err: - raise cv.Invalid("Invalid key format, please check it's using base64") from err - - if len(decoded) != 32: - raise cv.Invalid("Encryption key must be base64 and 32 bytes long") - - # Return original data for roundtrip conversion - return value - - CONF_SUPPORTS_RESPONSE = "supports_response" # Enum values in api::enums namespace @@ -250,18 +254,6 @@ ACTIONS_SCHEMA = automation.validate_automation( ), ) -ENCRYPTION_SCHEMA = cv.Schema( - { - cv.Optional(CONF_KEY): cv.sensitive(validate_encryption_key), - } -) - - -def _encryption_schema(config: ConfigType | None) -> ConfigType: - if config is None: - config = {} - return ENCRYPTION_SCHEMA(config) - def _consume_api_sockets(config: ConfigType) -> ConfigType: """Register socket needs for API component.""" @@ -297,7 +289,7 @@ CONFIG_SCHEMA = cv.All( CONF_SERVICES, group_of_exclusion=CONF_ACTIONS ): ACTIONS_SCHEMA, cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA, - cv.Optional(CONF_ENCRYPTION): _encryption_schema, + cv.Optional(CONF_ENCRYPTION): encryption_schema, cv.Optional(CONF_BATCH_DELAY, default="100ms"): cv.All( cv.positive_time_period_milliseconds, cv.Range(max=cv.TimePeriod(milliseconds=65535)), @@ -484,7 +476,7 @@ async def to_code(config: ConfigType) -> None: if (encryption_config := config.get(CONF_ENCRYPTION, None)) is not None: if key := encryption_config.get(CONF_KEY): - decoded = base64.b64decode(key) + decoded = decode_encryption_key(key) cg.add(var.set_noise_psk(list(decoded))) cg.add_define("USE_API_NOISE_PSK_FROM_YAML") else: @@ -498,10 +490,6 @@ async def to_code(config: ConfigType) -> None: # and plaintext disabled. Only a factory reset can remove it. cg.add_define("USE_API_PLAINTEXT") cg.add_define("USE_API_NOISE") - cg.add_library("esphome/noise-c", "0.1.21") - # Enable optimized memzero/memcmp in libsodium instead of volatile byte loops - cg.add_build_flag("-DHAVE_WEAK_SYMBOLS=1") - cg.add_build_flag("-DHAVE_INLINE_ASM=1") else: cg.add_define("USE_API_PLAINTEXT") @@ -847,11 +835,20 @@ async def api_connected_to_code( return var +# user_services.cpp is only needed when user defined actions exist; the +# frame helpers are fully #ifdef'd on the protocol defines set in to_code +# (both are set when encryption is configured without a key). +_define_filter = filter_source_files_from_defines( + { + "user_services.cpp": "USE_API_USER_DEFINED_ACTIONS", + "api_frame_helper_noise.cpp": "USE_API_NOISE", + "api_frame_helper_plaintext.cpp": "USE_API_PLAINTEXT", + } +) + + def FILTER_SOURCE_FILES() -> list[str]: - """Filter out api_pb2_dump.cpp when proto message dumping is not enabled, - user_services.cpp when no services are defined, and protocol-specific - implementations based on encryption configuration.""" - files_to_filter: list[str] = [] + files_to_filter = _define_filter() # api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined # This is a particularly large file that still needs to be opened and read @@ -862,21 +859,4 @@ def FILTER_SOURCE_FILES() -> list[str]: if get_logger_level() != "VERY_VERBOSE": files_to_filter.append("api_pb2_dump.cpp") - # user_services.cpp is only needed when services are defined - config = CORE.config.get(DOMAIN, {}) - if config and not config.get(CONF_ACTIONS) and not config[CONF_CUSTOM_SERVICES]: - files_to_filter.append("user_services.cpp") - - # Filter protocol-specific implementations based on encryption configuration - encryption_config = config.get(CONF_ENCRYPTION) if config else None - - # If encryption is not configured at all, we only need plaintext - if encryption_config is None: - files_to_filter.append("api_frame_helper_noise.cpp") - # If encryption is configured with a key, we only need noise - elif encryption_config.get(CONF_KEY): - files_to_filter.append("api_frame_helper_plaintext.cpp") - # If encryption is configured but no key is provided, we need both - # (this allows a plaintext client to provide a noise key) - return files_to_filter diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index f1bc9b003a..6ea124d155 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1002,8 +1002,12 @@ message GetTimeResponse { option (no_delay) = true; fixed32 epoch_seconds = 1; - string timezone = 2; - ParsedTimezone parsed_timezone = 3; + // Deprecated in 2026.9.0: clients still send this string for older firmware, + // but new firmware only reads parsed_timezone. Clients older than Home + // Assistant 2026.3.0 that send only the string leave the device on its + // codegen-configured timezone (or UTC). + string timezone = 2 [deprecated = true]; + ParsedTimezone parsed_timezone = 3 [(track_presence) = true]; } // ==================== USER-DEFINES SERVICES ==================== diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 2eb8c21c73..bb6c1695dd 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -412,15 +412,15 @@ void APIConnection::finalize_iterator_sync_() { } void APIConnection::process_iterator_batch_(ComponentIterator &iterator) { - size_t initial_size = this->deferred_batch_.size(); - size_t max_batch = MAX_INITIAL_PER_BATCH; - while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) { - iterator.advance(); - } + // Budget by remaining batch capacity so a pass cannot overfill the batch; + // stops early on a refused send and resumes next loop pass + size_t batch_size = this->deferred_batch_.size(); + if (batch_size < MAX_INITIAL_BATCH_SIZE) + iterator.try_advance(MAX_INITIAL_BATCH_SIZE - batch_size); - // If the batch is full, process it immediately - // Note: iterator.advance() already calls schedule_batch_() via schedule_message_() - if (this->deferred_batch_.size() >= max_batch) { + // Flush immediately once enough is queued (not guaranteed every pass); + // partial batches go out via the batch timer or finalize_iterator_sync_() + if (this->deferred_batch_.size() >= MAX_INITIAL_BATCH_SIZE) { this->process_batch_(); } } @@ -1204,31 +1204,28 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) { if (homeassistant::global_homeassistant_time != nullptr) { homeassistant::global_homeassistant_time->set_epoch_time(value.epoch_seconds); #if defined(USE_HOMEASSISTANT_TIMEZONE) && defined(USE_TIME_TIMEZONE) - if (!value.timezone.empty()) { - // Check if the sender provided pre-parsed timezone data. - // If std_offset is non-zero or DST rules are present, the parsed data was populated. - // For UTC (all zeros), string parsing produces the same result, so the fallback is equivalent. + // Apply only if the sender provided pre-parsed timezone data (Home Assistant 2026.3.0 + // and newer); field presence distinguishes a genuine all-zero UTC timezone from an + // absent field. Older clients send only the deprecated timezone string, which is no + // longer decoded; for them the device keeps its codegen-configured timezone. + if (value.has_parsed_timezone) { const auto &pt = value.parsed_timezone; - if (pt.std_offset_seconds != 0 || pt.dst_start.type != enums::DST_RULE_TYPE_NONE) { - time::ParsedTimezone tz{}; - tz.std_offset_seconds = pt.std_offset_seconds; - tz.dst_offset_seconds = pt.dst_offset_seconds; - tz.dst_start.time_seconds = pt.dst_start.time_seconds; - tz.dst_start.day = static_cast(pt.dst_start.day); - tz.dst_start.type = static_cast(pt.dst_start.type); - tz.dst_start.month = static_cast(pt.dst_start.month); - tz.dst_start.week = static_cast(pt.dst_start.week); - tz.dst_start.day_of_week = static_cast(pt.dst_start.day_of_week); - tz.dst_end.time_seconds = pt.dst_end.time_seconds; - tz.dst_end.day = static_cast(pt.dst_end.day); - tz.dst_end.type = static_cast(pt.dst_end.type); - tz.dst_end.month = static_cast(pt.dst_end.month); - tz.dst_end.week = static_cast(pt.dst_end.week); - tz.dst_end.day_of_week = static_cast(pt.dst_end.day_of_week); - time::set_global_tz(tz); - } else { - homeassistant::global_homeassistant_time->set_timezone(value.timezone.c_str(), value.timezone.size()); - } + time::ParsedTimezone tz{}; + tz.std_offset_seconds = pt.std_offset_seconds; + tz.dst_offset_seconds = pt.dst_offset_seconds; + tz.dst_start.time_seconds = pt.dst_start.time_seconds; + tz.dst_start.day = static_cast(pt.dst_start.day); + tz.dst_start.type = static_cast(pt.dst_start.type); + tz.dst_start.month = static_cast(pt.dst_start.month); + tz.dst_start.week = static_cast(pt.dst_start.week); + tz.dst_start.day_of_week = static_cast(pt.dst_start.day_of_week); + tz.dst_end.time_seconds = pt.dst_end.time_seconds; + tz.dst_end.day = static_cast(pt.dst_end.day); + tz.dst_end.type = static_cast(pt.dst_end.type); + tz.dst_end.month = static_cast(pt.dst_end.month); + tz.dst_end.week = static_cast(pt.dst_end.week); + tz.dst_end.day_of_week = static_cast(pt.dst_end.day_of_week); + time::set_global_tz(tz); } #endif } @@ -2130,7 +2127,7 @@ bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptio } #endif - psk_t psk{}; + noise::psk_t psk{}; if (msg.key_len == 0) { if (this->parent_->clear_noise_psk(true)) { resp.success = true; @@ -2139,7 +2136,7 @@ bool APIConnection::send_noise_encryption_set_key_response_(const NoiseEncryptio } } else if (base64_decode(msg.key, msg.key_len, psk.data(), psk.size()) != psk.size()) { ESP_LOGW(TAG, "Invalid encryption key length"); - } else if (APINoiseContext::is_all_zeros(psk)) { + } else if (noise::NoiseContext::is_all_zeros(psk)) { // Accepting the reserved provisioning PSK would report success without // enabling encryption (or silently clear an existing key) ESP_LOGW(TAG, "Rejecting all-zero encryption key"); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index bb51a13000..1b47c23cfe 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -53,11 +53,11 @@ void log_dropped_message(const char *tag, int line, const LogString *what); // Keepalive timeout in milliseconds static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; -// Maximum number of entities to process in a single batch during initial state/info sending -static constexpr size_t MAX_INITIAL_PER_BATCH = 34; +// Deferred batch size cap during initial state/info sync +static constexpr size_t MAX_INITIAL_BATCH_SIZE = 34; // Verify MAX_MESSAGES_PER_BATCH (defined in api_frame_helper.h) can hold the initial batch -static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH, - "MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH"); +static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_BATCH_SIZE, + "MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_BATCH_SIZE"); #ifdef USE_BENCHMARK class APIConnection; diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index 9c49956bbd..1c60bb87a5 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -36,7 +36,7 @@ static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and oth static constexpr uint16_t RX_BUF_NULL_TERMINATOR = 1; // Maximum number of messages to batch in a single write operation -// Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there) +// Must be >= MAX_INITIAL_BATCH_SIZE in api_connection.h (enforced by static_assert there) static constexpr size_t MAX_MESSAGES_PER_BATCH = 34; // Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars) diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index 09e3ca2b9e..d7554e62c5 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -2,9 +2,9 @@ #ifdef USE_API #ifdef USE_API_NOISE #include "api_connection.h" // For ClientInfo struct +#include "esphome/components/noise/noise.h" #include "esphome/core/application.h" #include "esphome/core/entity_base.h" -#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "proto.h" @@ -17,6 +17,14 @@ namespace esphome::api { +using noise::noise_err_to_logstr; + +// api_frame_helper.h keeps its own MAX_HANDSHAKE_SIZE because that header is +// also compiled in plaintext-only builds without the noise component; keep +// the two definitions from drifting apart. +static_assert(MAX_HANDSHAKE_SIZE == noise::MAX_HANDSHAKE_SIZE, + "api and noise component handshake size limits must match"); + static const char *const TAG = "api.noise"; #ifdef USE_ESP8266 static constexpr char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit"; @@ -51,45 +59,6 @@ static constexpr size_t API_MAX_LOG_BYTES = 168; #define LOG_PACKET_RECEIVED(buffer) ((void) 0) #endif -/// Convert a noise error code to a readable error -const LogString *noise_err_to_logstr(int err) { - if (err == NOISE_ERROR_NO_MEMORY) - return LOG_STR("NO_MEMORY"); - if (err == NOISE_ERROR_UNKNOWN_ID) - return LOG_STR("UNKNOWN_ID"); - if (err == NOISE_ERROR_UNKNOWN_NAME) - return LOG_STR("UNKNOWN_NAME"); - if (err == NOISE_ERROR_MAC_FAILURE) - return LOG_STR("MAC_FAILURE"); - if (err == NOISE_ERROR_NOT_APPLICABLE) - return LOG_STR("NOT_APPLICABLE"); - if (err == NOISE_ERROR_SYSTEM) - return LOG_STR("SYSTEM"); - if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED) - return LOG_STR("REMOTE_KEY_REQUIRED"); - if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED) - return LOG_STR("LOCAL_KEY_REQUIRED"); - if (err == NOISE_ERROR_PSK_REQUIRED) - return LOG_STR("PSK_REQUIRED"); - if (err == NOISE_ERROR_INVALID_LENGTH) - return LOG_STR("INVALID_LENGTH"); - if (err == NOISE_ERROR_INVALID_PARAM) - return LOG_STR("INVALID_PARAM"); - if (err == NOISE_ERROR_INVALID_STATE) - return LOG_STR("INVALID_STATE"); - if (err == NOISE_ERROR_INVALID_NONCE) - return LOG_STR("INVALID_NONCE"); - if (err == NOISE_ERROR_INVALID_PRIVATE_KEY) - return LOG_STR("INVALID_PRIVATE_KEY"); - if (err == NOISE_ERROR_INVALID_PUBLIC_KEY) - return LOG_STR("INVALID_PUBLIC_KEY"); - if (err == NOISE_ERROR_INVALID_FORMAT) - return LOG_STR("INVALID_FORMAT"); - if (err == NOISE_ERROR_INVALID_SIGNATURE) - return LOG_STR("INVALID_SIGNATURE"); - return LOG_STR("UNKNOWN"); -} - /// Initialize the frame helper, returns OK if successful. APIError APINoiseFrameHelper::init() { APIError err = init_common_(); @@ -194,9 +163,9 @@ APIError APINoiseFrameHelper::loop() { */ APIError APINoiseFrameHelper::try_read_frame_() { // read header - if (rx_header_buf_len_ < 3) { + if (rx_header_buf_len_ < noise::FRAME_HEADER_SIZE) { // no header information yet - uint8_t to_read = 3 - rx_header_buf_len_; + uint8_t to_read = static_cast(noise::FRAME_HEADER_SIZE) - rx_header_buf_len_; ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); APIError err = handle_socket_read_result_(received); if (err != APIError::OK) { @@ -208,7 +177,7 @@ APIError APINoiseFrameHelper::try_read_frame_() { return APIError::WOULD_BLOCK; } - if (rx_header_buf_[0] != 0x01) { + if (rx_header_buf_[0] != noise::FRAME_INDICATOR) { state_ = State::FAILED; HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]); return APIError::BAD_INDICATOR; @@ -348,15 +317,15 @@ APIError APINoiseFrameHelper::state_action_server_hello_() { return APIError::OK; } APIError APINoiseFrameHelper::state_action_handshake_() { - int action = noise_handshakestate_get_action(this->handshake_); - if (action == NOISE_ACTION_READ_MESSAGE) { + noise::NoiseResponderHandshake::Action action = this->handshake_.action(); + if (action == noise::NoiseResponderHandshake::Action::ACTION_READ) { return this->state_action_handshake_read_(); - } else if (action == NOISE_ACTION_WRITE_MESSAGE) { + } else if (action == noise::NoiseResponderHandshake::Action::ACTION_WRITE) { return this->state_action_handshake_write_(); } // bad state for action this->state_ = State::FAILED; - HELPER_LOG("Bad action for handshake: %d", action); + HELPER_LOG("Bad action for handshake: %d", (int) action); return APIError::HANDSHAKESTATE_BAD_STATE; } APIError APINoiseFrameHelper::state_action_handshake_read_() { @@ -368,20 +337,16 @@ APIError APINoiseFrameHelper::state_action_handshake_read_() { if (this->rx_buf_.empty()) { this->send_explicit_handshake_reject_(LOG_STR("Empty handshake message")); return APIError::BAD_HANDSHAKE_ERROR_BYTE; - } else if (this->rx_buf_[0] != 0x00) { + } else if (this->rx_buf_[0] != noise::HANDSHAKE_STATUS_OK) { HELPER_LOG("Bad handshake error byte: %u", this->rx_buf_[0]); this->send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte")); return APIError::BAD_HANDSHAKE_ERROR_BYTE; } - NoiseBuffer mbuf; - noise_buffer_init(mbuf); - noise_buffer_set_input(mbuf, this->rx_buf_.data() + 1, this->rx_buf_.size() - 1); - int err = noise_handshakestate_read_message(this->handshake_, &mbuf, nullptr); + int err = this->handshake_.read_message(this->rx_buf_.data() + 1, this->rx_buf_.size() - 1); if (err != 0) { // Special handling for MAC failure - this->send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure") - : LOG_STR("Handshake error")); + this->send_explicit_handshake_reject_(noise::reject_reason_for(err)); return this->handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"), APIError::HANDSHAKESTATE_READ_FAILED); } @@ -390,18 +355,16 @@ APIError APINoiseFrameHelper::state_action_handshake_read_() { } APIError APINoiseFrameHelper::state_action_handshake_write_() { uint8_t buffer[65]; - NoiseBuffer mbuf; - noise_buffer_init(mbuf); - noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1); + size_t msg_len = 0; - int err = noise_handshakestate_write_message(this->handshake_, &mbuf, nullptr); + int err = this->handshake_.write_message(buffer + 1, sizeof(buffer) - 1, msg_len); APIError aerr = this->handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"), APIError::HANDSHAKESTATE_WRITE_FAILED); if (aerr != APIError::OK) return aerr; - buffer[0] = 0x00; // success + buffer[0] = noise::HANDSHAKE_STATUS_OK; - aerr = this->write_frame_(buffer, mbuf.size + 1); + aerr = this->write_frame_(buffer, msg_len + 1); if (aerr != APIError::OK) return aerr; return this->check_handshake_finished_(); @@ -409,33 +372,22 @@ APIError APINoiseFrameHelper::state_action_handshake_write_() { void APINoiseFrameHelper::send_explicit_handshake_reject_(const LogString *reason) { // Max reject message: "Bad handshake packet len" (24) + 1 (failure byte) = 25 bytes uint8_t data[32]; - data[0] = 0x01; // failure - -#ifdef USE_STORE_LOG_STR_IN_FLASH - // On ESP8266 with flash strings, we need to use PROGMEM-aware functions - size_t reason_len = strlen_P(reinterpret_cast(reason)); - reason_len = std::min(reason_len, sizeof(data) - 1); - if (reason_len > 0) { - memcpy_P(data + 1, reinterpret_cast(reason), reason_len); - } -#else - // Normal memory access - const char *reason_str = LOG_STR_ARG(reason); - size_t reason_len = strlen(reason_str); - reason_len = std::min(reason_len, sizeof(data) - 1); - if (reason_len > 0) { - // NOLINTNEXTLINE(bugprone-not-null-terminated-result) - binary protocol, not a C string - std::memcpy(data + 1, reason_str, reason_len); - } -#endif - - size_t data_size = reason_len + 1; + static_assert(sizeof(data) >= noise::MAC_FAILURE_PAYLOAD_SIZE, + "reject buffer must fit the MAC failure wire contract"); + size_t data_size = noise::format_reject_payload(data, sizeof(data), reason); // temporarily remove failed state auto orig_state = state_; state_ = State::EXPLICIT_REJECT; - write_frame_(data, data_size); - state_ = orig_state; + APIError aerr = write_frame_(data, data_size); + if (aerr != APIError::OK) { + // Best effort; the reject reason is a diagnosis aid, not a protocol step + ESP_LOGW(TAG, "Sending handshake reject failed: %d", (int) aerr); + } + if (state_ == State::EXPLICIT_REJECT) { + // write_frame_ may have moved the state to FAILED; keep that decision + state_ = orig_state; + } } APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { APIError aerr = this->check_data_state_(); @@ -492,12 +444,10 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) { // Returns APIError::OK on success. APIError APINoiseFrameHelper::encrypt_noise_message_(uint8_t *buf_start, uint16_t payload_size, uint8_t message_type, uint16_t &encrypted_len_out) { - // Write noise header - buf_start[0] = 0x01; // indicator - // buf_start[1], buf_start[2] to be set after encryption + // The noise frame header is written after encryption, when the size is known // Write message header (to be encrypted) - constexpr uint8_t msg_offset = 3; + constexpr uint8_t msg_offset = noise::FRAME_HEADER_SIZE; buf_start[msg_offset] = static_cast(message_type >> 8); // type high byte buf_start[msg_offset + 1] = static_cast(message_type); // type low byte buf_start[msg_offset + 2] = static_cast(payload_size >> 8); // data_len high byte @@ -515,11 +465,10 @@ APIError APINoiseFrameHelper::encrypt_noise_message_(uint8_t *buf_start, uint16_ if (aerr != APIError::OK) return aerr; - // Fill in the encrypted size - buf_start[1] = static_cast(mbuf.size >> 8); - buf_start[2] = static_cast(mbuf.size); + // Fill in the frame header now that the encrypted size is known + noise::write_frame_header(buf_start, static_cast(mbuf.size)); - encrypted_len_out = static_cast(3 + mbuf.size); // indicator + size + encrypted data + encrypted_len_out = static_cast(noise::FRAME_HEADER_SIZE + mbuf.size); return APIError::OK; } @@ -568,21 +517,19 @@ APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, s } APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { - uint8_t header[3]; - header[0] = 0x01; // indicator - header[1] = (uint8_t) (len >> 8); - header[2] = (uint8_t) len; + uint8_t header[noise::FRAME_HEADER_SIZE]; + noise::write_frame_header(header, len); if (len == 0) { - return this->write_raw_buf_(header, 3); + return this->write_raw_buf_(header, noise::FRAME_HEADER_SIZE); } struct iovec iov[2]; iov[0].iov_base = header; - iov[0].iov_len = 3; + iov[0].iov_len = noise::FRAME_HEADER_SIZE; iov[1].iov_base = const_cast(data); iov[1].iov_len = len; - return this->write_raw_iov_(iov, 2, 3 + len); + return this->write_raw_iov_(iov, 2, noise::FRAME_HEADER_SIZE + len); } /** Initiate the data structures for the handshake. @@ -590,45 +537,12 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) { * @return 0 on success, -1 on error (check errno) */ APIError APINoiseFrameHelper::init_handshake_() { - int err; - // Noise_NNpsk0_25519_ChaChaPoly_SHA256, built on the stack: - // noise_handshakestate_new_by_id copies it, so a member would waste - // 104 bytes per connection, and a static const would sit in RAM on - // ESP8266 (.rodata is DRAM there). - const NoiseProtocolId nid = { - .prefix_id = NOISE_PREFIX_STANDARD, - .pattern_id = NOISE_PATTERN_NN, - .modifier_ids = {NOISE_MODIFIER_PSK0}, - .dh_id = NOISE_DH_CURVE25519, - .cipher_id = NOISE_CIPHER_CHACHAPOLY, - .hash_id = NOISE_HASH_SHA256, - .hybrid_id = NOISE_DH_NONE, - }; - - err = noise_handshakestate_new_by_id(&handshake_, &nid, NOISE_ROLE_RESPONDER); - APIError aerr = - handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED); + int err = this->handshake_.init(this->ctx_.get_psk(), prologue_.data(), prologue_.size()); + APIError aerr = handle_noise_error_(err, LOG_STR("noise_handshake_init"), APIError::HANDSHAKESTATE_SETUP_FAILED); if (aerr != APIError::OK) return aerr; - - const auto &psk = this->ctx_.get_psk(); - err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size()); - aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"), - APIError::HANDSHAKESTATE_SETUP_FAILED); - if (aerr != APIError::OK) - return aerr; - - err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size()); - aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED); - if (aerr != APIError::OK) - return aerr; - // set_prologue copies it into handshakestate, so we can get rid of it now + // init copies the prologue into the handshakestate, so we can get rid of it now prologue_.release(); - - err = noise_handshakestate_start(handshake_); - aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED); - if (aerr != APIError::OK) - return aerr; return APIError::OK; } @@ -637,15 +551,17 @@ APIError APINoiseFrameHelper::check_handshake_finished_() { assert(state_ == State::HANDSHAKE); #endif - int action = noise_handshakestate_get_action(handshake_); - if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE) + noise::NoiseResponderHandshake::Action action = this->handshake_.action(); + if (action == noise::NoiseResponderHandshake::Action::ACTION_READ || + action == noise::NoiseResponderHandshake::Action::ACTION_WRITE) return APIError::OK; - if (action != NOISE_ACTION_SPLIT) { + if (action != noise::NoiseResponderHandshake::Action::ACTION_SPLIT) { state_ = State::FAILED; - HELPER_LOG("Bad action for handshake: %d", action); + HELPER_LOG("Bad action for handshake: %d", (int) action); return APIError::HANDSHAKESTATE_BAD_STATE; } - int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_); + // split() also frees the handshake state + int err = this->handshake_.split(send_cipher_, recv_cipher_); APIError aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED); if (aerr != APIError::OK) @@ -654,17 +570,11 @@ APIError APINoiseFrameHelper::check_handshake_finished_() { this->frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_); HELPER_LOG("Handshake complete!"); - noise_handshakestate_free(handshake_); - handshake_ = nullptr; state_ = State::DATA; return APIError::OK; } APINoiseFrameHelper::~APINoiseFrameHelper() { - if (handshake_ != nullptr) { - noise_handshakestate_free(handshake_); - handshake_ = nullptr; - } if (send_cipher_ != nullptr) { noise_cipherstate_free(send_cipher_); send_cipher_ = nullptr; @@ -675,16 +585,6 @@ APINoiseFrameHelper::~APINoiseFrameHelper() { } } -extern "C" { -// declare how noise generates random bytes (here with a good HWRNG based on the RF system) -void noise_rand_bytes(void *output, size_t len) { - if (!esphome::random_bytes(reinterpret_cast(output), len)) { - ESP_LOGE(TAG, "Acquiring random bytes failed; rebooting"); - arch_restart(); - } -} -} - } // namespace esphome::api #endif // USE_API_NOISE #endif // USE_API diff --git a/esphome/components/api/api_frame_helper_noise.h b/esphome/components/api/api_frame_helper_noise.h index 46bd366672..05060c77de 100644 --- a/esphome/components/api/api_frame_helper_noise.h +++ b/esphome/components/api/api_frame_helper_noise.h @@ -3,7 +3,7 @@ #ifdef USE_API #ifdef USE_API_NOISE #include "noise/protocol.h" -#include "api_noise_context.h" +#include "esphome/components/noise/noise_handshake.h" namespace esphome::api { @@ -14,9 +14,9 @@ class APINoiseFrameHelper final : public APIFrameHelper { // Pos 1-2: encrypted payload size (16-bit big-endian) // Pos 3-6: encrypted type (16-bit) + data_len (16-bit) // Pos 7+: actual payload data - static constexpr uint8_t HEADER_PADDING = 1 + 2 + 2 + 2; // indicator + size + type + data_len + static constexpr uint8_t HEADER_PADDING = noise::FRAME_HEADER_SIZE + 2 + 2; // frame header + type + data_len - APINoiseFrameHelper(std::unique_ptr socket, APINoiseContext &ctx) + APINoiseFrameHelper(std::unique_ptr socket, noise::NoiseContext &ctx) : APIFrameHelper(std::move(socket)), ctx_(ctx) { frame_header_padding_ = HEADER_PADDING; } @@ -52,13 +52,13 @@ class APINoiseFrameHelper final : public APIFrameHelper { APIError handle_handshake_frame_error_(APIError aerr); APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err); - // Pointers first (4 bytes each) - NoiseHandshakeState *handshake_{nullptr}; + // Pointers first (4 bytes each; the handshake wrapper holds one pointer) + noise::NoiseResponderHandshake handshake_; NoiseCipherState *send_cipher_{nullptr}; NoiseCipherState *recv_cipher_{nullptr}; // Reference to noise context (4 bytes on 32-bit) - APINoiseContext &ctx_; + noise::NoiseContext &ctx_; // Buffer for noise handshake prologue (released after handshake) APIBuffer prologue_; @@ -67,7 +67,7 @@ class APINoiseFrameHelper final : public APIFrameHelper { // Fixed-size header buffer for noise protocol: // 1 byte for indicator + 2 bytes for message size (16-bit value, not varint) // Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase - uint8_t rx_header_buf_[3]; + uint8_t rx_header_buf_[noise::FRAME_HEADER_SIZE]; uint8_t rx_header_buf_len_ = 0; // 4 bytes total, no padding }; diff --git a/esphome/components/api/api_noise_context.h b/esphome/components/api/api_noise_context.h deleted file mode 100644 index 44484ffa2c..0000000000 --- a/esphome/components/api/api_noise_context.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once -#include -#include -#include "esphome/core/defines.h" - -namespace esphome::api { - -#ifdef USE_API_NOISE -using psk_t = std::array; - -class APINoiseContext { - public: - // The all-zeros PSK is reserved: it marks the device as unprovisioned and - // doubles as the well-known provisioning PSK that unprovisioned devices - // accept for Noise handshakes (passive-sniffing protection only, no - // authentication). It is never a valid real key. - static bool is_all_zeros(const psk_t &psk) { - uint8_t acc = 0; - for (uint8_t b : psk) { - acc |= b; - } - return acc == 0; - } - void set_psk(psk_t psk) { - this->psk_ = psk; - this->has_psk_ = !is_all_zeros(psk); - } - const psk_t &get_psk() const { return this->psk_; } - bool has_psk() const { return this->has_psk_; } - - protected: - psk_t psk_{}; - bool has_psk_{false}; -}; -#endif // USE_API_NOISE - -} // namespace esphome::api diff --git a/esphome/components/api/api_options.proto b/esphome/components/api/api_options.proto index ac9c4e59cc..66295b3d53 100644 --- a/esphome/components/api/api_options.proto +++ b/esphome/components/api/api_options.proto @@ -116,4 +116,10 @@ extend google.protobuf.FieldOptions { // the per-byte loop when the upper bits are non-zero (the common case // for real MAC addresses, since OUIs occupy the top 24 bits). optional bool mac_address = 50019 [default=false]; + + // track_presence: Track whether this message-typed field was present on the wire. + // Generates a `bool has_{false};` member on the decoding side that is set + // to true when the field arrives, so an all-default submessage can be told apart + // from an absent one (e.g. a UTC ParsedTimezone, which is all zeros). + optional bool track_presence = 50020 [default=false]; } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1b8c6b05bd..33611c5ee1 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -1249,12 +1249,9 @@ bool ParsedTimezone::decode_length(uint32_t field_id, ProtoLengthDelimited value } bool GetTimeResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: { - this->timezone = StringRef(reinterpret_cast(value.data()), value.size()); - break; - } case 3: value.decode_to_message(this->parsed_timezone); + this->has_parsed_timezone = true; break; default: return false; diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 8335dae1f2..13db857467 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1283,13 +1283,13 @@ class ParsedTimezone final : public ProtoDecodableMessage { class GetTimeResponse final : public ProtoDecodableMessage { public: static constexpr uint8_t MESSAGE_TYPE = 37; - static constexpr uint8_t ESTIMATED_SIZE = 31; + static constexpr uint8_t ESTIMATED_SIZE = 22; #ifdef HAS_PROTO_MESSAGE_DUMP const LogString *message_name() const override { return LOG_STR("get_time_response"); } #endif uint32_t epoch_seconds{0}; - StringRef timezone{}; ParsedTimezone parsed_timezone{}; + bool has_parsed_timezone{false}; #ifdef HAS_PROTO_MESSAGE_DUMP const char *dump_to(DumpBuffer &out) const override; #endif diff --git a/esphome/components/api/api_pb2_dump.cpp b/esphome/components/api/api_pb2_dump.cpp index 4d5829e45d..d54215ba2e 100644 --- a/esphome/components/api/api_pb2_dump.cpp +++ b/esphome/components/api/api_pb2_dump.cpp @@ -1468,7 +1468,7 @@ const char *ParsedTimezone::dump_to(DumpBuffer &out) const { const char *GetTimeResponse::dump_to(DumpBuffer &out) const { MessageDumpHelper helper(out, ESPHOME_PSTR("GetTimeResponse")); dump_field(out, ESPHOME_PSTR("epoch_seconds"), this->epoch_seconds); - dump_field(out, ESPHOME_PSTR("timezone"), this->timezone); + dump_field(out, ESPHOME_PSTR("has_parsed_timezone"), this->has_parsed_timezone); out.append(2, ' ').append_p(ESPHOME_PSTR("parsed_timezone")).append(": "); this->parsed_timezone.dump_to(out); out.append("\n"); diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 2d5f9e4155..751f2e4c3b 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -588,7 +588,7 @@ bool APIServer::load_and_apply_noise_psk_() { return true; } -bool APIServer::save_noise_psk(psk_t psk, bool make_active) { +bool APIServer::save_noise_psk(noise::psk_t psk, bool make_active) { #ifdef USE_API_NOISE_PSK_FROM_YAML // When PSK is set from YAML, this function should never be called // but if it is, reject the change diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index a58e42534b..072a583901 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -5,7 +5,10 @@ #include "api_buffer.h" // Must precede clients_ so APIConnection is complete for default_delete (libc++). #include "api_connection.h" -#include "api_noise_context.h" +#ifdef USE_API_NOISE +// Only present in the build when the noise component is loaded +#include "esphome/components/noise/noise.h" +#endif #include "api_pb2.h" #include "api_pb2_service.h" #include "esphome/components/socket/socket.h" @@ -37,7 +40,7 @@ class UserServiceDescriptor; #ifdef USE_API_NOISE struct SavedNoisePsk { - psk_t psk; + noise::psk_t psk; } PACKED; // NOLINT #endif @@ -73,10 +76,10 @@ class APIServer final : public Component, APIBuffer &get_shared_buffer_ref() { return shared_write_buffer_; } #ifdef USE_API_NOISE - bool save_noise_psk(psk_t psk, bool make_active = true); + bool save_noise_psk(noise::psk_t psk, bool make_active = true); bool clear_noise_psk(bool make_active = true); - void set_noise_psk(psk_t psk) { this->noise_ctx_.set_psk(psk); } - APINoiseContext &get_noise_ctx() { return this->noise_ctx_; } + void set_noise_psk(noise::psk_t psk) { this->noise_ctx_.set_psk(psk); } + noise::NoiseContext &get_noise_ctx() { return this->noise_ctx_; } #endif // USE_API_NOISE void handle_disconnect(APIConnection *conn); @@ -354,7 +357,7 @@ class APIServer final : public Component, #endif #ifdef USE_API_NOISE - APINoiseContext noise_ctx_; + noise::NoiseContext noise_ctx_; ESPPreferenceObject noise_pref_; #endif // USE_API_NOISE }; diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index f9e645b506..57ff616ca7 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -95,9 +95,17 @@ bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done( ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} #ifdef USE_API_USER_DEFINED_ACTIONS +// Yield after every Nth service; bounds direct (non-batched) writes per loop pass +static constexpr uint8_t SERVICE_YIELD_INTERVAL = 3; + bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { auto resp = service->encode_list_service_response(); - return this->client_->send_message(resp); + if (!this->client_->send_message(resp)) + return false; + // at_ is this service's index + if ((this->at_ + 1) % SERVICE_YIELD_INTERVAL == 0) + this->yield_after_step_(); + return true; } #endif diff --git a/esphome/components/aqi/sensor.py b/esphome/components/aqi/sensor.py index 9c361560df..a98d977227 100644 --- a/esphome/components/aqi/sensor.py +++ b/esphome/components/aqi/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( DEVICE_CLASS_AQI, STATE_CLASS_MEASUREMENT, ) +from esphome.types import ConfigType from . import AQI_CALCULATION_TYPE, CONF_CALCULATION_TYPE, CONF_EXTENDED_RANGE, aqi_ns @@ -16,7 +17,7 @@ DEPENDENCIES = ["sensor"] AQISensor = aqi_ns.class_("AQISensor", sensor.Sensor, cg.Component) -def _validate_extended_range(config): +def _validate_extended_range(config: ConfigType) -> ConfigType: if CONF_EXTENDED_RANGE in config and config[CONF_CALCULATION_TYPE] == "CAQI": raise cv.Invalid( f"'{CONF_EXTENDED_RANGE}' is not supported with 'calculation_type: CAQI'. " @@ -48,7 +49,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/as3935_i2c/__init__.py b/esphome/components/as3935_i2c/__init__.py index 09b588cb0c..83924de760 100644 --- a/esphome/components/as3935_i2c/__init__.py +++ b/esphome/components/as3935_i2c/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import as3935, i2c import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType AUTO_LOAD = ["as3935"] DEPENDENCIES = ["i2c"] @@ -20,7 +21,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await as3935.setup_as3935(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/as3935_spi/__init__.py b/esphome/components/as3935_spi/__init__.py index f4cf07a906..332a51c7a9 100644 --- a/esphome/components/as3935_spi/__init__.py +++ b/esphome/components/as3935_spi/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import as3935, spi import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType AUTO_LOAD = ["as3935"] DEPENDENCIES = ["spi"] @@ -20,7 +21,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await as3935.setup_as3935(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/as7341/sensor.py b/esphome/components/as7341/sensor.py index 8b6cf61028..f70c5e999f 100644 --- a/esphome/components/as7341/sensor.py +++ b/esphome/components/as7341/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( ICON_BRIGHTNESS_5, STATE_CLASS_MEASUREMENT, ) +from esphome.types import ConfigType CODEOWNERS = ["@mrgnr"] DEPENDENCIES = ["i2c"] @@ -96,7 +97,7 @@ SENSORS = { } -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index 22d544ba37..31007022d5 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] @@ -25,7 +26,7 @@ CONFIG_SCHEMA = cv.Schema({}) @coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT) -async def to_code(config): +async def to_code(config: ConfigType) -> None: if CORE.is_esp32: # https://github.com/ESP32Async/AsyncTCP from esphome.components.esp32 import add_idf_component diff --git a/esphome/components/atc_mithermometer/sensor.py b/esphome/components/atc_mithermometer/sensor.py index 5c2d75753c..184b2e8733 100644 --- a/esphome/components/atc_mithermometer/sensor.py +++ b/esphome/components/atc_mithermometer/sensor.py @@ -21,6 +21,7 @@ from esphome.const import ( UNIT_PERCENT, UNIT_VOLT, ) +from esphome.types import ConfigType CODEOWNERS = ["@ahpohl"] @@ -77,7 +78,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await ble_device_base.register_ble_device(var, config) diff --git a/esphome/components/atm90e26/sensor.py b/esphome/components/atm90e26/sensor.py index 5941cb35b4..87db214233 100644 --- a/esphome/components/atm90e26/sensor.py +++ b/esphome/components/atm90e26/sensor.py @@ -30,6 +30,7 @@ from esphome.const import ( UNIT_WATT, UNIT_WATT_HOURS, ) +from esphome.types import ConfigType CONF_METER_CONSTANT = "meter_constant" CONF_PL_CONST = "pl_const" @@ -123,7 +124,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/axs15231/touchscreen/__init__.py b/esphome/components/axs15231/touchscreen/__init__.py index 8c18d8ca75..2616cb281b 100644 --- a/esphome/components/axs15231/touchscreen/__init__.py +++ b/esphome/components/axs15231/touchscreen/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN +from esphome.types import ConfigType from .. import axs15231_ns @@ -25,7 +26,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/b_parasite/sensor.py b/esphome/components/b_parasite/sensor.py index 673c5981b7..cb8f569c0d 100644 --- a/esphome/components/b_parasite/sensor.py +++ b/esphome/components/b_parasite/sensor.py @@ -20,6 +20,7 @@ from esphome.const import ( UNIT_PERCENT, UNIT_VOLT, ) +from esphome.types import ConfigType CODEOWNERS = ["@rbaron"] @@ -74,7 +75,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await ble_device_base.register_ble_device(var, config) diff --git a/esphome/components/ballu/climate.py b/esphome/components/ballu/climate.py index 1127084632..c4d64cd692 100644 --- a/esphome/components/ballu/climate.py +++ b/esphome/components/ballu/climate.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir"] CODEOWNERS = ["@bazuchan"] @@ -10,5 +11,5 @@ BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR) CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(BalluClimate) -async def to_code(config): +async def to_code(config: ConfigType) -> None: await climate_ir.new_climate_ir(config) diff --git a/esphome/components/bang_bang/climate.py b/esphome/components/bang_bang/climate.py index bfdb12278f..65c5eaed18 100644 --- a/esphome/components/bang_bang/climate.py +++ b/esphome/components/bang_bang/climate.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_IDLE_ACTION, CONF_SENSOR, ) +from esphome.types import ConfigType bang_bang_ns = cg.esphome_ns.namespace("bang_bang") BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Component) @@ -41,7 +42,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await climate.new_climate(config) await cg.register_component(var, config) diff --git a/esphome/components/beken_spi_led_strip/light.py b/esphome/components/beken_spi_led_strip/light.py index 2be5842818..5576f286f1 100644 --- a/esphome/components/beken_spi_led_strip/light.py +++ b/esphome/components/beken_spi_led_strip/light.py @@ -56,7 +56,7 @@ SUPPORTED_PINS = { } -def _validate_pin(value): +def _validate_pin(value: int) -> int: family = libretiny.get_libretiny_family() if family not in SUPPORTED_PINS: raise cv.Invalid(f"Chip family {family} is not supported.") diff --git a/esphome/components/bh1750/sensor.py b/esphome/components/bh1750/sensor.py index 36af5aeef9..07272b3b4f 100644 --- a/esphome/components/bh1750/sensor.py +++ b/esphome/components/bh1750/sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c, sensor import esphome.config_validation as cv from esphome.const import DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, UNIT_LUX +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] CODEOWNERS = ["@OttoWinter"] @@ -25,7 +26,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bh1900nux/sensor.py b/esphome/components/bh1900nux/sensor.py index a70db3555a..4ddffb7940 100644 --- a/esphome/components/bh1900nux/sensor.py +++ b/esphome/components/bh1900nux/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] CODEOWNERS = ["@B48D81EFCC"] @@ -28,7 +29,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/binary/fan/__init__.py b/esphome/components/binary/fan/__init__.py index dadcf52372..03a03ec7ca 100644 --- a/esphome/components/binary/fan/__init__.py +++ b/esphome/components/binary/fan/__init__.py @@ -2,6 +2,7 @@ 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 +from esphome.types import ConfigType from .. import binary_ns @@ -20,7 +21,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await fan.new_fan(config) await cg.register_component(var, config) diff --git a/esphome/components/binary/light/__init__.py b/esphome/components/binary/light/__init__.py index ebb22f4409..b6eddac341 100644 --- a/esphome/components/binary/light/__init__.py +++ b/esphome/components/binary/light/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import light, output import esphome.config_validation as cv from esphome.const import CONF_OUTPUT, CONF_OUTPUT_ID +from esphome.types import ConfigType from .. import binary_ns @@ -15,7 +16,7 @@ CONFIG_SCHEMA = light.BINARY_LIGHT_SCHEMA.extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) await light.register_light(var, config) diff --git a/esphome/components/binary_sensor_map/sensor.py b/esphome/components/binary_sensor_map/sensor.py index 965e332e28..f3133c0621 100644 --- a/esphome/components/binary_sensor_map/sensor.py +++ b/esphome/components/binary_sensor_map/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_VALUE, ICON_CHECK_CIRCLE_OUTLINE, ) +from esphome.types import ConfigType DEPENDENCIES = ["binary_sensor"] @@ -82,7 +83,7 @@ CONFIG_SCHEMA = cv.typed_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/bk72xx/__init__.py b/esphome/components/bk72xx/__init__.py index ee9bf1e0d4..e64237c95a 100644 --- a/esphome/components/bk72xx/__init__.py +++ b/esphome/components/bk72xx/__init__.py @@ -28,6 +28,8 @@ from esphome.components.libretiny.const import ( LibreTinyComponent, ) from esphome.core import CORE +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType from .boards import BK72XX_BOARD_PINS, BK72XX_BOARDS @@ -45,7 +47,7 @@ COMPONENT_DATA = LibreTinyComponent( ) -def _set_core_data(config): +def _set_core_data(config: ConfigType) -> ConfigType: CORE.data[KEY_LIBRETINY] = {} CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] = COMPONENT_DATA return config @@ -62,12 +64,12 @@ PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA CONFIG_SCHEMA.prepend_extra(_set_core_data) -async def to_code(config): +async def to_code(config: ConfigType) -> MockObj: return await libretiny.component_to_code(config) @pins.PIN_SCHEMA_REGISTRY.register("bk72xx", PIN_SCHEMA) -async def pin_to_code(config): +async def pin_to_code(config: ConfigType) -> MockObj: return await libretiny.gpio.component_pin_to_code(config) diff --git a/esphome/components/bl0939/sensor.py b/esphome/components/bl0939/sensor.py index bd4bdd93e5..ec17ef2c7e 100644 --- a/esphome/components/bl0939/sensor.py +++ b/esphome/components/bl0939/sensor.py @@ -15,6 +15,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, ) +from esphome.types import ConfigType DEPENDENCIES = ["uart"] @@ -88,7 +89,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/bl0942/sensor.py b/esphome/components/bl0942/sensor.py index f9fe7f5a5e..5531fe411b 100644 --- a/esphome/components/bl0942/sensor.py +++ b/esphome/components/bl0942/sensor.py @@ -24,6 +24,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, ) +from esphome.types import ConfigType CONF_CURRENT_REFERENCE = "current_reference" CONF_ENERGY_REFERENCE = "energy_reference" @@ -95,7 +96,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/ble_device_base/__init__.py b/esphome/components/ble_device_base/__init__.py index 15a8b08139..43ec736727 100644 --- a/esphome/components/ble_device_base/__init__.py +++ b/esphome/components/ble_device_base/__init__.py @@ -206,32 +206,36 @@ def validate_scan_parameters(config: ConfigType) -> ConfigType: interval = config[CONF_INTERVAL] window = config[CONF_WINDOW] - if window > interval: - raise cv.Invalid( - f"Scan window ({window}) needs to be smaller than scan interval ({interval})" - ) + # Labels are reused in every error below; the optional one names its key. + windows = [("Scan window", window)] + if (connection_window := config.get(CONF_CONNECTION_SCAN_WINDOW)) is not None: + windows.append((CONF_CONNECTION_SCAN_WINDOW, connection_window)) + + for name, value in windows: + if value > interval: + raise cv.Invalid( + f"{name} ({value}) needs to be smaller than scan interval ({interval})" + ) # BLE scan interval/window are programmed in 0.625 ms units as a 16-bit value; the # controller only accepts 2.5 ms .. 10240 ms (0x0004 .. 0x4000). Reject out-of-range # values here instead of letting the unit conversion silently overflow. - for name, value in (("interval", interval), ("window", window)): + for name, value in (("Scan interval", interval), *windows): if value.total_microseconds < 2500 or value.total_microseconds > 10_240_000: - raise cv.Invalid( - f"Scan {name} ({value}) must be between 2.5 ms and 10240 ms" - ) + raise cv.Invalid(f"{name} ({value}) must be between 2.5 ms and 10240 ms") # Validate what actually reaches the controller: both values are truncated to # whole 0.625 ms units, so a window/interval pair that differs by less than one # unit collapses to the same value — silently programming a 100 % duty cycle # (radio permanently on) from a config that asked for less. interval_units = to_ble_units(interval) - window_units = to_ble_units(window) - if window_units == interval_units and window < interval: - raise cv.Invalid( - f"Scan window ({window}) and interval ({interval}) both truncate to " - f"{interval_units} x 0.625 ms, which the controller scans at a 100 % duty " - f"cycle. Separate them by at least 0.625 ms." - ) + for name, value in windows: + if to_ble_units(value) == interval_units and value < interval: + raise cv.Invalid( + f"{name} ({value}) and interval ({interval}) both truncate to " + f"{interval_units} x 0.625 ms, which the controller scans at a 100 % duty " + f"cycle. Separate them by at least 0.625 ms." + ) if interval.total_microseconds * 3 > duration.total_microseconds: raise cv.Invalid( @@ -247,11 +251,14 @@ def validate_scan_parameters(config: ConfigType) -> ConfigType: # their own; also the fallback for esp32's conditional default. DEFAULT_SCAN_WINDOW = "30ms" +CONF_CONNECTION_SCAN_WINDOW = "connection_scan_window" + def scan_parameters_schema( interval_default: str, *, window_default: str | Callable[[], TimePeriod] = DEFAULT_SCAN_WINDOW, + connection_window: bool = False, ) -> cv.All: """Build the scan_parameters value schema shared by all BLE trackers. @@ -263,7 +270,9 @@ def scan_parameters_schema( can adjust it once sibling keys are resolved). The `active` option (default on) is unconditional: active scanning is part of the tracker contract — every current proxy client assumes it, so a passive-only - tracker must not share this schema. + tracker must not share this schema. connection_window opts in to the + `connection_scan_window` option for trackers that can fall back to a + smaller window while a GATT connection is active. """ schema = { cv.Optional(CONF_DURATION, default="5min"): cv.positive_time_period_seconds, @@ -272,6 +281,8 @@ def scan_parameters_schema( cv.Optional(CONF_CONTINUOUS, default=True): cv.boolean, cv.Optional(CONF_ACTIVE, default=True): cv.boolean, } + if connection_window: + schema[cv.Optional(CONF_CONNECTION_SCAN_WINDOW)] = cv.positive_time_period return cv.All(cv.Schema(schema), validate_scan_parameters) diff --git a/esphome/components/ble_device_base/automation.py b/esphome/components/ble_device_base/automation.py index eb63061a8d..6acc4edb92 100644 --- a/esphome/components/ble_device_base/automation.py +++ b/esphome/components/ble_device_base/automation.py @@ -1,5 +1,6 @@ """Shared codegen for the neutral BLE advertisement triggers (automation.h).""" +from collections.abc import Callable from typing import Any from esphome import automation @@ -52,7 +53,7 @@ _UUID_WIDTHS = { def uuid_trigger_schema( trigger_class: MockObjClass, extra: dict[Any, Any] | None = None -): +) -> Callable[[Any], Any]: """Schema for a UUID-filtered trigger — pairs with uuid_trigger_to_code(). `extra` carries the required UUID key (a cv marker, so a dict rather than @@ -68,7 +69,7 @@ def uuid_trigger_schema( ) -def advertise_trigger_schema(trigger_class: MockObjClass): +def advertise_trigger_schema(trigger_class: MockObjClass) -> Callable[[Any], Any]: """on_ble_advertise schema: multi-mac list filter, unlike the single-mac uuid_trigger_schema() — pairs with advertise_trigger_to_code().""" return automation.validate_automation( @@ -79,7 +80,7 @@ def advertise_trigger_schema(trigger_class: MockObjClass): ) -def scan_end_trigger_schema(trigger_class: MockObjClass): +def scan_end_trigger_schema(trigger_class: MockObjClass) -> Callable[[Any], Any]: """on_scan_end schema: id only — pairs with scan_end_trigger_to_code().""" return automation.validate_automation( {cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(trigger_class)} diff --git a/esphome/components/ble_presence/binary_sensor.py b/esphome/components/ble_presence/binary_sensor.py index a7713d9a4b..a43d3561f8 100644 --- a/esphome/components/ble_presence/binary_sensor.py +++ b/esphome/components/ble_presence/binary_sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_SERVICE_UUID, CONF_TIMEOUT, ) +from esphome.types import ConfigType CONF_IRK = "irk" @@ -24,7 +25,7 @@ BLEPresenceDevice = ble_presence_ns.class_( ) -def _validate(config): +def _validate(config: ConfigType) -> ConfigType: if CONF_IBEACON_MAJOR in config and CONF_IBEACON_UUID not in config: raise cv.Invalid("iBeacon major identifier requires iBeacon UUID") if CONF_IBEACON_MINOR in config and CONF_IBEACON_UUID not in config: @@ -58,7 +59,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await ble_device_base.register_ble_device(var, config) diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index 43e5813ea2..6813505d58 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_DECIBEL_MILLIWATT, ) +from esphome.types import ConfigType CONF_IRK = "irk" @@ -22,7 +23,7 @@ BLERSSISensor = ble_rssi_ns.class_( ) -def _validate(config): +def _validate(config: ConfigType) -> ConfigType: if CONF_IBEACON_MAJOR in config and CONF_IBEACON_UUID not in config: raise cv.Invalid("iBeacon major identifier requires iBeacon UUID") if CONF_IBEACON_MINOR in config and CONF_IBEACON_UUID not in config: @@ -58,7 +59,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await ble_device_base.register_ble_device(var, config) diff --git a/esphome/components/ble_scanner/text_sensor.py b/esphome/components/ble_scanner/text_sensor.py index 0c08e1f734..0c60b53783 100644 --- a/esphome/components/ble_scanner/text_sensor.py +++ b/esphome/components/ble_scanner/text_sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import ble_device_base, text_sensor import esphome.config_validation as cv +from esphome.types import ConfigType AUTO_LOAD = ["ble_device_base"] @@ -20,7 +21,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) await ble_device_base.register_ble_device(var, config) diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp b/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp index c8f97f207e..ddab4812cc 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp +++ b/esphome/components/bluetooth_connection/bluetooth_connection_hub.cpp @@ -275,10 +275,15 @@ void BluetoothConnection::send_ack_(PendingAck kind, uint16_t handle, conn_err_t if (this->try_send_ack_(kind, handle, error)) return; // Report a newly owed reply and a displaced one; displacing is the case - // that loses a reply. Re-refusing the same one stays quiet. + // that loses a reply. Re-refusing the same one stays quiet, and so does a + // fresh deferral for the handle already warned about: a congested bulk + // transfer re-asks the same handle every cycle and each ack would warn. if (!this->has_pending_ack_()) { - ESP_LOGW(TAG, "[%d] [%s] GATT reply for handle 0x%04X deferred, TCP buffer full", this->connection_index_, - this->address_str_, handle); + if (!this->ack_deferred_warned_ || this->pending_ack_handle_ != handle) { + ESP_LOGW(TAG, "[%d] [%s] GATT reply for handle 0x%04X deferred, TCP buffer full", this->connection_index_, + this->address_str_, handle); + this->ack_deferred_warned_ = true; + } } else if (this->pending_ack_handle_ != handle || this->pending_ack_ != kind) { ESP_LOGW(TAG, "[%d] [%s] GATT reply for handle 0x%04X dropped for handle 0x%04X", this->connection_index_, this->address_str_, this->pending_ack_handle_, handle); @@ -365,8 +370,16 @@ void BluetoothConnection::on_notify_data(uint16_t handle, const uint8_t *data, u resp.set_data(data, len); if (!api_connection->send_message(resp)) { // Not latched, same reason as the read reply. Notify data is lossy: the - // peripheral will not resend it. - ESP_LOGW(TAG, "[%d] [%s] Failed to send notify data response", this->connection_index_, this->address_str_); + // peripheral will not resend it. Warn on the first drop only; a congested + // link drops a whole stream and one line per notify floods the log. + if (!this->notify_drop_warned_) { + ESP_LOGW(TAG, "[%d] [%s] Failed to send notify data response, handle 0x%04X", this->connection_index_, + this->address_str_, handle); + this->notify_drop_warned_ = true; + } else { + ESP_LOGV(TAG, "[%d] [%s] Failed to send notify data response, handle 0x%04X", this->connection_index_, + this->address_str_, handle); + } } } diff --git a/esphome/components/bluetooth_connection/bluetooth_connection_hub.h b/esphome/components/bluetooth_connection/bluetooth_connection_hub.h index f87d545f7d..47181e81a7 100644 --- a/esphome/components/bluetooth_connection/bluetooth_connection_hub.h +++ b/esphome/components/bluetooth_connection/bluetooth_connection_hub.h @@ -164,6 +164,8 @@ class BluetoothConnection final : public ble_device_base::GattClientListener { this->pending_ack_ = PendingAck::PENDING_ACK_NONE; this->batch_stalled_ = false; this->connected_reply_owed_ = false; + this->ack_deferred_warned_ = false; + this->notify_drop_warned_ = false; } /// Sole construction site for these replies, shared by send and retry. bool try_send_ack_(PendingAck kind, uint16_t handle, conn_err_t error); @@ -238,7 +240,7 @@ class BluetoothConnection final : public ble_device_base::GattClientListener { // Group 5: bit-packed tail. The first two bytes were already full, so the // first added bit forced a third and took the 8-aligned object 48 -> 56; - // the handle, error and retry counter ride in that padding. Four bitfield + // the handle, error and retry counter ride in that padding. Two bitfield // bits left; another byte-sized member costs 8 per slot. static_assert(static_cast(ClientState::ESTABLISHED) < (1 << 3), "state_ bitfield too narrow"); static_assert(static_cast(ConnectionType::V3_WITHOUT_CACHE) < (1 << 2), @@ -258,6 +260,11 @@ class BluetoothConnection final : public ble_device_base::GattClientListener { bool batch_stalled_ : 1 {false}; /// An owed connected=true reply; the proxy's paced drain re-offers it. bool connected_reply_owed_ : 1 {false}; + /// Set once the deferred warn fired; with an unchanged pending_ack_handle_ + /// it keeps re-deferrals of the same handle quiet (see send_ack_). + bool ack_deferred_warned_ : 1 {false}; + /// Set on the first dropped notify; later drops log at verbose only. + bool notify_drop_warned_ : 1 {false}; // Plain byte after the bitfields: takes the padding byte instead of // straddling pending_ack_'s storage unit and growing the object. static_assert(PENDING_ACK_RETRY_LIMIT <= 0xFF, "retry counter too narrow"); diff --git a/esphome/components/bluetooth_proxy/__init__.py b/esphome/components/bluetooth_proxy/__init__.py index 95f71fc8ea..1b761849a5 100644 --- a/esphome/components/bluetooth_proxy/__init__.py +++ b/esphome/components/bluetooth_proxy/__init__.py @@ -92,7 +92,7 @@ def _esp32_config_schema() -> cv.All: CONNECTION_SCHEMA = bluetooth_connection.hub_connection_schema(PLATFORM_ESP32) - def validate_connections(config): + def validate_connections(config: ConfigType) -> ConfigType: if CONF_CONNECTIONS in config: if not config[CONF_ACTIVE]: raise cv.Invalid( diff --git a/esphome/components/bme280_base/__init__.py b/esphome/components/bme280_base/__init__.py index c37191bc07..287946801e 100644 --- a/esphome/components/bme280_base/__init__.py +++ b/esphome/components/bme280_base/__init__.py @@ -16,6 +16,8 @@ from esphome.const import ( UNIT_HECTOPASCAL, UNIT_PERCENT, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] @@ -84,7 +86,7 @@ CONFIG_SCHEMA_BASE = cv.Schema( ).extend(cv.polling_component_schema("60s")) -async def to_code_base(config): +async def to_code_base(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/bme280_i2c/sensor.py b/esphome/components/bme280_i2c/sensor.py index 1c37033613..536e8ec794 100644 --- a/esphome/components/bme280_i2c/sensor.py +++ b/esphome/components/bme280_i2c/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv +from esphome.types import ConfigType from ..bme280_base import CONFIG_SCHEMA_BASE, to_code_base @@ -17,6 +18,6 @@ CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( ).extend({cv.GenerateID(): cv.declare_id(BME280I2CComponent)}) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bme280_spi/sensor.py b/esphome/components/bme280_spi/sensor.py index 7f4fb5cf44..1d53fe25fa 100644 --- a/esphome/components/bme280_spi/sensor.py +++ b/esphome/components/bme280_spi/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import spi import esphome.config_validation as cv +from esphome.types import ConfigType from ..bme280_base import CONFIG_SCHEMA_BASE, to_code_base @@ -19,6 +20,6 @@ CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema()).extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await spi.register_spi_device(var, config) diff --git a/esphome/components/bme680/sensor.py b/esphome/components/bme680/sensor.py index f41aefcec3..dce5c88cfa 100644 --- a/esphome/components/bme680/sensor.py +++ b/esphome/components/bme680/sensor.py @@ -22,6 +22,7 @@ from esphome.const import ( UNIT_OHM, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -125,7 +126,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bme68x_bsec2_i2c/__init__.py b/esphome/components/bme68x_bsec2_i2c/__init__.py index dacd4e32ad..1da3bdbd6c 100644 --- a/esphome/components/bme68x_bsec2_i2c/__init__.py +++ b/esphome/components/bme68x_bsec2_i2c/__init__.py @@ -6,6 +6,7 @@ from esphome.components.bme68x_bsec2 import ( to_code_base, ) import esphome.config_validation as cv +from esphome.types import ConfigType CODEOWNERS = ["@neffs", "@kbx81"] @@ -29,6 +30,6 @@ CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( ).extend(i2c.i2c_device_schema(0x76)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bmi160/sensor.py b/esphome/components/bmi160/sensor.py index cc4037c1ee..4309f0a79f 100644 --- a/esphome/components/bmi160/sensor.py +++ b/esphome/components/bmi160/sensor.py @@ -22,6 +22,7 @@ from esphome.const import ( UNIT_DEGREE_PER_SECOND, UNIT_METER_PER_SECOND_SQUARED, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -82,7 +83,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bmi270/motion.py b/esphome/components/bmi270/motion.py index c1616665f9..ad36e592d1 100644 --- a/esphome/components/bmi270/motion.py +++ b/esphome/components/bmi270/motion.py @@ -8,6 +8,7 @@ from esphome.components.const import ( ) from esphome.components.motion import motion_schema, new_motion_component import esphome.config_validation as cv +from esphome.types import ConfigType from . import BMI270Component, bmi270_ns @@ -79,7 +80,7 @@ CONFIG_SCHEMA = ( # Code generation -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await new_motion_component(config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bmi270/sensor.py b/esphome/components/bmi270/sensor.py index 69235ed8dc..0e1b0604a3 100644 --- a/esphome/components/bmi270/sensor.py +++ b/esphome/components/bmi270/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, ) from esphome.cpp_generator import MockObj +from esphome.types import ConfigType from . import CONF_BMI270_ID, BMI270Component @@ -30,7 +31,7 @@ CONFIG_SCHEMA = sensor.sensor_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) parent = await cg.get_variable(config[CONF_BMI270_ID]) data = MockObj("data") diff --git a/esphome/components/bmp085/sensor.py b/esphome/components/bmp085/sensor.py index 6e51984e1f..e4e559844e 100644 --- a/esphome/components/bmp085/sensor.py +++ b/esphome/components/bmp085/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_HECTOPASCAL, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bmp280_base/__init__.py b/esphome/components/bmp280_base/__init__.py index d612920dd4..c0f0ae90bf 100644 --- a/esphome/components/bmp280_base/__init__.py +++ b/esphome/components/bmp280_base/__init__.py @@ -13,6 +13,8 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_HECTOPASCAL, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@ademuri"] @@ -69,7 +71,7 @@ CONFIG_SCHEMA_BASE = cv.Schema( ).extend(cv.polling_component_schema("60s")) -async def to_code_base(config): +async def to_code_base(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/bmp280_i2c/sensor.py b/esphome/components/bmp280_i2c/sensor.py index 3ff556d51a..8e3c14f50a 100644 --- a/esphome/components/bmp280_i2c/sensor.py +++ b/esphome/components/bmp280_i2c/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv +from esphome.types import ConfigType from ..bmp280_base import CONFIG_SCHEMA_BASE, to_code_base @@ -18,6 +19,6 @@ CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( ).extend({cv.GenerateID(): cv.declare_id(BMP280I2CComponent)}) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bmp280_spi/sensor.py b/esphome/components/bmp280_spi/sensor.py index b3678ec01d..d97a6ea579 100644 --- a/esphome/components/bmp280_spi/sensor.py +++ b/esphome/components/bmp280_spi/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import spi import esphome.config_validation as cv +from esphome.types import ConfigType from ..bmp280_base import CONFIG_SCHEMA_BASE, to_code_base @@ -18,6 +19,6 @@ CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( ).extend({cv.GenerateID(): cv.declare_id(BMP280SPIComponent)}) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await spi.register_spi_device(var, config) diff --git a/esphome/components/bmp3xx_base/__init__.py b/esphome/components/bmp3xx_base/__init__.py index c31db31761..75e168378e 100644 --- a/esphome/components/bmp3xx_base/__init__.py +++ b/esphome/components/bmp3xx_base/__init__.py @@ -13,6 +13,8 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_HECTOPASCAL, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@martgras", "@latonita"] @@ -73,7 +75,7 @@ CONFIG_SCHEMA_BASE = cv.Schema( ).extend(cv.polling_component_schema("60s")) -async def to_code_base(config): +async def to_code_base(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/bmp3xx_i2c/sensor.py b/esphome/components/bmp3xx_i2c/sensor.py index 6fed9fc9ee..46e50ea39f 100644 --- a/esphome/components/bmp3xx_i2c/sensor.py +++ b/esphome/components/bmp3xx_i2c/sensor.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import i2c +from esphome.types import ConfigType from ..bmp3xx_base import CONFIG_SCHEMA_BASE, cv, to_code_base @@ -18,6 +19,6 @@ CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( ).extend({cv.GenerateID(): cv.declare_id(BMP3XXI2CComponent)}) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bmp3xx_spi/sensor.py b/esphome/components/bmp3xx_spi/sensor.py index 22aab71977..fb3580bbad 100644 --- a/esphome/components/bmp3xx_spi/sensor.py +++ b/esphome/components/bmp3xx_spi/sensor.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import spi +from esphome.types import ConfigType from ..bmp3xx_base import CONFIG_SCHEMA_BASE, cv, to_code_base @@ -18,6 +19,6 @@ CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema()).extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await spi.register_spi_device(var, config) diff --git a/esphome/components/bmp581_base/__init__.py b/esphome/components/bmp581_base/__init__.py index 6a7cf45089..1c2c5c37d4 100644 --- a/esphome/components/bmp581_base/__init__.py +++ b/esphome/components/bmp581_base/__init__.py @@ -15,6 +15,8 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PASCAL, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@kahrendt", "@danielkent-net"] @@ -47,7 +49,7 @@ IIR_FILTER_OPTIONS = { BMP581Component = bmp581_ns.class_("BMP581Component", cg.PollingComponent) -def compute_measurement_conversion_time(config): +def compute_measurement_conversion_time(config: ConfigType) -> int: # - adds up sensor conversion time based on temperature and pressure oversampling rates given in datasheet # - returns a rounded up time in ms @@ -132,7 +134,7 @@ CONFIG_SCHEMA_BASE = cv.Schema( ).extend(cv.polling_component_schema("60s")) -async def to_code_base(config): +async def to_code_base(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) if temperature_config := config.get(CONF_TEMPERATURE): diff --git a/esphome/components/bmp581_i2c/sensor.py b/esphome/components/bmp581_i2c/sensor.py index 42645022a6..b4cd00325d 100644 --- a/esphome/components/bmp581_i2c/sensor.py +++ b/esphome/components/bmp581_i2c/sensor.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv +from esphome.types import ConfigType from ..bmp581_base import CONFIG_SCHEMA_BASE, to_code_base @@ -18,6 +19,6 @@ CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( ).extend({cv.GenerateID(): cv.declare_id(BMP581I2CComponent)}) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/bmp581_spi/sensor.py b/esphome/components/bmp581_spi/sensor.py index db0d0cd529..435c5cd6f9 100644 --- a/esphome/components/bmp581_spi/sensor.py +++ b/esphome/components/bmp581_spi/sensor.py @@ -4,6 +4,7 @@ import esphome.codegen as cg from esphome.components import spi from esphome.components.spi import CONF_SPI_MODE import esphome.config_validation as cv +from esphome.types import ConfigType from ..bmp581_base import CONFIG_SCHEMA_BASE, to_code_base @@ -28,7 +29,7 @@ BMP581SPIComponent = bmp581_ns.class_( ) -def check_spi_mode(config): +def check_spi_mode(config: ConfigType) -> ConfigType: spi_mode = config.get(CONF_SPI_MODE) if spi_mode not in VALID_SPI_MODES: raise cv.Invalid("BMP581 only supports SPI mode 0 or mode 3") @@ -43,6 +44,6 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await spi.register_spi_device(var, config) diff --git a/esphome/components/bp1658cj/__init__.py b/esphome/components/bp1658cj/__init__.py index dc80c67b44..b45272d2ea 100644 --- a/esphome/components/bp1658cj/__init__.py +++ b/esphome/components/bp1658cj/__init__.py @@ -2,6 +2,7 @@ from esphome import pins import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_ID +from esphome.types import ConfigType CODEOWNERS = ["@Cossid"] MULTI_CONF = True @@ -28,7 +29,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/bp1658cj/output.py b/esphome/components/bp1658cj/output.py index 78cf717aba..93e3c75daf 100644 --- a/esphome/components/bp1658cj/output.py +++ b/esphome/components/bp1658cj/output.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import output import esphome.config_validation as cv from esphome.const import CONF_CHANNEL, CONF_ID +from esphome.types import ConfigType from . import BP1658CJ @@ -19,7 +20,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await output.register_output(var, config) diff --git a/esphome/components/bp5758d/__init__.py b/esphome/components/bp5758d/__init__.py index af78b38ef5..fa4e8a231b 100644 --- a/esphome/components/bp5758d/__init__.py +++ b/esphome/components/bp5758d/__init__.py @@ -2,6 +2,7 @@ from esphome import pins import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_ID +from esphome.types import ConfigType CODEOWNERS = ["@Cossid"] MULTI_CONF = True @@ -19,7 +20,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/bp5758d/output.py b/esphome/components/bp5758d/output.py index 9adf13de55..bbca7c18cc 100644 --- a/esphome/components/bp5758d/output.py +++ b/esphome/components/bp5758d/output.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import output import esphome.config_validation as cv from esphome.const import CONF_CHANNEL, CONF_CURRENT, CONF_ID +from esphome.types import ConfigType from . import BP5758D @@ -20,7 +21,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await output.register_output(var, config) diff --git a/esphome/components/cap1188/__init__.py b/esphome/components/cap1188/__init__.py index cde9dd46ae..eff0a05163 100644 --- a/esphome/components/cap1188/__init__.py +++ b/esphome/components/cap1188/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_RESET_PIN +from esphome.types import ConfigType CONF_TOUCH_THRESHOLD = "touch_threshold" CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches" @@ -32,7 +33,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD])) cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES])) diff --git a/esphome/components/cap1188/binary_sensor.py b/esphome/components/cap1188/binary_sensor.py index b7af53638a..21fd98ed41 100644 --- a/esphome/components/cap1188/binary_sensor.py +++ b/esphome/components/cap1188/binary_sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv from esphome.const import CONF_CHANNEL +from esphome.types import ConfigType from . import CONF_CAP1188_ID, CAP1188Component, cap1188_ns @@ -16,7 +17,7 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CAP1188Channel).extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_CAP1188_ID]) cg.add(var.set_channel(config[CONF_CHANNEL])) diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index 8e5274f58f..e490d89062 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -93,7 +93,7 @@ FINAL_VALIDATE_SCHEMA = _final_validate @coroutine_with_priority(CoroPriority.CAPTIVE_PORTAL) -async def to_code(config): +async def to_code(config: ConfigType) -> None: paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) var = cg.new_Pvariable(config[CONF_ID], paren) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index ffd121499b..e80f9e669f 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -4,9 +4,9 @@ #include "esphome/core/application.h" #include "esphome/core/helpers.h" #include "esphome/core/string_ref.h" +#include "esphome/components/wifi/scan_list.h" #include "esphome/components/wifi/wifi_component.h" #include "captive_index.h" -#include "scan_list.h" namespace esphome::captive_portal { @@ -37,7 +37,7 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) { const auto &results = wifi::global_wifi_component->get_scan_result(); for (const auto &scan : results) { bool with_auth = false; - if (!should_show_scan_entry(results, scan, with_auth)) + if (!wifi::should_show_scan_entry(results, scan, with_auth)) continue; json_escape_into_buffer(escaped_ssid, scan.get_ssid()); diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index d9023a415f..d134d2cf21 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -18,6 +18,7 @@ from esphome.const import ( UNIT_PARTS_PER_BILLION, UNIT_PARTS_PER_MILLION, ) +from esphome.types import ConfigType AUTO_LOAD = ["text_sensor"] CODEOWNERS = ["@habbie"] @@ -59,7 +60,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/cd74hc4067/__init__.py b/esphome/components/cd74hc4067/__init__.py index af6866df78..5f7778e186 100644 --- a/esphome/components/cd74hc4067/__init__.py +++ b/esphome/components/cd74hc4067/__init__.py @@ -2,6 +2,7 @@ from esphome import pins import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_DELAY, CONF_ID +from esphome.types import ConfigType AUTO_LOAD = ["sensor", "voltage_sampler"] CODEOWNERS = ["@asoehlke"] @@ -33,7 +34,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/cd74hc4067/sensor.py b/esphome/components/cd74hc4067/sensor.py index dceaf6f371..670b050271 100644 --- a/esphome/components/cd74hc4067/sensor.py +++ b/esphome/components/cd74hc4067/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) +from esphome.types import ConfigType from . import CD74HC4067Component, cd74hc4067_ns @@ -44,7 +45,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: parent = await cg.get_variable(config[CONF_CD74HC4067_ID]) var = cg.new_Pvariable(config[CONF_ID], parent) diff --git a/esphome/components/ch422g/__init__.py b/esphome/components/ch422g/__init__.py index 6a7bace0a2..7f0c5bb95e 100644 --- a/esphome/components/ch422g/__init__.py +++ b/esphome/components/ch422g/__init__.py @@ -13,6 +13,8 @@ from esphome.const import ( CONF_OPEN_DRAIN, CONF_OUTPUT, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@jesterret", "@clydebarrow"] DEPENDENCIES = ["i2c"] @@ -35,7 +37,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) # Can't use register_i2c_device because there is no CONF_ADDRESS @@ -44,7 +46,7 @@ async def to_code(config): # This is used as a final validation step so that modes have been fully transformed. -def pin_mode_check(pin_config, _): +def pin_mode_check(pin_config: ConfigType, _: ConfigType) -> None: if pin_config[CONF_MODE][CONF_INPUT] and pin_config[CONF_NUMBER] >= 8: raise cv.Invalid("CH422G only supports input on pins 0-7") if pin_config[CONF_MODE][CONF_OPEN_DRAIN] and pin_config[CONF_NUMBER] < 8: @@ -63,7 +65,7 @@ CH422G_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA, pin_mode_check) -async def ch422g_pin_to_code(config): +async def ch422g_pin_to_code(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_CH422G]) diff --git a/esphome/components/ch423/__init__.py b/esphome/components/ch423/__init__.py index e3990ee631..9fbf3ea515 100644 --- a/esphome/components/ch423/__init__.py +++ b/esphome/components/ch423/__init__.py @@ -14,6 +14,8 @@ from esphome.const import ( CONF_OUTPUT, ) from esphome.core import CORE +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@dwmw2"] DEPENDENCIES = ["i2c"] @@ -36,7 +38,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) # Can't use register_i2c_device because there is no CONF_ADDRESS @@ -45,7 +47,7 @@ async def to_code(config): # This is used as a final validation step so that modes have been fully transformed. -def pin_mode_check(pin_config, _): +def pin_mode_check(pin_config: ConfigType, _: ConfigType) -> None: if pin_config[CONF_MODE][CONF_INPUT] and pin_config[CONF_NUMBER] >= 8: raise cv.Invalid("CH423 only supports input on pins 0-7") if pin_config[CONF_MODE][CONF_OPEN_DRAIN] and pin_config[CONF_NUMBER] < 8: @@ -90,7 +92,7 @@ CH423_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_CH423, CH423_PIN_SCHEMA, pin_mode_check) -async def ch423_pin_to_code(config): +async def ch423_pin_to_code(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_CH423]) diff --git a/esphome/components/chsc6x/touchscreen.py b/esphome/components/chsc6x/touchscreen.py index 759e38609e..de974d2a79 100644 --- a/esphome/components/chsc6x/touchscreen.py +++ b/esphome/components/chsc6x/touchscreen.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN +from esphome.types import ConfigType chsc6x_ns = cg.esphome_ns.namespace("chsc6x") @@ -24,7 +25,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/climate_ir/__init__.py b/esphome/components/climate_ir/__init__.py index 5315be3db6..0667bd91a2 100644 --- a/esphome/components/climate_ir/__init__.py +++ b/esphome/components/climate_ir/__init__.py @@ -9,7 +9,8 @@ from esphome.const import ( CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, ) -from esphome.cpp_generator import MockObjClass +from esphome.cpp_generator import MockObj, MockObjClass +from esphome.types import ConfigType, SafeExpType _LOGGER = logging.getLogger(__name__) @@ -57,7 +58,7 @@ def climate_ir_with_receiver_schema( ) -async def register_climate_ir(var, config): +async def register_climate_ir(var: MockObj, config: ConfigType) -> None: await cg.register_component(var, config) await remote_base.register_transmittable(var, config) cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL])) @@ -72,7 +73,7 @@ async def register_climate_ir(var, config): cg.add(var.set_humidity_sensor(sens)) -async def new_climate_ir(config, *args): +async def new_climate_ir(config: ConfigType, *args: SafeExpType) -> MockObj: 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 9c832642ce..48fd373b78 100644 --- a/esphome/components/climate_ir_lg/climate.py +++ b/esphome/components/climate_ir_lg/climate.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir"] @@ -34,7 +35,7 @@ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(LgIrClimate).extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await climate_ir.new_climate_ir(config) cg.add(var.set_header_high(config[CONF_HEADER_HIGH])) diff --git a/esphome/components/color_temperature/light.py b/esphome/components/color_temperature/light.py index 045ab265cd..7686ede155 100644 --- a/esphome/components/color_temperature/light.py +++ b/esphome/components/color_temperature/light.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_WARM_WHITE_COLOR_TEMPERATURE, ) +from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] @@ -28,7 +29,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) await light.register_light(var, config) diff --git a/esphome/components/combination/sensor.py b/esphome/components/combination/sensor.py index 327cedee1e..ccc5a03964 100644 --- a/esphome/components/combination/sensor.py +++ b/esphome/components/combination/sensor.py @@ -16,6 +16,7 @@ from esphome.const import ( CONF_UNIT_OF_MEASUREMENT, ) from esphome.core.entity_helpers import inherit_property_from +from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -74,7 +75,7 @@ KALMAN_SOURCE_SCHEMA = cv.Schema( ) -def _migrate_coeffecient(config): +def _migrate_coeffecient(config: ConfigType) -> ConfigType: """Migrate deprecated 'coeffecient' spelling to 'coefficient'.""" if CONF_COEFFECIENT in config: if CONF_COEFFICIENT in config: @@ -172,7 +173,7 @@ FINAL_VALIDATE_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await sensor.register_sensor(var, config) diff --git a/esphome/components/coolix/climate.py b/esphome/components/coolix/climate.py index 1ebcff3c1b..3eb8dbe2f4 100644 --- a/esphome/components/coolix/climate.py +++ b/esphome/components/coolix/climate.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir"] CODEOWNERS = ["@glmnet"] @@ -10,5 +11,5 @@ CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR) CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(CoolixClimate) -async def to_code(config): +async def to_code(config: ConfigType) -> None: await climate_ir.new_climate_ir(config) diff --git a/esphome/components/cse7761/sensor.py b/esphome/components/cse7761/sensor.py index 7e8caf1ae1..b53ed26ca3 100644 --- a/esphome/components/cse7761/sensor.py +++ b/esphome/components/cse7761/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, ) +from esphome.types import ConfigType CODEOWNERS = ["@berfenger"] DEPENDENCIES = ["uart"] @@ -71,7 +72,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 94ed66d7cc..a1a68e18e8 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -26,6 +26,7 @@ from esphome.const import ( UNIT_WATT, UNIT_WATT_HOURS, ) +from esphome.types import ConfigType DEPENDENCIES = ["uart"] @@ -87,7 +88,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/cst226/binary_sensor/__init__.py b/esphome/components/cst226/binary_sensor/__init__.py index 324d794772..7fd81f6c18 100644 --- a/esphome/components/cst226/binary_sensor/__init__.py +++ b/esphome/components/cst226/binary_sensor/__init__.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv +from esphome.types import ConfigType from .. import cst226_ns from ..touchscreen import CST226ButtonListener, CST226Touchscreen @@ -26,7 +27,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await cg.register_parented(var, config[CONF_CST226_ID]) diff --git a/esphome/components/cst226/touchscreen/__init__.py b/esphome/components/cst226/touchscreen/__init__.py index 62c2e3b20a..459cba61cd 100644 --- a/esphome/components/cst226/touchscreen/__init__.py +++ b/esphome/components/cst226/touchscreen/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN +from esphome.types import ConfigType from .. import cst226_ns @@ -26,7 +27,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/cst328/binary_sensor/__init__.py b/esphome/components/cst328/binary_sensor/__init__.py index 6d881cc6c1..33e68a112b 100644 --- a/esphome/components/cst328/binary_sensor/__init__.py +++ b/esphome/components/cst328/binary_sensor/__init__.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv +from esphome.types import ConfigType from .. import cst328_ns from ..touchscreen import CST328ButtonListener, CST328Touchscreen @@ -22,7 +23,7 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CST328Button).extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await cg.register_parented(var, config[CONF_CST328_ID]) diff --git a/esphome/components/cst328/touchscreen/__init__.py b/esphome/components/cst328/touchscreen/__init__.py index 18c00bb6c5..9bc7744b7c 100644 --- a/esphome/components/cst328/touchscreen/__init__.py +++ b/esphome/components/cst328/touchscreen/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN +from esphome.types import ConfigType from .. import cst328_ns @@ -27,7 +28,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/cst816/touchscreen/__init__.py b/esphome/components/cst816/touchscreen/__init__.py index 288ca17593..029a544a91 100644 --- a/esphome/components/cst816/touchscreen/__init__.py +++ b/esphome/components/cst816/touchscreen/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN +from esphome.types import ConfigType from .. import cst816_ns @@ -25,7 +26,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( ).extend(i2c.i2c_device_schema(0x15)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/cst9220/touchscreen/__init__.py b/esphome/components/cst9220/touchscreen/__init__.py index 6d8fc5e2f6..393685e67b 100644 --- a/esphome/components/cst9220/touchscreen/__init__.py +++ b/esphome/components/cst9220/touchscreen/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN +from esphome.types import ConfigType from .. import cst9220_ns @@ -25,7 +26,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ct_clamp/sensor.py b/esphome/components/ct_clamp/sensor.py index 6ad7990e80..8ef211cb24 100644 --- a/esphome/components/ct_clamp/sensor.py +++ b/esphome/components/ct_clamp/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_AMPERE, ) +from esphome.types import ConfigType AUTO_LOAD = ["voltage_sampler"] CODEOWNERS = ["@jesserockz"] @@ -36,7 +37,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/current_based/cover.py b/esphome/components/current_based/cover.py index 99952adb12..a552956082 100644 --- a/esphome/components/current_based/cover.py +++ b/esphome/components/current_based/cover.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_OPEN_DURATION, CONF_STOP_ACTION, ) +from esphome.types import ConfigType CONF_OPEN_SENSOR = "open_sensor" CONF_OPEN_MOVING_CURRENT_THRESHOLD = "open_moving_current_threshold" @@ -67,7 +68,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await cover.new_cover(config) await cg.register_component(var, config) diff --git a/esphome/components/cwww/light.py b/esphome/components/cwww/light.py index 50d84a582d..90fe6d0bad 100644 --- a/esphome/components/cwww/light.py +++ b/esphome/components/cwww/light.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_WARM_WHITE, CONF_WARM_WHITE_COLOR_TEMPERATURE, ) +from esphome.types import ConfigType cwww_ns = cg.esphome_ns.namespace("cwww") CWWWLightOutput = cwww_ns.class_("CWWWLightOutput", light.LightOutput) @@ -31,7 +32,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) await light.register_light(var, config) diff --git a/esphome/components/dac7678/__init__.py b/esphome/components/dac7678/__init__.py index 842c84832e..668cc87cec 100644 --- a/esphome/components/dac7678/__init__.py +++ b/esphome/components/dac7678/__init__.py @@ -2,6 +2,8 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType AUTO_LOAD = ["output"] CODEOWNERS = ["@NickB1"] @@ -24,7 +26,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) cg.add(var.set_internal_reference(config[CONF_INTERNAL_REFERENCE])) diff --git a/esphome/components/dac7678/output.py b/esphome/components/dac7678/output.py index cb7739242c..8bc9e119c2 100644 --- a/esphome/components/dac7678/output.py +++ b/esphome/components/dac7678/output.py @@ -2,6 +2,8 @@ import esphome.codegen as cg from esphome.components import output import esphome.config_validation as cv from esphome.const import CONF_CHANNEL, CONF_ID +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType from . import DAC7678Output, dac7678_ns @@ -19,7 +21,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> MockObj: paren = await cg.get_variable(config[CONF_DAC7678_ID]) var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_channel(config[CONF_CHANNEL])) diff --git a/esphome/components/daikin/climate.py b/esphome/components/daikin/climate.py index 7f0226143b..c9f9cb189f 100644 --- a/esphome/components/daikin/climate.py +++ b/esphome/components/daikin/climate.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir"] @@ -9,5 +10,5 @@ DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR) CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinClimate) -async def to_code(config): +async def to_code(config: ConfigType) -> None: await climate_ir.new_climate_ir(config) diff --git a/esphome/components/daikin_arc/climate.py b/esphome/components/daikin_arc/climate.py index dbaf12d959..210ec6987e 100644 --- a/esphome/components/daikin_arc/climate.py +++ b/esphome/components/daikin_arc/climate.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir"] @@ -9,5 +10,5 @@ DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinArcClimate) -async def to_code(config): +async def to_code(config: ConfigType) -> None: await climate_ir.new_climate_ir(config) diff --git a/esphome/components/daikin_brc/climate.py b/esphome/components/daikin_brc/climate.py index 5b7a4631a9..c5c1d3739e 100644 --- a/esphome/components/daikin_brc/climate.py +++ b/esphome/components/daikin_brc/climate.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv from esphome.const import CONF_USE_FAHRENHEIT +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir"] @@ -16,6 +17,6 @@ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DaikinBrcClimate).ext ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await climate_ir.new_climate_ir(config) cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT])) diff --git a/esphome/components/dallas_temp/sensor.py b/esphome/components/dallas_temp/sensor.py index 3d35881722..c441504947 100644 --- a/esphome/components/dallas_temp/sensor.py +++ b/esphome/components/dallas_temp/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType dallas_temp_ns = cg.esphome_ns.namespace("dallas_temp") @@ -35,7 +36,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await one_wire.register_one_wire_device(var, config) diff --git a/esphome/components/delonghi/climate.py b/esphome/components/delonghi/climate.py index 63576f032d..919bf7b806 100644 --- a/esphome/components/delonghi/climate.py +++ b/esphome/components/delonghi/climate.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir"] @@ -9,5 +10,5 @@ DelonghiClimate = delonghi_ns.class_("DelonghiClimate", climate_ir.ClimateIR) CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(DelonghiClimate) -async def to_code(config): +async def to_code(config: ConfigType) -> None: await climate_ir.new_climate_ir(config) diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index 2af0c18c18..75feaa65af 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -55,6 +55,7 @@ from esphome.const import ( UNIT_PERCENT, UNIT_WATT_HOURS, ) +from esphome.types import ConfigType AUTO_LOAD = [ "alarm_control_panel", @@ -550,7 +551,7 @@ CONFIG_SCHEMA = cv.Schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: for conf in config[CONF_ALARM_CONTROL_PANELS]: var = await alarm_control_panel.new_alarm_control_panel(conf) cg.add(var.set_type(conf[CONF_TYPE])) diff --git a/esphome/components/dew_point/sensor.py b/esphome/components/dew_point/sensor.py index 4fee095602..555fdef289 100644 --- a/esphome/components/dew_point/sensor.py +++ b/esphome/components/dew_point/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType DEPENDENCIES = ["sensor"] @@ -35,7 +36,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/dht/sensor.py b/esphome/components/dht/sensor.py index d907495ba2..7376adb287 100644 --- a/esphome/components/dht/sensor.py +++ b/esphome/components/dht/sensor.py @@ -15,6 +15,7 @@ from esphome.const import ( UNIT_PERCENT, ) from esphome.cpp_helpers import gpio_pin_expression +from esphome.types import ConfigType dht_ns = cg.esphome_ns.namespace("dht") DHTModel = dht_ns.enum("DHTModel") @@ -53,7 +54,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.polling_component_schema("60s")) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/dht12/sensor.py b/esphome/components/dht12/sensor.py index eb93cbae2c..2bc6e94515 100644 --- a/esphome/components/dht12/sensor.py +++ b/esphome/components/dht12/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -40,7 +41,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/dps310/sensor.py b/esphome/components/dps310/sensor.py index 605812beaa..8b8fd8373b 100644 --- a/esphome/components/dps310/sensor.py +++ b/esphome/components/dps310/sensor.py @@ -13,6 +13,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_HECTOPASCAL, ) +from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] @@ -48,7 +49,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ds2484/one_wire.py b/esphome/components/ds2484/one_wire.py index 384b2d01e6..f6277cd68e 100644 --- a/esphome/components/ds2484/one_wire.py +++ b/esphome/components/ds2484/one_wire.py @@ -3,6 +3,7 @@ from esphome.components import i2c from esphome.components.one_wire import OneWireBus import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType ds2484_ns = cg.esphome_ns.namespace("ds2484") @@ -29,7 +30,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await i2c.register_i2c_device(var, config) await cg.register_component(var, config) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index eaf36d34fa..96a1e75668 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -60,7 +60,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: uart_component = await cg.get_variable(config[CONF_UART_ID]) if CONF_REQUEST_PIN in config: request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN]) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 7d93ee62e1..6aecc62d6b 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -27,6 +27,7 @@ from esphome.const import ( UNIT_SECOND, UNIT_VOLT, ) +from esphome.types import ConfigType from . import CONF_DSMR_ID, Dsmr @@ -812,7 +813,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_DSMR_ID]) sensors = [] diff --git a/esphome/components/dsmr/text_sensor.py b/esphome/components/dsmr/text_sensor.py index 54b5711923..4945ba965c 100644 --- a/esphome/components/dsmr/text_sensor.py +++ b/esphome/components/dsmr/text_sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import text_sensor import esphome.config_validation as cv from esphome.const import CONF_INTERNAL +from esphome.types import ConfigType from . import CONF_DSMR_ID, Dsmr @@ -39,7 +40,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: hub = await cg.get_variable(config[CONF_DSMR_ID]) text_sensors = [] diff --git a/esphome/components/duty_cycle/sensor.py b/esphome/components/duty_cycle/sensor.py index 37c889cd85..b7aa1777c8 100644 --- a/esphome/components/duty_cycle/sensor.py +++ b/esphome/components/duty_cycle/sensor.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv from esphome.const import CONF_PIN, ICON_PERCENT, STATE_CLASS_MEASUREMENT, UNIT_PERCENT +from esphome.types import ConfigType duty_cycle_ns = cg.esphome_ns.namespace("duty_cycle") DutyCycleSensor = duty_cycle_ns.class_( @@ -22,7 +23,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/e131/__init__.py b/esphome/components/e131/__init__.py index a1a8e0aec5..3b1eb99e60 100644 --- a/esphome/components/e131/__init__.py +++ b/esphome/components/e131/__init__.py @@ -3,6 +3,9 @@ from esphome.components.light.effects import register_addressable_effect from esphome.components.light.types import AddressableLightEffect import esphome.config_validation as cv from esphome.const import CONF_CHANNELS, CONF_ID, CONF_METHOD, CONF_NAME +from esphome.core import ID +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType AUTO_LOAD = ["socket"] DEPENDENCIES = ["network"] @@ -32,7 +35,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) cg.add(var.set_method(METHODS[config[CONF_METHOD]])) @@ -48,7 +51,7 @@ async def to_code(config): cv.Optional(CONF_CHANNELS, default="RGB"): cv.one_of(*CHANNELS, upper=True), }, ) -async def e131_light_effect_to_code(config, effect_id): +async def e131_light_effect_to_code(config: ConfigType, effect_id: ID) -> MockObj: parent = await cg.get_variable(config[CONF_E131_ID]) effect = cg.new_Pvariable(effect_id, config[CONF_NAME]) diff --git a/esphome/components/ee895/sensor.py b/esphome/components/ee895/sensor.py index 8c9c7e7238..fdad47fb05 100644 --- a/esphome/components/ee895/sensor.py +++ b/esphome/components/ee895/sensor.py @@ -14,6 +14,7 @@ from esphome.const import ( UNIT_HECTOPASCAL, UNIT_PARTS_PER_MILLION, ) +from esphome.types import ConfigType CODEOWNERS = ["@Stock-M"] @@ -51,7 +52,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ektf2232/touchscreen/__init__.py b/esphome/components/ektf2232/touchscreen/__init__.py index 64bb17a7db..7636b6993a 100644 --- a/esphome/components/ektf2232/touchscreen/__init__.py +++ b/esphome/components/ektf2232/touchscreen/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN +from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["i2c"] @@ -29,7 +30,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/emmeti/climate.py b/esphome/components/emmeti/climate.py index 56e8e2b804..ea44606300 100644 --- a/esphome/components/emmeti/climate.py +++ b/esphome/components/emmeti/climate.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir +from esphome.types import ConfigType CODEOWNERS = ["@E440QF"] AUTO_LOAD = ["climate_ir"] @@ -10,5 +11,5 @@ EmmetiClimate = emmeti_ns.class_("EmmetiClimate", climate_ir.ClimateIR) CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(EmmetiClimate) -async def to_code(config): +async def to_code(config: ConfigType) -> None: await climate_ir.new_climate_ir(config) diff --git a/esphome/components/endstop/cover.py b/esphome/components/endstop/cover.py index c16680b6af..0e27189500 100644 --- a/esphome/components/endstop/cover.py +++ b/esphome/components/endstop/cover.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_OPEN_ENDSTOP, CONF_STOP_ACTION, ) +from esphome.types import ConfigType endstop_ns = cg.esphome_ns.namespace("endstop") EndstopCover = endstop_ns.class_("EndstopCover", cover.Cover, cg.Component) @@ -34,7 +35,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await cover.new_cover(config) await cg.register_component(var, config) diff --git a/esphome/components/ens160_base/__init__.py b/esphome/components/ens160_base/__init__.py index 46c53c3b10..1bdfb0c0a6 100644 --- a/esphome/components/ens160_base/__init__.py +++ b/esphome/components/ens160_base/__init__.py @@ -18,6 +18,8 @@ from esphome.const import ( UNIT_PARTS_PER_BILLION, UNIT_PARTS_PER_MILLION, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@vincentscode", "@latonita"] @@ -57,7 +59,7 @@ CONFIG_SCHEMA_BASE = cv.Schema( ).extend(cv.polling_component_schema("60s")) -async def to_code_base(config): +async def to_code_base(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/ens160_i2c/sensor.py b/esphome/components/ens160_i2c/sensor.py index cad4e81afc..398b9b4804 100644 --- a/esphome/components/ens160_i2c/sensor.py +++ b/esphome/components/ens160_i2c/sensor.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import i2c +from esphome.types import ConfigType from ..ens160_base import CONFIG_SCHEMA_BASE, cv, to_code_base @@ -18,6 +19,6 @@ CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend( ).extend({cv.GenerateID(): cv.declare_id(ENS160I2CComponent)}) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ens160_spi/sensor.py b/esphome/components/ens160_spi/sensor.py index 1bda05c7bb..cc6a90a33e 100644 --- a/esphome/components/ens160_spi/sensor.py +++ b/esphome/components/ens160_spi/sensor.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import spi +from esphome.types import ConfigType from ..ens160_base import CONFIG_SCHEMA_BASE, cv, to_code_base @@ -18,6 +19,6 @@ CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema()).extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await to_code_base(config) await spi.register_spi_device(var, config) diff --git a/esphome/components/ens210/sensor.py b/esphome/components/ens210/sensor.py index 289a559673..bfd758f92f 100644 --- a/esphome/components/ens210/sensor.py +++ b/esphome/components/ens210/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType CODEOWNERS = ["@itn3rd77"] DEPENDENCIES = ["i2c"] @@ -44,7 +45,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/es7210/audio_adc.py b/esphome/components/es7210/audio_adc.py index f0bd8bc25a..2defdb0c35 100644 --- a/esphome/components/es7210/audio_adc.py +++ b/esphome/components/es7210/audio_adc.py @@ -3,6 +3,7 @@ from esphome.components import i2c from esphome.components.audio_adc import AudioAdc import esphome.config_validation as cv from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_MIC_GAIN, CONF_SAMPLE_RATE +from esphome.types import ConfigType CODEOWNERS = ["@kahrendt"] DEPENDENCIES = ["i2c"] @@ -41,7 +42,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/es7243e/audio_adc.py b/esphome/components/es7243e/audio_adc.py index c305d60172..4916133982 100644 --- a/esphome/components/es7243e/audio_adc.py +++ b/esphome/components/es7243e/audio_adc.py @@ -3,6 +3,7 @@ from esphome.components import i2c from esphome.components.audio_adc import AudioAdc import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_MIC_GAIN +from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] DEPENDENCIES = ["i2c"] @@ -26,7 +27,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/es8156/audio_dac.py b/esphome/components/es8156/audio_dac.py index c5fb6096da..305aa92125 100644 --- a/esphome/components/es8156/audio_dac.py +++ b/esphome/components/es8156/audio_dac.py @@ -4,6 +4,7 @@ from esphome.components.audio_dac import AudioDac import esphome.config_validation as cv from esphome.const import CONF_AUDIO_DAC, CONF_BITS_PER_SAMPLE, CONF_ID import esphome.final_validate as fv +from esphome.types import ConfigType CODEOWNERS = ["@kbx81"] DEPENDENCIES = ["i2c"] @@ -22,7 +23,7 @@ CONFIG_SCHEMA = ( ) -def _final_validate(config): +def _final_validate(config: ConfigType) -> None: full_config = fv.full_config.get() # Check all speaker configurations for ones that reference this es8156 @@ -45,7 +46,7 @@ def _final_validate(config): FINAL_VALIDATE_SCHEMA = _final_validate -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/es8311/audio_dac.py b/esphome/components/es8311/audio_dac.py index 5941a81935..f9cfb822cc 100644 --- a/esphome/components/es8311/audio_dac.py +++ b/esphome/components/es8311/audio_dac.py @@ -3,6 +3,7 @@ from esphome.components import i2c from esphome.components.audio_dac import AudioDac import esphome.config_validation as cv from esphome.const import CONF_BITS_PER_SAMPLE, CONF_ID, CONF_MIC_GAIN, CONF_SAMPLE_RATE +from esphome.types import ConfigType CODEOWNERS = ["@kroimon", "@kahrendt"] DEPENDENCIES = ["i2c"] @@ -55,7 +56,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/es8388/audio_dac.py b/esphome/components/es8388/audio_dac.py index 77e07b2e01..2616cbfa53 100644 --- a/esphome/components/es8388/audio_dac.py +++ b/esphome/components/es8388/audio_dac.py @@ -3,6 +3,7 @@ from esphome.components import i2c from esphome.components.audio_dac import AudioDac import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType CODEOWNERS = ["@P4uLT"] CONF_ES8388_ID = "es8388_id" @@ -20,7 +21,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/es8388/select/__init__.py b/esphome/components/es8388/select/__init__.py index 068d9f9fb8..b81bcd13cf 100644 --- a/esphome/components/es8388/select/__init__.py +++ b/esphome/components/es8388/select/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import select import esphome.config_validation as cv from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_CHIP # noqa: F401 +from esphome.types import ConfigType from ..audio_dac import CONF_ES8388_ID, ES8388, es8388_ns @@ -28,7 +29,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: parent = await cg.get_variable(config[CONF_ES8388_ID]) if dac_output_config := config.get(CONF_DAC_OUTPUT): s = await select.new_select( diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 79747c6f31..7e97111686 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -22,6 +22,7 @@ from esphome.components.esp32 import ( request_bluetooth, ) from esphome.components.esp32.const import VARIANT_ESP32C2 +from esphome.config_helpers import filter_source_files_from_defines import esphome.config_validation as cv from esphome.const import ( CONF_ENABLE_ON_BOOT, @@ -637,3 +638,10 @@ async def ble_disable_to_code( args: TemplateArgsType, ) -> MockObj: return cg.new_Pvariable(action_id, template_arg) + + +# ble_advertising.cpp is fully #ifdef'd on USE_ESP32_BLE_ADVERTISING, set +# when advertising is enabled here or by esp32_ble_server / esp32_ble_beacon. +FILTER_SOURCE_FILES = filter_source_files_from_defines( + {"ble_advertising.cpp": "USE_ESP32_BLE_ADVERTISING"} +) diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index d762255040..e9c44284e4 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -5,6 +5,7 @@ from esphome.components.esp32_ble import CONF_BLE_ID import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_TX_POWER, CONF_TYPE, CONF_UUID from esphome.core import TimePeriod +from esphome.types import ConfigType AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] @@ -18,7 +19,7 @@ CONF_MAX_INTERVAL = "max_interval" CONF_MEASURED_POWER = "measured_power" -def validate_config(config): +def validate_config(config: ConfigType) -> ConfigType: if config[CONF_MIN_INTERVAL] > config.get(CONF_MAX_INTERVAL): raise cv.Invalid("min_interval must be <= max_interval") return config @@ -61,7 +62,7 @@ CONFIG_SCHEMA = cv.All( FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant -async def to_code(config): +async def to_code(config: ConfigType) -> None: cg.add_define("USE_ESP32_BLE_UUID") uuid = config[CONF_UUID].hex diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py index 4f6355df70..c6e34f37ca 100644 --- a/esphome/components/esp32_ble_tracker/__init__.py +++ b/esphome/components/esp32_ble_tracker/__init__.py @@ -7,6 +7,7 @@ import logging from esphome import automation import esphome.codegen as cg from esphome.components import ble_device_base, esp32_ble, ota +from esphome.components.ble_device_base import CONF_CONNECTION_SCAN_WINDOW from esphome.components.const import CONF_ON_SCAN_END, CONF_SCAN_PARAMETERS, CONF_WINDOW from esphome.components.esp32 import ( add_idf_sdkconfig_option, @@ -73,8 +74,9 @@ def _get_required_features() -> set[BLEFeatures]: # Slot counters sizing the tracker's StaticVector storage; one request per # registered listener or client. +CLIENT_COUNT_DEFINE = "ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT" _request_listener_slot = cg.slot_counter("ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT") -_request_client_slot = cg.slot_counter("ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT") +_request_client_slot = cg.slot_counter(CLIENT_COUNT_DEFINE) def register_ble_features(features: set[BLEFeatures]) -> None: @@ -147,6 +149,7 @@ class TrackerData: """Per-run validation state, namespaced under DOMAIN in CORE.data.""" scan_window_defaulted: bool = False + connection_window_injected: bool = False def _get_data() -> TrackerData: @@ -175,17 +178,34 @@ def _raise_defaulted_scan_window(config: ConfigType) -> ConfigType: honors the window strictly (>= 5.5.5); without the arbiter a full-duty scan would starve wifi outright, and a user-set window is never touched. Raising to the interval cannot invalidate the already-validated - parameters, so no re-validation is needed. + parameters, so no re-validation is needed. The connection window is + checked against the window here, after the raise. """ + params = config[CONF_SCAN_PARAMETERS] if ( _get_data().scan_window_defaulted and config.get(CONF_SOFTWARE_COEXISTENCE) and idf_version() >= IDF_SCAN_WINDOW_FIX_VERSION ): - params = config[CONF_SCAN_PARAMETERS] # Copy so the config dump shows a plain value instead of a YAML # anchor/alias pair pointing at the interval. params[CONF_WINDOW] = copy.copy(params[CONF_INTERVAL]) + # Arm the connection-time fallback unless the user set one. Injected + # after validation; safe because it equals the validated window default. + if CONF_CONNECTION_SCAN_WINDOW not in params: + params[CONF_CONNECTION_SCAN_WINDOW] = cv.positive_time_period( + ble_device_base.DEFAULT_SCAN_WINDOW + ) + _get_data().connection_window_injected = True + if ( + connection_window := params.get(CONF_CONNECTION_SCAN_WINDOW) + ) is not None and connection_window > params[CONF_WINDOW]: + # A larger value would widen the scan during connections. + raise cv.Invalid( + f"{CONF_CONNECTION_SCAN_WINDOW} ({connection_window}) needs to be " + f"smaller than the scan window ({params[CONF_WINDOW]})", + path=[CONF_SCAN_PARAMETERS, CONF_CONNECTION_SCAN_WINDOW], + ) return config @@ -194,7 +214,7 @@ def _raise_defaulted_scan_window(config: ConfigType) -> ConfigType: # window/interval pairs that collapse to the same 0.625 ms unit count. # The window default is conditional (see _scan_window_default above). SCAN_PARAMETERS_SCHEMA = ble_device_base.scan_parameters_schema( - "320ms", window_default=_scan_window_default + "320ms", window_default=_scan_window_default, connection_window=True ) # Codegen helpers are owned by ble_device_base; kept under the historical names @@ -288,6 +308,25 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_scan_duration(params[CONF_DURATION])) cg.add(var.set_scan_interval(ble_device_base.to_ble_units(params[CONF_INTERVAL]))) cg.add(var.set_scan_window(ble_device_base.to_ble_units(params[CONF_WINDOW]))) + if (connection_window := params.get(CONF_CONNECTION_SCAN_WINDOW)) is not None: + # Emitted at FINAL so a scan-only build, where the guarded C++ path + # compiles out, skips the call entirely. + window_units = ble_device_base.to_ble_units(connection_window) + + @coroutine_with_priority(CoroPriority.FINAL) + async def _emit_connection_scan_window() -> None: + if cg.get_slot_count(CLIENT_COUNT_DEFINE): + cg.add(var.set_connection_scan_window(window_units)) + elif not _get_data().connection_window_injected: + # Warn only for a user-set value; the injected default drops silently. + _LOGGER.warning( + "'%s' has no effect because this build has no BLE client " + "components (for example bluetooth_proxy with active " + "connections, or ble_client)", + CONF_CONNECTION_SCAN_WINDOW, + ) + + CORE.add_job(_emit_connection_scan_window) cg.add(var.set_scan_active(params[CONF_ACTIVE])) cg.add(var.set_scan_continuous(params[CONF_CONTINUOUS])) diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 798fd6e0ca..5339565a32 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -122,6 +122,9 @@ void ESP32BLETracker::loop() { // - start_scan_(): scanner_state_ becomes IDLE via set_scanner_state_() in cleanup_scan_state_() // - try_promote_discovered_clients_(): client enters DISCOVERED via set_state(), or // connecting client finishes (state change), or scanner reaches RUNNING/IDLE + // - connection-window restart: scan_params_ is only written in start_scan_() + // (which changes scanner state via set_scanner_state_()), and + // counts.active/disconnecting only change on client state changes // // All conditions that affect the logic below are tied to state changes that increment // state_version_, so the fast path is safe. @@ -144,6 +147,19 @@ void ESP32BLETracker::loop() { (this->scan_set_param_failed_ && this->scanner_state_ == ScannerState::RUNNING)) { this->handle_scanner_failure_(); } + +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + // The programmed window no longer matches the connection state (typically + // the last connection dropped): restart so the right window applies now + // instead of at the end of the scan period. Continuous only (a user-started + // scan would not restart); !disconnecting matches the restart gate below. + if (this->scanner_state_ == ScannerState::RUNNING && this->scan_continuous_ && !counts.disconnecting && + this->scan_params_.scan_window != this->desired_scan_window_(counts.active)) { + // Same logical scan period continues: no on_scan_end sweeps for this + // restart. Only armed when the stop was issued. + this->skip_next_scan_end_ = this->stop_scan_(); + } +#endif /* Avoid starting the scanner if: @@ -195,19 +211,23 @@ void ESP32BLETracker::stop_scan() { // reason at D themselves, and the user-facing stop action is deliberate. ESP_LOGV(TAG, "Stopping scan."); this->scan_continuous_ = false; +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + // The window-change restart is abandoned with continuous scanning. + this->skip_next_scan_end_ = false; +#endif this->stop_scan_(); } void ESP32BLETracker::ble_before_disabled_event_handler() { this->stop_scan_(); } -void ESP32BLETracker::stop_scan_() { +bool ESP32BLETracker::stop_scan_() { if (this->scanner_state_ != ScannerState::RUNNING && this->scanner_state_ != ScannerState::FAILED) { // IDLE means there is nothing to stop; STOPPING means a stop is already in // flight and will finish on its own. Neither is an error. if (this->scanner_state_ != ScannerState::IDLE && this->scanner_state_ != ScannerState::STOPPING) { ESP_LOGE(TAG, "Cannot stop scan: %s", this->scanner_state_to_string_(this->scanner_state_)); } - return; + return false; } // Reset timeout state machine when stopping scan this->scan_timeout_state_ = ScanTimeoutState::INACTIVE; @@ -215,8 +235,9 @@ void ESP32BLETracker::stop_scan_() { esp_err_t err = esp_ble_gap_stop_scanning(); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gap_stop_scanning failed: %d", err); - return; + return false; } + return true; } void ESP32BLETracker::start_scan_(bool first) { @@ -230,16 +251,11 @@ void ESP32BLETracker::start_scan_(bool first) { } this->set_scanner_state_(ScannerState::STARTING); ESP_LOGV(TAG, "Starting scan, set scanner state to STARTING."); - if (!first) { -#ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT - for (auto *listener : this->listeners_) - listener->on_scan_end(); + if (!first) + this->notify_scan_end_(); +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + this->skip_next_scan_end_ = false; #endif -#ifdef ESPHOME_BLE_DEVICE_BASE_LISTENER_COUNT - for (auto *listener : this->neutral_listeners_) - listener->on_scan_end(); -#endif - } #ifdef USE_ESP32_BLE_DEVICE this->discovered_log_.clear(); #endif @@ -247,7 +263,17 @@ void ESP32BLETracker::start_scan_(bool first) { this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC; this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL; this->scan_params_.scan_interval = this->scan_interval_; - this->scan_params_.scan_window = this->scan_window_; +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + // Count fresh: an automation can start a scan before loop() refreshes the counts. + const uint32_t window = this->desired_scan_window_(this->count_client_states_().active); + if (window != this->scan_window_) { + // Guarantee the connection airtime instead of scanning wall to wall. + ESP_LOGV(TAG, "Connection active, using %" PRIu32 " unit scan window", window); + } +#else + const uint32_t window = this->scan_window_; +#endif + this->scan_params_.scan_window = window; // Start timeout monitoring in loop() instead of using scheduler // This prevents false reboots when the loop is blocked @@ -408,6 +434,11 @@ void ESP32BLETracker::dump_config() { " Continuous Scanning: %s", this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f, this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_)); +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + if (this->connection_scan_window_ != 0) { + ESP_LOGCONFIG(TAG, " Connection Scan Window: %.1f ms", this->connection_scan_window_ * 0.625f); + } +#endif ESP_LOGCONFIG(TAG, " Scanner State: %s\n" " Connecting: %d, discovered: %d, disconnecting: %d, active: %d", @@ -487,6 +518,18 @@ void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) { // Reset timeout state machine instead of cancelling scheduler timeout this->scan_timeout_state_ = ScanTimeoutState::INACTIVE; + this->notify_scan_end_(); + + this->set_scanner_state_(ScannerState::IDLE); +} + +void ESP32BLETracker::notify_scan_end_() { +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + // Window-change restart continues the same scan period; the flag stays set + // across the stop and is cleared by the restart in start_scan_. + if (this->skip_next_scan_end_) + return; +#endif #ifdef ESPHOME_ESP32_BLE_TRACKER_LISTENER_COUNT for (auto *listener : this->listeners_) listener->on_scan_end(); @@ -495,8 +538,6 @@ void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) { for (auto *listener : this->neutral_listeners_) listener->on_scan_end(); #endif - - this->set_scanner_state_(ScannerState::IDLE); } void ESP32BLETracker::handle_scanner_failure_() { @@ -534,6 +575,8 @@ void ESP32BLETracker::try_promote_discovered_clients_() { } ESP_LOGD(TAG, "Promoting client to connect"); + // A connect ends the scan period a window-change restart was continuing. + this->skip_next_scan_end_ = false; #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE this->update_coex_preference_(true); #endif diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 7c3e5538fd..618444e626 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -169,6 +169,9 @@ class ESP32BLETracker final : public Component, void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; } void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; } void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; } +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + void set_connection_scan_window(uint32_t scan_window) { connection_scan_window_ = scan_window; } +#endif void set_scan_active(bool scan_active) { scan_active_ = scan_active; } bool get_scan_active() const { return scan_active_; } void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; } @@ -226,7 +229,10 @@ class ESP32BLETracker final : public Component, ScannerState get_scanner_state() const { return this->scanner_state_; } protected: - void stop_scan_(); + /// Returns true when a stop was issued to the controller. + bool stop_scan_(); + /// Fire on_scan_end on every listener unless a window-change restart suppressed it. + void notify_scan_end_(); /// Start a single scan by setting up the parameters and doing some esp-idf calls. void start_scan_(bool first); /// Called when a `ESP_GAP_BLE_SCAN_RESULT_EVT` event is received. @@ -313,6 +319,15 @@ class ESP32BLETracker final : public Component, uint32_t scan_duration_; uint32_t scan_interval_; uint32_t scan_window_; +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + /// Window used while a GATT connection is active; set by the user, or + /// defaulted when the window was raised to full duty (0 = no fallback). + uint32_t connection_scan_window_{0}; + /// The window to scan at for the given number of active GATT connections. + uint32_t desired_scan_window_(uint8_t active) const { + return (this->connection_scan_window_ != 0 && active > 0) ? this->connection_scan_window_ : this->scan_window_; + } +#endif esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; @@ -330,15 +345,20 @@ class ESP32BLETracker final : public Component, /// state_version_ to detect if any state changed since last iteration. uint8_t last_processed_version_{0}; ScannerState scanner_state_{ScannerState::IDLE}; - bool scan_continuous_; - bool scan_active_; + // Packed 1-bit flags. + bool scan_continuous_ : 1; + bool scan_active_ : 1; #ifdef USE_OTA_STATE_LISTENER - bool scan_continuous_before_ota_{false}; + bool scan_continuous_before_ota_ : 1 {false}; +#endif + bool ble_was_disabled_ : 1 {true}; + bool parse_advertisements_ : 1 {false}; +#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT + /// Suppress the window-change restart's on_scan_end sweeps (stop and start). + bool skip_next_scan_end_ : 1 {false}; #endif - bool ble_was_disabled_{true}; - bool parse_advertisements_{false}; #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE - bool coex_prefer_ble_{false}; + bool coex_prefer_ble_ : 1 {false}; #endif // Scan timeout state machine enum class ScanTimeoutState : uint8_t { @@ -346,10 +366,10 @@ class ESP32BLETracker final : public Component, MONITORING, // Actively monitoring for timeout EXCEEDED_WAIT, // Timeout exceeded, waiting one loop before reboot }; + ScanTimeoutState scan_timeout_state_{ScanTimeoutState::INACTIVE}; uint32_t scan_start_time_{0}; /// Precomputed timeout value: scan_duration_ * 2000 uint32_t scan_timeout_ms_{0}; - ScanTimeoutState scan_timeout_state_{ScanTimeoutState::INACTIVE}; }; // NOLINTNEXTLINE diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index c3b35a8279..3c41d22903 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -1,4 +1,5 @@ import logging +from typing import Any from esphome import automation, pins import esphome.codegen as cg @@ -24,6 +25,7 @@ from esphome.const import ( ) from esphome.core import CORE from esphome.core.entity_helpers import setup_entity +from esphome.cpp_generator import MockObj import esphome.final_validate as fv from esphome.types import ConfigType @@ -179,7 +181,7 @@ CONF_ON_IMAGE = "on_image" camera_range_param = cv.int_range(min=-2, max=2) -def validate_fb_location_(value): +def validate_fb_location_(value: Any) -> MockObj: validator = cv.enum(ENUM_FB_LOCATION, upper=True) if value.lower() == psram_domain: validator = cv.All(validator, cv.requires_component(psram_domain)) @@ -310,7 +312,7 @@ CONFIG_SCHEMA = cv.All( ) -def _final_validate(config): +def _final_validate(config: ConfigType) -> None: # Check psram requirement for non-JPEG formats if ( config.get(CONF_PIXEL_FORMAT, "JPEG") != "JPEG" @@ -368,7 +370,7 @@ SETTERS = { } -async def to_code(config): +async def to_code(config: ConfigType) -> None: cg.add_define("USE_CAMERA") var = cg.new_Pvariable(config[CONF_ID]) await setup_entity(var, config, "camera") diff --git a/esphome/components/esp32_camera_web_server/__init__.py b/esphome/components/esp32_camera_web_server/__init__.py index da260ad7a1..55ace66681 100644 --- a/esphome/components/esp32_camera_web_server/__init__.py +++ b/esphome/components/esp32_camera_web_server/__init__.py @@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: server = cg.new_Pvariable(config[CONF_ID]) cg.add(server.set_port(config[CONF_PORT])) cg.add(server.set_mode(config[CONF_MODE])) diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index 7245ba7513..2272459fb8 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -1,4 +1,5 @@ import math +from typing import Any from esphome import pins import esphome.codegen as cg @@ -26,6 +27,7 @@ from esphome.const import ( CONF_TX_PIN, CONF_TX_QUEUE_LEN, ) +from esphome.types import ConfigType CODEOWNERS = ["@Sympatron"] DEPENDENCIES = ["esp32"] @@ -88,7 +90,7 @@ CAN_SPEEDS = { } -def validate_bit_rate(value): +def validate_bit_rate(value: Any) -> str: variant = get_esp32_variant() if variant not in CAN_SPEEDS: raise cv.Invalid(f"{variant} is not supported by component {esp32_can_ns}") @@ -112,7 +114,7 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( ) -def get_default_tx_enqueue_timeout(bit_rate): +def get_default_tx_enqueue_timeout(bit_rate: str) -> int: bit_rate_numeric = canbus.get_rate(bit_rate) bits_per_packet = 140 # ~max CAN message length ms_per_packet = bits_per_packet / bit_rate_numeric * 1000 @@ -121,7 +123,7 @@ def get_default_tx_enqueue_timeout(bit_rate): ) # ~10 packet lengths, min 1ms, max 1000ms -async def to_code(config): +async def to_code(config: ConfigType) -> None: # Legacy driver component provides driver/twai.h header include_builtin_idf_component("driver") # Also enable esp_driver_twai for future migration to new API diff --git a/esphome/components/esp32_dac/output.py b/esphome/components/esp32_dac/output.py index 7c63d7bd11..c87a9e2a1d 100644 --- a/esphome/components/esp32_dac/output.py +++ b/esphome/components/esp32_dac/output.py @@ -9,6 +9,7 @@ from esphome.components.esp32 import ( ) import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN +from esphome.types import ConfigType DEPENDENCIES = ["esp32"] @@ -18,7 +19,7 @@ DAC_PINS = { } -def valid_dac_pin(value): +def valid_dac_pin(value: ConfigType) -> ConfigType: variant = get_esp32_variant() try: valid_pins = DAC_PINS[variant] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: include_builtin_idf_component("esp_driver_dac") var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index ad2f057163..32eb166014 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -4,6 +4,7 @@ from esphome.components import binary_sensor, esp32_ble, improv_base, output from esphome.components.esp32_ble import BTLoggers import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_ON_START, CONF_ON_STATE, CONF_TRIGGER_ID +from esphome.types import ConfigType AUTO_LOAD = ["esp32_ble_server", "improv_base"] CODEOWNERS = ["@jesserockz"] @@ -106,7 +107,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: # Register the loggers this component needs esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP) diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index d5f4efbc02..3ef4c7ba13 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -37,7 +37,7 @@ esphome = cg.esphome_ns.namespace("esphome") ESPHomeOTAComponent = esphome.class_("ESPHomeOTAComponent", OTAComponent) -def ota_esphome_final_validate(config): +def ota_esphome_final_validate(config: ConfigType) -> None: full_conf = fv.full_config.get() full_ota_conf = full_conf[CONF_OTA] new_ota_conf = [] diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index cd5904f501..a44a609d3c 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -10,7 +10,10 @@ from esphome.components.network import ( get_priority_interfaces_from_full_config, ip_address_literal, ) -from esphome.config_helpers import filter_source_files_from_platform +from esphome.config_helpers import ( + filter_source_files_from_defines, + filter_source_files_from_platform, +) import esphome.config_validation as cv from esphome.const import ( CONF_ADDRESS, @@ -821,8 +824,15 @@ _platform_filter = filter_source_files_from_platform( ) +# The custom W5500 SPI driver is fully #ifdef'd on USE_ESP32 and +# USE_ETHERNET_W5500 (the platform filter map above handles non-ESP32). +_define_filter = filter_source_files_from_defines( + {"w5500_custom_spi.cpp": "USE_ETHERNET_W5500"} +) + + def _filter_source_files() -> list[str]: - excluded = _platform_filter() + excluded = _platform_filter() + _define_filter() eth_data = CORE.data.get(KEY_ETHERNET, {}) eth_type = eth_data.get(ETHERNET_TYPE_KEY) # Only compile the custom JL1101 driver when JL1101 is configured @@ -836,12 +846,8 @@ def _filter_source_files() -> list[str]: # to avoid shadowing. Native IDF builds always need the custom driver. if cv.Version(5, 4, 2) <= idf_version() < cv.Version(6, 0, 0): excluded.append("esp_eth_phy_jl1101.c") - # The custom W5500 SPI driver is fully #ifdef'd on USE_ESP32 and - # USE_ETHERNET_W5500 (the platform filter map above handles non-ESP32); - # skip it entirely for the other ethernet types. - if eth_type != "W5500": - excluded.append("w5500_custom_spi.cpp") - return excluded + # The platform and define filters can both name the same file + return list(dict.fromkeys(excluded)) FILTER_SOURCE_FILES = _filter_source_files diff --git a/esphome/components/ethernet_info/text_sensor.py b/esphome/components/ethernet_info/text_sensor.py index 8c20cf332c..66483cdb85 100644 --- a/esphome/components/ethernet_info/text_sensor.py +++ b/esphome/components/ethernet_info/text_sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( CONF_MAC_ADDRESS, ENTITY_CATEGORY_DIAGNOSTIC, ) +from esphome.types import ConfigType DEPENDENCIES = ["ethernet"] @@ -46,7 +47,7 @@ CONFIG_SCHEMA = cv.Schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: # Request Ethernet IP state listener slots - one per sensor type if CONF_IP_ADDRESS in config: ethernet.request_ethernet_ip_state_listener() diff --git a/esphome/components/exposure_notifications/__init__.py b/esphome/components/exposure_notifications/__init__.py index 6cb5b750dd..4f7e698e23 100644 --- a/esphome/components/exposure_notifications/__init__.py +++ b/esphome/components/exposure_notifications/__init__.py @@ -58,7 +58,7 @@ CONFIG_SCHEMA = cv.Schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: for conf in config.get(CONF_ON_EXPOSURE_NOTIFICATION, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) await automation.build_automation(trigger, [(ExposureNotification, "x")], conf) diff --git a/esphome/components/ezo/sensor.py b/esphome/components/ezo/sensor.py index b931885149..d1ee57a09b 100644 --- a/esphome/components/ezo/sensor.py +++ b/esphome/components/ezo/sensor.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, sensor import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType CODEOWNERS = ["@ssieb"] @@ -58,7 +59,7 @@ _CALLBACK_AUTOMATIONS = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await sensor.register_sensor(var, config) diff --git a/esphome/components/fastled_base/__init__.py b/esphome/components/fastled_base/__init__.py index a26a235da7..e2fc8578cd 100644 --- a/esphome/components/fastled_base/__init__.py +++ b/esphome/components/fastled_base/__init__.py @@ -8,6 +8,8 @@ from esphome.const import ( CONF_RGB_ORDER, ) from esphome.core import CORE +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CODEOWNERS = ["@OttoWinter"] fastled_base_ns = cg.esphome_ns.namespace("fastled_base") @@ -34,7 +36,7 @@ BASE_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend( ).extend(cv.COMPONENT_SCHEMA) -async def new_fastled_light(config): +async def new_fastled_light(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) await cg.register_component(var, config) diff --git a/esphome/components/fastled_clockless/light.py b/esphome/components/fastled_clockless/light.py index aa2172bf88..56c1ee93fa 100644 --- a/esphome/components/fastled_clockless/light.py +++ b/esphome/components/fastled_clockless/light.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_RGB_ORDER, Framework, ) +from esphome.types import ConfigType AUTO_LOAD = ["fastled_base"] @@ -41,7 +42,7 @@ CHIPSETS = [ ] -def _validate(value): +def _validate(value: ConfigType) -> ConfigType: if value[CONF_CHIPSET] == "NEOPIXEL" and CONF_RGB_ORDER in value: raise cv.Invalid("NEOPIXEL doesn't support RGB order") return value @@ -73,7 +74,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await fastled_base.new_fastled_light(config) rgb_order = None diff --git a/esphome/components/fastled_spi/light.py b/esphome/components/fastled_spi/light.py index e863d33846..1c6b6e7148 100644 --- a/esphome/components/fastled_spi/light.py +++ b/esphome/components/fastled_spi/light.py @@ -11,6 +11,7 @@ from esphome.const import ( CONF_RGB_ORDER, Framework, ) +from esphome.types import ConfigType AUTO_LOAD = ["fastled_base"] @@ -52,7 +53,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await fastled_base.new_fastled_light(config) rgb_order = cg.RawExpression(config.get(CONF_RGB_ORDER, "RGB")) diff --git a/esphome/components/feedback/cover.py b/esphome/components/feedback/cover.py index 856818280f..032d01c8e5 100644 --- a/esphome/components/feedback/cover.py +++ b/esphome/components/feedback/cover.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_STOP_ACTION, CONF_UPDATE_INTERVAL, ) +from esphome.types import ConfigType CONF_OPEN_SENSOR = "open_sensor" CONF_CLOSE_SENSOR = "close_sensor" @@ -29,7 +30,7 @@ endstop_ns = cg.esphome_ns.namespace("feedback") FeedbackCover = endstop_ns.class_("FeedbackCover", cover.Cover, cg.Component) -def validate_infer_endstop(config): +def validate_infer_endstop(config: ConfigType) -> ConfigType: if config[CONF_INFER_ENDSTOP_FROM_MOVEMENT] is True: if config[CONF_HAS_BUILT_IN_ENDSTOP] is False: raise cv.Invalid( @@ -95,7 +96,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await cover.new_cover(config) await cg.register_component(var, config) diff --git a/esphome/components/fs3000/sensor.py b/esphome/components/fs3000/sensor.py index a168a36c31..8c389a2593 100644 --- a/esphome/components/fs3000/sensor.py +++ b/esphome/components/fs3000/sensor.py @@ -4,6 +4,7 @@ import esphome.codegen as cg from esphome.components import i2c, sensor import esphome.config_validation as cv from esphome.const import CONF_MODEL, DEVICE_CLASS_WIND_SPEED, STATE_CLASS_MEASUREMENT +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] CODEOWNERS = ["@kahrendt"] @@ -38,7 +39,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ft5x06/touchscreen/__init__.py b/esphome/components/ft5x06/touchscreen/__init__.py index e94791da4e..3ebff693c0 100644 --- a/esphome/components/ft5x06/touchscreen/__init__.py +++ b/esphome/components/ft5x06/touchscreen/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN +from esphome.types import ConfigType from .. import ft5x06_ns @@ -22,7 +23,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( ).extend(i2c.i2c_device_schema(0x48)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await i2c.register_i2c_device(var, config) await touchscreen.register_touchscreen(var, config) diff --git a/esphome/components/ft63x6/touchscreen.py b/esphome/components/ft63x6/touchscreen.py index 7615b3046f..0d8537bde9 100644 --- a/esphome/components/ft63x6/touchscreen.py +++ b/esphome/components/ft63x6/touchscreen.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN, CONF_THRESHOLD +from esphome.types import ConfigType CODEOWNERS = ["@gpambrozio"] DEPENDENCIES = ["i2c"] @@ -31,7 +32,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/fujitsu_general/climate.py b/esphome/components/fujitsu_general/climate.py index a104eafbcc..c2c41730e3 100644 --- a/esphome/components/fujitsu_general/climate.py +++ b/esphome/components/fujitsu_general/climate.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir"] @@ -11,5 +12,5 @@ FujitsuGeneralClimate = fujitsu_general_ns.class_( CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(FujitsuGeneralClimate) -async def to_code(config): +async def to_code(config: ConfigType) -> None: await climate_ir.new_climate_ir(config) diff --git a/esphome/components/gcja5/sensor.py b/esphome/components/gcja5/sensor.py index e4de7721c6..49907443ff 100644 --- a/esphome/components/gcja5/sensor.py +++ b/esphome/components/gcja5/sensor.py @@ -18,6 +18,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ) +from esphome.types import ConfigType CODEOWNERS = ["@gcormier"] DEPENDENCIES = ["uart"] @@ -111,7 +112,7 @@ TYPES = { } -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/gl_r01_i2c/sensor.py b/esphome/components/gl_r01_i2c/sensor.py index 6a8d47213c..73f7339e66 100644 --- a/esphome/components/gl_r01_i2c/sensor.py +++ b/esphome/components/gl_r01_i2c/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_MILLIMETER, ) +from esphome.types import ConfigType CODEOWNERS = ["@pkejval"] DEPENDENCIES = ["i2c"] @@ -29,7 +30,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await sensor.register_sensor(var, config) diff --git a/esphome/components/gp2y1010au0f/sensor.py b/esphome/components/gp2y1010au0f/sensor.py index 4ff8a38226..3121aa1de5 100644 --- a/esphome/components/gp2y1010au0f/sensor.py +++ b/esphome/components/gp2y1010au0f/sensor.py @@ -9,6 +9,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ) +from esphome.types import ConfigType DEPENDENCIES = ["output"] AUTO_LOAD = ["voltage_sampler"] @@ -43,7 +44,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/gp8403/__init__.py b/esphome/components/gp8403/__init__.py index 83859a4030..17c88b6875 100644 --- a/esphome/components/gp8403/__init__.py +++ b/esphome/components/gp8403/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_MODEL, CONF_VOLTAGE +from esphome.types import ConfigType CODEOWNERS = ["@jesserockz", "@sebydocky"] DEPENDENCIES = ["i2c"] @@ -38,7 +39,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/gp8403/output/__init__.py b/esphome/components/gp8403/output/__init__.py index 5245c405db..432f387b1b 100644 --- a/esphome/components/gp8403/output/__init__.py +++ b/esphome/components/gp8403/output/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c, output import esphome.config_validation as cv from esphome.const import CONF_CHANNEL, CONF_ID +from esphome.types import ConfigType from .. import CONF_GP8403_ID, GP8403Component, gp8403_ns @@ -20,7 +21,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( ).extend(cv.COMPONENT_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await output.register_output(var, config) diff --git a/esphome/components/gpio/binary_sensor/__init__.py b/esphome/components/gpio/binary_sensor/__init__.py index 7cc16eb5b2..8a40e4e732 100644 --- a/esphome/components/gpio/binary_sensor/__init__.py +++ b/esphome/components/gpio/binary_sensor/__init__.py @@ -133,6 +133,7 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_pin(pin)) if config[CONF_USE_INTERRUPT]: + cg.add_define("USE_GPIO_BINARY_SENSOR_INTERRUPT") cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE])) else: cg.add(var.set_use_interrupt(False)) diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp index ff07d76901..9d044dca2d 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp @@ -7,6 +7,7 @@ namespace esphome::gpio { static const char *const TAG = "gpio.binary_sensor"; #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT // Interrupt type strings indexed by edge-triggered InterruptType values: // indices 1-3: RISING_EDGE, FALLING_EDGE, ANY_EDGE; other values (e.g. level-triggered) map to UNKNOWN (index 0). PROGMEM_STRING_TABLE(InterruptTypeStrings, "UNKNOWN", "RISING_EDGE", "FALLING_EDGE", "ANY_EDGE"); @@ -19,7 +20,9 @@ static const LogString *gpio_mode_to_string(bool use_interrupt) { return use_interrupt ? LOG_STR("interrupt") : LOG_STR("polling"); } #endif +#endif +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) { bool new_state = arg->isr_pin_.digital_read(); if (new_state != arg->state_) { @@ -43,28 +46,36 @@ void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, Component *component) { // Attach interrupt - from this point on, any changes will be caught by the interrupt pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, this->interrupt_type_); } +#endif // USE_GPIO_BINARY_SENSOR_INTERRUPT void GPIOBinarySensor::setup() { +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT if (this->store_.use_interrupt_) { auto *internal_pin = static_cast(this->pin_); this->store_.setup(internal_pin, this); this->publish_initial_state(this->store_.get_state()); - } else { - this->pin_->setup(); - this->publish_initial_state(this->pin_->digital_read()); + return; } +#endif + this->pin_->setup(); + this->publish_initial_state(this->pin_->digital_read()); } void GPIOBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this); LOG_PIN(" Pin: ", this->pin_); +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT ESP_LOGCONFIG(TAG, " Mode: %s", LOG_STR_ARG(gpio_mode_to_string(this->store_.use_interrupt_))); if (this->store_.use_interrupt_) { ESP_LOGCONFIG(TAG, " Interrupt Type: %s", LOG_STR_ARG(interrupt_type_to_string(this->store_.interrupt_type_))); } +#else + ESP_LOGCONFIG(TAG, " Mode: polling"); +#endif } void GPIOBinarySensor::loop() { +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT if (this->store_.use_interrupt_) { if (this->store_.is_changed()) { // Clear the flag immediately to minimize the window where we might miss changes @@ -78,9 +89,10 @@ void GPIOBinarySensor::loop() { // No changes, disable the loop until the next interrupt this->disable_loop(); } - } else { - this->publish_state(this->pin_->digital_read()); + return; } +#endif + this->publish_state(this->pin_->digital_read()); } float GPIOBinarySensor::get_setup_priority() const { return setup_priority::HARDWARE; } diff --git a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h index 100edb4cca..956443fab5 100644 --- a/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h +++ b/esphome/components/gpio/binary_sensor/gpio_binary_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/components/binary_sensor/binary_sensor.h" @@ -10,6 +11,7 @@ namespace esphome::gpio { // Store class for ISR data and configuration (no vtables, ISR-safe) class GPIOBinarySensorStore { public: +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT void setup(InternalGPIOPin *pin, Component *component); static void gpio_intr(GPIOBinarySensorStore *arg); @@ -29,15 +31,18 @@ class GPIOBinarySensorStore { // Separate method to clear the flag this->changed_ = false; } +#endif protected: friend class GPIOBinarySensor; +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT ISRInternalGPIOPin isr_pin_; Component *component_{nullptr}; // Pointer to the component for enable_loop_soon_any_context() volatile bool state_{false}; volatile bool changed_{false}; - bool use_interrupt_{true}; gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE}; + bool use_interrupt_{true}; +#endif }; class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Component { @@ -46,8 +51,14 @@ class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Compon // Interrupts are only detached on reboot when memory is cleared anyway. void set_pin(GPIOPin *pin) { this->pin_ = pin; } +#ifdef USE_GPIO_BINARY_SENSOR_INTERRUPT void set_use_interrupt(bool use_interrupt) { this->store_.use_interrupt_ = use_interrupt; } void set_interrupt_type(gpio::InterruptType type) { this->store_.interrupt_type_ = type; } +#else + // Polling-only build: codegen still emits set_use_interrupt(false) calls, + // so keep the setter as an inlined no-op instead of storing the flag. + void set_use_interrupt(bool /*use_interrupt*/) {} +#endif // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) /// Setup pin diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index ab48417a4e..94a36a0afa 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -16,6 +16,7 @@ from esphome.const import ( UNIT_KILOMETER_PER_HOUR, UNIT_METER, ) +from esphome.types import ConfigType CONF_GPS_ID = "gps_id" CONF_HDOP = "hdop" @@ -93,7 +94,7 @@ CONFIG_SCHEMA = cv.All( FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/gps/time/__init__.py b/esphome/components/gps/time/__init__.py index bdeeb86e00..faa06e6ae3 100644 --- a/esphome/components/gps/time/__init__.py +++ b/esphome/components/gps/time/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import time as time_ import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType from .. import CONF_GPS_ID, GPS, GPSListener, gps_ns @@ -19,7 +20,7 @@ CONFIG_SCHEMA = time_.TIME_SCHEMA.extend( ).extend(cv.polling_component_schema("5min")) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await time_.register_time(var, config) await cg.register_component(var, config) diff --git a/esphome/components/graphical_display_menu/__init__.py b/esphome/components/graphical_display_menu/__init__.py index 56b720e75c..668a0d74d1 100644 --- a/esphome/components/graphical_display_menu/__init__.py +++ b/esphome/components/graphical_display_menu/__init__.py @@ -15,6 +15,7 @@ from esphome.const import ( CONF_ID, CONF_TRIGGER_ID, ) +from esphome.types import ConfigType CONF_MENU_ITEM_VALUE = "menu_item_value" CONF_ON_REDRAW = "on_redraw" @@ -59,7 +60,7 @@ CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/gree/climate.py b/esphome/components/gree/climate.py index 0892155fd2..356845a7a6 100644 --- a/esphome/components/gree/climate.py +++ b/esphome/components/gree/climate.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv from esphome.const import CONF_MODEL +from esphome.types import ConfigType from . import gree_ns @@ -28,6 +29,6 @@ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(GreeClimate).extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await climate_ir.new_climate_ir(config) cg.add(var.set_model(config[CONF_MODEL])) diff --git a/esphome/components/gree/switch/__init__.py b/esphome/components/gree/switch/__init__.py index 111fea65d2..9bec3751d6 100644 --- a/esphome/components/gree/switch/__init__.py +++ b/esphome/components/gree/switch/__init__.py @@ -3,6 +3,7 @@ from esphome.components import switch import esphome.config_validation as cv from esphome.const import CONF_LIGHT, DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG import esphome.final_validate as fv +from esphome.types import ConfigType from .. import gree_ns from ..climate import CONF_MODEL, GreeClimate @@ -48,7 +49,7 @@ CONFIG_SCHEMA = cv.Schema( ) -def _validate_model(config): +def _validate_model(config: ConfigType) -> None: full_config = fv.full_config.get() climate_path = full_config.get_path_for_id(config[CONF_GREE_ID])[:-1] climate_conf = full_config.get_config_for_path(climate_path) @@ -63,7 +64,7 @@ def _validate_model(config): FINAL_VALIDATE_SCHEMA = _validate_model -async def to_code(config): +async def to_code(config: ConfigType) -> None: parent = await cg.get_variable(config[CONF_GREE_ID]) for conf_key, name, bit_mask, _ in SWITCH_CONFIGS: diff --git a/esphome/components/grove_gas_mc_v2/sensor.py b/esphome/components/grove_gas_mc_v2/sensor.py index 0c35047850..da687c4cd3 100644 --- a/esphome/components/grove_gas_mc_v2/sensor.py +++ b/esphome/components/grove_gas_mc_v2/sensor.py @@ -18,6 +18,7 @@ from esphome.const import ( UNIT_MICROGRAMS_PER_CUBIC_METER, UNIT_PARTS_PER_MILLION, ) +from esphome.types import ConfigType CODEOWNERS = ["@YorkshireIoT"] DEPENDENCIES = ["i2c"] @@ -66,7 +67,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/growatt_solar/sensor.py b/esphome/components/growatt_solar/sensor.py index d62486f5ec..2e2b218730 100644 --- a/esphome/components/growatt_solar/sensor.py +++ b/esphome/components/growatt_solar/sensor.py @@ -170,7 +170,7 @@ def _final_validate(config: ConfigType) -> None: FINAL_VALIDATE_SCHEMA = _final_validate -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await modbus.register_modbus_client_device(var, config) diff --git a/esphome/components/gsl3670/touchscreen.py b/esphome/components/gsl3670/touchscreen.py index ccccf06d69..703887864b 100644 --- a/esphome/components/gsl3670/touchscreen.py +++ b/esphome/components/gsl3670/touchscreen.py @@ -174,7 +174,7 @@ def _extract_firmware_ref(entry: ConfigType) -> RemoteFile | None: PREFETCH_FILES = external_files.single_stage_prefetch(_extract_firmware_ref) -def _config_schema(config): +def _config_schema(config: ConfigType) -> ConfigType: model_option = { cv.Optional(CONF_MODEL, default="CUSTOM"): cv.one_of(*MODELS, upper=True) } @@ -206,7 +206,7 @@ def _config_schema(config): CONFIG_SCHEMA = _config_schema -def _read_firmware(config) -> bytes: +def _read_firmware(config: ConfigType) -> bytes: path = firmware_path(config[CONF_FIRMWARE]) data = path.read_bytes() LOGGER.info( @@ -221,7 +221,7 @@ def _read_firmware(config) -> bytes: # --------------------------------------------------------------------------- # Code generation # --------------------------------------------------------------------------- -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/gt911/binary_sensor/__init__.py b/esphome/components/gt911/binary_sensor/__init__.py index 941b7bb847..95c072977e 100644 --- a/esphome/components/gt911/binary_sensor/__init__.py +++ b/esphome/components/gt911/binary_sensor/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv from esphome.const import CONF_INDEX +from esphome.types import ConfigType from .. import gt911_ns from ..touchscreen import GT911ButtonListener, GT911Touchscreen @@ -24,7 +25,7 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(GT911Button).extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await cg.register_parented(var, config[CONF_GT911_ID]) diff --git a/esphome/components/gt911/touchscreen/__init__.py b/esphome/components/gt911/touchscreen/__init__.py index b850eeea8b..fa929d4ba0 100644 --- a/esphome/components/gt911/touchscreen/__init__.py +++ b/esphome/components/gt911/touchscreen/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN +from esphome.types import ConfigType from .. import gt911_ns @@ -22,7 +23,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( ).extend(i2c.i2c_device_schema(0x5D)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 8eafe1d9d6..dcea1afd04 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -224,7 +224,7 @@ def _final_validate(config: ConfigType) -> None: FINAL_VALIDATE_SCHEMA = _final_validate -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await modbus.register_modbus_client_device(var, config) diff --git a/esphome/components/hdc1080/sensor.py b/esphome/components/hdc1080/sensor.py index e47a88545b..b2b6dc533a 100644 --- a/esphome/components/hdc1080/sensor.py +++ b/esphome/components/hdc1080/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/hdc2010/sensor.py b/esphome/components/hdc2010/sensor.py index 15e19f2cc8..ad0311fb4f 100644 --- a/esphome/components/hdc2010/sensor.py +++ b/esphome/components/hdc2010/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/hdc2080/sensor.py b/esphome/components/hdc2080/sensor.py index 777fc51cba..b5388b4c2b 100644 --- a/esphome/components/hdc2080/sensor.py +++ b/esphome/components/hdc2080/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -43,7 +44,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/he60r/cover.py b/esphome/components/he60r/cover.py index a3a1b19f5a..4cb635b047 100644 --- a/esphome/components/he60r/cover.py +++ b/esphome/components/he60r/cover.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import cover, uart import esphome.config_validation as cv from esphome.const import CONF_CLOSE_DURATION, CONF_OPEN_DURATION +from esphome.types import ConfigType he60r_ns = cg.esphome_ns.namespace("he60r") HE60rCover = he60r_ns.class_("HE60rCover", cover.Cover, cg.Component) @@ -33,7 +34,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await cover.new_cover(config) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 21f7ea6393..2583839ca8 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -125,7 +125,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await climate_ir.new_climate_ir(config) cg.add(var.set_protocol(config[CONF_PROTOCOL])) cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT])) diff --git a/esphome/components/hitachi_ac344/climate.py b/esphome/components/hitachi_ac344/climate.py index ebdf4e8db4..15da73b79c 100644 --- a/esphome/components/hitachi_ac344/climate.py +++ b/esphome/components/hitachi_ac344/climate.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir"] @@ -9,5 +10,5 @@ HitachiClimate = hitachi_ac344_ns.class_("HitachiClimate", climate_ir.ClimateIR) CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(HitachiClimate) -async def to_code(config): +async def to_code(config: ConfigType) -> None: await climate_ir.new_climate_ir(config) diff --git a/esphome/components/hitachi_ac424/climate.py b/esphome/components/hitachi_ac424/climate.py index fde4e77545..d2a66223b1 100644 --- a/esphome/components/hitachi_ac424/climate.py +++ b/esphome/components/hitachi_ac424/climate.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import climate_ir +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir"] @@ -9,5 +10,5 @@ HitachiClimate = hitachi_ac424_ns.class_("HitachiClimate", climate_ir.ClimateIR) CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(HitachiClimate) -async def to_code(config): +async def to_code(config: ConfigType) -> None: await climate_ir.new_climate_ir(config) diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index 1d793ac6b1..384477be3d 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -27,6 +27,7 @@ from esphome.const import ( UNIT_WATT_HOURS, ) from esphome.core import CORE +from esphome.types import ConfigType AUTO_LOAD = ["pulse_counter"] @@ -92,7 +93,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.polling_component_schema("60s")) -async def to_code(config): +async def to_code(config: ConfigType) -> None: if CORE.is_esp32: include_builtin_idf_component("esp_driver_pcnt") diff --git a/esphome/components/hlw8032/sensor.py b/esphome/components/hlw8032/sensor.py index 846c9a398b..7b069d85d0 100644 --- a/esphome/components/hlw8032/sensor.py +++ b/esphome/components/hlw8032/sensor.py @@ -21,6 +21,7 @@ from esphome.const import ( UNIT_VOLT_AMPS, UNIT_WATT, ) +from esphome.types import ConfigType DEPENDENCIES = ["uart"] @@ -73,7 +74,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/hm3301/sensor.py b/esphome/components/hm3301/sensor.py index 9546ae1c3c..2fa82b2710 100644 --- a/esphome/components/hm3301/sensor.py +++ b/esphome/components/hm3301/sensor.py @@ -17,6 +17,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_MICROGRAMS_PER_CUBIC_METER, ) +from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -32,7 +33,7 @@ HM3301Component = hm3301_ns.class_( UNIT_INDEX = "index" -def _validate(config): +def _validate(config: ConfigType) -> ConfigType: if CONF_AQI in config and CONF_PM_2_5 not in config: raise cv.Invalid("AQI sensor requires PM 2.5") if CONF_AQI in config and CONF_PM_10_0 not in config: @@ -86,7 +87,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/honeywell_hih_i2c/sensor.py b/esphome/components/honeywell_hih_i2c/sensor.py index 93ae2b6056..5250e1c1c7 100644 --- a/esphome/components/honeywell_hih_i2c/sensor.py +++ b/esphome/components/honeywell_hih_i2c/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/honeywellabp/sensor.py b/esphome/components/honeywellabp/sensor.py index 25d03d31a6..4b116f0f16 100644 --- a/esphome/components/honeywellabp/sensor.py +++ b/esphome/components/honeywellabp/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType DEPENDENCIES = ["spi"] CODEOWNERS = ["@RubyBailey"] @@ -50,7 +51,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/honeywellabp2_i2c/sensor.py b/esphome/components/honeywellabp2_i2c/sensor.py index 2708e5d423..299acd4b52 100644 --- a/esphome/components/honeywellabp2_i2c/sensor.py +++ b/esphome/components/honeywellabp2_i2c/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -57,7 +58,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/hrxl_maxsonar_wr/sensor.py b/esphome/components/hrxl_maxsonar_wr/sensor.py index d335d76dfa..e4daacd869 100644 --- a/esphome/components/hrxl_maxsonar_wr/sensor.py +++ b/esphome/components/hrxl_maxsonar_wr/sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_METER, ) +from esphome.types import ConfigType CODEOWNERS = ["@netmikey"] DEPENDENCIES = ["uart"] @@ -23,7 +24,7 @@ CONFIG_SCHEMA = sensor.sensor_schema( ).extend(uart.UART_DEVICE_SCHEMA) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/hte501/sensor.py b/esphome/components/hte501/sensor.py index 17ae3a3d1b..bf9fe4000e 100644 --- a/esphome/components/hte501/sensor.py +++ b/esphome/components/hte501/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType CODEOWNERS = ["@Stock-M"] @@ -44,7 +45,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index ddff954950..470ed332f1 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -196,6 +196,9 @@ std::shared_ptr HttpRequestIDF::perform(const std::string &url, c } container->feed_wdt(); + // IDF is the only backend reusing the container across redirect hops; + // drop the previous hop's headers (Arduino/host collect only the final response) + container->response_headers_.clear(); container->content_length = esp_http_client_fetch_headers(client); container->set_chunked(esp_http_client_is_chunked_response(client)); container->feed_wdt(); diff --git a/esphome/components/htu31d/sensor.py b/esphome/components/htu31d/sensor.py index 638a8d77c5..8960759d9b 100644 --- a/esphome/components/htu31d/sensor.py +++ b/esphome/components/htu31d/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/hub75/boards/__init__.py b/esphome/components/hub75/boards/__init__.py index 52f8864c60..818ee732a3 100644 --- a/esphome/components/hub75/boards/__init__.py +++ b/esphome/components/hub75/boards/__init__.py @@ -49,7 +49,7 @@ class BoardConfig: # Derived field for pin lookup pins: dict[str, int | None] = field(default_factory=dict, init=False, repr=False) - def __post_init__(self): + def __post_init__(self) -> None: """Initialize derived fields and register board.""" self.name = self.name.lower() self.pins = { diff --git a/esphome/components/hub75/display.py b/esphome/components/hub75/display.py index 24b8197073..3522acf049 100644 --- a/esphome/components/hub75/display.py +++ b/esphome/components/hub75/display.py @@ -131,7 +131,7 @@ SCAN_WIRINGS = { } -def _validate_scan_wiring(value): +def _validate_scan_wiring(value: Any) -> str: """Validate scan_wiring against the allowed names.""" value = cv.string(value).upper().replace(" ", "_") @@ -477,7 +477,7 @@ def _build_pins_struct( ) -> cg.StructInitializer: """Build Hub75Pins struct from pin expressions.""" - def pin_cast(pin): + def pin_cast(pin: Any) -> cg.RawExpression: return cg.RawExpression(f"static_cast({pin.get_pin()})") return cg.StructInitializer( diff --git a/esphome/components/hx711/sensor.py b/esphome/components/hx711/sensor.py index a5d11e9241..2739589c66 100644 --- a/esphome/components/hx711/sensor.py +++ b/esphome/components/hx711/sensor.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv from esphome.const import CONF_CLK_PIN, CONF_GAIN, ICON_SCALE, STATE_CLASS_MEASUREMENT +from esphome.types import ConfigType hx711_ns = cg.esphome_ns.namespace("hx711") HX711Sensor = hx711_ns.class_("HX711Sensor", sensor.Sensor, cg.PollingComponent) @@ -34,7 +35,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/hydreon_rgxx/binary_sensor.py b/esphome/components/hydreon_rgxx/binary_sensor.py index f899ce71ce..193db9b20d 100644 --- a/esphome/components/hydreon_rgxx/binary_sensor.py +++ b/esphome/components/hydreon_rgxx/binary_sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv from esphome.const import CONF_ID, DEVICE_CLASS_COLD, DEVICE_CLASS_PROBLEM +from esphome.types import ConfigType from . import HydreonRGxxComponent, hydreon_rgxx_ns @@ -32,7 +33,7 @@ CONFIG_SCHEMA = cv.Schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: main_sensor = await cg.get_variable(config[CONF_HYDREON_RGXX_ID]) bin_component = cg.new_Pvariable(config[CONF_ID], main_sensor) await cg.register_component(bin_component, config) diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index fdb606182f..58e72571ff 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -16,6 +16,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_MILLIMETER, ) +from esphome.types import ConfigType from . import HydreonRGxxComponent, RG15Resolution, RGModel @@ -65,7 +66,7 @@ PROTOCOL_NAMES = { } -def _validate(config): +def _validate(config: ConfigType) -> ConfigType: for conf, models in SUPPORTED_OPTIONS.items(): if conf in config and config[CONF_MODEL] not in models: raise cv.Invalid( @@ -130,7 +131,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/hyt271/sensor.py b/esphome/components/hyt271/sensor.py index bf37646d4f..3f006a65fe 100644 --- a/esphome/components/hyt271/sensor.py +++ b/esphome/components/hyt271/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/i2c_device/__init__.py b/esphome/components/i2c_device/__init__.py index 531c363bd1..f890fefdb9 100644 --- a/esphome/components/i2c_device/__init__.py +++ b/esphome/components/i2c_device/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] CODEOWNERS = ["@gabest11"] @@ -20,7 +21,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(i2c.i2c_device_schema(None)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/i2s_audio/speaker/__init__.py b/esphome/components/i2s_audio/speaker/__init__.py index 1849c376aa..4dc15681bf 100644 --- a/esphome/components/i2s_audio/speaker/__init__.py +++ b/esphome/components/i2s_audio/speaker/__init__.py @@ -1,6 +1,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import audio, esp32, speaker +from esphome.config_helpers import filter_source_files_from_defines import esphome.config_validation as cv from esphome.const import ( CONF_BITS_PER_SAMPLE, @@ -261,3 +262,13 @@ async def to_code(config: ConfigType) -> None: if config[CONF_TIMEOUT] != CONF_NEVER: cg.add(var.set_timeout(config[CONF_TIMEOUT])) cg.add(var.set_buffer_duration(config[CONF_BUFFER_DURATION])) + + +# The SPDIF encoder and speaker are fully #ifdef'd on USE_I2S_AUDIO_SPDIF_MODE, +# set only when spdif_mode is enabled. +FILTER_SOURCE_FILES = filter_source_files_from_defines( + { + "spdif_encoder.cpp": "USE_I2S_AUDIO_SPDIF_MODE", + "i2s_audio_spdif.cpp": "USE_I2S_AUDIO_SPDIF_MODE", + } +) diff --git a/esphome/components/iaqcore/sensor.py b/esphome/components/iaqcore/sensor.py index d3306fd0f8..1b905e4c63 100644 --- a/esphome/components/iaqcore/sensor.py +++ b/esphome/components/iaqcore/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_PARTS_PER_BILLION, UNIT_PARTS_PER_MILLION, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] CODEOWNERS = ["@yozik04"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/improv_base/__init__.py b/esphome/components/improv_base/__init__.py index 5929f2b60a..f132dbacb0 100644 --- a/esphome/components/improv_base/__init__.py +++ b/esphome/components/improv_base/__init__.py @@ -1,4 +1,5 @@ import re +from typing import Any import esphome.codegen as cg import esphome.config_validation as cv @@ -13,7 +14,7 @@ CONF_NEXT_URL = "next_url" VALID_SUBSTITUTIONS = ["esphome_version", "ip_address", "device_name"] -def validate_next_url(value): +def validate_next_url(value: Any) -> str: value = cv.url(value) test = r"{{(?!" + r"\b|".join(VALID_SUBSTITUTIONS) + r"\b)(\w+)}}" result = re.search(test, value) @@ -31,13 +32,13 @@ IMPROV_SCHEMA = cv.Schema( ) -def _process_next_url(url: str): +def _process_next_url(url: str) -> str: if "{{esphome_version}}" in url: url = url.replace("{{esphome_version}}", __version__) return url -async def setup_improv_core(var: MockObj, config: ConfigType, component: str): +async def setup_improv_core(var: MockObj, config: ConfigType, component: str) -> None: if next_url := config.get(CONF_NEXT_URL): cg.add(var.set_next_url(_process_next_url(next_url))) cg.add_define(f"USE_{component.upper()}_NEXT_URL") diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index 3e2a6db1bc..40ef14c6bc 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -6,6 +6,7 @@ import esphome.config_validation as cv from esphome.const import CONF_BAUD_RATE, CONF_HARDWARE_UART, CONF_ID, CONF_LOGGER from esphome.core import CORE import esphome.final_validate as fv +from esphome.types import ConfigType AUTO_LOAD = ["improv_base"] CODEOWNERS = ["@esphome/core"] @@ -22,7 +23,7 @@ CONFIG_SCHEMA = ( ) -def validate_logger(config) -> None: +def validate_logger(config: ConfigType) -> None: logger_conf = fv.full_config.get()[CONF_LOGGER] if logger_conf[CONF_BAUD_RATE] == 0: raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0") @@ -38,7 +39,7 @@ def validate_logger(config) -> None: FINAL_VALIDATE_SCHEMA = validate_logger -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await improv_base.setup_improv_core(var, config, "improv_serial") diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index a191889138..de9c7899cd 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -7,6 +7,7 @@ #include "esphome/core/version.h" #include "esphome/components/logger/logger.h" +#include "esphome/components/wifi/scan_list.h" namespace esphome::improv_serial { @@ -230,31 +231,17 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command return true; } case improv::GET_WIFI_NETWORKS: { - std::vector networks; const auto &results = wifi::global_wifi_component->get_scan_result(); - for (auto &scan : results) { - if (scan.get_is_hidden()) + for (const auto &scan : results) { + bool with_auth = false; + if (!wifi::should_show_scan_entry(results, scan, with_auth)) continue; - const char *ssid_cstr = scan.get_ssid().c_str(); - // Check if we've already sent this SSID - bool duplicate = false; - for (const auto &seen : networks) { - if (strcmp(seen.c_str(), ssid_cstr) == 0) { - duplicate = true; - break; - } - } - if (duplicate) - continue; - // Only allocate std::string after confirming it's not a duplicate - std::string ssid(ssid_cstr); // Send each ssid separately to avoid overflowing the buffer char rssi_buf[5]; // int8_t: -128 to 127, max 4 chars + null *int8_to_str(rssi_buf, scan.get_rssi()) = '\0'; - std::vector data = - improv::build_rpc_response(improv::GET_WIFI_NETWORKS, {ssid, rssi_buf, YESNO(scan.get_with_auth())}, false); + std::vector data = improv::build_rpc_response( + improv::GET_WIFI_NETWORKS, {scan.get_ssid().str(), rssi_buf, YESNO(with_auth)}, false); this->send_response_(data); - networks.push_back(std::move(ssid)); } // Send empty response to signify the end of the list. std::vector data = diff --git a/esphome/components/ina219/sensor.py b/esphome/components/ina219/sensor.py index 621fd62e82..97482f81a0 100644 --- a/esphome/components/ina219/sensor.py +++ b/esphome/components/ina219/sensor.py @@ -18,6 +18,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -70,7 +71,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ina226/sensor.py b/esphome/components/ina226/sensor.py index 2a7b3fc212..4fd98fbcd4 100644 --- a/esphome/components/ina226/sensor.py +++ b/esphome/components/ina226/sensor.py @@ -1,3 +1,5 @@ +from typing import Any + import esphome.codegen as cg from esphome.components import i2c, sensor import esphome.config_validation as cv @@ -18,6 +20,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -54,7 +57,7 @@ ADC_AVG_SAMPLES = { } -def validate_adc_time(value): +def validate_adc_time(value: Any) -> int: value = cv.positive_time_period_microseconds(value).total_microseconds return cv.enum(ADC_TIMES, int=True)(value) @@ -112,7 +115,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ina260/sensor.py b/esphome/components/ina260/sensor.py index b98b4ce6cb..b7b94a248b 100644 --- a/esphome/components/ina260/sensor.py +++ b/esphome/components/ina260/sensor.py @@ -14,6 +14,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] CODEOWNERS = ["@mreditor97"] @@ -52,7 +53,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/ina2xx_i2c/sensor.py b/esphome/components/ina2xx_i2c/sensor.py index 1a470aa628..4bcbca8762 100644 --- a/esphome/components/ina2xx_i2c/sensor.py +++ b/esphome/components/ina2xx_i2c/sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c, ina2xx_base import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_MODEL +from esphome.types import ConfigType AUTO_LOAD = ["ina2xx_base"] CODEOWNERS = ["@latonita"] @@ -28,7 +29,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await ina2xx_base.setup_ina2xx(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ina2xx_spi/sensor.py b/esphome/components/ina2xx_spi/sensor.py index 3ebe2cac73..dc72dce7a9 100644 --- a/esphome/components/ina2xx_spi/sensor.py +++ b/esphome/components/ina2xx_spi/sensor.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import ina2xx_base, spi import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_MODEL +from esphome.types import ConfigType AUTO_LOAD = ["ina2xx_base"] CODEOWNERS = ["@latonita"] @@ -27,7 +28,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await ina2xx_base.setup_ina2xx(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/ina3221/sensor.py b/esphome/components/ina3221/sensor.py index acf7d7cdf0..db8dad2f54 100644 --- a/esphome/components/ina3221/sensor.py +++ b/esphome/components/ina3221/sensor.py @@ -16,6 +16,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -74,7 +75,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/infrared/__init__.py b/esphome/components/infrared/__init__.py index f8e77209b2..d04c82ea96 100644 --- a/esphome/components/infrared/__init__.py +++ b/esphome/components/infrared/__init__.py @@ -14,7 +14,7 @@ from esphome.const import CONF_ID from esphome.core import CORE, coroutine_with_priority from esphome.core.entity_helpers import queue_entity_register, setup_entity from esphome.coroutine import CoroPriority -from esphome.types import ConfigType +from esphome.types import ConfigType, SafeExpType CODEOWNERS = ["@kbx81"] AUTO_LOAD = ["remote_base"] @@ -46,11 +46,11 @@ def infrared_schema(class_: type[cg.MockObjClass]) -> cv.Schema: @setup_entity("infrared") -async def setup_infrared_core_(var: cg.Pvariable, config: ConfigType) -> None: +async def setup_infrared_core_(var: cg.MockObj, config: ConfigType) -> None: """Set up core infrared configuration.""" -async def register_infrared(var: cg.Pvariable, config: ConfigType) -> None: +async def register_infrared(var: cg.MockObj, config: ConfigType) -> None: """Register an infrared device with the core.""" cg.add_define("USE_IR_RF") await cg.register_component(var, config) @@ -59,7 +59,7 @@ async def register_infrared(var: cg.Pvariable, config: ConfigType) -> None: CORE.register_platform_component("infrared", var) -async def new_infrared(config: ConfigType, *args) -> cg.Pvariable: +async def new_infrared(config: ConfigType, *args: SafeExpType) -> cg.MockObj: """Create a new Infrared instance. :param config: Configuration dictionary. diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py index 2dcdb9a118..84a207020e 100644 --- a/esphome/components/inkbird_ibsth1_mini/sensor.py +++ b/esphome/components/inkbird_ibsth1_mini/sensor.py @@ -16,6 +16,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_PERCENT, ) +from esphome.types import ConfigType CODEOWNERS = ["@fkirill"] AUTO_LOAD = ["ble_device_base"] @@ -63,7 +64,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await ble_device_base.register_ble_device(var, config) diff --git a/esphome/components/inkplate/display.py b/esphome/components/inkplate/display.py index a0c0d5dc18..350a0c1652 100644 --- a/esphome/components/inkplate/display.py +++ b/esphome/components/inkplate/display.py @@ -19,6 +19,7 @@ from esphome.const import ( PLATFORM_ESP32, ) import esphome.final_validate as fv +from esphome.types import ConfigType from .const import INKPLATE_10_CUSTOM_WAVEFORMS, WAVEFORMS @@ -68,7 +69,7 @@ MODELS = { CONF_CUSTOM_WAVEFORM = "custom_waveform" -def _validate_custom_waveform(config): +def _validate_custom_waveform(config: ConfigType) -> ConfigType: if CONF_CUSTOM_WAVEFORM in config and config[CONF_MODEL] != "inkplate_10": raise cv.Invalid("Custom waveforms are only supported on the Inkplate 10") return config @@ -146,7 +147,7 @@ CONFIG_SCHEMA = cv.All( ) -def _validate_cpu_frequency(config) -> None: +def _validate_cpu_frequency(config: ConfigType) -> None: esp32_config = fv.full_config.get()[PLATFORM_ESP32] if esp32_config[CONF_CPU_FREQUENCY] != "240MHZ": raise cv.Invalid( @@ -157,7 +158,7 @@ def _validate_cpu_frequency(config) -> None: FINAL_VALIDATE_SCHEMA = _validate_cpu_frequency -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await display.register_display(var, config) diff --git a/esphome/components/internal_temperature/sensor.py b/esphome/components/internal_temperature/sensor.py index 805138071e..d3101f4a7c 100644 --- a/esphome/components/internal_temperature/sensor.py +++ b/esphome/components/internal_temperature/sensor.py @@ -16,6 +16,7 @@ from esphome.const import ( PlatformFramework, ) from esphome.core import CORE +from esphome.types import ConfigType internal_temperature_ns = cg.esphome_ns.namespace("internal_temperature") InternalTemperatureSensor = internal_temperature_ns.class_( @@ -43,7 +44,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) diff --git a/esphome/components/interval/__init__.py b/esphome/components/interval/__init__.py index ac9219ff6a..11c3e15b0d 100644 --- a/esphome/components/interval/__init__.py +++ b/esphome/components/interval/__init__.py @@ -2,6 +2,7 @@ from esphome import automation import esphome.codegen as cg import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERVAL, CONF_STARTUP_DELAY +from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] interval_ns = cg.esphome_ns.namespace("interval") @@ -22,7 +23,7 @@ CONFIG_SCHEMA = automation.validate_automation( ) -async def to_code(config): +async def to_code(config: list[ConfigType]) -> None: for conf in config: var = cg.new_Pvariable(conf[CONF_ID]) await cg.register_component(var, conf) diff --git a/esphome/components/jsn_sr04t/sensor.py b/esphome/components/jsn_sr04t/sensor.py index 214724aa3f..0c4187b823 100644 --- a/esphome/components/jsn_sr04t/sensor.py +++ b/esphome/components/jsn_sr04t/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_METER, ) +from esphome.types import ConfigType CODEOWNERS = ["@Mafus1"] DEPENDENCIES = ["uart"] @@ -49,7 +50,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/json/__init__.py b/esphome/components/json/__init__.py index 3cb89a6cd9..af7eb7e733 100644 --- a/esphome/components/json/__init__.py +++ b/esphome/components/json/__init__.py @@ -1,6 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.core import CORE, CoroPriority, coroutine_with_priority +from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] json_ns = cg.esphome_ns.namespace("json") @@ -11,7 +12,7 @@ CONFIG_SCHEMA = cv.All( @coroutine_with_priority(CoroPriority.BUS) -async def to_code(config): +async def to_code(config: ConfigType) -> None: if CORE.is_esp32: from esphome.components.esp32 import add_idf_component diff --git a/esphome/components/kamstrup_kmp/sensor.py b/esphome/components/kamstrup_kmp/sensor.py index 75ec432ad9..6465012897 100644 --- a/esphome/components/kamstrup_kmp/sensor.py +++ b/esphome/components/kamstrup_kmp/sensor.py @@ -23,6 +23,7 @@ from esphome.const import ( UNIT_KILOWATT, UNIT_LITRE_PER_HOUR, ) +from esphome.types import ConfigType CODEOWNERS = ["@cfeenstra1024"] DEPENDENCIES = ["uart"] @@ -105,7 +106,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/kmeteriso/sensor.py b/esphome/components/kmeteriso/sensor.py index 4f6cb7d091..3e007d1310 100644 --- a/esphome/components/kmeteriso/sensor.py +++ b/esphome/components/kmeteriso/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/kuntze/sensor.py b/esphome/components/kuntze/sensor.py index 2b53e70756..51d23991e2 100644 --- a/esphome/components/kuntze/sensor.py +++ b/esphome/components/kuntze/sensor.py @@ -96,7 +96,7 @@ def _final_validate(config: ConfigType) -> None: FINAL_VALIDATE_SCHEMA = _final_validate -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await modbus.register_modbus_client_device(var, config) diff --git a/esphome/components/lc709203f/sensor.py b/esphome/components/lc709203f/sensor.py index d4e6213425..3319c9be4b 100644 --- a/esphome/components/lc709203f/sensor.py +++ b/esphome/components/lc709203f/sensor.py @@ -18,6 +18,7 @@ from esphome.const import ( UNIT_PERCENT, UNIT_VOLT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -71,7 +72,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/lcd_gpio/display.py b/esphome/components/lcd_gpio/display.py index 0a77daf336..10e21c87d1 100644 --- a/esphome/components/lcd_gpio/display.py +++ b/esphome/components/lcd_gpio/display.py @@ -10,6 +10,7 @@ from esphome.const import ( CONF_RS_PIN, CONF_RW_PIN, ) +from esphome.types import ConfigType AUTO_LOAD = ["lcd_base"] @@ -17,7 +18,7 @@ lcd_gpio_ns = cg.esphome_ns.namespace("lcd_gpio") GPIOLCDDisplay = lcd_gpio_ns.class_("GPIOLCDDisplay", lcd_base.LCDDisplay) -def validate_pin_length(value): +def validate_pin_length(value: list[ConfigType]) -> list[ConfigType]: if len(value) != 4 and len(value) != 8: raise cv.Invalid( f"LCD Displays can either operate in 4-pin or 8-pin mode,not {len(value)}-pin mode" @@ -38,7 +39,7 @@ CONFIG_SCHEMA = lcd_base.LCD_SCHEMA.extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await lcd_base.setup_lcd_display(var, config) pins_ = [await cg.gpio_pin_expression(conf) for conf in config[CONF_DATA_PINS]] diff --git a/esphome/components/lcd_menu/__init__.py b/esphome/components/lcd_menu/__init__.py index 3f3162e31e..88b8ac21d4 100644 --- a/esphome/components/lcd_menu/__init__.py +++ b/esphome/components/lcd_menu/__init__.py @@ -8,6 +8,7 @@ from esphome.components.display_menu_base import ( import esphome.config_validation as cv from esphome.const import CONF_DIMENSIONS, CONF_DISPLAY_ID, CONF_ID from esphome.core.entity_helpers import inherit_property_from +from esphome.types import ConfigType CODEOWNERS = ["@numo68"] @@ -29,7 +30,7 @@ LCDCharacterMenuComponent = lcd_menu_ns.class_( MULTI_CONF = True -def validate_lcd_dimensions(config): +def validate_lcd_dimensions(config: ConfigType) -> ConfigType: if config[CONF_DIMENSIONS][0] < MINIMUM_COLUMNS: raise cv.Invalid( f"LCD display must have at least {MINIMUM_COLUMNS} columns to be usable with the menu" @@ -56,7 +57,7 @@ FINAL_VALIDATE_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) disp = await cg.get_variable(config[CONF_DISPLAY_ID]) diff --git a/esphome/components/lcd_pcf8574/display.py b/esphome/components/lcd_pcf8574/display.py index 410c7f81b7..85a79e99ce 100644 --- a/esphome/components/lcd_pcf8574/display.py +++ b/esphome/components/lcd_pcf8574/display.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c, lcd_base import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_LAMBDA +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] AUTO_LOAD = ["lcd_base"] @@ -18,7 +19,7 @@ CONFIG_SCHEMA = lcd_base.LCD_SCHEMA.extend( ).extend(i2c.i2c_device_schema(0x3F)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await lcd_base.setup_lcd_display(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index 175f5b43cf..dbcc28d64a 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -7,6 +7,7 @@ import esphome.automation as auto import esphome.codegen as cg from esphome.components import mqtt, power_supply, web_server from esphome.components.const import CONF_CHANNEL_COLORS, CONF_IS_WRGB +from esphome.config_helpers import filter_source_files_from_defines import esphome.config_validation as cv from esphome.const import ( CONF_BLUE, @@ -557,3 +558,10 @@ async def new_light(config, *args): @coroutine_with_priority(CoroPriority.CORE) async def to_code(config): cg.add_global(light_ns.using) + + +# light_json_schema.cpp is only used by mqtt and web_server, which both +# auto load json; USE_JSON alone is too broad since other components load it. +FILTER_SOURCE_FILES = filter_source_files_from_defines( + {"light_json_schema.cpp": ("USE_MQTT", "USE_WEBSERVER")} +) diff --git a/esphome/components/lilygo_t5_47/touchscreen/__init__.py b/esphome/components/lilygo_t5_47/touchscreen/__init__.py index 93687846e2..1e70f379a1 100644 --- a/esphome/components/lilygo_t5_47/touchscreen/__init__.py +++ b/esphome/components/lilygo_t5_47/touchscreen/__init__.py @@ -3,6 +3,7 @@ import esphome.codegen as cg from esphome.components import i2c, touchscreen import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTERRUPT_PIN +from esphome.types import ConfigType from .. import lilygo_t5_47_ns @@ -29,7 +30,7 @@ CONFIG_SCHEMA = touchscreen.touchscreen_schema("250ms").extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await touchscreen.register_touchscreen(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/lm75b/sensor.py b/esphome/components/lm75b/sensor.py index 335446b62f..c59515b5b0 100644 --- a/esphome/components/lm75b/sensor.py +++ b/esphome/components/lm75b/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType CODEOWNERS = ["@beormund"] DEPENDENCIES = ["i2c"] @@ -28,7 +29,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/ln882x/__init__.py b/esphome/components/ln882x/__init__.py index 6da5a4969c..b4e179c4a9 100644 --- a/esphome/components/ln882x/__init__.py +++ b/esphome/components/ln882x/__init__.py @@ -28,6 +28,8 @@ from esphome.components.libretiny.const import ( LibreTinyComponent, ) from esphome.core import CORE +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType from .boards import LN882X_BOARD_PINS, LN882X_BOARDS @@ -45,7 +47,7 @@ COMPONENT_DATA = LibreTinyComponent( ) -def _set_core_data(config): +def _set_core_data(config: ConfigType) -> ConfigType: CORE.data[KEY_LIBRETINY] = {} CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] = COMPONENT_DATA return config @@ -62,12 +64,12 @@ PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA CONFIG_SCHEMA.prepend_extra(_set_core_data) -async def to_code(config): +async def to_code(config: ConfigType) -> MockObj: return await libretiny.component_to_code(config) @pins.PIN_SCHEMA_REGISTRY.register("ln882x", PIN_SCHEMA) -async def pin_to_code(config): +async def pin_to_code(config: ConfigType) -> MockObj: return await libretiny.gpio.component_pin_to_code(config) diff --git a/esphome/components/lps22/sensor.py b/esphome/components/lps22/sensor.py index 08e97ee7b7..2eec2c586c 100644 --- a/esphome/components/lps22/sensor.py +++ b/esphome/components/lps22/sensor.py @@ -12,6 +12,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_HECTOPASCAL, ) +from esphome.types import ConfigType CODEOWNERS = ["@nagisa"] DEPENDENCIES = ["i2c"] @@ -44,7 +45,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/lsm6ds/motion.py b/esphome/components/lsm6ds/motion.py index 8c2c5198ea..064cd312a9 100644 --- a/esphome/components/lsm6ds/motion.py +++ b/esphome/components/lsm6ds/motion.py @@ -8,6 +8,7 @@ from esphome.components.const import ( ) from esphome.components.motion import motion_schema, new_motion_component import esphome.config_validation as cv +from esphome.types import ConfigType from . import LSM6DSComponent, lsm6ds_ns @@ -93,7 +94,7 @@ CONFIG_SCHEMA = ( # ── Code generation ────────────────────────────────────────────────────────── -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await new_motion_component(config) # Let the motion platform handle sensor wiring, axis mapping, and polling diff --git a/esphome/components/lsm6ds/sensor.py b/esphome/components/lsm6ds/sensor.py index 980e84a2e9..c0c37f8527 100644 --- a/esphome/components/lsm6ds/sensor.py +++ b/esphome/components/lsm6ds/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_CELSIUS, ) from esphome.cpp_generator import MockObj +from esphome.types import ConfigType from . import CONF_LSM6DS_ID, LSM6DSComponent @@ -28,7 +29,7 @@ CONFIG_SCHEMA = sensor.sensor_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) parent = await cg.get_variable(config[CONF_LSM6DS_ID]) data = MockObj("data") diff --git a/esphome/components/ltr390/sensor.py b/esphome/components/ltr390/sensor.py index 37fceaf984..c3ac90ad11 100644 --- a/esphome/components/ltr390/sensor.py +++ b/esphome/components/ltr390/sensor.py @@ -13,6 +13,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_LUX, ) +from esphome.types import ConfigType CODEOWNERS = ["@sjtrny", "@latonita"] DEPENDENCIES = ["i2c"] @@ -117,7 +118,7 @@ TYPES = { } -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/max31855/sensor.py b/esphome/components/max31855/sensor.py index 35ae28d04c..a52f45a18f 100644 --- a/esphome/components/max31855/sensor.py +++ b/esphome/components/max31855/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType max31855_ns = cg.esphome_ns.namespace("max31855") MAX31855Sensor = max31855_ns.class_( @@ -36,7 +37,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/max31856/sensor.py b/esphome/components/max31856/sensor.py index 679e02b11d..43a2e18db8 100644 --- a/esphome/components/max31856/sensor.py +++ b/esphome/components/max31856/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType max31856_ns = cg.esphome_ns.namespace("max31856") MAX31856Sensor = max31856_ns.class_( @@ -58,7 +59,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/max31865/sensor.py b/esphome/components/max31865/sensor.py index d4498b062f..167a0997e4 100644 --- a/esphome/components/max31865/sensor.py +++ b/esphome/components/max31865/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType CODEOWNERS = ["@DAVe3283"] DEPENDENCIES = ["spi"] @@ -52,7 +53,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/max44009/sensor.py b/esphome/components/max44009/sensor.py index 5aea7f0be2..88673c5d00 100644 --- a/esphome/components/max44009/sensor.py +++ b/esphome/components/max44009/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_LUX, ) +from esphome.types import ConfigType CODEOWNERS = ["@berfenger"] DEPENDENCIES = ["i2c"] @@ -44,7 +45,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/max6675/sensor.py b/esphome/components/max6675/sensor.py index e42abb68d1..94d857cab9 100644 --- a/esphome/components/max6675/sensor.py +++ b/esphome/components/max6675/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType max6675_ns = cg.esphome_ns.namespace("max6675") MAX6675Sensor = max6675_ns.class_( @@ -25,7 +26,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/max7219/display.py b/esphome/components/max7219/display.py index abb20702bd..b21f66b553 100644 --- a/esphome/components/max7219/display.py +++ b/esphome/components/max7219/display.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import display, spi import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_INTENSITY, CONF_LAMBDA, CONF_NUM_CHIPS +from esphome.types import ConfigType DEPENDENCIES = ["spi"] @@ -27,7 +28,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID], config[CONF_NUM_CHIPS]) await spi.register_spi_device(var, config, write_only=True) await display.register_display(var, config) diff --git a/esphome/components/max9611/sensor.py b/esphome/components/max9611/sensor.py index b3a73d8c10..9332274a95 100644 --- a/esphome/components/max9611/sensor.py +++ b/esphome/components/max9611/sensor.py @@ -19,6 +19,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] max9611_ns = cg.esphome_ns.namespace("max9611") @@ -70,7 +71,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mcp23008/__init__.py b/esphome/components/mcp23008/__init__.py index 8ff938114a..3d1480a6f7 100644 --- a/esphome/components/mcp23008/__init__.py +++ b/esphome/components/mcp23008/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c, mcp23x08_base, mcp23xxx_base import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType AUTO_LOAD = ["mcp23x08_base"] CODEOWNERS = ["@jesserockz"] @@ -23,6 +24,6 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await mcp23xxx_base.register_mcp23xxx(config, mcp23x08_base.NUM_PINS) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mcp23016/__init__.py b/esphome/components/mcp23016/__init__.py index 37c5205fe8..5f4b7276d8 100644 --- a/esphome/components/mcp23016/__init__.py +++ b/esphome/components/mcp23016/__init__.py @@ -11,6 +11,8 @@ from esphome.const import ( CONF_NUMBER, CONF_OUTPUT, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType AUTO_LOAD = ["gpio_expander"] DEPENDENCIES = ["i2c"] @@ -33,7 +35,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) @@ -41,7 +43,7 @@ async def to_code(config): cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) -def validate_mode(value): +def validate_mode(value: ConfigType) -> ConfigType: if not (value[CONF_INPUT] or value[CONF_OUTPUT]): raise cv.Invalid("Mode must be either input or output") if value[CONF_INPUT] and value[CONF_OUTPUT]: @@ -64,7 +66,7 @@ MCP23016_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23016, MCP23016_PIN_SCHEMA) -async def mcp23016_pin_to_code(config): +async def mcp23016_pin_to_code(config: ConfigType) -> MockObj: var = cg.new_Pvariable(config[CONF_ID]) parent = await cg.get_variable(config[CONF_MCP23016]) diff --git a/esphome/components/mcp23017/__init__.py b/esphome/components/mcp23017/__init__.py index e5cc1856eb..474d75f6ff 100644 --- a/esphome/components/mcp23017/__init__.py +++ b/esphome/components/mcp23017/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c, mcp23x17_base, mcp23xxx_base import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType AUTO_LOAD = ["mcp23x17_base"] CODEOWNERS = ["@jesserockz"] @@ -23,6 +24,6 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await mcp23xxx_base.register_mcp23xxx(config, mcp23x17_base.NUM_PINS) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mcp23s08/__init__.py b/esphome/components/mcp23s08/__init__.py index 312da79b75..ffc51b8146 100644 --- a/esphome/components/mcp23s08/__init__.py +++ b/esphome/components/mcp23s08/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import mcp23x08_base, mcp23xxx_base, spi import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType AUTO_LOAD = ["mcp23x08_base"] CODEOWNERS = ["@SenexCrenshaw", "@jesserockz"] @@ -26,7 +27,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await mcp23xxx_base.register_mcp23xxx(config, mcp23x08_base.NUM_PINS) cg.add(var.set_device_address(config[CONF_DEVICEADDRESS])) await spi.register_spi_device(var, config) diff --git a/esphome/components/mcp23s17/__init__.py b/esphome/components/mcp23s17/__init__.py index 599bfa0851..d693a64ce8 100644 --- a/esphome/components/mcp23s17/__init__.py +++ b/esphome/components/mcp23s17/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import mcp23x17_base, mcp23xxx_base, spi import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType AUTO_LOAD = ["mcp23x17_base"] CODEOWNERS = ["@SenexCrenshaw", "@jesserockz"] @@ -26,7 +27,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await mcp23xxx_base.register_mcp23xxx(config, mcp23x17_base.NUM_PINS) cg.add(var.set_device_address(config[CONF_DEVICEADDRESS])) await spi.register_spi_device(var, config) diff --git a/esphome/components/mcp2515/canbus.py b/esphome/components/mcp2515/canbus.py index d34a77248c..8bb8918f96 100644 --- a/esphome/components/mcp2515/canbus.py +++ b/esphome/components/mcp2515/canbus.py @@ -3,6 +3,7 @@ from esphome.components import canbus, spi from esphome.components.canbus import CanbusComponent import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_MODE +from esphome.types import ConfigType CODEOWNERS = ["@mvturnho", "@danielschramm"] DEPENDENCIES = ["spi"] @@ -36,7 +37,7 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend( ).extend(spi.spi_device_schema(True)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: rhs = mcp2515.new() var = cg.Pvariable(config[CONF_ID], rhs) await canbus.register_canbus(var, config) diff --git a/esphome/components/mcp3008/__init__.py b/esphome/components/mcp3008/__init__.py index 41ccdd403a..6d1bd5a970 100644 --- a/esphome/components/mcp3008/__init__.py +++ b/esphome/components/mcp3008/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import spi import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType DEPENDENCIES = ["spi"] AUTO_LOAD = ["sensor"] @@ -19,7 +20,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(spi.spi_device_schema(cs_pin_required=True)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await spi.register_spi_device(var, config) diff --git a/esphome/components/mcp3008/sensor/__init__.py b/esphome/components/mcp3008/sensor/__init__.py index 2576ef50e5..de31f81345 100644 --- a/esphome/components/mcp3008/sensor/__init__.py +++ b/esphome/components/mcp3008/sensor/__init__.py @@ -9,6 +9,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) +from esphome.types import ConfigType from .. import MCP3008, mcp3008_ns @@ -43,7 +44,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_parented(var, config[CONF_MCP3008_ID]) await cg.register_component(var, config) diff --git a/esphome/components/mcp3204/__init__.py b/esphome/components/mcp3204/__init__.py index 612297f934..5757bdbfa9 100644 --- a/esphome/components/mcp3204/__init__.py +++ b/esphome/components/mcp3204/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import spi import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_REFERENCE_VOLTAGE +from esphome.types import ConfigType DEPENDENCIES = ["spi"] MULTI_CONF = True @@ -19,7 +20,7 @@ CONFIG_SCHEMA = cv.Schema( ).extend(spi.spi_device_schema(cs_pin_required=True)) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE])) await cg.register_component(var, config) diff --git a/esphome/components/mcp3204/sensor/__init__.py b/esphome/components/mcp3204/sensor/__init__.py index 5f9aa9fdb6..728a1c0611 100644 --- a/esphome/components/mcp3204/sensor/__init__.py +++ b/esphome/components/mcp3204/sensor/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import sensor, voltage_sampler import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_NUMBER +from esphome.types import ConfigType from .. import MCP3204, mcp3204_ns @@ -28,7 +29,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable( config[CONF_ID], config[CONF_NUMBER], diff --git a/esphome/components/mcp3221/sensor.py b/esphome/components/mcp3221/sensor.py index 993876c2c8..30e972d808 100644 --- a/esphome/components/mcp3221/sensor.py +++ b/esphome/components/mcp3221/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_VOLT, ) +from esphome.types import ConfigType AUTO_LOAD = ["voltage_sampler"] DEPENDENCIES = ["i2c"] @@ -42,7 +43,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE])) await cg.register_component(var, config) diff --git a/esphome/components/mcp4725/output.py b/esphome/components/mcp4725/output.py index 5ec6a9d686..0c1224a1a7 100644 --- a/esphome/components/mcp4725/output.py +++ b/esphome/components/mcp4725/output.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c, output import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -19,7 +20,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mcp4728/__init__.py b/esphome/components/mcp4728/__init__.py index da3244be84..f48bdde681 100644 --- a/esphome/components/mcp4728/__init__.py +++ b/esphome/components/mcp4728/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType CODEOWNERS = ["@berfenger"] DEPENDENCIES = ["i2c"] @@ -24,7 +25,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID], config[CONF_STORE_IN_EEPROM]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mcp4728/output/__init__.py b/esphome/components/mcp4728/output/__init__.py index 6f4a41510f..e8cb4c47d6 100644 --- a/esphome/components/mcp4728/output/__init__.py +++ b/esphome/components/mcp4728/output/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import output import esphome.config_validation as cv from esphome.const import CONF_CHANNEL, CONF_GAIN, CONF_ID +from esphome.types import ConfigType from .. import CONF_MCP4728_ID, MCP4728Component, mcp4728_ns @@ -50,7 +51,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: paren = await cg.get_variable(config[CONF_MCP4728_ID]) var = cg.new_Pvariable( config[CONF_ID], diff --git a/esphome/components/mcp47a1/output.py b/esphome/components/mcp47a1/output.py index ebd597cfeb..91bb3b47db 100644 --- a/esphome/components/mcp47a1/output.py +++ b/esphome/components/mcp47a1/output.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import i2c, output import esphome.config_validation as cv from esphome.const import CONF_ID +from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["i2c"] @@ -20,7 +21,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mcp9600/sensor.py b/esphome/components/mcp9600/sensor.py index 65ae5f2eec..5542ffaa6c 100644 --- a/esphome/components/mcp9600/sensor.py +++ b/esphome/components/mcp9600/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType CONF_HOT_JUNCTION = "hot_junction" CONF_COLD_JUNCTION = "cold_junction" @@ -62,7 +63,7 @@ FINAL_VALIDATE_SCHEMA = i2c.final_validate_device_schema( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/mcp9808/sensor.py b/esphome/components/mcp9808/sensor.py index ba6718ca56..1daaa9c131 100644 --- a/esphome/components/mcp9808/sensor.py +++ b/esphome/components/mcp9808/sensor.py @@ -6,6 +6,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType CODEOWNERS = ["@k7hpn"] DEPENDENCIES = ["i2c"] @@ -28,7 +29,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/md5/__init__.py b/esphome/components/md5/__init__.py index 1710b00e66..6a928d1682 100644 --- a/esphome/components/md5/__init__.py +++ b/esphome/components/md5/__init__.py @@ -1,11 +1,12 @@ import esphome.codegen as cg from esphome.core import CORE from esphome.helpers import IS_MACOS +from esphome.types import ConfigType CODEOWNERS = ["@esphome/core"] -async def to_code(config): +async def to_code(config: ConfigType) -> None: cg.add_define("USE_MD5") # Add OpenSSL library for host platform diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index 24bce0cc3c..c9334ea97a 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -31,7 +31,7 @@ MDNSTXTRecord = mdns_ns.struct("MDNSTXTRecord") MDNSService = mdns_ns.struct("MDNSService") -def _remove_id_if_disabled(value): +def _remove_id_if_disabled(value: ConfigType) -> ConfigType: value = value.copy() if value[CONF_DISABLED]: value.pop(CONF_ID) @@ -117,7 +117,7 @@ def mdns_txt_record(key: str, value: str) -> cg.RawExpression: async def _mdns_txt_record_templated( - mdns_comp: cg.Pvariable, key: str, value: Lambda | str + mdns_comp: cg.MockObj, key: str, value: Lambda | str ) -> cg.RawExpression: """Create a mDNS TXT record with support for templated values. @@ -172,7 +172,7 @@ def mdns_service( ) -def enable_mdns_storage(): +def enable_mdns_storage() -> None: """Enable persistent storage of mDNS services in the MDNSComponent. Called by external components (like OpenThread) that need access to @@ -184,7 +184,7 @@ def enable_mdns_storage(): @coroutine_with_priority(CoroPriority.NETWORK_SERVICES) -async def to_code(config): +async def to_code(config: ConfigType) -> None: if config[CONF_DISABLED] is True: return diff --git a/esphome/components/media_source/__init__.py b/esphome/components/media_source/__init__.py index 43256db4af..c9dab7e4d2 100644 --- a/esphome/components/media_source/__init__.py +++ b/esphome/components/media_source/__init__.py @@ -3,7 +3,8 @@ import esphome.config_validation as cv from esphome.const import CONF_ID from esphome.core import CORE from esphome.coroutine import CoroPriority, coroutine_with_priority -from esphome.cpp_generator import MockObjClass +from esphome.cpp_generator import MockObj, MockObjClass +from esphome.types import ConfigType CODEOWNERS = ["@kahrendt"] @@ -16,7 +17,7 @@ media_source_ns = cg.esphome_ns.namespace("media_source") MediaSource = media_source_ns.class_("MediaSource") -async def register_media_source(var, config): +async def register_media_source(var: MockObj, config: ConfigType) -> MockObj: if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) CORE.register_platform_component("media_source", var) @@ -35,6 +36,6 @@ def media_source_schema( @coroutine_with_priority(CoroPriority.CORE) -async def to_code(config): +async def to_code(config: ConfigType) -> None: cg.add_global(media_source_ns.using) cg.add_define("USE_MEDIA_SOURCE") diff --git a/esphome/components/mics_4514/sensor.py b/esphome/components/mics_4514/sensor.py index 09329ebfcf..3ba560c781 100644 --- a/esphome/components/mics_4514/sensor.py +++ b/esphome/components/mics_4514/sensor.py @@ -14,6 +14,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, ) +from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["i2c"] @@ -56,7 +57,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/midea_ir/climate.py b/esphome/components/midea_ir/climate.py index cbf5fae6fe..84bfeab0d4 100644 --- a/esphome/components/midea_ir/climate.py +++ b/esphome/components/midea_ir/climate.py @@ -2,6 +2,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv from esphome.const import CONF_USE_FAHRENHEIT +from esphome.types import ConfigType AUTO_LOAD = ["climate_ir", "coolix"] CODEOWNERS = ["@dudanov"] @@ -17,6 +18,6 @@ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MideaIR).extend( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: 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 8291d70346..2d38351898 100644 --- a/esphome/components/mitsubishi/climate.py +++ b/esphome/components/mitsubishi/climate.py @@ -1,6 +1,7 @@ import esphome.codegen as cg from esphome.components import climate_ir import esphome.config_validation as cv +from esphome.types import ConfigType CODEOWNERS = ["@RubyBailey"] AUTO_LOAD = ["climate_ir"] @@ -58,7 +59,7 @@ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(MitsubishiClimate).ex ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = await climate_ir.new_climate_ir(config) cg.add(var.set_fan_mode(config[CONF_SET_FAN_MODE])) diff --git a/esphome/components/mitsubishi_cn105/automation.h b/esphome/components/mitsubishi_cn105/automation.h index 2fc6ba3c32..f9ca3a47e6 100644 --- a/esphome/components/mitsubishi_cn105/automation.h +++ b/esphome/components/mitsubishi_cn105/automation.h @@ -9,7 +9,7 @@ namespace esphome::mitsubishi_cn105 { template -class SetRemoteTemperatureAction : public Action, public Parented { +class SetRemoteTemperatureAction final : public Action, public Parented { public: TEMPLATABLE_VALUE(float, temperature) @@ -17,12 +17,12 @@ class SetRemoteTemperatureAction : public Action, public Parented -class ClearRemoteTemperatureAction : public Action, public Parented { +class ClearRemoteTemperatureAction final : public Action, public Parented { public: void play(const Ts &...x) override { this->parent_->clear_remote_temperature(); } }; -template class VaneControlAction : public Action { +template class VaneControlAction final : public Action { public: using ApplyFn = void (*)(VaneCall &, const std::remove_cvref_t &...); diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp index 17ff6d34ca..53b8c21de6 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.cpp @@ -74,7 +74,7 @@ climate::ClimateTraits MitsubishiCN105Climate::traits() { traits.add_supported_fan_mode(p.second); } - traits.set_supported_swing_modes(this->supported_swing_modes_); + traits.set_supported_swing_modes(this->swing_mode_manager_.supported_swing_modes()); const bool use_fahrenheit = this->parent_->get_temperature_mapping().get_use_fahrenheit(); traits.set_temperature_unit(use_fahrenheit ? TemperatureUnit::FAHRENHEIT : TemperatureUnit::CELSIUS); @@ -109,33 +109,11 @@ void MitsubishiCN105Climate::control(const climate::ClimateCall &call) { } if (const auto swing_mode = call.get_swing_mode()) { - auto vane = this->last_non_swing_vane_mode_; - auto wide = this->last_non_swing_wide_vane_mode_; - - switch (*swing_mode) { - case climate::CLIMATE_SWING_BOTH: - vane = MitsubishiCN105::VaneMode::SWING; - wide = MitsubishiCN105::WideVaneMode::SWING; - break; - - case climate::CLIMATE_SWING_VERTICAL: - vane = MitsubishiCN105::VaneMode::SWING; - break; - - case climate::CLIMATE_SWING_HORIZONTAL: - wide = MitsubishiCN105::WideVaneMode::SWING; - break; - - case climate::CLIMATE_SWING_OFF: - default: - break; + if (const auto vane = this->swing_mode_manager_.vane_from(*swing_mode)) { + this->parent_->set_vane_mode(*vane); } - - if (this->supported_swing_modes_.count(climate::CLIMATE_SWING_VERTICAL)) { - this->parent_->set_vane_mode(vane); - } - if (this->supported_swing_modes_.count(climate::CLIMATE_SWING_HORIZONTAL)) { - this->parent_->set_wide_vane_mode(wide); + if (const auto wide = this->swing_mode_manager_.wide_vane_from(*swing_mode)) { + this->parent_->set_wide_vane_mode(*wide); } } @@ -166,64 +144,39 @@ void MitsubishiCN105Climate::apply_values_() { ESP_LOGD(TAG, "Unable to map fan mode"); } - if (!this->supported_swing_modes_.empty()) { - bool vertical_swinging = false; - bool horizontal_swinging = false; - - if (this->supported_swing_modes_.count(climate::CLIMATE_SWING_VERTICAL)) { - if (status.vane_mode == MitsubishiCN105::VaneMode::SWING) { - vertical_swinging = true; - } else if (status.vane_mode != MitsubishiCN105::VaneMode::UNKNOWN) { - this->last_non_swing_vane_mode_ = status.vane_mode; - } - } - - if (this->supported_swing_modes_.count(climate::CLIMATE_SWING_HORIZONTAL)) { - if (status.wide_vane_mode == MitsubishiCN105::WideVaneMode::SWING) { - horizontal_swinging = true; - } else if (status.wide_vane_mode != MitsubishiCN105::WideVaneMode::UNKNOWN) { - this->last_non_swing_wide_vane_mode_ = status.wide_vane_mode; - } - } - - if (vertical_swinging && horizontal_swinging) { - this->swing_mode = climate::CLIMATE_SWING_BOTH; - } else if (vertical_swinging) { - this->swing_mode = climate::CLIMATE_SWING_VERTICAL; - } else if (horizontal_swinging) { - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - } else { - this->swing_mode = climate::CLIMATE_SWING_OFF; - } + if (const auto swing_mode = + this->swing_mode_manager_.update_and_get_swing_mode(status.vane_mode, status.wide_vane_mode)) { + this->swing_mode = *swing_mode; } this->publish_state(); } void MitsubishiCN105Climate::set_supported_swing_mode(climate::ClimateSwingMode mode) { - this->supported_swing_modes_.clear(); + climate::ClimateSwingModeMask supported_swing_modes; switch (mode) { case climate::CLIMATE_SWING_VERTICAL: - this->supported_swing_modes_.insert(climate::CLIMATE_SWING_OFF); - this->supported_swing_modes_.insert(climate::CLIMATE_SWING_VERTICAL); + supported_swing_modes.insert(climate::CLIMATE_SWING_OFF); + supported_swing_modes.insert(climate::CLIMATE_SWING_VERTICAL); break; case climate::CLIMATE_SWING_HORIZONTAL: - this->supported_swing_modes_.insert(climate::CLIMATE_SWING_OFF); - this->supported_swing_modes_.insert(climate::CLIMATE_SWING_HORIZONTAL); + supported_swing_modes.insert(climate::CLIMATE_SWING_OFF); + supported_swing_modes.insert(climate::CLIMATE_SWING_HORIZONTAL); break; case climate::CLIMATE_SWING_BOTH: - this->supported_swing_modes_.insert(climate::CLIMATE_SWING_OFF); - this->supported_swing_modes_.insert(climate::CLIMATE_SWING_VERTICAL); - this->supported_swing_modes_.insert(climate::CLIMATE_SWING_HORIZONTAL); - this->supported_swing_modes_.insert(climate::CLIMATE_SWING_BOTH); + supported_swing_modes.insert(climate::CLIMATE_SWING_OFF); + supported_swing_modes.insert(climate::CLIMATE_SWING_VERTICAL); + supported_swing_modes.insert(climate::CLIMATE_SWING_HORIZONTAL); + supported_swing_modes.insert(climate::CLIMATE_SWING_BOTH); break; case climate::CLIMATE_SWING_OFF: default: break; } + this->swing_mode_manager_.set_supported_swing_modes(supported_swing_modes); } } // namespace esphome::mitsubishi_cn105 diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.h b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.h index 5341c2d2d9..cea76278ab 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.h +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_climate.h @@ -6,10 +6,13 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/components/climate/climate.h" +#include "mitsubishi_cn105_swing_mode_manager.h" namespace esphome::mitsubishi_cn105 { -class MitsubishiCN105Climate : public climate::Climate, public Component, public Parented { +class MitsubishiCN105Climate final : public climate::Climate, + public Component, + public Parented { public: void setup() override; void dump_config() override; @@ -25,14 +28,12 @@ class MitsubishiCN105Climate : public climate::Climate, public Component, public protected: void apply_values_(); - climate::ClimateSwingModeMask supported_swing_modes_{}; - MitsubishiCN105::VaneMode last_non_swing_vane_mode_{MitsubishiCN105::VaneMode::AUTO}; - MitsubishiCN105::WideVaneMode last_non_swing_wide_vane_mode_{MitsubishiCN105::WideVaneMode::CENTER}; + SwingModeManager swing_mode_manager_; }; // Legacy climate action compatibility. Remove in 2027.2.0. template -class LegacySetRemoteTemperatureAction : public Action, public Parented { +class LegacySetRemoteTemperatureAction final : public Action, public Parented { public: TEMPLATABLE_VALUE(float, temperature) @@ -41,7 +42,7 @@ class LegacySetRemoteTemperatureAction : public Action, public Parented -class LegacyClearRemoteTemperatureAction : public Action, public Parented { +class LegacyClearRemoteTemperatureAction final : public Action, public Parented { public: void play(const Ts &...x) override { this->parent_->clear_remote_temperature(); } }; diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.h b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.h index 508a15e6d5..aa9bfe0d8c 100644 --- a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.h +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_component.h @@ -80,7 +80,7 @@ struct VaneCall { MitsubishiCN105Component *parent_; }; -class MitsubishiCN105Component : public Component, public uart::UARTDevice { +class MitsubishiCN105Component final : public Component, public uart::UARTDevice { public: explicit MitsubishiCN105Component() : hp_(*this) {} diff --git a/esphome/components/mitsubishi_cn105/mitsubishi_cn105_swing_mode_manager.h b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_swing_mode_manager.h new file mode 100644 index 0000000000..20f54f0bbb --- /dev/null +++ b/esphome/components/mitsubishi_cn105/mitsubishi_cn105_swing_mode_manager.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include "esphome/components/climate/climate.h" +#include "mitsubishi_cn105.h" + +namespace esphome::mitsubishi_cn105 { + +class SwingModeManager final { + public: + const climate::ClimateSwingModeMask &supported_swing_modes() const { return this->supported_swing_modes_; } + void set_supported_swing_modes(const climate::ClimateSwingModeMask &supported_swing_modes) { + this->supported_swing_modes_ = supported_swing_modes; + } + + std::optional vane_from(climate::ClimateSwingMode swing_mode) const { + if (!this->supported_swing_modes_.count(climate::CLIMATE_SWING_VERTICAL)) { + return std::nullopt; + } + + switch (swing_mode) { + case climate::CLIMATE_SWING_BOTH: + case climate::CLIMATE_SWING_VERTICAL: + return MitsubishiCN105::VaneMode::SWING; + default: + return this->last_non_swing_vane_mode_; + } + } + + std::optional wide_vane_from(climate::ClimateSwingMode swing_mode) const { + if (!this->supported_swing_modes_.count(climate::CLIMATE_SWING_HORIZONTAL)) { + return std::nullopt; + } + + switch (swing_mode) { + case climate::CLIMATE_SWING_BOTH: + case climate::CLIMATE_SWING_HORIZONTAL: + return MitsubishiCN105::WideVaneMode::SWING; + default: + return this->last_non_swing_wide_vane_mode_; + } + } + + std::optional update_and_get_swing_mode(MitsubishiCN105::VaneMode vane_mode, + MitsubishiCN105::WideVaneMode wide_vane_mode) { + if (this->supported_swing_modes_.empty()) { + return std::nullopt; + } + + bool vertical_swinging = false; + bool horizontal_swinging = false; + if (this->supported_swing_modes_.count(climate::CLIMATE_SWING_VERTICAL)) { + if (vane_mode == MitsubishiCN105::VaneMode::SWING) { + vertical_swinging = true; + } else if (vane_mode != MitsubishiCN105::VaneMode::UNKNOWN) { + this->last_non_swing_vane_mode_ = vane_mode; + } + } + if (this->supported_swing_modes_.count(climate::CLIMATE_SWING_HORIZONTAL)) { + if (wide_vane_mode == MitsubishiCN105::WideVaneMode::SWING) { + horizontal_swinging = true; + } else if (wide_vane_mode != MitsubishiCN105::WideVaneMode::UNKNOWN) { + this->last_non_swing_wide_vane_mode_ = wide_vane_mode; + } + } + + if (vertical_swinging && horizontal_swinging) { + return climate::CLIMATE_SWING_BOTH; + } + if (vertical_swinging) { + return climate::CLIMATE_SWING_VERTICAL; + } + if (horizontal_swinging) { + return climate::CLIMATE_SWING_HORIZONTAL; + } + return climate::CLIMATE_SWING_OFF; + } + + private: + climate::ClimateSwingModeMask supported_swing_modes_{}; + MitsubishiCN105::VaneMode last_non_swing_vane_mode_{MitsubishiCN105::VaneMode::AUTO}; + MitsubishiCN105::WideVaneMode last_non_swing_wide_vane_mode_{MitsubishiCN105::WideVaneMode::CENTER}; +}; + +} // namespace esphome::mitsubishi_cn105 diff --git a/esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.h b/esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.h index 76977d59d7..656b78b487 100644 --- a/esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.h +++ b/esphome/components/mitsubishi_cn105/select/mitsubishi_cn105_vane_select_vertical.h @@ -7,9 +7,9 @@ namespace esphome::mitsubishi_cn105 { -class MitsubishiCN105VerticalVaneDirectionSelect : public select::Select, - public Component, - public Parented { +class MitsubishiCN105VerticalVaneDirectionSelect final : public select::Select, + public Component, + public Parented { public: void setup() override; void publish_vane_state(MitsubishiCN105::VaneMode mode); diff --git a/esphome/components/mlx90393/sensor.py b/esphome/components/mlx90393/sensor.py index a6330b1cc0..59bdffc114 100644 --- a/esphome/components/mlx90393/sensor.py +++ b/esphome/components/mlx90393/sensor.py @@ -17,6 +17,7 @@ from esphome.const import ( UNIT_CELSIUS, UNIT_MICROTESLA, ) +from esphome.types import ConfigType CODEOWNERS = ["@functionpointer"] DEPENDENCIES = ["i2c"] @@ -52,7 +53,7 @@ CONF_DRDY_PIN = "drdy_pin" CONF_HALLCONF = "hallconf" -def _validate(config): +def _validate(config: ConfigType) -> ConfigType: if config[CONF_TEMPERATURE_COMPENSATION]: for axis in [CONF_X_AXIS, CONF_Y_AXIS, CONF_Z_AXIS]: if axis not in config: @@ -74,7 +75,7 @@ def _validate(config): return config -def mlx90393_axis_schema(): +def mlx90393_axis_schema() -> cv.Schema: return sensor.sensor_schema( unit_of_measurement=UNIT_MICROTESLA, accuracy_decimals=0, @@ -127,7 +128,7 @@ CONFIG_SCHEMA = cv.All( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mlx90614/sensor.py b/esphome/components/mlx90614/sensor.py index 6a34c4bdc0..0cf9b95dde 100644 --- a/esphome/components/mlx90614/sensor.py +++ b/esphome/components/mlx90614/sensor.py @@ -7,6 +7,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, ) +from esphome.types import ConfigType CODEOWNERS = ["@jesserockz"] DEPENDENCIES = ["i2c"] @@ -47,7 +48,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mmc5603/sensor.py b/esphome/components/mmc5603/sensor.py index 6d2bafdd0e..a9f240508c 100644 --- a/esphome/components/mmc5603/sensor.py +++ b/esphome/components/mmc5603/sensor.py @@ -15,6 +15,8 @@ from esphome.const import ( UNIT_DEGREES, UNIT_MICROTESLA, ) +from esphome.cpp_generator import MockObj +from esphome.types import ConfigType CONF_AUTO_SET_RESET = "auto_set_reset" @@ -65,7 +67,7 @@ CONFIG_SCHEMA = ( ) -def auto_data_rate(config): +def auto_data_rate(config: ConfigType) -> MockObj: interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds interval_hz = 1000.0 / interval_msec for datarate in sorted(MMC5603Datarates.keys()): @@ -74,7 +76,7 @@ def auto_data_rate(config): return MMC5603Datarates[75] -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mmc5983/sensor.py b/esphome/components/mmc5983/sensor.py index aaff2946f2..797181690f 100644 --- a/esphome/components/mmc5983/sensor.py +++ b/esphome/components/mmc5983/sensor.py @@ -10,6 +10,7 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_MICROTESLA, ) +from esphome.types import ConfigType DEPENDENCIES = ["i2c"] @@ -39,7 +40,7 @@ CONFIG_SCHEMA = ( ) -async def to_code(config): +async def to_code(config: ConfigType) -> None: var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/modbus/__init__.py b/esphome/components/modbus/__init__.py index a98591c6bc..c9ba00f111 100644 --- a/esphome/components/modbus/__init__.py +++ b/esphome/components/modbus/__init__.py @@ -1,17 +1,23 @@ from __future__ import annotations import logging -from typing import Any, Literal +from typing import Any, Literal, NamedTuple from esphome import pins import esphome.codegen as cg from esphome.components import uart import esphome.config_validation as cv -from esphome.const import CONF_ADDRESS, CONF_DISABLE_CRC, CONF_FLOW_CONTROL_PIN, CONF_ID +from esphome.const import ( + CONF_ADDRESS, + CONF_CONTINUOUS, + CONF_DISABLE_CRC, + CONF_FLOW_CONTROL_PIN, + CONF_ID, +) from esphome.cpp_generator import MockObj from esphome.cpp_helpers import gpio_pin_expression import esphome.final_validate as fv -from esphome.types import ConfigType +from esphome.types import ConfigType, TemplateArgsType _LOGGER = logging.getLogger(__name__) @@ -48,6 +54,73 @@ CONF_TURNAROUND_TIME = "turnaround_time" MODBUS_ROLES = ["client", "server"] + +class _CommandOption(NamedTuple): + """One per-command option forwarded to the hub (modbus::CommandOptions).""" + + conf_key: str + field: str # the C++ field, and so the set_() setter name + validator: Any # the static (non-templatable) validator for the key + cpp_type: Any # the C++ type the value is generated as + default: Any + + +# Per-direction command options. Single-sourcing the schema and the setter generation here keeps +# them from drifting; the C++ side must add the matching field per the rules documented on +# CommandOptions (modbus.h). +_COMMAND_OPTIONS: dict[str, list[_CommandOption]] = { + "read": [_CommandOption(CONF_CONTINUOUS, "continuous", cv.boolean, bool, False)], + "write": [], +} + + +def _command_options(direction: str) -> list[_CommandOption]: + try: + return _COMMAND_OPTIONS[direction] + except KeyError: + raise ValueError(f"unknown command-options direction {direction!r}") from None + + +def command_options_schema( + *, direction: Literal["read", "write"], templatable: bool = False +) -> dict[cv.Optional, Any]: + """Schema fragment for the per-command options a component forwards to the hub + (modbus::CommandOptions). Extend this into any schema that queues commands. Keys are + direction-specific so a schema never offers an option the hub would strip (e.g. + continuous on a write); the write side has no options yet. For actions (templatable=True the + keys also accept lambdas), register the values with register_templatable_command_options(). + """ + return { + cv.Optional(option.conf_key, default=option.default): ( + cv.templatable(option.validator) if templatable else option.validator + ) + for option in _command_options(direction) + } + + +async def register_templatable_command_options( + var: MockObj, config: ConfigType, args: TemplateArgsType, direction: str +) -> None: + """Generate the set_