esphome 2025.9.3__py3-none-any.whl → 2025.10.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esphome/__main__.py +87 -31
- esphome/address_cache.py +142 -0
- esphome/automation.py +130 -32
- esphome/build_gen/platformio.py +1 -3
- esphome/codegen.py +1 -0
- esphome/components/animation/animation.cpp +2 -2
- esphome/components/api/__init__.py +166 -3
- esphome/components/api/api_connection.cpp +84 -41
- esphome/components/api/api_connection.h +22 -16
- esphome/components/api/api_frame_helper.cpp +33 -19
- esphome/components/api/api_frame_helper.h +19 -4
- esphome/components/api/api_frame_helper_noise.cpp +41 -53
- esphome/components/api/api_frame_helper_noise.h +1 -1
- esphome/components/api/api_frame_helper_plaintext.cpp +22 -31
- esphome/components/api/api_frame_helper_plaintext.h +1 -1
- esphome/components/api/api_pb2.cpp +189 -15
- esphome/components/api/api_pb2.h +132 -20
- esphome/components/api/api_pb2_dump.cpp +97 -9
- esphome/components/api/api_pb2_service.cpp +118 -160
- esphome/components/api/api_pb2_service.h +31 -3
- esphome/components/api/api_server.cpp +68 -10
- esphome/components/api/api_server.h +32 -4
- esphome/components/api/custom_api_device.h +8 -8
- esphome/components/api/homeassistant_service.h +123 -6
- esphome/components/api/proto.h +6 -2
- esphome/components/api/user_services.h +2 -2
- esphome/components/as7341/sensor.py +1 -1
- esphome/components/audio/__init__.py +1 -1
- esphome/components/audio/audio.cpp +1 -1
- esphome/components/audio/audio_decoder.cpp +9 -9
- esphome/components/bl0906/bl0906.cpp +2 -2
- esphome/components/bl0942/bl0942.cpp +2 -2
- esphome/components/ble_client/__init__.py +1 -1
- esphome/components/bluetooth_proxy/__init__.py +4 -30
- esphome/components/bluetooth_proxy/bluetooth_connection.cpp +11 -4
- esphome/components/bluetooth_proxy/bluetooth_connection.h +2 -2
- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +2 -2
- esphome/components/camera_encoder/__init__.py +2 -4
- esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +4 -2
- esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +3 -1
- esphome/components/canbus/canbus.cpp +7 -5
- esphome/components/canbus/canbus.h +4 -4
- esphome/components/captive_portal/__init__.py +18 -1
- esphome/components/captive_portal/captive_portal.cpp +40 -46
- esphome/components/captive_portal/captive_portal.h +20 -22
- esphome/components/captive_portal/dns_server_esp32_idf.cpp +205 -0
- esphome/components/captive_portal/dns_server_esp32_idf.h +27 -0
- esphome/components/ccs811/ccs811.cpp +1 -1
- esphome/components/climate/climate.cpp +10 -7
- esphome/components/cm1106/cm1106.cpp +1 -1
- esphome/components/copy/lock/copy_lock.cpp +1 -1
- esphome/components/cover/cover.cpp +1 -0
- esphome/components/daikin_arc/daikin_arc.cpp +19 -12
- esphome/components/deep_sleep/__init__.py +9 -2
- esphome/components/deep_sleep/deep_sleep_component.h +11 -9
- esphome/components/deep_sleep/deep_sleep_esp32.cpp +51 -27
- esphome/components/ektf2232/touchscreen/__init__.py +8 -5
- esphome/components/ektf2232/touchscreen/ektf2232.cpp +4 -4
- esphome/components/ektf2232/touchscreen/ektf2232.h +2 -2
- esphome/components/epaper_spi/__init__.py +1 -0
- esphome/components/epaper_spi/display.py +80 -0
- esphome/components/epaper_spi/epaper_spi.cpp +227 -0
- esphome/components/epaper_spi/epaper_spi.h +93 -0
- esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.cpp +42 -0
- esphome/components/epaper_spi/epaper_spi_model_7p3in_spectra_e6.h +45 -0
- esphome/components/epaper_spi/epaper_spi_spectra_e6.cpp +135 -0
- esphome/components/epaper_spi/epaper_spi_spectra_e6.h +23 -0
- esphome/components/es7210/es7210.cpp +3 -3
- esphome/components/esp32/__init__.py +254 -339
- esphome/components/esp32/boards.py +81 -0
- esphome/components/esp32/preferences.cpp +23 -17
- esphome/components/esp32_ble/__init__.py +159 -44
- esphome/components/esp32_ble/ble.cpp +47 -3
- esphome/components/esp32_ble/ble.h +18 -0
- esphome/components/esp32_ble/ble_advertising.cpp +7 -3
- esphome/components/esp32_ble/ble_advertising.h +4 -0
- esphome/components/esp32_ble/ble_uuid.cpp +16 -42
- esphome/components/esp32_ble_beacon/__init__.py +3 -4
- esphome/components/esp32_ble_client/ble_client_base.cpp +14 -12
- esphome/components/esp32_ble_server/__init__.py +28 -14
- esphome/components/esp32_ble_server/ble_characteristic.cpp +67 -57
- esphome/components/esp32_ble_server/ble_characteristic.h +27 -16
- esphome/components/esp32_ble_server/ble_descriptor.cpp +4 -3
- esphome/components/esp32_ble_server/ble_descriptor.h +13 -9
- esphome/components/esp32_ble_server/ble_server.cpp +59 -24
- esphome/components/esp32_ble_server/ble_server.h +38 -20
- esphome/components/esp32_ble_server/ble_server_automations.cpp +49 -33
- esphome/components/esp32_ble_server/ble_server_automations.h +39 -24
- esphome/components/esp32_ble_tracker/__init__.py +25 -80
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +2 -4
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +0 -3
- esphome/components/esp32_camera/__init__.py +1 -3
- esphome/components/esp32_can/esp32_can.cpp +22 -4
- esphome/components/esp32_can/esp32_can.h +3 -0
- esphome/components/esp32_hosted/__init__.py +2 -1
- esphome/components/esp32_improv/esp32_improv_component.cpp +102 -44
- esphome/components/esp32_improv/esp32_improv_component.h +6 -1
- esphome/components/esp32_rmt_led_strip/led_strip.cpp +1 -1
- esphome/components/esp8266/__init__.py +3 -3
- esphome/components/esphome/ota/__init__.py +21 -2
- esphome/components/esphome/ota/ota_esphome.cpp +455 -145
- esphome/components/esphome/ota/ota_esphome.h +49 -2
- esphome/components/ethernet/__init__.py +39 -22
- esphome/components/ethernet/ethernet_component.cpp +28 -5
- esphome/components/ethernet/ethernet_component.h +5 -1
- esphome/components/external_components/__init__.py +8 -6
- esphome/components/fingerprint_grow/fingerprint_grow.cpp +1 -1
- esphome/components/fingerprint_grow/fingerprint_grow.h +2 -1
- esphome/components/font/__init__.py +5 -5
- esphome/components/graph/graph.cpp +1 -1
- esphome/components/graphical_display_menu/graphical_display_menu.cpp +3 -2
- esphome/components/haier/hon_climate.cpp +2 -2
- esphome/components/haier/hon_climate.h +1 -1
- esphome/components/hdc1080/hdc1080.cpp +42 -34
- esphome/components/hdc1080/hdc1080.h +1 -3
- esphome/components/homeassistant/number/homeassistant_number.cpp +2 -2
- esphome/components/homeassistant/switch/homeassistant_switch.cpp +2 -2
- esphome/components/http_request/__init__.py +3 -3
- esphome/components/htu21d/htu21d.cpp +13 -18
- esphome/components/htu21d/htu21d.h +1 -1
- esphome/components/i2s_audio/__init__.py +1 -2
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
- esphome/components/ili9xxx/ili9xxx_display.cpp +2 -2
- esphome/components/improv_serial/improv_serial_component.cpp +12 -15
- esphome/components/improv_serial/improv_serial_component.h +6 -8
- esphome/components/json/json_util.cpp +35 -43
- esphome/components/json/json_util.h +57 -0
- esphome/components/kamstrup_kmp/kamstrup_kmp.cpp +2 -2
- esphome/components/key_collector/key_collector.h +4 -4
- esphome/components/libretiny/__init__.py +6 -6
- esphome/components/libretiny/preferences.cpp +23 -16
- esphome/components/light/light_call.cpp +98 -120
- esphome/components/light/light_call.h +17 -7
- esphome/components/lm75b/__init__.py +0 -0
- esphome/components/lm75b/lm75b.cpp +39 -0
- esphome/components/lm75b/lm75b.h +19 -0
- esphome/components/lm75b/sensor.py +34 -0
- esphome/components/lock/lock.h +12 -6
- esphome/components/logger/__init__.py +15 -27
- esphome/components/logger/logger.cpp +10 -20
- esphome/components/logger/logger.h +105 -62
- esphome/components/logger/logger_esp32.cpp +0 -48
- esphome/components/logger/logger_zephyr.cpp +2 -3
- esphome/components/logger/select/logger_level_select.cpp +6 -7
- esphome/components/logger/select/logger_level_select.h +7 -0
- esphome/components/ltr501/ltr501.cpp +7 -6
- esphome/components/ltr_als_ps/ltr_als_ps.cpp +7 -6
- esphome/components/matrix_keypad/matrix_keypad.h +4 -4
- esphome/components/max7219digit/max7219digit.cpp +1 -1
- esphome/components/mcp2515/mcp2515.cpp +31 -3
- esphome/components/mcp2515/mcp2515_defs.h +3 -1
- esphome/components/md5/md5.cpp +0 -26
- esphome/components/md5/md5.h +10 -20
- esphome/components/mdns/__init__.py +19 -6
- esphome/components/mdns/mdns_component.cpp +27 -59
- esphome/components/mdns/mdns_component.h +23 -10
- esphome/components/mdns/mdns_esp32.cpp +7 -7
- esphome/components/mdns/mdns_esp8266.cpp +6 -6
- esphome/components/mdns/mdns_libretiny.cpp +3 -3
- esphome/components/mdns/mdns_rp2040.cpp +3 -3
- esphome/components/mipi/__init__.py +1 -5
- esphome/components/mipi_spi/display.py +24 -8
- esphome/components/mipi_spi/mipi_spi.h +3 -3
- esphome/components/mixer/speaker/mixer_speaker.cpp +3 -3
- esphome/components/mmc5603/mmc5603.cpp +3 -3
- esphome/components/modbus/modbus.cpp +27 -13
- esphome/components/modbus/modbus.h +5 -3
- esphome/components/modbus/modbus_definitions.h +86 -0
- esphome/components/modbus_controller/__init__.py +29 -1
- esphome/components/modbus_controller/const.py +4 -0
- esphome/components/modbus_controller/modbus_controller.cpp +38 -13
- esphome/components/modbus_controller/modbus_controller.h +18 -29
- esphome/components/mpr121/mpr121.cpp +41 -42
- esphome/components/mpr121/mpr121.h +0 -1
- esphome/components/nau7802/nau7802.cpp +2 -2
- esphome/components/network/__init__.py +7 -3
- esphome/components/nextion/display.py +4 -4
- esphome/components/nextion/nextion.cpp +8 -8
- esphome/components/number/__init__.py +2 -0
- esphome/components/number/number_call.cpp +23 -12
- esphome/components/number/number_call.h +5 -0
- esphome/components/online_image/bmp_image.cpp +2 -1
- esphome/components/online_image/jpeg_image.cpp +4 -2
- esphome/components/openthread/openthread.cpp +6 -7
- esphome/components/openthread/openthread.h +0 -1
- esphome/components/ota/ota_backend.h +1 -0
- esphome/components/packages/__init__.py +10 -8
- esphome/components/packet_transport/packet_transport.cpp +2 -0
- esphome/components/pid/pid_controller.cpp +1 -1
- esphome/components/prometheus/prometheus_handler.cpp +239 -239
- esphome/components/psram/__init__.py +30 -28
- esphome/components/qmc5883l/qmc5883l.cpp +15 -0
- esphome/components/qmc5883l/qmc5883l.h +3 -0
- esphome/components/qmc5883l/sensor.py +31 -12
- esphome/components/remote_base/gobox_protocol.cpp +3 -3
- esphome/components/remote_receiver/__init__.py +14 -2
- esphome/components/remote_receiver/{remote_receiver_esp8266.cpp → remote_receiver.cpp} +2 -2
- esphome/components/remote_receiver/remote_receiver.h +4 -0
- esphome/components/remote_receiver/remote_receiver_esp32.cpp +18 -1
- esphome/components/remote_transmitter/__init__.py +2 -2
- esphome/components/remote_transmitter/remote_transmitter.cpp +103 -0
- esphome/components/rp2040/__init__.py +11 -11
- esphome/components/rtttl/rtttl.cpp +2 -2
- esphome/components/scd30/sensor.py +1 -1
- esphome/components/script/__init__.py +1 -1
- esphome/components/script/script.h +7 -7
- esphome/components/select/select.cpp +5 -4
- esphome/components/select/select_call.cpp +1 -1
- esphome/components/sensirion_common/i2c_sensirion.cpp +2 -1
- esphome/components/sensor/__init__.py +2 -0
- esphome/components/sha256/__init__.py +22 -0
- esphome/components/sha256/sha256.cpp +116 -0
- esphome/components/sha256/sha256.h +60 -0
- esphome/components/socket/lwip_raw_tcp_impl.cpp +34 -6
- esphome/components/sonoff_d1/sonoff_d1.cpp +1 -1
- esphome/components/spi/__init__.py +0 -3
- esphome/components/split_buffer/__init__.py +5 -0
- esphome/components/split_buffer/split_buffer.cpp +133 -0
- esphome/components/split_buffer/split_buffer.h +40 -0
- esphome/components/sps30/sps30.cpp +14 -10
- esphome/components/sps30/sps30.h +2 -0
- esphome/components/st7567_i2c/st7567_i2c.cpp +3 -1
- esphome/components/st7789v/st7789v.cpp +3 -2
- esphome/components/statsd/statsd.cpp +1 -1
- esphome/components/substitutions/__init__.py +3 -1
- esphome/components/substitutions/jinja.py +13 -3
- esphome/components/sx126x/__init__.py +16 -0
- esphome/components/sx126x/sx126x.cpp +15 -1
- esphome/components/sx126x/sx126x.h +9 -1
- esphome/components/sx126x/sx126x_reg.h +2 -0
- esphome/components/text_sensor/text_sensor.cpp +16 -0
- esphome/components/text_sensor/text_sensor.h +3 -10
- esphome/components/tormatic/tormatic_cover.cpp +1 -1
- esphome/components/tuya/select/tuya_select.cpp +1 -1
- esphome/components/tuya/tuya.cpp +29 -4
- esphome/components/uart/__init__.py +36 -26
- esphome/components/uart/uart.h +6 -0
- esphome/components/uart/uart_component.cpp +8 -0
- esphome/components/uart/uart_component.h +28 -0
- esphome/components/uart/uart_component_esp_idf.cpp +64 -10
- esphome/components/uart/uart_component_esp_idf.h +5 -2
- esphome/components/uponor_smatrix/climate/uponor_smatrix_climate.cpp +1 -1
- esphome/components/uponor_smatrix/sensor/uponor_smatrix_sensor.cpp +1 -1
- esphome/components/uponor_smatrix/uponor_smatrix.cpp +3 -3
- esphome/components/usb_host/__init__.py +2 -1
- esphome/components/usb_host/usb_host.h +82 -13
- esphome/components/usb_host/usb_host_client.cpp +180 -24
- esphome/components/usb_host/usb_host_component.cpp +1 -1
- esphome/components/usb_uart/__init__.py +0 -1
- esphome/components/usb_uart/ch34x.cpp +4 -4
- esphome/components/usb_uart/cp210x.cpp +3 -3
- esphome/components/usb_uart/usb_uart.cpp +88 -32
- esphome/components/usb_uart/usb_uart.h +30 -6
- esphome/components/valve/valve.cpp +1 -0
- esphome/components/veml7700/veml7700.cpp +7 -6
- esphome/components/version/version_text_sensor.cpp +2 -1
- esphome/components/voice_assistant/voice_assistant.cpp +3 -2
- esphome/components/waveshare_epaper/waveshare_epaper.cpp +4 -4
- esphome/components/web_server/list_entities.cpp +3 -4
- esphome/components/web_server/list_entities.h +8 -10
- esphome/components/web_server/ota/__init__.py +1 -1
- esphome/components/web_server/ota/ota_web_server.cpp +9 -3
- esphome/components/web_server/web_server.cpp +509 -404
- esphome/components/web_server/web_server.h +5 -6
- esphome/components/web_server/web_server_v1.cpp +21 -19
- esphome/components/web_server_base/__init__.py +5 -2
- esphome/components/web_server_base/web_server_base.h +27 -7
- esphome/components/web_server_idf/__init__.py +1 -1
- esphome/components/web_server_idf/multipart.cpp +2 -2
- esphome/components/web_server_idf/multipart.h +2 -2
- esphome/components/web_server_idf/utils.cpp +2 -2
- esphome/components/web_server_idf/utils.h +2 -2
- esphome/components/web_server_idf/web_server_idf.cpp +118 -26
- esphome/components/web_server_idf/web_server_idf.h +12 -10
- esphome/components/wifi/__init__.py +13 -11
- esphome/components/wifi/wifi_component.cpp +73 -56
- esphome/components/wifi/wifi_component.h +4 -4
- esphome/components/wifi/wifi_component_esp8266.cpp +1 -1
- esphome/components/wifi/wifi_component_esp_idf.cpp +24 -4
- esphome/components/wireguard/__init__.py +1 -1
- esphome/components/wts01/__init__.py +0 -0
- esphome/components/wts01/sensor.py +41 -0
- esphome/components/wts01/wts01.cpp +91 -0
- esphome/components/wts01/wts01.h +27 -0
- esphome/components/zephyr/__init__.py +5 -5
- esphome/components/zwave_proxy/__init__.py +43 -0
- esphome/components/zwave_proxy/zwave_proxy.cpp +346 -0
- esphome/components/zwave_proxy/zwave_proxy.h +93 -0
- esphome/config.py +79 -24
- esphome/config_validation.py +13 -15
- esphome/const.py +9 -2
- esphome/core/__init__.py +31 -22
- esphome/core/component.cpp +28 -18
- esphome/core/component_iterator.h +2 -1
- esphome/core/config.py +15 -15
- esphome/core/defines.h +19 -0
- esphome/core/hash_base.h +56 -0
- esphome/core/helpers.cpp +19 -3
- esphome/core/helpers.h +26 -0
- esphome/core/scheduler.cpp +5 -21
- esphome/core/scheduler.h +19 -8
- esphome/core/string_ref.h +1 -1
- esphome/core/time.cpp +5 -5
- esphome/cpp_generator.py +4 -29
- esphome/dashboard/const.py +21 -4
- esphome/dashboard/core.py +10 -8
- esphome/dashboard/dns.py +15 -0
- esphome/dashboard/entries.py +15 -21
- esphome/dashboard/models.py +76 -0
- esphome/dashboard/settings.py +7 -7
- esphome/dashboard/status/mdns.py +46 -2
- esphome/dashboard/web_server.py +367 -93
- esphome/espota2.py +111 -31
- esphome/external_files.py +6 -7
- esphome/git.py +8 -0
- esphome/helpers.py +124 -77
- esphome/loader.py +8 -9
- esphome/platformio_api.py +25 -18
- esphome/storage_json.py +26 -21
- esphome/types.py +30 -2
- esphome/util.py +32 -16
- esphome/vscode.py +8 -8
- esphome/wizard.py +10 -10
- esphome/writer.py +50 -15
- esphome/yaml_util.py +37 -31
- esphome/zeroconf.py +12 -3
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/METADATA +11 -11
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/RECORD +332 -312
- esphome/components/event_emitter/__init__.py +0 -5
- esphome/components/event_emitter/event_emitter.cpp +0 -14
- esphome/components/event_emitter/event_emitter.h +0 -63
- esphome/components/remote_receiver/remote_receiver_libretiny.cpp +0 -125
- esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +0 -107
- esphome/components/remote_transmitter/remote_transmitter_libretiny.cpp +0 -110
- esphome/components/uart/uart_component_esp32_arduino.cpp +0 -214
- esphome/components/uart/uart_component_esp32_arduino.h +0 -60
- esphome/components/wifi/wifi_component_esp32_arduino.cpp +0 -860
- esphome/core/string_ref.cpp +0 -12
- esphome/dashboard/util/file.py +0 -63
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/WHEEL +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/entry_points.txt +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0b1.dist-info}/top_level.txt +0 -0
esphome/__main__.py
CHANGED
@@ -6,6 +6,7 @@ import getpass
|
|
6
6
|
import importlib
|
7
7
|
import logging
|
8
8
|
import os
|
9
|
+
from pathlib import Path
|
9
10
|
import re
|
10
11
|
import sys
|
11
12
|
import time
|
@@ -13,9 +14,11 @@ from typing import Protocol
|
|
13
14
|
|
14
15
|
import argcomplete
|
15
16
|
|
17
|
+
# Note: Do not import modules from esphome.components here, as this would
|
18
|
+
# cause them to be loaded before external components are processed, resulting
|
19
|
+
# in the built-in version being used instead of the external component one.
|
16
20
|
from esphome import const, writer, yaml_util
|
17
21
|
import esphome.codegen as cg
|
18
|
-
from esphome.components.mqtt import CONF_DISCOVER_IP
|
19
22
|
from esphome.config import iter_component_configs, read_config, strip_default_ids
|
20
23
|
from esphome.const import (
|
21
24
|
ALLOWED_NAME_CHARS,
|
@@ -114,6 +117,14 @@ class Purpose(StrEnum):
|
|
114
117
|
LOGGING = "logging"
|
115
118
|
|
116
119
|
|
120
|
+
def _resolve_with_cache(address: str, purpose: Purpose) -> list[str]:
|
121
|
+
"""Resolve an address using cache if available, otherwise return the address itself."""
|
122
|
+
if CORE.address_cache and (cached := CORE.address_cache.get_addresses(address)):
|
123
|
+
_LOGGER.debug("Using cached addresses for %s: %s", purpose.value, cached)
|
124
|
+
return cached
|
125
|
+
return [address]
|
126
|
+
|
127
|
+
|
117
128
|
def choose_upload_log_host(
|
118
129
|
default: list[str] | str | None,
|
119
130
|
check_default: str | None,
|
@@ -142,7 +153,7 @@ def choose_upload_log_host(
|
|
142
153
|
(purpose == Purpose.LOGGING and has_api())
|
143
154
|
or (purpose == Purpose.UPLOADING and has_ota())
|
144
155
|
):
|
145
|
-
resolved.
|
156
|
+
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
146
157
|
|
147
158
|
if purpose == Purpose.LOGGING:
|
148
159
|
if has_api() and has_mqtt_ip_lookup():
|
@@ -152,15 +163,14 @@ def choose_upload_log_host(
|
|
152
163
|
resolved.append("MQTT")
|
153
164
|
|
154
165
|
if has_api() and has_non_ip_address():
|
155
|
-
resolved.
|
166
|
+
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
156
167
|
|
157
168
|
elif purpose == Purpose.UPLOADING:
|
158
169
|
if has_ota() and has_mqtt_ip_lookup():
|
159
170
|
resolved.append("MQTTIP")
|
160
171
|
|
161
172
|
if has_ota() and has_non_ip_address():
|
162
|
-
resolved.
|
163
|
-
|
173
|
+
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
164
174
|
else:
|
165
175
|
resolved.append(device)
|
166
176
|
if not resolved:
|
@@ -232,6 +242,8 @@ def has_ota() -> bool:
|
|
232
242
|
|
233
243
|
def has_mqtt_ip_lookup() -> bool:
|
234
244
|
"""Check if MQTT is available and IP lookup is supported."""
|
245
|
+
from esphome.components.mqtt import CONF_DISCOVER_IP
|
246
|
+
|
235
247
|
if CONF_MQTT not in CORE.config:
|
236
248
|
return False
|
237
249
|
# Default Enabled
|
@@ -445,7 +457,7 @@ def upload_using_esptool(
|
|
445
457
|
"detect",
|
446
458
|
]
|
447
459
|
for img in flash_images:
|
448
|
-
cmd += [img.offset, img.path]
|
460
|
+
cmd += [img.offset, str(img.path)]
|
449
461
|
|
450
462
|
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
451
463
|
import esptool
|
@@ -531,7 +543,10 @@ def upload_program(
|
|
531
543
|
|
532
544
|
remote_port = int(ota_conf[CONF_PORT])
|
533
545
|
password = ota_conf.get(CONF_PASSWORD, "")
|
534
|
-
|
546
|
+
if getattr(args, "file", None) is not None:
|
547
|
+
binary = Path(args.file)
|
548
|
+
else:
|
549
|
+
binary = CORE.firmware_bin
|
535
550
|
|
536
551
|
# MQTT address resolution
|
537
552
|
if get_port_type(host) in ("MQTT", "MQTTIP"):
|
@@ -598,7 +613,7 @@ def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
|
|
598
613
|
def command_wizard(args: ArgsProtocol) -> int | None:
|
599
614
|
from esphome import wizard
|
600
615
|
|
601
|
-
return wizard.wizard(args.configuration)
|
616
|
+
return wizard.wizard(Path(args.configuration))
|
602
617
|
|
603
618
|
|
604
619
|
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
|
@@ -720,6 +735,16 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
720
735
|
return clean_mqtt(config, args)
|
721
736
|
|
722
737
|
|
738
|
+
def command_clean_all(args: ArgsProtocol) -> int | None:
|
739
|
+
try:
|
740
|
+
writer.clean_all(args.configuration)
|
741
|
+
except OSError as err:
|
742
|
+
_LOGGER.error("Error cleaning all files: %s", err)
|
743
|
+
return 1
|
744
|
+
_LOGGER.info("Done!")
|
745
|
+
return 0
|
746
|
+
|
747
|
+
|
723
748
|
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
|
724
749
|
from esphome import mqtt
|
725
750
|
|
@@ -761,7 +786,7 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
|
761
786
|
safe_print(f"{half_line}{middle_text}{half_line}")
|
762
787
|
|
763
788
|
for f in files:
|
764
|
-
safe_print(f"Updating {color(AnsiFore.CYAN, f)}")
|
789
|
+
safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}")
|
765
790
|
safe_print("-" * twidth)
|
766
791
|
safe_print()
|
767
792
|
if CORE.dashboard:
|
@@ -773,10 +798,10 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
|
773
798
|
"esphome", "run", f, "--no-logs", "--device", "OTA"
|
774
799
|
)
|
775
800
|
if rc == 0:
|
776
|
-
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
|
801
|
+
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
|
777
802
|
success[f] = True
|
778
803
|
else:
|
779
|
-
print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
|
804
|
+
print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {str(f)}")
|
780
805
|
success[f] = False
|
781
806
|
|
782
807
|
safe_print()
|
@@ -787,9 +812,9 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
|
787
812
|
failed = 0
|
788
813
|
for f in files:
|
789
814
|
if success[f]:
|
790
|
-
safe_print(f" - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
|
815
|
+
safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
|
791
816
|
else:
|
792
|
-
safe_print(f" - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
|
817
|
+
safe_print(f" - {str(f)}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
|
793
818
|
failed += 1
|
794
819
|
return failed
|
795
820
|
|
@@ -811,7 +836,8 @@ def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
|
811
836
|
|
812
837
|
|
813
838
|
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
814
|
-
|
839
|
+
new_name = args.name
|
840
|
+
for c in new_name:
|
815
841
|
if c not in ALLOWED_NAME_CHARS:
|
816
842
|
print(
|
817
843
|
color(
|
@@ -822,8 +848,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
822
848
|
)
|
823
849
|
return 1
|
824
850
|
# Load existing yaml file
|
825
|
-
|
826
|
-
raw_contents = raw_file.read()
|
851
|
+
raw_contents = CORE.config_path.read_text(encoding="utf-8")
|
827
852
|
|
828
853
|
yaml = yaml_util.load_yaml(CORE.config_path)
|
829
854
|
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
|
@@ -838,7 +863,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
838
863
|
if match is None:
|
839
864
|
new_raw = re.sub(
|
840
865
|
rf"name:\s+[\"']?{old_name}[\"']?",
|
841
|
-
f'name: "{
|
866
|
+
f'name: "{new_name}"',
|
842
867
|
raw_contents,
|
843
868
|
)
|
844
869
|
else:
|
@@ -858,29 +883,28 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
858
883
|
|
859
884
|
new_raw = re.sub(
|
860
885
|
rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
|
861
|
-
f'\\1: "{
|
886
|
+
f'\\1: "{new_name}"',
|
862
887
|
raw_contents,
|
863
888
|
flags=re.MULTILINE,
|
864
889
|
)
|
865
890
|
|
866
|
-
new_path =
|
891
|
+
new_path: Path = CORE.config_dir / (new_name + ".yaml")
|
867
892
|
print(
|
868
|
-
f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}"
|
893
|
+
f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
|
869
894
|
)
|
870
895
|
print()
|
871
896
|
|
872
|
-
|
873
|
-
new_file.write(new_raw)
|
897
|
+
new_path.write_text(new_raw, encoding="utf-8")
|
874
898
|
|
875
|
-
rc = run_external_process("esphome", "config", new_path)
|
899
|
+
rc = run_external_process("esphome", "config", str(new_path))
|
876
900
|
if rc != 0:
|
877
901
|
print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
|
878
|
-
|
902
|
+
new_path.unlink()
|
879
903
|
return 1
|
880
904
|
|
881
905
|
cli_args = [
|
882
906
|
"run",
|
883
|
-
new_path,
|
907
|
+
str(new_path),
|
884
908
|
"--no-logs",
|
885
909
|
"--device",
|
886
910
|
CORE.address,
|
@@ -894,11 +918,11 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
894
918
|
except KeyboardInterrupt:
|
895
919
|
rc = 1
|
896
920
|
if rc != 0:
|
897
|
-
|
921
|
+
new_path.unlink()
|
898
922
|
return 1
|
899
923
|
|
900
924
|
if CORE.config_path != new_path:
|
901
|
-
|
925
|
+
CORE.config_path.unlink()
|
902
926
|
|
903
927
|
print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
|
904
928
|
print()
|
@@ -911,6 +935,7 @@ PRE_CONFIG_ACTIONS = {
|
|
911
935
|
"dashboard": command_dashboard,
|
912
936
|
"vscode": command_vscode,
|
913
937
|
"update-all": command_update_all,
|
938
|
+
"clean-all": command_clean_all,
|
914
939
|
}
|
915
940
|
|
916
941
|
POST_CONFIG_ACTIONS = {
|
@@ -919,9 +944,9 @@ POST_CONFIG_ACTIONS = {
|
|
919
944
|
"upload": command_upload,
|
920
945
|
"logs": command_logs,
|
921
946
|
"run": command_run,
|
947
|
+
"clean": command_clean,
|
922
948
|
"clean-mqtt": command_clean_mqtt,
|
923
949
|
"mqtt-fingerprint": command_mqtt_fingerprint,
|
924
|
-
"clean": command_clean,
|
925
950
|
"idedata": command_idedata,
|
926
951
|
"rename": command_rename,
|
927
952
|
"discover": command_discover,
|
@@ -965,6 +990,18 @@ def parse_args(argv):
|
|
965
990
|
help="Add a substitution",
|
966
991
|
metavar=("key", "value"),
|
967
992
|
)
|
993
|
+
options_parser.add_argument(
|
994
|
+
"--mdns-address-cache",
|
995
|
+
help="mDNS address cache mapping in format 'hostname=ip1,ip2'",
|
996
|
+
action="append",
|
997
|
+
default=[],
|
998
|
+
)
|
999
|
+
options_parser.add_argument(
|
1000
|
+
"--dns-address-cache",
|
1001
|
+
help="DNS address cache mapping in format 'hostname=ip1,ip2'",
|
1002
|
+
action="append",
|
1003
|
+
default=[],
|
1004
|
+
)
|
968
1005
|
|
969
1006
|
parser = argparse.ArgumentParser(
|
970
1007
|
description=f"ESPHome {const.__version__}", parents=[options_parser]
|
@@ -1122,6 +1159,13 @@ def parse_args(argv):
|
|
1122
1159
|
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
1123
1160
|
)
|
1124
1161
|
|
1162
|
+
parser_clean_all = subparsers.add_parser(
|
1163
|
+
"clean-all", help="Clean all build and platform files."
|
1164
|
+
)
|
1165
|
+
parser_clean_all.add_argument(
|
1166
|
+
"configuration", help="Your YAML configuration directory.", nargs="*"
|
1167
|
+
)
|
1168
|
+
|
1125
1169
|
parser_dashboard = subparsers.add_parser(
|
1126
1170
|
"dashboard", help="Create a simple web server for a dashboard."
|
1127
1171
|
)
|
@@ -1168,7 +1212,7 @@ def parse_args(argv):
|
|
1168
1212
|
|
1169
1213
|
parser_update = subparsers.add_parser("update-all")
|
1170
1214
|
parser_update.add_argument(
|
1171
|
-
"configuration", help="Your YAML configuration file
|
1215
|
+
"configuration", help="Your YAML configuration file or directory.", nargs="+"
|
1172
1216
|
)
|
1173
1217
|
|
1174
1218
|
parser_idedata = subparsers.add_parser("idedata")
|
@@ -1212,9 +1256,15 @@ def parse_args(argv):
|
|
1212
1256
|
|
1213
1257
|
|
1214
1258
|
def run_esphome(argv):
|
1259
|
+
from esphome.address_cache import AddressCache
|
1260
|
+
|
1215
1261
|
args = parse_args(argv)
|
1216
1262
|
CORE.dashboard = args.dashboard
|
1217
1263
|
|
1264
|
+
# Create address cache from command-line arguments
|
1265
|
+
CORE.address_cache = AddressCache.from_cli_args(
|
1266
|
+
args.mdns_address_cache, args.dns_address_cache
|
1267
|
+
)
|
1218
1268
|
# Override log level if verbose is set
|
1219
1269
|
if args.verbose:
|
1220
1270
|
args.log_level = "DEBUG"
|
@@ -1237,14 +1287,20 @@ def run_esphome(argv):
|
|
1237
1287
|
_LOGGER.info("ESPHome %s", const.__version__)
|
1238
1288
|
|
1239
1289
|
for conf_path in args.configuration:
|
1240
|
-
|
1290
|
+
conf_path = Path(conf_path)
|
1291
|
+
if any(conf_path.name == x for x in SECRETS_FILES):
|
1241
1292
|
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
1242
1293
|
continue
|
1243
1294
|
|
1244
1295
|
CORE.config_path = conf_path
|
1245
1296
|
CORE.dashboard = args.dashboard
|
1246
1297
|
|
1247
|
-
|
1298
|
+
# For logs command, skip updating external components
|
1299
|
+
skip_external = args.command == "logs"
|
1300
|
+
config = read_config(
|
1301
|
+
dict(args.substitution) if args.substitution else {},
|
1302
|
+
skip_external_update=skip_external,
|
1303
|
+
)
|
1248
1304
|
if config is None:
|
1249
1305
|
return 2
|
1250
1306
|
CORE.config = config
|
esphome/address_cache.py
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
"""Address cache for DNS and mDNS lookups."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import TYPE_CHECKING
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from collections.abc import Iterable
|
10
|
+
|
11
|
+
_LOGGER = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
def normalize_hostname(hostname: str) -> str:
|
15
|
+
"""Normalize hostname for cache lookups.
|
16
|
+
|
17
|
+
Removes trailing dots and converts to lowercase.
|
18
|
+
"""
|
19
|
+
return hostname.rstrip(".").lower()
|
20
|
+
|
21
|
+
|
22
|
+
class AddressCache:
|
23
|
+
"""Cache for DNS and mDNS address lookups.
|
24
|
+
|
25
|
+
This cache stores pre-resolved addresses from command-line arguments
|
26
|
+
to avoid slow DNS/mDNS lookups during builds.
|
27
|
+
"""
|
28
|
+
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
mdns_cache: dict[str, list[str]] | None = None,
|
32
|
+
dns_cache: dict[str, list[str]] | None = None,
|
33
|
+
) -> None:
|
34
|
+
"""Initialize the address cache.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
mdns_cache: Pre-populated mDNS addresses (hostname -> IPs)
|
38
|
+
dns_cache: Pre-populated DNS addresses (hostname -> IPs)
|
39
|
+
"""
|
40
|
+
self.mdns_cache = mdns_cache or {}
|
41
|
+
self.dns_cache = dns_cache or {}
|
42
|
+
|
43
|
+
def _get_cached_addresses(
|
44
|
+
self, hostname: str, cache: dict[str, list[str]], cache_type: str
|
45
|
+
) -> list[str] | None:
|
46
|
+
"""Get cached addresses from a specific cache.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
hostname: The hostname to look up
|
50
|
+
cache: The cache dictionary to check
|
51
|
+
cache_type: Type of cache for logging ("mDNS" or "DNS")
|
52
|
+
|
53
|
+
Returns:
|
54
|
+
List of IP addresses if found in cache, None otherwise
|
55
|
+
"""
|
56
|
+
normalized = normalize_hostname(hostname)
|
57
|
+
if addresses := cache.get(normalized):
|
58
|
+
_LOGGER.debug("Using %s cache for %s: %s", cache_type, hostname, addresses)
|
59
|
+
return addresses
|
60
|
+
return None
|
61
|
+
|
62
|
+
def get_mdns_addresses(self, hostname: str) -> list[str] | None:
|
63
|
+
"""Get cached mDNS addresses for a hostname.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
hostname: The hostname to look up (should end with .local)
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
List of IP addresses if found in cache, None otherwise
|
70
|
+
"""
|
71
|
+
return self._get_cached_addresses(hostname, self.mdns_cache, "mDNS")
|
72
|
+
|
73
|
+
def get_dns_addresses(self, hostname: str) -> list[str] | None:
|
74
|
+
"""Get cached DNS addresses for a hostname.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
hostname: The hostname to look up
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
List of IP addresses if found in cache, None otherwise
|
81
|
+
"""
|
82
|
+
return self._get_cached_addresses(hostname, self.dns_cache, "DNS")
|
83
|
+
|
84
|
+
def get_addresses(self, hostname: str) -> list[str] | None:
|
85
|
+
"""Get cached addresses for a hostname.
|
86
|
+
|
87
|
+
Checks mDNS cache for .local domains, DNS cache otherwise.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
hostname: The hostname to look up
|
91
|
+
|
92
|
+
Returns:
|
93
|
+
List of IP addresses if found in cache, None otherwise
|
94
|
+
"""
|
95
|
+
normalized = normalize_hostname(hostname)
|
96
|
+
if normalized.endswith(".local"):
|
97
|
+
return self.get_mdns_addresses(hostname)
|
98
|
+
return self.get_dns_addresses(hostname)
|
99
|
+
|
100
|
+
def has_cache(self) -> bool:
|
101
|
+
"""Check if any cache entries exist."""
|
102
|
+
return bool(self.mdns_cache or self.dns_cache)
|
103
|
+
|
104
|
+
@classmethod
|
105
|
+
def from_cli_args(
|
106
|
+
cls, mdns_args: Iterable[str], dns_args: Iterable[str]
|
107
|
+
) -> AddressCache:
|
108
|
+
"""Create cache from command-line arguments.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
mdns_args: List of mDNS cache entries like ['host=ip1,ip2']
|
112
|
+
dns_args: List of DNS cache entries like ['host=ip1,ip2']
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
Configured AddressCache instance
|
116
|
+
"""
|
117
|
+
mdns_cache = cls._parse_cache_args(mdns_args)
|
118
|
+
dns_cache = cls._parse_cache_args(dns_args)
|
119
|
+
return cls(mdns_cache=mdns_cache, dns_cache=dns_cache)
|
120
|
+
|
121
|
+
@staticmethod
|
122
|
+
def _parse_cache_args(cache_args: Iterable[str]) -> dict[str, list[str]]:
|
123
|
+
"""Parse cache arguments into a dictionary.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
cache_args: List of cache mappings like ['host1=ip1,ip2', 'host2=ip3']
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
Dictionary mapping normalized hostnames to list of IP addresses
|
130
|
+
"""
|
131
|
+
cache: dict[str, list[str]] = {}
|
132
|
+
for arg in cache_args:
|
133
|
+
if "=" not in arg:
|
134
|
+
_LOGGER.warning(
|
135
|
+
"Invalid cache format: %s (expected 'hostname=ip1,ip2')", arg
|
136
|
+
)
|
137
|
+
continue
|
138
|
+
hostname, ips = arg.split("=", 1)
|
139
|
+
# Normalize hostname for consistent lookups
|
140
|
+
normalized = normalize_hostname(hostname)
|
141
|
+
cache[normalized] = [ip.strip() for ip in ips.split(",")]
|
142
|
+
return cache
|