Skip to content

Commit

Permalink
Merge pull request #39 from bdraco/lock_with_detail_so_we_can_get_doo…
Browse files Browse the repository at this point in the history
…r_state_sq

Add lock_return_activity and unlock_return_activity apis (additional reduction in august api calls)
  • Loading branch information
snjoetw authored Feb 20, 2020
2 parents 13d2a55 + 78078dc commit f41a3a9
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 f41a3a9

Please sign in to comment.