Remove MyQ Integration (#103565)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Franck Nijhof <frenck@frenck.nl>pull/103594/head
parent
38acad8263
commit
c13744f4cf
|
@ -769,9 +769,6 @@ omit =
|
|||
homeassistant/components/mutesync/binary_sensor.py
|
||||
homeassistant/components/mvglive/sensor.py
|
||||
homeassistant/components/mycroft/*
|
||||
homeassistant/components/myq/__init__.py
|
||||
homeassistant/components/myq/cover.py
|
||||
homeassistant/components/myq/light.py
|
||||
homeassistant/components/mysensors/__init__.py
|
||||
homeassistant/components/mysensors/climate.py
|
||||
homeassistant/components/mysensors/cover.py
|
||||
|
|
|
@ -811,8 +811,6 @@ build.json @home-assistant/supervisor
|
|||
/tests/components/mutesync/ @currentoor
|
||||
/homeassistant/components/my/ @home-assistant/core
|
||||
/tests/components/my/ @home-assistant/core
|
||||
/homeassistant/components/myq/ @ehendrix23 @Lash-L
|
||||
/tests/components/myq/ @ehendrix23 @Lash-L
|
||||
/homeassistant/components/mysensors/ @MartinHjelmare @functionpointer
|
||||
/tests/components/mysensors/ @MartinHjelmare @functionpointer
|
||||
/homeassistant/components/mystrom/ @fabaff
|
||||
|
|
|
@ -1,122 +1,38 @@
|
|||
"""The MyQ integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import pymyq
|
||||
from pymyq.const import (
|
||||
DEVICE_STATE as MYQ_DEVICE_STATE,
|
||||
DEVICE_STATE_ONLINE as MYQ_DEVICE_STATE_ONLINE,
|
||||
KNOWN_MODELS,
|
||||
MANUFACTURER,
|
||||
)
|
||||
from pymyq.device import MyQDevice
|
||||
from pymyq.errors import InvalidCredentialsError, MyQError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
UpdateFailed,
|
||||
)
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY, PLATFORMS, UPDATE_INTERVAL
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DOMAIN = "myq"
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, _: ConfigEntry) -> bool:
|
||||
"""Set up MyQ from a config entry."""
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
conf = entry.data
|
||||
|
||||
try:
|
||||
myq = await pymyq.login(conf[CONF_USERNAME], conf[CONF_PASSWORD], websession)
|
||||
except InvalidCredentialsError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
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 InvalidCredentialsError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
except MyQError as err:
|
||||
raise UpdateFailed(str(err)) from err
|
||||
|
||||
coordinator = DataUpdateCoordinator(
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name="myq devices",
|
||||
update_method=async_update_data,
|
||||
update_interval=timedelta(seconds=UPDATE_INTERVAL),
|
||||
DOMAIN,
|
||||
DOMAIN,
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key="integration_removed",
|
||||
translation_placeholders={
|
||||
"blog": "https://www.home-assistant.io/blog/2023/11/06/removal-of-myq-integration/",
|
||||
"entries": "/config/integrations/integration/myQ",
|
||||
},
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = {MYQ_GATEWAY: myq, MYQ_COORDINATOR: coordinator}
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
if all(
|
||||
config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
for config_entry in hass.config_entries.async_entries(DOMAIN)
|
||||
if config_entry.entry_id != entry.entry_id
|
||||
):
|
||||
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class MyQEntity(CoordinatorEntity):
|
||||
"""Base class for MyQ Entities."""
|
||||
|
||||
def __init__(self, coordinator: DataUpdateCoordinator, device: MyQDevice) -> None:
|
||||
"""Initialize class."""
|
||||
super().__init__(coordinator)
|
||||
self._device = device
|
||||
self._attr_unique_id = device.device_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name if any, name can change if user changes it within MyQ."""
|
||||
return self._device.name
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return the device_info of the device."""
|
||||
model = (
|
||||
KNOWN_MODELS.get(self._device.device_id[2:4])
|
||||
if self._device.device_id is not None
|
||||
else None
|
||||
)
|
||||
via_device: tuple[str, str] | None = None
|
||||
if self._device.parent_device_id:
|
||||
via_device = (DOMAIN, self._device.parent_device_id)
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self._device.device_id)},
|
||||
manufacturer=MANUFACTURER,
|
||||
model=model,
|
||||
name=self._device.name,
|
||||
sw_version=self._device.firmware_version,
|
||||
via_device=via_device,
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return if the device is online."""
|
||||
# Not all devices report online so assume True if its missing
|
||||
return super().available and self._device.device_json[MYQ_DEVICE_STATE].get(
|
||||
MYQ_DEVICE_STATE_ONLINE, True
|
||||
)
|
||||
return True
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
"""Support for MyQ gateways."""
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import MyQEntity
|
||||
from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up mysq covers."""
|
||||
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
myq = data[MYQ_GATEWAY]
|
||||
coordinator = data[MYQ_COORDINATOR]
|
||||
|
||||
entities = []
|
||||
|
||||
for device in myq.gateways.values():
|
||||
entities.append(MyQBinarySensorEntity(coordinator, device))
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class MyQBinarySensorEntity(MyQEntity, BinarySensorEntity):
|
||||
"""Representation of a MyQ gateway."""
|
||||
|
||||
_attr_device_class = BinarySensorDeviceClass.CONNECTIVITY
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the garage door if any."""
|
||||
return f"{self._device.name} MyQ Gateway"
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return if the device is online."""
|
||||
return super().available
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Entity is always available."""
|
||||
return True
|
|
@ -1,101 +1,11 @@
|
|||
"""Config flow for MyQ integration."""
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import pymyq
|
||||
from pymyq.errors import InvalidCredentialsError, MyQError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
|
||||
)
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for MyQ."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Start a myq config flow."""
|
||||
self._reauth_unique_id = None
|
||||
|
||||
async def _async_validate_input(self, username, password):
|
||||
"""Validate the user input allows us to connect."""
|
||||
websession = aiohttp_client.async_get_clientsession(self.hass)
|
||||
try:
|
||||
await pymyq.login(username, password, websession, True)
|
||||
except InvalidCredentialsError:
|
||||
return {CONF_PASSWORD: "invalid_auth"}
|
||||
except MyQError:
|
||||
return {"base": "cannot_connect"}
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
return {"base": "unknown"}
|
||||
|
||||
return None
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
errors = await self._async_validate_input(
|
||||
user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
|
||||
)
|
||||
if not errors:
|
||||
await self.async_set_unique_id(user_input[CONF_USERNAME])
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_USERNAME], data=user_input
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
|
||||
"""Handle reauth."""
|
||||
self._reauth_unique_id = self.context["unique_id"]
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(self, user_input=None):
|
||||
"""Handle reauth input."""
|
||||
errors = {}
|
||||
existing_entry = await self.async_set_unique_id(self._reauth_unique_id)
|
||||
if user_input is not None:
|
||||
errors = await self._async_validate_input(
|
||||
existing_entry.data[CONF_USERNAME], user_input[CONF_PASSWORD]
|
||||
)
|
||||
if not errors:
|
||||
self.hass.config_entries.async_update_entry(
|
||||
existing_entry,
|
||||
data={
|
||||
**existing_entry.data,
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
},
|
||||
)
|
||||
await self.hass.config_entries.async_reload(existing_entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
return self.async_show_form(
|
||||
description_placeholders={
|
||||
CONF_USERNAME: existing_entry.data[CONF_USERNAME]
|
||||
},
|
||||
step_id="reauth_confirm",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
"""The MyQ integration."""
|
||||
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 pymyq.lamp import STATE_OFF as MYQ_LIGHT_STATE_OFF, STATE_ON as MYQ_LIGHT_STATE_ON
|
||||
|
||||
from homeassistant.const import (
|
||||
STATE_CLOSED,
|
||||
STATE_CLOSING,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_OPEN,
|
||||
STATE_OPENING,
|
||||
Platform,
|
||||
)
|
||||
|
||||
DOMAIN = "myq"
|
||||
|
||||
PLATFORMS = [Platform.COVER, Platform.BINARY_SENSOR, Platform.LIGHT]
|
||||
|
||||
MYQ_TO_HASS = {
|
||||
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_LIGHT_STATE_ON: STATE_ON,
|
||||
MYQ_LIGHT_STATE_OFF: STATE_OFF,
|
||||
}
|
||||
|
||||
MYQ_GATEWAY = "myq_gateway"
|
||||
MYQ_COORDINATOR = "coordinator"
|
||||
|
||||
UPDATE_INTERVAL = 30
|
|
@ -1,116 +0,0 @@
|
|||
"""Support for MyQ-Enabled Garage Doors."""
|
||||
from typing import Any
|
||||
|
||||
from pymyq.const import DEVICE_TYPE_GATE as MYQ_DEVICE_TYPE_GATE
|
||||
from pymyq.errors import MyQError
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
CoverDeviceClass,
|
||||
CoverEntity,
|
||||
CoverEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import MyQEntity
|
||||
from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY, MYQ_TO_HASS
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up mysq covers."""
|
||||
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
myq = data[MYQ_GATEWAY]
|
||||
coordinator = data[MYQ_COORDINATOR]
|
||||
|
||||
async_add_entities(
|
||||
[MyQCover(coordinator, device) for device in myq.covers.values()]
|
||||
)
|
||||
|
||||
|
||||
class MyQCover(MyQEntity, CoverEntity):
|
||||
"""Representation of a MyQ cover."""
|
||||
|
||||
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
||||
|
||||
def __init__(self, coordinator, device):
|
||||
"""Initialize with API object, device id."""
|
||||
super().__init__(coordinator, device)
|
||||
self._device = device
|
||||
if device.device_type == MYQ_DEVICE_TYPE_GATE:
|
||||
self._attr_device_class = CoverDeviceClass.GATE
|
||||
else:
|
||||
self._attr_device_class = CoverDeviceClass.GARAGE
|
||||
self._attr_unique_id = device.device_id
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
"""Return true if cover is closed, else False."""
|
||||
return MYQ_TO_HASS.get(self._device.state) == STATE_CLOSED
|
||||
|
||||
@property
|
||||
def is_closing(self) -> bool:
|
||||
"""Return if the cover is closing or not."""
|
||||
return MYQ_TO_HASS.get(self._device.state) == STATE_CLOSING
|
||||
|
||||
@property
|
||||
def is_open(self) -> bool:
|
||||
"""Return if the cover is opening or not."""
|
||||
return MYQ_TO_HASS.get(self._device.state) == STATE_OPEN
|
||||
|
||||
@property
|
||||
def is_opening(self) -> bool:
|
||||
"""Return if the cover is opening or not."""
|
||||
return MYQ_TO_HASS.get(self._device.state) == STATE_OPENING
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Issue close command to cover."""
|
||||
if self.is_closing or self.is_closed:
|
||||
return
|
||||
|
||||
try:
|
||||
wait_task = await self._device.close(wait_for_state=False)
|
||||
except MyQError as err:
|
||||
raise HomeAssistantError(
|
||||
f"Closing of cover {self._device.name} failed with error: {err}"
|
||||
) from err
|
||||
|
||||
# Write closing state to HASS
|
||||
self.async_write_ha_state()
|
||||
|
||||
result = wait_task if isinstance(wait_task, bool) else await wait_task
|
||||
|
||||
# Write final state to HASS
|
||||
self.async_write_ha_state()
|
||||
|
||||
if not result:
|
||||
raise HomeAssistantError(f"Closing of cover {self._device.name} failed")
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Issue open command to cover."""
|
||||
if self.is_opening or self.is_open:
|
||||
return
|
||||
|
||||
try:
|
||||
wait_task = await self._device.open(wait_for_state=False)
|
||||
except MyQError as err:
|
||||
raise HomeAssistantError(
|
||||
f"Opening of cover {self._device.name} failed with error: {err}"
|
||||
) from err
|
||||
|
||||
# Write opening state to HASS
|
||||
self.async_write_ha_state()
|
||||
|
||||
result = wait_task if isinstance(wait_task, bool) else await wait_task
|
||||
|
||||
# Write final state to HASS
|
||||
self.async_write_ha_state()
|
||||
|
||||
if not result:
|
||||
raise HomeAssistantError(f"Opening of cover {self._device.name} failed")
|
|
@ -1,76 +0,0 @@
|
|||
"""Support for MyQ-Enabled lights."""
|
||||
from typing import Any
|
||||
|
||||
from pymyq.errors import MyQError
|
||||
|
||||
from homeassistant.components.light import ColorMode, LightEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import MyQEntity
|
||||
from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY, MYQ_TO_HASS
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up myq lights."""
|
||||
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
myq = data[MYQ_GATEWAY]
|
||||
coordinator = data[MYQ_COORDINATOR]
|
||||
|
||||
async_add_entities(
|
||||
[MyQLight(coordinator, device) for device in myq.lamps.values()], True
|
||||
)
|
||||
|
||||
|
||||
class MyQLight(MyQEntity, LightEntity):
|
||||
"""Representation of a MyQ light."""
|
||||
|
||||
_attr_color_mode = ColorMode.ONOFF
|
||||
_attr_supported_color_modes = {ColorMode.ONOFF}
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the light is on, else False."""
|
||||
return MYQ_TO_HASS.get(self._device.state) == STATE_ON
|
||||
|
||||
@property
|
||||
def is_off(self):
|
||||
"""Return true if the light is off, else False."""
|
||||
return MYQ_TO_HASS.get(self._device.state) == STATE_OFF
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Issue on command to light."""
|
||||
if self.is_on:
|
||||
return
|
||||
|
||||
try:
|
||||
await self._device.turnon(wait_for_state=True)
|
||||
except MyQError as err:
|
||||
raise HomeAssistantError(
|
||||
f"Turning light {self._device.name} on failed with error: {err}"
|
||||
) from err
|
||||
|
||||
# Write new state to HASS
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Issue off command to light."""
|
||||
if self.is_off:
|
||||
return
|
||||
|
||||
try:
|
||||
await self._device.turnoff(wait_for_state=True)
|
||||
except MyQError as err:
|
||||
raise HomeAssistantError(
|
||||
f"Turning light {self._device.name} off failed with error: {err}"
|
||||
) from err
|
||||
|
||||
# Write new state to HASS
|
||||
self.async_write_ha_state()
|
|
@ -1,18 +1,9 @@
|
|||
{
|
||||
"domain": "myq",
|
||||
"name": "MyQ",
|
||||
"codeowners": ["@ehendrix23", "@Lash-L"],
|
||||
"config_flow": true,
|
||||
"dhcp": [
|
||||
{
|
||||
"macaddress": "645299*"
|
||||
}
|
||||
],
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/myq",
|
||||
"homekit": {
|
||||
"models": ["819LMB", "MYQ"]
|
||||
},
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pkce", "pymyq"],
|
||||
"requirements": ["python-myq==3.1.13"]
|
||||
"requirements": []
|
||||
}
|
||||
|
|
|
@ -1,29 +1,8 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Connect to the MyQ Gateway",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"description": "The password for {username} is no longer valid.",
|
||||
"title": "Reauthenticate your MyQ Account",
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
|
||||
"issues": {
|
||||
"integration_removed": {
|
||||
"title": "The MyQ integration has been removed",
|
||||
"description": "The MyQ integration has been removed from Home Assistant.\n\nMyQ has blocked all third-party integrations. Read about it [here]({blog}).\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing MyQ integration entries]({entries})."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -301,7 +301,6 @@ FLOWS = {
|
|||
"mqtt",
|
||||
"mullvad",
|
||||
"mutesync",
|
||||
"myq",
|
||||
"mysensors",
|
||||
"mystrom",
|
||||
"nam",
|
||||
|
|
|
@ -316,10 +316,6 @@ DHCP: list[dict[str, str | bool]] = [
|
|||
"domain": "motion_blinds",
|
||||
"hostname": "connector_*",
|
||||
},
|
||||
{
|
||||
"domain": "myq",
|
||||
"macaddress": "645299*",
|
||||
},
|
||||
{
|
||||
"domain": "nest",
|
||||
"macaddress": "18B430*",
|
||||
|
|
|
@ -3625,12 +3625,6 @@
|
|||
"config_flow": false,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"myq": {
|
||||
"name": "MyQ",
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"mysensors": {
|
||||
"name": "MySensors",
|
||||
"integration_type": "hub",
|
||||
|
|
|
@ -20,10 +20,6 @@ HOMEKIT = {
|
|||
"always_discover": True,
|
||||
"domain": "roku",
|
||||
},
|
||||
"819LMB": {
|
||||
"always_discover": True,
|
||||
"domain": "myq",
|
||||
},
|
||||
"AC02": {
|
||||
"always_discover": True,
|
||||
"domain": "tado",
|
||||
|
@ -144,10 +140,6 @@ HOMEKIT = {
|
|||
"always_discover": True,
|
||||
"domain": "lifx",
|
||||
},
|
||||
"MYQ": {
|
||||
"always_discover": True,
|
||||
"domain": "myq",
|
||||
},
|
||||
"NL29": {
|
||||
"always_discover": False,
|
||||
"domain": "nanoleaf",
|
||||
|
|
|
@ -528,8 +528,6 @@ filterwarnings = [
|
|||
"ignore:datetime.*utcfromtimestamp\\(\\) is deprecated:DeprecationWarning:proto.datetime_helpers",
|
||||
# https://github.com/MatsNl/pyatag/issues/11 - v0.3.7.1
|
||||
"ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:pyatag.gateway",
|
||||
# https://github.com/Python-MyQ/Python-MyQ - v3.1.13
|
||||
"ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:pymyq.(api|account)",
|
||||
# Wrong stacklevel
|
||||
# https://bugs.launchpad.net/beautifulsoup/+bug/2034451
|
||||
"ignore:It looks like you're parsing an XML document using an HTML parser:UserWarning:bs4.builder",
|
||||
|
|
|
@ -2161,9 +2161,6 @@ python-miio==0.5.12
|
|||
# homeassistant.components.mpd
|
||||
python-mpd2==3.0.5
|
||||
|
||||
# homeassistant.components.myq
|
||||
python-myq==3.1.13
|
||||
|
||||
# homeassistant.components.mystrom
|
||||
python-mystrom==2.2.0
|
||||
|
||||
|
|
|
@ -1611,9 +1611,6 @@ python-matter-server==4.0.0
|
|||
# homeassistant.components.xiaomi_miio
|
||||
python-miio==0.5.12
|
||||
|
||||
# homeassistant.components.myq
|
||||
python-myq==3.1.13
|
||||
|
||||
# homeassistant.components.mystrom
|
||||
python-mystrom==2.2.0
|
||||
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
{
|
||||
"count": 6,
|
||||
"href": "http://api.myqdevice.com/api/v5/accounts/account_id/devices",
|
||||
"items": [
|
||||
{
|
||||
"device_type": "ethernetgateway",
|
||||
"created_date": "2020-02-10T22:54:58.423",
|
||||
"href": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gateway_serial",
|
||||
"device_family": "gateway",
|
||||
"name": "Happy place",
|
||||
"device_platform": "myq",
|
||||
"state": {
|
||||
"homekit_enabled": false,
|
||||
"pending_bootload_abandoned": false,
|
||||
"online": true,
|
||||
"last_status": "2020-03-30T02:49:46.4121303Z",
|
||||
"physical_devices": [],
|
||||
"firmware_version": "1.6",
|
||||
"learn_mode": false,
|
||||
"learn": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gateway_serial/learn",
|
||||
"homekit_capable": false,
|
||||
"updated_date": "2020-03-30T02:49:46.4171299Z"
|
||||
},
|
||||
"serial_number": "gateway_serial"
|
||||
},
|
||||
{
|
||||
"serial_number": "gate_serial",
|
||||
"state": {
|
||||
"report_ajar": false,
|
||||
"aux_relay_delay": "00:00:00",
|
||||
"is_unattended_close_allowed": true,
|
||||
"door_ajar_interval": "00:00:00",
|
||||
"aux_relay_behavior": "None",
|
||||
"last_status": "2020-03-30T02:47:40.2794038Z",
|
||||
"online": true,
|
||||
"rex_fires_door": false,
|
||||
"close": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gate_serial/close",
|
||||
"invalid_shutout_period": "00:00:00",
|
||||
"invalid_credential_window": "00:00:00",
|
||||
"use_aux_relay": false,
|
||||
"command_channel_report_status": false,
|
||||
"last_update": "2020-03-28T23:07:39.5611776Z",
|
||||
"door_state": "closed",
|
||||
"max_invalid_attempts": 0,
|
||||
"open": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gate_serial/open",
|
||||
"passthrough_interval": "00:00:00",
|
||||
"control_from_browser": false,
|
||||
"report_forced": false,
|
||||
"is_unattended_open_allowed": true
|
||||
},
|
||||
"parent_device_id": "gateway_serial",
|
||||
"name": "Gate",
|
||||
"device_platform": "myq",
|
||||
"device_family": "garagedoor",
|
||||
"parent_device": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gateway_serial",
|
||||
"href": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gate_serial",
|
||||
"device_type": "gate",
|
||||
"created_date": "2020-02-10T22:54:58.423"
|
||||
},
|
||||
{
|
||||
"parent_device": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gateway_serial",
|
||||
"href": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/large_garage_serial",
|
||||
"device_type": "wifigaragedooropener",
|
||||
"created_date": "2020-02-10T22:55:25.863",
|
||||
"device_platform": "myq",
|
||||
"name": "Large Garage Door",
|
||||
"device_family": "garagedoor",
|
||||
"serial_number": "large_garage_serial",
|
||||
"state": {
|
||||
"report_forced": false,
|
||||
"is_unattended_open_allowed": true,
|
||||
"passthrough_interval": "00:00:00",
|
||||
"control_from_browser": false,
|
||||
"attached_work_light_error_present": false,
|
||||
"max_invalid_attempts": 0,
|
||||
"open": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/large_garage_serial/open",
|
||||
"command_channel_report_status": false,
|
||||
"last_update": "2020-03-28T23:58:55.5906643Z",
|
||||
"door_state": "closed",
|
||||
"invalid_shutout_period": "00:00:00",
|
||||
"use_aux_relay": false,
|
||||
"invalid_credential_window": "00:00:00",
|
||||
"rex_fires_door": false,
|
||||
"close": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/large_garage_serial/close",
|
||||
"online": true,
|
||||
"last_status": "2020-03-30T02:49:46.4121303Z",
|
||||
"aux_relay_behavior": "None",
|
||||
"door_ajar_interval": "00:00:00",
|
||||
"gdo_lock_connected": false,
|
||||
"report_ajar": false,
|
||||
"aux_relay_delay": "00:00:00",
|
||||
"is_unattended_close_allowed": true
|
||||
},
|
||||
"parent_device_id": "gateway_serial"
|
||||
},
|
||||
{
|
||||
"serial_number": "small_garage_serial",
|
||||
"state": {
|
||||
"last_status": "2020-03-30T02:48:45.7501595Z",
|
||||
"online": true,
|
||||
"report_ajar": false,
|
||||
"aux_relay_delay": "00:00:00",
|
||||
"is_unattended_close_allowed": true,
|
||||
"gdo_lock_connected": false,
|
||||
"door_ajar_interval": "00:00:00",
|
||||
"aux_relay_behavior": "None",
|
||||
"attached_work_light_error_present": false,
|
||||
"control_from_browser": false,
|
||||
"passthrough_interval": "00:00:00",
|
||||
"is_unattended_open_allowed": true,
|
||||
"report_forced": false,
|
||||
"close": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/small_garage_serial/close",
|
||||
"rex_fires_door": false,
|
||||
"invalid_credential_window": "00:00:00",
|
||||
"use_aux_relay": false,
|
||||
"invalid_shutout_period": "00:00:00",
|
||||
"door_state": "closed",
|
||||
"last_update": "2020-03-26T15:45:31.4713796Z",
|
||||
"command_channel_report_status": false,
|
||||
"open": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/small_garage_serial/open",
|
||||
"max_invalid_attempts": 0
|
||||
},
|
||||
"parent_device_id": "gateway_serial",
|
||||
"device_platform": "myq",
|
||||
"name": "Small Garage Door",
|
||||
"device_family": "garagedoor",
|
||||
"parent_device": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/gateway_serial",
|
||||
"href": "http://api.myqdevice.com/api/v5/accounts/account_id/devices/small_garage_serial",
|
||||
"device_type": "wifigaragedooropener",
|
||||
"created_date": "2020-02-10T23:11:47.487"
|
||||
},
|
||||
{
|
||||
"serial_number": "garage_light_off",
|
||||
"state": {
|
||||
"last_status": "2020-03-30T02:48:45.7501595Z",
|
||||
"online": true,
|
||||
"lamp_state": "off",
|
||||
"last_update": "2020-03-26T15:45:31.4713796Z"
|
||||
},
|
||||
"parent_device_id": "gateway_serial",
|
||||
"device_platform": "myq",
|
||||
"name": "Garage Door Light Off",
|
||||
"device_family": "lamp",
|
||||
"device_type": "lamp",
|
||||
"created_date": "2020-02-10T23:11:47.487"
|
||||
},
|
||||
{
|
||||
"serial_number": "garage_light_on",
|
||||
"state": {
|
||||
"last_status": "2020-03-30T02:48:45.7501595Z",
|
||||
"online": true,
|
||||
"lamp_state": "on",
|
||||
"last_update": "2020-03-26T15:45:31.4713796Z"
|
||||
},
|
||||
"parent_device_id": "gateway_serial",
|
||||
"device_platform": "myq",
|
||||
"name": "Garage Door Light On",
|
||||
"device_family": "lamp",
|
||||
"device_type": "lamp",
|
||||
"created_date": "2020-02-10T23:11:47.487"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
"""The scene tests for the myq platform."""
|
||||
from homeassistant.const import STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
|
||||
async def test_create_binary_sensors(hass: HomeAssistant) -> None:
|
||||
"""Test creation of binary_sensors."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
state = hass.states.get("binary_sensor.happy_place_myq_gateway")
|
||||
assert state.state == STATE_ON
|
||||
expected_attributes = {"device_class": "connectivity"}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
|
@ -1,166 +0,0 @@
|
|||
"""Test the MyQ config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from pymyq.errors import InvalidCredentialsError, MyQError
|
||||
|
||||
from homeassistant import config_entries
|
||||
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
|
||||
|
||||
|
||||
async def test_form_user(hass: HomeAssistant) -> None:
|
||||
"""Test we get the user form."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.myq.config_flow.pymyq.login",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.components.myq.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"username": "test-username", "password": "test-password"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == "create_entry"
|
||||
assert result2["title"] == "test-username"
|
||||
assert result2["data"] == {
|
||||
"username": "test-username",
|
||||
"password": "test-password",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
||||
"""Test we handle invalid auth."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.myq.config_flow.pymyq.login",
|
||||
side_effect=InvalidCredentialsError,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"username": "test-username", "password": "test-password"},
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"password": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.myq.config_flow.pymyq.login",
|
||||
side_effect=MyQError,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"username": "test-username", "password": "test-password"},
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_form_unknown_exception(hass: HomeAssistant) -> None:
|
||||
"""Test we handle unknown exceptions."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.myq.config_flow.pymyq.login",
|
||||
side_effect=Exception,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"username": "test-username", "password": "test-password"},
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "unknown"}
|
||||
|
||||
|
||||
async def test_reauth(hass: HomeAssistant) -> None:
|
||||
"""Test we can reauth."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_USERNAME: "test@test.org",
|
||||
CONF_PASSWORD: "secret",
|
||||
},
|
||||
unique_id="test@test.org",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_REAUTH, "unique_id": "test@test.org"},
|
||||
)
|
||||
|
||||
assert result["type"] == "form"
|
||||
assert result["step_id"] == "reauth_confirm"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.myq.config_flow.pymyq.login",
|
||||
side_effect=InvalidCredentialsError,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_PASSWORD: "test-password",
|
||||
},
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"password": "invalid_auth"}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.myq.config_flow.pymyq.login",
|
||||
side_effect=MyQError,
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
{
|
||||
CONF_PASSWORD: "test-password",
|
||||
},
|
||||
)
|
||||
|
||||
assert result3["type"] == "form"
|
||||
assert result3["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.myq.config_flow.pymyq.login",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.components.myq.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
{
|
||||
CONF_PASSWORD: "test-password",
|
||||
},
|
||||
)
|
||||
|
||||
assert mock_setup_entry.called
|
||||
assert result4["type"] == "abort"
|
||||
assert result4["reason"] == "reauth_successful"
|
|
@ -1,50 +0,0 @@
|
|||
"""The scene tests for the myq platform."""
|
||||
from homeassistant.const import STATE_CLOSED
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
|
||||
async def test_create_covers(hass: HomeAssistant) -> None:
|
||||
"""Test creation of covers."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
state = hass.states.get("cover.large_garage_door")
|
||||
assert state.state == STATE_CLOSED
|
||||
expected_attributes = {
|
||||
"device_class": "garage",
|
||||
"friendly_name": "Large Garage Door",
|
||||
"supported_features": 3,
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("cover.small_garage_door")
|
||||
assert state.state == STATE_CLOSED
|
||||
expected_attributes = {
|
||||
"device_class": "garage",
|
||||
"friendly_name": "Small Garage Door",
|
||||
"supported_features": 3,
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("cover.gate")
|
||||
assert state.state == STATE_CLOSED
|
||||
expected_attributes = {
|
||||
"device_class": "gate",
|
||||
"friendly_name": "Gate",
|
||||
"supported_features": 3,
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
|
@ -0,0 +1,50 @@
|
|||
"""Tests for the MyQ Connected Services integration."""
|
||||
|
||||
from homeassistant.components.myq import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_myq_repair_issue(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
||||
) -> None:
|
||||
"""Test the MyQ configuration entry loading/unloading handles the repair."""
|
||||
config_entry_1 = MockConfigEntry(
|
||||
title="Example 1",
|
||||
domain=DOMAIN,
|
||||
)
|
||||
config_entry_1.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry_1.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry_1.state is ConfigEntryState.LOADED
|
||||
|
||||
# Add a second one
|
||||
config_entry_2 = MockConfigEntry(
|
||||
title="Example 2",
|
||||
domain=DOMAIN,
|
||||
)
|
||||
config_entry_2.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry_2.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_2.state is ConfigEntryState.LOADED
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
|
||||
|
||||
# Remove the first one
|
||||
await hass.config_entries.async_remove(config_entry_1.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_1.state is ConfigEntryState.NOT_LOADED
|
||||
assert config_entry_2.state is ConfigEntryState.LOADED
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
|
||||
|
||||
# Remove the second one
|
||||
await hass.config_entries.async_remove(config_entry_2.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_1.state is ConfigEntryState.NOT_LOADED
|
||||
assert config_entry_2.state is ConfigEntryState.NOT_LOADED
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN) is None
|
|
@ -1,39 +0,0 @@
|
|||
"""The scene tests for the myq platform."""
|
||||
from homeassistant.components.light import ColorMode
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .util import async_init_integration
|
||||
|
||||
|
||||
async def test_create_lights(hass: HomeAssistant) -> None:
|
||||
"""Test creation of lights."""
|
||||
|
||||
await async_init_integration(hass)
|
||||
|
||||
state = hass.states.get("light.garage_door_light_off")
|
||||
assert state.state == STATE_OFF
|
||||
expected_attributes = {
|
||||
"friendly_name": "Garage Door Light Off",
|
||||
"supported_features": 0,
|
||||
"supported_color_modes": [ColorMode.ONOFF],
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
||||
|
||||
state = hass.states.get("light.garage_door_light_on")
|
||||
assert state.state == STATE_ON
|
||||
expected_attributes = {
|
||||
"friendly_name": "Garage Door Light On",
|
||||
"supported_features": 0,
|
||||
"supported_color_modes": [ColorMode.ONOFF],
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
|
||||
assert all(
|
||||
state.attributes[key] == expected_attributes[key] for key in expected_attributes
|
||||
)
|
|
@ -1,54 +0,0 @@
|
|||
"""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,
|
||||
skip_setup: bool = False,
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the myq integration in Home Assistant."""
|
||||
|
||||
devices_fixture = "myq/devices.json"
|
||||
devices_json = load_fixture(devices_fixture)
|
||||
devices_dict = json.loads(devices_json)
|
||||
|
||||
def _handle_mock_api_oauth_authenticate():
|
||||
return 1234, 1800
|
||||
|
||||
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"}
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
if not skip_setup:
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return entry
|
Loading…
Reference in New Issue