python-izone 1.3.2__py3-none-any.whl → 1.3.3__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 CHANGED
@@ -33,9 +33,10 @@ class Controller:
33
33
  They raise :exc:`ConnectionError` when the device cannot be reached.
34
34
  They raise :exc:`~pizone.exceptions.ControllerCommandError` when the
35
35
  device responds but rejects the request (``{ERROR...}`` body or HTTP 4xx).
36
- A successful transport request clears a prior connection failure and
37
- notifies listeners via
38
- :meth:`~pizone.discovery.Listener.controller_reconnected`.
36
+
37
+ :attr:`connected` is ``True`` when the ASH bridge is reachable over HTTP
38
+ and the iZone AC subsystem last returned valid system data. Check
39
+ :attr:`bridge_connected` for bridge transport health alone.
39
40
  """
40
41
 
41
42
  class Mode(Enum):
@@ -113,7 +114,8 @@ class Controller:
113
114
  self._power: Power | None = None
114
115
 
115
116
  self._initialized: bool = False
116
- self._fail_exception: Exception | None = None
117
+ self._bridge_ok: bool = True
118
+ self._izone_ok: bool = True
117
119
 
118
120
  self._sending_lock = Lock()
119
121
  self._scan_condition = Condition()
@@ -125,15 +127,20 @@ class Controller:
125
127
  ConnectionError: If a required HTTP request fails.
126
128
  KeyError: If a required field is missing from a device response.
127
129
  """
128
- await self._refresh_system(notify=False)
129
-
130
- self.fan_modes = Controller._VALID_FAN_MODES[
131
- str(self._system_settings.get("FanAuto", "disabled"))
132
- ]
130
+ settings = await self._refresh_system(notify=False)
131
+ if settings is None:
132
+ raise ConnectionError("SystemSettings device ID mismatch")
133
+
134
+ if self._system_settings:
135
+ self.fan_modes = Controller._VALID_FAN_MODES[
136
+ str(self._system_settings.get("FanAuto", "disabled"))
137
+ ]
138
+ else:
139
+ self.fan_modes = Controller._VALID_FAN_MODES["disabled"]
133
140
 
134
141
  await self._probe_v2_api()
135
142
 
136
- zone_count = int(self._system_settings["NoOfZones"])
143
+ zone_count = int(settings["NoOfZones"])
137
144
  self.zones = [Zone(self, i) for i in range(zone_count)]
138
145
 
139
146
  await self._refresh_zones(notify=False)
@@ -141,15 +148,18 @@ class Controller:
141
148
  await self._probe_power()
142
149
 
143
150
  self._initialized = True
151
+ if not self.connected:
152
+ self._event_coordinator.controller_disconnected(
153
+ self, ConnectionError("iZone controller unavailable")
154
+ )
144
155
  self._discovery_service.create_task(self._poll_loop())
145
156
 
146
157
  async def _probe_v2_api(self) -> None:
147
158
  """Detect V2 API support; non-fatal on failure."""
148
159
  try:
