reachy-mini 1.2.5rc1__py3-none-any.whl → 1.2.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. reachy_mini/apps/app.py +24 -21
  2. reachy_mini/apps/manager.py +17 -3
  3. reachy_mini/apps/sources/hf_auth.py +92 -0
  4. reachy_mini/apps/sources/hf_space.py +1 -1
  5. reachy_mini/apps/sources/local_common_venv.py +199 -24
  6. reachy_mini/apps/templates/main.py.j2 +4 -3
  7. reachy_mini/daemon/app/dashboard/static/js/apps.js +9 -1
  8. reachy_mini/daemon/app/dashboard/static/js/appstore.js +228 -0
  9. reachy_mini/daemon/app/dashboard/static/js/logs.js +148 -0
  10. reachy_mini/daemon/app/dashboard/templates/logs.html +37 -0
  11. reachy_mini/daemon/app/dashboard/templates/sections/appstore.html +92 -0
  12. reachy_mini/daemon/app/dashboard/templates/sections/cache.html +82 -0
  13. reachy_mini/daemon/app/dashboard/templates/sections/daemon.html +5 -0
  14. reachy_mini/daemon/app/dashboard/templates/settings.html +1 -0
  15. reachy_mini/daemon/app/main.py +172 -7
  16. reachy_mini/daemon/app/models.py +8 -0
  17. reachy_mini/daemon/app/routers/apps.py +56 -0
  18. reachy_mini/daemon/app/routers/cache.py +58 -0
  19. reachy_mini/daemon/app/routers/hf_auth.py +57 -0
  20. reachy_mini/daemon/app/routers/logs.py +124 -0
  21. reachy_mini/daemon/app/routers/state.py +25 -1
  22. reachy_mini/daemon/app/routers/wifi_config.py +75 -0
  23. reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py +1 -1
  24. reachy_mini/daemon/app/services/bluetooth/commands/WIFI_RESET.sh +8 -0
  25. reachy_mini/daemon/app/services/wireless/launcher.sh +8 -2
  26. reachy_mini/daemon/app/services/wireless/reachy-mini-daemon.service +13 -0
  27. reachy_mini/daemon/backend/abstract.py +29 -9
  28. reachy_mini/daemon/backend/mockup_sim/__init__.py +12 -0
  29. reachy_mini/daemon/backend/mockup_sim/backend.py +176 -0
  30. reachy_mini/daemon/backend/mujoco/backend.py +0 -5
  31. reachy_mini/daemon/backend/robot/backend.py +78 -5
  32. reachy_mini/daemon/daemon.py +46 -7
  33. reachy_mini/daemon/utils.py +71 -15
  34. reachy_mini/io/zenoh_client.py +26 -0
  35. reachy_mini/io/zenoh_server.py +10 -6
  36. reachy_mini/kinematics/nn_kinematics.py +2 -2
  37. reachy_mini/kinematics/placo_kinematics.py +15 -15
  38. reachy_mini/media/__init__.py +55 -1
  39. reachy_mini/media/audio_base.py +185 -13
  40. reachy_mini/media/audio_control_utils.py +60 -5
  41. reachy_mini/media/audio_gstreamer.py +97 -16
  42. reachy_mini/media/audio_sounddevice.py +120 -19
  43. reachy_mini/media/audio_utils.py +110 -5
  44. reachy_mini/media/camera_base.py +182 -11
  45. reachy_mini/media/camera_constants.py +132 -4
  46. reachy_mini/media/camera_gstreamer.py +42 -2
  47. reachy_mini/media/camera_opencv.py +83 -5
  48. reachy_mini/media/camera_utils.py +95 -7
  49. reachy_mini/media/media_manager.py +139 -6
  50. reachy_mini/media/webrtc_client_gstreamer.py +142 -13
  51. reachy_mini/media/webrtc_daemon.py +72 -7
  52. reachy_mini/motion/recorded_move.py +76 -2
  53. reachy_mini/reachy_mini.py +196 -40
  54. reachy_mini/tools/reflash_motors.py +1 -1
  55. reachy_mini/tools/scan_motors.py +86 -0
  56. reachy_mini/tools/setup_motor.py +49 -31
  57. reachy_mini/utils/interpolation.py +1 -1
  58. reachy_mini/utils/wireless_version/startup_check.py +278 -21
  59. reachy_mini/utils/wireless_version/update.py +44 -1
  60. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/METADATA +7 -6
  61. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/RECORD +65 -53
  62. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/WHEEL +0 -0
  63. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/entry_points.txt +0 -0
  64. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/licenses/LICENSE +0 -0
  65. {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,27 @@
1
- """Camera constants for Reachy Mini."""
1
+ r"""Camera constants for Reachy Mini.
2
+
3
+ This module defines camera specifications and resolutions for various camera models
4
+ used with the Reachy Mini robot. It includes camera calibration parameters,
5
+ supported resolutions, and camera identification information.
6
+
7
+ The module provides:
8
+ - CameraResolution enum: Standardized resolutions and frame rates
9
+ - CameraSpecs dataclass: Base camera specifications with calibration data
10
+ - Specific camera specifications for different camera models
11
+
12
+ Example usage:
13
+ >>> from reachy_mini.media.camera_constants import CameraResolution, ReachyMiniLiteCamSpecs
14
+ >>>
15
+ >>> # Get available resolutions for Reachy Mini Lite Camera
16
+ >>> print("Available resolutions:")
17
+ >>> for res in ReachyMiniLiteCamSpecs.available_resolutions:
18
+ ... width, height, fps = res.value
19
+ ... print(f" {width}x{height}@{fps}fps")
20
+ >>>
21
+ >>> # Access camera calibration parameters
22
+ >>> print(f"Camera matrix:\\n{ReachyMiniLiteCamSpecs.K}")
23
+ >>> print(f"Distortion coefficients: {ReachyMiniLiteCamSpecs.D}")
24
+ """
2
25
 
3
26
  from dataclasses import dataclass, field
4
27
  from enum import Enum
@@ -9,7 +32,48 @@ import numpy.typing as npt
9
32
 
10
33
 
11
34
  class CameraResolution(Enum):
12
- """Base class for camera resolutions."""
35
+ """Base class for camera resolutions.
36
+
37
+ Enumeration of standardized camera resolutions and frame rates supported
38
+ by Reachy Mini cameras. Each enum value contains a tuple of (width, height, fps).
39
+
40
+ Attributes:
41
+ R1536x864at40fps: 1536x864 resolution at 40 fps
42
+ R1280x720at60fps: 1280x720 resolution at 60 fps (HD)
43
+ R1280x720at30fps: 1280x720 resolution at 30 fps (HD)
44
+ R1920x1080at30fps: 1920x1080 resolution at 30 fps (Full HD)
45
+ R1920x1080at60fps: 1920x1080 resolution at 60 fps (Full HD)
46
+ R2304x1296at30fps: 2304x1296 resolution at 30 fps
47
+ R1600x1200at30fps: 1600x1200 resolution at 30 fps
48
+ R3264x2448at30fps: 3264x2448 resolution at 30 fps
49
+ R3264x2448at10fps: 3264x2448 resolution at 10 fps
50
+ R3840x2592at30fps: 3840x2592 resolution at 30 fps
51
+ R3840x2592at10fps: 3840x2592 resolution at 10 fps
52
+ R3840x2160at30fps: 3840x2160 resolution at 30 fps (4K UHD)
53
+ R3840x2160at10fps: 3840x2160 resolution at 10 fps (4K UHD)
54
+ R3072x1728at10fps: 3072x1728 resolution at 10 fps
55
+ R4608x2592at10fps: 4608x2592 resolution at 10 fps
56
+
57
+ Note:
58
+ The enum values are tuples containing (width, height, frames_per_second).
59
+ Not all resolutions are supported by all camera models - check the specific
60
+ camera specifications for available resolutions.
61
+
62
+ Example:
63
+ >>> from reachy_mini.media.camera_constants import CameraResolution
64
+ >>>
65
+ >>> # Get resolution information
66
+ >>> res = CameraResolution.R1280x720at30fps
67
+ >>> width, height, fps = res.value
68
+ >>> print(f"Resolution: {width}x{height}@{fps}fps")
69
+ >>>
70
+ >>> # Check if a resolution is supported by a camera
71
+ >>> from reachy_mini.media.camera_constants import ReachyMiniLiteCamSpecs
72
+ >>> res = CameraResolution.R1920x1080at60fps
73
+ >>> if res in ReachyMiniLiteCamSpecs.available_resolutions:
74
+ ... print("This resolution is supported")
75
+
76
+ """
13
77
 
14
78
  R1536x864at40fps = (1536, 864, 40)
15
79
 
@@ -37,7 +101,48 @@ class CameraResolution(Enum):
37
101
 
38
102
  @dataclass
39
103
  class CameraSpecs:
40
- """Base camera specifications."""
104
+ """Base camera specifications.
105
+
106
+ Dataclass containing specifications for a camera model, including supported
107
+ resolutions, calibration parameters, and USB identification information.
108
+
109
+ Attributes:
110
+ name (str): Human-readable name of the camera model.
111
+ available_resolutions (List[CameraResolution]): List of supported resolutions
112
+ and frame rates for this camera model.
113
+ default_resolution (CameraResolution): Default resolution used when the camera
114
+ is initialized.
115
+ vid (int): USB Vendor ID for identifying this camera model.
116
+ pid (int): USB Product ID for identifying this camera model.
117
+ K (npt.NDArray[np.float64]): 3x3 camera intrinsic matrix containing focal
118
+ lengths and principal point coordinates.
119
+ D (npt.NDArray[np.float64]): 5-element array containing distortion coefficients
120
+ (k1, k2, p1, p2, k3) for radial and tangential distortion.
121
+
122
+ Note:
123
+ The intrinsic matrix K has the format:
124
+ [[fx, 0, cx],
125
+ [ 0, fy, cy],
126
+ [ 0, 0, 1]]
127
+
128
+ Where fx, fy are focal lengths in pixels, and cx, cy are the principal
129
+ point coordinates (typically near the image center).
130
+
131
+ Example:
132
+ >>> from reachy_mini.media.camera_constants import CameraSpecs
133
+ >>>
134
+ >>> # Create a custom camera specification
135
+ >>> custom_specs = CameraSpecs(
136
+ ... name="custom_camera",
137
+ ... available_resolutions=[CameraResolution.R1280x720at30fps],
138
+ ... default_resolution=CameraResolution.R1280x720at30fps,
139
+ ... vid=0x1234,
140
+ ... pid=0x5678,
141
+ ... K=np.array([[800, 0, 640], [0, 800, 360], [0, 0, 1]]),
142
+ ... D=np.zeros(5)
143
+ ... )
144
+
145
+ """
41
146
 
42
147
  name: str = ""
43
148
  available_resolutions: List[CameraResolution] = field(default_factory=list)
@@ -106,6 +211,7 @@ class ReachyMiniWirelessCamSpecs(ReachyMiniLiteCamSpecs):
106
211
 
107
212
  name = "wireless"
108
213
  available_resolutions = [
214
+ CameraResolution.R1280x720at30fps, # Default for H264 Level 3.1 (Safari/WebKit)
109
215
  CameraResolution.R1920x1080at30fps,
110
216
  CameraResolution.R1280x720at60fps,
111
217
  CameraResolution.R3840x2592at10fps,
@@ -113,7 +219,8 @@ class ReachyMiniWirelessCamSpecs(ReachyMiniLiteCamSpecs):
113
219
  CameraResolution.R3264x2448at10fps,
114
220
  CameraResolution.R3072x1728at10fps,
115
221
  ]
116
- default_resolution = CameraResolution.R1920x1080at30fps
222
+ # 720p@30fps for H264 Level 3.1 compatibility (Safari/WebKit)
223
+ default_resolution = CameraResolution.R1280x720at30fps
117
224
 
118
225
 
119
226
  @dataclass
@@ -150,3 +257,24 @@ class MujocoCameraSpecs(CameraSpecs):
150
257
  ]
