|
| 1 | +# SPDX-FileCopyrightText: Copyright (c) 2024 Trevor Beaton for Adafruit Industries |
| 2 | +# SPDX-License-Identifier: MIT |
| 3 | + |
| 4 | +import os |
| 5 | +import time |
| 6 | +import ssl |
| 7 | +import binascii |
| 8 | +import gc |
| 9 | +import wifi |
| 10 | +import socketpool |
| 11 | +import adafruit_requests |
| 12 | +import adafruit_minimqtt.adafruit_minimqtt as MQTT |
| 13 | +from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError |
| 14 | +import adafruit_pycamera |
| 15 | + |
| 16 | +# WiFi and Adafruit IO setup |
| 17 | +aio_username = os.getenv("AIO_USERNAME") |
| 18 | +aio_key = os.getenv("AIO_KEY") |
| 19 | + |
| 20 | +print(f"Connecting to {os.getenv('CIRCUITPY_WIFI_SSID')}") |
| 21 | +wifi.radio.connect( |
| 22 | + os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") |
| 23 | +) |
| 24 | +print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}!") |
| 25 | + |
| 26 | +mqtt_broker = "io.adafruit.com" |
| 27 | +mqtt_port = 1883 |
| 28 | +mqtt_topic = aio_username + "/feeds/cameratrigger" |
| 29 | + |
| 30 | +pool = socketpool.SocketPool(wifi.radio) |
| 31 | +requests = adafruit_requests.Session(pool, ssl.create_default_context()) |
| 32 | + |
| 33 | +# Initialize an Adafruit IO HTTP API object |
| 34 | +io = IO_HTTP(aio_username, aio_key, requests) |
| 35 | + |
| 36 | +# Adafruit IO feed configuration |
| 37 | +try: |
| 38 | + feed_camera = io.get_feed("camera") |
| 39 | +except AdafruitIO_RequestError: |
| 40 | + feed_camera = io.create_new_feed("camera") |
| 41 | + |
| 42 | +# Initialize memento camera |
| 43 | +pycam = adafruit_pycamera.PyCamera() |
| 44 | +time.sleep(2) # |
| 45 | + |
| 46 | +# Camera settings |
| 47 | +pycam.mode = 0 |
| 48 | +pycam.resolution = 3 |
| 49 | +pycam.led_level = 1 |
| 50 | +pycam.led_color = 0 |
| 51 | +pycam.effect = 0 |
| 52 | + |
| 53 | +def cameraChime(): |
| 54 | + notes = [ |
| 55 | + (1046, 0.1), # C6 |
| 56 | + (1318, 0.1), # E6 |
| 57 | + (1568, 0.4), # G6 |
| 58 | + ] |
| 59 | + for frequency, duration in notes: |
| 60 | + pycam.tone(frequency, duration) |
| 61 | + time.sleep(0.05) |
| 62 | + |
| 63 | +cameraChime() |
| 64 | + |
| 65 | +def on_mqtt_connect(client, _, __, ___): |
| 66 | + print(f"Connected to MQTT broker! Listening for topic changes on {mqtt_topic}") |
| 67 | + client.subscribe(mqtt_topic) |
| 68 | + |
| 69 | +def on_mqtt_disconnect(_, __, ___): |
| 70 | + print("Disconnected from MQTT broker!") |
| 71 | + |
| 72 | +def on_mqtt_message(_, topic, message): |
| 73 | + print(f"New message on topic {topic}: {message}") |
| 74 | + cameraChime() |
| 75 | + capture_send_image() |
| 76 | + |
| 77 | +# Set up MQTT client |
| 78 | +ssl_context = ssl.create_default_context() |
| 79 | +mqtt_client = MQTT.MQTT( |
| 80 | + broker=mqtt_broker, |
| 81 | + port=mqtt_port, |
| 82 | + username=aio_username, |
| 83 | + password=aio_key, |
| 84 | + socket_pool=pool, |
| 85 | + ssl_context=ssl_context, |
| 86 | + socket_timeout=5, |
| 87 | + keep_alive=60 |
| 88 | +) |
| 89 | +# Set up callbacks |
| 90 | +mqtt_client.on_connect = on_mqtt_connect |
| 91 | +mqtt_client.on_disconnect = on_mqtt_disconnect |
| 92 | +mqtt_client.on_message = on_mqtt_message |
| 93 | + |
| 94 | + |
| 95 | +# Connect to MQTT broker |
| 96 | +print("Connecting to MQTT broker...") |
| 97 | +mqtt_client.connect() |
| 98 | + |
| 99 | +def capture_send_image(): |
| 100 | + """Captures an image and sends it to Adafruit IO.""" |
| 101 | + gc.collect() # Free up memory before capture |
| 102 | + try: |
| 103 | + pycam.autofocus() |
| 104 | + time.sleep(1) # Add a small delay after autofocus |
| 105 | + jpeg = pycam.capture_into_jpeg() |
| 106 | + print("Captured image!") |
| 107 | + if jpeg is not None: |
| 108 | + print("Encoding image...") |
| 109 | + encoded_data = binascii.b2a_base64(jpeg).strip() |
| 110 | + print("Sending image to Adafruit IO...") |
| 111 | + io.send_data(feed_camera["key"], encoded_data) |
| 112 | + print("Sent image to IO!") |
| 113 | + cameraChime() |
| 114 | + else: |
| 115 | + print("ERROR: JPEG frame capture failed!") |
| 116 | + except (OSError, RuntimeError) as capture_error: |
| 117 | + print(f"Error during capture: {capture_error}") |
| 118 | + |
| 119 | +last_mqtt_check = 0 |
| 120 | +MQTT_CHECK_INTERVAL = 1 # Check MQTT every 1 second |
| 121 | + |
| 122 | +while True: |
| 123 | + try: |
| 124 | + current_time = time.monotonic() |
| 125 | + |
| 126 | + # Check MQTT messages periodically |
| 127 | + if current_time - last_mqtt_check >= MQTT_CHECK_INTERVAL: |
| 128 | + mqtt_client.loop(timeout=6) |
| 129 | + last_mqtt_check = current_time |
| 130 | + |
| 131 | + pycam.keys_debounce() |
| 132 | + if pycam.shutter.short_count: |
| 133 | + print("Manual capture triggered") |
| 134 | + capture_send_image() |
| 135 | + |
| 136 | + time.sleep(0.1) |
| 137 | + except MQTT.MMQTTException as mqtt_error: |
| 138 | + print(f"MQTT Error: {mqtt_error}") |
| 139 | + time.sleep(5) |
| 140 | + print("Attempting to reconnect...") |
| 141 | + try: |
| 142 | + mqtt_client.reconnect() |
| 143 | + print("Reconnected successfully!") |
| 144 | + except (OSError, RuntimeError) as reconnect_error: |
| 145 | + print(f"Failed to reconnect: {reconnect_error}") |
| 146 | + except (OSError, RuntimeError) as loop_error: |
| 147 | + print(f"Error in main loop: {loop_error}") |
| 148 | + time.sleep(5) |
0 commit comments