python-izone 1.3.3__py3-none-any.whl → 1.3.4__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/__init__.py CHANGED
@@ -8,17 +8,24 @@ Synchronous property reads return cached device data and do not raise
8
8
  raise :exc:`ConnectionError` when the device cannot be reached. They raise
9
9
  :exc:`~pizone.exceptions.ControllerCommandError` when the device responds but
10
10
  rejects the request (``{ERROR...}`` body or HTTP 4xx).
11
+
12
+ Layered connection state on :class:`~pizone.controller.Controller`:
13
+
14
+ - :attr:`~pizone.controller.Controller.bridge_connected` — ASH bridge HTTP transport
15
+ - :attr:`~pizone.controller.Controller.connected` — bridge plus valid iZone AC data
16
+ - :attr:`~pizone.power.Power.connected` — power monitor I/O (when enabled)
11
17
  """
12
18
 
13
19
  from .controller import Controller
14
20
  from .discovery import DiscoveryService, Listener, discovery
15
- from .exceptions import ControllerCommandError
21
+ from .exceptions import ControllerCommandError, ResponseDecodeError
16
22
  from .power import BatteryLevel, Power, PowerChannel, PowerDevice, PowerGroup
17
23
  from .zone import Zone
18
24
 
19
25
  __all__ = [
20
26
  "Controller",
21
27
  "ControllerCommandError",
28
+ "ResponseDecodeError",
22
29
  "Zone",
23
30
  "DiscoveryService",
24
31
  "Listener",
pizone/controller.py CHANGED
@@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any, cast
11
11
 
12
12
  import aiohttp
13
13
 
14
- from .exceptions import ControllerCommandError
14
+ from .exceptions import ControllerCommandError, ResponseDecodeError
15
15
  from .power import Power
16
16
  from .zone import Zone
17
17
 
@@ -79,6 +79,27 @@ class Controller:
79
79
  "var-speed": [Fan.LOW, Fan.MED, Fan.HIGH, Fan.AUTO],
80
80
  }
81
81
 
82
+ _FAULT_STATE_VALUES = frozenset({"error"})
83
+ _SYSTEM_STATE_DEFAULTS: ControllerData = {
84
+ "SysOn": "off",
85
+ "SysMode": "cool",
86
+ "SysFan": "auto",
87
+ "SleepTimer": 0,
88
+ "FreeAir": "disabled",
89
+ "Supply": "0.0",
90
+ "Setpoint": "0.0",
91
+ "Temp": "0.0",
92
+ "EcoLock": "false",
93
+ "EcoMin": "15.0",
94
+ "EcoMax": "30.0",
95
+ "RAS": "zones",
96
+ "CtrlZone": 0,
97
+ "NoOfZones": 0,
98
+ "NoOfConst": 0,
99
+ "SysType": "0",
100
+ "FanAuto": "disabled",
101
+ }
102
+
82
103
  def __init__(
83
104
  self,
84
105
  discovery_service: DiscoveryService,
@@ -280,14 +301,13 @@ class Controller:
280
301
 
281
302
  @property
282
303
  def mode(self) -> Mode:
283
- """System mode (cooling, heating, etc.).
284
-
285
- Raises:
286
- ValueError: If the cached mode is not a valid :class:`Mode` member.
287
- """
304
+ """System mode (cooling, heating, etc.)."""
288
305
  if self.free_air:
289
306
  return self.Mode.FREE_AIR
290
- return self.Mode(self._get_system_state("SysMode"))
307
+ try:
308
+ return self.Mode(self._get_system_state("SysMode"))
309
+ except ValueError:
310
+ return self.Mode.COOL
291
311
 
292
312
  async def set_mode(self, value: Mode) -> None:
293
313
  """Set the system mode (cooling, heating, etc.).
@@ -309,12 +329,11 @@ class Controller:
309
329
 
310
330
  @property
311
331
  def fan(self) -> Fan:
312
- """The current fan level.
313
-
314
- Raises:
315
- ValueError: If the cached fan mode is not a valid :class:`Fan` member.
316
- """
317
- return self.Fan(self._get_system_state("SysFan"))
332
+ """The current fan level."""
333
+ try:
334
+ return self.Fan(self._get_system_state("SysFan"))
335
+ except ValueError:
336
+ return self.Fan.AUTO
318
337
 
319
338
  async def set_fan(self, value: Fan) -> None:
