Metadata-Version: 2.4
Name: zendo-sdk
Version: 0.1.3
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Rust
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries
Requires-Dist: pytest>=8 ; extra == 'dev'
Requires-Dist: websockets>=12 ; extra == 'dev'
Requires-Dist: ruff>=0.6 ; extra == 'dev'
Requires-Dist: ty>=0.0.1 ; extra == 'dev'
Provides-Extra: dev
License-File: LICENSE-APACHE
License-File: LICENSE-MIT
Summary: Python client for the Zendo motion-tracking WebSocket stream.
Keywords: zendo,motion-capture,pose,websocket,robotics
Author-email: Akina <michele.xiloyannis@akina.health>
License-Expression: Apache-2.0 OR MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/akina-health/zendo
Project-URL: Issues, https://github.com/akina-health/zendo/issues
Project-URL: Repository, https://github.com/akina-health/zendo

# zendo-sdk (Python)

[![PyPI](https://img.shields.io/pypi/v/zendo-sdk.svg)](https://pypi.org/project/zendo-sdk/)
[![Python versions](https://img.shields.io/pypi/pyversions/zendo-sdk.svg)](https://pypi.org/project/zendo-sdk/)

Python client for the [Zendo](https://github.com/akina-health/zendo)
motion-tracking WebSocket stream. Connect to a running Zendo instance and get
typed quaternion and landmark events — no byte-parsing, no port hunting.

The package is a thin layer over the Rust [`zendo-sdk`](https://crates.io/crates/zendo-sdk)
crate (via PyO3), so decoding and the connection lifecycle are handled in Rust.
It imports as `zendo`.

## Install

```bash
pip install zendo-sdk
```

Wheels are published for Linux (x86_64, aarch64), macOS (Apple Silicon and
Intel), and Windows (x86_64), on CPython 3.10 and newer.

## Quickstart

The client is **synchronous and blocking**: iterating it waits for the next
frame. This keeps it ergonomic in notebooks and scripts.

```python
import zendo

with zendo.connect() as client:          # auto-discovers the port (5432-5435)
    print(f"connected on port {client.port}")
    for event in client:
        match event:
            case zendo.BodyQuaternions(frame):
                print("hips:", frame.hips.w, frame.hips.x, frame.hips.y, frame.hips.z)
            case zendo.BodyLandmarks(frame):
                print("nose:", frame.nose.x, frame.nose.y, frame.nose.z)
            case zendo.HandQuaternions(side, frame):
                print(side, "wrist:", frame.wrist.w)
            case zendo.HandLandmarks(side, frame):
                print(side, "thumb tip:", frame.thumb_tip.x, frame.thumb_tip.confidence)
```

Without structural pattern matching:

```python
import zendo

with zendo.connect() as client:
    for event in client:
        if isinstance(event, zendo.BodyQuaternions):
            print(event.frame.hips)
```

## Connecting

| Function | Behaviour |
|---|---|
| `zendo.connect()` | Scans ports 5432-5435 on `127.0.0.1`, connects to the first Zendo found. |
| `zendo.connect_at(port)` | Connects to an explicit port on `127.0.0.1`. |
| `zendo.connect_url(url)` | Connects to an explicit `ws://host:port` URL. |

To reach a Zendo on another machine, copy the address from Zendo's Settings:
`zendo.connect_url("ws://192.168.1.42:5432")`. The stream is plain `ws://` —
unencrypted and unauthenticated — so only use it on a trusted network.

All three accept `reconnect=True` to transparently re-establish a dropped
connection with exponential backoff:

```python
client = zendo.connect(reconnect=True, base_delay_ms=250, max_retries=10)
```

## Types

Events are immutable dataclasses. Frames expose one attribute per joint or
landmark — `frame.hips`, `frame.left_wrist`, `frame.thumb_tip` — each a
`Quaternion` (`w, x, y, z`) or `Landmark` (`x, y, z, confidence`). Frames are
also iterable: `for name, value in frame: ...`.

The package ships type information (`py.typed`) and is checked with
[ty](https://github.com/astral-sh/ty) and linted with
[ruff](https://docs.astral.sh/ruff/).

## Offline decoding

`zendo.decode_frame(data: bytes)` decodes a single raw protocol frame into a
typed event — handy for replaying captures or testing.

## License

Licensed under either of Apache-2.0 or MIT at your option.