151
258
  )
152
259
  D = np.zeros((5,)) # no distortion
260
+
261
+
262
+ @dataclass
263
+ class GenericWebcamSpecs(CameraSpecs):
264
+ """Generic webcam specifications (fallback for any webcam)."""
265
+
266
+ name = "generic"
267
+ available_resolutions = [
268
+ CameraResolution.R1280x720at30fps,
269
+ CameraResolution.R1920x1080at30fps,
270
+ ]
271
+ default_resolution = CameraResolution.R1280x720at30fps
272
+ # Approximate camera matrix for generic 720p webcam
273
+ K = np.array(
274
+ [
275
+ [640.0, 0.0, 640.0],
276
+ [0.0, 640.0, 360.0],
277
+ [0.0, 0.0, 1.0],
278
+ ]
279
+ )
280
+ D = np.zeros((5,)) # assume no distortion
@@ -1,7 +1,42 @@
1
1
  """GStreamer camera backend.
2
2
 
3
3
  This module provides an implementation of the CameraBase class using GStreamer.
4
- By default the module directly returns JPEG images as output by the camera.
4
+ It offers advanced video processing capabilities including hardware-accelerated
5
+ decoding, image format conversion, and support for various camera models.
6
+
7
+ The GStreamer camera backend features:
8
+ - Hardware-accelerated video decoding
9
+ - Support for multiple camera models (Reachy Mini Lite, Arducam, etc.)
10
+ - Advanced image processing pipelines
11
+ - Automatic camera detection and configuration
12
+ - Multiple resolution and frame rate support
13
+ - JPEG and raw image format support
14
+
15
+ Example usage:
16
+ >>> from reachy_mini.media.camera_gstreamer import GStreamerCamera
17
+ >>> from reachy_mini.media.camera_constants import CameraResolution
18
+ >>>
19
+ >>> # Create GStreamer camera instance
20
+ >>> camera = GStreamerCamera(log_level="INFO")
21
+ >>>
22
+ >>> # Open the camera
23
+ >>> camera.open()
24
+ >>>
25
+ >>> # Set resolution (optional)
26
+ >>> camera.set_resolution(CameraResolution.R1280x720at30fps)
27
+ >>>
28
+ >>> # Capture frames
29
+ >>> frame = camera.read()
30
+ >>> if frame is not None:
31
+ ... print(f"Captured frame with shape: {frame.shape}")
32
+ >>>
33
+ >>> # Get camera information
34
+ >>> width, height = camera.resolution
35
+ >>> fps = camera.framerate
36
+ >>> print(f"Camera: {width}x{height}@{fps}fps")
37
+ >>>
38
+ >>> # Clean up
39
+ >>> camera.close()
5
40
  """
