Skip to content

Commit 2e8961a

Browse files
committed
Initial Commit
1 parent e7ba8a3 commit 2e8961a

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

pymyq/__init__.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import requests
2+
import logging
3+
4+
5+
class MyQAPI:
6+
"""Class for interacting with the MyQ iOS App API."""
7+
8+
LIFTMASTER = 'liftmaster'
9+
CHAMBERLAIN = 'chamberlain'
10+
CRAFTMASTER = 'craftmaster'
11+
12+
SUPPORTED_BRANDS = [LIFTMASTER, CHAMBERLAIN, CRAFTMASTER]
13+
SUPPORTED_DEVICE_TYPE_NAMES = ['GarageDoorOpener', 'Garage Door Opener WGDO', 'VGDO']
14+
15+
APP_ID = 'app_id'
16+
HOST_URI = 'host_uri'
17+
18+
BRAND_MAPPINGS = {
19+
LIFTMASTER: {
20+
APP_ID: 'JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu',
21+
HOST_URI: 'myqexternal.myqdevice.com'
22+
},
23+
CHAMBERLAIN: {
24+
APP_ID: 'Vj8pQggXLhLy0WHahglCD4N1nAkkXQtGYpq2HrHD7H1nvmbT55KqtN6RSF4ILB%2Fi',
25+
HOST_URI: 'myqexternal.myqdevice.com'
26+
},
27+
CRAFTMASTER: {
28+
APP_ID: 'eU97d99kMG4t3STJZO/Mu2wt69yTQwM0WXZA5oZ74/ascQ2xQrLD/yjeVhEQccBZ',
29+
HOST_URI: 'craftexternal.myqdevice.com'
30+
}
31+
}
32+
33+
STATE_OPEN = 'open'
34+
STATE_CLOSED = 'closed'
35+
36+
LOCALE = "en"
37+
LOGIN_ENDPOINT = "api/user/validatewithculture"
38+
DEVICE_LIST_ENDPOINT = "api/v4/userdevicedetails/get"
39+
DEVICE_SET_ENDPOINT = "api/v4/DeviceAttribute/PutDeviceAttribute"
40+
HEADERS = {'User-Agent': 'Chamberlain/3773 (iPhone; iOS 10.0.1; Scale/2.00)'}
41+
42+
REQUEST_TIMEOUT = 3.0
43+
44+
DOOR_STATE = {
45+
'1': STATE_OPEN, #'open',
46+
'2': STATE_CLOSED, #'close',
47+
'4': STATE_OPEN, #'opening',
48+
'5': STATE_CLOSED, #'closing',
49+
'8': STATE_OPEN, #'in_transition',
50+
'9': STATE_OPEN, #'open'
51+
}
52+
53+
logger = logging.getLogger(__name__)
54+
55+
def __init__(self, username, password, brand):
56+
"""Initialize the API object."""
57+
self.username = username
58+
self.password = password
59+
self.brand = brand
60+
self.security_token = None
61+
self._logged_in = False
62+
self._valid_brand = False
63+
64+
def is_supported_brand(self):
65+
try:
66+
brand = self.BRAND_MAPPINGS[self.brand];
67+
except KeyError:
68+
return False
69+
70+
return True
71+
72+
def is_login_valid(self):
73+
"""Log in to the MyQ service."""
74+
params = {
75+
'username': self.username,
76+
'password': self.password,
77+
'appId': self.BRAND_MAPPINGS[self.brand][self.APP_ID],
78+
'culture': self.LOCALE
79+
}
80+
81+
try:
82+
login = requests.get(
83+
'https://{host_uri}/{login_endpoint}'.format(
84+
host_uri=self.BRAND_MAPPINGS[self.brand][self.HOST_URI],
85+
login_endpoint=self.LOGIN_ENDPOINT),
86+
params=params,
87+
headers=self.HEADERS,
88+
timeout=self.REQUEST_TIMEOUT
89+
)
90+
91+
login.raise_for_status()
92+
except requests.exceptions.HTTPError as err:
93+
self.logger.error("MyQ - API Error %s", ex)
94+
return False
95+
96+
try:
97+
self.security_token = login.json()['SecurityToken']
98+
except KeyError:
99+
return False
100+
101+
return True
102+
103+
def get_devices(self):
104+
"""List all MyQ devices."""
105+
if not self._logged_in:
106+
self._logged_in = self.is_login_valid()
107+
108+
params = {
109+
'appId': self.BRAND_MAPPINGS[self.brand][self.APP_ID],
110+
'securityToken': self.security_token
111+
}
112+
113+
try:
114+
devices = requests.get(
115+
'https://{host_uri}/{device_list_endpoint}'.format(
116+
host_uri=self.BRAND_MAPPINGS[self.brand][self.HOST_URI],
117+
device_list_endpoint=self.DEVICE_LIST_ENDPOINT),
118+
params=params,
119+
headers=self.HEADERS
120+
)
121+
122+
devices.raise_for_status()
123+
124+
devices = devices.json()['Devices']
125+
126+
return devices
127+
except requests.exceptions.HTTPError as err:
128+
self.logger.error("MyQ - API Error %s", ex)
129+
return False
130+
131+
def get_garage_doors(self):
132+
"""List only MyQ garage door devices."""
133+
devices = self.get_devices()
134+
135+
garage_doors = []
136+
137+
try:
138+
for device in devices:
139+
if device['MyQDeviceTypeName'] in self.SUPPORTED_DEVICE_TYPE_NAMES:
140+
dev = {}
141+
for attribute in device['Attributes']:
142+
if attribute['AttributeDisplayName'] == 'desc':
143+
dev['deviceid'] = device['MyQDeviceId']
144+
dev['name'] = attribute['Value']
145+
garage_doors.append(dev)
146+
147+
return garage_doors
148+
except TypeError:
149+
return False
150+
151+
def get_status(self, device_id):
152+
"""List only MyQ garage door devices."""
153+
devices = self.get_devices()
154+
155+
for device in devices:
156+
if device['MyQDeviceTypeName'] in self.SUPPORTED_DEVICE_TYPE_NAMES and device['MyQDeviceId'] == device_id:
157+
dev = {}
158+
for attribute in device['Attributes']:
159+
if attribute['AttributeDisplayName'] == 'doorstate':
160+
garage_state = attribute['Value']
161+
162+
garage_state = self.DOOR_STATE[garage_state]
163+
return garage_state
164+
165+
def close_device(self, device_id):
166+
"""Close MyQ Device."""
167+
return self.set_state(device_id, '0')
168+
169+
def open_device(self, device_id):
170+
"""Open MyQ Device."""
171+
return self.set_state(device_id, '1')
172+
173+
def set_state(self, device_id, state):
174+
"""Set device state."""
175+
payload = {
176+
'AttributeName': 'desireddoorstate',
177+
'MyQDeviceId': device_id,
178+
'ApplicationId': 'JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu',
179+
'AttributeValue': state,
180+
'SecurityToken': self.security_token,
181+
}
182+
183+
try:
184+
device_action = requests.put(
185+
'https://{host_uri}/{device_set_endpoint}'.format(
186+
host_uri=self.BRAND_MAPPINGS[self.brand][self.HOST_URI],
187+
device_set_endpoint=self.DEVICE_SET_ENDPOINT),
188+
data=payload,
189+
headers={
190+
'MyQApplicationId': 'JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu',
191+
'SecurityToken': self.security_token
192+
}
193+
)
194+
195+
devices.raise_for_status()
196+
except (NameError, requests.exceptions.HTTPError) as ex:
197+
self.logger.error("MyQ - API Error %s", ex)
198+
return False
199+
200+
return device_action.status_code == 200
201+

0 commit comments

Comments
 (0)