1
0
mirror of https://github.com/esphome/esphome.git synced 2025-03-15 15:18:16 +00:00

Merge branch 'dev' into vornado-ir

This commit is contained in:
Jordan Zucker 2025-02-19 12:08:18 -08:00
commit 2da0cf27c4
21 changed files with 75 additions and 87 deletions

View File

@ -61,8 +61,8 @@ jobs:
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
pip install -e . pip install -e .
black: ruff:
name: Check black name: Check ruff
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- common - common
@ -74,10 +74,10 @@ jobs:
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }} cache-key: ${{ needs.common.outputs.cache-key }}
- name: Run black - name: Run Ruff
run: | run: |
. venv/bin/activate . venv/bin/activate
black --verbose esphome tests ruff format esphome tests
- name: Suggested changes - name: Suggested changes
run: script/ci-suggest-changes run: script/ci-suggest-changes
if: always() if: always()
@ -255,7 +255,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- common - common
- black - ruff
- ci-custom - ci-custom
- clang-format - clang-format
- flake8 - flake8
@ -482,7 +482,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- common - common
- black - ruff
- ci-custom - ci-custom
- clang-format - clang-format
- flake8 - flake8

View File

@ -4,7 +4,7 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.5.4 rev: v0.9.2
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff

View File

@ -66,7 +66,7 @@ def choose_prompt(options, purpose: str = None):
return options[0][1] return options[0][1]
safe_print( safe_print(
f'Found multiple options{f" for {purpose}" if purpose else ""}, please choose one:' f"Found multiple options{f' for {purpose}' if purpose else ''}, please choose one:"
) )
for i, (desc, _) in enumerate(options): for i, (desc, _) in enumerate(options):
safe_print(f" [{i + 1}] {desc}") safe_print(f" [{i + 1}] {desc}")

View File

@ -128,7 +128,6 @@ VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema(
def visual_temperature_step(value): def visual_temperature_step(value):
# Allow defining target/current temperature steps separately # Allow defining target/current temperature steps separately
if isinstance(value, dict): if isinstance(value, dict):
return VISUAL_TEMPERATURE_STEP_SCHEMA(value) return VISUAL_TEMPERATURE_STEP_SCHEMA(value)

View File

@ -66,7 +66,9 @@ FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
async def to_code(config): async def to_code(config):
uuid = config[CONF_UUID].hex uuid = config[CONF_UUID].hex
uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)] uuid_arr = [
cg.RawExpression(f"0x{uuid[i : i + 2]}") for i in range(0, len(uuid), 2)
]
var = cg.new_Pvariable(config[CONF_ID], uuid_arr) var = cg.new_Pvariable(config[CONF_ID], uuid_arr)
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])

View File

@ -112,8 +112,7 @@ def validate_supports(value):
) )
if is_pullup and num == 16: if is_pullup and num == 16:
raise cv.Invalid( raise cv.Invalid(
"GPIO Pin 16 does not support pullup pin mode. " "GPIO Pin 16 does not support pullup pin mode. Please choose another pin.",
"Please choose another pin.",
[CONF_MODE, CONF_PULLUP], [CONF_MODE, CONF_PULLUP],
) )
if is_pulldown and num != 16: if is_pulldown and num != 16:

View File

