plexus-python 0.1.0__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 (50) hide show
  1. plexus/__init__.py +31 -0
  2. plexus/__main__.py +4 -0
  3. plexus/adapters/__init__.py +122 -0
  4. plexus/adapters/base.py +409 -0
  5. plexus/adapters/ble.py +257 -0
  6. plexus/adapters/can.py +439 -0
  7. plexus/adapters/can_detect.py +174 -0
  8. plexus/adapters/mavlink.py +642 -0
  9. plexus/adapters/mavlink_detect.py +192 -0
  10. plexus/adapters/modbus.py +622 -0
  11. plexus/adapters/mqtt.py +350 -0
  12. plexus/adapters/opcua.py +607 -0
  13. plexus/adapters/registry.py +206 -0
  14. plexus/adapters/serial_adapter.py +547 -0
  15. plexus/buffer.py +257 -0
  16. plexus/cameras/__init__.py +57 -0
  17. plexus/cameras/auto.py +239 -0
  18. plexus/cameras/base.py +189 -0
  19. plexus/cameras/picamera.py +171 -0
  20. plexus/cameras/usb.py +143 -0
  21. plexus/cli.py +783 -0
  22. plexus/client.py +465 -0
  23. plexus/config.py +169 -0
  24. plexus/connector.py +666 -0
  25. plexus/deps.py +246 -0
  26. plexus/detect.py +1238 -0
  27. plexus/importers/__init__.py +25 -0
  28. plexus/importers/rosbag.py +778 -0
  29. plexus/sensors/__init__.py +118 -0
  30. plexus/sensors/ads1115.py +164 -0
  31. plexus/sensors/adxl345.py +179 -0
  32. plexus/sensors/auto.py +290 -0
  33. plexus/sensors/base.py +412 -0
  34. plexus/sensors/bh1750.py +102 -0
  35. plexus/sensors/bme280.py +241 -0
  36. plexus/sensors/gps.py +317 -0
  37. plexus/sensors/ina219.py +149 -0
  38. plexus/sensors/magnetometer.py +239 -0
  39. plexus/sensors/mpu6050.py +162 -0
  40. plexus/sensors/sht3x.py +139 -0
  41. plexus/sensors/spi_scan.py +164 -0
  42. plexus/sensors/system.py +261 -0
  43. plexus/sensors/vl53l0x.py +109 -0
  44. plexus/streaming.py +743 -0
  45. plexus/tui.py +642 -0
  46. plexus_python-0.1.0.dist-info/METADATA +470 -0
  47. plexus_python-0.1.0.dist-info/RECORD +50 -0
  48. plexus_python-0.1.0.dist-info/WHEEL +4 -0
  49. plexus_python-0.1.0.dist-info/entry_points.txt +2 -0
  50. plexus_python-0.1.0.dist-info/licenses/LICENSE +190 -0
