diff --git a/august/api.py b/august/api.py index 89a6042..829deb5 100644 --- a/august/api.py +++ b/august/api.py @@ -19,7 +19,12 @@ ) from august.doorbell import Doorbell, DoorbellDetail from august.exceptions import AugustApiHTTPError -from august.lock import Lock, LockDetail, LockDoorStatus, LockStatus +from august.lock import ( + Lock, + LockDetail, + determine_lock_status, + determine_door_state, +) from august.pin import Pin HEADER_ACCEPT_VERSION = "Accept-Version" @@ -75,28 +80,6 @@ def _api_headers(access_token=None): return headers -LOCKED_STATUS = ("locked", "kAugLockState_Locked") -UNLOCKED_STATUS = ("unlocked", "kAugLockState_Unlocked") -CLOSED_STATUS = ("closed", "kAugLockDoorState_Closed") -OPEN_STATUS = ("open", "kAugLockDoorState_Open") - - -def _determine_lock_status(status): - if status in LOCKED_STATUS: - return LockStatus.LOCKED - if status in UNLOCKED_STATUS: - return LockStatus.UNLOCKED - return LockStatus.UNKNOWN - - -def _determine_lock_door_status(status): - if status in CLOSED_STATUS: - return LockDoorStatus.CLOSED - if status in OPEN_STATUS: - return LockDoorStatus.OPEN - return LockDoorStatus.UNKNOWN - - class Api: def __init__(self, timeout=10, command_timeout=60, http_session: Session = None): self._timeout = timeout @@ -228,11 +211,11 @@ def get_lock_status(self, access_token, lock_id, door_status=False): if door_status: return ( - _determine_lock_status(json_dict.get("status")), - _determine_lock_door_status(json_dict.get("doorState")), + determine_lock_status(json_dict.get("status")), + determine_door_state(json_dict.get("doorState")), ) - return _determine_lock_status(json_dict.get("status")) + return determine_lock_status(json_dict.get("status")) def get_lock_door_status(self, access_token, lock_id, lock_status=False): json_dict = self._call_api( @@ -243,11 +226,11 @@ def get_lock_door_status(self, access_token, lock_id, lock_status=False): if lock_status: return ( - _determine_lock_door_status(json_dict.get("doorState")), - _determine_lock_status(json_dict.get("status")), + determine_door_state(json_dict.get("doorState")), + determine_lock_status(json_dict.get("status")), ) - return _determine_lock_door_status(json_dict.get("doorState")) + return determine_door_state(json_dict.get("doorState")) def get_pins(self, access_token, lock_id): json_dict = self._call_api( @@ -264,7 +247,7 @@ def lock(self, access_token, lock_id): timeout=self._command_timeout, ).json() - return _determine_lock_status(json_dict.get("status")) + return determine_lock_status(json_dict.get("status")) def unlock(self, access_token, lock_id): json_dict = self._call_api( @@ -274,7 +257,7 @@ def unlock(self, access_token, lock_id): timeout=self._command_timeout, ).json() - return _determine_lock_status(json_dict.get("status")) + return determine_lock_status(json_dict.get("status")) def refresh_access_token(self, access_token): response = self._call_api("get", API_GET_HOUSES_URL, access_token=access_token) diff --git a/august/lock.py b/august/lock.py index 1f78999..a5098d0 100644 --- a/august/lock.py +++ b/august/lock.py @@ -1,9 +1,17 @@ from enum import Enum +import datetime +import dateutil.parser + from august.bridge import BridgeDetail from august.device import Device, DeviceDetail from august.keypad import KeypadDetail +LOCKED_STATUS = ("locked", "kAugLockState_Locked") +UNLOCKED_STATUS = ("unlocked", "kAugLockState_Unlocked") +CLOSED_STATUS = ("closed", "kAugLockDoorState_Closed") +OPEN_STATUS = ("open", "kAugLockDoorState_Open") + class Lock(Device): def __init__(self, device_id, data): @@ -38,11 +46,24 @@ def __init__(self, data): self._bridge = None self._doorsense = False + self._lock_status = LockStatus.UNKNOWN + self._door_state = LockDoorStatus.UNKNOWN + self._lock_status_datetime = None + self._door_state_datetime = None + if "LockStatus" in data: - if ( - "doorState" in data["LockStatus"] - and data["LockStatus"]["doorState"] != "init" - ): + lock_status = data["LockStatus"] + + self._lock_status = determine_lock_status(lock_status.get("status")) + self._door_state = determine_door_state(lock_status.get("doorState")) + + if "dateTime" in lock_status: + self._lock_status_datetime = dateutil.parser.parse( + lock_status["dateTime"] + ) + self._door_state_datetime = self._lock_status_datetime + + if "doorState" in lock_status and lock_status["doorState"] != "init": self._doorsense = True if "keypad" in data: @@ -68,6 +89,50 @@ def bridge(self): def doorsense(self): return self._doorsense + @property + def lock_status(self): + return self._lock_status + + @lock_status.setter + def lock_status(self, var): + """Update the lock status (usually form the activity log).""" + if var not in LockStatus: + raise ValueError + self._lock_status = var + + @property + def lock_status_datetime(self): + return self._lock_status_datetime + + @lock_status_datetime.setter + def lock_status_datetime(self, var): + """Update the lock status datetime (usually form the activity log).""" + if not isinstance(var, datetime.date): + raise ValueError + self._lock_status_datetime = var + + @property + def door_state(self): + return self._door_state + + @door_state.setter + def door_state(self, var): + """Update the door state (usually form the activity log).""" + if var not in LockDoorStatus: + raise ValueError + self._door_state = var + + @property + def door_state_datetime(self): + return self._door_state_datetime + + @door_state_datetime.setter + def door_state_datetime(self, var): + """Update the door state datetime (usually form the activity log).""" + if not isinstance(var, datetime.date): + raise ValueError + self._door_state_datetime = var + class LockStatus(Enum): LOCKED = "locked" @@ -79,3 +144,19 @@ class LockDoorStatus(Enum): CLOSED = "closed" OPEN = "open" UNKNOWN = "unknown" + + +def determine_lock_status(status): + if status in LOCKED_STATUS: + return LockStatus.LOCKED + if status in UNLOCKED_STATUS: + return LockStatus.UNLOCKED + return LockStatus.UNKNOWN + + +def determine_door_state(status): + if status in CLOSED_STATUS: + return LockDoorStatus.CLOSED + if status in OPEN_STATUS: + return LockDoorStatus.OPEN + return LockDoorStatus.UNKNOWN diff --git a/setup.py b/setup.py index 407bd02..fb28a4f 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='py-august', - version='0.14.0', + version='0.15.0', packages=['august'], url='https://github.com/snjoetw/py-august', license='MIT', diff --git a/tests/fixtures/get_lock.online_with_doorsense.json b/tests/fixtures/get_lock.online_with_doorsense.json index c1ec645..b0f9475 100644 --- a/tests/fixtures/get_lock.online_with_doorsense.json +++ b/tests/fixtures/get_lock.online_with_doorsense.json @@ -20,7 +20,7 @@ "LockID" : "ABC", "LockName" : "Online door with doorsense", "LockStatus" : { - "dateTime" : "2000-00-00T00:00:00.447Z", + "dateTime" : "2017-12-10T04:48:30.272Z", "doorState" : "open", "isLockStatusChanged" : false, "status" : "locked", diff --git a/tests/test_api.py b/tests/test_api.py index 31c4aa1..fc1d001 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -3,17 +3,26 @@ from datetime import datetime import requests_mock +import dateutil.parser from dateutil.tz import tzutc from requests.exceptions import HTTPError from requests.models import Response from requests.structures import CaseInsensitiveDict import august.activity -from august.api import (API_GET_DOORBELL_URL, API_GET_DOORBELLS_URL, - API_GET_HOUSE_ACTIVITIES_URL, API_GET_LOCK_STATUS_URL, - API_GET_LOCK_URL, API_GET_LOCKS_URL, API_GET_PINS_URL, - API_LOCK_URL, API_UNLOCK_URL, Api, - _raise_response_exceptions) +from august.api import ( + API_GET_DOORBELL_URL, + API_GET_DOORBELLS_URL, + API_GET_HOUSE_ACTIVITIES_URL, + API_GET_LOCK_STATUS_URL, + API_GET_LOCK_URL, + API_GET_LOCKS_URL, + API_GET_PINS_URL, + API_LOCK_URL, + API_UNLOCK_URL, + Api, + _raise_response_exceptions, +) from august.bridge import BridgeDetail, BridgeStatus, BridgeStatusDetail from august.exceptions import AugustApiHTTPError from august.lock import LockDoorStatus, LockStatus @@ -146,6 +155,15 @@ def test_get_lock_detail_with_doorsense_bridge_online(self, mock): self.assertEqual(True, lock.bridge.operative) self.assertEqual(True, lock.doorsense) + 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 + ) + @requests_mock.Mocker() def test_get_lock_detail_bridge_online(self, mock): mock.register_uri( @@ -169,6 +187,15 @@ def test_get_lock_detail_bridge_online(self, mock): self.assertEqual(True, lock.bridge.operative) self.assertEqual(True, lock.doorsense) + self.assertEqual(LockStatus.LOCKED, lock.lock_status) + self.assertEqual(LockDoorStatus.CLOSED, 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 + ) + @requests_mock.Mocker() def test_get_lock_detail_bridge_offline(self, mock): mock.register_uri( @@ -190,6 +217,11 @@ def test_get_lock_detail_bridge_offline(self, mock): self.assertEqual(None, lock.bridge) self.assertEqual(False, lock.doorsense) + self.assertEqual(LockStatus.UNKNOWN, lock.lock_status) + self.assertEqual(LockDoorStatus.UNKNOWN, lock.door_state) + self.assertEqual(None, lock.lock_status_datetime) + self.assertEqual(None, lock.door_state_datetime) + @requests_mock.Mocker() def test_get_lock_detail_doorsense_init_state(self, mock): mock.register_uri( @@ -213,6 +245,30 @@ def test_get_lock_detail_doorsense_init_state(self, mock): self.assertEqual(True, lock.bridge.operative) self.assertEqual(False, lock.doorsense) + self.assertEqual(LockStatus.LOCKED, lock.lock_status) + self.assertEqual(LockDoorStatus.UNKNOWN, 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.lock_status = LockStatus.UNLOCKED + self.assertEqual(LockStatus.UNLOCKED, lock.lock_status) + + lock.door_state = LockDoorStatus.OPEN + self.assertEqual(LockDoorStatus.OPEN, lock.door_state) + + lock.lock_status_datetime = dateutil.parser.parse("2020-12-10T04:48:30.272Z") + self.assertEqual( + dateutil.parser.parse("2020-12-10T04:48:30.272Z"), lock.lock_status_datetime + ) + lock.door_state_datetime = dateutil.parser.parse("2019-12-10T04:48:30.272Z") + self.assertEqual( + dateutil.parser.parse("2019-12-10T04:48:30.272Z"), lock.door_state_datetime + ) + @requests_mock.Mocker() def test_get_lock_status_with_locked_response(self, mock): lock_id = 1234