esphome 2025.9.3__py3-none-any.whl → 2025.10.0__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 +103 -37
- 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 +7 -7
- 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/dashboard_import/dashboard_import.cpp +1 -1
- esphome/components/dashboard_import/dashboard_import.h +1 -1
- 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 +256 -340
- esphome/components/esp32/boards.py +81 -0
- esphome/components/esp32/preferences.cpp +23 -17
- esphome/components/esp32_ble/__init__.py +167 -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_beacon/esp32_ble_beacon.cpp +0 -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 -8
- 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 +135 -65
- esphome/components/esp32_improv/esp32_improv_component.h +7 -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 +456 -146
- 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 +42 -44
- 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/mcp23xxx_base/mcp23xxx_base.h +3 -3
- 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 +93 -19
- esphome/components/mdns/mdns_component.cpp +57 -94
- esphome/components/mdns/mdns_component.h +35 -11
- esphome/components/mdns/mdns_esp32.cpp +7 -13
- esphome/components/mdns/mdns_esp8266.cpp +7 -7
- esphome/components/mdns/mdns_libretiny.cpp +3 -4
- esphome/components/mdns/mdns_rp2040.cpp +3 -4
- esphome/components/mipi/__init__.py +1 -5
- esphome/components/mipi_dsi/models/waveshare.py +27 -36
- 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/opentherm/opentherm.cpp +5 -5
- esphome/components/opentherm/opentherm.h +3 -3
- esphome/components/openthread/openthread.cpp +11 -10
- 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 +32 -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/speaker/media_player/__init__.py +16 -3
- 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 +37 -27
- 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 +12 -2
- esphome/components/usb_host/usb_host.h +89 -14
- esphome/components/usb_host/usb_host_client.cpp +157 -22
- 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 +74 -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 +33 -22
- esphome/core/component.cpp +28 -18
- esphome/core/component_iterator.h +2 -1
- esphome/core/config.py +15 -15
- esphome/core/defines.h +21 -0
- esphome/core/entity_helpers.py +9 -6
- 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 +112 -32
- esphome/external_files.py +6 -7
- esphome/git.py +8 -0
- esphome/helpers.py +124 -77
- esphome/loader.py +8 -9
- esphome/pins.py +2 -2
- esphome/platformio_api.py +56 -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 +57 -15
- esphome/yaml_util.py +37 -31
- esphome/zeroconf.py +12 -3
- {esphome-2025.9.3.dist-info → esphome-2025.10.0.dist-info}/METADATA +12 -12
- {esphome-2025.9.3.dist-info → esphome-2025.10.0.dist-info}/RECORD +342 -322
- 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.0.dist-info}/WHEEL +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0.dist-info}/entry_points.txt +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.9.3.dist-info → esphome-2025.10.0.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
|
|
@@ -256,8 +268,10 @@ def has_ip_address() -> bool:
|
|
|
256
268
|
|
|
257
269
|
|
|
258
270
|
def has_resolvable_address() -> bool:
|
|
259
|
-
"""Check if CORE.address is resolvable (via mDNS or is an IP address)."""
|
|
260
|
-
|
|
271
|
+
"""Check if CORE.address is resolvable (via mDNS, DNS, or is an IP address)."""
|
|
272
|
+
# Any address (IP, mDNS hostname, or regular DNS hostname) is resolvable
|
|
273
|
+
# The resolve_ip_address() function in helpers.py handles all types via AsyncResolver
|
|
274
|
+
return CORE.address is not None
|
|
261
275
|
|
|
262
276
|
|
|
263
277
|
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
|
|
@@ -445,7 +459,7 @@ def upload_using_esptool(
|
|
|
445
459
|
"detect",
|
|
446
460
|
]
|
|
447
461
|
for img in flash_images:
|
|
448
|
-
cmd += [img.offset, img.path]
|
|
462
|
+
cmd += [img.offset, str(img.path)]
|
|
449
463
|
|
|
450
464
|
if os.environ.get("ESPHOME_USE_SUBPROCESS") is None:
|
|
451
465
|
import esptool
|
|
@@ -531,7 +545,10 @@ def upload_program(
|
|
|
531
545
|
|
|
532
546
|
remote_port = int(ota_conf[CONF_PORT])
|
|
533
547
|
password = ota_conf.get(CONF_PASSWORD, "")
|
|
534
|
-
|
|
548
|
+
if getattr(args, "file", None) is not None:
|
|
549
|
+
binary = Path(args.file)
|
|
550
|
+
else:
|
|
551
|
+
binary = CORE.firmware_bin
|
|
535
552
|
|
|
536
553
|
# MQTT address resolution
|
|
537
554
|
if get_port_type(host) in ("MQTT", "MQTTIP"):
|
|
@@ -563,11 +580,12 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
|
|
|
563
580
|
if has_api():
|
|
564
581
|
addresses_to_use: list[str] | None = None
|
|
565
582
|
|
|
566
|
-
if port_type == "NETWORK"
|
|
583
|
+
if port_type == "NETWORK":
|
|
584
|
+
# Network addresses (IPs, mDNS names, or regular DNS hostnames) can be used
|
|
585
|
+
# The resolve_ip_address() function in helpers.py handles all types
|
|
567
586
|
addresses_to_use = devices
|
|
568
|
-
elif port_type in ("
|
|
569
|
-
#
|
|
570
|
-
# (for MQTT/MQTTIP types, or for NETWORK when mdns/ip check fails)
|
|
587
|
+
elif port_type in ("MQTT", "MQTTIP") and has_mqtt_ip_lookup():
|
|
588
|
+
# Use MQTT IP lookup for MQTT/MQTTIP types
|
|
571
589
|
addresses_to_use = mqtt_get_ip(
|
|
572
590
|
config, args.username, args.password, args.client_id
|
|
573
591
|
)
|
|
@@ -598,7 +616,7 @@ def clean_mqtt(config: ConfigType, args: ArgsProtocol) -> int | None:
|
|
|
598
616
|
def command_wizard(args: ArgsProtocol) -> int | None:
|
|
599
617
|
from esphome import wizard
|
|
600
618
|
|
|
601
|
-
return wizard.wizard(args.configuration)
|
|
619
|
+
return wizard.wizard(Path(args.configuration))
|
|
602
620
|
|
|
603
621
|
|
|
604
622
|
def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
@@ -720,6 +738,16 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
|
720
738
|
return clean_mqtt(config, args)
|
|
721
739
|
|
|
722
740
|
|
|
741
|
+
def command_clean_all(args: ArgsProtocol) -> int | None:
|
|
742
|
+
try:
|
|
743
|
+
writer.clean_all(args.configuration)
|
|
744
|
+
except OSError as err:
|
|
745
|
+
_LOGGER.error("Error cleaning all files: %s", err)
|
|
746
|
+
return 1
|
|
747
|
+
_LOGGER.info("Done!")
|
|
748
|
+
return 0
|
|
749
|
+
|
|
750
|
+
|
|
723
751
|
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
724
752
|
from esphome import mqtt
|
|
725
753
|
|
|
@@ -761,7 +789,7 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
|
|
761
789
|
safe_print(f"{half_line}{middle_text}{half_line}")
|
|
762
790
|
|
|
763
791
|
for f in files:
|
|
764
|
-
safe_print(f"Updating {color(AnsiFore.CYAN, f)}")
|
|
792
|
+
safe_print(f"Updating {color(AnsiFore.CYAN, str(f))}")
|
|
765
793
|
safe_print("-" * twidth)
|
|
766
794
|
safe_print()
|
|
767
795
|
if CORE.dashboard:
|
|
@@ -773,10 +801,10 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
|
|
773
801
|
"esphome", "run", f, "--no-logs", "--device", "OTA"
|
|
774
802
|
)
|
|
775
803
|
if rc == 0:
|
|
776
|
-
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {f}")
|
|
804
|
+
print_bar(f"[{color(AnsiFore.BOLD_GREEN, 'SUCCESS')}] {str(f)}")
|
|
777
805
|
success[f] = True
|
|
778
806
|
else:
|
|
779
|
-
print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {f}")
|
|
807
|
+
print_bar(f"[{color(AnsiFore.BOLD_RED, 'ERROR')}] {str(f)}")
|
|
780
808
|
success[f] = False
|
|
781
809
|
|
|
782
810
|
safe_print()
|
|
@@ -787,9 +815,9 @@ def command_update_all(args: ArgsProtocol) -> int | None:
|
|
|
787
815
|
failed = 0
|
|
788
816
|
for f in files:
|
|
789
817
|
if success[f]:
|
|
790
|
-
safe_print(f" - {f}: {color(AnsiFore.GREEN, 'SUCCESS')}")
|
|
818
|
+
safe_print(f" - {str(f)}: {color(AnsiFore.GREEN, 'SUCCESS')}")
|
|
791
819
|
else:
|
|
792
|
-
safe_print(f" - {f}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
|
|
820
|
+
safe_print(f" - {str(f)}: {color(AnsiFore.BOLD_RED, 'FAILED')}")
|
|
793
821
|
failed += 1
|
|
794
822
|
return failed
|
|
795
823
|
|
|
@@ -811,7 +839,8 @@ def command_idedata(args: ArgsProtocol, config: ConfigType) -> int:
|
|
|
811
839
|
|
|
812
840
|
|
|
813
841
|
def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
814
|
-
|
|
842
|
+
new_name = args.name
|
|
843
|
+
for c in new_name:
|
|
815
844
|
if c not in ALLOWED_NAME_CHARS:
|
|
816
845
|
print(
|
|
817
846
|
color(
|
|
@@ -822,8 +851,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
|
822
851
|
)
|
|
823
852
|
return 1
|
|
824
853
|
# Load existing yaml file
|
|
825
|
-
|
|
826
|
-
raw_contents = raw_file.read()
|
|
854
|
+
raw_contents = CORE.config_path.read_text(encoding="utf-8")
|
|
827
855
|
|
|
828
856
|
yaml = yaml_util.load_yaml(CORE.config_path)
|
|
829
857
|
if CONF_ESPHOME not in yaml or CONF_NAME not in yaml[CONF_ESPHOME]:
|
|
@@ -838,7 +866,7 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
|
838
866
|
if match is None:
|
|
839
867
|
new_raw = re.sub(
|
|
840
868
|
rf"name:\s+[\"']?{old_name}[\"']?",
|
|
841
|
-
f'name: "{
|
|
869
|
+
f'name: "{new_name}"',
|
|
842
870
|
raw_contents,
|
|
843
871
|
)
|
|
844
872
|
else:
|
|
@@ -858,29 +886,28 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
|
858
886
|
|
|
859
887
|
new_raw = re.sub(
|
|
860
888
|
rf"^(\s+{match.group(1)}):\s+[\"']?{old_name}[\"']?",
|
|
861
|
-
f'\\1: "{
|
|
889
|
+
f'\\1: "{new_name}"',
|
|
862
890
|
raw_contents,
|
|
863
891
|
flags=re.MULTILINE,
|
|
864
892
|
)
|
|
865
893
|
|
|
866
|
-
new_path =
|
|
894
|
+
new_path: Path = CORE.config_dir / (new_name + ".yaml")
|
|
867
895
|
print(
|
|
868
|
-
f"Updating {color(AnsiFore.CYAN, CORE.config_path)} to {color(AnsiFore.CYAN, new_path)}"
|
|
896
|
+
f"Updating {color(AnsiFore.CYAN, str(CORE.config_path))} to {color(AnsiFore.CYAN, str(new_path))}"
|
|
869
897
|
)
|
|
870
898
|
print()
|
|
871
899
|
|
|
872
|
-
|
|
873
|
-
new_file.write(new_raw)
|
|
900
|
+
new_path.write_text(new_raw, encoding="utf-8")
|
|
874
901
|
|
|
875
|
-
rc = run_external_process("esphome", "config", new_path)
|
|
902
|
+
rc = run_external_process("esphome", "config", str(new_path))
|
|
876
903
|
if rc != 0:
|
|
877
904
|
print(color(AnsiFore.BOLD_RED, "Rename failed. Reverting changes."))
|
|
878
|
-
|
|
905
|
+
new_path.unlink()
|
|
879
906
|
return 1
|
|
880
907
|
|
|
881
908
|
cli_args = [
|
|
882
909
|
"run",
|
|
883
|
-
new_path,
|
|
910
|
+
str(new_path),
|
|
884
911
|
"--no-logs",
|
|
885
912
|
"--device",
|
|
886
913
|
CORE.address,
|
|
@@ -894,11 +921,11 @@ def command_rename(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|
|
894
921
|
except KeyboardInterrupt:
|
|
895
922
|
rc = 1
|
|
896
923
|
if rc != 0:
|
|
897
|
-
|
|
924
|
+
new_path.unlink()
|
|
898
925
|
return 1
|
|
899
926
|
|
|
900
927
|
if CORE.config_path != new_path:
|
|
901
|
-
|
|
928
|
+
CORE.config_path.unlink()
|
|
902
929
|
|
|
903
930
|
print(color(AnsiFore.BOLD_GREEN, "SUCCESS"))
|
|
904
931
|
print()
|
|
@@ -911,6 +938,7 @@ PRE_CONFIG_ACTIONS = {
|
|
|
911
938
|
"dashboard": command_dashboard,
|
|
912
939
|
"vscode": command_vscode,
|
|
913
940
|
"update-all": command_update_all,
|
|
941
|
+
"clean-all": command_clean_all,
|
|
914
942
|
}
|
|
915
943
|
|
|
916
944
|
POST_CONFIG_ACTIONS = {
|
|
@@ -919,9 +947,9 @@ POST_CONFIG_ACTIONS = {
|
|
|
919
947
|
"upload": command_upload,
|
|
920
948
|
"logs": command_logs,
|
|
921
949
|
"run": command_run,
|
|
950
|
+
"clean": command_clean,
|
|
922
951
|
"clean-mqtt": command_clean_mqtt,
|
|
923
952
|
"mqtt-fingerprint": command_mqtt_fingerprint,
|
|
924
|
-
"clean": command_clean,
|
|
925
953
|
"idedata": command_idedata,
|
|
926
954
|
"rename": command_rename,
|
|
927
955
|
"discover": command_discover,
|
|
@@ -965,6 +993,24 @@ def parse_args(argv):
|
|
|
965
993
|
help="Add a substitution",
|
|
966
994
|
metavar=("key", "value"),
|
|
967
995
|
)
|
|
996
|
+
options_parser.add_argument(
|
|
997
|
+
"--mdns-address-cache",
|
|
998
|
+
help="mDNS address cache mapping in format 'hostname=ip1,ip2'",
|
|
999
|
+
action="append",
|
|
1000
|
+
default=[],
|
|
1001
|
+
)
|
|
1002
|
+
options_parser.add_argument(
|
|
1003
|
+
"--dns-address-cache",
|
|
1004
|
+
help="DNS address cache mapping in format 'hostname=ip1,ip2'",
|
|
1005
|
+
action="append",
|
|
1006
|
+
default=[],
|
|
1007
|
+
)
|
|
1008
|
+
options_parser.add_argument(
|
|
1009
|
+
"--testing-mode",
|
|
1010
|
+
help="Enable testing mode (disables validation checks for grouped component testing)",
|
|
1011
|
+
action="store_true",
|
|
1012
|
+
default=False,
|
|
1013
|
+
)
|
|
968
1014
|
|
|
969
1015
|
parser = argparse.ArgumentParser(
|
|
970
1016
|
description=f"ESPHome {const.__version__}", parents=[options_parser]
|
|
@@ -1122,6 +1168,13 @@ def parse_args(argv):
|
|
|
1122
1168
|
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
|
1123
1169
|
)
|
|
1124
1170
|
|
|
1171
|
+
parser_clean_all = subparsers.add_parser(
|
|
1172
|
+
"clean-all", help="Clean all build and platform files."
|
|
1173
|
+
)
|
|
1174
|
+
parser_clean_all.add_argument(
|
|
1175
|
+
"configuration", help="Your YAML configuration directory.", nargs="*"
|
|
1176
|
+
)
|
|
1177
|
+
|
|
1125
1178
|
parser_dashboard = subparsers.add_parser(
|
|
1126
1179
|
"dashboard", help="Create a simple web server for a dashboard."
|
|
1127
1180
|
)
|
|
@@ -1168,7 +1221,7 @@ def parse_args(argv):
|
|
|
1168
1221
|
|
|
1169
1222
|
parser_update = subparsers.add_parser("update-all")
|
|
1170
1223
|
parser_update.add_argument(
|
|
1171
|
-
"configuration", help="Your YAML configuration file
|
|
1224
|
+
"configuration", help="Your YAML configuration file or directory.", nargs="+"
|
|
1172
1225
|
)
|
|
1173
1226
|
|
|
1174
1227
|
parser_idedata = subparsers.add_parser("idedata")
|
|
@@ -1212,9 +1265,16 @@ def parse_args(argv):
|
|
|
1212
1265
|
|
|
1213
1266
|
|
|
1214
1267
|
def run_esphome(argv):
|
|
1268
|
+
from esphome.address_cache import AddressCache
|
|
1269
|
+
|
|
1215
1270
|
args = parse_args(argv)
|
|
1216
1271
|
CORE.dashboard = args.dashboard
|
|
1272
|
+
CORE.testing_mode = args.testing_mode
|
|
1217
1273
|
|
|
1274
|
+
# Create address cache from command-line arguments
|
|
1275
|
+
CORE.address_cache = AddressCache.from_cli_args(
|
|
1276
|
+
args.mdns_address_cache, args.dns_address_cache
|
|
1277
|
+
)
|
|
1218
1278
|
# Override log level if verbose is set
|
|
1219
1279
|
if args.verbose:
|
|
1220
1280
|
args.log_level = "DEBUG"
|
|
@@ -1237,14 +1297,20 @@ def run_esphome(argv):
|
|
|
1237
1297
|
_LOGGER.info("ESPHome %s", const.__version__)
|
|
1238
1298
|
|
|
1239
1299
|
for conf_path in args.configuration:
|
|
1240
|
-
|
|
1300
|
+
conf_path = Path(conf_path)
|
|
1301
|
+
if any(conf_path.name == x for x in SECRETS_FILES):
|
|
1241
1302
|
_LOGGER.warning("Skipping secrets file %s", conf_path)
|
|
1242
1303
|
continue
|
|
1243
1304
|
|
|
1244
1305
|
CORE.config_path = conf_path
|
|
1245
1306
|
CORE.dashboard = args.dashboard
|
|
1246
1307
|
|
|
1247
|
-
|
|
1308
|
+
# For logs command, skip updating external components
|
|
1309
|
+
skip_external = args.command == "logs"
|
|
1310
|
+
config = read_config(
|
|
1311
|
+
dict(args.substitution) if args.substitution else {},
|
|
1312
|
+
skip_external_update=skip_external,
|
|
1313
|
+
)
|
|
1248
1314
|
if config is None:
|
|
1249
1315
|
return 2
|
|
1250
1316
|
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
|