Skip to content

Commit

Permalink
Add lock_return_activity and unlock_return_activity apis
Browse files Browse the repository at this point in the history
It is now possible to call the lock and unlock remote operation
and get back a LockOperationActivity that can be consumed by
update_lock_detail_from_activity.  If the lock supports doorsense,
a DoorOperationActivity is also returned since the underlying
August API returns this.

Lock operations now avoid the need to fetch lock details afterward
which further reduces the number of API calls we make to the
August API
  • Loading branch information
bdraco committed Feb 19, 2020
1 parent 24b08ee commit 78078dc
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 28 deletions.
122 changes: 100 additions & 22 deletions august/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import time

import dateutil.parser
from requests import Session, request
from requests.exceptions import HTTPError

Expand All @@ -22,8 +23,10 @@
from august.lock import (
Lock,
LockDetail,
determine_lock_status,
LockDoorStatus,
determine_door_state,
determine_lock_status,
door_state_to_string,
)
from august.pin import Pin

Expand Down Expand Up @@ -168,18 +171,9 @@ def get_house_activities(self, access_token, house_id, limit=8):

activities = []
for activity_json in response.json():
action = activity_json.get("action")

if action in ACTIVITY_ACTIONS_DOORBELL_DING:
activities.append(DoorbellDingActivity(activity_json))
elif action in ACTIVITY_ACTIONS_DOORBELL_MOTION:
activities.append(DoorbellMotionActivity(activity_json))
elif action in ACTIVITY_ACTIONS_DOORBELL_VIEW:
activities.append(DoorbellViewActivity(activity_json))
elif action in ACTIVITY_ACTIONS_LOCK_OPERATION:
activities.append(LockOperationActivity(activity_json))
elif action in ACTIVITY_ACTIONS_DOOR_OPERATION:
activities.append(DoorOperationActivity(activity_json))
activity = _activity_from_dict(activity_json)
if activity:
activities.append(activity)

return activities

Expand Down Expand Up @@ -239,26 +233,58 @@ def get_pins(self, access_token, lock_id):

return [Pin(pin_json) for pin_json in json_dict.get("loaded", [])]

def lock(self, access_token, lock_id):
json_dict = self._call_api(
def _call_lock_operation(self, url_str, access_token, lock_id):
return self._call_api(
"put",
API_LOCK_URL.format(lock_id=lock_id),
url_str.format(lock_id=lock_id),
access_token=access_token,
timeout=self._command_timeout,
).json()

def _lock(self, access_token, lock_id):
return self._call_lock_operation(API_LOCK_URL, access_token, lock_id)

def lock(self, access_token, lock_id):
"""Execute a remote lock operation.
Returns a LockStatus state.
"""
json_dict = self._lock(access_token, lock_id)
return determine_lock_status(json_dict.get("status"))

def lock_return_activities(self, access_token, lock_id):
"""Execute a remote lock operation.
Returns an array of one or more august.activity.Activity objects
If the lock supports door sense one of the activities
will include the current door state.
"""
json_dict = self._lock(access_token, lock_id)
return _convert_lock_result_to_activities(json_dict)

def _unlock(self, access_token, lock_id):
return self._call_lock_operation(API_UNLOCK_URL, access_token, lock_id)

def unlock(self, access_token, lock_id):
json_dict = self._call_api(
"put",
API_UNLOCK_URL.format(lock_id=lock_id),
access_token=access_token,
timeout=self._command_timeout,
).json()
"""Execute a remote unlock operation.
Returns a LockStatus state.
"""
json_dict = self._unlock(access_token, lock_id)
return determine_lock_status(json_dict.get("status"))

def unlock_return_activities(self, access_token, lock_id):
"""Execute a remote lock operation.
Returns an array of one or more august.activity.Activity objects
If the lock supports door sense one of the activities
will include the current door state.
"""
json_dict = self._unlock(access_token, lock_id)
return _convert_lock_result_to_activities(json_dict)

def refresh_access_token(self, access_token):
response = self._call_api("get", API_GET_HOUSES_URL, access_token=access_token)

Expand Down Expand Up @@ -335,3 +361,55 @@ def _raise_response_exceptions(response):
response=err.response,
) from err
raise err


def _convert_lock_result_to_activities(lock_json_dict):
activities = []
lock_info_json_dict = lock_json_dict.get("info", {})
lock_id = lock_info_json_dict.get("lockID")
lock_action_text = lock_info_json_dict.get("action")
activity_epoch = _datetime_string_to_epoch(lock_info_json_dict.get("startTime"))
activity_lock_dict = _map_lock_result_to_activity(
lock_id, activity_epoch, lock_action_text
)
activities.append(activity_lock_dict)

door_state = determine_door_state(lock_json_dict.get("doorState"))
if door_state != LockDoorStatus.UNKNOWN:
activity_door_dict = _map_lock_result_to_activity(
lock_id, activity_epoch, door_state_to_string(door_state)
)
activities.append(activity_door_dict)

return activities


def _activity_from_dict(activity_dict):
action = activity_dict.get("action")

if action in ACTIVITY_ACTIONS_DOORBELL_DING:
return DoorbellDingActivity(activity_dict)
if action in ACTIVITY_ACTIONS_DOORBELL_MOTION:
return DoorbellMotionActivity(activity_dict)
if action in ACTIVITY_ACTIONS_DOORBELL_VIEW:
return DoorbellViewActivity(activity_dict)
if action in ACTIVITY_ACTIONS_LOCK_OPERATION:
return LockOperationActivity(activity_dict)
if action in ACTIVITY_ACTIONS_DOOR_OPERATION:
return DoorOperationActivity(activity_dict)
return None


