[{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eAdafruit Memento Time-lapse Camera with Online Upload and Email Notification\u003c/p\u003e\n\u003cp\u003eThis guide shows how to turn your Adafruit Memento (ESP32-S3) board into a time-lapse camera that:\u003c/p\u003e\n\u003cp\u003e- Captures images on a schedule or with a button press\u003cbr\u003e- Uploads them to Adafruit IO over Wi-Fi\u003cbr\u003e- Triggers email notifications using a feed\u003cbr\u003e- All using CircuitPython and the PyCamera library!\u003c/p\u003e\n\u003cp\u003eWhat You Need\u003c/p\u003e\n\u003cp\u003e- Adafruit Memento Board: https://www.adafruit.com/product/5843\u003cbr\u003e- microSD card (optional, for GIF recording)\u003cbr\u003e- USB-C cable\u003cbr\u003e- Wi-Fi network\u003cbr\u003e- Adafruit IO account: https://io.adafruit.com\u003cbr\u003e- CircuitPython is installed on the Memento\u003c/p\u003e\n\u003cp\u003eSetup\u003c/p\u003e\n\u003cp\u003e1. Installing CircuitPython\u003c/p\u003e\n\u003cp\u003eTo install CircuitPython on the Adafruit Memento, I followed this official guide by Anne Barela and John Park:\u003c/p\u003e\n\u003cp\u003e🔗 [Memento Camera Quick Start Guide – Install CircuitPython](\u003ca href=\"https://learn.adafruit.com/memento-camera-quick-start-guide/install-circuitpython\"\u003ehttps://learn.adafruit.com/memento-camera-quick-start-guide/install-circuitpython\u003c/a\u003e)\u003c/p\u003e\n\u003cp\u003eThat page walks you through how to:\u003c/p\u003e\n\u003cp\u003e- Put the board into bootloader mode (double-tap reset)\u003cbr\u003e- Drag the `.uf2` file onto the board\u003cbr\u003e- Verify that the **CIRCUITPY** drive appears\u003c/p\u003e\n\u003cp\u003eMake sure you use **CircuitPython 9.0.0 or later** to avoid filesystem corruption issues.\u003c/p\u003e\n\u003cp\u003e2. Install Libraries\u003cbr\u003e- adafruit_io\u0026nbsp;\u003cbr\u003e- adafruit_requests.mpy\u0026nbsp;\u0026nbsp;\u003cbr\u003e- adafruit_ntp.mpy\u003cbr\u003e- adafruit_logging.mpy\u0026nbsp;\u003cbr\u003e- adafruit_pycamera\u0026nbsp;\u0026nbsp;\u003cbr\u003e- adafruit_ov5640\u0026nbsp;\u0026nbsp;\u003cbr\u003e- adafruit_connection_manager.mpy\u0026nbsp;\u0026nbsp;\u003cbr\u003e- gifio.mpy\u0026nbsp;\u0026nbsp;\u003cbr\u003e- bitmaptools.mpy\u0026nbsp;\u0026nbsp;\u003cbr\u003e- ulab (folder)\u003c/p\u003e\n\u003cp\u003e3. Create settings.toml file on CIRCUITPY with this:\u003c/p\u003e\n\u003cp\u003eCIRCUITPY_WIFI_SSID = \"YourNetworkName\"\u003cbr\u003eCIRCUITPY_WIFI_PASSWORD = \"YourNetworkPassword\"\u003cbr\u003eADAFRUIT_AIO_USERNAME = \"your_username\"\u003cbr\u003eADAFRUIT_AIO_KEY = \"your_aio_key\"\u003c/p\u003e\n\u003cp\u003e4. Main Code\u003c/p\u003e\n\u003cp\u003ePaste the following into code.py on your CIRCUITPY drive:\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"# SPDX-FileCopyrightText: 2023 Adafruit Industries\n# SPDX-FileCopyrightText: 2023 Brent Rubell for Adafruit Industries\n# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries\n# SPDX-FileCopyrightText: 2023 Limor Fried for Adafruit Industries\n# SPDX-FileCopyrightText: 2025 Sophia Anderson for USDA Agricultural Research Service - Biosciences Research Lab\n#\n# Based on the Adafruit Memento Doorbell Camera and PyCamera Timelapse examples\n#\n# SPDX-License-Identifier: MIT\n\"\"\" Camera with Adafruit IO integration and button navigation. \"\"\"\n\nimport time\nimport os\nimport ssl\nimport binascii\nimport adafruit_pycamera\nimport wifi\nimport socketpool\nimport adafruit_requests\nimport displayio\nfrom adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError\nimport rtc\nimport digitalio\nimport adafruit_ntp\nimport bitmaptools\nimport gifio\nimport ulab.numpy as np\nimport board\nimport adafruit_logging as logging\nimport gc\n\n# Wifi details are in settings.toml file, also,\n# timezone info should be included to allow local time and DST adjustments\n# UTC_OFFSET, if present, will override TZ and DST and no API query will be done\n# UTC_OFFSET=-25200\n# # TZ=\"America/Phoenix\"\n\n# Use YOUR Wi-Fi environment variables\nWIFI_SSID = os.getenv(\"CIRCUITPY_WIFI_SSID\")\nWIFI_PASSWORD = os.getenv(\"CIRCUITPY_WIFI_PASSWORD\")\n\n# Create a socket pool (used for networking)\npool = socketpool.SocketPool(wifi.radio)\n\n# Track last Wi-Fi check\nlast_wifi_check = 0\nWIFI_CHECK_INTERVAL = 60  # Only check Wi-Fi every 60 seconds (adjustable)\n\ndef check_internet(host=\"8.8.8.8\", port=53, timeout=3):\n    \"\"\"Checks if the device has an active internet connection.\"\"\"\n    try:\n        conn = pool.socket(pool.AF_INET, pool.SOCK_STREAM)\n        conn.settimeout(timeout)\n        conn.connect((host, port))\n        conn.close()\n        return True\n    except Exception:\n        return False\n    \ndef check_wifi():\n    \"\"\"Checks Wi-Fi connection periodically (does NOT slow down camera).\"\"\"\n    global last_wifi_check\n\n    # Only check Wi-Fi every X seconds (prevents lag)\n    if time.monotonic() - last_wifi_check \u003e WIFI_CHECK_INTERVAL:\n        last_wifi_check = time.monotonic()  # Update last check time\n        if not wifi.radio.connected:\n            print(\"Wi-Fi lost! Attempting to reconnect...\")\n            connect_wifi()  # Only reconnect if Wi-Fi is lost\n\ndef reset_wifi():\n    \"\"\"Resets Wi-Fi if connection slows down over time.\"\"\"\n    print(\"[WIFI] Resetting Wi-Fi radio due to slow performance...\")\n    try:\n        wifi.radio.enabled = False\n        time.sleep(2)\n        wifi.radio.enabled = True\n    except Exception as e:\n        print(f\"[WIFI] Error during reset: {e}\")\n    connect_wifi()\nis_reconnecting = False\n\ndef connect_wifi():\n    \"\"\"Attempts to connect to Wi-Fi and retries in a background loop.\"\"\"\n    global is_reconnecting\n    if is_reconnecting:\n        return  # Skip if a connection attempt is already in progress\n\n    is_reconnecting = True  # Set flag\n    retry_delay = 2  # Start with 2 seconds\n\n    while True:  # Keep retrying indefinitely\n        try:\n            print(f\" Connecting to Wi-Fi: {WIFI_SSID} ...\")\n            wifi.radio.connect(WIFI_SSID, WIFI_PASSWORD)\n            print(f\" Connected to Wi-Fi: {WIFI_SSID} ({wifi.radio.ipv4_address})\")\n            is_reconnecting = False  # Reset flag\n            return True  # Connection successful\n        except Exception as e:\n            print(f\" Wi-Fi connection failed: {e}\")\n            print(f\" Retrying in {retry_delay} seconds...\")\n            time.sleep(retry_delay)\n            retry_delay = min(retry_delay * 2, 60)  # Exponential backoff (max 60 sec)\n\ndef check_memory():\n    \"\"\"Monitor memory usage and force garbage collection if needed.\"\"\"\n    free_mem = gc.mem_free()\n    print(f\"[MEMORY] Free: {free_mem} bytes\")\n    \n    # If free memory is critically low, force garbage collection\n    if free_mem \u003c 10_000:  # Adjust threshold based on your board\n        print(\"[MEMORY] Running garbage collection...\")\n        gc.collect()\n        print(f\"[MEMORY] After GC: {gc.mem_free()} bytes\")\n\n# Ensure Wi-Fi is connected before starting\nconnect_wifi()\n\n# === Network Diagnostics (Safe) ===\nprint(\"Wi-Fi connected!\")\nprint(\"IP Address:\", wifi.radio.ipv4_address)\n\n# Check DNS \u0026 internet connectivity before continuing\nprint(\"Checking internet connectivity...\")\nif not check_internet():\n    print(\"Internet not reachable — DNS may be blocked or unavailable.\")\n    print(\"Try switching to a mobile hotspot or home Wi-Fi.\")\n    while True:\n        time.sleep(1)  # Halt here so user sees the error\nelse:\n    print(\"Internet reachable!\")\n\n# === Wi-Fi and Adafruit IO Setup ===\naio_username = os.getenv(\"ADAFRUIT_AIO_USERNAME\")\naio_key = os.getenv(\"ADAFRUIT_AIO_KEY\")\n\n# Create Network Connection\nrequests = adafruit_requests.Session(pool, ssl.create_default_context())\n\n# Initialize Adafruit IO HTTP API\nio = IO_HTTP(aio_username, aio_key, requests)\n\n# Adafruit IO feed configuration\ntry:\n    feed_camera = io.get_feed(\"camera\")\n    feed_trigger = io.get_feed(\"camera-trigger\")\n    print(\"Camera connected to Adafruit IO successfully\")\nexcept AdafruitIO_RequestError as exception:\n    feed_camera = io.create_new_feed(\"camera\")\n    feed_trigger = io.create_new_feed(\"camera-trigger\")\n    print(f\"RuntimeError during Adafruit IO connection: {exception}\")\n    \n### Initialize the PyCamera ###\npycam = adafruit_pycamera.PyCamera()\n\nprint(\"Camera initialized and ready!\")\n\n# Provide startup feedback\npycam.tone(800, 0.1)\npycam.tone(1200, 0.05)\n\nsettings = (\n    None,\n    \"resolution\",\n    \"effect\",\n    \"mode\",\n    \"led_level\",\n    \"led_color\",\n    \"timelapse_rate\",\n)\ncurr_setting = 0\n\n# Override the timelapse_rates\npycam.timelapse_rates = (\n    5,         # 5 seconds\n    10,        # 10 seconds\n    20,        # 20 seconds\n    30,        # 30 seconds\n    60,        # 1 minute\n    90,        # 1.5 minutes\n    60 * 2,    # 2 minutes\n    60 * 3,    # 3 minutes\n    60 * 4,    # 4 minutes\n    60 * 5,    # 5 minutes\n    60 * 10,   # 10 minutes\n    60 * 15,   # 15 minutes\n    60 * 30,   # 30 minutes\n    60 * 60,   # 1 hour\n    60 * 120,  # 2 hours\n    60 * 240,  # 4 hours\n    60 * 480,  # 8 hours\n    60 * 960,  # 16 hours\n    60 * 1440, # 24 hours\n)\n\nprint(\"Starting!\")\n# pycam.tone(200, 0.1)\nlast_frame = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)\nonionskin = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)\ntimelapse_remaining = None\ntimelapse_timestamp = None\n\ncapture_count = 0\ndef capture_send_image():\n    global capture_count\n    \"\"\"Captures an image and sends it to Adafruit IO with backoff retries.\"\"\"\n\n    if not wifi.radio.connected:\n        print(\"[UPLOAD] No Wi-Fi! Reconnecting...\")\n        connect_wifi()\n\n    try:\n        jpeg = pycam.capture_into_jpeg()\n        if jpeg:\n            encoded_data = binascii.b2a_base64(jpeg).strip().decode(\"utf-8\")\n            \n            max_retries = 5\n            delay = 2  # Start with 2 seconds\n            for attempt in range(max_retries):\n                try:\n                    if attempt \u003e 0:\n                        print(f\"[UPLOAD] Retrying... Attempt {attempt+1}\")\n                    io.send_data(feed_camera[\"key\"], encoded_data)\n                    print(\"[UPLOAD] Success!\")\n\n                    # **THIS IS THE CRUCIAL PART FOR EMAIL NOTIFICATIONS**\n                    print(\"Sending trigger to camera-trigger feed...\")\n                    io.send_data(feed_trigger[\"key\"], 1)  # Notify Adafruit IO\n                    print(\"Trigger sent successfully!\")\n\n                    break\n                except Exception as e:\n                    print(f\"[UPLOAD] Failed: {e}\")\n                    time.sleep(delay)\n                    delay = min(delay * 2, 60)  # Exponential backoff (max 60 sec)\n\n    except Exception as e:\n        print(f\"[UPLOAD] Capture failed: {e}\")\n\n    finally:\n        capture_count += 1\n        if capture_count % 5 == 0:  # Only collect every 5 images\n            gc.collect()\n\n# Main loop\nwhile True:\n    if time.monotonic() % 300 \u003c 1:  # Every 5 minutes\n        check_memory()\n    \n    check_wifi()  # Check Wi-Fi periodically in the background\n    \n    pycam.blit(pycam.continuous_capture())  # Keep live preview active\n    pycam.keys_debounce()\n    \n    if pycam.mode_text == \"STOP\" and pycam.stop_motion_frame != 0:\n        # alpha blend\n        new_frame = pycam.continuous_capture()\n        bitmaptools.alphablend(\n            onionskin, last_frame, new_frame, displayio.Colorspace.RGB565_SWAPPED\n        )\n        pycam.blit(onionskin)\n    elif pycam.mode_text == \"GBOY\":\n        bitmaptools.dither(\n            last_frame, pycam.continuous_capture(), displayio.Colorspace.RGB565_SWAPPED\n        )\n        pycam.blit(last_frame)\n    elif pycam.mode_text == \"LAPS\":\n        if timelapse_remaining is None:\n            pycam.timelapsestatus_label.text = \"STOP\"\n        else:\n            timelapse_remaining = timelapse_timestamp - time.monotonic()\n            pycam.timelapsestatus_label.text = f\"{timelapse_remaining}s /    \"\n        # Manually updating the label text a second time ensures that the label\n        # is re-painted over the blitted preview.\n        pycam.timelapse_rate_label.text = pycam.timelapse_rate_label.text\n        pycam.timelapse_submode_label.text = pycam.timelapse_submode_label.text\n\n        # only in high power mode do we continuously preview\n        if (timelapse_remaining is None) or (\n            pycam.timelapse_submode_label.text == \"HiPwr\"\n        ):\n            pycam.blit(pycam.continuous_capture())\n        if pycam.timelapse_submode_label.text == \"LowPwr\" and (\n            timelapse_remaining is not None\n        ):\n            pycam.display.brightness = 0.05\n        else:\n            pycam.display.brightness = 1\n        pycam.display.refresh()\n\n        if timelapse_remaining is not None and timelapse_remaining \u003c= 0:\n            # no matter what, show what was just on the camera\n            pycam.blit(pycam.continuous_capture())\n            # pycam.tone(200, 0.1) # uncomment to add a beep when a photo is taken\n            try:\n                pycam.display_message(\"Snap!\", color=0x0000FF)\n               \n                # Capture and send the image to Adafruit IO\n                capture_send_image()\n                \n            except TypeError as e:\n                pycam.display_message(\"Failed\", color=0xFF0000)\n                time.sleep(0.5)\n            except RuntimeError as e:\n                pycam.display_message(\"Error\\nNo SD Card\", color=0xFF0000)\n                time.sleep(0.5)\n            pycam.live_preview_mode()\n            pycam.display.refresh()\n            pycam.blit(pycam.continuous_capture())\n            timelapse_timestamp = (\n                time.monotonic() + pycam.timelapse_rates[pycam.timelapse_rate] + 1\n            )\n    else:\n        pycam.blit(pycam.continuous_capture())\n    # print(\"\\t\\t\", capture_time, blit_time)\n    \n    pycam.keys_debounce()\n    \n     # Handle shutter button actions\n    if pycam.shutter.long_press:\n        print(\"FOCUS\")\n        pycam.autofocus()\n        print(pycam.autofocus_status)\n     \n    if pycam.shutter.short_count:\n        print(\"Shutter released\")\n        pycam.led_level = 4  # Activate the LED\n        pycam.led_color = 0  # Set the LED color, if applicable\n        pycam.display_message(\"Snap!\", color=0x00DD00)  # Display a snap message on screen\n        pycam.tone(1200, 0.05)  # Play a higher-pitched tone to indicate capture\n        pycam.tone(1600, 0.05)  # Play another tone immediately after\n        time.sleep(0.5)  # Delay to ensure the 'flash' is on long enough to illuminate the scene\n        try:\n            capture_send_image()  # Attempt to capture and send the image to Adafruit IO\n        \n        except TypeError as exception:\n            pycam.display_message(\"Failed\", color=0xFF0000)  # Display failure message for TypeError\n            print(f\"TypeError during image capture: {exception}\")\n        except RuntimeError as exception:\n            pycam.display_message(\"Error\\nNo SD Card\", color=0xFF0000)  # Display SD card error message\n            print(f\"RuntimeError during image capture: {exception}\")\n        finally:\n            pycam.led_level = 0  # Ensure the LED is turned off regardless of capture outcome\n            print(\"LED turned off.\")  # Confirm LED status in console\n            pycam.live_preview_mode()  # Return the camera to live preview mode\n            print(\"Camera returned to live preview mode.\")\n\n        if pycam.mode_text == \"GBOY\":\n            try:\n                f = pycam.open_next_image(\"gif\")\n            except RuntimeError as e:\n                pycam.display_message(\"Error\\nNo SD Card\", color=0xFF0000)\n                time.sleep(0.5)\n                continue\n\n            with gifio.GifWriter(\n                f,\n                pycam.camera.width,\n                pycam.camera.height,\n                displayio.Colorspace.RGB565_SWAPPED,\n                dither=True,\n            ) as g:\n                g.add_frame(last_frame, 1)\n\n        if pycam.mode_text == \"GIF\":\n            try:\n                f = pycam.open_next_image(\"gif\")\n            except RuntimeError as e:\n                pycam.display_message(\"Error\\nNo SD Card\", color=0xFF0000)\n                time.sleep(0.5)\n                continue\n            i = 0\n            ft = []\n            pycam._mode_label.text = \"RECORDING\"  # pylint: disable=protected-access\n\n            pycam.display.refresh()\n            with gifio.GifWriter(\n                f,\n                pycam.camera.width,\n                pycam.camera.height,\n                displayio.Colorspace.RGB565_SWAPPED,\n                dither=True,\n            ) as g:\n                t00 = t0 = time.monotonic()\n                while (i \u003c 15) or not pycam.shutter_button.value:\n                    i += 1\n                    _gifframe = pycam.continuous_capture()\n                    g.add_frame(_gifframe, 0.12)\n                    pycam.blit(_gifframe)\n                    t1 = time.monotonic()\n                    ft.append(1 / (t1 - t0))\n                    print(end=\".\")\n                    t0 = t1\n            pycam._mode_label.text = \"GIF\"  # pylint: disable=protected-access\n            print(f\"\\nfinal size {f.tell()} for {i} frames\")\n            print(f\"average framerate {i / (t1 - t00)}fps\")\n            print(f\"best {max(ft)} worst {min(ft)} std. deviation {np.std(ft)}\")\n            f.close()\n            pycam.display.refresh()\n            \n    if pycam.card_detect.fell:\n        print(\"SD card removed\")\n        pycam.unmount_sd_card()\n        pycam.display.refresh()\n\n    if pycam.card_detect.rose:\n        print(\"SD card inserted\")\n        pycam.display_message(\"Mounting\\nSD Card\", color=0xFFFFFF)\n        for _ in range(3):\n            try:\n                print(\"Mounting card\")\n                pycam.mount_sd_card()\n                print(\"Success!\")\n                break\n            except OSError as exception:\n                print(\"Retrying!\", exception)\n                time.sleep(0.5)\n        else:\n            pycam.display_message(\"SD Card\\nFailed!\", color=0xFF0000)\n            time.sleep(0.5)\n        pycam.display.refresh()\n    \n    if pycam.up.fell:\n        print(\"UP\")\n        key = settings[curr_setting]\n        if key:\n            print(\"getting\", key, getattr(pycam, key))\n            setattr(pycam, key, getattr(pycam, key) + 1)\n    if pycam.down.fell:\n        print(\"DN\")\n        key = settings[curr_setting]\n        if key:\n            setattr(pycam, key, getattr(pycam, key) - 1)\n    if pycam.right.fell:\n        print(\"RT\")\n        curr_setting = (curr_setting + 1) % len(settings)\n        if pycam.mode_text != \"LAPS\" and settings[curr_setting] == \"timelapse_rate\":\n            curr_setting = (curr_setting + 1) % len(settings)\n        print(settings[curr_setting])\n        # new_res = min(len(pycam.resolutions)-1, pycam.get_resolution()+1)\n        # pycam.set_resolution(pycam.resolutions[new_res])\n        pycam.select_setting(settings[curr_setting])\n    if pycam.left.fell:\n        print(\"LF\")\n        curr_setting = (curr_setting - 1 + len(settings)) % len(settings)\n        if pycam.mode_text != \"LAPS\" and settings[curr_setting] == \"timelaps_rate\":\n            curr_setting = (curr_setting + 1) % len(settings)\n        print(settings[curr_setting])\n        pycam.select_setting(settings[curr_setting])\n        # new_res = max(1, pycam.get_resolution()-1)\n        # pycam.set_resolution(pycam.resolutions[new_res])\n    if pycam.select.fell:\n        print(\"SEL\")\n        if pycam.mode_text == \"LAPS\":\n            pycam.timelapse_submode += 1\n            pycam.display.refresh()\n    if pycam.ok.fell:\n        print(\"OK\")\n        if pycam.mode_text == \"LAPS\":\n            if timelapse_remaining is None:  # stopped\n                print(\"Starting timelapse\")\n                timelapse_remaining = pycam.timelapse_rates[pycam.timelapse_rate]\n                timelapse_timestamp = time.monotonic() + timelapse_remaining + 1\n                # dont let the camera take over auto-settings\n                saved_settings = pycam.get_camera_autosettings()\n                # print(f\"Current exposure {saved_settings=}\")\n                pycam.set_camera_exposure(saved_settings[\"exposure\"])\n                pycam.set_camera_gain(saved_settings[\"gain\"])\n                pycam.set_camera_wb(saved_settings[\"wb\"])\n            else:  # is running, turn off\n                print(\"Stopping timelapse\")\n\n                timelapse_remaining = None\n                pycam.camera.exposure_ctrl = True\n                pycam.set_camera_gain(None)  # go back to autogain\n                pycam.set_camera_wb(None)  # go back to autobalance\n                pycam.set_camera_exposure(None)  # go back to auto shutter\n   \n    time.sleep(0.1)  # Small delay for responsiveness","metadata":{"language":"auto","linenums":false}},{"element_type":"button","content":"https://github.com/SuperCoder-101/Adafruit-Memento-Time-lapse-w-online-upload-email-notification/blob/main/code.py","metadata":{"class":"btn btn-large btn-block btn-primary","button_type":"primary","target":"_self","zip_folder":"false"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eWhat I did\u003c/p\u003e\n\u003cp\u003e- I used the fancy camera CircuitPython code by Anne Barela and John Park, and the Doorbell Camera code by Brent Rubell to help create the code above.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003e- My main focus was to get photos uploaded to Adafruit IO with the timelapse and camera shutter options.\u003c/p\u003e\n\u003cp\u003eHow It Works\u003c/p\u003e\n\u003cp\u003e- The camera connects to Wi-Fi and Adafruit IO\u003cbr\u003e- Takes snapshots using the shutter button or on a time-lapse interval\u003cbr\u003e- Encodes image data as base64 and uploads to Adafruit IO feed\u003cbr\u003e- Sends a \"trigger\" signal to another feed to notify you via email\u003c/p\u003e\n\u003cp\u003e5. Set up Feeds on Adafruit IO\u003c/p\u003e\n\u003cp\u003e- Go to io.adafruit.com\u003c/p\u003e\n\u003cp\u003e- Click on the tab 'IO'\u003c/p\u003e\n\u003cp\u003e- Go to Feeds and create two. I set up a 'camera' feed and a 'camera-trigger' feed.\u003c/p\u003e\n\u003cp\u003e- The \"camera\" feed will take photo uploads, and the \"camera-trigger\" feed gets a 1 or 0; this is used to trigger an automated email action.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/743/original/Screenshot_2025-04-03_121649.png?1743700642","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/739/original/Screenshot_2025-04-03_120249.png?1743699782","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eNext, you want to modify the settings\u003c/p\u003e\n\u003cp\u003e- Look on the right-hand side of the page and turn off the 'Feed History' setting. I also changed the value size to 100 KB.\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/740/original/Screenshot_2025-04-03_120333.png?1743699830","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e5. Set Up Email Notifications\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eGo back to the Overview page and look for the 'Actions' tab\u003c/li\u003e\n\u003cli\u003eOnce on this page, create a new action\u003c/li\u003e\n\u003c/ol\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/738/original/Screenshot_2025-04-03_120130.png?1743699734","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e3. After creating a new action, set it up like the picture below and hit submit when done\u003c/p\u003e\n\u003cp\u003e4. You will know it is working if the 'Status' says 'Active' like the picture above\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/741/original/Screenshot_2025-04-03_120601.png?1743699980","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/742/original/Screenshot_2025-04-03_120637.png?1743700008","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e6. Troubleshooting\u003c/p\u003e\n\u003cp\u003e- Wi-Fi connected, but upload fails? Check for DNS or firewall issues\u003cbr\u003e- gaierror: (-2, 'Name or service not known') → means DNS is blocked or not assigned\u003cbr\u003e- Rebooting your router may fix DHCP issues\u003cbr\u003e- Use print statements to debug Wi-Fi and Adafruit IO status\u003c/p\u003e\n\u003cp\u003e7. Final Notes\u003c/p\u003e\n\u003cp\u003e- Make sure to look at the guides posted below, they will have details on the CircuitPython install, Fancy Camera code, as well as photo upload \u0026amp; email notification.\u003c/p\u003e\n\u003cp\u003eThanks for reading!\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"guide","content":"https://learn.adafruit.com/memento-camera-quick-start-guide/install-circuitpython","metadata":{"guide_id":"3952","page_id":"28107"}},{"element_type":"guide","content":"https://learn.adafruit.com/diy-iot-doorbell-camera-with-memento/create-your-settings-toml-file","metadata":{"guide_id":"3920","page_id":"27912"}},{"element_type":"product","content":"https://www.adafruit.com/product/5843","metadata":{}}]