Skip to content

ESPHome 2026.4.0 - April 2026

ESPHome 2026.4.0 delivers major performance and reliability improvements across all platforms. ESP32 devices default to maximum CPU frequency (33% faster API operations), unlock 40KB extra IRAM, signed OTA verification, and custom partition tables. ESP8266 gets a crash handler matching ESP32/RP2040, and a new client-side state logging architecture achieves up to 46x faster sensor publishing by moving log formatting off the device. Bluetooth Proxy and API overhead dropped to just 2.3% of main loop time on ESP32-C3. 31 PRs prepare the codebase for ESP-IDF 6.0, and CodSpeed benchmarking infrastructure now catches performance regressions before they ship. The substitution system has been redesigned for up to 18x faster config loading with dynamic !include paths, Ethernet expands with 5 new chip types across ESP32 and RP2040, and GPIO expanders gain interrupt-driven operation cutting idle I2C traffic by 99.7%. This release also adds LVGL v9.5.0, native Mitsubishi CN105 climate control, 4 new sensor components, and a esphome bundle CLI command to assist with remote compilation.

  • If you use LVGL with rotation configured on the display component, move the rotation setting to the lvgl: block instead
  • If you use the i2s_audio media player, migrate to the speaker media player component (the i2s_audio media player has been removed)
  • If you use use_legacy in your i2s_audio config, remove it (the legacy I2S driver has been removed)
  • If you have battery-powered ESP32/S2/S3/C5 devices, add cpu_frequency: 160MHZ to your config (the default is now 240MHz)
  • If you have two YAML keys that resolve to the same substituted name, note that the later key now takes precedence (“last writer wins”)
  • If you use sen5x with voc_baseline, remove it (it was dead code that never worked; use store_baseline: true instead)
  • If you use rp2040_pio_led_strip with chipset: CUSTOM, use explicit bit0_high/bit0_low/bit1_high/bit1_low timing parameters instead
  • If you use Nextion get_wave_chan_id() in lambdas, switch to get_wave_channel_id() (the old method is deprecated, removal in 2026.10.0)
  • If you use sensor.raw_state in lambdas, migrate to using filtered state or on_raw_value triggers (.raw_state is deprecated)
  • If you maintain external components that call TEMPLATABLE_VALUE macro-generated setters with raw C++ constants, route values through cg.templatable() in Python codegen
  • If you maintain external components using ButtonPressTrigger, SensorStateTrigger, or similar trigger classes, migrate to build_callback_automation()
  • If you maintain external components using FlushResult, rename to UARTFlushResult with UART_FLUSH_RESULT_ prefix

ESP32 Performance and Security Improvements

Section titled “ESP32 Performance and Security Improvements”

Several ESP32 platform changes improve performance, security, and flexibility.

