Metadata-Version: 2.4
Name: plexus-python
Version: 0.4.1
Summary: Thin Python SDK for Plexus — send telemetry in one line
Project-URL: Homepage, https://plexus.dev
Project-URL: Documentation, https://docs.plexus.dev
Project-URL: Repository, https://github.com/plexus-oss/plexus-python
Project-URL: Issues, https://github.com/plexus-oss/plexus-python/issues
Author-email: Plexus <hello@plexus.dev>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: fleet,hardware,iot,monitoring,observability,telemetry
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: System :: Hardware
Requires-Python: >=3.8
Requires-Dist: requests>=2.28.0
Requires-Dist: websocket-client>=1.7
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Requires-Dist: websockets>=12; extra == 'dev'
Description-Content-Type: text/markdown

# plexus-python

**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.

[![PyPI](https://img.shields.io/pypi/v/plexus-python)](https://pypi.org/project/plexus-python/)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)

## Quick Start

```bash
pip install plexus-python
```

```python
from plexus import Plexus

px = Plexus(api_key="plx_xxx", source_id="device-001")
px.send("temperature", 72.5)
```

Get an API key at [app.plexus.company](https://app.plexus.company) → Devices → Add Device.

## Device identity

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:

```bash
curl -sL https://app.plexus.company/setup | bash -s -- \
  --key plx_xxx --name drone-01
```

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`.

**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."

In normal code, you usually just pass `source_id=...` explicitly to `Plexus(...)` and never have to think about it.

## Usage

```python
from plexus import Plexus

px = Plexus(source_id="rig-01")   # reads PLEXUS_API_KEY from env

# Numbers
px.send("engine.rpm", 3450)
px.send("coolant.temperature", 82.3, tags={"unit": "C"})

# Strings, bools, objects, arrays — all JSON-serializable
px.send("vehicle.state", "RUNNING")
px.send("motor.enabled", True)
px.send("position", {"x": 1.5, "y": 2.3, "z": 0.8})

# Batch
px.send_batch([
    ("temperature", 72.5),
    ("pressure", 1013.25),
])

# Named run for grouping related data
with px.run("thermal-cycle-001"):
    while running:
        px.send("temperature", read_temp())
```

## Bring Your Own Protocol

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()`.

```python
# MAVLink (pymavlink)
for msg in conn:
    if msg.get_type() == "ATTITUDE":
        px.send("attitude.roll", msg.roll)

# CAN (python-can)
for msg in bus:
    px.send(f"can.0x{msg.arbitration_id:x}", int.from_bytes(msg.data, "big"))

# MQTT (paho-mqtt)
def on_message(_c, _u, msg):
    px.send(msg.topic.replace("/", "."), float(msg.payload))

# I2C sensor (Adafruit CircuitPython)
px.send("temperature", bme.temperature)
```

See [`examples/`](examples/) for runnable versions of each.

## Reliability

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:

```python
px = Plexus(persistent_buffer=True)
```

Point counts and flush:

```python
px.buffer_size()
px.flush_buffer()
```

## Transport

By default the SDK connects over a **WebSocket** to `/ws/device` on the gateway — same wire protocol as the C SDK. This gives you:

- lower-latency streaming of telemetry,
- live command delivery from the UI / API to the device.

If the socket is unavailable, sends transparently fall back to `POST /ingest` so no data is lost.

```python
# default — ws with http fallback
px = Plexus()

# force http (legacy)
px = Plexus(transport="http")
```

### Handling commands

Register a handler before the first `send()` so the command is advertised in the auth frame:

```python
def reboot(name, params):
    delay = params.get("delay_s", 0)
    # ... reboot logic ...
    return {"ok": True, "delay": delay}

px = Plexus()
px.on_command("reboot", reboot, description="reboot the device")
px.send("temperature", 72.5)   # opens the socket, waits for auth
```

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).

## Environment Variables

| Variable                | Description                  | Default                          |
| ----------------------- | ---------------------------- | -------------------------------- |
| `PLEXUS_API_KEY`        | API key (required)           | none                             |
| `PLEXUS_GATEWAY_URL`    | HTTP ingest URL              | `https://plexus-gateway.fly.dev` |
| `PLEXUS_GATEWAY_WS_URL` | WebSocket URL              | `wss://plexus-gateway.fly.dev`   |

## Architecture

```
Your code ── px.send() ── HTTP POST /ingest ──> plexus-gateway ──> ClickHouse + Dashboard
```

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.

## License

Apache 2.0
