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.
This commit is contained in:
Thomas Farstrike
2026-02-10 15:38:13 +01:00
parent 451a72a14e
commit 52d7fed8de
4 changed files with 160 additions and 100 deletions
+2 -1
View File
@@ -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
@@ -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")
+58 -1
View File
@@ -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)
@@ -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}")
"""