plexus-python 0.4.9__tar.gz → 0.5.2__tar.gz
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_python-0.4.9 → plexus_python-0.5.2}/.github/workflows/ci.yml +1 -1
- {plexus_python-0.4.9 → plexus_python-0.5.2}/CHANGELOG.md +82 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/PKG-INFO +91 -19
- {plexus_python-0.4.9 → plexus_python-0.5.2}/README.md +87 -13
- {plexus_python-0.4.9 → plexus_python-0.5.2}/examples/uv.lock +3 -3
- {plexus_python-0.4.9 → plexus_python-0.5.2}/plexus/__init__.py +1 -1
- plexus_python-0.5.2/plexus/_log.py +15 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/plexus/client.py +105 -44
- {plexus_python-0.4.9 → plexus_python-0.5.2}/plexus/ws.py +87 -19
- {plexus_python-0.4.9 → plexus_python-0.5.2}/pyproject.toml +4 -6
- plexus_python-0.5.2/skills/plexus/SKILL.md +189 -0
- plexus_python-0.5.2/skills/plexus/references/api.md +331 -0
- plexus_python-0.5.2/skills/plexus/references/sdk.md +227 -0
- plexus_python-0.5.2/tests/test_basic.py +114 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/tests/test_retry.py +12 -11
- plexus_python-0.5.2/uv.lock +503 -0
- plexus_python-0.4.9/tests/test_basic.py +0 -61
- plexus_python-0.4.9/uv.lock +0 -1061
- {plexus_python-0.4.9 → plexus_python-0.5.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/.github/workflows/publish.yml +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/.gitignore +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/AGENTS.md +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/API.md +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/CODE_OF_CONDUCT.md +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/CONTRIBUTING.md +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/LICENSE +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/SECURITY.md +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/examples/.python-version +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/examples/README.md +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/examples/basic.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/examples/can.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/examples/i2c_bme280.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/examples/mac_metrics.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/examples/mavlink.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/examples/mqtt.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/examples/pyproject.toml +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/plexus/buffer.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/plexus/cli.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/plexus/config.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/scripts/plexus.service +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/scripts/release.sh +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/scripts/scan_buses.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/scripts/setup.sh +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/tests/test_buffer.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/tests/test_config.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/tests/test_video.py +0 -0
- {plexus_python-0.4.9 → plexus_python-0.5.2}/tests/test_ws.py +0 -0
|
@@ -1,5 +1,87 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.2] - 2026-05-21 - DX hardening for hardware engineers
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Error messages now reference `plexus init` instead of the non-existent `plexus start`.
|
|
8
|
+
- `close()` now attempts to flush any buffered points before tearing down the transport,
|
|
9
|
+
preventing silent data loss on graceful shutdown.
|
|
10
|
+
- `persistent_buffer` default changed from `False` to `True` — store-and-forward is now
|
|
11
|
+
on by default, matching the `~/.plexus/config.json` default and the right choice for
|
|
12
|
+
field hardware. Pass `persistent_buffer=False` to opt out (e.g. in test fixtures).
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `send_batch()` now accepts 3-tuples `(metric, value, timestamp)` alongside the existing
|
|
17
|
+
2-tuple form. Per-point timestamps let sensors on different interrupt timers share a
|
|
18
|
+
single batch call. 2-tuples continue to use the shared `timestamp` argument.
|
|
19
|
+
- `on_command()` now warns immediately via stderr if called after the WebSocket has already
|
|
20
|
+
authenticated, making the "register before first send()" ordering requirement visible
|
|
21
|
+
rather than silently broken.
|
|
22
|
+
- `source_id` is validated against `^[a-z0-9][a-z0-9_-]{1,62}$` at construction time.
|
|
23
|
+
Invalid names now raise `ValueError` with a clear message instead of failing obscurely
|
|
24
|
+
at the gateway.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- `_say()` / `_QUIET` consolidated into a new internal `plexus/_log.py` module.
|
|
29
|
+
Previously duplicated verbatim between `client.py` and `ws.py`.
|
|
30
|
+
|
|
31
|
+
## [0.5.1] - 2026-05-19 - Binary video frames + non-blocking send
|
|
32
|
+
|
|
33
|
+
### Performance
|
|
34
|
+
|
|
35
|
+
- `send_video_frame()` now sends a compact binary WebSocket frame instead of
|
|
36
|
+
JSON+base64. The binary header encodes source_id, camera_id, width, height,
|
|
37
|
+
and timestamp_ms; the JPEG payload follows raw. Eliminates the 33% base64
|
|
38
|
+
wire overhead, reducing per-frame bandwidth by ~25% and raising the
|
|
39
|
+
sustainable FPS ceiling from ~15–20 fps to ~20–25 fps at 1280×720 quality 85.
|
|
40
|
+
- Gateway decodes the binary header and re-encodes as JSON+base64 before
|
|
41
|
+
relaying to browsers — no changes required in the frontend, data_api, or any
|
|
42
|
+
other consumer.
|
|
43
|
+
|
|
44
|
+
### Reliability
|
|
45
|
+
|
|
46
|
+
- `send_video_frame()` is now non-blocking. Frames are placed into a
|
|
47
|
+
`queue.Queue(maxsize=2)` drained by a dedicated `plexus-video` daemon thread.
|
|
48
|
+
When the queue is full (sender backlogged) frames are dropped rather than
|
|
49
|
+
blocking the capture pipeline, preventing deadlocks at any FPS.
|
|
50
|
+
- `stop()` / `close()` now exits cleanly within 0.5 s regardless of in-flight
|
|
51
|
+
sends. Previously a slow or hung WebSocket write could stall shutdown
|
|
52
|
+
indefinitely.
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- Removed `import base64` from `client.py` (no longer needed on the send path).
|
|
57
|
+
- `send_video_frame()` calls `ws.send_video_frame_async()` instead of the
|
|
58
|
+
internal `ws._send_frame()`.
|
|
59
|
+
|
|
60
|
+
### Wire protocol
|
|
61
|
+
|
|
62
|
+
- Gateway handles both binary frames (SDK ≥ 0.5.1) and legacy JSON text frames
|
|
63
|
+
transparently — older SDKs continue to work unchanged.
|
|
64
|
+
|
|
65
|
+
## [0.5.0] - 2026-05-19 - Security hardening, dep cleanup, Python 3.10+ only
|
|
66
|
+
|
|
67
|
+
### Security
|
|
68
|
+
|
|
69
|
+
- Removed `requests` (and its transitive deps `urllib3`, `idna`) entirely —
|
|
70
|
+
replaced with stdlib `urllib.request`. Closes 6 Dependabot alerts (#6, #9,
|
|
71
|
+
#10, #11, #12, #13, #19) by eliminating the vulnerability surface rather than
|
|
72
|
+
patching it.
|
|
73
|
+
- Bumped `Pillow>=12.2.0` (fixes #14, #15, #16, #17, #18, #20 — OOB write,
|
|
74
|
+
FITS decompression bomb, font integer overflow, PDF parsing DoS, and related
|
|
75
|
+
CVEs).
|
|
76
|
+
- Bumped `pytest>=9.0.3` in dev deps (fixes #7).
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- Dropped Python 3.8 and 3.9 support — both are past EOL and the patched
|
|
81
|
+
versions of Pillow and pytest all require `>=3.10`. `requires-python` is now
|
|
82
|
+
`>=3.10`.
|
|
83
|
+
- CI matrix: removed 3.8/3.9 runners, added 3.13.
|
|
84
|
+
|
|
3
85
|
## [0.4.9] - 2026-05-19 - Video input broadening and wire safety
|
|
4
86
|
|
|
5
87
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plexus-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Thin Python SDK for Plexus — send telemetry in one line
|
|
5
5
|
Project-URL: Homepage, https://plexus.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.plexus.dev
|
|
@@ -15,22 +15,20 @@ Classifier: Intended Audience :: Developers
|
|
|
15
15
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
21
|
Classifier: Topic :: Scientific/Engineering
|
|
23
22
|
Classifier: Topic :: System :: Hardware
|
|
24
|
-
Requires-Python: >=3.
|
|
25
|
-
Requires-Dist: requests>=2.32.4
|
|
23
|
+
Requires-Python: >=3.10
|
|
26
24
|
Requires-Dist: websocket-client>=1.7
|
|
27
25
|
Provides-Extra: dev
|
|
28
26
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
29
|
-
Requires-Dist: pytest>=
|
|
27
|
+
Requires-Dist: pytest>=9.0.3; extra == 'dev'
|
|
30
28
|
Requires-Dist: ruff; extra == 'dev'
|
|
31
29
|
Requires-Dist: websockets>=12; extra == 'dev'
|
|
32
30
|
Provides-Extra: video
|
|
33
|
-
Requires-Dist: pillow>=
|
|
31
|
+
Requires-Dist: pillow>=12.2.0; extra == 'video'
|
|
34
32
|
Description-Content-Type: text/markdown
|
|
35
33
|
|
|
36
34
|
# plexus-python
|
|
@@ -70,34 +68,108 @@ The name must match `^[a-z0-9][a-z0-9_-]{1,62}$`. `setup.sh` refuses to run with
|
|
|
70
68
|
|
|
71
69
|
In normal code, you usually just pass `source_id=...` explicitly to `Plexus(...)` and never have to think about it.
|
|
72
70
|
|
|
73
|
-
##
|
|
71
|
+
## Core methods
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
### `send(metric, value)` — stream a reading
|
|
74
|
+
|
|
75
|
+
The main method. Call it every time you have a new sensor reading.
|
|
77
76
|
|
|
77
|
+
```python
|
|
78
78
|
px = Plexus(source_id="rig-01") # reads PLEXUS_API_KEY from env
|
|
79
79
|
|
|
80
|
-
# Numbers
|
|
81
80
|
px.send("engine.rpm", 3450)
|
|
82
|
-
px.send("coolant.
|
|
81
|
+
px.send("coolant.temp", 82.3)
|
|
82
|
+
```
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
px.send("vehicle.state", "RUNNING")
|
|
86
|
-
px.send("motor.enabled", True)
|
|
87
|
-
px.send("position", {"x": 1.5, "y": 2.3, "z": 0.8})
|
|
84
|
+
`metric` is a dot-namespaced string (`"motor.rpm"`, `"gps.fix_quality"`). `value` accepts any JSON-serializable type:
|
|
88
85
|
|
|
89
|
-
|
|
86
|
+
| Type | Example | When to use |
|
|
87
|
+
|------|---------|-------------|
|
|
88
|
+
| `float` / `int` | `72.5`, `3450` | Sensor readings, counters |
|
|
89
|
+
| `str` | `"RUNNING"`, `"E_STALL"` | State machines, error codes |
|
|
90
|
+
| `bool` | `True` | Binary flags |
|
|
91
|
+
| `dict` | `{"x": 1.5, "y": 2.3}` | Vectors, structured readings |
|
|
92
|
+
| `list` | `[0.5, 1.2, -0.3]` | Waveforms, joint angles |
|
|
93
|
+
|
|
94
|
+
Optional arguments:
|
|
95
|
+
- `tags={"motor_id": "A1"}` — key-value labels for filtering in the dashboard
|
|
96
|
+
- `timestamp=t` — explicit Unix timestamp in seconds; omit to let the SDK pick (see [Timestamps](#timestamps-and-clock-correction))
|
|
97
|
+
|
|
98
|
+
### `send_batch(points)` — send multiple readings at once
|
|
99
|
+
|
|
100
|
+
Use this when you sample several sensors together and want them to share a timestamp and land in one network call.
|
|
101
|
+
|
|
102
|
+
```python
|
|
90
103
|
px.send_batch([
|
|
91
|
-
("temperature",
|
|
92
|
-
("
|
|
104
|
+
("temperature", 22.4),
|
|
105
|
+
("humidity", 58.1),
|
|
106
|
+
("pressure", 1013.2),
|
|
93
107
|
])
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`points` is a list of `(metric, value)` tuples. All points share the same timestamp (now, unless you pass `timestamp=t`). For independent timestamps per point, call `send()` in a loop instead.
|
|
111
|
+
|
|
112
|
+
### `event(name, data)` — record a discrete occurrence
|
|
113
|
+
|
|
114
|
+
Use `event()` for things that *happen* rather than things you *measure continuously*. Faults, state transitions, operator actions, log entries — anything you'd put on a timeline as a marker rather than plot as a graph.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
px.event("fault", "E-stop triggered")
|
|
118
|
+
px.event("state_change", {"from": "IDLE", "to": "RUNNING"})
|
|
119
|
+
px.event("sensor_error", {"sensor": "imu", "code": 42}, tags={"motor": "A"})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The platform displays events as markers overlaid on your telemetry charts, not as time-series lines.
|
|
123
|
+
|
|
124
|
+
### `run(run_id)` — group data into a named recording
|
|
94
125
|
|
|
95
|
-
|
|
126
|
+
```python
|
|
96
127
|
with px.run("thermal-cycle-001"):
|
|
97
128
|
while running:
|
|
98
129
|
px.send("temperature", read_temp())
|
|
99
130
|
```
|
|
100
131
|
|
|
132
|
+
All `send()` calls inside the context are tagged with `run_id`, making it easy to isolate and replay that slice of data in the dashboard.
|
|
133
|
+
|
|
134
|
+
## Video streaming
|
|
135
|
+
|
|
136
|
+
Two methods depending on whether you control the capture loop or just have a URL.
|
|
137
|
+
|
|
138
|
+
### `send_video_frame(frame, camera_id)` — send frames you capture yourself
|
|
139
|
+
|
|
140
|
+
Use this when your code owns the capture loop — a `picamera2` callback, an OpenCV `VideoCapture` loop, or an FFmpeg pipe you manage. Pass each frame and the SDK ships it to Plexus over WebSocket.
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
import cv2
|
|
144
|
+
|
|
145
|
+
cap = cv2.VideoCapture(0)
|
|
146
|
+
while True:
|
|
147
|
+
ok, frame = cap.read()
|
|
148
|
+
if ok:
|
|
149
|
+
px.send_video_frame(frame, camera_id="front")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Accepted frame types:
|
|
153
|
+
- **numpy ndarray** (H × W × C) — from OpenCV or picamera2; requires `opencv-python`
|
|
154
|
+
- **JPEG bytes** — passed through as-is, zero re-encode overhead
|
|
155
|
+
- **Other image bytes** (PNG, BMP, WebP) — decoded and re-encoded as JPEG via Pillow; requires `pip install plexus-python[video]`
|
|
156
|
+
|
|
157
|
+
`camera_id` identifies which camera the frame came from. Use distinct IDs when streaming from multiple cameras simultaneously (`"front"`, `"rear"`, `"cam:0"`).
|
|
158
|
+
|
|
159
|
+
### `stream_camera(url, camera_id)` — stream from an RTSP URL or file
|
|
160
|
+
|
|
161
|
+
Use this when you have an RTSP stream or video file and don't want to manage the capture loop yourself. The SDK runs FFmpeg internally and handles the rest. Requires FFmpeg on `$PATH`.
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
stop = px.stream_camera("rtsp://192.168.1.100/stream", camera_id="front")
|
|
165
|
+
# ... do other work ...
|
|
166
|
+
stop.set() # stop streaming
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Returns a `threading.Event` — call `.set()` to stop. Runs in a background thread so it doesn't block your main loop.
|
|
170
|
+
|
|
171
|
+
**Which to use:** if you're piping from `rpicam-vid`, `picamera2`, or your own capture process, use `send_video_frame()`. If you have an RTSP URL or file path, use `stream_camera()`.
|
|
172
|
+
|
|
101
173
|
## Bring Your Own Protocol
|
|
102
174
|
|
|
103
175
|
This package ships no adapters, auto-detection, or daemons — just the client. Use whatever library you'd use anyway and pipe values into `px.send()`.
|
|
@@ -35,34 +35,108 @@ The name must match `^[a-z0-9][a-z0-9_-]{1,62}$`. `setup.sh` refuses to run with
|
|
|
35
35
|
|
|
36
36
|
In normal code, you usually just pass `source_id=...` explicitly to `Plexus(...)` and never have to think about it.
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## Core methods
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
### `send(metric, value)` — stream a reading
|
|
41
|
+
|
|
42
|
+
The main method. Call it every time you have a new sensor reading.
|
|
42
43
|
|
|
44
|
+
```python
|
|
43
45
|
px = Plexus(source_id="rig-01") # reads PLEXUS_API_KEY from env
|
|
44
46
|
|
|
45
|
-
# Numbers
|
|
46
47
|
px.send("engine.rpm", 3450)
|
|
47
|
-
px.send("coolant.
|
|
48
|
+
px.send("coolant.temp", 82.3)
|
|
49
|
+
```
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
px.send("vehicle.state", "RUNNING")
|
|
51
|
-
px.send("motor.enabled", True)
|
|
52
|
-
px.send("position", {"x": 1.5, "y": 2.3, "z": 0.8})
|
|
51
|
+
`metric` is a dot-namespaced string (`"motor.rpm"`, `"gps.fix_quality"`). `value` accepts any JSON-serializable type:
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
| Type | Example | When to use |
|
|
54
|
+
|------|---------|-------------|
|
|
55
|
+
| `float` / `int` | `72.5`, `3450` | Sensor readings, counters |
|
|
56
|
+
| `str` | `"RUNNING"`, `"E_STALL"` | State machines, error codes |
|
|
57
|
+
| `bool` | `True` | Binary flags |
|
|
58
|
+
| `dict` | `{"x": 1.5, "y": 2.3}` | Vectors, structured readings |
|
|
59
|
+
| `list` | `[0.5, 1.2, -0.3]` | Waveforms, joint angles |
|
|
60
|
+
|
|
61
|
+
Optional arguments:
|
|
62
|
+
- `tags={"motor_id": "A1"}` — key-value labels for filtering in the dashboard
|
|
63
|
+
- `timestamp=t` — explicit Unix timestamp in seconds; omit to let the SDK pick (see [Timestamps](#timestamps-and-clock-correction))
|
|
64
|
+
|
|
65
|
+
### `send_batch(points)` — send multiple readings at once
|
|
66
|
+
|
|
67
|
+
Use this when you sample several sensors together and want them to share a timestamp and land in one network call.
|
|
68
|
+
|
|
69
|
+
```python
|
|
55
70
|
px.send_batch([
|
|
56
|
-
("temperature",
|
|
57
|
-
("
|
|
71
|
+
("temperature", 22.4),
|
|
72
|
+
("humidity", 58.1),
|
|
73
|
+
("pressure", 1013.2),
|
|
58
74
|
])
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`points` is a list of `(metric, value)` tuples. All points share the same timestamp (now, unless you pass `timestamp=t`). For independent timestamps per point, call `send()` in a loop instead.
|
|
78
|
+
|
|
79
|
+
### `event(name, data)` — record a discrete occurrence
|
|
80
|
+
|
|
81
|
+
Use `event()` for things that *happen* rather than things you *measure continuously*. Faults, state transitions, operator actions, log entries — anything you'd put on a timeline as a marker rather than plot as a graph.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
px.event("fault", "E-stop triggered")
|
|
85
|
+
px.event("state_change", {"from": "IDLE", "to": "RUNNING"})
|
|
86
|
+
px.event("sensor_error", {"sensor": "imu", "code": 42}, tags={"motor": "A"})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The platform displays events as markers overlaid on your telemetry charts, not as time-series lines.
|
|
90
|
+
|
|
91
|
+
### `run(run_id)` — group data into a named recording
|
|
59
92
|
|
|
60
|
-
|
|
93
|
+
```python
|
|
61
94
|
with px.run("thermal-cycle-001"):
|
|
62
95
|
while running:
|
|
63
96
|
px.send("temperature", read_temp())
|
|
64
97
|
```
|
|
65
98
|
|
|
99
|
+
All `send()` calls inside the context are tagged with `run_id`, making it easy to isolate and replay that slice of data in the dashboard.
|
|
100
|
+
|
|
101
|
+
## Video streaming
|
|
102
|
+
|
|
103
|
+
Two methods depending on whether you control the capture loop or just have a URL.
|
|
104
|
+
|
|
105
|
+
### `send_video_frame(frame, camera_id)` — send frames you capture yourself
|
|
106
|
+
|
|
107
|
+
Use this when your code owns the capture loop — a `picamera2` callback, an OpenCV `VideoCapture` loop, or an FFmpeg pipe you manage. Pass each frame and the SDK ships it to Plexus over WebSocket.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import cv2
|
|
111
|
+
|
|
112
|
+
cap = cv2.VideoCapture(0)
|
|
113
|
+
while True:
|
|
114
|
+
ok, frame = cap.read()
|
|
115
|
+
if ok:
|
|
116
|
+
px.send_video_frame(frame, camera_id="front")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Accepted frame types:
|
|
120
|
+
- **numpy ndarray** (H × W × C) — from OpenCV or picamera2; requires `opencv-python`
|
|
121
|
+
- **JPEG bytes** — passed through as-is, zero re-encode overhead
|
|
122
|
+
- **Other image bytes** (PNG, BMP, WebP) — decoded and re-encoded as JPEG via Pillow; requires `pip install plexus-python[video]`
|
|
123
|
+
|
|
124
|
+
`camera_id` identifies which camera the frame came from. Use distinct IDs when streaming from multiple cameras simultaneously (`"front"`, `"rear"`, `"cam:0"`).
|
|
125
|
+
|
|
126
|
+
### `stream_camera(url, camera_id)` — stream from an RTSP URL or file
|
|
127
|
+
|
|
128
|
+
Use this when you have an RTSP stream or video file and don't want to manage the capture loop yourself. The SDK runs FFmpeg internally and handles the rest. Requires FFmpeg on `$PATH`.
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
stop = px.stream_camera("rtsp://192.168.1.100/stream", camera_id="front")
|
|
132
|
+
# ... do other work ...
|
|
133
|
+
stop.set() # stop streaming
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Returns a `threading.Event` — call `.set()` to stop. Runs in a background thread so it doesn't block your main loop.
|
|
137
|
+
|
|
138
|
+
**Which to use:** if you're piping from `rpicam-vid`, `picamera2`, or your own capture process, use `send_video_frame()`. If you have an RTSP URL or file path, use `stream_camera()`.
|
|
139
|
+
|
|
66
140
|
## Bring Your Own Protocol
|
|
67
141
|
|
|
68
142
|
This package ships no adapters, auto-detection, or daemons — just the client. Use whatever library you'd use anyway and pipe values into `px.send()`.
|
|
@@ -674,11 +674,11 @@ wheels = [
|
|
|
674
674
|
|
|
675
675
|
[[package]]
|
|
676
676
|
name = "urllib3"
|
|
677
|
-
version = "2.
|
|
677
|
+
version = "2.7.0"
|
|
678
678
|
source = { registry = "https://pypi.org/simple" }
|
|
679
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
679
|
+
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
|
680
680
|
wheels = [
|
|
681
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
681
|
+
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
|
682
682
|
]
|
|
683
683
|
|
|
684
684
|
[[package]]
|
|
@@ -10,5 +10,5 @@ Plexus — thin Python SDK for sending telemetry to the Plexus gateway.
|
|
|
10
10
|
from plexus.client import Plexus, read_mjpeg_frames
|
|
11
11
|
from plexus.ws import WebSocketTransport
|
|
12
12
|
|
|
13
|
-
__version__ = "0.
|
|
13
|
+
__version__ = "0.5.2"
|
|
14
14
|
__all__ = ["Plexus", "WebSocketTransport", "read_mjpeg_frames"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
_QUIET = os.environ.get("PLEXUS_QUIET", "").lower() in ("1", "true", "yes")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _say(line: str) -> None:
|
|
8
|
+
"""Single-line status message to stderr. Skipped if PLEXUS_QUIET=1."""
|
|
9
|
+
if _QUIET:
|
|
10
|
+
return
|
|
11
|
+
try:
|
|
12
|
+
sys.stderr.write(f"[plexus] {line}\n")
|
|
13
|
+
sys.stderr.flush()
|
|
14
|
+
except Exception:
|
|
15
|
+
pass
|