plexus-python 0.3.0__tar.gz → 0.4.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.3.0 → plexus_python-0.4.2}/AGENTS.md +5 -20
- {plexus_python-0.3.0 → plexus_python-0.4.2}/API.md +16 -14
- plexus_python-0.4.2/CHANGELOG.md +123 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/PKG-INFO +16 -1
- {plexus_python-0.3.0 → plexus_python-0.4.2}/README.md +15 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/SECURITY.md +1 -1
- {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/basic.py +1 -1
- {plexus_python-0.3.0 → plexus_python-0.4.2}/plexus/__init__.py +1 -1
- plexus_python-0.4.2/plexus/cli.py +486 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/plexus/client.py +15 -1
- {plexus_python-0.3.0 → plexus_python-0.4.2}/plexus/config.py +48 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/plexus/ws.py +31 -2
- {plexus_python-0.3.0 → plexus_python-0.4.2}/pyproject.toml +4 -1
- {plexus_python-0.3.0 → plexus_python-0.4.2}/scripts/setup.sh +44 -12
- {plexus_python-0.3.0 → plexus_python-0.4.2}/tests/test_retry.py +18 -14
- {plexus_python-0.3.0 → plexus_python-0.4.2}/tests/test_ws.py +81 -2
- plexus_python-0.3.0/CHANGELOG.md +0 -45
- {plexus_python-0.3.0 → plexus_python-0.4.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/.github/workflows/ci.yml +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/.github/workflows/publish.yml +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/.gitignore +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/CODE_OF_CONDUCT.md +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/CONTRIBUTING.md +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/LICENSE +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/README.md +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/can.py +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/i2c_bme280.py +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/mavlink.py +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/mqtt.py +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/plexus/buffer.py +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/scripts/plexus.service +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/scripts/scan_buses.py +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/tests/test_basic.py +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/tests/test_buffer.py +0 -0
- {plexus_python-0.3.0 → plexus_python-0.4.2}/tests/test_config.py +0 -0
|
@@ -4,11 +4,11 @@ Machine-readable interface for AI assistants and automation scripts.
|
|
|
4
4
|
|
|
5
5
|
## Environment Variables
|
|
6
6
|
|
|
7
|
-
| Variable | Description
|
|
8
|
-
| ----------------------- |
|
|
9
|
-
| `PLEXUS_API_KEY` | API key for authentication (required)
|
|
10
|
-
| `PLEXUS_GATEWAY_URL` | Gateway HTTP ingest URL
|
|
11
|
-
| `PLEXUS_GATEWAY_WS_URL` | Gateway WebSocket URL
|
|
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
12
|
|
|
13
13
|
## CLI Commands
|
|
14
14
|
|
|
@@ -48,21 +48,6 @@ px.send_batch([
|
|
|
48
48
|
px = Plexus(api_key="plx_xxxxx", persistent_buffer=True)
|
|
49
49
|
```
|
|
50
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
51
|
## Key Conventions
|
|
67
52
|
|
|
68
53
|
- Config lives in `~/.plexus/config.json`
|
|
@@ -152,34 +152,36 @@ For real-time UI-controlled streaming, devices connect via WebSocket.
|
|
|
152
152
|
|
|
153
153
|
### Device Authentication
|
|
154
154
|
|
|
155
|
-
Devices authenticate using an API key
|
|
155
|
+
Devices authenticate using an API key. The `source_id` in the request is the device's *desired* name; the server may return a different, auto-suffixed name in the `authenticated` frame if the desired name is already claimed by another device (see [Device identity](../README.md#device-identity) in the README).
|
|
156
156
|
|
|
157
157
|
```json
|
|
158
158
|
// Device → Server
|
|
159
159
|
{
|
|
160
160
|
"type": "device_auth",
|
|
161
161
|
"api_key": "plx_xxxxx",
|
|
162
|
-
"source_id": "
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
|
|
166
|
-
"name": "MPU6050",
|
|
167
|
-
"description": "6-axis IMU",
|
|
168
|
-
"metrics": ["accel_x", "accel_y", "accel_z", "gyro_x", "gyro_y", "gyro_z"],
|
|
169
|
-
"sample_rate": 100,
|
|
170
|
-
"prefix": "",
|
|
171
|
-
"available": true
|
|
172
|
-
}
|
|
173
|
-
]
|
|
162
|
+
"source_id": "drone-01",
|
|
163
|
+
"install_id": "c9f2e0b46f4a4f6a8c3e1d5b0a2e7f91",
|
|
164
|
+
"platform": "python-sdk",
|
|
165
|
+
"agent_version": "0.3.1"
|
|
174
166
|
}
|
|
175
167
|
|
|
176
168
|
// Server → Device
|
|
177
169
|
{
|
|
178
170
|
"type": "authenticated",
|
|
179
|
-
"source_id": "
|
|
171
|
+
"source_id": "drone-01"
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Server → Device (collision case)
|
|
175
|
+
{
|
|
176
|
+
"type": "authenticated",
|
|
177
|
+
"source_id": "drone-01_2"
|
|
180
178
|
}
|
|
181
179
|
```
|
|
182
180
|
|
|
181
|
+
The SDK **adopts** whatever `source_id` the server returns and uses it for all subsequent frames, heartbeats, and reconnects. It also persists the assigned name locally so reconnects go straight to the claimed slot.
|
|
182
|
+
|
|
183
|
+
`install_id` is a stable per-installation UUID, generated on the device's first run and saved to `~/.plexus/config.json`. It lets the server distinguish a rebooting device from a new device trying to claim an existing name. Legacy SDKs that omit `install_id` continue to work as before (the server passes the declared `source_id` through unchanged).
|
|
184
|
+
|
|
183
185
|
### Message Types (Dashboard → Device)
|
|
184
186
|
|
|
185
187
|
| Type | Description |
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.4.2] - 2026-04-27 - CLI auth: branded success page + auto-redirect
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- `plexus/cli.py` — the localhost callback's success and error pages now
|
|
8
|
+
match the Plexus app's dark aesthetic (black background, zinc-800
|
|
9
|
+
bordered card, white headlines, monospace URL, status-color badge).
|
|
10
|
+
- After a successful `plexus init`, the browser tab now auto-redirects
|
|
11
|
+
to the configured app endpoint (`PLEXUS_ENDPOINT`, default
|
|
12
|
+
`https://app.plexus.company`) after a 10-second countdown, so first-
|
|
13
|
+
time users land on their dashboard without having to navigate there
|
|
14
|
+
manually. Falls back to `<meta http-equiv="refresh">` when JS is off.
|
|
15
|
+
|
|
16
|
+
## [0.4.1] - 2026-04-27 - CI fixes for 0.4.0
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `plexus/cli.py` — drop a stray `f` prefix on a non-interpolated string
|
|
21
|
+
that ruff (`F541`) caught in CI.
|
|
22
|
+
- `tests/test_retry.py::test_concurrent_sends` — move `patch.object` out
|
|
23
|
+
of the per-thread closure. `mock.patch.object` mutates instance
|
|
24
|
+
attributes and is not thread-safe; under 20 concurrent threads the
|
|
25
|
+
state would leak and surface as a spurious `AttributeError` on Python
|
|
26
|
+
3.8.
|
|
27
|
+
|
|
28
|
+
## [0.4.0] - 2026-04-27 - Stable device identity + CLI
|
|
29
|
+
|
|
30
|
+
The gateway is now authoritative for a device's `source_id`. The SDK sends a
|
|
31
|
+
locally-generated `install_id` in the auth frame; the gateway atomically
|
|
32
|
+
claims `(org, source_id)` and, if the desired name is already owned by a
|
|
33
|
+
different install, returns an auto-suffixed name (`drone-01` → `drone-01_2`
|
|
34
|
+
→ `drone-01_3`…) in the `authenticated` frame. The SDK adopts and persists
|
|
35
|
+
the assigned name so subsequent reconnects are stable.
|
|
36
|
+
|
|
37
|
+
This fixes the silent stream-merging that happened when cloned SD-card
|
|
38
|
+
images shared a hostname or when two operators picked the same name.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `plexus init` (alias `plexus login`) — fly.io / vercel-style browser auth
|
|
43
|
+
flow. Spins up a localhost listener, opens `${PLEXUS_ENDPOINT}/auth/cli`
|
|
44
|
+
with a state-protected callback, and persists the issued key to
|
|
45
|
+
`~/.plexus/config.json`. Console script registered in `pyproject.toml`
|
|
46
|
+
(`plexus = "plexus.cli:main"`); stdlib-only, no new runtime deps.
|
|
47
|
+
- `plexus.config.get_install_id()` — lazy per-installation UUID, persisted
|
|
48
|
+
to `~/.plexus/config.json`. **Not** written by `setup.sh`: it's minted by
|
|
49
|
+
the SDK on first run so pre-baked images get distinct IDs per boot.
|
|
50
|
+
- `PLEXUS_INSTALL_ID` env var — override for `get_install_id()` so
|
|
51
|
+
ephemeral containers (Fly machines, k8s pods, CI runners) can pin a
|
|
52
|
+
stable identity across restarts when the config filesystem is ephemeral.
|
|
53
|
+
Without this, every redeploy gets a fresh UUID and the gateway
|
|
54
|
+
auto-suffixes the source_id.
|
|
55
|
+
- `plexus.config.set_source_id()` — persist the gateway-assigned name after
|
|
56
|
+
auto-suffix resolution.
|
|
57
|
+
- `WebSocketTransport(install_id=..., on_source_id_assigned=...)` — the
|
|
58
|
+
transport sends `install_id` in the `device_auth` frame and invokes the
|
|
59
|
+
callback whenever the gateway returns a different `source_id` than
|
|
60
|
+
requested.
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- `WebSocketTransport` now reads the `source_id` back from the
|
|
65
|
+
`authenticated` frame and updates `self.source_id` in place if the gateway
|
|
66
|
+
auto-suffixed. The rename is logged at INFO level on first occurrence.
|
|
67
|
+
- `Plexus` wires `install_id` into the transport and persists the assigned
|
|
68
|
+
`source_id` to config on rename.
|
|
69
|
+
- `scripts/setup.sh` — `--name` is **required**. The hostname fallback is
|
|
70
|
+
removed (it was the main source of cloned-image collisions). In a TTY the
|
|
71
|
+
script prompts interactively; in non-TTY it exits with an error. Names are
|
|
72
|
+
validated against `^[a-z0-9][a-z0-9_-]{1,62}$`. Stale `plexus start` /
|
|
73
|
+
`plexus reset` hints were dropped.
|
|
74
|
+
|
|
75
|
+
### Wire-protocol (compatible)
|
|
76
|
+
|
|
77
|
+
- `device_auth` frame gains an optional `install_id` field. The gateway
|
|
78
|
+
treats a missing `install_id` as legacy pass-through, so older SDKs and
|
|
79
|
+
the C SDK continue to work unchanged.
|
|
80
|
+
|
|
81
|
+
## [0.3.0] - WebSocket transport
|
|
82
|
+
|
|
83
|
+
Adds a wire-compatible WebSocket transport matching the `plexus-c` SDK. WS is now the default; failed sends transparently fall back to `POST /ingest`.
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
|
|
87
|
+
- `plexus.WebSocketTransport` — connects to `/ws/device` on the gateway. Exchanges the same `device_auth` / `authenticated` / `telemetry` / `heartbeat` / `typed_command` / `command_result` frames as `plexus-c`.
|
|
88
|
+
- `Plexus(transport="ws" | "http")` — defaults to `"ws"`.
|
|
89
|
+
- `Plexus.on_command(name, handler, description=..., params=...)` — register command handlers; automatic `ack`, handler return becomes `result`, exceptions become `error`.
|
|
90
|
+
- `Plexus.close()` — stops the WebSocket thread.
|
|
91
|
+
- Runtime dep: `websocket-client>=1.7`.
|
|
92
|
+
- Tests: `tests/test_ws.py` (auth handshake, telemetry, command roundtrip, error paths).
|
|
93
|
+
|
|
94
|
+
## [0.2.0] - Thin SDK rewrite
|
|
95
|
+
|
|
96
|
+
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()`.
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
|
|
100
|
+
- 5 runnable example scripts: `basic.py`, `mavlink.py`, `can.py`, `mqtt.py`, `i2c_bme280.py`
|
|
101
|
+
|
|
102
|
+
### Removed
|
|
103
|
+
|
|
104
|
+
- `plexus/adapters/` (MAVLink, CAN, MQTT, Modbus, OPC-UA, BLE, Serial — use the upstream lib directly)
|
|
105
|
+
- `plexus/sensors/` (I2C drivers + auto-detect — use Adafruit CircuitPython or smbus2 directly)
|
|
106
|
+
- `plexus/cameras/` (frame upload — out of scope)
|
|
107
|
+
- `plexus/cli.py`, `plexus/connector.py`, `plexus/streaming.py`, `plexus/detect.py`, `plexus/tui.py`, `plexus/deps.py`
|
|
108
|
+
- `plexus` console script, `python -m plexus`
|
|
109
|
+
- Extras: `[sensors]`, `[system]`, `[tui]`, `[mqtt]`, `[can]`, `[mavlink]`, `[modbus]`, `[opcua]`, `[ble]`, `[serial]`, `[ros]`, `[camera]`, `[picamera]`, `[all]`
|
|
110
|
+
- Runtime deps: `click`, `websockets`
|
|
111
|
+
|
|
112
|
+
### Changed
|
|
113
|
+
|
|
114
|
+
- Default ingest endpoint points directly at the Plexus gateway (`https://plexus-gateway.fly.dev/ingest`), not the Next.js app proxy
|
|
115
|
+
- Client raises `ValueError` clearly when no API key is available, instead of invoking a login flow
|
|
116
|
+
|
|
117
|
+
## [0.1.0] - Initial release
|
|
118
|
+
|
|
119
|
+
- `Plexus` thin client for HTTP ingest
|
|
120
|
+
- `plexus start` daemon with WebSocket streaming
|
|
121
|
+
- Protocol adapters: MAVLink, CAN, MQTT, Modbus, OPC-UA, Serial, BLE
|
|
122
|
+
- I2C sensor auto-detection and drivers
|
|
123
|
+
- Store-and-forward buffering (SQLite)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plexus-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -54,6 +54,21 @@ px.send("temperature", 72.5)
|
|
|
54
54
|
|
|
55
55
|
Get an API key at [app.plexus.company](https://app.plexus.company) → Devices → Add Device.
|
|
56
56
|
|
|
57
|
+
## Device identity
|
|
58
|
+
|
|
59
|
+
Every device needs a unique `source_id`. The recommended way to set one on a real host is the bootstrap script, which requires a device name up front:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
curl -sL https://app.plexus.company/setup | bash -s -- \
|
|
63
|
+
--key plx_xxx --name drone-01
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The name must match `^[a-z0-9][a-z0-9_-]{1,62}$`. `setup.sh` refuses to run without `--name` (or without a TTY to prompt for one) — this is deliberate, because the previous `hostname` fallback silently merged telemetry from cloned SD-card images that all booted as `raspberrypi`.
|
|
67
|
+
|
|
68
|
+
**If two devices end up requesting the same name**, the gateway auto-suffixes: the first connection gets `drone-01`, the second gets `drone-01_2`, the third `drone-01_3`, and so on. The SDK logs the rename at INFO and persists the assigned name to `~/.plexus/config.json` so the device keeps its identity across reboots. Under the hood, a per-installation UUID (`install_id`, lazily generated on first run) is what lets the gateway tell "same device reconnecting" from "different device claiming the same name."
|
|
69
|
+
|
|
70
|
+
In normal code, you usually just pass `source_id=...` explicitly to `Plexus(...)` and never have to think about it.
|
|
71
|
+
|
|
57
72
|
## Usage
|
|
58
73
|
|
|
59
74
|
```python
|
|
@@ -20,6 +20,21 @@ px.send("temperature", 72.5)
|
|
|
20
20
|
|
|
21
21
|
Get an API key at [app.plexus.company](https://app.plexus.company) → Devices → Add Device.
|
|
22
22
|
|
|
23
|
+
## Device identity
|
|
24
|
+
|
|
25
|
+
Every device needs a unique `source_id`. The recommended way to set one on a real host is the bootstrap script, which requires a device name up front:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
curl -sL https://app.plexus.company/setup | bash -s -- \
|
|
29
|
+
--key plx_xxx --name drone-01
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The name must match `^[a-z0-9][a-z0-9_-]{1,62}$`. `setup.sh` refuses to run without `--name` (or without a TTY to prompt for one) — this is deliberate, because the previous `hostname` fallback silently merged telemetry from cloned SD-card images that all booted as `raspberrypi`.
|
|
33
|
+
|
|
34
|
+
**If two devices end up requesting the same name**, the gateway auto-suffixes: the first connection gets `drone-01`, the second gets `drone-01_2`, the third `drone-01_3`, and so on. The SDK logs the rename at INFO and persists the assigned name to `~/.plexus/config.json` so the device keeps its identity across reboots. Under the hood, a per-installation UUID (`install_id`, lazily generated on first run) is what lets the gateway tell "same device reconnecting" from "different device claiming the same name."
|
|
35
|
+
|
|
36
|
+
In normal code, you usually just pass `source_id=...` explicitly to `Plexus(...)` and never have to think about it.
|
|
37
|
+
|
|
23
38
|
## Usage
|
|
24
39
|
|
|
25
40
|
```python
|