python-izone 1.3.4__tar.gz → 1.3.5__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.
- {python_izone-1.3.4 → python_izone-1.3.5}/.github/workflows/main.yml +4 -2
- python_izone-1.3.5/.pre-commit-config.yaml +7 -0
- python_izone-1.3.5/.vscode/extensions.json +7 -0
- python_izone-1.3.5/.vscode/settings.json +34 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/PKG-INFO +3 -2
- {python_izone-1.3.4 → python_izone-1.3.5}/README.md +2 -1
- python_izone-1.3.5/docs/release-notes/v1.3.5.md +92 -0
- python_izone-1.3.5/pizone/__init__.py +79 -0
- python_izone-1.3.5/pizone/const.py +4 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/pizone/controller.py +445 -171
- python_izone-1.3.5/pizone/discovery.py +1006 -0
- python_izone-1.3.5/pizone/exceptions.py +37 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/pizone/power.py +29 -19
- python_izone-1.3.5/pizone/types.py +11 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/pizone/version.py +2 -2
- {python_izone-1.3.4 → python_izone-1.3.5}/pizone/zone.py +25 -13
- python_izone-1.3.5/pyproject.toml +261 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/scripts/check +2 -1
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/conftest.py +65 -29
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/http_fakes.py +13 -2
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/power_data.py +21 -3
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/resources.py +65 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/test_controller.py +332 -59
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/test_discovery.py +52 -18
- python_izone-1.3.5/tests/test_discovery_api.py +703 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/test_fullstack.py +25 -26
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/test_http.py +16 -10
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/test_power.py +6 -1
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/test_zone.py +25 -11
- {python_izone-1.3.4 → python_izone-1.3.5}/uv.lock +24 -71
- python_izone-1.3.4/.pre-commit-config.yaml +0 -6
- python_izone-1.3.4/.pylintrc +0 -609
- python_izone-1.3.4/.vscode/settings.json +0 -26
- python_izone-1.3.4/pizone/__init__.py +0 -38
- python_izone-1.3.4/pizone/discovery.py +0 -521
- python_izone-1.3.4/pizone/exceptions.py +0 -18
- python_izone-1.3.4/pyproject.toml +0 -103
- {python_izone-1.3.4 → python_izone-1.3.5}/.gitattributes +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/.gitignore +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/.python-version +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/.vscode/.ropeproject/config.py +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/.vscode/launch.json +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/api_docs/AC-DOC-1401-11_iZoneEthernetInterface.pdf +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/api_docs/iPower_JSON_datastrings_Out.h +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/api_docs/iZone_JSON_datastrings.h +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/docs/release-notes/v1.3.4.md +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/licence.txt +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/pizone/py.typed +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/scripts/coverage +0 -0
- {python_izone-1.3.4 → python_izone-1.3.5}/tests/__init__.py +0 -0
|
@@ -23,8 +23,10 @@ jobs:
|
|
|
23
23
|
cache-suffix: build
|
|
24
24
|
- name: Install dependencies
|
|
25
25
|
run: uv sync --frozen --all-extras --group dev
|
|
26
|
-
- name: Lint with
|
|
27
|
-
run:
|
|
26
|
+
- name: Lint with Ruff
|
|
27
|
+
run: |
|
|
28
|
+
uv run ruff check pizone tests
|
|
29
|
+
uv run ruff format --check pizone tests
|
|
28
30
|
- name: Type check with mypy
|
|
29
31
|
run: uv run mypy pizone
|
|
30
32
|
- name: Test with pytest
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
|
|
3
|
+
"python.testing.pytestEnabled": true,
|
|
4
|
+
"python.globalModuleInstallation": false,
|
|
5
|
+
"[python]": {
|
|
6
|
+
"editor.defaultFormatter": "charliermarsh.ruff",
|
|
7
|
+
"editor.formatOnSave": true,
|
|
8
|
+
"editor.codeActionsOnSave": {
|
|
9
|
+
"source.fixAll.ruff": "explicit",
|
|
10
|
+
"source.organizeImports.ruff": "explicit"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"ruff.enable": true,
|
|
14
|
+
"ruff.nativeServer": "on",
|
|
15
|
+
"ruff.configurationPreference": "filesystemFirst",
|
|
16
|
+
"ruff.interpreter": ["${workspaceFolder}/.venv/bin/python"],
|
|
17
|
+
"python.analysis.typeCheckingMode": "off",
|
|
18
|
+
"cursorpyright.analysis.typeCheckingMode": "off",
|
|
19
|
+
"pylint.enabled": false,
|
|
20
|
+
"mypy-type-checker.interpreter": ["${workspaceFolder}/.venv/bin/python"],
|
|
21
|
+
"mypy-type-checker.args": ["--config-file=${workspaceFolder}/pyproject.toml"],
|
|
22
|
+
"cSpell.words": [
|
|
23
|
+
"inetaddrs",
|
|
24
|
+
"inetaddr",
|
|
25
|
+
"ifaddresses",
|
|
26
|
+
"rescan",
|
|
27
|
+
"pizone",
|
|
28
|
+
"ipower",
|
|
29
|
+
"COOLDOWN",
|
|
30
|
+
"coro",
|
|
31
|
+
"datagram",
|
|
32
|
+
"fanout"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-izone
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.5
|
|
4
4
|
Summary: A python interface to the iZone airconditioner controller
|
|
5
5
|
Project-URL: Homepage, https://github.com/Swamp-Ig/pizone
|
|
6
6
|
Author-email: Penny Wood <pypl@ninjateaparty.com>
|
|
@@ -68,7 +68,8 @@ The iZone Ethernet interface is documented in
|
|
|
68
68
|
Individual commands:
|
|
69
69
|
|
|
70
70
|
- `uv run pytest tests/`
|
|
71
|
-
- `uv run
|
|
71
|
+
- `uv run ruff check pizone tests`
|
|
72
|
+
- `uv run ruff format pizone tests`
|
|
72
73
|
- `uv run mypy pizone`
|
|
73
74
|
- `uv build`
|
|
74
75
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Release notes — v1.3.5
|
|
2
|
+
|
|
3
|
+
Released 2026-07-17. **Beta release** for the upcoming Home Assistant iZone
|
|
4
|
+
Phase 2 integration work (izone 1.4 program). The version number stays on the
|
|
5
|
+
**1.3.x** line for now; the program name is “1.4” internally.
|
|
6
|
+
|
|
7
|
+
**Compare range:** https://github.com/Swamp-Ig/pizone/compare/v1.3.4...v1.3.5
|
|
8
|
+
|
|
9
|
+
## Beta status
|
|
10
|
+
|
|
11
|
+
This release ships the full dual-track library on `main` so Home Assistant can
|
|
12
|
+
pin a real PyPI version while Phase 2a/2b land. The **current HA integration
|
|
13
|
+
still uses the legacy API** (`discovery()`, `fetch_*`, `Listener`). The new
|
|
14
|
+
consumer-driven API is available for new integration code but is not required
|
|
15
|
+
for a pin-only HA bump.
|
|
16
|
+
|
|
17
|
+
**`1.4.0`** (planned after HA Phase 2 merges) will remove the legacy surface
|
|
18
|
+
(`discovery()`, `fetch_*`, `Listener`, and related poll/scan loops).
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
|
|
22
|
+
Since `v1.3.4`, this release adds the izone 1.4 discovery and refresh model
|
|
23
|
+
alongside the legacy path, gates iPower behind an opt-in flag, rejects
|
|
24
|
+
unpaired-bridge placeholder UIDs, and mitigates HTTP keepalive stalls on some
|
|
25
|
+
ASH bridges.
|
|
26
|
+
|
|
27
|
+
## Connection: close (core#176536)
|
|
28
|
+
|
|
29
|
+
- HTTP **GET** requests send `Connection: close` (controller reads and
|
|
30
|
+
discovery probes)
|
|
31
|
+
- When pizone owns the aiohttp session, `TCPConnector(force_close=True)` is used
|
|
32
|
+
- Mitigation for hubs that stall under connection reuse; does not fix hub-side
|
|
33
|
+
stalls reported in [home-assistant/core#176536](https://github.com/home-assistant/core/issues/176536)
|
|
34
|
+
|
|
35
|
+
## New 1.4 API (dual-track — beta)
|
|
36
|
+
|
|
37
|
+
Available now; legacy path remains for existing consumers:
|
|
38
|
+
|
|
39
|
+
- **`create_discovery()`** — one-shot discovery service with callbacks
|
|
40
|
+
- **`discover_by_host`**, **`discover_by_uid`**, **`discover_all`** — ephemeral
|
|
41
|
+
or service-scoped endpoint discovery with HTTP verify
|
|
42
|
+
- **`create_controller(uid, host)`** — explicit controller factory after
|
|
43
|
+
discovery
|
|
44
|
+
- **`refresh_all`**, **`refresh_system`**, **`refresh_zones`**, etc. —
|
|
45
|
+
consumer-owned polling on the 1.4 path (no library debounce loops)
|
|
46
|
+
- **`ControllerEndpoint`**, **`on_endpoint_discovered`**, **`on_address_changed`**
|
|
47
|
+
callbacks for integration discovery flows
|
|
48
|
+
|
|
49
|
+
Legacy **`discovery()`**, **`fetch_controller`**, **`fetch_controllers`**, and
|
|
50
|
+
**`Listener`** continue to work unchanged on the deprecated pathway.
|
|
51
|
+
|
|
52
|
+
## Power gate
|
|
53
|
+
|
|
54
|
+
- **`ENABLE_POWER`** defaults to **`False`**; no iPower HTTP I/O until set at
|
|
55
|
+
runtime (for example `pizone.ENABLE_POWER = True` before create/refresh)
|
|
56
|
+
- Home Assistant does not expose power entities yet; this avoids surprise I/O on
|
|
57
|
+
bridges that advertise iPower in UDP
|
|
58
|
+
|
|
59
|
+
## Unpaired bridge UID
|
|
60
|
+
|
|
61
|
+
- **`PLACEHOLDER_DEVICE_UID`** (`"000000000"`) — exact match for ASH bridges
|
|
62
|
+
with no paired AC
|
|
63
|
+
- Passive UDP discovery ignores this UID (no cache, no notify)
|
|
64
|
+
- **`discover_by_uid`** / **`create_controller`** reject immediately with
|
|
65
|
+
**`UnpairedBridgeError`** (no network I/O)
|
|
66
|
+
- **`discover_by_host`** raises after HTTP probe if the UID is the placeholder
|
|
67
|
+
(no cache)
|
|
68
|
+
|
|
69
|
+
## UDP and discovery (1.4 path)
|
|
70
|
+
|
|
71
|
+
- **`CHANGED_*`** push datagrams recognized and ignored (no refresh)
|
|
72
|
+
- ASPort passive discovery caches unclaimed endpoints and fans out
|
|
73
|
+
**`on_endpoint_discovered`** for new or host-changed endpoints
|
|
74
|
+
- **`discover_all`** HTTP-verifies collected hosts and dedupes notifications
|
|
75
|
+
|
|
76
|
+
## Other
|
|
77
|
+
|
|
78
|
+
- Live fault-shaped **SystemSettings** fixtures for unpaired and paired cold-restart
|
|
79
|
+
specimens (tests)
|
|
80
|
+
- Test suite labelled with disposition comments (`1.4` vs `deprecate`) for
|
|
81
|
+
dual-track cleanup
|
|
82
|
+
- Ruff replaces Black and pylint for lint and format
|
|
83
|
+
|
|
84
|
+
## For Home Assistant integrators
|
|
85
|
+
|
|
86
|
+
Pin **`python-izone==1.3.5`** for:
|
|
87
|
+
|
|
88
|
+
1. **Immediate:** Connection: close mitigation on the existing legacy integration
|
|
89
|
+
path (pin-only bump)
|
|
90
|
+
2. **Upcoming:** Access to the 1.4 API when Phase 2 runtime and config flow land
|
|
91
|
+
|
|
92
|
+
Do not expect breaking changes on the legacy API until **`1.4.0`**.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Interface to the iZone air conditioner controller.
|
|
2
|
+
|
|
3
|
+
Prefer :func:`~pizone.discovery.create_discovery` or
|
|
4
|
+
:func:`~pizone.discovery.discovery` to obtain a discovery service, then create
|
|
5
|
+
or discover :class:`~pizone.controller.Controller` instances through that
|
|
6
|
+
service. Zones and power monitors are owned by their controller.
|
|
7
|
+
|
|
8
|
+
Synchronous property reads return cached device data and do not raise
|
|
9
|
+
:exc:`ConnectionError`. Async command and refresh methods perform HTTP I/O and
|
|
10
|
+
raise :exc:`ConnectionError` when the device cannot be reached. They raise
|
|
11
|
+
:exc:`~pizone.exceptions.ControllerCommandError` when the device responds but
|
|
12
|
+
rejects the request (``{ERROR...}`` body or HTTP 4xx).
|
|
13
|
+
|
|
14
|
+
Layered connection state on :class:`~pizone.controller.Controller`:
|
|
15
|
+
|
|
16
|
+
- :attr:`~pizone.controller.Controller.bridge_connected` — ASH bridge HTTP transport
|
|
17
|
+
- :attr:`~pizone.controller.Controller.connected` — bridge plus valid iZone AC data
|
|
18
|
+
- :attr:`~pizone.power.Power.connected` — power monitor I/O (when enabled)
|
|
19
|
+
|
|
20
|
+
Power support is off by default. Enable at runtime before create/refresh when
|
|
21
|
+
the consumer needs iPower (for example future Home Assistant power entities)::
|
|
22
|
+
|
|
23
|
+
import pizone
|
|
24
|
+
pizone.ENABLE_POWER = True
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import sys
|
|
28
|
+
from types import ModuleType
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
from . import power as power_mod
|
|
32
|
+
from .const import PLACEHOLDER_DEVICE_UID
|
|
33
|
+
from .controller import Controller
|
|
34
|
+
from .discovery import DiscoveryService, Listener, create_discovery, discovery
|
|
35
|
+
from .exceptions import ControllerCommandError, ResponseDecodeError, UnpairedBridgeError
|
|
36
|
+
from .power import BatteryLevel, Power, PowerChannel, PowerDevice, PowerGroup
|
|
37
|
+
from .types import ControllerEndpoint
|
|
38
|
+
from .zone import Zone
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def __getattr__(name: str) -> Any:
|
|
42
|
+
if name == "ENABLE_POWER":
|
|
43
|
+
return power_mod.ENABLE_POWER
|
|
44
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _PizoneModule(ModuleType):
|
|
48
|
+
"""Allow ``pizone.ENABLE_POWER = True`` to update :mod:`pizone.power`."""
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def ENABLE_POWER(self) -> bool:
|
|
52
|
+
return power_mod.ENABLE_POWER
|
|
53
|
+
|
|
54
|
+
@ENABLE_POWER.setter
|
|
55
|
+
def ENABLE_POWER(self, value: bool) -> None:
|
|
56
|
+
power_mod.ENABLE_POWER = bool(value)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
sys.modules[__name__].__class__ = _PizoneModule
|
|
60
|
+
|
|
61
|
+
__all__ = [
|
|
62
|
+
"ENABLE_POWER",
|
|
63
|
+
"PLACEHOLDER_DEVICE_UID",
|
|
64
|
+
"BatteryLevel",
|
|
65
|
+
"Controller",
|
|
66
|
+
"ControllerCommandError",
|
|
67
|
+
"ControllerEndpoint",
|
|
68
|
+
"DiscoveryService",
|
|
69
|
+
"Listener",
|
|
70
|
+
"Power",
|
|
71
|
+
"PowerChannel",
|
|
72
|
+
"PowerDevice",
|
|
73
|
+
"PowerGroup",
|
|
74
|
+
"ResponseDecodeError",
|
|
75
|
+
"UnpairedBridgeError",
|
|
76
|
+
"Zone",
|
|
77
|
+
"create_discovery",
|
|
78
|
+
"discovery",
|
|
79
|
+
]
|