Repository: craigjmidwinter/total-connect-client

Issue:
total_connect_client.zone Unknown Zone Type after upgrade
I just upgraded to Home Assistant 2021.11.1, after doing so my log is being spammed with these messages.

2021-11-08 18:38:49 ERROR (SyncWorker_8) [total_connect_client.zone] unknown ZoneType 50 in OrderedDict([('AlarmTriggerTime', None), ('AlarmTriggerTimeLocalized', None), ('ZoneTypeID', 50), ('DeviceType', -1), ('ZoneID', 850), ('ZoneDescription', 'Garage Entry Keypad'), ('ZoneStatus', 0), ('PartitionID', 1), ('CanBeBypassed', 0)]) -- please file an issue at https://github.com/craigjmidwinter/total-connect-client/issues
2021-11-08 18:40:19 ERROR (SyncWorker_7) [total_connect_client.zone] unknown ZoneType 50 in OrderedDict([('AlarmTriggerTime', None), ('AlarmTriggerTimeLocalized', None), ('ZoneTypeID', 50), ('DeviceType', -1), ('ZoneID', 851), ('ZoneDescription', 'Master Bedroom Keypad'), ('ZoneStatus', 0), ('PartitionID', 1), ('CanBeBypassed', 0)]) -- please file an issue at https://github.com/craigjmidwinter/total-connect-client/issues


These Particular keypads are both Lyric Keypads.

Discussion:

Patch:
diff --git a/README.md b/README.md
index 4e87c67..edc5339 100644
--- a/README.md
+++ b/README.md
@@ -15,10 +15,10 @@ The code currently supports:
 
 If you're having trouble with your system, or find an error message, we may ask you to submit information about your alarm system.  To do that from the command line do the following steps (assuming you are running from within Home Assistant):
  
-`python3 -m total_connect_client.info username password`
+`python3 -m total_connect_client username`
  
 If you want to easily put the info into a file for sharing: 
- - `python3 -m total_connect_client.info username password > my_info.txt`
+ - `python3 -m total_connect_client username > my_info.txt`
  - Now the file my_info.txt in the same directory will hold all of that information
 
 **WARNING**:  the output of this command includes private information including your username and password.  Carefully remove it before sharing with the developers or posting on Github.
diff --git a/ZONE_TYPES.md b/ZONE_TYPES.md
index 0bc277d..39c09c0 100644
--- a/ZONE_TYPES.md
+++ b/ZONE_TYPES.md
@@ -4,15 +4,22 @@ Information on devices found through testing.
 
 Zone Type # | Type | Notes
 ------------ | - | -
-0 | Security | Lynx Touch 7000 and sensors 5820L, 5818MNL, 5800RPS, 5898
-1 | Security | Lyric and 5800MINI
-3 | Security | 5800mini via the ProA7Plus "takeover" module
-4 | Security | Lyric and SiXPIR motion
-6 | tbd | Lyric "Police", perhaps a button?
-7 | Police | ProA7Plus police button
-9 | Fire/Smoke | Lynx Touch 7000 and 5808W3 smoke detectors.  Lynx Touch 7000 Fire button.  
-10 | tbd | ProSixMini2 set to Interior with Delay via ProA7Plus panel
-12 | tbd | Lyric "Temperature", ProSixFlood via ProA7Plus panel
+0 | Security | Lynx Touch 7000 and sensors 5820L, 5818MNL, 5800RPS, 5898.  Lynx Touch 7000 with 5898 set as Temperature.
+1 | Entry/Exit 1 | Lyric and 5800MINI
+2 | Entry/Exit 2 |
+3 | Perimeter | 5800mini via the ProA7Plus "takeover" module
+4 | Interior Follower | Lyric and SiXPIR motion
+5 | Trouble | Trouble by day, alarm by night
+6 | Silent 24 Hour | 
+7 | Audible 24 Hour | ProA7Plus police button
+8 | Auxilliary 24 Hour | 
+9 | Fire/Smoke | Lynx Touch 7000 and 5808W3 smoke detectors.  Lynx Touch 7000 Fire button.  Lynx Touch 7000 with 5898 set as Heat.
+10 | Interior Delay | ProSixMini2 set to Interior with Delay via ProA7Plus panel
+12 | Monitor | Lyric "Temperature", ProSixFlood via ProA7Plus panel
 14 | Carbon Monoxide | Lynx Touch 7000 and 5800CO sensors
 15 | Medical | ProA7Plus medical button
