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.
- plexus/__init__.py +31 -0
- plexus/__main__.py +4 -0
- plexus/adapters/__init__.py +122 -0
- plexus/adapters/base.py +409 -0
- plexus/adapters/ble.py +257 -0
- plexus/adapters/can.py +439 -0
- plexus/adapters/can_detect.py +174 -0
- plexus/adapters/mavlink.py +642 -0
- plexus/adapters/mavlink_detect.py +192 -0
- plexus/adapters/modbus.py +622 -0
- plexus/adapters/mqtt.py +350 -0
- plexus/adapters/opcua.py +607 -0
- plexus/adapters/registry.py +206 -0
- plexus/adapters/serial_adapter.py +547 -0
- plexus/buffer.py +257 -0
- plexus/cameras/__init__.py +57 -0
- plexus/cameras/auto.py +239 -0
- plexus/cameras/base.py +189 -0
- plexus/cameras/picamera.py +171 -0
- plexus/cameras/usb.py +143 -0
- plexus/cli.py +783 -0
- plexus/client.py +465 -0
- plexus/config.py +169 -0
- plexus/connector.py +666 -0
- plexus/deps.py +246 -0
- plexus/detect.py +1238 -0
- plexus/importers/__init__.py +25 -0
- plexus/importers/rosbag.py +778 -0
- plexus/sensors/__init__.py +118 -0
- plexus/sensors/ads1115.py +164 -0
- plexus/sensors/adxl345.py +179 -0
- plexus/sensors/auto.py +290 -0
- plexus/sensors/base.py +412 -0
- plexus/sensors/bh1750.py +102 -0
- plexus/sensors/bme280.py +241 -0
- plexus/sensors/gps.py +317 -0
- plexus/sensors/ina219.py +149 -0
- plexus/sensors/magnetometer.py +239 -0
- plexus/sensors/mpu6050.py +162 -0
- plexus/sensors/sht3x.py +139 -0
- plexus/sensors/spi_scan.py +164 -0
- plexus/sensors/system.py +261 -0
- plexus/sensors/vl53l0x.py +109 -0
- plexus/streaming.py +743 -0
- plexus/tui.py +642 -0
- plexus_python-0.1.0.dist-info/METADATA +470 -0
- plexus_python-0.1.0.dist-info/RECORD +50 -0
- plexus_python-0.1.0.dist-info/WHEEL +4 -0
- plexus_python-0.1.0.dist-info/entry_points.txt +2 -0
- plexus_python-0.1.0.dist-info/licenses/LICENSE +190 -0
plexus/deps.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lazy dependency management for Plexus optional extras.
|
|
3
|
+
|
|
4
|
+
Provides helpful error messages when optional packages are missing,
|
|
5
|
+
and auto-install support for CLI usage.
|
|
6
|
+
|
|
7
|
+
Usage in adapter/sensor code:
|
|
8
|
+
from plexus.deps import require
|
|
9
|
+
|
|
10
|
+
# In a function that needs an optional dependency:
|
|
11
|
+
smbus2 = require("smbus2", extra="sensors")
|
|
12
|
+
bus = smbus2.SMBus(1)
|
|
13
|
+
|
|
14
|
+
# Or check availability without raising:
|
|
15
|
+
if is_available("opencv-python"):
|
|
16
|
+
import cv2
|
|
17
|
+
|
|
18
|
+
CLI auto-install:
|
|
19
|
+
When running via CLI with --auto-install, missing dependencies
|
|
20
|
+
are installed automatically instead of raising errors.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import importlib
|
|
24
|
+
import logging
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
from typing import Optional
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
# Maps import names to pip package names and the plexus extra that includes them
|
|
32
|
+
DEPENDENCY_MAP = {
|
|
33
|
+
# import_name: (pip_package, plexus_extra, description)
|
|
34
|
+
"smbus2": ("smbus2>=0.4.0", "sensors", "I2C sensor communication"),
|
|
35
|
+
"cv2": ("opencv-python>=4.8.0", "camera", "USB webcam support"),
|
|
36
|
+
"numpy": ("numpy>=1.20.0", "camera", "Numerical arrays for camera frames"),
|
|
37
|
+
"picamera2": ("picamera2>=0.3.12", "picamera", "Raspberry Pi Camera Module"),
|
|
38
|
+
"paho": ("paho-mqtt>=1.6.0", "mqtt", "MQTT broker bridging"),
|
|
39
|
+
"can": ("python-can>=4.0.0", "can", "CAN bus interface"),
|
|
40
|
+
"cantools": ("cantools>=39.0.0", "can", "DBC file parsing"),
|
|
41
|
+
"rosbags": ("rosbags>=0.9.0", "ros", "ROS bag file reading"),
|
|
42
|
+
"mcap": ("mcap>=1.0.0", "ros", "MCAP format support"),
|
|
43
|
+
"serial": ("pyserial>=3.5", "serial", "Serial port communication"),
|
|
44
|
+
"rich": ("rich>=13.0.0", "tui", "Rich terminal output"),
|
|
45
|
+
"bluetooth": ("pybluez>=0.23", "bluetooth", "Bluetooth device scanning"),
|
|
46
|
+
"psutil": ("psutil>=5.9.0", "system", "System resource monitoring"),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Global flag for CLI auto-install mode
|
|
50
|
+
_auto_install_enabled = False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def enable_auto_install():
|
|
54
|
+
"""Enable automatic installation of missing dependencies (CLI mode)."""
|
|
55
|
+
global _auto_install_enabled
|
|
56
|
+
_auto_install_enabled = True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def disable_auto_install():
|
|
60
|
+
"""Disable automatic installation."""
|
|
61
|
+
global _auto_install_enabled
|
|
62
|
+
_auto_install_enabled = False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def is_available(import_name: str) -> bool:
|
|
66
|
+
"""Check if a package is importable without raising."""
|
|
67
|
+
try:
|
|
68
|
+
importlib.import_module(import_name)
|
|
69
|
+
return True
|
|
70
|
+
except ImportError:
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def require(import_name: str, extra: Optional[str] = None):
|
|
75
|
+
"""
|
|
76
|
+
Import and return a module, with helpful errors and optional auto-install.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
import_name: The Python import name (e.g., "smbus2", "cv2")
|
|
80
|
+
extra: The plexus extra that provides this (e.g., "sensors")
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
The imported module
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ImportError: With a helpful message including the pip install command
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
return importlib.import_module(import_name)
|
|
90
|
+
except ImportError:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
# Look up package info
|
|
94
|
+
dep_info = DEPENDENCY_MAP.get(import_name)
|
|
95
|
+
if dep_info:
|
|
96
|
+
pip_package, plexus_extra, description = dep_info
|
|
97
|
+
else:
|
|
98
|
+
pip_package = import_name
|
|
99
|
+
plexus_extra = extra
|
|
100
|
+
description = import_name
|
|
101
|
+
|
|
102
|
+
# Try auto-install if enabled
|
|
103
|
+
if _auto_install_enabled:
|
|
104
|
+
if _pip_install(pip_package, description):
|
|
105
|
+
return importlib.import_module(import_name)
|
|
106
|
+
|
|
107
|
+
# Build helpful error message
|
|
108
|
+
if plexus_extra:
|
|
109
|
+
msg = (
|
|
110
|
+
f"\n"
|
|
111
|
+
f" Missing dependency: {import_name} ({description})\n"
|
|
112
|
+
f"\n"
|
|
113
|
+
f" Install with:\n"
|
|
114
|
+
f" pip install plexus-python[{plexus_extra}]\n"
|
|
115
|
+
f"\n"
|
|
116
|
+
f" Or install directly:\n"
|
|
117
|
+
f" pip install {pip_package}\n"
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
msg = (
|
|
121
|
+
f"\n"
|
|
122
|
+
f" Missing dependency: {import_name} ({description})\n"
|
|
123
|
+
f"\n"
|
|
124
|
+
f" Install with:\n"
|
|
125
|
+
f" pip install {pip_package}\n"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
raise ImportError(msg)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def prompt_install(import_name: str, extra: Optional[str] = None) -> bool:
|
|
132
|
+
"""
|
|
133
|
+
Check if a dependency is available; if not, prompt user to install.
|
|
134
|
+
|
|
135
|
+
For CLI interactive use. Returns True if the dependency is available
|
|
136
|
+
(either already installed or just installed).
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
import_name: The Python import name
|
|
140
|
+
extra: The plexus extra that provides this
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
True if the module is now importable
|
|
144
|
+
"""
|
|
145
|
+
if is_available(import_name):
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
dep_info = DEPENDENCY_MAP.get(import_name)
|
|
149
|
+
if dep_info:
|
|
150
|
+
pip_package, plexus_extra, description = dep_info
|
|
151
|
+
else:
|
|
152
|
+
pip_package = import_name
|
|
153
|
+
plexus_extra = extra
|
|
154
|
+
description = import_name
|
|
155
|
+
|
|
156
|
+
# Auto-install mode: don't prompt
|
|
157
|
+
if _auto_install_enabled:
|
|
158
|
+
return _pip_install(pip_package, description)
|
|
159
|
+
|
|
160
|
+
# Interactive prompt
|
|
161
|
+
try:
|
|
162
|
+
import click
|
|
163
|
+
|
|
164
|
+
click.echo()
|
|
165
|
+
click.secho(f" {description} requires '{import_name}' which is not installed.", fg="yellow")
|
|
166
|
+
click.echo()
|
|
167
|
+
|
|
168
|
+
if plexus_extra:
|
|
169
|
+
click.echo(f" Install with: pip install plexus-python[{plexus_extra}]")
|
|
170
|
+
else:
|
|
171
|
+
click.echo(f" Install with: pip install {pip_package}")
|
|
172
|
+
|
|
173
|
+
click.echo()
|
|
174
|
+
|
|
175
|
+
if click.confirm(" Install now?", default=True):
|
|
176
|
+
return _pip_install(pip_package, description)
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
except (ImportError, EOFError):
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _pip_install(package: str, description: str) -> bool:
|
|
184
|
+
"""Install a package via pip. Returns True on success."""
|
|
185
|
+
try:
|
|
186
|
+
import click
|
|
187
|
+
click.secho(f" Installing {description}...", fg="cyan", nl=False)
|
|
188
|
+
except ImportError:
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
result = subprocess.run(
|
|
193
|
+
[sys.executable, "-m", "pip", "install", "-q", package],
|
|
194
|
+
capture_output=True,
|
|
195
|
+
text=True,
|
|
196
|
+
timeout=120,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if result.returncode == 0:
|
|
200
|
+
try:
|
|
201
|
+
import click
|
|
202
|
+
click.secho(" done", fg="green")
|
|
203
|
+
except ImportError:
|
|
204
|
+
pass
|
|
205
|
+
logger.info(f"Installed {package}")
|
|
206
|
+
return True
|
|
207
|
+
else:
|
|
208
|
+
try:
|
|
209
|
+
import click
|
|
210
|
+
click.secho(" failed", fg="red")
|
|
211
|
+
if result.stderr:
|
|
212
|
+
click.secho(f" {result.stderr.strip()[:200]}", fg="bright_black")
|
|
213
|
+
except ImportError:
|
|
214
|
+
pass
|
|
215
|
+
logger.warning(f"Failed to install {package}: {result.stderr}")
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
except subprocess.TimeoutExpired:
|
|
219
|
+
try:
|
|
220
|
+
import click
|
|
221
|
+
click.secho(" timed out", fg="red")
|
|
222
|
+
except ImportError:
|
|
223
|
+
pass
|
|
224
|
+
return False
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.warning(f"pip install failed: {e}")
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def check_extras_for_scan() -> dict:
|
|
231
|
+
"""
|
|
232
|
+
Check which optional extras are installed.
|
|
233
|
+
Returns a dict of {extra_name: bool}.
|
|
234
|
+
Used by `plexus doctor` and `plexus scan`.
|
|
235
|
+
"""
|
|
236
|
+
extras = {
|
|
237
|
+
"sensors": is_available("smbus2"),
|
|
238
|
+
"camera": is_available("cv2"),
|
|
239
|
+
"picamera": is_available("picamera2"),
|
|
240
|
+
"mqtt": is_available("paho"),
|
|
241
|
+
"can": is_available("can"),
|
|
242
|
+
"ros": is_available("rosbags"),
|
|
243
|
+
"serial": is_available("serial"),
|
|
244
|
+
"tui": is_available("rich"),
|
|
245
|
+
}
|
|
246
|
+
return extras
|