def _map_lock_result_to_activity(lock_id, activity_epoch, action_text):
"""Create an august activity from a lock result."""
mapped_dict = {
"dateTime": activity_epoch,
"deviceID": lock_id,
"deviceType": "lock",
"action": action_text,
}
return _activity_from_dict(mapped_dict)


def _datetime_string_to_epoch(datetime_string):
return dateutil.parser.parse(datetime_string).timestamp() * 1000
13 changes: 11 additions & 2 deletions august/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

LOCKED_STATUS = ("locked", "kAugLockState_Locked")
UNLOCKED_STATUS = ("unlocked", "kAugLockState_Unlocked")
CLOSED_STATUS = ("closed", "kAugLockDoorState_Closed")
OPEN_STATUS = ("open", "kAugLockDoorState_Open")
CLOSED_STATUS = ("closed", "kAugLockDoorState_Closed", "kAugDoorState_Closed")
OPEN_STATUS = ("open", "kAugLockDoorState_Open", "kAugDoorState_Open")


class Lock(Device):
Expand Down Expand Up @@ -160,3 +160,12 @@ def determine_door_state(status):
if status in OPEN_STATUS:
return LockDoorStatus.OPEN
return LockDoorStatus.UNKNOWN


def door_state_to_string(door_status):
"""Returns the normalized value that determine_door_state represents."""
if door_status == LockDoorStatus.OPEN:
return "dooropen"
if door_status == LockDoorStatus.CLOSED:
return "doorclosed"
raise ValueError
2 changes: 0 additions & 2 deletions august/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
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):
Expand Down
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.16.0',
version='0.17.0',
packages=['august'],
url='https://github.com/snjoetw/py-august',
license='MIT',
Expand Down
26 changes: 26 additions & 0 deletions tests/fixtures/lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"resultsFromOperationCache" : false,
"retryCount" : 1,
"info" : {
"lockType" : "lock_version_3",
"lockID" : "ABC123",
"lockStatusChanged" : true,
"rssi" : -87,
"wlanRSSI" : -42,
"context" : {
"startDate" : "2020-02-19T19:44:54.370Z",
"transactionID" : "transid",
"retryCount" : 1
},
"serialNumber" : "serial",
"action" : "lock",
"wlanSNR" : 56,
"duration" : 3119,
"startTime" : "2020-02-19T19:44:54.371Z",
"serial" : "serial",
"bridgeID" : "brdigeid"
},
"doorState" : "kAugDoorState_Closed",
"status" : "kAugLockState_Locked",
"totalTime" : 3133
}
25 changes: 25 additions & 0 deletions tests/fixtures/lock_without_doorstate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"resultsFromOperationCache" : false,
"retryCount" : 1,
"info" : {
"lockType" : "lock_version_3",
"lockID" : "ABC123",
"lockStatusChanged" : true,
"rssi" : -87,
"wlanRSSI" : -42,
"context" : {
"startDate" : "2020-02-19T19:44:54.370Z",
"transactionID" : "transid",
"retryCount" : 1
},
"serialNumber" : "serial",
"action" : "lock",
"wlanSNR" : 56,
"duration" : 3119,
"startTime" : "2020-02-19T19:44:54.371Z",
"serial" : "serial",
"bridgeID" : "brdigeid"
},
"status" : "kAugLockState_Locked",
"totalTime" : 3133
}
26 changes: 26 additions & 0 deletions tests/fixtures/unlock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"resultsFromOperationCache" : false,
"info" : {
"bridgeID" : "bridgeid",
"duration" : 3773,
"lockStatusChanged" : true,
"serial" : "serial",
"startTime" : "2020-02-19T19:44:26.745Z",
"lockID" : "ABC",
"context" : {
"transactionID" : "transid",
"retryCount" : 1,
"startDate" : "2020-02-19T19:44:26.744Z"
},
"lockType" : "lock_version_3",
"serialNumber" : "serialnum",
"wlanRSSI" : -41,
"action" : "unlock",
"rssi" : -88,
"wlanSNR" : 58
},
"status" : "kAugLockState_Unlocked",
"totalTime" : 3784,
"retryCount" : 1,
"doorState" : "kAugDoorState_Closed"
}
25 changes: 25 additions & 0 deletions tests/fixtures/unlock_without_doorstate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"resultsFromOperationCache" : false,
"info" : {
"bridgeID" : "bridgeid",
"duration" : 3773,
"lockStatusChanged" : true,
"serial" : "serial",
"startTime" : "2020-02-19T19:44:26.745Z",
"lockID" : "ABC123",
"context" : {
"transactionID" : "transid",
"retryCount" : 1,
"startDate" : "2020-02-19T19:44:26.744Z"
},
"lockType" : "lock_version_3",
"serialNumber" : "serialnum",
"wlanRSSI" : -41,
"action" : "unlock",
"rssi" : -88,
"wlanSNR" : 58
},
"status" : "kAugLockState_Unlocked",
"totalTime" : 3784,
"retryCount" : 1
}
Loading

0 comments on commit 78078dc

Please sign in to comment.