From 52d7fed8ded83a6f48214809ab5db18c13b2aabf Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Tue, 10 Feb 2026 15:38:13 +0100 Subject: [PATCH] CameraManager: add init and deinit callbacks Rather than hardcoding all the pins, they are set a board init time, and then camera_activity.py calls them when needed. --- CHANGELOG.md | 3 +- .../lib/mpos/board/matouch_esp32_s3_2_8.py | 100 +++++++++++++++++- .../lib/mpos/camera_manager.py | 59 ++++++++++- .../lib/mpos/ui/camera_activity.py | 98 +---------------- 4 files changed, 160 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44d0a492..305f6752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ Builtin Apps: - OSUpdate: replace "force update" checkbox with improved button labels Frameworks: -- SDCard framework: add support for SDIO/SD/MMC mode +- SDCard: add support for SDIO/SD/MMC mode +- CameraManager: add init and deinit callbacks OS: - Scale MicroPythonOS boot logo down if necessary diff --git a/internal_filesystem/lib/mpos/board/matouch_esp32_s3_2_8.py b/internal_filesystem/lib/mpos/board/matouch_esp32_s3_2_8.py index 208512e0..1ffc8a86 100644 --- a/internal_filesystem/lib/mpos/board/matouch_esp32_s3_2_8.py +++ b/internal_filesystem/lib/mpos/board/matouch_esp32_s3_2_8.py @@ -109,6 +109,100 @@ sdcard.init(cmd_pin=2,clk_pin=42,d0_pin=41) # LightsManager will not be initialized (functions will return False) # === CAMERA HARDWARE === +def init_cam(width, height, colormode): + try: + from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling + + # Map resolution to FrameSize enum using CameraManager + frame_size = CameraManager.resolution_to_framesize(width, height) + print(f"init_internal_cam: Using FrameSize {frame_size} for {width}x{height}") + + # Try to initialize, with one retry for I2C poweroff issue + max_attempts = 3 + toreturn = None + for attempt in range(max_attempts): + try: + cam = Camera( + data_pins=[7,5,4,6,16,8,3,46], + vsync_pin=11, + href_pin=10, + sda_pin=39, + scl_pin=38, + pclk_pin=17, + xclk_pin=9, + xclk_freq=20000000, + powerdown_pin=-1, + reset_pin=-1, + pixel_format=PixelFormat.RGB565 if colormode else PixelFormat.GRAYSCALE, + frame_size=frame_size, + #grab_mode=GrabMode.WHEN_EMPTY, + grab_mode=GrabMode.LATEST, + fb_count=1 + ) + cam.set_vflip(True) + + toreturn=cam + break + except Exception as e: + if attempt < max_attempts-1: + print(f"init_cam attempt {attempt} failed: {e}, retrying...") + else: + print(f"init_cam final exception: {e}") + break + + if toreturn: + try: + from mpos import InputManager + indev = InputManager.list_indevs()[0] + indev.enable(False) + InputManager.unregister_indev(indev) + print("input disabled") + except Exception as e: + print(f"disabling indev got exception: {e}") + + return toreturn + except Exception as e: + print(f"init_cam exception: {e}") + return None + +def deinit_cam(cam): + cam.deinit() + # Power off, otherwise it keeps using a lot of current + try: + from machine import Pin, I2C + i2c = I2C(1, scl=Pin(38), sda=Pin(39)) # Adjust pins and frequency + #devices = i2c.scan() + #print([hex(addr) for addr in devices]) # finds it on 60 = 0x3C after init + camera_addr = 0x3C # for OV5640 + reg_addr = 0x3008 + reg_high = (reg_addr >> 8) & 0xFF # 0x30 + reg_low = reg_addr & 0xFF # 0x08 + power_off_command = 0x42 # Power off command + i2c.writeto(camera_addr, bytes([reg_high, reg_low, power_off_command])) + except Exception as e: + print(f"Warning: powering off camera got exception: {e}") + import time + time.sleep_ms(100) + try: + # hardware reset might work too, but doesn't seem to: + #from mpos import InputManager + #indev = InputManager.list_indevs()[0] + #indev.hw_reset() + #indev.enable(True) + #print("input enabled") + #time.sleep(1) + import i2c + i2c_bus = i2c.I2C.Bus(host=0, scl=38, sda=39) + import mpos.indev.gt911 as gt911 + touch_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=gt911.I2C_ADDR, reg_bits=gt911.BITS) + indev = gt911.GT911(touch_dev, reset_pin=1, interrupt_pin=40, debug=True) # remove debug because it's slower + print("new indev created") + from mpos import InputManager + InputManager.register_indev(indev) + print("new indev registered") + except Exception as e: + print(f"Indev enable got exception: {e}") + from mpos import CameraManager # MaTouch ESP32-S3 has OV3660 camera (3MP, up to 2048x1536) @@ -116,13 +210,13 @@ from mpos import CameraManager CameraManager.add_camera(CameraManager.Camera( lens_facing=CameraManager.CameraCharacteristics.LENS_FACING_BACK, name="OV3660", - vendor="OmniVision" + vendor="OmniVision", + init=init_cam, + deinit=deinit_cam, )) print("matouch_esp32_s3_2_8.py finished") print("Board capabilities:") print(" - Display: 320x240 ST7789 with GT911 touch") print(" - Camera: OV3660 (3MP)") -print(" - No IMU sensor") print(" - No LEDs") -print(" - No audio hardware") diff --git a/internal_filesystem/lib/mpos/camera_manager.py b/internal_filesystem/lib/mpos/camera_manager.py index 990aee68..cb505cbf 100644 --- a/internal_filesystem/lib/mpos/camera_manager.py +++ b/internal_filesystem/lib/mpos/camera_manager.py @@ -37,7 +37,7 @@ class Camera: Represents a camera device with its characteristics. """ - def __init__(self, lens_facing, name=None, vendor=None, version=None): + def __init__(self, lens_facing, name=None, vendor=None, version=None, init=None, deinit=None): """Initialize camera metadata. Args: @@ -50,6 +50,8 @@ class Camera: self.name = name or "Camera" self.vendor = vendor or "Unknown" self.version = version or 1 + self.init_function = init + self.deinit_function = deinit def __repr__(self): facing_names = { @@ -60,6 +62,13 @@ class Camera: facing_str = facing_names.get(self.lens_facing, f"UNKNOWN({self.lens_facing})") return f"Camera({self.name}, facing={facing_str})" + def init(self, width, height, colormode): + if self.init_function: + return self.init_function(width, height, colormode) + + def deinit(self, cam_obj=None): + if self.deinit_function: + return self.deinit_function(cam_obj) class CameraManager: """ @@ -180,6 +189,54 @@ class CameraManager: """ return len(CameraManager._cameras) + @staticmethod + def resolution_to_framesize(width, height): + """Map resolution (width, height) to FrameSize enum. + + Args: + width: Image width in pixels + height: Image height in pixels + + Returns: + FrameSize enum value corresponding to the resolution, or R240X240 as default + """ + try: + from camera import FrameSize + except ImportError: + print("Warning: camera module not available") + return None + + # Format: (width, height): FrameSize + resolution_map = { + (96, 96): FrameSize.R96X96, + (160, 120): FrameSize.QQVGA, + (128, 128): FrameSize.R128X128, + (176, 144): FrameSize.QCIF, + (240, 176): FrameSize.HQVGA, + (240, 240): FrameSize.R240X240, + (320, 240): FrameSize.QVGA, + (320, 320): FrameSize.R320X320, + (400, 296): FrameSize.CIF, + (480, 320): FrameSize.HVGA, + (480, 480): FrameSize.R480X480, + (640, 480): FrameSize.VGA, + (640, 640): FrameSize.R640X640, + (720, 720): FrameSize.R720X720, + (800, 600): FrameSize.SVGA, + (800, 800): FrameSize.R800X800, + (1024, 768): FrameSize.XGA, + (960, 960): FrameSize.R960X960, + (1280, 720): FrameSize.HD, + (1024, 1024): FrameSize.R1024X1024, + # These are disabled in camera_settings.py because they use a lot of RAM: + (1280, 1024): FrameSize.SXGA, + (1280, 1280): FrameSize.R1280X1280, + (1600, 1200): FrameSize.UXGA, + (1920, 1080): FrameSize.FHD, + } + + return resolution_map.get((width, height), FrameSize.R240X240) + # ============================================================================ # Class method delegation (at module level) diff --git a/internal_filesystem/lib/mpos/ui/camera_activity.py b/internal_filesystem/lib/mpos/ui/camera_activity.py index 2cf1e0dc..b260439c 100644 --- a/internal_filesystem/lib/mpos/ui/camera_activity.py +++ b/internal_filesystem/lib/mpos/ui/camera_activity.py @@ -8,7 +8,7 @@ except Exception as e: from ..time import epoch_seconds from .camera_settings import CameraSettingsActivity -from .camera_manager import CameraManager +from ..camera_manager import CameraManager from .. import ui as mpos_ui from ..app.activity import Activity @@ -137,17 +137,8 @@ class CameraActivity(Activity): def start_cam(self): # Init camera: - self.cam = self.init_internal_cam(self.width, self.height) + self.cam = CameraManager.get_cameras()[0].init(self.width, self.height, self.colormode) if self.cam: - try: - from mpos import InputManager - indev = InputManager.list_indevs()[0] - indev.enable(False) - InputManager.unregister_indev(indev) - print("input disabled") - except Exception as e: - print(f"disabling indev got exception: {e}") - #self.image.set_rotation(900) # internal camera is rotated 90 degrees # Apply saved camera settings, only for internal camera for now: self.apply_camera_settings(self.scanqr_prefs if self.scanqr_mode else self.prefs, self.cam, self.use_webcam) # needs to be done AFTER the camera is initialized else: @@ -171,43 +162,7 @@ class CameraActivity(Activity): if self.use_webcam: webcam.deinit(self.cam) elif self.cam: - self.cam.deinit() - # Power off, otherwise it keeps using a lot of current - try: - from machine import Pin, I2C - i2c = I2C(1, scl=Pin(38), sda=Pin(39)) # Adjust pins and frequency - #devices = i2c.scan() - #print([hex(addr) for addr in devices]) # finds it on 60 = 0x3C after init - camera_addr = 0x3C # for OV5640 - reg_addr = 0x3008 - reg_high = (reg_addr >> 8) & 0xFF # 0x30 - reg_low = reg_addr & 0xFF # 0x08 - power_off_command = 0x42 # Power off command - i2c.writeto(camera_addr, bytes([reg_high, reg_low, power_off_command])) - except Exception as e: - print(f"Warning: powering off camera got exception: {e}") - import time - time.sleep_ms(100) - try: - # hardware reset might work too, but doesn't seem to: - #from mpos import InputManager - #indev = InputManager.list_indevs()[0] - #indev.hw_reset() - #indev.enable(True) - #print("input enabled") - #time.sleep(1) - import i2c - i2c_bus = i2c.I2C.Bus(host=0, scl=38, sda=39) - import mpos.indev.gt911 as gt911 - touch_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=gt911.I2C_ADDR, reg_bits=gt911.BITS) - indev = gt911.GT911(touch_dev, reset_pin=1, interrupt_pin=40, debug=True) # remove debug because it's slower - print("new indev created") - from mpos import InputManager - InputManager.register_indev(indev) - print("new indev registered") - except Exception as e: - print(f"Indev enable got exception: {e}") - + CameraManager.get_cameras()[0].deinit(self.cam) self.cam = None if self.image_dsc: # it's important to delete the image when stopping the camera, otherwise LVGL might try to display it and crash print("emptying self.current_cam_buffer...") @@ -399,51 +354,6 @@ class CameraActivity(Activity): if not self.use_webcam and self.cam: self.cam.free_buffer() # After QR decoding, free the old buffer, otherwise the camera doesn't provide a new one - def init_internal_cam(self, width, height): - """Initialize internal camera with specified resolution. - - Automatically retries once if initialization fails (to handle I2C poweroff issue). - """ - try: - from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling - - # Map resolution to FrameSize enum using CameraManager - frame_size = CameraManager.resolution_to_framesize(width, height) - print(f"init_internal_cam: Using FrameSize {frame_size} for {width}x{height}") - - # Try to initialize, with one retry for I2C poweroff issue - max_attempts = 3 - for attempt in range(max_attempts): - try: - cam = Camera( - data_pins=[7,5,4,6,16,8,3,46], - vsync_pin=11, - href_pin=10, - sda_pin=39, - scl_pin=38, - pclk_pin=17, - xclk_pin=9, - xclk_freq=20000000, - powerdown_pin=-1, - reset_pin=-1, - pixel_format=PixelFormat.RGB565 if self.colormode else PixelFormat.GRAYSCALE, - frame_size=frame_size, - #grab_mode=GrabMode.WHEN_EMPTY, - grab_mode=GrabMode.LATEST, - fb_count=1 - ) - cam.set_vflip(True) - return cam - except Exception as e: - if attempt < max_attempts-1: - print(f"init_cam attempt {attempt} failed: {e}, retrying...") - else: - print(f"init_cam final exception: {e}") - return None - except Exception as e: - print(f"init_cam exception: {e}") - return None - def print_qr_buffer(self, buffer): try: # Try to decode buffer as a UTF-8 string @@ -583,8 +493,6 @@ class CameraActivity(Activity): except Exception as e: print(f"Error applying camera settings: {e}") - - """