From 1f030d953bb870fb91026b2a2d13510ef1bb0e7c Mon Sep 17 00:00:00 2001 From: John Gentilin Date: Tue, 21 Jan 2025 21:19:41 -0800 Subject: [PATCH 01/16] Add esp32 camera dir (#23) minor change that allows to override the directories where the includes exist for the esp32-camera component. It should function the same as before, if you do not provide an override. --------- Signed-off-by: John Gentilin --- src/micropython.cmake | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/micropython.cmake b/src/micropython.cmake index 66fd308..369f867 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -8,14 +8,20 @@ target_sources(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR}/modcamera_api.c ) -if(EXISTS "${IDF_PATH}/components/esp32-camera") +if(NOT ESP32_CAMERA_DIR) + set(ESP32_CAMERA_DIR "${IDF_PATH}/components/esp32-camera") +endif() + +message("Camera Dir: ${ESP32_CAMERA_DIR}") + +if(EXISTS "${ESP32_CAMERA_DIR}") target_include_directories(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR} - ${IDF_PATH}/components/esp32-camera/driver/include - ${IDF_PATH}/components/esp32-camera/driver/private_include - ${IDF_PATH}/components/esp32-camera/conversions/include - ${IDF_PATH}/components/esp32-camera/conversions/private_include - ${IDF_PATH}/components/esp32-camera/sensors/private_include + ${ESP32_CAMERA_DIR}/driver/include + ${ESP32_CAMERA_DIR}/driver/private_include + ${ESP32_CAMERA_DIR}/conversions/include + ${ESP32_CAMERA_DIR}/conversions/private_include + ${ESP32_CAMERA_DIR}/sensors/private_include ) else() target_include_directories(usermod_mp_camera INTERFACE @@ -32,4 +38,4 @@ endif() target_link_libraries(usermod INTERFACE usermod_mp_camera) -micropy_gather_target_properties(usermod_mp_camera) \ No newline at end of file +micropy_gather_target_properties(usermod_mp_camera) From eaca8eeb722c98dbd23c30045e0fd6f70d5ef84f Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Mon, 24 Feb 2025 07:09:53 +0100 Subject: [PATCH 02/16] README.md aktualisieren --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c733b47..854965e 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ If you want to customize additional camera setting or reduce the FW size by remo ### Build the API -To build the project, you could do it the following way: +To build the project, you could do it the following way (Micropython and camera-api folders are at the same level): ```bash . /esp-idf/export.sh From d42addac428f0c8fef17e932ef72bdb9ed14f2b3 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:02:03 +0100 Subject: [PATCH 03/16] README.md aktualisieren --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 854965e..940c64c 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ If you want to customize additional camera setting or reduce the FW size by remo ### Build the API -To build the project, you could do it the following way (Micropython and camera-api folders are at the same level): +To build the project, you could do it the following way: ```bash . /esp-idf/export.sh @@ -279,6 +279,7 @@ make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOA make USER_C_MODULES=../../../../micropython-camera-API/src/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. If you experience problems, visit [MicroPython external C modules](https://docs.micropython.org/en/latest/develop/cmodules.html). ## Notes From 7fbb4143abaf4988bd603992a8ecb94077b9ad2e Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Fri, 21 Mar 2025 20:59:59 +0100 Subject: [PATCH 04/16] asyncio support for capture method and get rid of convert methods (#28) Adds `acamera `instance supporting async capture method Use [mp_jpeg](https://github.com/cnadler86/mp_jpeg) instead of convert methods. Fix #25 Fix #26 --- .github/workflows/Create-Release.yml | 37 ------ .github/workflows/ESP32.yml | 19 ++- README.md | 140 +++++++++++-------- examples/CameraSettings.py | 4 +- src/acamera.py | 12 ++ src/manifest.py | 4 + src/micropython.cmake | 68 ++++++++-- src/modcamera.c | 192 ++++++++------------------- src/modcamera.h | 19 +-- src/modcamera_api.c | 48 +++---- 10 files changed, 256 insertions(+), 287 deletions(-) delete mode 100644 .github/workflows/Create-Release.yml create mode 100644 src/acamera.py create mode 100644 src/manifest.py diff --git a/.github/workflows/Create-Release.yml b/.github/workflows/Create-Release.yml deleted file mode 100644 index aec7e05..0000000 --- a/.github/workflows/Create-Release.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Add-Artifacts-to-Draft-Release - -on: - release: - types: - - created - -jobs: - add_artifacts: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up GitHub CLI - uses: actions/setup-gh-cli@v2 - - - name: Authenticate GitHub CLI - run: echo "${{ secrets.GITHUB_TOKEN }}" | gh auth login --with-token - - - name: Check if release is a draft - id: check_draft - run: echo "is_draft=${{ github.event.release.draft }}" >> $GITHUB_ENV - - - name: Download artifacts - if: ${{ env.is_draft == 'true' }} - uses: actions/download-artifact@v4 - with: - name: firmware-* - path: ./artifacts - - - name: Upload Release Assets - if: ${{ env.is_draft == 'true' }} - run: | - for file in ./artifacts/*; do - gh release upload "${{ github.event.release.tag_name }}" "$file" --clobber - done \ No newline at end of file diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index 01e422d..ea142af 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -76,17 +76,20 @@ jobs: if: steps.cache_esp_idf.outputs.cache-hit != 'true' run: | cd ~ - # git clone --depth 1 --branch release/v5.2 https://github.com/espressif/esp-idf.git + # git clone --depth 1 --branch release/v5.3 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 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 + # 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 + cd ~ + git clone https://github.com/espressif/esp-adf-libs.git + cp -r ~/esp-adf-libs/esp_new_jpeg ~/esp-idf/components/ # Dynamically create jobs for each board build: @@ -151,6 +154,8 @@ jobs: # Build MicroPython for each board - name: Build MicroPython run: | + cd ${{ github.workspace }} + git clone https://github.com/cnadler86/mp_jpeg.git cd ~/esp-idf/components/esp32-camera CAM_DRIVER=$(git describe --tags --always --dirty) cd ~/micropython/ports/esp32 @@ -161,9 +166,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 }}/src/micropython.cmake -D MICROPY_BOARD_VARIANT=$BOARD_VARIANT -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER -D MP_JPEG_DIR=${{ github.workspace }}/mp_jpeg" 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 }}/src/micropython.cmake -B build-$BUILD_TARGET -D MP_CAMERA_DRIVER_VERSION=$CAM_DRIVER -D MP_JPEG_DIR=${{ github.workspace }}/mp_jpeg" fi if [ -n "${CAMERA_MODEL}" ]; then echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV @@ -183,6 +188,6 @@ jobs: - name: Upload firmware artifact uses: actions/upload-artifact@v4 with: - name: firmware-${{ env.FW_NAME }} + name: mpy_cam-${{ env.MPY_RELEASE }}-${{ env.FW_NAME }} path: ~/artifacts/** retention-days: 5 \ No newline at end of file diff --git a/README.md b/README.md index 940c64c..da880cd 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,24 @@ [![ESP32 Port](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml/badge.svg)](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml) -This project aims to support various cameras (e.g. OV2640, OV5640) on different MicroPython ports, starting with the ESP32 port. The project implements a general API, has precompiled FW images and supports a lot of cameras out of the box. Defaults are set to work with the OV2640. +This project aims to support various cameras (e.g. OV2640, OV5640) on different MicroPython ports, starting with the ESP32 port. The project implements a general API, has precompiled firmware images and supports a lot of cameras out of the box. Defaults are set to work with the OV2640. At the moment, this is a micropython user module, but it might get in the micropython repo in the future. The API is stable, but it might change without previous announce. ## Content -- [Precomiled FW (the easy way)](#precomiled-fw-the-easy-way) +- [Precompiled firmware (the easy way)](#Precompiled-firmware-the-easy-way) - [Using the API](#using-the-api) - [Importing the camera module](#importing-the-camera-module) - [Creating a camera object](#creating-a-camera-object) - [Initializing the camera](#initializing-the-camera) - [Capture image](#capture-image) - - [Convert image to another format](#convert-image-to-another-format) - [Camera reconfiguration](#camera-reconfiguration) + - [Freeing the buffer](#freeing-the-buffer) + - [Is a frame available](#is-frame-available) - [Additional methods](#additional-methods) - [Additional information](#additional-information) -- [Build your custom FW](#build-your-custom-fw) +- [Build your custom firmware](#build-your-custom-firmware) - [Setting up the build environment (DIY method)](#setting-up-the-build-environment-diy-method) - [Add camera configurations to your board (optional, but recommended)](#add-camera-configurations-to-your-board-optional-but-recommended) - [Build the API](#build-the-api) @@ -27,18 +28,28 @@ The API is stable, but it might change without previous announce. - [Troubleshooting](#troubleshooting) - [Donate](#donate) -## Precomiled FW (the easy way) +## Precompiled firmware (the easy way) If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** +This firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpe) to encode/decode JPEGs. + ## Using the API ### Importing the camera module +There general way of using the api is as follow: + ```python from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling ``` +There is also a camera class with asyncio support! To use it, you need to import from the acamera package: + +```python +from acamera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling +``` + ### Creating a camera object Camera construction using defaults. This is the case if you are using a **non-generic** precompiled firmware or if you specified the camera model or pins in mpconfigboard.h during your build. Then you can just call the construction without any keyword arguments. @@ -83,23 +94,22 @@ cam = Camera( - href_pin: HREF pin - sda_pin: SDA pin - scl_pin: SCL pin -- xclk_pin: XCLK pin -- xclk_freq: XCLK frequency in Hz -- powerdown_pin: Powerdown pin -- reset_pin: Reset pin +- xclk_pin: XCLK pin ( set to -1, if you have an external clock source) +- xclk_freq: XCLK frequency in Hz (consult the camera sensor specification) +- powerdown_pin: Powerdown pin (set to -1 if not used) +- reset_pin: Reset pin (set to -1 if not used) - pixel_format: Pixel format as PixelFormat - frame_size: Frame size as FrameSize - jpeg_quality: JPEG quality - fb_count: Frame buffer count - grab_mode: Grab mode as GrabMode - init: Initialize camera at construction time (default: True) -- bmp_out: Image captured output converted to bitmap (default: False) **Default values:** The following keyword arguments have default values: -- xclk_freq: 20MHz // Frequencies are normally either 10 MHz or 20 MHz +- xclk_freq: 20MHz // Default for OV2640 (normally either 10 MHz or 20 MHz). Plase adapt it to your camera sensor. - frame_size: QQVGA - pixel_format: RGB565 - jpeg_quality: 85 // Quality of JPEG output in percent. Higher means higher quality. @@ -119,31 +129,22 @@ cam.init() ### Capture image +The general way of capturing an image is calling the `capture` method: + ```python img = cam.capture() ``` -Arguments for capture - -- out_format: Output format as PixelFormat (optional) +Each time you call the method, you will receive a new frame as memoryview. +You can convert it to bytes and free the memoryview buffer, so a new frame can be pushed to it. This will reduce the image latency but need more RAM. (see [freeing the buffer](#freeing-the-buffer)) -### Convert image to another format +The probably better way of capturing an image would be in an asyncio-loop: -You can either convert the image with the `capture` method directly passing the desired output format: -```python -img_rgb888 = cam.capture(PixelFormat.RGB888) #capture image as configured (e.g. JPEG), convert it to RGB888 and return the converted image -``` -Or you can first capture the image and then convert it to the desired PixelFormat with the `convert` method. -Doing so you can have both, the captured and the converted image. Note that more memory will be used. ```python -img = cam.capture() -img_rgb888 = cam.convert(PixelFormat.RGB888) #converts the last captured image to RGB888 and returns the converted image +img = await cam.acapture() #To access this method, you need to import from acamera ``` -Convertion supported -- from JPEG to RGB565 -- to RGB888 in general -- to JPEG in gerenal (use the `set_quality` method to set the desired JPEG quality) +Please consult the [asyncio documentation](https://docs.micropython.org/en/latest/library/asyncio.html), if you have questions on this. ### Camera reconfiguration @@ -158,21 +159,43 @@ Keyword arguments for reconfigure - grab_mode: Grab mode as GrabMode (optional) - fb_count: Frame buffer count (optional) -### Additional methods +### Freeing the buffer + +This is optional, but can reduce the latency of capturing an image in some cases (especially with fb_count = 1).. + +```python +Img = bytes(cam.capture()) #Create a new bytes object from the memoryview (because we want to free it afterwards) +cam.free_buffer() # This will free the captured image or in other words "deleting"" the memoryview +``` + +### Is frame available + +```python +Img = bytes(cam.capture()) +cam.free_buffer() +while not cam.frame_available(): + +print('The frame is available now. You can grab the image by the capture method =)') +``` + +This gives you the possibility of creating an asynchronous application without using asyncio. + +### Additional methods and examples Here are just a few examples: ```python cam.set_quality(90) # The quality goes from 0% to 100%, meaning 100% is the highest but has probably no compression -cam.set_bmp_out(True) # Enables convertion to bmp when capturing image camera.get_brightness() camera.set_vflip(True) #Enable vertical flip ``` See autocompletions in Thonny in order to see the list of methods. -If you want more insides in the methods and what they actually do, you can find a very good documentation [here](https://docs.circuitpython.org/en/latest/shared-bindings/espcamera/index.html). +If you want more insights in the methods and what they actually do, you can find a very good documentation [here](https://docs.circuitpython.org/en/latest/shared-bindings/espcamera/index.html). Note that each method requires a "get_" or "set_" prefix, depending on the desired action. +Take also a look in the examples folder. + To get the version of the camera driver used: ```python @@ -182,9 +205,9 @@ vers = camera.Version() ### Additional information -The FW 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 +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 -## Build your custom FW +## Build your custom firmware ### Setting up the build environment (DIY method) @@ -192,14 +215,14 @@ 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 (I used v2.0.15). 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 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): ```yml espressif/esp32-camera: - git: https://github.com/espressif/esp32-camera.git + 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. +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) @@ -265,7 +288,11 @@ Example for Xiao sense: ``` #### Customize additional camera settings -If you want to customize additional camera setting or reduce the FW size by removing support for unused camera sensors, then take a look at the kconfig file of the esp32-camera driver and specify these on the sdkconfig file of your board. +If you want to customize additional camera setting or reduce the firmware size by removing support for unused camera sensors, then take a look at the kconfig file of the esp32-camera driver and specify these on the sdkconfig file of your board. + +#### (Optional) Add the mp_jpeg module + +If you also want to include the [mp_jpeg module](https://github.com/cnadler86/mp_jpeg) in your build, clone the mp_jpeg repo at the same level and folder as the mp_camera_api repo and meet the requirements from the mp_jpeg repo. ### Build the API @@ -293,28 +320,25 @@ If you experience problems, visit [MicroPython external C modules](https://docs. ## Benchmark -I didn't use a calibrated osziloscope, but here is a FPS benchmark with my ESP32S3 (xclck_freq = 20MHz, GrabMode=LATEST, fb_count = 1, jpeg_quality=85%) and OV2640. -Using fb_count=2 theoretically can double the FPS (see JPEG with fb_count=2). This might also aplly for other PixelFormats. - -| Frame Size | GRAYSCALE | RGB565 | YUV422 | JPEG | JPEG -> RGB565 | JPEG -> RGB888 | JPEG (fb=2) | -|------------|-----------|--------|--------|--------|----------------|----------------|-------------| -| R96X96 | 12.5 | 12.5 | 12.5 | No img | No img | No img | No img | -| QQVGA | 12.5 | 12.5 | 12.5 | 25 | 25 | 25 | 50 | -| QCIF | 11 | 11 | 11.5 | 25 | 25 | 25 | 50 | -| HQVGA | 12.5 | 12.5 | 12.5 | 25 | 16.7 | 16.7 | 50 | -| R240X240 | 12.5 | 12.5 | 11.5 | 25 | 16.7 | 12.5 | 50 | -| QVGA | 12 | 11 | 12 | 25 | 25 | 25 | 50 | -| CIF | 12.5 | No img | No img | 6.3 | 8.3 | 8.3 | 12.5 | -| HVGA | 3 | 3 | 2.5 | 12.5 | 6.3 | 6.3 | 25 | -| VGA | 3 | 3 | 3 | 12.5 | 3.6 | 3.6 | 25 | -| SVGA | 3 | 3 | 3 | 12.5 | 2.8 | 2.5 | 25 | -| XGA | No img | No img | No img | 6.3 | 1.6 | 1.6 | 12.5 | -| HD | No img | No img | No img | 6.3 | 1.4 | 1.3 | 12.5 | -| SXGA | 2 | 2 | 2 | 6.3 | 1 | 1 | 12.5 | -| UXGA | No img | No img | No img | 6.3 | 0.7 | 0.7 | 12.5 | - - -Looking at the results: image conversion make only sense for frame sized below QVGA or if capturing the image in the intended pixelformat and frame size combination fails. +I didn't use a calibrated oscilloscope, but here is a FPS benchmark with my ESP32S3 (xclk_freq = 20MHz, GrabMode=LATEST, fb_count = 1, jpeg_quality=85%) and OV2640. +Using fb_count=2 theoretically can double the FPS (see JPEG with fb_count=2). This might also apply for other PixelFormats. + +| Frame Size | GRAYSCALE | RGB565 | YUV422 | JPEG | JPEG (fb=2) | +|------------|-----------|--------|--------|--------|-------------| +| R96X96 | 12.5 | 12.5 | 12.5 | No img | No img | +| QQVGA | 12.5 | 12.5 | 12.5 | 25 | 50 | +| QCIF | 11 | 11 | 11.5 | 25 | 50 | +| HQVGA | 12.5 | 12.5 | 12.5 | 25 | 50 | +| R240X240 | 12.5 | 12.5 | 11.5 | 25 | 50 | +| QVGA | 12 | 11 | 12 | 25 | 50 | +| CIF | 12.5 | No img | No img | 6.3 | 12.5 | +| HVGA | 3 | 3 | 2.5 | 12.5 | 25 | +| VGA | 3 | 3 | 3 | 12.5 | 25 | +| SVGA | 3 | 3 | 3 | 12.5 | 25 | +| XGA | No img | No img | No img | 6.3 | 12.5 | +| HD | No img | No img | No img | 6.3 | 12.5 | +| SXGA | 2 | 2 | 2 | 6.3 | 12.5 | +| UXGA | No img | No img | No img | 6.3 | 12.5 | ## Troubleshooting diff --git a/examples/CameraSettings.py b/examples/CameraSettings.py index 21f50c4..3a45772 100644 --- a/examples/CameraSettings.py +++ b/examples/CameraSettings.py @@ -1,7 +1,7 @@ import network import asyncio import time -from camera import Camera, FrameSize, PixelFormat +from acamera import Camera, FrameSize, PixelFormat # Import the async version of the Camera class, you can also use the sync version (camera.Camera) cam = Camera(frame_size=FrameSize.VGA, pixel_format=PixelFormat.JPEG, jpeg_quality=85, init=False) # WLAN config @@ -34,7 +34,7 @@ async def stream_camera(writer): await writer.drain() while True: - frame = cam.capture() + frame = await cam.acapture() # This is the async version of capture, you can also use frame = cam.capture() instead if frame: if cam.get_pixel_format() == PixelFormat.JPEG: writer.write(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n') diff --git a/src/acamera.py b/src/acamera.py new file mode 100644 index 0000000..46c2a6f --- /dev/null +++ b/src/acamera.py @@ -0,0 +1,12 @@ +import asyncio +from camera import Camera as _Camera +from camera import FrameSize, PixelFormat, GainCeiling, GrabMode + +class Camera(_Camera): + async def acapture(self): + self.free_buffer() # Free the buffer so the camera task grabs a new frame + while not self.frame_available(): + await asyncio.sleep(0) # Yield control to the event loop + return self.capture() + +__all__ = ['Camera', 'FrameSize', 'PixelFormat', 'GainCeiling', 'GrabMode'] \ No newline at end of file diff --git a/src/manifest.py b/src/manifest.py new file mode 100644 index 0000000..ff69f76 --- /dev/null +++ b/src/manifest.py @@ -0,0 +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 diff --git a/src/micropython.cmake b/src/micropython.cmake index 369f867..276c25f 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -1,6 +1,7 @@ -# `py.cmake` for `micropy_gather_target_properties` macro usage 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 @@ -8,34 +9,77 @@ target_sources(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR}/modcamera_api.c ) -if(NOT ESP32_CAMERA_DIR) - set(ESP32_CAMERA_DIR "${IDF_PATH}/components/esp32-camera") -endif() - -message("Camera Dir: ${ESP32_CAMERA_DIR}") +# 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}") -if(EXISTS "${ESP32_CAMERA_DIR}") target_include_directories(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR} ${ESP32_CAMERA_DIR}/driver/include - ${ESP32_CAMERA_DIR}/driver/private_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 else() - target_include_directories(usermod_mp_camera INTERFACE - ${CMAKE_CURRENT_LIST_DIR}) + 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) - target_compile_definitions(usermod_mp_camera INTERFACE MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1) + 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}\") + target_compile_definitions(usermod_mp_camera INTERFACE + MP_CAMERA_DRIVER_VERSION=\"${MP_CAMERA_DRIVER_VERSION}\" + ) endif() +# 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/modcamera.c b/src/modcamera.c index 6c1cc9a..5867b16 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -28,7 +28,6 @@ #include "modcamera.h" #include "esp_err.h" #include "esp_log.h" -#include "img_converters.h" #include "mphalport.h" #define TAG "MPY_CAMERA" @@ -153,10 +152,6 @@ void mp_camera_hal_construct( self->initialized = false; self->captured_buffer = NULL; - self->converted_buffer.len = 0; - self->converted_buffer.buf = NULL; - self->converted_buffer.typecode = 'B'; - self->bmp_out = false; } void mp_camera_hal_init(mp_camera_obj_t *self) { @@ -175,11 +170,6 @@ void mp_camera_hal_init(mp_camera_obj_t *self) { void mp_camera_hal_deinit(mp_camera_obj_t *self) { if (self->initialized) { - if (self->converted_buffer.buf) { - free(self->converted_buffer.buf); - self->converted_buffer.buf = NULL; - self->converted_buffer.len = 0; - } if (self->captured_buffer) { esp_camera_return_all(); self->captured_buffer = NULL; @@ -214,62 +204,7 @@ void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t fram ESP_LOGI(TAG, "Camera reconfigured successfully"); } -static bool ensure_buffer(mp_camera_obj_t *self, size_t req_len) { - if (self->converted_buffer.len == req_len) { - return true; - } - if (self->converted_buffer.len > 0 || self->converted_buffer.buf) { - free(self->converted_buffer.buf); - } - self->converted_buffer.buf = (uint8_t *)malloc(req_len); - if (!self->converted_buffer.buf) { - self->converted_buffer.len = 0; - ESP_LOGE(TAG, "converted_buffer malloc failed"); - return false; - } - self->converted_buffer.len = req_len; - return true; -} - -static bool mp_camera_convert(mp_camera_obj_t *self, mp_camera_pixformat_t out_format) { - ESP_LOGI(TAG, "Converting image to pixel format: %d", out_format); - - switch (out_format) { - case PIXFORMAT_JPEG: - return frame2jpg(self->captured_buffer, self->camera_config.jpeg_quality, self->converted_buffer.buf, &self->converted_buffer.len); - - case PIXFORMAT_RGB888: - if (ensure_buffer(self, self->captured_buffer->width * self->captured_buffer->height * 3)) { - return fmt2rgb888(self->captured_buffer->buf, self->captured_buffer->len, self->captured_buffer->format, self->converted_buffer.buf); - } else { - return false; - } - - case PIXFORMAT_RGB565: - if (self->camera_config.pixel_format == PIXFORMAT_JPEG) { - if (ensure_buffer(self, self->captured_buffer->width * self->captured_buffer->height * 2)) { - return jpg2rgb565(self->captured_buffer->buf, self->captured_buffer->len, self->converted_buffer.buf, JPG_SCALE_NONE); - } else { - return false; - } - } else { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Can only convert JPEG to RGB565")); - } - - default: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported pixel format for conversion")); - } -} -mp_obj_t mp_camera_hal_convert(mp_camera_obj_t *self, int8_t out_format) { - check_init(self); - if (mp_camera_convert(self, (mp_camera_pixformat_t)out_format)) { - return mp_obj_new_memoryview('b', self->converted_buffer.len, self->converted_buffer.buf); - } else { - return mp_const_none; - } -} - -mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) { +mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self) { check_init(self); if (self->captured_buffer) { esp_camera_fb_return(self->captured_buffer); @@ -282,91 +217,78 @@ mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) { ESP_LOGE(TAG, "Failed to capture image"); return mp_const_none; } - - if (out_format >= 0 && (mp_camera_pixformat_t)out_format != self->camera_config.pixel_format) { - if (mp_camera_convert(self, (mp_camera_pixformat_t)out_format)) { - esp_camera_fb_return(self->captured_buffer); - mp_obj_t result = mp_obj_new_memoryview('b', self->converted_buffer.len, self->converted_buffer.buf); - return result; - } else { - return mp_const_none; - } - } + return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf); - if (self->bmp_out == false) { - ESP_LOGI(TAG, "Returning image without conversion"); - return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf); - } else { - ESP_LOGI(TAG, "Returning image as bitmap"); - if (frame2bmp(self->captured_buffer, self->converted_buffer.buf, &self->converted_buffer.len)) { - esp_camera_fb_return(self->captured_buffer); - mp_obj_t result = mp_obj_new_memoryview('b', self->converted_buffer.len, self->converted_buffer.buf); - return result; - } else { - free(self->converted_buffer.buf); - self->converted_buffer.buf = NULL; - self->converted_buffer.len = 0; - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to convert image to BMP")); - } - } -} // mp_camera_hal_capture +} + +mp_obj_t mp_camera_hal_frame_available(mp_camera_obj_t *self) { + check_init(self); + return mp_obj_new_bool(esp_camera_available_frames()); +} bool mp_camera_hal_initialized(mp_camera_obj_t *self){ return self->initialized; } +void mp_camera_hal_free_buffer(mp_camera_obj_t *self) { + if (self->captured_buffer) { + esp_camera_fb_return(self->captured_buffer); + self->captured_buffer = NULL; + } +} + const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[] = { - { MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT(PIXFORMAT_JPEG) }, - { MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422) }, - { MP_ROM_QSTR(MP_QSTR_YUV420), MP_ROM_INT(PIXFORMAT_YUV420) }, - { MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(PIXFORMAT_GRAYSCALE) }, - { MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(PIXFORMAT_RGB565) }, - { MP_ROM_QSTR(MP_QSTR_RGB888), MP_ROM_INT(PIXFORMAT_RGB888) }, - { MP_ROM_QSTR(MP_QSTR_RAW), MP_ROM_INT(PIXFORMAT_RAW) }, - { MP_ROM_QSTR(MP_QSTR_RGB444), MP_ROM_INT(PIXFORMAT_RGB444) }, - { MP_ROM_QSTR(MP_QSTR_RGB555), MP_ROM_INT(PIXFORMAT_RGB555) }, + { MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT((mp_uint_t)PIXFORMAT_JPEG) }, + { MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT((mp_uint_t)PIXFORMAT_YUV422) }, + { MP_ROM_QSTR(MP_QSTR_YUV420), MP_ROM_INT((mp_uint_t)PIXFORMAT_YUV420) }, + { MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT((mp_uint_t)PIXFORMAT_GRAYSCALE) }, + { MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB565) }, + { MP_ROM_QSTR(MP_QSTR_RGB888), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB888) }, + { MP_ROM_QSTR(MP_QSTR_RAW), MP_ROM_INT((mp_uint_t)PIXFORMAT_RAW) }, + { MP_ROM_QSTR(MP_QSTR_RGB444), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB444) }, + { MP_ROM_QSTR(MP_QSTR_RGB555), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB555) }, }; const mp_rom_map_elem_t mp_camera_hal_frame_size_table[] = { - { MP_ROM_QSTR(MP_QSTR_R96X96), MP_ROM_INT(FRAMESIZE_96X96) }, - { MP_ROM_QSTR(MP_QSTR_QQVGA), MP_ROM_INT(FRAMESIZE_QQVGA) }, - { MP_ROM_QSTR(MP_QSTR_R128x128), MP_ROM_INT(FRAMESIZE_128X128) }, - { MP_ROM_QSTR(MP_QSTR_QCIF), MP_ROM_INT(FRAMESIZE_QCIF) }, - { MP_ROM_QSTR(MP_QSTR_HQVGA), MP_ROM_INT(FRAMESIZE_HQVGA) }, - { MP_ROM_QSTR(MP_QSTR_R240X240), MP_ROM_INT(FRAMESIZE_240X240) }, - { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT(FRAMESIZE_QVGA) }, - { MP_ROM_QSTR(MP_QSTR_R320X320), MP_ROM_INT(FRAMESIZE_320X320) }, - { MP_ROM_QSTR(MP_QSTR_CIF), MP_ROM_INT(FRAMESIZE_CIF) }, - { MP_ROM_QSTR(MP_QSTR_HVGA), MP_ROM_INT(FRAMESIZE_HVGA) }, - { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT(FRAMESIZE_VGA) }, - { MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT(FRAMESIZE_SVGA) }, - { MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT(FRAMESIZE_XGA) }, - { MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT(FRAMESIZE_HD) }, - { MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT(FRAMESIZE_SXGA) }, - { MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT(FRAMESIZE_UXGA) }, - { MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT(FRAMESIZE_FHD) }, - { MP_ROM_QSTR(MP_QSTR_P_HD), MP_ROM_INT(FRAMESIZE_P_HD) }, - { MP_ROM_QSTR(MP_QSTR_P_3MP), MP_ROM_INT(FRAMESIZE_P_3MP) }, - { MP_ROM_QSTR(MP_QSTR_QXGA), MP_ROM_INT(FRAMESIZE_QXGA) }, - { MP_ROM_QSTR(MP_QSTR_QHD), MP_ROM_INT(FRAMESIZE_QHD) }, - { MP_ROM_QSTR(MP_QSTR_WQXGA), MP_ROM_INT(FRAMESIZE_WQXGA) }, - { MP_ROM_QSTR(MP_QSTR_P_FHD), MP_ROM_INT(FRAMESIZE_P_FHD) }, - { MP_ROM_QSTR(MP_QSTR_QSXGA), MP_ROM_INT(FRAMESIZE_QSXGA) }, + { MP_ROM_QSTR(MP_QSTR_R96X96), MP_ROM_INT((mp_uint_t)FRAMESIZE_96X96) }, + { MP_ROM_QSTR(MP_QSTR_QQVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QQVGA) }, + { MP_ROM_QSTR(MP_QSTR_R128x128), MP_ROM_INT((mp_uint_t)FRAMESIZE_128X128) }, + { MP_ROM_QSTR(MP_QSTR_QCIF), MP_ROM_INT((mp_uint_t)FRAMESIZE_QCIF) }, + { MP_ROM_QSTR(MP_QSTR_HQVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_HQVGA) }, + { MP_ROM_QSTR(MP_QSTR_R240X240), MP_ROM_INT((mp_uint_t)FRAMESIZE_240X240) }, + { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QVGA) }, + { MP_ROM_QSTR(MP_QSTR_R320X320), MP_ROM_INT((mp_uint_t)FRAMESIZE_320X320) }, + { MP_ROM_QSTR(MP_QSTR_CIF), MP_ROM_INT((mp_uint_t)FRAMESIZE_CIF) }, + { MP_ROM_QSTR(MP_QSTR_HVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_HVGA) }, + { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_VGA) }, + { MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_SVGA) }, + { MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_XGA) }, + { MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT((mp_uint_t)FRAMESIZE_HD) }, + { MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_SXGA) }, + { MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_UXGA) }, + { MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT((mp_uint_t)FRAMESIZE_FHD) }, + { MP_ROM_QSTR(MP_QSTR_P_HD), MP_ROM_INT((mp_uint_t)FRAMESIZE_P_HD) }, + { MP_ROM_QSTR(MP_QSTR_P_3MP), MP_ROM_INT((mp_uint_t)FRAMESIZE_P_3MP) }, + { MP_ROM_QSTR(MP_QSTR_QXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QXGA) }, + { MP_ROM_QSTR(MP_QSTR_QHD), MP_ROM_INT((mp_uint_t)FRAMESIZE_QHD) }, + { MP_ROM_QSTR(MP_QSTR_WQXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_WQXGA) }, + { MP_ROM_QSTR(MP_QSTR_P_FHD), MP_ROM_INT((mp_uint_t)FRAMESIZE_P_FHD) }, + { MP_ROM_QSTR(MP_QSTR_QSXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QSXGA) }, }; const mp_rom_map_elem_t mp_camera_hal_grab_mode_table[] = { - { MP_ROM_QSTR(MP_QSTR_WHEN_EMPTY), MP_ROM_INT(CAMERA_GRAB_WHEN_EMPTY) }, - { MP_ROM_QSTR(MP_QSTR_LATEST), MP_ROM_INT(CAMERA_GRAB_LATEST) }, + { MP_ROM_QSTR(MP_QSTR_WHEN_EMPTY), MP_ROM_INT((mp_uint_t)CAMERA_GRAB_WHEN_EMPTY) }, + { MP_ROM_QSTR(MP_QSTR_LATEST), MP_ROM_INT((mp_uint_t)CAMERA_GRAB_LATEST) }, }; const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { - { MP_ROM_QSTR(MP_QSTR_2X), MP_ROM_INT(GAINCEILING_2X) }, - { MP_ROM_QSTR(MP_QSTR_4X), MP_ROM_INT(GAINCEILING_4X) }, - { MP_ROM_QSTR(MP_QSTR_8X), MP_ROM_INT(GAINCEILING_8X) }, - { MP_ROM_QSTR(MP_QSTR_16X), MP_ROM_INT(GAINCEILING_16X) }, - { MP_ROM_QSTR(MP_QSTR_32X), MP_ROM_INT(GAINCEILING_32X) }, - { MP_ROM_QSTR(MP_QSTR_64X), MP_ROM_INT(GAINCEILING_64X) }, - { MP_ROM_QSTR(MP_QSTR_128X), MP_ROM_INT(GAINCEILING_128X) }, + { MP_ROM_QSTR(MP_QSTR_2X), MP_ROM_INT((mp_uint_t)GAINCEILING_2X) }, + { MP_ROM_QSTR(MP_QSTR_4X), MP_ROM_INT((mp_uint_t)GAINCEILING_4X) }, + { MP_ROM_QSTR(MP_QSTR_8X), MP_ROM_INT((mp_uint_t)GAINCEILING_8X) }, + { MP_ROM_QSTR(MP_QSTR_16X), MP_ROM_INT((mp_uint_t)GAINCEILING_16X) }, + { MP_ROM_QSTR(MP_QSTR_32X), MP_ROM_INT((mp_uint_t)GAINCEILING_32X) }, + { MP_ROM_QSTR(MP_QSTR_64X), MP_ROM_INT((mp_uint_t)GAINCEILING_64X) }, + { MP_ROM_QSTR(MP_QSTR_128X), MP_ROM_INT((mp_uint_t)GAINCEILING_128X) }, }; // Helper functions to get and set camera and sensor information diff --git a/src/modcamera.h b/src/modcamera.h index 4d86ce8..a3ce749 100644 --- a/src/modcamera.h +++ b/src/modcamera.h @@ -96,8 +96,6 @@ typedef struct hal_camera_obj { camera_config_t camera_config; bool initialized; camera_fb_t *captured_buffer; - bool bmp_out; - mp_buffer_info_t converted_buffer; } hal_camera_obj_t; #endif // CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 @@ -184,19 +182,24 @@ extern void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize * @brief Captures an image and returns it as mp_obj_t (e.g. mp_obj_new_memoryview). * * @param self Pointer to the camera object. - * @param out_format Output pixelformat format. * @return Captured image as micropython object. */ -extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format); +extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self); /** - * @brief Converts an image from one pixelformat to another. + * @brief Returns true, if a frame is available. * * @param self Pointer to the camera object. - * @param out_format Output pixelformat format. - * @return Converted image as micropython object. + * @return True, if a frame is available. */ -extern mp_obj_t mp_camera_hal_convert(mp_camera_obj_t *self, int8_t out_format); +extern mp_obj_t mp_camera_hal_frame_available(mp_camera_obj_t *self); + +/** + * @brief Frees the buffer of the camera object. + * + * @param self Pointer to the camera object. + */ +extern void mp_camera_hal_free_buffer(mp_camera_obj_t *self); /** * @brief Table mapping pixel formats API to their corresponding values at HAL. diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 4799ce3..39afa71 100644 --- a/src/modcamera_api.c +++ b/src/modcamera_api.c @@ -40,7 +40,7 @@ const mp_obj_type_t camera_type; //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, ARG_bmp_out, 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_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 } }, @@ -68,7 +68,6 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz { MP_QSTR_fb_count, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_FB_COUNT } }, { MP_QSTR_grab_mode, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_GRAB_MODE } }, { MP_QSTR_init, MP_ARG_BOOL | MP_ARG_KW_ONLY, { .u_bool = true } }, - { MP_QSTR_bmp_out, MP_ARG_BOOL | MP_ARG_KW_ONLY, { .u_bool = false } }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -129,37 +128,43 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_camera_obj_t *self = mp_obj_malloc_with_finaliser(mp_camera_obj_t, &camera_type); self->base.type = &camera_type; - self->bmp_out = args[ARG_bmp_out].u_bool; 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); mp_camera_hal_init(self); - if (mp_camera_hal_capture(self, -1) == mp_const_none){ + 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.")); } else { if ( !args[ARG_init].u_bool ){ mp_camera_hal_deinit(self); + } else { + mp_camera_hal_free_buffer(self); } return MP_OBJ_FROM_PTR(self); } } // camera_construct // Main methods -static mp_obj_t camera_capture(size_t n_args, const mp_obj_t *args){ - mp_camera_obj_t *self = MP_OBJ_TO_PTR(args[0]); - int8_t out_format = n_args < 2 ? -1 : mp_obj_get_int(args[1]); - return mp_camera_hal_capture(self, out_format); +static mp_obj_t camera_capture(mp_obj_t self_in){ + mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_camera_hal_capture(self); +} +static MP_DEFINE_CONST_FUN_OBJ_1(camera_capture_obj, camera_capture); + +static mp_obj_t camera_frame_available(mp_obj_t self_in){ + mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_camera_hal_frame_available(self); } -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(camera_capture_obj, 1, 2, camera_capture); +static MP_DEFINE_CONST_FUN_OBJ_1(camera_frame_available_obj, camera_frame_available); -static mp_obj_t camera_convert(mp_obj_t self_in, mp_obj_t arg) { +static mp_obj_t camera_free_buf(mp_obj_t self_in) { mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); - int8_t out_format = mp_obj_get_int(arg); - return mp_camera_hal_convert(self, out_format); + mp_camera_hal_free_buffer(self); + return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_2(camera_convert_obj, camera_convert); +static MP_DEFINE_CONST_FUN_OBJ_1(camera_free_buf_obj, camera_free_buf); static mp_obj_t camera_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args){ mp_camera_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); @@ -210,19 +215,6 @@ static mp_obj_t mp_camera_deinit(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(mp_camera_deinit_obj, mp_camera_deinit); -static mp_obj_t camera_get_bmp_out(mp_obj_t self_in) { - mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_bool(self->bmp_out); -} -static MP_DEFINE_CONST_FUN_OBJ_1(camera_get_bmp_out_obj, camera_get_bmp_out); - -static mp_obj_t camera_set_bmp_out(mp_obj_t self_in, mp_obj_t arg) { - mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); - self->bmp_out = mp_obj_is_true(arg); - return mp_const_none; -} -static MP_DEFINE_CONST_FUN_OBJ_2(camera_set_bmp_out_obj, camera_set_bmp_out); - // Destructor static mp_obj_t mp_camera_obj___exit__(size_t n_args, const mp_obj_t *args) { (void)n_args; @@ -297,7 +289,8 @@ CREATE_GETSET_FUNCTIONS(lenc, mp_obj_new_bool, mp_obj_is_true); static const mp_rom_map_elem_t camera_camera_locals_table[] = { { MP_ROM_QSTR(MP_QSTR_reconfigure), MP_ROM_PTR(&camera_reconfigure_obj) }, { MP_ROM_QSTR(MP_QSTR_capture), MP_ROM_PTR(&camera_capture_obj) }, - { MP_ROM_QSTR(MP_QSTR_convert), MP_ROM_PTR(&camera_convert_obj) }, + { MP_ROM_QSTR(MP_QSTR_frame_available), MP_ROM_PTR(&camera_frame_available_obj) }, + { MP_ROM_QSTR(MP_QSTR_free_buffer), MP_ROM_PTR(&camera_free_buf_obj) }, { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&camera_init_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mp_camera_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_camera_deinit_obj) }, @@ -336,7 +329,6 @@ static const mp_rom_map_elem_t camera_camera_locals_table[] = { ADD_PROPERTY_TO_TABLE(wpc), ADD_PROPERTY_TO_TABLE(raw_gma), ADD_PROPERTY_TO_TABLE(lenc), - ADD_PROPERTY_TO_TABLE(bmp_out), }; static MP_DEFINE_CONST_DICT(camera_camera_locals_dict, camera_camera_locals_table); From 6a03743cb7c32093c16324bf90d97fdb6def525f Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 22 Mar 2025 05:20:42 +0100 Subject: [PATCH 05/16] correct bug in micropython.cmake when using esp32-camera component from esp registry --- src/micropython.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/micropython.cmake b/src/micropython.cmake index 276c25f..914db83 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -4,6 +4,8 @@ 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 @@ -23,7 +25,7 @@ if(DEFINED ESP32_CAMERA_DIR AND EXISTS "${ESP32_CAMERA_DIR}") ) # If no manual directory is provided, try to fetch it from ESP-IDF -else() +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) From 84d3b0f07cd4d1d8d2d93324fc19e6ee3b993cf7 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Sat, 22 Mar 2025 05:31:41 +0100 Subject: [PATCH 06/16] Update example --- examples/CameraSettings.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/CameraSettings.py b/examples/CameraSettings.py index 3a45772..9515a19 100644 --- a/examples/CameraSettings.py +++ b/examples/CameraSettings.py @@ -26,8 +26,6 @@ async def stream_camera(writer): try: cam.init() - if not cam.get_bmp_out() and cam.get_pixel_format() != PixelFormat.JPEG: - cam.set_bmp_out(True) await asyncio.sleep(1) writer.write(b'HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n') @@ -36,10 +34,7 @@ async def stream_camera(writer): while True: frame = await cam.acapture() # This is the async version of capture, you can also use frame = cam.capture() instead if frame: - if cam.get_pixel_format() == PixelFormat.JPEG: - writer.write(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n') - else: - writer.write(b'--frame\r\nContent-Type: image/bmp\r\n\r\n') + writer.write(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n') writer.write(frame) await writer.drain() From 0cc4247af20f05e96c9fcbaf28149c9139e3fc17 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sat, 22 Mar 2025 14:22:21 +0100 Subject: [PATCH 07/16] README.md aktualisieren --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da880cd..251430a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The API is stable, but it might change without previous announce. If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** -This firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpe) to encode/decode JPEGs. +This firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. ## Using the API From 5befb52c9225b674798d0611a61501051a8fbf0a Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sat, 22 Mar 2025 14:23:53 +0100 Subject: [PATCH 08/16] README.md aktualisieren --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 251430a..d8552b7 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The API is stable, but it might change without previous announce. If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** -This firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. +These firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. ## Using the API From 7567703bd5ad39ff4bdc6bda726d8959acdf842a Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sat, 22 Mar 2025 17:17:50 +0100 Subject: [PATCH 09/16] micropython.cmake aktualisieren Add new function to register usermodule only once --- src/micropython.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/micropython.cmake b/src/micropython.cmake index 914db83..7398d9f 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -1,5 +1,12 @@ include(${MICROPY_DIR}/py/py.cmake) +function(check_and_register_component target component) + get_target_property(LIBRARIES ${target} INTERFACE_LINK_LIBRARIES) + if(NOT "${LIBRARIES}" MATCHES "${component}") + target_link_libraries(${target} INTERFACE ${component}) + endif() +endfunction() + set(MICROPY_FROZEN_MANIFEST ${CMAKE_CURRENT_LIST_DIR}/manifest.py) add_library(usermod_mp_camera INTERFACE) @@ -81,7 +88,7 @@ if (MP_CAMERA_DRIVER_VERSION) endif() # Link the camera module with the main usermod target -target_link_libraries(usermod INTERFACE usermod_mp_camera) +check_and_register_component(usermod usermod_mp_camera) # Gather target properties for MicroPython build system micropy_gather_target_properties(usermod_mp_camera) From 2b63e5158e9be96c565d7f5e6bfb70be077085f0 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 2 Apr 2025 15:29:07 +0200 Subject: [PATCH 10/16] small improvements (still poor performance) --- src/micropython.cmake | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/micropython.cmake b/src/micropython.cmake index 7398d9f..d08205b 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -1,12 +1,5 @@ include(${MICROPY_DIR}/py/py.cmake) -function(check_and_register_component target component) - get_target_property(LIBRARIES ${target} INTERFACE_LINK_LIBRARIES) - if(NOT "${LIBRARIES}" MATCHES "${component}") - target_link_libraries(${target} INTERFACE ${component}) - endif() -endfunction() - set(MICROPY_FROZEN_MANIFEST ${CMAKE_CURRENT_LIST_DIR}/manifest.py) add_library(usermod_mp_camera INTERFACE) @@ -75,6 +68,7 @@ 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 ) @@ -88,7 +82,7 @@ if (MP_CAMERA_DRIVER_VERSION) endif() # Link the camera module with the main usermod target -check_and_register_component(usermod usermod_mp_camera) +target_link_libraries(usermod INTERFACE usermod_mp_camera) # Gather target properties for MicroPython build system micropy_gather_target_properties(usermod_mp_camera) From f34fcc93ed316e34bcaf6c2648e9f6f693394991 Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sat, 5 Apr 2025 16:05:27 +0200 Subject: [PATCH 11/16] README.md aktualisieren --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d8552b7..a3c9404 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ The API is stable, but it might change without previous announce. ## Precompiled firmware (the easy way) If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** +To flash the firmware, you can use for example https://esp.huhn.me/](https://esp.huhn.me/). These firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. From 9c6cd1248726a7a14ecb64c0c4b45d703f820ddd Mon Sep 17 00:00:00 2001 From: Christopher Nadler <147471517+cnadler86@users.noreply.github.com> Date: Sat, 5 Apr 2025 16:05:58 +0200 Subject: [PATCH 12/16] README.md aktualisieren --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3c9404..d0f5834 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ The API is stable, but it might change without previous announce. ## Precompiled firmware (the easy way) If you are not familiar with building custom firmware, visit the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page to download firmware that suits your board. **There are over 20 precompiled board images with the latest micropython!** -To flash the firmware, you can use for example https://esp.huhn.me/](https://esp.huhn.me/). +To flash the firmware, you can use for example [https://esp.huhn.me/](https://esp.huhn.me/). These firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. From 71c868e4e122ddc1d4c9537ea0abd6ac4e1b02ec Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Fri, 11 Apr 2025 15:51:19 +0200 Subject: [PATCH 13/16] Update example --- examples/CameraSettings.html | 4 ++++ examples/CameraSettings.py | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/examples/CameraSettings.html b/examples/CameraSettings.html index 63d0055..e392005 100644 --- a/examples/CameraSettings.html +++ b/examples/CameraSettings.html @@ -199,6 +199,10 @@

Micropython Camera Stream

+
+ + +
diff --git a/examples/CameraSettings.py b/examples/CameraSettings.py index 9515a19..77d6caa 100644 --- a/examples/CameraSettings.py +++ b/examples/CameraSettings.py @@ -64,7 +64,14 @@ async def handle_client(reader, writer): writer.write(response.encode()) await writer.drain() else: - response = 'HTTP/1.1 404 Not Found\r\n\r\n' + try: + cam.reconfigure(**{method_name: value}) + print(f"Camera reconfigured with {method_name}={value}") + print("This action restores all previous configuration!") + response = 'HTTP/1.1 200 OK\r\n\r\n' + except Exception as e: + print(f"Error with {method_name}: {e}") + response = 'HTTP/1.1 404 Not Found\r\n\r\n' writer.write(response.encode()) await writer.drain() From 4334c80334cddff72a2db9f7e202fb013cd695df Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Tue, 15 Apr 2025 11:57:51 +0200 Subject: [PATCH 14/16] Update readme --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d0f5834..6380fe9 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,11 @@ [![ESP32 Port](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml/badge.svg)](https://github.com/cnadler86/micropython-camera-API/actions/workflows/ESP32.yml) This project aims to support various cameras (e.g. OV2640, OV5640) on different MicroPython ports, starting with the ESP32 port. The project implements a general API, has precompiled firmware images and supports a lot of cameras out of the box. Defaults are set to work with the OV2640. -At the moment, this is a micropython user module, but it might get in the micropython repo in the future. -The API is stable, but it might change without previous announce. + +The API is stable, but it might change. Please look into the release section for the latest changes. + +I tied to make things as easy as possible. If you find this project useful, please consider donating to support my work. Thanks! +[![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://www.paypal.me/cnadler) ## Content From 33328e888ddc04e4d5730a840eb9f974f28d71f2 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 16 Apr 2025 05:26:37 +0200 Subject: [PATCH 15/16] Release version --- .github/workflows/ESP32.yml | 4 ++-- README.md | 2 ++ src/modcamera.c | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index ea142af..c0ec164 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -76,8 +76,8 @@ jobs: if: steps.cache_esp_idf.outputs.cache-hit != 'true' run: | cd ~ - # git clone --depth 1 --branch release/v5.3 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.4 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 diff --git a/README.md b/README.md index 6380fe9..c46598e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ The API is stable, but it might change. Please look into the release section for I tied to make things as easy as possible. If you find this project useful, please consider donating to support my work. Thanks! [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://www.paypal.me/cnadler) +If you want to play arround with AI, take a look at the [micropython binding for esp-dl](https://github.com/cnadler86/mp_esp_dl_models). + ## Content - [Precompiled firmware (the easy way)](#Precompiled-firmware-the-easy-way) diff --git a/src/modcamera.c b/src/modcamera.c index 5867b16..5a0bd05 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -97,7 +97,7 @@ static bool init_camera(mp_camera_obj_t *self) { self->camera_config.jpeg_quality = get_mapped_jpeg_quality(api_jpeg_quality); esp_err_t err = esp_camera_init(&self->camera_config); self->camera_config.jpeg_quality = api_jpeg_quality; - check_esp_err_(err); + check_esp_err(err); return true; } @@ -175,7 +175,7 @@ void mp_camera_hal_deinit(mp_camera_obj_t *self) { self->captured_buffer = NULL; } esp_err_t err = esp_camera_deinit(); - check_esp_err_(err); + check_esp_err(err); self->initialized = false; ESP_LOGI(TAG, "Camera deinitialized"); } @@ -198,7 +198,7 @@ void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t fram set_check_grab_mode(self, grab_mode); set_check_fb_count(self, fb_count); - check_esp_err_(esp_camera_deinit()); + check_esp_err(esp_camera_deinit()); self->initialized = false; self->initialized = init_camera(self); ESP_LOGI(TAG, "Camera reconfigured successfully"); From 2dd97117359d00729d50448df19404d18f67ac30 Mon Sep 17 00:00:00 2001 From: Christopher Nadler Date: Wed, 16 Apr 2025 05:38:04 +0200 Subject: [PATCH 16/16] Fix pipeline --- .github/workflows/ESP32.yml | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index c0ec164..ad39378 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -53,23 +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 - # 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 ~ - git clone --depth 1 --branch ${{ env.MPY_RELEASE }} https://github.com/micropython/micropython.git - cd micropython - 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 - # Download and set up ESP-IDF (if not cached) - name: Set up ESP-IDF id: export-idf @@ -91,6 +74,25 @@ jobs: git clone https://github.com/espressif/esp-adf-libs.git cp -r ~/esp-adf-libs/esp_new_jpeg ~/esp-idf/components/ + # 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 + cd micropython + # 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 + # Dynamically create jobs for each board build: needs: setup-environment