diff --git a/.github/workflows/Create-Release.yml b/.github/workflows/Create-Release.yml new file mode 100644 index 0000000..4b8dccf --- /dev/null +++ b/.github/workflows/Create-Release.yml @@ -0,0 +1,32 @@ +name: Add-Artifacts-to-Draft-Release + +on: + release: + types: + - created + +jobs: + add_artifacts: + if: ${{ github.event.release.draft }} + 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: Download artifacts + uses: actions/download-artifact@v4 + with: + name: firmware-* + path: ./artifacts + + - name: Upload Release Assets + run: | + for file in ./artifacts/*; do + gh release upload "${{ github.event.release.tag_name }}" "$file" --clobber + done diff --git a/.github/workflows/ESP32.yml b/.github/workflows/ESP32.yml index ce5a7d1..65a89cc 100644 --- a/.github/workflows/ESP32.yml +++ b/.github/workflows/ESP32.yml @@ -5,9 +5,13 @@ on: push: paths: - 'src/**' + - '.github/workflows/*.yml' pull_request: branches: - master + paths: + - 'src/**' + - '.github/workflows/*.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -68,13 +72,15 @@ 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 ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git + # 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 - git clone https://github.com/espressif/esp32-camera + 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 @@ -85,12 +91,30 @@ jobs: strategy: fail-fast: false matrix: - board: + board: - ESP32_GENERIC-SPIRAM - ESP32_GENERIC_S2 - ESP32_GENERIC_S3 - ESP32_GENERIC_S3-SPIRAM_OCT - ESP32_GENERIC_S3-FLASH_4M + - ESP32_GENERIC-SPIRAM@WROVER_KIT + - ESP32_GENERIC-SPIRAM@ESP_EYE + - ESP32_GENERIC-SPIRAM@M5STACK_PSRAM + - ESP32_GENERIC-SPIRAM@M5STACK_V2_PSRAM + - ESP32_GENERIC-SPIRAM@M5STACK_WIDE + - ESP32_GENERIC-SPIRAM@M5STACK_ESP32CAM + - ESP32_GENERIC-SPIRAM@M5STACK_UNITCAM + - ESP32_GENERIC-SPIRAM@AI_THINKER + - ESP32_GENERIC-SPIRAM@TTGO_T_JOURNAL + - ESP32_GENERIC-SPIRAM@TTGO_T_CAMERA_PLUS + - ESP32_GENERIC_S3-SPIRAM_OCT@M5STACK_CAMS3_UNIT + - ESP32_GENERIC_S3-SPIRAM_OCT@XIAO_ESP32S3 + - ESP32_GENERIC_S3-SPIRAM_OCT@ESP32S3_CAM_LCD + - ESP32_GENERIC_S3-SPIRAM_OCT@ESP32S3_EYE + - ESP32_GENERIC_S3-SPIRAM_OCT@FREENOVE_ESP32S3_CAM + - ESP32_GENERIC_S3-SPIRAM_OCT@DFRobot_ESP32S3 + - ESP32_GENERIC_S3-SPIRAM_OCT@NEW_ESPS3_RE1_0 + - ESP32_GENERIC_S3-SPIRAM_OCT@XENOIONEX steps: # Get the latest MicroPython release @@ -123,21 +147,38 @@ jobs: # Build MicroPython for each board - name: Build MicroPython run: | + cd ~/esp-idf/components/esp32-camera + CAM_DRIVER=$(git describe --tags --always --dirty) cd ~/micropython/ports/esp32 source ~/esp-idf/export.sh - # Check if a variant is defined and adjust the make command - IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${{ matrix.board }}" + # Check if a variant is defined and adjust the idf.py command + IFS='@' read -r BUILD_TARGET CAMERA_MODEL <<< "${{ matrix.board }}" + IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" + if [ -n "${BOARD_VARIANT}" ]; then - make USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake BOARD=$BOARD_NAME BOARD_VARIANT=$BOARD_VARIANT all + 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" + 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" + fi + if [ -n "${CAMERA_MODEL}" ]; then + echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV + FINAL_CMD="${IDF_CMD} -D MICROPY_CAMERA_MODEL=${CAMERA_MODEL} build" else - make USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake BOARD=$BOARD_NAME all + echo "FW_NAME=${BUILD_TARGET}" >> $GITHUB_ENV + FINAL_CMD="${IDF_CMD} build" fi - mv ~/micropython/ports/esp32/build-${{ matrix.board }}/firmware.bin ~/${{ matrix.board }}.bin + make USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake BOARD=$BOARD_NAME submodules + echo "Running command: $FINAL_CMD" + eval $FINAL_CMD + cd ~/micropython/ports/esp32/build-${BUILD_TARGET} + python ../makeimg.py sdkconfig bootloader/bootloader.bin partition_table/partition-table.bin micropython.bin firmware.bin micropython.uf2 + mkdir -p ~/artifacts + mv ~/micropython/ports/esp32/build-${BUILD_TARGET}/firmware.bin ~/artifacts/firmware.bin - name: Upload firmware artifact uses: actions/upload-artifact@v4 with: - name: firmware-${{ matrix.board }} - path: ~/${{ matrix.board }}.bin - retention-days: 90 \ No newline at end of file + name: firmware-${{ env.FW_NAME }} + path: ~/artifacts/** + retention-days: 5 \ No newline at end of file diff --git a/README.md b/README.md index acb2778..373eaa5 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,45 @@ [![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 cameras in different ports in micropython, starting with the ESP32-Port and omnivision (OV2640 & OV5640) cameras. The project implements a general API for cameras in micropython (such as circuitpython have done). +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. 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 anounce. +The API is stable, but it might change without previous announce. ## Precomiled FW (the easy way) -If you are not familiar with building a custom firmware, you can go to the [releases](https://github.com/cnadler86/micropython-camera-API/releases) page and download one of the generic FWs that suits your board. +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!** ## Using the API +### Importing the Camera Module + ```python from camera 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. + +```python +cam = Camera() +``` + +or with relevant keyword arguments: -# Camera construction and initialization -# These pins are just examples and if you use them just like that will get a watchdog error. Adapt them to your board! -camera = Camera( +```python +cam = Camera(pixel_format=PixelFormat.JPEG, + frame_size=FrameSize.QVGA, + jpeg_quality=90, + fb_count=2, + grab_mode=GrabMode.WHEN_EMPTY) +``` + +When using a **generic** precompiled firmware, the camera constructor requires specific keyword arguments (namely the camera pins to be used). +These pins are just examples and if used as-is, a error will occur. Adapt them to your board! + +```python +cam = Camera( data_pins=[1,2,3,4,5,6,7,8], vsync_pin=9, href_pin=10, @@ -28,49 +51,154 @@ camera = Camera( xclk_freq=20000000, powerdown_pin=-1, reset_pin=-1, - pixel_format=PixelFormat.RGB565, - frame_size=FrameSize.QVGA, - jpeg_quality=15, - fb_count=1, - grab_mode=GrabMode.WHEN_EMPTY ) +``` -#Camera construction using defaults (if you specified them in mpconfigboard.h) -camera = Camera() +**Keyword arguments for construction:** -# Capture image -img = camera.capture() +- data_pins: List of data pins +- pclk_pin: Pixel clock pin + -vsync_pin: VSYNC pin +- 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 (default: -1, meaning not used) +- reset_pin: Reset pin (default: -1, meaning 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 capture output converted to bitmap (default: False) -# Camera reconfiguration -camera.reconfigure(pixel_format=PixelFormat.JPEG,frame_size=FrameSize.QVGA,grab_mode=GrabMode.LATEST, fb_count=2) -camera.set_quality(10) +**Default values:** + +The following keyword arguments have default values: + +- xclk_freq: 20MHz // Frequencies are normally either 10 MHz or 20 MHz +- frame_size: QQVGA +- pixel_format: RGB565 +- jpeg_quality: 85 // Quality of JPEG output in percent. Higher means higher quality. +- powerdown_pin and reset_pin: -1 (not used/available/needed) +- fb_count: + - 2 for ESP32S3 boards + - 1 for all other +- grab_mode: + - LATEST for ESP32S3 boards + - WHEN_EMPTY for all other + +### Initializing the Camera + +```python +cam.init() +``` + +### Capture image + +```python +img = cam.capture() +``` + +Keyword arguments for capture + +- out_format: Output format as PixelFormat (optional) + +### Camera reconfiguration + +```python +cam.reconfigure(pixel_format=PixelFormat.JPEG,frame_size=FrameSize.QVGA,grab_mode=GrabMode.LATEST, fb_count=2) ``` -You can get and set sensor properties by the respective methods (e.g. camera.get_brightness() or camera.set_vflip(True). See autocompletitions in Thonny in order to see the list of methods. +Keyword arguments for reconfigure + +- frame_size: Frame size as FrameSize (optional) +- pixel_format: Pixel format as PixelFormat(optional) +- grab_mode: Grab mode as GrabMode (optional) +- fb_count: Frame buffer count (optional) + +### Additional methods + +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). -Notice that for the methods in here you need to prefix a get/set, depending on what you want to do. +Note that each method requires a "get_" or "set_" prefix, depending on the desired action. + +To get the version of the camera driver used: + +```python +import camera +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 ## Build your custom FW -### Setup build environment (the DIY way) +### Setting up the build environment (DIY method) -To build the project, follow the following instructions: +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". I used the actual micropython master branch (between v1.23 and before 1.24). -- You will have to add the ESP32-Camera driver (I used v2.0.12). To do this, add the following to the respective idf_component.yml file (e.g. in micropython/ports/esp32/main_esp32s3/idf_component.yml): +- 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): ```yml espressif/esp32-camera: - git: https://github.com/espressif/esp32-camera + git: https://github.com/espressif/esp32-camera.git ``` Alternatively, you can clone the repository inside the esp-idf/components folder instead of altering the idf_component.yml file. -### Add camera configurations to your board (Optional, but recommended) +### Add camera configurations to your board (optional, but recommended) + +#### Supported Camera Models + +This project supports various boards with camera interface out of the box. You typically only need to add a single line to your board config file ("mpconfigboard.h). +Example (don't forget to add the empty line at the bottom): + +```c +#define MICROPY_CAMERA_MODEL_WROVER_KIT 1 -To make things easier, add the following lines to your board config-file "mpconfigboard.h" with the respective pins and camera parameters. Otherwise, you will need to pass all parameters during construction. -Don't forget the empty line at the bottom. +``` + +Below is a list of supported `MICROPY_CAMERA_MODEL_xxx` definitions: + +- MICROPY_CAMERA_MODEL_WROVER_KIT - [ESP32-WROVER-KIT](https://www.espressif.com/en/products/devkits/esp32-wrover-kit/overview) +- MICROPY_CAMERA_MODEL_ESP_EYE - [ESP-EYE](https://www.espressif.com/en/products/devkits/esp-eye/overview) +- MICROPY_CAMERA_MODEL_M5STACK_PSRAM - [M5Stack PSRAM](https://shop.m5stack.com/collections/m5-cameras) +- MICROPY_CAMERA_MODEL_M5STACK_UNITCAM - [M5Stack UnitCam](https://shop.m5stack.com/collections/m5-cameras) +- MICROPY_CAMERA_MODEL_M5STACK_V2_PSRAM - [M5Stack V2 PSRAM](https://shop.m5stack.com/collections/m5-cameras) +- MICROPY_CAMERA_MODEL_M5STACK_WIDE - [M5Stack Wide](https://shop.m5stack.com/collections/m5-cameras) +- MICROPY_CAMERA_MODEL_M5STACK_ESP32CAM - [M5Stack ESP32CAM](https://shop.m5stack.com/collections/m5-cameras) +- MICROPY_CAMERA_MODEL_M5STACK_CAMS3_UNIT - [M5Stack CAMS3 Unit](https://shop.m5stack.com/collections/m5-cameras) +- MICROPY_CAMERA_MODEL_AI_THINKER - [AI-Thinker ESP32-CAM] +- MICROPY_CAMERA_MODEL_XIAO_ESP32S3 - [XIAO ESP32S3](https://www.seeedstudio.com/xiao-series-page) +- MICROPY_CAMERA_MODEL_ESP32_MP_CAMERA_BOARD - [ESP32 MP Camera Board] +- MICROPY_CAMERA_MODEL_ESP32S3_CAM_LCD - [ESP32-S3 CAM LCD] +- MICROPY_CAMERA_MODEL_ESP32S3_EYE - [ESP32-S3 EYE](https://www.espressif.com/en/products/devkits/esp32-s3-eye/overview) +- MICROPY_CAMERA_MODEL_FREENOVE_ESP32S3_CAM - [Freenove ESP32-S3 CAM](https://store.freenove.com/products/fnk0085) +- MICROPY_CAMERA_MODEL_DFRobot_ESP32S3 - [DFRobot ESP32-S3](https://www.dfrobot.com/) +- MICROPY_CAMERA_MODEL_TTGO_T_JOURNAL - [TTGO T-Journal](https://www.lilygo.cc/products/) +- MICROPY_CAMERA_MODEL_TTGO_T_CAMERA_PLUS - [TTGO T-Camera Plus](https://www.lilygo.cc/products/) +- MICROPY_CAMERA_MODEL_NEW_ESPS3_RE1_0 - [New ESP32-S3 RE:1.0] +- MICROPY_CAMERA_MODEL_XENOIONEX - [Xenoionex] + +#### For unsupported camera models + +If your board is not yet supported, add the following lines to your board config-file "mpconfigboard.h" with the respective pins and camera parameters. Otherwise, you will need to pass all parameters during construction. Example for Xiao sense: ```c @@ -91,11 +219,14 @@ Example for Xiao sense: #define MICROPY_CAMERA_PIN_SIOD (40) // SDA #define MICROPY_CAMERA_PIN_SIOC (39) // SCL #define MICROPY_CAMERA_XCLK_FREQ (20000000) // Frequencies are normally either 10 MHz or 20 MHz -#define MICROPY_CAMERA_FB_COUNT (2) // Usually the value is between 1 (slow) and 2 (fast, but more load on CPU) -#define MICROPY_CAMERA_JPEG_QUALITY (10) // Quality of JPEG output. 0-63 lower means higher quality. Definition will change in the future +#define MICROPY_CAMERA_FB_COUNT (2) // The value is between 1 (slow) and 2 (fast, but more load on CPU and more ram usage) +#define MICROPY_CAMERA_JPEG_QUALITY (85) // Quality of JPEG output in percent. Higher means higher quality. #define MICROPY_CAMERA_GRAB_MODE (1) // 0=WHEN_EMPTY (might have old data, but less resources), 1=LATEST (best, but more resources) ``` +#### 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. ### Build the API @@ -110,13 +241,48 @@ make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOA ``` If you experience problems, visit [MicroPython external C modules](https://docs.micropython.org/en/latest/develop/cmodules.html). + ## Notes +- For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has significantly improved, but JPEG mode always gives better frame rates. - The OV5640 pinout is compatible with boards designed for the OV2640 but the voltage supply is too high for the internal 1.5V regulator, so the camera overheats unless a heat sink is applied. For recording purposes the OV5640 should only be used with an ESP32S3 board. Frame sizes above FHD framesize should only be used for still images due to memory limitations. - If your target board is a ESP32, I recommend using IDF >= 5.2, since older versions may lead to IRAM overflow during build. Alternatively you can modify your sdkconfig-file (see [issue #1](https://github.com/cnadler86/micropython-camera-API/issues/1)). +- The driver requires PSRAM to be installed and activated. +- Most of the precompiled firmware images are untested, but the only difference between them are the target architecture and pin definitions, so they should work out of the box. If not, please raise an issue. + +## FPS benchmark + +I didn't use a calibrated osziloscope, but here is a benchmark with my ESP32S3 (GrabMode=LATEST, fb_count = 1, jpeg_quality=85%). +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 | 12.5 | 12.5 | 50 | +| CIF | 12.5 | No img | No img | 6.3 | 1.6 | 1.6 | 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. + +## Troubleshoot + +You can find information on the following sites: +- [ESP-FAQ](https://docs.espressif.com/projects/esp-faq/en/latest/application-solution/camera-application.html) +- [ChatGPT](https://chatgpt.com/) +- [Issues in here](https://github.com/cnadler86/micropython-camera-API/issues?q=is%3Aissue) + +## Future Plans -## Plans for the future -- [ ] imolrment structure in repo to include other boards like xiao sense -- [ ] harmonize properties to standard ones at API level, e.g. jpeg quality to the range 100=very good, 1/0= very bad -- [ ] edge case: enable usage of pins such as i2c for other applications -- [ ] provide examples in binary image with lfs-merge \ No newline at end of file +- Edge case: enable usage of pins such as i2c for other applications +- Provide examples in binary image diff --git a/examples/CameraSettings.html b/examples/CameraSettings.html new file mode 100644 index 0000000..27e7447 --- /dev/null +++ b/examples/CameraSettings.html @@ -0,0 +1,316 @@ + + + + Micropython Camera Stream + + + + +
+

Micropython Camera Stream

+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ Loading stream... +
+
+ + \ No newline at end of file diff --git a/examples/CameraSettings.py b/examples/CameraSettings.py new file mode 100644 index 0000000..21f50c4 --- /dev/null +++ b/examples/CameraSettings.py @@ -0,0 +1,110 @@ +import network +import asyncio +import time +from camera import Camera, FrameSize, PixelFormat + +cam = Camera(frame_size=FrameSize.VGA, pixel_format=PixelFormat.JPEG, jpeg_quality=85, init=False) +# WLAN config +ssid = '' +password = '' + +station = network.WLAN(network.STA_IF) +station.active(True) +station.connect(ssid, password) + +while not station.isconnected(): + time.sleep(1) + +print(f'Connected! IP: {station.ifconfig()[0]}. Open this IP in your browser') + +try: + with open("CameraSettings.html", 'r') as file: + html = file.read() +except Exception as e: + print("Error reading CameraSettings.html file. You might forgot to copy it from the examples folder.") + raise e +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() + 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(frame) + await writer.drain() + + finally: + cam.deinit() + writer.close() + await writer.wait_closed() + print("Streaming stopped and camera deinitialized.") + +async def handle_client(reader, writer): + try: + request = await reader.read(1024) + request = request.decode() + + if 'GET /stream' in request: + print("Start streaming...") + await stream_camera(writer) + + elif 'GET /set_' in request: + method_name = request.split('GET /set_')[1].split('?')[0] + value = int(request.split('value=')[1].split(' ')[0]) + set_method = getattr(cam, f'set_{method_name}', None) + if callable(set_method): + print(f"setting {method_name} to {value}") + set_method(value) + response = 'HTTP/1.1 200 OK\r\n\r\n' + writer.write(response.encode()) + await writer.drain() + else: + response = 'HTTP/1.1 404 Not Found\r\n\r\n' + writer.write(response.encode()) + await writer.drain() + + elif 'GET /get_' in request: + method_name = request.split('GET /get_')[1].split(' ')[0] + get_method = getattr(cam, f'get_{method_name}', None) + if callable(get_method): + value = get_method() + print(f"{method_name} is {value}") + response = f'HTTP/1.1 200 OK\r\n\r\n{value}' + writer.write(response.encode()) + await writer.drain() + else: + response = 'HTTP/1.1 404 Not Found\r\n\r\n' + writer.write(response.encode()) + await writer.drain() + + else: + writer.write('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n'.encode() + html.encode()) + await writer.drain() + except Exception as e: + print(f"Error: {e}") + finally: + writer.close() + await writer.wait_closed() + +async def start_server(): + server = await asyncio.start_server(handle_client, "0.0.0.0", 80) + print(f'Server is running on {station.ifconfig()[0]}:80') + while True: + await asyncio.sleep(3600) + +try: + asyncio.run(start_server()) +except KeyboardInterrupt: + cam.deinit() + print("Server stopped") + diff --git a/examples/WebCam.py b/examples/SimpleWebCam.py similarity index 99% rename from examples/WebCam.py rename to examples/SimpleWebCam.py index bf78488..b4f5180 100644 --- a/examples/WebCam.py +++ b/examples/SimpleWebCam.py @@ -4,7 +4,7 @@ from camera import Camera, FrameSize, PixelFormat # Cam Config -cam = Camera(frame_size = FrameSize.VGA,pixel_format=PixelFormat.JPEG) +cam = Camera(frame_size = FrameSize.VGA,pixel_format=PixelFormat.JPEG,init=False) # WLAN config ssid = '' diff --git a/examples/benchmark.py b/examples/benchmark.py new file mode 100644 index 0000000..1ba7aac --- /dev/null +++ b/examples/benchmark.py @@ -0,0 +1,109 @@ +from camera import Camera, FrameSize, PixelFormat +import time +import gc +import os +gc.enable() + +def measure_fps(duration=2): + start_time = time.ticks_ms() + while time.ticks_ms() - start_time < 500: + cam.capture() + + start_time = time.ticks_ms() + frame_count = 0 + + while time.ticks_ms() - start_time < duration*1000: + img = cam.capture() + if img: + frame_count += 1 + + end_time = time.ticks_ms() + fps = frame_count / (end_time - start_time) * 1000 + return round(fps,1) + +def print_summary_table(results, cam): + print(f"\nBenchmark {os.uname().machine} with {cam.get_sensor_name()}, GrabMode: {cam.get_grab_mode()}:") + + fb_counts = sorted(results.keys()) + frame_size_names = {getattr(FrameSize, f): f for f in dir(FrameSize) if not f.startswith('_')} + + header_row = f"{'Frame Size':<15}" + sub_header_row = " " * 15 + + for fb in fb_counts: + for p in results[fb].keys(): + header_row += f"{'fb_count ' + str(fb):<15}" + sub_header_row += f"{p:<15}" + + print(header_row) + print(sub_header_row) + + frame_sizes = list(next(iter(next(iter(results.values())).values())).keys()) + + for f in frame_sizes: + frame_size_name = frame_size_names.get(f, str(f)) + print(f"{frame_size_name:<15}", end="") + + for fb in fb_counts: + for p in results[fb].keys(): + fps = results[fb][p].get(f, "N/A") + print(f"{fps:<15}", end="") + print() + +if __name__ == "__main__": + cam = Camera() + results = {} + + try: + for fb in [1, 2]: + cam.reconfigure(fb_count=fb) + results[fb] = {} + for p in dir(PixelFormat): + if not p.startswith('_'): + p_value = getattr(PixelFormat, p) + if (p_value == PixelFormat.RGB888 and cam.get_sensor_name() == "OV2640") or (p_value != PixelFormat.JPEG and fb > 1): + continue + try: + cam.reconfigure(pixel_format=p_value) + results[fb][p] = {} + except Exception as e: + print('ERR:', e) + continue + + for f in dir(FrameSize): + if not f.startswith('_'): + f_value = getattr(FrameSize, f) + if f_value > cam.get_max_frame_size(): + continue + gc.collect() + print('Set', p, f,f'fb={fb}',':') + + try: + cam.reconfigure(frame_size=f_value) #set_frame_size fails for YUV422 + time.sleep_ms(10) + img = cam.capture() + + if img: + print('---> Image size:', len(img)) + fps = measure_fps(2) + print(f"---> FPS: {fps}") + results[fb][p][f_value] = fps + else: + print('No image captured') + results[fb][p][f_value] = 'No img' + + print(f"---> Free Memory: {gc.mem_free()}") + except Exception as e: + print('ERR:', e) + results[fb][p][f_value] = 'ERR' + finally: + time.sleep_ms(250) + gc.collect() + print('') + + except KeyboardInterrupt: + print("\nScript interrupted by user.") + + finally: + print_summary_table(results, cam) + cam.deinit() diff --git a/examples/benchmark_img_conv.py b/examples/benchmark_img_conv.py new file mode 100644 index 0000000..9a34b64 --- /dev/null +++ b/examples/benchmark_img_conv.py @@ -0,0 +1,107 @@ +from camera import Camera, FrameSize, PixelFormat +import time +import gc +import os +gc.enable() + +def measure_fps(cam,out_fmt,duration=2): + start_time = time.ticks_ms() + while time.ticks_ms() - start_time < 500: + cam.capture(out_fmt) + + start_time = time.ticks_ms() + frame_count = 0 + + while time.ticks_ms() - start_time < duration*1000: + img = cam.capture(out_fmt) + if img: + frame_count += 1 + + end_time = time.ticks_ms() + fps = frame_count / (end_time - start_time) * 1000 + return round(fps,1) + +def print_summary_table(results, cam): + print(f"\nBenchmark {os.uname().machine} with {cam.get_sensor_name()}, GrabMode: {cam.get_grab_mode()}:") + + fb_counts = sorted(results.keys()) + frame_size_names = {getattr(FrameSize, f): f for f in dir(FrameSize) if not f.startswith('_')} + + header_row = f"{'Frame Size':<15}" + sub_header_row = " " * 15 + + for fb in fb_counts: + for p in results[fb].keys(): + header_row += f"{'fb_count ' + str(fb):<15}" + sub_header_row += f"{p:<15}" + + print(header_row) + print(sub_header_row) + + frame_sizes = list(next(iter(next(iter(results.values())).values())).keys()) + + for f in frame_sizes: + frame_size_name = frame_size_names.get(f, str(f)) + print(f"{frame_size_name:<15}", end="") + + for fb in fb_counts: + for p in results[fb].keys(): + fps = results[fb][p].get(f, "N/A") + print(f"{fps:<15}", end="") + print() + +if __name__ == "__main__": + cam = Camera(pixel_format=PixelFormat.JPEG) + results = {} + + try: + for fb in [1, 2]: + cam.reconfigure(fb_count=fb, frame_size=FrameSize.QQVGA) + results[fb] = {} + for p in dir(PixelFormat): + if not p.startswith('_'): + p_value = getattr(PixelFormat, p) + try: + if p_value == PixelFormat.JPEG: + continue + cam.capture(p_value) + results[fb][p] = {} + gc.collect() + except: + continue + for f in dir(FrameSize): + if not f.startswith('_'): + f_value = getattr(FrameSize, f) + if f_value > cam.get_max_frame_size(): + continue + gc.collect() + print('Set', p, f,f'fb={fb}',':') + + try: + cam.set_frame_size(f_value) + time.sleep_ms(10) + img = cam.capture(p_value) + if img: + print('---> Image size:', len(img)) + fps = measure_fps(cam,p_value,2) + print(f"---> FPS: {fps}") + results[fb][p][f_value] = fps + else: + print('No image captured') + results[fb][p][f_value] = 'No img' + + print(f"---> Free Memory: {gc.mem_free()}") + except Exception as e: + print('ERR:', e) + results[fb][p][f_value] = 'ERR' + finally: + time.sleep_ms(250) + gc.collect() + print('') + + except KeyboardInterrupt: + print("\nScript interrupted by user.") + + finally: + print_summary_table(results, cam) + cam.deinit() diff --git a/src/camera_pins.h b/src/camera_pins.h new file mode 100644 index 0000000..203f7f1 --- /dev/null +++ b/src/camera_pins.h @@ -0,0 +1,321 @@ +// Camera pins definitions for different boards +#ifndef MICROPY_CAMERA_MODEL_PINS_H +#define MICROPY_CAMERA_MODEL_PINS_H + +#if defined(MICROPY_CAMERA_MODEL_WROVER_KIT) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 21 +#define MICROPY_CAMERA_PIN_SIOD 26 +#define MICROPY_CAMERA_PIN_SIOC 27 + +#define MICROPY_CAMERA_PIN_D7 35 +#define MICROPY_CAMERA_PIN_D6 34 +#define MICROPY_CAMERA_PIN_D5 39 +#define MICROPY_CAMERA_PIN_D4 36 +#define MICROPY_CAMERA_PIN_D3 19 +#define MICROPY_CAMERA_PIN_D2 18 +#define MICROPY_CAMERA_PIN_D1 5 +#define MICROPY_CAMERA_PIN_D0 4 +#define MICROPY_CAMERA_PIN_VSYNC 25 +#define MICROPY_CAMERA_PIN_HREF 23 +#define MICROPY_CAMERA_PIN_PCLK 22 + +#elif defined(MICROPY_CAMERA_MODEL_ESP_EYE) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 4 +#define MICROPY_CAMERA_PIN_SIOD 18 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 36 +#define MICROPY_CAMERA_PIN_D6 37 +#define MICROPY_CAMERA_PIN_D5 38 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 35 +#define MICROPY_CAMERA_PIN_D2 14 +#define MICROPY_CAMERA_PIN_D1 13 +#define MICROPY_CAMERA_PIN_D0 34 +#define MICROPY_CAMERA_PIN_VSYNC 5 +#define MICROPY_CAMERA_PIN_HREF 27 +#define MICROPY_CAMERA_PIN_PCLK 25 + +#elif defined(MICROPY_CAMERA_MODEL_M5STACK_PSRAM) || defined(MICROPY_CAMERA_MODEL_M5STACK_UNITCAM) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET 15 +#define MICROPY_CAMERA_PIN_XCLK 27 +#define MICROPY_CAMERA_PIN_SIOD 25 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 19 +#define MICROPY_CAMERA_PIN_D6 36 +#define MICROPY_CAMERA_PIN_D5 18 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 5 +#define MICROPY_CAMERA_PIN_D2 34 +#define MICROPY_CAMERA_PIN_D1 35 +#define MICROPY_CAMERA_PIN_D0 32 +#define MICROPY_CAMERA_PIN_VSYNC 22 +#define MICROPY_CAMERA_PIN_HREF 26 +#define MICROPY_CAMERA_PIN_PCLK 21 + +#elif defined(MICROPY_CAMERA_MODEL_M5STACK_V2_PSRAM) || defined(MICROPY_CAMERA_MODEL_M5STACK_WIDE) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET 15 +#define MICROPY_CAMERA_PIN_XCLK 27 +#define MICROPY_CAMERA_PIN_SIOD 22 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 19 +#define MICROPY_CAMERA_PIN_D6 36 +#define MICROPY_CAMERA_PIN_D5 18 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 5 +#define MICROPY_CAMERA_PIN_D2 34 +#define MICROPY_CAMERA_PIN_D1 35 +#define MICROPY_CAMERA_PIN_D0 32 +#define MICROPY_CAMERA_PIN_VSYNC 25 +#define MICROPY_CAMERA_PIN_HREF 26 +#define MICROPY_CAMERA_PIN_PCLK 21 + +#elif defined(MICROPY_CAMERA_MODEL_M5STACK_ESP32CAM) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET 15 +#define MICROPY_CAMERA_PIN_XCLK 27 +#define MICROPY_CAMERA_PIN_SIOD 25 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 19 +#define MICROPY_CAMERA_PIN_D6 36 +#define MICROPY_CAMERA_PIN_D5 18 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 5 +#define MICROPY_CAMERA_PIN_D2 34 +#define MICROPY_CAMERA_PIN_D1 35 +#define MICROPY_CAMERA_PIN_D0 17 +#define MICROPY_CAMERA_PIN_VSYNC 22 +#define MICROPY_CAMERA_PIN_HREF 26 +#define MICROPY_CAMERA_PIN_PCLK 21 + +#elif defined(MICROPY_CAMERA_MODEL_M5STACK_CAMS3_UNIT) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET 21 +#define MICROPY_CAMERA_PIN_XCLK 11 +#define MICROPY_CAMERA_PIN_SIOD 17 +#define MICROPY_CAMERA_PIN_SIOC 41 + +#define MICROPY_CAMERA_PIN_D7 13 +#define MICROPY_CAMERA_PIN_D6 4 +#define MICROPY_CAMERA_PIN_D5 10 +#define MICROPY_CAMERA_PIN_D4 5 +#define MICROPY_CAMERA_PIN_D3 7 +#define MICROPY_CAMERA_PIN_D2 16 +#define MICROPY_CAMERA_PIN_D1 15 +#define MICROPY_CAMERA_PIN_D0 6 +#define MICROPY_CAMERA_PIN_VSYNC 42 +#define MICROPY_CAMERA_PIN_HREF 18 +#define MICROPY_CAMERA_PIN_PCLK 12 + +#elif defined(MICROPY_CAMERA_MODEL_AI_THINKER) +#define MICROPY_CAMERA_PIN_PWDN 32 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 0 +#define MICROPY_CAMERA_PIN_SIOD 26 +#define MICROPY_CAMERA_PIN_SIOC 27 + +#define MICROPY_CAMERA_PIN_D7 35 +#define MICROPY_CAMERA_PIN_D6 34 +#define MICROPY_CAMERA_PIN_D5 39 +#define MICROPY_CAMERA_PIN_D4 36 +#define MICROPY_CAMERA_PIN_D3 21 +#define MICROPY_CAMERA_PIN_D2 19 +#define MICROPY_CAMERA_PIN_D1 18 +#define MICROPY_CAMERA_PIN_D0 5 +#define MICROPY_CAMERA_PIN_VSYNC 25 +#define MICROPY_CAMERA_PIN_HREF 23 +#define MICROPY_CAMERA_PIN_PCLK 22 + +#elif defined(MICROPY_CAMERA_MODEL_XIAO_ESP32S3) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 10 +#define MICROPY_CAMERA_PIN_SIOD 40 +#define MICROPY_CAMERA_PIN_SIOC 39 + +#define MICROPY_CAMERA_PIN_D7 48 +#define MICROPY_CAMERA_PIN_D6 11 +#define MICROPY_CAMERA_PIN_D5 12 +#define MICROPY_CAMERA_PIN_D4 14 +#define MICROPY_CAMERA_PIN_D3 16 +#define MICROPY_CAMERA_PIN_D2 18 +#define MICROPY_CAMERA_PIN_D1 17 +#define MICROPY_CAMERA_PIN_D0 15 +#define MICROPY_CAMERA_PIN_VSYNC 38 +#define MICROPY_CAMERA_PIN_HREF 47 +#define MICROPY_CAMERA_PIN_PCLK 13 + +#elif defined(MICROPY_CAMERA_MODEL_ESP32_MP_CAMERA_BOARD) +// The 18 pin header on the board has Y5 and Y3 swapped +#define ESP32_MP_CAMERA_BOARD_HEADER 0 +#define MICROPY_CAMERA_PIN_PWDN 32 +#define MICROPY_CAMERA_PIN_RESET 33 +#define MICROPY_CAMERA_PIN_XCLK 4 +#define MICROPY_CAMERA_PIN_SIOD 18 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 36 +#define MICROPY_CAMERA_PIN_D6 19 +#define MICROPY_CAMERA_PIN_D5 21 +#define MICROPY_CAMERA_PIN_D4 39 +#if ESP32_MP_CAMERA_BOARD_HEADER +#define MICROPY_CAMERA_PIN_D3 13 +#else +#define MICROPY_CAMERA_PIN_D3 35 +#endif +#define MICROPY_CAMERA_PIN_D2 14 +#if ESP32_MP_CAMERA_BOARD_HEADER +#define MICROPY_CAMERA_PIN_D1 35 +#else +#define MICROPY_CAMERA_PIN_D1 13 +#endif +#define MICROPY_CAMERA_PIN_D0 34 +#define MICROPY_CAMERA_PIN_VSYNC 5 +#define MICROPY_CAMERA_PIN_HREF 27 +#define MICROPY_CAMERA_PIN_PCLK 25 + +#elif defined(MICROPY_CAMERA_MODEL_ESP32S3_CAM_LCD) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 40 +#define MICROPY_CAMERA_PIN_SIOD 17 +#define MICROPY_CAMERA_PIN_SIOC 18 + +#define MICROPY_CAMERA_PIN_D7 39 +#define MICROPY_CAMERA_PIN_D6 41 +#define MICROPY_CAMERA_PIN_D5 42 +#define MICROPY_CAMERA_PIN_D4 12 +#define MICROPY_CAMERA_PIN_D3 3 +#define MICROPY_CAMERA_PIN_D2 14 +#define MICROPY_CAMERA_PIN_D1 47 +#define MICROPY_CAMERA_PIN_D0 13 +#define MICROPY_CAMERA_PIN_VSYNC 21 +#define MICROPY_CAMERA_PIN_HREF 38 +#define MICROPY_CAMERA_PIN_PCLK 11 + +#elif defined(MICROPY_CAMERA_MODEL_ESP32S3_EYE) || defined(MICROPY_CAMERA_MODEL_FREENOVE_ESP32S3_CAM) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 15 +#define MICROPY_CAMERA_PIN_SIOD 4 +#define MICROPY_CAMERA_PIN_SIOC 5 + +#define MICROPY_CAMERA_PIN_D0 11 +#define MICROPY_CAMERA_PIN_D1 9 +#define MICROPY_CAMERA_PIN_D2 8 +#define MICROPY_CAMERA_PIN_D3 10 +#define MICROPY_CAMERA_PIN_D4 12 +#define MICROPY_CAMERA_PIN_D5 18 +#define MICROPY_CAMERA_PIN_D6 17 +#define MICROPY_CAMERA_PIN_D7 16 +#define MICROPY_CAMERA_PIN_VSYNC 6 +#define MICROPY_CAMERA_PIN_HREF 7 +#define MICROPY_CAMERA_PIN_PCLK 13 + +#elif defined(MICROPY_CAMERA_MODEL_DFRobot_ESP32S3) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 45 +#define MICROPY_CAMERA_PIN_SIOD 1 +#define MICROPY_CAMERA_PIN_SIOC 2 + +#define MICROPY_CAMERA_PIN_D7 48 +#define MICROPY_CAMERA_PIN_D6 46 +#define MICROPY_CAMERA_PIN_D5 8 +#define MICROPY_CAMERA_PIN_D4 7 +#define MICROPY_CAMERA_PIN_D3 4 +#define MICROPY_CAMERA_PIN_D2 41 +#define MICROPY_CAMERA_PIN_D1 40 +#define MICROPY_CAMERA_PIN_D0 39 +#define MICROPY_CAMERA_PIN_VSYNC 6 +#define MICROPY_CAMERA_PIN_HREF 42 +#define MICROPY_CAMERA_PIN_PCLK 5 + +#elif defined(MICROPY_CAMERA_MODEL_TTGO_T_JOURNAL) +#define MICROPY_CAMERA_PIN_PWDN 0 +#define MICROPY_CAMERA_PIN_RESET 15 +#define MICROPY_CAMERA_PIN_XCLK 27 +#define MICROPY_CAMERA_PIN_SIOD 25 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 19 +#define MICROPY_CAMERA_PIN_D6 36 +#define MICROPY_CAMERA_PIN_D5 18 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 5 +#define MICROPY_CAMERA_PIN_D2 34 +#define MICROPY_CAMERA_PIN_D1 35 +#define MICROPY_CAMERA_PIN_D0 17 +#define MICROPY_CAMERA_PIN_VSYNC 22 +#define MICROPY_CAMERA_PIN_HREF 26 +#define MICROPY_CAMERA_PIN_PCLK 21 + +#elif defined(MICROPY_CAMERA_MODEL_TTGO_T_CAMERA_PLUS) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 4 +#define MICROPY_CAMERA_PIN_SIOD 18 +#define MICROPY_CAMERA_PIN_SIOC 23 + +#define MICROPY_CAMERA_PIN_D7 36 +#define MICROPY_CAMERA_PIN_D6 37 +#define MICROPY_CAMERA_PIN_D5 38 +#define MICROPY_CAMERA_PIN_D4 39 +#define MICROPY_CAMERA_PIN_D3 35 +#define MICROPY_CAMERA_PIN_D2 26 +#define MICROPY_CAMERA_PIN_D1 13 +#define MICROPY_CAMERA_PIN_D0 34 +#define MICROPY_CAMERA_PIN_VSYNC 5 +#define MICROPY_CAMERA_PIN_HREF 27 +#define MICROPY_CAMERA_PIN_PCLK 25 + +#elif defined(MICROPY_CAMERA_MODEL_NEW_ESPS3_RE1_0) +// aliexpress board with label RE:1.0, uses slow 8MB QSPI PSRAM, only 4MB addressable +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 10 +#define MICROPY_CAMERA_PIN_SIOD 21 +#define MICROPY_CAMERA_PIN_SIOC 14 + +#define MICROPY_CAMERA_PIN_D7 11 +#define MICROPY_CAMERA_PIN_D6 9 +#define MICROPY_CAMERA_PIN_D5 8 +#define MICROPY_CAMERA_PIN_D4 6 +#define MICROPY_CAMERA_PIN_D3 4 +#define MICROPY_CAMERA_PIN_D2 2 +#define MICROPY_CAMERA_PIN_D1 3 +#define MICROPY_CAMERA_PIN_D0 5 +#define MICROPY_CAMERA_PIN_VSYNC 13 +#define MICROPY_CAMERA_PIN_HREF 12 +#define MICROPY_CAMERA_PIN_PCLK 7 + +#elif defined(MICROPY_CAMERA_MODEL_XENOIONEX) +#define MICROPY_CAMERA_PIN_PWDN -1 +#define MICROPY_CAMERA_PIN_RESET -1 +#define MICROPY_CAMERA_PIN_XCLK 1 // Can use +#define MICROPY_CAMERA_PIN_SIOD 8 // Can use other i2c SDA pin, set this to -1 | If not using i2c set to 8 or 47 +#define MICROPY_CAMERA_PIN_SIOC 9 // Can use other i2c SCL pin, set this to -1 | If not using i2c set to 9 or 21 + +#define MICROPY_CAMERA_PIN_D7 3 //D7 +#define MICROPY_CAMERA_PIN_D6 18 //D6 +#define MICROPY_CAMERA_PIN_D5 42 //D5 +#define MICROPY_CAMERA_PIN_D4 16 //D4 +#define MICROPY_CAMERA_PIN_D3 41 //D3 +#define MICROPY_CAMERA_PIN_D2 17 //D2 +#define MICROPY_CAMERA_PIN_D1 40 //D1 +#define MICROPY_CAMERA_PIN_D0 39 //D0 +#define MICROPY_CAMERA_PIN_VSYNC 45 +#define MICROPY_CAMERA_PIN_HREF 38 +#define MICROPY_CAMERA_PIN_PCLK 2 + +#endif // definition of camera pins for different boards +#endif // MICROPY_CAMERA_MODEL_PINS_H \ No newline at end of file diff --git a/src/micropython.cmake b/src/micropython.cmake index 9527545..66fd308 100644 --- a/src/micropython.cmake +++ b/src/micropython.cmake @@ -1,15 +1,35 @@ +# `py.cmake` for `micropy_gather_target_properties` macro usage +include(${MICROPY_DIR}/py/py.cmake) + add_library(usermod_mp_camera INTERFACE) + target_sources(usermod_mp_camera INTERFACE ${CMAKE_CURRENT_LIST_DIR}/modcamera.c ${CMAKE_CURRENT_LIST_DIR}/modcamera_api.c ) -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 -) -target_compile_definitions(usermod_mp_camera INTERFACE) -target_link_libraries(usermod INTERFACE usermod_mp_camera) \ No newline at end of file + +if(EXISTS "${IDF_PATH}/components/esp32-camera") + 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 + ) +else() + target_include_directories(usermod_mp_camera INTERFACE + ${CMAKE_CURRENT_LIST_DIR}) +endif() + +if (MICROPY_CAMERA_MODEL) + target_compile_definitions(usermod_mp_camera INTERFACE MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1) +endif() + +if (MP_CAMERA_DRIVER_VERSION) + target_compile_definitions(usermod_mp_camera INTERFACE MP_CAMERA_DRIVER_VERSION=\"${MP_CAMERA_DRIVER_VERSION}\") +endif() + +target_link_libraries(usermod INTERFACE usermod_mp_camera) + +micropy_gather_target_properties(usermod_mp_camera) \ No newline at end of file diff --git a/src/micropython.mk b/src/micropython.mk index 749223e..7d5012b 100644 --- a/src/micropython.mk +++ b/src/micropython.mk @@ -1,3 +1,4 @@ CAMERA_MOD_DIR := $(USERMOD_DIR) SRC_USERMOD_C += $(addprefix $(CAMERA_MOD_DIR)/, modcamera_api.c) SRC_USERMOD_LIB_C += $(addprefix $(CAMERA_MOD_DIR)/, modcamera.c) +CFLAGS_USERMOD += -I$(CAMERA_MOD_DIR) \ No newline at end of file diff --git a/src/modcamera.c b/src/modcamera.c index bd9f731..63611e0 100644 --- a/src/modcamera.c +++ b/src/modcamera.c @@ -28,49 +28,81 @@ #include "modcamera.h" #include "esp_err.h" #include "esp_log.h" +#include "img_converters.h" +#include "mphalport.h" -#define TAG "ESP32_MPY_CAMERA" +#define TAG "MPY_CAMERA" #if !CONFIG_SPIRAM #error Camera only works on boards configured with spiram #endif -void raise_micropython_error_from_esp_err(esp_err_t err) { - switch (err) { - case ESP_OK: - return; - - case ESP_ERR_NO_MEM: - mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("Out of memory")); - break; +// Helper functions +static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) { + if (fromHigh == fromLow) { + mp_raise_ValueError(MP_ERROR_TEXT("fromLow und fromHigh shall not be equal")); + } + return (int)((int32_t)(value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow); +} - case ESP_ERR_INVALID_ARG: - mp_raise_ValueError(MP_ERROR_TEXT("Invalid argument")); - break; +static inline int get_mapped_jpeg_quality(int8_t quality) { + return map(quality, 0, 100, 63, 0); +} - case ESP_ERR_INVALID_STATE: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Invalid state")); - break; +static inline void check_init(mp_camera_obj_t *self) { + if (!self->initialized) { + mp_raise_OSError(ENOENT); + } +} - case ESP_ERR_NOT_FOUND: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Camera not found")); - break; +static void set_check_xclk_freq(mp_camera_obj_t *self, int32_t xclk_freq_hz) { + if ( xclk_freq_hz > 20000000) { + mp_raise_ValueError(MP_ERROR_TEXT("xclk frequency cannot be grather than 20MHz")); + } else { + self->camera_config.xclk_freq_hz = xclk_freq_hz; + } +} - case ESP_ERR_NOT_SUPPORTED: - mp_raise_NotImplementedError(MP_ERROR_TEXT("Operation/Function not supported/implemented")); - break; +static void set_check_fb_count(mp_camera_obj_t *self, mp_int_t fb_count) { + if (fb_count > 2) { + self->camera_config.fb_count = 2; + mp_warning(NULL, "Frame buffer size limited to 2"); + } else if (fb_count < 1) { + self->camera_config.fb_count = 1; + mp_warning(NULL, "Frame buffer size must be >0. Setting it to 1"); + } + else { + self->camera_config.fb_count = fb_count; + } +} - case ESP_ERR_TIMEOUT: - mp_raise_OSError(MP_ETIMEDOUT); - break; +static void set_check_grab_mode(mp_camera_obj_t *self, mp_camera_grabmode_t grab_mode) { + if (grab_mode != CAMERA_GRAB_WHEN_EMPTY && grab_mode != CAMERA_GRAB_LATEST) { + mp_raise_ValueError(MP_ERROR_TEXT("Invalid grab_mode")); + } else { + self->camera_config.grab_mode = grab_mode; + } +} - default: - mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Unknown error 0x%04x"), err); - // mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Unknown error")); - break; +static void set_check_pixel_format(mp_camera_obj_t *self, mp_camera_pixformat_t pixel_format) { + if ( pixel_format > PIXFORMAT_RGB555) { //Maximal enum value, but validation should be better since wrong pixelformat leads to reboot. + mp_raise_ValueError(MP_ERROR_TEXT("Invalid pixel_format")); + } else { + self->camera_config.pixel_format = pixel_format; } } +static bool init_camera(mp_camera_obj_t *self) { + // Correct the quality before it is passed to esp32 driver and then "undo" the correction in the camera_config + int8_t api_jpeg_quality = self->camera_config.jpeg_quality; + 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); + return true; +} + +// Camera HAL Funcitons void mp_camera_hal_construct( mp_camera_obj_t *self, int8_t data_pins[8], @@ -89,9 +121,6 @@ void mp_camera_hal_construct( int8_t fb_count, mp_camera_grabmode_t grab_mode) { // configure camera based on arguments - self->camera_config.pixel_format = pixel_format; - self->camera_config.frame_size = frame_size; - self->camera_config.jpeg_quality = jpeg_quality; //0-63 lower number means higher quality. TODO: Harmonization in API and Validation self->camera_config.pin_d0 = data_pins[0]; self->camera_config.pin_d1 = data_pins[1]; self->camera_config.pin_d2 = data_pins[2]; @@ -108,9 +137,14 @@ void mp_camera_hal_construct( self->camera_config.pin_xclk = external_clock_pin; self->camera_config.pin_sscb_sda = sccb_sda_pin; self->camera_config.pin_sscb_scl = sccb_scl_pin; - self->camera_config.xclk_freq_hz = xclk_freq_hz; - self->camera_config.fb_count = fb_count; //if more than one, i2s runs in continuous mode. TODO: Test with others than JPEG - self->camera_config.grab_mode = grab_mode; + + self->camera_config.frame_size = frame_size; + self->camera_config.jpeg_quality = jpeg_quality; //save value in here, but will be corrected (with map) before passing it to the esp32-driver + + set_check_pixel_format(self, pixel_format); + set_check_xclk_freq(self, xclk_freq_hz); + set_check_fb_count(self, fb_count); + set_check_grab_mode(self, grab_mode); // defaul parameters self->camera_config.fb_location = CAMERA_FB_IN_PSRAM; @@ -125,119 +159,146 @@ void mp_camera_hal_init(mp_camera_obj_t *self) { if (self->initialized) { return; } + #ifndef CONFIG_IDF_TARGET_ESP32S3 + if (self->camera_config.fb_count > 1 && self->camera_config.pixel_format != PIXFORMAT_JPEG) { + mp_warning(NULL, "It is recomended to use a frame buffer size of 1 for non-JPEG pixel format"); + } + #endif ESP_LOGI(TAG, "Initializing camera"); - camera_config_t temp_config = self->camera_config; - temp_config.frame_size = FRAMESIZE_QVGA; //use values supported by all cameras - temp_config.pixel_format = PIXFORMAT_RGB565; //use values supported by all cameras - esp_err_t err = esp_camera_init(&temp_config); - if (err != ESP_OK) { - self->initialized = false; - raise_micropython_error_from_esp_err(err); - } else { - self->initialized = true; - } - mp_camera_hal_reconfigure(self, self->camera_config.frame_size, self->camera_config.pixel_format, - self->camera_config.grab_mode, self->camera_config.fb_count); + self->initialized = init_camera(self); ESP_LOGI(TAG, "Camera initialized successfully"); } void mp_camera_hal_deinit(mp_camera_obj_t *self) { if (self->initialized) { if (self->captured_buffer) { - esp_camera_fb_return(self->captured_buffer); + esp_camera_return_all(); self->captured_buffer = NULL; } esp_err_t err = esp_camera_deinit(); - raise_micropython_error_from_esp_err(err); + check_esp_err_(err); self->initialized = false; ESP_LOGI(TAG, "Camera deinitialized"); } } void mp_camera_hal_reconfigure(mp_camera_obj_t *self, mp_camera_framesize_t frame_size, mp_camera_pixformat_t pixel_format, mp_camera_grabmode_t grab_mode, mp_int_t fb_count) { - if (self->initialized) { - ESP_LOGI(TAG, "Reconfiguring camera"); - sensor_t *sensor = esp_camera_sensor_get(); - camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); - - if (PIXFORMAT_JPEG == self->camera_config.pixel_format && (!sensor_info->support_jpeg)) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("Sensor does not support JPEG")); - } - - if (frame_size > sensor_info->max_size) { - mp_warning(NULL, "Frame size will be scaled down to maximal frame size supported by the camera sensor"); - self->camera_config.frame_size = sensor_info->max_size; - } else { - self->camera_config.frame_size = frame_size; - } - - if ( pixel_format > PIXFORMAT_RGB555) { //Maximal enum value, but validation should be better since wrong pixelformat leads to reboot. - mp_raise_ValueError(MP_ERROR_TEXT("Invalid pixel_format")); - } else { - self->camera_config.pixel_format = pixel_format; - } - - if (grab_mode != CAMERA_GRAB_WHEN_EMPTY && grab_mode != CAMERA_GRAB_LATEST) { - mp_raise_ValueError(MP_ERROR_TEXT("Invalid grab_mode")); - } else { - self->camera_config.grab_mode = grab_mode; - } - - if (fb_count > 2) { - self->camera_config.fb_count = 2; - mp_warning(NULL, "Frame buffer size limited to 2"); - } else { - self->camera_config.fb_count = fb_count; - } - - raise_micropython_error_from_esp_err(esp_camera_deinit()); + check_init(self); + ESP_LOGI(TAG, "Reconfiguring camera with frame size: %d, pixel format: %d, grab mode: %d, fb count: %d", (int)frame_size, (int)pixel_format, (int)grab_mode, (int)fb_count); + + sensor_t *sensor = esp_camera_sensor_get(); + camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); + if (frame_size > sensor_info->max_size) { + mp_warning(NULL, "Frame size will be scaled down to maximal frame size supported by the camera sensor"); + self->camera_config.frame_size = sensor_info->max_size; + } else { + self->camera_config.frame_size = frame_size; + } - // sensor->set_pixformat(sensor, self->camera_config.pixel_format); //seems to be needed because of some bug? - // sensor->set_framesize(sensor, self->camera_config.frame_size); //seems to be needed because of some bug? + set_check_pixel_format(self, pixel_format); + set_check_grab_mode(self, grab_mode); + set_check_fb_count(self, fb_count); - esp_err_t err = esp_camera_init(&self->camera_config); - if (err != ESP_OK) { - self->initialized = false; - raise_micropython_error_from_esp_err(err); - } else { - ESP_LOGI(TAG, "Camera reconfigured successfully"); - } - } + check_esp_err_(esp_camera_deinit()); + self->initialized = false; + self->initialized = init_camera(self); + ESP_LOGI(TAG, "Camera reconfigured successfully"); } -mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int timeout_ms) { - // Timeout not used at the moment - if (!self->initialized) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to capture image: Camera not initialized")); - } +mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format) { + check_init(self); if (self->captured_buffer) { esp_camera_fb_return(self->captured_buffer); self->captured_buffer = NULL; } + + static size_t out_len = 0; + static uint8_t *out_buf = NULL; + if (out_len > 0 || out_buf) { + free(out_buf); + out_len = 0; + out_buf = NULL; + } + ESP_LOGI(TAG, "Capturing image"); self->captured_buffer = esp_camera_fb_get(); - if (self->captured_buffer) { - if (self->camera_config.pixel_format == PIXFORMAT_JPEG) { - ESP_LOGI(TAG, "Captured image in JPEG format"); - return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf); - } else { - ESP_LOGI(TAG, "Captured image in raw format"); - return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf); - // TODO: Stub at the moment in order to return raw data, but it sould be implemented to return a Bitmap, see following circuitpython example: - // - // int width = common_hal_espcamera_camera_get_width(self); - // int height = common_hal_espcamera_camera_get_height(self); - // displayio_bitmap_t *bitmap = m_new_obj(displayio_bitmap_t); - // bitmap->base.type = &displayio_bitmap_type; - // common_hal_displayio_bitmap_construct_from_buffer(bitmap, width, height, (format == PIXFORMAT_RGB565) ? 16 : 8, (uint32_t *)(void *)result->buf, true); - // return bitmap; + if (!self->captured_buffer) { + 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) { + ESP_LOGI(TAG, "Converting image to pixel format: %d", out_format); + switch ((mp_camera_pixformat_t)out_format) { + case PIXFORMAT_JPEG: + if (frame2jpg(self->captured_buffer, self->camera_config.jpeg_quality, &out_buf, &out_len)) { + esp_camera_fb_return(self->captured_buffer); + mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf); + return result; + } else { + return mp_const_none; + } + + case PIXFORMAT_RGB888: + out_len = self->captured_buffer->width * self->captured_buffer->height * 3; + out_buf = (uint8_t *)malloc(out_len); + if (!out_buf) { + ESP_LOGE(TAG, "out_buf malloc failed"); + return mp_const_none; + } + if (fmt2rgb888(self->captured_buffer->buf, self->captured_buffer->len, self->captured_buffer->format, out_buf)) { + esp_camera_fb_return(self->captured_buffer); + mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf); + return result; + } else { + return mp_const_none; + } + + case PIXFORMAT_RGB565: + out_len = self->captured_buffer->width * self->captured_buffer->height * 2; + out_buf = (uint8_t *)malloc(out_len); + if (!out_buf) { + ESP_LOGE(TAG, "out_buf malloc failed"); + return mp_const_none; + } + if(self->camera_config.pixel_format == PIXFORMAT_JPEG){ + if (jpg2rgb565(self->captured_buffer->buf, self->captured_buffer->len, out_buf, JPG_SCALE_NONE)) { + esp_camera_fb_return(self->captured_buffer); + mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf); + return result; + } else { + return mp_const_none; + } + } else { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Can only convert JPEG to RGB565")); + return mp_const_none; + } + + default: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported pixel format for conversion")); + return mp_const_none; + } + } + + 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_camera_fb_return(self->captured_buffer); - self->captured_buffer = NULL; - return mp_const_none; + ESP_LOGI(TAG, "Returning image as bitmap"); + if (frame2bmp(self->captured_buffer, &out_buf, &out_len)) { + esp_camera_fb_return(self->captured_buffer); + mp_obj_t result = mp_obj_new_memoryview('b', out_len, out_buf); + return result; + } else { + free(out_buf); + out_buf = NULL; + out_len = 0; + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to convert image to BMP")); + return mp_const_none; + } } -} +} // mp_camera_hal_capture bool mp_camera_hal_initialized(mp_camera_obj_t *self){ return self->initialized; @@ -248,6 +309,7 @@ const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[] = { { MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422) }, { 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) }, }; const mp_rom_map_elem_t mp_camera_hal_frame_size_table[] = { @@ -290,14 +352,6 @@ const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { { MP_ROM_QSTR(MP_QSTR_128X), MP_ROM_INT(GAINCEILING_128X) }, }; -// Supporting functions -static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) { - if (fromHigh == fromLow) { - mp_raise_ValueError(MP_ERROR_TEXT("fromLow und fromHigh shall not be equal")); - } - return (int)((int32_t)(value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow); -} - //TODO: Makros with convertion function, since the API will use standarized values. // Helper functions to get and set camera and sensor information #define SENSOR_STATUS_GETSET_IN_RANGE(type, name, status_field_name, setter_function_name, min_val, max_val) \ @@ -308,30 +362,23 @@ static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) { // For subsequent modules using this as example, you will probably only need the makros below. #define SENSOR_GETSET(type, name, field_name, setter_function_name) \ - SENSOR_GET(type, name, field_name, setter_function_name) \ + SENSOR_GET(type, name, field_name) \ SENSOR_SET(type, name, setter_function_name) #define SENSOR_GETSET_IN_RANGE(type, name, field_name, setter_function_name, min_val, max_val) \ - SENSOR_GET(type, name, field_name, setter_function_name) \ + SENSOR_GET(type, name, field_name) \ SENSOR_SET_IN_RANGE(type, name, setter_function_name, min_val, max_val) -#define SENSOR_GET(type, name, status_field_name, getter_function_name) \ +#define SENSOR_GET(type, name, status_field_name) \ type mp_camera_hal_get_##name(mp_camera_obj_t * self) { \ - if (!self->initialized) { \ - mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized")); \ - } \ + check_init(self); \ sensor_t *sensor = esp_camera_sensor_get(); \ - if (!sensor->getter_function_name) { \ - mp_raise_ValueError(MP_ERROR_TEXT("No attribute " #name)); \ - } \ return sensor->status_field_name; \ } #define SENSOR_SET(type, name, setter_function_name) \ void mp_camera_hal_set_##name(mp_camera_obj_t * self, type value) { \ - if (!self->initialized) { \ - mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized")); \ - } \ + check_init(self); \ sensor_t *sensor = esp_camera_sensor_get(); \ if (!sensor->setter_function_name) { \ mp_raise_ValueError(MP_ERROR_TEXT("No attribute " #name)); \ @@ -344,9 +391,7 @@ static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) { #define SENSOR_SET_IN_RANGE(type, name, setter_function_name, min_val, max_val) \ void mp_camera_hal_set_##name(mp_camera_obj_t * self, type value) { \ sensor_t *sensor = esp_camera_sensor_get(); \ - if (!self->initialized) { \ - mp_raise_ValueError(MP_ERROR_TEXT("Camera not initialized")); \ - } \ + check_init(self); \ if (value < min_val || value > max_val) { \ mp_raise_ValueError(MP_ERROR_TEXT(#name " value must be between " #min_val " and " #max_val)); \ } \ @@ -358,13 +403,13 @@ static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) { } \ } +SENSOR_GET(framesize_t, frame_size, status.framesize); SENSOR_STATUS_GETSET_IN_RANGE(int, contrast, contrast, set_contrast, -2, 2); SENSOR_STATUS_GETSET_IN_RANGE(int, brightness, brightness, set_brightness, -2, 2); SENSOR_STATUS_GETSET_IN_RANGE(int, saturation, saturation, set_saturation, -2, 2); SENSOR_STATUS_GETSET_IN_RANGE(int, sharpness, sharpness, set_sharpness, -2, 2); SENSOR_STATUS_GETSET(int, denoise, denoise, set_denoise); SENSOR_STATUS_GETSET(mp_camera_gainceiling_t, gainceiling, gainceiling, set_gainceiling); -SENSOR_STATUS_GETSET(int, quality, quality, set_quality); //in_Range not needed since driver limits value SENSOR_STATUS_GETSET(bool, colorbar, colorbar, set_colorbar); SENSOR_STATUS_GETSET(bool, whitebal, awb, set_whitebal); SENSOR_STATUS_GETSET(bool, gain_ctrl, agc, set_gain_ctrl); @@ -384,12 +429,45 @@ SENSOR_STATUS_GETSET(bool, wpc, wpc, set_wpc); SENSOR_STATUS_GETSET(bool, raw_gma, raw_gma, set_raw_gma); SENSOR_STATUS_GETSET(bool, lenc, lenc, set_lenc); -mp_camera_pixformat_t mp_camera_hal_get_pixel_format(mp_camera_obj_t *self) { - return self->camera_config.pixel_format; +void mp_camera_hal_set_frame_size(mp_camera_obj_t * self, framesize_t value) { + check_init(self); + sensor_t *sensor = esp_camera_sensor_get(); + if (!sensor->set_framesize) { + mp_raise_ValueError(MP_ERROR_TEXT("No attribute frame_size")); + } + + if (self->captured_buffer) { + esp_camera_return_all(); + self->captured_buffer = NULL; + } + + if (sensor->set_framesize(sensor, value) < 0) { + mp_raise_ValueError(MP_ERROR_TEXT("Invalid setting for frame_size")); + } else { + self->camera_config.frame_size = value; + } } -mp_camera_framesize_t mp_camera_hal_get_frame_size(mp_camera_obj_t *self) { - return self->camera_config.frame_size; +int mp_camera_hal_get_quality(mp_camera_obj_t * self) { + check_init(self); + return self->camera_config.jpeg_quality; +} + +void mp_camera_hal_set_quality(mp_camera_obj_t * self, int value) { + check_init(self); + sensor_t *sensor = esp_camera_sensor_get(); + if (!sensor->set_quality) { + mp_raise_ValueError(MP_ERROR_TEXT("No attribute quality")); + } + if (sensor->set_quality(sensor, get_mapped_jpeg_quality(value)) < 0) { + mp_raise_ValueError(MP_ERROR_TEXT("Invalid setting for quality")); + } else { + self->camera_config.jpeg_quality = value; + } +} + +mp_camera_pixformat_t mp_camera_hal_get_pixel_format(mp_camera_obj_t *self) { + return self->camera_config.pixel_format; } camera_grab_mode_t mp_camera_hal_get_grab_mode(mp_camera_obj_t *self) { @@ -401,36 +479,42 @@ int mp_camera_hal_get_fb_count(mp_camera_obj_t *self) { } const char *mp_camera_hal_get_sensor_name(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); return sensor_info->name; } bool mp_camera_hal_get_supports_jpeg(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); return sensor_info->support_jpeg; } mp_camera_framesize_t mp_camera_hal_get_max_frame_size(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); return sensor_info->max_size; } int mp_camera_hal_get_address(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); return sensor_info->sccb_addr; } int mp_camera_hal_get_pixel_width(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); framesize_t framesize = sensor->status.framesize; return resolution[framesize].width; } int mp_camera_hal_get_pixel_height(mp_camera_obj_t *self) { + check_init(self); sensor_t *sensor = esp_camera_sensor_get(); framesize_t framesize = sensor->status.framesize; return resolution[framesize].height; diff --git a/src/modcamera.h b/src/modcamera.h index 07b9890..e7d557b 100644 --- a/src/modcamera.h +++ b/src/modcamera.h @@ -42,10 +42,11 @@ #include "esp_camera.h" #include "sensor.h" +#include "camera_pins.h" -#if defined(MICROPY_CAMERA_PIN_SIOD) && defined(MICROPY_CAMERA_PIN_SIOC) && defined(MICROPY_CAMERA_PIN_D0) && defined(MICROPY_CAMERA_PIN_D1) && defined(MICROPY_CAMERA_PIN_D2) && \ -defined(MICROPY_CAMERA_PIN_D3) && defined(MICROPY_CAMERA_PIN_D4) && defined(MICROPY_CAMERA_PIN_D5) && defined(MICROPY_CAMERA_PIN_D6) && defined(MICROPY_CAMERA_PIN_D7) && \ -defined(MICROPY_CAMERA_PIN_PCLK) && defined(MICROPY_CAMERA_PIN_VSYNC) && defined(MICROPY_CAMERA_PIN_HREF) && defined(MICROPY_CAMERA_PIN_XCLK) +#if defined (MICROPY_CAMERA_PIN_SIOD) && defined (MICROPY_CAMERA_PIN_SIOC) && defined (MICROPY_CAMERA_PIN_D0) && defined (MICROPY_CAMERA_PIN_D1) && defined (MICROPY_CAMERA_PIN_D2) && \ +defined (MICROPY_CAMERA_PIN_D3) && defined (MICROPY_CAMERA_PIN_D4) && defined (MICROPY_CAMERA_PIN_D5) && defined (MICROPY_CAMERA_PIN_D6) && defined (MICROPY_CAMERA_PIN_D7) && \ +defined (MICROPY_CAMERA_PIN_PCLK) && defined (MICROPY_CAMERA_PIN_VSYNC) && defined (MICROPY_CAMERA_PIN_HREF) && defined (MICROPY_CAMERA_PIN_XCLK) #define MICROPY_CAMERA_ALL_REQ_PINS_DEFINED (1) #endif @@ -58,36 +59,31 @@ defined(MICROPY_CAMERA_PIN_PCLK) && defined(MICROPY_CAMERA_PIN_VSYNC) && defined #endif #ifndef MICROPY_CAMERA_XCLK_FREQ -#define MICROPY_CAMERA_XCLK_FREQ (10) +#define MICROPY_CAMERA_XCLK_FREQ (20) #endif -#ifndef MICROPY_CAMERA_DEFAULT_FRAME_SIZE -#define MICROPY_CAMERA_DEFAULT_FRAME_SIZE FRAMESIZE_QQVGA +#if !defined (MICROPY_CAMERA_GRAB_MODE) && defined (CONFIG_IDF_TARGET_ESP32S3) +#define MICROPY_CAMERA_GRAB_MODE CAMERA_GRAB_LATEST +#elif !defined(MICROPY_CAMERA_GRAB_MODE) +#define MICROPY_CAMERA_GRAB_MODE CAMERA_GRAB_WHEN_EMPTY #endif -#ifndef MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT -#define MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT PIXFORMAT_RGB565 +#if !defined (MICROPY_CAMERA_FB_COUNT) && defined (CONFIG_IDF_TARGET_ESP32S3) +#define MICROPY_CAMERA_FB_COUNT (2) +#elif !defined(MICROPY_CAMERA_FB_COUNT) +#define MICROPY_CAMERA_FB_COUNT (1) #endif -#ifndef MICROPY_CAMERA_GRAB_MODE -#define MICROPY_CAMERA_GRAB_MODE CAMERA_GRAB_WHEN_EMPTY +#ifndef MICROPY_CAMERA_DEFAULT_FRAME_SIZE +#define MICROPY_CAMERA_DEFAULT_FRAME_SIZE FRAMESIZE_QQVGA #endif -#ifndef MICROPY_CAMERA_FB_COUNT -#define MICROPY_CAMERA_FB_COUNT (1) +#ifndef MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT +#define MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT PIXFORMAT_RGB565 #endif #ifndef MICROPY_CAMERA_JPEG_QUALITY -#define MICROPY_CAMERA_JPEG_QUALITY (15) -#endif - -//Supported Camera sensors -#ifndef CONFIG_OV2640_SUPPORT -#define CONFIG_OV2640_SUPPORT 1 -#endif - -#if !defined(CONFIG_OV5640_SUPPORT) && defined(CONFIG_IDF_TARGET_ESP32S3) -#define CONFIG_OV5640_SUPPORT 1 +#define MICROPY_CAMERA_JPEG_QUALITY (85) #endif typedef pixformat_t hal_camera_pixformat_t; @@ -101,6 +97,7 @@ typedef struct hal_camera_obj { camera_config_t camera_config; bool initialized; camera_fb_t *captured_buffer; + bool bmp_out; } hal_camera_obj_t; #endif // CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 @@ -189,16 +186,16 @@ 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 timeout_ms Timeout in milliseconds. + * @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, int timeout_ms); +extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self, int8_t out_format); /** * @brief Table mapping pixel formats API to their corresponding values at HAL. * @details Needs to be defined in the port-specific implementation. */ -extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[4]; +extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[5]; /** * @brief Table mapping frame sizes API to their corresponding values at HAL. @@ -262,7 +259,7 @@ DECLARE_CAMERA_HAL_GETSET(bool, wpc) DECLARE_CAMERA_HAL_GET(int, address) DECLARE_CAMERA_HAL_GET(int, fb_count) -DECLARE_CAMERA_HAL_GET(mp_camera_framesize_t, frame_size) +DECLARE_CAMERA_HAL_GETSET(mp_camera_framesize_t, frame_size) DECLARE_CAMERA_HAL_GET(camera_grab_mode_t, grab_mode) DECLARE_CAMERA_HAL_GET(mp_camera_framesize_t, max_frame_size) DECLARE_CAMERA_HAL_GET(mp_camera_pixformat_t, pixel_format) diff --git a/src/modcamera_api.c b/src/modcamera_api.c index 79013db..71cc2b0 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, 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, ARG_bmp_out, 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 } }, @@ -67,6 +67,8 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz { MP_QSTR_jpeg_quality, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_JPEG_QUALITY } }, { 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)]; @@ -119,32 +121,36 @@ static mp_obj_t mp_camera_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_camera_pixformat_t pixel_format = args[ARG_pixel_format].u_int; mp_camera_framesize_t frame_size = args[ARG_frame_size].u_int; int8_t jpeg_quality = args[ARG_jpeg_quality].u_int; + if ((jpeg_quality < 0) || (jpeg_quality > 100)) { + mp_raise_ValueError(MP_ERROR_TEXT("jpeg quality must be in range 0-100")); + } int8_t fb_count = args[ARG_fb_count].u_int; mp_camera_grabmode_t grab_mode = args[ARG_grab_mode].u_int; mp_camera_obj_t *self = mp_obj_malloc_with_finaliser(mp_camera_obj_t, &camera_type); self->base.type = &camera_type; + 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, 100) == mp_const_none){ + if (mp_camera_hal_capture(self, -1) == mp_const_none){ mp_camera_hal_deinit(self); - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to capture initial frame. \ - Run reconfigure method or construct a new object with appropriate configuration (e.g. FrameSize).")); - return MP_OBJ_FROM_PTR(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); + } 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]); - mp_float_t timeout = n_args < 2 ? MICROPY_FLOAT_CONST(0.25) : mp_obj_get_float(args[1]); - return mp_camera_hal_capture(self, timeout); + int8_t out_format = n_args < 2 ? -1 : mp_obj_get_int(args[1]); + return mp_camera_hal_capture(self, out_format); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(camera_capture_obj, 1, 2, camera_capture); @@ -174,7 +180,7 @@ static mp_obj_t camera_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_m args[ARG_grab_mode].u_obj != MP_ROM_NONE ? args[ARG_grab_mode].u_int : mp_camera_hal_get_grab_mode(self); - uint8_t fb_count = + mp_int_t fb_count = args[ARG_fb_count].u_obj != MP_ROM_NONE ? args[ARG_fb_count].u_int : mp_camera_hal_get_fb_count(self); @@ -198,14 +204,28 @@ 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; return mp_camera_deinit(args[0]); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_camera___exit___obj, 4, 4, mp_camera_obj___exit__); -// Camera propertiy functions -// Camera sensor propertiy functions +// Camera property functions +// Camera sensor property functions #define CREATE_GETTER(property, get_function) \ static mp_obj_t camera_get_##property(const mp_obj_t self_in) { \ mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); \ @@ -233,12 +253,14 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_camera___exit___obj, 4, 4, mp_came { MP_ROM_QSTR(MP_QSTR_get_##property), MP_ROM_PTR(&camera_get_##property##_obj) }, \ { MP_ROM_QSTR(MP_QSTR_set_##property), MP_ROM_PTR(&camera_set_##property##_obj) } -CREATE_GETTER(frame_size, mp_obj_new_int) -CREATE_GETTER(pixel_format, mp_obj_new_int) -CREATE_GETTER(grab_mode, mp_obj_new_int) -CREATE_GETTER(fb_count, mp_obj_new_int) -CREATE_GETTER(pixel_width, mp_obj_new_int) -CREATE_GETTER(pixel_height, mp_obj_new_int) +CREATE_GETSET_FUNCTIONS(frame_size, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); +CREATE_GETTER(pixel_format, mp_obj_new_int); +CREATE_GETTER(grab_mode, mp_obj_new_int); +CREATE_GETTER(fb_count, mp_obj_new_int); +CREATE_GETTER(pixel_width, mp_obj_new_int); +CREATE_GETTER(pixel_height, mp_obj_new_int); +CREATE_GETTER(max_frame_size, mp_obj_new_int); +CREATE_GETTER(sensor_name, mp_obj_new_str_from_cstr); CREATE_GETSET_FUNCTIONS(contrast, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); CREATE_GETSET_FUNCTIONS(brightness, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); CREATE_GETSET_FUNCTIONS(saturation, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); @@ -274,12 +296,14 @@ static const mp_rom_map_elem_t camera_camera_locals_table[] = { { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_camera_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mp_camera___exit___obj) }, - { MP_ROM_QSTR(MP_QSTR_get_framesize), MP_ROM_PTR(&camera_get_frame_size_obj) }, { MP_ROM_QSTR(MP_QSTR_get_pixel_format), MP_ROM_PTR(&camera_get_pixel_format_obj) }, { MP_ROM_QSTR(MP_QSTR_get_grab_mode), MP_ROM_PTR(&camera_get_grab_mode_obj) }, { MP_ROM_QSTR(MP_QSTR_get_fb_count), MP_ROM_PTR(&camera_get_fb_count_obj) }, { MP_ROM_QSTR(MP_QSTR_get_pixel_width), MP_ROM_PTR(&camera_get_pixel_width_obj) }, { MP_ROM_QSTR(MP_QSTR_get_pixel_height), MP_ROM_PTR(&camera_get_pixel_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_max_frame_size), MP_ROM_PTR(&camera_get_max_frame_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_sensor_name), MP_ROM_PTR(&camera_get_sensor_name_obj) }, + ADD_PROPERTY_TO_TABLE(frame_size), ADD_PROPERTY_TO_TABLE(contrast), ADD_PROPERTY_TO_TABLE(brightness), ADD_PROPERTY_TO_TABLE(saturation), @@ -305,6 +329,7 @@ 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); @@ -339,6 +364,13 @@ MP_CREATE_CONST_TYPE(GainCeiling, mp_camera_gainceiling); static MP_DEFINE_CONST_DICT(mp_camera_grab_mode_locals_dict,mp_camera_hal_grab_mode_table); MP_CREATE_CONST_TYPE(GrabMode, mp_camera_grab_mode); +#ifdef MP_CAMERA_DRIVER_VERSION + static mp_obj_t mp_camera_driver_version(void) { + return mp_obj_new_str(MP_CAMERA_DRIVER_VERSION, strlen(MP_CAMERA_DRIVER_VERSION)); + } + static MP_DEFINE_CONST_FUN_OBJ_0(mp_camera_driver_version_obj, mp_camera_driver_version); +#endif + static const mp_rom_map_elem_t camera_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_camera) }, { MP_ROM_QSTR(MP_QSTR_Camera), MP_ROM_PTR(&camera_type) }, @@ -346,6 +378,9 @@ static const mp_rom_map_elem_t camera_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_FrameSize), MP_ROM_PTR(&mp_camera_frame_size_type) }, { MP_ROM_QSTR(MP_QSTR_GainCeiling), MP_ROM_PTR(&mp_camera_gainceiling_type) }, { MP_ROM_QSTR(MP_QSTR_GrabMode), MP_ROM_PTR(&mp_camera_grab_mode_type) }, + #ifdef MP_CAMERA_DRIVER_VERSION + { MP_ROM_QSTR(MP_QSTR_Version), MP_ROM_PTR(&mp_camera_driver_version_obj) }, + #endif }; static MP_DEFINE_CONST_DICT(camera_module_globals, camera_module_globals_table); diff --git a/tests/esp32_test.py b/tests/esp32_test.py index 16b65c6..e29c79f 100644 --- a/tests/esp32_test.py +++ b/tests/esp32_test.py @@ -1,27 +1,82 @@ +import time from camera import Camera, FrameSize, PixelFormat def test_property_get_frame_size(): - camera = Camera() - Frame_Size = FrameSize.VGA - camera.reconfigure(frame_size=Frame_Size.VGA) - assert camera.get_frame_size == Frame_Size - assert camera.get_pixel_width == 640 - assert camera.get_pixel_height == 480 + with Camera() as cam: + print("Test frame size") + Frame_Size = FrameSize.VGA + cam.reconfigure(frame_size=Frame_Size) + assert cam.get_frame_size() == Frame_Size + assert cam.get_pixel_width() == 640 + assert cam.get_pixel_height() == 480 def test_property_get_pixel_format(): - camera = Camera() - Pixel_Format = PixelFormat.RGB565 - camera.reconfigure(pixel_format=PixelFormat.RGB) - assert camera.get_pixel_format == Pixel_Format + with Camera() as cam: + print("Test pixel format") + for Pixel_Format_Name in dir(PixelFormat): + Pixel_Format = getattr(PixelFormat, Pixel_Format_Name) + try: + if Pixel_Format_Name.startswith("_") or Pixel_Format_Name.startswith("RGB888"): + continue + cam.reconfigure(pixel_format=Pixel_Format) + assert cam.get_pixel_format() == Pixel_Format + except Exception: + print("\tFailed test for pixel format", Pixel_Format) +def test_must_be_initialized(): + with Camera(init=False) as cam: + print(f"Testing capture without initalization") + try: + cam.capture() + assert False, "Capture should have failed" + except Exception as e: + if e == "Camera not initialized": + assert False, "Capture should have failed" + else: + assert True + def test_camera_properties(): - camera = Camera() - for name in dir(camera): - if name.startswith('get_'): - prop_name = name[4:] - set_method_name = f'set_{prop_name}' - if hasattr(camera, set_method_name): - set_method = getattr(camera, set_method_name) - get_method = getattr(camera, name) - set_method(1) - assert get_method() == 1, f"Failed for property {prop_name}" \ No newline at end of file + with Camera() as cam: + print(f"Testing get/set methods") + for name in dir(cam): + if name.startswith('get_'): + prop_name = name[4:] + set_method_name = f'set_{prop_name}' + if hasattr(cam, set_method_name): + set_method = getattr(cam, set_method_name) + get_method = getattr(cam, name) + try: + set_method(1) + assert get_method() == 1 + except Exception: + print("\tFailed test for property", prop_name) + +def test_invalid_settings(): + print(f"Testing invalid settings") + invalid_settings = [ + {"xclk_freq": 21000000}, + {"frame_size": 23}, + {"pixel_format": 7}, + {"jpeg_quality": 101}, + {"jpeg_quality": -1}, + {"grab_mode": 3}, + ] + Delay= 10 + + for settings in invalid_settings: + param, invalid_value = next(iter(settings.items())) + try: + print("testing",param,"=",invalid_value) + time.sleep_ms(Delay) + with Camera(**{param: invalid_value}) as cam: + print(f"\tFailed test for {param} with value {invalid_value}") + except Exception as e: + time.sleep_ms(Delay) + +if __name__ == "__main__": + test_property_get_frame_size() + test_property_get_pixel_format() + test_must_be_initialized() + test_camera_properties() + test_invalid_settings() +