From 091a05ccde035ed9812aaedabf8b171f9d6aacb7 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Fri, 29 May 2026 01:16:55 -0400 Subject: [PATCH] [esp32_camera] Enable PicolibC Newlib compatibility on IDF 6.0+ (#16703) --- esphome/components/camera_encoder/__init__.py | 7 ++- esphome/components/esp32/__init__.py | 43 ++++++++++++++----- esphome/components/esp32_camera/__init__.py | 8 +++- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/esphome/components/camera_encoder/__init__.py b/esphome/components/camera_encoder/__init__.py index a0c59a517a..7d4cdc881e 100644 --- a/esphome/components/camera_encoder/__init__.py +++ b/esphome/components/camera_encoder/__init__.py @@ -1,5 +1,8 @@ import esphome.codegen as cg -from esphome.components.esp32 import add_idf_component +from esphome.components.esp32 import ( + add_idf_component, + require_libc_picolibc_newlib_compat, +) import esphome.config_validation as cv from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TYPE from esphome.types import ConfigType @@ -51,6 +54,8 @@ async def to_code(config: ConfigType) -> None: cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE])) if config[CONF_TYPE] == ESP32_CAMERA_ENCODER: add_idf_component(name="espressif/esp32-camera", ref="2.1.5") + # esp32-camera 2.1.5 needs the Newlib shim on IDF 6.0+; remove when fixed upstream + require_libc_picolibc_newlib_compat() cg.add_define("USE_ESP32_CAMERA_JPEG_ENCODER") var = cg.new_Pvariable( config[CONF_ID], diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 4e3ffdc1e4..beb41b30f4 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -1245,6 +1245,7 @@ KEY_MBEDTLS_PKCS7_REQUIRED = "mbedtls_pkcs7_required" KEY_FATFS_REQUIRED = "fatfs_required" KEY_MBEDTLS_SHA512_REQUIRED = "mbedtls_sha512_required" KEY_ADC_ONESHOT_IRAM_REQUIRED = "adc_oneshot_iram_required" +KEY_LIBC_PICOLIBC_NEWLIB_COMPAT_REQUIRED = "libc_picolibc_newlib_compat_required" def require_vfs_select() -> None: @@ -1353,6 +1354,15 @@ def require_adc_oneshot_iram() -> None: CORE.data[KEY_ESP32][KEY_ADC_ONESHOT_IRAM_REQUIRED] = True +def require_libc_picolibc_newlib_compat() -> None: + """Keep CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY enabled on IDF 6.0+. + + Call this from components that link against precompiled Newlib binaries + referencing types/symbols the shim provides (e.g. esp32-camera). + """ + CORE.data[KEY_ESP32][KEY_LIBC_PICOLIBC_NEWLIB_COMPAT_REQUIRED] = True + + def _parse_idf_component(value: str) -> ConfigType: """Parse IDF component shorthand syntax like 'owner/component^version'""" # Match operator followed by version-like string (digit or *) @@ -1758,6 +1768,26 @@ async def _write_arduino_libraries_sdkconfig() -> None: add_idf_sdkconfig_option(f"CONFIG_ARDUINO_SELECTIVE_{lib}", lib in enabled_libs) +@coroutine_with_priority(CoroPriority.FINAL) +async def _set_libc_picolibc_newlib_compat() -> None: + """Apply the PicolibC Newlib compatibility shim option on IDF 6.0+. + + IDF 6.0 switched from Newlib to PicolibC; the shim is disabled by default. + Runs at FINAL priority so every require_libc_picolibc_newlib_compat() call + (default priority) is seen before the option is written. A user-supplied + sdkconfig_options value takes precedence. + """ + if idf_version() < cv.Version(6, 0, 0): + return + option = "CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY" + if option in CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS]: + return + add_idf_sdkconfig_option( + option, + CORE.data[KEY_ESP32].get(KEY_LIBC_PICOLIBC_NEWLIB_COMPAT_REQUIRED, False), + ) + + @coroutine_with_priority(CoroPriority.FINAL) async def _add_yaml_idf_components(components: list[ConfigType]): """Add IDF components from YAML config with final priority to override code-added components.""" @@ -2291,17 +2321,8 @@ async def to_code(config): add_idf_sdkconfig_option("CONFIG_MBEDTLS_SHA384_C", False) add_idf_sdkconfig_option("CONFIG_MBEDTLS_SHA512_C", False) - # Disable PicolibC Newlib compatibility shim on IDF 6.0+ - # IDF 6.0 switched from Newlib to PicolibC. The shim provides thread-local - # stdin/stdout/stderr and getreent() for code compiled against Newlib. - # ESPHome doesn't link against Newlib-built libraries that use stdio. - # If a component needs it (e.g. precompiled Newlib binaries), re-enable via: - # esp32: - # framework: - # sdkconfig_options: - # CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY: "y" - if idf_version() >= cv.Version(6, 0, 0): - add_idf_sdkconfig_option("CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY", False) + # FINAL priority: runs after every require_libc_picolibc_newlib_compat() call + CORE.add_job(_set_libc_picolibc_newlib_compat) # Disable regi2c control functions in IRAM # Only needed if using analog peripherals (ADC, DAC, etc.) from ISRs while cache is disabled diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index 9883a0a43e..763a1f3405 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -3,7 +3,11 @@ import logging from esphome import automation, pins import esphome.codegen as cg from esphome.components import i2c -from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option +from esphome.components.esp32 import ( + add_idf_component, + add_idf_sdkconfig_option, + require_libc_picolibc_newlib_compat, +) from esphome.components.psram import DOMAIN as psram_domain import esphome.config_validation as cv from esphome.const import ( @@ -402,6 +406,8 @@ async def to_code(config): add_idf_component(name="espressif/esp32-camera", ref="2.1.5") add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True) add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY", False) + # esp32-camera 2.1.5 needs the Newlib shim on IDF 6.0+; remove when fixed upstream + require_libc_picolibc_newlib_compat() for conf in config.get(CONF_ON_STREAM_START, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)