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.
Files changed (37) hide show
  1. {plexus_python-0.3.0 → plexus_python-0.4.2}/AGENTS.md +5 -20
  2. {plexus_python-0.3.0 → plexus_python-0.4.2}/API.md +16 -14
  3. plexus_python-0.4.2/CHANGELOG.md +123 -0
  4. {plexus_python-0.3.0 → plexus_python-0.4.2}/PKG-INFO +16 -1
  5. {plexus_python-0.3.0 → plexus_python-0.4.2}/README.md +15 -0
  6. {plexus_python-0.3.0 → plexus_python-0.4.2}/SECURITY.md +1 -1
  7. {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/basic.py +1 -1
  8. {plexus_python-0.3.0 → plexus_python-0.4.2}/plexus/__init__.py +1 -1
  9. plexus_python-0.4.2/plexus/cli.py +486 -0
  10. {plexus_python-0.3.0 → plexus_python-0.4.2}/plexus/client.py +15 -1
  11. {plexus_python-0.3.0 → plexus_python-0.4.2}/plexus/config.py +48 -0
  12. {plexus_python-0.3.0 → plexus_python-0.4.2}/plexus/ws.py +31 -2
  13. {plexus_python-0.3.0 → plexus_python-0.4.2}/pyproject.toml +4 -1
  14. {plexus_python-0.3.0 → plexus_python-0.4.2}/scripts/setup.sh +44 -12
  15. {plexus_python-0.3.0 → plexus_python-0.4.2}/tests/test_retry.py +18 -14
  16. {plexus_python-0.3.0 → plexus_python-0.4.2}/tests/test_ws.py +81 -2
  17. plexus_python-0.3.0/CHANGELOG.md +0 -45
  18. {plexus_python-0.3.0 → plexus_python-0.4.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  19. {plexus_python-0.3.0 → plexus_python-0.4.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  20. {plexus_python-0.3.0 → plexus_python-0.4.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  21. {plexus_python-0.3.0 → plexus_python-0.4.2}/.github/workflows/ci.yml +0 -0
  22. {plexus_python-0.3.0 → plexus_python-0.4.2}/.github/workflows/publish.yml +0 -0
  23. {plexus_python-0.3.0 → plexus_python-0.4.2}/.gitignore +0 -0
  24. {plexus_python-0.3.0 → plexus_python-0.4.2}/CODE_OF_CONDUCT.md +0 -0
  25. {plexus_python-0.3.0 → plexus_python-0.4.2}/CONTRIBUTING.md +0 -0
  26. {plexus_python-0.3.0 → plexus_python-0.4.2}/LICENSE +0 -0
  27. {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/README.md +0 -0
  28. {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/can.py +0 -0
  29. {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/i2c_bme280.py +0 -0
  30. {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/mavlink.py +0 -0
  31. {plexus_python-0.3.0 → plexus_python-0.4.2}/examples/mqtt.py +0 -0
  32. {plexus_python-0.3.0 → plexus_python-0.4.2}/plexus/buffer.py +0 -0
  33. {plexus_python-0.3.0 → plexus_python-0.4.2}/scripts/plexus.service +0 -0
  34. {plexus_python-0.3.0 → plexus_python-0.4.2}/scripts/scan_buses.py +0 -0
  35. {plexus_python-0.3.0 → plexus_python-0.4.2}/tests/test_basic.py +0 -0
  36. {plexus_python-0.3.0 → plexus_python-0.4.2}/tests/test_buffer.py +0 -0
  37. {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 | 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` |
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": "my-device-001",
163
- "platform": "Linux",
164
- "sensors": [
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": "my-device-001"
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.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
@@ -22,7 +22,7 @@ Instead, email **support@plexus.company** with:
22
22
  ## Supported Versions
23
23
 
24
24
  | Version | Supported |
25
- |---------|-----------|
25
+ | ------- | --------- |
26
26
  | 0.9.x | Yes |
27
27
  | < 0.9 | No |
28
28
 
@@ -11,7 +11,7 @@ import time
11
11
 
12
12
  from plexus import Plexus
13
13
 
14
- px = Plexus(source_id="demo-device")
14
+ px = Plexus(api_key="plx_123...", source_id="demo-device")
15
15
 
16
16
  while True:
17
17
  px.send("temperature", 20 + random.random() * 5)
@@ -10,5 +10,5 @@ Plexus — thin Python SDK for sending telemetry to the Plexus gateway.
10
10
  from plexus.client import Plexus
11
11
  from plexus.ws import WebSocketTransport
12
12
 
13
- __version__ = "0.3.0"
13
+ __version__ = "0.4.1"
14
14
  __all__ = ["Plexus", "WebSocketTransport"]