6
41
 
7
42
  import os
@@ -78,7 +113,7 @@ class GStreamerCamera(CameraBase):
78
113
  self.pipeline.add(camsrc)
79
114
  queue = Gst.ElementFactory.make("queue")
80
115
  self.pipeline.add(queue)
81
- videoconvert = Gst.ElementFactory.make("videoconvert")
116
+ videoconvert = Gst.ElementFactory.make("v4l2convert")
82
117
  self.pipeline.add(videoconvert)
83
118
  camsrc.link(queue)
84
119
  queue.link(videoconvert)
@@ -96,6 +131,11 @@ class GStreamerCamera(CameraBase):
96
131
  else:
97
132
  camsrc = Gst.ElementFactory.make("v4l2src")
98
133
  camsrc.set_property("device", cam_path)
134
+ # examples of camera controls settings:
135
+ # extra_controls_structure = Gst.Structure.new_empty("extra-controls")
136
+ # extra_controls_structure.set_value("saturation", 64)
137
+ # extra_controls_structure.set_value("brightness", 50)
138
+ # camsrc.set_property("extra-controls", extra_controls_structure)
99
139
  self.pipeline.add(camsrc)
100
140
  queue = Gst.ElementFactory.make("queue")
101
141
  self.pipeline.add(queue)