@ -1,9 +1,15 @@
import logging import logging
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.components import uart, climate, logger
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components import climate, logger, uart
from esphome.components.climate import (
CONF_CURRENT_TEMPERATURE,
ClimateMode,
ClimatePreset,
ClimateSwingMode,
)
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_BEEPER, CONF_BEEPER,
CONF_DISPLAY, CONF_DISPLAY,
@ -24,12 +30,7 @@ from esphome.const import (
CONF_VISUAL, CONF_VISUAL,
CONF_WIFI, CONF_WIFI,
) )
from esphome.components.climate import ( import esphome.final_validate as fv
ClimateMode,
ClimatePreset,
ClimateSwingMode,
CONF_CURRENT_TEMPERATURE,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -36,6 +36,7 @@ from esphome.const import (
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
CONF_PORT, CONF_PORT,
CONF_PUBLISH_NAN_AS_NONE,
CONF_QOS, CONF_QOS,
CONF_REBOOT_TIMEOUT, CONF_REBOOT_TIMEOUT,
CONF_RETAIN, CONF_RETAIN,
@ -49,7 +50,6 @@ from esphome.const import (
CONF_USE_ABBREVIATIONS, CONF_USE_ABBREVIATIONS,
CONF_USERNAME, CONF_USERNAME,
CONF_WILL_MESSAGE, CONF_WILL_MESSAGE,
CONF_PUBLISH_NAN_AS_NONE,
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
@ -406,7 +406,7 @@ async def to_code(config):
if CONF_SSL_FINGERPRINTS in config: if CONF_SSL_FINGERPRINTS in config:
for fingerprint in config[CONF_SSL_FINGERPRINTS]: for fingerprint in config[CONF_SSL_FINGERPRINTS]:
arr = [ arr = [
cg.RawExpression(f"0x{fingerprint[i:i + 2]}") for i in range(0, 40, 2) cg.RawExpression(f"0x{fingerprint[i : i + 2]}") for i in range(0, 40, 2)
] ]
cg.add(var.add_ssl_fingerprint(arr)) cg.add(var.add_ssl_fingerprint(arr))
cg.add_build_flag("-DASYNC_TCP_SSL_ENABLED=1") cg.add_build_flag("-DASYNC_TCP_SSL_ENABLED=1")

View File

@ -1,9 +1,10 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_UID from esphome.const import CONF_UID
from esphome.core import HexInt from esphome.core import HexInt
from .. import nfc_ns, Nfcc, NfcTagListener
from .. import Nfcc, NfcTagListener, nfc_ns
DEPENDENCIES = ["nfc"] DEPENDENCIES = ["nfc"]
@ -25,8 +26,7 @@ def validate_uid(value):
for x in value.split("-"): for x in value.split("-"):
if len(x) != 2: if len(x) != 2:
raise cv.Invalid( raise cv.Invalid(
"Each part (separated by '-') of the UID must be two characters " "Each part (separated by '-') of the UID must be two characters long."
"long."
) )
try: try:
x = int(x, 16) x = int(x, 16)

View File

@ -1,8 +1,9 @@
from typing import Any from typing import Any
import esphome.config_validation as cv
from esphome.components import binary_sensor from esphome.components import binary_sensor
from .. import const, schema, validate, generate import esphome.config_validation as cv
from .. import const, generate, schema, validate
DEPENDENCIES = [const.OPENTHERM] DEPENDENCIES = [const.OPENTHERM]
COMPONENT_TYPE = const.BINARY_SENSOR COMPONENT_TYPE = const.BINARY_SENSOR
@ -11,8 +12,7 @@ COMPONENT_TYPE = const.BINARY_SENSOR
def get_entity_validation_schema(entity: schema.BinarySensorSchema) -> cv.Schema: def get_entity_validation_schema(entity: schema.BinarySensorSchema) -> cv.Schema:
return binary_sensor.binary_sensor_schema( return binary_sensor.binary_sensor_schema(
device_class=( device_class=(
entity.device_class entity.device_class or binary_sensor._UNDEF # pylint: disable=protected-access
or binary_sensor._UNDEF # pylint: disable=protected-access
), ),
icon=(entity.icon or binary_sensor._UNDEF), # pylint: disable=protected-access icon=(entity.icon or binary_sensor._UNDEF), # pylint: disable=protected-access
) )

View File

@ -3,8 +3,9 @@ from typing import Any, Callable, Optional
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import CONF_ID from esphome.const import CONF_ID
from . import const from . import const
from .schema import TSchema, SettingSchema from .schema import SettingSchema, TSchema
opentherm_ns = cg.esphome_ns.namespace("opentherm") opentherm_ns = cg.esphome_ns.namespace("opentherm")
OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component)
@ -112,11 +113,10 @@ def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]):
msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}") msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}")
if keep_updated: if keep_updated:
cg.add(hub.add_repeating_message(msg_expr)) cg.add(hub.add_repeating_message(msg_expr))
elif order is not None:
cg.add(hub.add_initial_message(msg_expr, order))
else: else:
if order is not None: cg.add(hub.add_initial_message(msg_expr))
cg.add(hub.add_initial_message(msg_expr, order))
else:
cg.add(hub.add_initial_message(msg_expr))
def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None: def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None:
@ -128,7 +128,7 @@ Create = Callable[[dict[str, Any], str, cg.MockObj], Awaitable[cg.Pvariable]]
def create_only_conf( def create_only_conf(
create: Callable[[dict[str, Any]], Awaitable[cg.Pvariable]] create: Callable[[dict[str, Any]], Awaitable[cg.Pvariable]],
) -> Create: ) -> Create:
return lambda conf, _key, _hub: create(conf) return lambda conf, _key, _hub: create(conf)

