esphome 2025.8.3__py3-none-any.whl → 2025.9.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 (345) hide show
  1. esphome/__main__.py +36 -42
  2. esphome/components/absolute_humidity/absolute_humidity.cpp +3 -5
  3. esphome/components/adc/adc_sensor_esp32.cpp +29 -6
  4. esphome/components/ags10/ags10.cpp +3 -18
  5. esphome/components/ags10/ags10.h +2 -12
  6. esphome/components/aht10/aht10.cpp +3 -3
  7. esphome/components/airthings_ble/__init__.py +2 -2
  8. esphome/components/alarm_control_panel/__init__.py +2 -2
  9. esphome/components/am2315c/am2315c.cpp +1 -17
  10. esphome/components/am2315c/am2315c.h +2 -3
  11. esphome/components/api/__init__.py +2 -2
  12. esphome/components/api/api_connection.cpp +34 -23
  13. esphome/components/api/api_connection.h +20 -39
  14. esphome/components/api/api_frame_helper.cpp +25 -25
  15. esphome/components/api/api_frame_helper.h +3 -3
  16. esphome/components/api/api_frame_helper_noise.cpp +75 -40
  17. esphome/components/api/api_frame_helper_noise.h +3 -7
  18. esphome/components/api/api_frame_helper_plaintext.cpp +17 -4
  19. esphome/components/api/api_frame_helper_plaintext.h +1 -4
  20. esphome/components/api/api_pb2.cpp +20 -2
  21. esphome/components/api/api_pb2.h +146 -141
  22. esphome/components/api/api_pb2_dump.cpp +12 -1
  23. esphome/components/api/proto.cpp +33 -37
  24. esphome/components/async_tcp/__init__.py +2 -2
  25. esphome/components/atm90e26/sensor.py +2 -0
  26. esphome/components/atm90e32/sensor.py +4 -2
  27. esphome/components/audio_adc/__init__.py +2 -2
  28. esphome/components/audio_dac/__init__.py +2 -2
  29. esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +1 -1
  30. esphome/components/bedjet/bedjet_hub.cpp +1 -1
  31. esphome/components/binary_sensor/__init__.py +2 -2
  32. esphome/components/binary_sensor/binary_sensor.cpp +13 -0
  33. esphome/components/binary_sensor/binary_sensor.h +4 -7
  34. esphome/components/bl0940/__init__.py +6 -1
  35. esphome/components/bl0940/bl0940.cpp +178 -41
  36. esphome/components/bl0940/bl0940.h +121 -76
  37. esphome/components/bl0940/button/__init__.py +27 -0
  38. esphome/components/bl0940/button/calibration_reset_button.cpp +20 -0
  39. esphome/components/bl0940/button/calibration_reset_button.h +19 -0
  40. esphome/components/bl0940/number/__init__.py +94 -0
  41. esphome/components/bl0940/number/calibration_number.cpp +29 -0
  42. esphome/components/bl0940/number/calibration_number.h +26 -0
  43. esphome/components/bl0940/sensor.py +151 -2
  44. esphome/components/bl0942/bl0942.cpp +1 -1
  45. esphome/components/ble_client/output/__init__.py +4 -4
  46. esphome/components/bluetooth_proxy/__init__.py +1 -1
  47. esphome/components/bluetooth_proxy/bluetooth_connection.h +1 -1
  48. esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +15 -7
  49. esphome/components/bluetooth_proxy/bluetooth_proxy.h +3 -2
  50. esphome/components/button/__init__.py +2 -2
  51. esphome/components/button/button.cpp +13 -0
  52. esphome/components/button/button.h +4 -7
  53. esphome/components/camera/buffer.h +18 -0
  54. esphome/components/camera/buffer_impl.cpp +20 -0
  55. esphome/components/camera/buffer_impl.h +26 -0
  56. esphome/components/camera/camera.h +43 -0
  57. esphome/components/camera/encoder.h +69 -0
  58. esphome/components/camera_encoder/__init__.py +62 -0
  59. esphome/components/camera_encoder/encoder_buffer_impl.cpp +23 -0
  60. esphome/components/camera_encoder/encoder_buffer_impl.h +25 -0
  61. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +82 -0
  62. esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +39 -0
  63. esphome/components/captive_portal/__init__.py +2 -2
  64. esphome/components/captive_portal/captive_portal.cpp +35 -12
  65. esphome/components/captive_portal/captive_portal.h +3 -3
  66. esphome/components/ccs811/ccs811.cpp +3 -3
  67. esphome/components/climate/__init__.py +2 -2
  68. esphome/components/climate/climate.cpp +1 -1
  69. esphome/components/cover/__init__.py +5 -5
  70. esphome/components/cover/cover.cpp +1 -1
  71. esphome/components/cover/cover.h +2 -2
  72. esphome/components/dallas_temp/dallas_temp.cpp +2 -2
  73. esphome/components/datetime/__init__.py +2 -2
  74. esphome/components/datetime/date_entity.h +2 -2
  75. esphome/components/datetime/datetime_entity.h +2 -2
  76. esphome/components/datetime/time_entity.h +2 -2
  77. esphome/components/debug/debug_esp32.cpp +1 -1
  78. esphome/components/display/__init__.py +4 -4
  79. esphome/components/duty_time/duty_time_sensor.cpp +1 -1
  80. esphome/components/esp32/__init__.py +0 -5
  81. esphome/components/esp32/gpio.cpp +27 -23
  82. esphome/components/esp32/gpio.h +26 -11
  83. esphome/components/esp32/preferences.cpp +8 -4
  84. esphome/components/esp32_ble/__init__.py +7 -2
  85. esphome/components/esp32_ble_client/ble_client_base.cpp +7 -3
  86. esphome/components/esp32_ble_tracker/__init__.py +2 -2
  87. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +9 -44
  88. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +2 -14
  89. esphome/components/esp8266/__init__.py +2 -2
  90. esphome/components/esp8266/core.cpp +2 -2
  91. esphome/components/esp8266/gpio.py +4 -4
  92. esphome/components/esp8266/preferences.cpp +30 -28
  93. esphome/components/esphome/ota/__init__.py +2 -2
  94. esphome/components/esphome/ota/ota_esphome.cpp +21 -19
  95. esphome/components/esphome/ota/ota_esphome.h +6 -5
  96. esphome/components/ethernet/__init__.py +7 -2
  97. esphome/components/ethernet/ethernet_component.cpp +1 -1
  98. esphome/components/event/__init__.py +2 -2
  99. esphome/components/event/event.h +4 -4
  100. esphome/components/fan/__init__.py +2 -2
  101. esphome/components/fan/fan.cpp +2 -1
  102. esphome/components/gdk101/gdk101.cpp +4 -4
  103. esphome/components/globals/__init__.py +2 -2
  104. esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +19 -18
  105. esphome/components/gpio_expander/cached_gpio.h +36 -16
  106. esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp +5 -5
  107. esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +1 -1
  108. esphome/components/haier/haier_base.cpp +1 -1
  109. esphome/components/haier/hon_climate.cpp +1 -1
  110. esphome/components/hlw8012/hlw8012.cpp +5 -5
  111. esphome/components/honeywellabp2_i2c/honeywellabp2.cpp +4 -4
  112. esphome/components/host/preferences.h +3 -2
  113. esphome/components/hte501/hte501.cpp +3 -21
  114. esphome/components/hte501/hte501.h +2 -3
  115. esphome/components/http_request/ota/__init__.py +2 -2
  116. esphome/components/i2c/__init__.py +2 -2
  117. esphome/components/i2c/i2c.cpp +13 -9
  118. esphome/components/i2c/i2c_bus.h +36 -6
  119. esphome/components/i2s_audio/__init__.py +8 -2
  120. esphome/components/i2s_audio/media_player/__init__.py +1 -1
  121. esphome/components/i2s_audio/microphone/__init__.py +1 -1
  122. esphome/components/i2s_audio/speaker/__init__.py +1 -1
  123. esphome/components/inkplate/__init__.py +1 -0
  124. esphome/components/inkplate/const.py +105 -0
  125. esphome/components/inkplate/display.py +238 -0
  126. esphome/components/{inkplate6 → inkplate}/inkplate.cpp +156 -74
  127. esphome/components/{inkplate6 → inkplate}/inkplate.h +28 -68
  128. esphome/components/inkplate6/__init__.py +0 -1
  129. esphome/components/inkplate6/display.py +2 -211
  130. esphome/components/integration/integration_sensor.cpp +1 -1
  131. esphome/components/json/__init__.py +2 -2
  132. esphome/components/kmeteriso/kmeteriso.cpp +1 -1
  133. esphome/components/lc709203f/lc709203f.cpp +4 -17
  134. esphome/components/lc709203f/lc709203f.h +2 -3
  135. esphome/components/ld2420/text_sensor/{text_sensor.cpp → ld2420_text_sensor.cpp} +1 -1
  136. esphome/components/ld2450/ld2450.cpp +1 -1
  137. esphome/components/libretiny/preferences.cpp +13 -5
  138. esphome/components/light/__init__.py +2 -2
  139. esphome/components/light/addressable_light_effect.h +7 -0
  140. esphome/components/light/base_light_effects.h +8 -0
  141. esphome/components/light/light_call.cpp +22 -20
  142. esphome/components/light/light_effect.cpp +36 -0
  143. esphome/components/light/light_effect.h +14 -0
  144. esphome/components/light/light_json_schema.cpp +9 -1
  145. esphome/components/light/light_state.cpp +2 -2
  146. esphome/components/light/light_state.h +39 -0
  147. esphome/components/lock/__init__.py +2 -2
  148. esphome/components/lock/lock.h +2 -2
  149. esphome/components/logger/__init__.py +2 -2
  150. esphome/components/logger/logger.cpp +25 -4
  151. esphome/components/logger/logger.h +1 -1
  152. esphome/components/logger/logger_esp32.cpp +16 -8
  153. esphome/components/logger/logger_esp8266.cpp +11 -3
  154. esphome/components/logger/logger_libretiny.cpp +13 -3
  155. esphome/components/logger/logger_rp2040.cpp +14 -3
  156. esphome/components/logger/logger_zephyr.cpp +15 -4
  157. esphome/components/lvgl/defines.py +1 -0
  158. esphome/components/lvgl/hello_world.py +96 -33
  159. esphome/components/lvgl/number/lvgl_number.h +1 -1
  160. esphome/components/lvgl/select/lvgl_select.h +1 -1
  161. esphome/components/lvgl/widgets/__init__.py +0 -1
  162. esphome/components/lvgl/widgets/spinbox.py +20 -11
  163. esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.cpp +1 -1
  164. esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.cpp +1 -1
  165. esphome/components/mapping/__init__.py +13 -5
  166. esphome/components/mapping/mapping.h +69 -0
  167. esphome/components/max17043/max17043.cpp +2 -2
  168. esphome/components/mcp23016/__init__.py +1 -0
  169. esphome/components/mcp23016/mcp23016.cpp +20 -5
  170. esphome/components/mcp23016/mcp23016.h +10 -4
  171. esphome/components/mcp23x08_base/mcp23x08_base.cpp +1 -1
  172. esphome/components/mcp23x17_base/mcp23x17_base.cpp +2 -2
  173. esphome/components/mdns/__init__.py +2 -2
  174. esphome/components/mdns/mdns_component.cpp +145 -54
  175. esphome/components/media_player/__init__.py +2 -2
  176. esphome/components/micro_wake_word/__init__.py +2 -2
  177. esphome/components/microphone/__init__.py +2 -2
  178. esphome/components/mipi/__init__.py +77 -33
  179. esphome/components/mipi_rgb/__init__.py +2 -0
  180. esphome/components/mipi_rgb/display.py +321 -0
  181. esphome/components/mipi_rgb/mipi_rgb.cpp +388 -0
  182. esphome/components/mipi_rgb/mipi_rgb.h +127 -0
  183. esphome/components/mipi_rgb/models/guition.py +24 -0
  184. esphome/components/mipi_rgb/models/lilygo.py +228 -0
  185. esphome/components/mipi_rgb/models/rpi.py +9 -0
  186. esphome/components/mipi_rgb/models/st7701s.py +214 -0
  187. esphome/components/mipi_rgb/models/waveshare.py +64 -0
  188. esphome/components/mipi_spi/models/jc.py +229 -0
  189. esphome/components/mlx90614/mlx90614.cpp +1 -16
  190. esphome/components/mlx90614/mlx90614.h +0 -1
  191. esphome/components/mqtt/__init__.py +2 -2
  192. esphome/components/mqtt/mqtt_sensor.cpp +7 -2
  193. esphome/components/ms5611/ms5611.cpp +7 -6
  194. esphome/components/network/__init__.py +2 -2
  195. esphome/components/nextion/nextion_upload.cpp +4 -1
  196. esphome/components/nrf52/__init__.py +49 -6
  197. esphome/components/nrf52/const.py +1 -0
  198. esphome/components/nrf52/dfu.cpp +51 -0
  199. esphome/components/nrf52/dfu.h +24 -0
  200. esphome/components/ntc/ntc.cpp +1 -1
  201. esphome/components/number/__init__.py +2 -2
  202. esphome/components/number/automation.cpp +1 -1
  203. esphome/components/number/number.cpp +21 -0
  204. esphome/components/number/number.h +4 -13
  205. esphome/components/opentherm/hub.h +6 -6
  206. esphome/components/opentherm/number/{number.cpp → opentherm_number.cpp} +2 -2
  207. esphome/components/opentherm/output/{output.cpp → opentherm_output.cpp} +1 -1
  208. esphome/components/opentherm/switch/{switch.cpp → opentherm_switch.cpp} +1 -1
  209. esphome/components/ota/__init__.py +2 -2
  210. esphome/components/pca6416a/__init__.py +1 -0
  211. esphome/components/pca6416a/pca6416a.cpp +20 -5
  212. esphome/components/pca6416a/pca6416a.h +12 -5
  213. esphome/components/pca9554/__init__.py +2 -1
  214. esphome/components/pca9554/pca9554.cpp +12 -18
  215. esphome/components/pca9554/pca9554.h +10 -9
  216. esphome/components/pcf8574/__init__.py +1 -0
  217. esphome/components/pcf8574/pcf8574.cpp +14 -5
  218. esphome/components/pcf8574/pcf8574.h +13 -6
  219. esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +7 -7
  220. esphome/components/pipsolar/__init__.py +3 -3
  221. esphome/components/pipsolar/output/__init__.py +4 -4
  222. esphome/components/pulse_width/pulse_width.cpp +2 -2
  223. esphome/components/qmp6988/qmp6988.cpp +81 -126
  224. esphome/components/qmp6988/qmp6988.h +31 -37
  225. esphome/components/radon_eye_ble/__init__.py +2 -2
  226. esphome/components/remote_base/__init__.py +6 -8
  227. esphome/components/rotary_encoder/rotary_encoder.cpp +1 -1
  228. esphome/components/rp2040/__init__.py +2 -2
  229. esphome/components/runtime_stats/runtime_stats.cpp +10 -23
  230. esphome/components/runtime_stats/runtime_stats.h +4 -10
  231. esphome/components/safe_mode/__init__.py +2 -2
  232. esphome/components/safe_mode/safe_mode.cpp +33 -31
  233. esphome/components/script/script.cpp +6 -0
  234. esphome/components/script/script.h +19 -5
  235. esphome/components/sdm_meter/sensor.py +3 -1
  236. esphome/components/select/__init__.py +2 -2
  237. esphome/components/select/select.h +2 -2
  238. esphome/components/sen5x/sen5x.cpp +58 -55
  239. esphome/components/sen5x/sen5x.h +21 -15
  240. esphome/components/sen5x/sensor.py +67 -44
  241. esphome/components/sensirion_common/i2c_sensirion.cpp +18 -47
  242. esphome/components/sensirion_common/i2c_sensirion.h +39 -55
  243. esphome/components/sensor/__init__.py +2 -2
  244. esphome/components/sensor/automation.h +1 -1
  245. esphome/components/sensor/sensor.cpp +34 -6
  246. esphome/components/sensor/sensor.h +4 -21
  247. esphome/components/sgp30/sgp30.cpp +34 -35
  248. esphome/components/sgp30/sgp30.h +11 -10
  249. esphome/components/sgp4x/sgp4x.cpp +2 -2
  250. esphome/components/shelly_dimmer/light.py +7 -7
  251. esphome/components/sht4x/sht4x.cpp +1 -1
  252. esphome/components/sntp/sntp_component.cpp +36 -9
  253. esphome/components/sntp/sntp_component.h +7 -0
  254. esphome/components/sound_level/sound_level.cpp +1 -1
  255. esphome/components/speaker/__init__.py +2 -2
  256. esphome/components/speaker/media_player/__init__.py +2 -2
  257. esphome/components/speaker/media_player/speaker_media_player.cpp +1 -1
  258. esphome/components/spi/__init__.py +2 -2
  259. esphome/components/sprinkler/sprinkler.cpp +1 -1
  260. esphome/components/sps30/sps30.cpp +18 -23
  261. esphome/components/sps30/sps30.h +3 -3
  262. esphome/components/status_led/__init__.py +2 -2
  263. esphome/components/stepper/__init__.py +2 -2
  264. esphome/components/switch/__init__.py +2 -2
  265. esphome/components/switch/switch.cpp +5 -5
  266. esphome/components/sx1509/__init__.py +1 -1
  267. esphome/components/sx1509/sx1509.cpp +12 -7
  268. esphome/components/sx1509/sx1509.h +11 -4
  269. esphome/components/tca9555/tca9555.cpp +5 -5
  270. esphome/components/tee501/tee501.cpp +2 -21
  271. esphome/components/tee501/tee501.h +2 -4
  272. esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +1 -1
  273. esphome/components/template/datetime/template_date.cpp +1 -1
  274. esphome/components/template/datetime/template_datetime.cpp +2 -2
  275. esphome/components/template/datetime/template_time.cpp +1 -1
  276. esphome/components/template/number/template_number.cpp +1 -1
  277. esphome/components/template/select/template_select.cpp +1 -1
  278. esphome/components/template/text/template_text.cpp +1 -1
  279. esphome/components/text/__init__.py +2 -2
  280. esphome/components/text/text.h +2 -2
  281. esphome/components/text_sensor/__init__.py +2 -2
  282. esphome/components/text_sensor/text_sensor.h +4 -4
  283. esphome/components/thermostat/climate.py +11 -7
  284. esphome/components/thermostat/thermostat_climate.cpp +237 -206
  285. esphome/components/thermostat/thermostat_climate.h +52 -41
  286. esphome/components/time/__init__.py +2 -2
  287. esphome/components/tmp1075/tmp1075.cpp +1 -1
  288. esphome/components/total_daily_energy/total_daily_energy.cpp +1 -1
  289. esphome/components/touchscreen/__init__.py +2 -2
  290. esphome/components/tuya/number/tuya_number.cpp +1 -1
  291. esphome/components/udp/udp_component.cpp +3 -3
  292. esphome/components/ufire_ec/ufire_ec.cpp +4 -4
  293. esphome/components/ufire_ise/ufire_ise.cpp +4 -4
  294. esphome/components/update/__init__.py +2 -2
  295. esphome/components/usb_uart/usb_uart.cpp +1 -1
  296. esphome/components/valve/__init__.py +5 -5
  297. esphome/components/valve/valve.cpp +1 -1
  298. esphome/components/valve/valve.h +2 -2
  299. esphome/components/wake_on_lan/wake_on_lan.cpp +2 -2
  300. esphome/components/waveshare_epaper/waveshare_213v3.cpp +1 -1
  301. esphome/components/web_server/__init__.py +2 -2
  302. esphome/components/web_server/ota/__init__.py +2 -2
  303. esphome/components/web_server/ota/ota_web_server.cpp +11 -0
  304. esphome/components/web_server/web_server.cpp +58 -12
  305. esphome/components/web_server_base/__init__.py +2 -2
  306. esphome/components/wifi/__init__.py +5 -5
  307. esphome/components/wifi/wifi_component.cpp +3 -3
  308. esphome/components/wifi/wifi_component_esp_idf.cpp +2 -0
  309. esphome/config_validation.py +2 -2
  310. esphome/const.py +2 -1
  311. esphome/core/__init__.py +1 -0
  312. esphome/core/application.cpp +89 -51
  313. esphome/core/application.h +1 -0
  314. esphome/core/component.cpp +41 -19
  315. esphome/core/component.h +17 -13
  316. esphome/core/config.py +7 -7
  317. esphome/core/defines.h +4 -0
  318. esphome/core/entity_base.cpp +22 -8
  319. esphome/core/entity_base.h +43 -0
  320. esphome/core/helpers.cpp +26 -13
  321. esphome/core/helpers.h +4 -3
  322. esphome/core/ring_buffer.cpp +6 -2
  323. esphome/core/ring_buffer.h +2 -1
  324. esphome/core/scheduler.cpp +175 -94
  325. esphome/core/scheduler.h +66 -35
  326. esphome/core/time.cpp +6 -20
  327. esphome/coroutine.py +80 -3
  328. esphome/cpp_generator.py +13 -0
  329. esphome/cpp_helpers.py +2 -2
  330. esphome/dashboard/web_server.py +67 -10
  331. esphome/espota2.py +13 -6
  332. esphome/helpers.py +68 -83
  333. esphome/resolver.py +67 -0
  334. esphome/util.py +9 -6
  335. esphome/wizard.py +39 -26
  336. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/METADATA +9 -9
  337. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/RECORD +345 -314
  338. /esphome/components/ld2420/text_sensor/{text_sensor.h → ld2420_text_sensor.h} +0 -0
  339. /esphome/components/opentherm/number/{number.h → opentherm_number.h} +0 -0
  340. /esphome/components/opentherm/output/{output.h → opentherm_output.h} +0 -0
  341. /esphome/components/opentherm/switch/{switch.h → opentherm_switch.h} +0 -0
  342. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/WHEEL +0 -0
  343. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/entry_points.txt +0 -0
  344. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/licenses/LICENSE +0 -0
  345. {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/top_level.txt +0 -0
esphome/core/scheduler.h CHANGED
@@ -88,19 +88,22 @@ class Scheduler {
88
88
  struct SchedulerItem {
89
89
  // Ordered by size to minimize padding
90
90
  Component *component;
91
- uint32_t interval;
92
- // 64-bit time to handle millis() rollover. The scheduler combines the 32-bit millis()
93
- // with a 16-bit rollover counter to create a 64-bit time that won't roll over for
94
- // billions of years. This ensures correct scheduling even when devices run for months.
95
- uint64_t next_execution_;
96
-
97
91
  // Optimized name storage using tagged union
98
92
  union {
99
93
  const char *static_name; // For string literals (no allocation)
100
94
  char *dynamic_name; // For allocated strings
101
95
  } name_;
102
-
96
+ uint32_t interval;
97
+ // Split time to handle millis() rollover. The scheduler combines the 32-bit millis()
98
+ // with a 16-bit rollover counter to create a 48-bit time space (using 32+16 bits).
99
+ // This is intentionally limited to 48 bits, not stored as a full 64-bit value.
100
+ // With 49.7 days per 32-bit rollover, the 16-bit counter supports
101
+ // 49.7 days × 65536 = ~8900 years. This ensures correct scheduling
102
+ // even when devices run for months. Split into two fields for better memory
103
+ // alignment on 32-bit systems.
104
+ uint32_t next_execution_low_; // Lower 32 bits of execution time (millis value)
103
105
  std::function<void()> callback;
106
+ uint16_t next_execution_high_; // Upper 16 bits (millis_major counter)
104
107
 
105
108
  #ifdef ESPHOME_THREAD_MULTI_ATOMICS
106
109
  // Multi-threaded with atomics: use atomic for lock-free access
@@ -126,7 +129,8 @@ class Scheduler {
126
129
  SchedulerItem()
127
130
  : component(nullptr),
128
131
  interval(0),
129
- next_execution_(0),
132
+ next_execution_low_(0),
133
+ next_execution_high_(0),
130
134
  #ifdef ESPHOME_THREAD_MULTI_ATOMICS
131
135
  // remove is initialized in the member declaration as std::atomic<bool>{false}
132
136
  type(TIMEOUT),
@@ -142,11 +146,7 @@ class Scheduler {
142
146
  }
143
147
 
144
148
  // Destructor to clean up dynamic names
145
- ~SchedulerItem() {
146
- if (name_is_dynamic) {
147
- delete[] name_.dynamic_name;
148
- }
149
- }
149
+ ~SchedulerItem() { clear_dynamic_name(); }
150
150
 
151
151
  // Delete copy operations to prevent accidental copies
152
152
  SchedulerItem(const SchedulerItem &) = delete;
@@ -159,13 +159,19 @@ class Scheduler {
159
159
  // Helper to get the name regardless of storage type
160
160
  const char *get_name() const { return name_is_dynamic ? name_.dynamic_name : name_.static_name; }
161
161
 
162
- // Helper to set name with proper ownership
163
- void set_name(const char *name, bool make_copy = false) {
164
- // Clean up old dynamic name if any
162
+ // Helper to clear dynamic name if allocated
163
+ void clear_dynamic_name() {
165
164
  if (name_is_dynamic && name_.dynamic_name) {
166
165
  delete[] name_.dynamic_name;
166
+ name_.dynamic_name = nullptr;
167
167
  name_is_dynamic = false;
168
168
  }
169
+ }
170
+
171
+ // Helper to set name with proper ownership
172
+ void set_name(const char *name, bool make_copy = false) {
173
+ // Clean up old dynamic name if any
174
+ clear_dynamic_name();
169
175
 
170
176
  if (!name) {
171
177
  // nullptr case - no name provided
@@ -183,8 +189,22 @@ class Scheduler {
183
189
  }
184
190
 
185
191
  static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
186
- const char *get_type_str() const { return (type == TIMEOUT) ? "timeout" : "interval"; }
187
- const char *get_source() const { return component ? component->get_component_source() : "unknown"; }
192
+
193
+ // Note: We use 48 bits total (32 + 16), stored in a 64-bit value for API compatibility.
194
+ // The upper 16 bits of the 64-bit value are always zero, which is fine since
195
+ // millis_major_ is also 16 bits and they must match.
196
+ constexpr uint64_t get_next_execution() const {
197
+ return (static_cast<uint64_t>(next_execution_high_) << 32) | next_execution_low_;
198
+ }
199
+
200
+ constexpr void set_next_execution(uint64_t value) {
201
+ next_execution_low_ = static_cast<uint32_t>(value);
202
+ // Cast to uint16_t intentionally truncates to lower 16 bits of the upper 32 bits.
203
+ // This is correct because millis_major_ that creates these values is also 16 bits.
204
+ next_execution_high_ = static_cast<uint16_t>(value >> 32);
205
+ }
206
+ constexpr const char *get_type_str() const { return (type == TIMEOUT) ? "timeout" : "interval"; }
207
+ const LogString *get_source() const { return component ? component->get_component_log_str() : LOG_STR("unknown"); }
188
208
  };
189
209
 
190
210
  // Common implementation for both timeout and interval
@@ -214,6 +234,15 @@ class Scheduler {
214
234
  // Common implementation for cancel operations
215
235
  bool cancel_item_(Component *component, bool is_static_string, const void *name_ptr, SchedulerItem::Type type);
216
236
 
237
+ // Helper to check if two scheduler item names match
238
+ inline bool HOT names_match_(const char *name1, const char *name2) const {
239
+ // Check pointer equality first (common for static strings), then string contents
240
+ // The core ESPHome codebase uses static strings (const char*) for component names,
241
+ // making pointer comparison effective. The std::string overloads exist only for
242
+ // compatibility with external components but are rarely used in practice.
243
+ return (name1 != nullptr && name2 != nullptr) && ((name1 == name2) || (strcmp(name1, name2) == 0));
244
+ }
245
+
217
246
  // Helper function to check if item matches criteria for cancellation
218
247
  inline bool HOT matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr,
219
248
  SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const {
@@ -221,29 +250,20 @@ class Scheduler {
221
250
  (match_retry && !item->is_retry)) {
222
251
  return false;
223
252
  }
224
- const char *item_name = item->get_name();
225
- if (item_name == nullptr) {
226
- return false;
227
- }
228
- // Fast path: if pointers are equal
229
- // This is effective because the core ESPHome codebase uses static strings (const char*)
230
- // for component names. The std::string overloads exist only for compatibility with
231
- // external components, but are rarely used in practice.
232
- if (item_name == name_cstr) {
233
- return true;
234
- }
235
- // Slow path: compare string contents
236
- return strcmp(name_cstr, item_name) == 0;
253
+ return this->names_match_(item->get_name(), name_cstr);
237
254
  }
238
255
 
239
256
  // Helper to execute a scheduler item
240
257
  void execute_item_(SchedulerItem *item, uint32_t now);
241
258
 
242
259
  // Helper to check if item should be skipped
243
- bool should_skip_item_(const SchedulerItem *item) const {
244
- return item->remove || (item->component != nullptr && item->component->is_failed());
260
+ bool should_skip_item_(SchedulerItem *item) const {
261
+ return is_item_removed_(item) || (item->component != nullptr && item->component->is_failed());
245
262
  }
246
263
 
264
+ // Helper to recycle a SchedulerItem
265
+ void recycle_item_(std::unique_ptr<SchedulerItem> item);
266
+
247
267
  // Helper to check if item is marked for removal (platform-specific)
248
268
  // Returns true if item should be skipped, handles platform-specific synchronization
249
269
  // For ESPHOME_THREAD_MULTI_NO_ATOMICS platforms, the caller must hold the scheduler lock before calling this
@@ -280,8 +300,9 @@ class Scheduler {
280
300
  bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr,
281
301
  bool match_retry) const {
282
302
  for (const auto &item : container) {
283
- if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry,
284
- /* skip_removed= */ false)) {
303
+ if (is_item_removed_(item.get()) &&
304
+ this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry,
305
+ /* skip_removed= */ false)) {
285
306
  return true;
286
307
  }
287
308
  }
@@ -297,6 +318,16 @@ class Scheduler {
297
318
  #endif /* ESPHOME_THREAD_SINGLE */
298
319
  uint32_t to_remove_{0};
299
320
 
321
+ // Memory pool for recycling SchedulerItem objects to reduce heap churn.
322
+ // Design decisions:
323
+ // - std::vector is used instead of a fixed array because many systems only need 1-2 scheduler items
324
+ // - The vector grows dynamically up to MAX_POOL_SIZE (5) only when needed, saving memory on simple setups
325
+ // - Pool size of 5 matches typical usage (2-4 timers) while keeping memory overhead low (~250 bytes on ESP32)
326
+ // - The pool significantly reduces heap fragmentation which is critical because heap allocation/deallocation
327
+ // can stall the entire system, causing timing issues and dropped events for any components that need
328
+ // to synchronize between tasks (see https://github.com/esphome/backlog/issues/52)
329
+ std::vector<std::unique_ptr<SchedulerItem>> scheduler_item_pool_;
330
+
300
331
  #ifdef ESPHOME_THREAD_MULTI_ATOMICS
301
332
  /*
302
333
  * Multi-threaded platforms with atomic support: last_millis_ needs atomic for lock-free updates
esphome/core/time.cpp CHANGED
@@ -203,27 +203,13 @@ void ESPTime::recalc_timestamp_local() {
203
203
  }
204
204
 
205
205
  int32_t ESPTime::timezone_offset() {
206
- int32_t offset = 0;
207
206
  time_t now = ::time(nullptr);
208
- auto local = ESPTime::from_epoch_local(now);
209
- auto utc = ESPTime::from_epoch_utc(now);
210
- bool negative = utc.hour > local.hour && local.day_of_year <= utc.day_of_year;
211
-
212
- if (utc.minute > local.minute) {
213
- local.minute += 60;
214
- local.hour -= 1;
215
- }
216
- offset += (local.minute - utc.minute) * 60;
217
-
218
- if (negative) {
219
- offset -= (utc.hour - local.hour) * 3600;
220
- } else {
221
- if (utc.hour > local.hour) {
222
- local.hour += 24;
223
- }
224
- offset += (local.hour - utc.hour) * 3600;
225
- }
226
- return offset;
207
+ struct tm local_tm = *::localtime(&now);
208
+ local_tm.tm_isdst = 0; // Cause mktime to ignore daylight saving time because we want to include it in the offset.
209
+ time_t local_time = mktime(&local_tm);
210
+ struct tm utc_tm = *::gmtime(&now);
211
+ time_t utc_time = mktime(&utc_tm);
212
+ return static_cast<int32_t>(local_time - utc_time);
227
213
  }
228
214
 
229
215
  bool ESPTime::operator<(const ESPTime &other) const { return this->timestamp < other.timestamp; }
esphome/coroutine.py CHANGED
@@ -42,7 +42,10 @@ Here everything is combined in `yield` expressions. You await other coroutines u
42
42
  the last `yield` expression defines what is returned.
43
43
  """
44
44
 
45
+ from __future__ import annotations
46
+
45
47
  from collections.abc import Awaitable, Callable, Generator, Iterator
48
+ import enum
46
49
  import functools
47
50
  import heapq
48
51
  import inspect
@@ -53,6 +56,79 @@ from typing import Any
53
56
  _LOGGER = logging.getLogger(__name__)
54
57
 
55
58
 
59
+ class CoroPriority(enum.IntEnum):
60
+ """Execution priority stages for ESPHome code generation.
61
+
62
+ Higher values run first. These stages ensure proper dependency
63
+ resolution during code generation.
64
+ """
65
+
66
+ # Platform initialization - must run first
67
+ # Examples: esp32, esp8266, rp2040
68
+ PLATFORM = 1000
69
+
70
+ # Network infrastructure setup
71
+ # Examples: network (201)
72
+ NETWORK = 201
73
+
74
+ # Network transport layer
75
+ # Examples: async_tcp (200)
76
+ NETWORK_TRANSPORT = 200
77
+
78
+ # Core system components
79
+ # Examples: esphome core, most entity base components (cover, update, datetime,
80
+ # valve, alarm_control_panel, lock, event, binary_sensor, button, climate, fan,
81
+ # light, media_player, number, select, sensor, switch, text_sensor, text),
82
+ # microphone, speaker, audio_dac, touchscreen, stepper
83
+ CORE = 100
84
+
85
+ # Diagnostic and debugging systems
86
+ # Examples: logger (90)
87
+ DIAGNOSTICS = 90
88
+
89
+ # Status and monitoring systems
90
+ # Examples: status_led (80)
91
+ STATUS = 80
92
+
93
+ # Communication protocols and services
94
+ # Examples: web_server_base (65), captive_portal (64), wifi (60), ethernet (60),
95
+ # mdns (55), ota_updates (54), web_server_ota (52)
96
+ COMMUNICATION = 60
97
+
98
+ # Application-level services
99
+ # Examples: safe_mode (50)
100
+ APPLICATION = 50
101
+
102
+ # Web and UI services
103
+ # Examples: web_server (40)
104
+ WEB = 40
105
+
106
+ # Automations and user logic
107
+ # Examples: esphome core automations (30)
108
+ AUTOMATION = 30
109
+
110
+ # Bus and peripheral setup
111
+ # Examples: i2c (1)
112
+ BUS = 1
113
+
114
+ # Standard component priority (default)
115
+ # Components without explicit priority run at 0
116
+ COMPONENT = 0
117
+
118
+ # Components that need others to be registered first
119
+ # Examples: globals (-100)
120
+ LATE = -100
121
+
122
+ # Platform-specific workarounds and fixes
123
+ # Examples: add_arduino_global_workaround (-999), esp8266 pin states (-999)
124
+ WORKAROUNDS = -999
125
+
126
+ # Final setup that requires all components to be registered
127
+ # Examples: add_includes, _add_platformio_options, _add_platform_defines (all -1000),
128
+ # esp32_ble_tracker feature defines (-1000)
129
+ FINAL = -1000
130
+
131
+
56
132
  def coroutine(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
57
133
  """Decorator to apply to methods to convert them to ESPHome coroutines."""
58
134
  if getattr(func, "_esphome_coroutine", False):
@@ -95,15 +171,16 @@ def coroutine(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
95
171
  return coro
96
172
 
97
173
 
98
- def coroutine_with_priority(priority: float):
174
+ def coroutine_with_priority(priority: float | CoroPriority):
99
175
  """Decorator to apply to functions to convert them to ESPHome coroutines.
100
176
 
101
177
  :param priority: priority with which to schedule the coroutine, higher priorities run first.
178
+ Can be a float or a CoroPriority enum value.
102
179
  """
103
180
 
104
181
  def decorator(func):
105
182
  coro = coroutine(func)
106
- coro.priority = priority
183
+ coro.priority = float(priority)
107
184
  return coro
108
185
 
109
186
  return decorator
@@ -173,7 +250,7 @@ class _Task:
173
250
  self.iterator = iterator
174
251
  self.original_function = original_function
175
252
 
176
- def with_priority(self, priority: float) -> "_Task":
253
+ def with_priority(self, priority: float) -> _Task:
177
254
  return _Task(priority, self.id_number, self.iterator, self.original_function)
178
255
 
179
256
  @property
esphome/cpp_generator.py CHANGED
@@ -253,6 +253,19 @@ class StringLiteral(Literal):
253
253
  return cpp_string_escape(self.string)
254
254
 
255
255
 
256
+ class LogStringLiteral(Literal):
257
+ """A string literal that uses LOG_STR() macro for flash storage on ESP8266."""
258
+
259
+ __slots__ = ("string",)
260
+
261
+ def __init__(self, string: str) -> None:
262
+ super().__init__()
263
+ self.string = string
264
+
265
+ def __str__(self) -> str:
266
+ return f"LOG_STR({cpp_string_escape(self.string)})"
267
+
268
+
256
269
  class IntLiteral(Literal):
257
270
  __slots__ = ("i",)
258
271
 
esphome/cpp_helpers.py CHANGED
@@ -9,7 +9,7 @@ from esphome.const import (
9
9
  )
10
10
  from esphome.core import CORE, ID, coroutine
11
11
  from esphome.coroutine import FakeAwaitable
12
- from esphome.cpp_generator import add, get_variable
12
+ from esphome.cpp_generator import LogStringLiteral, add, get_variable
13
13
  from esphome.cpp_types import App
14
14
  from esphome.types import ConfigFragmentType, ConfigType
15
15
  from esphome.util import Registry, RegistryEntry
@@ -76,7 +76,7 @@ async def register_component(var, config):
76
76
  "Error while finding name of component, please report this", exc_info=e
77
77
  )
78
78
  if name is not None:
79
- add(var.set_component_source(name))
79
+ add(var.set_component_source(LogStringLiteral(name)))
80
80
 
81
81
  add(App.register_component(var))
82
82
  return var
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import base64
5
+ import binascii
5
6
  from collections.abc import Callable, Iterable
6
7
  import datetime
7
8
  import functools
@@ -490,7 +491,17 @@ class WizardRequestHandler(BaseHandler):
490
491
  kwargs = {
491
492
  k: v
492
493
  for k, v in json.loads(self.request.body.decode()).items()
493
- if k in ("name", "platform", "board", "ssid", "psk", "password")
494
+ if k
495
+ in (
496
+ "type",
497
+ "name",
498
+ "platform",
499
+ "board",
500
+ "ssid",
501
+ "psk",
502
+ "password",
503
+ "file_content",
504
+ )
494
505
  }
495
506
  if not kwargs["name"]:
496
507
  self.set_status(422)
@@ -498,19 +509,65 @@ class WizardRequestHandler(BaseHandler):
498
509
  self.write(json.dumps({"error": "Name is required"}))
499
510
  return
500
511
 
512
+ if "type" not in kwargs:
513
+ # Default to basic wizard type for backwards compatibility
514
+ kwargs["type"] = "basic"
515
+
501
516
  kwargs["friendly_name"] = kwargs["name"]
502
517
  kwargs["name"] = friendly_name_slugify(kwargs["friendly_name"])
503
-
504
- kwargs["ota_password"] = secrets.token_hex(16)
505
- noise_psk = secrets.token_bytes(32)
506
- kwargs["api_encryption_key"] = base64.b64encode(noise_psk).decode()
518
+ if kwargs["type"] == "basic":
519
+ kwargs["ota_password"] = secrets.token_hex(16)
520
+ noise_psk = secrets.token_bytes(32)
521
+ kwargs["api_encryption_key"] = base64.b64encode(noise_psk).decode()
522
+ elif kwargs["type"] == "upload":
523
+ try:
524
+ kwargs["file_text"] = base64.b64decode(kwargs["file_content"]).decode(
525
+ "utf-8"
526
+ )
527
+ except (binascii.Error, UnicodeDecodeError):
528
+ self.set_status(422)
529
+ self.set_header("content-type", "application/json")
530
+ self.write(
531
+ json.dumps({"error": "The uploaded file is not correctly encoded."})
532
+ )
533
+ return
534
+ elif kwargs["type"] != "empty":
535
+ self.set_status(422)
536
+ self.set_header("content-type", "application/json")
537
+ self.write(
538
+ json.dumps(
539
+ {"error": f"Invalid wizard type specified: {kwargs['type']}"}
540
+ )
541
+ )
542
+ return
507
543
  filename = f"{kwargs['name']}.yaml"
508
544
  destination = settings.rel_path(filename)
509
- wizard.wizard_write(path=destination, **kwargs)
510
- self.set_status(200)
511
- self.set_header("content-type", "application/json")
512
- self.write(json.dumps({"configuration": filename}))
513
- self.finish()
545
+
546
+ # Check if destination file already exists
547
+ if os.path.exists(destination):
548
+ self.set_status(409) # Conflict status code
549
+ self.set_header("content-type", "application/json")
550
+ self.write(
551
+ json.dumps({"error": f"Configuration file '{filename}' already exists"})
552
+ )
553
+ self.finish()
554
+ return
555
+
556
+ success = wizard.wizard_write(path=destination, **kwargs)
557
+ if success:
558
+ self.set_status(200)
559
+ self.set_header("content-type", "application/json")
560
+ self.write(json.dumps({"configuration": filename}))
561
+ self.finish()
562
+ else:
563
+ self.set_status(500)
564
+ self.set_header("content-type", "application/json")
565
+ self.write(
566
+ json.dumps(
567
+ {"error": "Failed to write configuration, see logs for details"}
568
+ )
569
+ )
570
+ self.finish()
514
571
 
515
572
 
516
573
  class ImportRequestHandler(BaseHandler):
esphome/espota2.py CHANGED
@@ -308,8 +308,12 @@ def perform_ota(
308
308
  time.sleep(1)
309
309
 
310
310
 
311
- def run_ota_impl_(remote_host, remote_port, password, filename):
311
+ def run_ota_impl_(
312
+ remote_host: str | list[str], remote_port: int, password: str, filename: str
313
+ ) -> tuple[int, str | None]:
314
+ # Handle both single host and list of hosts
312
315
  try:
316
+ # Resolve all hosts at once for parallel DNS resolution
313
317
  res = resolve_ip_address(remote_host, remote_port)
314
318
  except EsphomeError as err:
315
319
  _LOGGER.error(
@@ -340,19 +344,22 @@ def run_ota_impl_(remote_host, remote_port, password, filename):
340
344
  perform_ota(sock, password, file_handle, filename)
341
345
  except OTAError as err:
342
346
  _LOGGER.error(str(err))
343
- return 1
347
+ return 1, None
344
348
  finally:
345
349
  sock.close()
346
350
 
347
- return 0
351
+ # Successfully uploaded to sa[0]
352
+ return 0, sa[0]
348
353
 
349
354
  _LOGGER.error("Connection failed.")
350
- return 1
355
+ return 1, None
351
356
 
352
357
 
353
- def run_ota(remote_host, remote_port, password, filename):
358
+ def run_ota(
359
+ remote_host: str | list[str], remote_port: int, password: str, filename: str
360
+ ) -> tuple[int, str | None]:
354
361
  try:
355
362
  return run_ota_impl_(remote_host, remote_port, password, filename)
356
363
  except OTAError as err:
357
364
  _LOGGER.error(err)
358
- return 1
365
+ return 1, None