Repository: bdraco/yalexs (language: python)

Issue:
Please add support for the linked locking actions
I have got the Yale Unity Screen Door Lock and Front Door Lock which has a link locking facility. So when I lock the Screen Door Lock the Front Door will also lock automatically. The issue is that when you lock or unlock one of these locks, the other one follows suit, but the status of the second lock isn’t updated in Home Assistant. To refresh the status, you have to open the Yale Home app and check the associated lock. As a result, even if both locks are actually in the same state, one may show as locked while the other shows as unlocked. Can I request to add linked locking action to the current Yale Home Intigration so it is able to refresh correctly.


Discussion / hints:
`2024-09-01 14:06:40.348 DEBUG (MainThread) [yalexs.api_common] Processing activity: {'id': '0af6bd11-621b-4e32-b0ea-1d13804d31e7', 'timestamp': 1725061953081, 'icon': 'https://d3osa7xy9vsc0q.cloudfront.net/app/ActivityFeedIcons/linked_lock@3x.png', 'action': 'linked_lock', 'deviceID': 'X', 'deviceType': 'lock', 'title': '<b>Front Door</b> locked by linked operation.'}
`

`2024-09-01 14:06:40.348 DEBUG (MainThread) [yalexs.api_common] Unknown activity: {'id': '74b5a4b6-8c89-4aac-8eb5-38b0350f4164', 'timestamp': 1725062446640, 'icon': 'https://d3osa7xy9vsc0q.cloudfront.net/app/ActivityFeedIcons/linked_unlock@3x.png', 'action': 'linked_unlock', 'deviceID': 'x', 'deviceType': 'lock', 'title': '<b>Front Door</b> unlocked by linked operation.'}
`

Fix patch:
diff --git a/yalexs/activity.py b/yalexs/activity.py
index 0b243af..ef452fc 100644
--- a/yalexs/activity.py
+++ b/yalexs/activity.py
@@ -38,6 +38,8 @@
 ACTION_HOMEKEY_LOCK = "homekey_lock"
 ACTION_HOMEKEY_UNLATCH = "homekey_unlatch"
 ACTION_HOMEKEY_UNLOCK = "homekey_unlock"
+ACTION_LINKED_LOCK = "linked_lock"
+ACTION_LINKED_UNLOCK = "linked_unlock"
 
 ACTION_DOOR_OPEN = "dooropen"
 ACTION_DOOR_OPEN_2 = "door_open"
@@ -75,6 +77,8 @@
     ACTION_HOMEKEY_LOCK,
     ACTION_HOMEKEY_UNLATCH,
     ACTION_HOMEKEY_UNLOCK,
+    ACTION_LINKED_LOCK,
+    ACTION_LINKED_UNLOCK,
     ACTION_LOCK_AUTO_LOCK,
     ACTION_LOCK_ONETOUCHLOCK,
     ACTION_LOCK_ONETOUCHLOCK_2,
@@ -107,6 +111,8 @@
     ACTION_HOMEKEY_LOCK: ("Homekey", "Lock"),
     ACTION_HOMEKEY_UNLATCH: ("Homekey", "Unlatch"),
     ACTION_HOMEKEY_UNLOCK: ("Homekey", "Unlock"),
+    ACTION_LINKED_LOCK: ("Linked", "Lock"),
+    ACTION_LINKED_UNLOCK: ("Linked", "Unlock"),
     ACTION_LOCK_AUTO_LOCK: ("Auto", "Lock"),
     ACTION_LOCK_ONETOUCHLOCK: ("One-Touch", "Lock"),
     ACTION_LOCK_ONETOUCHLOCK_2: ("One-Touch", "Lock"),
@@ -161,6 +167,8 @@
     ACTION_RF_UNLATCH: LockStatus.UNLATCHED,
     ACTION_RF_UNLOCK: LockStatus.UNLOCKED,
     ACTION_HOMEKEY_LOCK: LockStatus.LOCKED,
+    ACTION_LINKED_LOCK: LockStatus.LOCKED,
+    ACTION_LINKED_UNLOCK: LockStatus.UNLOCKED,
     ACTION_HOMEKEY_UNLATCH: LockStatus.UNLATCHED,
     ACTION_HOMEKEY_UNLOCK: LockStatus.UNLOCKED,
     ACTION_LOCK_AUTO_LOCK: LockStatus.LOCKED,


