From f417055983349b53e5d51aedcbf51f1b2e8efd37 Mon Sep 17 00:00:00 2001 From: ladyada Date: Sat, 27 Jan 2024 13:09:42 -0500 Subject: [PATCH 1/7] fix for https://github.com/adafruit/Adafruit_CircuitPython_PyCamera/issues/18 --- adafruit_pycamera/__init__.py | 4 ++-- examples/camera/code.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/adafruit_pycamera/__init__.py b/adafruit_pycamera/__init__.py index 89bb283..bd76ad9 100644 --- a/adafruit_pycamera/__init__.py +++ b/adafruit_pycamera/__init__.py @@ -63,9 +63,9 @@ _AW_CARDDET = const(8) _AW_SDPWR = const(9) _AW_DOWN = const(15) -_AW_LEFT = const(14) +_AW_RIGHT = const(14) _AW_UP = const(13) -_AW_RIGHT = const(12) +_AW_LEFT = const(12) _AW_OK = const(11) _NVM_RESOLUTION = const(1) diff --git a/examples/camera/code.py b/examples/camera/code.py index ddb0d5e..179d66e 100644 --- a/examples/camera/code.py +++ b/examples/camera/code.py @@ -158,15 +158,15 @@ key = settings[curr_setting] if key: setattr(pycam, key, getattr(pycam, key) - 1) - if pycam.left.fell: - print("LF") + if pycam.right.fell: + print("RT") curr_setting = (curr_setting + 1) % len(settings) print(settings[curr_setting]) # new_res = min(len(pycam.resolutions)-1, pycam.get_resolution()+1) # pycam.set_resolution(pycam.resolutions[new_res]) pycam.select_setting(settings[curr_setting]) - if pycam.right.fell: - print("RT") + if pycam.left.fell: + print("LF") curr_setting = (curr_setting - 1 + len(settings)) % len(settings) print(settings[curr_setting]) pycam.select_setting(settings[curr_setting]) From 40ff754214faaf75e2e54a7c3fec0bf4fba6f4a3 Mon Sep 17 00:00:00 2001 From: ladyada Date: Sat, 27 Jan 2024 13:58:22 -0500 Subject: [PATCH 2/7] some beginnings of timelapse --- adafruit_pycamera/__init__.py | 65 +++++++++++++++++++++++++++++++++-- examples/camera/code.py | 19 +++++++++- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/adafruit_pycamera/__init__.py b/adafruit_pycamera/__init__.py index bd76ad9..472e951 100644 --- a/adafruit_pycamera/__init__.py +++ b/adafruit_pycamera/__init__.py @@ -71,6 +71,7 @@ _NVM_RESOLUTION = const(1) _NVM_EFFECT = const(2) _NVM_MODE = const(3) +_NVM_TIMELAPSE_RATE = const(4) class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-public-methods @@ -168,7 +169,25 @@ class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-pub "Sepia", "Solarize", ) - modes = ("JPEG", "GIF", "GBOY", "STOP") + + timelapse_rates = ( + 5, + 10, + 20, + 30, + 60, + 90, + 60 * 2, + 60 * 3, + 60 * 4, + 60 * 5, + 60 * 10, + 60 * 15, + 60 * 30, + 60 * 60 + ) + + modes = ("JPEG", "GIF", "GBOY", "STOP", "LAPS") _INIT_SEQUENCE = ( b"\x01\x80\x78" # _SWRESET and Delay 120ms @@ -249,13 +268,13 @@ def make_debounced_expander_pin(pin_no): def make_camera_ui(self): """Create displayio widgets for the standard camera UI""" self._sd_label = label.Label( - terminalio.FONT, text="SD ??", color=0x0, x=150, y=10, scale=2 + terminalio.FONT, text="SD ??", color=0x0, x=170, y=10, scale=2 ) self._effect_label = label.Label( terminalio.FONT, text="EFFECT", color=0xFFFFFF, x=4, y=10, scale=2 ) self._mode_label = label.Label( - terminalio.FONT, text="MODE", color=0xFFFFFF, x=150, y=10, scale=2 + terminalio.FONT, text="MODE", color=0xFFFFFF, x=170, y=10, scale=2 ) self._topbar = displayio.Group() self._res_label = label.Label( @@ -268,8 +287,19 @@ def make_camera_ui(self): self._botbar.append(self._effect_label) self._botbar.append(self._mode_label) + self._timelapsebar = displayio.Group(x=0, y=180) + self._timelapse_rate_label = label.Label( + terminalio.FONT, text="Time", color=0xFFFFFF, x=150, y=10, scale=2 + ) + self._timelapsestatus_label = label.Label( + terminalio.FONT, text="Status", color=0xFFFFFF, x=0, y=10, scale=2 + ) + self._timelapsebar.append(self._timelapse_rate_label) + self._timelapsebar.append(self._timelapsestatus_label) + self.splash.append(self._topbar) self.splash.append(self._botbar) + self.splash.append(self._timelapsebar) def init_accelerometer(self): """Initialize the accelerometer""" @@ -338,6 +368,7 @@ def init_camera(self, init_autofocus=True) -> None: self.camera.saturation = 3 self.resolution = microcontroller.nvm[_NVM_RESOLUTION] self.mode = microcontroller.nvm[_NVM_MODE] + self.timelapse_rate = microcontroller.nvm[_NVM_TIMELAPSE_RATE] if init_autofocus: self.autofocus_init() @@ -461,6 +492,9 @@ def select_setting(self, setting_name): self._res_label.text = self.resolutions[self._resolution] self._mode_label.color = 0xFFFFFF self._mode_label.background_color = 0x0 + self._timelapse_rate_label.color = 0xFFFFFF + self._timelapse_rate_label.background_color = 0x0 + if setting_name == "effect": self._effect_label.color = 0x0 self._effect_label.background_color = 0xFFFFFF @@ -478,6 +512,13 @@ def select_setting(self, setting_name): self._res_label.text = "LED CLR" self._res_label.color = 0x0 self._res_label.background_color = 0xFFFFFF + elif setting_name == "led_color": + self._res_label.text = "LED CLR" + self._res_label.color = 0x0 + self._res_label.background_color = 0xFFFFFF + elif setting_name == "timelapse_rate": + self._timelapse_rate_label.color = 0x0 + self._timelapse_rate_label.background_color = 0xFFFFFF self.display.refresh() @property @@ -538,6 +579,24 @@ def resolution(self, res): self._res_label.text = self.resolutions[res] self.display.refresh() + + @property + def timelapse_rate(self): + """Get or set the amount of time between timelapse shots""" + return self._timelapse_rate + + @timelapse_rate.setter + def timelapse_rate(self, setting): + setting = (setting + len(self.timelapse_rates)) % len(self.timelapse_rates) + self._timelapse_rate = setting + if self.timelapse_rates[setting] < 60: + self._timelapse_rate_label.text = "%d S" % self.timelapse_rates[setting] + else: + self._timelapse_rate_label.text = "%d M" % (self.timelapse_rates[setting] / 60) + microcontroller.nvm[_NVM_TIMELAPSE_RATE] = setting + self.display.refresh() + + def init_display(self): """Initialize the TFT display""" # construct displayio by hand diff --git a/examples/camera/code.py b/examples/camera/code.py index 179d66e..6e4e81e 100644 --- a/examples/camera/code.py +++ b/examples/camera/code.py @@ -14,13 +14,15 @@ pycam = adafruit_pycamera.PyCamera() # pycam.live_preview_mode() -settings = (None, "resolution", "effect", "mode", "led_level", "led_color") +settings = (None, "resolution", "effect", "mode", "led_level", "led_color", "timelapse_rate") curr_setting = 0 print("Starting!") # pycam.tone(200, 0.1) last_frame = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535) onionskin = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535) +timelapse_remaining = None + while True: if pycam.mode_text == "STOP" and pycam.stop_motion_frame != 0: # alpha blend @@ -34,6 +36,15 @@ last_frame, pycam.continuous_capture(), displayio.Colorspace.RGB565_SWAPPED ) pycam.blit(last_frame) + elif pycam.mode_text == "LAPS": + pycam.blit(pycam.continuous_capture()) + pycam._timelapse_rate_label.text = pycam._timelapse_rate_label.text + if timelapse_remaining is None: + pycam._timelapsestatus_label.text = "STOP" + #pycam.display_message("Timelapse\nNot Running\nPress Select to set timing") + else: + pycam.display_message("%d Seconds left", timelapse_remaining) + pycam.display.refresh() else: pycam.blit(pycam.continuous_capture()) # print("\t\t", capture_time, blit_time) @@ -127,6 +138,7 @@ except RuntimeError as e: pycam.display_message("Error\nNo SD Card", color=0xFF0000) time.sleep(0.5) + if pycam.card_detect.fell: print("SD card removed") pycam.unmount_sd_card() @@ -152,6 +164,7 @@ print("UP") key = settings[curr_setting] if key: + print("getting", key, getattr(pycam, key)) setattr(pycam, key, getattr(pycam, key) + 1) if pycam.down.fell: print("DN") @@ -161,6 +174,8 @@ if pycam.right.fell: print("RT") curr_setting = (curr_setting + 1) % len(settings) + if pycam.mode_text != "LAPS" and settings[curr_setting] == "timelapse_rate": + curr_setting = (curr_setting + 1) % len(settings) print(settings[curr_setting]) # new_res = min(len(pycam.resolutions)-1, pycam.get_resolution()+1) # pycam.set_resolution(pycam.resolutions[new_res]) @@ -168,6 +183,8 @@ if pycam.left.fell: print("LF") curr_setting = (curr_setting - 1 + len(settings)) % len(settings) + if pycam.mode_text != "LAPS" and settings[curr_setting] == "timelaps_rate": + curr_setting = (curr_setting + 1) % len(settings) print(settings[curr_setting]) pycam.select_setting(settings[curr_setting]) # new_res = max(1, pycam.get_resolution()-1) From ad34c6c0a113055f2b1be045f691431325ce073d Mon Sep 17 00:00:00 2001 From: ladyada Date: Sat, 27 Jan 2024 14:44:12 -0500 Subject: [PATCH 3/7] more timelapse! --- adafruit_pycamera/__init__.py | 6 ++++-- examples/camera/code.py | 27 ++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/adafruit_pycamera/__init__.py b/adafruit_pycamera/__init__.py index 472e951..97f9de7 100644 --- a/adafruit_pycamera/__init__.py +++ b/adafruit_pycamera/__init__.py @@ -289,11 +289,13 @@ def make_camera_ui(self): self._timelapsebar = displayio.Group(x=0, y=180) self._timelapse_rate_label = label.Label( - terminalio.FONT, text="Time", color=0xFFFFFF, x=150, y=10, scale=2 + terminalio.FONT, text="Time", color=0xFFFFFF,x=90, y=10, scale=2 ) + self._timelapse_rate_label.background_color = None self._timelapsestatus_label = label.Label( terminalio.FONT, text="Status", color=0xFFFFFF, x=0, y=10, scale=2 ) + self._timelapsestatus_label.background_color = None self._timelapsebar.append(self._timelapse_rate_label) self._timelapsebar.append(self._timelapsestatus_label) @@ -493,7 +495,7 @@ def select_setting(self, setting_name): self._mode_label.color = 0xFFFFFF self._mode_label.background_color = 0x0 self._timelapse_rate_label.color = 0xFFFFFF - self._timelapse_rate_label.background_color = 0x0 + self._timelapse_rate_label.background_color = None if setting_name == "effect": self._effect_label.color = 0x0 diff --git a/examples/camera/code.py b/examples/camera/code.py index 6e4e81e..a2e9ddc 100644 --- a/examples/camera/code.py +++ b/examples/camera/code.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Unlicense import time - +import math import bitmaptools import displayio import gifio @@ -22,6 +22,7 @@ last_frame = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535) onionskin = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535) timelapse_remaining = None +timelapse_timestamp = None while True: if pycam.mode_text == "STOP" and pycam.stop_motion_frame != 0: @@ -41,10 +42,24 @@ pycam._timelapse_rate_label.text = pycam._timelapse_rate_label.text if timelapse_remaining is None: pycam._timelapsestatus_label.text = "STOP" - #pycam.display_message("Timelapse\nNot Running\nPress Select to set timing") else: - pycam.display_message("%d Seconds left", timelapse_remaining) + timelapse_remaining = timelapse_timestamp - time.time() + pycam._timelapsestatus_label.text = f"{timelapse_remaining}s / " pycam.display.refresh() + + if timelapse_remaining is not None and timelapse_remaining <= 0: + #pycam.tone(200, 0.1) # uncomment to add a beep when a photo is taken + try: + pycam.display_message("Snap!", color=0x0000FF) + pycam.capture_jpeg() + 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() + timelapse_timestamp = time.time() + pycam.timelapse_rates[pycam.timelapse_rate] + 1 else: pycam.blit(pycam.continuous_capture()) # print("\t\t", capture_time, blit_time) @@ -193,3 +208,9 @@ print("SEL") if pycam.ok.fell: print("OK") + if pycam.mode_text == "LAPS": + if timelapse_remaining is None: # stopped + timelapse_remaining = pycam.timelapse_rates[pycam.timelapse_rate] + timelapse_timestamp = time.time() + timelapse_remaining + 1 + else: # is running, turn off + timelapse_remaining = None From fa81fa01a812c839b95dd4bb6b75ef73c962856e Mon Sep 17 00:00:00 2001 From: ladyada Date: Sat, 27 Jan 2024 15:07:20 -0500 Subject: [PATCH 4/7] add low power modes: med power turns off the camera preview, low power also dims the screen --- adafruit_pycamera/__init__.py | 26 ++++++++++++++++++++++++-- examples/camera/code.py | 19 +++++++++++++++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/adafruit_pycamera/__init__.py b/adafruit_pycamera/__init__.py index 97f9de7..9ffe4f2 100644 --- a/adafruit_pycamera/__init__.py +++ b/adafruit_pycamera/__init__.py @@ -72,6 +72,7 @@ _NVM_EFFECT = const(2) _NVM_MODE = const(3) _NVM_TIMELAPSE_RATE = const(4) +_NVM_TIMELAPSE_SUBMODE = const(5) class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-public-methods @@ -186,6 +187,12 @@ class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-pub 60 * 30, 60 * 60 ) + + timelapse_submodes = ( + "HiPwr", + "MedPwr", + "LowPwr" + ) modes = ("JPEG", "GIF", "GBOY", "STOP", "LAPS") @@ -288,16 +295,18 @@ def make_camera_ui(self): self._botbar.append(self._mode_label) self._timelapsebar = displayio.Group(x=0, y=180) + self._timelapse_submode_label = label.Label( + terminalio.FONT, text="SubM", color=0xFFFFFF,x=160, y=10, scale=2 + ) self._timelapse_rate_label = label.Label( terminalio.FONT, text="Time", color=0xFFFFFF,x=90, y=10, scale=2 ) - self._timelapse_rate_label.background_color = None self._timelapsestatus_label = label.Label( terminalio.FONT, text="Status", color=0xFFFFFF, x=0, y=10, scale=2 ) - self._timelapsestatus_label.background_color = None self._timelapsebar.append(self._timelapse_rate_label) self._timelapsebar.append(self._timelapsestatus_label) + self._timelapsebar.append(self._timelapse_submode_label) self.splash.append(self._topbar) self.splash.append(self._botbar) @@ -371,6 +380,7 @@ def init_camera(self, init_autofocus=True) -> None: self.resolution = microcontroller.nvm[_NVM_RESOLUTION] self.mode = microcontroller.nvm[_NVM_MODE] self.timelapse_rate = microcontroller.nvm[_NVM_TIMELAPSE_RATE] + self.timelapse_submode = microcontroller.nvm[_NVM_TIMELAPSE_SUBMODE] if init_autofocus: self.autofocus_init() @@ -599,6 +609,18 @@ def timelapse_rate(self, setting): self.display.refresh() + @property + def timelapse_submode(self): + """Get or set the power mode for timelapsing""" + return self._timelapse_submode + + @timelapse_submode.setter + def timelapse_submode(self, setting): + setting = (setting + len(self.timelapse_submodes)) % len(self.timelapse_submodes) + self._timelapse_submode = setting + self._timelapse_submode_label.text = self.timelapse_submodes[self._timelapse_submode] + microcontroller.nvm[_NVM_TIMELAPSE_SUBMODE] = setting + def init_display(self): """Initialize the TFT display""" # construct displayio by hand diff --git a/examples/camera/code.py b/examples/camera/code.py index a2e9ddc..53db67d 100644 --- a/examples/camera/code.py +++ b/examples/camera/code.py @@ -38,16 +38,26 @@ ) pycam.blit(last_frame) elif pycam.mode_text == "LAPS": - pycam.blit(pycam.continuous_capture()) - pycam._timelapse_rate_label.text = pycam._timelapse_rate_label.text if timelapse_remaining is None: pycam._timelapsestatus_label.text = "STOP" else: timelapse_remaining = timelapse_timestamp - time.time() pycam._timelapsestatus_label.text = f"{timelapse_remaining}s / " + pycam._timelapse_rate_label.text = pycam._timelapse_rate_label.text + pycam._timelapse_submode_label.text = pycam._timelapse_submode_label.text + + # only in high power mode do we continuously preview + if (timelapse_remaining is None) or (pycam._timelapse_submode_label.text == "HiPwr"): + pycam.blit(pycam.continuous_capture()) + if pycam._timelapse_submode_label.text == "LowPwr" and (timelapse_remaining is not None): + pycam.display.brightness = 0.05 + else: + pycam.display.brightness = 1 pycam.display.refresh() if timelapse_remaining is not None and timelapse_remaining <= 0: + # no matter what, show what was just on the camera + pycam.blit(pycam.continuous_capture()) #pycam.tone(200, 0.1) # uncomment to add a beep when a photo is taken try: pycam.display_message("Snap!", color=0x0000FF) @@ -59,6 +69,8 @@ pycam.display_message("Error\nNo SD Card", color=0xFF0000) time.sleep(0.5) pycam.live_preview_mode() + pycam.display.refresh() + pycam.blit(pycam.continuous_capture()) timelapse_timestamp = time.time() + pycam.timelapse_rates[pycam.timelapse_rate] + 1 else: pycam.blit(pycam.continuous_capture()) @@ -206,6 +218,9 @@ # pycam.set_resolution(pycam.resolutions[new_res]) if pycam.select.fell: print("SEL") + if pycam.mode_text == "LAPS": + pycam.timelapse_submode += 1 + pycam.display.refresh() if pycam.ok.fell: print("OK") if pycam.mode_text == "LAPS": From 60e15dd80429563db4e97f6b067e0d5abae26d6c Mon Sep 17 00:00:00 2001 From: ladyada Date: Sun, 28 Jan 2024 17:37:14 -0500 Subject: [PATCH 5/7] allow saving and setting exposure/shutter, gain and balance so that it can be frozen during timelapse --- adafruit_pycamera/__init__.py | 49 +++++++++++++++++++++++++++++++++++ examples/camera/code.py | 13 ++++++++++ 2 files changed, 62 insertions(+) diff --git a/adafruit_pycamera/__init__.py b/adafruit_pycamera/__init__.py index 9ffe4f2..8b15924 100644 --- a/adafruit_pycamera/__init__.py +++ b/adafruit_pycamera/__init__.py @@ -882,6 +882,55 @@ def led_color(self, new_color): else: self.pixels[:] = colors + def get_camera_autosettings(self): + exposure = (self.read_camera_register(0x3500) << 12) + \ + (self.read_camera_register(0x3501) << 4) + \ + (self.read_camera_register(0x3502) >> 4); + wb = [self.read_camera_register(x) for x in \ + (0x3400, 0x3401, 0x3402, 0x3403, 0x3404, 0x3405)] + + settings = { + 'gain': self.read_camera_register(0x350b), + 'exposure': exposure, + 'wb': wb + } + return settings + + def set_camera_wb(self, wb_register_values=None): + if wb_register_values is None: + # just set to auto balance + self.camera.whitebal = True + return + + if len(wb_register_values) != 6: + raise RuntimeError("Please pass in 0x3400~0x3405 inclusive!") + + self.write_camera_register(0x3212, 0x03) + self.write_camera_register(0x3406, 0x01) + for i, reg_val in enumerate(wb_register_values): + self.write_camera_register(0x3400+i, reg_val) + self.write_camera_register(0x3212, 0x13) + self.write_camera_register(0x3212, 0xa3) + + def set_camera_exposure(self, new_exposure=None): + if new_exposure is None: + # just set auto expose + self.camera.exposure_ctrl = True + return + exposure_ctrl = False + + self.write_camera_register(0x3500, (new_exposure >> 12) & 0xFF) + self.write_camera_register(0x3501, (new_exposure >> 4) & 0xFF) + self.write_camera_register(0x3502, (new_exposure << 4) & 0xFF) + + def set_camera_gain(self, new_gain=None): + if new_gain is None: + # just set auto expose + self.camera.gain_ctrl = True + return + + self.camera.gain_ctrl = False + self.write_camera_register(0x350b, new_gain) class PyCamera(PyCameraBase): """Wrapper class for the PyCamera hardware""" diff --git a/examples/camera/code.py b/examples/camera/code.py index 53db67d..02a2a6b 100644 --- a/examples/camera/code.py +++ b/examples/camera/code.py @@ -225,7 +225,20 @@ print("OK") if pycam.mode_text == "LAPS": if timelapse_remaining is None: # stopped + print("Starting timelapse") timelapse_remaining = pycam.timelapse_rates[pycam.timelapse_rate] timelapse_timestamp = time.time() + timelapse_remaining + 1 + # dont let the camera take over auto-settings + saved_settings = pycam.get_camera_autosettings() + #print(f"Current exposure {saved_settings['exposure']}, gain {saved_settings['gain']}, wb {saved_settings['wb']}") + pycam.set_camera_exposure(saved_settings['exposure']) + pycam.set_camera_gain(saved_settings['gain']) + pycam.set_camera_wb(saved_settings['wb']) else: # is running, turn off + print("Stopping timelapse") + timelapse_remaining = None + pycam.camera.exposure_ctrl = True + pycam.set_camera_gain(None) # go back to autogain + pycam.set_camera_wb(None) # go back to autobalance + pycam.set_camera_exposure(None) # go back to auto shutter From 008d5532ceeee97e4fccdc394a10924eb144d9fd Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 29 Jan 2024 14:07:58 -0600 Subject: [PATCH 6/7] Bring code up to standards --- adafruit_pycamera/__init__.py | 116 +++++++++++++++++++++------------- examples/camera/code.py | 53 ++++++++++------ 2 files changed, 107 insertions(+), 62 deletions(-) diff --git a/adafruit_pycamera/__init__.py b/adafruit_pycamera/__init__.py index 8b15924..591ca59 100644 --- a/adafruit_pycamera/__init__.py +++ b/adafruit_pycamera/__init__.py @@ -185,15 +185,11 @@ class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-pub 60 * 10, 60 * 15, 60 * 30, - 60 * 60 + 60 * 60, ) - timelapse_submodes = ( - "HiPwr", - "MedPwr", - "LowPwr" - ) - + timelapse_submodes = ("HiPwr", "MedPwr", "LowPwr") + modes = ("JPEG", "GIF", "GBOY", "STOP", "LAPS") _INIT_SEQUENCE = ( @@ -213,6 +209,11 @@ def __init__(self) -> None: # pylint: disable=too-many-statements self._timestamp = time.monotonic() self._bigbuf = None self._botbar = None + self._timelapsebar = None + self.timelapse_rate_label = None + self._timelapsestatus = None + self.timelapsestatus_label = None + self.timelapse_submode_label = None self._camera_device = None self._display_bus = None self._effect_label = None @@ -295,19 +296,19 @@ def make_camera_ui(self): self._botbar.append(self._mode_label) self._timelapsebar = displayio.Group(x=0, y=180) - self._timelapse_submode_label = label.Label( - terminalio.FONT, text="SubM", color=0xFFFFFF,x=160, y=10, scale=2 + self.timelapse_submode_label = label.Label( + terminalio.FONT, text="SubM", color=0xFFFFFF, x=160, y=10, scale=2 ) - self._timelapse_rate_label = label.Label( - terminalio.FONT, text="Time", color=0xFFFFFF,x=90, y=10, scale=2 + self.timelapse_rate_label = label.Label( + terminalio.FONT, text="Time", color=0xFFFFFF, x=90, y=10, scale=2 ) - self._timelapsestatus_label = label.Label( + self.timelapsestatus_label = label.Label( terminalio.FONT, text="Status", color=0xFFFFFF, x=0, y=10, scale=2 ) - self._timelapsebar.append(self._timelapse_rate_label) - self._timelapsebar.append(self._timelapsestatus_label) - self._timelapsebar.append(self._timelapse_submode_label) - + self._timelapsebar.append(self.timelapse_rate_label) + self._timelapsebar.append(self.timelapsestatus_label) + self._timelapsebar.append(self.timelapse_submode_label) + self.splash.append(self._topbar) self.splash.append(self._botbar) self.splash.append(self._timelapsebar) @@ -504,9 +505,9 @@ def select_setting(self, setting_name): self._res_label.text = self.resolutions[self._resolution] self._mode_label.color = 0xFFFFFF self._mode_label.background_color = 0x0 - self._timelapse_rate_label.color = 0xFFFFFF - self._timelapse_rate_label.background_color = None - + self.timelapse_rate_label.color = 0xFFFFFF + self.timelapse_rate_label.background_color = None + if setting_name == "effect": self._effect_label.color = 0x0 self._effect_label.background_color = 0xFFFFFF @@ -529,8 +530,8 @@ def select_setting(self, setting_name): self._res_label.color = 0x0 self._res_label.background_color = 0xFFFFFF elif setting_name == "timelapse_rate": - self._timelapse_rate_label.color = 0x0 - self._timelapse_rate_label.background_color = 0xFFFFFF + self.timelapse_rate_label.color = 0x0 + self.timelapse_rate_label.background_color = 0xFFFFFF self.display.refresh() @property @@ -591,7 +592,6 @@ def resolution(self, res): self._res_label.text = self.resolutions[res] self.display.refresh() - @property def timelapse_rate(self): """Get or set the amount of time between timelapse shots""" @@ -602,13 +602,14 @@ def timelapse_rate(self, setting): setting = (setting + len(self.timelapse_rates)) % len(self.timelapse_rates) self._timelapse_rate = setting if self.timelapse_rates[setting] < 60: - self._timelapse_rate_label.text = "%d S" % self.timelapse_rates[setting] + self.timelapse_rate_label.text = "%d S" % self.timelapse_rates[setting] else: - self._timelapse_rate_label.text = "%d M" % (self.timelapse_rates[setting] / 60) + self.timelapse_rate_label.text = "%d M" % ( + self.timelapse_rates[setting] / 60 + ) microcontroller.nvm[_NVM_TIMELAPSE_RATE] = setting self.display.refresh() - @property def timelapse_submode(self): """Get or set the power mode for timelapsing""" @@ -616,9 +617,13 @@ def timelapse_submode(self): @timelapse_submode.setter def timelapse_submode(self, setting): - setting = (setting + len(self.timelapse_submodes)) % len(self.timelapse_submodes) + setting = (setting + len(self.timelapse_submodes)) % len( + self.timelapse_submodes + ) self._timelapse_submode = setting - self._timelapse_submode_label.text = self.timelapse_submodes[self._timelapse_submode] + self.timelapse_submode_label.text = self.timelapse_submodes[ + self._timelapse_submode + ] microcontroller.nvm[_NVM_TIMELAPSE_SUBMODE] = setting def init_display(self): @@ -883,54 +888,79 @@ def led_color(self, new_color): self.pixels[:] = colors def get_camera_autosettings(self): - exposure = (self.read_camera_register(0x3500) << 12) + \ - (self.read_camera_register(0x3501) << 4) + \ - (self.read_camera_register(0x3502) >> 4); - wb = [self.read_camera_register(x) for x in \ - (0x3400, 0x3401, 0x3402, 0x3403, 0x3404, 0x3405)] - + """Collect all the settings related to exposure and white balance""" + exposure = ( + (self.read_camera_register(0x3500) << 12) + + (self.read_camera_register(0x3501) << 4) + + (self.read_camera_register(0x3502) >> 4) + ) + white_balance = [ + self.read_camera_register(x) + for x in (0x3400, 0x3401, 0x3402, 0x3403, 0x3404, 0x3405) + ] + settings = { - 'gain': self.read_camera_register(0x350b), - 'exposure': exposure, - 'wb': wb - } + "gain": self.read_camera_register(0x350B), + "exposure": exposure, + "wb": white_balance, + } return settings def set_camera_wb(self, wb_register_values=None): + """Set the camera white balance. + + The argument of `None` selects auto white balance, while + a list of 6 numbers sets a specific white balance. + + The numbers can come from the datasheet or from + ``get_camera_autosettings()["wb"]``.""" if wb_register_values is None: # just set to auto balance self.camera.whitebal = True return - + if len(wb_register_values) != 6: raise RuntimeError("Please pass in 0x3400~0x3405 inclusive!") self.write_camera_register(0x3212, 0x03) self.write_camera_register(0x3406, 0x01) for i, reg_val in enumerate(wb_register_values): - self.write_camera_register(0x3400+i, reg_val) + self.write_camera_register(0x3400 + i, reg_val) self.write_camera_register(0x3212, 0x13) - self.write_camera_register(0x3212, 0xa3) + self.write_camera_register(0x3212, 0xA3) def set_camera_exposure(self, new_exposure=None): + """Set the camera's exposure values + + The argument of `None` selects auto exposure. + + Otherwise, the new exposure data should come from + ``get_camera_autosettings()["exposure"].""" if new_exposure is None: # just set auto expose self.camera.exposure_ctrl = True return - exposure_ctrl = False + self.camera.exposure_ctrl = False self.write_camera_register(0x3500, (new_exposure >> 12) & 0xFF) self.write_camera_register(0x3501, (new_exposure >> 4) & 0xFF) self.write_camera_register(0x3502, (new_exposure << 4) & 0xFF) - + def set_camera_gain(self, new_gain=None): + """Set the camera's exposure values + + The argument of `None` selects auto gain control. + + Otherwise, the new exposure data should come from + ``get_camera_autosettings()["gain"].""" if new_gain is None: # just set auto expose self.camera.gain_ctrl = True return self.camera.gain_ctrl = False - self.write_camera_register(0x350b, new_gain) + self.write_camera_register(0x350B, new_gain) + class PyCamera(PyCameraBase): """Wrapper class for the PyCamera hardware""" diff --git a/examples/camera/code.py b/examples/camera/code.py index 02a2a6b..4f8c0a5 100644 --- a/examples/camera/code.py +++ b/examples/camera/code.py @@ -3,7 +3,6 @@ # SPDX-License-Identifier: Unlicense import time -import math import bitmaptools import displayio import gifio @@ -14,7 +13,15 @@ pycam = adafruit_pycamera.PyCamera() # pycam.live_preview_mode() -settings = (None, "resolution", "effect", "mode", "led_level", "led_color", "timelapse_rate") +settings = ( + None, + "resolution", + "effect", + "mode", + "led_level", + "led_color", + "timelapse_rate", +) curr_setting = 0 print("Starting!") @@ -39,17 +46,23 @@ pycam.blit(last_frame) elif pycam.mode_text == "LAPS": if timelapse_remaining is None: - pycam._timelapsestatus_label.text = "STOP" + pycam.timelapsestatus_label.text = "STOP" else: timelapse_remaining = timelapse_timestamp - time.time() - pycam._timelapsestatus_label.text = f"{timelapse_remaining}s / " - pycam._timelapse_rate_label.text = pycam._timelapse_rate_label.text - pycam._timelapse_submode_label.text = pycam._timelapse_submode_label.text + pycam.timelapsestatus_label.text = f"{timelapse_remaining}s / " + # Manually updating the label text a second time ensures that the label + # is re-painted over the blitted preview. + pycam.timelapse_rate_label.text = pycam.timelapse_rate_label.text + pycam.timelapse_submode_label.text = pycam.timelapse_submode_label.text # only in high power mode do we continuously preview - if (timelapse_remaining is None) or (pycam._timelapse_submode_label.text == "HiPwr"): + if (timelapse_remaining is None) or ( + pycam.timelapse_submode_label.text == "HiPwr" + ): pycam.blit(pycam.continuous_capture()) - if pycam._timelapse_submode_label.text == "LowPwr" and (timelapse_remaining is not None): + if pycam.timelapse_submode_label.text == "LowPwr" and ( + timelapse_remaining is not None + ): pycam.display.brightness = 0.05 else: pycam.display.brightness = 1 @@ -58,7 +71,7 @@ if timelapse_remaining is not None and timelapse_remaining <= 0: # no matter what, show what was just on the camera pycam.blit(pycam.continuous_capture()) - #pycam.tone(200, 0.1) # uncomment to add a beep when a photo is taken + # pycam.tone(200, 0.1) # uncomment to add a beep when a photo is taken try: pycam.display_message("Snap!", color=0x0000FF) pycam.capture_jpeg() @@ -71,7 +84,9 @@ pycam.live_preview_mode() pycam.display.refresh() pycam.blit(pycam.continuous_capture()) - timelapse_timestamp = time.time() + pycam.timelapse_rates[pycam.timelapse_rate] + 1 + timelapse_timestamp = ( + time.time() + pycam.timelapse_rates[pycam.timelapse_rate] + 1 + ) else: pycam.blit(pycam.continuous_capture()) # print("\t\t", capture_time, blit_time) @@ -224,21 +239,21 @@ if pycam.ok.fell: print("OK") if pycam.mode_text == "LAPS": - if timelapse_remaining is None: # stopped + if timelapse_remaining is None: # stopped print("Starting timelapse") timelapse_remaining = pycam.timelapse_rates[pycam.timelapse_rate] timelapse_timestamp = time.time() + timelapse_remaining + 1 # dont let the camera take over auto-settings saved_settings = pycam.get_camera_autosettings() - #print(f"Current exposure {saved_settings['exposure']}, gain {saved_settings['gain']}, wb {saved_settings['wb']}") - pycam.set_camera_exposure(saved_settings['exposure']) - pycam.set_camera_gain(saved_settings['gain']) - pycam.set_camera_wb(saved_settings['wb']) - else: # is running, turn off + # print(f"Current exposure {saved_settings=}") + pycam.set_camera_exposure(saved_settings["exposure"]) + pycam.set_camera_gain(saved_settings["gain"]) + pycam.set_camera_wb(saved_settings["wb"]) + else: # is running, turn off print("Stopping timelapse") timelapse_remaining = None pycam.camera.exposure_ctrl = True - pycam.set_camera_gain(None) # go back to autogain - pycam.set_camera_wb(None) # go back to autobalance - pycam.set_camera_exposure(None) # go back to auto shutter + pycam.set_camera_gain(None) # go back to autogain + pycam.set_camera_wb(None) # go back to autobalance + pycam.set_camera_exposure(None) # go back to auto shutter From 5b02b8fca582fbb8fc10eca65471c6d5de6848c6 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 29 Jan 2024 14:19:27 -0600 Subject: [PATCH 7/7] fix doc build --- adafruit_pycamera/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adafruit_pycamera/__init__.py b/adafruit_pycamera/__init__.py index 591ca59..718d082 100644 --- a/adafruit_pycamera/__init__.py +++ b/adafruit_pycamera/__init__.py @@ -935,7 +935,7 @@ def set_camera_exposure(self, new_exposure=None): The argument of `None` selects auto exposure. Otherwise, the new exposure data should come from - ``get_camera_autosettings()["exposure"].""" + ``get_camera_autosettings()["exposure"]``.""" if new_exposure is None: # just set auto expose self.camera.exposure_ctrl = True @@ -952,7 +952,7 @@ def set_camera_gain(self, new_gain=None): The argument of `None` selects auto gain control. Otherwise, the new exposure data should come from - ``get_camera_autosettings()["gain"].""" + ``get_camera_autosettings()["gain"]``.""" if new_gain is None: # just set auto expose self.camera.gain_ctrl = True