@@ -1,6 +1,51 @@
1
- """OpenCv camera backend.
1
+ r"""OpenCV camera backend.
2
2
 
3
3
  This module provides an implementation of the CameraBase class using OpenCV.
4
+ It offers cross-platform camera support with automatic camera detection and
5
+ configuration for various Reachy Mini camera models.
6
+
7
+ The OpenCV camera backend features:
8
+ - Cross-platform compatibility (Windows, macOS, Linux)
9
+ - Automatic camera detection and model identification
10
+ - Support for multiple camera models (Reachy Mini Lite, Beta (Arducam), etc.)
11
+ - Resolution and frame rate configuration
12
+ - Camera calibration parameter access
13
+ - Simulation mode support (Mujoco)
14
+
15
+ Note:
16
+ This class is typically used internally by the MediaManager when the DEFAULT
17
+ backend is selected. Direct usage is possible but usually not necessary.
18
+
19
+ Example usage via MediaManager:
20
+ >>> from reachy_mini.media.media_manager import MediaManager, MediaBackend
21
+ >>>
22
+ >>> # Create media manager with OpenCV backend (default)
23
+ >>> media = MediaManager(backend=MediaBackend.DEFAULT, log_level="INFO")
24
+ >>>
25
+ >>> # Capture frames
26
+ >>> frame = media.get_frame()
27
+ >>> if frame is not None:
28
+ ... print(f"Captured frame with shape: {frame.shape}")
29
+ ... cv2.imshow("Camera", frame)
30
+ ... cv2.waitKey(1)
31
+ >>>
32
+ >>> # Get camera information
33
+ >>> if media.camera is not None:
34
+ ... width, height = media.camera.resolution
35
+ ... fps = media.camera.framerate
36
+ ... print(f"Camera: {width}x{height}@{fps}fps")
37
+ ...
38
+ ... # Access calibration information
39
+ ... K = media.camera.K
40
+ ... D = media.camera.D
41
+ ... if K is not None:
42
+ ... print(f"Camera matrix:\\n{K}")
43
+ ... if D is not None:
44
+ ... print(f"Distortion coefficients: {D}")
45
+ >>>
46
+ >>> # Clean up
47
+ >>> media.close()
48
+
4
49
  """
5
50
 
6
51
  from typing import Optional, cast
@@ -20,13 +65,33 @@ from .camera_base import CameraBase
20
65
 
21
66
 
22
67
  class OpenCVCamera(CameraBase):
23
- """Camera implementation using OpenCV."""
68
+ """Camera implementation using OpenCV.
69
+
70
+ This class implements the CameraBase interface using OpenCV, providing
71
+ cross-platform camera support for Reachy Mini robots. It automatically
72
+ detects and configures supported camera models.
73
+
74
+ Attributes:
75
+ Inherits all attributes from CameraBase.
76
+ Additionally manages OpenCV VideoCapture objects and camera connections.
77
+
78
+ """
24
79
 
25
80
  def __init__(
26
81
  self,
27
82
  log_level: str = "INFO",
28
83
  ) -> None:
29
- """Initialize the OpenCV camera."""
84
+ """Initialize the OpenCV camera.
85
+
86
+ Args:
87
+ log_level (str): Logging level for camera operations.
88
+ Default: 'INFO'.
89
+
90
+ Note:
91
+ This constructor initializes the OpenCV camera system. The actual
92
+ camera device is opened when the open() method is called.
93
+
94
+ """
30
95
  super().__init__(log_level=log_level)
31
96
  self.cap: Optional[cv2.VideoCapture] = None
32
97
 
@@ -40,7 +105,10 @@ class OpenCVCamera(CameraBase):
40
105
  self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self._resolution.value[1])
41
106
 
42
107
  def open(self, udp_camera: Optional[str] = None) -> None:
43
- """Open the camera using OpenCV VideoCapture."""
108
+ """Open the camera using OpenCV VideoCapture.
109
+
110
+ See CameraBase.open() for complete documentation.
111
+ """
44
112
  if udp_camera:
45
113
  self.cap = cv2.VideoCapture(udp_camera)
46
114
  self.camera_specs = cast(CameraSpecs, MujocoCameraSpecs)
@@ -57,6 +125,11 @@ class OpenCVCamera(CameraBase):
57
125
  self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self._resolution.value[0])
58
126
  self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self._resolution.value[1])
59
127
 
128
+ # example of camera controls settings:
129
+ # self.cap.set(cv2.CAP_PROP_BRIGHTNESS, 0.5)
130
+ # self.cap.set(cv2.CAP_PROP_CONTRAST, 0.5)
131
+ # self.cap.set(cv2.CAP_PROP_SATURATION, 64)
132
+
60
133
  self.resized_K = self.camera_specs.K
61
134
 
62
135
  if not self.cap.isOpened():
@@ -65,6 +138,8 @@ class OpenCVCamera(CameraBase):
65
138
  def read(self) -> Optional[npt.NDArray[np.uint8]]:
66
139
  """Read a frame from the camera.
