Metadata-Version: 2.4
Name: psmovebridge
Version: 0.1.1
Summary: Python client & 6DoF telemetry bridge for PSMoveServiceEx
Home-page: https://github.com/fullyohan/psmovebridge
Author: Yohan Konan
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: protobuf>=3.20.0
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# PSMoveBridge

**PSMoveBridge** is an asynchronous, lightweight Python library engineered to interface directly with **PSMoveServiceEx** via TCP and UDP sockets. It receives real-time 6DoF / 3DoF positional tracking streams and seamlessly relays them to external applications such as OpenTrack, FreeTrack, etc...

---

## 🛠️ Features

* **Dual-Mode TCP/UDP Protocol:**
  * **TCP (Port 9512):** Handshake negotiation, session connection ID retrieval, subscription requests, and ACK handling.
  * **UDP (Port 9512):** High-frequency, low-latency stream of serialized `DeviceOutputDataFrame` packets.
* **Built-in Protobuf Serialization:** Direct parsing of native PSMoveServiceEx Protobuf objects (`hmd_data_packet`, `virtual_hmd_state`).

---

## 📦 Project Architecture

```text
psmovebridge/
├── psmovebridge/
│   ├── __init__.py    
│   ├── client.py          # PSMoveClient: Socket manager and event loop
│   ├── tracker.py         # HMDData: Pose data abstraction and state updates
│   └── protocol_pb2.py    # Compiled PSMoveServiceEx Protobuf bindings
├── examples/
│   └── opentrack_example.py # Binary UDP packet relay for OpenTrack
├── .gitignore             # Git exclusion rules
├── LICENSE                # MIT License
├── README.md
└── setup.py               # Package installation configuration

```

---

## 🚀 Installation

### Option 1: Via PyPI (Recommended)

Once published, you can install the latest stable version of `psmovebridge` directly from PyPI:

```bash
pip install psmovebridge

```

---

### Option 2: From Source (Local / Development)

If you want to contribute to the project, run the latest code, or modify the library locally:

1. **Clone the repository:**

```bash
git clone https://github.com/fullyohan/psmovebridge.git
cd psmovebridge

```

2. **Install in editable mode:**

```bash
pip install -e .

```



> **Note:** The `-e` (editable) flag links the package directly to your source directory. Any changes made to the code inside `psmovebridge/` will immediately reflect across your environment without needing a reinstall.

---

## 💻 Usage Examples

### 1. OpenTrack Bridge (UDP Relay)

This script intercepts HMD 0's pose from PSMoveServiceEx and forwards it over UDP in OpenTrack's expected binary format (Port 4242):

```python
import socket
import struct
from psmovebridge import PSMoveClient

# OpenTrack socket settings
OPENTRACK_IP = "127.0.0.1"
OPENTRACK_PORT = 4242
sock_opentrack = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)


def process_pose(hmd):
    # Extract coordinates (x, y, z) and Euler angles (Yaw, Pitch, Roll)
    x, y, z = hmd.position.x, hmd.position.y, hmd.position.z
    yaw, pitch, roll = hmd.orientation.yaw, hmd.orientation.pitch, hmd.orientation.roll

    # Pack binary payload expected by OpenTrack's "UDP over network" input (6 x float64 / double)
    payload = struct.pack("dddddd", x, y, z, yaw, pitch, roll)
    sock_opentrack.sendto(payload, (OPENTRACK_IP, OPENTRACK_PORT))


def main():
    client = PSMoveClient(ip="127.0.0.1", port=9512)

    print("[PSMoveBridge] Connecting to service...")
    client.connect()

    print("[PSMoveBridge] Subscribing to HMD ID 0 stream...")
    client.subscribe_hmd(hmd_id=0)

    print("[PSMoveBridge] Streaming active. Press Ctrl+C to stop.")
    client.listen(callback=process_pose)


if __name__ == "__main__":
    main()

```

### 2. Basic Console Monitoring

```python
from psmovebridge import PSMoveClient


def print_hmd_info(hmd_data):
    pos = hmd_data.position
    print(f"\r[HMD] Pos -> X: {pos.x:.2f} | Y: {pos.y:.2f} | Z: {pos.z:.2f}", end="")


client = PSMoveClient()
client.connect()
client.subscribe_hmd(0)
client.listen(callback=print_hmd_info)

```

---

## ⚙️ Protocol & Handshake Breakdown

The client executes the following sequence during initialization:

1. **Local UDP Binding:** Binds an ephemeral port and applies `SIO_UDP_CONNRESET` fix for Windows stability.
2. **TCP Handshake:** Connects to port `9512` to receive the initial `tcp_connection_id` generated by PSMoveServiceEx.
3. **UDP Binding Packet:** Transmits a `DeviceInputDataFrame` over UDP containing the assigned `tcp_connection_id` to link the UDP socket session on the server.
4. **Stream Subscription:** Sends a `START_HMD_DATA_STREAM` TCP request ordering the server to begin broadcasting `DeviceOutputDataFrame` UDP packets.

---

## 📜 License & Credits

* **License:** Distributed under the [MIT License](https://github.com/fullyohan/psmovebridge/LICENSE).
* **Credits:** Protobuf definitions (`PSMoveProtocol.proto`) are derived from the [PSMoveServiceEx](https://github.com/Timocop/PSMoveServiceEx) project, licensed under MIT.

---