View File

@ -1,8 +1,9 @@
from typing import Any from typing import Any
import esphome.config_validation as cv
from esphome.components import sensor from esphome.components import sensor
from .. import const, schema, validate, generate import esphome.config_validation as cv
from .. import const, generate, schema, validate
DEPENDENCIES = [const.OPENTHERM] DEPENDENCIES = [const.OPENTHERM]
COMPONENT_TYPE = const.SENSOR COMPONENT_TYPE = const.SENSOR
@ -22,11 +23,9 @@ MSG_DATA_TYPES = {
def get_entity_validation_schema(entity: schema.SensorSchema) -> cv.Schema: def get_entity_validation_schema(entity: schema.SensorSchema) -> cv.Schema:
return sensor.sensor_schema( return sensor.sensor_schema(
unit_of_measurement=entity.unit_of_measurement unit_of_measurement=entity.unit_of_measurement or sensor._UNDEF, # pylint: disable=protected-access
or sensor._UNDEF, # pylint: disable=protected-access
accuracy_decimals=entity.accuracy_decimals, accuracy_decimals=entity.accuracy_decimals,
device_class=entity.device_class device_class=entity.device_class or sensor._UNDEF, # pylint: disable=protected-access
or sensor._UNDEF, # pylint: disable=protected-access
icon=entity.icon or sensor._UNDEF, # pylint: disable=protected-access icon=entity.icon or sensor._UNDEF, # pylint: disable=protected-access
state_class=entity.state_class, state_class=entity.state_class,
).extend( ).extend(

View File

@ -1,9 +1,10 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_UID from esphome.const import CONF_UID
from esphome.core import HexInt from esphome.core import HexInt
from . import pn532_ns, PN532, CONF_PN532_ID
from . import CONF_PN532_ID, PN532, pn532_ns
DEPENDENCIES = ["pn532"] DEPENDENCIES = ["pn532"]
@ -13,8 +14,7 @@ def validate_uid(value):
for x in value.split("-"): for x in value.split("-"):
if len(x) != 2: if len(x) != 2:
raise cv.Invalid( raise cv.Invalid(
"Each part (separated by '-') of the UID must be two characters " "Each part (separated by '-') of the UID must be two characters long."
"long."
) )
try: try:
x = int(x, 16) x = int(x, 16)

View File

@ -1,9 +1,10 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_UID from esphome.const import CONF_UID
from esphome.core import HexInt from esphome.core import HexInt
from . import rc522_ns, RC522, CONF_RC522_ID
from . import CONF_RC522_ID, RC522, rc522_ns
DEPENDENCIES = ["rc522"] DEPENDENCIES = ["rc522"]
@ -13,8 +14,7 @@ def validate_uid(value):
for x in value.split("-"): for x in value.split("-"):
if len(x) != 2: if len(x) != 2:
raise cv.Invalid( raise cv.Invalid(
"Each part (separated by '-') of the UID must be two characters " "Each part (separated by '-') of the UID must be two characters long."
"long."
) )
try: try:
x = int(x, 16) x = int(x, 16)

View File

@ -137,7 +137,7 @@ def validate_temperature_preset(preset, root_config, name, requirements):
def generate_comparable_preset(config, name): def generate_comparable_preset(config, name):
comparable_preset = f"{CONF_PRESET}:\n" f" - {CONF_NAME}: {name}\n" comparable_preset = f"{CONF_PRESET}:\n - {CONF_NAME}: {name}\n"
if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config: if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
comparable_preset += f" {CONF_DEFAULT_TARGET_TEMPERATURE_LOW}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]}\n" comparable_preset += f" {CONF_DEFAULT_TARGET_TEMPERATURE_LOW}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]}\n"

View File

@ -1223,8 +1223,7 @@ def subscribe_topic(value):
if index != len(value) - 1: if index != len(value) - 1:
# If there are multiple wildcards, this will also trigger # If there are multiple wildcards, this will also trigger
raise Invalid( raise Invalid(
"Multi-level wildcard must be the last " "Multi-level wildcard must be the last character in the topic filter."
"character in the topic filter."
) )
if len(value) > 1 and value[index - 1] != "/": if len(value) > 1 and value[index - 1] != "/":
raise Invalid("Multi-level wildcard must be after a topic level separator.") raise Invalid("Multi-level wildcard must be after a topic level separator.")

View File

@ -506,9 +506,9 @@ def with_local_variable(id_: ID, rhs: SafeExpType, callback: Callable, *args) ->
""" """
# throw if the callback is async: # throw if the callback is async:
assert not inspect.iscoroutinefunction( assert not inspect.iscoroutinefunction(callback), (
callback "with_local_variable() callback cannot be async!"
), "with_local_variable() callback cannot be async!" )
CORE.add(RawStatement("{")) # output opening curly brace CORE.add(RawStatement("{")) # output opening curly brace
obj = variable(id_, rhs, None, True) obj = variable(id_, rhs, None, True)

View File

@ -144,17 +144,17 @@ def wizard_file(**kwargs):
# Configure API # Configure API
if "password" in kwargs: if "password" in kwargs:
config += f" password: \"{kwargs['password']}\"\n" config += f' password: "{kwargs["password"]}"\n'
if "api_encryption_key" in kwargs: if "api_encryption_key" in kwargs:
config += f" encryption:\n key: \"{kwargs['api_encryption_key']}\"\n" config += f' encryption:\n key: "{kwargs["api_encryption_key"]}"\n'
# Configure OTA # Configure OTA
config += "\nota:\n" config += "\nota:\n"
config += " - platform: esphome\n" config += " - platform: esphome\n"
if "ota_password" in kwargs: if "ota_password" in kwargs:
config += f" password: \"{kwargs['ota_password']}\"" config += f' password: "{kwargs["ota_password"]}"'
elif "password" in kwargs: elif "password" in kwargs:
config += f" password: \"{kwargs['password']}\"" config += f' password: "{kwargs["password"]}"'
# Configuring wifi # Configuring wifi
config += "\n\nwifi:\n" config += "\n\nwifi:\n"
@ -181,18 +181,14 @@ def wizard_file(**kwargs):
password: "{fallback_psk}" password: "{fallback_psk}"
captive_portal: captive_portal:
""".format( """.format(**kwargs)
**kwargs
)
else: else:
config += """ config += """
# Enable fallback hotspot in case wifi connection fails # Enable fallback hotspot in case wifi connection fails
ap: ap:
ssid: "{fallback_name}" ssid: "{fallback_name}"
password: "{fallback_psk}" password: "{fallback_psk}"
""".format( """.format(**kwargs)
**kwargs
)
return config return config
@ -388,19 +384,19 @@ def wizard(path):
safe_print() safe_print()
# Don't sleep because user needs to copy link # Don't sleep because user needs to copy link
if platform == "ESP32": if platform == "ESP32":
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcu-32s')}\".") safe_print(f'For example "{color(Fore.BOLD_WHITE, "nodemcu-32s")}".')
boards_list = esp32_boards.BOARDS.items() boards_list = esp32_boards.BOARDS.items()
elif platform == "ESP8266": elif platform == "ESP8266":
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'nodemcuv2')}\".") safe_print(f'For example "{color(Fore.BOLD_WHITE, "nodemcuv2")}".')
boards_list = esp8266_boards.BOARDS.items() boards_list = esp8266_boards.BOARDS.items()
elif platform == "BK72XX": elif platform == "BK72XX":
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'cb2s')}\".") safe_print(f'For example "{color(Fore.BOLD_WHITE, "cb2s")}".')
boards_list = bk72xx_boards.BOARDS.items() boards_list = bk72xx_boards.BOARDS.items()
elif platform == "RTL87XX": elif platform == "RTL87XX":
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'wr3')}\".") safe_print(f'For example "{color(Fore.BOLD_WHITE, "wr3")}".')
boards_list = rtl87xx_boards.BOARDS.items() boards_list = rtl87xx_boards.BOARDS.items()
elif platform == "RP2040": elif platform == "RP2040":
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'rpipicow')}\".") safe_print(f'For example "{color(Fore.BOLD_WHITE, "rpipicow")}".')
boards_list = rp2040_boards.BOARDS.items() boards_list = rp2040_boards.BOARDS.items()
else: else:
@ -439,7 +435,7 @@ def wizard(path):
f"First, what's the {color(Fore.GREEN, 'SSID')} (the name) of the WiFi network {name} should connect to?" f"First, what's the {color(Fore.GREEN, 'SSID')} (the name) of the WiFi network {name} should connect to?"
) )
sleep(1.5) sleep(1.5)
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'Abraham Linksys')}\".") safe_print(f'For example "{color(Fore.BOLD_WHITE, "Abraham Linksys")}".')
while True: while True:
ssid = safe_input(color(Fore.BOLD_WHITE, "(ssid): ")) ssid = safe_input(color(Fore.BOLD_WHITE, "(ssid): "))
try: try:
@ -465,7 +461,7 @@ def wizard(path):
f"Now please state the {color(Fore.GREEN, 'password')} of the WiFi network so that I can connect to it (Leave empty for no password)" f"Now please state the {color(Fore.GREEN, 'password')} of the WiFi network so that I can connect to it (Leave empty for no password)"
) )
safe_print() safe_print()
safe_print(f"For example \"{color(Fore.BOLD_WHITE, 'PASSWORD42')}\"") safe_print(f'For example "{color(Fore.BOLD_WHITE, "PASSWORD42")}"')
sleep(0.5) sleep(0.5)
psk = safe_input(color(Fore.BOLD_WHITE, "(PSK): ")) psk = safe_input(color(Fore.BOLD_WHITE, "(PSK): "))
safe_print( safe_print(

View File

@ -212,9 +212,7 @@ def write_platformio_project():
write_platformio_ini(content) write_platformio_ini(content)
DEFINES_H_FORMAT = ( DEFINES_H_FORMAT = ESPHOME_H_FORMAT = """\
ESPHOME_H_FORMAT
) = """\
#pragma once #pragma once
#include "esphome/core/macros.h" #include "esphome/core/macros.h"
{} {}

View File

@ -1,6 +1,6 @@
pylint==3.2.7 pylint==3.2.7
flake8==7.0.0 # also change in .pre-commit-config.yaml when updating flake8==7.0.0 # also change in .pre-commit-config.yaml when updating
black==24.4.2 # also change in .pre-commit-config.yaml when updating ruff==0.9.2 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.15.2 # also change in .pre-commit-config.yaml when updating pyupgrade==3.15.2 # also change in .pre-commit-config.yaml when updating
pre-commit pre-commit

View File

@ -1,11 +1,9 @@
from collections.abc import Iterator from collections.abc import Iterator
import math import math
import pytest import pytest
from esphome import cpp_generator as cg from esphome import cpp_generator as cg, cpp_types as ct
from esphome import cpp_types as ct
class TestExpressions: class TestExpressions:
@ -156,10 +154,7 @@ class TestLambdaExpression:
actual = str(target) actual = str(target)
assert actual == ( assert actual == (
"[=](int32_t foo, float bar) {\n" "[=](int32_t foo, float bar) {\n if ((foo == 5) && (bar < 10))) {\n }\n}"
" if ((foo == 5) && (bar < 10))) {\n"
" }\n"
"}"
) )
def test_str__with_return(self): def test_str__with_return(self):