67
140
 
141
+ See CameraBase.read() for complete documentation.
142
+
68
143
  Returns:
69
144
  The frame as a uint8 numpy array, or None if no frame could be read.
70
145
 
@@ -83,7 +158,10 @@ class OpenCVCamera(CameraBase):
83
158
  return cast(npt.NDArray[np.uint8], frame)
84
159
 
85
160
  def close(self) -> None:
86
- """Release the camera resource."""
161
+ """Release the camera resource.
162
+
163
+ See CameraBase.close() for complete documentation.
164
+ """
87
165
  if self.cap is not None:
88
166
  self.cap.release()
89
167
  self.cap = None
@@ -1,4 +1,28 @@
1
- """Camera utility for Reachy Mini."""
1
+ """Camera utility for Reachy Mini.
2
+
3
+ This module provides utility functions for working with cameras on the Reachy Mini robot.
4
+ It includes functions for detecting and identifying different camera models, managing
5
+ camera connections, and handling camera-specific configurations.
6
+
7
+ Supported camera types:
8
+ - Reachy Mini Lite Camera
9
+ - Arducam
10
+ - Older Raspberry Pi Camera
11
+ - Generic Webcams (fallback)
12
+
13
+ Example usage:
14
+ >>> from reachy_mini.media.camera_utils import find_camera
15
+ >>>
16
+ >>> # Find and open the Reachy Mini camera
17
+ >>> cap, camera_specs = find_camera()
18
+ >>> if cap is not None:
19
+ ... print(f"Found {camera_specs.name} camera")
20
+ ... # Use the camera
21
+ ... ret, frame = cap.read()
22
+ ... cap.release()
23
+ ... else:
24
+ ... print("No camera found")
25
+ """
2
26
 
