With a WiFi-enabled board like the Espressif Kaluga, you can upload your image data to Adafruit IO. We made sure that this example works with the free version, so you can try it out even if you haven't upgraded to an IO+ subscription yet. New to Adafruit IO? Start with this guide to learn the basics.

Use CircuitPython 7.0.0-alpha.5 or newer for this project.
ESP32-S2 Kaluga Dev Kit featuring ESP32-S2 WROVER with TFT screen module, capacitive touch pads, speaker, camera and hardware.
The ESP32-S2-Kaluga-1 kit is a full featured development kit by Espressif for the ESP32-S2 that comes with everything but the kitchen sink! From TFTs to touch panels,...
In Stock
USB Type A Plug Breakout Cable with Premium Female Jumpers
If you'd like to connect a USB-capable chip to your USB host, this cable will make the task very simple. There is no converter chip in this cable! Its basically a...
In Stock
USB Type A Extension Cable
This handy USB extension cable will make it easy for you to extend your USB cable when it won't reach. The connectors are gold plated for years of reliability. We use these handy...
In Stock
Text image that reads "IO+"
The all-in-one Internet of Things service from Adafruit you know and love is now even better with IO+. The 'plus' stands for MORE STUFF! More feeds, dashboards,...
Out of Stock

Set up the IO Feed

Create a feed called "image" and then set "Feed History" to "OFF". This allows storage of data up to 100kB, which is plenty to upload JPEGs at 640x480 resolution. (You can choose another feed name but you'll need to make sure that Adafruit IO's "key" for the feed matches what you use in your CircuitPython program!)

Set up the IO Dashboard

Create a new dashboard. Click the gear icon and then "Create New Block". Choose the camera icon ("image") and then select your feed named "image". Enter a block title if you like, and click "Create Block".

Secrets File Setup for Adafruit IO

If you don't have a secrets.py file in your CIRCUITPY drive yet, create one and add the information about your wifi connection.

Then, add the following code to your secrets.py file, replacing _your_adafruit_io_username with your Adafruit IO username.

Then, replace _your_big_huge_super_long_aio_key_ with your Adafruit IO Active Key.

secrets = {
    'ssid' : '_your_wifi_ssid_',
    'password' : '_your_wifi_password_',
    'aio_username' : '_your_adafruit_io_username_',
    'aio_key' : '_your_big_huge_super_long_aio_key_',

Make sure you save this file before proceeding as secrets.py in the root directory of your board CIRCUITPY drive.

Upload the code

Click "Download Project Bundle" below and unzip it on your Espressif Kaluga's CIRCUITPY drive. It will automatically start the code and upload a 640x480 JPEG to Adafruit IO every 3 seconds or so. If you run into trouble, open up the REPL to look for clues. Carefully double-check your WIFI and Adafruit IO login information, and that you properly created & configured the Adafruit IO Feed and Dashboard.

# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2021 Jeff Epler for Adafruit Industries
# SPDX-License-Identifier: Unlicense

The Kaluga development kit comes in two versions (v1.2 and v1.3); this demo is
tested on v1.3.

The audio board must be mounted between the Kaluga and the LCD, it provides the
I2C pull-ups(!)

This example requires that your WIFI and Adafruit IO credentials be configured
in CIRCUITPY/secrets.py, and that you have created a feed called "image" with
history disabled.

The maximum image size is 100kB after base64 encoding, or about 65kB before
base64 encoding.  In practice, "SVGA" (800x600) images are typically around
40kB even though the "capture_buffer_size" (theoretical maximum size) is
(width*height/5) bytes or 96kB.

import binascii
import ssl
import time
from secrets import secrets  # pylint: disable=no-name-in-module

import board
import busio
import wifi
import socketpool
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_io.adafruit_io import IO_MQTT
import adafruit_ov2640

feed_name = "image"

print("Connecting to WIFI")
wifi.radio.connect(secrets["ssid"], secrets["password"])
pool = socketpool.SocketPool(wifi.radio)

print("Connecting to Adafruit IO")
mqtt_client = MQTT.MQTT(
io = IO_MQTT(mqtt_client)

bus = busio.I2C(scl=board.CAMERA_SIOC, sda=board.CAMERA_SIOD)
cam = adafruit_ov2640.OV2640(

cam.flip_x = False
cam.flip_y = False
cam.test_pattern = False

cam.size = adafruit_ov2640.OV2640_SIZE_SVGA
cam.colorspace = adafruit_ov2640.OV2640_COLOR_JPEG
jpeg_buffer = bytearray(cam.capture_buffer_size)
while True:
    jpeg = cam.capture(jpeg_buffer)
    print(f"Captured {len(jpeg)} bytes of jpeg data")

    # b2a_base64() appends a trailing newline, which IO does not like
    encoded_data = binascii.b2a_base64(jpeg).strip()
    print(f"Expanded to {len(encoded_data)} for IO upload")

    io.publish("image", encoded_data)

    print("Waiting 3s")

Watch the camera feed

Just open up the Dashboard on Adafruit IO. The image in your browser will update shortly after each new image is uploaded from the ESP32-S2.

Take it Further

Here are some improvements and changes you might want to make to the code:

  • WiFi camera: Add a button to take a photo, and an LCD to act as a viewfinder.
  • Add a button to the Adafruit IO Dashboard that signals the camera to upload a fresh photo
  • Video Doorbell: Add a button to take a photo, and send you a notification on your phone (requires an IFTTT account)
  • Use a PC or Raspberry Pi to download & analyze the images

This guide was first published on Aug 04, 2021. It was last updated on Aug 04, 2021.