aiopyronix 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ .coverage*
8
+ coverage.xml
9
+ htmlcov/
10
+ *.log
11
+ .env*
12
+ dist/
13
+ build/
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ This project follows [Semantic Versioning](https://semver.org/). Until 1.0, a
4
+ minor version may change the public API.
5
+
6
+ ## 0.1.0
7
+
8
+ First release. The PyronixCloud client and panel protocol move out of the
9
+ [Pyronix HomeControl integration](https://github.com/Herbertmt978/HA-Pyronix)
10
+ 0.3.0 unchanged, with a public API, type information and full branch test
11
+ coverage.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Herbertmt978
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.5
2
+ Name: aiopyronix
3
+ Version: 0.1.0
4
+ Summary: Asynchronous PyronixCloud client for Pyronix alarm panels, with explicit sessions
5
+ Project-URL: Source, https://github.com/Herbertmt978/aiopyronix
6
+ Project-URL: Issues, https://github.com/Herbertmt978/aiopyronix/issues
7
+ Project-URL: Changelog, https://github.com/Herbertmt978/aiopyronix/blob/main/CHANGELOG.md
8
+ Author: Herbertmt978
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: alarm,asyncio,home-assistant,homecontrol,pyronix
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Home Automation
20
+ Classifier: Topic :: Security
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.14
23
+ Requires-Dist: aiohttp>=3.13.0
24
+ Requires-Dist: cryptography>=47.0.0
25
+ Description-Content-Type: text/markdown
26
+
27
+ # aiopyronix
28
+
29
+ [![PyPI](https://img.shields.io/pypi/v/aiopyronix)](https://pypi.org/project/aiopyronix/)
30
+ [![CI](https://github.com/Herbertmt978/aiopyronix/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Herbertmt978/aiopyronix/actions/workflows/ci.yml)
31
+ [![Licence: MIT](https://img.shields.io/badge/Licence-MIT-0F766E.svg)](https://github.com/Herbertmt978/aiopyronix/blob/main/LICENSE)
32
+
33
+ An asynchronous Python client for Pyronix alarm panels reached through
34
+ PyronixCloud, the service used by the HomeControl 2.0 app.
35
+
36
+ It powers the [Pyronix HomeControl integration for Home Assistant](https://github.com/Herbertmt978/HA-Pyronix),
37
+ and it can be used on its own. This is independent community software. It is
38
+ not an official Pyronix, Hikvision or Home Assistant product.
39
+
40
+ ## Design
41
+
42
+ The client controls an intruder alarm, so caution takes priority over convenience:
43
+
44
+ - **Explicit sessions.** Nothing connects when a `PanelClient` is created. A
45
+ session opens only when `connect()` is called. There is no polling or
46
+ automatic reconnection.
47
+ - **Idle sessions close.** A session closes after five minutes without a
48
+ `connect()` or `control()` call. Heartbeats keep the socket alive, but they
49
+ do not extend that limit.
50
+ - **Commands are sent once.** `control()` arms or disarms exactly one area and
51
+ waits for the panel to confirm it. When the outcome cannot be confirmed, it
52
+ raises `CommandUnconfirmedError`. The command is never repeated, even after
53
+ a lost connection.
54
+ - **A narrow command set.** Only single-area arm and disarm are possible. The
55
+ library has no forced omissions, outputs, installer functions or cloud
56
+ account changes.
57
+ - **Nothing sensitive is logged.** The library does not log. Its error
58
+ messages are fixed text and never contain credentials, panel data or wire
59
+ payloads.
60
+
61
+ Status updates and commands share one WebSocket. Each frame is encrypted with
62
+ a session key agreed through ECDH (P-256) and checked for integrity and
63
+ sequence.
64
+
65
+ ## Installation
66
+
67
+ ```bash
68
+ pip install aiopyronix
69
+ ```
70
+
71
+ Python 3.14 or later is required.
72
+
73
+ ## Connection details
74
+
75
+ `PanelClient` needs the connection details that the HomeControl 2.0 app saves
76
+ when you sign in:
77
+
78
+ | Field | Meaning |
79
+ | --- | --- |
80
+ | `AppUser`, `PushId`, `DeviceId` | The app installation registered with PyronixCloud |
81
+ | `PanelId` | The eight-character panel identifier |
82
+ | `SystemName` | The panel name shown in the app |
83
+ | `PanelPwd` | The app password stored for the panel |
84
+ | `UserCode` | The panel user code, four to eight digits |
85
+
86
+ `AppVersion`, `AppType`, `OSName`, `OSVersion`, `NetworkType` and `AppLang`
87
+ are optional. Other keys are discarded. The Home Assistant integration
88
+ includes a [setup helper](https://github.com/Herbertmt978/HA-Pyronix#connect-your-own-alarm)
89
+ that reads these details from your own signed-in app.
90
+
91
+ ## Usage
92
+
93
+ ```python
94
+ import asyncio
95
+ import json
96
+ from pathlib import Path
97
+
98
+ import aiohttp
99
+
100
+ from aiopyronix import CommandUnconfirmedError, PanelClient
101
+
102
+
103
+ async def main() -> None:
104
+ auth = json.loads(Path("pyronix.json").read_text())
105
+ async with aiohttp.ClientSession() as http:
106
+ client = PanelClient(http, auth)
107
+ try:
108
+ snapshot = await client.connect()
109
+ for index, area in snapshot["areas"].items():
110
+ print(index, area["name"], area["status"])
111
+
112
+ if 0 in snapshot["can_arm"]:
113
+ try:
114
+ await client.control("arm", 0)
115
+ except CommandUnconfirmedError:
116
+ print("Check the panel before trying again")
117
+ finally:
118
+ await client.disconnect()
119
+
120
+
121
+ asyncio.run(main())
122
+ ```
123
+
124
+ `connect()` returns once every area the user may control has reported its
125
+ state. Calling it on an open session returns the current state and restarts
126
+ the idle timer.
127
+
128
+ ### Callbacks
129
+
130
+ - `on_update(snapshot)` runs whenever the panel reports new state.
131
+ - `on_state()` runs when `state`, `detail` or `idle_expires_at` changes.
132
+ - `create_task(coroutine)` starts the session's background reader. It defaults
133
+ to `asyncio.create_task`. Home Assistant passes
134
+ `entry.async_create_background_task`.
135
+
136
+ ### State
137
+
138
+ - `snapshot()` returns an independent copy of the latest `Snapshot`: the
139
+ `areas`, the `can_arm` and `can_disarm` area sets, `permissions_received`,
140
+ `observed_at` and, when reported, `firmware`.
141
+ - `state` is `disconnected`, `connecting`, `connected` or `error`. After an
142
+ error, `detail` holds a fixed explanation.
143
+ - `idle_expires_at` is the wall-clock time at which an idle session will close.
144
+
145
+ Area `value` codes are listed in `STATUS`: `0` Unset, `1` Set, `2` Alarmed,
146
+ `3` Setting, `4` Cannot Set, `5` Can Override and `6` Set Cancelled. An arm
147
+ request counts as confirmed once the area reports Setting. Arming is refused
148
+ while an area reports Cannot Set or Can Override.
149
+
150
+ ### Errors
151
+
152
+ All errors derive from `ProtocolError`:
153
+
154
+ | Exception | Meaning |
155
+ | --- | --- |
156
+ | `AuthenticationError` | The cloud or panel rejected the credentials. Ask for new details; do not retry automatically. |
157
+ | `PanelBusyError` | Another command on this client is still waiting for its result. |
158
+ | `CommandUnconfirmedError` | The command may have reached the panel, but its outcome is unknown. `snapshot` holds the latest state, if the session is still open. |
159
+ | `ProtocolError` | Any other connection, validation or panel failure. |
160
+
161
+ ## Development
162
+
163
+ ```bash
164
+ python -m pip install -e . --group dev
165
+ ruff check .
166
+ ruff format --check .
167
+ mypy
168
+ pytest --cov
169
+ ```
170
+
171
+ The tests use a simulated panel and require 100% branch coverage. They never
172
+ contact PyronixCloud.
173
+
174
+ ## Licence
175
+
176
+ [MIT](LICENSE)
@@ -0,0 +1,150 @@
1
+ # aiopyronix
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/aiopyronix)](https://pypi.org/project/aiopyronix/)
4
+ [![CI](https://github.com/Herbertmt978/aiopyronix/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Herbertmt978/aiopyronix/actions/workflows/ci.yml)
5
+ [![Licence: MIT](https://img.shields.io/badge/Licence-MIT-0F766E.svg)](https://github.com/Herbertmt978/aiopyronix/blob/main/LICENSE)
6
+
7
+ An asynchronous Python client for Pyronix alarm panels reached through
8
+ PyronixCloud, the service used by the HomeControl 2.0 app.
9
+
10
+ It powers the [Pyronix HomeControl integration for Home Assistant](https://github.com/Herbertmt978/HA-Pyronix),
11
+ and it can be used on its own. This is independent community software. It is
12
+ not an official Pyronix, Hikvision or Home Assistant product.
13
+
14
+ ## Design
15
+
16
+ The client controls an intruder alarm, so caution takes priority over convenience:
17
+
18
+ - **Explicit sessions.** Nothing connects when a `PanelClient` is created. A
19
+ session opens only when `connect()` is called. There is no polling or
20
+ automatic reconnection.
21
+ - **Idle sessions close.** A session closes after five minutes without a
22
+ `connect()` or `control()` call. Heartbeats keep the socket alive, but they
23
+ do not extend that limit.
24
+ - **Commands are sent once.** `control()` arms or disarms exactly one area and
25
+ waits for the panel to confirm it. When the outcome cannot be confirmed, it
26
+ raises `CommandUnconfirmedError`. The command is never repeated, even after
27
+ a lost connection.
28
+ - **A narrow command set.** Only single-area arm and disarm are possible. The
29
+ library has no forced omissions, outputs, installer functions or cloud
30
+ account changes.
31
+ - **Nothing sensitive is logged.** The library does not log. Its error
32
+ messages are fixed text and never contain credentials, panel data or wire
33
+ payloads.
34
+
35
+ Status updates and commands share one WebSocket. Each frame is encrypted with
36
+ a session key agreed through ECDH (P-256) and checked for integrity and
37
+ sequence.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install aiopyronix
43
+ ```
44
+
45
+ Python 3.14 or later is required.
46
+
47
+ ## Connection details
48
+
49
+ `PanelClient` needs the connection details that the HomeControl 2.0 app saves
50
+ when you sign in:
51
+
52
+ | Field | Meaning |
53
+ | --- | --- |
54
+ | `AppUser`, `PushId`, `DeviceId` | The app installation registered with PyronixCloud |
55
+ | `PanelId` | The eight-character panel identifier |
56
+ | `SystemName` | The panel name shown in the app |
57
+ | `PanelPwd` | The app password stored for the panel |
58
+ | `UserCode` | The panel user code, four to eight digits |
59
+
60
+ `AppVersion`, `AppType`, `OSName`, `OSVersion`, `NetworkType` and `AppLang`
61
+ are optional. Other keys are discarded. The Home Assistant integration
62
+ includes a [setup helper](https://github.com/Herbertmt978/HA-Pyronix#connect-your-own-alarm)
63
+ that reads these details from your own signed-in app.
64
+
65
+ ## Usage
66
+
67
+ ```python
68
+ import asyncio
69
+ import json
70
+ from pathlib import Path
71
+
72
+ import aiohttp
73
+
74
+ from aiopyronix import CommandUnconfirmedError, PanelClient
75
+
76
+
77
+ async def main() -> None:
78
+ auth = json.loads(Path("pyronix.json").read_text())
79
+ async with aiohttp.ClientSession() as http:
80
+ client = PanelClient(http, auth)
81
+ try:
82
+ snapshot = await client.connect()
83
+ for index, area in snapshot["areas"].items():
84
+ print(index, area["name"], area["status"])
85
+
86
+ if 0 in snapshot["can_arm"]:
87
+ try:
88
+ await client.control("arm", 0)
89
+ except CommandUnconfirmedError:
90
+ print("Check the panel before trying again")
91
+ finally:
92
+ await client.disconnect()
93
+
94
+
95
+ asyncio.run(main())
96
+ ```
97
+
98
+ `connect()` returns once every area the user may control has reported its
99
+ state. Calling it on an open session returns the current state and restarts
100
+ the idle timer.
101
+
102
+ ### Callbacks
103
+
104
+ - `on_update(snapshot)` runs whenever the panel reports new state.
105
+ - `on_state()` runs when `state`, `detail` or `idle_expires_at` changes.
106
+ - `create_task(coroutine)` starts the session's background reader. It defaults
107
+ to `asyncio.create_task`. Home Assistant passes
108
+ `entry.async_create_background_task`.
109
+
110
+ ### State
111
+
112
+ - `snapshot()` returns an independent copy of the latest `Snapshot`: the
113
+ `areas`, the `can_arm` and `can_disarm` area sets, `permissions_received`,
114
+ `observed_at` and, when reported, `firmware`.
115
+ - `state` is `disconnected`, `connecting`, `connected` or `error`. After an
116
+ error, `detail` holds a fixed explanation.
117
+ - `idle_expires_at` is the wall-clock time at which an idle session will close.
118
+
119
+ Area `value` codes are listed in `STATUS`: `0` Unset, `1` Set, `2` Alarmed,
120
+ `3` Setting, `4` Cannot Set, `5` Can Override and `6` Set Cancelled. An arm
121
+ request counts as confirmed once the area reports Setting. Arming is refused
122
+ while an area reports Cannot Set or Can Override.
123
+
124
+ ### Errors
125
+
126
+ All errors derive from `ProtocolError`:
127
+
128
+ | Exception | Meaning |
129
+ | --- | --- |
130
+ | `AuthenticationError` | The cloud or panel rejected the credentials. Ask for new details; do not retry automatically. |
131
+ | `PanelBusyError` | Another command on this client is still waiting for its result. |
132
+ | `CommandUnconfirmedError` | The command may have reached the panel, but its outcome is unknown. `snapshot` holds the latest state, if the session is still open. |
133
+ | `ProtocolError` | Any other connection, validation or panel failure. |
134
+
135
+ ## Development
136
+
137
+ ```bash
138
+ python -m pip install -e . --group dev
139
+ ruff check .
140
+ ruff format --check .
141
+ mypy
142
+ pytest --cov
143
+ ```
144
+
145
+ The tests use a simulated panel and require 100% branch coverage. They never
146
+ contact PyronixCloud.
147
+
148
+ ## Licence
149
+
150
+ [MIT](LICENSE)
@@ -0,0 +1,67 @@
1
+ # Releasing aiopyronix
2
+
3
+ Releases are built and uploaded by GitHub Actions. No PyPI API token exists:
4
+ PyPI trusts the `Release` workflow through
5
+ [trusted publishing](https://docs.pypi.org/trusted-publishers/), and each
6
+ upload carries a signed attestation of the workflow run that built it.
7
+
8
+ ## One-time setup
9
+
10
+ - **GitHub:** the public repository `Herbertmt978/aiopyronix`, with issues
11
+ enabled. Its `pypi` environment accepts deployments only from tags matching
12
+ `v*`.
13
+ - **PyPI:** a trusted publisher for the `aiopyronix` project, bound to owner
14
+ `Herbertmt978`, repository `aiopyronix`, workflow `release.yml` and
15
+ environment `pypi`. Before the first upload it is a pending publisher; the
16
+ first successful upload creates the project.
17
+
18
+ Changing the workflow file name, the environment name or the repository owner
19
+ breaks publishing until the PyPI publisher is updated to match.
20
+
21
+ ## Versioning
22
+
23
+ The project follows [Semantic Versioning](https://semver.org/). Before 1.0:
24
+
25
+ - a patch release fixes behaviour without changing the public API;
26
+ - a minor release adds or changes public API, or changes what reaches the
27
+ panel. The changelog calls out any change to commands, confirmation or
28
+ session lifetime.
29
+
30
+ The version is written once, in `pyproject.toml`. The release workflow stops
31
+ unless the tag is exactly `v` followed by that version.
32
+
33
+ ## Cutting a release
34
+
35
+ 1. Update `version` in `pyproject.toml` and add a `CHANGELOG.md` entry.
36
+ 2. Run the checks locally:
37
+
38
+ ```bash
39
+ ruff check .
40
+ ruff format --check .
41
+ mypy
42
+ pytest --cov
43
+ ```
44
+
45
+ 3. Merge to `main` and wait for CI to pass.
46
+ 4. Publish a GitHub release whose tag is `v<version>`, targeting `main`:
47
+
48
+ ```bash
49
+ gh release create v0.1.1 --target main --title 0.1.1 --notes-file notes.md
50
+ ```
51
+
52
+ 5. The `Release` workflow reruns CI, checks the tag, builds the sdist and
53
+ wheel, and uploads both to PyPI. Confirm the new version at
54
+ <https://pypi.org/project/aiopyronix/>.
55
+ 6. In [HA-Pyronix](https://github.com/Herbertmt978/HA-Pyronix), update the
56
+ pin in `manifest.json` and `requirements-dev.txt`, then run its tests on
57
+ the minimum and current Home Assistant versions.
58
+
59
+ A published version cannot be replaced on PyPI. If a release is wrong, yank it
60
+ on PyPI and publish a new patch version.
61
+
62
+ ## Licence
63
+
64
+ aiopyronix is MIT licensed, like the integration. Its runtime dependencies are
65
+ also OSI approved: aiohttp (Apache-2.0) and cryptography (Apache-2.0 or
66
+ BSD-3-Clause). The wheel records the licence as an SPDX expression and
67
+ includes the `LICENSE` file.
@@ -0,0 +1,107 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "aiopyronix"
7
+ version = "0.1.0"
8
+ description = "Asynchronous PyronixCloud client for Pyronix alarm panels, with explicit sessions"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Herbertmt978" }]
13
+ requires-python = ">=3.14"
14
+ dependencies = [
15
+ "aiohttp>=3.13.0",
16
+ "cryptography>=47.0.0",
17
+ ]
18
+ keywords = ["pyronix", "homecontrol", "alarm", "asyncio", "home-assistant"]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Framework :: AsyncIO",
22
+ "Intended Audience :: Developers",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3 :: Only",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Topic :: Home Automation",
28
+ "Topic :: Security",
29
+ "Typing :: Typed",
30
+ ]
31
+
32
+ [project.urls]
33
+ Source = "https://github.com/Herbertmt978/aiopyronix"
34
+ Issues = "https://github.com/Herbertmt978/aiopyronix/issues"
35
+ Changelog = "https://github.com/Herbertmt978/aiopyronix/blob/main/CHANGELOG.md"
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "mypy==2.4.1",
40
+ "pytest==9.0.3",
41
+ "pytest-asyncio==1.4.0",
42
+ "pytest-cov==7.1.0",
43
+ "ruff==0.17.3",
44
+ ]
45
+
46
+ [tool.hatch.build.targets.sdist]
47
+ include = ["/src", "/tests", "/CHANGELOG.md", "/RELEASING.md"]
48
+
49
+ [tool.pytest.ini_options]
50
+ asyncio_mode = "auto"
51
+ asyncio_default_fixture_loop_scope = "function"
52
+ testpaths = ["tests"]
53
+ # Unhandled task exceptions and resource warnings are defects here, not noise.
54
+ filterwarnings = ["error"]
55
+
56
+ [tool.coverage.run]
57
+ branch = true
58
+ source = ["aiopyronix"]
59
+
60
+ [tool.coverage.report]
61
+ fail_under = 100
62
+ show_missing = true
63
+
64
+ [tool.mypy]
65
+ python_version = "3.14"
66
+ strict = true
67
+ files = ["src/aiopyronix"]
68
+
69
+ [tool.ruff]
70
+ target-version = "py314"
71
+
72
+ [tool.ruff.lint]
73
+ # Home Assistant Core's rule selection, so the library reads like the integration.
74
+ select = [
75
+ "A001", "ASYNC", "B", "BLE", "C", "COM818", "D", "DTZ003", "DTZ004", "DTZ011", "E", "F",
76
+ "FLY", "FURB", "G", "I", "INP", "ISC", "ICN001", "ICN002", "LOG", "N804", "N805", "N806",
77
+ "N815", "PERF", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "S102", "S103",
78
+ "S107", "S108", "S301", "S306", "S307", "S313", "S314", "S315", "S316", "S317", "S318",
79
+ "S319", "S601", "S602", "S604", "S608", "S609", "SIM", "SLF", "SLOT", "T100", "T20", "TC",
80
+ "TID", "TRY", "UP", "W",
81
+ ]
82
+ ignore = [
83
+ "D202", "D203", "D213", "D406", "D407", "D417", "E501", "PLC1901", "PLR0911", "PLR0912",
84
+ "PLR0913", "PLR0915", "PLR0917", "PLR2004", "PLW0108", "PLW2901", "PT011", "PT018",
85
+ "RUF001", "RUF012", "SIM102", "SIM103", "SIM108", "TC001", "TC002", "TC003", "TRY003",
86
+ "COM812",
87
+ ]
88
+
89
+ [tool.ruff.lint.isort]
90
+ force-sort-within-sections = true
91
+ known-first-party = ["aiopyronix", "tests"]
92
+ combine-as-imports = true
93
+ split-on-trailing-comma = false
94
+
95
+ [tool.ruff.lint.flake8-tidy-imports.banned-api]
96
+ "__future__.annotations".msg = "Not needed; the library requires Python 3.14"
97
+ "async_timeout".msg = "use asyncio.timeout instead"
98
+
99
+ [tool.ruff.lint.mccabe]
100
+ max-complexity = 25
101
+
102
+ [tool.ruff.lint.pydocstyle]
103
+ convention = "google"
104
+
105
+ [tool.ruff.lint.per-file-ignores]
106
+ # Tests exercise private session state directly.
107
+ "tests/*.py" = ["D", "SLF001"]
@@ -0,0 +1,37 @@
1
+ """Asynchronous PyronixCloud client for Pyronix alarm panels.
2
+
3
+ A session opens only when the caller asks for it. Each arm or disarm request is
4
+ sent once and never repeated. Connection fields and wire payloads are never logged.
5
+ """
6
+
7
+ from .client import (
8
+ OPTIONAL_AUTH,
9
+ REQUIRED_AUTH,
10
+ STATUS,
11
+ AreaState,
12
+ AuthenticationError,
13
+ CommandUnconfirmedError,
14
+ ConnectionState,
15
+ PanelBusyError,
16
+ PanelClient,
17
+ Snapshot,
18
+ empty_snapshot,
19
+ validate_auth,
20
+ )
21
+ from .protocol import ProtocolError
22
+
23
+ __all__ = [
24
+ "OPTIONAL_AUTH",
25
+ "REQUIRED_AUTH",
26
+ "STATUS",
27
+ "AreaState",
28
+ "AuthenticationError",
29
+ "CommandUnconfirmedError",
30
+ "ConnectionState",
31
+ "PanelBusyError",
32
+ "PanelClient",
33
+ "ProtocolError",
34
+ "Snapshot",
35
+ "empty_snapshot",
36
+ "validate_auth",
37
+ ]