Skip to content

Changelog

ESPHome 2026.4.0 introduces native Mitsubishi A/C control via the CN105 connector, upgrades LVGL to v9.5.0 with runtime rotation and dark mode support, and massively expands Ethernet with 5 new chip types across ESP32 and RP2040 platforms. Performance improvements include interrupt-driven GPIO expanders with 99.7% I2C reduction, ESP32 CPU defaults raised to 240MHz for 34% faster operations, and an 18x faster substitution engine with support for variables in !include paths. This release also adds OTA signature verification, config bundles for remote compilation, custom ESP32 partition tables, and 4 new sensor components.

  • If you use rotation in a display config with LVGL, move the rotation option from the display component to the lvgl: block
  • 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: true in i2s_audio, remove it (the legacy I2S driver has been removed)
  • If you have battery-powered or thermally constrained ESP32/S2/S3/C5 devices, consider adding cpu_frequency: 160MHZ (the default has been raised to 240MHz)
  • If you use sen5x with voc_baseline, remove it (the option was dead code and has been removed; 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 sensor.raw_state in lambdas, migrate to using filtered state or on_raw_value triggers (.raw_state is deprecated)
  • If you use Nextion get_wave_chan_id() in lambdas, switch to get_wave_channel_id() (the old name 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 FlushResult, rename to UARTFlushResult with UART_FLUSH_RESULT_ prefix

The new mitsubishi_cn105 component brings native climate control for Mitsubishi A/C units equipped with the CN105 connector. This has been one of the most requested integrations, enabling direct UART communication with a wide range of Mitsubishi heat pumps and air conditioners without relying on external libraries or custom components.

Key Features:

  • Full climate control - Power, mode, target temperature, and fan speed can be controlled directly from Home Assistant
  • Non-blocking protocol driver - The parser implements the same CN105 protocol used by the popular SwiCago HeatPump project, ensuring compatibility with the same range of devices
  • 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

ESPHome’s LVGL integration has been upgraded from v8 to LVGL v9.5.0 (#12312), a major library migration that brings significant improvements to the graphics framework. Existing configs should compile and run, though some properties are deprecated.

Key Changes:

  • Runtime rotation - Rotation is now an LVGL-level config option with dynamic runtime changes via lvgl.display.set_rotation. This is a breaking change that requires moving rotation from the display configuration to the lvgl: block (#14955)
  • Dark mode support - A simple dark_mode: true option under theme: enables LVGL’s built-in dark theme with a single line of YAML (#15389)
  • All LVGL 9 events - Complete mapping of LVGL 9 events to ESPHome automation triggers (#15362)
  • Drop shadow and bitmap masking - New bitmap_mask_src style property and A8 image format for drop shadow effects (#15334)

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.

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
  • SPI interface selection (#10285) - New interface config variable lets users select spi2 or spi3 host, resolving conflicts with display components on boards like the M5Stack CoreS3

A new optional interrupt_pin configuration for GPIO expanders eliminates constant I2C/SPI polling, achieving 99.7% reduction in I2C traffic during idle periods (#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

Configuration is simple and fully backward compatible:

pcf8574:
id: my_pcf8574
interrupt_pin: GPIO16 # Connect to INT pin on PCF8574

Two changes significantly improve ESP32 performance out of the box.

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.

SRAM1 as IRAM Option (#14874):

A new sram1_as_iram: true option for the original ESP32 reclaims 40KB of SRAM1 as additional IRAM at no cost to heap. This expands the flash cache window, reducing cache misses for WiFi, BLE, and API operations. ESPHome includes bootloader detection to prevent enabling this on old bootloaders (which would brick the device).

esp32:
framework:
type: esp-idf
advanced:
sram1_as_iram: true

Substitutions and Config System Improvements

Section titled “Substitutions and Config System Improvements”

The substitution engine has been rewritten to perform all variable substitutions in a single linear pass, achieving up to 18x faster config load times in large projects (#14918).

Additionally, substitutions now work in !include file paths (#12213), enabling dynamic file selection:

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

Key Improvements:

  • Single-pass evaluation - Eliminates the old JinjaStr deferred-evaluation workaround; all variables are visible at evaluation time
  • Cross-scope expressions - ${A * B} inside included files now reliably resolves when A is a local var and B is a root substitution
  • Better error messages - Circular dependency warnings include the actual cause and variable paths

The substitution pass has a minor breaking change: when multiple YAML keys resolve to the same substituted name, the last-appearing key now takes precedence (“last writer wins”).

A new signed_ota_verification option enables firmware signature verification during OTA updates without requiring hardware Secure Boot (eFuse burning) (#15357). This protects against tampered firmware from network-based attacks.

Two Workflows:

  • 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

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

Key Features:

  • Automatic dependency discovery - Finds fonts, images, certificates, C++ includes, web assets, local external_components, and secrets
  • Filtered secrets - Only secrets actually referenced by bundled files are included
  • Reproducible archives - Deterministic output with sorted entries for consistent builds
  • Incremental builds - Preserves 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

ESP32 users can now define custom data partitions directly in YAML (#7682). Custom partitions are appended at the end of flash and steal space from the OTA app partitions, enabling use cases like storing calibration data, custom file systems, or additional NVS namespaces.

The partition table generation has also been unified across Arduino and IDF frameworks, and NVS has been moved to the end of flash with significantly increased size (Arduino: 20KB to 384KB, IDF: 436KB to 448KB). The partition layout changes are a breaking change but NVS data is preserved as long as the partition name remains nvs.

esp32:
partitions:
- name: my_data
type: data
subtype: spiffs
size: 0x4000

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

  • SPA06-003 (#14521, #14522, #14523) - Goermicro digital pressure and temperature sensor with both I2C and SPI support, available on Adafruit and Seeed Grove breakout boards
  • HDC2080 (#9331) - Texas Instruments temperature and humidity sensor
  • emonTx (#9027) - OpenEnergyMonitor energy monitoring devices (emonTx, emonPi) via UART, with JSON data parsing, automation triggers, and command sending
  • BMP581 SPI (#13124) - SPI interface support for the BMP581 pressure sensor (previously I2C only)
  • BMP585 support (#15277) - Adds the BMP585 ASIC ID to the existing BMP581 driver
  • ESP8266 crash handler (#15465) - Post-mortem diagnostics matching existing ESP32 and RP2040 implementations, logging exception info and stack-scanned backtrace on reboot after a crash
  • ESP32 Hosted SPI transport (#15551) - SPI transport and 1-bit SDIO bus width support for the esp32_hosted WiFi offloading component
  • ESP32 Partition Table: Partition layout has changed for both Arduino and IDF frameworks. NVS has been moved to the end of flash with increased size (Arduino: 20KB to 384KB, IDF: 436KB to 448KB), and a phy_init partition has been added to Arduino. Existing devices will receive the new partition table on the next OTA update. NVS data is preserved as long as the partition name remains nvs. #7682

  • ESP32 CPU Frequency: Default CPU frequency is now set to the maximum supported for each variant (ESP32/S2/S3/C5: 240MHz, up from 160MHz). This increases power consumption (~30-50% on CPU-bound workloads). Battery-powered or thermally constrained devices should explicitly set cpu_frequency: 160MHZ. #15143

  • ESP8266 RTC Preferences: The crash handler reserves 72 bytes of RTC memory, reducing RTC preferences capacity from 96 to 78 words. Overflow goes to flash automatically. Very unlikely to affect real configs. #15465

  • LVGL Rotation: The rotation option must be moved from the display configuration to the lvgl: block. LVGL now handles rotation directly, with support for runtime rotation changes via lvgl.display.set_rotation. #14955

  • I2S Audio: The legacy I2S driver (use_legacy option) has been removed. The i2s_audio media player sub-component has also been removed. Users should migrate to the speaker media player component. #14932

  • Substitutions: When multiple YAML keys resolve to the same substituted name, the last-appearing key now takes precedence (“last writer wins”), instead of the first. This only affects the edge case where multiple keys in the same dict resolve to the same substituted name. #14918

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

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

Several internal C++ APIs have changed in this release. These are not covered by the formal breaking change policy, but may affect users who write lambdas or developers maintaining external components.

  • Component: Shrunk from 12 to 8 bytes per instance. set_component_source(const LogString *) has been removed and replaced with a protected set_component_source_(uint8_t) index. warn_if_blocking_over_ changed from milliseconds (uint16_t) to centiseconds (uint8_t). #15103

  • Preferences: ESPPreferenceBackend and ESPPreferences virtual base classes have been removed and replaced with concrete final classes per platform. The type aliases ESPPreferenceBackend and ESPPreferences remain as backward-compatible aliases. No caller changes required. #14825

  • Trigger trampolines: Trigger objects for common entity automations (button, sensor, binary_sensor, switch, text_sensor, number, event) are no longer instantiated as separate objects. The Automation::trigger_ back-pointer (protected field) has been removed. build_automation() and Trigger subclasses remain available for external components. #15174

  • TemplatableFn: The TEMPLATABLE_VALUE macro now uses TemplatableFn (4 bytes) instead of TemplatableValue (8 bytes) for trivially copyable types. TemplatableValue<T> for non-string types no longer accepts stateful lambdas (lambdas with captures). TemplatableValue used directly is fully backward compatible. #15545

  • Nextion: get_wave_chan_id() is deprecated in favor of get_wave_channel_id() (removal target: 2026.10.0). Getter methods are now const-qualified; get_variable_name() and get_variable_name_to_send() now return const references instead of copies. Waveform code is gated behind USE_NEXTION_WAVEFORM define. #15204, #15273

  • ATM90E32: get_phase_angle_() return type changed from uint16_t to float for correct precision. Unused last_periodic_millis member removed. #15238

  • AS5600: Dead angle, raw_angle, and position sensor code removed (setters, members, and codegen that were never connected to the config schema). #15254

  • SEN5x: Dead voc_baseline config option removed (was accepted in schema but never passed to C++). Use store_baseline: true for VOC baseline management. #15391

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

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

  • RP2040 PIO LED Strip: CUSTOM removed from the CHIPSETS enum (it crashed at codegen time). Use explicit bit0_high/bit0_low/bit1_high/bit1_low timing parameters for custom LED strips. #15537

  • CallbackManager: std::function replaced with lightweight Callback type. Components registering callbacks must use compatible function signatures. #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. #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
  • 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
  • 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
  • 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)