|
| 1 | +# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MIT |
| 4 | + |
| 5 | +import os |
| 6 | +import time |
| 7 | +import gc |
| 8 | +import microcontroller |
| 9 | +import digitalio |
| 10 | +import sdcardio |
| 11 | +import storage |
| 12 | +import board |
| 13 | +import busio |
| 14 | +from adafruit_ticks import ticks_ms, ticks_add, ticks_diff |
| 15 | +import displayio |
| 16 | +from adafruit_neokey.neokey1x4 import NeoKey1x4 |
| 17 | +import adafruit_st7789 |
| 18 | +from adafruit_display_text import label |
| 19 | +from adafruit_bitmap_font import bitmap_font |
| 20 | +import adafruit_ov5640 |
| 21 | + |
| 22 | +gc.collect() |
| 23 | +# Release any resources currently in use for the displays |
| 24 | +displayio.release_displays() |
| 25 | +gc.collect() |
| 26 | +print("Initializing SD card") |
| 27 | +spi = busio.SPI(clock=board.GP18, MOSI=board.GP19, MISO=board.GP16) |
| 28 | +sd_cs = board.GP17 |
| 29 | +sdcard = sdcardio.SDCard(spi, sd_cs) |
| 30 | +vfs = storage.VfsFat(sdcard) |
| 31 | +storage.mount(vfs, "/sd") |
| 32 | +gc.collect() |
| 33 | +i2c = busio.I2C(board.GP5, board.GP4) |
| 34 | +tft_cs = board.GP21 |
| 35 | +tft_dc = board.GP20 |
| 36 | +display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=None) |
| 37 | +display = adafruit_st7789.ST7789(display_bus, width=240, height=240, rowstart=80, rotation=0) |
| 38 | + |
| 39 | +gc.collect() |
| 40 | +splash = displayio.Group() |
| 41 | +display.root_group = splash |
| 42 | + |
| 43 | +gc.collect() |
| 44 | +font = bitmap_font.load_font("Helvetica-Bold-16.pcf") |
| 45 | +t2 = label.Label(font, text="Start?", color=(255, 255, 255)) |
| 46 | +t2.anchor_point = (0.5, 0.0) |
| 47 | +t2.anchored_position = (display.width / 2, 0) |
| 48 | +splash.append(t2) |
| 49 | +t1 = label.Label(font, text="Interval: 3s", color=(255, 255, 255)) |
| 50 | +t1.anchor_point = (0.5, 1.0) |
| 51 | +t1.anchored_position = (display.width / 2, display.height) |
| 52 | +splash.append(t1) |
| 53 | + |
| 54 | +print("construct camera") |
| 55 | +gc.collect() |
| 56 | +reset = digitalio.DigitalInOut(board.GP14) |
| 57 | +cam = adafruit_ov5640.OV5640( |
| 58 | + i2c, |
| 59 | + data_pins=( |
| 60 | + board.GP6, |
| 61 | + board.GP7, |
| 62 | + board.GP8, |
| 63 | + board.GP9, |
| 64 | + board.GP10, |
| 65 | + board.GP11, |
| 66 | + board.GP12, |
| 67 | + board.GP13, |
| 68 | + ), |
| 69 | + clock=board.GP3, |
| 70 | + vsync=board.GP0, |
| 71 | + href=board.GP2, |
| 72 | + mclk=None, |
| 73 | + shutdown=None, |
| 74 | + reset=reset, |
| 75 | + size=adafruit_ov5640.OV5640_SIZE_QCIF, |
| 76 | +) |
| 77 | +print("print chip id") |
| 78 | +print(cam.chip_id) |
| 79 | +gc.collect() |
| 80 | +cam.quality = 3 |
| 81 | +gc.collect() |
| 82 | + |
| 83 | +def exists(filename): |
| 84 | + try: |
| 85 | + os.stat(filename) |
| 86 | + return True |
| 87 | + except OSError as _: |
| 88 | + return False |
| 89 | + |
| 90 | +_image_counter = 0 |
| 91 | + |
| 92 | +def open_next_image(): |
| 93 | + global _image_counter # pylint: disable=global-statement |
| 94 | + while True: |
| 95 | + filename = f"/sd/img{_image_counter:04d}.jpg" |
| 96 | + _image_counter += 1 |
| 97 | + if exists(filename): |
| 98 | + continue |
| 99 | + print("# writing to", filename) |
| 100 | + return open(filename, "wb") |
| 101 | + |
| 102 | +cam.colorspace = adafruit_ov5640.OV5640_COLOR_RGB |
| 103 | +gc.collect() |
| 104 | + |
| 105 | +bitmap = displayio.Bitmap(cam.width, cam.height, 65535) |
| 106 | +if bitmap is None: |
| 107 | + raise SystemExit("Could not allocate a bitmap") |
| 108 | + |
| 109 | +tg = displayio.TileGrid(bitmap, |
| 110 | + pixel_shader=displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB565_SWAPPED), |
| 111 | + x=int((display.width - bitmap.width) / 2), y=int((display.height - bitmap.height) / 2) |
| 112 | +) |
| 113 | +splash.append(tg) |
| 114 | +gc.collect() |
| 115 | + |
| 116 | +# Create a NeoKey object |
| 117 | +neokey = NeoKey1x4(i2c, addr=0x30) |
| 118 | +# states for key presses |
| 119 | +key_0_state = False |
| 120 | +key_1_state = False |
| 121 | +key_2_state = False |
| 122 | +key_3_state = False |
| 123 | + |
| 124 | +tm0 = time.monotonic_ns() |
| 125 | +display.auto_refresh = False |
| 126 | +mode = 0 |
| 127 | +intervals = [3, 5, 10, 30, 60] |
| 128 | +index = 0 |
| 129 | +clock = ticks_ms() |
| 130 | +count = 0 |
| 131 | +b = bytearray() |
| 132 | +while True: |
| 133 | + if mode == 0: # preview |
| 134 | + cam.capture(bitmap) |
| 135 | + bitmap.dirty() |
| 136 | + display.refresh(minimum_frames_per_second=0) |
| 137 | + tm1 = time.monotonic_ns() |
| 138 | + # print("fps", 1e9 / (t1 - t0)) |
| 139 | + tm0 = tm1 |
| 140 | + if mode == 1: # timelapse |
| 141 | + if ticks_diff(ticks_ms(), clock) >= intervals[index]*1000: |
| 142 | + gc.collect() |
| 143 | + time.sleep(0.01) |
| 144 | + jpeg = cam.capture(b) |
| 145 | + # print(f"Captured {len(jpeg)} bytes of jpeg data") |
| 146 | + print(f" (had allocated {cam.capture_buffer_size} bytes") |
| 147 | + print(f"Resolution {cam.width}x{cam.height}") |
| 148 | + try: |
| 149 | + with open_next_image() as f: |
| 150 | + f.write(jpeg) |
| 151 | + print("# Wrote image") |
| 152 | + except OSError as e: |
| 153 | + print(e) |
| 154 | + count += 1 |
| 155 | + t2.text = f"Timelapsing! {count} photos taken" |
| 156 | + display.refresh(minimum_frames_per_second=0) |
| 157 | + clock = ticks_add(clock, intervals[index]*1000) |
| 158 | + if not neokey[0] and key_0_state: |
| 159 | + key_0_state = False |
| 160 | + neokey.pixels[0] = 0x0 |
| 161 | + if not neokey[1] and key_1_state: |
| 162 | + key_1_state = False |
| 163 | + neokey.pixels[1] = 0x0 |
| 164 | + if not neokey[2] and key_2_state: |
| 165 | + key_2_state = False |
| 166 | + neokey.pixels[2] = 0x0 |
| 167 | + if not neokey[3] and key_3_state: |
| 168 | + key_3_state = False |
| 169 | + neokey.pixels[3] = 0x0 |
| 170 | + # if 1st neokey is pressed... |
| 171 | + if neokey[0] and not key_0_state: |
| 172 | + mode = 1 |
| 173 | + print("Button A") |
| 174 | + # turn on NeoPixel |
| 175 | + neokey.pixels[0] = 0xFF0000 |
| 176 | + time.sleep(.2) |
| 177 | + try: |
| 178 | + del bitmap |
| 179 | + del tg |
| 180 | + except KeyError: |
| 181 | + continue |
| 182 | + gc.collect() |
| 183 | + #cam.size=adafruit_ov5640.OV5640_SIZE_VGA |
| 184 | + cam.colorspace = adafruit_ov5640.OV5640_COLOR_JPEG |
| 185 | + b = bytearray(cam.capture_buffer_size) |
| 186 | + t1.text=f"Taking photos every {intervals[index]}s" |
| 187 | + clock = ticks_ms() |
| 188 | + key_0_state = True |
| 189 | + |
| 190 | + # if 2nd neokey is pressed...change interval |
| 191 | + if neokey[1] and not key_1_state: |
| 192 | + print("Button B") |
| 193 | + # turn on NeoPixel |
| 194 | + neokey.pixels[1] = 0xFFFF00 |
| 195 | + time.sleep(.2) |
| 196 | + index = (index + 1) % len(intervals) |
| 197 | + t1.text=f"Interval: {intervals[index]}s" |
| 198 | + key_1_state = True |
| 199 | + |
| 200 | + # if 3rd neokey is pressed...autofocus |
| 201 | + if neokey[2] and not key_2_state: |
| 202 | + print("Button C") |
| 203 | + # turn on NeoPixel |
| 204 | + neokey.pixels[2] = 0x00FF00 |
| 205 | + time.sleep(.2) |
| 206 | + t2.text = "Autofocusing.." |
| 207 | + try: |
| 208 | + display.refresh(minimum_frames_per_second=0) |
| 209 | + cam.autofocus() |
| 210 | + except AttributeError: |
| 211 | + print("This camera module does not have autofocus..") |
| 212 | + continue |
| 213 | + key_2_state = True |
| 214 | + |
| 215 | + # if 4th neokey is pressed...stop timelapse & reboot |
| 216 | + if neokey[3] and not key_3_state: |
| 217 | + print("Button D") |
| 218 | + # turn on NeoPixel |
| 219 | + neokey.pixels[3] = 0x00FFFF |
| 220 | + time.sleep(.2) |
| 221 | + gc.collect() |
| 222 | + del b |
| 223 | + gc.collect() |
| 224 | + t2.text = "Ending timelapse.." |
| 225 | + t1.text = f"Took {count} photos" |
| 226 | + display.refresh(minimum_frames_per_second=0) |
| 227 | + print("resetting..") |
| 228 | + time.sleep(5) |
| 229 | + del t1 |
| 230 | + del t2 |
| 231 | + gc.collect() |
| 232 | + neokey.pixels[3] = 0x000000 |
| 233 | + microcontroller.reset() |
| 234 | + key_3_state = True |
0 commit comments