3
27
  import platform
4
28
  from typing import Optional, Tuple, cast
@@ -9,6 +33,7 @@ from cv2_enumerate_cameras import enumerate_cameras
9
33
  from reachy_mini.media.camera_constants import (
10
34
  ArducamSpecs,
11
35
  CameraSpecs,
36
+ GenericWebcamSpecs,
12
37
  OlderRPiCamSpecs,
13
38
  ReachyMiniLiteCamSpecs,
14
39
  )
@@ -19,14 +44,45 @@ def find_camera(
19
44
  ) -> Tuple[Optional[cv2.VideoCapture], Optional[CameraSpecs]]:
20
45
  """Find and return the Reachy Mini camera.
21
46
 
22
- Looks for the Reachy Mini camera first, then Arducam, then older Raspberry Pi Camera. Returns None if no camera is found.
47
+ Looks for the Reachy Mini camera first, then Arducam, then older Raspberry Pi Camera.
48
+ Returns None if no camera is found. Falls back to generic webcam if no specific camera is detected.
23
49
 
24
50
  Args:
25
51
  apiPreference (int): Preferred API backend for the camera. Default is cv2.CAP_ANY.
26
- no_cap (bool): If True, close the camera after finding it. Default is False.
52
+ Options include cv2.CAP_V4L2 (Linux), cv2.CAP_DSHOW (Windows),
53
+ cv2.CAP_MSMF (Windows), etc.
54
+ no_cap (bool): If True, close the camera after finding it. Useful for testing
55
+ camera detection without keeping the camera open. Default is False.
27
56
 
28
57
  Returns:
29
- cv2.VideoCapture | None: A VideoCapture object if the camera is found and opened successfully, otherwise None.
58
+ Tuple[Optional[cv2.VideoCapture], Optional[CameraSpecs]]: A tuple containing:
59
+ - cv2.VideoCapture: A VideoCapture object if the camera is found and opened
60
+ successfully, otherwise None.
61
+ - CameraSpecs: The camera specifications for the detected camera, or None if
62
+ no camera was found.
63
+
64
+ Note:
65
+ This function tries to detect cameras in the following order:
66
+ 1. Reachy Mini Lite Camera (preferred)
67
+ 2. Older Raspberry Pi Camera
68
+ 3. Arducam
69
+ 4. Generic Webcam (fallback)
70
+
71
+ The function automatically sets the appropriate video codec (MJPG) for
72
+ Reachy Mini and Raspberry Pi cameras to ensure compatibility.
73
+
74
+ Example:
75
+ >>> cap, specs = find_camera()
76
+ >>> if cap is not None:
77
+ ... print(f"Found {specs.name} camera")
78
+ ... # Set resolution
79
+ ... cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
80
+ ... cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
81
+ ... # Capture a frame
82
+ ... ret, frame = cap.read()
83
+ ... cap.release()
84
+ ... else:
85
+ ... print("No camera found")
30
86
 
31
87
  """
