Skip to content

Commit 0aa1814

Browse files
committed
Factor out a PyCameraBase class
This gives the coder control over what is initialized. For the image review app, for instance, the camera & autofocus would not be initialized. Support for the display backlight is also added.
1 parent 460b169 commit 0aa1814

File tree

3 files changed

+113
-88
lines changed

3 files changed

+113
-88
lines changed

adafruit_pycamera/__init__.py

Lines changed: 113 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,10 @@
7171
_NVM_MODE = const(3)
7272

7373

74-
class PyCamera: # pylint: disable=too-many-instance-attributes,too-many-public-methods
75-
"""Wrapper class for the PyCamera hardware"""
74+
class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-public-methods
75+
"""Base class for PyCamera hardware"""
76+
77+
"""Wrapper class for the PyCamera hardware with lots of smarts"""
7678

7779
_finalize_firmware_load = (
7880
0x3022,
@@ -176,59 +178,41 @@ class PyCamera: # pylint: disable=too-many-instance-attributes,too-many-public-
176178
b"\x29\x80\x05" # _DISPON and Delay 5ms
177179
)
178180

179-
def i2c_scan(self):
180-
"""Print an I2C bus scan"""
181-
while not self._i2c.try_lock():
182-
pass
183-
184-
try:
185-
print(
186-
"I2C addresses found:",
187-
[hex(device_address) for device_address in self._i2c.scan()],
188-
)
189-
finally: # unlock the i2c bus when ctrl-c'ing out of the loop
190-
self._i2c.unlock()
191-
192181
def __init__(self) -> None: # pylint: disable=too-many-statements
193-
self._timestamp = time.monotonic()
182+
displayio.release_displays()
194183
self._i2c = board.I2C()
195184
self._spi = board.SPI()
196-
self.deinit_display()
197-
198-
self.splash = displayio.Group()
199-
self._sd_label = label.Label(
200-
terminalio.FONT, text="SD ??", color=0x0, x=150, y=10, scale=2
201-
)
202-
self._effect_label = label.Label(
203-
terminalio.FONT, text="EFFECT", color=0xFFFFFF, x=4, y=10, scale=2
204-
)
205-
self._mode_label = label.Label(
206-
terminalio.FONT, text="MODE", color=0xFFFFFF, x=150, y=10, scale=2
207-
)
185+
self._timestamp = time.monotonic()
186+
self._bigbuf = None
187+
self._botbar = None
188+
self._camera_device = None
189+
self._display_bus = None
190+
self._effect_label = None
191+
self._image_counter = 0
192+
self._mode_label = None
193+
self._res_label = None
194+
self._sd_label = None
195+
self._topbar = None
196+
self.accel = None
197+
self.camera = None
198+
self.display = None
199+
self.pixels = None
200+
self.sdcard = None
201+
self.splash = None
208202

209-
# turn on the display first, its reset line may be shared with the IO expander(?)
210-
if not self.display:
211-
self.init_display()
203+
# Reset display and I/O expander
204+
self._tft_aw_reset = DigitalInOut(board.TFT_RESET)
205+
self._tft_aw_reset.switch_to_output(False)
206+
time.sleep(0.05)
207+
self._tft_aw_reset.switch_to_output(True)
212208

213209
self.shutter_button = DigitalInOut(board.BUTTON)
214210
self.shutter_button.switch_to_input(Pull.UP)
215211
self.shutter = Button(self.shutter_button)
216212

217-
print("reset camera")
218213
self._cam_reset = DigitalInOut(board.CAMERA_RESET)
219214
self._cam_pwdn = DigitalInOut(board.CAMERA_PWDN)
220215

221-
self._cam_reset.switch_to_output(False)
222-
self._cam_pwdn.switch_to_output(True)
223-
time.sleep(0.01)
224-
self._cam_pwdn.switch_to_output(False)
225-
time.sleep(0.01)
226-
self._cam_reset.switch_to_output(True)
227-
time.sleep(0.01)
228-
229-
print("pre cam @", time.monotonic() - self._timestamp)
230-
self.i2c_scan()
231-
232216
# AW9523 GPIO expander
233217
self._aw = adafruit_aw9523.AW9523(self._i2c, address=0x58)
234218
print("Found AW9523")
@@ -260,17 +244,40 @@ def make_debounced_expander_pin(pin_no):
260244

261245
self.mute = make_expander_output(_AW_MUTE, False)
262246

