plexus-python 0.11.3__tar.gz → 0.11.5__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.11.3 → plexus_python-0.11.5}/API.md +11 -5
- {plexus_python-0.11.3 → plexus_python-0.11.5}/CHANGELOG.md +55 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/CONTRIBUTING.md +2 -2
- {plexus_python-0.11.3 → plexus_python-0.11.5}/PKG-INFO +6 -4
- {plexus_python-0.11.3 → plexus_python-0.11.5}/README.md +5 -3
- {plexus_python-0.11.3 → plexus_python-0.11.5}/SECURITY.md +3 -3
- {plexus_python-0.11.3 → plexus_python-0.11.5}/plexus/__init__.py +1 -1
- {plexus_python-0.11.3 → plexus_python-0.11.5}/plexus/ws.py +3 -4
- {plexus_python-0.11.3 → plexus_python-0.11.5}/pyproject.toml +1 -1
- {plexus_python-0.11.3 → plexus_python-0.11.5}/skills/plexus/SKILL.md +5 -6
- {plexus_python-0.11.3 → plexus_python-0.11.5}/skills/plexus-firmware/SKILL.md +1 -1
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_ws.py +1 -1
- {plexus_python-0.11.3 → plexus_python-0.11.5}/uv.lock +1 -1
- plexus_python-0.11.3/TODO.md +0 -1
- plexus_python-0.11.3/scripts/plexus.service +0 -27
- plexus_python-0.11.3/scripts/scan_buses.py +0 -17
- {plexus_python-0.11.3 → plexus_python-0.11.5}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/.github/workflows/ci.yml +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/.github/workflows/publish.yml +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/.gitignore +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/AGENTS.md +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/CODE_OF_CONDUCT.md +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/LICENSE +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/.python-version +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/README.md +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/basic.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/can.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/i2c_bme280.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/mac_metrics.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/mavlink.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/mqtt.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/pyproject.toml +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/thermal_camera.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/examples/uv.lock +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/plexus/_log.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/plexus/batching.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/plexus/buffer.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/plexus/cameras/__init__.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/plexus/cameras/thermal.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/plexus/cli.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/plexus/client.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/plexus/config.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/scripts/release.sh +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/scripts/setup.sh +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/scripts/verify_skills.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/skills/README.md +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/skills/plexus-dashboard/SKILL.md +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/conftest.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_basic.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_batching.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_batching_wire.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_buffer.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_cli_diagnostics.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_config.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_isolation.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_rate_limit.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_retry.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_runs.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_skills.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_thermal.py +0 -0
- {plexus_python-0.11.3 → plexus_python-0.11.5}/tests/test_video.py +0 -0
|
@@ -35,6 +35,7 @@ curl -X POST https://gateway.plexus.company/ingest \
|
|
|
35
35
|
-H "Content-Type: application/json" \
|
|
36
36
|
-d '{
|
|
37
37
|
"points": [{
|
|
38
|
+
"class": "metric",
|
|
38
39
|
"metric": "temperature",
|
|
39
40
|
"value": 72.5,
|
|
40
41
|
"timestamp": 1699900000,
|
|
@@ -83,6 +84,7 @@ x-api-key: plx_xxxxx
|
|
|
83
84
|
{
|
|
84
85
|
"points": [
|
|
85
86
|
{
|
|
87
|
+
"class": "metric",
|
|
86
88
|
"metric": "temperature",
|
|
87
89
|
"value": 72.5,
|
|
88
90
|
"timestamp": 1699900000.123,
|
|
@@ -163,7 +165,7 @@ For real-time UI-controlled streaming, devices connect via WebSocket.
|
|
|
163
165
|
1. Device connects to the gateway
|
|
164
166
|
2. Device authenticates with API key (and advertises any registered commands)
|
|
165
167
|
3. Device streams `telemetry` frames
|
|
166
|
-
4.
|
|
168
|
+
4. Plexus invokes registered commands via `typed_command` (see the note under Commands)
|
|
167
169
|
|
|
168
170
|
### Device Authentication
|
|
169
171
|
|
|
@@ -216,16 +218,18 @@ Devices authenticate using an API key. The gateway echoes the declared `source_i
|
|
|
216
218
|
{
|
|
217
219
|
"type": "telemetry",
|
|
218
220
|
"points": [
|
|
219
|
-
{ "metric": "accel_x", "value": 0.12, "timestamp": 1699900000123 },
|
|
220
|
-
{ "metric": "accel_y", "value": 0.05, "timestamp": 1699900000123 },
|
|
221
|
-
{ "metric": "accel_z", "value": 9.81, "timestamp": 1699900000123 }
|
|
221
|
+
{ "class": "metric", "metric": "accel_x", "value": 0.12, "timestamp": 1699900000123 },
|
|
222
|
+
{ "class": "metric", "metric": "accel_y", "value": 0.05, "timestamp": 1699900000123 },
|
|
223
|
+
{ "class": "metric", "metric": "accel_z", "value": 9.81, "timestamp": 1699900000123 }
|
|
222
224
|
]
|
|
223
225
|
}
|
|
224
226
|
```
|
|
225
227
|
|
|
226
228
|
### Commands
|
|
227
229
|
|
|
228
|
-
|
|
230
|
+
Commands reach the device as a single `typed_command` envelope; the device replies with `command_result` frames. Register handlers with `px.on_command(...)` before the first `send()`.
|
|
231
|
+
|
|
232
|
+
> **Note:** the public API route for sending commands (`POST /v1/sources/{id}/commands`) was turned off on 2026-09-21 and returns `410 Gone`. Nothing in Plexus can currently trigger a custom handler.
|
|
229
233
|
|
|
230
234
|
```json
|
|
231
235
|
// Server → Device
|
|
@@ -251,6 +255,7 @@ requests.post(
|
|
|
251
255
|
headers={"x-api-key": "plx_xxxxx"},
|
|
252
256
|
json={
|
|
253
257
|
"points": [{
|
|
258
|
+
"class": "metric",
|
|
254
259
|
"metric": "temperature",
|
|
255
260
|
"value": 72.5,
|
|
256
261
|
"timestamp": time.time(),
|
|
@@ -297,6 +302,7 @@ import (
|
|
|
297
302
|
func main() {
|
|
298
303
|
points := map[string]interface{}{
|
|
299
304
|
"points": []map[string]interface{}{{
|
|
305
|
+
"class": "metric",
|
|
300
306
|
"metric": "temperature",
|
|
301
307
|
"value": 72.5,
|
|
302
308
|
"timestamp": float64(time.Now().Unix()),
|
|
@@ -2,6 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.11.5] - 2026-09-21 - The commands API is off
|
|
6
|
+
|
|
7
|
+
No code changes. The docs described a way to send commands to a device that
|
|
8
|
+
no longer exists.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **`POST /v1/sources/{id}/commands` is turned off, and the docs now say so.**
|
|
13
|
+
Plexus turned the endpoint off on 2026-09-21. It returns `410 Gone`.
|
|
14
|
+
Any API key of any scope could send any command name to any device in
|
|
15
|
+
the org, which is not safe for commands that reach hardware.
|
|
16
|
+
- The agent skill no longer offers to send commands, and it tells agents
|
|
17
|
+
not to call the endpoint.
|
|
18
|
+
- README and `API.md` say plainly that nothing in Plexus can currently
|
|
19
|
+
trigger an `on_command` handler.
|
|
20
|
+
- `on_command` itself is unchanged.
|
|
21
|
+
|
|
22
|
+
## [0.11.4] - 2026-09-08 - Docs that actually work
|
|
23
|
+
|
|
24
|
+
No code changes. Every edit here is documentation or dead weight, but the
|
|
25
|
+
first item was breaking people who copied from the docs.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **Every raw-HTTP example in `API.md` would have returned 400.** The
|
|
30
|
+
gateway requires `class` on each point (`points[i].class is required`)
|
|
31
|
+
and not one of the five curl/Python/Go/WebSocket examples included it.
|
|
32
|
+
The SDK sets `class` for you, which is exactly why this survived so
|
|
33
|
+
long: the library path worked, and only people copying the HTTP
|
|
34
|
+
examples hit it.
|
|
35
|
+
|
|
36
|
+
- **The documented `source_id` pattern rejected valid ids.** README and
|
|
37
|
+
the firmware skill both said `^[a-z0-9][a-z0-9_-]{1,62}$`, while
|
|
38
|
+
`client.py` enforces `^[a-z0-9][a-z0-9._-]*$` at 256 characters. Dotted
|
|
39
|
+
ids like `sat.01` are legal and read as forbidden.
|
|
40
|
+
|
|
41
|
+
- **`SECURITY.md` declared the shipping version unsupported** — the table
|
|
42
|
+
stopped at `0.5.x` — and pointed at an address that isn't monitored.
|
|
43
|
+
It now says `0.11.x` and `info@plexus.company`.
|
|
44
|
+
|
|
45
|
+
### Removed
|
|
46
|
+
|
|
47
|
+
- **All references to the C SDK.** `plexus-oss/plexus-c` is deleted, so
|
|
48
|
+
the README, `ws.py` and `test_ws.py` were describing the wire protocol
|
|
49
|
+
by pointing at a repository that 404s. Now described as what it is: the
|
|
50
|
+
gateway's device wire protocol. `CHANGELOG` history is left alone.
|
|
51
|
+
|
|
52
|
+
- **`scripts/plexus.service`** — a systemd unit running `plexus start`, a
|
|
53
|
+
subcommand that does not exist. Nothing referenced it, and the `/setup`
|
|
54
|
+
installer tells you to write your own unit.
|
|
55
|
+
|
|
56
|
+
- **`scripts/scan_buses.py`** (17-line scratch script, undeclared
|
|
57
|
+
`smbus2` dependency, no callers) and a 7-byte `TODO.md`.
|
|
58
|
+
|
|
59
|
+
|
|
5
60
|
## [0.11.3] - 2026-09-02 - Let the CLI diagnose the CLI
|
|
6
61
|
|
|
7
62
|
All three of these came out of one evening of actually using it.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Contributing to
|
|
1
|
+
# Contributing to plexus-python
|
|
2
2
|
|
|
3
3
|
Thanks for your interest in contributing! Here's how to get started.
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ Thanks for your interest in contributing! Here's how to get started.
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
git clone https://github.com/plexus-oss/plexus-python.git
|
|
9
|
-
cd
|
|
9
|
+
cd plexus-python
|
|
10
10
|
python -m venv .venv
|
|
11
11
|
source .venv/bin/activate
|
|
12
12
|
pip install -e ".[dev]"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: plexus-python
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.5
|
|
4
4
|
Summary: Thin Python SDK for Plexus — send telemetry in one line
|
|
5
5
|
Project-URL: Homepage, https://plexus.company
|
|
6
6
|
Project-URL: Documentation, https://docs.plexus.company
|
|
@@ -66,7 +66,7 @@ curl -sL https://app.plexus.company/setup | bash -s -- \
|
|
|
66
66
|
--key plx_xxx --name drone-01
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
The name must match `^[a-z0-9][a-z0-
|
|
69
|
+
The name must match `^[a-z0-9][a-z0-9._-]*$` (max 256 chars). `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`.
|
|
70
70
|
|
|
71
71
|
**Names are not auto-deduplicated.** The gateway echoes back whatever `source_id` you declare, unchanged — pick a unique name per device (that's what `--name` and `source_id=...` are for). Two devices that declare the same name write into the same source.
|
|
72
72
|
|
|
@@ -252,10 +252,10 @@ px.send("temperature", 72.5, timestamp=t) # your timestamp, used as-is, no cor
|
|
|
252
252
|
|
|
253
253
|
## Transport
|
|
254
254
|
|
|
255
|
-
By default the SDK connects over a **WebSocket** to `/ws/device` on the gateway —
|
|
255
|
+
By default the SDK connects over a **WebSocket** to `/ws/device` on the gateway — the gateway's device wire protocol. This gives you:
|
|
256
256
|
|
|
257
257
|
- lower-latency streaming of telemetry,
|
|
258
|
-
-
|
|
258
|
+
- the channel that will carry actions triggered from a Plexus dashboard.
|
|
259
259
|
|
|
260
260
|
If the socket is unavailable, sends transparently fall back to `POST /ingest` so no data is lost.
|
|
261
261
|
|
|
@@ -283,6 +283,8 @@ px.send("temperature", 72.5) # opens the socket, waits for auth
|
|
|
283
283
|
|
|
284
284
|
The SDK sends an `ack` frame before invoking the handler, then a `result` frame with whatever the handler returns (or an `error` frame if it raises).
|
|
285
285
|
|
|
286
|
+
> **Note:** nothing in Plexus can currently trigger a custom handler. The API route for sending commands was turned off on 2026-09-21. Triggering handlers from a dashboard, with permissions and a record of every run, is being rebuilt.
|
|
287
|
+
|
|
286
288
|
## Environment Variables
|
|
287
289
|
|
|
288
290
|
| Variable | Description | Default |
|
|
@@ -29,7 +29,7 @@ curl -sL https://app.plexus.company/setup | bash -s -- \
|
|
|
29
29
|
--key plx_xxx --name drone-01
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
The name must match `^[a-z0-9][a-z0-
|
|
32
|
+
The name must match `^[a-z0-9][a-z0-9._-]*$` (max 256 chars). `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
33
|
|
|
34
34
|
**Names are not auto-deduplicated.** The gateway echoes back whatever `source_id` you declare, unchanged — pick a unique name per device (that's what `--name` and `source_id=...` are for). Two devices that declare the same name write into the same source.
|
|
35
35
|
|
|
@@ -215,10 +215,10 @@ px.send("temperature", 72.5, timestamp=t) # your timestamp, used as-is, no cor
|
|
|
215
215
|
|
|
216
216
|
## Transport
|
|
217
217
|
|
|
218
|
-
By default the SDK connects over a **WebSocket** to `/ws/device` on the gateway —
|
|
218
|
+
By default the SDK connects over a **WebSocket** to `/ws/device` on the gateway — the gateway's device wire protocol. This gives you:
|
|
219
219
|
|
|
220
220
|
- lower-latency streaming of telemetry,
|
|
221
|
-
-
|
|
221
|
+
- the channel that will carry actions triggered from a Plexus dashboard.
|
|
222
222
|
|
|
223
223
|
If the socket is unavailable, sends transparently fall back to `POST /ingest` so no data is lost.
|
|
224
224
|
|
|
@@ -246,6 +246,8 @@ px.send("temperature", 72.5) # opens the socket, waits for auth
|
|
|
246
246
|
|
|
247
247
|
The SDK sends an `ack` frame before invoking the handler, then a `result` frame with whatever the handler returns (or an `error` frame if it raises).
|
|
248
248
|
|
|
249
|
+
> **Note:** nothing in Plexus can currently trigger a custom handler. The API route for sending commands was turned off on 2026-09-21. Triggering handlers from a dashboard, with permissions and a record of every run, is being rebuilt.
|
|
250
|
+
|
|
249
251
|
## Environment Variables
|
|
250
252
|
|
|
251
253
|
| Variable | Description | Default |
|
|
@@ -6,7 +6,7 @@ If you discover a security vulnerability in plexus-python, please report it resp
|
|
|
6
6
|
|
|
7
7
|
**Do NOT open a public GitHub issue for security vulnerabilities.**
|
|
8
8
|
|
|
9
|
-
Instead, email **
|
|
9
|
+
Instead, email **info@plexus.company** with:
|
|
10
10
|
|
|
11
11
|
- Description of the vulnerability
|
|
12
12
|
- Steps to reproduce
|
|
@@ -23,8 +23,8 @@ Instead, email **support@plexus.company** with:
|
|
|
23
23
|
|
|
24
24
|
| Version | Supported |
|
|
25
25
|
| ------- | --------- |
|
|
26
|
-
| 0.
|
|
27
|
-
| < 0.
|
|
26
|
+
| 0.11.x | Yes |
|
|
27
|
+
| < 0.11 | No |
|
|
28
28
|
|
|
29
29
|
## Disclosure Policy
|
|
30
30
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
WebSocket transport for the Plexus Python SDK.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Implements the gateway's device wire protocol. Targets its
|
|
5
5
|
`/ws/device` endpoint and exchanges the same JSON frames:
|
|
6
6
|
|
|
7
7
|
client → {"type": "device_auth", "api_key": ..., "source_id": ...,
|
|
@@ -392,7 +392,7 @@ class WebSocketTransport:
|
|
|
392
392
|
command = msg.get("command") or ""
|
|
393
393
|
params = msg.get("params") or {}
|
|
394
394
|
|
|
395
|
-
# Ack immediately
|
|
395
|
+
# Ack immediately, per the gateway wire contract
|
|
396
396
|
self._send_frame({
|
|
397
397
|
"type": "command_result",
|
|
398
398
|
"id": cmd_id,
|
|
@@ -547,8 +547,7 @@ def _safe_json(raw: Any) -> dict[str, Any]:
|
|
|
547
547
|
|
|
548
548
|
|
|
549
549
|
def _backoff_delay(attempt: int) -> float:
|
|
550
|
-
"""Exponential backoff with ±25% jitter, capped at BACKOFF_MAX_S.
|
|
551
|
-
Matches plexus_ws.c:44-52."""
|
|
550
|
+
"""Exponential backoff with ±25% jitter, capped at BACKOFF_MAX_S."""
|
|
552
551
|
base = min(BACKOFF_BASE_S * (2 ** attempt), BACKOFF_MAX_S)
|
|
553
552
|
jitter = base * 0.25 * (2 * random.random() - 1)
|
|
554
553
|
return max(0.1, base + jitter)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plexus
|
|
3
|
-
description: Integrate with the Plexus telemetry API — send data, query metrics, subscribe to live streams
|
|
3
|
+
description: Integrate with the Plexus telemetry API — send data, query metrics, subscribe to live streams. Use when the user mentions Plexus, plexus.company, gateway.plexus.company, plexus-data-api.fly.dev, or plx_ API keys. ALSO USE when the request involves IoT/hardware telemetry, fleet observability, sending sensor data to a backend, querying device time-series, building a fleet dashboard, monitoring drones/satellites/robots/edge devices, or any phrase like "send my sensor readings somewhere", "store telemetry", "track a fleet", "ingest metrics", or "device observability" — even if "Plexus" is never said.
|
|
4
4
|
tools: Read, Write, Edit, Bash, WebFetch
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ Plexus is a telemetry/observability platform for hardware fleets (drones, satell
|
|
|
13
13
|
Trigger on any of:
|
|
14
14
|
|
|
15
15
|
- The user mentions "Plexus", "plexus.company", `plx_` keys, `gateway.plexus.company`, or `plexus-data-api.fly.dev`
|
|
16
|
-
- The user wants to ingest telemetry, query device metrics, stream live points
|
|
16
|
+
- The user wants to ingest telemetry, query device metrics, or stream live points
|
|
17
17
|
- The user is building a dashboard, alert pipeline, or analysis on top of fleet telemetry
|
|
18
18
|
- The user pastes a Plexus curl example and asks for help
|
|
19
19
|
|
|
@@ -27,7 +27,7 @@ Two base URLs. Authenticate with an `x-api-key` header on HTTP.
|
|
|
27
27
|
| Purpose | Host |
|
|
28
28
|
| -------------------------------------------------- | -------------------------------- |
|
|
29
29
|
| Ingest | `https://gateway.plexus.company` |
|
|
30
|
-
| Read API (sources, metrics, logs, fleet,
|
|
30
|
+
| Read API (sources, metrics, logs, fleet, live stream) | `https://plexus-data-api.fly.dev` |
|
|
31
31
|
|
|
32
32
|
```
|
|
33
33
|
x-api-key: plx_...
|
|
@@ -141,9 +141,9 @@ You do **not** need to answer application-level pings on this endpoint; keepaliv
|
|
|
141
141
|
|
|
142
142
|
The gateway's own sockets (`/ws/device`, `/ws/browser`) are for the Python SDK and the Plexus app respectively. Don't write third-party clients against them. There is no `/v1/stream` on the gateway.
|
|
143
143
|
|
|
144
|
-
### Control
|
|
144
|
+
### Control
|
|
145
145
|
|
|
146
|
-
`POST /v1/sources/{id}/commands`
|
|
146
|
+
There is no API for sending commands to devices. `POST /v1/sources/{id}/commands` was turned off and returns `410 Gone`. Don't write code that calls it, and don't offer to trigger device behavior through the API.
|
|
147
147
|
|
|
148
148
|
## Standard scaffolding
|
|
149
149
|
|
|
@@ -216,7 +216,6 @@ let the pipeline settle first.
|
|
|
216
216
|
- **Telemetry frames are batched** — `points` is an array. Handling one frame as one point silently drops data.
|
|
217
217
|
- `start`/`end` are **ISO date-times on every endpoint** that takes them — `query`, `logs` and `fleet/metrics` alike. `last=1h` is easier and works on all three.
|
|
218
218
|
- `auto_downsampled: true` in a query response means the bucket size was picked for you — surface it in the UI so users understand what they're looking at.
|
|
219
|
-
- Commands queue on the device; they don't execute synchronously. Don't promise the user "it rebooted" — promise "reboot queued".
|
|
220
219
|
|
|
221
220
|
## When unsure
|
|
222
221
|
|
|
@@ -61,7 +61,7 @@ Four things the gateway will reject you for. Get these right or nothing lands:
|
|
|
61
61
|
1. **The array is `points`.** `"metrics": [...]` returns `400 {"error":"'points' array is required"}`.
|
|
62
62
|
2. **Every point needs `class`**, either `"metric"` or `"event"`. There is no default.
|
|
63
63
|
3. **`timestamp` is a number, never a string.** An ISO-8601 string returns `points[i].timestamp must be a number`. Epoch **milliseconds**; a positive value below `1e12` is read as **seconds** and scaled for you, so either unit works as long as it's numeric.
|
|
64
|
-
4. **`source_id` must match `^[a-z0-9][a-z0-
|
|
64
|
+
4. **`source_id` must match `^[a-z0-9][a-z0-9._-]*$` (max 256 chars)** and is not deduplicated — two devices declaring the same id merge into one source. This is what SD-card clones do when they all boot as `raspberrypi`.
|
|
65
65
|
|
|
66
66
|
`timestamp` is optional. Omit it and the gateway stamps the point with its receive time — which is the right move on a device whose clock has never been NTP-synced. Per-point `tags` (a flat string→string map) are supported and optional.
|
|
67
67
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Spins up a tiny `websockets`-based server on localhost that impersonates the
|
|
4
4
|
gateway's /ws/device endpoint and asserts the frames the SDK exchanges match
|
|
5
|
-
the
|
|
5
|
+
the gateway contract:
|
|
6
6
|
|
|
7
7
|
device_auth → authenticated → telemetry → heartbeat → typed_command roundtrip
|
|
8
8
|
"""
|
plexus_python-0.11.3/TODO.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# TODO
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
[Unit]
|
|
2
|
-
Description=Plexus Agent
|
|
3
|
-
Documentation=https://docs.plexus.company
|
|
4
|
-
After=network-online.target
|
|
5
|
-
Wants=network-online.target
|
|
6
|
-
|
|
7
|
-
[Service]
|
|
8
|
-
Type=simple
|
|
9
|
-
User=pi
|
|
10
|
-
ExecStart=/usr/local/bin/plexus start
|
|
11
|
-
Restart=always
|
|
12
|
-
RestartSec=10
|
|
13
|
-
Environment=PLEXUS_CONFIG_DIR=/home/pi/.plexus
|
|
14
|
-
|
|
15
|
-
# Logging
|
|
16
|
-
StandardOutput=journal
|
|
17
|
-
StandardError=journal
|
|
18
|
-
SyslogIdentifier=plexus
|
|
19
|
-
|
|
20
|
-
# Security hardening
|
|
21
|
-
NoNewPrivileges=true
|
|
22
|
-
ProtectSystem=strict
|
|
23
|
-
ProtectHome=read-only
|
|
24
|
-
ReadWritePaths=/home/pi/.plexus
|
|
25
|
-
|
|
26
|
-
[Install]
|
|
27
|
-
WantedBy=multi-user.target
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from smbus2 import SMBus
|
|
2
|
-
|
|
3
|
-
for bus in [0, 1, 10, 20, 21, 22]:
|
|
4
|
-
try:
|
|
5
|
-
s = SMBus(bus)
|
|
6
|
-
addrs = []
|
|
7
|
-
for a in range(0x03, 0x78):
|
|
8
|
-
try:
|
|
9
|
-
s.read_byte(a)
|
|
10
|
-
addrs.append(f'0x{a:02X}')
|
|
11
|
-
except OSError:
|
|
12
|
-
pass
|
|
13
|
-
s.close()
|
|
14
|
-
if addrs:
|
|
15
|
-
print(f'Bus {bus}: {addrs}')
|
|
16
|
-
except Exception as e:
|
|
17
|
-
print(f'Bus {bus}: {e}')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|