Skip to content

Commit

Permalink
fix: import upstream pymyq per home-assistant/core#99947 (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcusrbrown committed Sep 24, 2023
1 parent f6a80c6 commit 025159d
Show file tree
Hide file tree
Showing 16 changed files with 1,795 additions and 21 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
* text=auto eol=lf

/custom_components/** linguist-vendored
/pymyq/** linguist-vendored
/www/** linguist-vendored
.HA_VERSION linguist-generated
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ blueprints/
custom_components/
deps/
esphome/
pymyq/
www/
automations.yaml
scenes.yaml
Expand Down
7 changes: 1 addition & 6 deletions .prettierrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ arrowParens: avoid
bracketSpacing: false
overrides:
- files:
- '*.json'
options:
singleQuote: false
trailingComma: none
- files:
- '.vscode/*.json'
- '.vscode/**/.json'
options:
tabWidth: 4
printWidth: 120
Expand Down
30 changes: 15 additions & 15 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
{
"files.associations": {
"*.yaml": "home-assistant",
"**/.github/workflows/*.yaml": "yaml",
".prettierrc.yaml": "yaml",
".yamllint*": "yaml"
},
"files.associations": {
"*.yaml": "home-assistant",
"**/.github/workflows/*.yaml": "yaml",
".prettierrc.yaml": "yaml",
".yamllint*": "yaml"
},

"search.exclude": {
"**/.cloud": true,
"**/__pycache__/**": true,
"**/bower_components": true,
"**/custom_components": true,
"**/node_modules": true,
"**/www/community": true,
"**/*.code-search": true
}
"search.exclude": {
"**/.cloud": true,
"**/__pycache__/**": true,
"**/bower_components": true,
"**/custom_components": true,
"**/node_modules": true,
"**/www/community": true,
"**/*.code-search": true
}
}
1 change: 1 addition & 0 deletions .yamllint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ignore: |
blueprints/
custom_components/
esphome/
pymyq/
www/
automations.yaml
scenes.yaml
Expand Down
2 changes: 2 additions & 0 deletions pymyq/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""Define module-level imports."""
from .api import login # noqa
2 changes: 2 additions & 0 deletions pymyq/__version__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""Define a version constant."""
__version__ = "3.1.6"
217 changes: 217 additions & 0 deletions pymyq/account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
"""Define MyQ accounts."""

import asyncio
from datetime import datetime, timedelta
import logging
from typing import TYPE_CHECKING, Dict, Optional

from .const import (
DEVICE_FAMILY_GARAGEDOOR,
DEVICE_FAMILY_GATEWAY,
DEVICE_FAMILY_LAMP,
DEVICE_FAMILY_LOCK,
DEVICES_ENDPOINT,
)
from .device import MyQDevice
from .errors import MyQError
from .garagedoor import MyQGaragedoor
from .lamp import MyQLamp
from .lock import MyQLock

if TYPE_CHECKING:
from .api import API

_LOGGER = logging.getLogger(__name__)

DEFAULT_STATE_UPDATE_INTERVAL = timedelta(seconds=5)


class MyQAccount:
"""Define an account."""

def __init__(self, api: "API", account_json: dict, devices: Optional[dict] = None) -> None:

self._api = api
self.account_json = account_json
if devices is None:
devices = {}
self._devices = devices
self.last_state_update = None # type: Optional[datetime]
self._update = asyncio.Lock() # type: asyncio.Lock

@property
def api(self) -> "API":
"""Return API object"""
return self._api

@property
def id(self) -> Optional[str]:
"""Return account id """
return self.account_json.get("id")

@property
def name(self) -> Optional[str]:
"""Return account name"""
return self.account_json.get("name")

@property
def devices(self) -> Dict[str, MyQDevice]:
"""Return all devices within account"""
return self._devices

@property
def covers(self) -> Dict[str, MyQGaragedoor]:
"""Return only those devices that are covers."""
return {
device_id: device
for device_id, device in self.devices.items()
if isinstance(device, MyQGaragedoor)
}

