plexus-python 0.4.6__tar.gz → 0.4.8__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.6 → plexus_python-0.4.8}/CHANGELOG.md +47 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/PKG-INFO +3 -1
- plexus_python-0.4.8/examples/.python-version +1 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/examples/README.md +1 -0
- plexus_python-0.4.8/examples/mac_metrics.py +81 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/examples/mqtt.py +1 -1
- plexus_python-0.4.8/examples/pyproject.toml +15 -0
- plexus_python-0.4.8/examples/uv.lock +740 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/plexus/__init__.py +3 -3
- {plexus_python-0.4.6 → plexus_python-0.4.8}/plexus/client.py +262 -1
- {plexus_python-0.4.6 → plexus_python-0.4.8}/plexus/ws.py +0 -1
- {plexus_python-0.4.6 → plexus_python-0.4.8}/pyproject.toml +2 -1
- plexus_python-0.4.8/tests/test_video.py +208 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/uv.lock +315 -2
- {plexus_python-0.4.6 → plexus_python-0.4.8}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/.github/workflows/ci.yml +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/.github/workflows/publish.yml +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/.gitignore +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/AGENTS.md +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/API.md +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/CODE_OF_CONDUCT.md +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/CONTRIBUTING.md +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/LICENSE +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/README.md +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/SECURITY.md +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/examples/basic.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/examples/can.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/examples/i2c_bme280.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/examples/mavlink.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/plexus/buffer.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/plexus/cli.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/plexus/config.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/scripts/plexus.service +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/scripts/scan_buses.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/scripts/setup.sh +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/tests/test_basic.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/tests/test_buffer.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/tests/test_config.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/tests/test_retry.py +0 -0
- {plexus_python-0.4.6 → plexus_python-0.4.8}/tests/test_ws.py +0 -0
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.8] - 2026-05-19 - Video input broadening and wire safety
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `send_video_frame` now accepts raw bytes/bytearray: JPEG bytes are passed
|
|
8
|
+
through without re-encoding (zero CPU cost on hardware that outputs JPEG
|
|
9
|
+
natively); other formats (PNG, BMP, WebP) are decoded via Pillow and
|
|
10
|
+
re-encoded as JPEG. Install `plexus-python[video]` for Pillow support.
|
|
11
|
+
- `stream_camera(url, camera_id, fps, quality)` — streams from any
|
|
12
|
+
FFmpeg-supported source (RTSP, video file, capture device). Requires FFmpeg
|
|
13
|
+
on `$PATH`. Returns a `threading.Event`; call `.set()` to stop.
|
|
14
|
+
- `read_mjpeg_frames(pipe)` — public generator that parses raw MJPEG byte
|
|
15
|
+
streams (e.g. FFmpeg stdout) into individual JPEG frames by SOI/EOI markers.
|
|
16
|
+
Useful for custom FFmpeg pipelines before handing off to `send_video_frame`.
|
|
17
|
+
- Optional `video` extras group: `pip install plexus-python[video]` installs
|
|
18
|
+
Pillow for non-JPEG input decoding and automatic oversized-frame downsampling.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Frames that would exceed the gateway's 1 MB wire limit are automatically
|
|
23
|
+
re-encoded at a proportionally lower quality. A one-time warning is printed
|
|
24
|
+
to stderr; subsequent frames are silently clamped.
|
|
25
|
+
- `stream_camera` raises `PlexusError` synchronously (before spawning a thread)
|
|
26
|
+
when FFmpeg is not found, rather than silently dying in the background.
|
|
27
|
+
|
|
28
|
+
## [0.4.7] - 2026-05-14 - Video streaming API
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- `Plexus.send_video_frame(frame, camera_id, quality, timestamp)` — high-level
|
|
33
|
+
API for streaming camera frames. Accepts a numpy array (e.g. from
|
|
34
|
+
`cv2.VideoCapture.read()`), handles JPEG encoding, base64, dimensions, and
|
|
35
|
+
auth wait internally. Requires `transport="ws"` and `opencv-python`.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- Gateway WebSocket URL (`wss://plexus-gateway.fly.dev`) is now the SDK
|
|
40
|
+
default — no need to pass `ws_url` explicitly.
|
|
41
|
+
- Removed the `[plexus] endpoint: …` line from the connection printout.
|
|
42
|
+
|
|
43
|
+
### Performance
|
|
44
|
+
|
|
45
|
+
- Eliminated per-frame `buf.tobytes()` copy in `send_video_frame` by passing
|
|
46
|
+
the numpy buffer directly to `base64.b64encode` (buffer protocol).
|
|
47
|
+
- `base64` imported at module level; `cv2` imported once on first call and
|
|
48
|
+
cached, removing repeated import overhead from the hot path.
|
|
49
|
+
|
|
3
50
|
## [0.4.5] - 2026-04-27 - Stderr status output (re-release of 0.4.4)
|
|
4
51
|
|
|
5
52
|
Same code as 0.4.4 — the 0.4.4 publish workflow failed lint on a stray
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plexus-python
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.8
|
|
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
|
|
@@ -30,6 +30,8 @@ Requires-Dist: pytest; extra == 'dev'
|
|
|
30
30
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
31
31
|
Requires-Dist: ruff; extra == 'dev'
|
|
32
32
|
Requires-Dist: websockets>=12; extra == 'dev'
|
|
33
|
+
Provides-Extra: video
|
|
34
|
+
Requires-Dist: pillow>=9.0; extra == 'video'
|
|
33
35
|
Description-Content-Type: text/markdown
|
|
34
36
|
|
|
35
37
|
# plexus-python
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -9,5 +9,6 @@ Each script is standalone — copy into your project, adjust the `source_id`, an
|
|
|
9
9
|
| `can.py` | Vehicle CAN bus (with optional DBC decode) | `python-can`, `cantools` |
|
|
10
10
|
| `mqtt.py` | MQTT broker → Plexus bridge | `paho-mqtt` |
|
|
11
11
|
| `i2c_bme280.py` | Raspberry Pi environmental sensor | `adafruit-circuitpython-bme280` |
|
|
12
|
+
| `mac_metrics.py` | Mac system metrics + spike/pressure events | `psutil` |
|
|
12
13
|
|
|
13
14
|
The pattern is always the same: use whatever library you'd use anyway, then call `px.send(metric, value)`. Plexus stays out of your decode path.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mac system metrics with structured event logs.
|
|
3
|
+
|
|
4
|
+
Install deps:
|
|
5
|
+
pip install plexus-python psutil
|
|
6
|
+
|
|
7
|
+
Run:
|
|
8
|
+
python examples/mac_metrics.py --api-key plx_xxx
|
|
9
|
+
python examples/mac_metrics.py --api-key plx_xxx --interval 10
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import time
|
|
14
|
+
import psutil
|
|
15
|
+
from plexus import Plexus
|
|
16
|
+
|
|
17
|
+
parser = argparse.ArgumentParser(description="Stream Mac system metrics to Plexus.")
|
|
18
|
+
parser.add_argument("--api-key", required=True, help="Plexus API key (plx_xxx)")
|
|
19
|
+
parser.add_argument("--interval", type=float, default=5.0, metavar="SECONDS",
|
|
20
|
+
help="Sampling interval in seconds (default: 5)")
|
|
21
|
+
parser.add_argument("--source-id", default="macbook", help="Device source ID (default: macbook)")
|
|
22
|
+
args = parser.parse_args()
|
|
23
|
+
|
|
24
|
+
px = Plexus(api_key=args.api_key, source_id=args.source_id)
|
|
25
|
+
|
|
26
|
+
CPU_SPIKE_THRESHOLD = 80.0 # %
|
|
27
|
+
MEM_PRESSURE_THRESHOLD = 85.0 # %
|
|
28
|
+
|
|
29
|
+
prev_net = psutil.net_io_counters()
|
|
30
|
+
prev_disk = psutil.disk_io_counters()
|
|
31
|
+
prev_charging = None
|
|
32
|
+
|
|
33
|
+
while True:
|
|
34
|
+
net = psutil.net_io_counters()
|
|
35
|
+
disk = psutil.disk_io_counters()
|
|
36
|
+
|
|
37
|
+
cpu = psutil.cpu_percent(interval=None)
|
|
38
|
+
mem = psutil.virtual_memory()
|
|
39
|
+
|
|
40
|
+
# Numeric metrics
|
|
41
|
+
px.send("cpu.percent", cpu)
|
|
42
|
+
px.send("memory.used_gb", mem.used / 1e9)
|
|
43
|
+
px.send("memory.percent", mem.percent)
|
|
44
|
+
px.send("net.rx_mbps", (net.bytes_recv - prev_net.bytes_recv) / 1e6)
|
|
45
|
+
px.send("net.tx_mbps", (net.bytes_sent - prev_net.bytes_sent) / 1e6)
|
|
46
|
+
px.send("disk.read_mbps", (disk.read_bytes - prev_disk.read_bytes) / 1e6)
|
|
47
|
+
px.send("disk.write_mbps", (disk.write_bytes - prev_disk.write_bytes) / 1e6)
|
|
48
|
+
px.send("disk.free_gb", psutil.disk_usage("/").free / 1e9)
|
|
49
|
+
|
|
50
|
+
# Battery metrics + charge-state change event
|
|
51
|
+
battery = psutil.sensors_battery()
|
|
52
|
+
if battery:
|
|
53
|
+
px.send("battery.percent", battery.percent)
|
|
54
|
+
charging = battery.power_plugged
|
|
55
|
+
if charging != prev_charging and prev_charging is not None:
|
|
56
|
+
px.event("battery.state_change", {
|
|
57
|
+
"charging": charging,
|
|
58
|
+
"percent": battery.percent,
|
|
59
|
+
})
|
|
60
|
+
prev_charging = charging
|
|
61
|
+
|
|
62
|
+
# CPU spike event — fires once per interval when threshold is crossed
|
|
63
|
+
if cpu >= CPU_SPIKE_THRESHOLD:
|
|
64
|
+
top = max(psutil.process_iter(["name", "cpu_percent"]), key=lambda p: p.info["cpu_percent"] or 0)
|
|
65
|
+
px.event("cpu.spike", {
|
|
66
|
+
"cpu_percent": cpu,
|
|
67
|
+
"top_process": top.info["name"],
|
|
68
|
+
"top_process_percent": top.info["cpu_percent"],
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
# Memory pressure event
|
|
72
|
+
if mem.percent >= MEM_PRESSURE_THRESHOLD:
|
|
73
|
+
top = max(psutil.process_iter(["name", "memory_percent"]), key=lambda p: p.info["memory_percent"] or 0)
|
|
74
|
+
px.event("memory.pressure", {
|
|
75
|
+
"memory_percent": mem.percent,
|
|
76
|
+
"top_process": top.info["name"],
|
|
77
|
+
"top_process_percent": round(top.info["memory_percent"], 1),
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
prev_net, prev_disk = net, disk
|
|
81
|
+
time.sleep(args.interval)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "examples"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Standalone example scripts for plexus-python"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"adafruit-circuitpython-bme280>=2.6.32",
|
|
9
|
+
"cantools>=41.3.1",
|
|
10
|
+
"paho-mqtt>=2.1.0",
|
|
11
|
+
"plexus-python>=0.4.6",
|
|
12
|
+
"psutil>=7.2.2",
|
|
13
|
+
"pymavlink>=2.4.49",
|
|
14
|
+
"python-can>=4.6.1",
|
|
15
|
+
]
|