Skip to content

Commit d1ce153

Browse files
Add CameraManager framework
1 parent afcd94d commit d1ce153

File tree

5 files changed

+517
-1
lines changed

5 files changed

+517
-1
lines changed

internal_filesystem/lib/mpos/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
from . import content
5353
from . import time
5454
from . import sensor_manager
55+
from . import camera_manager
5556
from . import sdcard
5657
from . import battery_voltage
5758
from . import audio
@@ -89,5 +90,5 @@
8990
"get_all_widgets_with_text",
9091
# Submodules
9192
"apps", "ui", "config", "net", "content", "time", "sensor_manager",
92-
"sdcard", "battery_voltage", "audio", "hardware", "bootloader"
93+
"camera_manager", "sdcard", "battery_voltage", "audio", "hardware", "bootloader"
9394
]

internal_filesystem/lib/mpos/board/linux.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ def adc_to_voltage(adc_value):
122122
# (On Linux desktop, this will fail gracefully but set _initialized flag)
123123
SensorManager.init(None)
124124

125+
# === CAMERA HARDWARE ===
126+
import mpos.camera_manager as CameraManager
127+
128+
# Desktop builds can simulate a camera for testing
129+
CameraManager.add_camera(CameraManager.Camera(
130+
lens_facing=CameraManager.CameraCharacteristics.LENS_FACING_BACK,
131+
name="Desktop Simulated Camera",
132+
vendor="MicroPythonOS"
133+
))
134+
125135
print("linux.py finished")
126136

127137

internal_filesystem/lib/mpos/board/waveshare_esp32_s3_touch_lcd_2.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,14 @@ def adc_to_voltage(adc_value):
127127
# i2c_bus was created on line 75 for touch, reuse it for IMU
128128
SensorManager.init(i2c_bus, address=0x6B, mounted_position=SensorManager.FACING_EARTH)
129129

