Metadata-Version: 2.4
Name: pyswann
Version: 0.0.2
Summary: Python client for Swann DVRs
Author-email: brendann993 <brendann993@icloud.com>
License-Expression: MIT
Project-URL: Documentation, https://github.com/brendann993/pyswann#readme
Project-URL: Issues, https://github.com/brendann993/pyswann/issues
Project-URL: Source, https://github.com/brendann993/pyswann
Project-URL: Download, https://github.com/brendann993/pyswann/releases
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Home Automation
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.9.0
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: coverage>=7.6; extra == "dev"
Requires-Dist: ruff>=0.15.1; extra == "dev"
Requires-Dist: twine>=6.0; extra == "dev"
Dynamic: license-file

# pyswann

An unofficial, environment-agnostic, pure Python client under development for
Swann DVR/NVR hardware using the RaySharp client service on TCP port 9000.

This project is not affiliated with or endorsed by Swann or RaySharp. Protocol
support has been tested against one Swann recorder and firmware version; other
models and firmware may behave differently.

The project deliberately has no dependency on RaySharp's DLLs or shared
libraries. Protocol behaviour is added only after it is supported by SDK source
evidence and confirmed against the physical Swann recorder.

## Transport policy

This library is intentionally TCP-first. The native RaySharp client service on
TCP port 9000 remains the default path for authentication, session lifecycle,
heartbeat maintenance, runtime status, and live event delivery. The web API on
port 85 is kept for supplementary metadata and configuration enrichment when it
exposes the relevant data, but it is not the live runtime transport and it is
not the default path for motion or alarm handling.

Compatibility runs that intentionally prefer HTTP are useful as diagnostics and
coverage comparisons, but they are not the supported default architecture.

## Current capability

The package currently provides:

- a bounded asyncio TCP transport;
- explicit connection/session state;
- recorder-confirmed encrypted RaySharp login and response parsing;
- a managed authenticated session with encrypted type-32 heartbeats and a
  single protocol reader;
- framed, decrypted motion-alarm reception (`891/191`);
- combined pedestrian/vehicle (PD+VD) decoding with explicit ON/OFF state
  (`891/251`);
- a typed async event iterator currently yielding `MotionEvent` and `PdVdEvent`;
- typed read-only recorder and channel metadata from the HTTP API;
- lossless timestamped capture of every received byte;
- a diagnostic CLI that runs anywhere Python 3.12 runs;
- no dependency on any higher-level application framework; the library is
  intentionally standalone and reusable outside Home Assistant.

The physical recorder now confirms authentication, heartbeat maintenance, and
automatic motion-report delivery. Parallel channel/status bitmasks are decoded
into explicit per-channel ON/OFF transitions.

Only `MotionEvent` and `PdVdEvent` are currently implemented because those are
the event types validated against the available test recorder. RaySharp devices
may emit additional alarm families, but they remain unsupported until they can
be captured and decoded reliably.

## Managed client

The primary library API owns authentication, heartbeat maintenance, packet
reading, and typed event dispatch for the lifetime of the context manager:

```python
from swann import MotionEvent, PdVdEvent, SwannClient

client = SwannClient(
    "ip.address or hostname",
    username="admin",
    password="recorder-password",
)

async with client:
    info = client.device_info
    if info is not None:
        print(info.model, info.firmware_version, info.preferred_identifier)

    async for event in client.events():
        if isinstance(event, MotionEvent):
            print("motion", event.channel, event.active)
        elif isinstance(event, PdVdEvent):
            print("PD+VD", event.channel, event.active)
```

`RaySharpClient` remains available as a compatibility alias for existing
callers; new code should use `SwannClient`.

`PdVdEvent` intentionally represents the combined DVR category. Controlled
captures show that pedestrian and vehicle detections use the same wire subtype
and alarm value, so the low-level library does not invent a distinction that
the recorder does not transmit.

After authentication, `client.device_info` contains confirmed `LoginRsp`
fields including model, recorder name, firmware version, serial number, MAC
address, channel counts, and raw advertised push/control flags. The preferred
stable identifier uses the serial number when present and otherwise falls back
to the MAC address.

The managed client can also collect supplementary HTTP metadata after TCP login.
`client.http_metadata` contains the recorder model, hardware and software
versions, storage/video details, and typed channel aliases, enabled states, and
stream descriptions. HTTP failure is exposed as `client.http_metadata_error`
and does not stop TCP event delivery. TCP port 9000 remains the primary
transport for the live session, heartbeat management, and runtime event/state
information. The HTTP API serves as a fallback/enrichment source for
human-readable metadata and configuration whenever it exposes the relevant data.