320
339
  """Set the fan level.
@@ -606,7 +625,13 @@ class Controller:
606
625
  )
607
626
 
608
627
  def _get_system_state(self, state: str) -> DictValue:
609
- return self._system_settings[state]
628
+ default = self._SYSTEM_STATE_DEFAULTS.get(state, "")
629
+ value = self._system_settings.get(state)
630
+ if value is None:
631
+ return default
632
+ if state in ("SysFan", "RAS", "SysMode") and str(value) in self._FAULT_STATE_VALUES:
633
+ return default
634
+ return value
610
635
 
611
636
  async def _set_system_state(
612
637
  self,
@@ -691,8 +716,8 @@ class Controller:
691
716
  """Fetch a JSON resource via HTTP GET without updating connection state.
692
717
 
693
718
  Raises:
694
- ConnectionError: If the device cannot be reached or the response
695
- cannot be decoded.
719
+ ConnectionError: If the device cannot be reached.
720
+ ResponseDecodeError: If the response cannot be decoded as JSON.
696
721
  ControllerCommandError: If the device returns HTTP 4xx.
697
722
  """
698
723
  session = self._discovery_service.session
@@ -716,7 +741,7 @@ class Controller:
716
741
  if text[-4:] == "{OK}":
717
742
  return json.loads(text[:-4])
718
743
  _LOG.error('Decode error for "%s"', text, exc_info=True)
719
- raise ConnectionError(
744
+ raise ResponseDecodeError(
720
745
  "Unable to decode response from the controller"
721
746
  ) from ex
722
747
 
@@ -762,8 +787,8 @@ class Controller:
762
787
  """Fetch a JSON resource from the device via HTTP GET.
763
788
 
764
789
  Raises:
765
- ConnectionError: If the device cannot be reached, the response
766
- cannot be decoded, or the HTTP request fails.
790
+ ConnectionError: If the device cannot be reached or the HTTP request fails.
791
+ ResponseDecodeError: If the response cannot be decoded as JSON.
767
792
  ControllerCommandError: If the device returns HTTP 4xx.
768
793
  """
769
794
  try:
@@ -776,11 +801,11 @@ class Controller:
776
801
  False, ConnectionError("Unable to connect to the controller")
777
802
  )
778
803
  raise ConnectionError("Unable to connect to the controller") from ex
804
+ except ResponseDecodeError:
805
+ self._set_bridge_ok(True)
806
+ raise
779
807
  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)
808
+ self._set_bridge_ok(False, ex)
784
809
  raise
785
810
  self._set_bridge_ok(True)
786
811
  return result
pizone/exceptions.py CHANGED
@@ -8,3 +8,11 @@ class ControllerCommandError(Exception):
8
8
  device is reachable. Not used for transport failures (timeouts, connection
9
9
  refused, etc.) — those remain :exc:`ConnectionError`.
10
10
  """
11
+
12
+
13
+ class ResponseDecodeError(ConnectionError):
14
+ """The bridge responded over HTTP but the body could not be decoded as JSON.
15
+
16
+ Raised when transport succeeds and the response is not a transport failure,
17
+ but JSON parsing fails (and the ``{OK}`` suffix workaround does not apply).
18
+ """
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.3'
22
- __version_tuple__ = version_tuple = (1, 3, 3)
21
+ __version__ = version = '1.3.4'
22
+ __version_tuple__ = version_tuple = (1, 3, 4)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-izone
3
- Version: 1.3.3
3
+ Version: 1.3.4
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=F1D0SKGns5TUvAH4iZOovrE4ZQH3GXHWW3bUPcovDrw,1365
2
+ pizone/controller.py,sha256=K0cscWcwHe4OFaVI_F-wV9J-Da3QGNfCFDpKgdVFkCM,30012
3
+ pizone/discovery.py,sha256=8sBl1QlMlkGs_gwt-neGouwpYahwrlkAH37KEw9SbH0,18135
4
+ pizone/exceptions.py,sha256=8rqSGIrQV5KQeOx_Lc6Mh7FVAbSl4-Q6qyW-cwrqlZ0,658
5
+ pizone/power.py,sha256=FsKS8LhyHS2cUDKnDX6Vc1bWhpXSZABIpPLNQERqlLk,9899
6
+ pizone/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ pizone/version.py,sha256=J1PLKOPRRvOkHG-jR78Tmen2I1NkbGLw4y0NdfCA_Ts,520
8
+ pizone/zone.py,sha256=0rVYIiqGXUJeT3i_EBLPOTEsFIY6nNSheMuI7Up8GzU,7232
9
+ python_izone-1.3.4.dist-info/METADATA,sha256=mOBTQOsYuJe2T_NaTDB_G-CrBofSrGJoqs9-3qyKFOI,2657
10
+ python_izone-1.3.4.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
11
+ python_izone-1.3.4.dist-info/licenses/licence.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
12
+ python_izone-1.3.4.dist-info/RECORD,,
@@ -1,12 +0,0 @@
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,,