From 3b2be021b23bd0b2a31817026264517b6771331b Mon Sep 17 00:00:00 2001 From: Julian Lunz <117189+jlunz@users.noreply.github.com> Date: Thu, 25 Jun 2026 08:21:41 +0200 Subject: [PATCH 1/9] [adc] Only call cyw43_thread_enter/exit for VSYS when WiFi is active on RP2040 (#17203) --- esphome/components/adc/adc_sensor_rp2040.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/esphome/components/adc/adc_sensor_rp2040.cpp b/esphome/components/adc/adc_sensor_rp2040.cpp index 8d41edb814..894c346588 100644 --- a/esphome/components/adc/adc_sensor_rp2040.cpp +++ b/esphome/components/adc/adc_sensor_rp2040.cpp @@ -66,15 +66,18 @@ float ADCSensor::sample() { } uint8_t pin = this->pin_->get_pin(); -#ifdef CYW43_USES_VSYS_PIN +#if defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI) if (pin == PICO_VSYS_PIN) { // Measuring VSYS on Raspberry Pico W needs to be wrapped with // `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in // https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and - // VSYS ADC both share GPIO29 + // VSYS ADC both share GPIO29. + // The USE_WIFI guard is required because CYW43_USES_VSYS_PIN can be defined + // transitively (e.g. via lwip_wrap.h) even on non-WiFi boards where the CYW43 + // driver is never initialized; calling cyw43_thread_enter() there hard-faults. cyw43_thread_enter(); } -#endif // CYW43_USES_VSYS_PIN +#endif // defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI) adc_gpio_init(pin); adc_select_input(pin - 26); @@ -84,11 +87,11 @@ float ADCSensor::sample() { aggr.add_sample(raw); } -#ifdef CYW43_USES_VSYS_PIN +#if defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI) if (pin == PICO_VSYS_PIN) { cyw43_thread_exit(); } -#endif // CYW43_USES_VSYS_PIN +#endif // defined(CYW43_USES_VSYS_PIN) && defined(USE_WIFI) if (this->output_raw_) { return aggr.aggregate(); From 054c8ba48593774ce83fe7d4d497fa267a030311 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 26 Jun 2026 00:28:43 +1200 Subject: [PATCH 2/9] [config_validation] Fix multicast typo in error message (#17206) --- esphome/config_validation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 0ef6d212fe..3ff2c975a1 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1455,9 +1455,7 @@ def ipv6address(value): def ipv4address_multi_broadcast(value): address = ipv4address(value) if not (address.is_multicast or (address == IPv4Address("255.255.255.255"))): - raise Invalid( - f"{value} is not a multicasst address nor local broadcast address" - ) + raise Invalid(f"{value} is not a multicast address nor local broadcast address") return address From 5de508ad8caa279ba7b2ba5bcbd474f42f5a9612 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 27 Jun 2026 16:21:36 +0200 Subject: [PATCH 3/9] [es8388] Fix DAC unable to unmute once muted (#17221) Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- esphome/components/es8388/es8388.cpp | 8 +++++++- esphome/components/es8388/es8388_const.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/components/es8388/es8388.cpp b/esphome/components/es8388/es8388.cpp index c015393e14..0b97240230 100644 --- a/esphome/components/es8388/es8388.cpp +++ b/esphome/components/es8388/es8388.cpp @@ -173,8 +173,14 @@ bool ES8388::set_mute_state_(bool mute_state) { ES8388_ERROR_CHECK(this->read_byte(ES8388_DACCONTROL3, &value)); ESP_LOGV(TAG, "Read ES8388_DACCONTROL3: 0x%02X", value); + // Only toggle the DACMute bit; the other bits of this register hold unrelated + // DAC settings that must be preserved. Previously muting overwrote the whole + // register with 0x3C and unmuting never cleared the bit, so once muted the DAC + // could not be unmuted again. if (mute_state) { - value = 0x3C; + value |= ES8388_DACCONTROL3_DAC_MUTE; + } else { + value &= ~ES8388_DACCONTROL3_DAC_MUTE; } ESP_LOGV(TAG, "Setting ES8388_DACCONTROL3 to 0x%02X (muted: %s)", value, YESNO(mute_state)); diff --git a/esphome/components/es8388/es8388_const.h b/esphome/components/es8388/es8388_const.h index 451c9cc026..e081c55dbd 100644 --- a/esphome/components/es8388/es8388_const.h +++ b/esphome/components/es8388/es8388_const.h @@ -38,6 +38,7 @@ static const uint8_t ES8388_ADCCONTROL14 = 0x16; static const uint8_t ES8388_DACCONTROL1 = 0x17; static const uint8_t ES8388_DACCONTROL2 = 0x18; static const uint8_t ES8388_DACCONTROL3 = 0x19; +static const uint8_t ES8388_DACCONTROL3_DAC_MUTE = 0x04; // DACMute, bit 2 of DACCONTROL3 static const uint8_t ES8388_DACCONTROL4 = 0x1a; static const uint8_t ES8388_DACCONTROL5 = 0x1b; static const uint8_t ES8388_DACCONTROL6 = 0x1c; From 782b58bbeb6a7b2c0386ae3f3cd38ef6f0ee54f8 Mon Sep 17 00:00:00 2001 From: "esphome[bot]" <115708604+esphome[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 02:07:06 +0000 Subject: [PATCH 4/9] Bump bundled esphome-device-builder to 1.0.22 --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 8dce7861df..079fd0602b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -32,7 +32,7 @@ RUN \ -r /requirements.txt # Install the ESPHome Device Builder dashboard. -RUN uv pip install --no-cache-dir esphome-device-builder==1.0.21 +RUN uv pip install --no-cache-dir esphome-device-builder==1.0.22 RUN \ platformio settings set enable_telemetry No \ From b127363fa0a8391473bdff934b0aaab264d332e8 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:43:01 +1000 Subject: [PATCH 5/9] [mipi_spi] Bug fixes (#17247) --- esphome/components/mipi_spi/display.py | 2 ++ esphome/components/mipi_spi/mipi_spi.h | 3 +++ esphome/components/mipi_spi/models/ili.py | 7 +------ tests/component_tests/mipi_spi/test_init.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/esphome/components/mipi_spi/display.py b/esphome/components/mipi_spi/display.py index 4162459058..871736abd1 100644 --- a/esphome/components/mipi_spi/display.py +++ b/esphome/components/mipi_spi/display.py @@ -425,6 +425,8 @@ async def to_code(config): dc_pin = await cg.gpio_pin_expression(dc_pin) cg.add(var.set_dc_pin(dc_pin)) + if config.get(CONF_INVERT_COLORS): + cg.add(var.set_invert_colors(True)) if lamb := config.get(CONF_LAMBDA): lambda_ = await cg.process_lambda( lamb, [(display.DisplayRef, "it")], return_type=cg.void diff --git a/esphome/components/mipi_spi/mipi_spi.h b/esphome/components/mipi_spi/mipi_spi.h index d9627899e0..48184fa5c1 100644 --- a/esphome/components/mipi_spi/mipi_spi.h +++ b/esphome/components/mipi_spi/mipi_spi.h @@ -151,6 +151,9 @@ class MipiSpi : public display::Display, this->reset_pin_->digital_write(false); delay(5); this->reset_pin_->digital_write(true); + } else { + // no reset pin, send software reset command + this->write_command_(SW_RESET_CMD); } // need to know when the display is ready for SLPOUT command - will be 120ms after reset diff --git a/esphome/components/mipi_spi/models/ili.py b/esphome/components/mipi_spi/models/ili.py index 5df7a275df..5598a51073 100644 --- a/esphome/components/mipi_spi/models/ili.py +++ b/esphome/components/mipi_spi/models/ili.py @@ -24,13 +24,11 @@ from esphome.components.mipi import ( PWSET, PWSETN, SETEXTC, - SWRESET, VMCTR, VMCTR1, VMCTR2, VSCRSADD, DriverChip, - delay, ) from esphome.components.spi import TYPE_OCTAL @@ -367,7 +365,6 @@ ST7796 = DriverChip( width=320, height=480, initsequence=( - (SWRESET,), (CSCON, 0xC3), (CSCON, 0x96), (VMCTR1, 0x1C), @@ -728,8 +725,6 @@ DriverChip( width=128, height=160, initsequence=( - SWRESET, - delay(10), (FRMCTR1, 0x01, 0x2C, 0x2D), (FRMCTR2, 0x01, 0x2C, 0x2D), (FRMCTR3, 0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D), @@ -786,7 +781,7 @@ ST7796.extend( bus_mode=TYPE_OCTAL, mirror_x=True, reset_pin=4, - dc_pin=0, + dc_pin={"number": 0, "ignore_strapping_warning": True}, invert_colors=True, ) diff --git a/tests/component_tests/mipi_spi/test_init.py b/tests/component_tests/mipi_spi/test_init.py index dbd8e15702..8edbe095b7 100644 --- a/tests/component_tests/mipi_spi/test_init.py +++ b/tests/component_tests/mipi_spi/test_init.py @@ -377,6 +377,6 @@ def test_lvgl_generation( "mipi_spi::MipiSpi();" in main_cpp ) - assert "set_init_sequence({1, 0, 10, 255, 177" in main_cpp + assert "set_init_sequence({177, 3, 1, 44, 45, 178" in main_cpp assert "show_test_card();" not in main_cpp assert "set_auto_clear(false);" in main_cpp From 6c44775bf594ad1d8f51fda1dc0bd71eef91b4a2 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:56:30 -0400 Subject: [PATCH 6/9] [bluetooth_proxy] Fix -Wtype-limits warning with active: false (#17273) --- esphome/components/bluetooth_proxy/bluetooth_proxy.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 10449f21f1..2b6d29da43 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -68,11 +68,15 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, void loop() override; esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override; - void register_connection(BluetoothConnection *connection) { + // maybe_unused: in a passive proxy (active: false) MAX is 0, the body below is removed, and connection is unused. + void register_connection([[maybe_unused]] BluetoothConnection *connection) { + // Guard the always-false comparison (-Wtype-limits) in a passive proxy (active: false), where MAX is 0. +#if BLUETOOTH_PROXY_MAX_CONNECTIONS > 0 if (this->connection_count_ < BLUETOOTH_PROXY_MAX_CONNECTIONS) { this->connections_[this->connection_count_++] = connection; connection->proxy_ = this; } +#endif } void bluetooth_device_request(const api::BluetoothDeviceRequest &msg); From 06c5bcbc668bcf4591fe69569e0558917be389d2 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:57:34 -0400 Subject: [PATCH 7/9] [espnow] Drop oversized received frames to prevent buffer overflow (#17271) --- esphome/components/espnow/espnow_component.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/esphome/components/espnow/espnow_component.cpp b/esphome/components/espnow/espnow_component.cpp index 2756b615a1..91f2c067ca 100644 --- a/esphome/components/espnow/espnow_component.cpp +++ b/esphome/components/espnow/espnow_component.cpp @@ -94,6 +94,15 @@ void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status) } void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) { + // Drop oversized frames before copying. ESP-NOW v2 peers (IDF >= 5.4 builds a + // v2 stack with no opt-out) can send up to ESP_NOW_MAX_DATA_LEN_V2 (1470 B), + // but our receive buffer is ESP_NOW_MAX_DATA_LEN (250 B); copying a larger + // frame would overflow packet_.receive.data. + if (size < 0 || size > ESP_NOW_MAX_DATA_LEN) { + global_esp_now->receive_packet_queue_.increment_dropped_count(); + return; + } + // Allocate an event from the pool ESPNowPacket *packet = global_esp_now->receive_packet_pool_.allocate(); if (packet == nullptr) { @@ -327,13 +336,13 @@ void ESPNowComponent::loop() { // Log dropped received packets periodically uint16_t received_dropped = this->receive_packet_queue_.get_and_reset_dropped_count(); if (received_dropped > 0) { - ESP_LOGW(TAG, "Dropped %u received packets due to buffer overflow", received_dropped); + ESP_LOGW(TAG, "Dropped %u received packets (queue full or oversized frame)", received_dropped); } // Log dropped send packets periodically uint16_t send_dropped = this->send_packet_queue_.get_and_reset_dropped_count(); if (send_dropped > 0) { - ESP_LOGW(TAG, "Dropped %u send packets due to buffer overflow", send_dropped); + ESP_LOGW(TAG, "Dropped %u send packets (queue full)", send_dropped); } } From 4472d3b61bd45f8b8b8ff3c04a90c3fba1410879 Mon Sep 17 00:00:00 2001 From: "esphome[bot]" <115708604+esphome[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:09:48 +1200 Subject: [PATCH 8/9] Bump bundled esphome-device-builder to 1.0.23 (#17316) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 079fd0602b..0c3b27a04d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -32,7 +32,7 @@ RUN \ -r /requirements.txt # Install the ESPHome Device Builder dashboard. -RUN uv pip install --no-cache-dir esphome-device-builder==1.0.22 +RUN uv pip install --no-cache-dir esphome-device-builder==1.0.23 RUN \ platformio settings set enable_telemetry No \ From e47feace11b22d4d7fc30068f18fa983ee828668 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:18:54 +1200 Subject: [PATCH 9/9] Bump version to 2026.6.4 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index bc92241937..e38f280006 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2026.6.3 +PROJECT_NUMBER = 2026.6.4 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index b7ffb9121d..81bde6dfa2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2026.6.3" +__version__ = "2026.6.4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = (