Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timelapse mode #22

Merged
merged 7 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 167 additions & 5 deletions adafruit_pycamera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,16 @@
_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)
_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
Expand Down Expand Up @@ -168,7 +170,27 @@ 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,
)

timelapse_submodes = ("HiPwr", "MedPwr", "LowPwr")

modes = ("JPEG", "GIF", "GBOY", "STOP", "LAPS")

_INIT_SEQUENCE = (
b"\x01\x80\x78" # _SWRESET and Delay 120ms
Expand All @@ -187,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
Expand Down Expand Up @@ -249,13 +276,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(
Expand All @@ -268,8 +295,23 @@ 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_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.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.splash.append(self._topbar)
self.splash.append(self._botbar)
self.splash.append(self._timelapsebar)

def init_accelerometer(self):
"""Initialize the accelerometer"""
Expand Down Expand Up @@ -338,6 +380,8 @@ 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]
self.timelapse_submode = microcontroller.nvm[_NVM_TIMELAPSE_SUBMODE]

if init_autofocus:
self.autofocus_init()
Expand Down Expand Up @@ -461,6 +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

if setting_name == "effect":
self._effect_label.color = 0x0
self._effect_label.background_color = 0xFFFFFF
Expand All @@ -478,6 +525,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
Expand Down Expand Up @@ -538,6 +592,40 @@ 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()

@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
Expand Down Expand Up @@ -799,6 +887,80 @@ def led_color(self, new_color):
else:
self.pixels[:] = colors

def get_camera_autosettings(self):
"""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": 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(0x3212, 0x13)
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
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)


class PyCamera(PyCameraBase):
"""Wrapper class for the PyCamera hardware"""
Expand Down
Loading
Loading