263-
self.sdcard = None
264-
try:
265-
self.mount_sd_card()
266-
except RuntimeError:
267-
pass # no card found, its ok!
268-
print("sdcard done @", time.monotonic() - self._timestamp)
247+
def make_camera_ui(self):
248+
"""Create displayio widgets for the standard camera UI"""
249+
self.splash = displayio.Group()
250+
self._sd_label = label.Label(
251+
terminalio.FONT, text="SD ??", color=0x0, x=150, y=10, scale=2
252+
)
253+
self._effect_label = label.Label(
254+
terminalio.FONT, text="EFFECT", color=0xFFFFFF, x=4, y=10, scale=2
255+
)
256+
self._mode_label = label.Label(
257+
terminalio.FONT, text="MODE", color=0xFFFFFF, x=150, y=10, scale=2
258+
)
259+
self._topbar = displayio.Group()
260+
self._res_label = label.Label(
261+
terminalio.FONT, text="", color=0xFFFFFF, x=0, y=10, scale=2
262+
)
263+
self._topbar.append(self._res_label)
264+
self._topbar.append(self._sd_label)
265+
266+
self._botbar = displayio.Group(x=0, y=210)
267+
self._botbar.append(self._effect_label)
268+
self._botbar.append(self._mode_label)
269+
270+
self.splash.append(self._topbar)
271+
self.splash.append(self._botbar)
269272

273+
def init_accelerometer(self):
274+
"""Initialize the accelerometer"""
270275
# lis3dh accelerometer
271276
self.accel = adafruit_lis3dh.LIS3DH_I2C(self._i2c, address=0x19)
272277
self.accel.range = adafruit_lis3dh.RANGE_2_G
273278

279+
def init_neopixel(self):
280+
"""Initialize the neopixels (onboard & ring)"""
274281
# main board neopixel
275282
neopix = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.1)
276283
neopix.fill(0)
@@ -282,6 +289,17 @@ def make_debounced_expander_pin(pin_no):
282289
)
283290
self.pixels.fill(0)
284291

292+
def init_camera(self, init_autofocus=True) -> None:
293+
"""Initialize the camera, by default including autofocus"""
294+
print("reset camera")
295+
self._cam_reset.switch_to_output(False)
296+
self._cam_pwdn.switch_to_output(True)
297+
time.sleep(0.01)
298+
self._cam_pwdn.switch_to_output(False)
299+
time.sleep(0.01)
300+
self._cam_reset.switch_to_output(True)
301+
time.sleep(0.01)
302+
285303
print("Initializing camera")
286304
self.camera = espcamera.Camera(
287305
data_pins=board.CAMERA_DATA,
@@ -305,33 +323,12 @@ def make_debounced_expander_pin(pin_no):
305323
self.camera.address,
306324
)
307325
)
308-
print("camera done @", time.monotonic() - self._timestamp)
309-
print(dir(self.camera))
310326

311327
self._camera_device = I2CDevice(self._i2c, self.camera.address)
312-
# display.auto_refresh = False
313328

314329
self.camera.hmirror = False
315330
self.camera.vflip = True
316331

317-
self._bigbuf = None
318-
319-
self._topbar = displayio.Group()
320-
self._res_label = label.Label(
321-
terminalio.FONT, text="", color=0xFFFFFF, x=0, y=10, scale=2
322-
)
323-
self._topbar.append(self._res_label)
324-
self._topbar.append(self._sd_label)
325-
326-
self._botbar = displayio.Group(x=0, y=210)
327-
self._botbar.append(self._effect_label)
328-
self._botbar.append(self._mode_label)
329-
330-
self.splash.append(self._topbar)
331-
self.splash.append(self._botbar)
332-
self.display.root_group = self.splash
333-
self.display.refresh()
334-
335332
self.led_color = 0
336333
self.led_level = 0
337334

@@ -340,6 +337,10 @@ def make_debounced_expander_pin(pin_no):
340337
self.camera.saturation = 3
341338
self.resolution = microcontroller.nvm[_NVM_RESOLUTION]
342339
self.mode = microcontroller.nvm[_NVM_MODE]
340+
341+
if init_autofocus:
342+
self.autofocus_init()
343+
343344
print("init done @", time.monotonic() - self._timestamp)
344345

345346
def autofocus_init_from_file(self, filename):
@@ -526,15 +527,15 @@ def resolution(self, res):
526527
self._res_label.text = self.resolutions[res]
527528
self.display.refresh()
528529

529-
def init_display(self, reset=True):
530+
def init_display(self):
530531
"""Initialize the TFT display"""
531532
# construct displayio by hand
532533
displayio.release_displays()
533534
self._display_bus = displayio.FourWire(
534535
self._spi,
535536
command=board.TFT_DC,
536537
chip_select=board.TFT_CS,
537-
reset=board.TFT_RESET if reset else None,
538+
reset=None,
538539
baudrate=60_000_000,
539540
)
540541
self.display = board.DISPLAY
@@ -546,6 +547,7 @@ def init_display(self, reset=True):
546547
height=240,
547548
colstart=80,
548549
auto_refresh=False,
550+
backlight_pin=board.TFT_BACKLIGHT,
549551
)
550552
self.display.root_group = self.splash
551553
self.display.refresh()
@@ -562,7 +564,7 @@ def display_message(self, message, color=0xFF0000, scale=3):
562564
text_area = label.Label(terminalio.FONT, text=message, color=color, scale=scale)
563565
text_area.anchor_point = (0.5, 0.5)
564566
if not self.display:
565-
self.init_display(None)
567+
self.init_display()
566568
text_area.anchored_position = (self.display.width / 2, self.display.height / 2)
567569

