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.
- esphome/__main__.py +36 -42
- esphome/components/absolute_humidity/absolute_humidity.cpp +3 -5
- esphome/components/adc/adc_sensor_esp32.cpp +29 -6
- esphome/components/ags10/ags10.cpp +3 -18
- esphome/components/ags10/ags10.h +2 -12
- esphome/components/aht10/aht10.cpp +3 -3
- esphome/components/airthings_ble/__init__.py +2 -2
- esphome/components/alarm_control_panel/__init__.py +2 -2
- esphome/components/am2315c/am2315c.cpp +1 -17
- esphome/components/am2315c/am2315c.h +2 -3
- esphome/components/api/__init__.py +2 -2
- esphome/components/api/api_connection.cpp +34 -23
- esphome/components/api/api_connection.h +20 -39
- esphome/components/api/api_frame_helper.cpp +25 -25
- esphome/components/api/api_frame_helper.h +3 -3
- esphome/components/api/api_frame_helper_noise.cpp +75 -40
- esphome/components/api/api_frame_helper_noise.h +3 -7
- esphome/components/api/api_frame_helper_plaintext.cpp +17 -4
- esphome/components/api/api_frame_helper_plaintext.h +1 -4
- esphome/components/api/api_pb2.cpp +20 -2
- esphome/components/api/api_pb2.h +146 -141
- esphome/components/api/api_pb2_dump.cpp +12 -1
- esphome/components/api/proto.cpp +33 -37
- esphome/components/async_tcp/__init__.py +2 -2
- esphome/components/atm90e26/sensor.py +2 -0
- esphome/components/atm90e32/sensor.py +4 -2
- esphome/components/audio_adc/__init__.py +2 -2
- esphome/components/audio_dac/__init__.py +2 -2
- esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +1 -1
- esphome/components/bedjet/bedjet_hub.cpp +1 -1
- esphome/components/binary_sensor/__init__.py +2 -2
- esphome/components/binary_sensor/binary_sensor.cpp +13 -0
- esphome/components/binary_sensor/binary_sensor.h +4 -7
- esphome/components/bl0940/__init__.py +6 -1
- esphome/components/bl0940/bl0940.cpp +178 -41
- esphome/components/bl0940/bl0940.h +121 -76
- esphome/components/bl0940/button/__init__.py +27 -0
- esphome/components/bl0940/button/calibration_reset_button.cpp +20 -0
- esphome/components/bl0940/button/calibration_reset_button.h +19 -0
- esphome/components/bl0940/number/__init__.py +94 -0
- esphome/components/bl0940/number/calibration_number.cpp +29 -0
- esphome/components/bl0940/number/calibration_number.h +26 -0
- esphome/components/bl0940/sensor.py +151 -2
- esphome/components/bl0942/bl0942.cpp +1 -1
- esphome/components/ble_client/output/__init__.py +4 -4
- esphome/components/bluetooth_proxy/__init__.py +1 -1
- esphome/components/bluetooth_proxy/bluetooth_connection.h +1 -1
- esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +15 -7
- esphome/components/bluetooth_proxy/bluetooth_proxy.h +3 -2
- esphome/components/button/__init__.py +2 -2
- esphome/components/button/button.cpp +13 -0
- esphome/components/button/button.h +4 -7
- esphome/components/camera/buffer.h +18 -0
- esphome/components/camera/buffer_impl.cpp +20 -0
- esphome/components/camera/buffer_impl.h +26 -0
- esphome/components/camera/camera.h +43 -0
- esphome/components/camera/encoder.h +69 -0
- esphome/components/camera_encoder/__init__.py +62 -0
- esphome/components/camera_encoder/encoder_buffer_impl.cpp +23 -0
- esphome/components/camera_encoder/encoder_buffer_impl.h +25 -0
- esphome/components/camera_encoder/esp32_camera_jpeg_encoder.cpp +82 -0
- esphome/components/camera_encoder/esp32_camera_jpeg_encoder.h +39 -0
- esphome/components/captive_portal/__init__.py +2 -2
- esphome/components/captive_portal/captive_portal.cpp +35 -12
- esphome/components/captive_portal/captive_portal.h +3 -3
- esphome/components/ccs811/ccs811.cpp +3 -3
- esphome/components/climate/__init__.py +2 -2
- esphome/components/climate/climate.cpp +1 -1
- esphome/components/cover/__init__.py +5 -5
- esphome/components/cover/cover.cpp +1 -1
- esphome/components/cover/cover.h +2 -2
- esphome/components/dallas_temp/dallas_temp.cpp +2 -2
- esphome/components/datetime/__init__.py +2 -2
- esphome/components/datetime/date_entity.h +2 -2
- esphome/components/datetime/datetime_entity.h +2 -2
- esphome/components/datetime/time_entity.h +2 -2
- esphome/components/debug/debug_esp32.cpp +1 -1
- esphome/components/display/__init__.py +4 -4
- esphome/components/duty_time/duty_time_sensor.cpp +1 -1
- esphome/components/esp32/__init__.py +0 -5
- esphome/components/esp32/gpio.cpp +27 -23
- esphome/components/esp32/gpio.h +26 -11
- esphome/components/esp32/preferences.cpp +8 -4
- esphome/components/esp32_ble/__init__.py +7 -2
- esphome/components/esp32_ble_client/ble_client_base.cpp +7 -3
- esphome/components/esp32_ble_tracker/__init__.py +2 -2
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +9 -44
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +2 -14
- esphome/components/esp8266/__init__.py +2 -2
- esphome/components/esp8266/core.cpp +2 -2
- esphome/components/esp8266/gpio.py +4 -4
- esphome/components/esp8266/preferences.cpp +30 -28
- esphome/components/esphome/ota/__init__.py +2 -2
- esphome/components/esphome/ota/ota_esphome.cpp +21 -19
- esphome/components/esphome/ota/ota_esphome.h +6 -5
- esphome/components/ethernet/__init__.py +7 -2
- esphome/components/ethernet/ethernet_component.cpp +1 -1
- esphome/components/event/__init__.py +2 -2
- esphome/components/event/event.h +4 -4
- esphome/components/fan/__init__.py +2 -2
- esphome/components/fan/fan.cpp +2 -1
- esphome/components/gdk101/gdk101.cpp +4 -4
- esphome/components/globals/__init__.py +2 -2
- esphome/components/gpio/binary_sensor/gpio_binary_sensor.cpp +19 -18
- esphome/components/gpio_expander/cached_gpio.h +36 -16
- esphome/components/grove_gas_mc_v2/grove_gas_mc_v2.cpp +5 -5
- esphome/components/gt911/touchscreen/gt911_touchscreen.cpp +1 -1
- esphome/components/haier/haier_base.cpp +1 -1
- esphome/components/haier/hon_climate.cpp +1 -1
- esphome/components/hlw8012/hlw8012.cpp +5 -5
- esphome/components/honeywellabp2_i2c/honeywellabp2.cpp +4 -4
- esphome/components/host/preferences.h +3 -2
- esphome/components/hte501/hte501.cpp +3 -21
- esphome/components/hte501/hte501.h +2 -3
- esphome/components/http_request/ota/__init__.py +2 -2
- esphome/components/i2c/__init__.py +2 -2
- esphome/components/i2c/i2c.cpp +13 -9
- esphome/components/i2c/i2c_bus.h +36 -6
- esphome/components/i2s_audio/__init__.py +8 -2
- esphome/components/i2s_audio/media_player/__init__.py +1 -1
- esphome/components/i2s_audio/microphone/__init__.py +1 -1
- esphome/components/i2s_audio/speaker/__init__.py +1 -1
- esphome/components/inkplate/__init__.py +1 -0
- esphome/components/inkplate/const.py +105 -0
- esphome/components/inkplate/display.py +238 -0
- esphome/components/{inkplate6 → inkplate}/inkplate.cpp +156 -74
- esphome/components/{inkplate6 → inkplate}/inkplate.h +28 -68
- esphome/components/inkplate6/__init__.py +0 -1
- esphome/components/inkplate6/display.py +2 -211
- esphome/components/integration/integration_sensor.cpp +1 -1
- esphome/components/json/__init__.py +2 -2
- esphome/components/kmeteriso/kmeteriso.cpp +1 -1
- esphome/components/lc709203f/lc709203f.cpp +4 -17
- esphome/components/lc709203f/lc709203f.h +2 -3
- esphome/components/ld2420/text_sensor/{text_sensor.cpp → ld2420_text_sensor.cpp} +1 -1
- esphome/components/ld2450/ld2450.cpp +1 -1
- esphome/components/libretiny/preferences.cpp +13 -5
- esphome/components/light/__init__.py +2 -2
- esphome/components/light/addressable_light_effect.h +7 -0
- esphome/components/light/base_light_effects.h +8 -0
- esphome/components/light/light_call.cpp +22 -20
- esphome/components/light/light_effect.cpp +36 -0
- esphome/components/light/light_effect.h +14 -0
- esphome/components/light/light_json_schema.cpp +9 -1
- esphome/components/light/light_state.cpp +2 -2
- esphome/components/light/light_state.h +39 -0
- esphome/components/lock/__init__.py +2 -2
- esphome/components/lock/lock.h +2 -2
- esphome/components/logger/__init__.py +2 -2
- esphome/components/logger/logger.cpp +25 -4
- esphome/components/logger/logger.h +1 -1
- esphome/components/logger/logger_esp32.cpp +16 -8
- esphome/components/logger/logger_esp8266.cpp +11 -3
- esphome/components/logger/logger_libretiny.cpp +13 -3
- esphome/components/logger/logger_rp2040.cpp +14 -3
- esphome/components/logger/logger_zephyr.cpp +15 -4
- esphome/components/lvgl/defines.py +1 -0
- esphome/components/lvgl/hello_world.py +96 -33
- esphome/components/lvgl/number/lvgl_number.h +1 -1
- esphome/components/lvgl/select/lvgl_select.h +1 -1
- esphome/components/lvgl/widgets/__init__.py +0 -1
- esphome/components/lvgl/widgets/spinbox.py +20 -11
- esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.cpp +1 -1
- esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.cpp +1 -1
- esphome/components/mapping/__init__.py +13 -5
- esphome/components/mapping/mapping.h +69 -0
- esphome/components/max17043/max17043.cpp +2 -2
- esphome/components/mcp23016/__init__.py +1 -0
- esphome/components/mcp23016/mcp23016.cpp +20 -5
- esphome/components/mcp23016/mcp23016.h +10 -4
- esphome/components/mcp23x08_base/mcp23x08_base.cpp +1 -1
- esphome/components/mcp23x17_base/mcp23x17_base.cpp +2 -2
- esphome/components/mdns/__init__.py +2 -2
- esphome/components/mdns/mdns_component.cpp +145 -54
- esphome/components/media_player/__init__.py +2 -2
- esphome/components/micro_wake_word/__init__.py +2 -2
- esphome/components/microphone/__init__.py +2 -2
- esphome/components/mipi/__init__.py +77 -33
- esphome/components/mipi_rgb/__init__.py +2 -0
- esphome/components/mipi_rgb/display.py +321 -0
- esphome/components/mipi_rgb/mipi_rgb.cpp +388 -0
- esphome/components/mipi_rgb/mipi_rgb.h +127 -0
- esphome/components/mipi_rgb/models/guition.py +24 -0
- esphome/components/mipi_rgb/models/lilygo.py +228 -0
- esphome/components/mipi_rgb/models/rpi.py +9 -0
- esphome/components/mipi_rgb/models/st7701s.py +214 -0
- esphome/components/mipi_rgb/models/waveshare.py +64 -0
- esphome/components/mipi_spi/models/jc.py +229 -0
- esphome/components/mlx90614/mlx90614.cpp +1 -16
- esphome/components/mlx90614/mlx90614.h +0 -1
- esphome/components/mqtt/__init__.py +2 -2
- esphome/components/mqtt/mqtt_sensor.cpp +7 -2
- esphome/components/ms5611/ms5611.cpp +7 -6
- esphome/components/network/__init__.py +2 -2
- esphome/components/nextion/nextion_upload.cpp +4 -1
- esphome/components/nrf52/__init__.py +49 -6
- esphome/components/nrf52/const.py +1 -0
- esphome/components/nrf52/dfu.cpp +51 -0
- esphome/components/nrf52/dfu.h +24 -0
- esphome/components/ntc/ntc.cpp +1 -1
- esphome/components/number/__init__.py +2 -2
- esphome/components/number/automation.cpp +1 -1
- esphome/components/number/number.cpp +21 -0
- esphome/components/number/number.h +4 -13
- esphome/components/opentherm/hub.h +6 -6
- esphome/components/opentherm/number/{number.cpp → opentherm_number.cpp} +2 -2
- esphome/components/opentherm/output/{output.cpp → opentherm_output.cpp} +1 -1
- esphome/components/opentherm/switch/{switch.cpp → opentherm_switch.cpp} +1 -1
- esphome/components/ota/__init__.py +2 -2
- esphome/components/pca6416a/__init__.py +1 -0
- esphome/components/pca6416a/pca6416a.cpp +20 -5
- esphome/components/pca6416a/pca6416a.h +12 -5
- esphome/components/pca9554/__init__.py +2 -1
- esphome/components/pca9554/pca9554.cpp +12 -18
- esphome/components/pca9554/pca9554.h +10 -9
- esphome/components/pcf8574/__init__.py +1 -0
- esphome/components/pcf8574/pcf8574.cpp +14 -5
- esphome/components/pcf8574/pcf8574.h +13 -6
- esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +7 -7
- esphome/components/pipsolar/__init__.py +3 -3
- esphome/components/pipsolar/output/__init__.py +4 -4
- esphome/components/pulse_width/pulse_width.cpp +2 -2
- esphome/components/qmp6988/qmp6988.cpp +81 -126
- esphome/components/qmp6988/qmp6988.h +31 -37
- esphome/components/radon_eye_ble/__init__.py +2 -2
- esphome/components/remote_base/__init__.py +6 -8
- esphome/components/rotary_encoder/rotary_encoder.cpp +1 -1
- esphome/components/rp2040/__init__.py +2 -2
- esphome/components/runtime_stats/runtime_stats.cpp +10 -23
- esphome/components/runtime_stats/runtime_stats.h +4 -10
- esphome/components/safe_mode/__init__.py +2 -2
- esphome/components/safe_mode/safe_mode.cpp +33 -31
- esphome/components/script/script.cpp +6 -0
- esphome/components/script/script.h +19 -5
- esphome/components/sdm_meter/sensor.py +3 -1
- esphome/components/select/__init__.py +2 -2
- esphome/components/select/select.h +2 -2
- esphome/components/sen5x/sen5x.cpp +58 -55
- esphome/components/sen5x/sen5x.h +21 -15
- esphome/components/sen5x/sensor.py +67 -44
- esphome/components/sensirion_common/i2c_sensirion.cpp +18 -47
- esphome/components/sensirion_common/i2c_sensirion.h +39 -55
- esphome/components/sensor/__init__.py +2 -2
- esphome/components/sensor/automation.h +1 -1
- esphome/components/sensor/sensor.cpp +34 -6
- esphome/components/sensor/sensor.h +4 -21
- esphome/components/sgp30/sgp30.cpp +34 -35
- esphome/components/sgp30/sgp30.h +11 -10
- esphome/components/sgp4x/sgp4x.cpp +2 -2
- esphome/components/shelly_dimmer/light.py +7 -7
- esphome/components/sht4x/sht4x.cpp +1 -1
- esphome/components/sntp/sntp_component.cpp +36 -9
- esphome/components/sntp/sntp_component.h +7 -0
- esphome/components/sound_level/sound_level.cpp +1 -1
- esphome/components/speaker/__init__.py +2 -2
- esphome/components/speaker/media_player/__init__.py +2 -2
- esphome/components/speaker/media_player/speaker_media_player.cpp +1 -1
- esphome/components/spi/__init__.py +2 -2
- esphome/components/sprinkler/sprinkler.cpp +1 -1
- esphome/components/sps30/sps30.cpp +18 -23
- esphome/components/sps30/sps30.h +3 -3
- esphome/components/status_led/__init__.py +2 -2
- esphome/components/stepper/__init__.py +2 -2
- esphome/components/switch/__init__.py +2 -2
- esphome/components/switch/switch.cpp +5 -5
- esphome/components/sx1509/__init__.py +1 -1
- esphome/components/sx1509/sx1509.cpp +12 -7
- esphome/components/sx1509/sx1509.h +11 -4
- esphome/components/tca9555/tca9555.cpp +5 -5
- esphome/components/tee501/tee501.cpp +2 -21
- esphome/components/tee501/tee501.h +2 -4
- esphome/components/template/alarm_control_panel/template_alarm_control_panel.cpp +1 -1
- esphome/components/template/datetime/template_date.cpp +1 -1
- esphome/components/template/datetime/template_datetime.cpp +2 -2
- esphome/components/template/datetime/template_time.cpp +1 -1
- esphome/components/template/number/template_number.cpp +1 -1
- esphome/components/template/select/template_select.cpp +1 -1
- esphome/components/template/text/template_text.cpp +1 -1
- esphome/components/text/__init__.py +2 -2
- esphome/components/text/text.h +2 -2
- esphome/components/text_sensor/__init__.py +2 -2
- esphome/components/text_sensor/text_sensor.h +4 -4
- esphome/components/thermostat/climate.py +11 -7
- esphome/components/thermostat/thermostat_climate.cpp +237 -206
- esphome/components/thermostat/thermostat_climate.h +52 -41
- esphome/components/time/__init__.py +2 -2
- esphome/components/tmp1075/tmp1075.cpp +1 -1
- esphome/components/total_daily_energy/total_daily_energy.cpp +1 -1
- esphome/components/touchscreen/__init__.py +2 -2
- esphome/components/tuya/number/tuya_number.cpp +1 -1
- esphome/components/udp/udp_component.cpp +3 -3
- esphome/components/ufire_ec/ufire_ec.cpp +4 -4
- esphome/components/ufire_ise/ufire_ise.cpp +4 -4
- esphome/components/update/__init__.py +2 -2
- esphome/components/usb_uart/usb_uart.cpp +1 -1
- esphome/components/valve/__init__.py +5 -5
- esphome/components/valve/valve.cpp +1 -1
- esphome/components/valve/valve.h +2 -2
- esphome/components/wake_on_lan/wake_on_lan.cpp +2 -2
- esphome/components/waveshare_epaper/waveshare_213v3.cpp +1 -1
- esphome/components/web_server/__init__.py +2 -2
- esphome/components/web_server/ota/__init__.py +2 -2
- esphome/components/web_server/ota/ota_web_server.cpp +11 -0
- esphome/components/web_server/web_server.cpp +58 -12
- esphome/components/web_server_base/__init__.py +2 -2
- esphome/components/wifi/__init__.py +5 -5
- esphome/components/wifi/wifi_component.cpp +3 -3
- esphome/components/wifi/wifi_component_esp_idf.cpp +2 -0
- esphome/config_validation.py +2 -2
- esphome/const.py +2 -1
- esphome/core/__init__.py +1 -0
- esphome/core/application.cpp +89 -51
- esphome/core/application.h +1 -0
- esphome/core/component.cpp +41 -19
- esphome/core/component.h +17 -13
- esphome/core/config.py +7 -7
- esphome/core/defines.h +4 -0
- esphome/core/entity_base.cpp +22 -8
- esphome/core/entity_base.h +43 -0
- esphome/core/helpers.cpp +26 -13
- esphome/core/helpers.h +4 -3
- esphome/core/ring_buffer.cpp +6 -2
- esphome/core/ring_buffer.h +2 -1
- esphome/core/scheduler.cpp +175 -94
- esphome/core/scheduler.h +66 -35
- esphome/core/time.cpp +6 -20
- esphome/coroutine.py +80 -3
- esphome/cpp_generator.py +13 -0
- esphome/cpp_helpers.py +2 -2
- esphome/dashboard/web_server.py +67 -10
- esphome/espota2.py +13 -6
- esphome/helpers.py +68 -83
- esphome/resolver.py +67 -0
- esphome/util.py +9 -6
- esphome/wizard.py +39 -26
- {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/METADATA +9 -9
- {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/RECORD +345 -314
- /esphome/components/ld2420/text_sensor/{text_sensor.h → ld2420_text_sensor.h} +0 -0
- /esphome/components/opentherm/number/{number.h → opentherm_number.h} +0 -0
- /esphome/components/opentherm/output/{output.h → opentherm_output.h} +0 -0
- /esphome/components/opentherm/switch/{switch.h → opentherm_switch.h} +0 -0
- {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/WHEEL +0 -0
- {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/entry_points.txt +0 -0
- {esphome-2025.8.3.dist-info → esphome-2025.9.0b1.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
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
|
163
|
-
void
|
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
|
-
|
187
|
-
|
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
|
-
|
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_(
|
244
|
-
return item
|
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
|
284
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
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) ->
|
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
|
esphome/dashboard/web_server.py
CHANGED
@@ -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
|
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
|
-
|
505
|
-
|
506
|
-
|
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
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
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_(
|
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
|
-
|
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(
|
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
|