plexus-python 0.1.0__tar.gz → 0.2.0__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.2.0/AGENTS.md +72 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/API.md +26 -57
- plexus_python-0.2.0/CHANGELOG.md +32 -0
- plexus_python-0.2.0/PKG-INFO +140 -0
- plexus_python-0.2.0/README.md +108 -0
- plexus_python-0.2.0/examples/README.md +13 -0
- plexus_python-0.2.0/examples/basic.py +19 -0
- plexus_python-0.2.0/examples/can.py +40 -0
- plexus_python-0.2.0/examples/i2c_bme280.py +30 -0
- plexus_python-0.2.0/examples/mavlink.py +39 -0
- plexus_python-0.2.0/examples/mqtt.py +48 -0
- plexus_python-0.2.0/plexus/__init__.py +13 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/plexus/client.py +4 -4
- {plexus_python-0.1.0 → plexus_python-0.2.0}/plexus/config.py +11 -29
- plexus_python-0.2.0/pyproject.toml +48 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/scripts/setup.sh +1 -15
- plexus_python-0.1.0/AGENTS.md +0 -79
- plexus_python-0.1.0/CHANGELOG.md +0 -9
- plexus_python-0.1.0/PKG-INFO +0 -470
- plexus_python-0.1.0/README.md +0 -388
- plexus_python-0.1.0/examples/can_basic.py +0 -173
- plexus_python-0.1.0/examples/demo_field_unit.py +0 -254
- plexus_python-0.1.0/examples/demo_ground_station.py +0 -135
- plexus_python-0.1.0/examples/gateway_ble_relay.py +0 -97
- plexus_python-0.1.0/examples/mavlink_basic.py +0 -157
- plexus_python-0.1.0/plexus/__init__.py +0 -31
- plexus_python-0.1.0/plexus/__main__.py +0 -4
- plexus_python-0.1.0/plexus/adapters/__init__.py +0 -122
- plexus_python-0.1.0/plexus/adapters/base.py +0 -409
- plexus_python-0.1.0/plexus/adapters/ble.py +0 -257
- plexus_python-0.1.0/plexus/adapters/can.py +0 -439
- plexus_python-0.1.0/plexus/adapters/can_detect.py +0 -174
- plexus_python-0.1.0/plexus/adapters/mavlink.py +0 -642
- plexus_python-0.1.0/plexus/adapters/mavlink_detect.py +0 -192
- plexus_python-0.1.0/plexus/adapters/modbus.py +0 -622
- plexus_python-0.1.0/plexus/adapters/mqtt.py +0 -350
- plexus_python-0.1.0/plexus/adapters/opcua.py +0 -607
- plexus_python-0.1.0/plexus/adapters/registry.py +0 -206
- plexus_python-0.1.0/plexus/adapters/serial_adapter.py +0 -547
- plexus_python-0.1.0/plexus/cameras/__init__.py +0 -57
- plexus_python-0.1.0/plexus/cameras/auto.py +0 -239
- plexus_python-0.1.0/plexus/cameras/base.py +0 -189
- plexus_python-0.1.0/plexus/cameras/picamera.py +0 -171
- plexus_python-0.1.0/plexus/cameras/usb.py +0 -143
- plexus_python-0.1.0/plexus/cli.py +0 -783
- plexus_python-0.1.0/plexus/connector.py +0 -666
- plexus_python-0.1.0/plexus/deps.py +0 -246
- plexus_python-0.1.0/plexus/detect.py +0 -1238
- plexus_python-0.1.0/plexus/importers/__init__.py +0 -25
- plexus_python-0.1.0/plexus/importers/rosbag.py +0 -778
- plexus_python-0.1.0/plexus/sensors/__init__.py +0 -118
- plexus_python-0.1.0/plexus/sensors/ads1115.py +0 -164
- plexus_python-0.1.0/plexus/sensors/adxl345.py +0 -179
- plexus_python-0.1.0/plexus/sensors/auto.py +0 -290
- plexus_python-0.1.0/plexus/sensors/base.py +0 -412
- plexus_python-0.1.0/plexus/sensors/bh1750.py +0 -102
- plexus_python-0.1.0/plexus/sensors/bme280.py +0 -241
- plexus_python-0.1.0/plexus/sensors/gps.py +0 -317
- plexus_python-0.1.0/plexus/sensors/ina219.py +0 -149
- plexus_python-0.1.0/plexus/sensors/magnetometer.py +0 -239
- plexus_python-0.1.0/plexus/sensors/mpu6050.py +0 -162
- plexus_python-0.1.0/plexus/sensors/sht3x.py +0 -139
- plexus_python-0.1.0/plexus/sensors/spi_scan.py +0 -164
- plexus_python-0.1.0/plexus/sensors/system.py +0 -261
- plexus_python-0.1.0/plexus/sensors/vl53l0x.py +0 -109
- plexus_python-0.1.0/plexus/streaming.py +0 -743
- plexus_python-0.1.0/plexus/tui.py +0 -642
- plexus_python-0.1.0/pyproject.toml +0 -114
- plexus_python-0.1.0/tests/test_can_adapter.py +0 -332
- plexus_python-0.1.0/tests/test_connector.py +0 -30
- plexus_python-0.1.0/tests/test_gps.py +0 -147
- plexus_python-0.1.0/tests/test_mavlink_adapter.py +0 -711
- plexus_python-0.1.0/tests/test_modbus_adapter.py +0 -412
- plexus_python-0.1.0/tests/test_mqtt_adapter.py +0 -288
- plexus_python-0.1.0/tests/test_sensor_hub.py +0 -208
- plexus_python-0.1.0/tests/test_serial_adapter.py +0 -305
- {plexus_python-0.1.0 → plexus_python-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/.github/workflows/ci.yml +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/.github/workflows/publish.yml +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/.gitignore +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/CODE_OF_CONDUCT.md +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/CONTRIBUTING.md +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/LICENSE +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/SECURITY.md +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/plexus/buffer.py +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/scripts/plexus.service +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/scripts/scan_buses.py +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/tests/test_basic.py +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/tests/test_buffer.py +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/tests/test_config.py +0 -0
- {plexus_python-0.1.0 → plexus_python-0.2.0}/tests/test_retry.py +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# AGENTS.md — plexus-python
|
|
2
|
+
|
|
3
|
+
Machine-readable interface for AI assistants and automation scripts.
|
|
4
|
+
|
|
5
|
+
## Environment Variables
|
|
6
|
+
|
|
7
|
+
| Variable | Description | Default |
|
|
8
|
+
| ----------------------- | -------------------------------------------- | -------------------------------- |
|
|
9
|
+
| `PLEXUS_API_KEY` | API key for authentication (required) | none |
|
|
10
|
+
| `PLEXUS_GATEWAY_URL` | Gateway HTTP ingest URL | `https://plexus-gateway.fly.dev` |
|
|
11
|
+
| `PLEXUS_GATEWAY_WS_URL` | Gateway WebSocket URL | `wss://plexus-gateway.fly.dev` |
|
|
12
|
+
|
|
13
|
+
## CLI Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
plexus start # Stream with PLEXUS_API_KEY env var
|
|
17
|
+
plexus start --key plx_xxxxx # Pass key inline
|
|
18
|
+
plexus start --device-id my-device # Set device identifier
|
|
19
|
+
plexus reset # Clear config
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
An API key is required — set `PLEXUS_API_KEY` or pass `--key`. There is no interactive signup; get a key at app.plexus.company/devices.
|
|
23
|
+
|
|
24
|
+
## Exit Codes
|
|
25
|
+
|
|
26
|
+
| Code | Meaning |
|
|
27
|
+
| ----- | ------------------------------------- |
|
|
28
|
+
| `0` | Clean shutdown |
|
|
29
|
+
| `1` | Configuration or authentication error |
|
|
30
|
+
| `130` | Interrupted (SIGINT / Ctrl+C) |
|
|
31
|
+
|
|
32
|
+
## Python SDK
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from plexus import Plexus
|
|
36
|
+
|
|
37
|
+
px = Plexus(api_key="plx_xxxxx", source_id="device-001")
|
|
38
|
+
px.send("temperature", 72.5)
|
|
39
|
+
px.send("pressure", 1013.25, tags={"unit": "hPa"})
|
|
40
|
+
|
|
41
|
+
# Batch
|
|
42
|
+
px.send_batch([
|
|
43
|
+
("temperature", 72.5),
|
|
44
|
+
("pressure", 1013.25),
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
# Persistent buffering for reliability
|
|
48
|
+
px = Plexus(api_key="plx_xxxxx", persistent_buffer=True)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Project Structure
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
plexus/
|
|
55
|
+
├── cli.py # CLI entry point (click commands)
|
|
56
|
+
├── tui.py # Live terminal dashboard (Rich)
|
|
57
|
+
├── client.py # Plexus HTTP client (thin SDK)
|
|
58
|
+
├── connector.py # WebSocket daemon
|
|
59
|
+
├── config.py # Config file + env var management
|
|
60
|
+
├── detect.py # Hardware auto-detection
|
|
61
|
+
├── deps.py # Dependency helpers
|
|
62
|
+
├── sensors/ # I2C sensor drivers + SensorHub
|
|
63
|
+
└── adapters/ # Protocol adapters: CAN, MAVLink, MQTT, Modbus, OPC-UA, BLE, Serial
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Key Conventions
|
|
67
|
+
|
|
68
|
+
- Config lives in `~/.plexus/config.json`
|
|
69
|
+
- API keys are prefixed with `plx_`
|
|
70
|
+
- Source IDs (device slugs) namespace metrics
|
|
71
|
+
- HTTP ingest → `POST /ingest` on gateway; WebSocket → `/ws/device` for streaming + commands
|
|
72
|
+
- Gateway resolves `org_id` server-side from the API key — clients do not supply it
|
|
@@ -30,7 +30,7 @@ Then control streaming, recording, and configuration from [app.plexus.company/de
|
|
|
30
30
|
Send data directly via HTTP:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
curl -X POST https://
|
|
33
|
+
curl -X POST https://plexus-gateway.fly.dev/ingest \
|
|
34
34
|
-H "x-api-key: YOUR_API_KEY" \
|
|
35
35
|
-H "Content-Type: application/json" \
|
|
36
36
|
-d '{
|
|
@@ -280,7 +280,7 @@ import requests
|
|
|
280
280
|
import time
|
|
281
281
|
|
|
282
282
|
requests.post(
|
|
283
|
-
"https://
|
|
283
|
+
"https://plexus-gateway.fly.dev/ingest",
|
|
284
284
|
headers={"x-api-key": "plx_xxxxx"},
|
|
285
285
|
json={
|
|
286
286
|
"points": [{
|
|
@@ -296,7 +296,7 @@ requests.post(
|
|
|
296
296
|
### JavaScript
|
|
297
297
|
|
|
298
298
|
```javascript
|
|
299
|
-
await fetch("https://
|
|
299
|
+
await fetch("https://plexus-gateway.fly.dev/ingest", {
|
|
300
300
|
method: "POST",
|
|
301
301
|
headers: {
|
|
302
302
|
"x-api-key": "plx_xxxxx",
|
|
@@ -338,7 +338,7 @@ func main() {
|
|
|
338
338
|
}
|
|
339
339
|
|
|
340
340
|
body, _ := json.Marshal(points)
|
|
341
|
-
req, _ := http.NewRequest("POST", "https://
|
|
341
|
+
req, _ := http.NewRequest("POST", "https://plexus-gateway.fly.dev/ingest", bytes.NewBuffer(body))
|
|
342
342
|
req.Header.Set("x-api-key", "plx_xxxxx")
|
|
343
343
|
req.Header.Set("Content-Type", "application/json")
|
|
344
344
|
|
|
@@ -354,7 +354,7 @@ func main() {
|
|
|
354
354
|
|
|
355
355
|
void sendToPlexus(const char* metric, float value) {
|
|
356
356
|
HTTPClient http;
|
|
357
|
-
http.begin("https://
|
|
357
|
+
http.begin("https://plexus-gateway.fly.dev/ingest");
|
|
358
358
|
http.addHeader("Content-Type", "application/json");
|
|
359
359
|
http.addHeader("x-api-key", "plx_xxxxx");
|
|
360
360
|
|
|
@@ -377,7 +377,7 @@ void sendToPlexus(const char* metric, float value) {
|
|
|
377
377
|
API_KEY="plx_xxxxx"
|
|
378
378
|
SOURCE_ID="sensor-001"
|
|
379
379
|
|
|
380
|
-
curl -X POST https://
|
|
380
|
+
curl -X POST https://plexus-gateway.fly.dev/ingest \
|
|
381
381
|
-H "x-api-key: $API_KEY" \
|
|
382
382
|
-H "Content-Type: application/json" \
|
|
383
383
|
-d "{
|
|
@@ -390,69 +390,38 @@ curl -X POST https://app.plexus.company/api/ingest \
|
|
|
390
390
|
}"
|
|
391
391
|
```
|
|
392
392
|
|
|
393
|
-
## Protocol
|
|
393
|
+
## Bring Your Own Protocol
|
|
394
394
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
### CAN Bus Adapter
|
|
398
|
-
|
|
399
|
-
Read CAN bus data with optional DBC signal decoding:
|
|
400
|
-
|
|
401
|
-
```bash
|
|
402
|
-
pip install plexus-python[can]
|
|
403
|
-
```
|
|
395
|
+
For MAVLink, CAN, MQTT, Modbus, OPC-UA, BLE, Serial, or any other protocol, use the upstream Python library directly and pass values to `px.send()`. Plexus stays out of your decode path:
|
|
404
396
|
|
|
405
397
|
```python
|
|
406
|
-
|
|
398
|
+
# CAN example — using python-can directly
|
|
399
|
+
import can
|
|
407
400
|
from plexus import Plexus
|
|
408
401
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
interface="socketcan",
|
|
412
|
-
channel="can0",
|
|
413
|
-
dbc_path="vehicle.dbc", # Optional: decode signals
|
|
414
|
-
)
|
|
402
|
+
px = Plexus(api_key="plx_xxx", source_id="vehicle-001")
|
|
403
|
+
bus = can.interface.Bus(channel="can0", bustype="socketcan")
|
|
415
404
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
for metric in adapter.poll():
|
|
419
|
-
# Raw: can.raw.0x123 = "DEADBEEF"
|
|
420
|
-
# Decoded: engine_rpm = 2500
|
|
421
|
-
plexus.send(metric.name, metric.value, tags=metric.tags)
|
|
405
|
+
for msg in bus:
|
|
406
|
+
px.send(f"can.0x{msg.arbitration_id:x}", int.from_bytes(msg.data, "big"))
|
|
422
407
|
```
|
|
423
408
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
sudo ip link add dev vcan0 type vcan
|
|
429
|
-
sudo ip link set up vcan0
|
|
430
|
-
|
|
431
|
-
# Send test frames
|
|
432
|
-
cansend vcan0 123#DEADBEEF
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
**Supported interfaces:** socketcan, pcan, vector, kvaser, slcan, virtual
|
|
436
|
-
|
|
437
|
-
### MQTT Adapter
|
|
409
|
+
```python
|
|
410
|
+
# MAVLink example — using pymavlink directly
|
|
411
|
+
from pymavlink import mavutil
|
|
412
|
+
from plexus import Plexus
|
|
438
413
|
|
|
439
|
-
|
|
414
|
+
px = Plexus(api_key="plx_xxx", source_id="drone-001")
|
|
415
|
+
conn = mavutil.mavlink_connection("udpin:0.0.0.0:14550")
|
|
440
416
|
|
|
441
|
-
|
|
442
|
-
|
|
417
|
+
while True:
|
|
418
|
+
msg = conn.recv_match(blocking=True)
|
|
419
|
+
if msg.get_type() == "ATTITUDE":
|
|
420
|
+
px.send("attitude.roll", msg.roll)
|
|
421
|
+
px.send("attitude.pitch", msg.pitch)
|
|
443
422
|
```
|
|
444
423
|
|
|
445
|
-
|
|
446
|
-
from plexus.adapters import MQTTAdapter
|
|
447
|
-
|
|
448
|
-
adapter = MQTTAdapter(
|
|
449
|
-
broker="localhost",
|
|
450
|
-
topic="sensors/#",
|
|
451
|
-
port=1883,
|
|
452
|
-
)
|
|
453
|
-
adapter.connect()
|
|
454
|
-
adapter.run(on_data=my_callback)
|
|
455
|
-
```
|
|
424
|
+
See [docs.plexus.dev/recipes](https://docs.plexus.dev/recipes) for more.
|
|
456
425
|
|
|
457
426
|
## Python SDK with Sensor Drivers
|
|
458
427
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0] - Thin SDK rewrite
|
|
4
|
+
|
|
5
|
+
Breaking. `plexus-python` is now just the thin client — no agent, adapters, sensors, CLI, or TUI. The package is 886 lines with one runtime dependency (`requests`). Protocol integrations (MAVLink, CAN, MQTT, Modbus, OPC-UA, BLE, I2C sensors) now live as standalone recipes in `examples/`, using the upstream library directly (`pymavlink`, `python-can`, `paho-mqtt`, etc.) plus `px.send()`.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- 5 runnable example scripts: `basic.py`, `mavlink.py`, `can.py`, `mqtt.py`, `i2c_bme280.py`
|
|
10
|
+
|
|
11
|
+
### Removed
|
|
12
|
+
|
|
13
|
+
- `plexus/adapters/` (MAVLink, CAN, MQTT, Modbus, OPC-UA, BLE, Serial — use the upstream lib directly)
|
|
14
|
+
- `plexus/sensors/` (I2C drivers + auto-detect — use Adafruit CircuitPython or smbus2 directly)
|
|
15
|
+
- `plexus/cameras/` (frame upload — out of scope)
|
|
16
|
+
- `plexus/cli.py`, `plexus/connector.py`, `plexus/streaming.py`, `plexus/detect.py`, `plexus/tui.py`, `plexus/deps.py`
|
|
17
|
+
- `plexus` console script, `python -m plexus`
|
|
18
|
+
- Extras: `[sensors]`, `[system]`, `[tui]`, `[mqtt]`, `[can]`, `[mavlink]`, `[modbus]`, `[opcua]`, `[ble]`, `[serial]`, `[ros]`, `[camera]`, `[picamera]`, `[all]`
|
|
19
|
+
- Runtime deps: `click`, `websockets`
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Default ingest endpoint points directly at the Plexus gateway (`https://plexus-gateway.fly.dev/ingest`), not the Next.js app proxy
|
|
24
|
+
- Client raises `ValueError` clearly when no API key is available, instead of invoking a login flow
|
|
25
|
+
|
|
26
|
+
## [0.1.0] - Initial release
|
|
27
|
+
|
|
28
|
+
- `Plexus` thin client for HTTP ingest
|
|
29
|
+
- `plexus start` daemon with WebSocket streaming
|
|
30
|
+
- Protocol adapters: MAVLink, CAN, MQTT, Modbus, OPC-UA, Serial, BLE
|
|
31
|
+
- I2C sensor auto-detection and drivers
|
|
32
|
+
- Store-and-forward buffering (SQLite)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plexus-python
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Thin Python SDK for Plexus — send telemetry in one line
|
|
5
|
+
Project-URL: Homepage, https://plexus.dev
|
|
6
|
+
Project-URL: Documentation, https://docs.plexus.dev
|
|
7
|
+
Project-URL: Repository, https://github.com/plexus-oss/plexus-python
|
|
8
|
+
Project-URL: Issues, https://github.com/plexus-oss/plexus-python/issues
|
|
9
|
+
Author-email: Plexus <hello@plexus.dev>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: fleet,hardware,iot,monitoring,observability,telemetry
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering
|
|
24
|
+
Classifier: Topic :: System :: Hardware
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Requires-Dist: requests>=2.28.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# plexus-python
|
|
34
|
+
|
|
35
|
+
**Thin Python SDK for [Plexus](https://plexus.company).** Send telemetry to the Plexus gateway in one line. Storage, dashboards, alerts, and fleet management live in the platform — this package just ships your data.
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/plexus-python/)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install plexus-python
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from plexus import Plexus
|
|
48
|
+
|
|
49
|
+
px = Plexus(api_key="plx_xxx", source_id="device-001")
|
|
50
|
+
px.send("temperature", 72.5)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Get an API key at [app.plexus.company](https://app.plexus.company) → Devices → Add Device.
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from plexus import Plexus
|
|
59
|
+
|
|
60
|
+
px = Plexus(source_id="rig-01") # reads PLEXUS_API_KEY from env
|
|
61
|
+
|
|
62
|
+
# Numbers
|
|
63
|
+
px.send("engine.rpm", 3450)
|
|
64
|
+
px.send("coolant.temperature", 82.3, tags={"unit": "C"})
|
|
65
|
+
|
|
66
|
+
# Strings, bools, objects, arrays — all JSON-serializable
|
|
67
|
+
px.send("vehicle.state", "RUNNING")
|
|
68
|
+
px.send("motor.enabled", True)
|
|
69
|
+
px.send("position", {"x": 1.5, "y": 2.3, "z": 0.8})
|
|
70
|
+
|
|
71
|
+
# Batch
|
|
72
|
+
px.send_batch([
|
|
73
|
+
("temperature", 72.5),
|
|
74
|
+
("pressure", 1013.25),
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
# Named run for grouping related data
|
|
78
|
+
with px.run("thermal-cycle-001"):
|
|
79
|
+
while running:
|
|
80
|
+
px.send("temperature", read_temp())
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Bring Your Own Protocol
|
|
84
|
+
|
|
85
|
+
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()`.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
# MAVLink (pymavlink)
|
|
89
|
+
for msg in conn:
|
|
90
|
+
if msg.get_type() == "ATTITUDE":
|
|
91
|
+
px.send("attitude.roll", msg.roll)
|
|
92
|
+
|
|
93
|
+
# CAN (python-can)
|
|
94
|
+
for msg in bus:
|
|
95
|
+
px.send(f"can.0x{msg.arbitration_id:x}", int.from_bytes(msg.data, "big"))
|
|
96
|
+
|
|
97
|
+
# MQTT (paho-mqtt)
|
|
98
|
+
def on_message(_c, _u, msg):
|
|
99
|
+
px.send(msg.topic.replace("/", "."), float(msg.payload))
|
|
100
|
+
|
|
101
|
+
# I2C sensor (Adafruit CircuitPython)
|
|
102
|
+
px.send("temperature", bme.temperature)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
See [`examples/`](examples/) for runnable versions of each.
|
|
106
|
+
|
|
107
|
+
## Reliability
|
|
108
|
+
|
|
109
|
+
Every send buffers locally before hitting the network, retries with exponential backoff, and keeps your data safe across outages. Enable SQLite persistence to survive restarts and power loss:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
px = Plexus(persistent_buffer=True)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Point counts and flush:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
px.buffer_size()
|
|
119
|
+
px.flush_buffer()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Environment Variables
|
|
123
|
+
|
|
124
|
+
| Variable | Description | Default |
|
|
125
|
+
| ----------------------- | ---------------------------- | -------------------------------- |
|
|
126
|
+
| `PLEXUS_API_KEY` | API key (required) | none |
|
|
127
|
+
| `PLEXUS_GATEWAY_URL` | HTTP ingest URL | `https://plexus-gateway.fly.dev` |
|
|
128
|
+
| `PLEXUS_GATEWAY_WS_URL` | WebSocket URL (unused in SDK, for compatibility) | `wss://plexus-gateway.fly.dev` |
|
|
129
|
+
|
|
130
|
+
## Architecture
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
Your code ── px.send() ── HTTP POST /ingest ──> plexus-gateway ──> ClickHouse + Dashboard
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
One thin path. No agent, no daemon, no adapters. If you want the full HardwareOps platform — dashboards, alerts, RCA, fleet views — that's the web UI at app.plexus.company. This package gets your data there.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
Apache 2.0
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# plexus-python
|
|
2
|
+
|
|
3
|
+
**Thin Python SDK for [Plexus](https://plexus.company).** Send telemetry to the Plexus gateway in one line. Storage, dashboards, alerts, and fleet management live in the platform — this package just ships your data.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/plexus-python/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install plexus-python
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from plexus import Plexus
|
|
16
|
+
|
|
17
|
+
px = Plexus(api_key="plx_xxx", source_id="device-001")
|
|
18
|
+
px.send("temperature", 72.5)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Get an API key at [app.plexus.company](https://app.plexus.company) → Devices → Add Device.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from plexus import Plexus
|
|
27
|
+
|
|
28
|
+
px = Plexus(source_id="rig-01") # reads PLEXUS_API_KEY from env
|
|
29
|
+
|
|
30
|
+
# Numbers
|
|
31
|
+
px.send("engine.rpm", 3450)
|
|
32
|
+
px.send("coolant.temperature", 82.3, tags={"unit": "C"})
|
|
33
|
+
|
|
34
|
+
# Strings, bools, objects, arrays — all JSON-serializable
|
|
35
|
+
px.send("vehicle.state", "RUNNING")
|
|
36
|
+
px.send("motor.enabled", True)
|
|
37
|
+
px.send("position", {"x": 1.5, "y": 2.3, "z": 0.8})
|
|
38
|
+
|
|
39
|
+
# Batch
|
|
40
|
+
px.send_batch([
|
|
41
|
+
("temperature", 72.5),
|
|
42
|
+
("pressure", 1013.25),
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
# Named run for grouping related data
|
|
46
|
+
with px.run("thermal-cycle-001"):
|
|
47
|
+
while running:
|
|
48
|
+
px.send("temperature", read_temp())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Bring Your Own Protocol
|
|
52
|
+
|
|
53
|
+
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()`.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
# MAVLink (pymavlink)
|
|
57
|
+
for msg in conn:
|
|
58
|
+
if msg.get_type() == "ATTITUDE":
|
|
59
|
+
px.send("attitude.roll", msg.roll)
|
|
60
|
+
|
|
61
|
+
# CAN (python-can)
|
|
62
|
+
for msg in bus:
|
|
63
|
+
px.send(f"can.0x{msg.arbitration_id:x}", int.from_bytes(msg.data, "big"))
|
|
64
|
+
|
|
65
|
+
# MQTT (paho-mqtt)
|
|
66
|
+
def on_message(_c, _u, msg):
|
|
67
|
+
px.send(msg.topic.replace("/", "."), float(msg.payload))
|
|
68
|
+
|
|
69
|
+
# I2C sensor (Adafruit CircuitPython)
|
|
70
|
+
px.send("temperature", bme.temperature)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
See [`examples/`](examples/) for runnable versions of each.
|
|
74
|
+
|
|
75
|
+
## Reliability
|
|
76
|
+
|
|
77
|
+
Every send buffers locally before hitting the network, retries with exponential backoff, and keeps your data safe across outages. Enable SQLite persistence to survive restarts and power loss:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
px = Plexus(persistent_buffer=True)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Point counts and flush:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
px.buffer_size()
|
|
87
|
+
px.flush_buffer()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Environment Variables
|
|
91
|
+
|
|
92
|
+
| Variable | Description | Default |
|
|
93
|
+
| ----------------------- | ---------------------------- | -------------------------------- |
|
|
94
|
+
| `PLEXUS_API_KEY` | API key (required) | none |
|
|
95
|
+
| `PLEXUS_GATEWAY_URL` | HTTP ingest URL | `https://plexus-gateway.fly.dev` |
|
|
96
|
+
| `PLEXUS_GATEWAY_WS_URL` | WebSocket URL (unused in SDK, for compatibility) | `wss://plexus-gateway.fly.dev` |
|
|
97
|
+
|
|
98
|
+
## Architecture
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Your code ── px.send() ── HTTP POST /ingest ──> plexus-gateway ──> ClickHouse + Dashboard
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
One thin path. No agent, no daemon, no adapters. If you want the full HardwareOps platform — dashboards, alerts, RCA, fleet views — that's the web UI at app.plexus.company. This package gets your data there.
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
Apache 2.0
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Each script is standalone — copy into your project, adjust the `source_id`, and run. Every example takes `PLEXUS_API_KEY` from the environment.
|
|
4
|
+
|
|
5
|
+
| Script | What it shows | Extra deps |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `basic.py` | The 3-line starter | none |
|
|
8
|
+
| `mavlink.py` | Drone / autopilot telemetry | `pymavlink` |
|
|
9
|
+
| `can.py` | Vehicle CAN bus (with optional DBC decode) | `python-can`, `cantools` |
|
|
10
|
+
| `mqtt.py` | MQTT broker → Plexus bridge | `paho-mqtt` |
|
|
11
|
+
| `i2c_bme280.py` | Raspberry Pi environmental sensor | `adafruit-circuitpython-bme280` |
|
|
12
|
+
|
|
13
|
+
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,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Basic telemetry — the 3-line starter.
|
|
3
|
+
|
|
4
|
+
Run:
|
|
5
|
+
export PLEXUS_API_KEY=plx_xxx
|
|
6
|
+
python basic.py
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import random
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
from plexus import Plexus
|
|
13
|
+
|
|
14
|
+
px = Plexus(source_id="demo-device")
|
|
15
|
+
|
|
16
|
+
while True:
|
|
17
|
+
px.send("temperature", 20 + random.random() * 5)
|
|
18
|
+
px.send("humidity", 40 + random.random() * 10)
|
|
19
|
+
time.sleep(1)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pipe CAN bus frames into Plexus.
|
|
3
|
+
|
|
4
|
+
Prereq:
|
|
5
|
+
pip install plexus-python python-can cantools
|
|
6
|
+
sudo ip link set can0 type can bitrate 500000 && sudo ip link set can0 up
|
|
7
|
+
export PLEXUS_API_KEY=plx_xxx
|
|
8
|
+
|
|
9
|
+
Run:
|
|
10
|
+
python can.py can0 [vehicle.dbc]
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
import can
|
|
16
|
+
import cantools
|
|
17
|
+
|
|
18
|
+
from plexus import Plexus
|
|
19
|
+
|
|
20
|
+
channel = sys.argv[1] if len(sys.argv) > 1 else "can0"
|
|
21
|
+
dbc_path = sys.argv[2] if len(sys.argv) > 2 else None
|
|
22
|
+
|
|
23
|
+
px = Plexus(source_id="vehicle-001")
|
|
24
|
+
bus = can.interface.Bus(channel=channel, bustype="socketcan")
|
|
25
|
+
db = cantools.database.load_file(dbc_path) if dbc_path else None
|
|
26
|
+
|
|
27
|
+
for msg in bus:
|
|
28
|
+
if db:
|
|
29
|
+
try:
|
|
30
|
+
decoded = db.decode_message(msg.arbitration_id, msg.data)
|
|
31
|
+
for name, value in decoded.items():
|
|
32
|
+
if isinstance(value, (int, float)):
|
|
33
|
+
px.send(name, value)
|
|
34
|
+
continue
|
|
35
|
+
except KeyError:
|
|
36
|
+
pass
|
|
37
|
+
px.send(
|
|
38
|
+
f"can.raw.0x{msg.arbitration_id:x}",
|
|
39
|
+
int.from_bytes(msg.data, "big"),
|
|
40
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Read a BME280 over I2C on a Raspberry Pi and stream to Plexus.
|
|
3
|
+
|
|
4
|
+
Prereq:
|
|
5
|
+
pip install plexus-python adafruit-circuitpython-bme280
|
|
6
|
+
sudo usermod -aG i2c $USER && reboot # (once)
|
|
7
|
+
export PLEXUS_API_KEY=plx_xxx
|
|
8
|
+
|
|
9
|
+
Run:
|
|
10
|
+
python i2c_bme280.py
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
import board
|
|
16
|
+
import busio
|
|
17
|
+
from adafruit_bme280 import basic as adafruit_bme280
|
|
18
|
+
|
|
19
|
+
from plexus import Plexus
|
|
20
|
+
|
|
21
|
+
i2c = busio.I2C(board.SCL, board.SDA)
|
|
22
|
+
bme = adafruit_bme280.Adafruit_BME280_I2C(i2c)
|
|
23
|
+
|
|
24
|
+
px = Plexus(source_id="pi-lab-01")
|
|
25
|
+
|
|
26
|
+
while True:
|
|
27
|
+
px.send("temperature", bme.temperature)
|
|
28
|
+
px.send("humidity", bme.relative_humidity)
|
|
29
|
+
px.send("pressure", bme.pressure)
|
|
30
|
+
time.sleep(2)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pipe MAVLink telemetry into Plexus.
|
|
3
|
+
|
|
4
|
+
Prereq:
|
|
5
|
+
pip install plexus-python pymavlink
|
|
6
|
+
export PLEXUS_API_KEY=plx_xxx
|
|
7
|
+
|
|
8
|
+
Run:
|
|
9
|
+
python mavlink.py udpin:0.0.0.0:14550
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from pymavlink import mavutil
|
|
15
|
+
|
|
16
|
+
from plexus import Plexus
|
|
17
|
+
|
|
18
|
+
conn_str = sys.argv[1] if len(sys.argv) > 1 else "udpin:0.0.0.0:14550"
|
|
19
|
+
px = Plexus(source_id="drone-001")
|
|
20
|
+
conn = mavutil.mavlink_connection(conn_str)
|
|
21
|
+
conn.wait_heartbeat()
|
|
22
|
+
|
|
23
|
+
while True:
|
|
24
|
+
msg = conn.recv_match(blocking=True)
|
|
25
|
+
if msg is None:
|
|
26
|
+
continue
|
|
27
|
+
t = msg.get_type()
|
|
28
|
+
|
|
29
|
+
if t == "ATTITUDE":
|
|
30
|
+
px.send("attitude.roll", msg.roll)
|
|
31
|
+
px.send("attitude.pitch", msg.pitch)
|
|
32
|
+
px.send("attitude.yaw", msg.yaw)
|
|
33
|
+
elif t == "GLOBAL_POSITION_INT":
|
|
34
|
+
px.send("gps.lat", msg.lat / 1e7)
|
|
35
|
+
px.send("gps.lon", msg.lon / 1e7)
|
|
36
|
+
px.send("gps.alt_m", msg.alt / 1000.0)
|
|
37
|
+
elif t == "SYS_STATUS":
|
|
38
|
+
px.send("battery.voltage", msg.voltage_battery / 1000.0)
|
|
39
|
+
px.send("battery.current", msg.current_battery / 100.0)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bridge an MQTT broker into Plexus.
|
|
3
|
+
|
|
4
|
+
Prereq:
|
|
5
|
+
pip install plexus-python paho-mqtt
|
|
6
|
+
export PLEXUS_API_KEY=plx_xxx
|
|
7
|
+
|
|
8
|
+
Run:
|
|
9
|
+
python mqtt.py localhost sensors/#
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
import paho.mqtt.client as mqtt
|
|
16
|
+
|
|
17
|
+
from plexus import Plexus
|
|
18
|
+
|
|
19
|
+
broker = sys.argv[1] if len(sys.argv) > 1 else "localhost"
|
|
20
|
+
topic = sys.argv[2] if len(sys.argv) > 2 else "sensors/#"
|
|
21
|
+
|
|
22
|
+
px = Plexus(source_id="mqtt-gateway")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def on_message(_client, _userdata, msg):
|
|
26
|
+
name = msg.topic.replace("/", ".")
|
|
27
|
+
payload = msg.payload.decode("utf-8", errors="replace")
|
|
28
|
+
try:
|
|
29
|
+
data = json.loads(payload)
|
|
30
|
+
except ValueError:
|
|
31
|
+
try:
|
|
32
|
+
px.send(name, float(payload))
|
|
33
|
+
except ValueError:
|
|
34
|
+
px.send(name, payload)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
if isinstance(data, dict):
|
|
38
|
+
for key, value in data.items():
|
|
39
|
+
px.send(f"{name}.{key}", value)
|
|
40
|
+
else:
|
|
41
|
+
px.send(name, data)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
client = mqtt.Client()
|
|
45
|
+
client.on_message = on_message
|
|
46
|
+
client.connect(broker)
|
|
47
|
+
client.subscribe(topic)
|
|
48
|
+
client.loop_forever()
|