From 24b08ee13f503be9242a3c10adf476a37fb82dbb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 18 Feb 2020 01:16:04 -0600 Subject: [PATCH] Provide a util to update LockDetail from activity Home Assistant checks the activity log far more frequently than other apis in order to reduce the number of api calls. The new update_lock_from_activity util provides a way to update a LockDetail class with one of the following activities: LockOperationActivity DoorOperationActivity --- MANIFEST | 1 + august/util.py | 35 ++++++ setup.py | 2 +- tests/fixtures/door_closed_activity.json | 35 ++++++ .../door_closed_activity_wrong_deviceid.json | 35 ++++++ .../door_closed_activity_wrong_houseid.json | 35 ++++++ tests/fixtures/door_open_activity.json | 35 ++++++ tests/fixtures/lock_activity.json | 35 ++++++ tests/fixtures/unlock_activity.json | 34 ++++++ tests/test_lock.py | 104 ++++++++++++++++++ 10 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 august/util.py create mode 100644 tests/fixtures/door_closed_activity.json create mode 100644 tests/fixtures/door_closed_activity_wrong_deviceid.json create mode 100644 tests/fixtures/door_closed_activity_wrong_houseid.json create mode 100644 tests/fixtures/door_open_activity.json create mode 100644 tests/fixtures/lock_activity.json create mode 100644 tests/fixtures/unlock_activity.json create mode 100644 tests/test_lock.py diff --git a/MANIFEST b/MANIFEST index 59b55d0..516a296 100644 --- a/MANIFEST +++ b/MANIFEST @@ -11,3 +11,4 @@ august/exceptions.py august/keypad.py august/lock.py august/pin.py +august/util.py diff --git a/august/util.py b/august/util.py new file mode 100644 index 0000000..4a32ab4 --- /dev/null +++ b/august/util.py @@ -0,0 +1,35 @@ +import datetime + +from august.activity import ( + ACTIVITY_ACTION_STATES, + DoorOperationActivity, + LockOperationActivity, +) + + +def update_lock_detail_from_activity(lock_detail, activity): + """Update the LockDetail from an activity.""" + activity_end_time_utc = as_utc_from_local(activity.activity_end_time) + if activity.house_id != lock_detail.house_id: + raise ValueError + if activity.device_id != lock_detail.device_id: + raise ValueError + if isinstance(activity, LockOperationActivity): + if lock_detail.lock_status_datetime >= activity_end_time_utc: + return False + lock_detail.lock_status = ACTIVITY_ACTION_STATES[activity.action] + lock_detail.lock_status_datetime = activity_end_time_utc + elif isinstance(activity, DoorOperationActivity): + if lock_detail.door_state_datetime >= activity_end_time_utc: + return False + lock_detail.door_state = ACTIVITY_ACTION_STATES[activity.action] + lock_detail.door_state_datetime = activity_end_time_utc + else: + raise ValueError + + return True + + +def as_utc_from_local(dtime): + """Converts the datetime returned from an activity to UTC.""" + return dtime.astimezone(tz=datetime.timezone.utc) diff --git a/setup.py b/setup.py index fb28a4f..d459d8b 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='py-august', - version='0.15.0', + version='0.16.0', packages=['august'], url='https://github.com/snjoetw/py-august', license='MIT', diff --git a/tests/fixtures/door_closed_activity.json b/tests/fixtures/door_closed_activity.json new file mode 100644 index 0000000..7fe2d1b --- /dev/null +++ b/tests/fixtures/door_closed_activity.json @@ -0,0 +1,35 @@ +{ + "action" : "doorclosed", + "callingUser" : { + "FirstName" : "Unknown", + "LastName" : "User", + "PhoneNo" : "deleted", + "UserID" : "deleted", + "UserName" : "deleteduser" + }, + "dateTime" : 1582007217000, + "deviceID" : "ABC", + "deviceName" : "MockHouse Tech Room Door", + "deviceType" : "lock", + "entities" : { + "activity" : "activityId", + "callingUser" : "deleted", + "device" : "ABC", + "house" : "123", + "otherUser" : "deleted" + }, + "house" : { + "houseID" : "123", + "houseName" : "MockHouse" + }, + "info" : { + "DateLogActionID" : "ABC+Time" + }, + "otherUser" : { + "FirstName" : "Unknown", + "LastName" : "User", + "PhoneNo" : "deleted", + "UserID" : "deleted", + "UserName" : "deleteduser" + } +} diff --git a/tests/fixtures/door_closed_activity_wrong_deviceid.json b/tests/fixtures/door_closed_activity_wrong_deviceid.json new file mode 100644 index 0000000..b402ca1 --- /dev/null +++ b/tests/fixtures/door_closed_activity_wrong_deviceid.json @@ -0,0 +1,35 @@ +{ + "action" : "doorclosed", + "callingUser" : { + "FirstName" : "Unknown", + "LastName" : "User", + "PhoneNo" : "deleted", + "UserID" : "deleted", + "UserName" : "deleteduser" + }, + "dateTime" : 1582007217000, + "deviceID" : "notABC", + "deviceName" : "MockHouse Tech Room Door", + "deviceType" : "lock", + "entities" : { + "activity" : "activityId", + "callingUser" : "deleted", + "device" : "ABC", + "house" : "123", + "otherUser" : "deleted" + }, + "house" : { + "houseID" : "123", + "houseName" : "MockHouse" + }, + "info" : { + "DateLogActionID" : "ABC+Time" + }, + "otherUser" : { + "FirstName" : "Unknown", + "LastName" : "User", + "PhoneNo" : "deleted", + "UserID" : "deleted", + "UserName" : "deleteduser" + } +} diff --git a/tests/fixtures/door_closed_activity_wrong_houseid.json b/tests/fixtures/door_closed_activity_wrong_houseid.json new file mode 100644 index 0000000..a858593 --- /dev/null +++ b/tests/fixtures/door_closed_activity_wrong_houseid.json @@ -0,0 +1,35 @@ +{ + "action" : "doorclosed", + "callingUser" : { + "FirstName" : "Unknown", + "LastName" : "User", + "PhoneNo" : "deleted", + "UserID" : "deleted", + "UserName" : "deleteduser" + }, + "dateTime" : 1582007217000, + "deviceID" : "ABC", + "deviceName" : "MockHouse Tech Room Door", + "deviceType" : "lock", + "entities" : { + "activity" : "activityId", + "callingUser" : "deleted", + "device" : "ABC", + "house" : "not123", + "otherUser" : "deleted" + }, + "house" : { + "houseID" : "not123", + "houseName" : "MockHouse" + }, + "info" : { + "DateLogActionID" : "ABC+Time" + }, + "otherUser" : { + "FirstName" : "Unknown", + "LastName" : "User", + "PhoneNo" : "deleted", + "UserID" : "deleted", + "UserName" : "deleteduser" + } +} diff --git a/tests/fixtures/door_open_activity.json b/tests/fixtures/door_open_activity.json new file mode 100644 index 0000000..f01f494 --- /dev/null +++ b/tests/fixtures/door_open_activity.json @@ -0,0 +1,35 @@ +{ + "action" : "dooropen", + "callingUser" : { + "FirstName" : "Unknown", + "LastName" : "User", + "PhoneNo" : "deleted", + "UserID" : "deleted", + "UserName" : "deleteduser" + }, + "dateTime" : 1582007219000, + "deviceID" : "ABC", + "deviceName" : "MockHouse Tech Room Door", + "deviceType" : "lock", + "entities" : { + "activity" : "ActivityId", + "callingUser" : "deleted", + "device" : "ABC", + "house" : "123", + "otherUser" : "deleted" + }, + "house" : { + "houseID" : "123", + "houseName" : "MockHouse" + }, + "info" : { + "DateLogActionID" : "ABC+Time" + }, + "otherUser" : { + "FirstName" : "Unknown", + "LastName" : "User", + "PhoneNo" : "deleted", + "UserID" : "deleted", + "UserName" : "deleteduser" + } +} diff --git a/tests/fixtures/lock_activity.json b/tests/fixtures/lock_activity.json new file mode 100644 index 0000000..7187215 --- /dev/null +++ b/tests/fixtures/lock_activity.json @@ -0,0 +1,35 @@ + { + "action" : "lock", + "callingUser" : { + "FirstName" : "MockHouse", + "LastName" : "House", + "UserID" : "mockUserId2" + }, + "dateTime" : 1582007218000, + "deviceID" : "ABC", + "deviceName" : "MockHouseTDoor", + "deviceType" : "lock", + "entities" : { + "activity" : "mockActivity2", + "callingUser" : "mockUserId2", + "device" : "ABC", + "house" : "123", + "otherUser" : "deleted" + }, + "house" : { + "houseID" : "123", + "houseName" : "MockHouse" + }, + "info" : { + "DateLogActionID" : "ABC+Time", + "remote" : true + }, + "otherUser" : { + "FirstName" : "Unknown", + "LastName" : "User", + "PhoneNo" : "deleted", + "UserID" : "deleted", + "UserName" : "deleteduser" + } + } + diff --git a/tests/fixtures/unlock_activity.json b/tests/fixtures/unlock_activity.json new file mode 100644 index 0000000..9d7943c --- /dev/null +++ b/tests/fixtures/unlock_activity.json @@ -0,0 +1,34 @@ +{ + "action" : "unlock", + "callingUser" : { + "FirstName" : "MockHouse", + "LastName" : "House", + "UserID" : "mockUserId2" + }, + "dateTime" : 1582007217000, + "deviceID" : "ABC", + "deviceName" : "MockHouseXDoor", + "deviceType" : "lock", + "entities" : { + "activity" : "ActivityId", + "callingUser" : "mockUserId2", + "device" : "ABC", + "house" : "123", + "otherUser" : "deleted" + }, + "house" : { + "houseID" : "123", + "houseName" : "MockHouse" + }, + "info" : { + "DateLogActionID" : "ABC+Time", + "remote" : true + }, + "otherUser" : { + "FirstName" : "Unknown", + "LastName" : "User", + "PhoneNo" : "deleted", + "UserID" : "deleted", + "UserName" : "deleteduser" + } +} diff --git a/tests/test_lock.py b/tests/test_lock.py new file mode 100644 index 0000000..ef150ea --- /dev/null +++ b/tests/test_lock.py @@ -0,0 +1,104 @@ +import json +import os +import unittest + +import dateutil.parser +import datetime + +from august.activity import DoorOperationActivity, LockOperationActivity +from august.lock import LockDetail, LockDoorStatus, LockStatus +from august.util import update_lock_detail_from_activity, as_utc_from_local + + +def load_fixture(filename): + """Load a fixture.""" + path = os.path.join(os.path.dirname(__file__), "fixtures", filename) + with open(path) as fptr: + return fptr.read() + + +class TestLockDetail(unittest.TestCase): + def test_update_lock_with_activity(self): + lock = LockDetail( + json.loads(load_fixture("get_lock.online_with_doorsense.json")) + ) + self.assertEqual("ABC", lock.device_id) + self.assertEqual(LockStatus.LOCKED, lock.lock_status) + self.assertEqual(LockDoorStatus.OPEN, lock.door_state) + self.assertEqual( + dateutil.parser.parse("2017-12-10T04:48:30.272Z"), lock.lock_status_datetime + ) + self.assertEqual( + dateutil.parser.parse("2017-12-10T04:48:30.272Z"), lock.door_state_datetime + ) + + lock_operation_activity = LockOperationActivity( + json.loads(load_fixture("lock_activity.json")) + ) + unlock_operation_activity = LockOperationActivity( + json.loads(load_fixture("unlock_activity.json")) + ) + open_operation_activity = DoorOperationActivity( + json.loads(load_fixture("door_open_activity.json")) + ) + closed_operation_activity = DoorOperationActivity( + json.loads(load_fixture("door_closed_activity.json")) + ) + closed_operation_wrong_deviceid_activity = DoorOperationActivity( + json.loads(load_fixture("door_closed_activity_wrong_deviceid.json")) + ) + closed_operation_wrong_houseid_activity = DoorOperationActivity( + json.loads(load_fixture("door_closed_activity_wrong_houseid.json")) + ) + + self.assertTrue( + update_lock_detail_from_activity(lock, unlock_operation_activity) + ) + self.assertEqual(LockStatus.UNLOCKED, lock.lock_status) + self.assertEqual( + as_utc_from_local(datetime.datetime.fromtimestamp(1582007217000 / 1000)), + lock.lock_status_datetime, + ) + + self.assertTrue(update_lock_detail_from_activity(lock, lock_operation_activity)) + self.assertEqual(LockStatus.LOCKED, lock.lock_status) + self.assertEqual( + as_utc_from_local(datetime.datetime.fromtimestamp(1582007218000 / 1000)), + lock.lock_status_datetime, + ) + + # returns false we send an older activity + self.assertFalse( + update_lock_detail_from_activity(lock, unlock_operation_activity) + ) + + self.assertTrue( + update_lock_detail_from_activity(lock, closed_operation_activity) + ) + self.assertEqual(LockDoorStatus.CLOSED, lock.door_state) + self.assertEqual( + as_utc_from_local(datetime.datetime.fromtimestamp(1582007217000 / 1000)), + lock.door_state_datetime, + ) + + self.assertTrue(update_lock_detail_from_activity(lock, open_operation_activity)) + self.assertEqual(LockDoorStatus.OPEN, lock.door_state) + self.assertEqual( + as_utc_from_local(datetime.datetime.fromtimestamp(1582007219000 / 1000)), + lock.door_state_datetime, + ) + + # returns false we send an older activity + self.assertFalse( + update_lock_detail_from_activity(lock, closed_operation_activity) + ) + + with self.assertRaises(ValueError): + update_lock_detail_from_activity( + lock, closed_operation_wrong_deviceid_activity + ) + + with self.assertRaises(ValueError): + update_lock_detail_from_activity( + lock, closed_operation_wrong_houseid_activity + )