Simple test
Ensure your device works with this simple test.
examples/camera/code.py
1# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
2# SPDX-FileCopyrightText: 2023 Limor Fried for Adafruit Industries
3#
4# SPDX-License-Identifier: Unlicense
5import ssl
6import os
7import time
8import socketpool
9import adafruit_requests
10import rtc
11import adafruit_ntp
12import wifi
13import bitmaptools
14import displayio
15import gifio
16import ulab.numpy as np
17
18import adafruit_pycamera
19
20# Wifi details are in settings.toml file, also,
21# timezone info should be included to allow local time and DST adjustments
22# # UTC_OFFSET, if present, will override TZ and DST and no API query will be done
23# UTC_OFFSET=-25200
24# # TZ="America/Phoenix"
25
26UTC_OFFSET = os.getenv("UTC_OFFSET")
27TZ = os.getenv("TZ")
28
29print(f"Connecting to {os.getenv('CIRCUITPY_WIFI_SSID')}")
30SSID = os.getenv("CIRCUITPY_WIFI_SSID")
31PASSWORD = os.getenv("CIRCUITPY_WIFI_PASSWORD")
32
33if SSID and PASSWORD:
34 wifi.radio.connect(
35 os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
36 )
37 if wifi.radio.connected:
38 print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}!")
39 print("My IP address is", wifi.radio.ipv4_address)
40 pool = socketpool.SocketPool(wifi.radio)
41
42 if UTC_OFFSET is None:
43 requests = adafruit_requests.Session(pool, ssl.create_default_context())
44 response = requests.get("http://worldtimeapi.org/api/timezone/" + TZ)
45 response_as_json = response.json()
46 UTC_OFFSET = response_as_json["raw_offset"] + response_as_json["dst_offset"]
47 print(f"UTC_OFFSET: {UTC_OFFSET}")
48
49 ntp = adafruit_ntp.NTP(
50 pool, server="pool.ntp.org", tz_offset=UTC_OFFSET // 3600
51 )
52
53 print(f"ntp time: {ntp.datetime}")
54 rtc.RTC().datetime = ntp.datetime
55 else:
56 print("Wifi failed to connect. Time not set.")
57else:
58 print("Wifi config not found in settintgs.toml. Time not set.")
59
60pycam = adafruit_pycamera.PyCamera()
61# pycam.live_preview_mode()
62
63settings = (
64 None,
65 "resolution",
66 "effect",
67 "mode",
68 "led_level",
69 "led_color",
70 "timelapse_rate",
71)
72curr_setting = 0
73
74print("Starting!")
75# pycam.tone(200, 0.1)
76last_frame = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)
77onionskin = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)
78timelapse_remaining = None
79timelapse_timestamp = None
80
81while True:
82 if pycam.mode_text == "STOP" and pycam.stop_motion_frame != 0:
83 # alpha blend
84 new_frame = pycam.continuous_capture()
85 bitmaptools.alphablend(
86 onionskin, last_frame, new_frame, displayio.Colorspace.RGB565_SWAPPED
87 )
88 pycam.blit(onionskin)
89 elif pycam.mode_text == "GBOY":
90 bitmaptools.dither(
91 last_frame, pycam.continuous_capture(), displayio.Colorspace.RGB565_SWAPPED
92 )
93 pycam.blit(last_frame)
94 elif pycam.mode_text == "LAPS":
95 if timelapse_remaining is None:
96 pycam.timelapsestatus_label.text = "STOP"
97 else:
98 timelapse_remaining = timelapse_timestamp - time.time()
99 pycam.timelapsestatus_label.text = f"{timelapse_remaining}s / "
100 # Manually updating the label text a second time ensures that the label
101 # is re-painted over the blitted preview.
102 pycam.timelapse_rate_label.text = pycam.timelapse_rate_label.text
103 pycam.timelapse_submode_label.text = pycam.timelapse_submode_label.text
104
105 # only in high power mode do we continuously preview
106 if (timelapse_remaining is None) or (
107 pycam.timelapse_submode_label.text == "HiPwr"
108 ):
109 pycam.blit(pycam.continuous_capture())
110 if pycam.timelapse_submode_label.text == "LowPwr" and (
111 timelapse_remaining is not None
112 ):
113 pycam.display.brightness = 0.05
114 else:
115 pycam.display.brightness = 1
116 pycam.display.refresh()
117
118 if timelapse_remaining is not None and timelapse_remaining <= 0:
119 # no matter what, show what was just on the camera
120 pycam.blit(pycam.continuous_capture())
121 # pycam.tone(200, 0.1) # uncomment to add a beep when a photo is taken
122 try:
123 pycam.display_message("Snap!", color=0x0000FF)
124 pycam.capture_jpeg()
125 except TypeError as e:
126 pycam.display_message("Failed", color=0xFF0000)
127 time.sleep(0.5)
128 except RuntimeError as e:
129 pycam.display_message("Error\nNo SD Card", color=0xFF0000)
130 time.sleep(0.5)
131 pycam.live_preview_mode()
132 pycam.display.refresh()
133 pycam.blit(pycam.continuous_capture())
134 timelapse_timestamp = (
135 time.time() + pycam.timelapse_rates[pycam.timelapse_rate] + 1
136 )
137 else:
138 pycam.blit(pycam.continuous_capture())
139 # print("\t\t", capture_time, blit_time)
140
141 pycam.keys_debounce()
142 # test shutter button
143 if pycam.shutter.long_press:
144 print("FOCUS")
145 print(pycam.autofocus_status)
146 pycam.autofocus()
147 print(pycam.autofocus_status)
148 if pycam.shutter.short_count:
149 print("Shutter released")
150 if pycam.mode_text == "STOP":
151 pycam.capture_into_bitmap(last_frame)
152 pycam.stop_motion_frame += 1
153 try:
154 pycam.display_message("Snap!", color=0x0000FF)
155 pycam.capture_jpeg()
156 except TypeError as e:
157 pycam.display_message("Failed", color=0xFF0000)
158 time.sleep(0.5)
159 except RuntimeError as e:
160 pycam.display_message("Error\nNo SD Card", color=0xFF0000)
161 time.sleep(0.5)
162 pycam.live_preview_mode()
163
164 if pycam.mode_text == "GBOY":
165 try:
166 f = pycam.open_next_image("gif")
167 except RuntimeError as e:
168 pycam.display_message("Error\nNo SD Card", color=0xFF0000)
169 time.sleep(0.5)
170 continue
171
172 with gifio.GifWriter(
173 f,
174 pycam.camera.width,
175 pycam.camera.height,
176 displayio.Colorspace.RGB565_SWAPPED,
177 dither=True,
178 ) as g:
179 g.add_frame(last_frame, 1)
180
181 if pycam.mode_text == "GIF":
182 try:
183 f = pycam.open_next_image("gif")
184 except RuntimeError as e:
185 pycam.display_message("Error\nNo SD Card", color=0xFF0000)
186 time.sleep(0.5)
187 continue
188 i = 0
189 ft = []
190 pycam._mode_label.text = "RECORDING" # pylint: disable=protected-access
191
192 pycam.display.refresh()
193 with gifio.GifWriter(
194 f,
195 pycam.camera.width,
196 pycam.camera.height,
197 displayio.Colorspace.RGB565_SWAPPED,
198 dither=True,
199 ) as g:
200 t00 = t0 = time.monotonic()
201 while (i < 15) or not pycam.shutter_button.value:
202 i += 1
203 _gifframe = pycam.continuous_capture()
204 g.add_frame(_gifframe, 0.12)
205 pycam.blit(_gifframe)
206 t1 = time.monotonic()
207 ft.append(1 / (t1 - t0))
208 print(end=".")
209 t0 = t1
210 pycam._mode_label.text = "GIF" # pylint: disable=protected-access
211 print(f"\nfinal size {f.tell()} for {i} frames")
212 print(f"average framerate {i / (t1 - t00)}fps")
213 print(f"best {max(ft)} worst {min(ft)} std. deviation {np.std(ft)}")
214 f.close()
215 pycam.display.refresh()
216
217 if pycam.mode_text == "JPEG":
218 pycam.tone(200, 0.1)
219 try:
220 pycam.display_message("Snap!", color=0x0000FF)
221 pycam.capture_jpeg()
222 pycam.live_preview_mode()
223 except TypeError as e:
224 pycam.display_message("Failed", color=0xFF0000)
225 time.sleep(0.5)
226 pycam.live_preview_mode()
227 except RuntimeError as e:
228 pycam.display_message("Error\nNo SD Card", color=0xFF0000)
229 time.sleep(0.5)
230
231 if pycam.card_detect.fell:
232 print("SD card removed")
233 pycam.unmount_sd_card()
234 pycam.display.refresh()
235 if pycam.card_detect.rose:
236 print("SD card inserted")
237 pycam.display_message("Mounting\nSD Card", color=0xFFFFFF)
238 for _ in range(3):
239 try:
240 print("Mounting card")
241 pycam.mount_sd_card()
242 print("Success!")
243 break
244 except OSError as e:
245 print("Retrying!", e)
246 time.sleep(0.5)
247 else:
248 pycam.display_message("SD Card\nFailed!", color=0xFF0000)
249 time.sleep(0.5)
250 pycam.display.refresh()
251
252 if pycam.up.fell:
253 print("UP")
254 key = settings[curr_setting]
255 if key:
256 print("getting", key, getattr(pycam, key))
257 setattr(pycam, key, getattr(pycam, key) + 1)
258 if pycam.down.fell:
259 print("DN")
260 key = settings[curr_setting]
261 if key:
262 setattr(pycam, key, getattr(pycam, key) - 1)
263 if pycam.right.fell:
264 print("RT")
265 curr_setting = (curr_setting + 1) % len(settings)
266 if pycam.mode_text != "LAPS" and settings[curr_setting] == "timelapse_rate":
267 curr_setting = (curr_setting + 1) % len(settings)
268 print(settings[curr_setting])
269 # new_res = min(len(pycam.resolutions)-1, pycam.get_resolution()+1)
270 # pycam.set_resolution(pycam.resolutions[new_res])
271 pycam.select_setting(settings[curr_setting])
272 if pycam.left.fell:
273 print("LF")
274 curr_setting = (curr_setting - 1 + len(settings)) % len(settings)
275 if pycam.mode_text != "LAPS" and settings[curr_setting] == "timelaps_rate":
276 curr_setting = (curr_setting + 1) % len(settings)
277 print(settings[curr_setting])
278 pycam.select_setting(settings[curr_setting])
279 # new_res = max(1, pycam.get_resolution()-1)
280 # pycam.set_resolution(pycam.resolutions[new_res])
281 if pycam.select.fell:
282 print("SEL")
283 if pycam.mode_text == "LAPS":
284 pycam.timelapse_submode += 1
285 pycam.display.refresh()
286 if pycam.ok.fell:
287 print("OK")
288 if pycam.mode_text == "LAPS":
289 if timelapse_remaining is None: # stopped
290 print("Starting timelapse")
291 timelapse_remaining = pycam.timelapse_rates[pycam.timelapse_rate]
292 timelapse_timestamp = time.time() + timelapse_remaining + 1
293 # dont let the camera take over auto-settings
294 saved_settings = pycam.get_camera_autosettings()
295 # print(f"Current exposure {saved_settings=}")
296 pycam.set_camera_exposure(saved_settings["exposure"])
297 pycam.set_camera_gain(saved_settings["gain"])
298 pycam.set_camera_wb(saved_settings["wb"])
299 else: # is running, turn off
300 print("Stopping timelapse")
301
302 timelapse_remaining = None
303 pycam.camera.exposure_ctrl = True
304 pycam.set_camera_gain(None) # go back to autogain
305 pycam.set_camera_wb(None) # go back to autobalance
306 pycam.set_camera_exposure(None) # go back to auto shutter