Each `ChannelMetadata` also exposes `mainstream_url` and `substream_url`.
These use explicit URL fields when a recorder returns them; otherwise they use
the confirmed RaySharp RTSP path with the channel number formatted as `01`,
`02`, and so on.

The client accepts an injected `aiohttp.ClientSession` when a caller already has
one, but it never closes a caller-owned session. For standalone use, omitting
`websession` creates an internal session which is closed with the client. The
recorder HTTP flow uses Digest authentication plus the returned CSRF token and
session cookie. Credentials are not logged, and the sensitive P2P identifier
returned by the recorder is intentionally not included in `SystemMetadata`.

HTTP metadata collection includes the Motion alarm configuration endpoint. It
exposes Motion through `IntelligentAlarmType.MOTION` with the typed channel
configuration model, and this is the preferred config source when the recorder
provides it.

Compatibility and fallback helpers still exist for low-level TCP parameter
queries via `await client.refresh_alarm_configurations()`, but project policy is
to keep TCP as the primary transport for live alarms/events and runtime state,
with HTTP as the fallback/enrichment source for recorder metadata and
configuration when it exposes the relevant data.

TCP media/configuration refreshes remain available as compatibility helpers,
but they are not the default path for normal operation and should not be used
as an eager fallback simply because another transport is slower. The slow
optional Living/preview page 573 is disabled by default and must be requested
explicitly with `include_living=True`.

HDD, Motion, Abnormal, and AVD status pages are still available as optional
TCP compatibility queries, but they are not the primary source when the web
API exposes equivalent data. Set `collect_status_configurations=False` to skip
these pages entirely. Refresh failures remain available through
`client.status_configuration_errors`, and the last attempt time is exposed as
`client.status_configuration_refreshed_at`.

Alarm configuration uses a primary/fallback policy. TCP remains the primary
transport for live runtime status and compatibility pages, while HTTP is used
as the fallback/enrichment source for human-readable configuration when it
exposes the values. A TCP query should not be replaced by HTTP merely because
it is slower; fallback should happen only on actual failure or missing data.

Use `--http-primary-test --status-output captures/http-primary.json` only as an
explicit compatibility probe. This collects system/channel metadata and alarm
configuration through HTTP so the project can compare HTTP coverage against the
TCP-first runtime path. TCP remains responsible for authentication, heartbeats,
and live alarm events. It does not query TCP alarm configuration or the other
optional TCP status pages unless explicitly requested for compatibility testing.

## Diagnostic connection

Copy `.env.example` to `.env`, then replace the password value. `.env` is
ignored by Git and loaded automatically by the diagnostic CLI:

```bash
cp .env.example .env
```

```dotenv
RAYSHARP_HOST=ip.address or hostname
RAYSHARP_PORT=9000
RAYSHARP_HTTP_PORT=85
RAYSHARP_USERNAME=admin
RAYSHARP_PASSWORD=your-recorder-password
RAYSHARP_LOGIN_VARIANT=crypto
```

Then run from the project directory:

```bash
py -m swann.cli --output captures/connection.jsonl
```

To write an authenticated TCP status snapshot for review, use
`--status-output`. This reports recorder identity, channel names and
availability, motion/recording/video-loss masks, and intelligent-analysis
capabilities without writing credentials or the raw login request:

```bash
py -m swann.cli --status-output captures/status.json
```

The status snapshot can be combined with the raw JSON Lines capture:

```bash
py -m swann.cli --output captures/connection.jsonl --status-output captures/status.json
```

Existing exported environment variables take precedence over `.env`. Set
`PYSWANN_ENV_FILE` to load a different file.

PowerShell environment variables are also supported:

```powershell
$env:RAYSHARP_HOST = "ip.address or hostname"
$env:RAYSHARP_PORT = "9000"
$env:RAYSHARP_USERNAME = "admin"
$env:RAYSHARP_PASSWORD = "your-recorder-password"

py -m swann.cli --output captures\connection.jsonl
```

This sends the recorder-confirmed encrypted `CryptoMsgLogin` packet and then an
encrypted heartbeat every five seconds. Password-bearing transmit packets are
always omitted from logs and captures. Incoming motion reports are logged as
human-numbered channel transitions while retaining both raw bitmasks.

See [`docs/protocol.md`](docs/protocol.md) for established findings, observed
packet layouts, and remaining protocol questions.
