Metadata-Version: 2.4
Name: pyrustuyabridge
Version: 0.2.0rc24
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Home Automation
Classifier: Topic :: System :: Networking
Requires-Dist: pytest ; extra == 'test'
Requires-Dist: paho-mqtt>=2 ; extra == 'test'
Requires-Dist: tuyamock ; extra == 'test'
Provides-Extra: test
Summary: Python bindings for rustuyabridge — an MQTT bridge for Tuya devices
Keywords: tuya,mqtt,iot,bridge,smart-home,automation
Author: 3735943886
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Project-URL: Homepage, https://github.com/3735943886/rustuya-bridge
Project-URL: Issues, https://github.com/3735943886/rustuya-bridge/issues
Project-URL: Repository, https://github.com/3735943886/rustuya-bridge

# pyrustuyabridge

Python bindings for [rustuya-bridge](https://github.com/3735943886/rustuya-bridge),
an MQTT bridge for managing Tuya devices via the Tuya Local API.

This package exposes the bridge server as an embeddable component, so Python
code can run it alongside and interpret MQTT topics/payloads identically to
the native bridge.

## Install

```bash
pip install pyrustuyabridge
```

Pre-built wheels are provided for Linux (manylinux2014 / musllinux_1_2 on
x86_64 and aarch64), Windows x64, and macOS (x86_64 / arm64). Wheels are
built with PyO3 `abi3-py39`, so a single wheel per platform supports
CPython 3.9 and newer.

## Quick start

```python
import asyncio
from pyrustuyabridge import PyBridgeServer

async def main():
    server = PyBridgeServer(
        mqtt_broker="mqtt://localhost:1883",
        mqtt_root_topic="rustuya",
    )
    await server.start_async()

asyncio.run(main())
```

## Keeping the bridge running across `reconfigure`

The bridge is meant to be driven entirely over MQTT — including config changes.
To apply a change to the topic/payload templates or `mqtt_retain`, edit the
`config_path` file and publish a `reconfigure` action to the command topic. The
bridge clears the retained messages published under the old scheme and then
**exits cleanly** so the new config can take effect — exactly like the native
binary under `systemd Restart=always`.

In an embedded host there is no external supervisor, so wrap the server in a
small recreate loop. An intentional shutdown stops the *supervisor* (not the
bridge); any other exit — `reconfigure` or a crash — recreates it, re-reading
`config_path`:

```python
# asyncio
import asyncio
from pyrustuyabridge import PyBridgeServer

class AsyncBridgeSupervisor:
    def __init__(self, **kwargs):
        self._kwargs = {**kwargs, "no_signals": True}  # host owns signals
        self._stop, self._server = False, None

    async def run(self):
        while not self._stop:
            self._server = PyBridgeServer(**self._kwargs)  # re-reads config_path
            try:
                await self._server.start_async()           # returns on reconfigure/stop
            except Exception as e:
                print("bridge exited with error, restarting:", e)
                await asyncio.sleep(5)

    def stop(self):
        self._stop = True
        if self._server:
            self._server.stop()

# asyncio.run(AsyncBridgeSupervisor(config_path="/etc/rustuya/config.json").run())
```

```python
# thread
import threading, time
from pyrustuyabridge import PyBridgeServer

class BridgeSupervisor:
    def __init__(self, **kwargs):
        self._kwargs = {**kwargs, "no_signals": True}
        self._stop = threading.Event()
        self._server = None

    def run(self):                                   # run in a thread (blocking)
        while not self._stop.is_set():
            self._server = PyBridgeServer(**self._kwargs)
            try:
                self._server.start()                 # blocks until reconfigure/stop
            except Exception as e:
                print("bridge exited with error, restarting:", e)
                time.sleep(5)

    def stop(self):
        self._stop.set()
        if self._server:
            self._server.stop()
```

**Config precedence is kwargs > `config_path` file > defaults** (the file only
fills fields left unset). So put any field you want to change via
`reconfigure` in the **file**, not in kwargs — a field passed as a kwarg wins
and the file edit is ignored (same rule as CLI flags vs. config file in the
native binary).

## Helpers

The module also exposes topic/payload utilities for code that needs to
match the bridge's wire format:

- `tpl_to_wildcard(template, root_topic)` — template → MQTT wildcard.
- `match_topic(topic, template)` — returns extracted variables or `None`.
- `render_template(template, vars)` — substitutes `{key}` placeholders.
- `parse_payload(payload, vars)` — parses an MQTT payload into a structured value.
- `parse_payload_with_template(payload, template)` — reverse of `render_template`:
  recovers placeholder values from a rendered payload, or `None` if not reversible.
- `parse_seed_dps(payload, dp=None, template=None)` — extracts the DPS map from an
  event payload exactly as the bridge's seed phase does; pass `dp` for single-DP
  (`{dp}`) topics, `None` for multi-DP topics whose payload is the full DPS object.
- `validate_payload_template(template)` — `(ok, message)`; checks whether a payload
  template can be reverse-parsed.

See the [project repository](https://github.com/3735943886/rustuya-bridge)
for full documentation.