plexus/cameras/base.py ADDED
@@ -0,0 +1,189 @@
1
+ """
2
+ Base camera class and utilities for Plexus camera drivers.
3
+
4
+ All camera drivers inherit from BaseCamera and implement the capture() method.
5
+ """
6
+
7
+ import logging
8
+ import time
9
+ from abc import ABC, abstractmethod
10
+ from dataclasses import dataclass, field
11
+ from typing import Dict, List, Optional, Any, Tuple
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class CameraFrame:
18
+ """A single camera frame with JPEG-encoded image data."""
19
+ data: bytes
20
+ width: int
21
+ height: int
22
+ timestamp: float = field(default_factory=time.time)
23
+ camera_id: str = ""
24
+ tags: Dict[str, str] = field(default_factory=dict)
25
+
26
+
27
+ class BaseCamera(ABC):
28
+ """
29
+ Base class for all camera drivers.
30
+
31
+ Subclasses must implement:
32
+ - capture() -> Optional[CameraFrame]: Capture a single frame
33
+ - name: Human-readable camera name
34
+
35
+ Optional overrides:
36
+ - setup(): Initialize the camera (called once)
37
+ - cleanup(): Clean up resources (called on stop)
38
+ - is_available(): Check if camera is connected
39
+ """
40
+
41
+ name: str = "Unknown Camera"
42
+ description: str = ""
43
+
44
+ def __init__(
45
+ self,
46
+ frame_rate: float = 10.0,
47
+ resolution: Tuple[int, int] = (640, 480),
48
+ quality: int = 80,
49
+ camera_id: str = "",
50
+ tags: Optional[Dict[str, str]] = None,
51
+ ):
52
+ """
53
+ Initialize the camera driver.
54
+
55
+ Args:
56
+ frame_rate: Target frames per second. Default 10 fps.
57
+ resolution: (width, height) tuple. Default (640, 480).
58
+ quality: JPEG quality 1-100. Default 80.
59
+ camera_id: Unique identifier for this camera.
60
+ tags: Tags to add to all frames from this camera.
61
+ """
62
+ self.frame_rate = frame_rate
63
+ self.resolution = resolution
64
+ self.quality = max(1, min(100, quality))
65
+ self.camera_id = camera_id
66
+ self.tags = tags or {}
67
+ self._running = False
68
+ self._error: Optional[str] = None
69
+
70
+ @abstractmethod
71
+ def capture(self) -> Optional[CameraFrame]:
72
+ """
73
+ Capture a single frame from the camera.
74
+
75
+ Returns:
76
+ CameraFrame with JPEG-encoded image data, or None if capture failed.
77
+ """
78
+ pass
79
+
80
+ def setup(self) -> None:
81
+ """
82
+ Initialize the camera hardware.
83
+ Called once before capturing starts.
84
+ Override in subclass if needed.
85
+ """
86
+ pass
87
+
88
+ def cleanup(self) -> None:
89
+ """
90
+ Clean up camera resources.
91
+ Called when camera is stopped.
92
+ Override in subclass if needed.
93
+ """
94
+ pass
95
+
96
+ def is_available(self) -> bool:
97
+ """
98
+ Check if the camera is connected and responding.
99
+
100
+ Returns:
101
+ True if camera is available.
102
+ """
103
+ try:
104
+ frame = self.capture()
105
+ return frame is not None
106
+ except Exception:
107
+ return False
108
+
109
+ def get_info(self) -> Dict[str, Any]:
110
+ """Get camera information for display."""
111
+ return {
112
+ "camera_id": self.camera_id,
113
+ "name": self.name,
114
+ "description": self.description,
115
+ "frame_rate": self.frame_rate,
116
+ "resolution": list(self.resolution),
117
+ "quality": self.quality,
118
+ "available": self.is_available(),
119
+ }
120
+
121
+
122
+ class CameraHub:
123
+ """
124
+ Manages multiple cameras.
125
+
126
+ Usage:
127
+ from plexus.cameras import CameraHub, USBCamera
128
+
129
+ hub = CameraHub()
130
+ hub.add(USBCamera(device_index=0))
131
+ hub.add(USBCamera(device_index=1))
132
+
133
+ # Capture from all cameras
134
+ frames = hub.capture_all()
135
+ """
136
+
137
+ def __init__(self):
138
+ self.cameras: List[BaseCamera] = []
139
+
140
+ def add(self, camera: BaseCamera) -> "CameraHub":
141
+ """Add a camera to the hub."""
142
+ self.cameras.append(camera)
143
+ return self
144
+
145
+ def remove(self, camera: BaseCamera) -> "CameraHub":
146
+ """Remove a camera from the hub."""
147
+ self.cameras.remove(camera)
148
+ return self
149
+
150
+ def setup(self) -> None:
151
+ """Initialize all cameras."""
152
+ for camera in self.cameras:
153
+ try:
154
+ camera.setup()
155
+ except Exception as e:
156
+ logger.warning(f"Failed to setup {camera.name}: {e}")
157
+ camera._error = str(e)
158
+
159
+ def cleanup(self) -> None:
160
+ """Clean up all cameras."""
161
+ for camera in self.cameras:
162
+ try:
163
+ camera.cleanup()
164
+ except Exception:
165
+ pass
166
+
167
+ def capture_all(self) -> List[CameraFrame]:
168
+ """Capture from all cameras once."""
169
+ frames = []
170
+ for camera in self.cameras:
171
+ try:
172
+ frame = camera.capture()
173
+ if frame:
174
+ frames.append(frame)
175
+ except Exception as e:
176
+ camera._error = str(e)
177
+ return frames
178
+
179
+ def get_camera(self, camera_id: str) -> Optional[BaseCamera]:
180
+ """Get a camera by ID."""
181
+ for camera in self.cameras:
182
+ if camera.camera_id == camera_id:
183
+ return camera
184
+ return None
185
+
186
+ def get_info(self) -> List[Dict[str, Any]]:
187
+ """Get info about all cameras."""
188
+ return [c.get_info() for c in self.cameras]
189
+
@@ -0,0 +1,171 @@
1
+ """
2
+ Raspberry Pi Camera Module driver using picamera2.
3
+
4
+ Supports Pi Camera Module v1, v2, v3, and HQ Camera.
5
+ """
6
+
7
+ import logging
8
+ import time
9
+ from typing import Optional, Tuple
10
+
11
+ from plexus.cameras.base import BaseCamera, CameraFrame
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # picamera2 is optional - only imported when PiCamera is used
16
+ try:
17
+ from picamera2 import Picamera2
18
+ import io
19
+ PICAMERA_AVAILABLE = True
20
+ except ImportError:
21
+ PICAMERA_AVAILABLE = False
22
+
23
+
24
+ class PiCamera(BaseCamera):
25
+ """
26
+ Raspberry Pi Camera Module driver using picamera2.
27
+
28
+ Works with Pi Camera Module v1, v2, v3, and HQ Camera on
29
+ Raspberry Pi devices running Raspberry Pi OS.
30
+
31
+ Usage:
32
+ from plexus.cameras import PiCamera
33
+
34
+ camera = PiCamera(camera_num=0)
35
+ camera.setup()
36
+ frame = camera.capture()
37
+ camera.cleanup()
38
+ """
39
+
40
+ name = "Pi Camera"
41
+ description = "Raspberry Pi Camera Module via picamera2"
42
+
43
+ def __init__(
44
+ self,
45
+ camera_num: int = 0,
46
+ frame_rate: float = 30.0,
47
+ resolution: Tuple[int, int] = (1280, 720),
48
+ quality: int = 85,
49
+ camera_id: Optional[str] = None,
50
+ **kwargs,
51
+ ):
52
+ """
53
+ Initialize Pi Camera driver.
54
+
55
+ Args:
56
+ camera_num: Camera number (0 = first camera, 1 = second, etc.)
57
+ frame_rate: Target frames per second. Default 30 fps.
58
+ resolution: (width, height) tuple. Default (1280, 720).
59
+ quality: JPEG quality 1-100. Default 85.
60
+ camera_id: Unique identifier. Defaults to "picam:{camera_num}".
61
+ """
62
+ if not PICAMERA_AVAILABLE:
63
+ raise ImportError(
64
+ "picamera2 is required for Pi Camera support. "
65
+ "Install with: pip install plexus-python[picamera]"
66
+ )
67
+
68
+ super().__init__(
69
+ frame_rate=frame_rate,
70
+ resolution=resolution,
71
+ quality=quality,
72
+ camera_id=camera_id or f"picam:{camera_num}",
73
+ **kwargs,
74
+ )
75
+ self.camera_num = camera_num
76
+ self._picam: Optional[Picamera2] = None
77
+
78
+ def setup(self) -> None:
79
+ """Initialize the camera."""
80
+ if self._picam is not None:
81
+ self.cleanup()
82
+ self._picam = Picamera2(camera_num=self.camera_num)
83
+
84
+ # Configure for still capture with specified resolution
85
+ config = self._picam.create_still_configuration(
86
+ main={"size": self.resolution, "format": "RGB888"},
87
+ )
88
+ self._picam.configure(config)
89
+ self._picam.start()
90
+
91
+ def cleanup(self) -> None:
92
+ """Stop and close the camera."""
93
+ if self._picam is not None:
94
+ self._picam.stop()
95
+ self._picam.close()
96
+ self._picam = None
97
+
98
+ def capture(self) -> Optional[CameraFrame]:
99
+ """
100
+ Capture a single frame.
101
+
102
+ Returns:
103
+ CameraFrame with JPEG-encoded image, or None if capture failed.
104
+ """
105
+ if self._picam is None:
106
+ self.setup()
107
+
108
+ try:
109
+ # Capture to numpy array
110
+ frame = self._picam.capture_array()
111
+
112
+ if frame is None:
113
+ return None
114
+
115
+ # Encode to JPEG using OpenCV if available, otherwise use PIL
116
+ try:
117
+ import cv2
118
+ encode_params = [cv2.IMWRITE_JPEG_QUALITY, self.quality]
119
+ # Convert RGB to BGR for OpenCV
120
+ frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
121
+ success, jpeg_data = cv2.imencode('.jpg', frame_bgr, encode_params)
122
+ if not success:
123
+ return None
124
+ data = jpeg_data.tobytes()
125
+ except ImportError:
126
+ # Fallback to PIL
127
+ from PIL import Image
128
+ img = Image.fromarray(frame)
129
+ buffer = io.BytesIO()
130
+ img.save(buffer, format='JPEG', quality=self.quality)
131
+ data = buffer.getvalue()
132
+
133
+ return CameraFrame(
134
+ data=data,
135
+ width=frame.shape[1],
136
+ height=frame.shape[0],
137
+ timestamp=time.time(),
138
+ camera_id=self.camera_id,
139
+ tags=self.tags.copy(),
140
+ )
141
+
142
+ except Exception as e:
143
+ logger.debug(f"Pi camera capture failed: {e}")
144
+ return None
145
+
146
+ def is_available(self) -> bool:
147
+ """Check if camera is available."""
148
+ if not PICAMERA_AVAILABLE:
149
+ return False
150
+
151
+ try:
152
+ camera_info = Picamera2.global_camera_info()
153
+ return len(camera_info) > self.camera_num
154
+ except Exception:
155
+ return False
156
+
157
+ def get_info(self) -> dict:
158
+ """Get camera information."""
159
+ info = super().get_info()
160
+ info["camera_num"] = self.camera_num
161
+
162
+ # Get model info if available
163
+ if PICAMERA_AVAILABLE:
164
+ try:
165
+ camera_info = Picamera2.global_camera_info()
166
+ if len(camera_info) > self.camera_num:
167
+ info["model"] = camera_info[self.camera_num].get("Model", "Unknown")
168
+ except Exception:
169
+ pass
170
+
171
+ return info
plexus/cameras/usb.py ADDED
@@ -0,0 +1,143 @@
1
+ """
2
+ USB webcam driver using OpenCV.
3
+
4
+ Supports any camera compatible with cv2.VideoCapture (USB webcams, built-in cameras).
5
+ """
6
+
7
+ import time
8
+ from typing import Optional, Tuple
9
+
10
+ from plexus.cameras.base import BaseCamera, CameraFrame
11
+
12
+ # OpenCV is optional - only imported when USBCamera is used
13
+ try:
14
+ import cv2
15
+ OPENCV_AVAILABLE = True
16
+ except ImportError:
17
+ OPENCV_AVAILABLE = False
18
+
19
+
20
+ class USBCamera(BaseCamera):
21
+ """
22
+ USB webcam driver using OpenCV VideoCapture.
23
+
24
+ Works with USB webcams, built-in laptop cameras, and other
25
+ video capture devices supported by OpenCV.
26
+
27
+ Usage:
28
+ from plexus.cameras import USBCamera
29
+
30
+ camera = USBCamera(device_index=0)
31
+ camera.setup()
32
+ frame = camera.capture()
33
+ camera.cleanup()
34
+ """
35
+
36
+ name = "USB Camera"
37
+ description = "USB webcam via OpenCV VideoCapture"
38
+
39
+ def __init__(
40
+ self,
41
+ device_index: int = 0,
42
+ frame_rate: float = 15.0,
43
+ resolution: Tuple[int, int] = (640, 480),
44
+ quality: int = 80,
45
+ camera_id: Optional[str] = None,
46
+ **kwargs,
47
+ ):
48
+ """
49
+ Initialize USB camera driver.
50
+
51
+ Args:
52
+ device_index: Camera device index (0 = first camera, 1 = second, etc.)
53
+ frame_rate: Target frames per second. Default 15 fps.
54
+ resolution: (width, height) tuple. Default (640, 480).
55
+ quality: JPEG quality 1-100. Default 80.
56
+ camera_id: Unique identifier. Defaults to "usb:{device_index}".
57
+ """
58
+ if not OPENCV_AVAILABLE:
59
+ raise ImportError(
60
+ "OpenCV is required for USB camera support. "
61
+ "Install with: pip install plexus-python[camera]"
62
+ )
63
+
64
+ super().__init__(
65
+ frame_rate=frame_rate,
66
+ resolution=resolution,
67
+ quality=quality,
68
+ camera_id=camera_id or f"usb:{device_index}",
69
+ **kwargs,
70
+ )
71
+ self.device_index = device_index
72
+ self._cap: Optional[cv2.VideoCapture] = None
73
+
74
+ def setup(self) -> None:
75
+ """Initialize the camera."""
76
+ self._cap = cv2.VideoCapture(self.device_index)
77
+
78
+ if not self._cap.isOpened():
79
+ raise RuntimeError(f"Failed to open camera at index {self.device_index}")
80
+
81
+ # Set resolution
82
+ self._cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.resolution[0])
83
+ self._cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.resolution[1])
84
+
85
+ # Set frame rate if supported
86
+ self._cap.set(cv2.CAP_PROP_FPS, self.frame_rate)
87
+
88
+ # Read actual values (camera may not support requested settings)
89
+ actual_width = int(self._cap.get(cv2.CAP_PROP_FRAME_WIDTH))
90
+ actual_height = int(self._cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
91
+ self.resolution = (actual_width, actual_height)
92
+
93
+ def cleanup(self) -> None:
94
+ """Release the camera."""
95
+ if self._cap is not None:
96
+ self._cap.release()
97
+ self._cap = None
98
+
99
+ def capture(self) -> Optional[CameraFrame]:
100
+ """
101
+ Capture a single frame.
102
+
103
+ Returns:
104
+ CameraFrame with JPEG-encoded image, or None if capture failed.
105
+ """
106
+ if self._cap is None:
107
+ self.setup()
108
+
109
+ ret, frame = self._cap.read()
110
+ if not ret or frame is None:
111
+ return None
112
+
113
+ # Encode to JPEG
114
+ encode_params = [cv2.IMWRITE_JPEG_QUALITY, self.quality]
115
+ success, jpeg_data = cv2.imencode('.jpg', frame, encode_params)
116
+
117
+ if not success:
118
+ return None
119
+
120
+ return CameraFrame(
121
+ data=jpeg_data.tobytes(),
122
+ width=frame.shape[1],
123
+ height=frame.shape[0],
124
+ timestamp=time.time(),
125
+ camera_id=self.camera_id,
126
+ tags=self.tags.copy(),
127
+ )
128
+
129
+ def is_available(self) -> bool:
130
+ """Check if camera is available without fully initializing."""
131
+ if not OPENCV_AVAILABLE:
132
+ return False
133
+
134
+ cap = cv2.VideoCapture(self.device_index)
135
+ available = cap.isOpened()
136
+ cap.release()
137
+ return available
138
+
139
+ def get_info(self) -> dict:
140
+ """Get camera information."""
141
+ info = super().get_info()
142
+ info["device_index"] = self.device_index
143
+ return info