130+
# === CAMERA HARDWARE ===
131+
import mpos.camera_manager as CameraManager
132+
133+
# Waveshare ESP32-S3-Touch-LCD-2 has OV5640 camera
134+
CameraManager.add_camera(CameraManager.Camera(
135+
lens_facing=CameraManager.CameraCharacteristics.LENS_FACING_BACK,
136+
name="OV5640",
137+
vendor="OmniVision"
138+
))
139+
130140
print("waveshare_esp32_s3_touch_lcd_2.py finished")
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
"""Android-inspired CameraManager for MicroPythonOS.
2+
3+
Provides unified access to camera devices (back-facing, front-facing, external).
4+
Follows module-level singleton pattern (like SensorManager, AudioFlinger).
5+
6+
Example usage:
7+
import mpos.camera_manager as CameraManager
8+
9+
# In board init file:
10+
CameraManager.add_camera(CameraManager.Camera(
11+
lens_facing=CameraManager.CameraCharacteristics.LENS_FACING_BACK,
12+
name="OV5640",
13+
vendor="OmniVision"
14+
))
15+
16+
# In app:
17+
cam_list = CameraManager.get_cameras()
18+
if len(cam_list) > 0:
19+
print("we have a camera!")
20+
21+
MIT License
22+
Copyright (c) 2024 MicroPythonOS contributors
23+
"""
24+
25+
try:
26+
import _thread
27+
_lock = _thread.allocate_lock()
28+
except ImportError:
29+
_lock = None
30+
31+
32+
# Camera lens facing constants (matching Android Camera2 API)
33+
class CameraCharacteristics:
34+
"""Camera characteristics and constants."""
35+
LENS_FACING_BACK = 0 # Back-facing camera (primary)
36+
LENS_FACING_FRONT = 1 # Front-facing camera (selfie)
37+
LENS_FACING_EXTERNAL = 2 # External USB camera
38+
39+
40+
class Camera:
41+
"""Camera metadata (lightweight data class, Android-inspired).
42+
43+
Represents a camera device with its characteristics.
44+
"""
45+
46+
def __init__(self, lens_facing, name=None, vendor=None, version=None):
47+
"""Initialize camera metadata.
48+
49+
Args:
50+
lens_facing: Camera orientation (LENS_FACING_BACK, LENS_FACING_FRONT, etc.)
51+
name: Human-readable camera name (e.g., "OV5640", "Front Camera")
52+
vendor: Camera vendor/manufacturer (e.g., "OmniVision")
53+
version: Driver version (default 1)
54+
"""
55+
self.lens_facing = lens_facing
56+
self.name = name or "Camera"
57+
self.vendor = vendor or "Unknown"
58+
self.version = version or 1
59+
60+
def __repr__(self):
61+
facing_names = {
62+
CameraCharacteristics.LENS_FACING_BACK: "BACK",
63+
CameraCharacteristics.LENS_FACING_FRONT: "FRONT",
64+
CameraCharacteristics.LENS_FACING_EXTERNAL: "EXTERNAL"
65+
}
66+
facing_str = facing_names.get(self.lens_facing, f"UNKNOWN({self.lens_facing})")
67+
return f"Camera({self.name}, facing={facing_str})"
68+
69+
70+
# Module state
71+
_initialized = False
72+
_cameras = [] # List of Camera objects
73+
74+
75+
def init():
76+
"""Initialize CameraManager.
77+
78+
Returns:
79+
bool: True if initialized successfully
80+
"""
81+
global _initialized
82+
_initialized = True
83+
return True
84+
85+
86+
def is_available():
87+
"""Check if CameraManager is initialized.
88+
89+
Returns:
90+
bool: True if CameraManager is initialized
91+
"""
92+
return _initialized
93+
94+
95+
def add_camera(camera):
96+
"""Register a camera device.
97+
98+
Args:
99+
camera: Camera object to register
100+
101+
Returns:
102+
bool: True if camera added successfully
103+
"""
104+
if not isinstance(camera, Camera):
105+
print(f"[CameraManager] Error: add_camera() requires Camera object, got {type(camera)}")
106+
return False
107+
108+
if _lock:
109+
_lock.acquire()
110+
111+
try:
112+
# Check if camera with same facing already exists
113+
for existing in _cameras:
114+
if existing.lens_facing == camera.lens_facing:
115+
print(f"[CameraManager] Warning: Camera with facing {camera.lens_facing} already registered")
116+
# Still add it (allow multiple cameras with same facing)
117+
118+
_cameras.append(camera)
119+
print(f"[CameraManager] Registered camera: {camera}")
120+
return True
121+
finally:
122+
if _lock:
123+
_lock.release()
124+
125+
126+
def get_cameras():
127+
"""Get list of all registered cameras.
128+
129+
Returns:
130+
list: List of Camera objects (copy of internal list)
131+
"""
132+
if _lock:
133+
_lock.acquire()
134+
135+
try:
136+
return _cameras.copy() if _cameras else []
137+
finally:
138+
if _lock:
139+
_lock.release()
140+
141+
142+
def get_camera_by_facing(lens_facing):
143+
"""Get first camera with specified lens facing.
144+
145+
Args:
146+
lens_facing: Camera orientation (LENS_FACING_BACK, LENS_FACING_FRONT, etc.)
147+
148+
Returns:
149+
Camera object or None if not found
150+
"""
151+
if _lock:
152+
_lock.acquire()
153+
154+
try:
155+
for camera in _cameras:
156+
if camera.lens_facing == lens_facing:
157+
return camera
158+
return None
159+
finally:
160+
if _lock:
161+
_lock.release()
162+
163+
164+
def has_camera():
165+
"""Check if any camera is registered.
166+
167+
Returns:
168+
bool: True if at least one camera available
169+
"""
170+
if _lock:
171+
_lock.acquire()
172+
173+
try:
174+
return len(_cameras) > 0
175+
finally:
176+
if _lock:
177+
_lock.release()
178+
179+
180+
def get_camera_count():
181+
"""Get number of registered cameras.
182+
183+
Returns:
184+
int: Number of cameras
185+
"""
186+
if _lock:
187+
_lock.acquire()
188+
189+
try:
190+
return len(_cameras)
191+
finally:
192+
if _lock:
193+
_lock.release()
194+
195+
196+
# Initialize on module load
197+
init()

0 commit comments

Comments
 (0)