@property
def lamps(self) -> Dict[str, MyQLamp]:
"""Return only those devices that are lamps."""
return {
device_id: device
for device_id, device in self.devices.items()
if isinstance(device, MyQLamp)
}

@property
def gateways(self) -> Dict[str, MyQDevice]:
"""Return only those devices that are gateways."""
return {
device_id: device
for device_id, device in self.devices.items()
if device.device_json["device_family"] == DEVICE_FAMILY_GATEWAY
}

@property
def locks(self) -> Dict[str, MyQDevice]:
"""Return only those devices that are locks."""
return {
device_id: device
for device_id, device in self.devices.items()
if device.device_json["device_family"] == DEVICE_FAMILY_LOCK
}

@property
def other(self) -> Dict[str, MyQDevice]:
"""Return only those devices that are others."""
return {
device_id: device
for device_id, device in self.devices.items()
if type(device) is MyQDevice
and device.device_json["device_family"] != DEVICE_FAMILY_GATEWAY
}

async def _get_devices(self) -> None:

_LOGGER.debug("Retrieving devices for account %s", self.name or self.id)

_, devices_resp = await self._api.request(
method="get",
returns="json",
url=DEVICES_ENDPOINT.format(account_id=self.id),
)

if devices_resp is not None and not isinstance(devices_resp, dict):
raise MyQError(
f"Received object devices_resp of type {type(devices_resp)} but expecting type dict"
)

state_update_timestmp = datetime.utcnow()
if devices_resp is not None and devices_resp.get("items") is not None:
for device in devices_resp.get("items"):
serial_number = device.get("serial_number")
if serial_number is None:
_LOGGER.debug(
"No serial number for device with name %s.", device.get("name")
)
continue

if serial_number in self._devices:
_LOGGER.debug(
"Updating information for device with serial number %s",
serial_number,
)
myqdevice = self._devices[serial_number]
myqdevice.device_json = device
myqdevice.last_state_update = state_update_timestmp

else:
if device.get("device_family") == DEVICE_FAMILY_GARAGEDOOR:
_LOGGER.debug(
"Adding new garage door with serial number %s",
serial_number,
)
new_device = MyQGaragedoor(
account=self,
device_json=device,
state_update=state_update_timestmp,
)
elif device.get("device_family") == DEVICE_FAMILY_LAMP:
_LOGGER.debug(
"Adding new lamp with serial number %s", serial_number
)
new_device = MyQLamp(
account=self,
device_json=device,
state_update=state_update_timestmp,
)
elif device.get("device_family") == DEVICE_FAMILY_LOCK:
_LOGGER.debug(
"Adding new lock with serial number %s", serial_number
)
new_device = MyQLock(
account=self,
device_json=device,
state_update=state_update_timestmp,
)
else:
if device.get("device_family") == DEVICE_FAMILY_GATEWAY:
_LOGGER.debug(
"Adding new gateway with serial number %s",
serial_number,
)
else:
_LOGGER.debug(
"Adding unknown device family %s with serial number %s",
device.get("device_family"),
serial_number,
)

new_device = MyQDevice(
account=self,
device_json=device,
state_update=state_update_timestmp,
)

if new_device:
self._devices[serial_number] = new_device
else:
_LOGGER.debug("No devices found for account %s", self.name or self.id)

async def update(self) -> None:
"""Get up-to-date device info."""
# The MyQ API can time out if state updates are too frequent; therefore,
# if back-to-back requests occur within a threshold, respond to only the first
# Ensure only 1 update task can run at a time.
async with self._update:
call_dt = datetime.utcnow()
if not self.last_state_update:
self.last_state_update = call_dt - DEFAULT_STATE_UPDATE_INTERVAL
next_available_call_dt = (
self.last_state_update + DEFAULT_STATE_UPDATE_INTERVAL
)

# Ensure we're within our minimum update interval
if call_dt < next_available_call_dt:
_LOGGER.debug(
"Ignoring device update request for account %s as it is within throttle window",
self.name or self.id,
)
return

await self._get_devices()
self.last_state_update = datetime.utcnow()

0 comments on commit 025159d

Please sign in to comment.