Repository: craigjmidwinter/total-connect-client

Issue:
PartialResponseError on startup
See https://github.com/home-assistant/core/issues/91703

User can see status in TC app and website, but cannot load data via Home Assistant.

Full log at original ticket above, but basic issue is here:
```txt
  File "/usr/local/lib/python3.10/site-packages/total_connect_client/location.py", line 258, in _update_zone_details
    raise PartialResponseError("no ZoneStatusInfoWithPartitionId", result)
total_connect_client.exceptions.PartialResponseError: ('no ZoneStatusInfoWithPartitionId', OrderedDict([('ResultCode', 0), ('ResultData', 'Success'), ('ZoneStatus', OrderedDict([('Zones', None)]))]))```
```

Maybe our refactoring of code has led to #112 coming back ?

In `location._update_zones()` if we see Zones=None we highlight that with a log message telling the user how to fix it, and then raise TotalConnectError.

But in `location._update_zone_details()` [which only runs once at startup] we don't provide a log message and raise PartialResponseError.

Waiting for the user to verify that a panel sync fixes this.




Discussion:

Patch:
diff --git a/README.md b/README.md
index bafe6cc..12dabd9 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 # Total-Connect-Client
-Total-Connect-Client is a python client for interacting with the TotalConnect2 alarm system.
+Total-Connect-Client is a python client for interacting with the [TotalConnect2](https://totalconnect2.com) alarm system.
 
 Started by @craigjmidwinter to add alarm support for his personal HomeAssistant set-up, with later contributions from others.
 
@@ -139,6 +139,6 @@ similar methods on the values of self.locations.
 * Previously if the usercodes dictionary was invalid, the DEFAULT_USERCODE
 was silently used. In a future release, we will raise an exception on an invalid dictionary.
 
-If there's something about the interface you don't understand, check out the (Home Assistant integration)[https://github.com/home-assistant/core/blob/dev/homeassistant/components/totalconnect/] that uses this package, or submit an issue here.
+If there's something about the interface you don't understand, check out the [Home Assistant integration](https://github.com/home-assistant/core/blob/dev/homeassistant/components/totalconnect/) that uses this package, or [submit an issue](https://github.com/craigjmidwinter/total-connect-client/issues).
 
-During development, if you discover new status codes or other information not handled, please submit an issue to let us know, or even better submit a pull request.
+During development, if you discover new status codes or other information not handled, please [submit an issue](https://github.com/craigjmidwinter/total-connect-client/issues) to let us know, or even better submit a [pull request](https://github.com/craigjmidwinter/total-connect-client/pulls).
diff --git a/pyproject.toml b/pyproject.toml
index 9b48fb0..a9da19a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta:__legacy__"
 
 [project]
 name="total_connect_client"
-version="2023.2"
+version="2023.4"
 authors = [
   { name="Craig J. Midwinter", email="craig.j.midwinter@gmail.com" },
 ]
diff --git a/total_connect_client/location.py b/total_connect_client/location.py
index 4b999c4..f5cc2ba 100644
--- a/total_connect_client/location.py
+++ b/total_connect_client/location.py
@@ -255,10 +255,11 @@ class TotalConnectLocation:
             "ZoneStatusInfoWithPartitionId"
         )
         if not zone_info:
-            raise PartialResponseError("no ZoneStatusInfoWithPartitionId", result)
-
-        for zonedata in zone_info:
-            self.zones[zonedata["ZoneID"]] = TotalConnectZone(zonedata)
+            LOGGER.warning("No zones found when starting TotalConnect. Try to sync your panel using the TotalConnect app or website.")
+            LOGGER.debug(f"_update_zone_details result: {result}")
+        else:
+            for zonedata in zone_info:
+                self.zones[zonedata["ZoneID"]] = TotalConnectZone(zonedata)
 
     def _update_status(self, result):
         """Update from result."""


Test patch:
diff --git a/tests/test_client_misc.py b/tests/test_client_misc.py
index 2da74fd..0390414 100644
--- a/tests/test_client_misc.py
+++ b/tests/test_client_misc.py
@@ -21,7 +21,6 @@ from total_connect_client.zone import ZoneStatus
 
 from total_connect_client.exceptions import (
     BadResultCodeError,
-    PartialResponseError,
     TotalConnectError,
 )
 
@@ -77,5 +76,4 @@ class TestTotalConnectClient(unittest.TestCase):
                 self.location.get_zone_details()
             # third response is SUCCESS but with empty ZoneStatus
             # ...which we've seen before in the wild
-            with pytest.raises(PartialResponseError):
-                self.location.get_zone_details()
+            self.location.get_zone_details()
diff --git a/tests/test_location.py b/tests/test_location.py
index 2986b60..a977afa 100644
--- a/tests/test_location.py
+++ b/tests/test_location.py
@@ -106,20 +106,23 @@ class TestTotalConnectLocation(unittest.TestCase):
 
     def tests_set_zone_details(self):
         """Test set_zone_details with normal data passed in."""
-        self.location_normal._update_zone_details(RESPONSE_GET_ZONE_DETAILS_SUCCESS)
+        location = TotalConnectLocation(LOCATION_INFO_BASIC_NORMAL, None)
+        location._update_zone_details(RESPONSE_GET_ZONE_DETAILS_SUCCESS)
+        assert len(location.zones) == 1
 
-        # "Zones" is None
+        location = TotalConnectLocation(LOCATION_INFO_BASIC_NORMAL, None)
+        # "Zones" is None, as seen in #112 and #205
         response = deepcopy(RESPONSE_GET_ZONE_DETAILS_SUCCESS)
         response["ZoneStatus"]["Zones"] = None
-        with pytest.raises(PartialResponseError):
-            self.location_normal._update_zone_details(response)
+        location._update_zone_details(response)
+        assert len(location.zones) == 0
 
         # "ZoneStatusInfoWithPartitionId" is None
+        location = TotalConnectLocation(LOCATION_INFO_BASIC_NORMAL, None)
         response = deepcopy(RESPONSE_GET_ZONE_DETAILS_SUCCESS)
         response["ZoneStatus"]["Zones"] = {"ZoneStatusInfoWithPartitionId": None}
-        # now test with "ZoneInfo" is none
-        with pytest.raises(PartialResponseError):
-            self.location_normal._update_zone_details(response)
+        location._update_zone_details(response)
+        assert len(location.zones) == 0
 
     def tests_auto_bypass_low_battery(self):
         """Test auto bypass of low battery zones."""