esphome 2024.10.3__py3-none-any.whl → 2024.11.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.
Files changed (228) hide show
  1. esphome/__main__.py +22 -4
  2. esphome/automation.py +29 -2
  3. esphome/components/animation/__init__.py +5 -8
  4. esphome/components/animation/animation.cpp +1 -1
  5. esphome/components/audio/__init__.py +9 -0
  6. esphome/components/audio/audio.h +21 -0
  7. esphome/components/axs15231/__init__.py +6 -0
  8. esphome/components/axs15231/touchscreen/__init__.py +36 -0
  9. esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +64 -0
  10. esphome/components/axs15231/touchscreen/axs15231_touchscreen.h +27 -0
  11. esphome/components/bme68x_bsec2/__init__.py +1 -1
  12. esphome/components/bytebuffer/__init__.py +5 -0
  13. esphome/components/bytebuffer/bytebuffer.h +421 -0
  14. esphome/components/climate/__init__.py +14 -13
  15. esphome/components/datetime/__init__.py +3 -3
  16. esphome/components/debug/debug_esp32.cpp +16 -8
  17. esphome/components/dfplayer/dfplayer.cpp +132 -6
  18. esphome/components/dfplayer/dfplayer.h +19 -53
  19. esphome/components/display/display.cpp +142 -0
  20. esphome/components/display/display.h +7 -0
  21. esphome/components/es8311/__init__.py +0 -0
  22. esphome/components/es8311/audio_dac.py +70 -0
  23. esphome/components/es8311/es8311.cpp +227 -0
  24. esphome/components/es8311/es8311.h +135 -0
  25. esphome/components/es8311/es8311_const.h +195 -0
  26. esphome/components/esp32/boards.py +199 -1
  27. esphome/components/esp32/gpio.py +3 -1
  28. esphome/components/esp32_ble/const_esp32c6.h +7 -0
  29. esphome/components/esp32_ble_client/ble_client_base.h +1 -1
  30. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +2 -1
  31. esphome/components/esp32_rmt_led_strip/led_strip.cpp +2 -2
  32. esphome/components/esp32_rmt_led_strip/led_strip.h +2 -0
  33. esphome/components/esp32_rmt_led_strip/light.py +3 -1
  34. esphome/components/esp8266/gpio.py +7 -5
  35. esphome/components/ethernet/__init__.py +55 -1
  36. esphome/components/ethernet/ethernet_component.cpp +14 -1
  37. esphome/components/ethernet/ethernet_component.h +7 -1
  38. esphome/components/font/__init__.py +213 -108
  39. esphome/components/gp8403/output/__init__.py +1 -1
  40. esphome/components/host/gpio.py +6 -4
  41. esphome/components/http_request/__init__.py +12 -0
  42. esphome/components/http_request/http_request.h +65 -3
  43. esphome/components/http_request/http_request_arduino.cpp +2 -3
  44. esphome/components/http_request/http_request_idf.cpp +6 -14
  45. esphome/components/http_request/ota/ota_http_request.cpp +1 -1
  46. esphome/components/http_request/update/http_request_update.cpp +1 -1
  47. esphome/components/i2c_device/__init__.py +26 -0
  48. esphome/components/i2c_device/i2c_device.cpp +17 -0
  49. esphome/components/i2c_device/i2c_device.h +18 -0
  50. esphome/components/i2s_audio/__init__.py +1 -3
  51. esphome/components/i2s_audio/speaker/__init__.py +12 -4
  52. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +432 -197
  53. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +91 -32
  54. esphome/components/ili9xxx/display.py +5 -1
  55. esphome/components/image/__init__.py +5 -8
  56. esphome/components/image/image.cpp +14 -14
  57. esphome/components/image/image.h +20 -24
  58. esphome/components/internal_temperature/internal_temperature.cpp +51 -2
  59. esphome/components/internal_temperature/internal_temperature.h +1 -0
  60. esphome/components/libretiny/gpio.py +4 -2
  61. esphome/components/light/__init__.py +32 -1
  62. esphome/components/light/automation.py +39 -32
  63. esphome/components/light/effects.py +36 -36
  64. esphome/components/light/light_state.cpp +6 -16
  65. esphome/components/light/light_state.h +34 -0
  66. esphome/components/light/types.py +3 -1
  67. esphome/components/logger/logger_esp32.cpp +15 -0
  68. esphome/components/lvgl/__init__.py +202 -95
  69. esphome/components/lvgl/automation.py +42 -40
  70. esphome/components/lvgl/binary_sensor/__init__.py +8 -15
  71. esphome/components/lvgl/defines.py +14 -8
  72. esphome/components/lvgl/encoders.py +11 -8
  73. esphome/components/lvgl/keypads.py +77 -0
  74. esphome/components/lvgl/light/__init__.py +6 -8
  75. esphome/components/lvgl/lv_validation.py +2 -4
  76. esphome/components/lvgl/lvcode.py +3 -9
  77. esphome/components/lvgl/lvgl_esphome.cpp +210 -89
  78. esphome/components/lvgl/lvgl_esphome.h +113 -30
  79. esphome/components/lvgl/lvgl_proxy.h +17 -0
  80. esphome/components/lvgl/number/__init__.py +10 -15
  81. esphome/components/lvgl/schemas.py +4 -2
  82. esphome/components/lvgl/select/__init__.py +12 -37
  83. esphome/components/lvgl/select/lvgl_select.h +27 -33
  84. esphome/components/lvgl/sensor/__init__.py +8 -14
  85. esphome/components/lvgl/styles.py +3 -4
  86. esphome/components/lvgl/switch/__init__.py +8 -13
  87. esphome/components/lvgl/text/__init__.py +5 -6
  88. esphome/components/lvgl/text_sensor/__init__.py +15 -15
  89. esphome/components/lvgl/touchscreens.py +2 -3
  90. esphome/components/lvgl/trigger.py +7 -9
  91. esphome/components/lvgl/types.py +9 -3
  92. esphome/components/lvgl/widgets/__init__.py +32 -21
  93. esphome/components/lvgl/widgets/dropdown.py +22 -10
  94. esphome/components/lvgl/widgets/msgbox.py +6 -5
  95. esphome/components/lvgl/widgets/obj.py +4 -2
  96. esphome/components/lvgl/widgets/page.py +3 -2
  97. esphome/components/lvgl/widgets/qrcode.py +54 -0
  98. esphome/components/lvgl/widgets/roller.py +21 -14
  99. esphome/components/lvgl/widgets/tileview.py +2 -1
  100. esphome/components/max17043/__init__.py +1 -0
  101. esphome/components/max17043/automation.h +20 -0
  102. esphome/components/max17043/max17043.cpp +98 -0
  103. esphome/components/max17043/max17043.h +29 -0
  104. esphome/components/max17043/sensor.py +77 -0
  105. esphome/components/media_player/__init__.py +11 -0
  106. esphome/components/media_player/automation.h +10 -0
  107. esphome/components/media_player/media_player.cpp +4 -0
  108. esphome/components/midea/air_conditioner.cpp +17 -1
  109. esphome/components/mlx90393/sensor.py +1 -1
  110. esphome/components/modbus_controller/__init__.py +31 -1
  111. esphome/components/modbus_controller/automation.h +16 -0
  112. esphome/components/modbus_controller/const.py +2 -0
  113. esphome/components/modbus_controller/modbus_controller.cpp +14 -2
  114. esphome/components/modbus_controller/modbus_controller.h +9 -0
  115. esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +40 -21
  116. esphome/components/mopeka_pro_check/mopeka_pro_check.h +9 -2
  117. esphome/components/mopeka_pro_check/sensor.py +41 -0
  118. esphome/components/mqtt/__init__.py +36 -0
  119. esphome/components/mqtt/mqtt_client.cpp +27 -3
  120. esphome/components/mqtt/mqtt_client.h +27 -2
  121. esphome/components/mqtt/mqtt_climate.cpp +4 -2
  122. esphome/components/mqtt/mqtt_component.cpp +6 -0
  123. esphome/components/mqtt/mqtt_component.h +4 -0
  124. esphome/components/mqtt/mqtt_const.h +6 -0
  125. esphome/components/online_image/online_image.cpp +2 -8
  126. esphome/components/online_image/online_image.h +2 -6
  127. esphome/components/opentherm/__init__.py +35 -9
  128. esphome/components/opentherm/binary_sensor/__init__.py +33 -0
  129. esphome/components/opentherm/const.py +11 -0
  130. esphome/components/opentherm/generate.py +142 -0
  131. esphome/components/opentherm/hub.cpp +130 -24
  132. esphome/components/opentherm/hub.h +62 -9
  133. esphome/components/opentherm/input.h +18 -0
  134. esphome/components/opentherm/input.py +51 -0
  135. esphome/components/opentherm/number/__init__.py +74 -0
  136. esphome/components/opentherm/number/number.cpp +40 -0
  137. esphome/components/opentherm/number/number.h +31 -0
  138. esphome/components/opentherm/opentherm.cpp +30 -0
  139. esphome/components/opentherm/opentherm.h +34 -2
  140. esphome/components/opentherm/opentherm_macros.h +151 -0
  141. esphome/components/opentherm/output/__init__.py +47 -0
  142. esphome/components/opentherm/output/output.cpp +18 -0
  143. esphome/components/opentherm/output/output.h +33 -0
  144. esphome/components/opentherm/schema.py +814 -0
  145. esphome/components/opentherm/sensor/__init__.py +51 -0
  146. esphome/components/opentherm/switch/__init__.py +43 -0
  147. esphome/components/opentherm/switch/switch.cpp +28 -0
  148. esphome/components/opentherm/switch/switch.h +20 -0
  149. esphome/components/opentherm/validate.py +31 -0
  150. esphome/components/pcd8544/display.py +8 -4
  151. esphome/components/prometheus/prometheus_handler.cpp +176 -14
  152. esphome/components/prometheus/prometheus_handler.h +25 -7
  153. esphome/components/qspi_amoled/display.py +1 -141
  154. esphome/components/qspi_dbi/display.py +185 -0
  155. esphome/components/qspi_dbi/models.py +64 -0
  156. esphome/components/{qspi_amoled/qspi_amoled.cpp → qspi_dbi/qspi_dbi.cpp} +95 -46
  157. esphome/components/{qspi_amoled/qspi_amoled.h → qspi_dbi/qspi_dbi.h} +26 -15
  158. esphome/components/rp2040/__init__.py +6 -3
  159. esphome/components/rp2040/gpio.py +5 -3
  160. esphome/components/rtttl/rtttl.cpp +4 -1
  161. esphome/components/rtttl/rtttl.h +1 -0
  162. esphome/components/sdl/sdl_esphome.cpp +22 -5
  163. esphome/components/sdl/sdl_esphome.h +1 -0
  164. esphome/components/sensor/__init__.py +18 -8
  165. esphome/components/sensor/filter.cpp +19 -18
  166. esphome/components/sensor/filter.h +9 -10
  167. esphome/components/sgp4x/sgp4x.cpp +40 -74
  168. esphome/components/sgp4x/sgp4x.h +5 -3
  169. esphome/components/speaker/__init__.py +51 -5
  170. esphome/components/speaker/automation.h +25 -0
  171. esphome/components/speaker/speaker.h +72 -1
  172. esphome/components/spi/__init__.py +15 -14
  173. esphome/components/spi_device/__init__.py +4 -15
  174. esphome/components/ssd1306_spi/display.py +6 -2
  175. esphome/components/ssd1322_spi/display.py +6 -2
  176. esphome/components/ssd1325_spi/display.py +6 -2
  177. esphome/components/ssd1327_spi/display.py +6 -2
  178. esphome/components/ssd1331_spi/display.py +6 -2
  179. esphome/components/ssd1351_spi/display.py +6 -2
  180. esphome/components/st7567_spi/display.py +6 -2
  181. esphome/components/st7701s/display.py +5 -1
  182. esphome/components/st7735/display.py +10 -5
  183. esphome/components/st7789v/display.py +12 -7
  184. esphome/components/statsd/statsd.cpp +2 -0
  185. esphome/components/statsd/statsd.h +2 -0
  186. esphome/components/sun/sun.h +3 -0
  187. esphome/components/tc74/__init__.py +1 -0
  188. esphome/components/tc74/sensor.py +32 -0
  189. esphome/components/tc74/tc74.cpp +68 -0
  190. esphome/components/tc74/tc74.h +28 -0
  191. esphome/components/touchscreen/__init__.py +41 -50
  192. esphome/components/touchscreen/touchscreen.h +4 -8
  193. esphome/components/udp/udp_component.cpp +6 -3
  194. esphome/components/udp/udp_component.h +4 -2
  195. esphome/components/waveshare_epaper/display.py +6 -2
  196. esphome/components/web_server/web_server.cpp +22 -0
  197. esphome/components/web_server/web_server.h +3 -0
  198. esphome/components/weikai/weikai.h +2 -2
  199. esphome/components/wifi/wifi_component.cpp +2 -2
  200. esphome/components/wifi/wifi_component_esp32_arduino.cpp +4 -4
  201. esphome/components/wifi/wifi_component_esp8266.cpp +4 -4
  202. esphome/components/wifi/wifi_component_esp_idf.cpp +2 -2
  203. esphome/components/xpt2046/touchscreen/__init__.py +7 -32
  204. esphome/config_validation.py +3 -1
  205. esphome/const.py +8 -1
  206. esphome/core/defines.h +8 -2
  207. esphome/core/helpers.cpp +32 -17
  208. esphome/core/helpers.h +32 -16
  209. esphome/core/ring_buffer.cpp +2 -2
  210. esphome/core/ring_buffer.h +2 -2
  211. esphome/dashboard/core.py +25 -0
  212. esphome/dashboard/status/mdns.py +3 -4
  213. esphome/dashboard/web_server.py +54 -19
  214. esphome/espota2.py +36 -35
  215. esphome/helpers.py +68 -16
  216. esphome/mqtt.py +9 -2
  217. esphome/storage_json.py +4 -0
  218. esphome/writer.py +7 -18
  219. esphome/zeroconf.py +8 -6
  220. {esphome-2024.10.3.dist-info → esphome-2024.11.0b1.dist-info}/METADATA +7 -5
  221. {esphome-2024.10.3.dist-info → esphome-2024.11.0b1.dist-info}/RECORD +226 -180
  222. esphome/core/bytebuffer.cpp +0 -167
  223. esphome/core/bytebuffer.h +0 -144
  224. /esphome/components/{qspi_amoled → qspi_dbi}/__init__.py +0 -0
  225. {esphome-2024.10.3.dist-info → esphome-2024.11.0b1.dist-info}/LICENSE +0 -0
  226. {esphome-2024.10.3.dist-info → esphome-2024.11.0b1.dist-info}/WHEEL +0 -0
  227. {esphome-2024.10.3.dist-info → esphome-2024.11.0b1.dist-info}/entry_points.txt +0 -0
  228. {esphome-2024.10.3.dist-info → esphome-2024.11.0b1.dist-info}/top_level.txt +0 -0
