esphome 2025.8.4__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/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 +38 -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 +57 -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.4.dist-info → esphome-2025.9.0b1.dist-info}/METADATA +9 -9
- {esphome-2025.8.4.dist-info → esphome-2025.9.0b1.dist-info}/RECORD +344 -313
- /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.4.dist-info → esphome-2025.9.0b1.dist-info}/WHEEL +0 -0
- {esphome-2025.8.4.dist-info → esphome-2025.9.0b1.dist-info}/entry_points.txt +0 -0
- {esphome-2025.8.4.dist-info → esphome-2025.9.0b1.dist-info}/licenses/LICENSE +0 -0
- {esphome-2025.8.4.dist-info → esphome-2025.9.0b1.dist-info}/top_level.txt +0 -0
esphome/core/entity_base.h
CHANGED
@@ -12,6 +12,11 @@
|
|
12
12
|
|
13
13
|
namespace esphome {
|
14
14
|
|
15
|
+
// Forward declaration for friend access
|
16
|
+
namespace api {
|
17
|
+
class APIConnection;
|
18
|
+
} // namespace api
|
19
|
+
|
15
20
|
enum EntityCategory : uint8_t {
|
16
21
|
ENTITY_CATEGORY_NONE = 0,
|
17
22
|
ENTITY_CATEGORY_CONFIG = 1,
|
@@ -80,12 +85,50 @@ class EntityBase {
|
|
80
85
|
// Set has_state - for components that need to manually set this
|
81
86
|
void set_has_state(bool state) { this->flags_.has_state = state; }
|
82
87
|
|
88
|
+
/**
|
89
|
+
* @brief Get a unique hash for storing preferences/settings for this entity.
|
90
|
+
*
|
91
|
+
* This method returns a hash that uniquely identifies the entity for the purpose of
|
92
|
+
* storing preferences (such as calibration, state, etc.). Unlike get_object_id_hash(),
|
93
|
+
* this hash also incorporates the device_id (if devices are enabled), ensuring uniqueness
|
94
|
+
* across multiple devices that may have entities with the same object_id.
|
95
|
+
*
|
96
|
+
* Use this method when storing or retrieving preferences/settings that should be unique
|
97
|
+
* per device-entity pair. Use get_object_id_hash() when you need a hash that identifies
|
98
|
+
* the entity regardless of the device it belongs to.
|
99
|
+
*
|
100
|
+
* For backward compatibility, if device_id is 0 (the main device), the hash is unchanged
|
101
|
+
* from previous versions, so existing single-device configurations will continue to work.
|
102
|
+
*
|
103
|
+
* @return uint32_t The unique hash for preferences, including device_id if available.
|
104
|
+
*/
|
105
|
+
uint32_t get_preference_hash() {
|
106
|
+
#ifdef USE_DEVICES
|
107
|
+
// Combine object_id_hash with device_id to ensure uniqueness across devices
|
108
|
+
// Note: device_id is 0 for the main device, so XORing with 0 preserves the original hash
|
109
|
+
// This ensures backward compatibility for existing single-device configurations
|
110
|
+
return this->get_object_id_hash() ^ this->get_device_id();
|
111
|
+
#else
|
112
|
+
// Without devices, just use object_id_hash as before
|
113
|
+
return this->get_object_id_hash();
|
114
|
+
#endif
|
115
|
+
}
|
116
|
+
|
83
117
|
protected:
|
118
|
+
friend class api::APIConnection;
|
119
|
+
|
120
|
+
// Get object_id as StringRef when it's static (for API usage)
|
121
|
+
// Returns empty StringRef if object_id is dynamic (needs allocation)
|
122
|
+
StringRef get_object_id_ref_for_api_() const;
|
123
|
+
|
84
124
|
/// The hash_base() function has been deprecated. It is kept in this
|
85
125
|
/// class for now, to prevent external components from not compiling.
|
86
126
|
virtual uint32_t hash_base() { return 0L; }
|
87
127
|
void calc_object_id_();
|
88
128
|
|
129
|
+
/// Check if the object_id is dynamic (changes with MAC suffix)
|
130
|
+
bool is_object_id_dynamic_() const;
|
131
|
+
|
89
132
|
StringRef name_;
|
90
133
|
const char *object_id_c_str_{nullptr};
|
91
134
|
#ifdef USE_ENTITY_ICON
|
esphome/core/helpers.cpp
CHANGED
@@ -41,17 +41,28 @@ static const uint16_t CRC16_1021_BE_LUT_H[] = {0x0000, 0x1231, 0x2462, 0x3653, 0
|
|
41
41
|
|
42
42
|
// Mathematics
|
43
43
|
|
44
|
-
uint8_t crc8(const uint8_t *data, uint8_t len) {
|
45
|
-
uint8_t crc = 0;
|
46
|
-
|
44
|
+
uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc, uint8_t poly, bool msb_first) {
|
47
45
|
while ((len--) != 0u) {
|
48
46
|
uint8_t inbyte = *data++;
|
49
|
-
|
50
|
-
|
51
|
-
crc
|
52
|
-
|
53
|
-
crc
|
54
|
-
|
47
|
+
if (msb_first) {
|
48
|
+
// MSB first processing (for polynomials like 0x31, 0x07)
|
49
|
+
crc ^= inbyte;
|
50
|
+
for (uint8_t i = 8; i != 0u; i--) {
|
51
|
+
if (crc & 0x80) {
|
52
|
+
crc = (crc << 1) ^ poly;
|
53
|
+
} else {
|
54
|
+
crc <<= 1;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
} else {
|
58
|
+
// LSB first processing (default for Dallas/Maxim 0x8C)
|
59
|
+
for (uint8_t i = 8; i != 0u; i--) {
|
60
|
+
bool mix = (crc ^ inbyte) & 0x01;
|
61
|
+
crc >>= 1;
|
62
|
+
if (mix)
|
63
|
+
crc ^= poly;
|
64
|
+
inbyte >>= 1;
|
65
|
+
}
|
55
66
|
}
|
56
67
|
}
|
57
68
|
return crc;
|
@@ -131,11 +142,13 @@ uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t poly,
|
|
131
142
|
return refout ? (crc ^ 0xffff) : crc;
|
132
143
|
}
|
133
144
|
|
134
|
-
uint32_t fnv1_hash(const
|
145
|
+
uint32_t fnv1_hash(const char *str) {
|
135
146
|
uint32_t hash = 2166136261UL;
|
136
|
-
|
137
|
-
|
138
|
-
|
147
|
+
if (str) {
|
148
|
+
while (*str) {
|
149
|
+
hash *= 16777619UL;
|
150
|
+
hash ^= *str++;
|
151
|
+
}
|
139
152
|
}
|
140
153
|
return hash;
|
141
154
|
}
|
esphome/core/helpers.h
CHANGED
@@ -145,8 +145,8 @@ template<typename T, typename U> T remap(U value, U min, U max, T min_out, T max
|
|
145
145
|
return (value - min) * (max_out - min_out) / (max - min) + min_out;
|
146
146
|
}
|
147
147
|
|
148
|
-
/// Calculate a CRC-8 checksum of \p data with size \p len
|
149
|
-
uint8_t crc8(const uint8_t *data, uint8_t len);
|
148
|
+
/// Calculate a CRC-8 checksum of \p data with size \p len.
|
149
|
+
uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc = 0x00, uint8_t poly = 0x8C, bool msb_first = false);
|
150
150
|
|
151
151
|
/// Calculate a CRC-16 checksum of \p data with size \p len.
|
152
152
|
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc = 0xffff, uint16_t reverse_poly = 0xa001,
|
@@ -155,7 +155,8 @@ uint16_t crc16be(const uint8_t *data, uint16_t len, uint16_t crc = 0, uint16_t p
|
|
155
155
|
bool refout = false);
|
156
156
|
|
157
157
|
/// Calculate a FNV-1 hash of \p str.
|
158
|
-
uint32_t fnv1_hash(const
|
158
|
+
uint32_t fnv1_hash(const char *str);
|
159
|
+
inline uint32_t fnv1_hash(const std::string &str) { return fnv1_hash(str.c_str()); }
|
159
160
|
|
160
161
|
/// Return a random 32-bit unsigned integer.
|
161
162
|
uint32_t random_uint32();
|
esphome/core/ring_buffer.cpp
CHANGED
@@ -78,9 +78,13 @@ size_t RingBuffer::write(const void *data, size_t len) {
|
|
78
78
|
return this->write_without_replacement(data, len, 0);
|
79
79
|
}
|
80
80
|
|
81
|
-
size_t RingBuffer::write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait
|
81
|
+
size_t RingBuffer::write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait,
|
82
|
+
bool write_partial) {
|
82
83
|
if (!xRingbufferSend(this->handle_, data, len, ticks_to_wait)) {
|
83
|
-
|
84
|
+
if (!write_partial) {
|
85
|
+
return 0; // Not enough space available and not allowed to write partial data
|
86
|
+
}
|
87
|
+
// Couldn't fit all the data, write what will fit
|
84
88
|
size_t free = std::min(this->free(), len);
|
85
89
|
if (xRingbufferSend(this->handle_, data, free, 0)) {
|
86
90
|
return free;
|
esphome/core/ring_buffer.h
CHANGED
@@ -50,7 +50,8 @@ class RingBuffer {
|
|
50
50
|
* @param ticks_to_wait Maximum number of FreeRTOS ticks to wait (default: 0)
|
51
51
|
* @return Number of bytes written
|
52
52
|
*/
|
53
|
-
size_t write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait = 0
|
53
|
+
size_t write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait = 0,
|
54
|
+
bool write_partial = true);
|
54
55
|
|
55
56
|
/**
|
56
57
|
* @brief Returns the number of available bytes in the ring buffer.
|
esphome/core/scheduler.cpp
CHANGED
@@ -14,7 +14,19 @@ namespace esphome {
|
|
14
14
|
|
15
15
|
static const char *const TAG = "scheduler";
|
16
16
|
|
17
|
-
|
17
|
+
// Memory pool configuration constants
|
18
|
+
// Pool size of 5 matches typical usage patterns (2-4 active timers)
|
19
|
+
// - Minimal memory overhead (~250 bytes on ESP32)
|
20
|
+
// - Sufficient for most configs with a couple sensors/components
|
21
|
+
// - Still prevents heap fragmentation and allocation stalls
|
22
|
+
// - Complex setups with many timers will just allocate beyond the pool
|
23
|
+
// See https://github.com/esphome/backlog/issues/52
|
24
|
+
static constexpr size_t MAX_POOL_SIZE = 5;
|
25
|
+
|
26
|
+
// Maximum number of logically deleted (cancelled) items before forcing cleanup.
|
27
|
+
// Set to 5 to match the pool size - when we have as many cancelled items as our
|
28
|
+
// pool can hold, it's time to clean up and recycle them.
|
29
|
+
static constexpr uint32_t MAX_LOGICALLY_DELETED_ITEMS = 5;
|
18
30
|
// Half the 32-bit range - used to detect rollovers vs normal time progression
|
19
31
|
static constexpr uint32_t HALF_MAX_UINT32 = std::numeric_limits<uint32_t>::max() / 2;
|
20
32
|
// max delay to start an interval sequence
|
@@ -79,8 +91,28 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|
79
91
|
return;
|
80
92
|
}
|
81
93
|
|
94
|
+
// Get fresh timestamp BEFORE taking lock - millis_64_ may need to acquire lock itself
|
95
|
+
const uint64_t now = this->millis_64_(millis());
|
96
|
+
|
97
|
+
// Take lock early to protect scheduler_item_pool_ access
|
98
|
+
LockGuard guard{this->lock_};
|
99
|
+
|
82
100
|
// Create and populate the scheduler item
|
83
|
-
|
101
|
+
std::unique_ptr<SchedulerItem> item;
|
102
|
+
if (!this->scheduler_item_pool_.empty()) {
|
103
|
+
// Reuse from pool
|
104
|
+
item = std::move(this->scheduler_item_pool_.back());
|
105
|
+
this->scheduler_item_pool_.pop_back();
|
106
|
+
#ifdef ESPHOME_DEBUG_SCHEDULER
|
107
|
+
ESP_LOGD(TAG, "Reused item from pool (pool size now: %zu)", this->scheduler_item_pool_.size());
|
108
|
+
#endif
|
109
|
+
} else {
|
110
|
+
// Allocate new if pool is empty
|
111
|
+
item = make_unique<SchedulerItem>();
|
112
|
+
#ifdef ESPHOME_DEBUG_SCHEDULER
|
113
|
+
ESP_LOGD(TAG, "Allocated new item (pool empty)");
|
114
|
+
#endif
|
115
|
+
}
|
84
116
|
item->component = component;
|
85
117
|
item->set_name(name_cstr, !is_static_string);
|
86
118
|
item->type = type;
|
@@ -99,7 +131,6 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|
99
131
|
// Single-core platforms don't need thread-safe defer handling
|
100
132
|
if (delay == 0 && type == SchedulerItem::TIMEOUT) {
|
101
133
|
// Put in defer queue for guaranteed FIFO execution
|
102
|
-
LockGuard guard{this->lock_};
|
103
134
|
if (!skip_cancel) {
|
104
135
|
this->cancel_item_locked_(component, name_cstr, type);
|
105
136
|
}
|
@@ -108,21 +139,18 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|
108
139
|
}
|
109
140
|
#endif /* not ESPHOME_THREAD_SINGLE */
|
110
141
|
|
111
|
-
// Get fresh timestamp for new timer/interval - ensures accurate scheduling
|
112
|
-
const auto now = this->millis_64_(millis()); // Fresh millis() call
|
113
|
-
|
114
142
|
// Type-specific setup
|
115
143
|
if (type == SchedulerItem::INTERVAL) {
|
116
144
|
item->interval = delay;
|
117
145
|
// first execution happens immediately after a random smallish offset
|
118
146
|
// Calculate random offset (0 to min(interval/2, 5s))
|
119
147
|
uint32_t offset = (uint32_t) (std::min(delay / 2, MAX_INTERVAL_DELAY) * random_float());
|
120
|
-
item->
|
148
|
+
item->set_next_execution(now + offset);
|
121
149
|
ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms", name_cstr ? name_cstr : "", delay,
|
122
150
|
offset);
|
123
151
|
} else {
|
124
152
|
item->interval = 0;
|
125
|
-
item->
|
153
|
+
item->set_next_execution(now + delay);
|
126
154
|
}
|
127
155
|
|
128
156
|
#ifdef ESPHOME_DEBUG_SCHEDULER
|
@@ -134,16 +162,15 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|
134
162
|
// Debug logging
|
135
163
|
const char *type_str = (type == SchedulerItem::TIMEOUT) ? "timeout" : "interval";
|
136
164
|
if (type == SchedulerItem::TIMEOUT) {
|
137
|
-
ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ")", type_str, item->get_source(),
|
165
|
+
ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ")", type_str, LOG_STR_ARG(item->get_source()),
|
138
166
|
name_cstr ? name_cstr : "(null)", type_str, delay);
|
139
167
|
} else {
|
140
|
-
ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ", offset=%" PRIu32 ")", type_str, item->get_source(),
|
141
|
-
name_cstr ? name_cstr : "(null)", type_str, delay,
|
168
|
+
ESP_LOGD(TAG, "set_%s(name='%s/%s', %s=%" PRIu32 ", offset=%" PRIu32 ")", type_str, LOG_STR_ARG(item->get_source()),
|
169
|
+
name_cstr ? name_cstr : "(null)", type_str, delay,
|
170
|
+
static_cast<uint32_t>(item->get_next_execution() - now));
|
142
171
|
}
|
143
172
|
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
144
173
|
|
145
|
-
LockGuard guard{this->lock_};
|
146
|
-
|
147
174
|
// For retries, check if there's a cancelled timeout first
|
148
175
|
if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT &&
|
149
176
|
(has_cancelled_timeout_in_container_(this->items_, component, name_cstr, /* match_retry= */ true) ||
|
@@ -285,9 +312,10 @@ optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) {
|
|
285
312
|
auto &item = this->items_[0];
|
286
313
|
// Convert the fresh timestamp from caller (usually Application::loop()) to 64-bit
|
287
314
|
const auto now_64 = this->millis_64_(now); // 'now' from parameter - fresh from caller
|
288
|
-
|
315
|
+
const uint64_t next_exec = item->get_next_execution();
|
316
|
+
if (next_exec < now_64)
|
289
317
|
return 0;
|
290
|
-
return
|
318
|
+
return next_exec - now_64;
|
291
319
|
}
|
292
320
|
void HOT Scheduler::call(uint32_t now) {
|
293
321
|
#ifndef ESPHOME_THREAD_SINGLE
|
@@ -319,6 +347,8 @@ void HOT Scheduler::call(uint32_t now) {
|
|
319
347
|
if (!this->should_skip_item_(item.get())) {
|
320
348
|
this->execute_item_(item.get(), now);
|
321
349
|
}
|
350
|
+
// Recycle the defer item after execution
|
351
|
+
this->recycle_item_(std::move(item));
|
322
352
|
}
|
323
353
|
#endif /* not ESPHOME_THREAD_SINGLE */
|
324
354
|
|
@@ -326,6 +356,9 @@ void HOT Scheduler::call(uint32_t now) {
|
|
326
356
|
const auto now_64 = this->millis_64_(now); // 'now' from parameter - fresh from Application::loop()
|
327
357
|
this->process_to_add();
|
328
358
|
|
359
|
+
// Track if any items were added to to_add_ during this call (intervals or from callbacks)
|
360
|
+
bool has_added_items = false;
|
361
|
+
|
329
362
|
#ifdef ESPHOME_DEBUG_SCHEDULER
|
330
363
|
static uint64_t last_print = 0;
|
331
364
|
|
@@ -335,11 +368,11 @@ void HOT Scheduler::call(uint32_t now) {
|
|
335
368
|
#ifdef ESPHOME_THREAD_MULTI_ATOMICS
|
336
369
|
const auto last_dbg = this->last_millis_.load(std::memory_order_relaxed);
|
337
370
|
const auto major_dbg = this->millis_major_.load(std::memory_order_relaxed);
|
338
|
-
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(),
|
339
|
-
major_dbg, last_dbg);
|
371
|
+
ESP_LOGD(TAG, "Items: count=%zu, pool=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(),
|
372
|
+
this->scheduler_item_pool_.size(), now_64, major_dbg, last_dbg);
|
340
373
|
#else /* not ESPHOME_THREAD_MULTI_ATOMICS */
|
341
|
-
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(),
|
342
|
-
this->millis_major_, this->last_millis_);
|
374
|
+
ESP_LOGD(TAG, "Items: count=%zu, pool=%zu, now=%" PRIu64 " (%" PRIu16 ", %" PRIu32 ")", this->items_.size(),
|
375
|
+
this->scheduler_item_pool_.size(), now_64, this->millis_major_, this->last_millis_);
|
343
376
|
#endif /* else ESPHOME_THREAD_MULTI_ATOMICS */
|
344
377
|
// Cleanup before debug output
|
345
378
|
this->cleanup_();
|
@@ -352,9 +385,10 @@ void HOT Scheduler::call(uint32_t now) {
|
|
352
385
|
}
|
353
386
|
|
354
387
|
const char *name = item->get_name();
|
355
|
-
|
356
|
-
|
357
|
-
item->
|
388
|
+
bool is_cancelled = is_item_removed_(item.get());
|
389
|
+
ESP_LOGD(TAG, " %s '%s/%s' interval=%" PRIu32 " next_execution in %" PRIu64 "ms at %" PRIu64 "%s",
|
390
|
+
item->get_type_str(), LOG_STR_ARG(item->get_source()), name ? name : "(null)", item->interval,
|
391
|
+
item->get_next_execution() - now_64, item->get_next_execution(), is_cancelled ? " [CANCELLED]" : "");
|
358
392
|
|
359
393
|
old_items.push_back(std::move(item));
|
360
394
|
}
|
@@ -369,8 +403,13 @@ void HOT Scheduler::call(uint32_t now) {
|
|
369
403
|
}
|
370
404
|
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
371
405
|
|
372
|
-
//
|
373
|
-
|
406
|
+
// Cleanup removed items before processing
|
407
|
+
// First try to clean items from the top of the heap (fast path)
|
408
|
+
this->cleanup_();
|
409
|
+
|
410
|
+
// If we still have too many cancelled items, do a full cleanup
|
411
|
+
// This only happens if cancelled items are stuck in the middle/bottom of the heap
|
412
|
+
if (this->to_remove_ >= MAX_LOGICALLY_DELETED_ITEMS) {
|
374
413
|
// We hold the lock for the entire cleanup operation because:
|
375
414
|
// 1. We're rebuilding the entire items_ list, so we need exclusive access throughout
|
376
415
|
// 2. Other threads must see either the old state or the new state, not intermediate states
|
@@ -380,10 +419,13 @@ void HOT Scheduler::call(uint32_t now) {
|
|
380
419
|
|
381
420
|
std::vector<std::unique_ptr<SchedulerItem>> valid_items;
|
382
421
|
|
383
|
-
// Move all non-removed items to valid_items
|
422
|
+
// Move all non-removed items to valid_items, recycle removed ones
|
384
423
|
for (auto &item : this->items_) {
|
385
|
-
if (!item
|
424
|
+
if (!is_item_removed_(item.get())) {
|
386
425
|
valid_items.push_back(std::move(item));
|
426
|
+
} else {
|
427
|
+
// Recycle removed items
|
428
|
+
this->recycle_item_(std::move(item));
|
387
429
|
}
|
388
430
|
}
|
389
431
|
|
@@ -393,92 +435,92 @@ void HOT Scheduler::call(uint32_t now) {
|
|
393
435
|
std::make_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
|
394
436
|
this->to_remove_ = 0;
|
395
437
|
}
|
396
|
-
|
397
|
-
// Cleanup removed items before processing
|
398
|
-
this->cleanup_();
|
399
438
|
while (!this->items_.empty()) {
|
400
|
-
//
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
continue;
|
413
|
-
}
|
439
|
+
// Don't copy-by value yet
|
440
|
+
auto &item = this->items_[0];
|
441
|
+
if (item->get_next_execution() > now_64) {
|
442
|
+
// Not reached timeout yet, done for this call
|
443
|
+
break;
|
444
|
+
}
|
445
|
+
// Don't run on failed components
|
446
|
+
if (item->component != nullptr && item->component->is_failed()) {
|
447
|
+
LockGuard guard{this->lock_};
|
448
|
+
this->pop_raw_();
|
449
|
+
continue;
|
450
|
+
}
|
414
451
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
452
|
+
// Check if item is marked for removal
|
453
|
+
// This handles two cases:
|
454
|
+
// 1. Item was marked for removal after cleanup_() but before we got here
|
455
|
+
// 2. Item is marked for removal but wasn't at the front of the heap during cleanup_()
|
419
456
|
#ifdef ESPHOME_THREAD_MULTI_NO_ATOMICS
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
if (is_item_removed_(item.get())) {
|
424
|
-
this->pop_raw_();
|
425
|
-
this->to_remove_--;
|
426
|
-
continue;
|
427
|
-
}
|
428
|
-
}
|
429
|
-
#else
|
430
|
-
// Single-threaded or multi-threaded with atomics: can check without lock
|
457
|
+
// Multi-threaded platforms without atomics: must take lock to safely read remove flag
|
458
|
+
{
|
459
|
+
LockGuard guard{this->lock_};
|
431
460
|
if (is_item_removed_(item.get())) {
|
432
|
-
LockGuard guard{this->lock_};
|
433
461
|
this->pop_raw_();
|
434
462
|
this->to_remove_--;
|
435
463
|
continue;
|
436
464
|
}
|
465
|
+
}
|
466
|
+
#else
|
467
|
+
// Single-threaded or multi-threaded with atomics: can check without lock
|
468
|
+
if (is_item_removed_(item.get())) {
|
469
|
+
LockGuard guard{this->lock_};
|
470
|
+
this->pop_raw_();
|
471
|
+
this->to_remove_--;
|
472
|
+
continue;
|
473
|
+
}
|
437
474
|
#endif
|
438
475
|
|
439
476
|
#ifdef ESPHOME_DEBUG_SCHEDULER
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
477
|
+
const char *item_name = item->get_name();
|
478
|
+
ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")",
|
479
|
+
item->get_type_str(), LOG_STR_ARG(item->get_source()), item_name ? item_name : "(null)", item->interval,
|
480
|
+
item->get_next_execution(), now_64);
|
444
481
|
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
445
482
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
}
|
483
|
+
// Warning: During callback(), a lot of stuff can happen, including:
|
484
|
+
// - timeouts/intervals get added, potentially invalidating vector pointers
|
485
|
+
// - timeouts/intervals get cancelled
|
486
|
+
this->execute_item_(item.get(), now);
|
451
487
|
|
452
|
-
{
|
453
|
-
LockGuard guard{this->lock_};
|
488
|
+
LockGuard guard{this->lock_};
|
454
489
|
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
this->pop_raw_();
|
490
|
+
auto executed_item = std::move(this->items_[0]);
|
491
|
+
// Only pop after function call, this ensures we were reachable
|
492
|
+
// during the function call and know if we were cancelled.
|
493
|
+
this->pop_raw_();
|
460
494
|
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
495
|
+
if (executed_item->remove) {
|
496
|
+
// We were removed/cancelled in the function call, stop
|
497
|
+
this->to_remove_--;
|
498
|
+
continue;
|
499
|
+
}
|
466
500
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
501
|
+
if (executed_item->type == SchedulerItem::INTERVAL) {
|
502
|
+
executed_item->set_next_execution(now_64 + executed_item->interval);
|
503
|
+
// Add new item directly to to_add_
|
504
|
+
// since we have the lock held
|
505
|
+
this->to_add_.push_back(std::move(executed_item));
|
506
|
+
} else {
|
507
|
+
// Timeout completed - recycle it
|
508
|
+
this->recycle_item_(std::move(executed_item));
|
473
509
|
}
|
510
|
+
|
511
|
+
has_added_items |= !this->to_add_.empty();
|
474
512
|
}
|
475
513
|
|
476
|
-
|
514
|
+
if (has_added_items) {
|
515
|
+
this->process_to_add();
|
516
|
+
}
|
477
517
|
}
|
478
518
|
void HOT Scheduler::process_to_add() {
|
479
519
|
LockGuard guard{this->lock_};
|
480
520
|
for (auto &it : this->to_add_) {
|
481
|
-
if (it
|
521
|
+
if (is_item_removed_(it.get())) {
|
522
|
+
// Recycle cancelled items
|
523
|
+
this->recycle_item_(std::move(it));
|
482
524
|
continue;
|
483
525
|
}
|
484
526
|
|
@@ -518,6 +560,10 @@ size_t HOT Scheduler::cleanup_() {
|
|
518
560
|
}
|
519
561
|
void HOT Scheduler::pop_raw_() {
|
520
562
|
std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
|
563
|
+
|
564
|
+
// Instead of destroying, recycle the item
|
565
|
+
this->recycle_item_(std::move(this->items_.back()));
|
566
|
+
|
521
567
|
this->items_.pop_back();
|
522
568
|
}
|
523
569
|
|
@@ -552,7 +598,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|
552
598
|
|
553
599
|
// Check all containers for matching items
|
554
600
|
#ifndef ESPHOME_THREAD_SINGLE
|
555
|
-
//
|
601
|
+
// Mark items in defer queue as cancelled (they'll be skipped when processed)
|
556
602
|
if (type == SchedulerItem::TIMEOUT) {
|
557
603
|
for (auto &item : this->defer_queue_) {
|
558
604
|
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
@@ -564,11 +610,22 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|
564
610
|
#endif /* not ESPHOME_THREAD_SINGLE */
|
565
611
|
|
566
612
|
// Cancel items in the main heap
|
567
|
-
|
568
|
-
|
569
|
-
|
613
|
+
// Special case: if the last item in the heap matches, we can remove it immediately
|
614
|
+
// (removing the last element doesn't break heap structure)
|
615
|
+
if (!this->items_.empty()) {
|
616
|
+
auto &last_item = this->items_.back();
|
617
|
+
if (this->matches_item_(last_item, component, name_cstr, type, match_retry)) {
|
618
|
+
this->recycle_item_(std::move(this->items_.back()));
|
619
|
+
this->items_.pop_back();
|
570
620
|
total_cancelled++;
|
571
|
-
|
621
|
+
}
|
622
|
+
// For other items in heap, we can only mark for removal (can't remove from middle of heap)
|
623
|
+
for (auto &item : this->items_) {
|
624
|
+
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
625
|
+
this->mark_item_removed_(item.get());
|
626
|
+
total_cancelled++;
|
627
|
+
this->to_remove_++; // Track removals for heap items
|
628
|
+
}
|
572
629
|
}
|
573
630
|
}
|
574
631
|
|
@@ -744,7 +801,31 @@ uint64_t Scheduler::millis_64_(uint32_t now) {
|
|
744
801
|
|
745
802
|
bool HOT Scheduler::SchedulerItem::cmp(const std::unique_ptr<SchedulerItem> &a,
|
746
803
|
const std::unique_ptr<SchedulerItem> &b) {
|
747
|
-
|
804
|
+
// High bits are almost always equal (change only on 32-bit rollover ~49 days)
|
805
|
+
// Optimize for common case: check low bits first when high bits are equal
|
806
|
+
return (a->next_execution_high_ == b->next_execution_high_) ? (a->next_execution_low_ > b->next_execution_low_)
|
807
|
+
: (a->next_execution_high_ > b->next_execution_high_);
|
808
|
+
}
|
809
|
+
|
810
|
+
void Scheduler::recycle_item_(std::unique_ptr<SchedulerItem> item) {
|
811
|
+
if (!item)
|
812
|
+
return;
|
813
|
+
|
814
|
+
if (this->scheduler_item_pool_.size() < MAX_POOL_SIZE) {
|
815
|
+
// Clear callback to release captured resources
|
816
|
+
item->callback = nullptr;
|
817
|
+
// Clear dynamic name if any
|
818
|
+
item->clear_dynamic_name();
|
819
|
+
this->scheduler_item_pool_.push_back(std::move(item));
|
820
|
+
#ifdef ESPHOME_DEBUG_SCHEDULER
|
821
|
+
ESP_LOGD(TAG, "Recycled item to pool (pool size now: %zu)", this->scheduler_item_pool_.size());
|
822
|
+
#endif
|
823
|
+
} else {
|
824
|
+
#ifdef ESPHOME_DEBUG_SCHEDULER
|
825
|
+
ESP_LOGD(TAG, "Pool full (size: %zu), deleting item", this->scheduler_item_pool_.size());
|
826
|
+
#endif
|
827
|
+
}
|
828
|
+
// else: unique_ptr will delete the item when it goes out of scope
|
748
829
|
}
|
749
830
|
|
750
831
|
} // namespace esphome
|