+50 | Keypad | Lyric Keypad
 89 | tbd | Lyric "local alarm"
+90-93 | Vista configurable | (not yet seen in the wild)
+
+See https://www.alarmliquidators.com/content/Vista%2021IP-%20Programming%20Guide.pdf and http://techresource.online/training/ssnw/honeywell/zone-types
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 51859a4..f09d3a7 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
 
 setup(
     name="total_connect_client",
-    version="2021.11.2",
+    version="2021.11.3",
     description="Interact with Total Connect 2 alarm systems",
     author="Craig J. Midwinter",
     author_email="craig.j.midwinter@gmail.com",
diff --git a/total_connect_client/zone.py b/total_connect_client/zone.py
index 2dbe9cb..3337925 100644
--- a/total_connect_client/zone.py
+++ b/total_connect_client/zone.py
@@ -43,6 +43,7 @@ class ZoneType(Enum):
     CARBON_MONOXIDE   = 14
     PROA7_MEDICAL     = 15
     FIRE_W_VERIFICATION = 16  # must trigger twice to cause an alarm
+    LYRIC_KEYPAD = 50
     LYRIC_LOCAL_ALARM = 89
 
     # According to the VISTA docs, these can be programmed via downloader software
@@ -156,8 +157,11 @@ class TotalConnectZone:
 
     def is_type_fire(self):
         """Return true if zone type is fire or smoke."""
-        # TODO: why is ZoneType.MONITOR here? it's not a fire or smoke zone type
-        return self.zone_type_id in (ZoneType.FIRE_SMOKE, ZoneType.MONITOR)
+        return self.zone_type_id == ZoneType.FIRE_SMOKE
+
+    def is_type_temperature(self):
+        """Return true if zone monitors the temperature."""
+        return self.zone_type_id == ZoneType.MONITOR
 
     def is_type_carbon_monoxide(self):
         """Return true if zone type is carbon monoxide."""
@@ -167,6 +171,10 @@ class TotalConnectZone:
         """Return true if zone type is medical."""
         return self.zone_type_id == ZoneType.PROA7_MEDICAL
 
+    def is_type_keypad(self):
+        """Return true if zone type is keypad."""
+        return self.zone_type_id == ZoneType.LYRIC_KEYPAD
+
     def _update(self, zone):
         """Update the zone."""
         assert zone


Test patch:
diff --git a/tests/const.py b/tests/const.py
index 47e8f5c..7c493ae 100644
--- a/tests/const.py
+++ b/tests/const.py
@@ -88,6 +88,9 @@ ZONE_STATUS_LYRIC_POLICE["ZoneTypeId"] = ZoneType.SILENT_24HR
 ZONE_STATUS_LYRIC_TEMP = ZS_NORMAL.copy()
 ZONE_STATUS_LYRIC_TEMP["ZoneTypeId"] = ZoneType.MONITOR
 
+ZONE_STATUS_LYRIC_KEYPAD = ZS_NORMAL.copy()
+ZONE_STATUS_LYRIC_KEYPAD["ZoneTypeId"] = ZoneType.LYRIC_KEYPAD
+
 ZONE_STATUS_LYRIC_LOCAL_ALARM = ZS_NORMAL.copy()
 ZONE_STATUS_LYRIC_LOCAL_ALARM["ZoneTypeId"] = ZoneType.LYRIC_LOCAL_ALARM
 
diff --git a/tests/test_zone.py b/tests/test_zone.py
index 904a326..96d3c5a 100644
--- a/tests/test_zone.py
+++ b/tests/test_zone.py
@@ -7,6 +7,7 @@ from const import (
     ZONE_LOW_BATTERY,
     ZONE_STATUS_LYRIC_CONTACT,
     ZONE_STATUS_LYRIC_LOCAL_ALARM,
+    ZONE_STATUS_LYRIC_KEYPAD,
     ZONE_STATUS_LYRIC_MOTION,
     ZONE_STATUS_LYRIC_POLICE,
     ZONE_STATUS_LYRIC_TEMP,
@@ -110,6 +111,7 @@ class TestTotalConnectZone(unittest.TestCase):
         self.zone_lyric_motion = tcz(ZONE_STATUS_LYRIC_MOTION)
         self.zone_lyric_police = tcz(ZONE_STATUS_LYRIC_POLICE)
         self.zone_lyric_temp = tcz(ZONE_STATUS_LYRIC_TEMP)
+        self.zone_lyric_keypad = tcz(ZONE_STATUS_LYRIC_KEYPAD)
         self.zone_lyric_local_alarm = tcz(ZONE_STATUS_LYRIC_LOCAL_ALARM)
 
     def tearDown(self):
@@ -128,6 +130,7 @@ class TestTotalConnectZone(unittest.TestCase):
         self.zone_lyric_motion = None
         self.zone_lyric_police = None
         self.zone_lyric_temp = None
+        self.zone_lyric_keypad = None
         self.zone_lyric_local_alarm = None
 
     def tests_normal(self):
@@ -242,6 +245,7 @@ class TestTotalConnectZone(unittest.TestCase):
         self.assertFalse(self.zone_lyric_local_alarm.is_type_button())
         self.assertFalse(self.zone_lyric_motion.is_type_button())
         self.assertFalse(self.zone_lyric_temp.is_type_button())
+        self.assertFalse(self.zone_lyric_keypad.is_type_button())
 
     def tests_type_security(self):
         """Security zone."""
@@ -254,6 +258,7 @@ class TestTotalConnectZone(unittest.TestCase):
         self.assertTrue(self.zone_lyric_motion.is_type_security())
         self.assertTrue(self.zone_lyric_police.is_type_security())
         self.assertFalse(self.zone_lyric_temp.is_type_security())
+        self.assertFalse(self.zone_lyric_keypad.is_type_security())
 
     def tests_type_fire(self):
         """Fire zone."""
@@ -265,7 +270,7 @@ class TestTotalConnectZone(unittest.TestCase):
         self.assertFalse(self.zone_lyric_local_alarm.is_type_fire())
         self.assertFalse(self.zone_lyric_motion.is_type_fire())
         self.assertFalse(self.zone_lyric_police.is_type_fire())
-        self.assertTrue(self.zone_lyric_temp.is_type_fire())
+        self.assertFalse(self.zone_lyric_temp.is_type_fire())
 
     def tests_type_carbon_monoxide(self):
         """Carbon monoxide zone."""
@@ -291,6 +296,15 @@ class TestTotalConnectZone(unittest.TestCase):
         self.assertFalse(self.zone_lyric_police.is_type_motion())
         self.assertFalse(self.zone_lyric_temp.is_type_motion())
 
+    def tests_type_keypad(self):
+        """Keypad test."""
+        self.assertTrue(self.zone_lyric_keypad.is_type_keypad())
+        self.assertFalse(self.zone_lyric_temp.is_type_keypad())
+
+    def tests_type_temperature(self):
+        """Temperature test."""
+        self.assertFalse(self.zone_lyric_keypad.is_type_temperature())
+        self.assertTrue(self.zone_lyric_temp.is_type_temperature())
 
 def test_proa7_zones():
     """Test ProA7."""