Bump pymyq to 3.0.1 (#46079)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
c59b1c72c5
commit
9bc3c6c130
8 changed files with 95 additions and 85 deletions
|
@ -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),
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"}
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue