python-izone 1.3.3__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.3 → 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.3 → python_izone-1.3.5}/PKG-INFO +3 -2
- {python_izone-1.3.3 → python_izone-1.3.5}/README.md +2 -1
- python_izone-1.3.5/docs/release-notes/v1.3.4.md +55 -0
- 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.3 → python_izone-1.3.5}/pizone/controller.py +487 -188
- python_izone-1.3.5/pizone/discovery.py +1006 -0
- python_izone-1.3.5/pizone/exceptions.py +37 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/pizone/power.py +29 -19
- python_izone-1.3.5/pizone/types.py +11 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/pizone/version.py +2 -2
- {python_izone-1.3.3 → python_izone-1.3.5}/pizone/zone.py +25 -13
- python_izone-1.3.5/pyproject.toml +261 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/scripts/check +2 -1
- {python_izone-1.3.3 → python_izone-1.3.5}/tests/conftest.py +65 -29
- {python_izone-1.3.3 → python_izone-1.3.5}/tests/http_fakes.py +13 -2
- {python_izone-1.3.3 → python_izone-1.3.5}/tests/power_data.py +21 -3
- {python_izone-1.3.3 → python_izone-1.3.5}/tests/resources.py +65 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/tests/test_controller.py +370 -58
- {python_izone-1.3.3 → 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.3 → python_izone-1.3.5}/tests/test_fullstack.py +25 -26
- {python_izone-1.3.3 → python_izone-1.3.5}/tests/test_http.py +18 -11
- {python_izone-1.3.3 → python_izone-1.3.5}/tests/test_power.py +6 -1
- {python_izone-1.3.3 → python_izone-1.3.5}/tests/test_zone.py +25 -11
- {python_izone-1.3.3 → python_izone-1.3.5}/uv.lock +24 -71
- python_izone-1.3.3/.pre-commit-config.yaml +0 -6
- python_izone-1.3.3/.pylintrc +0 -609
- python_izone-1.3.3/.vscode/settings.json +0 -26
- python_izone-1.3.3/pizone/__init__.py +0 -31
- python_izone-1.3.3/pizone/discovery.py +0 -521
- python_izone-1.3.3/pizone/exceptions.py +0 -10
- python_izone-1.3.3/pyproject.toml +0 -103
- {python_izone-1.3.3 → python_izone-1.3.5}/.gitattributes +0 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/.gitignore +0 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/.python-version +0 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/.vscode/.ropeproject/config.py +0 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/.vscode/launch.json +0 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/api_docs/AC-DOC-1401-11_iZoneEthernetInterface.pdf +0 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/api_docs/iPower_JSON_datastrings_Out.h +0 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/api_docs/iZone_JSON_datastrings.h +0 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/licence.txt +0 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/pizone/py.typed +0 -0
- {python_izone-1.3.3 → python_izone-1.3.5}/scripts/coverage +0 -0
- {python_izone-1.3.3 → 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,55 @@
|
|
|
1
|
+
# Release notes — v1.3.4
|
|
2
|
+
|
|
3
|
+
Released 2026-07-12. Maintenance and modernization release for the Home Assistant
|
|
4
|
+
iZone integration.
|
|
5
|
+
|
|
6
|
+
Shipped as **1.3.4** because PyPI versions `1.3.0`–`1.3.3` from an earlier
|
|
7
|
+
premature release line remain yanked and cannot be replaced.
|
|
8
|
+
|
|
9
|
+
**Compare range:** https://github.com/Swamp-Ig/pizone/compare/v1.2.10...v1.3.4
|
|
10
|
+
|
|
11
|
+
## Summary
|
|
12
|
+
|
|
13
|
+
Since `v1.2.10`, the library has been brought up to current Python packaging
|
|
14
|
+
and typing practice, moved its HTTP stack to aiohttp, clarified how connection
|
|
15
|
+
and command failures are reported, expanded test coverage, and tightened
|
|
16
|
+
behaviour around partial device outages.
|
|
17
|
+
|
|
18
|
+
## Packaging, dependencies, and development
|
|
19
|
+
|
|
20
|
+
- **Python 3.14** minimum
|
|
21
|
+
- Development and releases migrated to **uv** and **Hatchling** (VCS versioning, `py.typed`)
|
|
22
|
+
- **aiohttp** (`>=3.14.1`) replaces the custom HTTP client; request timeout raised to **10 seconds**
|
|
23
|
+
- **`ifaddr`** replaces `netifaces` for broadcast discovery
|
|
24
|
+
- Stricter **mypy** configuration; typing and style cleanup across the codebase
|
|
25
|
+
- CI updated (current GitHub Actions, **PyPI trusted publishing**); hardware tests excluded from default CI runs
|
|
26
|
+
- Unit test coverage raised to **~92%** with dedicated power, HTTP, and discovery tests
|
|
27
|
+
|
|
28
|
+
## Error handling and connection behaviour
|
|
29
|
+
|
|
30
|
+
- **Cached property reads** no longer raise when disconnected; async I/O methods raise **`ConnectionError`** for transport failures
|
|
31
|
+
- **`ControllerCommandError`** for reachable devices that reject a request (`{ERROR}` POST body or HTTP 4xx)
|
|
32
|
+
- **`ResponseDecodeError`** for bridge-reachable HTTP responses that cannot be decoded as JSON (keeps `bridge_connected` true without string-matching exception messages)
|
|
33
|
+
- **Layered connection state:** `bridge_connected` (ASH bridge HTTP) and `connected` (bridge plus valid iZone AC data). Fault `SystemSettings` no longer overwrites cached state; Home Assistant can mark entities unavailable through existing disconnect listeners during partial outages (bridge up, AC subsystem down)
|
|
34
|
+
- **Fault-safe property reads:** empty or fault-shaped system settings return safe defaults so polling and entity bootstrap do not crash on `KeyError` or invalid enum values
|
|
35
|
+
- **`Power.connected`** tracks power monitor I/O separately and does not clear `Controller.connected`
|
|
36
|
+
- Optional subsystem I/O (power, V2 probe) uses untracked HTTP helpers so init-time probe failures do not poison controller connection state
|
|
37
|
+
- Connection restore and reconnect notification fixes, including deduplication of failed init attempts and a `DiscoveryService` shutdown race
|
|
38
|
+
|
|
39
|
+
## Protocol and performance
|
|
40
|
+
|
|
41
|
+
- **Zones 13–14** supported via the zone extender endpoint (`Zones13_14`)
|
|
42
|
+
- Controller resources refreshed **concurrently** (`asyncio.gather` with serialized HTTP via `_sending_lock`)
|
|
43
|
+
- Runtime **V2** and **iPower** capability detection via non-fatal init probes (overrides or supplements discovery datagram hints)
|
|
44
|
+
|
|
45
|
+
## Documentation
|
|
46
|
+
|
|
47
|
+
- Public API and I/O method docstrings document exception behaviour and cached-read semantics
|
|
48
|
+
- Package docstring documents layered connection properties (`bridge_connected`, `connected`, `Power.connected`)
|
|
49
|
+
- README and inline docs updated for aiohttp requirements and development workflow
|
|
50
|
+
|
|
51
|
+
## Other fixes
|
|
52
|
+
|
|
53
|
+
- Discovery accepts `X` in the AC capability slot (per iZone docs, means subsystem unavailable in that slot — not an invalid bridge)
|
|
54
|
+
- Power `status_last_reading` key correction
|
|
55
|
+
- `controller_reconnected` KeyError when UID missing from internal disconnect tracking
|
|
@@ -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
|
+
]
|