Bump pymyq to 3.0.1 (#46079)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
ehendrix23 2021-02-10 13:30:52 -07:00 committed by GitHub
parent c59b1c72c5
commit 9bc3c6c130
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 95 additions and 85 deletions

View file

@ -11,7 +11,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY, PLATFORMS, UPDATE_INTERVAL
@ -40,11 +40,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
except MyQError as err:
raise ConfigEntryNotReady from err
# Called by DataUpdateCoordinator, allows to capture any MyQError exceptions and to throw an HASS UpdateFailed
# exception instead, preventing traceback in HASS logs.
async def async_update_data():
try:
return await myq.update_device_info()
except MyQError as err:
raise UpdateFailed(str(err)) from err
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="myq devices",
update_method=myq.update_device_info,
update_method=async_update_data,
update_interval=timedelta(seconds=UPDATE_INTERVAL),
)

View file

@ -1,7 +1,5 @@
"""Support for MyQ gateways."""
from pymyq.const import (
DEVICE_FAMILY as MYQ_DEVICE_FAMILY,
DEVICE_FAMILY_GATEWAY as MYQ_DEVICE_FAMILY_GATEWAY,
DEVICE_STATE as MYQ_DEVICE_STATE,
DEVICE_STATE_ONLINE as MYQ_DEVICE_STATE_ONLINE,
KNOWN_MODELS,
@ -25,9 +23,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities = []
for device in myq.devices.values():
if device.device_json[MYQ_DEVICE_FAMILY] == MYQ_DEVICE_FAMILY_GATEWAY:
entities.append(MyQBinarySensorEntity(coordinator, device))
for device in myq.gateways.values():
entities.append(MyQBinarySensorEntity(coordinator, device))
async_add_entities(entities, True)

View file

@ -1,9 +1,9 @@
"""The MyQ integration."""
from pymyq.device import (
STATE_CLOSED as MYQ_STATE_CLOSED,
STATE_CLOSING as MYQ_STATE_CLOSING,
STATE_OPEN as MYQ_STATE_OPEN,
STATE_OPENING as MYQ_STATE_OPENING,
from pymyq.garagedoor import (
STATE_CLOSED as MYQ_COVER_STATE_CLOSED,
STATE_CLOSING as MYQ_COVER_STATE_CLOSING,
STATE_OPEN as MYQ_COVER_STATE_OPEN,
STATE_OPENING as MYQ_COVER_STATE_OPENING,
)
from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING
@ -13,10 +13,10 @@ DOMAIN = "myq"
PLATFORMS = ["cover", "binary_sensor"]
MYQ_TO_HASS = {
MYQ_STATE_CLOSED: STATE_CLOSED,
MYQ_STATE_CLOSING: STATE_CLOSING,
MYQ_STATE_OPEN: STATE_OPEN,
MYQ_STATE_OPENING: STATE_OPENING,
MYQ_COVER_STATE_CLOSED: STATE_CLOSED,
MYQ_COVER_STATE_CLOSING: STATE_CLOSING,
MYQ_COVER_STATE_OPEN: STATE_OPEN,
MYQ_COVER_STATE_OPENING: STATE_OPENING,
}
MYQ_GATEWAY = "myq_gateway"
@ -24,7 +24,7 @@ MYQ_COORDINATOR = "coordinator"
# myq has some ratelimits in place
# and 61 seemed to be work every time
UPDATE_INTERVAL = 61
UPDATE_INTERVAL = 15
# Estimated time it takes myq to start transition from one
# state to the next.

View file

@ -1,14 +1,14 @@
"""Support for MyQ-Enabled Garage Doors."""
import time
import logging
from pymyq.const import (
DEVICE_STATE as MYQ_DEVICE_STATE,
DEVICE_STATE_ONLINE as MYQ_DEVICE_STATE_ONLINE,
DEVICE_TYPE as MYQ_DEVICE_TYPE,
DEVICE_TYPE_GATE as MYQ_DEVICE_TYPE_GATE,
KNOWN_MODELS,
MANUFACTURER,
)
from pymyq.errors import MyQError
from homeassistant.components.cover import (
DEVICE_CLASS_GARAGE,
@ -17,19 +17,12 @@ from homeassistant.components.cover import (
SUPPORT_OPEN,
CoverEntity,
)
from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPENING
from homeassistant.core import callback
from homeassistant.helpers.event import async_call_later
from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
DOMAIN,
MYQ_COORDINATOR,
MYQ_GATEWAY,
MYQ_TO_HASS,
TRANSITION_COMPLETE_DURATION,
TRANSITION_START_DURATION,
)
from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY, MYQ_TO_HASS
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
@ -50,13 +43,11 @@ class MyQDevice(CoordinatorEntity, CoverEntity):
"""Initialize with API object, device id."""
super().__init__(coordinator)
self._device = device
self._last_action_timestamp = 0
self._scheduled_transition_update = None
@property
def device_class(self):
"""Define this cover as a garage door."""
device_type = self._device.device_json.get(MYQ_DEVICE_TYPE)
device_type = self._device.device_type
if device_type is not None and device_type == MYQ_DEVICE_TYPE_GATE:
return DEVICE_CLASS_GATE
return DEVICE_CLASS_GARAGE
@ -87,6 +78,11 @@ class MyQDevice(CoordinatorEntity, CoverEntity):
"""Return if the cover is closing or not."""
return MYQ_TO_HASS.get(self._device.state) == STATE_CLOSING
@property
def is_open(self):
"""Return if the cover is opening or not."""
return MYQ_TO_HASS.get(self._device.state) == STATE_OPEN
@property
def is_opening(self):
"""Return if the cover is opening or not."""
@ -104,37 +100,48 @@ class MyQDevice(CoordinatorEntity, CoverEntity):
async def async_close_cover(self, **kwargs):
"""Issue close command to cover."""
self._last_action_timestamp = time.time()
await self._device.close()
self._async_schedule_update_for_transition()
if self.is_closing or self.is_closed:
return
try:
wait_task = await self._device.close(wait_for_state=False)
except MyQError as err:
_LOGGER.error(
"Closing of cover %s failed with error: %s", self._device.name, str(err)
)
return
# Write closing state to HASS
self.async_write_ha_state()
if not await wait_task:
_LOGGER.error("Closing of cover %s failed", self._device.name)
# Write final state to HASS
self.async_write_ha_state()
async def async_open_cover(self, **kwargs):
"""Issue open command to cover."""
self._last_action_timestamp = time.time()
await self._device.open()
self._async_schedule_update_for_transition()
if self.is_opening or self.is_open:
return
@callback
def _async_schedule_update_for_transition(self):
try:
wait_task = await self._device.open(wait_for_state=False)
except MyQError as err:
_LOGGER.error(
"Opening of cover %s failed with error: %s", self._device.name, str(err)
)
return
# Write opening state to HASS
self.async_write_ha_state()
# Cancel any previous updates
if self._scheduled_transition_update:
self._scheduled_transition_update()
if not await wait_task:
_LOGGER.error("Opening of cover %s failed", self._device.name)
# Schedule an update for when we expect the transition
# to be completed so the garage door or gate does not
# seem like its closing or opening for a long time
self._scheduled_transition_update = async_call_later(
self.hass,
TRANSITION_COMPLETE_DURATION,
self._async_complete_schedule_update,
)
async def _async_complete_schedule_update(self, _):
"""Update status of the cover via coordinator."""
self._scheduled_transition_update = None
await self.coordinator.async_request_refresh()
# Write final state to HASS
self.async_write_ha_state()
@property
def device_info(self):
@ -152,22 +159,8 @@ class MyQDevice(CoordinatorEntity, CoverEntity):
device_info["via_device"] = (DOMAIN, self._device.parent_device_id)
return device_info
@callback
def _async_consume_update(self):
if time.time() - self._last_action_timestamp <= TRANSITION_START_DURATION:
# If we just started a transition we need
# to prevent a bouncy state
return
self.async_write_ha_state()
async def async_added_to_hass(self):
"""Subscribe to updates."""
self.async_on_remove(
self.coordinator.async_add_listener(self._async_consume_update)
self.coordinator.async_add_listener(self.async_write_ha_state)
)
async def async_will_remove_from_hass(self):
"""Undo subscription."""
if self._scheduled_transition_update:
self._scheduled_transition_update()

View file

@ -2,7 +2,7 @@
"domain": "myq",
"name": "MyQ",
"documentation": "https://www.home-assistant.io/integrations/myq",
"requirements": ["pymyq==2.0.14"],
"requirements": ["pymyq==3.0.1"],
"codeowners": ["@bdraco"],
"config_flow": true,
"homekit": {

View file

@ -1545,7 +1545,7 @@ pymsteams==0.1.12
pymusiccast==0.1.6
# homeassistant.components.myq
pymyq==2.0.14
pymyq==3.0.1
# homeassistant.components.mysensors
pymysensors==0.20.1

View file

@ -808,7 +808,7 @@ pymodbus==2.3.0
pymonoprice==0.3
# homeassistant.components.myq
pymyq==2.0.14
pymyq==3.0.1
# homeassistant.components.mysensors
pymysensors==0.20.1

View file

@ -1,14 +1,18 @@
"""Tests for the myq integration."""
import json
import logging
from unittest.mock import patch
from pymyq.const import ACCOUNTS_ENDPOINT, DEVICES_ENDPOINT
from homeassistant.components.myq.const import DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture
_LOGGER = logging.getLogger(__name__)
async def async_init_integration(
hass: HomeAssistant,
@ -20,16 +24,24 @@ async def async_init_integration(
devices_json = load_fixture(devices_fixture)
devices_dict = json.loads(devices_json)
def _handle_mock_api_request(method, endpoint, **kwargs):
if endpoint == "Login":
return {"SecurityToken": 1234}
if endpoint == "My":
return {"Account": {"Id": 1}}
if endpoint == "Accounts/1/Devices":
return devices_dict
return {}
def _handle_mock_api_oauth_authenticate():
return 1234, 1800
with patch("pymyq.api.API.request", side_effect=_handle_mock_api_request):
def _handle_mock_api_request(method, returns, url, **kwargs):
_LOGGER.debug("URL: %s", url)
if url == ACCOUNTS_ENDPOINT:
_LOGGER.debug("Accounts")
return None, {"accounts": [{"id": 1, "name": "mock"}]}
if url == DEVICES_ENDPOINT.format(account_id=1):
_LOGGER.debug("Devices")
return None, devices_dict
_LOGGER.debug("Something else")
return None, {}
with patch(
"pymyq.api.API._oauth_authenticate",
side_effect=_handle_mock_api_oauth_authenticate,
), patch("pymyq.api.API.request", side_effect=_handle_mock_api_request):
entry = MockConfigEntry(
domain=DOMAIN, data={CONF_USERNAME: "mock", CONF_PASSWORD: "mock"}
)