Default CPU Frequency Raised to Maximum (#15143):

ESP32, ESP32-S2, ESP32-S3, and ESP32-C5 now default to 240MHz instead of 160MHz. This restores the performance level that Arduino users had before May 2025 and delivers ~34% faster CPU-bound operations:

  • API encryption handshake: 90ms to 64ms (29% faster)
  • Protobuf encoding (BLE proxy): 34% faster
  • Noise encryption: 33-34% faster across all payload sizes

Users on battery power or with thermal constraints can override with cpu_frequency: 160MHZ. This is a breaking change as it increases power consumption on affected variants.

ESP32 crash handler improvements: The existing crash handler now captures backtraces from both cores on dual-core ESP32 variants (#15559) and preserves crash data across OTA rollback reboots (#15578).

40KB extra IRAM on original ESP32 (#14874): A new option reclaims 40KB of previously reserved SRAM1 as IRAM, expanding the flash cache window and reducing cache misses for WiFi, BLE, and API operations, at no cost to heap. ESPHome will automatically detect your bootloader version at boot and suggest enabling this option only when it is safe to do so. Do not enable this option without the bootloader check confirming it is safe; enabling it with a pre-v5.1 bootloader will brick the device (requiring USB reflash to recover). See the ESP32 documentation for details.

Signed OTA verification (#15357): New signed_ota_verification option enables firmware signature verification during OTA updates without requiring hardware Secure Boot (eFuse burning). Two workflows are supported:

  • Private key mode (signing_key) - Firmware is automatically signed during build. Simplest setup.
  • Public key mode (verification_key) - Only the public key is embedded; binaries are signed externally (e.g., in CI/CD with the private key in a secure vault).
esp32:
framework:
type: esp-idf
advanced:
signed_ota_verification:
signing_key: secure_boot_signing_key.pem
signing_scheme: rsa3072

Custom partition tables (#7682): Components and YAML configs can now define custom data/app partitions that are appended to the flash layout. The partition table generation has been unified across Arduino and IDF, and NVS has been moved to the end of flash with increased size (20KB to 384KB on Arduino). See the ESP32 documentation for details.

ESP8266 Crash Handler, Without a Serial Cable

Section titled “ESP8266 Crash Handler, Without a Serial Cable”

Completing the platform coverage started in 2026.3.0 (which added crash handlers for ESP32 and RP2040), ESP8266 devices now have the same post-mortem crash diagnostics (#15465). When a device crashes, the exception details and up to 16 stack-scanned backtrace addresses are saved to RTC memory and reported over the API on the next boot. No USB cable required; crash data appears in esphome logs (which automatically decodes addresses to source locations via addr2line) and the Home Assistant log viewer, entirely over WiFi.

[E][esp8266]: *** CRASH DETECTED ON PREVIOUS BOOT ***
[E][esp8266]: Reason: Exception - StoreProhibit (exccause=29)
[E][esp8266]: PC: 0x40212EC5
[E][esp8266]: EXCVADDR: 0x00000000
[E][esp8266]: BT0: 0x40212F5A
[E][esp8266]: BT1: 0x40203B10

The PC is the program counter where the crash occurred, EXCVADDR is the memory address that caused the fault, and BT0/BT1/etc. are the stack backtrace showing the call chain leading to the crash. When using esphome logs, these addresses are automatically decoded to source file and line numbers via addr2line, so you see the actual function names instead of raw hex.

No configuration needed; it’s automatically enabled for all ESP8266 devices. Resource cost is minimal: 0 bytes RAM, 119 bytes IRAM, ~1KB flash.

State change logging has been fundamentally redesigned to eliminate the single biggest CPU bottleneck on every ESPHome device (#15155).

Every entity state change ('CO2' >> 420 ppm, 'Light' - Setting ON) previously ran vsnprintf on the device to format a log string and sent it over UART, even when nobody was watching the serial console (which is 99.9% of devices). CodSpeed benchmarks revealed this was consuming 95%+ of sensor publish time.

State change logs are now reconstructed on the client instead of the device. When esphome logs or the dashboard connects, it subscribes to compact protobuf state messages and formats them locally. The protobuf messages are a fraction of the size of formatted strings, and formatting happens on your computer instead of the microcontroller. This achieves up to 46x faster in the publish path, with flash savings of 1.4KB (ESP32-C3) to 2.2KB (ESP8266). State changes still appear in logs as [S] lines in bright cyan:

[18:03:14.677][S][sensor]: 'CO2' >> 420 ppm
[18:03:14.677][S][sensor]: 'Temperature' >> 35.63 °C
[18:03:51.747][S][light]: 'Light' >> ON

A new --no-states flag (#15160) lets users suppress state change lines entirely for the first time, useful for debugging when the log is flooded with sensor readings. Users who need the old device-side state logging can opt back in by setting logger: level: VERBOSE.

This release includes 31 pull requests preparing ESPHome for ESP-IDF 6.0, which brings GCC 15, picolibc, and significant API changes from Espressif. Led by @swoboda1337, this effort touches cryptography, drivers, and toolchain compatibility across the entire codebase. While ESP-IDF 6.0 is not yet the default framework version, these changes ensure ESPHome is ready when it ships. All related PRs are tagged with the idf-6 label.

PSA Crypto API migration:

IDF 6.0 deprecates the legacy mbedTLS crypto APIs. All ESPHome cryptographic operations have been migrated to the new PSA Crypto API:

  • SHA-256 hashing (#14809)
  • HMAC-SHA256 (#14814)
  • BLE tracker PSA Crypto (#14811)
  • BTHome/Xiaomi BLE CCM decryption (#14816)
  • DLMS meter GCM decryption (#14817)

Driver and API compatibility fixes across 20+ components:

  • WiFi, ADC, LEDC, RMT (LED strips, remote transmitter/receiver), MIPI DSI, PSRAM, I2S audio, I2C, deep sleep, MQTT, USB host, OpenTherm timer, ethernet, debug, and camera components all updated for IDF 6.0 API changes

Toolchain compatibility:

  • PicoLibC std::isnan conflict resolved (#14768)
  • vfprintf printf stub fixed for picolibc (#15172)
  • Implicit int-to-gpio_num_t conversions fixed for GCC 15 (#14830)
  • Legacy I2S driver removed in preparation for IDF 6.0 where it no longer exists (#14932)

Led by @jpeletier, the YAML configuration system receives two major improvements that make complex, multi-file configurations far more powerful and faster to load.

Substitutions in !include paths (#12213): You can now use substitution variables and Jinja expressions directly in !include filenames, enabling dynamic file selection:

substitutions:
eth_model: LAN8720
packages:
- !include network/${eth_model}/config.yaml

This works with command-line substitutions too, allowing the same YAML to target different hardware configurations without duplicating files.

18x faster config loading (#14918): The substitution engine has been redesigned to perform all variable substitutions in a single linear pass instead of the previous multi-pass approach. This achieves up to 18x faster config load times for large projects with many packages and includes. The rewrite also fixes several edge cases where cross-file variable references (${A * B} mixing local and root variables) were unreliable. Note that merge_config argument order for duplicate substituted keys has changed to “last writer wins.”

Building on RP2040/RP2350’s first-class platform support in 2026.3.0, this release dramatically expands Ethernet support across platforms, adding 5 new chip types and bringing SPI Ethernet to the RP2040 platform for the first time. RP2040 boards like the WIZnet EVB-Pico series can now be used as wired IoT controllers without WiFi.

New RP2040 Ethernet Support:

  • W5500 (#14820) - 100Mbps SPI Ethernet, stress tested with 344 successful API connections on a WIZnet W5500-EVB-Pico
  • W5100/W5100S (#15131) - 10/100Mbps SPI Ethernet for boards like the W5100S-EVB-Pico
  • W6100 and W6300 (#15543) - Next-generation WIZnet controllers with IPv6 support for boards like the W6300-EVB-Pico2

Cross-Platform Additions:

  • ENC28J60 (#14945) - Microchip’s widely-available 10BASE-T controller now supported on both ESP32 (IDF) and RP2040

ESP32 Improvements:

  • SPI interface selection (#10285) - New interface config variable lets ESP32 users select spi2 or spi3 host, resolving conflicts with display components on boards like the M5Stack CoreS3

The PCF8574, PCA9554, MCP23008/MCP23017/MCP23S08/MCP23S17, and PI4IOE5V6408 GPIO expander components now support an optional interrupt_pin that eliminates I2C/SPI polling entirely (#15444, #15445).

Supported Expanders:

  • PCF8574 and PCA9554 - I2C GPIO expanders
  • MCP23008, MCP23017, MCP23S08, MCP23S17 - I2C and SPI GPIO expander family
  • PI4IOE5V6408 - I2C GPIO expander

Measured Impact (ESP32-IDF, PCF8574 + 3 binary sensors):

MetricPollingInterruptImprovement
I2C reads/min (idle)4100ms12ms99.7% reduction
Loop iterations per gate cycle747716468x fewer
Binary sensor read time~0.6ms (I2C)~0.002ms (cache)300x faster

When configured, binary sensors return from cache (no bus reads) between interrupts, and the main loop is completely disabled until a pin change occurs. The MCP23x17 automatically enables the MIRROR bit so a single interrupt wire catches changes on all 16 pins.

pcf8574:
id: my_pcf8574
interrupt_pin: GPIO16

This release introduces comprehensive C++ benchmarking infrastructure using CodSpeed, enabling continuous performance tracking and regression prevention. While initially added to protect the gains from 2026.3.0, the benchmarks quickly revealed remaining bottlenecks and drove a wave of targeted optimizations throughout this release cycle.

Benchmarks added:

  • Protobuf encoding/decoding (API messages, BLE advertisements, ListEntities)
  • Noise encryption handshakes and payload encryption
  • Sensor, binary sensor, light, cover, climate, fan, number, select, switch, text_sensor, and button publish/call paths
  • Scheduler interval and timeout operations
  • Plaintext and encrypted API frame writes

API layer optimizations (the native API is the primary communication path between ESPHome devices and Home Assistant). The goal was to make Bluetooth Proxy lightweight enough that every ESP32-C3 can run it without worrying about performance impact, and this release delivers: combined with changes from 2026.3.0, the API and BLE proxy now consume only ~2.3% of main loop runtime on ESP32-C3, down from ~3.3% in 2026.3.0 and a fraction of what it was a year ago:

  • Protobuf encoding: 17-20% faster with register-optimized write path (#15290)
  • Precomputed tag bytes: Varint and length-delimited field tags computed at compile time (#15067)
  • Frame write hot path: Peeled first write iteration, inlined socket writes, zero-gap batch encoding. Single sensor state writes 72% faster, batch writes 21% faster, stack frame reduced from 352 to 48 bytes (-86%) (#15063)
  • Varint encoding optimized: Plaintext varint encoding and write_protobuf_packet devirtualized (#14758)
  • Command dispatch devirtualized: Eliminates virtual call overhead on every incoming API message (#15044)
  • Protobuf decode non-virtual: ProtoDecodableMessage::decode() devirtualized (#15076)
  • Constant-size varint codegen: max_value proto option eliminates variable-length encoding for bounded fields (#15424)
  • Entity name/object_id optimized: max_data_length proto option for known-bounded string fields (#15426)
  • Fixed32 decode: Uses memcpy on little-endian platforms instead of byte-shifting (#15292)
  • Raw tag+value writes: Forced fixed32 key fields emit raw bytes (#15051)
  • Float zero checks: Integer comparison instead of floating-point (#15490)
  • Code size reduction: Buffer and nodelay optimizations (#14797)
  • Overflow buffer extracted: Frame helper buffer management simplified (#14871)
  • Noise encryption speedup: Continuing from the ChaCha20-Poly1305 optimization in 2026.3.0 (which achieved 8-32% faster encryption), this release enables HAVE_WEAK_SYMBOLS and HAVE_INLINE_ASM for libsodium, replacing a volatile byte-by-byte sodium_memzero() fallback with word-sized memset(). Small messages (the common case for sensor updates and service calls) see the biggest gains: 24% faster on ESP32, 11% on ESP8266, 10% on RP2040 (#15038)
  • Noise handshake: Split state_action_ to reduce stack pressure (#15464)
  • PSK update: Avoided heap allocation in timeout lambda (#14921)
  • Batch encoding: Simplified encode_to_buffer to single resize call, inlined DeferredBatch::add_item (#15355, #15353)
  • Enum auto-derivation: Auto-derive max_value for enum fields in protobuf codegen (#15469)

Core framework optimizations:

  • Scheduler: Integer math for interval offset calculation, reschedules fired intervals directly into the heap, early-exit cancel path, and inlined fast-path checks (#14755, #15516, #14902, #14905)
  • Main loop inlined: Application::loop() and calculate_looping_components_ inlined to eliminate stack frames (#15041, #14944)
  • std::bind eliminated: Replaced across 20+ components with lambdas to fit within small-buffer optimization (#14961 and related PRs)
  • Automation call chain collapsed: The Trigger::trigger()Automation::trigger()ActionList::play() forwarding chain is force-inlined, collapsing 3 stack frames into 1. Combined with ControllerRegistry dispatch inlining and call_loop_ removal, a button→lambda automation went from 16 stack frames to 10, significantly reducing stack overflow risk on devices with deep automation chains (#15042, #15173, #14931)
  • IfAction compile-time optimization: HasElse parameter eliminates the else-branch check at compile time when no else block is configured (#15134)
  • Logger: Reduced per-message overhead by inlining hot path helpers (#14851)
  • errno caching: Avoids duplicate __errno() calls across API, async_tcp, captive_portal, and web_server_idf (#14751)
  • Loop elimination: Many components were running their loop() every single main loop iteration just to check a condition or timestamp. These have been converted to event-driven patterns that only run when needed:
    • web_server: Loop completely disabled when no SSE clients are connected; most devices have zero browser clients 99%+ of the time (#15428)
    • total_daily_energy: Replaced loop() with a single timeout that fires at midnight (#15432)
    • CronTrigger: Replaced loop() with set_interval (#15433)
    • preferences: Loop compiled out entirely when flash_write_interval is non-zero (#14943)
    • GPIO expanders (PCF8574, PCA9554, MCP23xxx, PI4IOE5V6408): Loop disabled when all pins are outputs (#15455, #15460)
  • Bluetooth Proxy: BLE event handler dispatch devirtualized, eliminating virtual call overhead on every BLE advertisement and GATT event (#15310). BLE is_active/is_running inlined and STL bloat removed (#14875). Advertisement flushing replaced loop() (which ran ~7,400 times/60s just to check a timestamp) with set_interval(100ms) so the flush logic only runs when needed (~600 times/60s), eliminating ~6,800 wasted iterations per minute (#15347)
  • Devirtualization: Preferences backend, PollingComponent::set_update_interval (#14825, #14938)
  • Light: Reciprocal multiply in normalize_color, pass LightTraits to avoid redundant virtual calls (#15401, #15403)

CodSpeed runs on every pull request and push to dev, providing a continuous baseline that catches regressions before they ship. It was CodSpeed’s benchmark visualization that revealed vsnprintf was consuming 95%+ of sensor publish time, leading to the client-side state logging change that achieved a 46x speedup on the sensor publish path. Visit codspeed.io/esphome/esphome to explore the benchmark results. Thank you to CodSpeed for providing their service free to open source projects.

Driven by the increased visibility from benchmarking and memory analysis tooling, this release includes significant RAM and flash savings across the framework.

Component allocations moved from heap to BSS (#15079): This is a major architectural change; all component instances (new_Pvariable) are now allocated via placement new into statically-sized BSS storage instead of individual heap allocations. Previously, every component, sensor, automation, and filter was a separate new call, fragmenting the heap over time. Now these allocations are packed into a single contiguous BSS region, freeing heap space for WiFi, BLE, and network buffers that genuinely need dynamic allocation. This eliminates hundreds of small heap allocations per device and significantly reduces heap fragmentation on long-running devices.

ESPHome’s CI automatically runs a home-grown memory impact analysis on every pull request, showing contributors the exact flash and RAM impact of their changes before merging. Previously, component memory was invisible to this tooling because it was all allocated at runtime on the heap. Once the BSS change landed, every component’s memory became a statically-visible symbol that the CI could measure and attribute. The analyze-memory tool was extended to attribute placement new storage symbols back to their owning components (#15092), making it easy to spot which components still had room for improvement. This visibility drove the wave of targeted optimizations below.

Core framework savings:

  • Component base class: Shrunk from 12 to 8 bytes per instance, saving ~200-400 bytes across typical configs (#15103)
  • CallbackManager: Replaced std::function (16 bytes) with lightweight Callback (8 bytes) across all callback registrations. Small lambdas like [this] are now stored inline in the function pointer context with zero heap allocation, covering 96% of all callback registrations. Measured +488 bytes free heap on an ESP8266 config with MQTT + web_server + many entities (#14853)
  • Trigger trampolines eliminated across 23 components: Previously, every on_press, on_value, on_state, etc. automation instantiated a separate Trigger object just to forward a callback, a 4+ byte heap-allocated object that existed solely to hold a pointer. A new build_callback_automation() pattern collapses the trigger into a pointer-sized forwarder struct stored inline in the callback manager, eliminating the separate allocation entirely. The initial PR (#15174) migrated the core entity types (button, sensor, binary_sensor, switch, text_sensor, number, event) saving 88-208 bytes RAM, then 22 follow-up PRs migrated alarm_control_panel, lock, media_player, ld2450, rtttl, online_image, rotary_encoder, dfplayer, hlk_fm22x, pn532, pn7150/pn7160, sim800l, fingerprint_grow, ltr_als_ps, ltr501, nextion, ezo, haier, modbus_controller, rf_bridge, factory_reset, sml, and safe_mode
  • TemplatableValue → TemplatableFn: The function-pointer pattern was first proven on LightControlAction, shrinking each instance from 128 to 72 bytes (44% reduction, saving 616 bytes RAM on an ESP8266 light config with 11 actions) (#15132). This was then generalized codebase-wide: ~340 TEMPLATABLE_VALUE fields automatically dropped from 8 to 4 bytes each by storing function pointers instead of tagged unions (#15545)
  • ActionList: Removed actions_end_ pointer to save RAM per automation (#15283)

Entity and component savings:

  • Binary sensor: Removed redundant optional<bool> state, saving 8 bytes per instance (#15095)
  • Text sensor: raw_callback_ gated behind USE_TEXT_SENSOR_FILTER, saving 4 bytes per instance when unused (#15097)
  • Light: Reordered LightState fields to eliminate padding (#15112)
  • GPIO binary sensor: Packed fields and removed redundant last_state_ (#15113)
  • GPIO switch: Compiled out interlock fields when unused (#15111)
  • Climate/Fan: Custom mode vectors stored on entity directly, eliminating heap allocation (#15206, #15209)
  • WebServerBase: Reduced by 4 bytes (#15251)
  • Runtime stats: Stored inline on Component, eliminating std::map lookup (#15345)

Heap allocation elimination:

  • Sensor filters: OrFilter, CalibrateLinearFilter, CalibratePolynomialFilter, ValueList, FilterOut, ThrottleWithPriority migrated to std::array (#15262, #15263, #15264, #15265)
  • Text sensor filters: SubstituteFilter and MapFilter migrated to std::array (#15266, #15269)
  • Automation conditions: And, Or, Xor conditions migrated to std::array (#15282)
  • PID: Replaced std::deque with FixedRingBuffer (#14733)
  • Sensor sliding window: Replaced with FixedRingBuffer (#14736)

ESP8266 RAM savings:

  • API dump strings: Moved to PROGMEM (#14982)
  • Logger: Log level lookup tables and task log buffer moved to PROGMEM/BSS (#15003, #15153)

The new esphome bundle CLI command packages a YAML config and all its local dependencies (fonts, images, certificates, local external_components, secrets) into a self-contained .esphomebundle.tar.gz archive (#13791). This is the foundation for remote compilation support, enabling devices like the Home Assistant Green to offload compilation to a remote build server.

Key Features:

  • Automatic dependency discovery - Finds all referenced files (YAML includes, fonts, images, animations, certificates, C++ includes, web assets)
  • Secrets filtering - Only includes secrets actually referenced by bundled YAML files
  • Deterministic archives - Sorted entries and zeroed timestamps for reproducibility
  • Incremental builds - Preserves PlatformIO build caches across re-extractions
Terminal window
esphome bundle my_device.yaml # Create bundle
esphome bundle my_device.yaml --list-only # Preview files
esphome compile my_device.esphomebundle.tar.gz # Compile from bundle

Led by @clydebarrow with 27 PRs, ESPHome’s LVGL integration has been upgraded from v8 to LVGL v9.5.0 (#12312). This is a major library migration that brings a new rendering pipeline, improved performance, and new widget capabilities. Existing configurations should compile and run, though some properties are deprecated.

Key changes:

  • Native rotation with hardware acceleration - Rotation is now handled directly by LVGL instead of the display driver, with runtime rotation changes via lvgl.display.set_rotation and automatic use of hardware rotation when available. On ESP32-P4, rotation uses the PPA (Pixel Processing Accelerator) for zero-CPU-cost transforms (#14955, #15453). Users will need to move their rotation config from the display component to the lvgl: block.
  • Built-in dark theme - A single dark_mode: true under theme: enables LVGL’s native dark default theme, replacing the tedious process of manually replicating dark theme properties (#15389)
  • All LVGL 9 events - Complete mapping of LVGL 9 events to ESPHome automation triggers (#15362)
  • Drop shadow support - New bitmap_mask_src style property and A8 image format for drop shadow effects (#15334)

This release adds support for several new sensors and hardware platforms:

  • Number-to-sensor and text-to-text_sensor bridges - New read-only sensor/text_sensor views of number/text components for use in automations and displays (#15125, #15090)
  • ESP32 Hosted SPI transport - SPI transport and 1-bit SDIO bus width support for the esp32_hosted WiFi offloading component (#15551)
  • Nextion custom protocol triggers - on_custom_switch, on_custom_sensor, on_custom_text_sensor, on_custom_binary_sensor triggers for event-driven Nextion workflows without entities (#13248)
  • MQTT alarm panel JSON payloads - Alarm control panel command topic now accepts JSON payloads with PIN codes, e.g. {"state": "DISARM", "code": "1234"} (#14731)
  • Media player enqueue action - New media_player.enqueue action for queueing media URLs from automations (#14775)
  • MIPI RGB optional sync pins - hsync_pin/vsync_pin now optional for displays like the BigTreeTech PandaTouch (#14870)
  • Brennenstuhl remote switches - New 433MHz rolling code protocol for Brennenstuhl comfort-line switches (#9407)
  • DSMR thermal MBUS id - New thermal_mbus_id option for Warmtelink heat meters (#7519)
  • VBus DeltaSol CS4 - Support for Citrin Solar 1.3 controller (#12477)
  • TM1637 buffer manipulation - set_buffer method for raw segment writes and custom glyphs (#13686)
  • HUB75 scan wiring - New SCAN_1_8_32PX_FULL wiring option (#15130)
  • */N cron syntax - Time-based automations now support */5 style step expressions (#15434)

Built by @crnjan across 5 PRs, the new mitsubishi_cn105 component brings native support for controlling Mitsubishi A/C units through the CN105 connector, one of the most requested climate integrations in ESPHome. The component implements the same CN105 protocol used by the popular SwiCago HeatPump project, ensuring compatibility with the same range of devices (#15315, #15358, #15437, #15462, #15483).

Key Features:

  • Full climate control - Power, mode, target temperature, and fan speed can be controlled directly from Home Assistant
  • Non-blocking protocol driver - Custom UART parser designed for performance and reliability
  • Smart temperature polling - The current_temperature_min_interval option rate-limits room temperature reads to prevent rapid oscillations near measurement boundaries while keeping a fast update_interval for responsive control
  • Broad platform support - Works on ESP32, ESP32 IDF, ESP8266, and RP2040/RP2350
climate:
- platform: mitsubishi_cn105
name: "Air Condition"
uart_id: ac_uart
update_interval: 1s
current_temperature_min_interval: 60s

Continuing the correctness sweep started in 2026.3.0, @swoboda1337 contributed another 99 bug fix and validation PRs in this release, addressing missing state_class and device_class on sensor schemas, incorrect config validation (wrong types, missing range checks, broken ensure_list usage), dead code removal, format specifier warnings, and codegen type mismatches across 100+ components. This sustained effort has significantly improved the reliability and correctness of the ESPHome codebase.

This release includes 507 pull requests from over 40 contributors. A huge thank you to everyone who made 2026.4.0 possible:

  • @swoboda1337 - 121 PRs including a correctness sweep across 100+ components, ESP-IDF 6.0 compatibility across dozens of components, and the ESP32 CPU frequency default change
  • @clydebarrow - 27 PRs including the LVGL v9.5.0 migration, native rotation support, and display component improvements
  • @edwardtfn - 14 PRs including Nextion custom protocol triggers, performance optimizations, and code quality improvements across the Nextion component
  • @jpeletier - 10 PRs including the substitution system redesign for 18x faster config loading and !include path substitution support
  • @kbx81 - 10 PRs including signed OTA verification without hardware secure boot, IR receiver frequency metadata, and sensor code cleanups
  • @kahrendt - 7 PRs including the media player enqueue action, microFLAC decoding, and audio pipeline improvements
  • @danielkent-net - 5 PRs including the new SPA06-003 pressure/temperature sensor (I2C and SPI) and BMP581 SPI support
  • @crnjan - 5 PRs building the new Mitsubishi CN105 climate component from scaffold to full read/write control
  • @exciton - 4 PRs including modbus helper refactoring and integration tests
  • @jesserockz - 4 PRs including CI improvements and unbounded percentage validators
  • @diorcety - 4 PRs including git subpath support and compiler compatibility fixes
  • @FredM67 - 2 PRs including the new emonTx energy monitoring component
  • @tomaszduda23 - 2 PRs including debug peripherals status and Zigbee logging improvements
  • @P4uLT - 2 PRs including PID FixedRingBuffer optimization
  • @Kamilcuk - 2 PRs including placement new allocation for Pvariables
  • @luar123 - custom ESP32 partition tables and partition table refactoring
  • @G-Pereira - the new HDC2080 temperature and humidity sensor

Also thank you to @bdraco, @thomwiggers, @aanban, @rtyle, @Szewcson, @Pernotto, @mike1703, @tuct, @CFlix, @heythisisnate, @warthog618, @FWeinb, @droscy, @intcreator, @szupi-ipuzs, @fblaese, @glmnet, @nytaros, @Ardumine, @rguca, @Bl00d-B0b, @RAR, @Passific, and @Tomer27cz for their contributions, and to everyone who reported issues, tested pre-releases, and helped in the community.

  • ESP32 CPU frequency: Default CPU frequency changed to maximum supported per variant (ESP32/S2/S3/C5: 160MHz to 240MHz). This improves performance ~33% but increases power consumption. Battery-powered devices should add cpu_frequency: 160MHZ to their config. #15143

  • ESP32 partition table: Partition layout changed for both Arduino and IDF frameworks. NVS moved to end of flash with increased size (20KB to 384KB on Arduino). Existing devices will get the new partition table on next OTA update. NVS data is preserved since the library finds it by name, not position. #7682

  • ESP8266 RTC preferences: RTC preferences capacity reduced from 96 to 78 words to accommodate the new crash handler. Overflow goes to flash. Very unlikely to affect real configs. #15465

  • LVGL rotation: The rotation option must now be configured under lvgl: instead of the display component. LVGL now handles rotation directly, with support for runtime rotation changes via lvgl.display.set_rotation. Users must move their rotation setting from the display config to the lvgl: block. #14955

  • I2S Audio: The legacy I2S driver (use_legacy option) has been removed. The new I2S driver is always used. The i2s_audio media player sub-component has been removed entirely; users should migrate to the speaker media player component. #14932

  • Binary Sensor multi_click: on_multi_click timing sequences are now limited to 255 entries (previously unlimited). No real config would approach this limit. #15267

  • Binary Sensor autorepeat: Autorepeat timing lists are now limited to 254 entries (previously unlimited). No real config would approach this limit. #15268

  • Substitutions: When two YAML keys in the same dict resolve to the same substituted name, the later-appearing key’s value now takes precedence (“last writer wins”). Previously, the existing resolved value took precedence on dict merges. This only affects the edge case of duplicate substituted keys. #14918

Several internal C++ APIs have changed in this release. While these are not part of the public API, they may affect users who write lambdas or maintain external components.

  • Component: set_component_source(const LogString *) removed and replaced with protected set_component_source_(uint8_t). The warn_if_blocking_over_ field changed from uint16_t (milliseconds) to uint8_t (centiseconds). Component base class shrunk from 12 to 8 bytes. #15103

  • Preferences: ESPPreferenceBackend and ESPPreferences abstract base classes removed. They are now type aliases to concrete platform-specific final classes. All existing code using global_preferences or ESPPreferenceObject continues to work unchanged. #14825

  • Trigger classes removed: ButtonPressTrigger, SensorStateTrigger, SensorRawStateTrigger, PressTrigger, ReleaseTrigger, StateTrigger, StateChangeTrigger, SwitchStateTrigger, SwitchTurnOnTrigger, SwitchTurnOffTrigger, TextSensorStateTrigger, TextSensorStateRawTrigger, NumberStateTrigger, and EventTrigger are no longer instantiated. Lambdas using trigger_id to access these objects will break. Use Script for action control instead. The Automation::trigger_ protected field was also removed. #15174
  • TemplatableValue for non-string types: No longer accepts stateful lambdas (lambdas with captures). No external usage was found. If you need a stateful callable, use std::function<T(X...)> directly. The TEMPLATABLE_VALUE macro now uses TemplatableFn (4 bytes) for trivially copyable types. #15545
  • Nextion: get_wave_chan_id() deprecated in favor of get_wave_channel_id() (removal in 2026.10.0). Getter methods now return by const reference instead of by value. get_queue_type_string() returns const char * instead of std::string. Waveform-related methods are now gated behind USE_NEXTION_WAVEFORM and may not be available if no waveform sensors are configured. #15204, #15273

  • ATM90E32: get_phase_angle_() return type changed from uint16_t to float (fixes precision loss). last_periodic_millis member removed (was never used). #15238

  • AS5600: Dead angle_sensor_, raw_angle_sensor_, position_sensor_ members and their setters removed (were never wired to config schema). #15254

  • SEN5x: voc_baseline config option removed (was never wired to C++). Use store_baseline: true instead. #15391

  • Graph: legend config no longer accepts a list (only the first element was ever used). Single-value configs continue to work. #15522

  • Haier: control_method config no longer accepts a list. Single-value configs continue to work. #15523

  • RP2040 PIO LED Strip: CUSTOM removed from chipset enum (it crashed at compile time). Use explicit timing parameters (bit0_high, bit0_low, bit1_high, bit1_low) instead. #15537

  • CallbackManager: std::function replaced with lightweight Callback type. Existing code continues to work but external components should migrate callback registration methods from std::function parameters to templates for optimal performance (non-template methods fall back to a heap-allocation path). #14853
  • PollingComponent: set_update_interval() is now non-virtual. Subclasses overriding this method should use alternative patterns. #14938
  • UART FlushResult: FlushResult renamed to UARTFlushResult with UART_FLUSH_RESULT_ prefix for enum values. #15101
  • Sensor raw_state: .raw_state is deprecated. raw_callback_ is now gated behind USE_SENSOR_FILTER, saving RAM on every sensor instance when no filters are configured. #15094
  • Trigger trampolines eliminated: Trigger objects for common entity automations (button, sensor, binary_sensor, switch, text_sensor, number, event) are no longer instantiated. build_automation() and Trigger subclasses remain available. #15174
  • TemplatableFn: TEMPLATABLE_VALUE macro now uses 4-byte TemplatableFn for trivially copyable types. External components calling macro-generated setters with raw C++ constants instead of going through cg.templatable() will fail to compile. #15545
  • Climate/Fan custom modes: Custom mode/preset vectors are now stored on the entity instead of heap-allocated. Constructors for climate and fan traits have changed. #15206, #15209
  • Trigger migrations to callback automation: alarm_control_panel, lock, and media_player triggers now use callback automation pattern. #15198, #15199, #15200
  • BLE event dispatch: BLE event handler dispatch is devirtualized. #15310
  • wake_loop: Moved from socket component into core. #15446
  • Preferences devirtualized: ESPPreferenceBackend and ESPPreferences are now type aliases to concrete platform-specific classes (no virtual base). #14825
  • Component shrunk from 12 to 8 bytes: set_component_source(const LogString *) removed and replaced with set_component_source_(uint8_t) (protected). #15103
  • AS5600: Dead angle, raw_angle, and position sensor code removed. #15254
  • Modbus helpers: Shared helper functions refactored across modbus components. #15291, #14172

For detailed migration guides and API documentation, see the ESPHome Developers Documentation.

  • [spa06_base] Add SPA06-003 Temperature and Pressure Sensor (Part 1 of 3) esphome#14521 by @danielkent-net (new-component) (new-feature)
  • [spa06_i2c] Add SPA06-003 Temperature and Pressure Sensor - I2C support (Part 2 of 3) esphome#14522 by @danielkent-net (new-component) (new-feature) (new-platform)
  • [spa06_spi] Add SPA06-003 Temperature and Pressure Sensor - SPI support (Part 3 of 3) esphome#14523 by @danielkent-net (new-component) (new-feature) (new-platform)
  • [bmp581] Add SPI support for BMP581 esphome#13124 by @danielkent-net (new-component) (new-feature) (new-platform)
  • [hdc2080] Add support for HDC2080 sensor esphome#9331 by @G-Pereira (new-component) (new-feature) (new-platform)
  • [mitsubishi_cn105] Add climate component for Mitsubishi A/C units with CN105 connector (Part 1) esphome#15315 by @crnjan (new-component) (new-feature) (new-platform)
  • [emontx] emonTx component esphome#9027 by @FredM67 (new-component) (new-feature) (new-platform)
  • [spa06_i2c] Add SPA06-003 Temperature and Pressure Sensor - I2C support (Part 2 of 3) esphome#14522 by @danielkent-net (new-component) (new-feature) (new-platform)
  • [spa06_spi] Add SPA06-003 Temperature and Pressure Sensor - SPI support (Part 3 of 3) esphome#14523 by @danielkent-net (new-component) (new-feature) (new-platform)
  • [text] Add text_sensor for read-only view of text component esphome#15090 by @clydebarrow (new-feature) (new-platform)
  • [number] Add sensor platform esphome#15125 by @clydebarrow (new-feature) (new-platform)
  • [bmp581] Add SPI support for BMP581 esphome#13124 by @danielkent-net (new-component) (new-feature) (new-platform)
  • [hdc2080] Add support for HDC2080 sensor esphome#9331 by @G-Pereira (new-component) (new-feature) (new-platform)
  • [mitsubishi_cn105] Add climate component for Mitsubishi A/C units with CN105 connector (Part 1) esphome#15315 by @crnjan (new-component) (new-feature) (new-platform)
  • [emontx] emonTx component esphome#9027 by @FredM67 (new-component) (new-feature) (new-platform)