├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── general-issue.md └── workflows │ └── ESP32.yml ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── CameraSettings.html ├── CameraSettings.py ├── SimpleWebCam.py ├── benchmark.py └── benchmark_img_conv.py ├── src ├── acamera.py ├── camera_pins.h ├── manifest.py ├── micropython.cmake ├── micropython.mk ├── modcamera.c ├── modcamera.h └── modcamera_api.c └── tests └── esp32_test.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: cnadler86 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | #polar: # Replace with a single Polar username 13 | #buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | #thanks_dev: # Replace with a single thanks.dev username 15 | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug and help improving the API 4 | title: '' 5 | labels: bug 6 | assignees: cnadler86 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Code to reproduce 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Environment:** 20 | - Board: e.g. ESP32S3 XIAO 21 | - Camera drivers version: e.g. 2.0.15 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | 26 | Note: Also disconnect any peripherals to the board and try without them before raising a bug. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General issue 3 | about: General issue 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | PLEASE, read the documentation, search [previous issues](https://github.com/cnadler86/micropython-camera-API/issues?q=is%3Aissue) and/or ask ChatGPT before! 11 | Also disconnect any peripherals to the board and try without them before raising an issue. 12 | -------------------------------------------------------------------------------- /.github/workflows/ESP32.yml: -------------------------------------------------------------------------------- 1 | name: ESP32 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - '**' 8 | paths: 9 | - 'src/**' 10 | - '.github/workflows/*.yml' 11 | tags-ignore: 12 | - 'v*' 13 | pull_request: 14 | branches: 15 | - master 16 | paths: 17 | - 'src/**' 18 | - '.github/workflows/*.yml' 19 | 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.ref }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | setup-environment: 26 | runs-on: ubuntu-24.04 27 | steps: 28 | # Get the latest MicroPython release 29 | - name: Get MicroPython latest release 30 | run: | 31 | MPY_RELEASE=$(curl --silent "https://api.github.com/repos/micropython/micropython/releases/latest" | jq -r .tag_name) 32 | echo "MPY_RELEASE=${MPY_RELEASE}" >> $GITHUB_ENV 33 | 34 | # Cache ESP-IDF dependencies and MicroPython 35 | - name: Cache ESP-IDF and MicroPython 36 | id: cache_esp_idf 37 | uses: actions/cache@v4 38 | with: 39 | lookup-only: true 40 | path: | 41 | ~/esp-idf/ 42 | ~/.espressif/ 43 | !~/.espressif/dist/ 44 | ~/.cache/pip/ 45 | ~/micropython/ 46 | key: mpy-${{ env.MPY_RELEASE }} 47 | restore-keys: mpy- 48 | 49 | # Install ESP-IDF dependencies (if not cached) 50 | - name: Install dependencies 51 | if: steps.cache_esp_idf.outputs.cache-hit != 'true' 52 | run: | 53 | sudo apt-get update 54 | 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 55 | 56 | # Download and set up ESP-IDF (if not cached) 57 | - name: Set up ESP-IDF 58 | id: export-idf 59 | if: steps.cache_esp_idf.outputs.cache-hit != 'true' 60 | run: | 61 | cd ~ 62 | git clone --depth 1 --branch v5.4 https://github.com/espressif/esp-idf.git 63 | # git clone --depth 1 --branch ${{ env.IDF_VER }} https://github.com/espressif/esp-idf.git 64 | git -C esp-idf submodule update --init --recursive --filter=tree:0 65 | cd esp-idf 66 | ./install.sh all 67 | cd components 68 | # latest_cam_driver=$(curl -s https://api.github.com/repos/espressif/esp32-camera/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') 69 | # git clone --depth 1 --branch $latest_cam_driver https://github.com/espressif/esp32-camera.git 70 | git clone https://github.com/cnadler86/esp32-camera.git 71 | cd ~/esp-idf/ 72 | source ./export.sh 73 | cd ~ 74 | git clone https://github.com/espressif/esp-adf-libs.git 75 | cp -r ~/esp-adf-libs/esp_new_jpeg ~/esp-idf/components/ 76 | 77 | # Clone the latest MicroPython release (if not cached) 78 | - name: Clone MicroPython latest release 79 | id: clone-micropython 80 | if: steps.cache_esp_idf.outputs.cache-hit != 'true' 81 | run: | 82 | echo "Cloning MicroPython release: $MPY_RELEASE" 83 | cd ~/esp-idf/ 84 | source ./export.sh 85 | cd ~ 86 | git clone --depth 1 --branch ${{ env.MPY_RELEASE }} https://github.com/micropython/micropython.git 87 | cd micropython 88 | # git submodule update --init --depth 1 89 | cd mpy-cross 90 | make 91 | cd ~/micropython/ports/esp32 92 | make submodules 93 | echo "Micropython setup successfully" 94 | source ~/micropython/tools/ci.sh && echo "IDF_VER=$IDF_VER" >> $GITHUB_ENV 95 | 96 | # Dynamically create jobs for each board 97 | build: 98 | needs: setup-environment 99 | runs-on: ubuntu-24.04 100 | strategy: 101 | fail-fast: false 102 | matrix: 103 | board: 104 | - ESP32_GENERIC-SPIRAM 105 | - ESP32_GENERIC_S2 106 | - ESP32_GENERIC_S3 107 | - ESP32_GENERIC_S3-SPIRAM_OCT 108 | - ESP32_GENERIC_S3-FLASH_4M 109 | - ESP32_GENERIC-SPIRAM@WROVER_KIT 110 | - ESP32_GENERIC-SPIRAM@ESP_EYE 111 | - ESP32_GENERIC-SPIRAM@M5STACK_PSRAM 112 | - ESP32_GENERIC-SPIRAM@M5STACK_V2_PSRAM 113 | - ESP32_GENERIC-SPIRAM@M5STACK_WIDE 114 | - ESP32_GENERIC-SPIRAM@M5STACK_ESP32CAM 115 | - ESP32_GENERIC-SPIRAM@M5STACK_UNITCAM 116 | - ESP32_GENERIC-SPIRAM@AI_THINKER 117 | - ESP32_GENERIC-SPIRAM@TTGO_T_JOURNAL 118 | - ESP32_GENERIC-SPIRAM@TTGO_T_CAMERA_PLUS 119 | - ESP32_GENERIC_S3-SPIRAM_OCT@M5STACK_CAMS3_UNIT 120 | - ESP32_GENERIC_S3-SPIRAM_OCT@XIAO_ESP32S3 121 | - ESP32_GENERIC_S3-SPIRAM_OCT@ESP32S3_CAM_LCD 122 | - ESP32_GENERIC_S3-SPIRAM_OCT@ESP32S3_EYE 123 | - ESP32_GENERIC_S3-SPIRAM_OCT@FREENOVE_ESP32S3_CAM 124 | - ESP32_GENERIC_S3-SPIRAM_OCT@DFRobot_ESP32S3 125 | - ESP32_GENERIC_S3-SPIRAM_OCT@NEW_ESPS3_RE1_0 126 | - ESP32_GENERIC_S3-SPIRAM_OCT@XENOIONEX 127 | 128 | steps: 129 | # Get the latest MicroPython release 130 | - name: Get MicroPython latest release 131 | run: | 132 | MPY_RELEASE=$(curl --silent "https://api.github.com/repos/micropython/micropython/releases/latest" | jq -r .tag_name) 133 | echo "MPY_RELEASE=${MPY_RELEASE}" >> $GITHUB_ENV 134 | 135 | # Cache ESP-IDF dependencies and MicroPython 136 | - name: Cache ESP-IDF and MicroPython 137 | uses: actions/cache@v4 138 | with: 139 | path: | 140 | ~/esp-idf/ 141 | ~/.espressif/ 142 | !~/.espressif/dist/ 143 | ~/.cache/pip/ 144 | ~/micropython/ 145 | key: mpy-${{ env.MPY_RELEASE }} 146 | restore-keys: mpy- 147 | 148 | - name: Checkout repository 149 | uses: actions/checkout@v4 150 | 151 | - name: Install ESP-IDF dependencies 152 | run: | 153 | sudo apt-get update 154 | 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 build-essential pkg-config 155 | 156 | # Build MicroPython for each board 157 | - name: Build MicroPython 158 | run: | 159 | cd ${{ github.workspace }} 160 | git clone https://github.com/cnadler86/mp_jpeg.git 161 | cd ~/esp-idf/components/esp32-camera 162 | CAM_DRIVER=$(git describe --tags --always --dirty) 163 | cd ~/micropython/ports/esp32 164 | source ~/esp-idf/export.sh 165 | 166 | # Check if a variant is defined and adjust the idf.py command 167 | IFS='@' read -r BUILD_TARGET CAMERA_MODEL <<< "${{ matrix.board }}" 168 | IFS='-' read -r BOARD_NAME BOARD_VARIANT <<< "${BUILD_TARGET}" 169 | 170 | if [ -n "${BOARD_VARIANT}" ]; then 171 | 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" 172 | else 173 | 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" 174 | fi 175 | if [ -n "${CAMERA_MODEL}" ]; then 176 | echo "FW_NAME=${CAMERA_MODEL}" >> $GITHUB_ENV 177 | FINAL_CMD="${IDF_CMD} -D MICROPY_CAMERA_MODEL=${CAMERA_MODEL} build" 178 | else 179 | echo "FW_NAME=${BUILD_TARGET}" >> $GITHUB_ENV 180 | FINAL_CMD="${IDF_CMD} build" 181 | fi 182 | make USER_C_MODULES=${{ github.workspace }}/src/micropython.cmake BOARD=$BOARD_NAME submodules 183 | echo "Running command: $FINAL_CMD" 184 | eval $FINAL_CMD 185 | cd ~/micropython/ports/esp32/build-${BUILD_TARGET} 186 | python ../makeimg.py sdkconfig bootloader/bootloader.bin partition_table/partition-table.bin micropython.bin firmware.bin micropython.uf2 187 | mkdir -p ~/artifacts 188 | mv ~/micropython/ports/esp32/build-${BUILD_TARGET}/firmware.bin ~/artifacts/firmware.bin 189 | 190 | - name: Upload firmware artifact 191 | uses: actions/upload-artifact@v4 192 | with: 193 | name: mpy_cam-${{ env.MPY_RELEASE }}-${{ env.FW_NAME }} 194 | path: ~/artifacts/** 195 | retention-days: 5 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | #.vscode folder 7 | .vscode/ 8 | 9 | # User-specific files 10 | *.rsuser 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Mono auto generated files 20 | mono_crash.* 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Dd]ebugPublic/ 25 | [Rr]elease/ 26 | [Rr]eleases/ 27 | x64/ 28 | x86/ 29 | [Ww][Ii][Nn]32/ 30 | [Aa][Rr][Mm]/ 31 | [Aa][Rr][Mm]64/ 32 | bld/ 33 | [Bb]in/ 34 | [Oo]bj/ 35 | [Ll]og/ 36 | [Ll]ogs/ 37 | 38 | # Visual Studio 2015/2017 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # Visual Studio 2017 auto generated files 44 | Generated\ Files/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUnit 51 | *.VisualState.xml 52 | TestResult.xml 53 | nunit-*.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET Core 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | 68 | # ASP.NET Scaffolding 69 | ScaffoldingReadMe.txt 70 | 71 | # StyleCop 72 | StyleCopReport.xml 73 | 74 | # Files built by Visual Studio 75 | *_i.c 76 | *_p.c 77 | *_h.h 78 | *.ilk 79 | *.meta 80 | *.obj 81 | *.iobj 82 | *.pch 83 | *.pdb 84 | *.ipdb 85 | *.pgc 86 | *.pgd 87 | *.rsp 88 | *.sbr 89 | *.tlb 90 | *.tli 91 | *.tlh 92 | *.tmp 93 | *.tmp_proj 94 | *_wpftmp.csproj 95 | *.log 96 | *.tlog 97 | *.vspscc 98 | *.vssscc 99 | .builds 100 | *.pidb 101 | *.svclog 102 | *.scc 103 | 104 | # Chutzpah Test files 105 | _Chutzpah* 106 | 107 | # Visual C++ cache files 108 | ipch/ 109 | *.aps 110 | *.ncb 111 | *.opendb 112 | *.opensdf 113 | *.sdf 114 | *.cachefile 115 | *.VC.db 116 | *.VC.VC.opendb 117 | 118 | # Visual Studio profiler 119 | *.psess 120 | *.vsp 121 | *.vspx 122 | *.sap 123 | 124 | # Visual Studio Trace Files 125 | *.e2e 126 | 127 | # TFS 2012 Local Workspace 128 | $tf/ 129 | 130 | # Guidance Automation Toolkit 131 | *.gpState 132 | 133 | # ReSharper is a .NET coding add-in 134 | _ReSharper*/ 135 | *.[Rr]e[Ss]harper 136 | *.DotSettings.user 137 | 138 | # TeamCity is a build add-in 139 | _TeamCity* 140 | 141 | # DotCover is a Code Coverage Tool 142 | *.dotCover 143 | 144 | # AxoCover is a Code Coverage Tool 145 | .axoCover/* 146 | !.axoCover/settings.json 147 | 148 | # Coverlet is a free, cross platform Code Coverage Tool 149 | coverage*.json 150 | coverage*.xml 151 | coverage*.info 152 | 153 | # Visual Studio code coverage results 154 | *.coverage 155 | *.coveragexml 156 | 157 | # NCrunch 158 | _NCrunch_* 159 | .*crunch*.local.xml 160 | nCrunchTemp_* 161 | 162 | # MightyMoose 163 | *.mm.* 164 | AutoTest.Net/ 165 | 166 | # Web workbench (sass) 167 | .sass-cache/ 168 | 169 | # Installshield output folder 170 | [Ee]xpress/ 171 | 172 | # DocProject is a documentation generator add-in 173 | DocProject/buildhelp/ 174 | DocProject/Help/*.HxT 175 | DocProject/Help/*.HxC 176 | DocProject/Help/*.hhc 177 | DocProject/Help/*.hhk 178 | DocProject/Help/*.hhp 179 | DocProject/Help/Html2 180 | DocProject/Help/html 181 | 182 | # Click-Once directory 183 | publish/ 184 | 185 | # Publish Web Output 186 | *.[Pp]ublish.xml 187 | *.azurePubxml 188 | # Note: Comment the next line if you want to checkin your web deploy settings, 189 | # but database connection strings (with potential passwords) will be unencrypted 190 | *.pubxml 191 | *.publishproj 192 | 193 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 194 | # checkin your Azure Web App publish settings, but sensitive information contained 195 | # in these scripts will be unencrypted 196 | PublishScripts/ 197 | 198 | # NuGet Packages 199 | *.nupkg 200 | # NuGet Symbol Packages 201 | *.snupkg 202 | # The packages folder can be ignored because of Package Restore 203 | **/[Pp]ackages/* 204 | # except build/, which is used as an MSBuild target. 205 | !**/[Pp]ackages/build/ 206 | # Uncomment if necessary however generally it will be regenerated when needed 207 | #!**/[Pp]ackages/repositories.config 208 | # NuGet v3's project.json files produces more ignorable files 209 | *.nuget.props 210 | *.nuget.targets 211 | 212 | # Microsoft Azure Build Output 213 | csx/ 214 | *.build.csdef 215 | 216 | # Microsoft Azure Emulator 217 | ecf/ 218 | rcf/ 219 | 220 | # Windows Store app package directories and files 221 | AppPackages/ 222 | BundleArtifacts/ 223 | Package.StoreAssociation.xml 224 | _pkginfo.txt 225 | *.appx 226 | *.appxbundle 227 | *.appxupload 228 | 229 | # Visual Studio cache files 230 | # files ending in .cache can be ignored 231 | *.[Cc]ache 232 | # but keep track of directories ending in .cache 233 | !?*.[Cc]ache/ 234 | 235 | # Others 236 | ClientBin/ 237 | ~$* 238 | *~ 239 | *.dbmdl 240 | *.dbproj.schemaview 241 | *.jfm 242 | *.pfx 243 | *.publishsettings 244 | orleans.codegen.cs 245 | 246 | # Including strong name files can present a security risk 247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 248 | #*.snk 249 | 250 | # Since there are multiple workflows, uncomment next line to ignore bower_components 251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 252 | #bower_components/ 253 | 254 | # RIA/Silverlight projects 255 | Generated_Code/ 256 | 257 | # Backup & report files from converting an old project file 258 | # to a newer Visual Studio version. Backup files are not needed, 259 | # because we have git ;-) 260 | _UpgradeReport_Files/ 261 | Backup*/ 262 | UpgradeLog*.XML 263 | UpgradeLog*.htm 264 | ServiceFabricBackup/ 265 | *.rptproj.bak 266 | 267 | # SQL Server files 268 | *.mdf 269 | *.ldf 270 | *.ndf 271 | 272 | # Business Intelligence projects 273 | *.rdl.data 274 | *.bim.layout 275 | *.bim_*.settings 276 | *.rptproj.rsuser 277 | *- [Bb]ackup.rdl 278 | *- [Bb]ackup ([0-9]).rdl 279 | *- [Bb]ackup ([0-9][0-9]).rdl 280 | 281 | # Microsoft Fakes 282 | FakesAssemblies/ 283 | 284 | # GhostDoc plugin setting file 285 | *.GhostDoc.xml 286 | 287 | # Node.js Tools for Visual Studio 288 | .ntvs_analysis.dat 289 | node_modules/ 290 | 291 | # Visual Studio 6 build log 292 | *.plg 293 | 294 | # Visual Studio 6 workspace options file 295 | *.opt 296 | 297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 298 | *.vbw 299 | 300 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 301 | *.vbp 302 | 303 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 304 | *.dsw 305 | *.dsp 306 | 307 | # Visual Studio 6 technical files 308 | *.ncb 309 | *.aps 310 | 311 | # Visual Studio LightSwitch build output 312 | **/*.HTMLClient/GeneratedArtifacts 313 | **/*.DesktopClient/GeneratedArtifacts 314 | **/*.DesktopClient/ModelManifest.xml 315 | **/*.Server/GeneratedArtifacts 316 | **/*.Server/ModelManifest.xml 317 | _Pvt_Extensions 318 | 319 | # Paket dependency manager 320 | .paket/paket.exe 321 | paket-files/ 322 | 323 | # FAKE - F# Make 324 | .fake/ 325 | 326 | # CodeRush personal settings 327 | .cr/personal 328 | 329 | # Python Tools for Visual Studio (PTVS) 330 | __pycache__/ 331 | *.pyc 332 | 333 | # Cake - Uncomment if you are using it 334 | # tools/** 335 | # !tools/packages.config 336 | 337 | # Tabs Studio 338 | *.tss 339 | 340 | # Telerik's JustMock configuration file 341 | *.jmconfig 342 | 343 | # BizTalk build output 344 | *.btp.cs 345 | *.btm.cs 346 | *.odx.cs 347 | *.xsd.cs 348 | 349 | # OpenCover UI analysis results 350 | OpenCover/ 351 | 352 | # Azure Stream Analytics local run output 353 | ASALocalRun/ 354 | 355 | # MSBuild Binary and Structured Log 356 | *.binlog 357 | 358 | # NVidia Nsight GPU debugger configuration file 359 | *.nvuser 360 | 361 | # MFractors (Xamarin productivity tool) working folder 362 | .mfractor/ 363 | 364 | # Local History for Visual Studio 365 | .localhistory/ 366 | 367 | # Visual Studio History (VSHistory) files 368 | .vshistory/ 369 | 370 | # BeatPulse healthcheck temp database 371 | healthchecksdb 372 | 373 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 374 | MigrationBackup/ 375 | 376 | # Ionide (cross platform F# VS Code tools) working folder 377 | .ionide/ 378 | 379 | # Fody - auto-generated XML schema 380 | FodyWeavers.xsd 381 | 382 | # VS Code files for those working on multiple tools 383 | .vscode/* 384 | !.vscode/settings.json 385 | !.vscode/tasks.json 386 | !.vscode/launch.json 387 | !.vscode/extensions.json 388 | *.code-workspace 389 | 390 | # Local History for Visual Studio Code 391 | .history/ 392 | 393 | # Windows Installer files from build outputs 394 | *.cab 395 | *.msi 396 | *.msix 397 | *.msm 398 | *.msp 399 | 400 | # JetBrains Rider 401 | *.sln.iml 402 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Christopher Nadler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Camera API for micropython 2 | 3 | [![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) 4 | 5 | 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. 6 | 7 | The API is stable, but it might change. Please look into the release section for the latest changes. 8 | 9 | I tied to make things as easy as possible. If you find this project useful, please consider donating to support my work. Thanks! 10 | [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://www.paypal.me/cnadler) 11 | 12 | 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). 13 | 14 | ## Content 15 | 16 | - [Precompiled firmware (the easy way)](#Precompiled-firmware-the-easy-way) 17 | - [Using the API](#using-the-api) 18 | - [Importing the camera module](#importing-the-camera-module) 19 | - [Creating a camera object](#creating-a-camera-object) 20 | - [Initializing the camera](#initializing-the-camera) 21 | - [Capture image](#capture-image) 22 | - [Camera reconfiguration](#camera-reconfiguration) 23 | - [Freeing the buffer](#freeing-the-buffer) 24 | - [Is a frame available](#is-frame-available) 25 | - [Additional methods](#additional-methods) 26 | - [Additional information](#additional-information) 27 | - [Build your custom firmware](#build-your-custom-firmware) 28 | - [Setting up the build environment (DIY method)](#setting-up-the-build-environment-diy-method) 29 | - [Add camera configurations to your board (optional, but recommended)](#add-camera-configurations-to-your-board-optional-but-recommended) 30 | - [Build the API](#build-the-api) 31 | - [Notes](#notes) 32 | - [Benchmark](#benchmark) 33 | - [Troubleshooting](#troubleshooting) 34 | - [Donate](#donate) 35 | 36 | ## Precompiled firmware (the easy way) 37 | 38 | 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!** 39 | To flash the firmware, you can use for example [https://esp.huhn.me/](https://esp.huhn.me/). 40 | 41 | These firmware binaries also include the [mp_jpeg modul](https://github.com/cnadler86/mp_jpeg) to encode/decode JPEGs. 42 | 43 | ## Using the API 44 | 45 | ### Importing the camera module 46 | 47 | There general way of using the api is as follow: 48 | 49 | ```python 50 | from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling 51 | ``` 52 | 53 | There is also a camera class with asyncio support! To use it, you need to import from the acamera package: 54 | 55 | ```python 56 | from acamera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling 57 | ``` 58 | 59 | ### Creating a camera object 60 | 61 | 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. 62 | 63 | ```python 64 | cam = Camera() 65 | ``` 66 | 67 | or with relevant keyword arguments: 68 | 69 | ```python 70 | cam = Camera(pixel_format=PixelFormat.JPEG, 71 | frame_size=FrameSize.QVGA, 72 | jpeg_quality=90, 73 | fb_count=2, 74 | grab_mode=GrabMode.WHEN_EMPTY) 75 | ``` 76 | 77 | When using a **generic** precompiled firmware, the camera constructor requires specific keyword arguments (namely the camera pins to be used). 78 | These pins are just examples and if used as-is, a error will occur. Adapt them to your board! 79 | 80 | ```python 81 | cam = Camera( 82 | data_pins=[1,2,3,4,5,6,7,8], 83 | vsync_pin=9, 84 | href_pin=10, 85 | sda_pin=11, 86 | scl_pin=12, 87 | pclk_pin=13, 88 | xclk_pin=14, 89 | xclk_freq=20000000, 90 | powerdown_pin=-1, 91 | reset_pin=-1, 92 | ) 93 | ``` 94 | 95 | **Keyword arguments for construction:** 96 | 97 | - data_pins: List of data pins 98 | - pclk_pin: Pixel clock pin 99 | - vsync_pin: VSYNC pin 100 | - href_pin: HREF pin 101 | - sda_pin: SDA pin 102 | - scl_pin: SCL pin 103 | - xclk_pin: XCLK pin ( set to -1, if you have an external clock source) 104 | - xclk_freq: XCLK frequency in Hz (consult the camera sensor specification) 105 | - powerdown_pin: Powerdown pin (set to -1 if not used) 106 | - reset_pin: Reset pin (set to -1 if not used) 107 | - pixel_format: Pixel format as PixelFormat 108 | - frame_size: Frame size as FrameSize 109 | - jpeg_quality: JPEG quality 110 | - fb_count: Frame buffer count 111 | - grab_mode: Grab mode as GrabMode 112 | - init: Initialize camera at construction time (default: True) 113 | 114 | **Default values:** 115 | 116 | The following keyword arguments have default values: 117 | 118 | - xclk_freq: 20MHz // Default for OV2640 (normally either 10 MHz or 20 MHz). Plase adapt it to your camera sensor. 119 | - frame_size: QQVGA 120 | - pixel_format: RGB565 121 | - jpeg_quality: 85 // Quality of JPEG output in percent. Higher means higher quality. 122 | - powerdown_pin and reset_pin: -1 ( = not used/available/needed) 123 | - fb_count: 124 | - 2 for ESP32S3 boards 125 | - 1 for all other 126 | - grab_mode: 127 | - LATEST for ESP32S3 boards 128 | - WHEN_EMPTY for all other 129 | 130 | ### Initializing the camera 131 | 132 | ```python 133 | cam.init() 134 | ``` 135 | 136 | ### Capture image 137 | 138 | The general way of capturing an image is calling the `capture` method: 139 | 140 | ```python 141 | img = cam.capture() 142 | ``` 143 | 144 | Each time you call the method, you will receive a new frame as memoryview. 145 | 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)) 146 | 147 | The probably better way of capturing an image would be in an asyncio-loop: 148 | 149 | ```python 150 | img = await cam.acapture() #To access this method, you need to import from acamera 151 | ``` 152 | 153 | Please consult the [asyncio documentation](https://docs.micropython.org/en/latest/library/asyncio.html), if you have questions on this. 154 | 155 | ### Camera reconfiguration 156 | 157 | ```python 158 | cam.reconfigure(pixel_format=PixelFormat.JPEG,frame_size=FrameSize.QVGA,grab_mode=GrabMode.LATEST, fb_count=2) 159 | ``` 160 | 161 | Keyword arguments for reconfigure 162 | 163 | - frame_size: Frame size as FrameSize (optional) 164 | - pixel_format: Pixel format as PixelFormat(optional) 165 | - grab_mode: Grab mode as GrabMode (optional) 166 | - fb_count: Frame buffer count (optional) 167 | 168 | ### Freeing the buffer 169 | 170 | This is optional, but can reduce the latency of capturing an image in some cases (especially with fb_count = 1).. 171 | 172 | ```python 173 | Img = bytes(cam.capture()) #Create a new bytes object from the memoryview (because we want to free it afterwards) 174 | cam.free_buffer() # This will free the captured image or in other words "deleting"" the memoryview 175 | ``` 176 | 177 | ### Is frame available 178 | 179 | ```python 180 | Img = bytes(cam.capture()) 181 | cam.free_buffer() 182 | while not cam.frame_available(): 183 | 184 | print('The frame is available now. You can grab the image by the capture method =)') 185 | ``` 186 | 187 | This gives you the possibility of creating an asynchronous application without using asyncio. 188 | 189 | ### Additional methods and examples 190 | 191 | Here are just a few examples: 192 | 193 | ```python 194 | cam.set_quality(90) # The quality goes from 0% to 100%, meaning 100% is the highest but has probably no compression 195 | camera.get_brightness() 196 | camera.set_vflip(True) #Enable vertical flip 197 | ``` 198 | 199 | See autocompletions in Thonny in order to see the list of methods. 200 | 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). 201 | Note that each method requires a "get_" or "set_" prefix, depending on the desired action. 202 | 203 | Take also a look in the examples folder. 204 | 205 | To get the version of the camera driver used: 206 | 207 | ```python 208 | import camera 209 | vers = camera.Version() 210 | ``` 211 | 212 | ### Additional information 213 | 214 | 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 215 | 216 | ## Build your custom firmware 217 | 218 | ### Setting up the build environment (DIY method) 219 | 220 | To build the project, follow these instructions: 221 | 222 | - [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). 223 | - 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). 224 | - 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): 225 | 226 | ```yml 227 | espressif/esp32-camera: 228 | git: https://github.com/cnadler86/esp32-camera.git 229 | ``` 230 | 231 | Alternatively, you can clone the repository inside the esp-idf/components folder instead of altering the idf_component.yml file. 232 | 233 | ### Add camera configurations to your board (optional, but recommended) 234 | 235 | #### Supported camera models 236 | 237 | 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). 238 | Example (don't forget to add the empty line at the bottom): 239 | 240 | ```c 241 | #define MICROPY_CAMERA_MODEL_WROVER_KIT 1 242 | 243 | ``` 244 | 245 | Below is a list of supported `MICROPY_CAMERA_MODEL_xxx` definitions: 246 | 247 | - MICROPY_CAMERA_MODEL_WROVER_KIT - [ESP32-WROVER-KIT](https://www.espressif.com/en/products/devkits/esp32-wrover-kit/overview) 248 | - MICROPY_CAMERA_MODEL_ESP_EYE - [ESP-EYE](https://www.espressif.com/en/products/devkits/esp-eye/overview) 249 | - MICROPY_CAMERA_MODEL_M5STACK_PSRAM - [M5Stack PSRAM](https://shop.m5stack.com/collections/m5-cameras) 250 | - MICROPY_CAMERA_MODEL_M5STACK_UNITCAM - [M5Stack UnitCam](https://shop.m5stack.com/collections/m5-cameras) 251 | - MICROPY_CAMERA_MODEL_M5STACK_V2_PSRAM - [M5Stack V2 PSRAM](https://shop.m5stack.com/collections/m5-cameras) 252 | - MICROPY_CAMERA_MODEL_M5STACK_WIDE - [M5Stack Wide](https://shop.m5stack.com/collections/m5-cameras) 253 | - MICROPY_CAMERA_MODEL_M5STACK_ESP32CAM - [M5Stack ESP32CAM](https://shop.m5stack.com/collections/m5-cameras) 254 | - MICROPY_CAMERA_MODEL_M5STACK_CAMS3_UNIT - [M5Stack CAMS3 Unit](https://shop.m5stack.com/collections/m5-cameras) 255 | - MICROPY_CAMERA_MODEL_AI_THINKER - [AI-Thinker ESP32-CAM] 256 | - MICROPY_CAMERA_MODEL_XIAO_ESP32S3 - [XIAO ESP32S3](https://www.seeedstudio.com/xiao-series-page) 257 | - MICROPY_CAMERA_MODEL_ESP32_MP_CAMERA_BOARD - [ESP32 MP Camera Board] 258 | - MICROPY_CAMERA_MODEL_ESP32S3_CAM_LCD - [ESP32-S3 CAM LCD] 259 | - MICROPY_CAMERA_MODEL_ESP32S3_EYE - [ESP32-S3 EYE](https://www.espressif.com/en/products/devkits/esp32-s3-eye/overview) 260 | - MICROPY_CAMERA_MODEL_FREENOVE_ESP32S3_CAM - [Freenove ESP32-S3 CAM](https://store.freenove.com/products/fnk0085) 261 | - MICROPY_CAMERA_MODEL_DFRobot_ESP32S3 - [DFRobot ESP32-S3](https://www.dfrobot.com/) 262 | - MICROPY_CAMERA_MODEL_TTGO_T_JOURNAL - [TTGO T-Journal](https://www.lilygo.cc/products/) 263 | - MICROPY_CAMERA_MODEL_TTGO_T_CAMERA_PLUS - [TTGO T-Camera Plus](https://www.lilygo.cc/products/) 264 | - MICROPY_CAMERA_MODEL_NEW_ESPS3_RE1_0 - [New ESP32-S3 RE:1.0] 265 | - MICROPY_CAMERA_MODEL_XENOIONEX - [Xenoionex] 266 | 267 | #### For unsupported camera models 268 | 269 | 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. 270 | Example for Xiao sense: 271 | 272 | ```c 273 | #define MICROPY_CAMERA_PIN_D0 (15) 274 | #define MICROPY_CAMERA_PIN_D1 (17) 275 | #define MICROPY_CAMERA_PIN_D2 (18) 276 | #define MICROPY_CAMERA_PIN_D3 (16) 277 | #define MICROPY_CAMERA_PIN_D4 (14) 278 | #define MICROPY_CAMERA_PIN_D5 (12) 279 | #define MICROPY_CAMERA_PIN_D6 (11) 280 | #define MICROPY_CAMERA_PIN_D7 (48) 281 | #define MICROPY_CAMERA_PIN_PCLK (13) 282 | #define MICROPY_CAMERA_PIN_VSYNC (38) 283 | #define MICROPY_CAMERA_PIN_HREF (47) 284 | #define MICROPY_CAMERA_PIN_XCLK (10) 285 | #define MICROPY_CAMERA_PIN_PWDN (-1) 286 | #define MICROPY_CAMERA_PIN_RESET (-1) 287 | #define MICROPY_CAMERA_PIN_SIOD (40) // SDA 288 | #define MICROPY_CAMERA_PIN_SIOC (39) // SCL 289 | #define MICROPY_CAMERA_XCLK_FREQ (20000000) // Frequencies are normally either 10 MHz or 20 MHz 290 | #define MICROPY_CAMERA_FB_COUNT (2) // The value is between 1 (slow) and 2 (fast, but more load on CPU and more ram usage) 291 | #define MICROPY_CAMERA_JPEG_QUALITY (85) // Quality of JPEG output in percent. Higher means higher quality. 292 | #define MICROPY_CAMERA_GRAB_MODE (1) // 0=WHEN_EMPTY (might have old data, but less resources), 1=LATEST (best, but more resources) 293 | 294 | ``` 295 | #### Customize additional camera settings 296 | 297 | 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. 298 | 299 | #### (Optional) Add the mp_jpeg module 300 | 301 | 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. 302 | 303 | ### Build the API 304 | 305 | To build the project, you could do it the following way: 306 | 307 | ```bash 308 | . /esp-idf/export.sh 309 | cd MyESPCam/micropython/ports/esp32 310 | make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOARD= clean 311 | make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOARD= submodules 312 | make USER_C_MODULES=../../../../micropython-camera-API/src/micropython.cmake BOARD= all 313 | ``` 314 | 315 | Micropython and camera-api folders are at the same level. Note that you need those extra "/../"s while been inside the esp32 port folder. 316 | If you experience problems, visit [MicroPython external C modules](https://docs.micropython.org/en/latest/develop/cmodules.html). 317 | 318 | ## Notes 319 | 320 | - 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. 321 | - 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. 322 | - 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)). 323 | - The driver requires PSRAM to be installed and activated. 324 | - 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. 325 | - Every sensor has its own ranges and settings, please consult the respective specifications. 326 | 327 | ## Benchmark 328 | 329 | 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. 330 | Using fb_count=2 theoretically can double the FPS (see JPEG with fb_count=2). This might also apply for other PixelFormats. 331 | 332 | | Frame Size | GRAYSCALE | RGB565 | YUV422 | JPEG | JPEG (fb=2) | 333 | |------------|-----------|--------|--------|--------|-------------| 334 | | R96X96 | 12.5 | 12.5 | 12.5 | No img | No img | 335 | | QQVGA | 12.5 | 12.5 | 12.5 | 25 | 50 | 336 | | QCIF | 11 | 11 | 11.5 | 25 | 50 | 337 | | HQVGA | 12.5 | 12.5 | 12.5 | 25 | 50 | 338 | | R240X240 | 12.5 | 12.5 | 11.5 | 25 | 50 | 339 | | QVGA | 12 | 11 | 12 | 25 | 50 | 340 | | CIF | 12.5 | No img | No img | 6.3 | 12.5 | 341 | | HVGA | 3 | 3 | 2.5 | 12.5 | 25 | 342 | | VGA | 3 | 3 | 3 | 12.5 | 25 | 343 | | SVGA | 3 | 3 | 3 | 12.5 | 25 | 344 | | XGA | No img | No img | No img | 6.3 | 12.5 | 345 | | HD | No img | No img | No img | 6.3 | 12.5 | 346 | | SXGA | 2 | 2 | 2 | 6.3 | 12.5 | 347 | | UXGA | No img | No img | No img | 6.3 | 12.5 | 348 | 349 | ## Troubleshooting 350 | 351 | You can find information on the following sites: 352 | - [ESP-FAQ](https://docs.espressif.com/projects/esp-faq/en/latest/application-solution/camera-application.html) 353 | - [ChatGPT](https://chatgpt.com/) 354 | - [Issues in here](https://github.com/cnadler86/micropython-camera-API/issues?q=is%3Aissue) 355 | 356 | ## Donate 357 | 358 | If you enjoy this work and would like to share your enjoyment, please feel free to [donate](https://github.com/sponsors/cnadler86?frequency=one-time) and contribute to the project. Thanks! :blush: 359 | -------------------------------------------------------------------------------- /examples/CameraSettings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Micropython Camera Stream 5 | 54 | 161 | 162 | 163 |
164 |

