esphome 2025.9.2__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 +167 -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 +78 -11
- 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/sim800l/sim800l.cpp +8 -4
- 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 -3
- 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.2.dist-info → esphome-2025.10.0b1.dist-info}/METADATA +11 -11
- {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/RECORD +333 -313
- 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.2.dist-info → esphome-2025.10.0b1.dist-info}/WHEEL +0 -0
- {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/entry_points.txt +0 -0
- {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.9.2.dist-info → esphome-2025.10.0b1.dist-info}/top_level.txt +0 -0
esphome/core/scheduler.cpp
CHANGED
@@ -118,7 +118,6 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|
118
118
|
item->type = type;
|
119
119
|
item->callback = std::move(func);
|
120
120
|
// Initialize remove to false (though it should already be from constructor)
|
121
|
-
// Not using mark_item_removed_ helper since we're setting to false, not true
|
122
121
|
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
123
122
|
item->remove.store(false, std::memory_order_relaxed);
|
124
123
|
#else
|
@@ -600,12 +599,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|
600
599
|
#ifndef ESPHOME_THREAD_SINGLE
|
601
600
|
// Mark items in defer queue as cancelled (they'll be skipped when processed)
|
602
601
|
if (type == SchedulerItem::TIMEOUT) {
|
603
|
-
|
604
|
-
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
605
|
-
this->mark_item_removed_(item.get());
|
606
|
-
total_cancelled++;
|
607
|
-
}
|
608
|
-
}
|
602
|
+
total_cancelled += this->mark_matching_items_removed_(this->defer_queue_, component, name_cstr, type, match_retry);
|
609
603
|
}
|
610
604
|
#endif /* not ESPHOME_THREAD_SINGLE */
|
611
605
|
|
@@ -620,23 +614,13 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|
620
614
|
total_cancelled++;
|
621
615
|
}
|
622
616
|
// For other items in heap, we can only mark for removal (can't remove from middle of heap)
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
total_cancelled++;
|
627
|
-
this->to_remove_++; // Track removals for heap items
|
628
|
-
}
|
629
|
-
}
|
617
|
+
size_t heap_cancelled = this->mark_matching_items_removed_(this->items_, component, name_cstr, type, match_retry);
|
618
|
+
total_cancelled += heap_cancelled;
|
619
|
+
this->to_remove_ += heap_cancelled; // Track removals for heap items
|
630
620
|
}
|
631
621
|
|
632
622
|
// Cancel items in to_add_
|
633
|
-
|
634
|
-
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
635
|
-
this->mark_item_removed_(item.get());
|
636
|
-
total_cancelled++;
|
637
|
-
// Don't track removals for to_add_ items
|
638
|
-
}
|
639
|
-
}
|
623
|
+
total_cancelled += this->mark_matching_items_removed_(this->to_add_, component, name_cstr, type, match_retry);
|
640
624
|
|
641
625
|
return total_cancelled > 0;
|
642
626
|
}
|
esphome/core/scheduler.h
CHANGED
@@ -280,19 +280,30 @@ class Scheduler {
|
|
280
280
|
#endif
|
281
281
|
}
|
282
282
|
|
283
|
-
// Helper to mark
|
283
|
+
// Helper to mark matching items in a container as removed
|
284
|
+
// Returns the number of items marked for removal
|
284
285
|
// For ESPHOME_THREAD_MULTI_NO_ATOMICS platforms, the caller must hold the scheduler lock before calling this
|
285
286
|
// function.
|
286
|
-
|
287
|
+
template<typename Container>
|
288
|
+
size_t mark_matching_items_removed_(Container &container, Component *component, const char *name_cstr,
|
289
|
+
SchedulerItem::Type type, bool match_retry) {
|
290
|
+
size_t count = 0;
|
291
|
+
for (auto &item : container) {
|
292
|
+
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
293
|
+
// Mark item for removal (platform-specific)
|
287
294
|
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
288
|
-
|
289
|
-
|
295
|
+
// Multi-threaded with atomics: use atomic store
|
296
|
+
item->remove.store(true, std::memory_order_release);
|
290
297
|
#else
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
298
|
+
// Single-threaded (ESPHOME_THREAD_SINGLE) or
|
299
|
+
// multi-threaded without atomics (ESPHOME_THREAD_MULTI_NO_ATOMICS): direct write
|
300
|
+
// For ESPHOME_THREAD_MULTI_NO_ATOMICS, caller MUST hold lock!
|
301
|
+
item->remove = true;
|
295
302
|
#endif
|
303
|
+
count++;
|
304
|
+
}
|
305
|
+
}
|
306
|
+
return count;
|
296
307
|
}
|
297
308
|
|
298
309
|
// Template helper to check if any item in a container matches our criteria
|
esphome/core/string_ref.h
CHANGED
@@ -130,7 +130,7 @@ inline std::string operator+(const StringRef &lhs, const char *rhs) {
|
|
130
130
|
|
131
131
|
#ifdef USE_JSON
|
132
132
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
133
|
-
void convertToJson(const StringRef &src, JsonVariant dst);
|
133
|
+
inline void convertToJson(const StringRef &src, JsonVariant dst) { dst.set(src.c_str()); }
|
134
134
|
#endif // USE_JSON
|
135
135
|
|
136
136
|
} // namespace esphome
|
esphome/core/time.cpp
CHANGED
@@ -77,7 +77,7 @@ bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) {
|
|
77
77
|
&hour, // NOLINT
|
78
78
|
&minute, // NOLINT
|
79
79
|
&second, &num) == 6 && // NOLINT
|
80
|
-
num == time_to_parse.size()) {
|
80
|
+
num == static_cast<int>(time_to_parse.size())) {
|
81
81
|
esp_time.year = year;
|
82
82
|
esp_time.month = month;
|
83
83
|
esp_time.day_of_month = day;
|
@@ -87,7 +87,7 @@ bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) {
|
|
87
87
|
} else if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %02hhu:%02hhu %n", &year, &month, &day, // NOLINT
|
88
88
|
&hour, // NOLINT
|
89
89
|
&minute, &num) == 5 && // NOLINT
|
90
|
-
num == time_to_parse.size()) {
|
90
|
+
num == static_cast<int>(time_to_parse.size())) {
|
91
91
|
esp_time.year = year;
|
92
92
|
esp_time.month = month;
|
93
93
|
esp_time.day_of_month = day;
|
@@ -95,17 +95,17 @@ bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) {
|
|
95
95
|
esp_time.minute = minute;
|
96
96
|
esp_time.second = 0;
|
97
97
|
} else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu:%02hhu %n", &hour, &minute, &second, &num) == 3 && // NOLINT
|
98
|
-
num == time_to_parse.size()) {
|
98
|
+
num == static_cast<int>(time_to_parse.size())) {
|
99
99
|
esp_time.hour = hour;
|
100
100
|
esp_time.minute = minute;
|
101
101
|
esp_time.second = second;
|
102
102
|
} else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu %n", &hour, &minute, &num) == 2 && // NOLINT
|
103
|
-
num == time_to_parse.size()) {
|
103
|
+
num == static_cast<int>(time_to_parse.size())) {
|
104
104
|
esp_time.hour = hour;
|
105
105
|
esp_time.minute = minute;
|
106
106
|
esp_time.second = 0;
|
107
107
|
} else if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %n", &year, &month, &day, &num) == 3 && // NOLINT
|
108
|
-
num == time_to_parse.size()) {
|
108
|
+
num == static_cast<int>(time_to_parse.size())) {
|
109
109
|
esp_time.year = year;
|
110
110
|
esp_time.month = month;
|
111
111
|
esp_time.day_of_month = day;
|
esphome/cpp_generator.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import abc
|
2
|
-
from collections.abc import Callable
|
2
|
+
from collections.abc import Callable
|
3
3
|
import inspect
|
4
4
|
import math
|
5
5
|
import re
|
@@ -13,7 +13,6 @@ from esphome.core import (
|
|
13
13
|
HexInt,
|
14
14
|
Lambda,
|
15
15
|
Library,
|
16
|
-
TimePeriod,
|
17
16
|
TimePeriodMicroseconds,
|
18
17
|
TimePeriodMilliseconds,
|
19
18
|
TimePeriodMinutes,
|
@@ -21,35 +20,11 @@ from esphome.core import (
|
|
21
20
|
TimePeriodSeconds,
|
22
21
|
)
|
23
22
|
from esphome.helpers import cpp_string_escape, indent_all_but_first_and_last
|
23
|
+
from esphome.types import Expression, SafeExpType, TemplateArgsType
|
24
24
|
from esphome.util import OrderedDict
|
25
25
|
from esphome.yaml_util import ESPHomeDataBase
|
26
26
|
|
27
27
|
|
28
|
-
class Expression(abc.ABC):
|
29
|
-
__slots__ = ()
|
30
|
-
|
31
|
-
@abc.abstractmethod
|
32
|
-
def __str__(self):
|
33
|
-
"""
|
34
|
-
Convert expression into C++ code
|
35
|
-
"""
|
36
|
-
|
37
|
-
|
38
|
-
SafeExpType = (
|
39
|
-
Expression
|
40
|
-
| bool
|
41
|
-
| str
|
42
|
-
| str
|
43
|
-
| int
|
44
|
-
| float
|
45
|
-
| TimePeriod
|
46
|
-
| type[bool]
|
47
|
-
| type[int]
|
48
|
-
| type[float]
|
49
|
-
| Sequence[Any]
|
50
|
-
)
|
51
|
-
|
52
|
-
|
53
28
|
class RawExpression(Expression):
|
54
29
|
__slots__ = ("text",)
|
55
30
|
|
@@ -575,7 +550,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
|
|
575
550
|
return obj
|
576
551
|
|
577
552
|
|
578
|
-
def new_Pvariable(id_: ID, *args: SafeExpType) ->
|
553
|
+
def new_Pvariable(id_: ID, *args: SafeExpType) -> "MockObj":
|
579
554
|
"""Declare a new pointer variable in the code generation by calling it's constructor
|
580
555
|
with the given arguments.
|
581
556
|
|
@@ -681,7 +656,7 @@ async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]:
|
|
681
656
|
|
682
657
|
async def process_lambda(
|
683
658
|
value: Lambda,
|
684
|
-
parameters:
|
659
|
+
parameters: TemplateArgsType,
|
685
660
|
capture: str = "=",
|
686
661
|
return_type: SafeExpType = None,
|
687
662
|
) -> LambdaExpression | None:
|
esphome/dashboard/const.py
CHANGED
@@ -1,9 +1,26 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
from esphome.enum import StrEnum
|
4
|
+
|
5
|
+
|
6
|
+
class DashboardEvent(StrEnum):
|
7
|
+
"""Dashboard WebSocket event types."""
|
8
|
+
|
9
|
+
# Server -> Client events (backend sends to frontend)
|
10
|
+
ENTRY_ADDED = "entry_added"
|
11
|
+
ENTRY_REMOVED = "entry_removed"
|
12
|
+
ENTRY_UPDATED = "entry_updated"
|
13
|
+
ENTRY_STATE_CHANGED = "entry_state_changed"
|
14
|
+
IMPORTABLE_DEVICE_ADDED = "importable_device_added"
|
15
|
+
IMPORTABLE_DEVICE_REMOVED = "importable_device_removed"
|
16
|
+
INITIAL_STATE = "initial_state" # Sent on WebSocket connection
|
17
|
+
PONG = "pong" # Response to client ping
|
18
|
+
|
19
|
+
# Client -> Server events (frontend sends to backend)
|
20
|
+
PING = "ping" # WebSocket keepalive from client
|
21
|
+
REFRESH = "refresh" # Force backend to poll for changes
|
22
|
+
|
23
|
+
|
7
24
|
MAX_EXECUTOR_WORKERS = 48
|
8
25
|
|
9
26
|
|
esphome/dashboard/core.py
CHANGED
@@ -7,13 +7,13 @@ from dataclasses import dataclass
|
|
7
7
|
from functools import partial
|
8
8
|
import json
|
9
9
|
import logging
|
10
|
-
from pathlib import Path
|
11
10
|
import threading
|
12
11
|
from typing import Any
|
13
12
|
|
14
13
|
from esphome.storage_json import ignored_devices_storage_path
|
15
14
|
|
16
15
|
from ..zeroconf import DiscoveredImport
|
16
|
+
from .const import DashboardEvent
|
17
17
|
from .dns import DNSCache
|
18
18
|
from .entries import DashboardEntries
|
19
19
|
from .settings import DashboardSettings
|
@@ -31,7 +31,7 @@ MDNS_BOOTSTRAP_TIME = 7.5
|
|
31
31
|
class Event:
|
32
32
|
"""Dashboard Event."""
|
33
33
|
|
34
|
-
event_type:
|
34
|
+
event_type: DashboardEvent
|
35
35
|
data: dict[str, Any]
|
36
36
|
|
37
37
|
|
@@ -40,22 +40,24 @@ class EventBus:
|
|
40
40
|
|
41
41
|
def __init__(self) -> None:
|
42
42
|
"""Initialize the Dashboard event bus."""
|
43
|
-
self._listeners: dict[
|
43
|
+
self._listeners: dict[DashboardEvent, set[Callable[[Event], None]]] = {}
|
44
44
|
|
45
45
|
def async_add_listener(
|
46
|
-
self, event_type:
|
46
|
+
self, event_type: DashboardEvent, listener: Callable[[Event], None]
|
47
47
|
) -> Callable[[], None]:
|
48
48
|
"""Add a listener to the event bus."""
|
49
49
|
self._listeners.setdefault(event_type, set()).add(listener)
|
50
50
|
return partial(self._async_remove_listener, event_type, listener)
|
51
51
|
|
52
52
|
def _async_remove_listener(
|
53
|
-
self, event_type:
|
53
|
+
self, event_type: DashboardEvent, listener: Callable[[Event], None]
|
54
54
|
) -> None:
|
55
55
|
"""Remove a listener from the event bus."""
|
56
56
|
self._listeners[event_type].discard(listener)
|
57
57
|
|
58
|
-
def async_fire(
|
58
|
+
def async_fire(
|
59
|
+
self, event_type: DashboardEvent, event_data: dict[str, Any]
|
60
|
+
) -> None:
|
59
61
|
"""Fire an event."""
|
60
62
|
event = Event(event_type, event_data)
|
61
63
|
|
@@ -108,7 +110,7 @@ class ESPHomeDashboard:
|
|
108
110
|
await self.loop.run_in_executor(None, self.load_ignored_devices)
|
109
111
|
|
110
112
|
def load_ignored_devices(self) -> None:
|
111
|
-
storage_path =
|
113
|
+
storage_path = ignored_devices_storage_path()
|
112
114
|
try:
|
113
115
|
with storage_path.open("r", encoding="utf-8") as f_handle:
|
114
116
|
data = json.load(f_handle)
|
@@ -117,7 +119,7 @@ class ESPHomeDashboard:
|
|
117
119
|
pass
|
118
120
|
|
119
121
|
def save_ignored_devices(self) -> None:
|
120
|
-
storage_path =
|
122
|
+
storage_path = ignored_devices_storage_path()
|
121
123
|
with storage_path.open("w", encoding="utf-8") as f_handle:
|
122
124
|
json.dump(
|
123
125
|
{"ignored_devices": sorted(self.ignored_devices)}, indent=2, fp=f_handle
|
esphome/dashboard/dns.py
CHANGED
@@ -28,6 +28,21 @@ class DNSCache:
|
|
28
28
|
self._cache: dict[str, tuple[float, list[str] | Exception]] = {}
|
29
29
|
self._ttl = ttl
|
30
30
|
|
31
|
+
def get_cached_addresses(
|
32
|
+
self, hostname: str, now_monotonic: float
|
33
|
+
) -> list[str] | None:
|
34
|
+
"""Get cached addresses without triggering resolution.
|
35
|
+
|
36
|
+
Returns None if not in cache, list of addresses if found.
|
37
|
+
"""
|
38
|
+
# Normalize hostname for consistent lookups
|
39
|
+
normalized = hostname.rstrip(".").lower()
|
40
|
+
if expire_time_addresses := self._cache.get(normalized):
|
41
|
+
expire_time, addresses = expire_time_addresses
|
42
|
+
if expire_time > now_monotonic and not isinstance(addresses, Exception):
|
43
|
+
return addresses
|
44
|
+
return None
|
45
|
+
|
31
46
|
async def async_resolve(
|
32
47
|
self, hostname: str, now_monotonic: float
|
33
48
|
) -> list[str] | Exception:
|
esphome/dashboard/entries.py
CHANGED
@@ -5,20 +5,14 @@ from collections import defaultdict
|
|
5
5
|
from dataclasses import dataclass
|
6
6
|
from functools import lru_cache
|
7
7
|
import logging
|
8
|
-
import
|
8
|
+
from pathlib import Path
|
9
9
|
from typing import TYPE_CHECKING, Any
|
10
10
|
|
11
11
|
from esphome import const, util
|
12
12
|
from esphome.enum import StrEnum
|
13
13
|
from esphome.storage_json import StorageJSON, ext_storage_path
|
14
14
|
|
15
|
-
from .const import
|
16
|
-
DASHBOARD_COMMAND,
|
17
|
-
EVENT_ENTRY_ADDED,
|
18
|
-
EVENT_ENTRY_REMOVED,
|
19
|
-
EVENT_ENTRY_STATE_CHANGED,
|
20
|
-
EVENT_ENTRY_UPDATED,
|
21
|
-
)
|
15
|
+
from .const import DASHBOARD_COMMAND, DashboardEvent
|
22
16
|
from .util.subprocess import async_run_system_command
|
23
17
|
|
24
18
|
if TYPE_CHECKING:
|
@@ -102,12 +96,12 @@ class DashboardEntries:
|
|
102
96
|
# "path/to/file.yaml": DashboardEntry,
|
103
97
|
# ...
|
104
98
|
# }
|
105
|
-
self._entries: dict[
|
99
|
+
self._entries: dict[Path, DashboardEntry] = {}
|
106
100
|
self._loaded_entries = False
|
107
101
|
self._update_lock = asyncio.Lock()
|
108
102
|
self._name_to_entry: dict[str, set[DashboardEntry]] = defaultdict(set)
|
109
103
|
|
110
|
-
def get(self, path:
|
104
|
+
def get(self, path: Path) -> DashboardEntry | None:
|
111
105
|
"""Get an entry by path."""
|
112
106
|
return self._entries.get(path)
|
113
107
|
|
@@ -192,7 +186,7 @@ class DashboardEntries:
|
|
192
186
|
return
|
193
187
|
entry.state = state
|
194
188
|
self._dashboard.bus.async_fire(
|
195
|
-
|
189
|
+
DashboardEvent.ENTRY_STATE_CHANGED, {"entry": entry, "state": state}
|
196
190
|
)
|
197
191
|
|
198
192
|
async def async_request_update_entries(self) -> None:
|
@@ -260,22 +254,22 @@ class DashboardEntries:
|
|
260
254
|
for entry in added:
|
261
255
|
entries[entry.path] = entry
|
262
256
|
name_to_entry[entry.name].add(entry)
|
263
|
-
bus.async_fire(
|
257
|
+
bus.async_fire(DashboardEvent.ENTRY_ADDED, {"entry": entry})
|
264
258
|
|
265
259
|
for entry in removed:
|
266
260
|
del entries[entry.path]
|
267
261
|
name_to_entry[entry.name].discard(entry)
|
268
|
-
bus.async_fire(
|
262
|
+
bus.async_fire(DashboardEvent.ENTRY_REMOVED, {"entry": entry})
|
269
263
|
|
270
264
|
for entry in updated:
|
271
265
|
if (original_name := original_names[entry]) != (current_name := entry.name):
|
272
266
|
name_to_entry[original_name].discard(entry)
|
273
267
|
name_to_entry[current_name].add(entry)
|
274
|
-
bus.async_fire(
|
268
|
+
bus.async_fire(DashboardEvent.ENTRY_UPDATED, {"entry": entry})
|
275
269
|
|
276
|
-
def _get_path_to_cache_key(self) -> dict[
|
270
|
+
def _get_path_to_cache_key(self) -> dict[Path, DashboardCacheKeyType]:
|
277
271
|
"""Return a dict of path to cache key."""
|
278
|
-
path_to_cache_key: dict[
|
272
|
+
path_to_cache_key: dict[Path, DashboardCacheKeyType] = {}
|
279
273
|
#
|
280
274
|
# The cache key is (inode, device, mtime, size)
|
281
275
|
# which allows us to avoid locking since it ensures
|
@@ -287,12 +281,12 @@ class DashboardEntries:
|
|
287
281
|
for file in util.list_yaml_files([self._config_dir]):
|
288
282
|
try:
|
289
283
|
# Prefer the json storage path if it exists
|
290
|
-
stat =
|
284
|
+
stat = ext_storage_path(file.name).stat()
|
291
285
|
except OSError:
|
292
286
|
try:
|
293
287
|
# Fallback to the yaml file if the storage
|
294
288
|
# file does not exist or could not be generated
|
295
|
-
stat =
|
289
|
+
stat = file.stat()
|
296
290
|
except OSError:
|
297
291
|
# File was deleted, ignore
|
298
292
|
continue
|
@@ -329,10 +323,10 @@ class DashboardEntry:
|
|
329
323
|
"_to_dict",
|
330
324
|
)
|
331
325
|
|
332
|
-
def __init__(self, path:
|
326
|
+
def __init__(self, path: Path, cache_key: DashboardCacheKeyType) -> None:
|
333
327
|
"""Initialize the DashboardEntry."""
|
334
328
|
self.path = path
|
335
|
-
self.filename: str =
|
329
|
+
self.filename: str = path.name
|
336
330
|
self._storage_path = ext_storage_path(self.filename)
|
337
331
|
self.cache_key = cache_key
|
338
332
|
self.storage: StorageJSON | None = None
|
@@ -365,7 +359,7 @@ class DashboardEntry:
|
|
365
359
|
"loaded_integrations": sorted(self.loaded_integrations),
|
366
360
|
"deployed_version": self.update_old,
|
367
361
|
"current_version": self.update_new,
|
368
|
-
"path": self.path,
|
362
|
+
"path": str(self.path),
|
369
363
|
"comment": self.comment,
|
370
364
|
"address": self.address,
|
371
365
|
"web_port": self.web_port,
|
@@ -0,0 +1,76 @@
|
|
1
|
+
"""Data models and builders for the dashboard."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, TypedDict
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from esphome.zeroconf import DiscoveredImport
|
9
|
+
|
10
|
+
from .core import ESPHomeDashboard
|
11
|
+
from .entries import DashboardEntry
|
12
|
+
|
13
|
+
|
14
|
+
class ImportableDeviceDict(TypedDict):
|
15
|
+
"""Dictionary representation of an importable device."""
|
16
|
+
|
17
|
+
name: str
|
18
|
+
friendly_name: str | None
|
19
|
+
package_import_url: str
|
20
|
+
project_name: str
|
21
|
+
project_version: str
|
22
|
+
network: str
|
23
|
+
ignored: bool
|
24
|
+
|
25
|
+
|
26
|
+
class ConfiguredDeviceDict(TypedDict, total=False):
|
27
|
+
"""Dictionary representation of a configured device."""
|
28
|
+
|
29
|
+
name: str
|
30
|
+
friendly_name: str | None
|
31
|
+
configuration: str
|
32
|
+
loaded_integrations: list[str] | None
|
33
|
+
deployed_version: str | None
|
34
|
+
current_version: str | None
|
35
|
+
path: str
|
36
|
+
comment: str | None
|
37
|
+
address: str | None
|
38
|
+
web_port: int | None
|
39
|
+
target_platform: str | None
|
40
|
+
|
41
|
+
|
42
|
+
class DeviceListResponse(TypedDict):
|
43
|
+
"""Response for device list API."""
|
44
|
+
|
45
|
+
configured: list[ConfiguredDeviceDict]
|
46
|
+
importable: list[ImportableDeviceDict]
|
47
|
+
|
48
|
+
|
49
|
+
def build_importable_device_dict(
|
50
|
+
dashboard: ESPHomeDashboard, discovered: DiscoveredImport
|
51
|
+
) -> ImportableDeviceDict:
|
52
|
+
"""Build the importable device dictionary."""
|
53
|
+
return ImportableDeviceDict(
|
54
|
+
name=discovered.device_name,
|
55
|
+
friendly_name=discovered.friendly_name,
|
56
|
+
package_import_url=discovered.package_import_url,
|
57
|
+
project_name=discovered.project_name,
|
58
|
+
project_version=discovered.project_version,
|
59
|
+
network=discovered.network,
|
60
|
+
ignored=discovered.device_name in dashboard.ignored_devices,
|
61
|
+
)
|
62
|
+
|
63
|
+
|
64
|
+
def build_device_list_response(
|
65
|
+
dashboard: ESPHomeDashboard, entries: list[DashboardEntry]
|
66
|
+
) -> DeviceListResponse:
|
67
|
+
"""Build the device list response data."""
|
68
|
+
configured = {entry.name for entry in entries}
|
69
|
+
return DeviceListResponse(
|
70
|
+
configured=[entry.to_dict() for entry in entries],
|
71
|
+
importable=[
|
72
|
+
build_importable_device_dict(dashboard, res)
|
73
|
+
for res in dashboard.import_result.values()
|
74
|
+
if res.device_name not in configured
|
75
|
+
],
|
76
|
+
)
|
esphome/dashboard/settings.py
CHANGED
@@ -27,7 +27,7 @@ class DashboardSettings:
|
|
27
27
|
|
28
28
|
def __init__(self) -> None:
|
29
29
|
"""Initialize the dashboard settings."""
|
30
|
-
self.config_dir:
|
30
|
+
self.config_dir: Path = None
|
31
31
|
self.password_hash: str = ""
|
32
32
|
self.username: str = ""
|
33
33
|
self.using_password: bool = False
|
@@ -45,10 +45,10 @@ class DashboardSettings:
|
|
45
45
|
self.using_password = bool(password)
|
46
46
|
if self.using_password:
|
47
47
|
self.password_hash = password_hash(password)
|
48
|
-
self.config_dir = args.configuration
|
49
|
-
self.absolute_config_dir =
|
48
|
+
self.config_dir = Path(args.configuration)
|
49
|
+
self.absolute_config_dir = self.config_dir.resolve()
|
50
50
|
self.verbose = args.verbose
|
51
|
-
CORE.config_path =
|
51
|
+
CORE.config_path = self.config_dir / "."
|
52
52
|
|
53
53
|
@property
|
54
54
|
def relative_url(self) -> str:
|
@@ -81,9 +81,9 @@ class DashboardSettings:
|
|
81
81
|
# Compare password in constant running time (to prevent timing attacks)
|
82
82
|
return hmac.compare_digest(self.password_hash, password_hash(password))
|
83
83
|
|
84
|
-
def rel_path(self, *args: Any) ->
|
84
|
+
def rel_path(self, *args: Any) -> Path:
|
85
85
|
"""Return a path relative to the ESPHome config folder."""
|
86
|
-
joined_path =
|
86
|
+
joined_path = self.config_dir / Path(*args)
|
87
87
|
# Raises ValueError if not relative to ESPHome config folder
|
88
|
-
|
88
|
+
joined_path.resolve().relative_to(self.absolute_config_dir)
|
89
89
|
return joined_path
|
esphome/dashboard/status/mdns.py
CHANGED
@@ -4,16 +4,21 @@ import asyncio
|
|
4
4
|
import logging
|
5
5
|
import typing
|
6
6
|
|
7
|
+
from zeroconf import AddressResolver, IPVersion
|
8
|
+
|
9
|
+
from esphome.address_cache import normalize_hostname
|
7
10
|
from esphome.zeroconf import (
|
8
11
|
ESPHOME_SERVICE_TYPE,
|
9
12
|
AsyncEsphomeZeroconf,
|
10
13
|
DashboardBrowser,
|
11
14
|
DashboardImportDiscovery,
|
12
15
|
DashboardStatus,
|
16
|
+
DiscoveredImport,
|
13
17
|
)
|
14
18
|
|
15
|
-
from ..const import SENTINEL
|
19
|
+
from ..const import SENTINEL, DashboardEvent
|
16
20
|
from ..entries import DashboardEntry, EntryStateSource, bool_to_entry_state
|
21
|
+
from ..models import build_importable_device_dict
|
17
22
|
|
18
23
|
if typing.TYPE_CHECKING:
|
19
24
|
from ..core import ESPHomeDashboard
|
@@ -50,6 +55,44 @@ class MDNSStatus:
|
|
50
55
|
return await aiozc.async_resolve_host(host_name)
|
51
56
|
return None
|
52
57
|
|
58
|
+
def get_cached_addresses(self, host_name: str) -> list[str] | None:
|
59
|
+
"""Get cached addresses for a host without triggering resolution.
|
60
|
+
|
61
|
+
Returns None if not in cache or no zeroconf available.
|
62
|
+
"""
|
63
|
+
if not self.aiozc:
|
64
|
+
_LOGGER.debug("No zeroconf instance available for %s", host_name)
|
65
|
+
return None
|
66
|
+
|
67
|
+
# Normalize hostname and get the base name
|
68
|
+
normalized = normalize_hostname(host_name)
|
69
|
+
base_name = normalized.partition(".")[0]
|
70
|
+
|
71
|
+
# Try to load from zeroconf cache without triggering resolution
|
72
|
+
resolver_name = f"{base_name}.local."
|
73
|
+
info = AddressResolver(resolver_name)
|
74
|
+
# Let zeroconf use its own current time for cache checking
|
75
|
+
if info.load_from_cache(self.aiozc.zeroconf):
|
76
|
+
addresses = info.parsed_scoped_addresses(IPVersion.All)
|
77
|
+
_LOGGER.debug("Found %s in zeroconf cache: %s", resolver_name, addresses)
|
78
|
+
return addresses
|
79
|
+
_LOGGER.debug("Not found in zeroconf cache: %s", resolver_name)
|
80
|
+
return None
|
81
|
+
|
82
|
+
def _on_import_update(self, name: str, discovered: DiscoveredImport | None) -> None:
|
83
|
+
"""Handle importable device updates."""
|
84
|
+
if discovered is None:
|
85
|
+
# Device removed
|
86
|
+
self.dashboard.bus.async_fire(
|
87
|
+
DashboardEvent.IMPORTABLE_DEVICE_REMOVED, {"name": name}
|
88
|
+
)
|
89
|
+
else:
|
90
|
+
# Device added
|
91
|
+
self.dashboard.bus.async_fire(
|
92
|
+
DashboardEvent.IMPORTABLE_DEVICE_ADDED,
|
93
|
+
{"device": build_importable_device_dict(self.dashboard, discovered)},
|
94
|
+
)
|
95
|
+
|
53
96
|
async def async_refresh_hosts(self) -> None:
|
54
97
|
"""Refresh the hosts to track."""
|
55
98
|
dashboard = self.dashboard
|
@@ -106,7 +149,8 @@ class MDNSStatus:
|
|
106
149
|
self._async_set_state(entry, result)
|
107
150
|
|
108
151
|
stat = DashboardStatus(on_update)
|
109
|
-
|
152
|
+
|
153
|
+
imports = DashboardImportDiscovery(self._on_import_update)
|
110
154
|
dashboard.import_result = imports.import_state
|
111
155
|
|
112
156
|
browser = DashboardBrowser(
|