Metadata-Version: 2.4
Name: matter-python-client
Version: 1.5.0a0.dev20261014
Summary: Python Client for the OHF Matter Server
Author-email: Open Home Foundation <hello@openhomefoundation.io>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/matter-js/matterjs-server
Project-URL: Source, https://github.com/matter-js/matterjs-server/tree/main/python_client
Project-URL: Bug Tracker, https://github.com/matter-js/matterjs-server/issues
Project-URL: Changelog, https://github.com/matter-js/matterjs-server/blob/main/CHANGELOG.md
Platform: any
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Home Automation
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: aiohttp
Requires-Dist: dacite
Requires-Dist: orjson
Provides-Extra: test
Requires-Dist: codespell==2.4.1; extra == "test"
Requires-Dist: isort==7.0.0; extra == "test"
Requires-Dist: mypy==1.19.1; extra == "test"
Requires-Dist: pylint==4.0.4; extra == "test"
Requires-Dist: pytest>=9.0; extra == "test"
Requires-Dist: pytest-asyncio>=0.24; extra == "test"
Requires-Dist: pytest-aiohttp>=1.0; extra == "test"
Requires-Dist: pytest-cov>=7.0; extra == "test"
Requires-Dist: ruff==0.14.9; extra == "test"

# Python Client for the OHF Matter Server

A PyPI package (`matter-python-client`) providing Python bindings for the [OHF Matter Server](https://github.com/matter-js/matterjs-server). This is a drop-in replacement for the client portion of [`python-matter-server`](https://github.com/matter-js/python-matter-server), with custom cluster definitions updated to match the Matter.js server.

## Origin

The client and common modules were copied from [`python-matter-server` v8.1.2](https://github.com/matter-js/python-matter-server) and modified:

- **Source**: `matter_server/client/` and `matter_server/common/` from python-matter-server
- **Modified**: `matter_server/common/custom_clusters.py` — updated to match the JS server's cluster definitions
- **Excluded**: `matter_server/server/` (the JS server replaces it), `common/helpers/logger.py` (depends on `coloredlogs`, a server-only dependency)

### Custom Cluster Changes

| Cluster | Change |
|---------|--------|
| EveCluster | +8 attributes (getConfig, setConfig, loggingMetadata, loggingData, lastEventTime, statusFault, childLock, rloc16), wattAccumulatedControlPoint type fixed |
| HeimanCluster | Attribute IDs shortened to match JS server |
| NeoCluster | Types changed from float32 to uint |
| Polling | Removed (JS server handles polling natively) |

## Package

- **PyPI name**: `matter-python-client`
- **Python module**: `matter_server` (same import path as `python-matter-server`)
- **Python**: >= 3.12
- **Dependencies**: `aiohttp`, `orjson`, `home-assistant-chip-clusters`

## npm Scripts (from monorepo root)

```bash
# First-time setup: create venv and install with test dependencies
npm run python:install

# Run unit + mock server tests (fast, ~1s)
npm run python:test

# Run full integration tests against real Matter.js server + test device (~40s)
npm run python:test-integration

# Run all Python tests
npm run python:test-all

# Build the PyPI package (.tar.gz + .whl)
npm run python:build
```

## Usage

The package provides the same `matter_server.client` API as `python-matter-server`:

```python
import aiohttp
from matter_server.client import MatterClient

async with aiohttp.ClientSession() as session:
    client = MatterClient("ws://localhost:5580/ws", session)
    await client.connect()

    init_ready = asyncio.Event()
    listen_task = asyncio.create_task(client.start_listening(init_ready))
    await init_ready.wait()

    nodes = client.get_nodes()
```

To reach a server that listens on a unix socket (`--listen-address /path/to/socket`), pass
`unix:///path/to/socket` as the server URL. The client then opens its own session on that socket
for the WebSocket and the OTA upload, and the session passed in stays unused.

### OHF-only extensions

Beyond the `python-matter-server` API, the client exposes commands the Matter.js server adds:

- `get_network_topology(refresh=False)` — the Thread/Wi-Fi network as a graph (schema 13+)
- `upload_ota_file(image)` — store a local `.ota` firmware image in the server's OTA image store
  (schema 13+). Takes the raw bytes or a path; reserves an upload id over the WebSocket session,
  then streams the bytes to `POST /ota-upload/<upload_id>`. Raises `OtaUploadError` (code 101) for a
  corrupt image, an expired/spent id, or disabled OTA support.
- `get_icd_state`, `register_icd`, `unregister_icd`, `resync_icd` — ICD Check-In registration
- `camera_get_capabilities`, `camera_start_stream`, `camera_provide_answer`,
  `camera_provide_ice_candidates`, `camera_stop_stream`, `camera_snapshot`, `camera_release_stream`
  — the camera API (schema 14+). `camera_get_capabilities`, `camera_start_stream`, `camera_stop_stream`
  and `camera_snapshot` return dataclasses from `matter_server.common.models`; the other three return
  `None`.
  `camera_start_stream`'s `video` / `audio` take `None` (the server decides), a `CameraVideoHints` /
  `CameraAudioHints` dict, or `False` (no such track). `talkback=True` lets the offer ask the camera to
  play your audio; without it the server makes the offer's audio sections not send, so a browser-style
  `sendrecv` audio offer goes to the camera as `recvonly`. `camera_session_ended` and
  `camera_stream_evicted` events arrive as `CameraSessionEndedData` / `CameraStreamEvictedData`;
  `webrtc_callback` stays a dict. Errors 102 to 106 (`CameraStreamIncompatible`, ...) expose their
  details as attributes, e.g. `err.reason` and `err.bound` on `CameraStreamIncompatible`

  The minimal `camera_start_stream` call is `node_id`, `endpoint_id`, `stream_usage` and your SDP
  offer: the server reuses or allocates the best stream the camera can serve that still fits what
  the offer can decode, and adds audio when it can. Pass `video` / `audio` hints only to set hard bounds. A
  session opened this way is driven with `camera_provide_answer`, `camera_provide_ice_candidates`
  and `camera_stop_stream` only, not with `send_webrtc_provider_command` or a `send_device_command`
  `EndSession`: the server tracks the session and its streams, and only the `camera_*` methods keep
  that record correct. The exception is a re-offer (ICE restart), which goes through
  `send_webrtc_provider_command`. See `docs/websockets_api.md` for the flow and every option.

```python
    try:
        result = await client.camera_start_stream(node_id, 1, "LiveView", sdp=offer)
    except CameraStreamIncompatible as err:
        print(err.reason, err.bound)
    else:
        # The camera's answer arrives as a webrtc_callback "answer" event for this session.
        print(result.webrtc_session_id, result.video.codec if result.video else None)
```

```python
    version = await client.upload_ota_file("/path/to/firmware.ota")
    print(version.vid, version.pid, version.software_version)
```

To use this package instead of `python-matter-server` in the Home Assistant Matter integration, change `manifest.json`:

```diff
- "requirements": ["python-matter-server==8.1.2"],
+ "requirements": ["matter-python-client==1.0.0"],
```

## Testing

The test suite has three layers:

- **Import smoke tests** (8 tests) — verify all HA integration imports resolve
- **Mock server tests** (4 tests) — Python client against JS MockMatterServer subprocess
- **Integration tests** (63 tests) — Python client against real Matter.js server + test device, mirroring the JS `IntegrationTest.ts`
