Skip to content

Commit b887d3e

Browse files
committed
Account class
1 parent ca3174d commit b887d3e

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

pymyq/account.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
"""Define MyQ accounts."""
2+
3+
import logging
4+
import asyncio
5+
from .device import MyQDevice
6+
from datetime import datetime, timedelta
7+
from .garagedoor import MyQGaragedoor
8+
from .lamp import MyQLamp
9+
from typing import TYPE_CHECKING, Dict, Optional
10+
from .const import (
11+
DEVICE_FAMILY_GARAGEDOOR,
12+
DEVICE_FAMLY_LAMP,
13+
DEVICE_FAMILY_GATEWAY,
14+
DEVICES_ENDPOINT,
15+
)
16+
from .errors import MyQError
17+
18+
if TYPE_CHECKING:
19+
from .api import API
20+
21+
_LOGGER = logging.getLogger(__name__)
22+
23+
DEFAULT_STATE_UPDATE_INTERVAL = timedelta(seconds=5)
24+
25+
26+
class MyQAccount:
27+
"""Define an account."""
28+
29+
def __init__(self, api: "API", account_json: dict, devices: dict = {}) -> None:
30+
31+
self._api = api
32+
self.account_json = account_json
33+
self._devices = devices
34+
self.last_state_update = None # type: Optional[datetime]
35+
self._update = asyncio.Lock() # type: asyncio.Lock
36+
37+
@property
38+
def api(self) -> "API":
39+
"""Return API object"""
40+
return self._api
41+
42+
@property
43+
def account_id(self) -> Optional[str]:
44+
"""Return account id """
45+
return self.account_json.get("id")
46+
47+
@property
48+
def name(self) -> Optional[str]:
49+
"""Return account name"""
50+
return self.account_json.get("name")
51+
52+
@property
53+
def devices(self) -> Dict[str, MyQDevice]:
54+
"""Return all devices within account"""
55+
return self._devices
56+
57+
@property
58+
def covers(self) -> Dict[str, MyQGaragedoor]:
59+
"""Return only those devices that are covers."""
60+
return {
61+
device_id: device
62+
for device_id, device in self.devices.items()
63+
if isinstance(device, MyQGaragedoor)
64+
}
65+
66+
@property
67+
def lamps(self) -> Dict[str, MyQLamp]:
68+
"""Return only those devices that are covers."""
69+
return {
70+
device_id: device
71+
for device_id, device in self.devices.items()
72+
if isinstance(device, MyQLamp)
73+
}
74+
75+
@property
76+
def gateways(self) -> Dict[str, MyQDevice]:
77+
"""Return only those devices that are covers."""
78+
return {
79+
device_id: device
80+
for device_id, device in self.devices.items()
81+
if device.device_json["device_family"] == DEVICE_FAMILY_GATEWAY
82+
}
83+
84+
async def _get_devices(self) -> None:
85+
86+
_LOGGER.debug("Retrieving devices for account %s", self.name or self.account_id)
87+
88+
_, devices_resp = await self._api.request(
89+
method="get",
90+
returns="json",
91+
url=DEVICES_ENDPOINT.format(account_id=self.account_id),
92+
)
93+
94+
if devices_resp is not None and not isinstance(devices_resp, dict):
95+
raise MyQError(
96+
f"Received object devices_resp of type {type(devices_resp)} but expecting type dict"
97+
)
98+
99+
state_update_timestmp = datetime.utcnow()
100+
if devices_resp is not None and devices_resp.get("items") is not None:
101+
for device in devices_resp.get("items"):
102+
serial_number = device.get("serial_number")
103+
if serial_number is None:
104+
_LOGGER.debug(
105+
f"No serial number for device with name {device.get('name')}."
106+
)
107+
continue
108+
109+
if serial_number in self._devices:
110+
_LOGGER.debug(
111+
f"Updating information for device with serial number {serial_number}"
112+
)
113+
myqdevice = self._devices[serial_number]
114+
115+
# When performing commands we might update the state temporary, need to ensure
116+
# that the state is not set back to something else if MyQ does not yet have updated
117+
# state
118+
last_update = myqdevice.device_json["state"].get("last_update")
119+
myqdevice.device_json = device
120+
121+
if (
122+
myqdevice.device_json["state"].get("last_update") is not None
123+
and myqdevice.device_json["state"].get("last_update")
124+
!= last_update
125+
):
126+
# MyQ has updated device state, reset ours ensuring we have the one from MyQ.
127+
myqdevice.state = None
128+
_LOGGER.debug(
129+
f"State for device {myqdevice.name} was updated to {myqdevice.state}"
130+
)
131+
132+
myqdevice.state_update = state_update_timestmp
133+
else:
134+
if device.get("device_family") == DEVICE_FAMILY_GARAGEDOOR:
135+
_LOGGER.debug(
136+
f"Adding new garage door with serial number {serial_number}"
137+
)
138+
new_device = MyQGaragedoor(
139+
account=self,
140+
device_json=device,
141+
state_update=state_update_timestmp,
142+
)
143+
elif device.get("device_family") == DEVICE_FAMLY_LAMP:
144+
_LOGGER.debug(
145+
f"Adding new lamp with serial number {serial_number}"
146+
)
147+
new_device = MyQLamp(
148+
account=self,
149+
device_json=device,
150+
state_update=state_update_timestmp,
151+
)
152+
elif device.get("device_family") == DEVICE_FAMILY_GATEWAY:
153+
_LOGGER.debug(
154+
f"Adding new gateway with serial number {serial_number}"
155+
)
156+
new_device = MyQDevice(
157+
account=self,
158+
device_json=device,
159+
state_update=state_update_timestmp,
160+
)
161+
else:
162+
_LOGGER.warning(
163+
f"Unknown device family {device.get('device_family')}"
164+
)
165+
new_device = None
166+
167+
if new_device:
168+
self._devices[serial_number] = new_device
169+
else:
170+
_LOGGER.debug(
171+
f"No devices found for account {self.name or self.account_id}"
172+
)
173+
174+
async def update(self) -> None:
175+
"""Get up-to-date device info."""
176+
# The MyQ API can time out if state updates are too frequent; therefore,
177+
# if back-to-back requests occur within a threshold, respond to only the first
178+
# Ensure only 1 update task can run at a time.
179+
async with self._update:
180+
call_dt = datetime.utcnow()
181+
if not self.last_state_update:
182+
self.last_state_update = call_dt - DEFAULT_STATE_UPDATE_INTERVAL
183+
next_available_call_dt = (
184+
self.last_state_update + DEFAULT_STATE_UPDATE_INTERVAL
185+
)
186+
187+
# Ensure we're within our minimum update interval
188+
if call_dt < next_available_call_dt:
189+
_LOGGER.debug(
190+
"Ignoring device update request for account %s as it is within throttle window",
191+
self.name or self.account_id,
192+
)
193+
return
194+
195+
await self._get_devices()
196+
self.last_state_update = datetime.utcnow()

0 commit comments

Comments
 (0)