python-izone 1.3.1__py3-none-any.whl → 1.3.2__py3-none-any.whl
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.
- pizone/controller.py +58 -14
- pizone/power.py +18 -4
- pizone/version.py +2 -2
- {python_izone-1.3.1.dist-info → python_izone-1.3.2.dist-info}/METADATA +1 -1
- python_izone-1.3.2.dist-info/RECORD +12 -0
- python_izone-1.3.1.dist-info/RECORD +0 -12
- {python_izone-1.3.1.dist-info → python_izone-1.3.2.dist-info}/WHEEL +0 -0
- {python_izone-1.3.1.dist-info → python_izone-1.3.2.dist-info}/licenses/licence.txt +0 -0
pizone/controller.py
CHANGED
|
@@ -122,7 +122,7 @@ class Controller:
|
|
|
122
122
|
"""Load system, zone, and optional power data from the device.
|
|
123
123
|
|
|
124
124
|
Raises:
|
|
125
|
-
ConnectionError: If
|
|
125
|
+
ConnectionError: If a required HTTP request fails.
|
|
126
126
|
KeyError: If a required field is missing from a device response.
|
|
127
127
|
"""
|
|
128
128
|
await self._refresh_system(notify=False)
|
|
@@ -131,24 +131,58 @@ class Controller:
|
|
|
131
131
|
str(self._system_settings.get("FanAuto", "disabled"))
|
|
132
132
|
]
|
|
133
133
|
|
|
134
|
+
await self._probe_v2_api()
|
|
135
|
+
|
|
134
136
|
zone_count = int(self._system_settings["NoOfZones"])
|
|
135
137
|
self.zones = [Zone(self, i) for i in range(zone_count)]
|
|
136
138
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
self._refresh_zones(notify=False),
|
|
141
|
-
self._power.init(),
|
|
142
|
-
)
|
|
143
|
-
if self._power.enabled:
|
|
144
|
-
await self._refresh_power(notify=False)
|
|
145
|
-
else:
|
|
146
|
-
self._power = None
|
|
147
|
-
await self._refresh_zones(notify=False)
|
|
139
|
+
await self._refresh_zones(notify=False)
|
|
140
|
+
|
|
141
|
+
await self._probe_power()
|
|
148
142
|
|
|
149
143
|
self._initialized = True
|
|
150
144
|
self._discovery_service.create_task(self._poll_loop())
|
|
151
145
|
|
|
146
|
+
async def _probe_v2_api(self) -> None:
|
|
147
|
+
"""Detect V2 API support; non-fatal on failure."""
|
|
148
|
+
try:
|
|
149
|
+
response = await self._send_command_async(
|
|
150
|
+
"iZoneRequestV2",
|
|
151
|
+
{"iZoneV2Request": {"Type": 1, "No": 0, "No1": 0}},
|
|
152
|
+
mark_disconnected=False,
|
|
153
|
+
)
|
|
154
|
+
data = json.loads(response)
|
|
155
|
+
uid = data["AirStreamDeviceUId"]
|
|
156
|
+
self._is_v2 = uid == self._device_uid and "SystemV2" in data
|
|
157
|
+
except (ConnectionError, ControllerCommandError, json.JSONDecodeError, KeyError):
|
|
158
|
+
self._is_v2 = False
|
|
159
|
+
|
|
160
|
+
async def _probe_power(self) -> None:
|
|
161
|
+
"""Probe power monitor endpoint when discovery hinted iPower; non-fatal."""
|
|
162
|
+
if not self._is_ipower:
|
|
163
|
+
self._power = None
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
power = Power(self)
|
|
168
|
+
await power.init(mark_disconnected=False)
|
|
169
|
+
if power.enabled:
|
|
170
|
+
self._power = power
|
|
171
|
+
return
|
|
172
|
+
_LOG.warning(
|
|
173
|
+
"Power monitor disabled on uid=%s; skipping power support",
|
|
174
|
+
self._device_uid,
|
|
175
|
+
)
|
|
176
|
+
except (ConnectionError, ControllerCommandError, json.JSONDecodeError, KeyError):
|
|
177
|
+
_LOG.warning(
|
|
178
|
+
"Power monitor probe failed for uid=%s",
|
|
179
|
+
self._device_uid,
|
|
180
|
+
exc_info=True,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
self._is_ipower = False
|
|
184
|
+
self._power = None
|
|
185
|
+
|
|
152
186
|
async def _poll_loop(self) -> None:
|
|
153
187
|
while True:
|
|
154
188
|
try:
|
|
@@ -206,6 +240,11 @@ class Controller:
|
|
|
206
240
|
"""Return whether this is a v2 controller."""
|
|
207
241
|
return self._is_v2
|
|
208
242
|
|
|
243
|
+
@property
|
|
244
|
+
def is_ipower(self) -> bool:
|
|
245
|
+
"""Return whether power monitoring is available."""
|
|
246
|
+
return self._is_ipower
|
|
247
|
+
|
|
209
248
|
@property
|
|
210
249
|
def discovery(self) -> DiscoveryService:
|
|
211
250
|
"""Discovery service for this controller."""
|
|
@@ -630,10 +669,14 @@ class Controller:
|
|
|
630
669
|
raise ConnectionError("Unable to connect to the controller") from ex
|
|
631
670
|
|
|
632
671
|
async def _send_command_async(
|
|
633
|
-
self, command: str, data: dict[str, Any]
|
|
672
|
+
self, command: str, data: dict[str, Any], *, mark_disconnected: bool = True
|
|
634
673
|
) -> str:
|
|
635
674
|
"""Send a command to the device via HTTP POST.
|
|
636
675
|
|
|
676
|
+
Args:
|
|
677
|
+
mark_disconnected: When ``False``, transport failures do not mark
|
|
678
|
+
the controller disconnected.
|
|
679
|
+
|
|
637
680
|
Raises:
|
|
638
681
|
ConnectionError: If the device cannot be reached or the HTTP request fails.
|
|
639
682
|
ControllerCommandError: If the device returns HTTP 4xx or an
|
|
@@ -665,7 +708,8 @@ class Controller:
|
|
|
665
708
|
)
|
|
666
709
|
result = await response.text(encoding="latin_1")
|
|
667
710
|
except (asyncio.TimeoutError, aiohttp.ClientError) as ex:
|
|
668
|
-
|
|
711
|
+
if mark_disconnected:
|
|
712
|
+
self._failed_connection(ex)
|
|
669
713
|
raise ConnectionError("Unable to connect to controller") from ex
|
|
670
714
|
|
|
671
715
|
if len(result) >= 7 and result[:6] == "{ERROR":
|
pizone/power.py
CHANGED
|
@@ -191,15 +191,21 @@ class Power:
|
|
|
191
191
|
self._devices = tuple(PowerDevice(self, i) for i in range(5))
|
|
192
192
|
self._groups: tuple[PowerGroup, ...] | None = None
|
|
193
193
|
|
|
194
|
-
async def init(self) -> None:
|
|
194
|
+
async def init(self, *, mark_disconnected: bool = True) -> None:
|
|
195
195
|
"""Load power monitor configuration from the device.
|
|
196
196
|
|
|
197
|
+
Args:
|
|
198
|
+
mark_disconnected: When ``False``, connection failures during this
|
|
199
|
+
request do not mark the controller disconnected.
|
|
200
|
+
|
|
197
201
|
Raises:
|
|
198
202
|
ConnectionError: If the HTTP request fails.
|
|
199
203
|
json.JSONDecodeError: If the device response is not valid JSON.
|
|
200
204
|
KeyError: If the response is missing required fields.
|
|
201
205
|
"""
|
|
202
|
-
self._config = await self._do_request(
|
|
206
|
+
self._config = await self._do_request(
|
|
207
|
+
1, "PowerMonitorConfig", mark_disconnected=mark_disconnected
|
|
208
|
+
)
|
|
203
209
|
gdict: dict[int, list[PowerChannel]] = {}
|
|
204
210
|
for dev in self.devices:
|
|
205
211
|
for chan in dev.channels:
|
|
@@ -226,9 +232,15 @@ class Power:
|
|
|
226
232
|
self._status = status
|
|
227
233
|
return True
|
|
228
234
|
|
|
229
|
-
async def _do_request(
|
|
235
|
+
async def _do_request(
|
|
236
|
+
self, req_type: int, result: str, *, mark_disconnected: bool = True
|
|
237
|
+
) -> dict[str, Any]:
|
|
230
238
|
"""Send a power monitor request to the device.
|
|
231
239
|
|
|
240
|
+
Args:
|
|
241
|
+
mark_disconnected: When ``False``, connection failures do not mark
|
|
242
|
+
the controller disconnected.
|
|
243
|
+
|
|
232
244
|
Raises:
|
|
233
245
|
ConnectionError: If the HTTP request fails.
|
|
234
246
|
json.JSONDecodeError: If the device response is not valid JSON.
|
|
@@ -236,7 +248,9 @@ class Power:
|
|
|
236
248
|
"""
|
|
237
249
|
# pylint: disable=protected-access
|
|
238
250
|
datas = await self._controller._send_command_async(
|
|
239
|
-
"PowerRequest",
|
|
251
|
+
"PowerRequest",
|
|
252
|
+
{"PowerRequest": {"Type": req_type, "No": 0, "No1": 0}},
|
|
253
|
+
mark_disconnected=mark_disconnected,
|
|
240
254
|
)
|
|
241
255
|
data = json.loads(datas)
|
|
242
256
|
return cast(dict[str, Any], data[result])
|
pizone/version.py
CHANGED
|
@@ -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.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 3, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pizone/__init__.py,sha256=xRQgtXzCtsGhJDBOzDxV105Trv2NCFe8Em6fZEqZ4bc,997
|
|
2
|
+
pizone/controller.py,sha256=hO2B7sIZt8Djt_Nq0_ov7ocNxBy9Nk_DtkcEmixFt2s,26114
|
|
3
|
+
pizone/discovery.py,sha256=tvZs5npvL2y41vyYxn5V6EDCI9v27_Z-vy_ezSN-RA4,18133
|
|
4
|
+
pizone/exceptions.py,sha256=mWsvXzBNh6mKOPs0QFLqgO6ipa6Se6PEGhFTwr9_ehA,362
|
|
5
|
+
pizone/power.py,sha256=4kWS_XilZki7dLzlJ0cKYoz8s6pS76bAtrHhUXTZ_eg,9039
|
|
6
|
+
pizone/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
pizone/version.py,sha256=3UteJz_pk76FGRZGb0537LBHxRlaYKkTw4QYKVpfrjs,520
|
|
8
|
+
pizone/zone.py,sha256=rDv-Q9k9HGIzK3arBxpKoAa-xRGETQkgBOTulMdOhqc,7163
|
|
9
|
+
python_izone-1.3.2.dist-info/METADATA,sha256=pEMLJ6CTDnghdy4FJ8wIducmpD-ZxAnVPv26t347FuQ,2657
|
|
10
|
+
python_izone-1.3.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
11
|
+
python_izone-1.3.2.dist-info/licenses/licence.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
12
|
+
python_izone-1.3.2.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
pizone/__init__.py,sha256=xRQgtXzCtsGhJDBOzDxV105Trv2NCFe8Em6fZEqZ4bc,997
|
|
2
|
-
pizone/controller.py,sha256=mR7-tJkdkXx-s7M32ZWpOUED8Gw_7NPTkUnLjnPhB2Q,24568
|
|
3
|
-
pizone/discovery.py,sha256=tvZs5npvL2y41vyYxn5V6EDCI9v27_Z-vy_ezSN-RA4,18133
|
|
4
|
-
pizone/exceptions.py,sha256=mWsvXzBNh6mKOPs0QFLqgO6ipa6Se6PEGhFTwr9_ehA,362
|
|
5
|
-
pizone/power.py,sha256=Rp5ZoT-L8YR0mraF8YNaUqfgZTvgSZSNuBT0U5Nzt70,8536
|
|
6
|
-
pizone/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
pizone/version.py,sha256=x_Vew2VGpHa9c663j9Zg8FzEkVKHvv3k0XiN4-Ja9uw,520
|
|
8
|
-
pizone/zone.py,sha256=rDv-Q9k9HGIzK3arBxpKoAa-xRGETQkgBOTulMdOhqc,7163
|
|
9
|
-
python_izone-1.3.1.dist-info/METADATA,sha256=oN4I7S2YE8C3PtBfakmqkdr6rYs-tZsENG_M4WNM1a0,2657
|
|
10
|
-
python_izone-1.3.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
11
|
-
python_izone-1.3.1.dist-info/licenses/licence.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
12
|
-
python_izone-1.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|