You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
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:
+2
-1
@@ -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")
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user