568570
# Show it
@@ -572,10 +574,11 @@ def display_message(self, message, color=0xFF0000, scale=3):
572574

573575
def mount_sd_card(self):
574576
"""Attempt to mount the SD card"""
575-
self._sd_label.text = "NO SD"
576-
self._sd_label.color = 0xFF0000
577+
if self._sd_label is not None:
578+
self._sd_label.text = "NO SD"
579+
self._sd_label.color = 0xFF0000
577580
if not self.card_detect.value:
578-
raise RuntimeError("SD card detection failed")
581+
raise RuntimeError("No SD card inserted")
579582
if self.sdcard:
580583
self.sdcard.deinit()
581584
# depower SD card
@@ -585,6 +588,7 @@ def mount_sd_card(self):
585588
# deinit display and SPI bus because we need to drive all SD pins LOW
586589
# to ensure nothing, not even an I/O pin, could possibly power the SD
587590
# card
591+
had_display = self.display is not None
588592
self.deinit_display()
589593
self._spi.deinit()
590594
sckpin = DigitalInOut(board.SCK)
@@ -604,23 +608,28 @@ def mount_sd_card(self):
604608
self._card_power.value = False
605609
card_cs.deinit()
606610
print("sdcard init @", time.monotonic() - self._timestamp)
607-
self.sdcard = sdcardio.SDCard(self._spi, board.CARD_CS, baudrate=20_000_000)
608-
vfs = storage.VfsFat(self.sdcard)
609-
print("mount vfs @", time.monotonic() - self._timestamp)
610-
storage.mount(vfs, "/sd")
611-
self.init_display(None)
612-
self._image_counter = 0
613-
self._sd_label.text = "SD OK"
614-
self._sd_label.color = 0x00FF00
611+
try:
612+
self.sdcard = sdcardio.SDCard(self._spi, board.CARD_CS, baudrate=20_000_000)
613+
vfs = storage.VfsFat(self.sdcard)
614+
print("mount vfs @", time.monotonic() - self._timestamp)
615+
storage.mount(vfs, "/sd")
616+
self._image_counter = 0
617+
if self._sd_label is not None:
618+
self._sd_label.text = "SD OK"
619+
self._sd_label.color = 0x00FF00
620+
finally:
621+
if had_display:
622+
self.init_display()
615623

616624
def unmount_sd_card(self):
617625
"""Unmount the SD card, if mounted"""
618626
try:
619627
storage.umount("/sd")
620628
except OSError:
621629
pass
622-
self._sd_label.text = "NO SD"
623-
self._sd_label.color = 0xFF0000
630+
if self._sd_label is not None:
631+
self._sd_label.text = "NO SD"
632+
self._sd_label.color = 0xFF0000
624633

625634
def keys_debounce(self):
626635
"""Debounce all keys.
@@ -775,3 +784,21 @@ def led_color(self, new_color):
775784
self.pixels.fill(colors)
776785
else:
777786
self.pixels[:] = colors
787+
788+
789+
class PyCamera(PyCameraBase):
790+
"""Wrapper class for the PyCamera hardware"""
791+
792+
def __init__(self, init_autofocus=True):
793+
super().__init__()
794+
795+
self.make_camera_ui()
796+
self.init_accelerometer()
797+
self.init_neopixel()
798+
self.init_display()
799+
self.init_camera(init_autofocus)
800+
try:
801+
self.mount_sd_card()
802+
except Exception as exc: # pylint: disable=broad-exception-caught
803+
# No SD card inserted, it's OK
804+
print(exc)

examples/camera/code.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import adafruit_pycamera
1313

1414
pycam = adafruit_pycamera.PyCamera()
15-
pycam.autofocus_init()
1615
# pycam.live_preview_mode()
1716

1817
settings = (None, "resolution", "effect", "mode", "led_level", "led_color")

examples/ipcam2/code.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
supervisor.runtime.autoreload = False
3030

3131
pycam = adafruit_pycamera.PyCamera()
32-
pycam.autofocus_init()
3332

3433
if wifi.radio.ipv4_address:
3534
# use alt port if web workflow enabled

0 commit comments

Comments
 (0)