32
88
  cap = find_camera_by_vid_pid(
@@ -55,6 +111,13 @@ def find_camera(
55
111
  cap.release()
56
112
  return cap, cast(CameraSpecs, ArducamSpecs)
57
113
 
114
+ # Fallback: try to open any available webcam (useful for mockup-sim mode on desktop)
115
+ cap = cv2.VideoCapture(0)
116
+ if cap is not None and cap.isOpened():
117
+ if no_cap:
118
+ cap.release()
119
+ return cap, cast(CameraSpecs, GenericWebcamSpecs)
120
+
58
121
  return None, None
59
122
 
60
123
 
@@ -66,12 +129,37 @@ def find_camera_by_vid_pid(
66
129
  """Find and return a camera with the specified VID and PID.
67
130
 
68
131
  Args:
69
- vid (int): Vendor ID of the camera. Default is ReachyMiniCamera
70
- pid (int): Product ID of the camera. Default is ReachyMiniCamera
132
+ vid (int): Vendor ID of the camera. Default is ReachyMiniLiteCamSpecs.vid (0x38FB).
133
+ pid (int): Product ID of the camera. Default is ReachyMiniLiteCamSpecs.pid (0x1002).
71
134
  apiPreference (int): Preferred API backend for the camera. Default is cv2.CAP_ANY.
135
+ On Linux, this automatically uses cv2.CAP_V4L2 for better compatibility.
72
136
 
73
137
  Returns:
74
- cv2.VideoCapture | None: A VideoCapture object if the camera is found and opened successfully, otherwise None.
138
+ cv2.VideoCapture | None: A VideoCapture object if the camera with matching
139
+ VID/PID is found and opened successfully, otherwise None.
140
+
141
+ Note:
142
+ This function uses the cv2_enumerate_cameras package to enumerate available
143
+ cameras and find one with the specified USB Vendor ID and Product ID.
144
+ This is useful for selecting specific camera models when multiple cameras
145
+ are connected to the system.
146
+
147
+ The Arducam camera creates two /dev/videoX devices that enumerate_cameras
148
+ cannot differentiate, so this function tries to open each potential device
149
+ until it finds a working one.
150
+
151
+ Example:
152
+ >>> # Find Reachy Mini Lite Camera by its default VID/PID
153
+ >>> cap = find_camera_by_vid_pid()
154
+ >>> if cap is not None:
155
+ ... print("Found Reachy Mini Lite Camera")
156
+ ... cap.release()
157
+ >>>
158
+ >>> # Find a specific camera by custom VID/PID
159
+ >>> cap = find_camera_by_vid_pid(vid=0x0C45, pid=0x636D) # Arducam
160
+ >>> if cap is not None:
161
+ ... print("Found Arducam")
162
+ ... cap.release()
75
163
 
76
164
  """
77
165
  if platform.system() == "Linux":