From 3b891bc146f371b6e334f0463e80669a39eb3489 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 10 Nov 2023 03:17:40 -0600 Subject: [PATCH 1/8] Speed up YAML by using YAML C loader when available (#5721) --- esphome/yaml_util.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index 3d3fa8c5b4..a954415d12 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -23,6 +23,14 @@ from esphome.core import ( from esphome.helpers import add_class_to_obj from esphome.util import OrderedDict, filter_yaml_files +try: + from yaml import CSafeLoader as FastestAvailableSafeLoader +except ImportError: + from yaml import ( # type: ignore[assignment] + SafeLoader as FastestAvailableSafeLoader, + ) + + _LOGGER = logging.getLogger(__name__) # Mostly copied from Home Assistant because that code works fine and @@ -89,7 +97,7 @@ def _add_data_ref(fn): return wrapped -class ESPHomeLoader(yaml.SafeLoader): +class ESPHomeLoader(FastestAvailableSafeLoader): """Loader class that keeps track of line numbers.""" @_add_data_ref From 3363c8f434f09147877531b97c15143ed3b78bb8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 10 Nov 2023 03:55:21 -0600 Subject: [PATCH 2/8] Fix zeroconf name resolution refactoring error (#5725) --- esphome/zeroconf.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/esphome/zeroconf.py b/esphome/zeroconf.py index d20111ce20..f4cb7f080b 100644 --- a/esphome/zeroconf.py +++ b/esphome/zeroconf.py @@ -147,12 +147,13 @@ class DashboardImportDiscovery: class EsphomeZeroconf(Zeroconf): - def resolve_host(self, host: str, timeout=3.0): + def resolve_host(self, host: str, timeout: float = 3.0) -> str | None: """Resolve a host name to an IP address.""" name = host.partition(".")[0] - info = HostResolver(f"{name}.{ESPHOME_SERVICE_TYPE}", ESPHOME_SERVICE_TYPE) - if (info.load_from_cache(self) or info.request(self, timeout * 1000)) and ( - addresses := info.ip_addresses_by_version(IPVersion.V4Only) - ): + info = HostResolver(ESPHOME_SERVICE_TYPE, f"{name}.{ESPHOME_SERVICE_TYPE}") + if ( + info.load_from_cache(self) + or (timeout and info.request(self, timeout * 1000)) + ) and (addresses := info.ip_addresses_by_version(IPVersion.V4Only)): return str(addresses[0]) return None From 6a5cea171ea83156f06adc0dbd6d6696a196539a Mon Sep 17 00:00:00 2001 From: Mike La Spina Date: Fri, 10 Nov 2023 18:37:39 -0600 Subject: [PATCH 3/8] Missed ifdefs (#5727) --- esphome/components/ld2420/ld2420.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index 6130617457..bda1764cfc 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -68,6 +68,7 @@ void LD2420Component::dump_config() { ESP_LOGCONFIG(TAG, "LD2420:"); ESP_LOGCONFIG(TAG, " Firmware Version : %7s", this->ld2420_firmware_ver_); ESP_LOGCONFIG(TAG, "LD2420 Number:"); +#ifdef USE_NUMBER LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_); LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_); LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_); @@ -76,10 +77,13 @@ void LD2420Component::dump_config() { LOG_NUMBER(TAG, " Gate Move Threshold:", this->gate_move_threshold_numbers_[gate]); LOG_NUMBER(TAG, " Gate Still Threshold::", this->gate_still_threshold_numbers_[gate]); } +#endif +#ifdef USE_BUTTON LOG_BUTTON(TAG, " Apply Config:", this->apply_config_button_); LOG_BUTTON(TAG, " Revert Edits:", this->revert_config_button_); LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_); LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_); +#endif ESP_LOGCONFIG(TAG, "LD2420 Select:"); LOG_SELECT(TAG, " Operating Mode", this->operating_selector_); if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { @@ -183,9 +187,11 @@ void LD2420Component::factory_reset_action() { return; } this->set_min_max_distances_timeout(FACTORY_MAX_GATE, FACTORY_MIN_GATE, FACTORY_TIMEOUT); +#ifdef USE_NUMBER this->gate_timeout_number_->state = FACTORY_TIMEOUT; this->min_gate_distance_number_->state = FACTORY_MIN_GATE; this->max_gate_distance_number_->state = FACTORY_MAX_GATE; +#endif for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) { this->new_config.move_thresh[gate] = FACTORY_MOVE_THRESH[gate]; this->new_config.still_thresh[gate] = FACTORY_STILL_THRESH[gate]; From 64a5bab9f096f747f4ecb76bca7c2c25bb7d284e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 11 Nov 2023 13:48:52 -0600 Subject: [PATCH 4/8] Fix log runner using zeroconf sync close running in the event loop - Use the log runner code from aioesphomeapi since it handles zeroconf correctly - Avoid calling safe_print since it has a slow late import and does mulitple bytes/string conversions --- esphome/components/api/client.py | 67 +++++++++++++------------------- requirements.txt | 2 +- 2 files changed, 28 insertions(+), 41 deletions(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 819055ccf4..0d86f3cea5 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -1,19 +1,22 @@ import asyncio import logging from datetime import datetime -from typing import Optional +from typing import Any, Optional -from aioesphomeapi import APIClient, ReconnectLogic, APIConnectionError, LogLevel -import zeroconf +from aioesphomeapi import APIClient +from aioesphomeapi.api_pb2 import SubscribeLogsResponse +from aioesphomeapi.log_runner import async_run + +from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__ +from esphome.core import CORE -from esphome.const import CONF_KEY, CONF_PORT, CONF_PASSWORD, __version__ -from esphome.util import safe_print from . import CONF_ENCRYPTION _LOGGER = logging.getLogger(__name__) async def async_run_logs(config, address): + """Run the logs command in the event loop.""" conf = config["api"] port: int = int(conf[CONF_PORT]) password: str = conf[CONF_PASSWORD] @@ -28,44 +31,28 @@ async def async_run_logs(config, address): client_info=f"ESPHome Logs {__version__}", noise_psk=noise_psk, ) - first_connect = True + dashboard = CORE.dashboard - def on_log(msg): - time_ = datetime.now().time().strftime("[%H:%M:%S]") - text = msg.message.decode("utf8", "backslashreplace") - safe_print(time_ + text) - - async def on_connect(): - nonlocal first_connect - try: - await cli.subscribe_logs( - on_log, - log_level=LogLevel.LOG_LEVEL_VERY_VERBOSE, - dump_config=first_connect, - ) - first_connect = False - except APIConnectionError: - cli.disconnect() - - async def on_disconnect(expected_disconnect: bool) -> None: - _LOGGER.warning("Disconnected from API") - - zc = zeroconf.Zeroconf() - reconnect = ReconnectLogic( - client=cli, - on_connect=on_connect, - on_disconnect=on_disconnect, - zeroconf_instance=zc, - ) - await reconnect.start() + def on_log(msg: SubscribeLogsResponse) -> None: + """Handle a new log message.""" + time_ = datetime.now() + message: bytes = msg.message + text = message.decode("utf8", "backslashreplace") + if dashboard: + text = text.replace("\033", "\\033") + print(f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]{text}") + stop = await async_run(cli, on_log) try: while True: await asyncio.sleep(60) + finally: + await stop() + + +def run_logs(config: dict[str, Any], address: str) -> None: + """Run the logs command.""" + try: + asyncio.run(async_run_logs(config, address)) except KeyboardInterrupt: - await reconnect.stop() - zc.close() - - -def run_logs(config, address): - asyncio.run(async_run_logs(config, address)) + pass diff --git a/requirements.txt b/requirements.txt index f974967a00..51799bc685 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==6.1.11 # When updating platformio, also update Dockerfile esptool==4.6.2 click==8.1.7 esphome-dashboard==20231107.0 -aioesphomeapi==18.2.7 +aioesphomeapi==18.4.0 zeroconf==0.122.3 # esp-idf requires this, but doesn't bundle it by default From 51930a02430f440e3dcf6b9fdddae975a67b6e4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:59:36 +0000 Subject: [PATCH 5/8] Bump aioesphomeapi from 18.2.7 to 18.4.0 (#5735) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f974967a00..51799bc685 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==6.1.11 # When updating platformio, also update Dockerfile esptool==4.6.2 click==8.1.7 esphome-dashboard==20231107.0 -aioesphomeapi==18.2.7 +aioesphomeapi==18.4.0 zeroconf==0.122.3 # esp-idf requires this, but doesn't bundle it by default From 19b6ec9d5a9021a1b1cb518e6f13fbd871f07881 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 11 Nov 2023 16:20:04 -0600 Subject: [PATCH 6/8] lint --- esphome/components/api/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 0d86f3cea5..59f8cfd28c 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import asyncio import logging from datetime import datetime -from typing import Any, Optional +from typing import Any from aioesphomeapi import APIClient from aioesphomeapi.api_pb2 import SubscribeLogsResponse @@ -20,7 +22,7 @@ async def async_run_logs(config, address): conf = config["api"] port: int = int(conf[CONF_PORT]) password: str = conf[CONF_PASSWORD] - noise_psk: Optional[str] = None + noise_psk: str | None = None if CONF_ENCRYPTION in conf: noise_psk = conf[CONF_ENCRYPTION][CONF_KEY] _LOGGER.info("Starting log output from %s using esphome API", address) From b2e566041e183284019fbb42c373aee1e14a605a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 11 Nov 2023 16:25:51 -0600 Subject: [PATCH 7/8] actually only use one zc instance --- esphome/components/api/client.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 59f8cfd28c..1ad288e1c3 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -8,6 +8,7 @@ from typing import Any from aioesphomeapi import APIClient from aioesphomeapi.api_pb2 import SubscribeLogsResponse from aioesphomeapi.log_runner import async_run +from zeroconf.asyncio import AsyncZeroconf from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__ from esphome.core import CORE @@ -26,12 +27,15 @@ async def async_run_logs(config, address): if CONF_ENCRYPTION in conf: noise_psk = conf[CONF_ENCRYPTION][CONF_KEY] _LOGGER.info("Starting log output from %s using esphome API", address) + aiozc = AsyncZeroconf() + cli = APIClient( address, port, password, client_info=f"ESPHome Logs {__version__}", noise_psk=noise_psk, + zeroconf_instance=aiozc, ) dashboard = CORE.dashboard @@ -44,11 +48,12 @@ async def async_run_logs(config, address): text = text.replace("\033", "\\033") print(f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}]{text}") - stop = await async_run(cli, on_log) + stop = await async_run(cli, on_log, aio_zeroconf_instance=aiozc) try: while True: await asyncio.sleep(60) finally: + await aiozc.async_close() await stop() From 986fe25f19098dba1e38adefa855131aee9bc7b7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 11 Nov 2023 16:26:25 -0600 Subject: [PATCH 8/8] tweak --- esphome/components/api/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index 1ad288e1c3..2c43eca70c 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -35,7 +35,7 @@ async def async_run_logs(config, address): password, client_info=f"ESPHome Logs {__version__}", noise_psk=noise_psk, - zeroconf_instance=aiozc, + zeroconf_instance=aiozc.zeroconf, ) dashboard = CORE.dashboard