From 2cfd27b312ba45b17d815fb0839d46d33043d83d Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Fri, 24 Oct 2025 13:01:12 +0200 Subject: [PATCH 01/23] upate camera api to use static objects and add some guards to pin definitions --- src/camera_pins.h | 67 +++++++++++++++++++++++++++------------------ src/modcamera_api.c | 18 ++++++++++-- 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/camera_pins.h b/src/camera_pins.h index 6041cae..fd23766 100644 --- a/src/camera_pins.h +++ b/src/camera_pins.h @@ -2,9 +2,17 @@ #ifndef MICROPY_CAMERA_MODEL_PINS_H #define MICROPY_CAMERA_MODEL_PINS_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MICROPY_CAMERA_PIN_NONE +#define MICROPY_CAMERA_PIN_NONE (-1) +#endif + #if defined(MICROPY_CAMERA_MODEL_WROVER_KIT) -#define MICROPY_CAMERA_PIN_PWDN -1 -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 21 #define MICROPY_CAMERA_PIN_SIOD 26 #define MICROPY_CAMERA_PIN_SIOC 27 @@ -22,8 +30,8 @@ #define MICROPY_CAMERA_PIN_PCLK 22 #elif defined(MICROPY_CAMERA_MODEL_ESP_EYE) -#define MICROPY_CAMERA_PIN_PWDN -1 -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 4 #define MICROPY_CAMERA_PIN_SIOD 18 #define MICROPY_CAMERA_PIN_SIOC 23 @@ -41,7 +49,7 @@ #define MICROPY_CAMERA_PIN_PCLK 25 #elif defined(MICROPY_CAMERA_MODEL_M5STACK_PSRAM) || defined(MICROPY_CAMERA_MODEL_M5STACK_UNITCAM) -#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_RESET 15 #define MICROPY_CAMERA_PIN_XCLK 27 #define MICROPY_CAMERA_PIN_SIOD 25 @@ -60,7 +68,7 @@ #define MICROPY_CAMERA_PIN_PCLK 21 #elif defined(MICROPY_CAMERA_MODEL_M5STACK_V2_PSRAM) || defined(MICROPY_CAMERA_MODEL_M5STACK_WIDE) -#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_RESET 15 #define MICROPY_CAMERA_PIN_XCLK 27 #define MICROPY_CAMERA_PIN_SIOD 22 @@ -79,7 +87,7 @@ #define MICROPY_CAMERA_PIN_PCLK 21 #elif defined(MICROPY_CAMERA_MODEL_M5STACK_ESP32CAM) -#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_RESET 15 #define MICROPY_CAMERA_PIN_XCLK 27 #define MICROPY_CAMERA_PIN_SIOD 25 @@ -98,7 +106,7 @@ #define MICROPY_CAMERA_PIN_PCLK 21 #elif defined(MICROPY_CAMERA_MODEL_M5STACK_CAMS3_UNIT) -#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_RESET 21 #define MICROPY_CAMERA_PIN_XCLK 11 #define MICROPY_CAMERA_PIN_SIOD 17 @@ -117,8 +125,8 @@ #define MICROPY_CAMERA_PIN_PCLK 12 #elif defined(MICROPY_CAMERA_MODEL_M5STACK_ATOM_S3R) -#define MICROPY_CAMERA_PIN_PWDN -1 //needs to be low to run -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE //needs to be low to run +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 21 #define MICROPY_CAMERA_PIN_SIOD 12 #define MICROPY_CAMERA_PIN_SIOC 9 @@ -137,7 +145,7 @@ #elif defined(MICROPY_CAMERA_MODEL_AI_THINKER) #define MICROPY_CAMERA_PIN_PWDN 32 -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 0 #define MICROPY_CAMERA_PIN_SIOD 26 #define MICROPY_CAMERA_PIN_SIOC 27 @@ -155,8 +163,8 @@ #define MICROPY_CAMERA_PIN_PCLK 22 #elif defined(MICROPY_CAMERA_MODEL_XIAO_ESP32S3) -#define MICROPY_CAMERA_PIN_PWDN -1 -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 10 #define MICROPY_CAMERA_PIN_SIOD 40 #define MICROPY_CAMERA_PIN_SIOC 39 @@ -203,8 +211,8 @@ #define MICROPY_CAMERA_PIN_PCLK 25 #elif defined(MICROPY_CAMERA_MODEL_ESP32S3_CAM_LCD) -#define MICROPY_CAMERA_PIN_PWDN -1 -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 40 #define MICROPY_CAMERA_PIN_SIOD 17 #define MICROPY_CAMERA_PIN_SIOC 18 @@ -222,8 +230,8 @@ #define MICROPY_CAMERA_PIN_PCLK 11 #elif defined(MICROPY_CAMERA_MODEL_ESP32S3_EYE) || defined(MICROPY_CAMERA_MODEL_FREENOVE_ESP32S3_CAM) -#define MICROPY_CAMERA_PIN_PWDN -1 -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 15 #define MICROPY_CAMERA_PIN_SIOD 4 #define MICROPY_CAMERA_PIN_SIOC 5 @@ -241,8 +249,8 @@ #define MICROPY_CAMERA_PIN_PCLK 13 #elif defined(MICROPY_CAMERA_MODEL_DFRobot_ESP32S3) -#define MICROPY_CAMERA_PIN_PWDN -1 -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 45 #define MICROPY_CAMERA_PIN_SIOD 1 #define MICROPY_CAMERA_PIN_SIOC 2 @@ -279,8 +287,8 @@ #define MICROPY_CAMERA_PIN_PCLK 21 #elif defined(MICROPY_CAMERA_MODEL_TTGO_T_CAMERA_PLUS) -#define MICROPY_CAMERA_PIN_PWDN -1 -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 4 #define MICROPY_CAMERA_PIN_SIOD 18 #define MICROPY_CAMERA_PIN_SIOC 23 @@ -299,8 +307,8 @@ #elif defined(MICROPY_CAMERA_MODEL_NEW_ESPS3_RE1_0) // aliexpress board with label RE:1.0, uses slow 8MB QSPI PSRAM, only 4MB addressable -#define MICROPY_CAMERA_PIN_PWDN -1 -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 10 #define MICROPY_CAMERA_PIN_SIOD 21 #define MICROPY_CAMERA_PIN_SIOC 14 @@ -318,11 +326,11 @@ #define MICROPY_CAMERA_PIN_PCLK 7 #elif defined(MICROPY_CAMERA_MODEL_XENOIONEX) -#define MICROPY_CAMERA_PIN_PWDN -1 -#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_PWDN MICROPY_CAMERA_PIN_NONE +#define MICROPY_CAMERA_PIN_RESET MICROPY_CAMERA_PIN_NONE #define MICROPY_CAMERA_PIN_XCLK 1 // Can use -#define MICROPY_CAMERA_PIN_SIOD 8 // Can use other i2c SDA pin, set this to -1 | If not using i2c set to 8 or 47 -#define MICROPY_CAMERA_PIN_SIOC 9 // Can use other i2c SCL pin, set this to -1 | If not using i2c set to 9 or 21 +#define MICROPY_CAMERA_PIN_SIOD 8 // Can use other i2c SDA pin, set this to MICROPY_CAMERA_PIN_NONE | If not using i2c set to 8 or 47 +#define MICROPY_CAMERA_PIN_SIOC 9 // Can use other i2c SCL pin, set this to MICROPY_CAMERA_PIN_NONE | If not using i2c set to 9 or 21 #define MICROPY_CAMERA_PIN_D7 3 //D7 #define MICROPY_CAMERA_PIN_D6 18 //D6 @@ -337,4 +345,9 @@ #define MICROPY_CAMERA_PIN_PCLK 2 #endif // definition of camera pins for different boards + +#ifdef __cplusplus +} +#endif + #endif // MICROPY_CAMERA_MODEL_PINS_H \ No newline at end of file diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 39afa71..8126326 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -38,6 +38,9 @@ typedef struct mp_camera_obj_t mp_camera_obj; const mp_obj_type_t camera_type; +// Static camera object to ensure only one instance exists at a time +static mp_camera_obj_t mp_camera_singleton = { .base = { NULL } }; + //Constructor static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_data_pins, ARG_pixel_clock_pin, ARG_vsync_pin, ARG_href_pin, ARG_sda_pin, ARG_scl_pin, ARG_xclock_pin, ARG_xclock_frequency, ARG_powerdown_pin, ARG_reset_pin, ARG_pixel_format, ARG_frame_size, ARG_jpeg_quality, ARG_fb_count, ARG_grab_mode, ARG_init, NUM_ARGS }; @@ -126,8 +129,19 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz int8_t fb_count = args[ARG_fb_count].u_int; mp_camera_grabmode_t grab_mode = args[ARG_grab_mode].u_int; - mp_camera_obj_t *self = mp_obj_malloc_with_finaliser(mp_camera_obj_t, &camera_type); - self->base.type = &camera_type; + // Get static singleton object + mp_camera_obj_t *self = &mp_camera_singleton; + + // If camera was already initialized, deinit it first + bool first_init = false; + if (self->base.type == NULL) { + // First time initialization + self->base.type = &camera_type; + first_init = true; + } else { + // Camera already exists, deinit it before reinitializing + mp_camera_hal_deinit(self); + } mp_camera_hal_construct(self, data_pins, xclock_pin, pixel_clock_pin, vsync_pin, href_pin, powerdown_pin, reset_pin, sda_pin, scl_pin, xclock_frequency, pixel_format, frame_size, jpeg_quality, fb_count, grab_mode); From 1052cad340cc2387591223c3f1800bf7a27a1dbe Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 25 Oct 2025 10:26:19 +0200 Subject: [PATCH 02/23] Add suport of i2c reusage (experimental) --- README.md | 37 ++++++++++++++++++++++++ src/modcamera.c | 13 +++++++-- src/modcamera.h | 4 ++- src/modcamera_api.c | 68 +++++++++++++++++++++++++++++++++++++-------- 4 files changed, 106 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3768354..d5e7554 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ If you want to play arround with AI, take a look at the [micropython binding for - [Freeing the buffer](#freeing-the-buffer) - [Is a frame available](#is-frame-available) - [Additional methods](#additional-methods) + - [I2C Integration](#i2c-integration) - [Additional information](#additional-information) - [Build your custom firmware](#build-your-custom-firmware) - [Setting up the build environment (DIY method)](#setting-up-the-build-environment-diy-method) @@ -209,6 +210,42 @@ import camera vers = camera.Version() ``` +### I2C Integration + +The camera uses I2C (SCCB protocol) to communicate with the camera sensor. You can share this I2C bus with other devices by passing an external I2C object to the camera: + +#### Sharing I2C with Camera + +```python +import machine + +# Create your own I2C object first +i2c = machine.I2C(0, scl=22, sda=21, freq=400000) + +# Pass it to the camera (no need for sda_pin/scl_pin) +cam = camera.Camera(i2c=i2c, data_pins=..., pclk_pin=..., ...) + +# The same I2C object can be used for other devices on the same bus! +devices = i2c.scan() +print(f"I2C devices found: {devices}") + +# You can communicate with other I2C devices while camera is running +i2c.writeto(0x42, b'\x00\x01') # Write to another device + +# Camera sensor communication works too +cam.set_saturation(1) # Uses the shared I2C bus +``` + +#### Alternative: Camera Creates Its Own I2C (Default) + +```python +# Camera creates and manages its own I2C internally +cam = camera.Camera(sda_pin=21, scl_pin=22, ...) + +# In this mode, you cannot share I2C with other devices +# Use the first method if you need to share I2C +``` + ### Additional information The firmware images support the following cameras out of the box, but is therefore big: OV7670, OV7725, OV2640, OV3660, OV5640, NT99141, GC2145, GC032A, GC0308, BF3005, BF20A6, SC030IOT diff --git a/src/modcamera.c b/src/modcamera.c index 5a0bd05..d7307f5 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -113,6 +113,7 @@ void mp_camera_hal_construct( int8_t reset_pin, int8_t sccb_sda_pin, int8_t sccb_scl_pin, + int8_t sccb_i2c_port, int32_t xclk_freq_hz, mp_camera_pixformat_t pixel_format, mp_camera_framesize_t frame_size, @@ -134,9 +135,15 @@ void mp_camera_hal_construct( self->camera_config.pin_pwdn = powerdown_pin; self->camera_config.pin_reset = reset_pin; self->camera_config.pin_xclk = external_clock_pin; - self->camera_config.pin_sscb_sda = sccb_sda_pin; - self->camera_config.pin_sscb_scl = sccb_scl_pin; - + if (sccb_i2c_port != -1) { + self->camera_config.sccb_i2c_port = sccb_i2c_port; + self->camera_config.pin_sscb_sda = -1; // Set sda_pin = -1 to signal esp_camera_init to use SCCB_Use_Port() + self->camera_config.pin_sscb_scl = -1; + } else { + self->camera_config.pin_sscb_sda = sccb_sda_pin; + self->camera_config.pin_sscb_scl = sccb_scl_pin; + self->camera_config.sccb_i2c_port = sccb_i2c_port; + } self->camera_config.frame_size = frame_size; self->camera_config.jpeg_quality = jpeg_quality; //save value in here, but will be corrected (with map) before passing it to the esp32-driver diff --git a/src/modcamera.h b/src/modcamera.h index a3ce749..ce1a96e 100644 --- a/src/modcamera.h +++ b/src/modcamera.h @@ -119,8 +119,9 @@ typedef hal_camera_gainceiling_t mp_camera_gainceiling_t; * @param href_pin HREF pin. * @param powerdown_pin Power down pin. * @param reset_pin Reset pin. - * @param sccb_sda_pin SCCB SDA pin. + * @param sccb_sda_pin SCCB SDA pin (set to -1 to use sccb_i2c_port instead). * @param sccb_scl_pin SCCB SCL pin. + * @param sccb_i2c_port I2C port number to use (only if sccb_sda_pin is -1). * @param xclk_freq_hz External clock frequency in Hz. * @param pixel_format Pixel format. * @param frame_size Frame size. @@ -139,6 +140,7 @@ extern void mp_camera_hal_construct( int8_t reset_pin, int8_t sccb_sda_pin, int8_t sccb_scl_pin, + int8_t sccb_i2c_port, int32_t xclk_freq_hz, mp_camera_pixformat_t pixel_format, mp_camera_framesize_t frame_size, diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 8126326..f4eda17 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -35,6 +35,13 @@ #include "modcamera.h" +#if MICROPY_HW_ESP_NEW_I2C_DRIVER +#include "driver/i2c_master.h" +#else +#include "driver/i2c.h" +#include "hal/i2c_ll.h" +#endif + typedef struct mp_camera_obj_t mp_camera_obj; const mp_obj_type_t camera_type; @@ -43,7 +50,7 @@ static mp_camera_obj_t mp_camera_singleton = { .base = { NULL } }; //Constructor static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_data_pins, ARG_pixel_clock_pin, ARG_vsync_pin, ARG_href_pin, ARG_sda_pin, ARG_scl_pin, ARG_xclock_pin, ARG_xclock_frequency, ARG_powerdown_pin, ARG_reset_pin, ARG_pixel_format, ARG_frame_size, ARG_jpeg_quality, ARG_fb_count, ARG_grab_mode, ARG_init, NUM_ARGS }; + enum { ARG_data_pins, ARG_pixel_clock_pin, ARG_vsync_pin, ARG_href_pin, ARG_sda_pin, ARG_scl_pin, ARG_xclock_pin, ARG_i2c, ARG_xclock_frequency, ARG_powerdown_pin, ARG_reset_pin, ARG_pixel_format, ARG_frame_size, ARG_jpeg_quality, ARG_fb_count, ARG_grab_mode, ARG_init, NUM_ARGS }; static const mp_arg_t allowed_args[] = { #ifdef MICROPY_CAMERA_ALL_REQ_PINS_DEFINED { MP_QSTR_data_pins, MP_ARG_OBJ | MP_ARG_KW_ONLY , { .u_obj = MP_ROM_NONE } }, @@ -58,10 +65,11 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz { MP_QSTR_pclk_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, { MP_QSTR_vsync_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, { MP_QSTR_href_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, - { MP_QSTR_sda_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, - { MP_QSTR_scl_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, + { MP_QSTR_sda_pin, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = -1 } }, + { MP_QSTR_scl_pin, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = -1 } }, { MP_QSTR_xclk_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, #endif + { MP_QSTR_i2c, MP_ARG_OBJ | MP_ARG_KW_ONLY, { .u_obj = MP_ROM_NONE } }, { MP_QSTR_xclk_freq, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_XCLK_FREQ } }, { MP_QSTR_powerdown_pin, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_PIN_PWDN } }, { MP_QSTR_reset_pin, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_PIN_RESET } }, @@ -111,8 +119,48 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz int8_t pixel_clock_pin = args[ARG_pixel_clock_pin].u_int; int8_t vsync_pin = args[ARG_vsync_pin].u_int; int8_t href_pin = args[ARG_href_pin].u_int; - int8_t sda_pin = args[ARG_sda_pin].u_int; - int8_t scl_pin = args[ARG_scl_pin].u_int; + + // Handle I2C configuration: either use I2C object or individual pins + int8_t sda_pin = -1; + int8_t scl_pin = -1; + int8_t i2c_port = -1; + mp_obj_t i2c_obj = args[ARG_i2c].u_obj; + + if (i2c_obj != MP_ROM_NONE) { + // External I2C object provided - extract port number from it + extern const mp_obj_type_t machine_i2c_type; + if (!mp_obj_is_type(i2c_obj, &machine_i2c_type)) { + mp_raise_TypeError(MP_ERROR_TEXT("i2c must be a machine.I2C object")); + } + + // Get the I2C port from the I2C object + // Structure matches machine_hw_i2c_obj_t from machine_i2c.c + typedef struct _machine_hw_i2c_obj_t { + mp_obj_base_t base; + i2c_port_t port : 8; + gpio_num_t scl : 8; + gpio_num_t sda : 8; + uint32_t freq; + uint32_t timeout_us; + } machine_hw_i2c_obj_t; + + machine_hw_i2c_obj_t *i2c = (machine_hw_i2c_obj_t *)MP_OBJ_TO_PTR(i2c_obj); + i2c_port = (int8_t)i2c->port; + sda_pin = i2c->sda; + scl_pin = i2c->scl; + } else { + // Use individual pins + sda_pin = args[ARG_sda_pin].u_int; + scl_pin = args[ARG_scl_pin].u_int; + + // Validate that pins are provided (if no default pins defined) + #ifndef MICROPY_CAMERA_ALL_REQ_PINS_DEFINED + if (sda_pin == -1 || scl_pin == -1) { + mp_raise_ValueError(MP_ERROR_TEXT("Either i2c object or sda_pin/scl_pin must be specified")); + } + #endif + } + int8_t xclock_pin = args[ARG_xclock_pin].u_int; int32_t xclock_frequency = args[ARG_xclock_frequency].u_int; if (xclock_frequency < 1000) { @@ -132,21 +180,17 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz // Get static singleton object mp_camera_obj_t *self = &mp_camera_singleton; - // If camera was already initialized, deinit it first - bool first_init = false; if (self->base.type == NULL) { - // First time initialization self->base.type = &camera_type; - first_init = true; } else { - // Camera already exists, deinit it before reinitializing - mp_camera_hal_deinit(self); + mp_camera_hal_deinit(self); // Camera already exists, deinit it before reinitializing } mp_camera_hal_construct(self, data_pins, xclock_pin, pixel_clock_pin, vsync_pin, href_pin, powerdown_pin, reset_pin, - sda_pin, scl_pin, xclock_frequency, pixel_format, frame_size, jpeg_quality, fb_count, grab_mode); + sda_pin, scl_pin, i2c_port, xclock_frequency, pixel_format, frame_size, jpeg_quality, fb_count, grab_mode); mp_camera_hal_init(self); + mp_hal_delay_ms(10); // Small delay to ensure I2C/SCCB is fully initialized if (mp_camera_hal_capture(self) == mp_const_none){ mp_camera_hal_deinit(self); mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to capture initial frame. Construct a new object with appropriate configuration.")); From 12a0801a636da26700c4777494804d6fb8a8641b Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sun, 26 Oct 2025 11:17:23 +0100 Subject: [PATCH 03/23] README.md aktualisieren --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5e7554..0f9b524 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ vers = camera.Version() ### I2C Integration -The camera uses I2C (SCCB protocol) to communicate with the camera sensor. You can share this I2C bus with other devices by passing an external I2C object to the camera: +The camera uses I2C (SCCB protocol) to communicate with the camera sensor. You can share this I2C bus with other devices by passing an external I2C object (SoftI2C not supported) to the camera: #### Sharing I2C with Camera From ebbd907f9a791b9a2d26e50a1aaccaf8916afebc Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Tue, 28 Oct 2025 06:39:22 +0100 Subject: [PATCH 04/23] Add properties to API --- src/modcamera_api.c | 215 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) diff --git a/src/modcamera_api.c b/src/modcamera_api.c index f4eda17..77baabc 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -280,10 +280,223 @@ static mp_obj_t mp_camera_obj___exit__(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_camera___exit___obj, 4, 4, mp_camera_obj___exit__); +// Property handler +static void camera_obj_property(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (dest[0] == MP_OBJ_NULL) { + // Load (reading) + switch (attr) { + // Read-only properties + case MP_QSTR_pixel_format: + dest[0] = mp_obj_new_int(mp_camera_hal_get_pixel_format(self)); + break; + case MP_QSTR_grab_mode: + dest[0] = mp_obj_new_int(mp_camera_hal_get_grab_mode(self)); + break; + case MP_QSTR_fb_count: + dest[0] = mp_obj_new_int(mp_camera_hal_get_fb_count(self)); + break; + case MP_QSTR_pixel_width: + dest[0] = mp_obj_new_int(mp_camera_hal_get_pixel_width(self)); + break; + case MP_QSTR_pixel_height: + dest[0] = mp_obj_new_int(mp_camera_hal_get_pixel_height(self)); + break; + case MP_QSTR_max_frame_size: + dest[0] = mp_obj_new_int(mp_camera_hal_get_max_frame_size(self)); + break; + case MP_QSTR_sensor_name: + dest[0] = mp_obj_new_str_from_cstr(mp_camera_hal_get_sensor_name(self)); + break; + + // Read-write properties + case MP_QSTR_frame_size: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_frame_size(self)); + break; + case MP_QSTR_contrast: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_contrast(self)); + break; + case MP_QSTR_brightness: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_brightness(self)); + break; + case MP_QSTR_saturation: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_saturation(self)); + break; + case MP_QSTR_sharpness: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_sharpness(self)); + break; + case MP_QSTR_denoise: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_denoise(self)); + break; + case MP_QSTR_gainceiling: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_gainceiling(self)); + break; + case MP_QSTR_quality: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_quality(self)); + break; + case MP_QSTR_colorbar: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_colorbar(self)); + break; + case MP_QSTR_whitebal: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_whitebal(self)); + break; + case MP_QSTR_gain_ctrl: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_gain_ctrl(self)); + break; + case MP_QSTR_exposure_ctrl: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_exposure_ctrl(self)); + break; + case MP_QSTR_hmirror: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_hmirror(self)); + break; + case MP_QSTR_vflip: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_vflip(self)); + break; + case MP_QSTR_aec2: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_aec2(self)); + break; + case MP_QSTR_awb_gain: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_awb_gain(self)); + break; + case MP_QSTR_agc_gain: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_agc_gain(self)); + break; + case MP_QSTR_aec_value: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_aec_value(self)); + break; + case MP_QSTR_special_effect: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_special_effect(self)); + break; + case MP_QSTR_wb_mode: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_wb_mode(self)); + break; + case MP_QSTR_ae_level: + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_ae_level(self)); + break; + case MP_QSTR_dcw: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_dcw(self)); + break; + case MP_QSTR_bpc: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_bpc(self)); + break; + case MP_QSTR_wpc: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_wpc(self)); + break; + case MP_QSTR_raw_gma: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_raw_gma(self)); + break; + case MP_QSTR_lenc: + dest[0] = mp_obj_new_bool(mp_camera_hal_get_lenc(self)); + break; + default: + dest[1] = MP_OBJ_SENTINEL; + } + } else if (dest[1] != MP_OBJ_NULL) { + // Store (writing) + switch (attr) { + // Read-only properties + case MP_QSTR_pixel_format: + case MP_QSTR_grab_mode: + case MP_QSTR_fb_count: + case MP_QSTR_pixel_width: + case MP_QSTR_pixel_height: + case MP_QSTR_max_frame_size: + case MP_QSTR_sensor_name: + mp_raise_AttributeError(MP_ERROR_TEXT("read-only property")); + break; + + // Read-write properties + case MP_QSTR_frame_size: + mp_camera_hal_set_frame_size(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_contrast: + mp_camera_hal_set_contrast(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_brightness: + mp_camera_hal_set_brightness(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_saturation: + mp_camera_hal_set_saturation(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_sharpness: + mp_camera_hal_set_sharpness(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_denoise: + mp_camera_hal_set_denoise(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_gainceiling: + mp_camera_hal_set_gainceiling(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_quality: + mp_camera_hal_set_quality(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_colorbar: + mp_camera_hal_set_colorbar(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_whitebal: + mp_camera_hal_set_whitebal(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_gain_ctrl: + mp_camera_hal_set_gain_ctrl(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_exposure_ctrl: + mp_camera_hal_set_exposure_ctrl(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_hmirror: + mp_camera_hal_set_hmirror(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_vflip: + mp_camera_hal_set_vflip(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_aec2: + mp_camera_hal_set_aec2(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_awb_gain: + mp_camera_hal_set_awb_gain(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_agc_gain: + mp_camera_hal_set_agc_gain(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_aec_value: + mp_camera_hal_set_aec_value(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_special_effect: + mp_camera_hal_set_special_effect(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_wb_mode: + mp_camera_hal_set_wb_mode(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_ae_level: + mp_camera_hal_set_ae_level(self, mp_obj_get_int(dest[1])); + break; + case MP_QSTR_dcw: + mp_camera_hal_set_dcw(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_bpc: + mp_camera_hal_set_bpc(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_wpc: + mp_camera_hal_set_wpc(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_raw_gma: + mp_camera_hal_set_raw_gma(self, mp_obj_is_true(dest[1])); + break; + case MP_QSTR_lenc: + mp_camera_hal_set_lenc(self, mp_obj_is_true(dest[1])); + break; + default: + return; + } + dest[0] = MP_OBJ_NULL; + } +} + // Camera property functions // Camera sensor property functions #define CREATE_GETTER(property, get_function) \ static mp_obj_t camera_get_##property(const mp_obj_t self_in) { \ + mp_warning(NULL, "get_" #property "() is deprecated. Use the " #property " property instead."); \ mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); \ return get_function(mp_camera_hal_get_##property(self)); \ } \ @@ -291,6 +504,7 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_camera___exit___obj, 4, 4, mp_came #define CREATE_SETTER(property, set_conversion) \ static mp_obj_t camera_set_##property(const mp_obj_t self_in, const mp_obj_t arg) { \ + mp_warning(NULL, "set_" #property "() is deprecated. Use the " #property " property instead."); \ mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); \ mp_camera_hal_set_##property(self, set_conversion(arg)); \ if (mp_camera_hal_get_##property(self) != set_conversion(arg)) { \ @@ -447,6 +661,7 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_TYPE_FLAG_NONE, make_new, mp_camera_make_new, print, mp_camera_hal_print, + attr, camera_obj_property, locals_dict, &camera_camera_locals_dict ); From 3ff272a18e5f09da11a36b5fc90353d8fe631543 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Tue, 28 Oct 2025 06:48:43 +0100 Subject: [PATCH 05/23] Add properties to API --- src/modcamera_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 77baabc..193a634 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -403,7 +403,7 @@ static void camera_obj_property(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { case MP_QSTR_pixel_height: case MP_QSTR_max_frame_size: case MP_QSTR_sensor_name: - mp_raise_AttributeError(MP_ERROR_TEXT("read-only property")); + mp_raise_ValueError(MP_ERROR_TEXT("read-only property")); break; // Read-write properties From 3d55ebbfd610a2b2d9bd353fe1ac0964fe263b98 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Tue, 28 Oct 2025 07:05:35 +0100 Subject: [PATCH 06/23] optimize property size --- src/modcamera_api.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 193a634..b882eaa 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -289,13 +289,13 @@ static void camera_obj_property(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { switch (attr) { // Read-only properties case MP_QSTR_pixel_format: - dest[0] = mp_obj_new_int(mp_camera_hal_get_pixel_format(self)); + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_pixel_format(self)); break; case MP_QSTR_grab_mode: - dest[0] = mp_obj_new_int(mp_camera_hal_get_grab_mode(self)); + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_grab_mode(self)); break; case MP_QSTR_fb_count: - dest[0] = mp_obj_new_int(mp_camera_hal_get_fb_count(self)); + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_fb_count(self)); break; case MP_QSTR_pixel_width: dest[0] = mp_obj_new_int(mp_camera_hal_get_pixel_width(self)); @@ -304,7 +304,7 @@ static void camera_obj_property(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { dest[0] = mp_obj_new_int(mp_camera_hal_get_pixel_height(self)); break; case MP_QSTR_max_frame_size: - dest[0] = mp_obj_new_int(mp_camera_hal_get_max_frame_size(self)); + dest[0] = MP_OBJ_NEW_SMALL_INT(mp_camera_hal_get_max_frame_size(self)); break; case MP_QSTR_sensor_name: dest[0] = mp_obj_new_str_from_cstr(mp_camera_hal_get_sensor_name(self)); @@ -524,12 +524,12 @@ static void camera_obj_property(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { { MP_ROM_QSTR(MP_QSTR_set_##property), MP_ROM_PTR(&camera_set_##property##_obj) } CREATE_GETSET_FUNCTIONS(frame_size, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); -CREATE_GETTER(pixel_format, mp_obj_new_int); -CREATE_GETTER(grab_mode, mp_obj_new_int); -CREATE_GETTER(fb_count, mp_obj_new_int); +CREATE_GETTER(pixel_format, MP_OBJ_NEW_SMALL_INT); +CREATE_GETTER(grab_mode, MP_OBJ_NEW_SMALL_INT); +CREATE_GETTER(fb_count, MP_OBJ_NEW_SMALL_INT); CREATE_GETTER(pixel_width, mp_obj_new_int); CREATE_GETTER(pixel_height, mp_obj_new_int); -CREATE_GETTER(max_frame_size, mp_obj_new_int); +CREATE_GETTER(max_frame_size, MP_OBJ_NEW_SMALL_INT); CREATE_GETTER(sensor_name, mp_obj_new_str_from_cstr); CREATE_GETSET_FUNCTIONS(contrast, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); CREATE_GETSET_FUNCTIONS(brightness, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); From b31fef740c26db5505db7966da3b7039d88920cf Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:09:45 +0100 Subject: [PATCH 07/23] ESP32.yml aktualisieren --- .github/workflows/ESP32.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 3ad8407..9b234b4 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -88,8 +88,7 @@ jobs: # git submodule update --init --depth 1 cd mpy-cross make - cd ~/micropython/ports/esp32 - make submodules + echo "Micropython setup successfully" source ~/micropython/tools/ci.sh && echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV @@ -180,7 +179,8 @@ jobs: echo "FW_NAME=${BUILD_TARGET}" >> $GITHUB_ENV FINAL_CMD="${IDF_CMD} build" fi - make USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake BOARD=$BOARD_NAME submodules + + make BOARD=$BOARD_NAME submodules echo "Running command: $FINAL_CMD" eval $FINAL_CMD cd ~/micropython/ports/esp32/build-${BUILD_TARGET} From 82c64afdee11c8e18ef3637752edf03eb6e02979 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 22 Nov 2025 04:53:00 +0100 Subject: [PATCH 08/23] update file structure (improve) and cmake to better handle deps --- .github/workflows/ESP32.yml | 4 +- CMakeLists.txt | 5 ++ README.md | 12 ++--- idf_component.yml | 7 +++ src/manifest.py => manifest.py | 2 +- micropython.cmake | 96 ++++++++++++++++++++++++++++++++++ src/micropython.cmake | 93 -------------------------------- 7 files changed, 116 insertions(+), 103 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 idf_component.yml rename src/manifest.py => manifest.py (80%) create mode 100644 micropython.cmake delete mode 100644 src/micropython.cmake diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 9b234b4..ece3284 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -168,9 +168,9 @@ jobs: IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" if [ -n "${BOARD_VARIANT}" ]; then - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" else - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" fi if [ -n "${CAMERA_MODEL}" ]; then echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1d37979 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,5 @@ +# IDF component wrapper for micropython-camera-API user module +# This allows the IDF component manager to process idf_component.yml +# The actual MicroPython module is built via micropython.cmake + +idf_component_register() diff --git a/README.md b/README.md index 0f9b524..75fc6c1 100644 --- a/README.md +++ b/README.md @@ -258,21 +258,19 @@ To build the project, follow these instructions: - [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/v5.2.3/esp32/get-started/index.html): I used version 5.2.3, but it might work with other versions (see notes). - Clone the micropython repo and this repo in a folder, e.g. "MyESPCam". MicroPython version 1.24 or higher is required (at least commit 92484d8). -- You will have to add the ESP32-Camera driver from my fork. To do this, add the following to the respective idf_component.yml file (e.g. in micropython/ports/esp32/main_esp32s3/idf_component.yml): +- You will have to add the ESP32-Camera driver. To do this, add the following to the respective idf_component.yml file (e.g. in micropython/ports/esp32/main/idf_component.yml): ```yml espressif/esp32-camera: git: https://github.com/cnadler86/esp32-camera.git ``` -Alternatively, you can clone the repository inside the esp-idf/components folder instead of altering the idf_component.yml file. - ### Add camera configurations to your board (optional, but recommended) #### Supported camera models This project supports various boards with camera interface out of the box. You typically only need to add a single line to your board config file ("mpconfigboard.h). -Example (don't forget to add the empty line at the bottom): +Example: ```c #define MICROPY_CAMERA_MODEL_WROVER_KIT 1 @@ -345,9 +343,9 @@ To build the project, you could do it the following way: ```bash . /esp-idf/export.sh cd MyESPCam/micropython/ports/esp32 -make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOARD= clean -make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOARD= submodules -make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOARD= all +make USER_C_MODULES=../../../../micropython-camera-API/micropython.cmake BOARD= clean +make USER_C_MODULES=../../../../micropython-camera-API/micropython.cmake BOARD= submodules +make USER_C_MODULES=../../../../micropython-camera-API/micropython.cmake BOARD= all ``` Micropython and camera-api folders are at the same level. Note that you need those extra "/../"s while been inside the esp32 port folder. diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 0000000..386a17c --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,7 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp32-camera: + git: https://github.com/cnadler86/esp32-camera.git + espressif/esp_new_jpeg: "^1.0.0" + idf: + version: ">=5.2.0" diff --git a/src/manifest.py b/manifest.py similarity index 80% rename from src/manifest.py rename to manifest.py index ff69f76..1871431 100644 --- a/src/manifest.py +++ b/manifest.py @@ -1,4 +1,4 @@ # Include the board's default manifest. include("$(PORT_DIR)/boards/manifest.py") # Add custom driver -module("acamera.py") \ No newline at end of file +module("src/acamera.py") \ No newline at end of file diff --git a/micropython.cmake b/micropython.cmake new file mode 100644 index 0000000..1eaaba0 --- /dev/null +++ b/micropython.cmake @@ -0,0 +1,96 @@ +include(${MICROPY_DIR}/py/py.cmake) + +set(MICROPY_FROZEN_MANIFEST ${CMAKE_CURRENT_LIST_DIR}/manifest.py) + +add_library(usermod_mp_camera INTERFACE) + +target_sources(usermod_mp_camera INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src/modcamera.c + ${CMAKE_CURRENT_LIST_DIR}/src/modcamera_api.c +) + +# Register dependency on esp32-camera component +# The component is managed by IDF component manager via idf_component.yml +# Add include directories directly from managed_components (they exist after Component Manager ran) +# Allow manual override via ESP32_CAMERA_DIR +if(DEFINED ESP32_CAMERA_DIR AND EXISTS "${ESP32_CAMERA_DIR}") + message(STATUS "Using user-defined ESP32_CAMERA_DIR: ${ESP32_CAMERA_DIR}") + set(ESP32_CAMERA_MANAGED_DIR "${ESP32_CAMERA_DIR}") +else() + set(ESP32_CAMERA_MANAGED_DIR "${MICROPY_PORT_DIR}/managed_components/espressif__esp32-camera") +endif() + +if(EXISTS "${ESP32_CAMERA_MANAGED_DIR}") + # Add standard include directories for esp32-camera + list(APPEND MICROPY_INC_USERMOD + ${ESP32_CAMERA_MANAGED_DIR}/driver/include + ${ESP32_CAMERA_MANAGED_DIR}/driver/private_include + ${ESP32_CAMERA_MANAGED_DIR}/conversions/include + ${ESP32_CAMERA_MANAGED_DIR}/conversions/private_include + ${ESP32_CAMERA_MANAGED_DIR}/sensors/private_include + ) + + message(STATUS "Found esp32-camera at: ${ESP32_CAMERA_MANAGED_DIR}") + + # Link against the component library when target exists (during actual build) + # The target doesn't exist yet during include(), but will exist during build + if(TARGET espressif__esp32-camera) + idf_component_get_property(esp32_camera_lib espressif__esp32-camera COMPONENT_LIB) + target_link_libraries(usermod_mp_camera INTERFACE ${esp32_camera_lib}) + endif() + + # Set MP_CAMERA_DRIVER_VERSION if available + if(EXISTS "${ESP32_CAMERA_MANAGED_DIR}/idf_component.yml") + file(READ "${ESP32_CAMERA_MANAGED_DIR}/idf_component.yml" _camera_component_yml) + string(REGEX MATCH "version: ([0-9]+\\.[0-9]+(\\.[0-9]+)?)" _ ${_camera_component_yml}) + if(CMAKE_MATCH_1) + set(MP_CAMERA_DRIVER_VERSION "${CMAKE_MATCH_1}") + message(STATUS "Found esp32-camera version: ${MP_CAMERA_DRIVER_VERSION}") + endif() + endif() +else() + message(WARNING "esp32-camera component not found - component manager should have downloaded it based on idf_component.yml") +endif() + +# Check if MP_JPEG_DIR is set or if mp_jpeg directory exists two levels up +if(DEFINED MP_JPEG_DIR AND EXISTS "${MP_JPEG_DIR}") + message(STATUS "Using user-defined MP_JPEG_DIR: ${MP_JPEG_DIR}") + set(MP_JPEG_SRC "${MP_JPEG_DIR}/micropython.cmake") +elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../mp_jpeg") + message(STATUS "Found mp_jpeg directory at same level as the camera module") + set(MP_JPEG_SRC "${CMAKE_CURRENT_LIST_DIR}/../mp_jpeg/micropython.cmake") +endif() + +# Include mp_jpeg module if found +if(DEFINED MP_JPEG_SRC AND EXISTS "${MP_JPEG_SRC}") + include(${MP_JPEG_SRC}) + message(STATUS "Included mp_jpeg module from: ${MP_JPEG_SRC}") +else() + message(STATUS "mp_jpeg module not found - camera module will build without JPEG support") +endif() + +# Define MICROPY_CAMERA_MODEL if specified +if (MICROPY_CAMERA_MODEL) + message(STATUS "Using user-defined camera model: ${MICROPY_CAMERA_MODEL}") + target_compile_definitions(usermod_mp_camera INTERFACE + MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1 + ) +endif() + +# Define MP_CAMERA_DRIVER_VERSION if specified +if (MP_CAMERA_DRIVER_VERSION) + target_compile_definitions(usermod_mp_camera INTERFACE + MP_CAMERA_DRIVER_VERSION=\"${MP_CAMERA_DRIVER_VERSION}\" + ) +endif() + +# Camera module strings are not suitable for compression and cause size increase +target_compile_definitions(usermod_mp_camera INTERFACE + MICROPY_ROM_TEXT_COMPRESSION=0 +) + +# Link the camera module with the main usermod target +target_link_libraries(usermod INTERFACE usermod_mp_camera) + +# Gather target properties for MicroPython build system +micropy_gather_target_properties(usermod_mp_camera) diff --git a/src/micropython.cmake b/src/micropython.cmake deleted file mode 100644 index 178d39f..0000000 --- a/src/micropython.cmake +++ /dev/null @@ -1,93 +0,0 @@ -include(${MICROPY_DIR}/py/py.cmake) - -set(MICROPY_FROZEN_MANIFEST ${CMAKE_CURRENT_LIST_DIR}/manifest.py) - -add_library(usermod_mp_camera INTERFACE) - -add_dependencies(usermod_mp_camera esp32_camera) - -target_sources(usermod_mp_camera INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/modcamera.c - ${CMAKE_CURRENT_LIST_DIR}/modcamera_api.c -) - -# Prefer user-defined ESP32_CAMERA_DIR if provided -if(DEFINED ESP32_CAMERA_DIR AND EXISTS "${ESP32_CAMERA_DIR}") - message(STATUS "Using user-defined ESP32 Camera directory: ${ESP32_CAMERA_DIR}") - - target_include_directories(usermod_mp_camera INTERFACE - ${CMAKE_CURRENT_LIST_DIR} - ${ESP32_CAMERA_DIR}/driver/include - ${ESP32_CAMERA_DIR}/conversions/include - ${ESP32_CAMERA_DIR}/driver/private_include - ${ESP32_CAMERA_DIR}/conversions/private_include - ${ESP32_CAMERA_DIR}/sensors/private_include - ) - -# If no manual directory is provided, try to fetch it from ESP-IDF -elseif(EXISTS ${IDF_PATH}/components/esp32-camera) - idf_component_get_property(CAMERA_INCLUDES esp32-camera INCLUDE_DIRS) - idf_component_get_property(CAMERA_PRIV_INCLUDES esp32-camera PRIV_INCLUDE_DIRS) - idf_component_get_property(CAMERA_DIR esp32-camera COMPONENT_DIR) - - if(CAMERA_DIR) - message(STATUS "Using ESP32 Camera component from ESP-IDF: ${CAMERA_DIR}") - - # Add public include directories from ESP-IDF - if(CAMERA_INCLUDES) - list(TRANSFORM CAMERA_INCLUDES PREPEND ${CAMERA_DIR}/) - target_include_directories(usermod_mp_camera INTERFACE ${CAMERA_INCLUDES}) - endif() - - # Add private include directories from ESP-IDF - if(CAMERA_PRIV_INCLUDES) - list(TRANSFORM CAMERA_PRIV_INCLUDES PREPEND ${CAMERA_DIR}/) - target_include_directories(usermod_mp_camera INTERFACE ${CAMERA_PRIV_INCLUDES}) - endif() - else() - message(WARNING "ESP32 Camera component not found in ESP-IDF!") - target_include_directories(usermod_mp_camera PUBLIC ${CMAKE_CURRENT_LIST_DIR}) - endif() -endif() - -# Check if MP_JPEG_DIR is set or if mp_jpeg directory exists two levels up -if(DEFINED MP_JPEG_DIR AND EXISTS "${MP_JPEG_DIR}") - message(STATUS "Using user-defined MP_JPEG_DIR: ${MP_JPEG_DIR}") - set(MP_JPEG_SRC "${MP_JPEG_DIR}/src/micropython.cmake") -elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../mp_jpeg") - message(STATUS "Found mp_jpeg directory two levels up") - set(MP_JPEG_SRC "${CMAKE_CURRENT_LIST_DIR}/../../mp_jpeg/src/micropython.cmake") -endif() - -# Add MP_JPEG_SRC cmake file to target_sources if it is defined -if(DEFINED MP_JPEG_SRC AND EXISTS "${MP_JPEG_SRC}") - include(${MP_JPEG_SRC}) -else() - message(WARNING "MP_JPEG_SRC not found or not defined!") -endif() - -# Define MICROPY_CAMERA_MODEL if specified -if (MICROPY_CAMERA_MODEL) - message(STATUS "Using user-defined camera model: ${MICROPY_CAMERA_MODEL}") - target_compile_definitions(usermod_mp_camera INTERFACE - MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1 - ) -endif() - -# Define MP_CAMERA_DRIVER_VERSION if specified -if (MP_CAMERA_DRIVER_VERSION) - target_compile_definitions(usermod_mp_camera INTERFACE - MP_CAMERA_DRIVER_VERSION=\"${MP_CAMERA_DRIVER_VERSION}\" - ) -endif() - -# Camera module strings are not suitable for compression and cause size increase -target_compile_definitions(usermod_mp_camera INTERFACE - MICROPY_ROM_TEXT_COMPRESSION=0 -) - -# Link the camera module with the main usermod target -target_link_libraries(usermod INTERFACE usermod_mp_camera) - -# Gather target properties for MicroPython build system -micropy_gather_target_properties(usermod_mp_camera) From 448a9f1722c7b9b565fc0dc6672cef6ac7425594 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 22 Nov 2025 05:40:33 +0100 Subject: [PATCH 09/23] update pipeline --- .github/workflows/ESP32.yml | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index ece3284..1b549f5 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -43,6 +43,8 @@ jobs: !~/.espressif/dist/ ~/.cache/pip/ ~/micropython/ + ~/esp32-camera/ + ~/esp_new_jpeg/ key: mpy-${{ env.MPY_RELEASE }} restore-keys: mpy- @@ -64,15 +66,21 @@ jobs: git -C esp-idf submodule update --init --recursive --filter=tree:0 cd esp-idf ./install.sh all - cd components - # latest_cam_driver=$(curl -s https://api.github.com/repos/espressif/esp32-camera/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - # git clone --depth 1 --branch $latest_cam_driver https://github.com/espressif/esp32-camera.git - git clone https://github.com/cnadler86/esp32-camera.git - cd ~/esp-idf/ source ./export.sh + + # Clone external components (if not cached) + - name: Clone external components + if: steps.cache_esp_idf.outputs.cache-hit != 'true' + run: | cd ~ + # Clone camera driver + git clone https://github.com/cnadler86/esp32-camera.git + echo "ESP32_CAMERA_DIR=~/esp32-camera" >> $GITHUB_ENV + # Clone esp_new_jpeg git clone https://github.com/espressif/esp-adf-libs.git - cp -r ~/esp-adf-libs/esp_new_jpeg ~/esp-idf/components/ + mv ~/esp-adf-libs/esp_new_jpeg ~/esp_new_jpeg + rm -rf ~/esp-adf-libs + echo "ESP_JPEG_DIR=~/esp_new_jpeg" >> $GITHUB_ENV # Clone the latest MicroPython release (if not cached) - name: Clone MicroPython latest release @@ -141,6 +149,8 @@ jobs: !~/.espressif/dist/ ~/.cache/pip/ ~/micropython/ + ~/esp32-camera/ + ~/esp_new_jpeg/ key: mpy-${{ env.MPY_RELEASE }} restore-keys: mpy- @@ -158,7 +168,12 @@ jobs: cd ${{ github.workspace }} cd .. git clone https://github.com/cnadler86/mp_jpeg.git - cd ~/esp-idf/components/esp32-camera + # Set environment variables for component directories + echo "ESP32_CAMERA_DIR=~/esp32-camera" >> $GITHUB_ENV + echo "ESP_JPEG_DIR=~/esp_new_jpeg" >> $GITHUB_ENV + export ESP32_CAMERA_DIR=~/esp32-camera + export ESP_JPEG_DIR=~/esp_new_jpeg + cd ~/esp32-camera CAM_DRIVER=$(git describe --tags --always --dirty) cd ~/micropython/ports/esp32 source ~/esp-idf/export.sh From 2ee09db5994254e887a163f2c1a969b33a7e1eb7 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 22 Nov 2025 05:55:04 +0100 Subject: [PATCH 10/23] update pipeline --- .github/workflows/ESP32.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 1b549f5..933b482 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -75,12 +75,10 @@ jobs: cd ~ # Clone camera driver git clone https://github.com/cnadler86/esp32-camera.git - echo "ESP32_CAMERA_DIR=~/esp32-camera" >> $GITHUB_ENV # Clone esp_new_jpeg git clone https://github.com/espressif/esp-adf-libs.git mv ~/esp-adf-libs/esp_new_jpeg ~/esp_new_jpeg rm -rf ~/esp-adf-libs - echo "ESP_JPEG_DIR=~/esp_new_jpeg" >> $GITHUB_ENV # Clone the latest MicroPython release (if not cached) - name: Clone MicroPython latest release @@ -168,11 +166,6 @@ jobs: cd ${{ github.workspace }} cd .. git clone https://github.com/cnadler86/mp_jpeg.git - # Set environment variables for component directories - echo "ESP32_CAMERA_DIR=~/esp32-camera" >> $GITHUB_ENV - echo "ESP_JPEG_DIR=~/esp_new_jpeg" >> $GITHUB_ENV - export ESP32_CAMERA_DIR=~/esp32-camera - export ESP_JPEG_DIR=~/esp_new_jpeg cd ~/esp32-camera CAM_DRIVER=$(git describe --tags --always --dirty) cd ~/micropython/ports/esp32 @@ -183,9 +176,9 @@ jobs: IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" if [ -n "${BOARD_VARIANT}" ]; then - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER -D ESP32_CAMERA_DIR=$HOME/esp32-camera -D ESP_JPEG_DIR=$HOME/esp_new_jpeg" else - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER -D ESP32_CAMERA_DIR=$HOME/esp32-camera -D ESP_JPEG_DIR=$HOME/esp_new_jpeg" fi if [ -n "${CAMERA_MODEL}" ]; then echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV From 0758450ca893e4f6f23128dc886eb8c3c11e01b2 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Mon, 24 Nov 2025 09:55:52 +0100 Subject: [PATCH 11/23] update pipeline --- .github/workflows/ESP32.yml | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 933b482..83c8328 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -43,8 +43,6 @@ jobs: !~/.espressif/dist/ ~/.cache/pip/ ~/micropython/ - ~/esp32-camera/ - ~/esp_new_jpeg/ key: mpy-${{ env.MPY_RELEASE }} restore-keys: mpy- @@ -68,18 +66,6 @@ jobs: ./install.sh all source ./export.sh - # Clone external components (if not cached) - - name: Clone external components - if: steps.cache_esp_idf.outputs.cache-hit != 'true' - run: | - cd ~ - # Clone camera driver - git clone https://github.com/cnadler86/esp32-camera.git - # Clone esp_new_jpeg - git clone https://github.com/espressif/esp-adf-libs.git - mv ~/esp-adf-libs/esp_new_jpeg ~/esp_new_jpeg - rm -rf ~/esp-adf-libs - # Clone the latest MicroPython release (if not cached) - name: Clone MicroPython latest release id: clone-micropython @@ -147,8 +133,6 @@ jobs: !~/.espressif/dist/ ~/.cache/pip/ ~/micropython/ - ~/esp32-camera/ - ~/esp_new_jpeg/ key: mpy-${{ env.MPY_RELEASE }} restore-keys: mpy- @@ -176,9 +160,9 @@ jobs: IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" if [ -n "${BOARD_VARIANT}" ]; then - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER -D ESP32_CAMERA_DIR=$HOME/esp32-camera -D ESP_JPEG_DIR=$HOME/esp_new_jpeg" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D EXTRA_COMPONENT_DIRS=${{ github.workspace }};${{ github.workspace }}/../mp_jpeg" else - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER -D ESP32_CAMERA_DIR=$HOME/esp32-camera -D ESP_JPEG_DIR=$HOME/esp_new_jpeg" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -B build-$BUILD_TARGET -D EXTRA_COMPONENT_DIRS=${{ github.workspace }};${{ github.workspace }}/../mp_jpeg" fi if [ -n "${CAMERA_MODEL}" ]; then echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV From 3892f22d64ebf0e090fb26c0b4eed8d6f73a1117 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Mon, 24 Nov 2025 10:14:59 +0100 Subject: [PATCH 12/23] update pipeline --- .github/workflows/ESP32.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 83c8328..6e5f608 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -150,8 +150,8 @@ jobs: cd ${{ github.workspace }} cd .. git clone https://github.com/cnadler86/mp_jpeg.git - cd ~/esp32-camera - CAM_DRIVER=$(git describe --tags --always --dirty) + # cd ~/esp32-camera + # CAM_DRIVER=$(git describe --tags --always --dirty) cd ~/micropython/ports/esp32 source ~/esp-idf/export.sh From c5f249c01c1072ed809a58d0787b602b6a739030 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sun, 7 Dec 2025 06:19:32 +0100 Subject: [PATCH 13/23] Add build script, i2c objects and try newest icropython with latest idf in pipeline --- .github/workflows/ESP32.yml | 29 ++++---- README.md | 10 +-- build.sh | 132 ++++++++++++++++++++++++++++++++++++ src/modcamera_api.c | 39 ++++++++--- 4 files changed, 178 insertions(+), 32 deletions(-) create mode 100755 build.sh diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 6e5f608..0eb12c0 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -53,19 +53,6 @@ jobs: sudo apt-get update sudo apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 - # Download and set up ESP-IDF (if not cached) - - name: Set up ESP-IDF - id: export-idf - if: steps.cache_esp_idf.outputs.cache-hit != 'true' - run: | - cd ~ - git clone --depth 1 --branch v5.4.2 https://github.com/espressif/esp-idf.git - # git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git - git -C esp-idf submodule update --init --recursive --filter=tree:0 - cd esp-idf - ./install.sh all - source ./export.sh - # Clone the latest MicroPython release (if not cached) - name: Clone MicroPython latest release id: clone-micropython @@ -75,7 +62,8 @@ jobs: cd ~/esp-idf/ source ./export.sh cd ~ - git clone --depth 1 --branch ${{ env.MPY_RELEASE }} https://github.com/micropython/micropython.git + # git clone --depth 1 --branch ${{ env.MPY_RELEASE }} https://github.com/micropython/micropython.git + git clone --depth 1 https://github.com/micropython/micropython.git cd micropython # git submodule update --init --depth 1 cd mpy-cross @@ -84,6 +72,19 @@ jobs: echo "Micropython setup successfully" source ~/micropython/tools/ci.sh && echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV + # Download and set up ESP-IDF (if not cached) + - name: Set up ESP-IDF + id: export-idf + if: steps.cache_esp_idf.outputs.cache-hit != 'true' + run: | + cd ~ + # git clone --depth 1 --branch v5.5.1 https://github.com/espressif/esp-idf.git + git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git + git -C esp-idf submodule update --init --recursive --filter=tree:0 + cd esp-idf + ./install.sh all + source ./export.sh + # Dynamically create jobs for each board build: needs: setup-environment diff --git a/README.md b/README.md index 75fc6c1..aed81aa 100644 --- a/README.md +++ b/README.md @@ -338,17 +338,13 @@ If you also want to include the [mp_jpeg module](https://github.com/cnadler86/mp ### Build the API -To build the project, you could do it the following way: +To build the project, just use the buils script with the path to your micropython folder: ```bash -. /esp-idf/export.sh -cd MyESPCam/micropython/ports/esp32 -make USER_C_MODULES=../../../../micropython-camera-API/micropython.cmake BOARD= clean -make USER_C_MODULES=../../../../micropython-camera-API/micropython.cmake BOARD= submodules -make USER_C_MODULES=../../../../micropython-camera-API/micropython.cmake BOARD= all +./build.sh -m path/to/micropython -b ESP32_GENERIC_S3 ``` -Micropython and camera-api folders are at the same level. Note that you need those extra "/../"s while been inside the esp32 port folder. +Use `./build.sh -h` to see all available options. If you experience problems, visit [MicroPython external C modules](https://docs.micropython.org/en/latest/develop/cmodules.html). ## Notes diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..ed0d503 --- /dev/null +++ b/build.sh @@ -0,0 +1,132 @@ +#!/bin/bash +set -e + +# Default values +IDF_PATH_DEFAULT="$HOME/esp/esp-idf" +MICROPYTHON_PATH="" +IDF_PATH="$IDF_PATH_DEFAULT" +BOARD="" +BOARD_VARIANT="" +BUILD_DIR="build-mp_camera" + +# Parse arguments +usage() { + echo "Usage: $0 -m [-i ] [-b ] [-v ]" + echo "" + echo "Options:" + echo " -m Path to MicroPython directory (required)" + echo " -i Path to ESP-IDF directory (optional, default: $IDF_PATH_DEFAULT)" + echo " -b Board name (optional, e.g. ESP32_GENERIC_S3)" + echo " -v Board variant (optional, e.g. SPIRAM_OCT)" + echo " -h Show this help message" + echo "" + echo "Examples:" + echo " $0 -m ~/privat/micropython" + echo " $0 -m ~/privat/micropython -i ~/esp/esp-idf" + echo " $0 -m ~/privat/micropython -b ESP32_GENERIC_S3" + echo " $0 -m ~/privat/micropython -b ESP32_GENERIC_S3 -v SPIRAM_OCT" + exit 1 +} + +# Parse command line options +while getopts "m:i:b:v:h" opt; do + case $opt in + m) MICROPYTHON_PATH="$OPTARG" ;; + i) IDF_PATH="$OPTARG" ;; + b) BOARD="$OPTARG" ;; + v) BOARD_VARIANT="$OPTARG" ;; + h) usage ;; + *) usage ;; + esac +done + +# Check required arguments +if [ -z "$MICROPYTHON_PATH" ]; then + echo "Error: MicroPython path is required (-m option)" + echo "" + usage +fi + +# Validate paths +if [ ! -d "$MICROPYTHON_PATH" ]; then + echo "Error: MicroPython directory not found: $MICROPYTHON_PATH" + exit 1 +fi + +if [ ! -d "$IDF_PATH" ]; then + echo "Error: ESP-IDF directory not found: $IDF_PATH" + exit 1 +fi + +if [ ! -f "$IDF_PATH/export.sh" ]; then + echo "Error: ESP-IDF export.sh not found in: $IDF_PATH" + exit 1 +fi + +# Get absolute paths +MICROPYTHON_PATH=$(realpath "$MICROPYTHON_PATH") +IDF_PATH=$(realpath "$IDF_PATH") +MODULE_PATH=$(dirname "$(realpath "$0")") + +echo "==========================================" +echo "Building MicroPython with IR Learn Module" +echo "==========================================" +echo "MicroPython path: $MICROPYTHON_PATH" +echo "ESP-IDF path: $IDF_PATH" +echo "Module path: $MODULE_PATH" +if [ -n "$BOARD" ]; then + echo "Board: $BOARD" + if [ -n "$BOARD_VARIANT" ]; then + echo "Board variant: $BOARD_VARIANT" + BUILD_DIR="build-${BOARD_VARIANT}" + fi +fi +echo "Build directory: $BUILD_DIR" +echo "==========================================" +echo "" + +# Source ESP-IDF environment +echo "Setting up ESP-IDF environment..." +source "$IDF_PATH/export.sh" + +# Change to MicroPython ESP32 port directory +cd "$MICROPYTHON_PATH/ports/esp32" + +# Build idf.py command with optional parameters +IDF_CMD="idf.py -B $BUILD_DIR" + +if [ -n "$BOARD" ]; then + IDF_CMD="$IDF_CMD -D MICROPY_BOARD=$BOARD" +fi + +if [ -n "$BOARD_VARIANT" ]; then + IDF_CMD="$IDF_CMD -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT" +fi + +IDF_CMD="$IDF_CMD -D USER_C_MODULES=$MODULE_PATH/micropython.cmake" +IDF_CMD="$IDF_CMD -D EXTRA_COMPONENT_DIRS=$MODULE_PATH" +IDF_CMD="$IDF_CMD build" + +# Build MicroPython with IR Learn module +echo "" +echo "Building MicroPython..." +echo "Command: $IDF_CMD" +eval $IDF_CMD + +# Create firmware images +echo "" +echo "Creating firmware images..." +cd "$BUILD_DIR" + +python "$MICROPYTHON_PATH/ports/esp32/makeimg.py" \ + sdkconfig \ + bootloader/bootloader.bin \ + partition_table/partition-table.bin \ + micropython.bin \ + firmware.bin \ + micropython.uf2 + +echo "" +echo "Build completed successfully!" +echo "Firmware files in: $MICROPYTHON_PATH/ports/esp32/$BUILD_DIR" +echo "==========================================" \ No newline at end of file diff --git a/src/modcamera_api.c b/src/modcamera_api.c index b882eaa..64756de 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -42,6 +42,34 @@ #include "hal/i2c_ll.h" #endif +// Get the I2C port from the I2C object. This will be deleted in the future +// Structure matches machine_hw_i2c_obj_t from machine_i2c.c +#if MICROPY_HW_ESP_NEW_I2C_DRIVER && CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW + typedef struct _machine_hw_i2c_obj_t { + mp_obj_base_t base; + i2c_master_bus_handle_t bus_handle; + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + i2c_master_dev_handle_t dev_handle; + #endif + uint8_t port : 8; + gpio_num_t scl : 8; + gpio_num_t sda : 8; + uint32_t freq; + uint32_t timeout_us; + } machine_hw_i2c_obj_t; +#elif CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY + typedef struct _machine_hw_i2c_obj_t { + mp_obj_base_t base; + i2c_port_t port : 8; + gpio_num_t scl : 8; + gpio_num_t sda : 8; + uint32_t freq; + uint32_t timeout_us; + } machine_hw_i2c_obj_t; +#else + #error "Unsupported I2C driver configuration for casmera module" +#endif + typedef struct mp_camera_obj_t mp_camera_obj; const mp_obj_type_t camera_type; @@ -133,17 +161,6 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_raise_TypeError(MP_ERROR_TEXT("i2c must be a machine.I2C object")); } - // Get the I2C port from the I2C object - // Structure matches machine_hw_i2c_obj_t from machine_i2c.c - typedef struct _machine_hw_i2c_obj_t { - mp_obj_base_t base; - i2c_port_t port : 8; - gpio_num_t scl : 8; - gpio_num_t sda : 8; - uint32_t freq; - uint32_t timeout_us; - } machine_hw_i2c_obj_t; - machine_hw_i2c_obj_t *i2c = (machine_hw_i2c_obj_t *)MP_OBJ_TO_PTR(i2c_obj); i2c_port = (int8_t)i2c->port; sda_pin = i2c->sda; From a26b1dc64d9ae2e12b09beb35de8f637926d6a1d Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sun, 7 Dec 2025 16:51:28 +0100 Subject: [PATCH 14/23] ESP32.yml aktualisieren --- .github/workflows/ESP32.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 0eb12c0..bf59311 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -161,9 +161,9 @@ jobs: IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" if [ -n "${BOARD_VARIANT}" ]; then - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D EXTRA_COMPONENT_DIRS=${{ github.workspace }};${{ github.workspace }}/../mp_jpeg" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D EXTRA_COMPONENT_DIRS=${{ github.workspace }}" else - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -B build-$BUILD_TARGET -D EXTRA_COMPONENT_DIRS=${{ github.workspace }};${{ github.workspace }}/../mp_jpeg" + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -B build-$BUILD_TARGET -D EXTRA_COMPONENT_DIRS=${{ github.workspace }}" fi if [ -n "${CAMERA_MODEL}" ]; then echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV From 1f3bb4c6256fd55f442b18c722b7b729a141fd2e Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Tue, 9 Dec 2025 09:24:50 +0100 Subject: [PATCH 15/23] Integrates esp32-camera as submodule and build option Adds esp32-camera as a git submodule and build dependency, streamlines include path usage, and introduces camera model as a configurable build argument. Simplifies CMake logic for esp32-camera detection and improves build.sh usability. --- .gitignore | 3 +++ .gitmodules | 3 +++ CMakeLists.txt | 4 +++- build.sh | 30 ++++++++++++++++++------ esp32-camera | 1 + idf_component.yml | 2 +- micropython.cmake | 56 ++++++++++++--------------------------------- src/modcamera_api.c | 6 ++--- 8 files changed, 51 insertions(+), 54 deletions(-) create mode 100644 .gitmodules create mode 160000 esp32-camera diff --git a/.gitignore b/.gitignore index feee077..ee1de00 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ #.vscode folder .vscode/ +# Build +/build/ + # User-specific files *.rsuser *.suo diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1a2f52d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "esp32-camera"] + path = esp32-camera + url = https://github.com/cnadler86/esp32-camera.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d37979..77e03c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,4 +2,6 @@ # This allows the IDF component manager to process idf_component.yml # The actual MicroPython module is built via micropython.cmake -idf_component_register() +idf_component_register( + REQUIRES esp32-camera +) diff --git a/build.sh b/build.sh index ed0d503..3ac2377 100755 --- a/build.sh +++ b/build.sh @@ -5,19 +5,21 @@ set -e IDF_PATH_DEFAULT="$HOME/esp/esp-idf" MICROPYTHON_PATH="" IDF_PATH="$IDF_PATH_DEFAULT" -BOARD="" +BOARD="ESP32_GENERIC_S3" BOARD_VARIANT="" +CAMERA_MODEL="" BUILD_DIR="build-mp_camera" # Parse arguments usage() { - echo "Usage: $0 -m [-i ] [-b ] [-v ]" + echo "Usage: $0 -m [-i ] [-b ] [-v ] [-c ]" echo "" echo "Options:" echo " -m Path to MicroPython directory (required)" echo " -i Path to ESP-IDF directory (optional, default: $IDF_PATH_DEFAULT)" - echo " -b Board name (optional, e.g. ESP32_GENERIC_S3)" + echo " -b Board name (optional, e.g. ESP32_GENERIC_S3, default: $BOARD)" echo " -v Board variant (optional, e.g. SPIRAM_OCT)" + echo " -c Camera model (optional, e.g. FREENOVE_ESP32S3_CAM)" echo " -h Show this help message" echo "" echo "Examples:" @@ -25,16 +27,18 @@ usage() { echo " $0 -m ~/privat/micropython -i ~/esp/esp-idf" echo " $0 -m ~/privat/micropython -b ESP32_GENERIC_S3" echo " $0 -m ~/privat/micropython -b ESP32_GENERIC_S3 -v SPIRAM_OCT" + echo " $0 -m ~/privat/micropython -b ESP32_GENERIC_S3 -c FREENOVE_ESP32S3_CAM" exit 1 } # Parse command line options -while getopts "m:i:b:v:h" opt; do +while getopts "m:i:b:v:c:h" opt; do case $opt in m) MICROPYTHON_PATH="$OPTARG" ;; i) IDF_PATH="$OPTARG" ;; b) BOARD="$OPTARG" ;; v) BOARD_VARIANT="$OPTARG" ;; + c) CAMERA_MODEL="$OPTARG" ;; h) usage ;; *) usage ;; esac @@ -69,18 +73,22 @@ IDF_PATH=$(realpath "$IDF_PATH") MODULE_PATH=$(dirname "$(realpath "$0")") echo "==========================================" -echo "Building MicroPython with IR Learn Module" +echo "Building MicroPython with Camera Module" echo "==========================================" echo "MicroPython path: $MICROPYTHON_PATH" echo "ESP-IDF path: $IDF_PATH" echo "Module path: $MODULE_PATH" if [ -n "$BOARD" ]; then echo "Board: $BOARD" + BUILD_DIR="${BUILD_DIR}-${BOARD}" if [ -n "$BOARD_VARIANT" ]; then echo "Board variant: $BOARD_VARIANT" - BUILD_DIR="build-${BOARD_VARIANT}" + BUILD_DIR="${BUILD_DIR}_${BOARD_VARIANT}" fi fi +if [ -n "$CAMERA_MODEL" ]; then + echo "Camera model: $CAMERA_MODEL" +fi echo "Build directory: $BUILD_DIR" echo "==========================================" echo "" @@ -103,6 +111,10 @@ if [ -n "$BOARD_VARIANT" ]; then IDF_CMD="$IDF_CMD -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT" fi +if [ -n "$CAMERA_MODEL" ]; then + IDF_CMD="$IDF_CMD -D MICROPY_CAMERA_MODEL=$CAMERA_MODEL" +fi + IDF_CMD="$IDF_CMD -D USER_C_MODULES=$MODULE_PATH/micropython.cmake" IDF_CMD="$IDF_CMD -D EXTRA_COMPONENT_DIRS=$MODULE_PATH" IDF_CMD="$IDF_CMD build" @@ -129,4 +141,8 @@ python "$MICROPYTHON_PATH/ports/esp32/makeimg.py" \ echo "" echo "Build completed successfully!" echo "Firmware files in: $MICROPYTHON_PATH/ports/esp32/$BUILD_DIR" -echo "==========================================" \ No newline at end of file +echo "==========================================" + +# Clean up build directory +cd "$MODULE_PATH" +rm -rf build diff --git a/esp32-camera b/esp32-camera new file mode 160000 index 0000000..b9c5c51 --- /dev/null +++ b/esp32-camera @@ -0,0 +1 @@ +Subproject commit b9c5c51c48e1f67be3a85ee1dcf7c274abb67e0b diff --git a/idf_component.yml b/idf_component.yml index 386a17c..1c7473f 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,7 +1,7 @@ ## IDF Component Manager Manifest File dependencies: espressif/esp32-camera: - git: https://github.com/cnadler86/esp32-camera.git + override_path: esp32-camera espressif/esp_new_jpeg: "^1.0.0" idf: version: ">=5.2.0" diff --git a/micropython.cmake b/micropython.cmake index 1eaaba0..7a6e494 100644 --- a/micropython.cmake +++ b/micropython.cmake @@ -9,48 +9,22 @@ target_sources(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/modcamera_api.c ) -# Register dependency on esp32-camera component -# The component is managed by IDF component manager via idf_component.yml -# Add include directories directly from managed_components (they exist after Component Manager ran) -# Allow manual override via ESP32_CAMERA_DIR -if(DEFINED ESP32_CAMERA_DIR AND EXISTS "${ESP32_CAMERA_DIR}") - message(STATUS "Using user-defined ESP32_CAMERA_DIR: ${ESP32_CAMERA_DIR}") - set(ESP32_CAMERA_MANAGED_DIR "${ESP32_CAMERA_DIR}") -else() - set(ESP32_CAMERA_MANAGED_DIR "${MICROPY_PORT_DIR}/managed_components/espressif__esp32-camera") -endif() +idf_component_get_property(camera_dir esp32-camera COMPONENT_DIR) +target_include_directories(usermod_mp_camera INTERFACE + ${camera_dir}/driver/include + ${camera_dir}/driver/private_include + ${camera_dir}/sensors/private_include +) -if(EXISTS "${ESP32_CAMERA_MANAGED_DIR}") - # Add standard include directories for esp32-camera - list(APPEND MICROPY_INC_USERMOD - ${ESP32_CAMERA_MANAGED_DIR}/driver/include - ${ESP32_CAMERA_MANAGED_DIR}/driver/private_include - ${ESP32_CAMERA_MANAGED_DIR}/conversions/include - ${ESP32_CAMERA_MANAGED_DIR}/conversions/private_include - ${ESP32_CAMERA_MANAGED_DIR}/sensors/private_include - ) - - message(STATUS "Found esp32-camera at: ${ESP32_CAMERA_MANAGED_DIR}") - - # Link against the component library when target exists (during actual build) - # The target doesn't exist yet during include(), but will exist during build - if(TARGET espressif__esp32-camera) - idf_component_get_property(esp32_camera_lib espressif__esp32-camera COMPONENT_LIB) - target_link_libraries(usermod_mp_camera INTERFACE ${esp32_camera_lib}) - endif() - - # Set MP_CAMERA_DRIVER_VERSION if available - if(EXISTS "${ESP32_CAMERA_MANAGED_DIR}/idf_component.yml") - file(READ "${ESP32_CAMERA_MANAGED_DIR}/idf_component.yml" _camera_component_yml) - string(REGEX MATCH "version: ([0-9]+\\.[0-9]+(\\.[0-9]+)?)" _ ${_camera_component_yml}) - if(CMAKE_MATCH_1) - set(MP_CAMERA_DRIVER_VERSION "${CMAKE_MATCH_1}") - message(STATUS "Found esp32-camera version: ${MP_CAMERA_DRIVER_VERSION}") - endif() - endif() -else() - message(WARNING "esp32-camera component not found - component manager should have downloaded it based on idf_component.yml") -endif() +# # Set MP_CAMERA_DRIVER_VERSION if available +# if(EXISTS "${ESP32_CAMERA_MANAGED_DIR}/idf_component.yml") +# file(READ "${ESP32_CAMERA_MANAGED_DIR}/idf_component.yml" _camera_component_yml) +# string(REGEX MATCH "version: ([0-9]+\\.[0-9]+(\\.[0-9]+)?)" _ ${_camera_component_yml}) +# if(CMAKE_MATCH_1) +# set(MP_CAMERA_DRIVER_VERSION "${CMAKE_MATCH_1}") +# message(STATUS "Found esp32-camera version: ${MP_CAMERA_DRIVER_VERSION}") +# endif() +# endif() # Check if MP_JPEG_DIR is set or if mp_jpeg directory exists two levels up if(DEFINED MP_JPEG_DIR AND EXISTS "${MP_JPEG_DIR}") diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 64756de..eb07d38 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -44,7 +44,7 @@ // Get the I2C port from the I2C object. This will be deleted in the future // Structure matches machine_hw_i2c_obj_t from machine_i2c.c -#if MICROPY_HW_ESP_NEW_I2C_DRIVER && CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW +#if MICROPY_HW_ESP_NEW_I2C_DRIVER typedef struct _machine_hw_i2c_obj_t { mp_obj_base_t base; i2c_master_bus_handle_t bus_handle; @@ -57,7 +57,7 @@ uint32_t freq; uint32_t timeout_us; } machine_hw_i2c_obj_t; -#elif CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY +#else typedef struct _machine_hw_i2c_obj_t { mp_obj_base_t base; i2c_port_t port : 8; @@ -66,8 +66,6 @@ uint32_t freq; uint32_t timeout_us; } machine_hw_i2c_obj_t; -#else - #error "Unsupported I2C driver configuration for casmera module" #endif typedef struct mp_camera_obj_t mp_camera_obj; From 4f06c7ef11b83933eabcc083fd1027e505244f27 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Tue, 9 Dec 2025 14:30:35 +0100 Subject: [PATCH 16/23] ESP32.yml aktualisieren --- .github/workflows/ESP32.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index bf59311..db1c05f 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -139,6 +139,8 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + with: + submodules: recursive - name: Install ESP-IDF dependencies run: | From 099f76d6164e3c2d1db6ead2310d9bb034f86e74 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Tue, 9 Dec 2025 14:59:45 +0100 Subject: [PATCH 17/23] ESP32.yml aktualisieren --- .github/workflows/ESP32.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index db1c05f..fa4ba7a 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -62,8 +62,8 @@ jobs: cd ~/esp-idf/ source ./export.sh cd ~ - # git clone --depth 1 --branch ${{ env.MPY_RELEASE }} https://github.com/micropython/micropython.git - git clone --depth 1 https://github.com/micropython/micropython.git + git clone --depth 1 --branch ${{ env.MPY_RELEASE }} https://github.com/micropython/micropython.git + # git clone --depth 1 https://github.com/micropython/micropython.git cd micropython # git submodule update --init --depth 1 cd mpy-cross From 757bf7ce8d169b5abdcca27efd290eacfc9005e4 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 10 Dec 2025 05:25:51 +0100 Subject: [PATCH 18/23] release candidate --- .github/workflows/ESP32.yml | 13 +- build.sh | 44 ++-- typings/camera.pyi | 446 +++++++++++++++++++++++++++++------- 3 files changed, 396 insertions(+), 107 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index fa4ba7a..ed69532 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -162,19 +162,22 @@ jobs: IFS='@' read -r BUILD_TARGET CAMERA_MODEL <<< "${{ matrix.board }}" IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" + # Build command step by step + IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -B build-$BUILD_TARGET -D EXTRA_COMPONENT_DIRS=${{ github.workspace }}" + if [ -n "${BOARD_VARIANT}" ]; then - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D EXTRA_COMPONENT_DIRS=${{ github.workspace }}" - else - IDF_CMD="idf.py -D MICROPY_BOARD=$BOARD_NAME -D USER_C_MODULES=${{ github.workspace }}/micropython.cmake -B build-$BUILD_TARGET -D EXTRA_COMPONENT_DIRS=${{ github.workspace }}" + IDF_CMD="${IDF_CMD} -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT" fi + if [ -n "${CAMERA_MODEL}" ]; then echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV - FINAL_CMD="${IDF_CMD} -D MICROPY_CAMERA_MODEL=${CAMERA_MODEL} build" + IDF_CMD="${IDF_CMD} -D MICROPY_CAMERA_MODEL=${CAMERA_MODEL}" else echo "FW_NAME=${BUILD_TARGET}" >> $GITHUB_ENV - FINAL_CMD="${IDF_CMD} build" fi + FINAL_CMD="${IDF_CMD} build" + make BOARD=$BOARD_NAME submodules echo "Running command: $FINAL_CMD" eval $FINAL_CMD diff --git a/build.sh b/build.sh index 3ac2377..6f7caed 100755 --- a/build.sh +++ b/build.sh @@ -12,10 +12,12 @@ BUILD_DIR="build-mp_camera" # Parse arguments usage() { - echo "Usage: $0 -m [-i ] [-b ] [-v ] [-c ]" + echo "Usage: $0 [-i ] [-b ] [-v ] [-c ]" + echo "" + echo "Arguments:" + echo " Path to MicroPython directory (required)" echo "" echo "Options:" - echo " -m Path to MicroPython directory (required)" echo " -i Path to ESP-IDF directory (optional, default: $IDF_PATH_DEFAULT)" echo " -b Board name (optional, e.g. ESP32_GENERIC_S3, default: $BOARD)" echo " -v Board variant (optional, e.g. SPIRAM_OCT)" @@ -23,18 +25,27 @@ usage() { echo " -h Show this help message" echo "" echo "Examples:" - echo " $0 -m ~/privat/micropython" - echo " $0 -m ~/privat/micropython -i ~/esp/esp-idf" - echo " $0 -m ~/privat/micropython -b ESP32_GENERIC_S3" - echo " $0 -m ~/privat/micropython -b ESP32_GENERIC_S3 -v SPIRAM_OCT" - echo " $0 -m ~/privat/micropython -b ESP32_GENERIC_S3 -c FREENOVE_ESP32S3_CAM" + echo " $0 ~/privat/micropython" + echo " $0 ~/privat/micropython -i ~/esp/esp-idf" + echo " $0 ~/privat/micropython -b ESP32_GENERIC_S3" + echo " $0 ~/privat/micropython -b ESP32_GENERIC_S3 -v SPIRAM_OCT" + echo " $0 ~/privat/micropython -b ESP32_GENERIC_S3 -c FREENOVE_ESP32S3_CAM" exit 1 } +# Check required arguments first +if [ -z "$1" ]; then + echo "Error: MicroPython path is required as first argument" + echo "" + usage +fi + +MICROPYTHON_PATH="$1" +shift + # Parse command line options -while getopts "m:i:b:v:c:h" opt; do +while getopts "i:b:v:c:h" opt; do case $opt in - m) MICROPYTHON_PATH="$OPTARG" ;; i) IDF_PATH="$OPTARG" ;; b) BOARD="$OPTARG" ;; v) BOARD_VARIANT="$OPTARG" ;; @@ -44,13 +55,6 @@ while getopts "m:i:b:v:c:h" opt; do esac done -# Check required arguments -if [ -z "$MICROPYTHON_PATH" ]; then - echo "Error: MicroPython path is required (-m option)" - echo "" - usage -fi - # Validate paths if [ ! -d "$MICROPYTHON_PATH" ]; then echo "Error: MicroPython directory not found: $MICROPYTHON_PATH" @@ -97,8 +101,16 @@ echo "" echo "Setting up ESP-IDF environment..." source "$IDF_PATH/export.sh" +# Check and initialize camera API submodules if needed +cd "$MODULE_PATH" +if git submodule status | grep -q '^-'; then + echo "Initializing camera API submodules ..." + git submodule update --init --depth 1 +fi + # Change to MicroPython ESP32 port directory cd "$MICROPYTHON_PATH/ports/esp32" +make BOARD=$BOARD submodules # Build idf.py command with optional parameters IDF_CMD="idf.py -B $BUILD_DIR" diff --git a/typings/camera.pyi b/typings/camera.pyi index 679d9be..da2a19d 100644 --- a/typings/camera.pyi +++ b/typings/camera.pyi @@ -75,6 +75,7 @@ class Camera(): xclk_freq: int = 20000000, powerdown_pin: int = -1, reset_pin: int = -1, + i2c: object | None = None, pixel_format: int = PixelFormat.RGB565, frame_size: int = FrameSize.QVGA, jpeg_quality: int = 85, @@ -83,266 +84,539 @@ class Camera(): init: bool = True) -> None: ... + # Properties (read-only) + @property + def pixel_format(self) -> int: + """Get current pixel format (read-only).""" + ... + + @property + def grab_mode(self) -> int: + """Get current grab mode (read-only).""" + ... + + @property + def fb_count(self) -> int: + """Get frame buffer count (read-only).""" + ... + + @property + def pixel_width(self) -> int: + """Get frame width in pixels (read-only).""" + ... + + @property + def pixel_height(self) -> int: + """Get frame height in pixels (read-only).""" + ... + + @property + def max_frame_size(self) -> int: + """Get maximum supported frame size (read-only).""" + ... + + @property + def sensor_name(self) -> str: + """Get camera sensor name (read-only).""" + ... + + # Properties (read-write) + @property + def frame_size(self) -> int: + """Get/set frame size.""" + ... + + @frame_size.setter + def frame_size(self, value: int) -> None: + ... + + @property + def contrast(self) -> int: + """Get/set contrast level (-2 to 2).""" + ... + + @contrast.setter + def contrast(self, value: int) -> None: + ... + + @property + def brightness(self) -> int: + """Get/set brightness level (-2 to 2).""" + ... + + @brightness.setter + def brightness(self, value: int) -> None: + ... + + @property + def saturation(self) -> int: + """Get/set saturation level (-2 to 2).""" + ... + + @saturation.setter + def saturation(self, value: int) -> None: + ... + + @property + def sharpness(self) -> int: + """Get/set sharpness level (-2 to 2).""" + ... + + @sharpness.setter + def sharpness(self, value: int) -> None: + ... + + @property + def denoise(self) -> int: + """Get/set denoise level.""" + ... + + @denoise.setter + def denoise(self, value: int) -> None: + ... + + @property + def gainceiling(self) -> int: + """Get/set gain ceiling.""" + ... + + @gainceiling.setter + def gainceiling(self, value: int) -> None: + ... + + @property + def quality(self) -> int: + """Get/set JPEG quality (0-63).""" + ... + + @quality.setter + def quality(self, value: int) -> None: + ... + + @property + def colorbar(self) -> bool: + """Get/set color bar test pattern.""" + ... + + @colorbar.setter + def colorbar(self, value: bool) -> None: + ... + + @property + def whitebal(self) -> bool: + """Get/set white balance.""" + ... + + @whitebal.setter + def whitebal(self, value: bool) -> None: + ... + + @property + def gain_ctrl(self) -> bool: + """Get/set Auto Gain Control.""" + ... + + @gain_ctrl.setter + def gain_ctrl(self, value: bool) -> None: + ... + + @property + def exposure_ctrl(self) -> bool: + """Get/set Auto Exposure Control.""" + ... + + @exposure_ctrl.setter + def exposure_ctrl(self, value: bool) -> None: + ... + + @property + def hmirror(self) -> bool: + """Get/set horizontal mirror.""" + ... + + @hmirror.setter + def hmirror(self, value: bool) -> None: + ... + + @property + def vflip(self) -> bool: + """Get/set vertical flip.""" + ... + + @vflip.setter + def vflip(self, value: bool) -> None: + ... + + @property + def aec2(self) -> bool: + """Get/set AEC DSP.""" + ... + + @aec2.setter + def aec2(self, value: bool) -> None: + ... + + @property + def awb_gain(self) -> bool: + """Get/set Auto White Balance Gain.""" + ... + + @awb_gain.setter + def awb_gain(self, value: bool) -> None: + ... + + @property + def agc_gain(self) -> int: + """Get/set Auto Gain Control gain value.""" + ... + + @agc_gain.setter + def agc_gain(self, value: int) -> None: + ... + + @property + def aec_value(self) -> int: + """Get/set Auto Exposure Control value.""" + ... + + @aec_value.setter + def aec_value(self, value: int) -> None: + ... + + @property + def special_effect(self) -> int: + """Get/set special effect.""" + ... + + @special_effect.setter + def special_effect(self, value: int) -> None: + ... + + @property + def wb_mode(self) -> int: + """Get/set white balance mode.""" + ... + + @wb_mode.setter + def wb_mode(self, value: int) -> None: + ... + + @property + def ae_level(self) -> int: + """Get/set Auto Exposure level (-2 to 2).""" + ... + + @ae_level.setter + def ae_level(self, value: int) -> None: + ... + + @property + def dcw(self) -> bool: + """Get/set DCW (Downsize EN).""" + ... + + @dcw.setter + def dcw(self, value: bool) -> None: + ... + + @property + def bpc(self) -> bool: + """Get/set Bad Pixel Correction.""" + ... + + @bpc.setter + def bpc(self, value: bool) -> None: + ... + + @property + def wpc(self) -> bool: + """Get/set White Pixel Correction.""" + ... + + @wpc.setter + def wpc(self, value: bool) -> None: + ... + + @property + def raw_gma(self) -> bool: + """Get/set GMA (Gamma) Correction.""" + ... + + @raw_gma.setter + def raw_gma(self, value: bool) -> None: + ... + + @property + def lenc(self) -> bool: + """Get/set Lens Correction.""" + ... + + @lenc.setter + def lenc(self, value: bool) -> None: + ... + + # Core methods + def reconfigure(self, *, frame_size: int | None = None, + pixel_format: int | None = None, + grab_mode: int | None = None, + fb_count: int | None = None) -> None: + """Reconfigure camera with new settings.""" + ... + + def init(self) -> None: + """Initialize the camera.""" + ... + + def deinit(self) -> None: + """Deinitialize the camera.""" + ... + + def frame_available(self) -> bool: + """Check if a frame is available.""" + ... + + def capture(self) -> memoryview: + """Capture a frame and return it as a memoryview.""" + ... + + def free_buffer(self) -> None: + """Free the frame buffer.""" + ... + + # Deprecated methods (use properties instead) def get_special_effect(self) -> int: - """Get the current special effect setting.""" + """Deprecated: Use the special_effect property instead.""" ... def set_special_effect(self, value: int) -> None: - """Set the special effect.""" + """Deprecated: Use the special_effect property instead.""" ... def set_awb_gain(self, value: bool) -> None: - """Enable/disable Auto White Balance Gain.""" + """Deprecated: Use the awb_gain property instead.""" ... def get_awb_gain(self) -> bool: - """Get Auto White Balance Gain state.""" + """Deprecated: Use the awb_gain property instead.""" ... def set_agc_gain(self, value: int) -> None: - """Set the Auto Gain Control gain value.""" + """Deprecated: Use the agc_gain property instead.""" ... def get_agc_gain(self) -> int: - """Get the Auto Gain Control gain value.""" + """Deprecated: Use the agc_gain property instead.""" ... def set_aec_value(self, value: int) -> None: - """Set the Auto Exposure Control value.""" + """Deprecated: Use the aec_value property instead.""" ... def get_aec_value(self) -> int: - """Get the Auto Exposure Control value.""" + """Deprecated: Use the aec_value property instead.""" ... def set_bpc(self, value: bool) -> None: - """Enable/disable Bad Pixel Correction.""" + """Deprecated: Use the bpc property instead.""" ... def get_bpc(self) -> bool: - """Get Bad Pixel Correction state.""" + """Deprecated: Use the bpc property instead.""" ... def set_contrast(self, value: int) -> None: - """Set the contrast level (-2 to 2).""" + """Deprecated: Use the contrast property instead.""" ... def get_contrast(self) -> int: - """Get the contrast level.""" + """Deprecated: Use the contrast property instead.""" ... def set_colorbar(self, value: bool) -> None: - """Enable/disable color bar test pattern.""" + """Deprecated: Use the colorbar property instead.""" ... def get_colorbar(self) -> bool: - """Get color bar test pattern state.""" + """Deprecated: Use the colorbar property instead.""" ... def set_brightness(self, value: int) -> None: - """Set the brightness level (-2 to 2).""" + """Deprecated: Use the brightness property instead.""" ... def get_brightness(self) -> int: - """Get the brightness level.""" + """Deprecated: Use the brightness property instead.""" ... def set_aec2(self, value: bool) -> None: - """Enable/disable AEC DSP.""" + """Deprecated: Use the aec2 property instead.""" ... def get_aec2(self) -> bool: - """Get AEC DSP state.""" + """Deprecated: Use the aec2 property instead.""" ... def set_whitebal(self, value: bool) -> None: - """Enable/disable white balance.""" + """Deprecated: Use the whitebal property instead.""" ... def get_whitebal(self) -> bool: - """Get white balance state.""" + """Deprecated: Use the whitebal property instead.""" ... def set_wb_mode(self, value: int) -> None: - """Set white balance mode.""" + """Deprecated: Use the wb_mode property instead.""" ... def get_wb_mode(self) -> int: - """Get white balance mode.""" + """Deprecated: Use the wb_mode property instead.""" ... def set_vflip(self, value: bool) -> None: - """Enable/disable vertical flip.""" + """Deprecated: Use the vflip property instead.""" ... def get_vflip(self) -> bool: - """Get vertical flip state.""" + """Deprecated: Use the vflip property instead.""" ... def set_wpc(self, value: bool) -> None: - """Enable/disable White Pixel Correction.""" + """Deprecated: Use the wpc property instead.""" ... def get_wpc(self) -> bool: - """Get White Pixel Correction state.""" + """Deprecated: Use the wpc property instead.""" ... def set_ae_level(self, value: int) -> None: - """Set Auto Exposure level (-2 to 2).""" + """Deprecated: Use the ae_level property instead.""" ... def get_ae_level(self) -> int: - """Get Auto Exposure level.""" - ... - - def reconfigure(self, *, frame_size: int | None = None, - pixel_format: int | None = None, - grab_mode: int | None = None, - fb_count: int | None = None) -> None: - """Reconfigure camera with new settings.""" - ... - - def init(self) -> None: - """Initialize the camera.""" - ... - - def deinit(self) -> None: - """Deinitialize the camera.""" + """Deprecated: Use the ae_level property instead.""" ... def set_dcw(self, value: bool) -> None: - """Enable/disable DCW (Downsize EN).""" + """Deprecated: Use the dcw property instead.""" ... def get_dcw(self) -> bool: - """Get DCW (Downsize EN) state.""" + """Deprecated: Use the dcw property instead.""" ... def set_sharpness(self, value: int) -> None: - """Set the sharpness level (-2 to 2).""" + """Deprecated: Use the sharpness property instead.""" ... def get_sharpness(self) -> int: - """Get the sharpness level.""" + """Deprecated: Use the sharpness property instead.""" ... def set_saturation(self, value: int) -> None: - """Set the saturation level (-2 to 2).""" + """Deprecated: Use the saturation property instead.""" ... def get_saturation(self) -> int: - """Get the saturation level.""" + """Deprecated: Use the saturation property instead.""" ... def set_raw_gma(self, value: bool) -> None: - """Enable/disable GMA (Gamma) Correction.""" + """Deprecated: Use the raw_gma property instead.""" ... def get_raw_gma(self) -> bool: - """Get GMA (Gamma) Correction state.""" + """Deprecated: Use the raw_gma property instead.""" ... def set_quality(self, value: int) -> None: - """Set JPEG quality (0-63).""" + """Deprecated: Use the quality property instead.""" ... def get_quality(self) -> int: - """Get JPEG quality.""" + """Deprecated: Use the quality property instead.""" ... def set_frame_size(self, value: int) -> None: - """Set frame size.""" + """Deprecated: Use the frame_size property instead.""" ... def get_frame_size(self) -> int: - """Get frame size.""" + """Deprecated: Use the frame_size property instead.""" ... def set_exposure_ctrl(self, value: bool) -> None: - """Enable/disable Auto Exposure Control.""" + """Deprecated: Use the exposure_ctrl property instead.""" ... def get_exposure_ctrl(self) -> bool: - """Get Auto Exposure Control state.""" + """Deprecated: Use the exposure_ctrl property instead.""" ... def set_denoise(self, value: int) -> None: - """Set denoise level.""" + """Deprecated: Use the denoise property instead.""" ... def get_denoise(self) -> int: - """Get denoise level.""" + """Deprecated: Use the denoise property instead.""" ... def set_gain_ctrl(self, value: bool) -> None: - """Enable/disable Auto Gain Control.""" + """Deprecated: Use the gain_ctrl property instead.""" ... def get_gain_ctrl(self) -> bool: - """Get Auto Gain Control state.""" + """Deprecated: Use the gain_ctrl property instead.""" ... def set_lenc(self, value: bool) -> None: - """Enable/disable Lens Correction.""" + """Deprecated: Use the lenc property instead.""" ... def get_lenc(self) -> bool: - """Get Lens Correction state.""" + """Deprecated: Use the lenc property instead.""" ... def set_hmirror(self, value: bool) -> None: - """Enable/disable horizontal mirror.""" + """Deprecated: Use the hmirror property instead.""" ... def get_hmirror(self) -> bool: - """Get horizontal mirror state.""" + """Deprecated: Use the hmirror property instead.""" ... def set_gainceiling(self, value: int) -> None: - """Set gain ceiling.""" + """Deprecated: Use the gainceiling property instead.""" ... def get_gainceiling(self) -> int: - """Get gain ceiling.""" - ... - - def frame_available(self) -> bool: - """Check if a frame is available.""" - ... - - def capture(self) -> memoryview: - """Capture a frame and return it as a memoryview.""" - ... - - def free_buffer(self) -> None: - """Free the frame buffer.""" + """Deprecated: Use the gainceiling property instead.""" ... def get_pixel_width(self) -> int: - """Get frame width in pixels.""" + """Deprecated: Use the pixel_width property instead.""" ... def get_pixel_height(self) -> int: - """Get frame height in pixels.""" + """Deprecated: Use the pixel_height property instead.""" ... def get_pixel_format(self) -> int: - """Get current pixel format.""" + """Deprecated: Use the pixel_format property instead.""" ... def get_sensor_name(self) -> str: - """Get camera sensor name.""" + """Deprecated: Use the sensor_name property instead.""" ... def get_max_frame_size(self) -> int: - """Get maximum supported frame size.""" + """Deprecated: Use the max_frame_size property instead.""" ... def get_fb_count(self) -> int: - """Get frame buffer count.""" + """Deprecated: Use the fb_count property instead.""" ... def get_grab_mode(self) -> int: - """Get current grab mode.""" + """Deprecated: Use the grab_mode property instead.""" ... From 68ba1c514de8ffaff6822e1e557db72935cc3623 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 10 Dec 2025 05:28:07 +0100 Subject: [PATCH 19/23] update pipeline --- .github/workflows/ESP32.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index ed69532..14eabef 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -53,6 +53,19 @@ jobs: sudo apt-get update sudo apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 + # Download and set up ESP-IDF (if not cached) + - name: Set up ESP-IDF + id: export-idf + if: steps.cache_esp_idf.outputs.cache-hit != 'true' + run: | + cd ~ + # git clone --depth 1 --branch v5.5.1 https://github.com/espressif/esp-idf.git + git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git + git -C esp-idf submodule update --init --recursive --filter=tree:0 + cd esp-idf + ./install.sh all + source ./export.sh + # Clone the latest MicroPython release (if not cached) - name: Clone MicroPython latest release id: clone-micropython @@ -72,19 +85,6 @@ jobs: echo "Micropython setup successfully" source ~/micropython/tools/ci.sh && echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV - # Download and set up ESP-IDF (if not cached) - - name: Set up ESP-IDF - id: export-idf - if: steps.cache_esp_idf.outputs.cache-hit != 'true' - run: | - cd ~ - # git clone --depth 1 --branch v5.5.1 https://github.com/espressif/esp-idf.git - git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git - git -C esp-idf submodule update --init --recursive --filter=tree:0 - cd esp-idf - ./install.sh all - source ./export.sh - # Dynamically create jobs for each board build: needs: setup-environment From 56496bb7f88cb487399b65417f2b1cc71e0ad282 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 10 Dec 2025 05:32:30 +0100 Subject: [PATCH 20/23] update pipeline --- .github/workflows/ESP32.yml | 41 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 14eabef..9f0387e 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -52,7 +52,27 @@ jobs: run: | sudo apt-get update sudo apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 - + + # Clone MicroPython (if not cached) + - name: Clone MicroPython + id: clone-micropython + if: steps.cache_esp_idf.outputs.cache-hit != 'true' + run: | + cd ~ + if [ "${{ github.ref }}" == "refs/heads/master" ]; then + echo "Master branch detected - cloning MicroPython release: $MPY_RELEASE" + git clone --depth 1 --branch ${{ env.MPY_RELEASE }} https://github.com/micropython/micropython.git + else + echo "Development branch detected - cloning latest MicroPython master" + git clone --depth 1 https://github.com/micropython/micropython.git + fi + cd micropython + cd mpy-cross + make + + echo "Micropython setup successfully" + source ~/micropython/tools/ci.sh && echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV + # Download and set up ESP-IDF (if not cached) - name: Set up ESP-IDF id: export-idf @@ -66,25 +86,6 @@ jobs: ./install.sh all source ./export.sh - # Clone the latest MicroPython release (if not cached) - - name: Clone MicroPython latest release - id: clone-micropython - if: steps.cache_esp_idf.outputs.cache-hit != 'true' - run: | - echo "Cloning MicroPython release: $MPY_RELEASE" - cd ~/esp-idf/ - source ./export.sh - cd ~ - git clone --depth 1 --branch ${{ env.MPY_RELEASE }} https://github.com/micropython/micropython.git - # git clone --depth 1 https://github.com/micropython/micropython.git - cd micropython - # git submodule update --init --depth 1 - cd mpy-cross - make - - echo "Micropython setup successfully" - source ~/micropython/tools/ci.sh && echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV - # Dynamically create jobs for each board build: needs: setup-environment From c90e06235d2d28e71ba2ac4237b9f1a9b96af1ed Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 10 Dec 2025 05:37:19 +0100 Subject: [PATCH 21/23] update pipeline --- .github/workflows/ESP32.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 9f0387e..884a670 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -24,6 +24,8 @@ concurrency: jobs: setup-environment: runs-on: ubuntu-24.04 + outputs: + idf_ver: ${{ steps.get_idf_ver.outputs.idf_ver }} steps: # Get the latest MicroPython release - name: Get MicroPython latest release @@ -71,7 +73,14 @@ jobs: make echo "Micropython setup successfully" - source ~/micropython/tools/ci.sh && echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV + + # Get IDF version from MicroPython ci.sh + - name: Get IDF version + id: get_idf_ver + run: | + source ~/micropython/tools/ci.sh + echo "idf_ver=$IDF_VER" >> $GITHUB_OUTPUT + echo "IDF_VER=$IDF_VER" # Download and set up ESP-IDF (if not cached) - name: Set up ESP-IDF @@ -79,8 +88,7 @@ jobs: if: steps.cache_esp_idf.outputs.cache-hit != 'true' run: | cd ~ - # git clone --depth 1 --branch v5.5.1 https://github.com/espressif/esp-idf.git - git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git + git clone --depth 1 --branch ${{ steps.get_idf_ver.outputs.idf_ver }} https://github.com/espressif/esp-idf.git git -C esp-idf submodule update --init --recursive --filter=tree:0 cd esp-idf ./install.sh all @@ -124,6 +132,7 @@ jobs: run: | MPY_RELEASE=$(curl --silent "https://api.github.com/repos/micropython/micropython/releases/latest" | jq -r .tag_name) echo "MPY_RELEASE=${MPY_RELEASE}" >> $GITHUB_ENV + echo "IDF_VER=${{ needs.setup-environment.outputs.idf_ver }}" >> $GITHUB_ENV # Cache ESP-IDF dependencies and MicroPython - name: Cache ESP-IDF and MicroPython From 6a291ea105c143aea14fc2f0e62406e47d1909fc Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 10 Dec 2025 05:44:17 +0100 Subject: [PATCH 22/23] update pipeline --- .github/workflows/ESP32.yml | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 884a670..19fa0c5 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -24,8 +24,6 @@ concurrency: jobs: setup-environment: runs-on: ubuntu-24.04 - outputs: - idf_ver: ${{ steps.get_idf_ver.outputs.idf_ver }} steps: # Get the latest MicroPython release - name: Get MicroPython latest release @@ -73,14 +71,10 @@ jobs: make echo "Micropython setup successfully" - - # Get IDF version from MicroPython ci.sh - - name: Get IDF version - id: get_idf_ver - run: | - source ~/micropython/tools/ci.sh - echo "idf_ver=$IDF_VER" >> $GITHUB_OUTPUT - echo "IDF_VER=$IDF_VER" + # Extract IDF_VER directly from ci.sh + IDF_VER=$(grep "^IDF_VER=" ~/micropython/tools/ci.sh | cut -d'=' -f2) + echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV + echo "Using ESP-IDF version: $IDF_VER" # Download and set up ESP-IDF (if not cached) - name: Set up ESP-IDF @@ -88,7 +82,8 @@ jobs: if: steps.cache_esp_idf.outputs.cache-hit != 'true' run: | cd ~ - git clone --depth 1 --branch ${{ steps.get_idf_ver.outputs.idf_ver }} https://github.com/espressif/esp-idf.git + # git clone --depth 1 --branch v5.5.1 https://github.com/espressif/esp-idf.git + git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git git -C esp-idf submodule update --init --recursive --filter=tree:0 cd esp-idf ./install.sh all @@ -132,7 +127,6 @@ jobs: run: | MPY_RELEASE=$(curl --silent "https://api.github.com/repos/micropython/micropython/releases/latest" | jq -r .tag_name) echo "MPY_RELEASE=${MPY_RELEASE}" >> $GITHUB_ENV - echo "IDF_VER=${{ needs.setup-environment.outputs.idf_ver }}" >> $GITHUB_ENV # Cache ESP-IDF dependencies and MicroPython - name: Cache ESP-IDF and MicroPython From 6c770c46114b12a31dbb7aa12a6e1c4ecfcfe92c Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 10 Dec 2025 05:47:31 +0100 Subject: [PATCH 23/23] update pipeline --- .github/workflows/ESP32.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 19fa0c5..00237b1 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -71,10 +71,6 @@ jobs: make echo "Micropython setup successfully" - # Extract IDF_VER directly from ci.sh - IDF_VER=$(grep "^IDF_VER=" ~/micropython/tools/ci.sh | cut -d'=' -f2) - echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV - echo "Using ESP-IDF version: $IDF_VER" # Download and set up ESP-IDF (if not cached) - name: Set up ESP-IDF @@ -82,8 +78,7 @@ jobs: if: steps.cache_esp_idf.outputs.cache-hit != 'true' run: | cd ~ - # git clone --depth 1 --branch v5.5.1 https://github.com/espressif/esp-idf.git - git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git + git clone --depth 1 --branch v5.5.1 https://github.com/espressif/esp-idf.git git -C esp-idf submodule update --init --recursive --filter=tree:0 cd esp-idf ./install.sh all