esphome 2024.10.2__py3-none-any.whl → 2024.11.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 +22 -4
- esphome/automation.py +29 -2
- esphome/components/animation/__init__.py +5 -8
- esphome/components/animation/animation.cpp +1 -1
- esphome/components/audio/__init__.py +9 -0
- esphome/components/audio/audio.h +21 -0
- esphome/components/axs15231/__init__.py +6 -0
- esphome/components/axs15231/touchscreen/__init__.py +36 -0
- esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +64 -0
- esphome/components/axs15231/touchscreen/axs15231_touchscreen.h +27 -0
- esphome/components/bme68x_bsec2/__init__.py +1 -1
- esphome/components/bme68x_bsec2/bme68x_bsec2.cpp +50 -47
- esphome/components/bme68x_bsec2/bme68x_bsec2.h +0 -2
- esphome/components/bytebuffer/__init__.py +5 -0
- esphome/components/bytebuffer/bytebuffer.h +421 -0
- esphome/components/climate/__init__.py +14 -13
- esphome/components/datetime/__init__.py +3 -3
- esphome/components/debug/debug_esp32.cpp +16 -8
- esphome/components/dfplayer/dfplayer.cpp +132 -6
- esphome/components/dfplayer/dfplayer.h +19 -53
- esphome/components/display/display.cpp +142 -0
- esphome/components/display/display.h +7 -0
- esphome/components/es8311/__init__.py +0 -0
- esphome/components/es8311/audio_dac.py +70 -0
- esphome/components/es8311/es8311.cpp +227 -0
- esphome/components/es8311/es8311.h +135 -0
- esphome/components/es8311/es8311_const.h +195 -0
- esphome/components/esp32/boards.py +199 -1
- esphome/components/esp32/gpio.py +3 -1
- esphome/components/esp32_ble/const_esp32c6.h +7 -0
- esphome/components/esp32_ble_client/ble_client_base.h +1 -1
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +3 -0
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +2 -1
- esphome/components/esp32_rmt_led_strip/led_strip.cpp +2 -2
- esphome/components/esp32_rmt_led_strip/led_strip.h +2 -0
- esphome/components/esp32_rmt_led_strip/light.py +3 -1
- esphome/components/esp8266/gpio.py +7 -5
- esphome/components/ethernet/__init__.py +55 -1
- esphome/components/ethernet/ethernet_component.cpp +14 -1
- esphome/components/ethernet/ethernet_component.h +7 -1
- esphome/components/font/__init__.py +213 -108
- esphome/components/gp8403/output/__init__.py +1 -1
- esphome/components/host/gpio.py +6 -4
- esphome/components/http_request/__init__.py +12 -0
- esphome/components/http_request/http_request.h +65 -3
- esphome/components/http_request/http_request_arduino.cpp +4 -3
- esphome/components/http_request/http_request_idf.cpp +12 -14
- esphome/components/http_request/ota/ota_http_request.cpp +1 -1
- esphome/components/http_request/update/http_request_update.cpp +1 -1
- esphome/components/i2c_device/__init__.py +26 -0
- esphome/components/i2c_device/i2c_device.cpp +17 -0
- esphome/components/i2c_device/i2c_device.h +18 -0
- esphome/components/i2s_audio/__init__.py +1 -3
- esphome/components/i2s_audio/speaker/__init__.py +12 -4
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +426 -200
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +92 -33
- esphome/components/ili9xxx/display.py +5 -1
- esphome/components/image/__init__.py +5 -8
- esphome/components/image/image.cpp +14 -14
- esphome/components/image/image.h +20 -24
- esphome/components/internal_temperature/internal_temperature.cpp +51 -2
- esphome/components/internal_temperature/internal_temperature.h +1 -0
- esphome/components/ld2420/ld2420.cpp +1 -1
- esphome/components/libretiny/gpio.py +4 -2
- esphome/components/light/__init__.py +32 -1
- esphome/components/light/automation.py +39 -32
- esphome/components/light/effects.py +36 -36
- esphome/components/light/light_state.cpp +6 -16
- esphome/components/light/light_state.h +34 -0
- esphome/components/light/types.py +3 -1
- esphome/components/logger/logger_esp32.cpp +15 -0
- esphome/components/lvgl/__init__.py +202 -95
- esphome/components/lvgl/automation.py +42 -40
- esphome/components/lvgl/binary_sensor/__init__.py +8 -15
- esphome/components/lvgl/defines.py +14 -8
- esphome/components/lvgl/encoders.py +11 -8
- esphome/components/lvgl/keypads.py +77 -0
- esphome/components/lvgl/light/__init__.py +6 -8
- esphome/components/lvgl/lv_validation.py +2 -4
- esphome/components/lvgl/lvcode.py +3 -9
- esphome/components/lvgl/lvgl_esphome.cpp +210 -89
- esphome/components/lvgl/lvgl_esphome.h +113 -30
- esphome/components/lvgl/lvgl_proxy.h +17 -0
- esphome/components/lvgl/number/__init__.py +10 -15
- esphome/components/lvgl/schemas.py +4 -2
- esphome/components/lvgl/select/__init__.py +12 -37
- esphome/components/lvgl/select/lvgl_select.h +27 -33
- esphome/components/lvgl/sensor/__init__.py +8 -14
- esphome/components/lvgl/styles.py +3 -4
- esphome/components/lvgl/switch/__init__.py +8 -13
- esphome/components/lvgl/text/__init__.py +5 -6
- esphome/components/lvgl/text_sensor/__init__.py +15 -15
- esphome/components/lvgl/touchscreens.py +2 -3
- esphome/components/lvgl/trigger.py +7 -9
- esphome/components/lvgl/types.py +9 -3
- esphome/components/lvgl/widgets/__init__.py +32 -21
- esphome/components/lvgl/widgets/animimg.py +4 -3
- esphome/components/lvgl/widgets/dropdown.py +22 -10
- esphome/components/lvgl/widgets/img.py +2 -0
- esphome/components/lvgl/widgets/msgbox.py +6 -5
- esphome/components/lvgl/widgets/obj.py +4 -2
- esphome/components/lvgl/widgets/page.py +3 -2
- esphome/components/lvgl/widgets/qrcode.py +54 -0
- esphome/components/lvgl/widgets/roller.py +21 -14
- esphome/components/lvgl/widgets/tileview.py +2 -1
- esphome/components/max17043/__init__.py +1 -0
- esphome/components/max17043/automation.h +20 -0
- esphome/components/max17043/max17043.cpp +98 -0
- esphome/components/max17043/max17043.h +29 -0
- esphome/components/max17043/sensor.py +77 -0
- esphome/components/media_player/__init__.py +11 -0
- esphome/components/media_player/automation.h +10 -0
- esphome/components/media_player/media_player.cpp +4 -0
- esphome/components/midea/air_conditioner.cpp +17 -1
- esphome/components/mlx90393/sensor.py +1 -1
- esphome/components/modbus/modbus.cpp +24 -12
- esphome/components/modbus_controller/__init__.py +31 -1
- esphome/components/modbus_controller/automation.h +16 -0
- esphome/components/modbus_controller/const.py +2 -0
- esphome/components/modbus_controller/modbus_controller.cpp +14 -2
- esphome/components/modbus_controller/modbus_controller.h +9 -0
- esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +40 -21
- esphome/components/mopeka_pro_check/mopeka_pro_check.h +9 -2
- esphome/components/mopeka_pro_check/sensor.py +41 -0
- esphome/components/mqtt/__init__.py +36 -0
- esphome/components/mqtt/mqtt_client.cpp +27 -3
- esphome/components/mqtt/mqtt_client.h +27 -2
- esphome/components/mqtt/mqtt_climate.cpp +4 -2
- esphome/components/mqtt/mqtt_component.cpp +6 -0
- esphome/components/mqtt/mqtt_component.h +4 -0
- esphome/components/mqtt/mqtt_const.h +6 -0
- esphome/components/online_image/online_image.cpp +2 -8
- esphome/components/online_image/online_image.h +2 -6
- esphome/components/opentherm/__init__.py +35 -9
- esphome/components/opentherm/binary_sensor/__init__.py +33 -0
- esphome/components/opentherm/const.py +11 -0
- esphome/components/opentherm/generate.py +142 -0
- esphome/components/opentherm/hub.cpp +130 -24
- esphome/components/opentherm/hub.h +62 -9
- esphome/components/opentherm/input.h +18 -0
- esphome/components/opentherm/input.py +51 -0
- esphome/components/opentherm/number/__init__.py +74 -0
- esphome/components/opentherm/number/number.cpp +40 -0
- esphome/components/opentherm/number/number.h +31 -0
- esphome/components/opentherm/opentherm.cpp +30 -0
- esphome/components/opentherm/opentherm.h +34 -2
- esphome/components/opentherm/opentherm_macros.h +151 -0
- esphome/components/opentherm/output/__init__.py +47 -0
- esphome/components/opentherm/output/output.cpp +18 -0
- esphome/components/opentherm/output/output.h +33 -0
- esphome/components/opentherm/schema.py +814 -0
- esphome/components/opentherm/sensor/__init__.py +51 -0
- esphome/components/opentherm/switch/__init__.py +43 -0
- esphome/components/opentherm/switch/switch.cpp +28 -0
- esphome/components/opentherm/switch/switch.h +20 -0
- esphome/components/opentherm/validate.py +31 -0
- esphome/components/pcd8544/display.py +8 -4
- esphome/components/prometheus/prometheus_handler.cpp +176 -14
- esphome/components/prometheus/prometheus_handler.h +25 -7
- esphome/components/qspi_amoled/display.py +1 -141
- esphome/components/qspi_dbi/display.py +185 -0
- esphome/components/qspi_dbi/models.py +64 -0
- esphome/components/{qspi_amoled/qspi_amoled.cpp → qspi_dbi/qspi_dbi.cpp} +95 -46
- esphome/components/{qspi_amoled/qspi_amoled.h → qspi_dbi/qspi_dbi.h} +26 -15
- esphome/components/rp2040/__init__.py +6 -3
- esphome/components/rp2040/gpio.py +5 -3
- esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +20 -0
- esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h +3 -2
- esphome/components/rtttl/rtttl.cpp +4 -1
- esphome/components/rtttl/rtttl.h +1 -0
- esphome/components/sdl/sdl_esphome.cpp +22 -5
- esphome/components/sdl/sdl_esphome.h +1 -0
- esphome/components/sdm_meter/sdm_meter.cpp +1 -1
- esphome/components/sensor/__init__.py +18 -8
- esphome/components/sensor/filter.cpp +19 -18
- esphome/components/sensor/filter.h +9 -10
- esphome/components/sgp4x/sgp4x.cpp +40 -74
- esphome/components/sgp4x/sgp4x.h +5 -3
- esphome/components/speaker/__init__.py +51 -5
- esphome/components/speaker/automation.h +25 -0
- esphome/components/speaker/speaker.h +72 -1
- esphome/components/spi/__init__.py +15 -14
- esphome/components/spi_device/__init__.py +4 -15
- esphome/components/ssd1306_spi/display.py +6 -2
- esphome/components/ssd1322_spi/display.py +6 -2
- esphome/components/ssd1325_spi/display.py +6 -2
- esphome/components/ssd1327_spi/display.py +6 -2
- esphome/components/ssd1331_spi/display.py +6 -2
- esphome/components/ssd1351_spi/display.py +6 -2
- esphome/components/st7567_spi/display.py +6 -2
- esphome/components/st7701s/display.py +5 -1
- esphome/components/st7735/display.py +10 -5
- esphome/components/st7789v/display.py +12 -7
- esphome/components/statsd/statsd.cpp +2 -0
- esphome/components/statsd/statsd.h +2 -0
- esphome/components/sun/sun.h +3 -0
- esphome/components/tc74/__init__.py +1 -0
- esphome/components/tc74/sensor.py +32 -0
- esphome/components/tc74/tc74.cpp +68 -0
- esphome/components/tc74/tc74.h +28 -0
- esphome/components/touchscreen/__init__.py +41 -50
- esphome/components/touchscreen/touchscreen.h +4 -8
- esphome/components/tuya/fan/tuya_fan.cpp +1 -1
- esphome/components/udp/udp_component.cpp +6 -3
- esphome/components/udp/udp_component.h +4 -2
- esphome/components/waveshare_epaper/display.py +6 -2
- esphome/components/web_server/web_server.cpp +22 -0
- esphome/components/web_server/web_server.h +3 -0
- esphome/components/weikai/weikai.h +2 -2
- esphome/components/wifi/wifi_component.cpp +2 -2
- esphome/components/wifi/wifi_component_esp32_arduino.cpp +4 -4
- esphome/components/wifi/wifi_component_esp8266.cpp +4 -4
- esphome/components/wifi/wifi_component_esp_idf.cpp +2 -2
- esphome/components/xpt2046/touchscreen/__init__.py +7 -32
- esphome/config_validation.py +3 -1
- esphome/const.py +9 -2
- esphome/core/defines.h +8 -2
- esphome/core/helpers.cpp +32 -17
- esphome/core/helpers.h +32 -16
- esphome/core/ring_buffer.cpp +2 -2
- esphome/core/ring_buffer.h +2 -2
- esphome/dashboard/core.py +25 -0
- esphome/dashboard/status/mdns.py +3 -4
- esphome/dashboard/web_server.py +54 -19
- esphome/espota2.py +36 -35
- esphome/helpers.py +68 -16
- esphome/mqtt.py +9 -2
- esphome/storage_json.py +4 -0
- esphome/writer.py +7 -18
- esphome/zeroconf.py +8 -6
- {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/METADATA +7 -5
- {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/RECORD +237 -191
- esphome/core/bytebuffer.cpp +0 -167
- esphome/core/bytebuffer.h +0 -144
- /esphome/components/{qspi_amoled → qspi_dbi}/__init__.py +0 -0
- {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/LICENSE +0 -0
- {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/WHEEL +0 -0
- {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/entry_points.txt +0 -0
- {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/top_level.txt +0 -0
esphome/dashboard/core.py
CHANGED
@@ -5,10 +5,14 @@ from collections.abc import Coroutine
|
|
5
5
|
import contextlib
|
6
6
|
from dataclasses import dataclass
|
7
7
|
from functools import partial
|
8
|
+
import json
|
8
9
|
import logging
|
10
|
+
from pathlib import Path
|
9
11
|
import threading
|
10
12
|
from typing import TYPE_CHECKING, Any, Callable
|
11
13
|
|
14
|
+
from esphome.storage_json import ignored_devices_storage_path
|
15
|
+
|
12
16
|
from ..zeroconf import DiscoveredImport
|
13
17
|
from .dns import DNSCache
|
14
18
|
from .entries import DashboardEntries
|
@@ -20,6 +24,8 @@ if TYPE_CHECKING:
|
|
20
24
|
|
21
25
|
_LOGGER = logging.getLogger(__name__)
|
22
26
|
|
27
|
+
IGNORED_DEVICES_STORAGE_PATH = "ignored-devices.json"
|
28
|
+
|
23
29
|
|
24
30
|
@dataclass
|
25
31
|
class Event:
|
@@ -74,6 +80,7 @@ class ESPHomeDashboard:
|
|
74
80
|
"settings",
|
75
81
|
"dns_cache",
|
76
82
|
"_background_tasks",
|
83
|
+
"ignored_devices",
|
77
84
|
)
|
78
85
|
|
79
86
|
def __init__(self) -> None:
|
@@ -89,12 +96,30 @@ class ESPHomeDashboard:
|
|
89
96
|
self.settings = DashboardSettings()
|
90
97
|
self.dns_cache = DNSCache()
|
91
98
|
self._background_tasks: set[asyncio.Task] = set()
|
99
|
+
self.ignored_devices: set[str] = set()
|
92
100
|
|
93
101
|
async def async_setup(self) -> None:
|
94
102
|
"""Setup the dashboard."""
|
95
103
|
self.loop = asyncio.get_running_loop()
|
96
104
|
self.ping_request = asyncio.Event()
|
97
105
|
self.entries = DashboardEntries(self)
|
106
|
+
self.load_ignored_devices()
|
107
|
+
|
108
|
+
def load_ignored_devices(self) -> None:
|
109
|
+
storage_path = Path(ignored_devices_storage_path())
|
110
|
+
try:
|
111
|
+
with storage_path.open("r", encoding="utf-8") as f_handle:
|
112
|
+
data = json.load(f_handle)
|
113
|
+
self.ignored_devices = set(data.get("ignored_devices", set()))
|
114
|
+
except FileNotFoundError:
|
115
|
+
pass
|
116
|
+
|
117
|
+
def save_ignored_devices(self) -> None:
|
118
|
+
storage_path = Path(ignored_devices_storage_path())
|
119
|
+
with storage_path.open("w", encoding="utf-8") as f_handle:
|
120
|
+
json.dump(
|
121
|
+
{"ignored_devices": sorted(self.ignored_devices)}, indent=2, fp=f_handle
|
122
|
+
)
|
98
123
|
|
99
124
|
async def async_run(self) -> None:
|
100
125
|
"""Run the dashboard."""
|
esphome/dashboard/status/mdns.py
CHANGED
@@ -26,7 +26,7 @@ class MDNSStatus:
|
|
26
26
|
self.host_mdns_state: dict[str, bool | None] = {}
|
27
27
|
self._loop = asyncio.get_running_loop()
|
28
28
|
|
29
|
-
async def async_resolve_host(self, host_name: str) -> str | None:
|
29
|
+
async def async_resolve_host(self, host_name: str) -> list[str] | None:
|
30
30
|
"""Resolve a host name to an address in a thread-safe manner."""
|
31
31
|
if aiozc := self.aiozc:
|
32
32
|
return await aiozc.async_resolve_host(host_name)
|
@@ -50,13 +50,12 @@ class MDNSStatus:
|
|
50
50
|
poll_names.setdefault(entry.name, set()).add(entry)
|
51
51
|
elif (online := host_mdns_state.get(entry.name, SENTINEL)) != SENTINEL:
|
52
52
|
entries.async_set_state(entry, bool_to_entry_state(online))
|
53
|
-
|
54
53
|
if poll_names and self.aiozc:
|
55
54
|
results = await asyncio.gather(
|
56
55
|
*(self.aiozc.async_resolve_host(name) for name in poll_names)
|
57
56
|
)
|
58
|
-
for name,
|
59
|
-
result = bool(
|
57
|
+
for name, address_list in zip(poll_names, results):
|
58
|
+
result = bool(address_list)
|
60
59
|
host_mdns_state[name] = result
|
61
60
|
for entry in poll_names[name]:
|
62
61
|
entries.async_set_state(entry, bool_to_entry_state(result))
|
esphome/dashboard/web_server.py
CHANGED
@@ -7,6 +7,7 @@ import datetime
|
|
7
7
|
import functools
|
8
8
|
import gzip
|
9
9
|
import hashlib
|
10
|
+
import importlib
|
10
11
|
import json
|
11
12
|
import logging
|
12
13
|
import os
|
@@ -319,12 +320,12 @@ class EsphomePortCommandWebSocket(EsphomeCommandWebSocket):
|
|
319
320
|
and "api" in entry.loaded_integrations
|
320
321
|
):
|
321
322
|
if (mdns := dashboard.mdns_status) and (
|
322
|
-
|
323
|
+
address_list := await mdns.async_resolve_host(entry.name)
|
323
324
|
):
|
324
325
|
# Use the IP address if available but only
|
325
326
|
# if the API is loaded and the device is online
|
326
327
|
# since MQTT logging will not work otherwise
|
327
|
-
port =
|
328
|
+
port = address_list[0]
|
328
329
|
elif (
|
329
330
|
entry.address
|
330
331
|
and (
|
@@ -541,6 +542,46 @@ class ImportRequestHandler(BaseHandler):
|
|
541
542
|
self.finish()
|
542
543
|
|
543
544
|
|
545
|
+
class IgnoreDeviceRequestHandler(BaseHandler):
|
546
|
+
@authenticated
|
547
|
+
def post(self) -> None:
|
548
|
+
dashboard = DASHBOARD
|
549
|
+
try:
|
550
|
+
args = json.loads(self.request.body.decode())
|
551
|
+
device_name = args["name"]
|
552
|
+
ignore = args["ignore"]
|
553
|
+
except (json.JSONDecodeError, KeyError):
|
554
|
+
self.set_status(400)
|
555
|
+
self.set_header("content-type", "application/json")
|
556
|
+
self.write(json.dumps({"error": "Invalid payload"}))
|
557
|
+
return
|
558
|
+
|
559
|
+
ignored_device = next(
|
560
|
+
(
|
561
|
+
res
|
562
|
+
for res in dashboard.import_result.values()
|
563
|
+
if res.device_name == device_name
|
564
|
+
),
|
565
|
+
None,
|
566
|
+
)
|
567
|
+
|
568
|
+
if ignored_device is None:
|
569
|
+
self.set_status(404)
|
570
|
+
self.set_header("content-type", "application/json")
|
571
|
+
self.write(json.dumps({"error": "Device not found"}))
|
572
|
+
return
|
573
|
+
|
574
|
+
if ignore:
|
575
|
+
dashboard.ignored_devices.add(ignored_device.device_name)
|
576
|
+
else:
|
577
|
+
dashboard.ignored_devices.discard(ignored_device.device_name)
|
578
|
+
|
579
|
+
dashboard.save_ignored_devices()
|
580
|
+
|
581
|
+
self.set_status(204)
|
582
|
+
self.finish()
|
583
|
+
|
584
|
+
|
544
585
|
class DownloadListRequestHandler(BaseHandler):
|
545
586
|
@authenticated
|
546
587
|
@bind_config
|
@@ -555,26 +596,18 @@ class DownloadListRequestHandler(BaseHandler):
|
|
555
596
|
|
556
597
|
downloads = []
|
557
598
|
platform: str = storage_json.target_platform.lower()
|
558
|
-
if platform == const.PLATFORM_RP2040:
|
559
|
-
from esphome.components.rp2040 import get_download_types as rp2040_types
|
560
599
|
|
561
|
-
|
562
|
-
|
563
|
-
from esphome.components.esp8266 import get_download_types as esp8266_types
|
564
|
-
|
565
|
-
downloads = esp8266_types(storage_json)
|
566
|
-
elif platform.upper() in ESP32_VARIANTS:
|
567
|
-
from esphome.components.esp32 import get_download_types as esp32_types
|
568
|
-
|
569
|
-
downloads = esp32_types(storage_json)
|
600
|
+
if platform.upper() in ESP32_VARIANTS:
|
601
|
+
platform = "esp32"
|
570
602
|
elif platform in (const.PLATFORM_RTL87XX, const.PLATFORM_BK72XX):
|
571
|
-
|
572
|
-
get_download_types as libretiny_types,
|
573
|
-
)
|
603
|
+
platform = "libretiny"
|
574
604
|
|
575
|
-
|
576
|
-
|
577
|
-
|
605
|
+
try:
|
606
|
+
module = importlib.import_module(f"esphome.components.{platform}")
|
607
|
+
get_download_types = getattr(module, "get_download_types")
|
608
|
+
except AttributeError as exc:
|
609
|
+
raise ValueError(f"Unknown platform {platform}") from exc
|
610
|
+
downloads = get_download_types(storage_json)
|
578
611
|
|
579
612
|
self.set_status(200)
|
580
613
|
self.set_header("content-type", "application/json")
|
@@ -688,6 +721,7 @@ class ListDevicesHandler(BaseHandler):
|
|
688
721
|
"project_name": res.project_name,
|
689
722
|
"project_version": res.project_version,
|
690
723
|
"network": res.network,
|
724
|
+
"ignored": res.device_name in dashboard.ignored_devices,
|
691
725
|
}
|
692
726
|
for res in dashboard.import_result.values()
|
693
727
|
if res.device_name not in configured
|
@@ -1156,6 +1190,7 @@ def make_app(debug=get_bool_env(ENV_DEV)) -> tornado.web.Application:
|
|
1156
1190
|
(f"{rel}prometheus-sd", PrometheusServiceDiscoveryHandler),
|
1157
1191
|
(f"{rel}boards/([a-z0-9]+)", BoardsRequestHandler),
|
1158
1192
|
(f"{rel}version", EsphomeVersionHandler),
|
1193
|
+
(f"{rel}ignore-device", IgnoreDeviceRequestHandler),
|
1159
1194
|
],
|
1160
1195
|
**app_settings,
|
1161
1196
|
)
|
esphome/espota2.py
CHANGED
@@ -10,7 +10,7 @@ import sys
|
|
10
10
|
import time
|
11
11
|
|
12
12
|
from esphome.core import EsphomeError
|
13
|
-
from esphome.helpers import
|
13
|
+
from esphome.helpers import resolve_ip_address
|
14
14
|
|
15
15
|
RESPONSE_OK = 0x00
|
16
16
|
RESPONSE_REQUEST_AUTH = 0x01
|
@@ -311,44 +311,45 @@ def perform_ota(
|
|
311
311
|
|
312
312
|
|
313
313
|
def run_ota_impl_(remote_host, remote_port, password, filename):
|
314
|
-
if is_ip_address(remote_host):
|
315
|
-
_LOGGER.info("Connecting to %s", remote_host)
|
316
|
-
ip = remote_host
|
317
|
-
else:
|
318
|
-
_LOGGER.info("Resolving IP address of %s", remote_host)
|
319
|
-
try:
|
320
|
-
ip = resolve_ip_address(remote_host)
|
321
|
-
except EsphomeError as err:
|
322
|
-
_LOGGER.error(
|
323
|
-
"Error resolving IP address of %s. Is it connected to WiFi?",
|
324
|
-
remote_host,
|
325
|
-
)
|
326
|
-
_LOGGER.error(
|
327
|
-
"(If this error persists, please set a static IP address: "
|
328
|
-
"https://esphome.io/components/wifi.html#manual-ips)"
|
329
|
-
)
|
330
|
-
raise OTAError(err) from err
|
331
|
-
_LOGGER.info(" -> %s", ip)
|
332
|
-
|
333
|
-
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
334
|
-
sock.settimeout(10.0)
|
335
314
|
try:
|
336
|
-
|
337
|
-
except
|
338
|
-
|
339
|
-
|
340
|
-
|
315
|
+
res = resolve_ip_address(remote_host, remote_port)
|
316
|
+
except EsphomeError as err:
|
317
|
+
_LOGGER.error(
|
318
|
+
"Error resolving IP address of %s. Is it connected to WiFi?",
|
319
|
+
remote_host,
|
320
|
+
)
|
321
|
+
_LOGGER.error(
|
322
|
+
"(If this error persists, please set a static IP address: "
|
323
|
+
"https://esphome.io/components/wifi.html#manual-ips)"
|
324
|
+
)
|
325
|
+
raise OTAError(err) from err
|
341
326
|
|
342
|
-
|
327
|
+
for r in res:
|
328
|
+
af, socktype, _, _, sa = r
|
329
|
+
_LOGGER.info("Connecting to %s port %s...", sa[0], sa[1])
|
330
|
+
sock = socket.socket(af, socktype)
|
331
|
+
sock.settimeout(10.0)
|
343
332
|
try:
|
344
|
-
|
345
|
-
except
|
346
|
-
_LOGGER.error(str(err))
|
347
|
-
return 1
|
348
|
-
finally:
|
333
|
+
sock.connect(sa)
|
334
|
+
except OSError as err:
|
349
335
|
sock.close()
|
350
|
-
|
351
|
-
|
336
|
+
_LOGGER.error("Connecting to %s port %s failed: %s", sa[0], sa[1], err)
|
337
|
+
continue
|
338
|
+
|
339
|
+
_LOGGER.info("Connected to %s", sa[0])
|
340
|
+
with open(filename, "rb") as file_handle:
|
341
|
+
try:
|
342
|
+
perform_ota(sock, password, file_handle, filename)
|
343
|
+
except OTAError as err:
|
344
|
+
_LOGGER.error(str(err))
|
345
|
+
return 1
|
346
|
+
finally:
|
347
|
+
sock.close()
|
348
|
+
|
349
|
+
return 0
|
350
|
+
|
351
|
+
_LOGGER.error("Connection failed.")
|
352
|
+
return 1
|
352
353
|
|
353
354
|
|
354
355
|
def run_ota(remote_host, remote_port, password, filename):
|
esphome/helpers.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import codecs
|
2
2
|
from contextlib import suppress
|
3
|
+
import ipaddress
|
3
4
|
import logging
|
4
5
|
import os
|
5
6
|
from pathlib import Path
|
@@ -91,12 +92,8 @@ def mkdir_p(path):
|
|
91
92
|
|
92
93
|
|
93
94
|
def is_ip_address(host):
|
94
|
-
parts = host.split(".")
|
95
|
-
if len(parts) != 4:
|
96
|
-
return False
|
97
95
|
try:
|
98
|
-
|
99
|
-
int(p)
|
96
|
+
ipaddress.ip_address(host)
|
100
97
|
return True
|
101
98
|
except ValueError:
|
102
99
|
return False
|
@@ -127,25 +124,80 @@ def _resolve_with_zeroconf(host):
|
|
127
124
|
return info
|
128
125
|
|
129
126
|
|
130
|
-
def
|
127
|
+
def addr_preference_(res):
|
128
|
+
# Trivial alternative to RFC6724 sorting. Put sane IPv6 first, then
|
129
|
+
# Legacy IP, then IPv6 link-local addresses without an actual link.
|
130
|
+
sa = res[4]
|
131
|
+
ip = ipaddress.ip_address(sa[0])
|
132
|
+
if ip.version == 4:
|
133
|
+
return 2
|
134
|
+
if ip.is_link_local and sa[3] == 0:
|
135
|
+
return 3
|
136
|
+
return 1
|
137
|
+
|
138
|
+
|
139
|
+
def resolve_ip_address(host, port):
|
131
140
|
import socket
|
132
141
|
|
133
142
|
from esphome.core import EsphomeError
|
134
143
|
|
135
|
-
|
144
|
+
# There are five cases here. The host argument could be one of:
|
145
|
+
# • a *list* of IP addresses discovered by MQTT,
|
146
|
+
# • a single IP address specified by the user,
|
147
|
+
# • a .local hostname to be resolved by mDNS,
|
148
|
+
# • a normal hostname to be resolved in DNS, or
|
149
|
+
# • A URL from which we should extract the hostname.
|
150
|
+
#
|
151
|
+
# In each of the first three cases, we end up with IP addresses in
|
152
|
+
# string form which need to be converted to a 5-tuple to be used
|
153
|
+
# for the socket connection attempt. The easiest way to construct
|
154
|
+
# those is to pass the IP address string to getaddrinfo(). Which,
|
155
|
+
# coincidentally, is how we do hostname lookups in the other cases
|
156
|
+
# too. So first build a list which contains either IP addresses or
|
157
|
+
# a single hostname, then call getaddrinfo() on each element of
|
158
|
+
# that list.
|
136
159
|
|
137
|
-
|
160
|
+
errs = []
|
161
|
+
if isinstance(host, list):
|
162
|
+
addr_list = host
|
163
|
+
elif is_ip_address(host):
|
164
|
+
addr_list = [host]
|
165
|
+
else:
|
166
|
+
url = urlparse(host)
|
167
|
+
if url.scheme != "":
|
168
|
+
host = url.hostname
|
169
|
+
|
170
|
+
addr_list = []
|
171
|
+
if host.endswith(".local"):
|
172
|
+
try:
|
173
|
+
_LOGGER.info("Resolving IP address of %s in mDNS", host)
|
174
|
+
addr_list = _resolve_with_zeroconf(host)
|
175
|
+
except EsphomeError as err:
|
176
|
+
errs.append(str(err))
|
177
|
+
|
178
|
+
# If not mDNS, or if mDNS failed, use normal DNS
|
179
|
+
if not addr_list:
|
180
|
+
addr_list = [host]
|
181
|
+
|
182
|
+
# Now we have a list containing either IP addresses or a hostname
|
183
|
+
res = []
|
184
|
+
for addr in addr_list:
|
185
|
+
if not is_ip_address(addr):
|
186
|
+
_LOGGER.info("Resolving IP address of %s", host)
|
138
187
|
try:
|
139
|
-
|
140
|
-
except
|
188
|
+
r = socket.getaddrinfo(addr, port, proto=socket.IPPROTO_TCP)
|
189
|
+
except OSError as err:
|
141
190
|
errs.append(str(err))
|
191
|
+
raise EsphomeError(
|
192
|
+
f"Error resolving IP address: {', '.join(errs)}"
|
193
|
+
) from err
|
142
194
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
195
|
+
res = res + r
|
196
|
+
|
197
|
+
# Zeroconf tends to give us link-local IPv6 addresses without specifying
|
198
|
+
# the link. Put those last in the list to be attempted.
|
199
|
+
res.sort(key=addr_preference_)
|
200
|
+
return res
|
149
201
|
|
150
202
|
|
151
203
|
def get_bool_env(var, default=False):
|
esphome/mqtt.py
CHANGED
@@ -175,8 +175,15 @@ def get_esphome_device_ip(
|
|
175
175
|
_LOGGER.Warn("Wrong device answer")
|
176
176
|
return
|
177
177
|
|
178
|
-
|
179
|
-
|
178
|
+
dev_ip = []
|
179
|
+
key = "ip"
|
180
|
+
n = 0
|
181
|
+
while key in data:
|
182
|
+
dev_ip.append(data[key])
|
183
|
+
n = n + 1
|
184
|
+
key = "ip" + str(n)
|
185
|
+
|
186
|
+
if dev_ip:
|
180
187
|
client.disconnect()
|
181
188
|
|
182
189
|
def on_connect(client, userdata, flags, return_code):
|
esphome/storage_json.py
CHANGED
@@ -28,6 +28,10 @@ def esphome_storage_path() -> str:
|
|
28
28
|
return os.path.join(CORE.data_dir, "esphome.json")
|
29
29
|
|
30
30
|
|
31
|
+
def ignored_devices_storage_path() -> str:
|
32
|
+
return os.path.join(CORE.data_dir, "ignored-devices.json")
|
33
|
+
|
34
|
+
|
31
35
|
def trash_storage_path() -> str:
|
32
36
|
return CORE.relative_config_path("trash")
|
33
37
|
|
esphome/writer.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import importlib
|
1
2
|
import logging
|
2
3
|
import os
|
3
4
|
from pathlib import Path
|
@@ -299,25 +300,13 @@ def copy_src_tree():
|
|
299
300
|
CORE.relative_src_path("esphome", "core", "version.h"), generate_version_h()
|
300
301
|
)
|
301
302
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
copy_files()
|
306
|
-
|
307
|
-
elif CORE.is_esp8266:
|
308
|
-
from esphome.components.esp8266 import copy_files
|
309
|
-
|
303
|
+
platform = "esphome.components." + CORE.target_platform
|
304
|
+
try:
|
305
|
+
module = importlib.import_module(platform)
|
306
|
+
copy_files = getattr(module, "copy_files")
|
310
307
|
copy_files()
|
311
|
-
|
312
|
-
|
313
|
-
from esphome.components.rp2040 import copy_files
|
314
|
-
|
315
|
-
(pio) = copy_files()
|
316
|
-
if pio:
|
317
|
-
write_file_if_changed(
|
318
|
-
CORE.relative_src_path("esphome.h"),
|
319
|
-
ESPHOME_H_FORMAT.format(include_s + '\n#include "pio_includes.h"'),
|
320
|
-
)
|
308
|
+
except AttributeError:
|
309
|
+
pass
|
321
310
|
|
322
311
|
|
323
312
|
def generate_defines_h():
|
esphome/zeroconf.py
CHANGED
@@ -176,24 +176,26 @@ def _make_host_resolver(host: str) -> HostResolver:
|
|
176
176
|
|
177
177
|
|
178
178
|
class EsphomeZeroconf(Zeroconf):
|
179
|
-
def resolve_host(self, host: str, timeout: float = 3.0) -> str | None:
|
179
|
+
def resolve_host(self, host: str, timeout: float = 3.0) -> list[str] | None:
|
180
180
|
"""Resolve a host name to an IP address."""
|
181
181
|
info = _make_host_resolver(host)
|
182
182
|
if (
|
183
183
|
info.load_from_cache(self)
|
184
184
|
or (timeout and info.request(self, timeout * 1000))
|
185
|
-
) and (addresses := info.
|
186
|
-
return
|
185
|
+
) and (addresses := info.parsed_scoped_addresses(IPVersion.All)):
|
186
|
+
return addresses
|
187
187
|
return None
|
188
188
|
|
189
189
|
|
190
190
|
class AsyncEsphomeZeroconf(AsyncZeroconf):
|
191
|
-
async def async_resolve_host(
|
191
|
+
async def async_resolve_host(
|
192
|
+
self, host: str, timeout: float = 3.0
|
193
|
+
) -> list[str] | None:
|
192
194
|
"""Resolve a host name to an IP address."""
|
193
195
|
info = _make_host_resolver(host)
|
194
196
|
if (
|
195
197
|
info.load_from_cache(self.zeroconf)
|
196
198
|
or (timeout and await info.async_request(self.zeroconf, timeout * 1000))
|
197
|
-
) and (addresses := info.
|
198
|
-
return
|
199
|
+
) and (addresses := info.parsed_scoped_addresses(IPVersion.All)):
|
200
|
+
return addresses
|
199
201
|
return None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: esphome
|
3
|
-
Version: 2024.
|
3
|
+
Version: 2024.11.0
|
4
4
|
Summary: Make creating custom firmwares for ESP32/ESP8266 super easy.
|
5
5
|
Author-email: The ESPHome Authors <esphome@nabucasa.com>
|
6
6
|
License: MIT
|
@@ -25,7 +25,7 @@ Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
26
26
|
Requires-Dist: cryptography ==43.0.0
|
27
27
|
Requires-Dist: voluptuous ==0.14.2
|
28
|
-
Requires-Dist: PyYAML ==6.0.
|
28
|
+
Requires-Dist: PyYAML ==6.0.2
|
29
29
|
Requires-Dist: paho-mqtt ==1.6.1
|
30
30
|
Requires-Dist: colorama ==0.4.6
|
31
31
|
Requires-Dist: icmplib ==3.0.4
|
@@ -33,14 +33,17 @@ Requires-Dist: tornado ==6.4
|
|
33
33
|
Requires-Dist: tzlocal ==5.2
|
34
34
|
Requires-Dist: tzdata >=2021.1
|
35
35
|
Requires-Dist: pyserial ==3.5
|
36
|
-
Requires-Dist: platformio ==6.1.
|
36
|
+
Requires-Dist: platformio ==6.1.16
|
37
37
|
Requires-Dist: esptool ==4.7.0
|
38
38
|
Requires-Dist: click ==8.1.7
|
39
|
-
Requires-Dist: esphome-dashboard ==
|
39
|
+
Requires-Dist: esphome-dashboard ==20241120.0
|
40
40
|
Requires-Dist: aioesphomeapi ==24.6.2
|
41
41
|
Requires-Dist: zeroconf ==0.132.2
|
42
42
|
Requires-Dist: puremagic ==1.27
|
43
43
|
Requires-Dist: ruamel.yaml ==0.18.6
|
44
|
+
Requires-Dist: glyphsets ==1.0.0
|
45
|
+
Requires-Dist: pillow ==10.4.0
|
46
|
+
Requires-Dist: freetype-py ==2.5.1
|
44
47
|
Requires-Dist: kconfiglib ==13.7.1
|
45
48
|
Requires-Dist: pyparsing >=3.0
|
46
49
|
Requires-Dist: argcomplete >=2.0.0
|
@@ -50,7 +53,6 @@ Requires-Dist: clang-format ==13.0.1 ; extra == 'dev'
|
|
50
53
|
Requires-Dist: clang-tidy ==14.0.6 ; extra == 'dev'
|
51
54
|
Requires-Dist: yamllint ==1.35.1 ; extra == 'dev'
|
52
55
|
Provides-Extra: displays
|
53
|
-
Requires-Dist: pillow ==10.2.0 ; extra == 'displays'
|
54
56
|
Requires-Dist: cairosvg ==2.7.1 ; extra == 'displays'
|
55
57
|
Provides-Extra: test
|
56
58
|
Requires-Dist: pylint ==3.2.7 ; extra == 'test'
|