Test patch:
diff --git a/tests/fixtures/linked_lock_activity.json b/tests/fixtures/linked_lock_activity.json
new file mode 100644
index 0000000..929883f
--- /dev/null
+++ b/tests/fixtures/linked_lock_activity.json
@@ -0,0 +1,9 @@
+{
+  "id": "0af6bd11-621b-4e32-b0ea-1d13804d31e7",
+  "timestamp": 1725061953081,
+  "icon": "https://d3osa7xy9vsc0q.cloudfront.net/app/ActivityFeedIcons/linked_lock@3x.png",
+  "action": "linked_lock",
+  "deviceID": "X",
+  "deviceType": "lock",
+  "title": "<b>Front Door</b> locked by linked operation."
+}
diff --git a/tests/fixtures/linked_unlock_activity.json b/tests/fixtures/linked_unlock_activity.json
new file mode 100644
index 0000000..08267da
--- /dev/null
+++ b/tests/fixtures/linked_unlock_activity.json
@@ -0,0 +1,9 @@
+{
+  "id": "74b5a4b6-8c89-4aac-8eb5-38b0350f4164",
+  "timestamp": 1725062446640,
+  "icon": "https://d3osa7xy9vsc0q.cloudfront.net/app/ActivityFeedIcons/linked_unlock@3x.png",
+  "action": "linked_unlock",
+  "deviceID": "x",
+  "deviceType": "lock",
+  "title": "<b>Front Door</b> unlocked by linked operation."
+}
diff --git a/tests/test_activity.py b/tests/test_activity.py
index a6b46dd..bef2a4f 100644
--- a/tests/test_activity.py
+++ b/tests/test_activity.py
@@ -8,6 +8,8 @@
 
 from yalexs.activity import (
     ACTION_BRIDGE_OFFLINE,
+    ACTION_LINKED_LOCK,
+    ACTION_LINKED_UNLOCK,
     ACTION_BRIDGE_ONLINE,
     ACTION_DOOR_CLOSE_2,
     ACTION_DOOR_CLOSED,
@@ -140,6 +142,8 @@ def test_activity_actions(self):
                 ACTION_LOCK_MANUAL_LOCK,
                 ACTION_LOCK_MANUAL_UNLATCH,
                 ACTION_LOCK_MANUAL_UNLOCK,
+                ACTION_LINKED_LOCK,
+                ACTION_LINKED_UNLOCK,
             ],
         )
         self.assertCountEqual(
@@ -506,3 +510,39 @@ def test_websocket_activity(self):
         )
         assert manual_unlock_activity.source == SOURCE_WEBSOCKET
         assert manual_unlock_activity.was_pushed is True
+
+    def test_linked_unlock_activity_v4(self):
+        manual_lock_activity = LockOperationActivity(
+            SOURCE_LOG, json.loads(load_fixture("linked_unlock_activity.json"))
+        )
+        assert manual_lock_activity.operated_by == "Linked Unlock"
+        assert manual_lock_activity.operated_remote is False
+        assert manual_lock_activity.operated_keypad is False
+        assert manual_lock_activity.operated_manual is False
+        assert manual_lock_activity.operated_tag is False
+        assert (
+            manual_lock_activity.operator_image_url
+            == "https://d3osa7xy9vsc0q.cloudfront.net/app/ActivityFeedIcons/linked_unlock@3x.png"
+        )
+        assert (
+            manual_lock_activity.operator_thumbnail_url
+            == "https://d3osa7xy9vsc0q.cloudfront.net/app/ActivityFeedIcons/linked_unlock@3x.png"
+        )
+
+    def test_linked_lock_activity_v4(self):
+        manual_lock_activity = LockOperationActivity(
+            SOURCE_LOG, json.loads(load_fixture("linked_lock_activity.json"))
+        )
+        assert manual_lock_activity.operated_by == "Linked Lock"
+        assert manual_lock_activity.operated_remote is False
+        assert manual_lock_activity.operated_keypad is False
+        assert manual_lock_activity.operated_manual is False
+        assert manual_lock_activity.operated_tag is False
+        assert (
+            manual_lock_activity.operator_image_url
+            == "https://d3osa7xy9vsc0q.cloudfront.net/app/ActivityFeedIcons/linked_lock@3x.png"
+        )
+        assert (
+            manual_lock_activity.operator_thumbnail_url
+            == "https://d3osa7xy9vsc0q.cloudfront.net/app/ActivityFeedIcons/linked_lock@3x.png"
+        )