Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add door status to lock detail #37

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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