Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files.
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries, and the code.py file. To get everything you need, click on the Download Project Bundle button below, and uncompress the .zip file.
Connect your computer to the board via a known good USB power+data cable. A new flash drive should show up as CIRCUITPY.
Drag the contents of the uncompressed bundle directory onto your board CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
# SPDX-FileCopyrightText: 2023 Jeff Epler & John Park for Adafruit Industries # # SPDX-License-Identifier: MIT ''' Wireless remote for MEMENTO camera with TouchOSC''' import time import os import bitmaptools import displayio import gifio import ulab.numpy as np import adafruit_pycamera import wifi import socketpool import microosc UDP_HOST = "" UDP_PORT = 8000 ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") print("connecting to WiFi", ssid) wifi.radio.connect(ssid, password) print("my ip address:", wifi.radio.ipv4_address) socket_pool = socketpool.SocketPool(wifi.radio) time.sleep(10) # delay so people can get the IP address pycam = adafruit_pycamera.PyCamera() pycam.autofocus_init() settings = (None, "resolution", "effect", "mode", "led_level", "led_color") curr_setting = 0 print("Starting!") last_frame = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535) onionskin = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535) pycam.tone(800, 0.1) pycam.tone(1200, 0.05) def snap_jpeg(): pycam.tone(600, 0.1) try: pycam.display_message("Snap!", color=0x0000FF) pycam.capture_jpeg() pycam.live_preview_mode() # pylint: disable=unused-variable except TypeError as e: pycam.display_message("Failed", color=0xFF0000) time.sleep(0.5) pycam.live_preview_mode() except RuntimeError as e: pycam.display_message("Error\nNo SD Card", color=0xFF0000) time.sleep(0.5) def snap_gboy(): pycam.tone(600, 0.1) try: f = pycam.open_next_image("gif") pycam.display_message("Snap!", color=0x00ff44) # pylint: disable=unused-variable except RuntimeError as e: pycam.display_message("Error\nNo SD Card", color=0xFF0000) time.sleep(0.5) with gifio.GifWriter( f, pycam.camera.width, pycam.camera.height, displayio.Colorspace.RGB565_SWAPPED, dither=True, ) as g: g.add_frame(last_frame, 1) def snap_gif(): pycam.tone(600, 0.1) try: f = pycam.open_next_image("gif") # pylint: disable=unused-variable except RuntimeError as e: pycam.display_message("Error\nNo SD Card", color=0xFF0000) time.sleep(0.5) i = 0 ft = [] pycam._mode_label.text = "RECORDING" # pylint: disable=protected-access pycam.display.refresh() with gifio.GifWriter( f, pycam.camera.width, pycam.camera.height, displayio.Colorspace.RGB565_SWAPPED, dither=True, ) as g: t00 = t0 = time.monotonic() while (i < 15) or not pycam.shutter_button.value: i += 1 _gifframe = pycam.continuous_capture() g.add_frame(_gifframe, 0.12) pycam.blit(_gifframe) t1 = time.monotonic() ft.append(1 / (t1 - t0)) print(end=".") t0 = t1 pycam._mode_label.text = "GIF" # pylint: disable=protected-access print(f"\nfinal size {f.tell()} for {i} frames") print(f"average framerate {i/(t1-t00)}fps") print(f"best {max(ft)} worst {min(ft)} std. deviation {np.std(ft)}") f.close() pycam.display.refresh() pycam.tone(1200, 0.15) def snap_stop(): pycam.tone(600, 0.1) pycam.capture_into_bitmap(last_frame) pycam.stop_motion_frame += 1 try: pycam.display_message("Snap!", color=0x0000FF) pycam.capture_jpeg() # pylint: disable=unused-variable except TypeError as e: pycam.display_message("Failed", color=0xFF0000) time.sleep(0.5) except RuntimeError as e: pycam.display_message("Error\nNo SD Card", color=0xFF0000) time.sleep(0.5) pycam.live_preview_mode() def toggle_handler(msg): addr = msg.addr tog_num = int(addr.replace('/1/toggle','')) if msg.args[0] == 1.0: print(tog_num, "is ON") else: print(tog_num, "is off") def fader_handler(msg): # faders """Used to handle 'fader' OscMsgs, printing it as a '*' text progress bar :param OscMsg msg: message with one required float32 value """ osc_addr = msg.addr.split('/') # chop up the address into parts # page_num = osc_addr[1] fader_num = int(osc_addr[2].replace('fader', '')) # get the number only if fader_num == 1: # led level led_val = int(msg.args[0] * 5) pycam.led_level = led_val mode_texts = ("JPEG", "GIF", "GBOY", "STOP") def radio_handler(msg): # Radio buttons osc_addr = msg.addr.split('/') # chop up the address into parts print(osc_addr) page_num = osc_addr[1] print("page_num:", page_num) rad_num = int(osc_addr[2].replace('radio', '')) # get the number only print("rad_num:", rad_num) if rad_num == 1: # MODE print("switched mode to", mode_texts[msg.args[0]]) pycam.mode = msg.args[0] if rad_num == 2: # resolution print("switched resolution") pycam.resolution = msg.args[0] if rad_num == 3: # LED color print("set color") pycam.led_color = msg.args[0] if rad_num == 4: # effects print("switched effect") pycam.effect = msg.args[0] def button_handler(msg): # buttons addr = msg.addr button_num = int(addr.replace('/1/button','')) if msg.args[0] == 1.0: print(button_num, "is ON") if button_num == 1: pycam.tone(1200, 0.05) pycam.tone(1600, 0.05) if pycam.mode_text == "JPEG": snap_jpeg() if pycam.mode_text == "GBOY": snap_gboy() if pycam.mode_text == "GIF": snap_gif() if pycam.mode_text == "STOP": snap_stop() if button_num == 2: # focus pycam.tone(1800, 0.05) print("FOCUS") print(pycam.autofocus_status) pycam.autofocus() print(pycam.autofocus_status) pycam.tone(1400, 0.05) else: print(button_num, "is off") dispatch_map = { "/": lambda msg: print("msg:", msg.addr, msg.args), # prints all messages "/1/fader": fader_handler, "/2/fader": fader_handler, "/1/toggle": toggle_handler, "/1/button": button_handler, "/1/radio": radio_handler, "/2/radio": radio_handler } osc_server = microosc.OSCServer(socket_pool, UDP_HOST, UDP_PORT, dispatch_map) print("MicroOSC server started on ", UDP_HOST, UDP_PORT) while True: osc_server.poll() # check for incoming OSC messages if pycam.mode_text == "STOP" and pycam.stop_motion_frame != 0: # alpha blend new_frame = pycam.continuous_capture() bitmaptools.alphablend( onionskin, last_frame, new_frame, displayio.Colorspace.RGB565_SWAPPED ) pycam.blit(onionskin) elif pycam.mode_text == "GBOY": bitmaptools.dither( last_frame, pycam.continuous_capture(), displayio.Colorspace.RGB565_SWAPPED ) pycam.blit(last_frame) else: pycam.blit(pycam.continuous_capture()) pycam.keys_debounce() if pycam.shutter.long_press: print("FOCUS") print(pycam.autofocus_status) pycam.autofocus() print(pycam.autofocus_status) if pycam.shutter.short_count: print("Shutter released") if pycam.mode_text == "STOP": snap_stop() if pycam.mode_text == "GBOY": snap_gboy() if pycam.mode_text == "GIF": snap_gif() if pycam.mode_text == "JPEG": snap_jpeg() if pycam.card_detect.fell: print("SD card removed") pycam.unmount_sd_card() pycam.display.refresh() if pycam.card_detect.rose: print("SD card inserted") pycam.display_message("Mounting\nSD Card", color=0xFFFFFF) for _ in range(3): try: print("Mounting card") pycam.mount_sd_card() print("Success!") break except OSError as e: print("Retrying!", e) time.sleep(0.5) else: pycam.display_message("SD Card\nFailed!", color=0xFF0000) time.sleep(0.5) pycam.display.refresh() if pycam.up.fell: print("UP") key = settings[curr_setting] if key: setattr(pycam, key, getattr(pycam, key) + 1) if pycam.down.fell: print("DN") key = settings[curr_setting] if key: setattr(pycam, key, getattr(pycam, key) - 1) if pycam.left.fell: print("LF") curr_setting = (curr_setting + 1) % len(settings) print(settings[curr_setting]) pycam.select_setting(settings[curr_setting]) if pycam.right.fell: print("RT") curr_setting = (curr_setting - 1 + len(settings)) % len(settings) print(settings[curr_setting]) pycam.select_setting(settings[curr_setting]) if pycam.select.fell: print("SEL") if pycam.ok.fell: print("OK")
How It Works
The code boots up, connects to your WiFi, launches the camera app, and then waits for interactions from either the hardware buttons on the MEMENTO itself, or the equivalent software commands sent from your mobile device in the OSC messages.
Libraries
First, the libraries are imported. For more info on the OSC integration, check out Todbot's microosc library.
import time import os import bitmaptools import displayio import gifio import ulab.numpy as np import adafruit_pycamera import wifi import socketpool import microosc
Wireless Setup
Next, the UDP_Host
and UDP_Port
are set for the OSC server, and the WiFi access point credentials are set.
UDP_HOST = "" UDP_PORT = 8000 ssid = os.getenv("CIRCUITPY_WIFI_SSID") password = os.getenv("CIRCUITPY_WIFI_PASSWORD") print("connecting to WiFi", ssid) wifi.radio.connect(ssid, password) print("my ip address:", wifi.radio.ipv4_address) socket_pool = socketpool.SocketPool(wifi.radio)
PyCamera Initialization
The camera is set up as well as the autofocus.
pycam = adafruit_pycamera.PyCamera() pycam.autofocus_init()
Camera Functions
-
snap_jpeg()
: Captures a JPEG image -
snap_gboy()
: Captures a frame for a Game Boy-style effect (GIF) -
snap_gif()
: Records a GIF animation -
snap_stop()
: Captures a frame, displays a ghost/onion skin of the previous frame for use in stop motion animation
def snap_jpeg(): pycam.tone(600, 0.1) try: pycam.display_message("Snap!", color=0x0000FF) pycam.capture_jpeg() pycam.live_preview_mode() # pylint: disable=unused-variable except TypeError as e: pycam.display_message("Failed", color=0xFF0000) time.sleep(0.5) pycam.live_preview_mode() except RuntimeError as e: pycam.display_message("Error\nNo SD Card", color=0xFF0000) time.sleep(0.5) def snap_gboy(): pycam.tone(600, 0.1) try: f = pycam.open_next_image("gif") pycam.display_message("Snap!", color=0x00ff44) # pylint: disable=unused-variable except RuntimeError as e: pycam.display_message("Error\nNo SD Card", color=0xFF0000) time.sleep(0.5) with gifio.GifWriter( f, pycam.camera.width, pycam.camera.height, displayio.Colorspace.RGB565_SWAPPED, dither=True, ) as g: g.add_frame(last_frame, 1) def snap_gif(): pycam.tone(600, 0.1) try: f = pycam.open_next_image("gif") # pylint: disable=unused-variable except RuntimeError as e: pycam.display_message("Error\nNo SD Card", color=0xFF0000) time.sleep(0.5) i = 0 ft = [] pycam._mode_label.text = "RECORDING" # pylint: disable=protected-access pycam.display.refresh() with gifio.GifWriter( f, pycam.camera.width, pycam.camera.height, displayio.Colorspace.RGB565_SWAPPED, dither=True, ) as g: t00 = t0 = time.monotonic() while (i < 15) or not pycam.shutter_button.value: i += 1 _gifframe = pycam.continuous_capture() g.add_frame(_gifframe, 0.12) pycam.blit(_gifframe) t1 = time.monotonic() ft.append(1 / (t1 - t0)) print(end=".") t0 = t1 pycam._mode_label.text = "GIF" # pylint: disable=protected-access print(f"\nfinal size {f.tell()} for {i} frames") print(f"average framerate {i/(t1-t00)}fps") print(f"best {max(ft)} worst {min(ft)} std. deviation {np.std(ft)}") f.close() pycam.display.refresh() pycam.tone(1200, 0.15) def snap_stop(): pycam.tone(600, 0.1) pycam.capture_into_bitmap(last_frame) pycam.stop_motion_frame += 1 try: pycam.display_message("Snap!", color=0x0000FF) pycam.capture_jpeg() # pylint: disable=unused-variable except TypeError as e: pycam.display_message("Failed", color=0xFF0000) time.sleep(0.5) except RuntimeError as e: pycam.display_message("Error\nNo SD Card", color=0xFF0000) time.sleep(0.5) pycam.live_preview_mode()
OSC Message Handlers
When different OSC messages are dispatched, the message handler functions are triggered.
-
fader_handler(msg)
: Handles fader messages (float values) and adjusts the LED level based on the fader value. -
radio_handler(msg)
: Handles radio button messages for adjusting mode, resolution, LED color, and effects. -
button_handler(msg)
: Handles button messages (press/release) for setting focus and snapping the shutter
OSC Messages
The OSC messages contain path addresses and element names as well as values. For example: /1/button2 [1.0]
is page1, button2 with a value of 1.0 which the button_handler()
interprets as the focus button being pressed.
While /2/radio4 [5]
is page2, radio button 3 with a value of 5. This means the blue LED value has been selected.
When the LED brightness fader is adjusted, the message will look like this: /2/fader1 [0.629]
def fader_handler(msg): # faders """Used to handle 'fader' OscMsgs, printing it as a '*' text progress bar :param OscMsg msg: message with one required float32 value """ osc_addr = msg.addr.split('/') # chop up the address into parts # page_num = osc_addr[1] fader_num = int(osc_addr[2].replace('fader', '')) # get the number only if fader_num == 1: # led level led_val = int(msg.args[0] * 5) pycam.led_level = led_val mode_texts = ("JPEG", "GIF", "GBOY", "STOP") def radio_handler(msg): # Radio buttons osc_addr = msg.addr.split('/') # chop up the address into parts print(osc_addr) page_num = osc_addr[1] print("page_num:", page_num) rad_num = int(osc_addr[2].replace('radio', '')) # get the number only print("rad_num:", rad_num) if rad_num == 1: # MODE print("switched mode to", mode_texts[msg.args[0]]) pycam.mode = msg.args[0] if rad_num == 2: # resolution print("switched resolution") pycam.resolution = msg.args[0] if rad_num == 3: # LED color print("set color") pycam.led_color = msg.args[0] if rad_num == 4: # effects print("switched effect") pycam.effect = msg.args[0] def button_handler(msg): # buttons addr = msg.addr button_num = int(addr.replace('/1/button','')) if msg.args[0] == 1.0: print(button_num, "is ON") if button_num == 1: pycam.tone(1200, 0.05) pycam.tone(1600, 0.05) if pycam.mode_text == "JPEG": snap_jpeg() if pycam.mode_text == "GBOY": snap_gboy() if pycam.mode_text == "GIF": snap_gif() if pycam.mode_text == "STOP": snap_stop() if button_num == 2: # focus pycam.tone(1800, 0.05) print("FOCUS") print(pycam.autofocus_status) pycam.autofocus() print(pycam.autofocus_status) pycam.tone(1400, 0.05) else: print(button_num, "is off")
Dispatch Map
The dispatch_map
is a dictionary that maps the incoming OSC messages to their handler functions. You can get as granular as you like here, for example, dispatching messages from particular pages to their own functions.
dispatch_map = { "/": lambda msg: print("msg:", msg.addr, msg.args), # prints all messages "/1/fader": fader_handler, "/2/fader": fader_handler, "/1/toggle": toggle_handler, "/1/button": button_handler, "/1/radio": radio_handler, "/2/radio": radio_handler }
OSC Server
The OSC server is set up to receive messages using UDP.
osc_server = microosc.OSCServer(socket_pool, UDP_HOST, UDP_PORT, dispatch_map)
Main Loop
The main loop the code continuously polls the OSC server for incoming messages, which are then used by the dispatch_map
.
Additionally, all of the hardware buttons on the MEMENTO are used to snap the shutter, autofocus on long hold, and adjust the settings. These are the same as in the original PyCamera library example code, but some of the code has been moved into functions that both the buttons and the OSC messages can use.
osc_server.poll() # check for incoming OSC messages if pycam.mode_text == "STOP" and pycam.stop_motion_frame != 0: # alpha blend new_frame = pycam.continuous_capture() bitmaptools.alphablend( onionskin, last_frame, new_frame, displayio.Colorspace.RGB565_SWAPPED ) pycam.blit(onionskin) elif pycam.mode_text == "GBOY": bitmaptools.dither( last_frame, pycam.continuous_capture(), displayio.Colorspace.RGB565_SWAPPED ) pycam.blit(last_frame) else: pycam.blit(pycam.continuous_capture()) pycam.keys_debounce() if pycam.shutter.long_press: print("FOCUS") print(pycam.autofocus_status) pycam.autofocus() print(pycam.autofocus_status) if pycam.shutter.short_count: print("Shutter released") if pycam.mode_text == "STOP": snap_stop() if pycam.mode_text == "GBOY": snap_gboy() if pycam.mode_text == "GIF": snap_gif() if pycam.mode_text == "JPEG": snap_jpeg() if pycam.card_detect.fell: print("SD card removed") pycam.unmount_sd_card() pycam.display.refresh() if pycam.card_detect.rose: print("SD card inserted") pycam.display_message("Mounting\nSD Card", color=0xFFFFFF) for _ in range(3): try: print("Mounting card") pycam.mount_sd_card() print("Success!") break except OSError as e: print("Retrying!", e) time.sleep(0.5) else: pycam.display_message("SD Card\nFailed!", color=0xFF0000) time.sleep(0.5) pycam.display.refresh() if pycam.up.fell: print("UP") key = settings[curr_setting] if key: setattr(pycam, key, getattr(pycam, key) + 1) if pycam.down.fell: print("DN") key = settings[curr_setting] if key: setattr(pycam, key, getattr(pycam, key) - 1) if pycam.left.fell: print("LF") curr_setting = (curr_setting + 1) % len(settings) print(settings[curr_setting]) pycam.select_setting(settings[curr_setting]) if pycam.right.fell: print("RT") curr_setting = (curr_setting - 1 + len(settings)) % len(settings) print(settings[curr_setting]) pycam.select_setting(settings[curr_setting]) if pycam.select.fell: print("SEL") if pycam.ok.fell: print("OK")
Text editor powered by tinymce.