@@ -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
- address := await mdns.async_resolve_host(entry.name)
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 = address
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
- downloads = rp2040_types(storage_json)
562
- elif platform == const.PLATFORM_ESP8266:
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
- from esphome.components.libretiny import (
572
- get_download_types as libretiny_types,
573
- )
603
+ platform = "libretiny"
574
604
 
575
- downloads = libretiny_types(storage_json)
576
- else:
577
- raise ValueError(f"Unknown platform {platform}")
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 is_ip_address, resolve_ip_address
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
- sock.connect((ip, remote_port))
337
- except OSError as err:
338
- sock.close()
339
- _LOGGER.error("Connecting to %s:%s failed: %s", remote_host, remote_port, err)
340
- return 1
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
- with open(filename, "rb") as file_handle:
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
- perform_ota(sock, password, file_handle, filename)
345
- except OTAError as err:
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
- return 0
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
- for p in parts:
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 resolve_ip_address(host):
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
- errs = []
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
- if host.endswith(".local"):
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
- return _resolve_with_zeroconf(host)
140
- except EsphomeError as err:
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
- try:
144
- host_url = host if (urlparse(host).scheme != "") else "http://" + host
145
- return socket.gethostbyname(urlparse(host_url).hostname)
146
- except OSError as err:
147
- errs.append(str(err))
148
- raise EsphomeError(f"Error resolving IP address: {', '.join(errs)}") from err
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
- if "ip" in data:
179
- dev_ip = data["ip"]
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
- if CORE.is_esp32:
303
- from esphome.components.esp32 import copy_files
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
- elif CORE.is_rp2040:
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.ip_addresses_by_version(IPVersion.V4Only)):
186
- return str(addresses[0])
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(self, host: str, timeout: float = 3.0) -> str | None:
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.ip_addresses_by_version(IPVersion.V4Only)):
198
- return str(addresses[0])
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.10.3
3
+ Version: 2024.11.0b1
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.1
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.15
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 ==20240620.0
39
+ Requires-Dist: esphome-dashboard ==20241025.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'