Micropython Camera Stream

165 |
166 |
167 |
168 |
169 | 170 | 197 |
198 |
199 | 200 | 201 |
202 |
203 | 204 | 205 |
206 |
207 | 208 | 209 |
210 |
211 | 212 | 213 |
214 |
215 | 216 | 217 |
218 |
219 | 220 | 221 |
222 |
223 | 224 | 225 |
226 | 230 | 234 |
235 | 236 | 245 |
246 |
247 | 248 | 255 |
256 |
257 | 258 | 259 |
260 |
261 | 262 | 263 |
264 |
265 | 266 | 267 |
268 |
269 | 270 | 271 |
272 |
273 | 274 | 275 |
276 |
277 | 278 | 279 |
280 |
281 | 282 | 283 |
284 |
285 | 286 | 287 |
288 |
289 | 290 | 291 |
292 |
293 | 294 | 295 |
296 |
297 | 298 | 299 |
300 |
301 | 302 | 303 |
304 | 305 |
306 | 307 | 316 |
317 |
318 |
319 | Loading stream... 320 |
321 |
322 | 323 | -------------------------------------------------------------------------------- /examples/CameraSettings.py: -------------------------------------------------------------------------------- 1 | import network 2 | import asyncio 3 | import time 4 | from acamera import Camera, FrameSize, PixelFormat # Import the async version of the Camera class, you can also use the sync version (camera.Camera) 5 | 6 | cam = Camera(frame_size=FrameSize.VGA, pixel_format=PixelFormat.JPEG, jpeg_quality=85, init=False) 7 | # WLAN config 8 | ssid = '' 9 | password = '' 10 | 11 | station = network.WLAN(network.STA_IF) 12 | station.active(True) 13 | station.connect(ssid, password) 14 | 15 | while not station.isconnected(): 16 | time.sleep(1) 17 | 18 | print(f'Connected! IP: {station.ifconfig()[0]}. Open this IP in your browser') 19 | 20 | try: 21 | with open("CameraSettings.html", 'r') as file: 22 | html = file.read() 23 | except Exception as e: 24 | print("Error reading CameraSettings.html file. You might forgot to copy it from the examples folder.") 25 | raise e 26 | async def stream_camera(writer): 27 | try: 28 | cam.init() 29 | await asyncio.sleep(1) 30 | 31 | writer.write(b'HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n') 32 | await writer.drain() 33 | 34 | while True: 35 | frame = await cam.acapture() # This is the async version of capture, you can also use frame = cam.capture() instead 36 | if frame: 37 | writer.write(b'--frame\r\nContent-Type: image/jpeg\r\n\r\n') 38 | writer.write(frame) 39 | await writer.drain() 40 | 41 | finally: 42 | cam.deinit() 43 | writer.close() 44 | await writer.wait_closed() 45 | print("Streaming stopped and camera deinitialized.") 46 | 47 | async def handle_client(reader, writer): 48 | try: 49 | request = await reader.read(1024) 50 | request = request.decode() 51 | 52 | if 'GET /stream' in request: 53 | print("Start streaming...") 54 | await stream_camera(writer) 55 | 56 | elif 'GET /set_' in request: 57 | method_name = request.split('GET /set_')[1].split('?')[0] 58 | value = int(request.split('value=')[1].split(' ')[0]) 59 | set_method = getattr(cam, f'set_{method_name}', None) 60 | if callable(set_method): 61 | print(f"setting {method_name} to {value}") 62 | set_method(value) 63 | response = 'HTTP/1.1 200 OK\r\n\r\n' 64 | writer.write(response.encode()) 65 | await writer.drain() 66 | else: 67 | try: 68 | cam.reconfigure(**{method_name: value}) 69 | print(f"Camera reconfigured with {method_name}={value}") 70 | print("This action restores all previous configuration!") 71 | response = 'HTTP/1.1 200 OK\r\n\r\n' 72 | except Exception as e: 73 | print(f"Error with {method_name}: {e}") 74 | response = 'HTTP/1.1 404 Not Found\r\n\r\n' 75 | writer.write(response.encode()) 76 | await writer.drain() 77 | 78 | elif 'GET /get_' in request: 79 | method_name = request.split('GET /get_')[1].split(' ')[0] 80 | get_method = getattr(cam, f'get_{method_name}', None) 81 | if callable(get_method): 82 | value = get_method() 83 | print(f"{method_name} is {value}") 84 | response = f'HTTP/1.1 200 OK\r\n\r\n{value}' 85 | writer.write(response.encode()) 86 | await writer.drain() 87 | else: 88 | response = 'HTTP/1.1 404 Not Found\r\n\r\n' 89 | writer.write(response.encode()) 90 | await writer.drain() 91 | 92 | else: 93 | writer.write('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n'.encode() + html.encode()) 94 | await writer.drain() 95 | except Exception as e: 96 | print(f"Error: {e}") 97 | finally: 98 | writer.close() 99 | await writer.wait_closed() 100 | 101 | async def start_server(): 102 | server = await asyncio.start_server(handle_client, "0.0.0.0", 80) 103 | print(f'Server is running on {station.ifconfig()[0]}:80') 104 | while True: 105 | await asyncio.sleep(3600) 106 | 107 | try: 108 | asyncio.run(start_server()) 109 | except KeyboardInterrupt: 110 | cam.deinit() 111 | print("Server stopped") 112 | 113 | -------------------------------------------------------------------------------- /examples/SimpleWebCam.py: -------------------------------------------------------------------------------- 1 | import network 2 | import socket 3 | import time 4 | 5 | from camera import Camera, FrameSize, PixelFormat 6 | # Cam Config 7 | cam = Camera(frame_size = FrameSize.VGA,pixel_format=PixelFormat.JPEG,init=False) 8 | 9 | # WLAN config 10 | ssid = '' 11 | password = '' 12 | 13 | station = network.WLAN(network.STA_IF) 14 | station.active(True) 15 | station.connect(ssid, password) 16 | 17 | while not station.isconnected(): 18 | time.sleep(1) 19 | 20 | print(f'Connected! IP: {station.ifconfig()[0]}. Open this IP in your browser') 21 | 22 | # HTTP-Server starten 23 | addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] 24 | s = socket.socket() 25 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 26 | s.bind(addr) 27 | s.listen(1) 28 | 29 | print('Server on:', addr) 30 | 31 | html = """ 32 | 33 | 34 | Micropython Camera Stream 35 | 54 | 55 | 56 |
57 |

