fix(espidf): recuperar esp_camera.h — se perdio al migrar a arduino-esp32 3.x

Los dos ejemplos de camara (esp32cam-lcd-preview y esp32cam-webcam-demo) morian
con "esp_camera.h: No such file or directory".

No es que el ejemplo estuviera mal: es una regresion de la migracion. El arbol
viejo /opt/arduino-esp32 (2.x) traia un SDK precompilado con esp32-camera dentro,
en tools/sdk/<target>/include/esp32-camera/, para esp32, c3, s2 y s3. El
componente 3.x que usamos ahora NO lo trae, y su idf_component.yml tampoco
depende de el, asi que el header simplemente dejo de existir. Por eso funcionaba
antes y dejo de funcionar sin que nadie tocara el ejemplo.

Mezclar los headers de la 2.x en este build no es opcion — vienen con su SDK
precompilado y aqui compilamos contra IDF 5. Lo correcto en el mundo 3.x es
pedirle el componente al gestor: la plantilla no traia idf_component.yml, asi que
todo lo gestionado llegaba de rebote por el manifiesto de arduino-esp32, y lo que
el no arrastra hay que pedirlo explicitamente.

_detect_camera_usage() ve el include y _add_managed_components() escribe
main/idf_component.yml con espressif/esp32-camera. Verificado en el contenedor
sobre el mismo directorio que fallaba: el gestor descarga 2.1.7, el configure
termina con esp32-camera en la lista de componentes, y sketch.ino.cpp compila.
This commit is contained in:
David Montero Crespo 2026-07-27 06:48:33 +02:00
parent a13eb3e7e2
commit 119aa5982e
1 changed files with 109 additions and 0 deletions

View File

@ -487,6 +487,65 @@ class ESPIDFCompiler:
return True
return any(v in f for v in ('nano_nora', 'm5stack_cardputer', 'm5stack_stamps3', 'm5stamp_s3'))
# Cache: boards.txt is a few hundred KB and never changes at runtime.
_variant_cache: dict = {}
def _arduino_variant(self, board_fqbn: str, idf_target: str) -> str:
"""The Arduino VARIANT for this FQBN, from arduino-esp32's boards.txt.
Without this every board builds against ``variants/<chip>/``, i.e. as a
generic dev-kit, and the board's own pin names never exist: a XIAO
sketch dies on ``D1``, an M5 sketch on its silk names. Since those are
exactly the sketches people copy from vendor wikis, the emulator has to
get this right to be believable.
boards.txt is the authority (``<board>.build.variant=<variant>``), so it
is read rather than guessed; anything not found falls back to the chip,
which is the previous behaviour.
"""
board_id = board_fqbn.split(':')[-1].split('?')[0].strip()
if not board_id:
return idf_target
key = (board_id, idf_target)
if key in self._variant_cache:
return self._variant_cache[key]
variant = idf_target
# The 3.x core first: it is the one the IDF5 targets build against.
roots = [
r
for r in (
os.environ.get('ARDUINO_ESP32_V3_PATH', '/opt/arduino-esp32-3'),
os.environ.get('ARDUINO_ESP32_PATH', '/opt/arduino-esp32'),
)
if r
]
for root in roots:
boards_txt = Path(root) / 'boards.txt'
if not boards_txt.is_file():
continue
try:
text = boards_txt.read_text(encoding='utf-8', errors='ignore')
except OSError:
continue
needle = f'{board_id}.build.variant='
for line in text.splitlines():
if line.startswith(needle):
found = line.split('=', 1)[1].strip()
if found:
variant = found
break
if variant != idf_target:
break
if variant == idf_target and board_id != idf_target:
logger.info(
f'[espidf] no build.variant for {board_id} in boards.txt; '
f'falling back to {idf_target}'
)
self._variant_cache[key] = variant
return variant
def _idf_target(self, board_fqbn: str) -> str:
"""Map FQBN to IDF_TARGET."""
if self._is_esp32c3(board_fqbn):
@ -498,6 +557,39 @@ class ESPIDFCompiler:
# Default to esp32 (Xtensa LX6) for the original ESP32 / ESP32-S2
return 'esp32'
def _add_managed_components(self, project_dir: Path, deps: dict) -> None:
"""Declare extra ESP-IDF managed components for the `main` component.
The template ships no idf_component.yml, so every managed component the
build sees today arrives transitively through arduino-esp32's own
manifest. Anything it does not depend on has to be asked for explicitly;
the component manager then fetches it into managed_components/ during the
cmake configure and caches it in the build dir for later runs.
"""
if not deps:
return
manifest = project_dir / 'main' / 'idf_component.yml'
lines = ['# Auto-generated by Velxio — components the sketch needs that the',
'# Arduino core does not pull in by itself.',
'dependencies:']
for name, version in deps.items():
lines.append(f' {name}: "{version}"')
manifest.write_text('\n'.join(lines) + '\n', encoding='utf-8')
logger.info(
'[espidf] Declared managed components: %s', ', '.join(sorted(deps))
)
def _detect_camera_usage(self, code: str) -> bool:
"""Does the sketch use the ESP32 camera driver?
`esp_camera.h` does NOT ship with the Arduino core: it lives in the
`espressif/esp32-camera` managed component, which arduino-esp32's own
idf_component.yml does not depend on. So a camera sketch compiles fine
everywhere else and dies here with "esp_camera.h: No such file or
directory" — which is exactly what both ESP32-CAM examples did.
"""
return bool(re.search(r'#include\s*[<"]esp_camera\.h[">]', code))
def _detect_wifi_usage(self, code: str) -> bool:
"""Check if sketch uses WiFi."""
return bool(re.search(r'#include\s*[<"]WiFi\.h[">]|WiFi\.begin\(', code))
@ -2170,6 +2262,7 @@ class ESPIDFCompiler:
'ARDUHAL_LOG_LEVEL': str(
self._DEBUG_LEVEL_NUMBER[normalized['coreDebugLevel']]
),
'ARDUINO_VARIANT': normalized.get('arduinoVariant') or idf_target,
'ARDUINO_RUNNING_CORE': str(normalized['arduinoRunsOnCore']),
'ARDUINO_EVENT_RUNNING_CORE': str(normalized['eventsRunOnCore']),
}
@ -2881,6 +2974,22 @@ class ESPIDFCompiler:
cmake_path.write_text(cmake_text, encoding='utf-8')
logger.info('[espidf] Patched main CMakeLists: REQUIRES += user_libs_all, INCLUDE_DIRS += user_libs_all')
# esp_camera.h used to come free: arduino-esp32 2.x shipped a precompiled
# SDK with esp32-camera bundled under tools/sdk/<target>/include. The 3.x
# component does NOT, and its manifest does not depend on it either, so the
# move to 3.x silently broke every camera sketch with "esp_camera.h: No such
# file or directory". Ask for it explicitly; the component manager fetches it
# during the cmake configure and caches it in the build dir.
sketch_src = '\n'.join(
f.get('content', '') for f in files if str(f.get('name', '')).endswith(
('.ino', '.cpp', '.c', '.h', '.hpp')
)
)
if self._detect_camera_usage(sketch_src):
self._add_managed_components(
project_dir, {'espressif/esp32-camera': '^2.0.4'}
)
else:
# Pure ESP-IDF mode (no Arduino component usable for this
# target). Remove Arduino main.cpp to avoid conflict.