Skip to content

Commit 737b7ca

Browse files
committed
Request new security token if expired
Request a new security token if it is expired. Also new instance parameter self.online that is set True if able to retrieve states from MyQ and False if not.
1 parent d43b78f commit 737b7ca

File tree

1 file changed

+73
-24
lines changed

1 file changed

+73
-24
lines changed

pymyq/api.py

+73-24
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import asyncio
33
import logging
44
from datetime import datetime, timedelta
5+
from typing import Optional
56

67
from aiohttp import ClientSession
78
from aiohttp.client_exceptions import ClientError
@@ -57,11 +58,16 @@ def __init__(self, brand: str, websession: ClientSession) -> None:
5758
raise UnsupportedBrandError('Unknown brand: {0}'.format(brand))
5859

5960
self._brand = brand
61+
self._websession = websession
62+
63+
self._credentials = None
6064
self._security_token = None
6165
self._devices = []
6266
self._last_update = None
63-
self._websession = websession
67+
self.online = False
68+
6469
self._update_lock = asyncio.Lock()
70+
self._security_token_lock = asyncio.Lock()
6571

6672
async def _request(
6773
self,
@@ -72,8 +78,16 @@ async def _request(
7278
params: dict = None,
7379
data: dict = None,
7480
json: dict = None,
75-
**kwargs) -> dict:
76-
"""Make a request."""
81+
login_request: bool = False,
82+
**kwargs) -> Optional[dict]:
83+
84+
# Get a security token if we do not have one AND this request
85+
# is not to get a security token.
86+
if self._security_token is None and not login_request:
87+
await self._get_security_token()
88+
if self._security_token is None:
89+
return None
90+
7791
url = '{0}/{1}'.format(API_BASE, endpoint)
7892

7993
if not headers:
@@ -98,8 +112,10 @@ async def _request(
98112
resp.raise_for_status()
99113
return await resp.json(content_type=None)
100114
except asyncio.TimeoutError:
101-
timeout = timeout * 2
102-
_LOGGER.warning('%s Timeout requesting from %s',
115+
# Start increasing timeout if already tried twice..
116+
if attempt > 1:
117+
timeout = timeout * 2
118+
_LOGGER.debug('%s Timeout requesting from %s',
103119
start_request_time, endpoint)
104120
except ClientError as err:
105121
if attempt == DEFAULT_REQUEST_RETRIES - 1:
@@ -119,7 +135,7 @@ async def _update_device_state(self) -> None:
119135
async with self._update_lock:
120136
if datetime.utcnow() - self._last_update >\
121137
MIN_TIME_BETWEEN_UPDATES:
122-
await self._get_device_states()
138+
self.online = await self._get_device_states()
123139

124140
async def _get_device_states(self) -> bool:
125141
_LOGGER.debug('Retrieving new device states')
@@ -129,10 +145,21 @@ async def _get_device_states(self) -> bool:
129145
_LOGGER.error('Getting device states failed: %s', err)
130146
return False
131147

132-
if int(devices_resp.get('ReturnCode', 1)) != 0:
133-
_LOGGER.error(
134-
'Error while retrieving states: %s',
135-
devices_resp.get('ErrorMessage', 'Unknown Error'))
148+
if devices_resp is None:
149+
return False
150+
151+
return_code = int(devices_resp.get('ReturnCode', 1))
152+
153+
if return_code != 0:
154+
if return_code == -3333:
155+
# Login error, need to retrieve a new token next time.
156+
self._security_token = None
157+
_LOGGER.debug('Security token expired')
158+
else:
159+
_LOGGER.error(
160+
'Error %s while retrieving states: %s',
161+
devices_resp.get('ReturnCode'),
162+
devices_resp.get('ErrorMessage', 'Unknown Error'))
136163
return False
137164

138165
self._store_device_states(devices_resp.get('Devices', []))
@@ -153,20 +180,39 @@ def _store_device_states(self, devices: dict) -> None:
153180

154181
async def authenticate(self, username: str, password: str) -> None:
155182
"""Authenticate against the API."""
156-
_LOGGER.debug('Starting authentication')
157-
login_resp = await self._request(
158-
'post',
159-
LOGIN_ENDPOINT,
160-
json={
161-
'username': username,
162-
'password': password
163-
})
164-
165-
if int(login_resp['ReturnCode']) != 0:
166-
raise MyQError(login_resp['ErrorMessage'])
167-
168-
self._security_token = login_resp['SecurityToken']
169-
_LOGGER.debug('Authentication completed')
183+
self._credentials = {
184+
'username': username,
185+
'password': password,
186+
}
187+
188+
await self._get_security_token()
189+
190+
async def _get_security_token(self) -> None:
191+
"""Request a security token."""
192+
_LOGGER.debug('Requesting security token.')
193+
if self._credentials is None:
194+
return
195+
196+
# Make sure only 1 request can be sent at a time.
197+
async with self._security_token_lock:
198+
# Confirm there is still no security token.
199+
if self._security_token is None:
200+
login_resp = await self._request(
201+
'post',
202+
LOGIN_ENDPOINT,
203+
json=self._credentials,
204+
login_request=True,
205+
)
206+
207+
return_code = int(login_resp.get('ReturnCode', 1))
208+
if return_code != 0:
209+
if return_code == 203:
210+
# Invalid username or password.
211+
_LOGGER.debug('Invalid username or password')
212+
self._credentials = None
213+
raise MyQError(login_resp['ErrorMessage'])
214+
215+
self._security_token = login_resp['SecurityToken']
170216

171217
async def get_devices(self, covers_only: bool = True) -> list:
172218
"""Get a list of all devices associated with the account."""
@@ -177,6 +223,9 @@ async def get_devices(self, covers_only: bool = True) -> list:
177223
# print(json.dumps(devices_resp, indent=4))
178224

179225
device_list = []
226+
if devices_resp is None:
227+
return device_list
228+
180229
for device in devices_resp['Devices']:
181230
if not covers_only or \
182231
device['MyQDeviceTypeName'] in SUPPORTED_DEVICE_TYPE_NAMES:

0 commit comments

Comments
 (0)