ESP32 Camera Stream

58 | 59 |
60 | 61 | 62 | """ 63 | 64 | def handle_client(client): 65 | try: 66 | request = client.recv(1024).decode() 67 | if 'GET /stream' in request: 68 | response = b'HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n' 69 | client.send(response) 70 | cam.init() 71 | while cam: 72 | frame = cam.capture() 73 | if frame: 74 | client.send(b'--frame\r\n') 75 | client.send(b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') 76 | else: 77 | break 78 | else: 79 | response = 'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n' + html 80 | client.send(response.encode()) 81 | except OSError as e: 82 | print('Error:', e) 83 | finally: 84 | client.close() 85 | cam.deinit() 86 | 87 | while True: 88 | client, addr = s.accept() 89 | print('Connection from:', addr) 90 | handle_client(client) 91 | -------------------------------------------------------------------------------- /examples/benchmark.py: -------------------------------------------------------------------------------- 1 | from camera import Camera, FrameSize, PixelFormat 2 | import time 3 | import gc 4 | import os 5 | gc.enable() 6 | 7 | def measure_fps(duration=2): 8 | start_time = time.ticks_ms() 9 | while time.ticks_ms() - start_time < 500: 10 | cam.capture() 11 | 12 | start_time = time.ticks_ms() 13 | frame_count = 0 14 | 15 | while time.ticks_ms() - start_time < duration*1000: 16 | img = cam.capture() 17 | if img: 18 | frame_count += 1 19 | 20 | end_time = time.ticks_ms() 21 | fps = frame_count / (end_time - start_time) * 1000 22 | return round(fps,1) 23 | 24 | def print_summary_table(results, cam): 25 | print(f"\nBenchmark {os.uname().machine} with {cam.get_sensor_name()}, GrabMode: {cam.get_grab_mode()}:") 26 | 27 | fb_counts = sorted(results.keys()) 28 | frame_size_names = {getattr(FrameSize, f): f for f in dir(FrameSize) if not f.startswith('_')} 29 | 30 | header_row = f"{'Frame Size':<15}" 31 | sub_header_row = " " * 15 32 | 33 | for fb in fb_counts: 34 | for p in results[fb].keys(): 35 | header_row += f"{'fb_count ' + str(fb):<15}" 36 | sub_header_row += f"{p:<15}" 37 | 38 | print(header_row) 39 | print(sub_header_row) 40 | 41 | frame_sizes = list(next(iter(next(iter(results.values())).values())).keys()) 42 | 43 | for f in frame_sizes: 44 | frame_size_name = frame_size_names.get(f, str(f)) 45 | print(f"{frame_size_name:<15}", end="") 46 | 47 | for fb in fb_counts: 48 | for p in results[fb].keys(): 49 | fps = results[fb][p].get(f, "N/A") 50 | print(f"{fps:<15}", end="") 51 | print() 52 | 53 | if __name__ == "__main__": 54 | cam = Camera() 55 | results = {} 56 | 57 | try: 58 | for fb in [1, 2]: 59 | cam.reconfigure(fb_count=fb) 60 | results[fb] = {} 61 | for p in dir(PixelFormat): 62 | if not p.startswith('_'): 63 | p_value = getattr(PixelFormat, p) 64 | if (p_value == PixelFormat.RGB888 and cam.get_sensor_name() == "OV2640") or (p_value != PixelFormat.JPEG and fb > 1): 65 | continue 66 | try: 67 | cam.reconfigure(pixel_format=p_value) 68 | results[fb][p] = {} 69 | except Exception as e: 70 | print('ERR:', e) 71 | continue 72 | 73 | for f in dir(FrameSize): 74 | if not f.startswith('_'): 75 | f_value = getattr(FrameSize, f) 76 | if f_value > cam.get_max_frame_size(): 77 | continue 78 | gc.collect() 79 | print('Set', p, f,f'fb={fb}',':') 80 | 81 | try: 82 | cam.reconfigure(frame_size=f_value) #set_frame_size fails for YUV422 83 | time.sleep_ms(10) 84 | img = cam.capture() 85 | 86 | if img: 87 | print('---> Image size:', len(img)) 88 | fps = measure_fps(2) 89 | print(f"---> FPS: {fps}") 90 | results[fb][p][f_value] = fps 91 | else: 92 | print('No image captured') 93 | results[fb][p][f_value] = 'No img' 94 | 95 | print(f"---> Free Memory: {gc.mem_free()}") 96 | except Exception as e: 97 | print('ERR:', e) 98 | results[fb][p][f_value] = 'ERR' 99 | finally: 100 | time.sleep_ms(250) 101 | gc.collect() 102 | print('') 103 | 104 | except KeyboardInterrupt: 105 | print("\nScript interrupted by user.") 106 | 107 | finally: 108 | print_summary_table(results, cam) 109 | cam.deinit() 110 | -------------------------------------------------------------------------------- /examples/benchmark_img_conv.py: -------------------------------------------------------------------------------- 1 | from camera import Camera, FrameSize, PixelFormat 2 | import time 3 | import gc 4 | import os 5 | gc.enable() 6 | 7 | def measure_fps(cam,out_fmt,duration=2): 8 | start_time = time.ticks_ms() 9 | while time.ticks_ms() - start_time < 500: 10 | cam.capture(out_fmt) 11 | 12 | start_time = time.ticks_ms() 13 | frame_count = 0 14 | 15 | while time.ticks_ms() - start_time < duration*1000: 16 | img = cam.capture(out_fmt) 17 | if img: 18 | frame_count += 1 19 | 20 | end_time = time.ticks_ms() 21 | fps = frame_count / (end_time - start_time) * 1000 22 | return round(fps,1) 23 | 24 | def print_summary_table(results, cam): 25 | print(f"\nBenchmark {os.uname().machine} with {cam.get_sensor_name()}, GrabMode: {cam.get_grab_mode()}:") 26 | 27 | fb_counts = sorted(results.keys()) 28 | frame_size_names = {getattr(FrameSize, f): f for f in dir(FrameSize) if not f.startswith('_')} 29 | 30 | header_row = f"{'Frame Size':<15}" 31 | sub_header_row = " " * 15 32 | 33 | for fb in fb_counts: 34 | for p in results[fb].keys(): 35 | header_row += f"{'fb_count ' + str(fb):<15}" 36 | sub_header_row += f"{p:<15}" 37 | 38 | print(header_row) 39 | print(sub_header_row) 40 | 41 | frame_sizes = list(next(iter(next(iter(results.values())).values())).keys()) 42 | 43 | for f in frame_sizes: 44 | frame_size_name = frame_size_names.get(f, str(f)) 45 | print(f"{frame_size_name:<15}", end="") 46 | 47 | for fb in fb_counts: 48 | for p in results[fb].keys(): 49 | fps = results[fb][p].get(f, "N/A") 50 | print(f"{fps:<15}", end="") 51 | print() 52 | 53 | if __name__ == "__main__": 54 | cam = Camera(pixel_format=PixelFormat.JPEG) 55 | results = {} 56 | 57 | try: 58 | for fb in [1, 2]: 59 | cam.reconfigure(fb_count=fb, frame_size=FrameSize.QQVGA) 60 | results[fb] = {} 61 | for p in dir(PixelFormat): 62 | if not p.startswith('_'): 63 | p_value = getattr(PixelFormat, p) 64 | try: 65 | if p_value == PixelFormat.JPEG: 66 | continue 67 | cam.capture(p_value) 68 | results[fb][p] = {} 69 | gc.collect() 70 | except: 71 | continue 72 | for f in dir(FrameSize): 73 | if not f.startswith('_'): 74 | f_value = getattr(FrameSize, f) 75 | if f_value > cam.get_max_frame_size(): 76 | continue 77 | gc.collect() 78 | print('Set', p, f,f'fb={fb}',':') 79 | 80 | try: 81 | cam.set_frame_size(f_value) 82 | time.sleep_ms(10) 83 | img = cam.capture(p_value) 84 | if img: 85 | print('---> Image size:', len(img)) 86 | fps = measure_fps(cam,p_value,2) 87 | print(f"---> FPS: {fps}") 88 | results[fb][p][f_value] = fps 89 | else: 90 | print('No image captured') 91 | results[fb][p][f_value] = 'No img' 92 | 93 | print(f"---> Free Memory: {gc.mem_free()}") 94 | except Exception as e: 95 | print('ERR:', e) 96 | results[fb][p][f_value] = 'ERR' 97 | finally: 98 | time.sleep_ms(250) 99 | gc.collect() 100 | print('') 101 | 102 | except KeyboardInterrupt: 103 | print("\nScript interrupted by user.") 104 | 105 | finally: 106 | print_summary_table(results, cam) 107 | cam.deinit() 108 | -------------------------------------------------------------------------------- /src/acamera.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from camera import Camera as _Camera 3 | from camera import FrameSize, PixelFormat, GainCeiling, GrabMode 4 | 5 | class Camera(_Camera): 6 | async def acapture(self): 7 | self.free_buffer() # Free the buffer so the camera task grabs a new frame 8 | while not self.frame_available(): 9 | await asyncio.sleep(0) # Yield control to the event loop 10 | return self.capture() 11 | 12 | __all__ = ['Camera', 'FrameSize', 'PixelFormat', 'GainCeiling', 'GrabMode'] -------------------------------------------------------------------------------- /src/camera_pins.h: -------------------------------------------------------------------------------- 1 | // Camera pins definitions for different boards 2 | #ifndef MICROPY_CAMERA_MODEL_PINS_H 3 | #define MICROPY_CAMERA_MODEL_PINS_H 4 | 5 | #if defined(MICROPY_CAMERA_MODEL_WROVER_KIT) 6 | #define MICROPY_CAMERA_PIN_PWDN -1 7 | #define MICROPY_CAMERA_PIN_RESET -1 8 | #define MICROPY_CAMERA_PIN_XCLK 21 9 | #define MICROPY_CAMERA_PIN_SIOD 26 10 | #define MICROPY_CAMERA_PIN_SIOC 27 11 | 12 | #define MICROPY_CAMERA_PIN_D7 35 13 | #define MICROPY_CAMERA_PIN_D6 34 14 | #define MICROPY_CAMERA_PIN_D5 39 15 | #define MICROPY_CAMERA_PIN_D4 36 16 | #define MICROPY_CAMERA_PIN_D3 19 17 | #define MICROPY_CAMERA_PIN_D2 18 18 | #define MICROPY_CAMERA_PIN_D1 5 19 | #define MICROPY_CAMERA_PIN_D0 4 20 | #define MICROPY_CAMERA_PIN_VSYNC 25 21 | #define MICROPY_CAMERA_PIN_HREF 23 22 | #define MICROPY_CAMERA_PIN_PCLK 22 23 | 24 | #elif defined(MICROPY_CAMERA_MODEL_ESP_EYE) 25 | #define MICROPY_CAMERA_PIN_PWDN -1 26 | #define MICROPY_CAMERA_PIN_RESET -1 27 | #define MICROPY_CAMERA_PIN_XCLK 4 28 | #define MICROPY_CAMERA_PIN_SIOD 18 29 | #define MICROPY_CAMERA_PIN_SIOC 23 30 | 31 | #define MICROPY_CAMERA_PIN_D7 36 32 | #define MICROPY_CAMERA_PIN_D6 37 33 | #define MICROPY_CAMERA_PIN_D5 38 34 | #define MICROPY_CAMERA_PIN_D4 39 35 | #define MICROPY_CAMERA_PIN_D3 35 36 | #define MICROPY_CAMERA_PIN_D2 14 37 | #define MICROPY_CAMERA_PIN_D1 13 38 | #define MICROPY_CAMERA_PIN_D0 34 39 | #define MICROPY_CAMERA_PIN_VSYNC 5 40 | #define MICROPY_CAMERA_PIN_HREF 27 41 | #define MICROPY_CAMERA_PIN_PCLK 25 42 | 43 | #elif defined(MICROPY_CAMERA_MODEL_M5STACK_PSRAM) || defined(MICROPY_CAMERA_MODEL_M5STACK_UNITCAM) 44 | #define MICROPY_CAMERA_PIN_PWDN -1 45 | #define MICROPY_CAMERA_PIN_RESET 15 46 | #define MICROPY_CAMERA_PIN_XCLK 27 47 | #define MICROPY_CAMERA_PIN_SIOD 25 48 | #define MICROPY_CAMERA_PIN_SIOC 23 49 | 50 | #define MICROPY_CAMERA_PIN_D7 19 51 | #define MICROPY_CAMERA_PIN_D6 36 52 | #define MICROPY_CAMERA_PIN_D5 18 53 | #define MICROPY_CAMERA_PIN_D4 39 54 | #define MICROPY_CAMERA_PIN_D3 5 55 | #define MICROPY_CAMERA_PIN_D2 34 56 | #define MICROPY_CAMERA_PIN_D1 35 57 | #define MICROPY_CAMERA_PIN_D0 32 58 | #define MICROPY_CAMERA_PIN_VSYNC 22 59 | #define MICROPY_CAMERA_PIN_HREF 26 60 | #define MICROPY_CAMERA_PIN_PCLK 21 61 | 62 | #elif defined(MICROPY_CAMERA_MODEL_M5STACK_V2_PSRAM) || defined(MICROPY_CAMERA_MODEL_M5STACK_WIDE) 63 | #define MICROPY_CAMERA_PIN_PWDN -1 64 | #define MICROPY_CAMERA_PIN_RESET 15 65 | #define MICROPY_CAMERA_PIN_XCLK 27 66 | #define MICROPY_CAMERA_PIN_SIOD 22 67 | #define MICROPY_CAMERA_PIN_SIOC 23 68 | 69 | #define MICROPY_CAMERA_PIN_D7 19 70 | #define MICROPY_CAMERA_PIN_D6 36 71 | #define MICROPY_CAMERA_PIN_D5 18 72 | #define MICROPY_CAMERA_PIN_D4 39 73 | #define MICROPY_CAMERA_PIN_D3 5 74 | #define MICROPY_CAMERA_PIN_D2 34 75 | #define MICROPY_CAMERA_PIN_D1 35 76 | #define MICROPY_CAMERA_PIN_D0 32 77 | #define MICROPY_CAMERA_PIN_VSYNC 25 78 | #define MICROPY_CAMERA_PIN_HREF 26 79 | #define MICROPY_CAMERA_PIN_PCLK 21 80 | 81 | #elif defined(MICROPY_CAMERA_MODEL_M5STACK_ESP32CAM) 82 | #define MICROPY_CAMERA_PIN_PWDN -1 83 | #define MICROPY_CAMERA_PIN_RESET 15 84 | #define MICROPY_CAMERA_PIN_XCLK 27 85 | #define MICROPY_CAMERA_PIN_SIOD 25 86 | #define MICROPY_CAMERA_PIN_SIOC 23 87 | 88 | #define MICROPY_CAMERA_PIN_D7 19 89 | #define MICROPY_CAMERA_PIN_D6 36 90 | #define MICROPY_CAMERA_PIN_D5 18 91 | #define MICROPY_CAMERA_PIN_D4 39 92 | #define MICROPY_CAMERA_PIN_D3 5 93 | #define MICROPY_CAMERA_PIN_D2 34 94 | #define MICROPY_CAMERA_PIN_D1 35 95 | #define MICROPY_CAMERA_PIN_D0 17 96 | #define MICROPY_CAMERA_PIN_VSYNC 22 97 | #define MICROPY_CAMERA_PIN_HREF 26 98 | #define MICROPY_CAMERA_PIN_PCLK 21 99 | 100 | #elif defined(MICROPY_CAMERA_MODEL_M5STACK_CAMS3_UNIT) 101 | #define MICROPY_CAMERA_PIN_PWDN -1 102 | #define MICROPY_CAMERA_PIN_RESET 21 103 | #define MICROPY_CAMERA_PIN_XCLK 11 104 | #define MICROPY_CAMERA_PIN_SIOD 17 105 | #define MICROPY_CAMERA_PIN_SIOC 41 106 | 107 | #define MICROPY_CAMERA_PIN_D7 13 108 | #define MICROPY_CAMERA_PIN_D6 4 109 | #define MICROPY_CAMERA_PIN_D5 10 110 | #define MICROPY_CAMERA_PIN_D4 5 111 | #define MICROPY_CAMERA_PIN_D3 7 112 | #define MICROPY_CAMERA_PIN_D2 16 113 | #define MICROPY_CAMERA_PIN_D1 15 114 | #define MICROPY_CAMERA_PIN_D0 6 115 | #define MICROPY_CAMERA_PIN_VSYNC 42 116 | #define MICROPY_CAMERA_PIN_HREF 18 117 | #define MICROPY_CAMERA_PIN_PCLK 12 118 | 119 | #elif defined(MICROPY_CAMERA_MODEL_AI_THINKER) 120 | #define MICROPY_CAMERA_PIN_PWDN 32 121 | #define MICROPY_CAMERA_PIN_RESET -1 122 | #define MICROPY_CAMERA_PIN_XCLK 0 123 | #define MICROPY_CAMERA_PIN_SIOD 26 124 | #define MICROPY_CAMERA_PIN_SIOC 27 125 | 126 | #define MICROPY_CAMERA_PIN_D7 35 127 | #define MICROPY_CAMERA_PIN_D6 34 128 | #define MICROPY_CAMERA_PIN_D5 39 129 | #define MICROPY_CAMERA_PIN_D4 36 130 | #define MICROPY_CAMERA_PIN_D3 21 131 | #define MICROPY_CAMERA_PIN_D2 19 132 | #define MICROPY_CAMERA_PIN_D1 18 133 | #define MICROPY_CAMERA_PIN_D0 5 134 | #define MICROPY_CAMERA_PIN_VSYNC 25 135 | #define MICROPY_CAMERA_PIN_HREF 23 136 | #define MICROPY_CAMERA_PIN_PCLK 22 137 | 138 | #elif defined(MICROPY_CAMERA_MODEL_XIAO_ESP32S3) 139 | #define MICROPY_CAMERA_PIN_PWDN -1 140 | #define MICROPY_CAMERA_PIN_RESET -1 141 | #define MICROPY_CAMERA_PIN_XCLK 10 142 | #define MICROPY_CAMERA_PIN_SIOD 40 143 | #define MICROPY_CAMERA_PIN_SIOC 39 144 | 145 | #define MICROPY_CAMERA_PIN_D7 48 146 | #define MICROPY_CAMERA_PIN_D6 11 147 | #define MICROPY_CAMERA_PIN_D5 12 148 | #define MICROPY_CAMERA_PIN_D4 14 149 | #define MICROPY_CAMERA_PIN_D3 16 150 | #define MICROPY_CAMERA_PIN_D2 18 151 | #define MICROPY_CAMERA_PIN_D1 17 152 | #define MICROPY_CAMERA_PIN_D0 15 153 | #define MICROPY_CAMERA_PIN_VSYNC 38 154 | #define MICROPY_CAMERA_PIN_HREF 47 155 | #define MICROPY_CAMERA_PIN_PCLK 13 156 | 157 | #elif defined(MICROPY_CAMERA_MODEL_ESP32_MP_CAMERA_BOARD) 158 | // The 18 pin header on the board has Y5 and Y3 swapped 159 | #define ESP32_MP_CAMERA_BOARD_HEADER 0 160 | #define MICROPY_CAMERA_PIN_PWDN 32 161 | #define MICROPY_CAMERA_PIN_RESET 33 162 | #define MICROPY_CAMERA_PIN_XCLK 4 163 | #define MICROPY_CAMERA_PIN_SIOD 18 164 | #define MICROPY_CAMERA_PIN_SIOC 23 165 | 166 | #define MICROPY_CAMERA_PIN_D7 36 167 | #define MICROPY_CAMERA_PIN_D6 19 168 | #define MICROPY_CAMERA_PIN_D5 21 169 | #define MICROPY_CAMERA_PIN_D4 39 170 | #if ESP32_MP_CAMERA_BOARD_HEADER 171 | #define MICROPY_CAMERA_PIN_D3 13 172 | #else 173 | #define MICROPY_CAMERA_PIN_D3 35 174 | #endif 175 | #define MICROPY_CAMERA_PIN_D2 14 176 | #if ESP32_MP_CAMERA_BOARD_HEADER 177 | #define MICROPY_CAMERA_PIN_D1 35 178 | #else 179 | #define MICROPY_CAMERA_PIN_D1 13 180 | #endif 181 | #define MICROPY_CAMERA_PIN_D0 34 182 | #define MICROPY_CAMERA_PIN_VSYNC 5 183 | #define MICROPY_CAMERA_PIN_HREF 27 184 | #define MICROPY_CAMERA_PIN_PCLK 25 185 | 186 | #elif defined(MICROPY_CAMERA_MODEL_ESP32S3_CAM_LCD) 187 | #define MICROPY_CAMERA_PIN_PWDN -1 188 | #define MICROPY_CAMERA_PIN_RESET -1 189 | #define MICROPY_CAMERA_PIN_XCLK 40 190 | #define MICROPY_CAMERA_PIN_SIOD 17 191 | #define MICROPY_CAMERA_PIN_SIOC 18 192 | 193 | #define MICROPY_CAMERA_PIN_D7 39 194 | #define MICROPY_CAMERA_PIN_D6 41 195 | #define MICROPY_CAMERA_PIN_D5 42 196 | #define MICROPY_CAMERA_PIN_D4 12 197 | #define MICROPY_CAMERA_PIN_D3 3 198 | #define MICROPY_CAMERA_PIN_D2 14 199 | #define MICROPY_CAMERA_PIN_D1 47 200 | #define MICROPY_CAMERA_PIN_D0 13 201 | #define MICROPY_CAMERA_PIN_VSYNC 21 202 | #define MICROPY_CAMERA_PIN_HREF 38 203 | #define MICROPY_CAMERA_PIN_PCLK 11 204 | 205 | #elif defined(MICROPY_CAMERA_MODEL_ESP32S3_EYE) || defined(MICROPY_CAMERA_MODEL_FREENOVE_ESP32S3_CAM) 206 | #define MICROPY_CAMERA_PIN_PWDN -1 207 | #define MICROPY_CAMERA_PIN_RESET -1 208 | #define MICROPY_CAMERA_PIN_XCLK 15 209 | #define MICROPY_CAMERA_PIN_SIOD 4 210 | #define MICROPY_CAMERA_PIN_SIOC 5 211 | 212 | #define MICROPY_CAMERA_PIN_D0 11 213 | #define MICROPY_CAMERA_PIN_D1 9 214 | #define MICROPY_CAMERA_PIN_D2 8 215 | #define MICROPY_CAMERA_PIN_D3 10 216 | #define MICROPY_CAMERA_PIN_D4 12 217 | #define MICROPY_CAMERA_PIN_D5 18 218 | #define MICROPY_CAMERA_PIN_D6 17 219 | #define MICROPY_CAMERA_PIN_D7 16 220 | #define MICROPY_CAMERA_PIN_VSYNC 6 221 | #define MICROPY_CAMERA_PIN_HREF 7 222 | #define MICROPY_CAMERA_PIN_PCLK 13 223 | 224 | #elif defined(MICROPY_CAMERA_MODEL_DFRobot_ESP32S3) 225 | #define MICROPY_CAMERA_PIN_PWDN -1 226 | #define MICROPY_CAMERA_PIN_RESET -1 227 | #define MICROPY_CAMERA_PIN_XCLK 45 228 | #define MICROPY_CAMERA_PIN_SIOD 1 229 | #define MICROPY_CAMERA_PIN_SIOC 2 230 | 231 | #define MICROPY_CAMERA_PIN_D7 48 232 | #define MICROPY_CAMERA_PIN_D6 46 233 | #define MICROPY_CAMERA_PIN_D5 8 234 | #define MICROPY_CAMERA_PIN_D4 7 235 | #define MICROPY_CAMERA_PIN_D3 4 236 | #define MICROPY_CAMERA_PIN_D2 41 237 | #define MICROPY_CAMERA_PIN_D1 40 238 | #define MICROPY_CAMERA_PIN_D0 39 239 | #define MICROPY_CAMERA_PIN_VSYNC 6 240 | #define MICROPY_CAMERA_PIN_HREF 42 241 | #define MICROPY_CAMERA_PIN_PCLK 5 242 | 243 | #elif defined(MICROPY_CAMERA_MODEL_TTGO_T_JOURNAL) 244 | #define MICROPY_CAMERA_PIN_PWDN 0 245 | #define MICROPY_CAMERA_PIN_RESET 15 246 | #define MICROPY_CAMERA_PIN_XCLK 27 247 | #define MICROPY_CAMERA_PIN_SIOD 25 248 | #define MICROPY_CAMERA_PIN_SIOC 23 249 | 250 | #define MICROPY_CAMERA_PIN_D7 19 251 | #define MICROPY_CAMERA_PIN_D6 36 252 | #define MICROPY_CAMERA_PIN_D5 18 253 | #define MICROPY_CAMERA_PIN_D4 39 254 | #define MICROPY_CAMERA_PIN_D3 5 255 | #define MICROPY_CAMERA_PIN_D2 34 256 | #define MICROPY_CAMERA_PIN_D1 35 257 | #define MICROPY_CAMERA_PIN_D0 17 258 | #define MICROPY_CAMERA_PIN_VSYNC 22 259 | #define MICROPY_CAMERA_PIN_HREF 26 260 | #define MICROPY_CAMERA_PIN_PCLK 21 261 | 262 | #elif defined(MICROPY_CAMERA_MODEL_TTGO_T_CAMERA_PLUS) 263 | #define MICROPY_CAMERA_PIN_PWDN -1 264 | #define MICROPY_CAMERA_PIN_RESET -1 265 | #define MICROPY_CAMERA_PIN_XCLK 4 266 | #define MICROPY_CAMERA_PIN_SIOD 18 267 | #define MICROPY_CAMERA_PIN_SIOC 23 268 | 269 | #define MICROPY_CAMERA_PIN_D7 36 270 | #define MICROPY_CAMERA_PIN_D6 37 271 | #define MICROPY_CAMERA_PIN_D5 38 272 | #define MICROPY_CAMERA_PIN_D4 39 273 | #define MICROPY_CAMERA_PIN_D3 35 274 | #define MICROPY_CAMERA_PIN_D2 26 275 | #define MICROPY_CAMERA_PIN_D1 13 276 | #define MICROPY_CAMERA_PIN_D0 34 277 | #define MICROPY_CAMERA_PIN_VSYNC 5 278 | #define MICROPY_CAMERA_PIN_HREF 27 279 | #define MICROPY_CAMERA_PIN_PCLK 25 280 | 281 | #elif defined(MICROPY_CAMERA_MODEL_NEW_ESPS3_RE1_0) 282 | // aliexpress board with label RE:1.0, uses slow 8MB QSPI PSRAM, only 4MB addressable 283 | #define MICROPY_CAMERA_PIN_PWDN -1 284 | #define MICROPY_CAMERA_PIN_RESET -1 285 | #define MICROPY_CAMERA_PIN_XCLK 10 286 | #define MICROPY_CAMERA_PIN_SIOD 21 287 | #define MICROPY_CAMERA_PIN_SIOC 14 288 | 289 | #define MICROPY_CAMERA_PIN_D7 11 290 | #define MICROPY_CAMERA_PIN_D6 9 291 | #define MICROPY_CAMERA_PIN_D5 8 292 | #define MICROPY_CAMERA_PIN_D4 6 293 | #define MICROPY_CAMERA_PIN_D3 4 294 | #define MICROPY_CAMERA_PIN_D2 2 295 | #define MICROPY_CAMERA_PIN_D1 3 296 | #define MICROPY_CAMERA_PIN_D0 5 297 | #define MICROPY_CAMERA_PIN_VSYNC 13 298 | #define MICROPY_CAMERA_PIN_HREF 12 299 | #define MICROPY_CAMERA_PIN_PCLK 7 300 | 301 | #elif defined(MICROPY_CAMERA_MODEL_XENOIONEX) 302 | #define MICROPY_CAMERA_PIN_PWDN -1 303 | #define MICROPY_CAMERA_PIN_RESET -1 304 | #define MICROPY_CAMERA_PIN_XCLK 1 // Can use 305 | #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 306 | #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 307 | 308 | #define MICROPY_CAMERA_PIN_D7 3 //D7 309 | #define MICROPY_CAMERA_PIN_D6 18 //D6 310 | #define MICROPY_CAMERA_PIN_D5 42 //D5 311 | #define MICROPY_CAMERA_PIN_D4 16 //D4 312 | #define MICROPY_CAMERA_PIN_D3 41 //D3 313 | #define MICROPY_CAMERA_PIN_D2 17 //D2 314 | #define MICROPY_CAMERA_PIN_D1 40 //D1 315 | #define MICROPY_CAMERA_PIN_D0 39 //D0 316 | #define MICROPY_CAMERA_PIN_VSYNC 45 317 | #define MICROPY_CAMERA_PIN_HREF 38 318 | #define MICROPY_CAMERA_PIN_PCLK 2 319 | 320 | #endif // definition of camera pins for different boards 321 | #endif // MICROPY_CAMERA_MODEL_PINS_H -------------------------------------------------------------------------------- /src/manifest.py: -------------------------------------------------------------------------------- 1 | # Include the board's default manifest. 2 | include("$(PORT_DIR)/boards/manifest.py") 3 | # Add custom driver 4 | module("acamera.py") -------------------------------------------------------------------------------- /src/micropython.cmake: -------------------------------------------------------------------------------- 1 | include(${MICROPY_DIR}/py/py.cmake) 2 | 3 | set(MICROPY_FROZEN_MANIFEST ${CMAKE_CURRENT_LIST_DIR}/manifest.py) 4 | 5 | add_library(usermod_mp_camera INTERFACE) 6 | 7 | add_dependencies(usermod_mp_camera esp32_camera) 8 | 9 | target_sources(usermod_mp_camera INTERFACE 10 | ${CMAKE_CURRENT_LIST_DIR}/modcamera.c 11 | ${CMAKE_CURRENT_LIST_DIR}/modcamera_api.c 12 | ) 13 | 14 | # Prefer user-defined ESP32_CAMERA_DIR if provided 15 | if(DEFINED ESP32_CAMERA_DIR AND EXISTS "${ESP32_CAMERA_DIR}") 16 | message(STATUS "Using user-defined ESP32 Camera directory: ${ESP32_CAMERA_DIR}") 17 | 18 | target_include_directories(usermod_mp_camera INTERFACE 19 | ${CMAKE_CURRENT_LIST_DIR} 20 | ${ESP32_CAMERA_DIR}/driver/include 21 | ${ESP32_CAMERA_DIR}/conversions/include 22 | ${ESP32_CAMERA_DIR}/driver/private_include 23 | ${ESP32_CAMERA_DIR}/conversions/private_include 24 | ${ESP32_CAMERA_DIR}/sensors/private_include 25 | ) 26 | 27 | # If no manual directory is provided, try to fetch it from ESP-IDF 28 | elseif(EXISTS ${IDF_PATH}/components/esp32-camera) 29 | idf_component_get_property(CAMERA_INCLUDES esp32-camera INCLUDE_DIRS) 30 | idf_component_get_property(CAMERA_PRIV_INCLUDES esp32-camera PRIV_INCLUDE_DIRS) 31 | idf_component_get_property(CAMERA_DIR esp32-camera COMPONENT_DIR) 32 | 33 | if(CAMERA_DIR) 34 | message(STATUS "Using ESP32 Camera component from ESP-IDF: ${CAMERA_DIR}") 35 | 36 | # Add public include directories from ESP-IDF 37 | if(CAMERA_INCLUDES) 38 | list(TRANSFORM CAMERA_INCLUDES PREPEND ${CAMERA_DIR}/) 39 | target_include_directories(usermod_mp_camera INTERFACE ${CAMERA_INCLUDES}) 40 | endif() 41 | 42 | # Add private include directories from ESP-IDF 43 | if(CAMERA_PRIV_INCLUDES) 44 | list(TRANSFORM CAMERA_PRIV_INCLUDES PREPEND ${CAMERA_DIR}/) 45 | target_include_directories(usermod_mp_camera INTERFACE ${CAMERA_PRIV_INCLUDES}) 46 | endif() 47 | else() 48 | message(WARNING "ESP32 Camera component not found in ESP-IDF!") 49 | target_include_directories(usermod_mp_camera PUBLIC ${CMAKE_CURRENT_LIST_DIR}) 50 | endif() 51 | endif() 52 | 53 | # Check if MP_JPEG_DIR is set or if mp_jpeg directory exists two levels up 54 | if(DEFINED MP_JPEG_DIR AND EXISTS "${MP_JPEG_DIR}") 55 | message(STATUS "Using user-defined MP_JPEG_DIR: ${MP_JPEG_DIR}") 56 | set(MP_JPEG_SRC "${MP_JPEG_DIR}/src/micropython.cmake") 57 | elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../mp_jpeg") 58 | message(STATUS "Found mp_jpeg directory two levels up") 59 | set(MP_JPEG_SRC "${CMAKE_CURRENT_LIST_DIR}/../../mp_jpeg/src/micropython.cmake") 60 | endif() 61 | 62 | # Add MP_JPEG_SRC cmake file to target_sources if it is defined 63 | if(DEFINED MP_JPEG_SRC AND EXISTS "${MP_JPEG_SRC}") 64 | include(${MP_JPEG_SRC}) 65 | else() 66 | message(WARNING "MP_JPEG_SRC not found or not defined!") 67 | endif() 68 | 69 | # Define MICROPY_CAMERA_MODEL if specified 70 | if (MICROPY_CAMERA_MODEL) 71 | message(STATUS "Using user-defined camera model: ${MICROPY_CAMERA_MODEL}") 72 | target_compile_definitions(usermod_mp_camera INTERFACE 73 | MICROPY_CAMERA_MODEL_${MICROPY_CAMERA_MODEL}=1 74 | ) 75 | endif() 76 | 77 | # Define MP_CAMERA_DRIVER_VERSION if specified 78 | if (MP_CAMERA_DRIVER_VERSION) 79 | target_compile_definitions(usermod_mp_camera INTERFACE 80 | MP_CAMERA_DRIVER_VERSION=\"${MP_CAMERA_DRIVER_VERSION}\" 81 | ) 82 | endif() 83 | 84 | # Link the camera module with the main usermod target 85 | target_link_libraries(usermod INTERFACE usermod_mp_camera) 86 | 87 | # Gather target properties for MicroPython build system 88 | micropy_gather_target_properties(usermod_mp_camera) 89 | -------------------------------------------------------------------------------- /src/micropython.mk: -------------------------------------------------------------------------------- 1 | CAMERA_MOD_DIR := $(USERMOD_DIR) 2 | SRC_USERMOD_C += $(addprefix $(CAMERA_MOD_DIR)/, modcamera_api.c) 3 | SRC_USERMOD_LIB_C += $(addprefix $(CAMERA_MOD_DIR)/, modcamera.c) 4 | CFLAGS_USERMOD += -I$(CAMERA_MOD_DIR) -------------------------------------------------------------------------------- /src/modcamera.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Code based on circuitpython camera API by Jeff Epler 5 | * Copyright (c) 2022 Jeff Epler for Adafruit Industries 6 | * Adaptation to MicroPython by Christopher Nadler 7 | * Copyright (c) 2024 Christopher Nadler 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | 28 | #include "modcamera.h" 29 | #include "esp_err.h" 30 | #include "esp_log.h" 31 | #include "mphalport.h" 32 | 33 | #define TAG "MPY_CAMERA" 34 | 35 | #if !CONFIG_SPIRAM 36 | #error Camera only works on boards configured with spiram 37 | #endif 38 | 39 | // Helper functions 40 | static int map(int value, int fromLow, int fromHigh, int toLow, int toHigh) { 41 | if (fromHigh == fromLow) { 42 | mp_raise_ValueError(MP_ERROR_TEXT("fromLow und fromHigh shall not be equal")); 43 | } 44 | return (int)((int32_t)(value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow); 45 | } 46 | 47 | static inline int get_mapped_jpeg_quality(int8_t quality) { 48 | return map(quality, 0, 100, 63, 0); 49 | } 50 | 51 | static inline void check_init(mp_camera_obj_t *self) { 52 | if (!self->initialized) { 53 | mp_raise_OSError(ENOENT); 54 | } 55 | } 56 | 57 | static void set_check_xclk_freq(mp_camera_obj_t *self, int32_t xclk_freq_hz) { 58 | if ( xclk_freq_hz > 40000000) { 59 | mp_raise_ValueError(MP_ERROR_TEXT("xclk frequency cannot be grather than 40MHz")); 60 | } else { 61 | self->camera_config.xclk_freq_hz = xclk_freq_hz; 62 | } 63 | } 64 | 65 | static void set_check_fb_count(mp_camera_obj_t *self, mp_int_t fb_count) { 66 | if (fb_count > 2) { 67 | self->camera_config.fb_count = 2; 68 | mp_warning(NULL, "Frame buffer size limited to 2"); 69 | } else if (fb_count < 1) { 70 | self->camera_config.fb_count = 1; 71 | mp_warning(NULL, "Frame buffer size must be >0. Setting it to 1"); 72 | } 73 | else { 74 | self->camera_config.fb_count = fb_count; 75 | } 76 | } 77 | 78 | static void set_check_grab_mode(mp_camera_obj_t *self, mp_camera_grabmode_t grab_mode) { 79 | if (grab_mode != CAMERA_GRAB_WHEN_EMPTY && grab_mode != CAMERA_GRAB_LATEST) { 80 | mp_raise_ValueError(MP_ERROR_TEXT("Invalid grab_mode")); 81 | } else { 82 | self->camera_config.grab_mode = grab_mode; 83 | } 84 | } 85 | 86 | static void set_check_pixel_format(mp_camera_obj_t *self, mp_camera_pixformat_t pixel_format) { 87 | if ( pixel_format > PIXFORMAT_RGB555) { //Maximal enum value, but validation should be better since wrong pixelformat leads to reboot. 88 | mp_raise_ValueError(MP_ERROR_TEXT("Invalid pixel_format")); 89 | } else { 90 | self->camera_config.pixel_format = pixel_format; 91 | } 92 | } 93 | 94 | static bool init_camera(mp_camera_obj_t *self) { 95 | // Correct the quality before it is passed to esp32 driver and then "undo" the correction in the camera_config 96 | int8_t api_jpeg_quality = self->camera_config.jpeg_quality; 97 | self->camera_config.jpeg_quality = get_mapped_jpeg_quality(api_jpeg_quality); 98 | esp_err_t err = esp_camera_init(&self->camera_config); 99 | self->camera_config.jpeg_quality = api_jpeg_quality; 100 | check_esp_err(err); 101 | return true; 102 | } 103 | 104 | // Camera HAL Funcitons 105 | void mp_camera_hal_construct( 106 | mp_camera_obj_t *self, 107 | int8_t data_pins[8], 108 | int8_t external_clock_pin, 109 | int8_t pixel_clock_pin, 110 | int8_t vsync_pin, 111 | int8_t href_pin, 112 | int8_t powerdown_pin, 113 | int8_t reset_pin, 114 | int8_t sccb_sda_pin, 115 | int8_t sccb_scl_pin, 116 | int32_t xclk_freq_hz, 117 | mp_camera_pixformat_t pixel_format, 118 | mp_camera_framesize_t frame_size, 119 | int8_t jpeg_quality, 120 | int8_t fb_count, 121 | mp_camera_grabmode_t grab_mode) { 122 | // configure camera based on arguments 123 | self->camera_config.pin_d0 = data_pins[0]; 124 | self->camera_config.pin_d1 = data_pins[1]; 125 | self->camera_config.pin_d2 = data_pins[2]; 126 | self->camera_config.pin_d3 = data_pins[3]; 127 | self->camera_config.pin_d4 = data_pins[4]; 128 | self->camera_config.pin_d5 = data_pins[5]; 129 | self->camera_config.pin_d6 = data_pins[6]; 130 | self->camera_config.pin_d7 = data_pins[7]; 131 | self->camera_config.pin_vsync = vsync_pin; 132 | self->camera_config.pin_href = href_pin; 133 | self->camera_config.pin_pclk = pixel_clock_pin; 134 | self->camera_config.pin_pwdn = powerdown_pin; 135 | self->camera_config.pin_reset = reset_pin; 136 | self->camera_config.pin_xclk = external_clock_pin; 137 | self->camera_config.pin_sscb_sda = sccb_sda_pin; 138 | self->camera_config.pin_sscb_scl = sccb_scl_pin; 139 | 140 | self->camera_config.frame_size = frame_size; 141 | self->camera_config.jpeg_quality = jpeg_quality; //save value in here, but will be corrected (with map) before passing it to the esp32-driver 142 | 143 | set_check_pixel_format(self, pixel_format); 144 | set_check_xclk_freq(self, xclk_freq_hz); 145 | set_check_fb_count(self, fb_count); 146 | set_check_grab_mode(self, grab_mode); 147 | 148 | // defaul parameters 149 | self->camera_config.fb_location = CAMERA_FB_IN_PSRAM; 150 | self->camera_config.ledc_timer = LEDC_TIMER_3; 151 | self->camera_config.ledc_channel = LEDC_CHANNEL_0; 152 | 153 | self->initialized = false; 154 | self->captured_buffer = NULL; 155 | } 156 | 157 | void mp_camera_hal_init(mp_camera_obj_t *self) { 158 | if (self->initialized) { 159 | return; 160 | } 161 | #ifndef CONFIG_IDF_TARGET_ESP32S3 162 | if (self->camera_config.fb_count > 1 && self->camera_config.pixel_format != PIXFORMAT_JPEG) { 163 | mp_warning(NULL, "It is recomended to use a frame buffer size of 1 for non-JPEG pixel format"); 164 | } 165 | #endif 166 | ESP_LOGI(TAG, "Initializing camera"); 167 | self->initialized = init_camera(self); 168 | ESP_LOGI(TAG, "Camera initialized successfully"); 169 | } 170 | 171 | void mp_camera_hal_deinit(mp_camera_obj_t *self) { 172 | if (self->initialized) { 173 | if (self->captured_buffer) { 174 | esp_camera_return_all(); 175 | self->captured_buffer = NULL; 176 | } 177 | esp_err_t err = esp_camera_deinit(); 178 | check_esp_err(err); 179 | self->initialized = false; 180 | ESP_LOGI(TAG, "Camera deinitialized"); 181 | } 182 | } 183 | 184 | 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) { 185 | check_init(self); 186 | 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); 187 | 188 | sensor_t *sensor = esp_camera_sensor_get(); 189 | camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); 190 | if (frame_size > sensor_info->max_size) { 191 | mp_warning(NULL, "Frame size will be scaled down to maximal frame size supported by the camera sensor"); 192 | self->camera_config.frame_size = sensor_info->max_size; 193 | } else { 194 | self->camera_config.frame_size = frame_size; 195 | } 196 | 197 | set_check_pixel_format(self, pixel_format); 198 | set_check_grab_mode(self, grab_mode); 199 | set_check_fb_count(self, fb_count); 200 | 201 | check_esp_err(esp_camera_deinit()); 202 | self->initialized = false; 203 | self->initialized = init_camera(self); 204 | ESP_LOGI(TAG, "Camera reconfigured successfully"); 205 | } 206 | 207 | mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self) { 208 | check_init(self); 209 | if (self->captured_buffer) { 210 | esp_camera_fb_return(self->captured_buffer); 211 | self->captured_buffer = NULL; 212 | } 213 | 214 | ESP_LOGI(TAG, "Capturing image"); 215 | self->captured_buffer = esp_camera_fb_get(); 216 | if (!self->captured_buffer) { 217 | ESP_LOGE(TAG, "Failed to capture image"); 218 | return mp_const_none; 219 | } 220 | return mp_obj_new_memoryview('b', self->captured_buffer->len, self->captured_buffer->buf); 221 | 222 | } 223 | 224 | mp_obj_t mp_camera_hal_frame_available(mp_camera_obj_t *self) { 225 | check_init(self); 226 | return mp_obj_new_bool(esp_camera_available_frames()); 227 | } 228 | 229 | bool mp_camera_hal_initialized(mp_camera_obj_t *self){ 230 | return self->initialized; 231 | } 232 | 233 | void mp_camera_hal_free_buffer(mp_camera_obj_t *self) { 234 | if (self->captured_buffer) { 235 | esp_camera_fb_return(self->captured_buffer); 236 | self->captured_buffer = NULL; 237 | } 238 | } 239 | 240 | const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[] = { 241 | { MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT((mp_uint_t)PIXFORMAT_JPEG) }, 242 | { MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT((mp_uint_t)PIXFORMAT_YUV422) }, 243 | { MP_ROM_QSTR(MP_QSTR_YUV420), MP_ROM_INT((mp_uint_t)PIXFORMAT_YUV420) }, 244 | { MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT((mp_uint_t)PIXFORMAT_GRAYSCALE) }, 245 | { MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB565) }, 246 | { MP_ROM_QSTR(MP_QSTR_RGB888), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB888) }, 247 | { MP_ROM_QSTR(MP_QSTR_RAW), MP_ROM_INT((mp_uint_t)PIXFORMAT_RAW) }, 248 | { MP_ROM_QSTR(MP_QSTR_RGB444), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB444) }, 249 | { MP_ROM_QSTR(MP_QSTR_RGB555), MP_ROM_INT((mp_uint_t)PIXFORMAT_RGB555) }, 250 | }; 251 | 252 | const mp_rom_map_elem_t mp_camera_hal_frame_size_table[] = { 253 | { MP_ROM_QSTR(MP_QSTR_R96X96), MP_ROM_INT((mp_uint_t)FRAMESIZE_96X96) }, 254 | { MP_ROM_QSTR(MP_QSTR_QQVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QQVGA) }, 255 | { MP_ROM_QSTR(MP_QSTR_R128x128), MP_ROM_INT((mp_uint_t)FRAMESIZE_128X128) }, 256 | { MP_ROM_QSTR(MP_QSTR_QCIF), MP_ROM_INT((mp_uint_t)FRAMESIZE_QCIF) }, 257 | { MP_ROM_QSTR(MP_QSTR_HQVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_HQVGA) }, 258 | { MP_ROM_QSTR(MP_QSTR_R240X240), MP_ROM_INT((mp_uint_t)FRAMESIZE_240X240) }, 259 | { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QVGA) }, 260 | { MP_ROM_QSTR(MP_QSTR_R320X320), MP_ROM_INT((mp_uint_t)FRAMESIZE_320X320) }, 261 | { MP_ROM_QSTR(MP_QSTR_CIF), MP_ROM_INT((mp_uint_t)FRAMESIZE_CIF) }, 262 | { MP_ROM_QSTR(MP_QSTR_HVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_HVGA) }, 263 | { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_VGA) }, 264 | { MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_SVGA) }, 265 | { MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_XGA) }, 266 | { MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT((mp_uint_t)FRAMESIZE_HD) }, 267 | { MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_SXGA) }, 268 | { MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_UXGA) }, 269 | { MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT((mp_uint_t)FRAMESIZE_FHD) }, 270 | { MP_ROM_QSTR(MP_QSTR_P_HD), MP_ROM_INT((mp_uint_t)FRAMESIZE_P_HD) }, 271 | { MP_ROM_QSTR(MP_QSTR_P_3MP), MP_ROM_INT((mp_uint_t)FRAMESIZE_P_3MP) }, 272 | { MP_ROM_QSTR(MP_QSTR_QXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QXGA) }, 273 | { MP_ROM_QSTR(MP_QSTR_QHD), MP_ROM_INT((mp_uint_t)FRAMESIZE_QHD) }, 274 | { MP_ROM_QSTR(MP_QSTR_WQXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_WQXGA) }, 275 | { MP_ROM_QSTR(MP_QSTR_P_FHD), MP_ROM_INT((mp_uint_t)FRAMESIZE_P_FHD) }, 276 | { MP_ROM_QSTR(MP_QSTR_QSXGA), MP_ROM_INT((mp_uint_t)FRAMESIZE_QSXGA) }, 277 | }; 278 | 279 | const mp_rom_map_elem_t mp_camera_hal_grab_mode_table[] = { 280 | { MP_ROM_QSTR(MP_QSTR_WHEN_EMPTY), MP_ROM_INT((mp_uint_t)CAMERA_GRAB_WHEN_EMPTY) }, 281 | { MP_ROM_QSTR(MP_QSTR_LATEST), MP_ROM_INT((mp_uint_t)CAMERA_GRAB_LATEST) }, 282 | }; 283 | 284 | const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[] = { 285 | { MP_ROM_QSTR(MP_QSTR_2X), MP_ROM_INT((mp_uint_t)GAINCEILING_2X) }, 286 | { MP_ROM_QSTR(MP_QSTR_4X), MP_ROM_INT((mp_uint_t)GAINCEILING_4X) }, 287 | { MP_ROM_QSTR(MP_QSTR_8X), MP_ROM_INT((mp_uint_t)GAINCEILING_8X) }, 288 | { MP_ROM_QSTR(MP_QSTR_16X), MP_ROM_INT((mp_uint_t)GAINCEILING_16X) }, 289 | { MP_ROM_QSTR(MP_QSTR_32X), MP_ROM_INT((mp_uint_t)GAINCEILING_32X) }, 290 | { MP_ROM_QSTR(MP_QSTR_64X), MP_ROM_INT((mp_uint_t)GAINCEILING_64X) }, 291 | { MP_ROM_QSTR(MP_QSTR_128X), MP_ROM_INT((mp_uint_t)GAINCEILING_128X) }, 292 | }; 293 | 294 | // Helper functions to get and set camera and sensor information 295 | #define SENSOR_STATUS_GETSET(type, name, status_field_name, setter_function_name) \ 296 | SENSOR_GETSET(type, name, status.status_field_name, setter_function_name) 297 | 298 | // For subsequent modules using this as example, you will probably only need the makros below. 299 | #define SENSOR_GETSET(type, name, field_name, setter_function_name) \ 300 | SENSOR_GET(type, name, field_name) \ 301 | SENSOR_SET(type, name, setter_function_name) 302 | 303 | #define SENSOR_GET(type, name, status_field_name) \ 304 | type mp_camera_hal_get_##name(mp_camera_obj_t * self) { \ 305 | check_init(self); \ 306 | sensor_t *sensor = esp_camera_sensor_get(); \ 307 | return sensor->status_field_name; \ 308 | } 309 | 310 | #define SENSOR_SET(type, name, setter_function_name) \ 311 | void mp_camera_hal_set_##name(mp_camera_obj_t * self, type value) { \ 312 | check_init(self); \ 313 | sensor_t *sensor = esp_camera_sensor_get(); \ 314 | if (!sensor->setter_function_name) { \ 315 | mp_raise_ValueError(MP_ERROR_TEXT("No attribute " #name)); \ 316 | } \ 317 | if (sensor->setter_function_name(sensor, value) < 0) { \ 318 | mp_raise_ValueError(MP_ERROR_TEXT("Invalid setting for " #name)); \ 319 | } \ 320 | } 321 | 322 | SENSOR_GET(framesize_t, frame_size, status.framesize); 323 | SENSOR_STATUS_GETSET(int, contrast, contrast, set_contrast); 324 | SENSOR_STATUS_GETSET(int, brightness, brightness, set_brightness); 325 | SENSOR_STATUS_GETSET(int, saturation, saturation, set_saturation); 326 | SENSOR_STATUS_GETSET(int, sharpness, sharpness, set_sharpness); 327 | SENSOR_STATUS_GETSET(int, denoise, denoise, set_denoise); 328 | SENSOR_STATUS_GETSET(mp_camera_gainceiling_t, gainceiling, gainceiling, set_gainceiling); 329 | SENSOR_STATUS_GETSET(bool, colorbar, colorbar, set_colorbar); 330 | SENSOR_STATUS_GETSET(bool, whitebal, awb, set_whitebal); 331 | SENSOR_STATUS_GETSET(bool, gain_ctrl, agc, set_gain_ctrl); 332 | SENSOR_STATUS_GETSET(bool, exposure_ctrl, aec, set_exposure_ctrl); 333 | SENSOR_STATUS_GETSET(bool, hmirror, hmirror, set_hmirror); 334 | SENSOR_STATUS_GETSET(bool, vflip, vflip, set_vflip); 335 | SENSOR_STATUS_GETSET(bool, aec2, aec2, set_aec2); 336 | SENSOR_STATUS_GETSET(bool, awb_gain, awb_gain, set_awb_gain); 337 | SENSOR_STATUS_GETSET(int, agc_gain, agc_gain, set_agc_gain); 338 | SENSOR_STATUS_GETSET(int, aec_value, aec_value, set_aec_value); 339 | SENSOR_STATUS_GETSET(int, special_effect, special_effect, set_special_effect); 340 | SENSOR_STATUS_GETSET(int, wb_mode, wb_mode, set_wb_mode); 341 | SENSOR_STATUS_GETSET(int, ae_level, ae_level, set_ae_level); 342 | SENSOR_STATUS_GETSET(bool, dcw, dcw, set_dcw); 343 | SENSOR_STATUS_GETSET(bool, bpc, bpc, set_bpc); 344 | SENSOR_STATUS_GETSET(bool, wpc, wpc, set_wpc); 345 | SENSOR_STATUS_GETSET(bool, raw_gma, raw_gma, set_raw_gma); 346 | SENSOR_STATUS_GETSET(bool, lenc, lenc, set_lenc); 347 | 348 | void mp_camera_hal_set_frame_size(mp_camera_obj_t * self, framesize_t value) { 349 | check_init(self); 350 | sensor_t *sensor = esp_camera_sensor_get(); 351 | if (!sensor->set_framesize) { 352 | mp_raise_ValueError(MP_ERROR_TEXT("No attribute frame_size")); 353 | } 354 | 355 | if (self->captured_buffer) { 356 | esp_camera_return_all(); 357 | self->captured_buffer = NULL; 358 | } 359 | 360 | if (sensor->set_framesize(sensor, value) < 0) { 361 | mp_raise_ValueError(MP_ERROR_TEXT("Invalid setting for frame_size")); 362 | } else { 363 | self->camera_config.frame_size = value; 364 | } 365 | } 366 | 367 | int mp_camera_hal_get_quality(mp_camera_obj_t * self) { 368 | check_init(self); 369 | return self->camera_config.jpeg_quality; 370 | } 371 | 372 | void mp_camera_hal_set_quality(mp_camera_obj_t * self, int value) { 373 | check_init(self); 374 | sensor_t *sensor = esp_camera_sensor_get(); 375 | if (!sensor->set_quality) { 376 | mp_raise_ValueError(MP_ERROR_TEXT("No attribute quality")); 377 | } 378 | if (sensor->set_quality(sensor, get_mapped_jpeg_quality(value)) < 0) { 379 | mp_raise_ValueError(MP_ERROR_TEXT("Invalid setting for quality")); 380 | } else { 381 | self->camera_config.jpeg_quality = value; 382 | } 383 | } 384 | 385 | mp_camera_pixformat_t mp_camera_hal_get_pixel_format(mp_camera_obj_t *self) { 386 | return self->camera_config.pixel_format; 387 | } 388 | 389 | camera_grab_mode_t mp_camera_hal_get_grab_mode(mp_camera_obj_t *self) { 390 | return self->camera_config.grab_mode; 391 | } 392 | 393 | int mp_camera_hal_get_fb_count(mp_camera_obj_t *self) { 394 | return self->camera_config.fb_count; 395 | } 396 | 397 | const char *mp_camera_hal_get_sensor_name(mp_camera_obj_t *self) { 398 | check_init(self); 399 | sensor_t *sensor = esp_camera_sensor_get(); 400 | camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); 401 | return sensor_info->name; 402 | } 403 | 404 | bool mp_camera_hal_get_supports_jpeg(mp_camera_obj_t *self) { 405 | check_init(self); 406 | sensor_t *sensor = esp_camera_sensor_get(); 407 | camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); 408 | return sensor_info->support_jpeg; 409 | } 410 | 411 | mp_camera_framesize_t mp_camera_hal_get_max_frame_size(mp_camera_obj_t *self) { 412 | check_init(self); 413 | sensor_t *sensor = esp_camera_sensor_get(); 414 | camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); 415 | return sensor_info->max_size; 416 | } 417 | 418 | int mp_camera_hal_get_address(mp_camera_obj_t *self) { 419 | check_init(self); 420 | sensor_t *sensor = esp_camera_sensor_get(); 421 | camera_sensor_info_t *sensor_info = esp_camera_sensor_get_info(&sensor->id); 422 | return sensor_info->sccb_addr; 423 | } 424 | 425 | int mp_camera_hal_get_pixel_width(mp_camera_obj_t *self) { 426 | check_init(self); 427 | sensor_t *sensor = esp_camera_sensor_get(); 428 | framesize_t framesize = sensor->status.framesize; 429 | return resolution[framesize].width; 430 | } 431 | 432 | int mp_camera_hal_get_pixel_height(mp_camera_obj_t *self) { 433 | check_init(self); 434 | sensor_t *sensor = esp_camera_sensor_get(); 435 | framesize_t framesize = sensor->status.framesize; 436 | return resolution[framesize].height; 437 | } 438 | -------------------------------------------------------------------------------- /src/modcamera.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Code based on circuitpython camera API by Jeff Epler 5 | * Copyright (c) 2022 Jeff Epler for Adafruit Industries 6 | * Adaptation to MicroPython by Christopher Nadler 7 | * Copyright (c) 2024 Christopher Nadler 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | 28 | #ifndef MICROPY_INCLUDED_MODCAMERA_H 29 | #define MICROPY_INCLUDED_MODCAMERA_H 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #include "py/mperrno.h" 36 | #include "py/mphal.h" //TODO: Maybe we can add here MICROPY_PY_MACHINE_CAMERA (0) ? 37 | #include "py/runtime.h" 38 | #include "py/obj.h" 39 | 40 | #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 41 | // ESP32-Camera specifics-> could go in separate header file, if this project starts implementing more ports. 42 | 43 | #include "esp_camera.h" 44 | #include "sensor.h" 45 | #include "camera_pins.h" 46 | 47 | #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) && \ 48 | 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) && \ 49 | defined (MICROPY_CAMERA_PIN_PCLK) && defined (MICROPY_CAMERA_PIN_VSYNC) && defined (MICROPY_CAMERA_PIN_HREF) && defined (MICROPY_CAMERA_PIN_XCLK) 50 | #define MICROPY_CAMERA_ALL_REQ_PINS_DEFINED (1) 51 | #endif 52 | 53 | #ifndef MICROPY_CAMERA_PIN_PWDN 54 | #define MICROPY_CAMERA_PIN_PWDN (-1) 55 | #endif 56 | 57 | #ifndef MICROPY_CAMERA_PIN_RESET 58 | #define MICROPY_CAMERA_PIN_RESET (-1) 59 | #endif 60 | 61 | #ifndef MICROPY_CAMERA_XCLK_FREQ 62 | #define MICROPY_CAMERA_XCLK_FREQ (20) 63 | #endif 64 | 65 | #if !defined (MICROPY_CAMERA_GRAB_MODE) && defined (CONFIG_IDF_TARGET_ESP32S3) 66 | #define MICROPY_CAMERA_GRAB_MODE CAMERA_GRAB_LATEST 67 | #elif !defined(MICROPY_CAMERA_GRAB_MODE) 68 | #define MICROPY_CAMERA_GRAB_MODE CAMERA_GRAB_WHEN_EMPTY 69 | #endif 70 | 71 | #if !defined (MICROPY_CAMERA_FB_COUNT) && defined (CONFIG_IDF_TARGET_ESP32S3) 72 | #define MICROPY_CAMERA_FB_COUNT (2) 73 | #elif !defined(MICROPY_CAMERA_FB_COUNT) 74 | #define MICROPY_CAMERA_FB_COUNT (1) 75 | #endif 76 | 77 | #ifndef MICROPY_CAMERA_DEFAULT_FRAME_SIZE 78 | #define MICROPY_CAMERA_DEFAULT_FRAME_SIZE FRAMESIZE_QQVGA 79 | #endif 80 | 81 | #ifndef MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT 82 | #define MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT PIXFORMAT_RGB565 83 | #endif 84 | 85 | #ifndef MICROPY_CAMERA_JPEG_QUALITY 86 | #define MICROPY_CAMERA_JPEG_QUALITY (85) 87 | #endif 88 | 89 | typedef pixformat_t hal_camera_pixformat_t; 90 | typedef framesize_t hal_camera_framesize_t; 91 | typedef camera_grab_mode_t hal_camera_grabmode_t; 92 | typedef gainceiling_t hal_camera_gainceiling_t; 93 | 94 | typedef struct hal_camera_obj { 95 | mp_obj_base_t base; 96 | camera_config_t camera_config; 97 | bool initialized; 98 | camera_fb_t *captured_buffer; 99 | } hal_camera_obj_t; 100 | 101 | #endif // CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 102 | 103 | typedef hal_camera_obj_t mp_camera_obj_t; 104 | 105 | typedef hal_camera_pixformat_t mp_camera_pixformat_t; 106 | typedef hal_camera_framesize_t mp_camera_framesize_t; 107 | typedef hal_camera_grabmode_t mp_camera_grabmode_t; 108 | typedef hal_camera_gainceiling_t mp_camera_gainceiling_t; 109 | 110 | /** 111 | * @brief Constructs the camera hardware abstraction layer. 112 | * @details The Port-plattform shall define a default pwm-time source and also frame buffer location (no input) 113 | * 114 | * @param self Pointer to the camera object. 115 | * @param data_pins Array of data pins. 116 | * @param external_clock_pin External clock pin. 117 | * @param pixel_clock_pin Pixel clock pin. 118 | * @param vsync_pin VSYNC pin. 119 | * @param href_pin HREF pin. 120 | * @param powerdown_pin Power down pin. 121 | * @param reset_pin Reset pin. 122 | * @param sccb_sda_pin SCCB SDA pin. 123 | * @param sccb_scl_pin SCCB SCL pin. 124 | * @param xclk_freq_hz External clock frequency in Hz. 125 | * @param pixel_format Pixel format. 126 | * @param frame_size Frame size. 127 | * @param jpeg_quality JPEG quality. 128 | * @param fb_count Number of framebuffers. 129 | * @param grab_mode Grab mode (single-shot/when-empty OR continous/latest) 130 | */ 131 | extern void mp_camera_hal_construct( 132 | mp_camera_obj_t *self, 133 | int8_t data_pins[8], 134 | int8_t external_clock_pin, 135 | int8_t pixel_clock_pin, 136 | int8_t vsync_pin, 137 | int8_t href_pin, 138 | int8_t powerdown_pin, 139 | int8_t reset_pin, 140 | int8_t sccb_sda_pin, 141 | int8_t sccb_scl_pin, 142 | int32_t xclk_freq_hz, 143 | mp_camera_pixformat_t pixel_format, 144 | mp_camera_framesize_t frame_size, 145 | int8_t jpeg_quality, 146 | int8_t fb_count, 147 | mp_camera_grabmode_t grab_mode); 148 | 149 | /** 150 | * @brief Initializes the camera hardware abstraction layer. Will be called during API constructor 151 | * 152 | * @param self Pointer to the camera object. 153 | */ 154 | extern void mp_camera_hal_init(mp_camera_obj_t *self); //since we are not passing handles at construction, init() is used to create those handles 155 | 156 | /** 157 | * @brief Deinitializes the camera hardware abstraction layer. 158 | * 159 | * @param self Pointer to the camera object. 160 | */ 161 | extern void mp_camera_hal_deinit(mp_camera_obj_t *self); 162 | 163 | /** 164 | * @brief Returns true, if camera is initialized. 165 | * 166 | * @param self Pointer to the camera object. 167 | */ 168 | extern bool mp_camera_hal_initialized(mp_camera_obj_t *self); 169 | 170 | /** 171 | * @brief Reconfigures the camera hardware abstraction layer. 172 | * 173 | * @param self Pointer to the camera object. 174 | * @param frame_size Frame size. 175 | * @param pixel_format Pixel format. 176 | * @param grab_mode Grab mode. 177 | * @param fb_count Number of framebuffers. 178 | */ 179 | extern 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); 180 | 181 | /** 182 | * @brief Captures an image and returns it as mp_obj_t (e.g. mp_obj_new_memoryview). 183 | * 184 | * @param self Pointer to the camera object. 185 | * @return Captured image as micropython object. 186 | */ 187 | extern mp_obj_t mp_camera_hal_capture(mp_camera_obj_t *self); 188 | 189 | /** 190 | * @brief Returns true, if a frame is available. 191 | * 192 | * @param self Pointer to the camera object. 193 | * @return True, if a frame is available. 194 | */ 195 | extern mp_obj_t mp_camera_hal_frame_available(mp_camera_obj_t *self); 196 | 197 | /** 198 | * @brief Frees the buffer of the camera object. 199 | * 200 | * @param self Pointer to the camera object. 201 | */ 202 | extern void mp_camera_hal_free_buffer(mp_camera_obj_t *self); 203 | 204 | /** 205 | * @brief Table mapping pixel formats API to their corresponding values at HAL. 206 | * @details Needs to be defined in the port-specific implementation. 207 | */ 208 | extern const mp_rom_map_elem_t mp_camera_hal_pixel_format_table[9]; 209 | 210 | /** 211 | * @brief Table mapping frame sizes API to their corresponding values at HAL. 212 | * @details Needs to be defined in the port-specific implementation. 213 | */ 214 | extern const mp_rom_map_elem_t mp_camera_hal_frame_size_table[24]; 215 | 216 | /** 217 | * @brief Table mapping gainceiling API to their corresponding values at HAL. 218 | * @details Needs to be defined in the port-specific implementation. 219 | */ 220 | extern const mp_rom_map_elem_t mp_camera_hal_gainceiling_table[7]; 221 | 222 | /** 223 | * @brief Table mapping grab mode API to their corresponding values at HAL. 224 | * @details Needs to be defined in the port-specific implementation. 225 | */ 226 | extern const mp_rom_map_elem_t mp_camera_hal_grab_mode_table[2]; 227 | 228 | 229 | #define DECLARE_CAMERA_HAL_GET(type, name) \ 230 | extern type mp_camera_hal_get_##name(mp_camera_obj_t *self); 231 | 232 | #define DECLARE_CAMERA_HAL_SET(type, name) \ 233 | extern void mp_camera_hal_set_##name(mp_camera_obj_t *self, type value); 234 | 235 | /** 236 | * @brief Helper functions to get and set sensor properties. 237 | * @details The functions are used to get and set sensor properties in the camera object. 238 | * Needs to be defined in the port-specific implementation. 239 | */ 240 | #define DECLARE_CAMERA_HAL_GETSET(type, name) \ 241 | DECLARE_CAMERA_HAL_GET(type, name) \ 242 | DECLARE_CAMERA_HAL_SET(type, name) 243 | 244 | DECLARE_CAMERA_HAL_GETSET(bool, aec2) 245 | DECLARE_CAMERA_HAL_GETSET(int, aec_value) 246 | DECLARE_CAMERA_HAL_GETSET(int, ae_level) 247 | DECLARE_CAMERA_HAL_GETSET(int, agc_gain) 248 | DECLARE_CAMERA_HAL_GETSET(bool, awb_gain) 249 | DECLARE_CAMERA_HAL_GETSET(int, brightness) 250 | DECLARE_CAMERA_HAL_GETSET(bool, bpc) 251 | DECLARE_CAMERA_HAL_GETSET(bool, colorbar) 252 | DECLARE_CAMERA_HAL_GETSET(int, contrast) 253 | DECLARE_CAMERA_HAL_GETSET(bool, dcw) 254 | DECLARE_CAMERA_HAL_GETSET(int, denoise) 255 | DECLARE_CAMERA_HAL_GETSET(bool, exposure_ctrl) 256 | DECLARE_CAMERA_HAL_GETSET(mp_camera_gainceiling_t, gainceiling) 257 | DECLARE_CAMERA_HAL_GETSET(bool, gain_ctrl) 258 | DECLARE_CAMERA_HAL_GETSET(bool, hmirror) 259 | DECLARE_CAMERA_HAL_GETSET(bool, lenc) 260 | DECLARE_CAMERA_HAL_GETSET(int, quality) 261 | DECLARE_CAMERA_HAL_GETSET(bool, raw_gma) 262 | DECLARE_CAMERA_HAL_GETSET(int, saturation) 263 | DECLARE_CAMERA_HAL_GETSET(int, sharpness) 264 | DECLARE_CAMERA_HAL_GETSET(int, special_effect) 265 | DECLARE_CAMERA_HAL_GETSET(bool, vflip) 266 | DECLARE_CAMERA_HAL_GETSET(int, wb_mode) 267 | DECLARE_CAMERA_HAL_GETSET(bool, whitebal) 268 | DECLARE_CAMERA_HAL_GETSET(bool, wpc) 269 | 270 | DECLARE_CAMERA_HAL_GET(int, address) 271 | DECLARE_CAMERA_HAL_GET(int, fb_count) 272 | DECLARE_CAMERA_HAL_GETSET(mp_camera_framesize_t, frame_size) 273 | DECLARE_CAMERA_HAL_GET(camera_grab_mode_t, grab_mode) 274 | DECLARE_CAMERA_HAL_GET(mp_camera_framesize_t, max_frame_size) 275 | DECLARE_CAMERA_HAL_GET(mp_camera_pixformat_t, pixel_format) 276 | DECLARE_CAMERA_HAL_GET(int, pixel_height) 277 | DECLARE_CAMERA_HAL_GET(int, pixel_width) 278 | DECLARE_CAMERA_HAL_GET(const char *, sensor_name) 279 | DECLARE_CAMERA_HAL_GET(bool, supports_jpeg) 280 | 281 | #endif // MICROPY_INCLUDED_MODCAMERA_H -------------------------------------------------------------------------------- /src/modcamera_api.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Code based on circuitpython camera API by Jeff Epler 5 | * Copyright (c) 2022 Jeff Epler for Adafruit Industries 6 | * Adaptation to MicroPython by Christopher Nadler 7 | * Copyright (c) 2024 Christopher Nadler 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #include "py/mperrno.h" 33 | #include "py/mphal.h" 34 | #include "py/runtime.h" 35 | 36 | #include "modcamera.h" 37 | 38 | typedef struct mp_camera_obj_t mp_camera_obj; 39 | const mp_obj_type_t camera_type; 40 | 41 | //Constructor 42 | 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) { 43 | 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 }; 44 | static const mp_arg_t allowed_args[] = { 45 | #ifdef MICROPY_CAMERA_ALL_REQ_PINS_DEFINED 46 | { MP_QSTR_data_pins, MP_ARG_OBJ | MP_ARG_KW_ONLY , { .u_obj = MP_ROM_NONE } }, 47 | { MP_QSTR_pclk_pin, MP_ARG_INT | MP_ARG_KW_ONLY , { .u_int = MICROPY_CAMERA_PIN_PCLK } }, 48 | { MP_QSTR_vsync_pin, MP_ARG_INT | MP_ARG_KW_ONLY , { .u_int = MICROPY_CAMERA_PIN_VSYNC } }, 49 | { MP_QSTR_href_pin, MP_ARG_INT | MP_ARG_KW_ONLY , { .u_int = MICROPY_CAMERA_PIN_HREF } }, 50 | { MP_QSTR_sda_pin, MP_ARG_INT | MP_ARG_KW_ONLY , { .u_int = MICROPY_CAMERA_PIN_SIOD } }, 51 | { MP_QSTR_scl_pin, MP_ARG_INT | MP_ARG_KW_ONLY , { .u_int = MICROPY_CAMERA_PIN_SIOC } }, 52 | { MP_QSTR_xclk_pin, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_PIN_XCLK } }, 53 | #else 54 | { MP_QSTR_data_pins, MP_ARG_OBJ | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, 55 | { MP_QSTR_pclk_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, 56 | { MP_QSTR_vsync_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, 57 | { MP_QSTR_href_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, 58 | { MP_QSTR_sda_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, 59 | { MP_QSTR_scl_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, 60 | { MP_QSTR_xclk_pin, MP_ARG_INT | MP_ARG_KW_ONLY | MP_ARG_REQUIRED }, 61 | #endif 62 | { MP_QSTR_xclk_freq, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_XCLK_FREQ } }, 63 | { MP_QSTR_powerdown_pin, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_PIN_PWDN } }, 64 | { MP_QSTR_reset_pin, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_PIN_RESET } }, 65 | { MP_QSTR_pixel_format, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_DEFAULT_PIXEL_FORMAT } }, 66 | { MP_QSTR_frame_size, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_DEFAULT_FRAME_SIZE } }, 67 | { MP_QSTR_jpeg_quality, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_JPEG_QUALITY } }, 68 | { MP_QSTR_fb_count, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_FB_COUNT } }, 69 | { MP_QSTR_grab_mode, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = MICROPY_CAMERA_GRAB_MODE } }, 70 | { MP_QSTR_init, MP_ARG_BOOL | MP_ARG_KW_ONLY, { .u_bool = true } }, 71 | }; 72 | 73 | mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; 74 | MP_STATIC_ASSERT(MP_ARRAY_SIZE(allowed_args) == NUM_ARGS); 75 | mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); 76 | 77 | //TODO: validate inputs 78 | int8_t data_pins[8]; 79 | mp_obj_t data_pins_obj = args[ARG_data_pins].u_obj; 80 | if (data_pins_obj == MP_ROM_NONE) { 81 | #ifdef MICROPY_CAMERA_ALL_REQ_PINS_DEFINED 82 | if (MICROPY_CAMERA_PIN_D0 != -1) { 83 | data_pins[0] = MICROPY_CAMERA_PIN_D0; 84 | data_pins[1] = MICROPY_CAMERA_PIN_D1; 85 | data_pins[2] = MICROPY_CAMERA_PIN_D2; 86 | data_pins[3] = MICROPY_CAMERA_PIN_D3; 87 | data_pins[4] = MICROPY_CAMERA_PIN_D4; 88 | data_pins[5] = MICROPY_CAMERA_PIN_D5; 89 | data_pins[6] = MICROPY_CAMERA_PIN_D6; 90 | data_pins[7] = MICROPY_CAMERA_PIN_D7; 91 | } else { 92 | mp_raise_ValueError(MP_ERROR_TEXT("Specify a valid camera configuration")); 93 | } 94 | #endif 95 | } else { 96 | if (!mp_obj_is_type(data_pins_obj, &mp_type_list) && !mp_obj_is_type(data_pins_obj, &mp_type_bytearray)) { 97 | mp_raise_TypeError(MP_ERROR_TEXT("data_pins must be a list or bytearray")); 98 | } 99 | mp_uint_t data_pins_len = mp_obj_get_int(mp_obj_len_maybe(data_pins_obj)); 100 | if (data_pins_len != 8) { 101 | mp_raise_ValueError(MP_ERROR_TEXT("data_pins must have 8 elements")); 102 | } 103 | for (mp_uint_t i = 0; i < 8; i++) { 104 | mp_obj_t item = mp_obj_subscr(data_pins_obj, MP_OBJ_NEW_SMALL_INT(i), MP_OBJ_SENTINEL); 105 | data_pins[i] = mp_obj_get_int(item); 106 | } 107 | } 108 | int8_t pixel_clock_pin = args[ARG_pixel_clock_pin].u_int; 109 | int8_t vsync_pin = args[ARG_vsync_pin].u_int; 110 | int8_t href_pin = args[ARG_href_pin].u_int; 111 | int8_t sda_pin = args[ARG_sda_pin].u_int; 112 | int8_t scl_pin = args[ARG_scl_pin].u_int; 113 | int8_t xclock_pin = args[ARG_xclock_pin].u_int; 114 | int32_t xclock_frequency = args[ARG_xclock_frequency].u_int; 115 | if (xclock_frequency < 1000) { 116 | xclock_frequency = xclock_frequency * 1000000; 117 | } 118 | int8_t powerdown_pin = args[ARG_powerdown_pin].u_int; 119 | int8_t reset_pin = args[ARG_reset_pin].u_int; 120 | mp_camera_pixformat_t pixel_format = args[ARG_pixel_format].u_int; 121 | mp_camera_framesize_t frame_size = args[ARG_frame_size].u_int; 122 | int8_t jpeg_quality = args[ARG_jpeg_quality].u_int; 123 | if ((jpeg_quality < 0) || (jpeg_quality > 100)) { 124 | mp_raise_ValueError(MP_ERROR_TEXT("jpeg quality must be in range 0-100")); 125 | } 126 | int8_t fb_count = args[ARG_fb_count].u_int; 127 | mp_camera_grabmode_t grab_mode = args[ARG_grab_mode].u_int; 128 | 129 | mp_camera_obj_t *self = mp_obj_malloc_with_finaliser(mp_camera_obj_t, &camera_type); 130 | self->base.type = &camera_type; 131 | 132 | mp_camera_hal_construct(self, data_pins, xclock_pin, pixel_clock_pin, vsync_pin, href_pin, powerdown_pin, reset_pin, 133 | sda_pin, scl_pin, xclock_frequency, pixel_format, frame_size, jpeg_quality, fb_count, grab_mode); 134 | 135 | mp_camera_hal_init(self); 136 | if (mp_camera_hal_capture(self) == mp_const_none){ 137 | mp_camera_hal_deinit(self); 138 | mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to capture initial frame. Construct a new object with appropriate configuration.")); 139 | } else { 140 | if ( !args[ARG_init].u_bool ){ 141 | mp_camera_hal_deinit(self); 142 | } else { 143 | mp_camera_hal_free_buffer(self); 144 | } 145 | return MP_OBJ_FROM_PTR(self); 146 | } 147 | } // camera_construct 148 | 149 | // Main methods 150 | static mp_obj_t camera_capture(mp_obj_t self_in){ 151 | mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); 152 | return mp_camera_hal_capture(self); 153 | } 154 | static MP_DEFINE_CONST_FUN_OBJ_1(camera_capture_obj, camera_capture); 155 | 156 | static mp_obj_t camera_frame_available(mp_obj_t self_in){ 157 | mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); 158 | return mp_camera_hal_frame_available(self); 159 | } 160 | static MP_DEFINE_CONST_FUN_OBJ_1(camera_frame_available_obj, camera_frame_available); 161 | 162 | static mp_obj_t camera_free_buf(mp_obj_t self_in) { 163 | mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); 164 | mp_camera_hal_free_buffer(self); 165 | return mp_const_none; 166 | } 167 | static MP_DEFINE_CONST_FUN_OBJ_1(camera_free_buf_obj, camera_free_buf); 168 | 169 | static mp_obj_t camera_reconfigure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args){ 170 | mp_camera_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); 171 | enum { ARG_frame_size, ARG_pixel_format, ARG_grab_mode, ARG_fb_count }; 172 | static const mp_arg_t allowed_args[] = { 173 | { MP_QSTR_frame_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE} }, 174 | { MP_QSTR_pixel_format, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE} }, 175 | { MP_QSTR_grab_mode, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE} }, 176 | { MP_QSTR_fb_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE} }, 177 | }; 178 | 179 | mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; 180 | mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); 181 | 182 | mp_camera_framesize_t frame_size = 183 | args[ARG_frame_size].u_obj != MP_ROM_NONE 184 | ? args[ARG_frame_size].u_int 185 | : mp_camera_hal_get_frame_size(self); 186 | mp_camera_pixformat_t pixel_format = 187 | args[ARG_pixel_format].u_obj != MP_ROM_NONE 188 | ? args[ARG_pixel_format].u_int 189 | : mp_camera_hal_get_pixel_format(self); 190 | mp_camera_grabmode_t grab_mode = 191 | args[ARG_grab_mode].u_obj != MP_ROM_NONE 192 | ? args[ARG_grab_mode].u_int 193 | : mp_camera_hal_get_grab_mode(self); 194 | mp_int_t fb_count = 195 | args[ARG_fb_count].u_obj != MP_ROM_NONE 196 | ? args[ARG_fb_count].u_int 197 | : mp_camera_hal_get_fb_count(self); 198 | 199 | mp_camera_hal_reconfigure(self, frame_size, pixel_format, grab_mode, fb_count); 200 | return mp_const_none; 201 | } 202 | static MP_DEFINE_CONST_FUN_OBJ_KW(camera_reconfigure_obj, 1, camera_reconfigure); 203 | 204 | static mp_obj_t camera_init(mp_obj_t self_in) { 205 | mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); 206 | mp_camera_hal_init(self); 207 | return mp_const_none; 208 | } 209 | static MP_DEFINE_CONST_FUN_OBJ_1(camera_init_obj, camera_init); 210 | 211 | static mp_obj_t mp_camera_deinit(mp_obj_t self_in) { 212 | mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); 213 | mp_camera_hal_deinit(self); 214 | return mp_const_none; 215 | } 216 | static MP_DEFINE_CONST_FUN_OBJ_1(mp_camera_deinit_obj, mp_camera_deinit); 217 | 218 | // Destructor 219 | static mp_obj_t mp_camera_obj___exit__(size_t n_args, const mp_obj_t *args) { 220 | (void)n_args; 221 | return mp_camera_deinit(args[0]); 222 | } 223 | static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_camera___exit___obj, 4, 4, mp_camera_obj___exit__); 224 | 225 | // Camera property functions 226 | // Camera sensor property functions 227 | #define CREATE_GETTER(property, get_function) \ 228 | static mp_obj_t camera_get_##property(const mp_obj_t self_in) { \ 229 | mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); \ 230 | return get_function(mp_camera_hal_get_##property(self)); \ 231 | } \ 232 | static MP_DEFINE_CONST_FUN_OBJ_1(camera_get_##property##_obj, camera_get_##property); 233 | 234 | #define CREATE_SETTER(property, set_conversion) \ 235 | static mp_obj_t camera_set_##property(const mp_obj_t self_in, const mp_obj_t arg) { \ 236 | mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); \ 237 | mp_camera_hal_set_##property(self, set_conversion(arg)); \ 238 | if (mp_camera_hal_get_##property(self) != set_conversion(arg)) { \ 239 | mp_warning(NULL,"Failed to set " #property); \ 240 | }; \ 241 | return mp_const_none; \ 242 | } \ 243 | static MP_DEFINE_CONST_FUN_OBJ_2(camera_set_##property##_obj, camera_set_##property); 244 | 245 | #define CREATE_GETSET_FUNCTIONS(property, get_function, set_conversion) \ 246 | CREATE_GETTER(property, get_function) \ 247 | CREATE_SETTER(property, set_conversion) 248 | 249 | // Create table property entry 250 | #define ADD_PROPERTY_TO_TABLE(property) \ 251 | { MP_ROM_QSTR(MP_QSTR_get_##property), MP_ROM_PTR(&camera_get_##property##_obj) }, \ 252 | { MP_ROM_QSTR(MP_QSTR_set_##property), MP_ROM_PTR(&camera_set_##property##_obj) } 253 | 254 | CREATE_GETSET_FUNCTIONS(frame_size, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 255 | CREATE_GETTER(pixel_format, mp_obj_new_int); 256 | CREATE_GETTER(grab_mode, mp_obj_new_int); 257 | CREATE_GETTER(fb_count, mp_obj_new_int); 258 | CREATE_GETTER(pixel_width, mp_obj_new_int); 259 | CREATE_GETTER(pixel_height, mp_obj_new_int); 260 | CREATE_GETTER(max_frame_size, mp_obj_new_int); 261 | CREATE_GETTER(sensor_name, mp_obj_new_str_from_cstr); 262 | CREATE_GETSET_FUNCTIONS(contrast, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 263 | CREATE_GETSET_FUNCTIONS(brightness, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 264 | CREATE_GETSET_FUNCTIONS(saturation, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 265 | CREATE_GETSET_FUNCTIONS(sharpness, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 266 | CREATE_GETSET_FUNCTIONS(denoise, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 267 | CREATE_GETSET_FUNCTIONS(gainceiling, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 268 | CREATE_GETSET_FUNCTIONS(quality, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 269 | CREATE_GETSET_FUNCTIONS(colorbar, mp_obj_new_bool, mp_obj_is_true); 270 | CREATE_GETSET_FUNCTIONS(whitebal, mp_obj_new_bool, mp_obj_is_true); 271 | CREATE_GETSET_FUNCTIONS(gain_ctrl, mp_obj_new_bool, mp_obj_is_true); 272 | CREATE_GETSET_FUNCTIONS(exposure_ctrl, mp_obj_new_bool, mp_obj_is_true); 273 | CREATE_GETSET_FUNCTIONS(hmirror, mp_obj_new_bool, mp_obj_is_true); 274 | CREATE_GETSET_FUNCTIONS(vflip, mp_obj_new_bool, mp_obj_is_true); 275 | CREATE_GETSET_FUNCTIONS(aec2, mp_obj_new_bool, mp_obj_is_true); 276 | CREATE_GETSET_FUNCTIONS(awb_gain, mp_obj_new_bool, mp_obj_is_true); 277 | CREATE_GETSET_FUNCTIONS(agc_gain, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 278 | CREATE_GETSET_FUNCTIONS(aec_value, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 279 | CREATE_GETSET_FUNCTIONS(special_effect, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 280 | CREATE_GETSET_FUNCTIONS(wb_mode, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 281 | CREATE_GETSET_FUNCTIONS(ae_level, MP_OBJ_NEW_SMALL_INT, mp_obj_get_int); 282 | CREATE_GETSET_FUNCTIONS(dcw, mp_obj_new_bool, mp_obj_is_true); 283 | CREATE_GETSET_FUNCTIONS(bpc, mp_obj_new_bool, mp_obj_is_true); 284 | CREATE_GETSET_FUNCTIONS(wpc, mp_obj_new_bool, mp_obj_is_true); 285 | CREATE_GETSET_FUNCTIONS(raw_gma, mp_obj_new_bool, mp_obj_is_true); 286 | CREATE_GETSET_FUNCTIONS(lenc, mp_obj_new_bool, mp_obj_is_true); 287 | 288 | //API-Tables 289 | static const mp_rom_map_elem_t camera_camera_locals_table[] = { 290 | { MP_ROM_QSTR(MP_QSTR_reconfigure), MP_ROM_PTR(&camera_reconfigure_obj) }, 291 | { MP_ROM_QSTR(MP_QSTR_capture), MP_ROM_PTR(&camera_capture_obj) }, 292 | { MP_ROM_QSTR(MP_QSTR_frame_available), MP_ROM_PTR(&camera_frame_available_obj) }, 293 | { MP_ROM_QSTR(MP_QSTR_free_buffer), MP_ROM_PTR(&camera_free_buf_obj) }, 294 | { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&camera_init_obj) }, 295 | { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mp_camera_deinit_obj) }, 296 | { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_camera_deinit_obj) }, 297 | { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, 298 | { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mp_camera___exit___obj) }, 299 | { MP_ROM_QSTR(MP_QSTR_get_pixel_format), MP_ROM_PTR(&camera_get_pixel_format_obj) }, 300 | { MP_ROM_QSTR(MP_QSTR_get_grab_mode), MP_ROM_PTR(&camera_get_grab_mode_obj) }, 301 | { MP_ROM_QSTR(MP_QSTR_get_fb_count), MP_ROM_PTR(&camera_get_fb_count_obj) }, 302 | { MP_ROM_QSTR(MP_QSTR_get_pixel_width), MP_ROM_PTR(&camera_get_pixel_width_obj) }, 303 | { MP_ROM_QSTR(MP_QSTR_get_pixel_height), MP_ROM_PTR(&camera_get_pixel_height_obj) }, 304 | { MP_ROM_QSTR(MP_QSTR_get_max_frame_size), MP_ROM_PTR(&camera_get_max_frame_size_obj) }, 305 | { MP_ROM_QSTR(MP_QSTR_get_sensor_name), MP_ROM_PTR(&camera_get_sensor_name_obj) }, 306 | ADD_PROPERTY_TO_TABLE(frame_size), 307 | ADD_PROPERTY_TO_TABLE(contrast), 308 | ADD_PROPERTY_TO_TABLE(brightness), 309 | ADD_PROPERTY_TO_TABLE(saturation), 310 | ADD_PROPERTY_TO_TABLE(sharpness), 311 | ADD_PROPERTY_TO_TABLE(denoise), 312 | ADD_PROPERTY_TO_TABLE(gainceiling), 313 | ADD_PROPERTY_TO_TABLE(quality), 314 | ADD_PROPERTY_TO_TABLE(colorbar), 315 | ADD_PROPERTY_TO_TABLE(whitebal), 316 | ADD_PROPERTY_TO_TABLE(gain_ctrl), 317 | ADD_PROPERTY_TO_TABLE(exposure_ctrl), 318 | ADD_PROPERTY_TO_TABLE(hmirror), 319 | ADD_PROPERTY_TO_TABLE(vflip), 320 | ADD_PROPERTY_TO_TABLE(aec2), 321 | ADD_PROPERTY_TO_TABLE(awb_gain), 322 | ADD_PROPERTY_TO_TABLE(agc_gain), 323 | ADD_PROPERTY_TO_TABLE(aec_value), 324 | ADD_PROPERTY_TO_TABLE(special_effect), 325 | ADD_PROPERTY_TO_TABLE(wb_mode), 326 | ADD_PROPERTY_TO_TABLE(ae_level), 327 | ADD_PROPERTY_TO_TABLE(dcw), 328 | ADD_PROPERTY_TO_TABLE(bpc), 329 | ADD_PROPERTY_TO_TABLE(wpc), 330 | ADD_PROPERTY_TO_TABLE(raw_gma), 331 | ADD_PROPERTY_TO_TABLE(lenc), 332 | }; 333 | static MP_DEFINE_CONST_DICT(camera_camera_locals_dict, camera_camera_locals_table); 334 | 335 | //Helper methods 336 | static void mp_camera_hal_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { 337 | mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); 338 | if (mp_camera_hal_initialized(self)) { 339 | mp_printf(print, "Camera with sensor %s", mp_camera_hal_get_sensor_name(self)); 340 | } else { 341 | mp_printf(print, "Camera unknown"); 342 | } 343 | } 344 | 345 | //API module definition 346 | #define MP_CREATE_CONST_TYPE(type, typename, ...) \ 347 | MP_DEFINE_CONST_OBJ_TYPE(typename##_type, \ 348 | MP_QSTR_##type, \ 349 | MP_TYPE_FLAG_NONE, \ 350 | locals_dict, &typename##_locals_dict, \ 351 | ##__VA_ARGS__ \ 352 | ) 353 | 354 | static MP_DEFINE_CONST_DICT(mp_camera_frame_size_locals_dict,mp_camera_hal_frame_size_table); 355 | MP_CREATE_CONST_TYPE(FrameSize, mp_camera_frame_size); 356 | 357 | static MP_DEFINE_CONST_DICT(mp_camera_pixel_format_locals_dict,mp_camera_hal_pixel_format_table); 358 | MP_CREATE_CONST_TYPE(PixelFormat, mp_camera_pixel_format); 359 | 360 | static MP_DEFINE_CONST_DICT(mp_camera_gainceiling_locals_dict,mp_camera_hal_gainceiling_table); 361 | MP_CREATE_CONST_TYPE(GainCeiling, mp_camera_gainceiling); 362 | 363 | static MP_DEFINE_CONST_DICT(mp_camera_grab_mode_locals_dict,mp_camera_hal_grab_mode_table); 364 | MP_CREATE_CONST_TYPE(GrabMode, mp_camera_grab_mode); 365 | 366 | #ifdef MP_CAMERA_DRIVER_VERSION 367 | static mp_obj_t mp_camera_driver_version(void) { 368 | return mp_obj_new_str(MP_CAMERA_DRIVER_VERSION, strlen(MP_CAMERA_DRIVER_VERSION)); 369 | } 370 | static MP_DEFINE_CONST_FUN_OBJ_0(mp_camera_driver_version_obj, mp_camera_driver_version); 371 | #endif 372 | 373 | static const mp_rom_map_elem_t camera_module_globals_table[] = { 374 | { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_camera) }, 375 | { MP_ROM_QSTR(MP_QSTR_Camera), MP_ROM_PTR(&camera_type) }, 376 | { MP_ROM_QSTR(MP_QSTR_PixelFormat), MP_ROM_PTR(&mp_camera_pixel_format_type) }, 377 | { MP_ROM_QSTR(MP_QSTR_FrameSize), MP_ROM_PTR(&mp_camera_frame_size_type) }, 378 | { MP_ROM_QSTR(MP_QSTR_GainCeiling), MP_ROM_PTR(&mp_camera_gainceiling_type) }, 379 | { MP_ROM_QSTR(MP_QSTR_GrabMode), MP_ROM_PTR(&mp_camera_grab_mode_type) }, 380 | #ifdef MP_CAMERA_DRIVER_VERSION 381 | { MP_ROM_QSTR(MP_QSTR_Version), MP_ROM_PTR(&mp_camera_driver_version_obj) }, 382 | #endif 383 | }; 384 | static MP_DEFINE_CONST_DICT(camera_module_globals, camera_module_globals_table); 385 | 386 | MP_DEFINE_CONST_OBJ_TYPE( 387 | camera_type, 388 | MP_QSTR_Camera, 389 | MP_TYPE_FLAG_NONE, 390 | make_new, mp_camera_make_new, 391 | print, mp_camera_hal_print, 392 | locals_dict, &camera_camera_locals_dict 393 | ); 394 | 395 | const mp_obj_module_t camera_module = { 396 | .base = { &mp_type_module }, 397 | .globals = (mp_obj_dict_t *)&camera_module_globals, 398 | }; 399 | MP_REGISTER_MODULE(MP_QSTR_camera, camera_module); -------------------------------------------------------------------------------- /tests/esp32_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | from camera import Camera, FrameSize, PixelFormat 3 | 4 | def test_property_get_frame_size(): 5 | with Camera() as cam: 6 | print("Test frame size") 7 | Frame_Size = FrameSize.VGA 8 | cam.reconfigure(frame_size=Frame_Size) 9 | assert cam.get_frame_size() == Frame_Size 10 | assert cam.get_pixel_width() == 640 11 | assert cam.get_pixel_height() == 480 12 | 13 | def test_property_get_pixel_format(): 14 | with Camera() as cam: 15 | print("Test pixel format") 16 | for Pixel_Format_Name in dir(PixelFormat): 17 | Pixel_Format = getattr(PixelFormat, Pixel_Format_Name) 18 | try: 19 | if Pixel_Format_Name.startswith("_") or Pixel_Format_Name.startswith("RGB888"): 20 | continue 21 | cam.reconfigure(pixel_format=Pixel_Format) 22 | assert cam.get_pixel_format() == Pixel_Format 23 | except Exception: 24 | print("\tFailed test for pixel format", Pixel_Format) 25 | 26 | def test_must_be_initialized(): 27 | with Camera(init=False) as cam: 28 | print(f"Testing capture without initalization") 29 | try: 30 | cam.capture() 31 | assert False, "Capture should have failed" 32 | except Exception as e: 33 | if e == "Camera not initialized": 34 | assert False, "Capture should have failed" 35 | else: 36 | assert True 37 | 38 | def test_camera_properties(): 39 | with Camera() as cam: 40 | print(f"Testing get/set methods") 41 | for name in dir(cam): 42 | if name.startswith('get_'): 43 | prop_name = name[4:] 44 | set_method_name = f'set_{prop_name}' 45 | if hasattr(cam, set_method_name): 46 | set_method = getattr(cam, set_method_name) 47 | get_method = getattr(cam, name) 48 | try: 49 | set_method(1) 50 | assert get_method() == 1 51 | except Exception: 52 | print("\tFailed test for property", prop_name) 53 | 54 | def test_invalid_settings(): 55 | print(f"Testing invalid settings") 56 | invalid_settings = [ 57 | {"xclk_freq": 21000000}, 58 | {"frame_size": 23}, 59 | {"pixel_format": 7}, 60 | {"jpeg_quality": 101}, 61 | {"jpeg_quality": -1}, 62 | {"grab_mode": 3}, 63 | ] 64 | Delay= 10 65 | 66 | for settings in invalid_settings: 67 | param, invalid_value = next(iter(settings.items())) 68 | try: 69 | print("testing",param,"=",invalid_value) 70 | time.sleep_ms(Delay) 71 | with Camera(**{param: invalid_value}) as cam: 72 | print(f"\tFailed test for {param} with value {invalid_value}") 73 | except Exception as e: 74 | time.sleep_ms(Delay) 75 | 76 | if __name__ == "__main__": 77 | test_property_get_frame_size() 78 | test_property_get_pixel_format() 79 | test_must_be_initialized() 80 | test_camera_properties() 81 | test_invalid_settings() 82 | 83 | --------------------------------------------------------------------------------