-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathgarage_door_opener.py
194 lines (155 loc) · 7.14 KB
/
garage_door_opener.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import requests
import json
# Endpoint URLS for v5 API and v5.1 API
API_V5_ENDPOINT = "https://api.myqdevice.com/api/v5/"
API_V51_ENDPOINT = "https://api.myqdevice.com/api/v5.1/"
LOGIN_ENDPOINT = API_V5_ENDPOINT + "Login"
EXPAND_ACCOUNT_ENDPOINT = API_V5_ENDPOINT + "/My?expand=account"
# This ID seems to be static
application_id = "NWknvuBd7LoFHfXmKNMBcgajXtZEgKUh4V7WNzMidrpUUluDpVYVZx+xT4PCM5Kx"
# Device Index
GARAGE_DOOR_DEVICE_INDEX = 0
# HTTP Headers
LOGIN_HEADERS = {
"Accept": "*/*",
"Accept-Encoding": "br, gzip, deflate",
"Accept-Language": "en-ca",
"User-Agent": "Chamberlain/9273 CFNetwork/974.2.1 Darwin/18.0.0",
"MyQApplicationId": application_id
}
AUTHENTICATED_LOGIN_HEADERS = {
"Accept": "*/*",
"Accept-Encoding": "br, gzip, deflate",
"Accept-Language": "en-ca",
"User-Agent": "Chamberlain/9273 CFNetwork/974.2.1 Darwin/18.0.0",
"MyQApplicationId": application_id,
"Connection": "keep-alive",
"Content-Type": "application/json"
}
# Door Action Messages
OPENING_DOOR_INDEX = 1
CLOSING_DOOR_INDEX = 2
ALREADY_CLOSED_INDEX = 3
ALREADY_OPENED_INDEX = 4
OPENING_INDEX = 5
CLOSING_INDEX = 6
DOOR_MESSAGES = {OPENING_DOOR_INDEX: "Please wait. The garage door is opening.",
CLOSING_DOOR_INDEX: "Please wait. The garage door is closing.",
ALREADY_CLOSED_INDEX: "The door is already closed.",
ALREADY_OPENED_INDEX: "The door is already opened.",
OPENING_INDEX: "Opening the garage door",
CLOSING_INDEX: "Closing the garage door"}
def generate_security_token(username, password):
"""
Given the username and password used to sign into Chamberlain's website or myQ app, send an HTTP
GET request to login and retrieve the security token returned from the HTTP response. This security token
is then updated in the AUTHENTICATED_LOGIN_HEADERS.
:param username: the username used to sign in with Chamberlain
:param password: the password for your Chamberlain account
"""
login_data = {"Password": password,
"UserName": username}
login_post = requests.post(url=LOGIN_ENDPOINT, json=login_data, headers=LOGIN_HEADERS)
# This should be logged rather than printed
if login_post.status_code != 200:
print("Error logging in!")
response = login_post.json()
AUTHENTICATED_LOGIN_HEADERS["SecurityToken"] = response["SecurityToken"]
def get_devices_endpoint():
"""
Returns the devices endpoint URL. This endpoint URL is generated by appending the Accounts/[AccountID] to the v5.1
API endpoint URL.
:return: the devices endpoint URL
"""
account_request = requests.get(EXPAND_ACCOUNT_ENDPOINT, headers=AUTHENTICATED_LOGIN_HEADERS)
# This should be logged rather than printed
if account_request.status_code != 200:
print("ERROR!")
response = account_request.json()
'''Form your own device endpoint URL rather than get it from ["Devices"]["href"] since that has a URL to v5 API
which will not accept your PUT request'''
return API_V51_ENDPOINT + "/Accounts/" + response["Account"]["Id"] + "/Devices"
def get_door_state(devices_endpoint):
"""
Returns the garage door state (opened, closed, opening, or closing).
:param devices_endpoint: the devices endpoint URL
:return: the garage door state (opened, closed, opening or closing)
"""
devices_request = requests.get(devices_endpoint, headers=AUTHENTICATED_LOGIN_HEADERS)
# This should be logged rather than printed
if devices_request.status_code != 200:
print("Error retrieving door status.")
response = devices_request.json()
door_state = response["items"][GARAGE_DOOR_DEVICE_INDEX]["state"]["door_state"]
return door_state
def is_door_closed(devices_endpoint):
"""
Returns if the garage door is closed.
:param devices_endpoint: the devices endpoint URL
:return: whether or not the garage door is closed
"""
return get_door_state(devices_endpoint) == "closed"
def get_device_sn_endpoint(devices_endpoint):
"""
Returns the devices's serial number endpoint.
:param devices_endpoint: the devices endpoint URL
:return: the device's serial number endpoint
"""
devices_request = requests.get(devices_endpoint, headers=AUTHENTICATED_LOGIN_HEADERS)
# This should be logged rather than printed
if devices_request.status_code != 200:
print("ERROR!")
response = devices_request.json()
device_sn = response["items"][GARAGE_DOOR_DEVICE_INDEX]["serial_number"]
device_sn_endpoint = devices_endpoint + "/" + device_sn
return device_sn_endpoint
def validate_door_action(devices_endpoint, door_action):
"""
Returns a door error message number if the action is invalid. There are a few ways for an invalid action to occur:
1) The door is opening or closing.
2) The user requests to open the door when the door is already open.
3) The user requests to close the door when the door is already closed.
:param devices_endpoint: the devices endpoint URL
:return: an error message number if the action is invalid
"""
if get_door_state(devices_endpoint) == "opening":
return OPENING_DOOR_INDEX
elif get_door_state(devices_endpoint) == "closing":
return CLOSING_DOOR_INDEX
elif get_door_state(devices_endpoint) == "closed" and door_action == "close":
return ALREADY_CLOSED_INDEX
elif get_door_state(devices_endpoint) == "open" and door_action == "open":
return ALREADY_OPENED_INDEX
def do_door_action(devices_endpoint, door_action):
"""
Does a door action (open or close the door)
:param devices_endpoint: the devices endpoint URL
:param door_action: the door action (open or close)
"""
device_sn_endpoint = get_device_sn_endpoint(devices_endpoint)
actions_endpoint = device_sn_endpoint + "/actions"
door_error_message_number = validate_door_action(devices_endpoint, door_action)
# If there is a door action error, return the error number back
if door_error_message_number is not None:
return door_error_message_number
door_action_data = {"action_type": door_action}
door_state_change_request = requests.put(actions_endpoint, headers=AUTHENTICATED_LOGIN_HEADERS,
data=json.dumps(door_action_data))
# Notifies the user when the door is being open or closed
if door_state_change_request.status_code == 204:
if door_action == "open":
door_message_number = OPENING_DOOR_INDEX
elif door_action == "close":
door_message_number = CLOSING_DOOR_INDEX
return door_message_number
if __name__ == "__main__":
# Just some sample code to illustrate how this works (login, open and close)
with open("config.json") as config_file:
config_contents = config_file.read()
login_credentials = json.loads(config_contents)
username = login_credentials["Username"]
password = login_credentials["Password"]
generate_security_token(username, password)
devices_endpoint = get_devices_endpoint()
get_door_state(devices_endpoint)
message_index = do_door_action(devices_endpoint, "open")