python-izone 1.3.6__tar.gz → 1.3.7__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.6 → python_izone-1.3.7}/PKG-INFO +1 -1
- {python_izone-1.3.6 → python_izone-1.3.7}/docs/release-notes/v1.3.6.md +20 -48
- python_izone-1.3.7/docs/release-notes/v1.3.7.md +7 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/pizone/__init__.py +7 -1
- {python_izone-1.3.6 → python_izone-1.3.7}/pizone/discovery.py +10 -6
- {python_izone-1.3.6 → python_izone-1.3.7}/pizone/exceptions.py +8 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/pizone/version.py +2 -2
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/test_discovery_api.py +4 -3
- {python_izone-1.3.6 → python_izone-1.3.7}/.gitattributes +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/.github/workflows/main.yml +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/.gitignore +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/.pre-commit-config.yaml +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/.python-version +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/.vscode/.ropeproject/config.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/.vscode/extensions.json +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/.vscode/launch.json +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/.vscode/settings.json +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/README.md +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/api_docs/AC-DOC-1401-11_iZoneEthernetInterface.pdf +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/api_docs/iPower_JSON_datastrings_Out.h +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/api_docs/iZone_JSON_datastrings.h +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/docs/release-notes/v1.3.4.md +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/docs/release-notes/v1.3.5.md +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/licence.txt +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/pizone/const.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/pizone/controller.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/pizone/power.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/pizone/py.typed +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/pizone/types.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/pizone/zone.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/pyproject.toml +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/scripts/check +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/scripts/coverage +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/__init__.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/conftest.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/http_fakes.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/power_data.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/resources.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/test_controller.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/test_discovery.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/test_fullstack.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/test_http.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/test_power.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/tests/test_zone.py +0 -0
- {python_izone-1.3.6 → python_izone-1.3.7}/uv.lock +0 -0
|
@@ -4,86 +4,60 @@ Released 2026-07-19.
|
|
|
4
4
|
|
|
5
5
|
**Compare range:** https://github.com/Swamp-Ig/pizone/compare/v1.3.5...v1.3.6
|
|
6
6
|
|
|
7
|
-
This is a **pre-release** for integrators
|
|
8
|
-
work). It is not aimed at end users as a standalone upgrade. Prefer waiting for
|
|
9
|
-
a future stable line unless you are developing against the new discovery API.
|
|
7
|
+
This is a **pre-release** for integrators adopting the newer discovery API. It is not aimed at end users as a standalone upgrade. Prefer waiting for a future stable line unless you are developing against that API.
|
|
10
8
|
|
|
11
9
|
## Since v1.3.5
|
|
12
10
|
|
|
13
11
|
- Discovery scan wait (`SCAN_TIMEOUT`) reduced from 20s to 5s
|
|
14
|
-
- `close()` waits until the UDP transport has finished tearing down before
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- Failed `create_discovery` / `start_discovery` no longer leaves a sticky
|
|
18
|
-
process singleton that blocked retries until process restart
|
|
19
|
-
- `PLACEHOLDER_DEVICE_UID` is no longer exported from the public package API
|
|
20
|
-
(`UnpairedBridgeError` remains the supported signal)
|
|
12
|
+
- `close()` waits until the UDP transport has finished tearing down before returning, so a following `create_discovery()` can rebind `:7005` immediately (fixes delete → HomeKit rediscovery `EADDRINUSE` races)
|
|
13
|
+
- Failed `create_discovery` / `start_discovery` no longer leaves a sticky process singleton that blocked retries until process restart
|
|
14
|
+
- `PLACEHOLDER_DEVICE_UID` is no longer exported from the public package API (`UnpairedBridgeError` remains the supported signal)
|
|
21
15
|
|
|
22
16
|
Everything from v1.3.5 below still applies.
|
|
23
17
|
|
|
24
18
|
## Summary (since v1.3.4)
|
|
25
19
|
|
|
26
|
-
- HTTP connections are closed after each GET, which greatly reduces stalls on
|
|
27
|
-
|
|
28
|
-
- A new discovery and refresh API is available for integrators (the older API
|
|
29
|
-
still works for now)
|
|
20
|
+
- HTTP connections are closed after each GET, which greatly reduces stalls on some iZone bridges ([home-assistant/core#176536](https://github.com/home-assistant/core/issues/176536))
|
|
21
|
+
- A new discovery and refresh API is available for integrators (the older API still works for now)
|
|
30
22
|
- iPower monitoring is off unless you opt in with `ENABLE_POWER = True`
|
|
31
|
-
- Bridges that are not paired to an air conditioner (UID `000000000`) are
|
|
32
|
-
ignored or rejected instead of showing up as broken controllers
|
|
23
|
+
- Bridges that are not paired to an air conditioner (UID `000000000`) are ignored or rejected instead of showing up as broken controllers
|
|
33
24
|
- Faster discovery scans and reliable UDP close → recreate (this release)
|
|
34
25
|
|
|
35
26
|
## Bridge connection reliability
|
|
36
27
|
|
|
37
|
-
Some ASH bridges misbehave when HTTP connections are reused. This release
|
|
38
|
-
line:
|
|
28
|
+
Some ASH bridges misbehave when HTTP connections are reused. This release line:
|
|
39
29
|
|
|
40
30
|
- Sends `Connection: close` on every HTTP GET (polling and discovery probes)
|
|
41
|
-
- Closes TCP connections after each request when the library creates its own
|
|
42
|
-
aiohttp session
|
|
31
|
+
- Closes TCP connections after each request when the library creates its own aiohttp session
|
|
43
32
|
|
|
44
|
-
This reduces how often the bridge stops responding. It does not fix every
|
|
45
|
-
hub-side stall; replacing faulty hardware may still be needed.
|
|
33
|
+
This reduces how often the bridge stops responding. It does not fix every hub-side stall; replacing faulty hardware may still be needed.
|
|
46
34
|
|
|
47
35
|
## New discovery and refresh API (optional for now)
|
|
48
36
|
|
|
49
|
-
Integrators can adopt a clearer model without background poll loops inside the
|
|
50
|
-
library:
|
|
37
|
+
Integrators can adopt a clearer model without background poll loops inside the library:
|
|
51
38
|
|
|
52
|
-
- `create_discovery()` with callbacks when a controller is found or its IP
|
|
53
|
-
|
|
54
|
-
- `discover_by_host`, `discover_by_uid`, `discover_all` to find bridges on the
|
|
55
|
-
network
|
|
39
|
+
- `create_discovery()` with callbacks when a controller is found or its IP changes
|
|
40
|
+
- `discover_by_host`, `discover_by_uid`, `discover_all` to find bridges on the network
|
|
56
41
|
- `create_controller(uid, host)` to connect to a known bridge
|
|
57
|
-
- `refresh_all`, `refresh_system`, `refresh_zones`, and related methods so the
|
|
58
|
-
caller controls when to poll
|
|
42
|
+
- `refresh_all`, `refresh_system`, `refresh_zones`, and related methods so the caller controls when to poll
|
|
59
43
|
|
|
60
|
-
`close()` on the discovery service now waits for UDP teardown so the next
|
|
61
|
-
`create_discovery()` can bind the discovery port again without racing the OS
|
|
62
|
-
socket release.
|
|
44
|
+
`close()` on the discovery service now waits for UDP teardown so the next `create_discovery()` can bind the discovery port again without racing the OS socket release.
|
|
63
45
|
|
|
64
|
-
The previous API (`discovery()`, `fetch_controller`, `Listener`, and so on)
|
|
65
|
-
still works.
|
|
46
|
+
The previous API (`discovery()`, `fetch_controller`, `Listener`, and so on) still works.
|
|
66
47
|
|
|
67
48
|
## iPower opt-in
|
|
68
49
|
|
|
69
|
-
`ENABLE_POWER` now defaults to `False`. Power monitor HTTP traffic only runs
|
|
70
|
-
after you set `pizone.ENABLE_POWER = True` before creating or refreshing a
|
|
71
|
-
controller. This avoids extra requests on bridges that advertise iPower but are
|
|
72
|
-
not set up for it. The default may change if more field data shows power
|
|
73
|
-
monitoring is reliable enough to enable by default.
|
|
50
|
+
`ENABLE_POWER` now defaults to `False`. Power monitor HTTP traffic only runs after you set `pizone.ENABLE_POWER = True` before creating or refreshing a controller. This avoids extra requests on bridges that advertise iPower but are not set up for it. The default may change if more field data shows power monitoring is reliable enough to enable by default.
|
|
74
51
|
|
|
75
52
|
## Unpaired bridges
|
|
76
53
|
|
|
77
|
-
Wireless ASH bridges with no AC paired report device UID `000000000`. The
|
|
78
|
-
library now:
|
|
54
|
+
Wireless ASH bridges with no AC paired report device UID `000000000`. The library now:
|
|
79
55
|
|
|
80
56
|
- Ignores them during passive UDP discovery
|
|
81
57
|
- Raises `UnpairedBridgeError` if you try to connect to that UID on purpose
|
|
82
58
|
- Refuses to cache them after an IP lookup
|
|
83
59
|
|
|
84
|
-
Paired bridges that temporarily report fault-shaped settings (AC off, reboot,
|
|
85
|
-
and so on) are unchanged: they keep their real UID and show as disconnected
|
|
86
|
-
until the AC data recovers.
|
|
60
|
+
Paired bridges that temporarily report fault-shaped settings (AC off, reboot, and so on) are unchanged: they keep their real UID and show as disconnected until the AC data recovers.
|
|
87
61
|
|
|
88
62
|
## Other changes
|
|
89
63
|
|
|
@@ -93,6 +67,4 @@ until the AC data recovers.
|
|
|
93
67
|
|
|
94
68
|
## Home Assistant
|
|
95
69
|
|
|
96
|
-
Pin `python-izone==1.3.6` when
|
|
97
|
-
runtime. This pre-release is intended for that integration work, not as a
|
|
98
|
-
general end-user library upgrade on the legacy path alone.
|
|
70
|
+
Pin `python-izone==1.3.6` when moving the iZone integration onto the newer discovery and coordinator path. This pre-release is intended for that integration work, not as a general end-user library upgrade on the legacy path alone.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Release notes — v1.3.7
|
|
2
|
+
|
|
3
|
+
**Compare range:** https://github.com/Swamp-Ig/pizone/compare/v1.3.6...v1.3.7
|
|
4
|
+
|
|
5
|
+
## Since v1.3.6
|
|
6
|
+
|
|
7
|
+
- Add `ControllerAlreadyClaimedError` for duplicate controller claim/discovery attempts (replaces bare `RuntimeError` with message `already created` on controller paths; discovery-service singleton errors unchanged)
|
|
@@ -31,7 +31,12 @@ from typing import Any
|
|
|
31
31
|
from . import power as power_mod
|
|
32
32
|
from .controller import Controller
|
|
33
33
|
from .discovery import DiscoveryService, Listener, create_discovery, discovery
|
|
34
|
-
from .exceptions import
|
|
34
|
+
from .exceptions import (
|
|
35
|
+
ControllerAlreadyClaimedError,
|
|
36
|
+
ControllerCommandError,
|
|
37
|
+
ResponseDecodeError,
|
|
38
|
+
UnpairedBridgeError,
|
|
39
|
+
)
|
|
35
40
|
from .power import BatteryLevel, Power, PowerChannel, PowerDevice, PowerGroup
|
|
36
41
|
from .types import ControllerEndpoint
|
|
37
42
|
from .zone import Zone
|
|
@@ -61,6 +66,7 @@ __all__ = [
|
|
|
61
66
|
"ENABLE_POWER",
|
|
62
67
|
"BatteryLevel",
|
|
63
68
|
"Controller",
|
|
69
|
+
"ControllerAlreadyClaimedError",
|
|
64
70
|
"ControllerCommandError",
|
|
65
71
|
"ControllerEndpoint",
|
|
66
72
|
"DiscoveryService",
|
|
@@ -24,7 +24,7 @@ import ifaddr
|
|
|
24
24
|
|
|
25
25
|
from .const import PLACEHOLDER_DEVICE_UID
|
|
26
26
|
from .controller import Controller
|
|
27
|
-
from .exceptions import raise_if_placeholder_uid
|
|
27
|
+
from .exceptions import ControllerAlreadyClaimedError, raise_if_placeholder_uid
|
|
28
28
|
from .types import ControllerEndpoint
|
|
29
29
|
from .zone import Zone
|
|
30
30
|
|
|
@@ -405,7 +405,9 @@ class DiscoveryService:
|
|
|
405
405
|
def _ensure_endpoint_available(self, endpoint: ControllerEndpoint) -> None:
|
|
406
406
|
"""Raise if *endpoint* is already claimed."""
|
|
407
407
|
if endpoint.uid in self._claimed_endpoints:
|
|
408
|
-
raise
|
|
408
|
+
raise ControllerAlreadyClaimedError(
|
|
409
|
+
f"Controller {endpoint.uid} already created"
|
|
410
|
+
)
|
|
409
411
|
|
|
410
412
|
# disposition: 1.4
|
|
411
413
|
def _claim_endpoint(self, endpoint: ControllerEndpoint) -> None:
|
|
@@ -451,7 +453,9 @@ class DiscoveryService:
|
|
|
451
453
|
"""
|
|
452
454
|
claimed = self._endpoint_by_host(self._claimed_endpoints, host)
|
|
453
455
|
if claimed is not None:
|
|
454
|
-
raise
|
|
456
|
+
raise ControllerAlreadyClaimedError(
|
|
457
|
+
f"Controller {claimed.uid} already created"
|
|
458
|
+
)
|
|
455
459
|
known = self._endpoint_by_host(self._known_endpoints, host)
|
|
456
460
|
if known is not None:
|
|
457
461
|
return known
|
|
@@ -476,7 +480,7 @@ class DiscoveryService:
|
|
|
476
480
|
"""
|
|
477
481
|
raise_if_placeholder_uid(uid)
|
|
478
482
|
if uid in self._claimed_endpoints:
|
|
479
|
-
raise
|
|
483
|
+
raise ControllerAlreadyClaimedError(f"Controller {uid} already created")
|
|
480
484
|
endpoint = self._known_endpoints.get(uid)
|
|
481
485
|
if endpoint is not None:
|
|
482
486
|
return endpoint
|
|
@@ -569,7 +573,7 @@ class DiscoveryService:
|
|
|
569
573
|
|
|
570
574
|
Raises:
|
|
571
575
|
UnpairedBridgeError: If *uid* is the unpaired placeholder (no I/O).
|
|
572
|
-
|
|
576
|
+
ControllerAlreadyClaimedError: If a controller with *uid* already exists.
|
|
573
577
|
ConnectionError: If the controller cannot be reached after
|
|
574
578
|
address fallback.
|
|
575
579
|
ControllerCommandError: If the device rejects the protocol request.
|
|
@@ -579,7 +583,7 @@ class DiscoveryService:
|
|
|
579
583
|
"""
|
|
580
584
|
raise_if_placeholder_uid(uid)
|
|
581
585
|
if uid in self._claimed_endpoints:
|
|
582
|
-
raise
|
|
586
|
+
raise ControllerAlreadyClaimedError(f"Controller {uid} already created")
|
|
583
587
|
pending_address_changed: ControllerEndpoint | None = None
|
|
584
588
|
|
|
585
589
|
try:
|
|
@@ -28,6 +28,14 @@ class UnpairedBridgeError(ValueError):
|
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
class ControllerAlreadyClaimedError(Exception):
|
|
32
|
+
"""A controller UID or host is already claimed on this discovery service.
|
|
33
|
+
|
|
34
|
+
Raised when ``discover_by_host``, ``discover_by_uid``, or ``create_controller``
|
|
35
|
+
would duplicate an active controller registration.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
31
39
|
def raise_if_placeholder_uid(uid: str) -> None:
|
|
32
40
|
"""Raise :exc:`UnpairedBridgeError` if *uid* is the unpaired placeholder."""
|
|
33
41
|
if uid == PLACEHOLDER_DEVICE_UID:
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 3,
|
|
21
|
+
__version__ = version = '1.3.7'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 3, 7)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -17,6 +17,7 @@ import pytest
|
|
|
17
17
|
|
|
18
18
|
from pizone import (
|
|
19
19
|
Controller,
|
|
20
|
+
ControllerAlreadyClaimedError,
|
|
20
21
|
ControllerCommandError,
|
|
21
22
|
ControllerEndpoint,
|
|
22
23
|
UnpairedBridgeError,
|
|
@@ -282,7 +283,7 @@ async def test_discover_by_host_raises_if_controller_exists() -> None:
|
|
|
282
283
|
ClientSession,
|
|
283
284
|
FakeHttpSession(get_error=OSError("should not probe")),
|
|
284
285
|
)
|
|
285
|
-
with pytest.raises(
|
|
286
|
+
with pytest.raises(ControllerAlreadyClaimedError, match="already created"):
|
|
286
287
|
await service.discover_by_host("10.0.0.90")
|
|
287
288
|
await service.close()
|
|
288
289
|
|
|
@@ -324,7 +325,7 @@ async def test_discover_by_uid_raises_if_controller_exists() -> None:
|
|
|
324
325
|
scan = AsyncMock()
|
|
325
326
|
with (
|
|
326
327
|
patch.object(service, "scan", scan),
|
|
327
|
-
pytest.raises(
|
|
328
|
+
pytest.raises(ControllerAlreadyClaimedError, match="already created"),
|
|
328
329
|
):
|
|
329
330
|
await service.discover_by_uid("000025841")
|
|
330
331
|
scan.assert_not_awaited()
|
|
@@ -656,7 +657,7 @@ async def test_create_controller_raises_if_uid_exists() -> None:
|
|
|
656
657
|
FakeHttpSession(get_response=_system_settings_response("000025841")),
|
|
657
658
|
)
|
|
658
659
|
await service.create_controller("000025841", "10.0.0.90")
|
|
659
|
-
with pytest.raises(
|
|
660
|
+
with pytest.raises(ControllerAlreadyClaimedError, match="already created"):
|
|
660
661
|
await service.create_controller("000025841", "10.0.0.90")
|
|
661
662
|
await service.close()
|
|
662
663
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_izone-1.3.6 → python_izone-1.3.7}/api_docs/AC-DOC-1401-11_iZoneEthernetInterface.pdf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|