7171_NVM_RESOLUTION = const (1 )
7272_NVM_EFFECT = const (2 )
7373_NVM_MODE = const (3 )
74+ _NVM_TIMELAPSE_RATE = const (4 )
75+ _NVM_TIMELAPSE_SUBMODE = const (5 )
7476
7577
7678class PyCameraBase : # pylint: disable=too-many-instance-attributes,too-many-public-methods
@@ -168,7 +170,27 @@ class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-pub
168170 "Sepia" ,
169171 "Solarize" ,
170172 )
171- modes = ("JPEG" , "GIF" , "GBOY" , "STOP" )
173+
174+ timelapse_rates = (
175+ 5 ,
176+ 10 ,
177+ 20 ,
178+ 30 ,
179+ 60 ,
180+ 90 ,
181+ 60 * 2 ,
182+ 60 * 3 ,
183+ 60 * 4 ,
184+ 60 * 5 ,
185+ 60 * 10 ,
186+ 60 * 15 ,
187+ 60 * 30 ,
188+ 60 * 60 ,
189+ )
190+
191+ timelapse_submodes = ("HiPwr" , "MedPwr" , "LowPwr" )
192+
193+ modes = ("JPEG" , "GIF" , "GBOY" , "STOP" , "LAPS" )
172194
173195 _INIT_SEQUENCE = (
174196 b"\x01 \x80 \x78 " # _SWRESET and Delay 120ms
@@ -187,6 +209,11 @@ def __init__(self) -> None: # pylint: disable=too-many-statements
187209 self ._timestamp = time .monotonic ()
188210 self ._bigbuf = None
189211 self ._botbar = None
212+ self ._timelapsebar = None
213+ self .timelapse_rate_label = None
214+ self ._timelapsestatus = None
215+ self .timelapsestatus_label = None
216+ self .timelapse_submode_label = None
190217 self ._camera_device = None
191218 self ._display_bus = None
192219 self ._effect_label = None
@@ -249,13 +276,13 @@ def make_debounced_expander_pin(pin_no):
249276 def make_camera_ui (self ):
250277 """Create displayio widgets for the standard camera UI"""
251278 self ._sd_label = label .Label (
252- terminalio .FONT , text = "SD ??" , color = 0x0 , x = 150 , y = 10 , scale = 2
279+ terminalio .FONT , text = "SD ??" , color = 0x0 , x = 170 , y = 10 , scale = 2
253280 )
254281 self ._effect_label = label .Label (
255282 terminalio .FONT , text = "EFFECT" , color = 0xFFFFFF , x = 4 , y = 10 , scale = 2
256283 )
257284 self ._mode_label = label .Label (
258- terminalio .FONT , text = "MODE" , color = 0xFFFFFF , x = 150 , y = 10 , scale = 2
285+ terminalio .FONT , text = "MODE" , color = 0xFFFFFF , x = 170 , y = 10 , scale = 2
259286 )
260287 self ._topbar = displayio .Group ()
261288 self ._res_label = label .Label (
@@ -268,8 +295,23 @@ def make_camera_ui(self):
268295 self ._botbar .append (self ._effect_label )
269296 self ._botbar .append (self ._mode_label )
270297
298+ self ._timelapsebar = displayio .Group (x = 0 , y = 180 )
299+ self .timelapse_submode_label = label .Label (
300+ terminalio .FONT , text = "SubM" , color = 0xFFFFFF , x = 160 , y = 10 , scale = 2
301+ )
302+ self .timelapse_rate_label = label .Label (
303+ terminalio .FONT , text = "Time" , color = 0xFFFFFF , x = 90 , y = 10 , scale = 2
304+ )
305+ self .timelapsestatus_label = label .Label (
306+ terminalio .FONT , text = "Status" , color = 0xFFFFFF , x = 0 , y = 10 , scale = 2
307+ )
308+ self ._timelapsebar .append (self .timelapse_rate_label )
309+ self ._timelapsebar .append (self .timelapsestatus_label )
310+ self ._timelapsebar .append (self .timelapse_submode_label )
311+
271312 self .splash .append (self ._topbar )
272313 self .splash .append (self ._botbar )
314+ self .splash .append (self ._timelapsebar )
273315
274316 def init_accelerometer (self ):
275317 """Initialize the accelerometer"""
@@ -338,6 +380,8 @@ def init_camera(self, init_autofocus=True) -> None:
338380 self .camera .saturation = 3
339381 self .resolution = microcontroller .nvm [_NVM_RESOLUTION ]
340382 self .mode = microcontroller .nvm [_NVM_MODE ]
383+ self .timelapse_rate = microcontroller .nvm [_NVM_TIMELAPSE_RATE ]
384+ self .timelapse_submode = microcontroller .nvm [_NVM_TIMELAPSE_SUBMODE ]
341385
342386 if init_autofocus :
343387 self .autofocus_init ()
@@ -461,6 +505,9 @@ def select_setting(self, setting_name):
461505 self ._res_label .text = self .resolutions [self ._resolution ]
462506 self ._mode_label .color = 0xFFFFFF
463507 self ._mode_label .background_color = 0x0
508+ self .timelapse_rate_label .color = 0xFFFFFF
509+ self .timelapse_rate_label .background_color = None
510+
464511 if setting_name == "effect" :
465512 self ._effect_label .color = 0x0
466513 self ._effect_label .background_color = 0xFFFFFF
@@ -478,6 +525,13 @@ def select_setting(self, setting_name):
478525 self ._res_label .text = "LED CLR"
479526 self ._res_label .color = 0x0
480527 self ._res_label .background_color = 0xFFFFFF
528+ elif setting_name == "led_color" :
529+ self ._res_label .text = "LED CLR"
530+ self ._res_label .color = 0x0
531+ self ._res_label .background_color = 0xFFFFFF
532+ elif setting_name == "timelapse_rate" :
533+ self .timelapse_rate_label .color = 0x0
534+ self .timelapse_rate_label .background_color = 0xFFFFFF
481535 self .display .refresh ()
482536
483537 @property
@@ -538,6 +592,40 @@ def resolution(self, res):
538592 self ._res_label .text = self .resolutions [res ]
539593 self .display .refresh ()
540594
595+ @property
596+ def timelapse_rate (self ):
597+ """Get or set the amount of time between timelapse shots"""
598+ return self ._timelapse_rate
599+
600+ @timelapse_rate .setter
601+ def timelapse_rate (self , setting ):
602+ setting = (setting + len (self .timelapse_rates )) % len (self .timelapse_rates )
603+ self ._timelapse_rate = setting
604+ if self .timelapse_rates [setting ] < 60 :
605+ self .timelapse_rate_label .text = "%d S" % self .timelapse_rates [setting ]
606+ else :
607+ self .timelapse_rate_label .text = "%d M" % (
608+ self .timelapse_rates [setting ] / 60
609+ )
610+ microcontroller .nvm [_NVM_TIMELAPSE_RATE ] = setting
611+ self .display .refresh ()
612+
613+ @property
614+ def timelapse_submode (self ):
615+ """Get or set the power mode for timelapsing"""
616+ return self ._timelapse_submode
617+
618+ @timelapse_submode .setter
619+ def timelapse_submode (self , setting ):
620+ setting = (setting + len (self .timelapse_submodes )) % len (
621+ self .timelapse_submodes
622+ )
623+ self ._timelapse_submode = setting
624+ self .timelapse_submode_label .text = self .timelapse_submodes [
625+ self ._timelapse_submode
626+ ]
627+ microcontroller .nvm [_NVM_TIMELAPSE_SUBMODE ] = setting
628+
541629 def init_display (self ):
542630 """Initialize the TFT display"""
543631 # construct displayio by hand
@@ -799,6 +887,80 @@ def led_color(self, new_color):
799887 else :
800888 self .pixels [:] = colors
801889
890+ def get_camera_autosettings (self ):
891+ """Collect all the settings related to exposure and white balance"""
892+ exposure = (
893+ (self .read_camera_register (0x3500 ) << 12 )
894+ + (self .read_camera_register (0x3501 ) << 4 )
895+ + (self .read_camera_register (0x3502 ) >> 4 )
896+ )
897+ white_balance = [
898+ self .read_camera_register (x )
899+ for x in (0x3400 , 0x3401 , 0x3402 , 0x3403 , 0x3404 , 0x3405 )
900+ ]
901+
902+ settings = {
903+ "gain" : self .read_camera_register (0x350B ),
904+ "exposure" : exposure ,
905+ "wb" : white_balance ,
906+ }
907+ return settings
908+
909+ def set_camera_wb (self , wb_register_values = None ):
910+ """Set the camera white balance.
911+
912+ The argument of `None` selects auto white balance, while
913+ a list of 6 numbers sets a specific white balance.
914+
915+ The numbers can come from the datasheet or from
916+ ``get_camera_autosettings()["wb"]``."""
917+ if wb_register_values is None :
918+ # just set to auto balance
919+ self .camera .whitebal = True
920+ return
921+
922+ if len (wb_register_values ) != 6 :
923+ raise RuntimeError ("Please pass in 0x3400~0x3405 inclusive!" )
924+
925+ self .write_camera_register (0x3212 , 0x03 )
926+ self .write_camera_register (0x3406 , 0x01 )
927+ for i , reg_val in enumerate (wb_register_values ):
928+ self .write_camera_register (0x3400 + i , reg_val )
929+ self .write_camera_register (0x3212 , 0x13 )
930+ self .write_camera_register (0x3212 , 0xA3 )
931+
932+ def set_camera_exposure (self , new_exposure = None ):
933+ """Set the camera's exposure values
934+
935+ The argument of `None` selects auto exposure.
936+
937+ Otherwise, the new exposure data should come from
938+ ``get_camera_autosettings()["exposure"]``."""
939+ if new_exposure is None :
940+ # just set auto expose
941+ self .camera .exposure_ctrl = True
942+ return
943+ self .camera .exposure_ctrl = False
944+
945+ self .write_camera_register (0x3500 , (new_exposure >> 12 ) & 0xFF )
946+ self .write_camera_register (0x3501 , (new_exposure >> 4 ) & 0xFF )
947+ self .write_camera_register (0x3502 , (new_exposure << 4 ) & 0xFF )
948+
949+ def set_camera_gain (self , new_gain = None ):
950+ """Set the camera's exposure values
951+
952+ The argument of `None` selects auto gain control.
953+
954+ Otherwise, the new exposure data should come from
955+ ``get_camera_autosettings()["gain"]``."""
956+ if new_gain is None :
957+ # just set auto expose
958+ self .camera .gain_ctrl = True
959+ return
960+
961+ self .camera .gain_ctrl = False
962+ self .write_camera_register (0x350B , new_gain )
963+
802964
803965class PyCamera (PyCameraBase ):
804966 """Wrapper class for the PyCamera hardware"""
0 commit comments