Skip to content

Commit

Permalink
Merge pull request #37 from bdraco/add_status_info_to_detail_to_avoid…
Browse files Browse the repository at this point in the history
…_multiple_api_calls

Add door status to lock detail
  • Loading branch information
snjoetw authored Feb 20, 2020
2 parents 8a0f029 + 6beafb0 commit e417c71
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 42 deletions.
45 changes: 14 additions & 31 deletions august/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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)
Expand Down
89 changes: 85 additions & 4 deletions august/lock.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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:
Expand All @@ -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"
Expand All @@ -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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/get_lock.online_with_doorsense.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
66 changes: 61 additions & 5 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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
Expand Down

0 comments on commit e417c71

Please sign in to comment.