149
- response = await self._send_command_async(
160
+ response = await self._http_post(
150
161
  "iZoneRequestV2",
151
162
  {"iZoneV2Request": {"Type": 1, "No": 0, "No1": 0}},
152
- mark_disconnected=False,
153
163
  )
154
164
  data = json.loads(response)
155
165
  uid = data["AirStreamDeviceUId"]
@@ -165,7 +175,7 @@ class Controller:
165
175
 
166
176
  try:
167
177
  power = Power(self)
168
- await power.init(mark_disconnected=False)
178
+ await power.init()
169
179
  if power.enabled:
170
180
  self._power = power
171
181
  return
@@ -215,10 +225,15 @@ class Controller:
215
225
  async with self._scan_condition:
216
226
  self._scan_condition.notify()
217
227
 
228
+ @property
229
+ def bridge_connected(self) -> bool:
230
+ """True while the ASH bridge is reachable over HTTP."""
231
+ return self._bridge_ok
232
+
218
233
  @property
219
234
  def connected(self) -> bool:
220
- """True while the controller can reach the device over HTTP."""
221
- return self._fail_exception is None
235
+ """True while the bridge is up and the iZone AC subsystem is available."""
236
+ return self._bridge_ok and self._izone_ok
222
237
 
223
238
  @property
224
239
  def power(self) -> Power | None:
@@ -476,19 +491,25 @@ class Controller:
476
491
  ConnectionError: If any HTTP request fails.
477
492
  KeyError: If a required field is missing from a device response.
478
493
  """
479
- zones = int(self._system_settings["NoOfZones"])
480
- # gather schedules all refresh coroutines together; _sending_lock serializes
481
- # HTTP to one in-flight request per controller. Revisit the lock if firmware
482
- # supports concurrent requests — gather can stay.
494
+ zones = len(self.zones)
495
+ if zones == 0:
496
+ await asyncio.gather(
497
+ self._refresh_system(notify),
498
+ self._refresh_power(notify),
499
+ )
500
+ return
483
501
  await asyncio.gather(
484
502
  self._refresh_system(notify),
485
503
  self._refresh_power(notify),
486
504
  *[self._refresh_zone_group(i, notify) for i in range(0, zones, 4)],
487
505
  )
488
506
 
489
- async def _refresh_system(self, notify: bool = True) -> None:
507
+ async def _refresh_system(self, notify: bool = True) -> ControllerData | None:
490
508
  """Refresh the system settings from the device.
491
509
 
510
+ Returns the device response even when the payload is a fault placeholder,
511
+ so callers can read fields like ``NoOfZones`` during initialization.
512
+
492
513
  Raises:
493
514
  ConnectionError: If the HTTP request fails.
494
515
  KeyError: If the response is missing required fields.
@@ -496,25 +517,40 @@ class Controller:
496
517
  values: Controller.ControllerData = await self._get_resource("SystemSettings")
497
518
  if self._device_uid != values["AirStreamDeviceUId"]:
498
519
  _LOG.error("_refresh_system called with non-matching device ID")
499
- return
520
+ return None
500
521
 
501
- self._system_settings = values
522
+ if not self._system_settings_valid(values):
523
+ _LOG.warning(
524
+ "iZone subsystem fault uid=%s; retaining cache",
525
+ self._device_uid,
526
+ )
527
+ self._set_izone_ok(
528
+ False, ConnectionError("iZone controller unavailable")
529
+ )
530
+ if notify:
531
+ self._event_coordinator.controller_update(self)
532
+ return values
502
533
 
534
+ self._system_settings = values
535
+ self._set_izone_ok(True)
503
536
  if notify:
504
537
  self._event_coordinator.controller_update(self)
538
+ return values
505
539
 
506
540
  async def _refresh_power(self, notify: bool = True) -> None:
507
- """Refresh power monitor data when enabled.
508
-
509
- Raises:
510
- ConnectionError: If the HTTP request fails.
511
- json.JSONDecodeError: If the device response is not valid JSON.
512
- KeyError: If the response is missing required fields.
513
- """
541
+ """Refresh power monitor data when enabled."""
514
542
  if self._power is None or not self._power.enabled:
515
543
  return
516
544
 
517
- updated = await self._power.refresh()
545
+ try:
546
+ updated = await self._power.refresh()
547
+ except (ConnectionError, ControllerCommandError, json.JSONDecodeError, KeyError):
548
+ _LOG.warning(
549
+ "Power refresh failed for uid=%s",
550
+ self._device_uid,
551
+ exc_info=True,
552
+ )
553
+ return
518
554
 
519
555
  if updated and notify:
520
556
  self._event_coordinator.power_update(self)
@@ -527,7 +563,9 @@ class Controller:
527
563
  KeyError: If a response is missing required fields.
528
564
  AttributeError: If a zone index in the response does not match.
529
565
  """
530
- zones = int(self._system_settings["NoOfZones"])
566
+ zones = len(self.zones)
567
+ if zones == 0:
568
+ return
531
569
  await asyncio.gather(
532
570
  *[self._refresh_zone_group(i, notify) for i in range(0, zones, 4)]
533
571
  )
@@ -555,10 +593,18 @@ class Controller:
555
593
  def _refresh_address(self, address: str) -> None:
556
594
  """Update the device IP and schedule a reconnect attempt if needed."""
557
595
  self._ip = address
558
- # Signal to the retry connection loop to have another go.
559
- if self._fail_exception:
596
+ if not self._bridge_ok:
560
597
  self._discovery_service.create_task(self._retry_connection())
561
598
 
599
+ @staticmethod
600
+ def _system_settings_valid(values: ControllerData) -> bool:
601
+ """Return whether *values* look like healthy AC subsystem data."""
602
+ return (
603
+ int(values["NoOfZones"]) > 0
604
+ and str(values["SysFan"]) != "error"
605
+ and str(values["RAS"]) != "error"
606
+ )
607
+
562
608
  def _get_system_state(self, state: str) -> DictValue:
563
609
  return self._system_settings[state]
564
610
 
@@ -583,33 +629,47 @@ class Controller:
583
629
  self._event_coordinator.controller_update(self)
584
630
  await self.refresh()
585
631
 
586
- def _failed_connection(self, ex: Exception) -> None:
587
- if self._fail_exception:
588
- self._fail_exception = ex
589
- return
590
- self._fail_exception = ex
591
- if not self._initialized:
632
+ def _set_bridge_ok(self, ok: bool, ex: Exception | None = None) -> None:
633
+ was_connected = self.connected
634
+ self._bridge_ok = ok
635
+ if not ok and ex is None:
636
+ ex = ConnectionError("Unable to connect to the controller")
637
+ self._notify_connected_changed(was_connected, ex if not ok else None)
638
+
639
+ def _set_izone_ok(self, ok: bool, ex: Exception | None = None) -> None:
640
+ was_connected = self.connected
641
+ self._izone_ok = ok
642
+ if not ok and ex is None:
643
+ ex = ConnectionError("iZone controller unavailable")
644
+ self._notify_connected_changed(was_connected, ex if not ok else None)
645
+
646
+ def _notify_connected_changed(
647
+ self, was_connected: bool, ex: Exception | None
648
+ ) -> None:
649
+ if was_connected == self.connected or not self._initialized:
592
650
  return
593
- self._event_coordinator.controller_disconnected(self, ex)
651
+ if self.connected:
652
+ self._event_coordinator.controller_update(self)
653
+ for zone in self.zones:
654
+ self._event_coordinator.zone_update(self, zone)
655
+ self._event_coordinator.power_update(self)
656
+ self._event_coordinator.controller_reconnected(self)
657
+ elif ex is not None:
658
+ self._event_coordinator.controller_disconnected(self, ex)
659
+
660
+ def _failed_connection(self, ex: Exception) -> None:
661
+ """Mark the bridge transport as failed."""
662
+ self._set_bridge_ok(False, ex)
594
663
 
595
664
  def _restored_connection(self) -> None:
596
- """Clear a prior connection failure after successful I/O."""
597
- if self._fail_exception is None:
598
- return
599
- self._fail_exception = None
600
- if not self._initialized:
601
- return
602
- self._event_coordinator.controller_update(self)
603
- for zone in self.zones:
604
- self._event_coordinator.zone_update(self, zone)
605
- self._event_coordinator.power_update(self)
606
- self._event_coordinator.controller_reconnected(self)
665
+ """Mark the bridge transport as restored."""
666
+ self._set_bridge_ok(True)
607
667
 
608
668
  async def _retry_connection(self) -> None:
609
669
  """Attempt to restore connectivity after a connection failure.
610
670
 
611
671
  Connection errors are logged and not re-raised. A successful refresh
612
- restores :attr:`connected` via :meth:`_restored_connection`.
672
+ restores :attr:`connected` when both bridge and iZone layers recover.
613
673
  """
614
674
  _LOG.info(
615
675
  "Attempting to reconnect to server uid=%s ip=%s",
@@ -627,6 +687,77 @@ class Controller:
627
687
  exc_info=True,
628
688
  )
629
689
 
690
+ async def _http_get(self, resource: str) -> Any:
691
+ """Fetch a JSON resource via HTTP GET without updating connection state.
692
+
693
+ Raises:
694
+ ConnectionError: If the device cannot be reached or the response
695
+ cannot be decoded.
696
+ ControllerCommandError: If the device returns HTTP 4xx.
697
+ """
698
+ session = self._discovery_service.session
699
+ if session is None:
700
+ raise ConnectionError("Discovery service is not started")
701
+ async with (
702
+ self._sending_lock,
703
+ session.get(
704
+ f"http://{self.device_ip}/{resource}",
705
+ timeout=aiohttp.ClientTimeout(total=Controller.REQUEST_TIMEOUT),
706
+ ) as response,
707
+ ):
708
+ if response.status >= 400 and response.status < 500:
709
+ raise ControllerCommandError(
710
+ f"HTTP {response.status} for http://{self.device_ip}/{resource}"
711
+ )
712
+ try:
713
+ return await response.json(content_type=None)
714
+ except json.JSONDecodeError as ex:
715
+ text = await response.text()
716
+ if text[-4:] == "{OK}":
717
+ return json.loads(text[:-4])
718
+ _LOG.error('Decode error for "%s"', text, exc_info=True)
719
+ raise ConnectionError(
720
+ "Unable to decode response from the controller"
721
+ ) from ex
722
+
723
+ async def _http_post(self, command: str, data: dict[str, Any]) -> str:
724
+ """Send a command via HTTP POST without updating connection state.
725
+
726
+ Raises:
727
+ ConnectionError: If the device cannot be reached or the HTTP request fails.
728
+ ControllerCommandError: If the device returns HTTP 4xx or an
729
+ ``{ERROR...}`` payload.
730
+ """
731
+ session = self._discovery_service.session
732
+ if session is None:
733
+ raise ConnectionError("Discovery service is not started")
734
+ body = json.dumps(data).encode("latin_1")
735
+ async with (
736
+ self._sending_lock,
737
+ session.post(
738
+ f"http://{self.device_ip}/{command}",
739
+ data=body,
740
+ headers={"Connection": "close"},
741
+ timeout=aiohttp.ClientTimeout(total=Controller.REQUEST_TIMEOUT),
742
+ ) as response,
743
+ ):
744
+ if response.status >= 400 and response.status < 500:
745
+ raise ControllerCommandError(
746
+ f"HTTP {response.status} for http://{self.device_ip}/{command}"
747
+ )
748
+ if response.status != 200:
749
+ raise ConnectionError(
750
+ f"Unable to connect to: http://{self.device_ip}/{command}"
751
+ f" response={response.status} message={response.reason}"
752
+ )
753
+ result = await response.text(encoding="latin_1")
754
+
755
+ if len(result) >= 7 and result[:6] == "{ERROR":
756
+ raise ControllerCommandError(f"Server returned error state {result}")
757
+ if len(result) >= 4 and result[-4:] == "{OK}":
758
+ result = result[:-4]
759
+ return result
760
+
630
761
  async def _get_resource(self, resource: str) -> Any:
631
762
  """Fetch a JSON resource from the device via HTTP GET.
632
763
 
@@ -636,86 +767,44 @@ class Controller:
636
767
  ControllerCommandError: If the device returns HTTP 4xx.
637
768
  """
638
769
  try:
639
- session = self._discovery_service.session
640
- if session is None:
641
- raise ConnectionError("Discovery service is not started")
642
- async with (
643
- self._sending_lock,
644
- session.get(
645
- f"http://{self.device_ip}/{resource}",
646
- timeout=aiohttp.ClientTimeout(total=Controller.REQUEST_TIMEOUT),
647
- ) as response,
648
- ):
649
- if response.status >= 400 and response.status < 500:
650
- self._restored_connection()
651
- raise ControllerCommandError(
652
- f"HTTP {response.status} for http://{self.device_ip}/{resource}"
653
- )
654
- try:
655
- result = await response.json(content_type=None)
656
- except json.JSONDecodeError as ex:
657
- text = await response.text()
658
- if text[-4:] == "{OK}":
659
- result = json.loads(text[:-4])
660
- else:
661
- _LOG.error('Decode error for "%s"', text, exc_info=True)
662
- raise ConnectionError(
663
- "Unable to decode response from the controller"
664
- ) from ex
665
- self._restored_connection()
666
- return result
770
+ result = await self._http_get(resource)
771
+ except ControllerCommandError:
772
+ self._set_bridge_ok(True)
773
+ raise
667
774
  except (asyncio.TimeoutError, aiohttp.ClientError) as ex:
668
- self._failed_connection(ex)
775
+ self._set_bridge_ok(
776
+ False, ConnectionError("Unable to connect to the controller")
777
+ )
669
778
  raise ConnectionError("Unable to connect to the controller") from ex
779
+ except ConnectionError as ex:
780
+ if str(ex) == "Unable to decode response from the controller":
781
+ self._set_bridge_ok(True)
782
+ else:
783
+ self._set_bridge_ok(False, ex)
784
+ raise
785
+ self._set_bridge_ok(True)
786
+ return result
670
787
 
671
- async def _send_command_async(
672
- self, command: str, data: dict[str, Any], *, mark_disconnected: bool = True
673
- ) -> str:
788
+ async def _send_command_async(self, command: str, data: dict[str, Any]) -> str:
674
789
  """Send a command to the device via HTTP POST.
675
790
 
676
- Args:
677
- mark_disconnected: When ``False``, transport failures do not mark
678
- the controller disconnected.
679
-
680
791
  Raises:
681
792
  ConnectionError: If the device cannot be reached or the HTTP request fails.
682
793
  ControllerCommandError: If the device returns HTTP 4xx or an
683
794
  ``{ERROR...}`` payload.
684
795
  """
685
796
  try:
686
- session = self._discovery_service.session
687
- if session is None:
688
- raise ConnectionError("Discovery service is not started")
689
- body = json.dumps(data).encode("latin_1")
690
- async with (
691
- self._sending_lock,
692
- session.post(
693
- f"http://{self.device_ip}/{command}",
694
- data=body,
695
- headers={"Connection": "close"},
696
- timeout=aiohttp.ClientTimeout(total=Controller.REQUEST_TIMEOUT),
697
- ) as response,
698
- ):
699
- if response.status >= 400 and response.status < 500:
700
- self._restored_connection()
701
- raise ControllerCommandError(
702
- f"HTTP {response.status} for http://{self.device_ip}/{command}"
703
- )
704
- if response.status != 200:
705
- raise aiohttp.ClientError(
706
- f"Unable to connect to: http://{self.device_ip}/{command}"
707
- f" response={response.status} message={response.reason}"
708
- )
709
- result = await response.text(encoding="latin_1")
797
+ result = await self._http_post(command, data)
798
+ except ControllerCommandError:
799
+ self._set_bridge_ok(True)
800
+ raise
710
801
  except (asyncio.TimeoutError, aiohttp.ClientError) as ex:
711
- if mark_disconnected:
712
- self._failed_connection(ex)
802
+ self._set_bridge_ok(
803
+ False, ConnectionError("Unable to connect to controller")
804
+ )
713
805
  raise ConnectionError("Unable to connect to controller") from ex
714
-
715
- if len(result) >= 7 and result[:6] == "{ERROR":
716
- self._restored_connection()
717
- raise ControllerCommandError(f"Server returned error state {result}")
718
- if len(result) >= 4 and result[-4:] == "{OK}":
719
- result = result[:-4]
720
- self._restored_connection()
806
+ except ConnectionError as ex:
807
+ self._set_bridge_ok(False, ex)
808
+ raise
809
+ self._set_bridge_ok(True)
721
810
  return result
pizone/discovery.py CHANGED
@@ -415,7 +415,7 @@ class DiscoveryService:
415
415
  return ctrl
416
416
  return None
417
417
 
418
- async def _wrap_update(self, coro: Awaitable[None]) -> None:
418
+ async def _wrap_update(self, coro: Awaitable[object]) -> None:
419
419
  """Run a controller refresh coroutine and log connection failures."""
420
420
  try:
421
421
  await coro
pizone/power.py CHANGED
@@ -12,6 +12,8 @@ from enum import IntEnum, unique
12
12
  from collections.abc import Iterable
13
13
  from typing import TYPE_CHECKING, Any, cast
14
14
 
15
+ from .exceptions import ControllerCommandError
16
+
15
17
  if TYPE_CHECKING:
16
18
  from .controller import Controller
17
19
 
@@ -190,22 +192,17 @@ class Power:
190
192
  self._status: dict[str, Any] = {"LastReadingNo": -1}
191
193
  self._devices = tuple(PowerDevice(self, i) for i in range(5))
192
194
  self._groups: tuple[PowerGroup, ...] | None = None
195
+ self._power_ok: bool = True
193
196
 
194
- async def init(self, *, mark_disconnected: bool = True) -> None:
197
+ async def init(self) -> None:
195
198
  """Load power monitor configuration from the device.
196
199
 
197
- Args:
198
- mark_disconnected: When ``False``, connection failures during this
199
- request do not mark the controller disconnected.
200
-
201
200
  Raises:
202
201
  ConnectionError: If the HTTP request fails.
203
202
  json.JSONDecodeError: If the device response is not valid JSON.
204
203
  KeyError: If the response is missing required fields.
205
204
  """
206
- self._config = await self._do_request(
207
- 1, "PowerMonitorConfig", mark_disconnected=mark_disconnected
208
- )
205
+ self._config = await self._do_request(1, "PowerMonitorConfig")
209
206
  gdict: dict[int, list[PowerChannel]] = {}
210
207
  for dev in self.devices:
211
208
  for chan in dev.channels:
@@ -232,28 +229,53 @@ class Power:
232
229
  self._status = status
233
230
  return True
234
231
 
235
- async def _do_request(
236
- self, req_type: int, result: str, *, mark_disconnected: bool = True
237
- ) -> dict[str, Any]:
232
+ async def _do_request(self, req_type: int, result: str) -> dict[str, Any]:
238
233
  """Send a power monitor request to the device.
239
234
 
240
- Args:
241
- mark_disconnected: When ``False``, connection failures do not mark
242
- the controller disconnected.
243
-
244
235
  Raises:
245
236
  ConnectionError: If the HTTP request fails.
246
237
  json.JSONDecodeError: If the device response is not valid JSON.
247
238
  KeyError: If the response is missing *result*.
248
239
  """
240
+ if not self._controller.bridge_connected:
241
+ self._set_power_ok(False)
242
+ raise ConnectionError("Bridge not connected")
243
+ try:
244
+ # pylint: disable=protected-access
245
+ datas = await self._controller._http_post(
246
+ "PowerRequest",
247
+ {"PowerRequest": {"Type": req_type, "No": 0, "No1": 0}},
248
+ )
249
+ data = json.loads(datas)
250
+ payload = cast(dict[str, Any], data[result])
251
+ except (
252
+ ConnectionError,
253
+ ControllerCommandError,
254
+ json.JSONDecodeError,
255
+ KeyError,
256
+ ) as ex:
257
+ self._set_power_ok(False)
258
+ if isinstance(ex, ConnectionError):
259
+ raise
260
+ if isinstance(ex, json.JSONDecodeError):
261
+ raise ConnectionError("Invalid power monitor response") from ex
262
+ if isinstance(ex, KeyError):
263
+ raise ConnectionError("Invalid power monitor response") from ex
264
+ raise ConnectionError("Power monitor request failed") from ex
265
+ self._set_power_ok(True)
266
+ return payload
267
+
268
+ def _set_power_ok(self, ok: bool) -> None:
269
+ if self._power_ok == ok:
270
+ return
271
+ self._power_ok = ok
249
272
  # pylint: disable=protected-access
250
- datas = await self._controller._send_command_async(
251
- "PowerRequest",
252
- {"PowerRequest": {"Type": req_type, "No": 0, "No1": 0}},
253
- mark_disconnected=mark_disconnected,
254
- )
255
- data = json.loads(datas)
256
- return cast(dict[str, Any], data[result])
273
+ self._controller._event_coordinator.power_update(self._controller)
274
+
275
+ @property
276
+ def connected(self) -> bool:
277
+ """True while the bridge is up and power monitor I/O is healthy."""
278
+ return self._controller.bridge_connected and self._power_ok
257
279
 
258
280
  @property
259
281
  def enabled(self) -> bool:
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.2'
22
- __version_tuple__ = version_tuple = (1, 3, 2)
21
+ __version__ = version = '1.3.3'
22
+ __version_tuple__ = version_tuple = (1, 3, 3)
23
23
 
24
24
  __commit_id__ = commit_id = None
pizone/zone.py CHANGED
@@ -187,6 +187,8 @@ class Zone:
187
187
  """
188
188
  if zone_data["Index"] != self._index:
189
189
  raise AttributeError("Can't change index of existing zone.")
190
+ if str(zone_data.get("Type")) == "error":
191
+ return
190
192
  self._zone_data = zone_data
191
193
  if notify:
192
194
  self._fire_listeners()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-izone
3
- Version: 1.3.2
3
+ Version: 1.3.3
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>
@@ -0,0 +1,12 @@
1
+ pizone/__init__.py,sha256=xRQgtXzCtsGhJDBOzDxV105Trv2NCFe8Em6fZEqZ4bc,997
2
+ pizone/controller.py,sha256=bQ74OOnvm_zD3l0t2HHxnC2vngmabscKWMjSUcp5Teg,29242
3
+ pizone/discovery.py,sha256=8sBl1QlMlkGs_gwt-neGouwpYahwrlkAH37KEw9SbH0,18135
4
+ pizone/exceptions.py,sha256=mWsvXzBNh6mKOPs0QFLqgO6ipa6Se6PEGhFTwr9_ehA,362
5
+ pizone/power.py,sha256=FsKS8LhyHS2cUDKnDX6Vc1bWhpXSZABIpPLNQERqlLk,9899
6
+ pizone/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ pizone/version.py,sha256=WO3CO-96xugvoM5BpChi50zqqd0fgW_W282M3M9MvT4,520
8
+ pizone/zone.py,sha256=0rVYIiqGXUJeT3i_EBLPOTEsFIY6nNSheMuI7Up8GzU,7232
9
+ python_izone-1.3.3.dist-info/METADATA,sha256=n1Lm9EMNZfkWnQtG2OOAddebn3jwUMpWxPzAESiy-P8,2657
10
+ python_izone-1.3.3.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
11
+ python_izone-1.3.3.dist-info/licenses/licence.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
12
+ python_izone-1.3.3.dist-info/RECORD,,
@@ -1,12 +0,0 @@
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,,