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..ad39378 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -53,16 +53,39 @@ 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 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 + 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/ + # 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 + # git submodule update --init --depth 1 cd mpy-cross make cd ~/micropython/ports/esp32 @@ -70,24 +93,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 release/v5.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 - 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 - # Dynamically create jobs for each board build: needs: setup-environment @@ -151,6 +156,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 +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 }}/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 +190,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 c733b47..c46598e 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,29 @@ [![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. -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. +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. + +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) + +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 -- [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 +33,29 @@ 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!** +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. ## 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 +100,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 +135,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 +165,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 +211,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 +221,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 +294,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 @@ -279,6 +312,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 @@ -292,28 +326,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.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 21f50c4..77d6caa 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 @@ -26,20 +26,15 @@ 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') 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') - 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() @@ -69,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() 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 66fd308..d08205b 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -1,35 +1,88 @@ -# `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) +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 ) -if(EXISTS "${IDF_PATH}/components/esp32-camera") +# 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} - ${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}/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() - target_include_directories(usermod_mp_camera INTERFACE - ${CMAKE_CURRENT_LIST_DIR}) + 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) + 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}\") + 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) -micropy_gather_target_properties(usermod_mp_camera) \ No newline at end of file +# 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..5a0bd05 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" @@ -98,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; } @@ -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,17 +170,12 @@ 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; } esp_err_t err = esp_camera_deinit(); - check_esp_err_(err); + check_esp_err(err); self->initialized = false; ESP_LOGI(TAG, "Camera deinitialized"); } @@ -208,68 +198,13 @@ 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"); } -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);