<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>ESPHome Developer Documentation | Blog</title><description>Documentation for developers contributing to and building components for ESPHome.</description><link>https://developers.esphome.io/</link><language>en</language><item><title>ClimateTraits Boolean Accessors Removed in Favor of Feature Flags</title><link>https://developers.esphome.io/blog/2026/05/14/climatetraits-boolean-accessors-removed-in-favor-of-feature-flags/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/05/14/climatetraits-boolean-accessors-removed-in-favor-of-feature-flags/</guid><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The ten deprecated &lt;code dir=&quot;auto&quot;&gt;ClimateTraits::get/set_supports_*&lt;/code&gt; accessors have been removed. External climate components must migrate to the &lt;code dir=&quot;auto&quot;&gt;add_feature_flags()&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;has_feature_flags()&lt;/code&gt; API introduced in 2025.11.0 — the 6-month deprecation window has elapsed.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.5.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/16289&quot;&gt;PR #16289&lt;/a&gt;: Remove deprecations scheduled for 2026.5.0&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In 2025.11.0 the old boolean getter/setter pairs were superseded by an &lt;code dir=&quot;auto&quot;&gt;add_feature_flags()&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;has_feature_flags()&lt;/code&gt; bitmask API on &lt;code dir=&quot;auto&quot;&gt;ClimateTraits&lt;/code&gt;. The booleans were kept as thin shims marked &lt;code dir=&quot;auto&quot;&gt;ESPDEPRECATED(&quot;...&quot;, &quot;2025.11.0&quot;)&lt;/code&gt; to give external components a 6-month window. That window has now closed and the shims are gone. (The same release also migrated other &lt;code dir=&quot;auto&quot;&gt;ClimateTraits&lt;/code&gt; enum sets to packed-bitmask storage in a separate change; see the &lt;a href=&quot;https://developers.esphome.io/blog/2025/11/07/climate-entity-class-finitesetmask-and-flash-storage-optimizations/&quot;&gt;climate entity class optimizations&lt;/a&gt; post for that adjacent story.)&lt;/p&gt;
&lt;p&gt;The replacement API has been the canonical way to express climate capabilities since 2025.11.0, and every in-tree climate component (heat-pump IR codecs, mini-split bridges, thermostat platform, etc.) has been on it for months.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The following ten methods have been removed from &lt;code dir=&quot;auto&quot;&gt;ClimateTraits&lt;/code&gt;:&lt;/p&gt;

















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Removed method&lt;/th&gt;&lt;th&gt;Replacement flag&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;set_supports_current_temperature(bool)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_supports_current_temperature()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;set_supports_current_humidity(bool)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;add_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_supports_current_humidity()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;set_supports_two_point_target_temperature(bool)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;add_feature_flags(climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_supports_two_point_target_temperature()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;has_feature_flags(climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;set_supports_target_humidity(bool)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;add_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_supports_target_humidity()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;set_supports_action(bool)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;add_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_supports_action()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;watch-for-two_point_target_temperature&quot;&gt;Watch for &lt;code dir=&quot;auto&quot;&gt;two_point_target_temperature&lt;/code&gt;&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;set_supports_two_point_target_temperature(true)&lt;/code&gt; maps to &lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE&lt;/code&gt;&lt;/strong&gt;, not &lt;code dir=&quot;auto&quot;&gt;CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE&lt;/code&gt;. The bitmask flag name reflects the actual semantic — when set, the climate device &lt;em&gt;requires&lt;/em&gt; both &lt;code dir=&quot;auto&quot;&gt;target_temperature_low&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;target_temperature_high&lt;/code&gt; to be specified instead of a single &lt;code dir=&quot;auto&quot;&gt;target_temperature&lt;/code&gt;. If your old code called &lt;code dir=&quot;auto&quot;&gt;set_supports_two_point_target_temperature(true)&lt;/code&gt;, this is the flag you want — but double-check that your device actually requires both setpoints (rare for single-setpoint heat-pump and IR-controlled units) rather than just optionally supporting them, since the new name reflects the stricter semantic.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External climate components in C++&lt;/strong&gt; that still call the old accessors. A GitHub code search for the removed identifiers across public repositories turned up ~20+ such components — heat-pump IR drivers, mini-split bridges, brand-specific climate integrations (Haier, Daikin IR, Ecodan, Sanyou, Sharp, Mill Heat, …).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;YAML configurations are not affected.&lt;/strong&gt; Built-in climate platforms migrated in 2025.11.0 and any user-facing config keys are unchanged.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before (no longer compiles)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ClimateTraits traits;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set_supports_current_temperature&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set_supports_action&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set_supports_two_point_target_temperature&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_supports_action&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ClimateTraits traits;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_feature_flags&lt;/span&gt;&lt;span&gt;(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_feature_flags&lt;/span&gt;&lt;span&gt;(climate::CLIMATE_SUPPORTS_ACTION);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_feature_flags&lt;/span&gt;&lt;span&gt;(climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;has_feature_flags&lt;/span&gt;&lt;span&gt;(climate::CLIMATE_SUPPORTS_ACTION)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;add_feature_flags()&lt;/code&gt; accepts a bitwise OR of multiple flags, so a long sequence of individual calls can be condensed:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_feature_flags&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;climate::CLIMATE_SUPPORTS_ACTION);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;has_feature_flags()&lt;/code&gt; is a bitwise-AND check: it returns true when &lt;strong&gt;any&lt;/strong&gt; of the bits in the argument are set on the traits. For most capability checks you’ll be testing one flag at a time, which behaves exactly like the old &lt;code dir=&quot;auto&quot;&gt;get_supports_*()&lt;/code&gt; boolean — pass a single flag. If you pass several OR’d together, the result is “is at least one of these supported,” not “are all of these supported.”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2025.11.0:&lt;/strong&gt; Feature-flag API added; boolean accessors marked &lt;code dir=&quot;auto&quot;&gt;ESPDEPRECATED&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2026.5.0:&lt;/strong&gt; Boolean accessors removed.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find any of the ten removed boolean accessors in one pass.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# The two alternation groups isolate the accessor side (set/get) and the trait name,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# so the output line tells you exactly which one to migrate.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rEn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;(set|get)_supports_(current_temperature|current_humidity|two_point_target_temperature|target_humidity|action)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/16289&quot;&gt;PR #16289&lt;/a&gt; — Remove climate / ektf2232 deprecations&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.esphome.io/blog/2025/11/07/climate-entity-class-finitesetmask-and-flash-storage-optimizations/&quot;&gt;2025.11.0 Climate optimizations blog post&lt;/a&gt; — the deprecation that started the clock&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.esphome.io/blog/2026/04/09/climate-and-fan-custom-mode-vectors-moved-to-entity/&quot;&gt;2026.4.0 Climate fan custom mode storage blog post&lt;/a&gt; — related climate-API tightening&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Default-Constructed PollingComponent No Longer Polls</title><link>https://developers.esphome.io/blog/2026/05/14/default-constructed-pollingcomponent-no-longer-polls/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/05/14/default-constructed-pollingcomponent-no-longer-polls/</guid><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The no-argument &lt;code dir=&quot;auto&quot;&gt;PollingComponent()&lt;/code&gt; constructor now initializes the update interval to &lt;code dir=&quot;auto&quot;&gt;SCHEDULER_DONT_RUN&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;UINT32_MAX&lt;/code&gt;) instead of &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt;. External components that subclass &lt;code dir=&quot;auto&quot;&gt;PollingComponent&lt;/code&gt;, instantiate it with no constructor argument, and never call &lt;code dir=&quot;auto&quot;&gt;set_update_interval()&lt;/code&gt; will stop polling. Pass an interval to the constructor or call &lt;code dir=&quot;auto&quot;&gt;set_update_interval()&lt;/code&gt; explicitly.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.5.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15832&quot;&gt;PR #15832&lt;/a&gt;: Default PollingComponent() to not run when codegen is bypassed&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Codegen-driven components always pass the user’s &lt;code dir=&quot;auto&quot;&gt;update_interval:&lt;/code&gt; through to the constructor (or call &lt;code dir=&quot;auto&quot;&gt;set_update_interval()&lt;/code&gt; explicitly), so they were never affected by the default value. The default only mattered when an external component bypassed codegen — wrote &lt;code dir=&quot;auto&quot;&gt;PollingComponent()&lt;/code&gt; directly in C++ and never set an interval.&lt;/p&gt;
&lt;p&gt;The old default of &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt; was a workaround. Before the recent scheduler rework (#15516), the inefficient scheduler informally throttled &lt;code dir=&quot;auto&quot;&gt;interval=0&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;interval=1&lt;/code&gt; into something close to plain &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; behavior, so components that forgot to set an interval got “some” polling. After the scheduler became efficient and #15799 / #15831 hardened the runtime coercion path, an explicit &lt;code dir=&quot;auto&quot;&gt;1&lt;/code&gt; ms interval would have meant &lt;strong&gt;1000 polls per second&lt;/strong&gt; — starving the watchdog and masking any real symptoms.&lt;/p&gt;
&lt;p&gt;No author who forgets to set an interval actually wants 1000 polls per second. The correct behavior when an interval is genuinely missing is to &lt;strong&gt;fail loudly&lt;/strong&gt; — don’t poll at all, log &lt;code dir=&quot;auto&quot;&gt;Update Interval: never&lt;/code&gt;, and let the developer notice.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;PollingComponent&lt;/span&gt;&lt;span&gt;() : &lt;/span&gt;&lt;span&gt;update_interval_&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// → polled every 1 ms when never configured&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;PollingComponent&lt;/span&gt;&lt;span&gt;() : &lt;/span&gt;&lt;span&gt;update_interval_&lt;/span&gt;&lt;span&gt;(SCHEDULER_DONT_RUN) {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// → does not poll until set_update_interval() is called&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;SCHEDULER_DONT_RUN&lt;/code&gt; is &lt;code dir=&quot;auto&quot;&gt;UINT32_MAX&lt;/code&gt;. The scheduler already treats this value as disabled, and &lt;code dir=&quot;auto&quot;&gt;LOG_UPDATE_INTERVAL&lt;/code&gt; already prints &lt;code dir=&quot;auto&quot;&gt;Update Interval: never&lt;/code&gt; for this value — so the &lt;code dir=&quot;auto&quot;&gt;dump_config&lt;/code&gt; output is also self-describing. See &lt;a href=&quot;https://github.com/esphome/esphome/pull/15832&quot;&gt;PR #15832&lt;/a&gt; for the full picture of how the scheduler and &lt;code dir=&quot;auto&quot;&gt;Component&lt;/code&gt; handle this sentinel.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;External components written in C++ that meet &lt;strong&gt;all three&lt;/strong&gt; of these conditions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Inherit from &lt;code dir=&quot;auto&quot;&gt;PollingComponent&lt;/code&gt; in C++&lt;/li&gt;
&lt;li&gt;Construct the instance via the no-argument &lt;code dir=&quot;auto&quot;&gt;PollingComponent()&lt;/code&gt; constructor (not &lt;code dir=&quot;auto&quot;&gt;PollingComponent(interval)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Never call &lt;code dir=&quot;auto&quot;&gt;set_update_interval()&lt;/code&gt; afterwards&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Components driven by ESPHome’s Python code generation (&lt;code dir=&quot;auto&quot;&gt;cv.polling_component_schema(...)&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;sensor.sensor_schema()&lt;/code&gt;, etc.) are unaffected — the codegen always passes the configured (or schema-default) interval through to the constructor, so the default value never matters for them.&lt;/p&gt;
&lt;p&gt;A known example is &lt;a href=&quot;https://github.com/psvanstrom/esphome-p1reader/pull/110&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;psvanstrom/esphome-p1reader&lt;/code&gt;&lt;/a&gt;, which independently switched to &lt;code dir=&quot;auto&quot;&gt;PollingComponent(10)&lt;/code&gt; ahead of this change.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Pick whichever fits your component:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Option 1: pass the interval to the constructor (recommended for fixed-interval components)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; PollingComponent {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;MyComponent&lt;/span&gt;&lt;span&gt;() : &lt;/span&gt;&lt;span&gt;PollingComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;60000&lt;/span&gt;&lt;span&gt;) {}&lt;/span&gt;&lt;span&gt;  // 60 s&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Option 2: keep the no-arg constructor and let codegen set the interval before registration.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; PollingComponent {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;       // your periodic work&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dump_config&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// (no need to override setup() or pass an interval to the constructor —&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// the Python codegen below calls set_update_interval() for you)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Python side — use polling_component_schema; cg.register_component() handles set_update_interval&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# automatically when CONF_UPDATE_INTERVAL is in config.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;CONFIG_SCHEMA&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv.&lt;/span&gt;&lt;span&gt;Schema&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cv.&lt;/span&gt;&lt;span&gt;GenerateID&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;: cv.&lt;/span&gt;&lt;span&gt;declare_id&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;MyComponent&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;extend&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cv.&lt;/span&gt;&lt;span&gt;polling_component_schema&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;60s&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to_code&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;var &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cg.&lt;/span&gt;&lt;span&gt;new_Pvariable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;CONF_ID&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; cg.&lt;/span&gt;&lt;span&gt;register_component&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt;)   &lt;/span&gt;&lt;span&gt;# ← this sets the update interval for you&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cg.register_component()&lt;/code&gt; (in &lt;code dir=&quot;auto&quot;&gt;esphome/cpp_helpers.py&lt;/code&gt;) calls &lt;code dir=&quot;auto&quot;&gt;set_update_interval(config[CONF_UPDATE_INTERVAL])&lt;/code&gt; before it registers the variable with &lt;code dir=&quot;auto&quot;&gt;App&lt;/code&gt;, so by the time &lt;code dir=&quot;auto&quot;&gt;App::register_component()&lt;/code&gt; triggers &lt;code dir=&quot;auto&quot;&gt;call_setup()&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;start_poller()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;update_interval_&lt;/code&gt; already holds the user’s configured value.&lt;/p&gt;
&lt;div dir=&quot;auto&quot;&gt;
&lt;p dir=&quot;auto&quot;&gt;&lt;svg viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;WARNING&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don’t call &lt;code dir=&quot;auto&quot;&gt;set_update_interval()&lt;/code&gt; from inside &lt;code dir=&quot;auto&quot;&gt;setup()&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;PollingComponent::call_setup()&lt;/code&gt; runs &lt;code dir=&quot;auto&quot;&gt;start_poller()&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; the subclass’s &lt;code dir=&quot;auto&quot;&gt;setup()&lt;/code&gt; — see &lt;a href=&quot;https://github.com/esphome/esphome/blob/dev/esphome/core/component.cpp&quot;&gt;the
implementation&lt;/a&gt; (search for
&lt;code dir=&quot;auto&quot;&gt;PollingComponent::call_setup&lt;/code&gt;). By the time &lt;code dir=&quot;auto&quot;&gt;setup()&lt;/code&gt; executes, the scheduler interval has already been
registered with whatever &lt;code dir=&quot;auto&quot;&gt;update_interval_&lt;/code&gt; held at registration time (&lt;code dir=&quot;auto&quot;&gt;SCHEDULER_DONT_RUN&lt;/code&gt; if the default
constructor was used). Calling &lt;code dir=&quot;auto&quot;&gt;set_update_interval()&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;setup()&lt;/code&gt; only updates the field — it does &lt;strong&gt;not&lt;/strong&gt;
re-register the interval, so polling stays disabled.&lt;/p&gt;
&lt;p&gt;The header comment for &lt;code dir=&quot;auto&quot;&gt;call_setup&lt;/code&gt; explicitly says the poller starts first “allowing setup to cancel it if
desired.” So if you really need a runtime decision inside &lt;code dir=&quot;auto&quot;&gt;setup()&lt;/code&gt; to change the cadence, the supported
pattern is &lt;code dir=&quot;auto&quot;&gt;stop_poller()&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;set_update_interval(ms)&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;start_poller()&lt;/code&gt;. Setting the interval before
registration (Option 2 above, or via the constructor in Option 1) is much cleaner.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Option 3: if you want the component to behave like a plain Component (no polling),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// inherit from Component instead — that&apos;s the new explicit way to express &quot;no polling.&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you see &lt;code dir=&quot;auto&quot;&gt;Update Interval: never&lt;/code&gt; in your device’s startup log and the component used to poll, you’re hitting this case — pick option 1 or 2.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find subclasses of PollingComponent (also catches forward declarations and&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# struct/class variations on the same line).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rEn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;class .* PollingComponent|struct .* PollingComponent|: *public *PollingComponent&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find no-arg PollingComponent constructor calls (likely affected)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rEn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;PollingComponent\(\)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find whether set_update_interval is ever called&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;set_update_interval&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If your component has &lt;code dir=&quot;auto&quot;&gt;PollingComponent()&lt;/code&gt; (no argument) and no &lt;code dir=&quot;auto&quot;&gt;set_update_interval()&lt;/code&gt; call anywhere, you’ll need to add one of the three options above before 2026.5.0.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15832&quot;&gt;PR #15832&lt;/a&gt; — Default PollingComponent() to not run when codegen is bypassed&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15831&quot;&gt;PR #15831&lt;/a&gt; — Prerequisite: encode the old runtime coerce as a compile-time no-op&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15799&quot;&gt;PR #15799&lt;/a&gt; — Earlier scheduler hardening that made the old default actually mean 1 ms&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>FloatOutput Power Scaling Fields Gated Behind USE_OUTPUT_FLOAT_POWER_SCALING</title><link>https://developers.esphome.io/blog/2026/05/14/floatoutput-power-scaling-fields-gated-behind-use_output_float_power_scaling/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/05/14/floatoutput-power-scaling-fields-gated-behind-use_output_float_power_scaling/</guid><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;min_power_&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;max_power_&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;zero_means_zero_&lt;/code&gt; fields and their setters on &lt;code dir=&quot;auto&quot;&gt;FloatOutput&lt;/code&gt; are now gated behind a new &lt;code dir=&quot;auto&quot;&gt;USE_OUTPUT_FLOAT_POWER_SCALING&lt;/code&gt; build flag. The codegen turns the flag on automatically whenever a YAML config references the feature. Lambdas that call &lt;code dir=&quot;auto&quot;&gt;id(out).set_min_power(...)&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;set_max_power(...)&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;set_zero_means_zero(...)&lt;/code&gt; without any matching YAML key or action will now fail to compile with a clear &lt;code dir=&quot;auto&quot;&gt;static_assert&lt;/code&gt; pointing at the one-line YAML fix.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components and YAML configs that drive power scaling exclusively from lambdas in &lt;strong&gt;ESPHome 2026.5.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15998&quot;&gt;PR #15998&lt;/a&gt;: Gate FloatOutput power scaling fields behind USE_OUTPUT_FLOAT_POWER_SCALING&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;min_power_&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;max_power_&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;zero_means_zero_&lt;/code&gt; cost &lt;strong&gt;12 bytes per instance&lt;/strong&gt; on every PWM channel, DAC channel, LEDC output, and dimmer-chip channel — paid by every device with any &lt;code dir=&quot;auto&quot;&gt;output:&lt;/code&gt; block, regardless of whether the user touches the feature. Inside &lt;code dir=&quot;auto&quot;&gt;esphome/esphome&lt;/code&gt;, total YAML usage of &lt;code dir=&quot;auto&quot;&gt;min_power&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;max_power&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;zero_means_zero&lt;/code&gt; is on the order of 17 lines, mostly in test fixtures. The &lt;code dir=&quot;auto&quot;&gt;output.set_min_power&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;output.set_max_power&lt;/code&gt; runtime actions (added in #8934) have no callers outside the action’s own test fixture.&lt;/p&gt;
&lt;p&gt;This follows the same pattern the entity base classes already use — &lt;code dir=&quot;auto&quot;&gt;USE_POWER_SUPPLY&lt;/code&gt; gates &lt;code dir=&quot;auto&quot;&gt;BinaryOutput::power_&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;USE_ENTITY_ICON&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;USE_ENTITY_DEVICE_CLASS&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;USE_ENTITY_UNIT_OF_MEASUREMENT&lt;/code&gt; gate string members of &lt;code dir=&quot;auto&quot;&gt;EntityBase&lt;/code&gt;. Configs that opt in pay the cost; configs that don’t get it stripped.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;measured-savings&quot;&gt;Measured savings&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Built with the unmodified example configs from devices.esphome.io for two real ESP8266 PWM lights — neither uses &lt;code dir=&quot;auto&quot;&gt;min_power&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;max_power&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;zero_means_zero&lt;/code&gt; / scaling actions:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://devices.esphome.io/devices/H801-RGBW-LED-Controller/&quot;&gt;H801 RGBW LED Controller&lt;/a&gt;&lt;/strong&gt; — 5 PWM outputs (RGB + cold-white + warm-white, the “RGBWW” &lt;code dir=&quot;auto&quot;&gt;light:&lt;/code&gt; platform):&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;Baseline&lt;/th&gt;&lt;th&gt;PR&lt;/th&gt;&lt;th&gt;Δ&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;RAM (.bss + .data)&lt;/td&gt;&lt;td&gt;30,416 B&lt;/td&gt;&lt;td&gt;30,352 B&lt;/td&gt;&lt;td&gt;&lt;strong&gt;-64 B&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Flash&lt;/td&gt;&lt;td&gt;346,047 B&lt;/td&gt;&lt;td&gt;345,799 B&lt;/td&gt;&lt;td&gt;&lt;strong&gt;-248 B&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;sizeof(ESP8266PWM)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;40 B&lt;/td&gt;&lt;td&gt;28 B&lt;/td&gt;&lt;td&gt;&lt;strong&gt;-12 B&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;H802 RGBW LED Controller&lt;/strong&gt; — 4 PWM outputs: -48 B RAM, -248 B flash, same 12 B per channel.&lt;/p&gt;
&lt;p&gt;Per-instance .bss savings scale linearly with channel count (12 B × N). Flash savings (~248 B per binary) come from the elided multiply/subtract in &lt;code dir=&quot;auto&quot;&gt;set_level()&lt;/code&gt; and the dropped scaling-related strings/branches in &lt;code dir=&quot;auto&quot;&gt;dump_config()&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;USE_OUTPUT_FLOAT_POWER_SCALING&lt;/code&gt; is a new &lt;code dir=&quot;auto&quot;&gt;cg.add_define()&lt;/code&gt; flag that controls four things at compile time on &lt;code dir=&quot;auto&quot;&gt;FloatOutput&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;min_power_&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;max_power_&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;zero_means_zero_&lt;/code&gt; member fields&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;set_min_power()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;set_max_power()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;set_zero_means_zero()&lt;/code&gt; setters&lt;/li&gt;
&lt;li&gt;The min/max/zero-means-zero math inside &lt;code dir=&quot;auto&quot;&gt;FloatOutput::set_level()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The &lt;code dir=&quot;auto&quot;&gt;SetMinPowerAction&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;SetMaxPowerAction&lt;/code&gt; action templates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Python codegen turns the flag on &lt;strong&gt;automatically&lt;/strong&gt; whenever:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code dir=&quot;auto&quot;&gt;min_power:&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;max_power:&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;zero_means_zero:&lt;/code&gt; key is set on &lt;strong&gt;any&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;output:&lt;/code&gt; entry, &lt;strong&gt;or&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;An &lt;code dir=&quot;auto&quot;&gt;output.set_min_power&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;output.set_max_power&lt;/code&gt; action is registered anywhere in the config.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;zero_means_zero_&lt;/code&gt; now has a default initializer (it was undefined before, hidden behind an unconditional setter call driven by the schema default).&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;You are affected only if all three of these are true:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You do &lt;strong&gt;not&lt;/strong&gt; set &lt;code dir=&quot;auto&quot;&gt;min_power&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;max_power&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;zero_means_zero&lt;/code&gt; on any of your outputs in YAML, &lt;strong&gt;and&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;You do &lt;strong&gt;not&lt;/strong&gt; use the &lt;code dir=&quot;auto&quot;&gt;output.set_min_power&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;output.set_max_power&lt;/code&gt; automation actions anywhere, &lt;strong&gt;and&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;You &lt;strong&gt;do&lt;/strong&gt; call &lt;code dir=&quot;auto&quot;&gt;id(...).set_min_power(...)&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;id(...).set_max_power(...)&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;id(...).set_zero_means_zero(...)&lt;/code&gt; directly from a &lt;code dir=&quot;auto&quot;&gt;lambda:&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If any of those is false, your config is unaffected and behaves exactly as before.&lt;/p&gt;
&lt;p&gt;External hardware-driver components that ship their own C++ and call these setters from &lt;code dir=&quot;auto&quot;&gt;setup()&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;dump_config()&lt;/code&gt; fall in this bucket — typically inverter / charger / DC-DC bridges that auto-configure scaling per detected hardware (e.g. several of the &lt;code dir=&quot;auto&quot;&gt;syssi/esphome-*&lt;/code&gt; repos).&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;yaml-lambda-users--add-one-line-of-yaml&quot;&gt;YAML lambda users — add one line of YAML&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The build will fail with a clear error at your lambda:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;error: static assertion failed: set_min_power() requires USE_OUTPUT_FLOAT_POWER_SCALING.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;To enable it, add &apos;min_power: 0%&apos; (or any value) to one output entry in your YAML — the&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;codegen will then keep the scaling fields.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The fix is one line of YAML — add this to &lt;strong&gt;any one&lt;/strong&gt; output entry:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;platform&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;esp8266_pwm&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;out&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;pin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;min_power&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0%&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# ← add this (or max_power: 100%, or zero_means_zero: true)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;min_power: 0%&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;max_power: 100%&lt;/code&gt; are the existing defaults, so this changes no runtime behavior — it just tells the codegen “compile in runtime power scaling so my lambda can use it.”&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;external-component-authors--declare-the-define&quot;&gt;External component authors — declare the define&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If your component ships its own &lt;code dir=&quot;auto&quot;&gt;output.*&lt;/code&gt; C++ and depends on the scaling setters, you have two options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;(Recommended)&lt;/strong&gt; Add the YAML key on any output. The codegen will turn the define on for you. This is the same flow regular users follow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you cannot rely on user YAML, declare the define in your component’s Python codegen:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; esphome.codegen &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; cg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to_code&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cg.&lt;/span&gt;&lt;span&gt;add_define&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;USE_OUTPUT_FLOAT_POWER_SCALING&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;output&lt;/code&gt; component will then include the fields and setters regardless of YAML.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# C++ — find calls to the gated setters from lambdas or component code&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rEn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;set_min_power|set_max_power|set_zero_means_zero&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# YAML — find lambdas that call the setters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rEn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;set_min_power|set_max_power|set_zero_means_zero&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_configs/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# YAML — check if any output entry already enables the feature (no action needed if so).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Drop ^ to also catch top-level entries; expect false positives from unrelated&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# components that happen to use a same-named key — visually confirm matches are&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# under an output: entry before concluding you&apos;ve already opted in.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rEn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;(min_power|max_power|zero_means_zero):&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_configs/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component or configuration, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15998&quot;&gt;PR #15998&lt;/a&gt; — Gate FloatOutput power scaling fields behind USE_OUTPUT_FLOAT_POWER_SCALING&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://esphome.io/components/output/&quot;&gt;Output component documentation&lt;/a&gt; — &lt;code dir=&quot;auto&quot;&gt;min_power&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;max_power&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;zero_means_zero&lt;/code&gt; reference&lt;/li&gt;
&lt;li&gt;Pattern reference: &lt;code dir=&quot;auto&quot;&gt;USE_POWER_SUPPLY&lt;/code&gt; (gates &lt;code dir=&quot;auto&quot;&gt;BinaryOutput::power_&lt;/code&gt;), &lt;code dir=&quot;auto&quot;&gt;USE_ENTITY_ICON&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;USE_ENTITY_DEVICE_CLASS&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;USE_ENTITY_UNIT_OF_MEASUREMENT&lt;/code&gt; (gate &lt;code dir=&quot;auto&quot;&gt;EntityBase&lt;/code&gt; strings)&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Main Loop Cadence Decoupled from Scheduler Wake Timing</title><link>https://developers.esphome.io/blog/2026/05/14/main-loop-cadence-decoupled-from-scheduler-wake-timing/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/05/14/main-loop-cadence-decoupled-from-scheduler-wake-timing/</guid><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every component’s &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; now runs at the configured &lt;code dir=&quot;auto&quot;&gt;loop_interval_&lt;/code&gt; (default ~62 Hz). Previously, &lt;strong&gt;when other scheduler activity was running&lt;/strong&gt; — &lt;code dir=&quot;auto&quot;&gt;set_interval&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;set_timeout&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;PollingComponent&lt;/code&gt; updates with sub-&lt;code dir=&quot;auto&quot;&gt;loop_interval_&lt;/code&gt; cadences — the component phase got pulled forward up to ~128 Hz; quiet configs with no such scheduler entries were unaffected and always ran at the documented rate. Background events (MQTT RX, USB RX, BLE events, ESPNOW, camera, &lt;code dir=&quot;auto&quot;&gt;micro_wake_word&lt;/code&gt;, speaker, USB host/CDC, lwIP socket) still wake their component within one tick via the existing &lt;code dir=&quot;auto&quot;&gt;wake_loop_*&lt;/code&gt; paths. &lt;code dir=&quot;auto&quot;&gt;App.set_loop_interval()&lt;/code&gt; — the documented knob for power savings — finally works.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;behavior change&lt;/strong&gt; in &lt;strong&gt;ESPHome 2026.5.0 and later&lt;/strong&gt; with no API break, but it affects every component whose &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; implicitly depended on running at ~2× the configured rate when scheduler activity was driving the pull-forward.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15792&quot;&gt;PR #15792&lt;/a&gt;: decouple main loop cadence from scheduler wake timing&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-was-happening&quot;&gt;What was happening&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Sampling runtime stats over 60 s on a typical ESP32 IDF config (api + esp32_ble + esp32_ble_tracker + bluetooth_proxy + debug + a couple of sensors) at the default &lt;code dir=&quot;auto&quot;&gt;loop_interval_ = 16 ms&lt;/code&gt;:&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Component&lt;/th&gt;&lt;th&gt;Before&lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;th&gt;What’s counted&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;api&lt;/code&gt;&lt;/td&gt;&lt;td&gt;7650 / 60 s ≈ &lt;strong&gt;128 Hz&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;3720 / 60 s ≈ &lt;strong&gt;62 Hz&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; calls&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;esp32_ble&lt;/code&gt;&lt;/td&gt;&lt;td&gt;7650 ≈ &lt;strong&gt;128 Hz&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;3720 ≈ &lt;strong&gt;62 Hz&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; calls&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;esp32_ble_tracker&lt;/code&gt;&lt;/td&gt;&lt;td&gt;7650 ≈ &lt;strong&gt;128 Hz&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;3720 ≈ &lt;strong&gt;62 Hz&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; calls&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;debug&lt;/code&gt;&lt;/td&gt;&lt;td&gt;7651 ≈ &lt;strong&gt;128 Hz&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;3720 ≈ &lt;strong&gt;62 Hz&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; calls&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;bluetooth_proxy&lt;/code&gt;&lt;/td&gt;&lt;td&gt;589 ≈ 10 Hz&lt;/td&gt;&lt;td&gt;599 ≈ 10 Hz&lt;/td&gt;&lt;td&gt;100 ms &lt;code dir=&quot;auto&quot;&gt;set_interval&lt;/code&gt; (unaffected)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Every component with a real &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; was running at &lt;strong&gt;~2× the configured &lt;code dir=&quot;auto&quot;&gt;loop_interval_&lt;/code&gt;&lt;/strong&gt;. 62 Hz is what the documentation has always promised; the 128 Hz was emergent behavior.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;why-it-happened&quot;&gt;Why it happened&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Application::loop()&lt;/code&gt; bounded its sleep by &lt;code dir=&quot;auto&quot;&gt;min(loop_interval_ - elapsed, next_schedule_in())&lt;/code&gt; with a &lt;code dir=&quot;auto&quot;&gt;delay_time / 2&lt;/code&gt; floor. If the scheduler had any entry due sooner than &lt;code dir=&quot;auto&quot;&gt;loop_interval_ / 2&lt;/code&gt;, the loop slept for &lt;code dir=&quot;auto&quot;&gt;loop_interval_ / 2&lt;/code&gt; and ran the &lt;strong&gt;entire component phase&lt;/strong&gt; again — every component’s &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; got pulled forward by whatever else happened to be scheduling work. The effect was config-dependent and non-local: adding one scheduled item anywhere silently shifted every other component’s cadence.&lt;/p&gt;
&lt;p&gt;Two recent trends made this bite harder: (1) more components are &lt;code dir=&quot;auto&quot;&gt;PollingComponent&lt;/code&gt;s, each of which is a &lt;code dir=&quot;auto&quot;&gt;set_interval&lt;/code&gt; under the hood; (2) more components use &lt;code dir=&quot;auto&quot;&gt;set_interval&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;set_timeout&lt;/code&gt; directly for retries, debouncing, animations, and protocol timing. &lt;code dir=&quot;auto&quot;&gt;App.set_loop_interval()&lt;/code&gt; — the documented knob for power savings — was silently defeated by the same coupling.&lt;/p&gt;
&lt;p&gt;Removing the floor is safe now because &lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;wake_loop_threadsafe()&lt;/code&gt; is accessible everywhere as of 2026.4.0&lt;/strong&gt; (see the &lt;a href=&quot;https://developers.esphome.io/blog/2026/04/09/wake_loop-moved-from-socket-component-into-core/&quot;&gt;2026.4.0 wake_loop blog post&lt;/a&gt;). Any component that needs to wake the loop sooner than &lt;code dir=&quot;auto&quot;&gt;loop_interval_&lt;/code&gt; has a proper way to do it without the floor papering over missing wake-ups.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Application::loop()&lt;/code&gt; is restructured into two independent phases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phase A (every tick):&lt;/strong&gt; drain wake notifications, run &lt;code dir=&quot;auto&quot;&gt;scheduler.call()&lt;/code&gt;, feed the watchdog.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase B (gated):&lt;/strong&gt; iterate registered components. Runs when &lt;strong&gt;any&lt;/strong&gt; of these is true:
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;loop_interval_&lt;/code&gt; has elapsed since the last component phase, &lt;strong&gt;or&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;HighFrequencyLoopRequester&lt;/code&gt; is active, &lt;strong&gt;or&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;a background producer set the wake-request flag via &lt;code dir=&quot;auto&quot;&gt;wake_loop_*&lt;/code&gt; (MQTT RX, USB RX, BLE events, ESPNOW, camera, &lt;code dir=&quot;auto&quot;&gt;micro_wake_word&lt;/code&gt;, speaker, USB host/CDC, lwIP socket, or a component-level &lt;code dir=&quot;auto&quot;&gt;enable_loop_soon_any_context()&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sleep between ticks is &lt;code dir=&quot;auto&quot;&gt;min(time-until-next-component-phase, next_schedule_in())&lt;/code&gt;. A scheduler-timer wake runs only Phase A; a &lt;code dir=&quot;auto&quot;&gt;wake_loop_threadsafe()&lt;/code&gt; wake runs Phase B too so the producer’s component can drain its queued work.&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;delay_time / 2&lt;/code&gt; floor is removed entirely. Zero-delay &lt;code dir=&quot;auto&quot;&gt;set_interval(0, ...)&lt;/code&gt; registrations no longer busy-loop the component phase, because Phase B is gated separately from scheduler timer expiry. &lt;code dir=&quot;auto&quot;&gt;HighFrequencyLoopRequester&lt;/code&gt; remains the correct mechanism for “I need fast wakes sometimes.” Zero-delay &lt;code dir=&quot;auto&quot;&gt;defer()&lt;/code&gt; is unaffected.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ordering preserved:&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;defer()&lt;/code&gt; → FIFO before components; scheduled items before components when both are due; scheduled callbacks still main-thread-only; the WDT is fed ≥ once per tick.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Every component that overrides &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt;&lt;/strong&gt; — in-tree and external alike — but most won’t notice. Specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Components whose &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; depended on running at ~128 Hz&lt;/strong&gt; — animations stepping per loop tick, debounce / state-machine timing keyed to loop count, anything that implicitly assumed the doubled rate. Those will now step at ~62 Hz when scheduler activity was previously providing the pull-forward.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background-event producers&lt;/strong&gt; — already wired up. Every existing &lt;code dir=&quot;auto&quot;&gt;wake_loop_*&lt;/code&gt; caller (MQTT RX, USB RX, BLE events, ESPNOW, camera, &lt;code dir=&quot;auto&quot;&gt;micro_wake_word&lt;/code&gt;, speaker, USB host/CDC, lwIP socket, and per-component &lt;code dir=&quot;auto&quot;&gt;enable_loop_soon_any_context()&lt;/code&gt; calls) now sets a wake-request flag before signalling the platform so that Phase B runs in the next tick. No action needed in callers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Components that need sub-&lt;code dir=&quot;auto&quot;&gt;loop_interval_&lt;/code&gt; cadence&lt;/strong&gt; — see the migration guide below for the preferred event-driven pattern. &lt;code dir=&quot;auto&quot;&gt;HighFrequencyLoopRequester&lt;/code&gt; is available but should be a last resort.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Power-managed configs&lt;/strong&gt; using &lt;code dir=&quot;auto&quot;&gt;App.set_loop_interval()&lt;/code&gt; — this knob now actually saves power. If you previously bumped &lt;code dir=&quot;auto&quot;&gt;loop_interval_&lt;/code&gt; to 100 ms and saw no power reduction, expect a real change this release.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;For most components, &lt;strong&gt;do nothing&lt;/strong&gt;. Your &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; will simply run at the documented rate.&lt;/p&gt;
&lt;p&gt;If your component genuinely needs to run more often than &lt;code dir=&quot;auto&quot;&gt;loop_interval_&lt;/code&gt;, the &lt;strong&gt;preferred pattern is event-driven, not high-frequency&lt;/strong&gt;: keep the loop disabled while there’s nothing to do, and have your event source re-enable it. The &lt;code dir=&quot;auto&quot;&gt;Component&lt;/code&gt; base class has the primitives you need:&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;API&lt;/th&gt;&lt;th&gt;Where you call it&lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;this-&gt;disable_loop()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Main thread (typically the tail of &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; once your work is done)&lt;/td&gt;&lt;td&gt;Take the component out of the iteration list — its &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; won’t be called again until something re-enables it.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;this-&gt;enable_loop()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Main thread&lt;/td&gt;&lt;td&gt;Put the component back in the iteration list.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;this-&gt;enable_loop_soon_any_context()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;ISR, FreeRTOS task, or any thread&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;ISR-safe enable. Sets a pending flag that the main loop drains on the next tick. There is intentionally no &lt;code dir=&quot;auto&quot;&gt;disable_loop_soon_any_context()&lt;/code&gt; — use &lt;code dir=&quot;auto&quot;&gt;disable_loop()&lt;/code&gt; from &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; itself.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;App.wake_loop_threadsafe()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;FreeRTOS task / non-ISR thread (BLE callbacks, network events, platform task callbacks)&lt;/td&gt;&lt;td&gt;Forces Phase B in the next tick globally; lets every enabled component drain queued work without changing per-component enable state.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;App.wake_loop_any_context()&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;App.wake_loop_isrsafe()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;ISR context&lt;/td&gt;&lt;td&gt;ISR-safe global wake. See &lt;a href=&quot;https://developers.esphome.io/architecture/components/advanced#waking-from-isr&quot;&gt;Waking from ISR&lt;/a&gt; for per-platform IRAM and &lt;code dir=&quot;auto&quot;&gt;xHigherPriorityTaskWoken&lt;/code&gt; notes.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The typical event-driven pattern looks like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;esphome/core/application.h&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;esphome/core/component.h&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyEventDrivenComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ... register hardware / callbacks ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;disable_loop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt;  // nothing to do until an event arrives&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;loop&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;drain_queued_events_&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;queue_empty_&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;disable_loop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt;  // re-enable from on_event()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Called from a FreeRTOS task / network callback / etc.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;on_event&lt;/span&gt;&lt;span&gt;&lt;span&gt;(Event &lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;queue_&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(e);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;enable_loop_soon_any_context&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt;  // ISR-safe; also wakes the main loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;enable_loop_soon_any_context()&lt;/code&gt; both flips the component’s enable bit &lt;em&gt;and&lt;/em&gt; wakes the main loop, so &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; runs within a tick of the event landing — regardless of where &lt;code dir=&quot;auto&quot;&gt;loop_interval_&lt;/code&gt; is set. This composes cleanly with &lt;code dir=&quot;auto&quot;&gt;App.set_loop_interval(...)&lt;/code&gt; for power-managed configs: idle current drops because the loop sleeps; latency stays low because events wake it.&lt;/p&gt;
&lt;div dir=&quot;auto&quot;&gt;
&lt;p dir=&quot;auto&quot;&gt;&lt;svg viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot; height=&quot;16&quot; aria-hidden=&quot;true&quot;&gt;&lt;path d=&quot;M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;WARNING&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;HighFrequencyLoopRequester&lt;/code&gt; is a last resort&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;HighFrequencyLoopRequester::start()&lt;/code&gt; keeps Phase B running every tick unconditionally. It exists for genuinely time-critical workloads (e.g. tight bit-banged protocol timing where you can’t wait for an event), and even there, scoping the request as narrowly as possible — &lt;code dir=&quot;auto&quot;&gt;start()&lt;/code&gt; only while the critical work is in flight, &lt;code dir=&quot;auto&quot;&gt;stop()&lt;/code&gt; immediately after — is the supported usage. If you’re reaching for it because “I just need faster wakes,” prefer &lt;code dir=&quot;auto&quot;&gt;disable_loop()&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;enable_loop_soon_any_context()&lt;/code&gt; first — it gets you the same latency at a fraction of the wakeup count.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The full architectural rationale, including how to decide between &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;set_interval()&lt;/code&gt;, and the scheduler under the new cadence rules, is documented in &lt;a href=&quot;https://developers.esphome.io/architecture/components/advanced#choosing-between-loop-and-the-scheduler&quot;&gt;Choosing Between loop() and the Scheduler&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Components with a loop() override (the work you might want to review).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# The narrower `override`-style pattern catches modern code; the wider one&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# also picks up older external components that declared `void loop();` without&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# `override` and is precisely where you&apos;re most likely to find code that&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# implicitly depended on the old ~128 Hz cadence.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;void loop() override&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rEn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;void[[:space:]]+loop[[:space:]]*\([[:space:]]*\)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Zero-delay set_interval calls — previously busy-looped the component phase&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rEn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;set_interval\(\s*0\s*,&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Background producers that should be using a wake primitive&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;wake_loop_threadsafe\|enable_loop_soon_any_context\|wake_loop_any_context\|wake_loop_isrsafe&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Components already opted in to high-frequency wakes (no action needed)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;HighFrequencyLoopRequester&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If your &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; was implicitly relying on the ~128 Hz emergent rate and ~62 Hz is now insufficient, switch to &lt;code dir=&quot;auto&quot;&gt;HighFrequencyLoopRequester&lt;/code&gt; (for sustained fast wakes) or a wake primitive driven from your event source (for event-driven wakes).&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15792&quot;&gt;PR #15792&lt;/a&gt; — decouple main loop cadence from scheduler wake timing&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15846&quot;&gt;PR #15846&lt;/a&gt; — prerequisite WDT-feed rate-limit adjustment&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.esphome.io/architecture/components/advanced&quot;&gt;Architecture doc: loop control and main-loop cadence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.esphome.io/blog/2026/04/09/wake_loop-moved-from-socket-component-into-core/&quot;&gt;2026.4.0 blog: wake_loop moved to core&lt;/a&gt; — provided the unconditional wake primitive that made this fix safe&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Modbus Server Split Out of modbus_controller</title><link>https://developers.esphome.io/blog/2026/05/14/modbus-server-split-out-of-modbus_controller/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/05/14/modbus-server-split-out-of-modbus_controller/</guid><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Modbus server mode has been split out of &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; into a new dedicated &lt;code dir=&quot;auto&quot;&gt;modbus_server&lt;/code&gt; component. Configurations that used &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; as a Modbus server (the &lt;code dir=&quot;auto&quot;&gt;role: server&lt;/code&gt; setting lives on the &lt;code dir=&quot;auto&quot;&gt;modbus:&lt;/code&gt; bus, not on &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt;) must move their &lt;code dir=&quot;auto&quot;&gt;server_registers:&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;server_courtesy_response:&lt;/code&gt; blocks under a new top-level &lt;code dir=&quot;auto&quot;&gt;modbus_server:&lt;/code&gt; entry.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for YAML configurations using server mode in &lt;strong&gt;ESPHome 2026.5.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15509&quot;&gt;PR #15509&lt;/a&gt;: Split modbus_server from modbus_controller&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Server-mode register handling was wedged into &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; alongside its client-mode polling. The two roles share a transport (the &lt;code dir=&quot;auto&quot;&gt;modbus:&lt;/code&gt; bus) but almost nothing else: server mode dispatches inbound requests to user lambdas, while client mode polls outbound register reads on a schedule. Keeping both in the same component made the controller heavier than it needed to be, complicated review of server-side fixes, and prevented the server logic from sharing helpers with future modbus-based components.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_server&lt;/code&gt; is a thin &lt;code dir=&quot;auto&quot;&gt;Component&lt;/code&gt; that implements &lt;code dir=&quot;auto&quot;&gt;modbus::ModbusDevice&lt;/code&gt; and owns the register table plus the optional courtesy response, with no client-mode machinery. The migration in this PR is intentionally close to a copy/paste — functional improvements to server mode (out-of-range handling, multi-register transactions, parser fixes, etc.) come in follow-up PRs.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;flash-savings-approximate&quot;&gt;Flash savings (approximate)&lt;/h3&gt;&lt;/div&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Configuration&lt;/th&gt;&lt;th&gt;Before&lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Pure server&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; ~4.5 KB&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_server&lt;/code&gt; ~1.8 KB&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pure client&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; ~6.4 KB&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; ~3.9 KB&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Devices that previously combined client and server in one &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; will now have two separate components, but each is much smaller.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;yaml-keys-moved&quot;&gt;YAML keys moved&lt;/h3&gt;&lt;/div&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Old (under &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt;)&lt;/th&gt;&lt;th&gt;New (under &lt;code dir=&quot;auto&quot;&gt;modbus_server&lt;/code&gt;)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;server_registers:&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;registers:&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;server_courtesy_response:&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;courtesy_response:&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;modbus_controller-is-now-client-only&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; is now client-only&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;role: server&lt;/code&gt; on the bus is still supported — that selects the transport-layer role. What changed is that the &lt;strong&gt;register table&lt;/strong&gt; is no longer accepted on &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt;. Put it on &lt;code dir=&quot;auto&quot;&gt;modbus_server&lt;/code&gt; instead.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;YAML configurations that used &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; as a Modbus server&lt;/strong&gt; — typically battery management bridges (BMS protocol bridges, yamBMS forks), EVSE bridges, energy/inverter proxies, and any “act as a register slave” project. Pure-client &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; users (the more common case — reading from third-party Modbus devices) are not affected.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;modbus&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;role&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;modbus_controller&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;server_registers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0x0001&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;value_type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;U_WORD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;read_lambda&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;return id(my_sensor).state;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;server_courtesy_response&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;register_value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;modbus&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;role&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;modbus_server&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;registers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0x0001&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;value_type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;U_WORD&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;read_lambda&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;return id(my_sensor).state;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;courtesy_response&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;register_value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;modbus:&lt;/code&gt; bus has a single role and your device occupies one side of it: &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; requires &lt;code dir=&quot;auto&quot;&gt;role: client&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;modbus_server&lt;/code&gt; requires &lt;code dir=&quot;auto&quot;&gt;role: server&lt;/code&gt;, and the bus schema enforces this — you cannot mix the two on the same &lt;code dir=&quot;auto&quot;&gt;modbus:&lt;/code&gt; instance. Modbus only allows one client per bus, so the “both on one device” pattern was never valid; if a previous config had &lt;code dir=&quot;auto&quot;&gt;server_registers:&lt;/code&gt; under a client-mode &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt;, those registers were silently inactive. Run a second &lt;code dir=&quot;auto&quot;&gt;modbus:&lt;/code&gt; bus on a different UART if you need a device that’s both a server on one wire and a client on another.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2026.5.0:&lt;/strong&gt; Server-mode keys removed from &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt;; new &lt;code dir=&quot;auto&quot;&gt;modbus_server&lt;/code&gt; component active.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a clean break — there is no deprecation period or migration shim. &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; configs that still contain &lt;code dir=&quot;auto&quot;&gt;server_registers:&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;server_courtesy_response:&lt;/code&gt; will fail validation on 2026.5.0. (For contrast, the earlier &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; → &lt;code dir=&quot;auto&quot;&gt;modbus::helpers&lt;/code&gt; namespace refactor in 2026.4.0 kept deprecation shims through 2026.10.0 — that pattern doesn’t apply here because there’s no in-place rename to forward, and the affected configs are caught at config-validation time with a precise error message rather than at runtime.)&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# YAML — find server-mode keys still under modbus_controller.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# -E (extended regex) keeps the alternation portable across GNU and BSD/macOS grep.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rEn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;server_registers:|server_courtesy_response:&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_configs/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your configuration, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15509&quot;&gt;PR #15509&lt;/a&gt; — Split modbus_server from modbus_controller&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15291&quot;&gt;PR #15291&lt;/a&gt; and &lt;a href=&quot;https://github.com/esphome/esphome/pull/14172&quot;&gt;PR #14172&lt;/a&gt; — Earlier helper-function refactor that enabled this split (&lt;a href=&quot;https://developers.esphome.io/blog/2026/04/09/modbus-helper-functions-moved-to-modbushelpers/&quot;&gt;blog post&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://esphome.io/components/modbus_server/&quot;&gt;ESPHome Modbus Server documentation&lt;/a&gt; (new page)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://esphome.io/components/modbus_controller/&quot;&gt;ESPHome Modbus Controller documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>RingBuffer Moved Out of Core Into ring_buffer Helper Component</title><link>https://developers.esphome.io/blog/2026/05/14/ringbuffer-moved-out-of-core-into-ring_buffer-helper-component/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/05/14/ringbuffer-moved-out-of-core-into-ring_buffer-helper-component/</guid><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;RingBuffer&lt;/code&gt; has moved from &lt;code dir=&quot;auto&quot;&gt;esphome/core/ring_buffer.h&lt;/code&gt; into a new &lt;code dir=&quot;auto&quot;&gt;ring_buffer&lt;/code&gt; helper component at &lt;code dir=&quot;auto&quot;&gt;esphome/components/ring_buffer/ring_buffer.h&lt;/code&gt; under the &lt;code dir=&quot;auto&quot;&gt;esphome::ring_buffer&lt;/code&gt; namespace. The old location is deprecated and will be removed in &lt;strong&gt;ESPHome 2026.11.0&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.5.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/16298&quot;&gt;PR #16298&lt;/a&gt;: Move core ring buffer to helper component&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The core &lt;code dir=&quot;auto&quot;&gt;RingBuffer&lt;/code&gt; was an ESP32-only implementation used exclusively by audio components — &lt;code dir=&quot;auto&quot;&gt;audio&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;i2s_audio&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;micro_wake_word&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;mixer&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;resampler&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;sound_level&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;speaker&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;voice_assistant&lt;/code&gt;. Nothing outside that audio cluster touches it. Living in &lt;code dir=&quot;auto&quot;&gt;esphome/core/&lt;/code&gt; meant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code dir=&quot;auto&quot;&gt;ring_buffer.h&lt;/code&gt; header was parsed for every build, regardless of platform or whether audio was in use. Linker stripping kept unused code out of the final binary, but build-time parsing and tooling overhead applied universally.&lt;/li&gt;
&lt;li&gt;Changes to &lt;code dir=&quot;auto&quot;&gt;RingBuffer&lt;/code&gt; couldn’t be staged in an &lt;code dir=&quot;auto&quot;&gt;external_components&lt;/code&gt; PR — they always required a core change.&lt;/li&gt;
&lt;li&gt;The ESP32-only implementation lived alongside genuinely cross-platform core code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Moving it into a real component (&lt;code dir=&quot;auto&quot;&gt;esphome::ring_buffer::RingBuffer&lt;/code&gt;) lets audio consumers &lt;code dir=&quot;auto&quot;&gt;AUTO_LOAD&lt;/code&gt; it the same way every other shared utility is pulled in, and opens the door to iterating on the implementation without touching core.&lt;/p&gt;
&lt;p&gt;The old &lt;code dir=&quot;auto&quot;&gt;esphome/core/ring_buffer.h&lt;/code&gt; header now forwards to the new location and will be removed in &lt;strong&gt;2026.11.0&lt;/strong&gt; per the standard 6-month policy. The behavior of the shim depends on whether your component declares &lt;code dir=&quot;auto&quot;&gt;AUTO_LOAD = [&quot;ring_buffer&quot;]&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;With &lt;code dir=&quot;auto&quot;&gt;AUTO_LOAD&lt;/code&gt;:&lt;/strong&gt; the shim forwards &lt;code dir=&quot;auto&quot;&gt;esphome::RingBuffer&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;esphome::ring_buffer::RingBuffer&lt;/code&gt; via a &lt;code dir=&quot;auto&quot;&gt;using&lt;/code&gt; alias marked &lt;code dir=&quot;auto&quot;&gt;ESPDEPRECATED(...)&lt;/code&gt;. Code keeps compiling, but each use emits a deprecation warning at the call site.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Without &lt;code dir=&quot;auto&quot;&gt;AUTO_LOAD&lt;/code&gt;:&lt;/strong&gt; the new header isn’t on the include path, so the shim fires a hard &lt;code dir=&quot;auto&quot;&gt;#error&lt;/code&gt; telling you exactly what to do: &lt;em&gt;“&lt;code dir=&quot;auto&quot;&gt;esphome/components/ring_buffer/ring_buffer.h&lt;/code&gt; not found. Add ‘ring_buffer’ to your component’s AUTO_LOAD list to use esphome::ring_buffer::RingBuffer.”&lt;/em&gt; The build fails immediately rather than producing a confusing missing-header diagnostic from somewhere else.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;c-include-and-namespace&quot;&gt;C++ include and namespace&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;esphome/core/ring_buffer.h&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;std::unique_ptr&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;esphome::RingBuffer&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; buf &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; esphome::RingBuffer::&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1024&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;esphome/components/ring_buffer/ring_buffer.h&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;std::unique_ptr&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;esphome::ring_buffer::RingBuffer&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; buf &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;esphome::ring_buffer::RingBuffer::&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1024&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;python-codegen--declare-auto_load&quot;&gt;Python codegen — declare &lt;code dir=&quot;auto&quot;&gt;AUTO_LOAD&lt;/code&gt;&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Any component whose C++ uses &lt;code dir=&quot;auto&quot;&gt;RingBuffer&lt;/code&gt; must auto-load the helper component so the build system pulls in its sources. In your component’s &lt;code dir=&quot;auto&quot;&gt;__init__.py&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;AUTO_LOAD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ring_buffer&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If your component already has an &lt;code dir=&quot;auto&quot;&gt;AUTO_LOAD&lt;/code&gt;, append &lt;code dir=&quot;auto&quot;&gt;&quot;ring_buffer&quot;&lt;/code&gt; to it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External audio / streaming components&lt;/strong&gt; that use &lt;code dir=&quot;auto&quot;&gt;RingBuffer&lt;/code&gt; directly — typically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Custom microphone / I²S audio pipelines&lt;/li&gt;
&lt;li&gt;Speaker / playback components&lt;/li&gt;
&lt;li&gt;Streaming protocol bridges (BLE audio, Nordic UART, etc.)&lt;/li&gt;
&lt;li&gt;Forks or extensions of &lt;code dir=&quot;auto&quot;&gt;micro_wake_word&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;voice_assistant&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;speaker&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All in-tree consumers (&lt;code dir=&quot;auto&quot;&gt;audio&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;i2s_audio&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;micro_wake_word&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;mixer&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;resampler&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;sound_level&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;speaker&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;voice_assistant&lt;/code&gt;) were migrated in &lt;a href=&quot;https://github.com/esphome/esphome/pull/16298&quot;&gt;esphome/esphome#16298&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Non-audio components are unaffected.&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Update the include path:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#include &quot;esphome/core/ring_buffer.h&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#include &quot;esphome/components/ring_buffer/ring_buffer.h&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update the namespace at every callsite:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;esphome::RingBuffer::create(size);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;esphome::ring_buffer::RingBuffer::create(size);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you have many callsites, you can avoid editing each one by adding a &lt;code dir=&quot;auto&quot;&gt;using esphome::ring_buffer::RingBuffer;&lt;/code&gt; declaration &lt;strong&gt;inside your component’s own namespace block&lt;/strong&gt; so &lt;code dir=&quot;auto&quot;&gt;RingBuffer&lt;/code&gt; resolves correctly from unqualified references:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;namespace&lt;/span&gt;&lt;span&gt; esphome::my_component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; esphome::ring_buffer::RingBuffer;&lt;/span&gt;&lt;span&gt;   // ← here, not at file scope&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::unique_ptr&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;RingBuffer&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; buf_;&lt;/span&gt;&lt;span&gt;        // unqualified still works&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  // namespace esphome::my_component&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Avoid putting the &lt;code dir=&quot;auto&quot;&gt;using&lt;/code&gt; at file scope (outside any namespace) — that pulls the name into the global namespace, which works for &lt;code dir=&quot;auto&quot;&gt;::RingBuffer&lt;/code&gt; lookups but doesn’t help code inside &lt;code dir=&quot;auto&quot;&gt;esphome::my_component&lt;/code&gt; that previously relied on &lt;code dir=&quot;auto&quot;&gt;esphome::RingBuffer&lt;/code&gt; being a sibling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add &lt;code dir=&quot;auto&quot;&gt;AUTO_LOAD&lt;/code&gt; in your component’s &lt;code dir=&quot;auto&quot;&gt;__init__.py&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;AUTO_LOAD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ring_buffer&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The API of &lt;code dir=&quot;auto&quot;&gt;RingBuffer&lt;/code&gt; itself (&lt;code dir=&quot;auto&quot;&gt;create()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;read()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;write()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;write_without_replacement()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;available()&lt;/code&gt;, etc.) is unchanged — only its location and namespace move.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2026.5.0:&lt;/strong&gt; New location active; old &lt;code dir=&quot;auto&quot;&gt;esphome/core/ring_buffer.h&lt;/code&gt; deprecated (compile warning).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2026.11.0:&lt;/strong&gt; Old header removed.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# C++ — find the old include&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;esphome/core/ring_buffer.h&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# C++ — find old namespace usage&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;esphome::RingBuffer&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Python — find AUTO_LOAD declarations that need ring_buffer added&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;AUTO_LOAD&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/16298&quot;&gt;PR #16298&lt;/a&gt; — Move core ring buffer to helper component&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>BLE Event Handler Dispatch Devirtualized</title><link>https://developers.esphome.io/blog/2026/04/09/ble-event-handler-dispatch-devirtualized/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/04/09/ble-event-handler-dispatch-devirtualized/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Virtual handler interfaces (&lt;code dir=&quot;auto&quot;&gt;GAPEventHandler&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;GAPScanEventHandler&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;GATTcEventHandler&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;GATTsEventHandler&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;BLEStatusEventHandler&lt;/code&gt;) have been replaced with callback-based dispatch. External components that inherit from these interfaces must update their registration approach.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;developer breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.4.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15310&quot;&gt;PR #15310&lt;/a&gt;: Devirtualize BLE event handler dispatch&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The previous virtual handler pattern required vtable lookups and multiple-inheritance this-pointer adjustment thunks on every BLE event dispatch. The new approach uses &lt;code dir=&quot;auto&quot;&gt;StaticCallbackManager&lt;/code&gt; with lambda callbacks, eliminating vtable overhead. Each lambda captures a single pointer (fits in &lt;code dir=&quot;auto&quot;&gt;Callback&lt;/code&gt; inline storage, no heap allocation).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before (virtual dispatch):&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ESP32BLE::loop()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ vtable load → indirect call → thunk this-adjust → gap_scan_event_handler()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;After (callback dispatch):&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ESP32BLE::loop()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ StaticCallbackManager::call() → Callback::fn_(ctx_, args) → direct call&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The following virtual handler base classes are removed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;GAPEventHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;GAPScanEventHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;GATTcEventHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;GATTsEventHandler&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;BLEStatusEventHandler&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Registration now uses &lt;code dir=&quot;auto&quot;&gt;add_*_callback()&lt;/code&gt; methods with lambdas instead of &lt;code dir=&quot;auto&quot;&gt;register_*_handler()&lt;/code&gt; with object pointers. The Python &lt;code dir=&quot;auto&quot;&gt;register_*&lt;/code&gt; helper functions are unchanged — they generate lambda-based callbacks internally.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External ESP32 BLE components that inherit from the virtual handler classes.&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;c-changes&quot;&gt;C++ changes&lt;/h3&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Remove the handler base class from your inheritance list&lt;/li&gt;
&lt;li&gt;Remove &lt;code dir=&quot;auto&quot;&gt;override&lt;/code&gt; from handler methods&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component, &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; GAPScanEventHandler {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gap_scan_event_handler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; BLEScanResult &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;scan_result&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gap_scan_event_handler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; BLEScanResult &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;scan_result&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;python-codegen&quot;&gt;Python codegen&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The Python &lt;code dir=&quot;auto&quot;&gt;register_*&lt;/code&gt; helper functions are unchanged — no Python changes needed:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# This still works — generates lambda-based callbacks internally&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;esp32_ble.&lt;/span&gt;&lt;span&gt;register_gap_scan_event_handler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;parent&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;supporting-multiple-esphome-versions&quot;&gt;Supporting Multiple ESPHome Versions&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before 2026.4.0, inherit from the handler class&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After 2026.4.0, just declare the method&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ESPHOME_VERSION_CODE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VERSION_CODE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2026&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component, &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; GAPScanEventHandler {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gap_scan_event_handler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; BLEScanResult &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;scan_result&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ESPHOME_VERSION_CODE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VERSION_CODE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2026&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.4.0 (April 2026):&lt;/strong&gt; Virtual handler classes removed&lt;/li&gt;
&lt;li&gt;No deprecation period — this is a class removal&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find handler class inheritance&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;GAPEventHandler\|GAPScanEventHandler\|GATTcEventHandler\|GATTsEventHandler\|BLEStatusEventHandler&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15310&quot;&gt;PR #15310: Devirtualize BLE event handler dispatch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>CallbackManager: std::function Replaced with Lightweight Callback</title><link>https://developers.esphome.io/blog/2026/04/09/callbackmanager-stdfunction-replaced-with-lightweight-callback/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/04/09/callbackmanager-stdfunction-replaced-with-lightweight-callback/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;CallbackManager&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;LazyCallbackManager&lt;/code&gt; now use a lightweight 8-byte &lt;code dir=&quot;auto&quot;&gt;Callback&lt;/code&gt; struct instead of &lt;code dir=&quot;auto&quot;&gt;std::function&lt;/code&gt; (16 bytes). External components that define their own callback registration methods using &lt;code dir=&quot;auto&quot;&gt;std::function&lt;/code&gt; should update to templates for optimal performance.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.4.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14853&quot;&gt;PR #14853&lt;/a&gt;: Replace std::function with lightweight Callback in CallbackManager&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;std::function&lt;/code&gt; has significant overhead on embedded targets: 16 bytes per instance on 32-bit platforms, plus heap allocation for captured lambdas. The new &lt;code dir=&quot;auto&quot;&gt;Callback&lt;/code&gt; struct uses C++20 &lt;code dir=&quot;auto&quot;&gt;if constexpr&lt;/code&gt; to store small trivially-copyable callables (like &lt;code dir=&quot;auto&quot;&gt;[this]&lt;/code&gt; lambdas) inline within the &lt;code dir=&quot;auto&quot;&gt;Callback&lt;/code&gt; object, alongside a function pointer, avoiding heap allocation while keeping the total size at 8 bytes.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;measured-savings&quot;&gt;Measured savings&lt;/h3&gt;&lt;/div&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Platform&lt;/th&gt;&lt;th&gt;Config&lt;/th&gt;&lt;th&gt;Flash Delta&lt;/th&gt;&lt;th&gt;Heap Delta&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;ESP8266&lt;/td&gt;&lt;td&gt;minimal&lt;/td&gt;&lt;td&gt;&lt;strong&gt;-160 B&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ESP8266&lt;/td&gt;&lt;td&gt;ratgdo (cover + sensors)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;-592 B&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;+142 B free&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ESP8266&lt;/td&gt;&lt;td&gt;MQTT + web_server + many entities&lt;/td&gt;&lt;td&gt;&lt;strong&gt;-832 B&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;+488 B free&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ESP32 IDF&lt;/td&gt;&lt;td&gt;large (climate, BLE, web_server)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;-456 B&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ESP32 IDF&lt;/td&gt;&lt;td&gt;minimal&lt;/td&gt;&lt;td&gt;&lt;strong&gt;-212 B&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;+72 B free&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;All callback registration methods (&lt;code dir=&quot;auto&quot;&gt;add_on_*_callback&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;add_*_callback&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;register_listener&lt;/code&gt;, etc.) on entity base classes and components are now templates instead of taking &lt;code dir=&quot;auto&quot;&gt;std::function&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;(std::function&amp;#x3C;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;)&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;callback&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;state_callback_&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;move&lt;/span&gt;&lt;span&gt;(callback));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;typename&lt;/span&gt;&lt;span&gt; F&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;(F &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;callback&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;state_callback_&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;forward&lt;/span&gt;&lt;span&gt;&amp;#x3C;F&gt;(callback));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External components that define their own callback registration methods taking &lt;code dir=&quot;auto&quot;&gt;std::function&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Existing code &lt;strong&gt;continues to compile and work correctly&lt;/strong&gt;. However, unconverted methods use a less efficient heap-allocation path: the lambda gets wrapped in &lt;code dir=&quot;auto&quot;&gt;std::function&lt;/code&gt; before reaching &lt;code dir=&quot;auto&quot;&gt;CallbackManager::add()&lt;/code&gt;, which then heap-allocates it. Previously the &lt;code dir=&quot;auto&quot;&gt;std::function&lt;/code&gt; was stored directly in the vector element with no extra allocation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Callers of entity callbacks (registering callbacks on ESPHome entities) need no changes.&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Convert callback registration methods from &lt;code dir=&quot;auto&quot;&gt;std::function&lt;/code&gt; to templates:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;your_component.h&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before (still compiles, but now less efficient than before)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_on_data_callback&lt;/span&gt;&lt;span&gt;(std::function&amp;#x3C;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;)&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;callback&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;data_callback_&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;move&lt;/span&gt;&lt;span&gt;(callback));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;protected:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CallbackManager&lt;/span&gt;&lt;span&gt;&amp;#x3C;void&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; data_callback_;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;your_component.h&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After (optimal — inline storage, zero heap allocation for [this] lambdas)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;typename&lt;/span&gt;&lt;span&gt; F&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_on_data_callback&lt;/span&gt;&lt;span&gt;(F &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;callback&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;data_callback_&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(std::&lt;/span&gt;&lt;span&gt;forward&lt;/span&gt;&lt;span&gt;&amp;#x3C;F&gt;(callback));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;protected:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CallbackManager&lt;/span&gt;&lt;span&gt;&amp;#x3C;void&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; data_callback_;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you had a separate &lt;code dir=&quot;auto&quot;&gt;.cpp&lt;/code&gt; definition, move the body inline into the header and remove the &lt;code dir=&quot;auto&quot;&gt;.cpp&lt;/code&gt; definition (templates must be defined in headers).&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;supporting-multiple-esphome-versions&quot;&gt;Supporting Multiple ESPHome Versions&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The template approach is backward compatible — it works on older ESPHome versions too, since the template accepts &lt;code dir=&quot;auto&quot;&gt;std::function&lt;/code&gt; as well as raw lambdas. No version guard needed.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.4.0 (April 2026):&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;CallbackManager&lt;/code&gt; uses &lt;code dir=&quot;auto&quot;&gt;Callback&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;std::function&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;No deprecation period — existing code still compiles but uses a less efficient path&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find callback registration methods that still use std::function&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;std::function.*&amp;#x26;&amp;#x26;.*callback&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14853&quot;&gt;PR #14853: Replace std::function with lightweight Callback in CallbackManager&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Climate and Fan Custom Mode Vectors Moved to Entity</title><link>https://developers.esphome.io/blog/2026/04/09/climate-and-fan-custom-mode-vectors-moved-to-entity/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/04/09/climate-and-fan-custom-mode-vectors-moved-to-entity/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Custom mode vectors (&lt;code dir=&quot;auto&quot;&gt;custom_fan_modes&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;custom_presets&lt;/code&gt; for climate; &lt;code dir=&quot;auto&quot;&gt;preset_modes&lt;/code&gt; for fan) are now stored on the entity base class instead of being rebuilt inside traits on every call. The old &lt;code dir=&quot;auto&quot;&gt;ClimateTraits&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;FanTraits&lt;/code&gt; setter methods are deprecated.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.4.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15206&quot;&gt;PR #15206&lt;/a&gt;:&lt;/strong&gt; Climate — Store custom mode vectors on Climate entity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15209&quot;&gt;PR #15209&lt;/a&gt;:&lt;/strong&gt; Fan — Store preset mode vector on Fan entity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;ClimateTraits&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;FanTraits&lt;/code&gt; contained &lt;code dir=&quot;auto&quot;&gt;std::vector&lt;/code&gt; members that were reconstructed on every traits call. Climate uses &lt;code dir=&quot;auto&quot;&gt;traits()&lt;/code&gt; and Fan uses &lt;code dir=&quot;auto&quot;&gt;get_traits()&lt;/code&gt; — both are called during &lt;code dir=&quot;auto&quot;&gt;publish_state()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;control()&lt;/code&gt;, the hottest paths in these components. This caused &lt;strong&gt;heap allocations on every state update&lt;/strong&gt;. On long-running ESP devices, repeated allocations of different sizes fragment the heap and can lead to crashes.&lt;/p&gt;
&lt;p&gt;Moving the vectors to the entity base class eliminates all heap allocation from traits copies.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;climate&quot;&gt;Climate&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Custom fan modes and custom presets are now set directly on the &lt;code dir=&quot;auto&quot;&gt;Climate&lt;/code&gt; entity:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before — set on traits in traits() override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;climate::ClimateTraits &lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; traits &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; climate::&lt;/span&gt;&lt;span&gt;ClimateTraits&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set_supported_custom_fan_modes&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Low&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Medium&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;High&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set_supported_custom_presets&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Eco&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Sleep&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; traits;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — set once during setup or codegen&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;set_supported_custom_fan_modes&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Low&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Medium&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;High&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;set_supported_custom_presets&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Eco&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Sleep&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The old &lt;code dir=&quot;auto&quot;&gt;ClimateTraits::set_supported_custom_fan_modes()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;ClimateTraits::set_supported_custom_presets()&lt;/code&gt; methods still work but are deprecated and will be removed in &lt;strong&gt;2026.11.0&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;fan&quot;&gt;Fan&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Preset modes are now set directly on the &lt;code dir=&quot;auto&quot;&gt;Fan&lt;/code&gt; entity. Unlike Climate, &lt;code dir=&quot;auto&quot;&gt;Fan::get_traits()&lt;/code&gt; is the virtual method itself, so the &lt;code dir=&quot;auto&quot;&gt;get_traits()&lt;/code&gt; override must wire the stored preset modes into the returned traits via &lt;code dir=&quot;auto&quot;&gt;wire_preset_modes_()&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before — set on traits in get_traits() override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fan::FanTraits &lt;/span&gt;&lt;span&gt;get_traits&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; fan::&lt;/span&gt;&lt;span&gt;FanTraits&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;preset_modes_&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — set once during setup or codegen, wire into traits in get_traits()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;set_supported_preset_modes&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Auto&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Sleep&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Nature&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fan::FanTraits &lt;/span&gt;&lt;span&gt;get_traits&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fan::FanTraits &lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;wire_preset_modes_&lt;/span&gt;&lt;span&gt;(traits);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; traits;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The old &lt;code dir=&quot;auto&quot;&gt;FanTraits::set_supported_preset_modes()&lt;/code&gt; methods still work but are deprecated and will be removed in &lt;strong&gt;2026.11.0&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External climate components&lt;/strong&gt; (~35 affected) that call &lt;code dir=&quot;auto&quot;&gt;ClimateTraits::set_supported_custom_fan_modes()&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;ClimateTraits::set_supported_custom_presets()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;External fan components&lt;/strong&gt; (~15 affected) that pass preset modes through &lt;code dir=&quot;auto&quot;&gt;FanTraits&lt;/code&gt; constructors or call &lt;code dir=&quot;auto&quot;&gt;FanTraits::set_supported_preset_modes()&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;climate-1&quot;&gt;Climate&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before — in traits() override, called on every publish_state()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;climate::ClimateTraits &lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; traits &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; climate::&lt;/span&gt;&lt;span&gt;ClimateTraits&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set_supported_custom_fan_modes&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Low&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Medium&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;High&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set_supported_custom_presets&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Eco&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Sleep&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ... other traits&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; traits;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — set once, traits() gets them automatically&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;set_supported_custom_fan_modes&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Low&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Medium&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;High&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;set_supported_custom_presets&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Eco&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Sleep&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;climate::ClimateTraits &lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; traits &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; climate::&lt;/span&gt;&lt;span&gt;ClimateTraits&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// custom_fan_modes and custom_presets are wired automatically&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ... other traits&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; traits;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;fan-1&quot;&gt;Fan&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fan::FanTraits &lt;/span&gt;&lt;span&gt;get_traits&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; fan::&lt;/span&gt;&lt;span&gt;FanTraits&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;preset_modes_&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — get_traits() override must call wire_preset_modes_() to expose them&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;set_supported_preset_modes&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Auto&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Sleep&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Nature&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fan::FanTraits &lt;/span&gt;&lt;span&gt;get_traits&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fan::FanTraits &lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;wire_preset_modes_&lt;/span&gt;&lt;span&gt;(traits);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; traits;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Unlike Climate — where the base class’s non-virtual &lt;code dir=&quot;auto&quot;&gt;get_traits()&lt;/code&gt; wraps a virtual &lt;code dir=&quot;auto&quot;&gt;traits()&lt;/code&gt; and wires custom modes automatically — &lt;code dir=&quot;auto&quot;&gt;Fan::get_traits()&lt;/code&gt; is itself the virtual method overridden by components. The base class has no opportunity to inject preset modes, so each override must call &lt;code dir=&quot;auto&quot;&gt;wire_preset_modes_()&lt;/code&gt; explicitly. The internal &lt;code dir=&quot;auto&quot;&gt;speed&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;template&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;hbridge&lt;/code&gt; fan components follow this pattern.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h3 id=&quot;python-codegen&quot;&gt;Python codegen&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Before — setting custom modes via traits in C++ generated code&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cg.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;traits.&lt;/span&gt;&lt;span&gt;set_supported_custom_fan_modes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;modes&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# After — setting custom modes directly on the entity&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cg.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var.&lt;/span&gt;&lt;span&gt;set_supported_custom_fan_modes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;modes&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.4.0 (April 2026):&lt;/strong&gt; Old traits setters deprecated&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.11.0 (November 2026):&lt;/strong&gt; Deprecated setters removed, traits become trivially copyable&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Climate — find traits-based custom mode setters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;set_supported_custom_fan_modes\|set_supported_custom_presets&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Fan — find traits-based preset mode setters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;set_supported_preset_modes&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15206&quot;&gt;PR #15206: Climate — Store custom mode vectors on Climate entity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15209&quot;&gt;PR #15209: Fan — Store preset mode vector on Fan entity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>FlushResult Renamed to UARTFlushResult</title><link>https://developers.esphome.io/blog/2026/04/09/flushresult-renamed-to-uartflushresult/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/04/09/flushresult-renamed-to-uartflushresult/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;FlushResult&lt;/code&gt; enum introduced in ESPHome 2026.3.0 has been renamed to &lt;code dir=&quot;auto&quot;&gt;UARTFlushResult&lt;/code&gt; with prefixed enum values to follow ESPHome’s naming conventions and avoid macro collisions.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.4.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15101&quot;&gt;PR #15101&lt;/a&gt;: Rename FlushResult to UARTFlushResult with UART_FLUSH_RESULT_ prefix&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The original &lt;code dir=&quot;auto&quot;&gt;FlushResult::SUCCESS&lt;/code&gt; value collided with the Realtek RTL SDK’s &lt;code dir=&quot;auto&quot;&gt;#define SUCCESS 0&lt;/code&gt; macro, requiring ugly &lt;code dir=&quot;auto&quot;&gt;#pragma push_macro&lt;/code&gt; workarounds. Renaming to &lt;code dir=&quot;auto&quot;&gt;UARTFlushResult&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;UART_FLUSH_RESULT_&lt;/code&gt; prefixed values follows ESPHome’s conventional component-prefix naming style (matching &lt;code dir=&quot;auto&quot;&gt;UARTParityOptions&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;UART_CONFIG_PARITY_NONE&lt;/code&gt;) and eliminates the collision entirely.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Before&lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;FlushResult&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;UARTFlushResult&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;FlushResult::SUCCESS&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;UARTFlushResult::UART_FLUSH_RESULT_SUCCESS&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;FlushResult::TIMEOUT&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;FlushResult::FAILED&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;UARTFlushResult::UART_FLUSH_RESULT_FAILED&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;FlushResult::ASSUMED_SUCCESS&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External components that implement &lt;code dir=&quot;auto&quot;&gt;UARTComponent::flush()&lt;/code&gt; and reference the &lt;code dir=&quot;auto&quot;&gt;FlushResult&lt;/code&gt; type or enum values.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This only affects components updated for the 2026.3.0 &lt;code dir=&quot;auto&quot;&gt;flush()&lt;/code&gt; return type change. If you haven’t updated your &lt;code dir=&quot;auto&quot;&gt;flush()&lt;/code&gt; override yet, see the &lt;a href=&quot;https://developers.esphome.io/blog/2026/03/12/uart-flush-now-returns-flushresult/&quot;&gt;2026.3.0 UART flush blog post&lt;/a&gt; first.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before (2026.3.0)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;FlushResult &lt;/span&gt;&lt;span&gt;flush&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; FlushResult::ASSUMED_SUCCESS;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After (2026.4.0)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;UARTFlushResult &lt;/span&gt;&lt;span&gt;flush&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;supporting-multiple-esphome-versions&quot;&gt;Supporting Multiple ESPHome Versions&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ESPHOME_VERSION_CODE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VERSION_CODE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2026&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;UARTFlushResult &lt;/span&gt;&lt;span&gt;flush&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; UARTFlushResult::UART_FLUSH_RESULT_ASSUMED_SUCCESS;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#elif&lt;/span&gt;&lt;span&gt; ESPHOME_VERSION_CODE &lt;/span&gt;&lt;span&gt;&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VERSION_CODE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2026&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;FlushResult &lt;/span&gt;&lt;span&gt;flush&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; FlushResult::ASSUMED_SUCCESS;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;flush&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.3.0 (March 2026):&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;FlushResult&lt;/code&gt; introduced&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.4.0 (April 2026):&lt;/strong&gt; Renamed to &lt;code dir=&quot;auto&quot;&gt;UARTFlushResult&lt;/code&gt; with prefixed values&lt;/li&gt;
&lt;li&gt;No deprecation period — this is a rename of a type introduced one release ago&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find FlushResult references in your component&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;FlushResult&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15101&quot;&gt;PR #15101: Rename FlushResult to UARTFlushResult&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14608&quot;&gt;PR #14608: UART flush() return type change (2026.3.0)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Modbus Helper Functions Moved to modbus::helpers</title><link>https://developers.esphome.io/blog/2026/04/09/modbus-helper-functions-moved-to-modbushelpers/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/04/09/modbus-helper-functions-moved-to-modbushelpers/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Shared helper functions and types have been moved from &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;modbus::helpers&lt;/code&gt; to enable reuse by other modbus-based components. Deprecated shims keep the old names working until &lt;strong&gt;ESPHome 2026.10.0&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;developer breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.4.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15291&quot;&gt;PR #15291&lt;/a&gt;: Share helper functions across modbus components — part A&lt;/strong&gt;
&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14172&quot;&gt;PR #14172&lt;/a&gt;: Share helper functions across modbus components — part B&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These PRs are part of a series preparing for a new &lt;code dir=&quot;auto&quot;&gt;modbus_server&lt;/code&gt; component. Helper functions that were previously internal to &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt; are now in &lt;code dir=&quot;auto&quot;&gt;modbus::helpers&lt;/code&gt; so they can be shared across &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;modbus_server&lt;/code&gt;, and other modbus-based components.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;c-symbols-moved-part-a&quot;&gt;C++ symbols moved (Part A)&lt;/h3&gt;&lt;/div&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Old location&lt;/th&gt;&lt;th&gt;New location&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::SensorValueType&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::SensorValueType&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::value_type_is_float()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::value_type_is_float()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::modbus_register_read_function()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::modbus_register_read_function()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::modbus_register_write_function()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::modbus_register_write_function()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::c_to_hex()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::c_to_hex()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::byte_from_hex_str()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::byte_from_hex_str()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::word_from_hex_str()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::word_from_hex_str()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::dword_from_hex_str()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::dword_from_hex_str()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::qword_from_hex_str()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::qword_from_hex_str()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;c-symbols-moved-part-b&quot;&gt;C++ symbols moved (Part B)&lt;/h3&gt;&lt;/div&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Old location&lt;/th&gt;&lt;th&gt;New location&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::number_to_payload()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::number_to_payload()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::payload_to_number()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::payload_to_number()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::get_data()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::get_data()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::float_to_payload()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::float_to_payload()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::coil_from_vector()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::coil_from_vector()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus_controller::mask_and_shift_by_rightbit()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;modbus::helpers::mask_and_shift_by_rightbit()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;python-symbols-moved&quot;&gt;Python symbols moved&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The following Python symbols have moved from &lt;code dir=&quot;auto&quot;&gt;esphome.components.modbus_controller&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;esphome.components.modbus.helpers&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;MODBUS_FUNCTION_CODE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;MODBUS_REGISTER_TYPE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;MODBUS_WRITE_REGISTER_TYPE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;SENSOR_VALUE_TYPE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;TYPE_REGISTER_MAP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;CPP_TYPE_REGISTER_MAP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ModbusRegisterType&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External components that import helper functions from &lt;code dir=&quot;auto&quot;&gt;modbus_controller&lt;/code&gt;&lt;/strong&gt;, either in C++ (&lt;code dir=&quot;auto&quot;&gt;modbus_controller::number_to_payload()&lt;/code&gt;, etc.) or Python (&lt;code dir=&quot;auto&quot;&gt;from esphome.components.modbus_controller import SENSOR_VALUE_TYPE&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;c&quot;&gt;C++&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Deprecated shims in &lt;code dir=&quot;auto&quot;&gt;modbus_controller.h&lt;/code&gt; forward to the new location — existing code continues to compile with deprecation warnings. A &lt;code dir=&quot;auto&quot;&gt;using&lt;/code&gt; declaration keeps &lt;code dir=&quot;auto&quot;&gt;modbus_controller::SensorValueType&lt;/code&gt; working without warnings.&lt;/p&gt;
&lt;p&gt;To silence warnings, update the namespace:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;esphome/components/modbus_controller/modbus_controller.h&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; modbus_controller::&lt;/span&gt;&lt;span&gt;payload_to_number&lt;/span&gt;&lt;span&gt;(data, offset, type);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;esphome/components/modbus/helpers.h&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; modbus::helpers::&lt;/span&gt;&lt;span&gt;payload_to_number&lt;/span&gt;&lt;span&gt;(data, offset, type);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;python&quot;&gt;Python&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; esphome.components.modbus_controller &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SENSOR_VALUE_TYPE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; esphome.components.modbus.helpers &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SENSOR_VALUE_TYPE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.4.0 (April 2026):&lt;/strong&gt; Functions moved, deprecated shims added&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.10.0 (October 2026):&lt;/strong&gt; Deprecated shims removed&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# C++ — find references to old namespace&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;modbus_controller::number_to_payload\|modbus_controller::payload_to_number&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;modbus_controller::get_data\|modbus_controller::float_to_payload&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;modbus_controller::c_to_hex\|modbus_controller::byte_from_hex_str&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;modbus_controller::SensorValueType&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Python — find old imports&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;from esphome.components.modbus_controller import&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15291&quot;&gt;PR #15291: Share helper functions across modbus components — part A&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14172&quot;&gt;PR #14172: Share helper functions across modbus components — part B&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Sensor .raw_state Deprecated in Favor of get_raw_state()</title><link>https://developers.esphome.io/blog/2026/04/09/sensor-raw_state-deprecated-in-favor-of-get_raw_state/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/04/09/sensor-raw_state-deprecated-in-favor-of-get_raw_state/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Direct access to the sensor &lt;code dir=&quot;auto&quot;&gt;.raw_state&lt;/code&gt; member is now deprecated. Use &lt;code dir=&quot;auto&quot;&gt;get_raw_state()&lt;/code&gt; instead. The &lt;code dir=&quot;auto&quot;&gt;.raw_state&lt;/code&gt; member will be removed in &lt;strong&gt;ESPHome 2026.10.0&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.4.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15094&quot;&gt;PR #15094&lt;/a&gt;: Deprecate .raw_state, guard raw_callback_ behind USE_SENSOR_FILTER&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This follows the same pattern as the &lt;a href=&quot;https://github.com/esphome/esphome/pull/12246&quot;&gt;text_sensor &lt;code dir=&quot;auto&quot;&gt;.raw_state&lt;/code&gt; deprecation&lt;/a&gt;. Accessor methods allow future optimizations to the internal storage without breaking callers.&lt;/p&gt;
&lt;p&gt;Additionally, &lt;code dir=&quot;auto&quot;&gt;raw_callback_&lt;/code&gt; is now guarded behind &lt;code dir=&quot;auto&quot;&gt;USE_SENSOR_FILTER&lt;/code&gt;, saving 4 bytes per sensor instance when no filters are configured.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;(my_sensor).&lt;/span&gt;&lt;span&gt;raw_state&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;(my_sensor).&lt;/span&gt;&lt;span&gt;get_raw_state&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;.state&lt;/code&gt; member is &lt;strong&gt;not&lt;/strong&gt; deprecated — it remains a valid public member. However, examples in the docs now show &lt;code dir=&quot;auto&quot;&gt;get_state()&lt;/code&gt; to encourage the accessor pattern.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External components and lambda users that directly access &lt;code dir=&quot;auto&quot;&gt;.raw_state&lt;/code&gt; on sensor instances.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The deprecated member still works but emits a compiler warning. It will be removed in 2026.10.0.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Replace all direct &lt;code dir=&quot;auto&quot;&gt;.raw_state&lt;/code&gt; access with &lt;code dir=&quot;auto&quot;&gt;get_raw_state()&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;(my_sensor).&lt;/span&gt;&lt;span&gt;raw_state&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; current &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;raw_state&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;(my_sensor).&lt;/span&gt;&lt;span&gt;get_raw_state&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; current &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_raw_state&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Nothing outside of &lt;code dir=&quot;auto&quot;&gt;Sensor::publish_state()&lt;/code&gt; should ever write to &lt;code dir=&quot;auto&quot;&gt;raw_state&lt;/code&gt;. If your component does this, it is a bug — use &lt;code dir=&quot;auto&quot;&gt;publish_state()&lt;/code&gt; instead.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;supporting-multiple-esphome-versions&quot;&gt;Supporting Multiple ESPHome Versions&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;get_raw_state()&lt;/code&gt; has been available since before this deprecation (it was previously defined in &lt;code dir=&quot;auto&quot;&gt;.cpp&lt;/code&gt;), so you can simply switch to it without a version guard:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;(my_sensor).&lt;/span&gt;&lt;span&gt;get_raw_state&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.4.0 (April 2026):&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;.raw_state&lt;/code&gt; deprecated with compiler warning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.10.0 (October 2026):&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;.raw_state&lt;/code&gt; member removed&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find direct .raw_state access in your component&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\.raw_state&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;-&gt;raw_state&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15094&quot;&gt;PR #15094: Deprecate .raw_state, guard raw_callback_ behind USE_SENSOR_FILTER&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>TemplatableFn: 4-Byte Templatable Storage for Trivially Copyable Types</title><link>https://developers.esphome.io/blog/2026/04/09/templatablefn-4-byte-templatable-storage-for-trivially-copyable-types/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/04/09/templatablefn-4-byte-templatable-storage-for-trivially-copyable-types/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;TEMPLATABLE_VALUE&lt;/code&gt; macro now uses &lt;code dir=&quot;auto&quot;&gt;TemplatableFn&lt;/code&gt; (4 bytes) instead of &lt;code dir=&quot;auto&quot;&gt;TemplatableValue&lt;/code&gt; (8 bytes) for trivially copyable types like &lt;code dir=&quot;auto&quot;&gt;float&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;uint32_t&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;bool&lt;/code&gt;, and enums. External components that call macro-generated setters with raw C++ constants instead of going through &lt;code dir=&quot;auto&quot;&gt;cg.templatable()&lt;/code&gt; will fail to compile.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.4.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15545&quot;&gt;PR #15545&lt;/a&gt;: Add TemplatableFn for 4-byte function-pointer templatable storage&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every action in ESPHome uses &lt;code dir=&quot;auto&quot;&gt;TEMPLATABLE_VALUE&lt;/code&gt; to store values that can be either constants or lambdas. Previously, each field used &lt;code dir=&quot;auto&quot;&gt;TemplatableValue&lt;/code&gt; (8 bytes: a value slot + function pointer). For trivially copyable types, the value slot is unnecessary — a stateless lambda returning a constant compiles to a plain function pointer and costs only 4 bytes.&lt;/p&gt;
&lt;p&gt;With ~340 &lt;code dir=&quot;auto&quot;&gt;TEMPLATABLE_VALUE&lt;/code&gt; fields across the codebase for trivially copyable types, this saves ~1,360 bytes of RAM.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;three-tier-design&quot;&gt;Three-Tier Design&lt;/h2&gt;&lt;/div&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;Size (32-bit)&lt;/th&gt;&lt;th&gt;Use Case&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;TemplatableFn&amp;#x3C;T, Ts...&gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;4 bytes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Function pointer only. Used by &lt;code dir=&quot;auto&quot;&gt;TEMPLATABLE_VALUE&lt;/code&gt; macro for trivially copyable types.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;TemplatableValue&amp;#x3C;T, Ts...&gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;8 bytes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Value OR function pointer. Full backward compat — accepts raw constants.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;TemplatableValue&amp;#x3C;std::string, Ts...&gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;8 bytes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Full implementation with string paths, stateful lambdas. Unchanged.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;TEMPLATABLE_VALUE&lt;/code&gt; macro automatically selects the type via &lt;code dir=&quot;auto&quot;&gt;TemplatableStorage&amp;#x3C;T, Ts...&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Trivially copyable types&lt;/strong&gt; (&lt;code dir=&quot;auto&quot;&gt;float&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;uint32_t&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;bool&lt;/code&gt;, enums, pointers) → &lt;code dir=&quot;auto&quot;&gt;TemplatableFn&lt;/code&gt; (4 bytes)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-trivially copyable types&lt;/strong&gt; (&lt;code dir=&quot;auto&quot;&gt;std::string&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;std::vector&amp;#x3C;uint8_t&gt;&lt;/code&gt;) → &lt;code dir=&quot;auto&quot;&gt;TemplatableValue&lt;/code&gt; (8 bytes)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;1-external-components-calling-templatable_value-setters-with-raw-values&quot;&gt;1. External components calling TEMPLATABLE_VALUE setters with raw values&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If your Python codegen calls a macro-generated setter directly with a raw C++ constant instead of going through &lt;code dir=&quot;auto&quot;&gt;cg.templatable()&lt;/code&gt;, it will fail to compile:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# This will fail — raw value passed to TemplatableFn setter&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cg.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;action.&lt;/span&gt;&lt;span&gt;set_brightness&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# This works — cg.templatable wraps constants in stateless lambdas&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cg.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;action.&lt;/span&gt;&lt;span&gt;set_brightness&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; cg.&lt;/span&gt;&lt;span&gt;templatable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;CONF_BRIGHTNESS&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[],&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;2-external-components-using-templatablevalue-for-non-string-types-with-stateful-lambdas&quot;&gt;2. External components using TemplatableValue for non-string types with stateful lambdas&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;TemplatableValue&amp;#x3C;T, Ts...&gt;&lt;/code&gt; for non-string types no longer accepts stateful lambdas (lambdas with captures). A GitHub code search found no external components using this pattern. If needed, use &lt;code dir=&quot;auto&quot;&gt;std::function&amp;#x3C;T(Ts...)&gt;&lt;/code&gt; directly.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-codegen-route-values-through-cgtemplatable&quot;&gt;Fix codegen: route values through cg.templatable()&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Before — raw value passed to setter (broken)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cg.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;action.&lt;/span&gt;&lt;span&gt;set_value&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;CONF_VALUE&lt;/span&gt;&lt;span&gt;]))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# After — value wrapped via cg.templatable (correct)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# args is the list of template arguments, e.g. [(float, &quot;x&quot;)] or []&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;template_ &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; cg.&lt;/span&gt;&lt;span&gt;templatable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;CONF_VALUE&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; args&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cg.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;action.&lt;/span&gt;&lt;span&gt;set_value&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;template_&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This was technically incorrect before — the setter is part of the codegen API and should only receive values produced by &lt;code dir=&quot;auto&quot;&gt;cg.templatable()&lt;/code&gt;. The fix is straightforward.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;optional-migrate-direct-templatablevalue-usage-to-templatablefn&quot;&gt;Optional: migrate direct TemplatableValue usage to TemplatableFn&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If your component uses &lt;code dir=&quot;auto&quot;&gt;TemplatableValue&lt;/code&gt; directly (not via the macro), it continues to work without changes. To opt into the 4-byte savings:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before (8 bytes, still works)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;TemplatableValue&lt;/span&gt;&lt;span&gt;&amp;#x3C;float&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;float&gt;&lt;/span&gt;&lt;span&gt; min_{NAN};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After (4 bytes)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;TemplatableFn&lt;/span&gt;&lt;span&gt;&amp;#x3C;float&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;float&gt;&lt;/span&gt;&lt;span&gt; min_{[]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; -&gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; NAN; }};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before (8 bytes, still works)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;TemplatableValue&lt;/span&gt;&lt;span&gt;&amp;#x3C;uint16_t&gt;&lt;/span&gt;&lt;span&gt; port_{&lt;/span&gt;&lt;span&gt;80&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After (4 bytes)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;TemplatableFn&lt;/span&gt;&lt;span&gt;&amp;#x3C;uint16_t&gt;&lt;/span&gt;&lt;span&gt; port_{[]&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; -&gt; &lt;/span&gt;&lt;span&gt;uint16_t&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;80&lt;/span&gt;&lt;span&gt;; }};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;TemplatableFn&lt;/code&gt; has the same API as &lt;code dir=&quot;auto&quot;&gt;TemplatableValue&lt;/code&gt; (&lt;code dir=&quot;auto&quot;&gt;has_value()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;value()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;optional_value()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;value_or()&lt;/code&gt;), so no other code changes are needed.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;stateful-lambdas-on-non-string-types&quot;&gt;Stateful lambdas on non-string types&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before (hidden heap allocation via TemplatableValue)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; captured &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;42&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;TemplatableValue&lt;/span&gt;&lt;span&gt;&amp;#x3C;int&gt;&lt;/span&gt;&lt;span&gt; val &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;captured&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; captured; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After (explicit about the allocation)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;std::function&lt;/span&gt;&lt;span&gt;&amp;#x3C;int&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; val &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;captured&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; captured; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;TemplatableValue&amp;#x3C;std::string, Ts...&gt;&lt;/code&gt; still supports stateful lambdas — no changes needed for string types.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.4.0 (April 2026):&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;TEMPLATABLE_VALUE&lt;/code&gt; macro uses &lt;code dir=&quot;auto&quot;&gt;TemplatableFn&lt;/code&gt; for trivially copyable types&lt;/li&gt;
&lt;li&gt;No deprecation period for the macro change — &lt;code dir=&quot;auto&quot;&gt;TemplatableValue&lt;/code&gt; used directly is fully backward compatible&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find TEMPLATABLE_VALUE setters called without cg.templatable()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;cg.add.*set_.*config\[&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/__init__.py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find TemplatableValue usage that could migrate to TemplatableFn&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;TemplatableValue&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.esphome.io/architecture/components/automations&quot;&gt;Implementing Automations&lt;/a&gt; covers the full &lt;code dir=&quot;auto&quot;&gt;TEMPLATABLE_VALUE&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;cg.templatable()&lt;/code&gt; pairing for actions&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15545&quot;&gt;PR #15545: Add TemplatableFn for 4-byte function-pointer templatable storage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Trigger Trampolines Eliminated with build_callback_automation</title><link>https://developers.esphome.io/blog/2026/04/09/trigger-trampolines-eliminated-with-build_callback_automation/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/04/09/trigger-trampolines-eliminated-with-build_callback_automation/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Common entity trigger classes have been replaced with lightweight forwarder structs that fit inline in the callback system. The new &lt;code dir=&quot;auto&quot;&gt;build_callback_automation()&lt;/code&gt; API eliminates per-trigger object allocations. Several entity callback signatures have also changed to pass state as an argument.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.4.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15174&quot;&gt;PR #15174&lt;/a&gt;:&lt;/strong&gt; Eliminate trigger trampolines with deduplicated forwarder structs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15198&quot;&gt;PR #15198&lt;/a&gt;:&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;alarm_control_panel&lt;/code&gt; — Migrate triggers to callback automation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15199&quot;&gt;PR #15199&lt;/a&gt;:&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;lock&lt;/code&gt; — Migrate LockStateTrigger to callback automation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15200&quot;&gt;PR #15200&lt;/a&gt;:&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;media_player&lt;/code&gt; — Migrate triggers to callback automation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Previously, each automation trigger created a separate C++ object that existed solely to forward a callback to an &lt;code dir=&quot;auto&quot;&gt;Automation&lt;/code&gt;. For example:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;button press → callback → ButtonPressTrigger::trigger() → Automation::trigger()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Now a lightweight forwarder struct collapses this into the callback itself:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;button press → callback → TriggerForwarder::operator()() → Automation::trigger()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The forwarder fits in the &lt;code dir=&quot;auto&quot;&gt;Callback::ctx_&lt;/code&gt; field — no additional storage needed.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;memory-savings&quot;&gt;Memory savings&lt;/h3&gt;&lt;/div&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Config&lt;/th&gt;&lt;th&gt;Platform&lt;/th&gt;&lt;th&gt;RAM Saved&lt;/th&gt;&lt;th&gt;Flash Saved&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;ratgdo (garage door)&lt;/td&gt;&lt;td&gt;ESP8266&lt;/td&gt;&lt;td&gt;&lt;strong&gt;88 bytes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;224 bytes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;multi-sensor device&lt;/td&gt;&lt;td&gt;ESP32-IDF&lt;/td&gt;&lt;td&gt;&lt;strong&gt;208 bytes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;280 bytes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;1-callback-signatures-changed&quot;&gt;1. Callback signatures changed&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Several entity callback signatures changed to pass state as an argument, enabling single-pointer forwarders:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// alarm_control_panel — Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;(std::function&amp;#x3C;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;()&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;callback&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;typename&lt;/span&gt;&lt;span&gt; F&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;(F &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;callback&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Callback signature: void(AlarmControlPanelState)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// lock — Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;(std::function&amp;#x3C;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;()&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;callback&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;typename&lt;/span&gt;&lt;span&gt; F&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;(F &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;callback&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Callback signature: void(LockState)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// media_player — Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;(std::function&amp;#x3C;&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;()&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;callback&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;typename&lt;/span&gt;&lt;span&gt; F&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;(F &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt;&lt;span&gt;callback&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Callback signature: void(MediaPlayerState)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;2-automationtrigger_-field-removed&quot;&gt;2. Automation::trigger_ field removed&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;trigger_&lt;/code&gt; protected field on &lt;code dir=&quot;auto&quot;&gt;Automation&lt;/code&gt; (set in constructor, never read) has been removed.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;External components registering callbacks on &lt;code dir=&quot;auto&quot;&gt;alarm_control_panel&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;lock&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;media_player&lt;/code&gt;&lt;/strong&gt; — must update callback signature to accept the state parameter&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External components accessing &lt;code dir=&quot;auto&quot;&gt;Automation::trigger_&lt;/code&gt;&lt;/strong&gt; — this field no longer exists&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;callback-signature-changes&quot;&gt;Callback signature changes&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// alarm_control_panel — Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;parent_&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;parent_&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_state&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;parent_&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;AlarmControlPanelState &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// state is passed directly, no need to call get_state()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// lock — Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;parent_&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;parent_&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;parent_&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;LockState &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// state is passed directly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// media_player — Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;parent_&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;parent_&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;parent_&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;MediaPlayerState &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// state is passed directly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;python-codegen-migration&quot;&gt;Python codegen migration&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If your external component uses &lt;code dir=&quot;auto&quot;&gt;build_automation()&lt;/code&gt; with trigger classes, migrate to &lt;code dir=&quot;auto&quot;&gt;build_callback_automations()&lt;/code&gt; (&lt;a href=&quot;https://github.com/esphome/esphome/pull/15246&quot;&gt;PR #15246&lt;/a&gt;):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; esphome &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; automation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;MyStateTrigger &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; my_ns.&lt;/span&gt;&lt;span&gt;class_&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;MyStateTrigger&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; automation.Trigger.&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cg.bool_&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;MyPressTrigger &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; my_ns.&lt;/span&gt;&lt;span&gt;class_&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;MyPressTrigger&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; automation.Trigger.&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;CONFIG_SCHEMA&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv.&lt;/span&gt;&lt;span&gt;Schema&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cv.&lt;/span&gt;&lt;span&gt;Optional&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONF_ON_STATE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;: automation.&lt;/span&gt;&lt;span&gt;validate_automation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{cv.&lt;/span&gt;&lt;span&gt;GenerateID&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONF_TRIGGER_ID&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;: cv.&lt;/span&gt;&lt;span&gt;declare_id&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;MyStateTrigger&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cv.&lt;/span&gt;&lt;span&gt;Optional&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONF_ON_PRESS&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;: automation.&lt;/span&gt;&lt;span&gt;validate_automation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{cv.&lt;/span&gt;&lt;span&gt;GenerateID&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONF_TRIGGER_ID&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;: cv.&lt;/span&gt;&lt;span&gt;declare_id&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;MyPressTrigger&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to_code&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; conf &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; config.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONF_ON_STATE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;trigger &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cg.&lt;/span&gt;&lt;span&gt;new_Pvariable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;conf&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;CONF_TRIGGER_ID&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; automation.&lt;/span&gt;&lt;span&gt;build_automation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;trigger&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;span&gt; conf&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; conf &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; config.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONF_ON_PRESS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;trigger &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cg.&lt;/span&gt;&lt;span&gt;new_Pvariable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;conf&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;CONF_TRIGGER_ID&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; automation.&lt;/span&gt;&lt;span&gt;build_automation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;trigger&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[],&lt;/span&gt;&lt;span&gt; conf&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# After — no trigger classes needed, no CONF_TRIGGER_ID in schema&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; esphome &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; automation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;_CALLBACK_AUTOMATIONS&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;automation.&lt;/span&gt;&lt;span&gt;CallbackAutomation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONF_ON_STATE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;automation.&lt;/span&gt;&lt;span&gt;CallbackAutomation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONF_ON_PRESS&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;add_on_press_callback&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;CONFIG_SCHEMA&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cv.&lt;/span&gt;&lt;span&gt;Schema&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cv.&lt;/span&gt;&lt;span&gt;Optional&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONF_ON_STATE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;: automation.&lt;/span&gt;&lt;span&gt;validate_automation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cv.&lt;/span&gt;&lt;span&gt;Optional&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CONF_ON_PRESS&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;: automation.&lt;/span&gt;&lt;span&gt;validate_automation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to_code&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; automation.&lt;/span&gt;&lt;span&gt;build_callback_automations&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _CALLBACK_AUTOMATIONS&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;build_automation()&lt;/code&gt; and all &lt;code dir=&quot;auto&quot;&gt;Trigger&lt;/code&gt; subclasses remain available for triggers that need mutable state beyond a single &lt;code dir=&quot;auto&quot;&gt;Automation*&lt;/code&gt; pointer.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.4.0 (April 2026):&lt;/strong&gt; Trigger classes removed, callback signatures changed&lt;/li&gt;
&lt;li&gt;No deprecation period — these are signature changes and class removals&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find alarm_control_panel/lock/media_player callback registrations&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;add_on_state_callback&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15174&quot;&gt;PR #15174: Eliminate trigger trampolines with deduplicated forwarder structs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15198&quot;&gt;PR #15198: Migrate alarm_control_panel triggers to callback automation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15199&quot;&gt;PR #15199: Migrate lock triggers to callback automation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15200&quot;&gt;PR #15200: Migrate media_player triggers to callback automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>wake_loop Moved from Socket Component into Core</title><link>https://developers.esphome.io/blog/2026/04/09/wake_loop-moved-from-socket-component-into-core/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/04/09/wake_loop-moved-from-socket-component-into-core/</guid><pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;wake_loop_threadsafe()&lt;/code&gt; and related wake primitives have moved from the socket component into core. The &lt;code dir=&quot;auto&quot;&gt;require_wake_loop_threadsafe()&lt;/code&gt; opt-in is deprecated (now a no-op) — wake works unconditionally on all platforms.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.4.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15446&quot;&gt;PR #15446&lt;/a&gt;: Move wake_loop out of socket component into core&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The wake mechanism originally lived in the socket component because the first implementation used a UDP loopback socket. Since then, every platform has moved to nearly-free primitives (FreeRTOS task notifications, &lt;code dir=&quot;auto&quot;&gt;esp_schedule()&lt;/code&gt;, ARM &lt;code dir=&quot;auto&quot;&gt;__sev()&lt;/code&gt;), and the socket dependency was unnecessary complexity: 12 components needed to call &lt;code dir=&quot;auto&quot;&gt;require_wake_loop_threadsafe()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;#ifdef USE_WAKE_LOOP_THREADSAFE&lt;/code&gt; guards were needed at every call site, and platforms without networking had silent degradation (up to ~16ms latency).&lt;/p&gt;
&lt;p&gt;Now it just works — no opt-in, no defines, no guards.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;platform-wake-mechanisms&quot;&gt;Platform wake mechanisms&lt;/h3&gt;&lt;/div&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Platform&lt;/th&gt;&lt;th&gt;Mechanism&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;ESP32&lt;/td&gt;&lt;td&gt;FreeRTOS task notifications (ISR-safe)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LibreTiny&lt;/td&gt;&lt;td&gt;FreeRTOS task notifications&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ESP8266&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;esp_schedule()&lt;/code&gt; (IRAM-safe)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RP2040&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;__sev()&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;__wfe()&lt;/code&gt; (ARM CPU instructions)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Zephyr (nRF52)&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;k_sem_give()&lt;/code&gt; (ISR-safe) / &lt;code dir=&quot;auto&quot;&gt;k_sem_take()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Host&lt;/td&gt;&lt;td&gt;UDP loopback socket + &lt;code dir=&quot;auto&quot;&gt;select()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;removed-defines&quot;&gt;Removed defines&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;USE_WAKE_LOOP_THREADSAFE&lt;/code&gt; — no longer needed, wake is always available&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;USE_SOCKET_SELECT_SUPPORT&lt;/code&gt; — replaced by &lt;code dir=&quot;auto&quot;&gt;USE_HOST&lt;/code&gt; where needed&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;deprecated-python-function&quot;&gt;Deprecated Python function&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;socket.require_wake_loop_threadsafe()&lt;/code&gt; (Python codegen helper) is deprecated (warns, no-op). Remove calls to it.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;no-more-ifdef-guards&quot;&gt;No more &lt;code dir=&quot;auto&quot;&gt;#ifdef&lt;/code&gt; guards&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;defined&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;USE_SOCKET_SELECT_SUPPORT&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;defined&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;USE_WAKE_LOOP_THREADSAFE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;wake_loop_threadsafe&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;wake_loop_threadsafe&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;socket-dependency-no-longer-needed-for-wake&quot;&gt;Socket dependency no longer needed for wake&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Components no longer need to declare socket as a dependency just for wake functionality:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;AUTO_LOAD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;socket&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# just for wake_loop&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# After — remove if socket was only needed for wake&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# AUTO_LOAD = []&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;External components calling &lt;code dir=&quot;auto&quot;&gt;socket.require_wake_loop_threadsafe()&lt;/code&gt;&lt;/strong&gt; in Python codegen — remove the call&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External components using &lt;code dir=&quot;auto&quot;&gt;#ifdef USE_WAKE_LOOP_THREADSAFE&lt;/code&gt;&lt;/strong&gt; — remove the guard&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External components with socket in &lt;code dir=&quot;auto&quot;&gt;AUTO_LOAD&lt;/code&gt; only for wake&lt;/strong&gt; — remove it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External components checking &lt;code dir=&quot;auto&quot;&gt;USE_SOCKET_SELECT_SUPPORT&lt;/code&gt;&lt;/strong&gt; — use &lt;code dir=&quot;auto&quot;&gt;USE_HOST&lt;/code&gt; instead&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;c--remove-ifdef-guards&quot;&gt;C++ — remove &lt;code dir=&quot;auto&quot;&gt;#ifdef&lt;/code&gt; guards&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;some_background_thread&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;defined&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;USE_SOCKET_SELECT_SUPPORT&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;defined&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;USE_WAKE_LOOP_THREADSAFE&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;wake_loop_threadsafe&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;some_background_thread&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;wake_loop_threadsafe&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;python-codegen--remove-opt-in-call-and-socket-auto_load&quot;&gt;Python codegen — remove opt-in call and socket AUTO_LOAD&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; esphome.components &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; socket&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;AUTO_LOAD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;socket&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to_code&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;socket.&lt;/span&gt;&lt;span&gt;require_wake_loop_threadsafe&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# ... rest of codegen&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# After — remove socket AUTO_LOAD and require call&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;to_code&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# ... rest of codegen (no wake opt-in needed)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.4.0 (April 2026):&lt;/strong&gt; Wake moved to core, &lt;code dir=&quot;auto&quot;&gt;require_wake_loop_threadsafe()&lt;/code&gt; deprecated&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;USE_WAKE_LOOP_THREADSAFE&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;USE_SOCKET_SELECT_SUPPORT&lt;/code&gt; defines removed immediately&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find require_wake_loop_threadsafe calls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;require_wake_loop_threadsafe&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find #ifdef guards&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;USE_WAKE_LOOP_THREADSAFE&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find USE_SOCKET_SELECT_SUPPORT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;USE_SOCKET_SELECT_SUPPORT&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/15446&quot;&gt;PR #15446: Move wake_loop out of socket component into core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Application Name and Friendly Name Now Return StringRef</title><link>https://developers.esphome.io/blog/2026/03/12/application-name-and-friendly-name-now-return-stringref/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/03/12/application-name-and-friendly-name-now-return-stringref/</guid><pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;App.get_name()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;App.get_friendly_name()&lt;/code&gt; now return &lt;code dir=&quot;auto&quot;&gt;const StringRef &amp;#x26;&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;const std::string &amp;#x26;&lt;/code&gt;. Most code compiles unchanged because &lt;code dir=&quot;auto&quot;&gt;StringRef&lt;/code&gt; provides &lt;code dir=&quot;auto&quot;&gt;.c_str()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.size()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.empty()&lt;/code&gt;, and implicit conversion to &lt;code dir=&quot;auto&quot;&gt;std::string&lt;/code&gt;. However, binding the result to &lt;code dir=&quot;auto&quot;&gt;const std::string &amp;#x26;&lt;/code&gt; silently creates a heap-allocated temporary — update these references to &lt;code dir=&quot;auto&quot;&gt;const auto &amp;#x26;&lt;/code&gt; to avoid the allocation.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.3.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14532&quot;&gt;PR #14532&lt;/a&gt;: Application name and friendly_name return StringRef&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The application name and friendly name are set once at boot and never change. Storing them as &lt;code dir=&quot;auto&quot;&gt;std::string&lt;/code&gt; wastes heap memory for the string object overhead and its dynamic allocation. &lt;code dir=&quot;auto&quot;&gt;StringRef&lt;/code&gt; is a lightweight reference to a string with static lifetime — it avoids the heap allocation entirely.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;memory-savings&quot;&gt;Memory savings&lt;/h3&gt;&lt;/div&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Platform&lt;/th&gt;&lt;th&gt;Flash savings&lt;/th&gt;&lt;th&gt;RAM savings&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;ESP8266&lt;/td&gt;&lt;td&gt;752 bytes&lt;/td&gt;&lt;td&gt;176 bytes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;RP2040&lt;/td&gt;&lt;td&gt;2,508 bytes&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::string &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;get_name&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::string &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;get_friendly_name&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; StringRef &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;get_name&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; StringRef &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;get_friendly_name&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;StringRef&lt;/code&gt; provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;.c_str()&lt;/code&gt; — null-terminated C string pointer&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;.size()&lt;/code&gt; — string length&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;.empty()&lt;/code&gt; — true if empty&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;operator std::string()&lt;/code&gt; — implicit conversion to &lt;code dir=&quot;auto&quot;&gt;std::string&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;operator==&lt;/code&gt; — comparison with string literals and other strings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Most code compiles unchanged.&lt;/strong&gt; The key issue is when the return value is bound to &lt;code dir=&quot;auto&quot;&gt;const std::string &amp;#x26;&lt;/code&gt; — this triggers an implicit conversion that heap-allocates a temporary &lt;code dir=&quot;auto&quot;&gt;std::string&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External components that:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Store &lt;code dir=&quot;auto&quot;&gt;App.get_name()&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;App.get_friendly_name()&lt;/code&gt; in a &lt;code dir=&quot;auto&quot;&gt;const std::string &amp;#x26;&lt;/code&gt; variable&lt;/li&gt;
&lt;li&gt;Pass the result to functions expecting &lt;code dir=&quot;auto&quot;&gt;const std::string &amp;#x26;&lt;/code&gt; (triggers implicit conversion)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Standard YAML configurations are not affected.&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;fix-silent-heap-allocation&quot;&gt;Fix silent heap allocation&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before — compiles but silently heap-allocates a temporary:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::string &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_name&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — zero allocation:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_name&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;passing-to-functions-expecting-stdstring&quot;&gt;Passing to functions expecting std::string&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;If you need to pass the name to a function that takes &lt;code dir=&quot;auto&quot;&gt;const std::string &amp;#x26;&lt;/code&gt;, the implicit conversion works but allocates. If the function also accepts &lt;code dir=&quot;auto&quot;&gt;const char *&lt;/code&gt;, prefer &lt;code dir=&quot;auto&quot;&gt;.c_str()&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Heap-allocates (implicit conversion to std::string)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;some_function_taking_string&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_name&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Zero allocation&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;some_function_taking_cstr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_name&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;c_str&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;logging&quot;&gt;Logging&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Works — c_str() returns null-terminated string&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ESP_LOGD&lt;/span&gt;&lt;span&gt;(TAG, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Name: &lt;/span&gt;&lt;span&gt;%s&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_name&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;c_str&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;supporting-multiple-esphome-versions&quot;&gt;Supporting Multiple ESPHome Versions&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Using &lt;code dir=&quot;auto&quot;&gt;const auto &amp;#x26;&lt;/code&gt; works on all versions — it binds to &lt;code dir=&quot;auto&quot;&gt;const std::string &amp;#x26;&lt;/code&gt; on older versions and &lt;code dir=&quot;auto&quot;&gt;const StringRef &amp;#x26;&lt;/code&gt; on 2026.3.0+:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Safe on all versions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_name&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;ESP_LOGD&lt;/span&gt;&lt;span&gt;(TAG, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Name: &lt;/span&gt;&lt;span&gt;%s&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;c_str&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If you need to explicitly handle both:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ESPHOME_VERSION_CODE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VERSION_CODE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2026&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; StringRef &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_name&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; std::string &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_name&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.3.0 (March 2026):&lt;/strong&gt; Return type changed&lt;/li&gt;
&lt;li&gt;No deprecation period — this is a signature change&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find std::string references to App.get_name()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;std::string.*get_name\|std::string.*get_friendly_name&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find any usage of App.get_name() or App.get_friendly_name()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;App\.get_name\|App\.get_friendly_name\|get_name()\.c_str\|get_friendly_name()\.c_str&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14532&quot;&gt;PR #14532: Application name and friendly_name return StringRef&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/blob/dev/esphome/core/string_ref.h&quot;&gt;StringRef source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>call_loop(), mark_failed(), and call_dump_config() Are No Longer Virtual</title><link>https://developers.esphome.io/blog/2026/03/12/call_loop-mark_failed-and-call_dump_config-are-no-longer-virtual/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/03/12/call_loop-mark_failed-and-call_dump_config-are-no-longer-virtual/</guid><pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;Component::call_loop()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Component::mark_failed()&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;Component::call_dump_config()&lt;/code&gt; are no longer virtual methods. External components that override any of these methods must remove them entirely and move logic to &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;dump_config()&lt;/code&gt;. This saves 800+ bytes of flash globally from vtable elimination.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.3.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14083&quot;&gt;PR #14083&lt;/a&gt;: Devirtualize call_loop() and mark_failed() in Component&lt;/strong&gt;
&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14355&quot;&gt;PR #14355&lt;/a&gt;: Devirtualize call_dump_config()&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These methods were virtual since the original 2019 codebase, designed to let intermediate base classes inject behavior between the framework and the user’s &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;dump_config()&lt;/code&gt; without requiring base class calls. The components that historically used these overrides have all been refactored:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;MQTTComponent::call_loop()&lt;/code&gt;&lt;/strong&gt; — removed in &lt;a href=&quot;https://github.com/esphome/esphome/pull/13356&quot;&gt;#13356&lt;/a&gt; (Jan 2026) when MQTT was restructured&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;ESP32BLE::mark_failed()&lt;/code&gt;&lt;/strong&gt; — removed in &lt;a href=&quot;https://github.com/esphome/esphome/pull/4173&quot;&gt;#4173&lt;/a&gt; (Jan 2023) when BLE was refactored to use event handlers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;MQTTComponent::call_dump_config()&lt;/code&gt;&lt;/strong&gt; — removed in &lt;a href=&quot;https://github.com/esphome/esphome/pull/14355&quot;&gt;#14355&lt;/a&gt;; the override incorrectly skipped &lt;code dir=&quot;auto&quot;&gt;dump_config()&lt;/code&gt; for internal entities&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After those architectural improvements, zero overrides remained. Making these methods non-virtual eliminates one vtable slot per method from every &lt;code dir=&quot;auto&quot;&gt;Component&lt;/code&gt;-derived class (~100+ vtables in a typical build), saving &lt;strong&gt;800+ bytes of flash&lt;/strong&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before (virtual)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;virtual&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call_loop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;virtual&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mark_failed&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;virtual&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call_dump_config&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After (non-virtual)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call_loop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mark_failed&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call_dump_config&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Any class that declares &lt;code dir=&quot;auto&quot;&gt;call_loop() override&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;mark_failed() override&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;call_dump_config() override&lt;/code&gt; will fail to compile.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;External components that override &lt;code dir=&quot;auto&quot;&gt;call_loop()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;mark_failed()&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;call_dump_config()&lt;/code&gt;. No known external components currently override any of these methods — they were virtual with zero overrides in the wild.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Standard YAML configurations are not affected.&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;remove-overrides-and-move-logic-to-loopdump_config&quot;&gt;Remove overrides and move logic to loop()/dump_config()&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Simply removing the &lt;code dir=&quot;auto&quot;&gt;override&lt;/code&gt; keyword is not enough — a same-named non-virtual method in a derived class will never be called by the framework (calls through &lt;code dir=&quot;auto&quot;&gt;Component*&lt;/code&gt; always dispatch to &lt;code dir=&quot;auto&quot;&gt;Component::call_loop()&lt;/code&gt;). You must &lt;strong&gt;remove the method entirely&lt;/strong&gt; and move logic to the appropriate virtual hook.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before — fails to compile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call_loop&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Custom per-loop logic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call_dump_config&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Custom config output&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mark_failed&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Custom failure handling&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — use loop(), dump_config(), and is_failed() instead&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;loop&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Former call_loop() logic goes here&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;is_failed&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Former mark_failed() follow-up handling goes here&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dump_config&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Former call_dump_config() logic goes here&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;supporting-multiple-esphome-versions&quot;&gt;Supporting Multiple ESPHome Versions&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Since &lt;code dir=&quot;auto&quot;&gt;override&lt;/code&gt; on a non-virtual method is a compile error, you need a version guard:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ESPHOME_VERSION_CODE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VERSION_CODE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2026&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// These methods are no longer virtual — do not override&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call_loop&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; /* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mark_failed&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; /* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call_dump_config&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; /* ... */&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.3.0 (March 2026):&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;call_loop()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;mark_failed()&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;call_dump_config()&lt;/code&gt; are no longer virtual — &lt;code dir=&quot;auto&quot;&gt;override&lt;/code&gt; keyword causes compile error&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find call_loop() overrides&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;call_loop().*override&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find mark_failed() overrides&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;mark_failed().*override&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find call_dump_config() overrides&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;call_dump_config().*override&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14083&quot;&gt;PR #14083: Devirtualize call_loop() and mark_failed() in Component&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14355&quot;&gt;PR #14355: Devirtualize call_dump_config()&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>get_loop_priority() Now Conditionally Compiled with USE_LOOP_PRIORITY</title><link>https://developers.esphome.io/blog/2026/03/12/get_loop_priority-now-conditionally-compiled-with-use_loop_priority/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/03/12/get_loop_priority-now-conditionally-compiled-with-use_loop_priority/</guid><pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;get_loop_priority()&lt;/code&gt; virtual method on &lt;code dir=&quot;auto&quot;&gt;Component&lt;/code&gt; is now only available when &lt;code dir=&quot;auto&quot;&gt;USE_LOOP_PRIORITY&lt;/code&gt; is defined. This define is only set on RP2040, the only platform where loop priority has an effect. External components that override &lt;code dir=&quot;auto&quot;&gt;get_loop_priority()&lt;/code&gt; must guard the override with &lt;code dir=&quot;auto&quot;&gt;#ifdef USE_LOOP_PRIORITY&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.3.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14210&quot;&gt;PR #14210&lt;/a&gt;: Conditionally compile get_loop_priority with USE_LOOP_PRIORITY&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;get_loop_priority()&lt;/code&gt; virtual method existed on all platforms, but only had an effect on RP2040 — it controls the order in which components’ &lt;code dir=&quot;auto&quot;&gt;loop()&lt;/code&gt; methods are called. On ESP32, ESP8266, and LibreTiny, WiFi and Ethernet no longer block during setup (since &lt;a href=&quot;https://github.com/esphome/esphome/pull/9823&quot;&gt;PR #9823&lt;/a&gt;), making loop priority ordering unnecessary. The method was dead code on those platforms but still consumed a vtable entry in every &lt;code dir=&quot;auto&quot;&gt;Component&lt;/code&gt; subclass.&lt;/p&gt;
&lt;p&gt;By guarding the method behind &lt;code dir=&quot;auto&quot;&gt;#ifdef USE_LOOP_PRIORITY&lt;/code&gt;, non-RP2040 platforms eliminate one vtable entry per component and the insertion sort during &lt;code dir=&quot;auto&quot;&gt;Application::setup()&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;get_loop_priority()&lt;/code&gt; virtual method declaration and implementation are now guarded behind &lt;code dir=&quot;auto&quot;&gt;#ifdef USE_LOOP_PRIORITY&lt;/code&gt;. This define is only set on RP2040.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// In Component (esphome/core/component.h)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#ifdef&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;USE_LOOP_PRIORITY&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;virtual&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_loop_priority&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;On ESP32, ESP8266, and LibreTiny, the method does not exist at all. Overriding it without the guard produces a compile error.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External components that override &lt;code dir=&quot;auto&quot;&gt;get_loop_priority()&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The override was already a no-op on ESP32/ESP8266/LibreTiny — it only has a functional effect on RP2040. Adding the &lt;code dir=&quot;auto&quot;&gt;#ifdef&lt;/code&gt; guard makes this explicit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Standard YAML configurations are not affected.&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_loop_priority&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;10.0&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MyComponent : &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; Component {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;public:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#ifdef&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;USE_LOOP_PRIORITY&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_loop_priority&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;10.0&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;supporting-multiple-esphome-versions&quot;&gt;Supporting Multiple ESPHome Versions&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;#ifdef&lt;/code&gt; guard alone is the simplest approach and works on all versions:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Safe on all versions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#ifdef&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;USE_LOOP_PRIORITY&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_loop_priority&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;10.0&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;On ESPHome &amp;#x3C; 2026.3.0, &lt;code dir=&quot;auto&quot;&gt;USE_LOOP_PRIORITY&lt;/code&gt; is not defined on any platform, so the &lt;code dir=&quot;auto&quot;&gt;#ifdef&lt;/code&gt; block is skipped. This is fine because the override was a no-op on non-RP2040 platforms anyway.&lt;/p&gt;
&lt;p&gt;If you need the override to remain active on older ESPHome versions:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ESPHOME_VERSION_CODE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VERSION_CODE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2026&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#ifdef&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;USE_LOOP_PRIORITY&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_loop_priority&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;10.0&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get_loop_priority&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;10.0&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.3.0 (March 2026):&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;get_loop_priority()&lt;/code&gt; conditionally compiled&lt;/li&gt;
&lt;li&gt;No deprecation period — the method was dead code on affected platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find get_loop_priority overrides&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;get_loop_priority&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14210&quot;&gt;PR #14210: Conditionally compile get_loop_priority&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>http_request API Modernization: Vector-Based Headers</title><link>https://developers.esphome.io/blog/2026/03/12/http_request-api-modernization-vector-based-headers/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/03/12/http_request-api-modernization-vector-based-headers/</guid><pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;http_request&lt;/code&gt; component’s C++ API has been updated to use &lt;code dir=&quot;auto&quot;&gt;std::vector&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;std::map&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;std::list&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;std::set&lt;/code&gt; for header handling. The &lt;code dir=&quot;auto&quot;&gt;get_response_headers()&lt;/code&gt; method (returning the full headers map) has been removed — use &lt;code dir=&quot;auto&quot;&gt;get_response_header(name)&lt;/code&gt; instead. Deprecated overloads using the old container types are provided until 2027.1.0.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.3.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14024&quot;&gt;PR #14024&lt;/a&gt;: Modernize response header handling&lt;/strong&gt;
&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14027&quot;&gt;PR #14027&lt;/a&gt;: Modernize request header handling&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;http_request&lt;/code&gt; API used heavyweight STL containers (&lt;code dir=&quot;auto&quot;&gt;std::map&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;std::list&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;std::set&lt;/code&gt;) for header storage. These containers pull in significant template code (red-black trees, linked lists) and allocate heavily on the heap. Replacing them with &lt;code dir=&quot;auto&quot;&gt;std::vector&amp;#x3C;Header&gt;&lt;/code&gt; reduces flash usage and heap fragmentation.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;response-headers-pr-14024&quot;&gt;Response headers (PR #14024)&lt;/h3&gt;&lt;/div&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Before&lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;std::map&amp;#x3C;std::string, std::list&amp;#x3C;std::string&gt;&gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;std::vector&amp;#x3C;Header&gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;std::set&amp;#x3C;std::string&gt;&lt;/code&gt; for collect_headers&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;std::vector&amp;#x3C;std::string&gt;&lt;/code&gt; (pre-lowercased)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_response_headers()&lt;/code&gt; returns full map&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Removed&lt;/strong&gt; — use &lt;code dir=&quot;auto&quot;&gt;get_response_header(name)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;collect_headers&lt;/code&gt; parameter&lt;/td&gt;&lt;td&gt;Renamed to &lt;code dir=&quot;auto&quot;&gt;lower_case_collect_headers&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;start()&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;std::set&lt;/code&gt; overload&lt;/td&gt;&lt;td&gt;Deprecated (removal 2027.1.0)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;request-headers-pr-14027&quot;&gt;Request headers (PR #14027)&lt;/h3&gt;&lt;/div&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Before&lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;std::list&amp;#x3C;Header&gt;&lt;/code&gt; in &lt;code dir=&quot;auto&quot;&gt;perform()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;start()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;get()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;post()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;std::vector&amp;#x3C;Header&gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;std::list&amp;#x3C;Header&gt;&lt;/code&gt; overloads for &lt;code dir=&quot;auto&quot;&gt;start()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;get()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;post()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Deprecated (removal 2027.1.0)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External components that:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code dir=&quot;auto&quot;&gt;http_request&lt;/code&gt; APIs to make HTTP requests&lt;/li&gt;
&lt;li&gt;Access response headers via &lt;code dir=&quot;auto&quot;&gt;get_response_headers()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Pass request headers as &lt;code dir=&quot;auto&quot;&gt;std::list&amp;#x3C;Header&gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Pass collect_headers as &lt;code dir=&quot;auto&quot;&gt;std::set&amp;#x3C;std::string&gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Standard YAML configurations are not affected.&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;request-headers--change-one-word&quot;&gt;Request headers — change one word&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;std::list&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;http_request::Header&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; headers;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push_back&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Authorization&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Bearer token&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;std::vector&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;http_request::Header&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; headers;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push_back&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Authorization&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Bearer token&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;response-headers--use-singular-getter&quot;&gt;Response headers — use singular getter&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before (removed)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; headers_map &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_response_headers&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; content_type_list &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;headers_map&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;content-type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_response_header&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;content-type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;collect-headers--use-vector-with-lowercase-names&quot;&gt;Collect headers — use vector with lowercase names&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;std::set&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;std::string&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; collect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Content-Type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;X-Custom&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;(url, method, body, headers, collect);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — names must be pre-lowercased&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;std::vector&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;std::string&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; collect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;content-type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;x-custom&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;(url, method, body, headers, collect);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The parameter has been renamed to &lt;code dir=&quot;auto&quot;&gt;lower_case_collect_headers&lt;/code&gt; to make the lowercase requirement explicit.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;supporting-multiple-esphome-versions&quot;&gt;Supporting Multiple ESPHome Versions&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The deprecated &lt;code dir=&quot;auto&quot;&gt;std::list&amp;#x3C;Header&gt;&lt;/code&gt; overloads compile on 2026.3.0 with warnings, so existing code continues to work during the transition. To suppress warnings:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ESPHOME_VERSION_CODE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VERSION_CODE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2026&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::vector&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;http_request::Header&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; headers;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;std::list&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;http_request::Header&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; headers;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push_back&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Authorization&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Bearer token&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For response headers, &lt;code dir=&quot;auto&quot;&gt;get_response_header(name)&lt;/code&gt; is new in 2026.3.0:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ESPHOME_VERSION_CODE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VERSION_CODE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2026&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_response_header&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;content-type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; headers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_response_headers&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; it &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;content-type&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.3.0 (March 2026):&lt;/strong&gt; New vector-based API active, old overloads deprecated&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2027.1.0 (January 2027):&lt;/strong&gt; Deprecated &lt;code dir=&quot;auto&quot;&gt;std::list&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;std::set&lt;/code&gt; overloads removed&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find std::list&amp;#x3C;Header&gt; usage&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;std::list.*Header&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find get_response_headers() (plural — removed)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;get_response_headers&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find std::set for collect_headers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;std::set.*collect\|collect_headers&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14024&quot;&gt;PR #14024: Modernize response header handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14027&quot;&gt;PR #14027: Modernize request header handling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Icon and Device Class Getter Migration</title><link>https://developers.esphome.io/blog/2026/03/12/icon-and-device-class-getter-migration/</link><guid isPermaLink="true">https://developers.esphome.io/blog/2026/03/12/icon-and-device-class-getter-migration/</guid><pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;get_icon_ref()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;get_icon()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;get_device_class_ref()&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;get_device_class()&lt;/code&gt; methods are deprecated on &lt;strong&gt;all platforms&lt;/strong&gt; and replaced by new buffer-based APIs &lt;code dir=&quot;auto&quot;&gt;get_icon_to()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;get_device_class_to()&lt;/code&gt;. On ESP8266, the old methods produce a &lt;code dir=&quot;auto&quot;&gt;static_assert&lt;/code&gt; error because the underlying strings have been moved to PROGMEM and cannot be accessed through normal C string pointers. On other platforms, the old methods continue to work but emit deprecation warnings and will be removed in 2026.9.0.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;breaking change&lt;/strong&gt; for external components in &lt;strong&gt;ESPHome 2026.3.0 and later&lt;/strong&gt;.&lt;/p&gt;

&lt;div&gt;&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14171&quot;&gt;PR #14171&lt;/a&gt;: Pack entity string properties into PROGMEM-indexed fields&lt;/strong&gt;
&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14437&quot;&gt;PR #14437&lt;/a&gt;: Move icon strings to PROGMEM on ESP8266&lt;/strong&gt;
&lt;strong&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14443&quot;&gt;PR #14443&lt;/a&gt;: Move device class strings to PROGMEM on ESP8266&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Icon and device class strings are set at compile time and never change. PR #14171 packed these properties into PROGMEM-indexed &lt;code dir=&quot;auto&quot;&gt;uint8_t&lt;/code&gt; fields on &lt;code dir=&quot;auto&quot;&gt;EntityBase&lt;/code&gt;, replacing per-entity &lt;code dir=&quot;auto&quot;&gt;const char*&lt;/code&gt; pointers and the &lt;code dir=&quot;auto&quot;&gt;EntityBase_DeviceClass&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;EntityBase_UnitOfMeasurement&lt;/code&gt; mixin classes. Most entity types already had &lt;code dir=&quot;auto&quot;&gt;device_class&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;unit_of_measurement&lt;/code&gt; on the entity itself — the exception was &lt;code dir=&quot;auto&quot;&gt;NumberTraits&lt;/code&gt;, which previously inherited the mixin classes. &lt;code dir=&quot;auto&quot;&gt;NumberTraits&lt;/code&gt; no longer provides these accessors; use &lt;code dir=&quot;auto&quot;&gt;number-&gt;get_device_class_ref()&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;number-&gt;get_unit_of_measurement_ref()&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;number-&gt;traits.get_*_ref()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The old getter methods returned references or pointers to these strings, which required them to be stored as regular C strings in RAM. The new buffer-based APIs copy the string into a caller-provided buffer, which works regardless of where the string is stored.&lt;/p&gt;
&lt;p&gt;On ESP8266, PRs #14437 and #14443 also move the strings into PROGMEM (flash), freeing valuable heap RAM. Since PROGMEM strings cannot be accessed through normal C string pointers on ESP8266, the old methods produce a hard &lt;code dir=&quot;auto&quot;&gt;static_assert&lt;/code&gt; error on that platform rather than a deprecation warning.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-changing&quot;&gt;What’s Changing&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;deprecated-methods-all-platforms-removal-202690&quot;&gt;Deprecated methods (all platforms, removal 2026.9.0)&lt;/h3&gt;&lt;/div&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Method&lt;/th&gt;&lt;th&gt;Replacement&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_icon_ref()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_icon_to(buffer)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_icon()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_icon_to(buffer)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_device_class_ref()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_device_class_to(buffer)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_device_class()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;get_device_class_to(buffer)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;numbertraits-device_class-and-unit_of_measurement-moved-to-entity&quot;&gt;NumberTraits: device_class and unit_of_measurement moved to entity&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;NumberTraits&lt;/code&gt; no longer provides &lt;code dir=&quot;auto&quot;&gt;get_device_class()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;get_device_class_ref()&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;get_unit_of_measurement_ref()&lt;/code&gt;. These are now accessed directly on the &lt;code dir=&quot;auto&quot;&gt;Number&lt;/code&gt; entity, consistent with all other entity types:&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Before&lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;number-&gt;traits.get_device_class()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;number-&gt;get_device_class_to(buffer)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;number-&gt;traits.get_device_class_ref()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;number-&gt;get_device_class_to(buffer)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;number-&gt;traits.get_unit_of_measurement_ref()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;number-&gt;get_unit_of_measurement_ref()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h3 id=&quot;entitybase-setters-removed-and-packed-into-configure_entity_&quot;&gt;EntityBase setters removed and packed into configure_entity_()&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The following &lt;code dir=&quot;auto&quot;&gt;EntityBase&lt;/code&gt; setters have been removed and packed into the existing &lt;code dir=&quot;auto&quot;&gt;configure_entity_()&lt;/code&gt; call:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;set_name()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;set_icon()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;set_device_class()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;set_unit_of_measurement()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;set_internal()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;set_disabled_by_default()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;set_entity_category()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, &lt;code dir=&quot;auto&quot;&gt;set_device()&lt;/code&gt; has been renamed to &lt;code dir=&quot;auto&quot;&gt;set_device_()&lt;/code&gt; and made &lt;code dir=&quot;auto&quot;&gt;protected&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These were codegen-only setters — calling them at runtime was unsafe and could silently corrupt state or crash the device (undefined behavior). If you were calling &lt;code dir=&quot;auto&quot;&gt;set_icon()&lt;/code&gt; from lambdas to dynamically change icons at runtime, move the icon logic to the Home Assistant side (e.g., using template sensors with &lt;code dir=&quot;auto&quot;&gt;icon&lt;/code&gt; templates).&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/esphome/esphome/pull/14171&quot;&gt;PR #14171&lt;/a&gt;, &lt;a href=&quot;https://github.com/esphome/esphome/pull/14437&quot;&gt;PR #14437&lt;/a&gt;, &lt;a href=&quot;https://github.com/esphome/esphome/pull/14443&quot;&gt;PR #14443&lt;/a&gt;, and &lt;a href=&quot;https://github.com/esphome/esphome/pull/14564&quot;&gt;PR #14564&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;esp8266-specific&quot;&gt;ESP8266-specific&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;On ESP8266, using any of the deprecated methods produces a &lt;code dir=&quot;auto&quot;&gt;static_assert&lt;/code&gt; error at compile time — they cannot work because the underlying strings are in PROGMEM.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;new-apis&quot;&gt;New APIs&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Icon — copies into buffer, returns pointer to buffer (or nullptr if no icon set)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;icon_buf&lt;/span&gt;&lt;span&gt;[MAX_ICON_LENGTH];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;icon &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_icon_to&lt;/span&gt;&lt;span&gt;(icon_buf);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Device class — same pattern&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dc_buf&lt;/span&gt;&lt;span&gt;[MAX_DEVICE_CLASS_LENGTH];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;dc &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_device_class_to&lt;/span&gt;&lt;span&gt;(dc_buf);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;MAX_ICON_LENGTH&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;MAX_DEVICE_CLASS_LENGTH&lt;/code&gt; are both 64 bytes (63 characters + null terminator). Strings longer than 63 characters will produce a compile-time error.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;who-this-affects&quot;&gt;Who This Affects&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;External components that:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Call &lt;code dir=&quot;auto&quot;&gt;get_icon_ref()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;get_icon()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;get_device_class_ref()&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;get_device_class()&lt;/code&gt; on any entity — deprecation warning on all platforms, hard compile error on ESP8266&lt;/li&gt;
&lt;li&gt;Access &lt;code dir=&quot;auto&quot;&gt;device_class&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;unit_of_measurement&lt;/code&gt; through &lt;code dir=&quot;auto&quot;&gt;NumberTraits&lt;/code&gt; — moved to &lt;code dir=&quot;auto&quot;&gt;Number&lt;/code&gt; entity directly&lt;/li&gt;
&lt;li&gt;Call &lt;code dir=&quot;auto&quot;&gt;set_icon()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;set_device_class()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;set_unit_of_measurement()&lt;/code&gt;, or other removed EntityBase setters at runtime&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Standard YAML configurations are not affected.&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-guide&quot;&gt;Migration Guide&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;entity-base-migration&quot;&gt;Entity base migration&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;icon &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_icon_ref&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;dc &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_device_class_ref&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;icon_buf&lt;/span&gt;&lt;span&gt;[MAX_ICON_LENGTH];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;icon &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_icon_to&lt;/span&gt;&lt;span&gt;(icon_buf);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dc_buf&lt;/span&gt;&lt;span&gt;[MAX_DEVICE_CLASS_LENGTH];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;dc &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_device_class_to&lt;/span&gt;&lt;span&gt;(dc_buf);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;numbertraits-migration&quot;&gt;NumberTraits migration&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before — accessed through traits (only Number had this pattern)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; dc &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_device_class&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt;       // or get_device_class_ref()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; uom &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;traits&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_unit_of_measurement_ref&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — accessed directly on entity, consistent with all other entity types&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dc_buf&lt;/span&gt;&lt;span&gt;[MAX_DEVICE_CLASS_LENGTH];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;dc &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_device_class_to&lt;/span&gt;&lt;span&gt;(dc_buf);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;uom &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_unit_of_measurement_ref&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;checking-if-icondevice-class-is-set&quot;&gt;Checking if icon/device class is set&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;icon &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_icon_ref&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;icon&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;empty&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// use icon&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;icon_buf&lt;/span&gt;&lt;span&gt;[MAX_ICON_LENGTH];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;icon &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_icon_to&lt;/span&gt;&lt;span&gt;(icon_buf);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (icon &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nullptr&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// use icon&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;supporting-multiple-esphome-versions&quot;&gt;Supporting Multiple ESPHome Versions&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ESPHOME_VERSION_CODE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;VERSION_CODE&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2026&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;icon_buf&lt;/span&gt;&lt;span&gt;[MAX_ICON_LENGTH];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;char&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;icon &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_icon_to&lt;/span&gt;&lt;span&gt;(icon_buf);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt;icon &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;entity&lt;/span&gt;&lt;span&gt;-&gt;&lt;/span&gt;&lt;span&gt;get_icon_ref&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.3.0 (March 2026):&lt;/strong&gt; Old methods deprecated, &lt;code dir=&quot;auto&quot;&gt;static_assert&lt;/code&gt; on ESP8266, new buffer APIs available&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESPHome 2026.9.0 (September 2026):&lt;/strong&gt; Old methods removed on all platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;finding-code-that-needs-updates&quot;&gt;Finding Code That Needs Updates&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find deprecated method usage&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;get_icon_ref\|get_icon()&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;get_device_class_ref\|get_device_class()&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find NumberTraits accessors that moved to entity&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;traits\.get_device_class\|traits\.get_unit_of_measurement&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Find removed EntityBase setters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;grep&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-rn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;set_icon\|set_device_class\|set_unit_of_measurement\|set_internal\|set_disabled_by_default\|set_entity_category\|set_name(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;your_component/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;questions&quot;&gt;Questions?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you have questions about migrating your external component, please ask in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://discord.gg/KhAMKrd&quot;&gt;ESPHome Discord&lt;/a&gt; - #devs channel&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/discussions&quot;&gt;ESPHome GitHub Discussions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;related-documentation&quot;&gt;Related Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14171&quot;&gt;PR #14171: Pack entity string properties into PROGMEM-indexed fields&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14437&quot;&gt;PR #14437: Move icon strings to PROGMEM on ESP8266&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14443&quot;&gt;PR #14443: Move device class strings to PROGMEM on ESP8266&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/esphome/esphome/pull/14564&quot;&gt;PR #14564: Pack entity flags into configure_entity_() and protect setters&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>