mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add new integration for WMS WebControl pro using local API (#124176)
* Add new integration for WMS WebControl pro using local API Warema recently released a new local API for their WMS hub called "WebControl pro". This integration makes use of the new local API via a new dedicated Python library pywmspro. For now this integration only supports awnings as covers. But pywmspro is device-agnostic to ease future extensions. * Incorporated review feedback from joostlek Thanks a lot! * Incorporated more review feedback from joostlek Thanks a lot! * Incorporated more review feedback from joostlek Thanks a lot! * Fix * Follow-up fix * Improve handling of DHCP discovery * Further test improvements suggested by joostlek, thanks! --------- Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
parent
4fbc5a9558
commit
587ebd5d47
@ -1668,6 +1668,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/wiz/ @sbidy
|
||||
/homeassistant/components/wled/ @frenck
|
||||
/tests/components/wled/ @frenck
|
||||
/homeassistant/components/wmspro/ @mback2k
|
||||
/tests/components/wmspro/ @mback2k
|
||||
/homeassistant/components/wolflink/ @adamkrol93 @mtielen
|
||||
/tests/components/wolflink/ @adamkrol93 @mtielen
|
||||
/homeassistant/components/workday/ @fabaff @gjohansson-ST
|
||||
|
66
homeassistant/components/wmspro/__init__.py
Normal file
66
homeassistant/components/wmspro/__init__.py
Normal file
@ -0,0 +1,66 @@
|
||||
"""The WMS WebControl pro API integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import aiohttp
|
||||
from wmspro.webcontrol import WebControlPro
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.typing import UNDEFINED
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.COVER]
|
||||
|
||||
type WebControlProConfigEntry = ConfigEntry[WebControlPro]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: WebControlProConfigEntry
|
||||
) -> bool:
|
||||
"""Set up wmspro from a config entry."""
|
||||
host = entry.data[CONF_HOST]
|
||||
session = async_get_clientsession(hass)
|
||||
hub = WebControlPro(host, session)
|
||||
|
||||
try:
|
||||
await hub.ping()
|
||||
except aiohttp.ClientError as err:
|
||||
raise ConfigEntryNotReady(f"Error while connecting to {host}") from err
|
||||
|
||||
entry.runtime_data = hub
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, entry.unique_id)}
|
||||
if entry.unique_id
|
||||
else UNDEFINED,
|
||||
identifiers={(DOMAIN, entry.entry_id)},
|
||||
manufacturer=MANUFACTURER,
|
||||
model="WMS WebControl pro",
|
||||
configuration_url=f"http://{hub.host}/system",
|
||||
)
|
||||
|
||||
try:
|
||||
await hub.refresh()
|
||||
for dest in hub.dests.values():
|
||||
await dest.refresh()
|
||||
except aiohttp.ClientError as err:
|
||||
raise ConfigEntryNotReady(f"Error while refreshing from {host}") from err
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: WebControlProConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
89
homeassistant/components/wmspro/config_flow.py
Normal file
89
homeassistant/components/wmspro/config_flow.py
Normal file
@ -0,0 +1,89 @@
|
||||
"""Config flow for WMS WebControl pro API integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
from wmspro.webcontrol import WebControlPro
|
||||
|
||||
from homeassistant.components import dhcp
|
||||
from homeassistant.components.dhcp import DhcpServiceInfo
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
|
||||
from .const import DOMAIN, SUGGESTED_HOST
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class WebControlProConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for wmspro."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_dhcp(
|
||||
self, discovery_info: dhcp.DhcpServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the DHCP discovery step."""
|
||||
unique_id = format_mac(discovery_info.macaddress)
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
||||
if not entry.unique_id and entry.data[CONF_HOST] in (
|
||||
discovery_info.hostname,
|
||||
discovery_info.ip,
|
||||
):
|
||||
self.hass.config_entries.async_update_entry(entry, unique_id=unique_id)
|
||||
return self.async_abort(reason="already_configured")
|
||||
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the user-based step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
self._async_abort_entries_match(user_input)
|
||||
host = user_input[CONF_HOST]
|
||||
session = async_get_clientsession(self.hass)
|
||||
hub = WebControlPro(host, session)
|
||||
try:
|
||||
pong = await hub.ping()
|
||||
except aiohttp.ClientError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
if not pong:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
return self.async_create_entry(title=host, data=user_input)
|
||||
|
||||
if self.source == dhcp.DOMAIN:
|
||||
discovery_info: DhcpServiceInfo = self.init_data
|
||||
data_values = {CONF_HOST: discovery_info.hostname or discovery_info.ip}
|
||||
else:
|
||||
data_values = {CONF_HOST: SUGGESTED_HOST}
|
||||
|
||||
self.context["title_placeholders"] = data_values
|
||||
data_schema = self.add_suggested_values_to_schema(
|
||||
STEP_USER_DATA_SCHEMA, data_values
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=data_schema, errors=errors
|
||||
)
|
7
homeassistant/components/wmspro/const.py
Normal file
7
homeassistant/components/wmspro/const.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""Constants for the WMS WebControl pro API integration."""
|
||||
|
||||
DOMAIN = "wmspro"
|
||||
SUGGESTED_HOST = "webcontrol"
|
||||
|
||||
ATTRIBUTION = "Data provided by WMS WebControl pro API"
|
||||
MANUFACTURER = "WAREMA Renkhoff SE"
|
77
homeassistant/components/wmspro/cover.py
Normal file
77
homeassistant/components/wmspro/cover.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""Support for covers connected with WMS WebControl pro."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from wmspro.const import (
|
||||
WMS_WebControl_pro_API_actionDescription,
|
||||
WMS_WebControl_pro_API_actionType,
|
||||
)
|
||||
|
||||
from homeassistant.components.cover import ATTR_POSITION, CoverDeviceClass, CoverEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import WebControlProConfigEntry
|
||||
from .entity import WebControlProGenericEntity
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: WebControlProConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the WMS based covers from a config entry."""
|
||||
hub = config_entry.runtime_data
|
||||
|
||||
entities: list[WebControlProGenericEntity] = []
|
||||
for dest in hub.dests.values():
|
||||
if dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive):
|
||||
entities.append(WebControlProAwning(config_entry.entry_id, dest)) # noqa: PERF401
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class WebControlProAwning(WebControlProGenericEntity, CoverEntity):
|
||||
"""Representation of a WMS based awning."""
|
||||
|
||||
_attr_device_class = CoverDeviceClass.AWNING
|
||||
|
||||
@property
|
||||
def current_cover_position(self) -> int | None:
|
||||
"""Return current position of cover."""
|
||||
action = self._dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive)
|
||||
return action["percentage"]
|
||||
|
||||
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
||||
"""Move the cover to a specific position."""
|
||||
action = self._dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive)
|
||||
await action(percentage=kwargs[ATTR_POSITION])
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Return if the cover is closed."""
|
||||
return self.current_cover_position == 0
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Open the cover."""
|
||||
action = self._dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive)
|
||||
await action(percentage=100)
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Close the cover."""
|
||||
action = self._dest.action(WMS_WebControl_pro_API_actionDescription.AwningDrive)
|
||||
await action(percentage=0)
|
||||
|
||||
async def async_stop_cover(self, **kwargs: Any) -> None:
|
||||
"""Stop the device if in motion."""
|
||||
action = self._dest.action(
|
||||
WMS_WebControl_pro_API_actionDescription.ManualCommand,
|
||||
WMS_WebControl_pro_API_actionType.Stop,
|
||||
)
|
||||
await action()
|
43
homeassistant/components/wmspro/entity.py
Normal file
43
homeassistant/components/wmspro/entity.py
Normal file
@ -0,0 +1,43 @@
|
||||
"""Generic entity for the WMS WebControl pro API integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from wmspro.destination import Destination
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import ATTRIBUTION, DOMAIN, MANUFACTURER
|
||||
|
||||
|
||||
class WebControlProGenericEntity(Entity):
|
||||
"""Foundation of all WMS based entities."""
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, config_entry_id: str, dest: Destination) -> None:
|
||||
"""Initialize the entity with destination channel."""
|
||||
dest_id_str = str(dest.id)
|
||||
self._dest = dest
|
||||
self._attr_unique_id = dest_id_str
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, dest_id_str)},
|
||||
manufacturer=MANUFACTURER,
|
||||
model=dest.animationType.name,
|
||||
name=dest.name,
|
||||
serial_number=dest_id_str,
|
||||
suggested_area=dest.room.name,
|
||||
via_device=(DOMAIN, config_entry_id),
|
||||
configuration_url=f"http://{dest.host}/control",
|
||||
)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the entity."""
|
||||
await self._dest.refresh()
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return self._dest.available
|
19
homeassistant/components/wmspro/manifest.json
Normal file
19
homeassistant/components/wmspro/manifest.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"domain": "wmspro",
|
||||
"name": "WMS WebControl pro",
|
||||
"codeowners": ["@mback2k"],
|
||||
"config_flow": true,
|
||||
"dependencies": [],
|
||||
"dhcp": [
|
||||
{
|
||||
"macaddress": "0023D5*"
|
||||
},
|
||||
{
|
||||
"registered_devices": true
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/wmspro",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["pywmspro==0.1.0"]
|
||||
}
|
25
homeassistant/components/wmspro/strings.json
Normal file
25
homeassistant/components/wmspro/strings.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"config": {
|
||||
"flow_title": "{host}",
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "The hostname or IP address of your WMS WebControl pro."
|
||||
}
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
}
|
||||
}
|
||||
}
|
@ -670,6 +670,7 @@ FLOWS = {
|
||||
"withings",
|
||||
"wiz",
|
||||
"wled",
|
||||
"wmspro",
|
||||
"wolflink",
|
||||
"workday",
|
||||
"worldclock",
|
||||
|
@ -1089,6 +1089,14 @@ DHCP: Final[list[dict[str, str | bool]]] = [
|
||||
"domain": "wiz",
|
||||
"hostname": "wiz_*",
|
||||
},
|
||||
{
|
||||
"domain": "wmspro",
|
||||
"macaddress": "0023D5*",
|
||||
},
|
||||
{
|
||||
"domain": "wmspro",
|
||||
"registered_devices": True,
|
||||
},
|
||||
{
|
||||
"domain": "yale",
|
||||
"hostname": "yale-connect-plus",
|
||||
|
@ -6942,6 +6942,12 @@
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"wmspro": {
|
||||
"name": "WMS WebControl pro",
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"wolflink": {
|
||||
"name": "Wolf SmartSet Service",
|
||||
"integration_type": "hub",
|
||||
|
@ -2470,6 +2470,9 @@ pywilight==0.0.74
|
||||
# homeassistant.components.wiz
|
||||
pywizlight==0.5.14
|
||||
|
||||
# homeassistant.components.wmspro
|
||||
pywmspro==0.1.0
|
||||
|
||||
# homeassistant.components.ws66i
|
||||
pyws66i==1.1
|
||||
|
||||
|
@ -1970,6 +1970,9 @@ pywilight==0.0.74
|
||||
# homeassistant.components.wiz
|
||||
pywizlight==0.5.14
|
||||
|
||||
# homeassistant.components.wmspro
|
||||
pywmspro==0.1.0
|
||||
|
||||
# homeassistant.components.ws66i
|
||||
pyws66i==1.1
|
||||
|
||||
|
16
tests/components/wmspro/__init__.py
Normal file
16
tests/components/wmspro/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""Tests for the wmspro integration."""
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def setup_config_entry(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> bool:
|
||||
"""Set up a config entry."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
return result
|
106
tests/components/wmspro/conftest.py
Normal file
106
tests/components/wmspro/conftest.py
Normal file
@ -0,0 +1,106 @@
|
||||
"""Common fixtures for the wmspro tests."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.wmspro.const import DOMAIN
|
||||
from homeassistant.const import CONF_HOST
|
||||
|
||||
from tests.common import MockConfigEntry, load_json_object_fixture
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Return a dummy config entry."""
|
||||
return MockConfigEntry(
|
||||
title="WebControl",
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: "webcontrol"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.wmspro.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_hub_ping() -> Generator[AsyncMock]:
|
||||
"""Override WebControlPro.ping."""
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.ping",
|
||||
return_value=True,
|
||||
) as mock_hub_ping:
|
||||
yield mock_hub_ping
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_hub_refresh() -> Generator[AsyncMock]:
|
||||
"""Override WebControlPro.refresh."""
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.refresh",
|
||||
return_value=True,
|
||||
) as mock_hub_refresh:
|
||||
yield mock_hub_refresh
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_hub_configuration_test() -> Generator[AsyncMock]:
|
||||
"""Override WebControlPro.configuration."""
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro._getConfiguration",
|
||||
return_value=load_json_object_fixture("example_config_test.json", DOMAIN),
|
||||
) as mock_hub_configuration:
|
||||
yield mock_hub_configuration
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_hub_configuration_prod() -> Generator[AsyncMock]:
|
||||
"""Override WebControlPro._getConfiguration."""
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro._getConfiguration",
|
||||
return_value=load_json_object_fixture("example_config_prod.json", DOMAIN),
|
||||
) as mock_hub_configuration:
|
||||
yield mock_hub_configuration
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_hub_status_prod_awning() -> Generator[AsyncMock]:
|
||||
"""Override WebControlPro._getStatus."""
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro._getStatus",
|
||||
return_value=load_json_object_fixture(
|
||||
"example_status_prod_awning.json", DOMAIN
|
||||
),
|
||||
) as mock_dest_refresh:
|
||||
yield mock_dest_refresh
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_dest_refresh() -> Generator[AsyncMock]:
|
||||
"""Override Destination.refresh."""
|
||||
with patch(
|
||||
"wmspro.destination.Destination.refresh",
|
||||
return_value=True,
|
||||
) as mock_dest_refresh:
|
||||
yield mock_dest_refresh
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_action_call() -> Generator[AsyncMock]:
|
||||
"""Override Action.__call__."""
|
||||
|
||||
async def fake_call(self, **kwargs):
|
||||
self._update_params(kwargs)
|
||||
|
||||
with patch(
|
||||
"wmspro.action.Action.__call__",
|
||||
fake_call,
|
||||
) as mock_action_call:
|
||||
yield mock_action_call
|
77
tests/components/wmspro/fixtures/example_config_prod.json
Normal file
77
tests/components/wmspro/fixtures/example_config_prod.json
Normal file
@ -0,0 +1,77 @@
|
||||
{
|
||||
"command": "getConfiguration",
|
||||
"protocolVersion": "1.0.0",
|
||||
"destinations": [
|
||||
{
|
||||
"id": 58717,
|
||||
"animationType": 1,
|
||||
"names": ["Markise", "", "", ""],
|
||||
"actions": [
|
||||
{
|
||||
"id": 0,
|
||||
"actionType": 0,
|
||||
"actionDescription": 0,
|
||||
"minValue": 0,
|
||||
"maxValue": 100
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"actionType": 6,
|
||||
"actionDescription": 12
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"actionType": 8,
|
||||
"actionDescription": 13
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 97358,
|
||||
"animationType": 6,
|
||||
"names": ["Licht", "", "", ""],
|
||||
"actions": [
|
||||
{
|
||||
"id": 0,
|
||||
"actionType": 0,
|
||||
"actionDescription": 8,
|
||||
"minValue": 0,
|
||||
"maxValue": 100
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"actionType": 6,
|
||||
"actionDescription": 12
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"actionType": 4,
|
||||
"actionDescription": 6
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"actionType": 8,
|
||||
"actionDescription": 13
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rooms": [
|
||||
{
|
||||
"id": 19239,
|
||||
"name": "Terrasse",
|
||||
"destinations": [58717, 97358],
|
||||
"scenes": [687471, 765095]
|
||||
}
|
||||
],
|
||||
"scenes": [
|
||||
{
|
||||
"id": 687471,
|
||||
"names": ["Licht an", "", "", ""]
|
||||
},
|
||||
{
|
||||
"id": 765095,
|
||||
"names": ["Licht aus", "", "", ""]
|
||||
}
|
||||
]
|
||||
}
|
75
tests/components/wmspro/fixtures/example_config_test.json
Normal file
75
tests/components/wmspro/fixtures/example_config_test.json
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"command": "getConfiguration",
|
||||
"protocolVersion": "1.0.0",
|
||||
"destinations": [
|
||||
{
|
||||
"id": 17776,
|
||||
"animationType": 0,
|
||||
"names": ["Küche", "", "", ""],
|
||||
"actions": [
|
||||
{
|
||||
"id": 0,
|
||||
"actionType": 0,
|
||||
"actionDescription": 2,
|
||||
"minValue": 0,
|
||||
"maxValue": 100
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"actionType": 2,
|
||||
"actionDescription": 3,
|
||||
"minValue": -127,
|
||||
"maxValue": 127
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"actionType": 6,
|
||||
"actionDescription": 12
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"actionType": 8,
|
||||
"actionDescription": 13
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"actionType": 7,
|
||||
"actionDescription": 12
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 200951,
|
||||
"animationType": 999,
|
||||
"names": ["Aktor Potentialfrei", "", "", ""],
|
||||
"actions": [
|
||||
{
|
||||
"id": 22,
|
||||
"actionType": 8,
|
||||
"actionDescription": 13
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"actionType": 9,
|
||||
"actionDescription": 999,
|
||||
"minValue": 0,
|
||||
"maxValue": 16
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"rooms": [
|
||||
{
|
||||
"id": 42581,
|
||||
"name": "Raum 0",
|
||||
"destinations": [17776, 116682, 194367, 200951],
|
||||
"scenes": [688966]
|
||||
}
|
||||
],
|
||||
"scenes": [
|
||||
{
|
||||
"id": 688966,
|
||||
"names": ["Gute Nacht", "", "", ""]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
{
|
||||
"command": "getStatus",
|
||||
"protocolVersion": "1.0.0",
|
||||
"details": [
|
||||
{
|
||||
"destinationId": 58717,
|
||||
"data": {
|
||||
"drivingCause": 0,
|
||||
"heartbeatError": false,
|
||||
"blocking": false,
|
||||
"productData": [
|
||||
{
|
||||
"actionId": 0,
|
||||
"value": {
|
||||
"percentage": 100
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
50
tests/components/wmspro/snapshots/test_cover.ambr
Normal file
50
tests/components/wmspro/snapshots/test_cover.ambr
Normal file
@ -0,0 +1,50 @@
|
||||
# serializer version: 1
|
||||
# name: test_cover_device
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': 'terrasse',
|
||||
'config_entries': <ANY>,
|
||||
'configuration_url': 'http://webcontrol/control',
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': None,
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'wmspro',
|
||||
'58717',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'WAREMA Renkhoff SE',
|
||||
'model': 'Awning',
|
||||
'model_id': None,
|
||||
'name': 'Markise',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': '58717',
|
||||
'suggested_area': 'Terrasse',
|
||||
'sw_version': None,
|
||||
'via_device_id': <ANY>,
|
||||
})
|
||||
# ---
|
||||
# name: test_cover_update
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by WMS WebControl pro API',
|
||||
'current_position': 100,
|
||||
'device_class': 'awning',
|
||||
'friendly_name': 'Markise',
|
||||
'supported_features': <CoverEntityFeature: 15>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'cover.markise',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'open',
|
||||
})
|
||||
# ---
|
235
tests/components/wmspro/test_config_flow.py
Normal file
235
tests/components/wmspro/test_config_flow.py
Normal file
@ -0,0 +1,235 @@
|
||||
"""Test the wmspro config flow."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import aiohttp
|
||||
|
||||
from homeassistant.components.dhcp import DhcpServiceInfo
|
||||
from homeassistant.components.wmspro.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
|
||||
async def test_config_flow(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
|
||||
"""Test we can handle user-input to create a config entry."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.ping",
|
||||
return_value=True,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: "1.2.3.4",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "1.2.3.4"
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_config_flow_from_dhcp(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test we can handle DHCP discovery to create a config entry."""
|
||||
info = DhcpServiceInfo(
|
||||
ip="1.2.3.4", hostname="webcontrol", macaddress="00:11:22:33:44:55"
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_DHCP}, data=info
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.ping",
|
||||
return_value=True,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: "1.2.3.4",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "1.2.3.4"
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_config_flow_from_dhcp_add_mac(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test we can use DHCP discovery to add MAC address to a config entry."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.ping",
|
||||
return_value=True,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: "1.2.3.4",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "1.2.3.4"
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
assert hass.config_entries.async_entries(DOMAIN)[0].unique_id is None
|
||||
|
||||
info = DhcpServiceInfo(
|
||||
ip="1.2.3.4", hostname="webcontrol", macaddress="00:11:22:33:44:55"
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_DHCP}, data=info
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
||||
assert hass.config_entries.async_entries(DOMAIN)[0].unique_id == "00:11:22:33:44:55"
|
||||
|
||||
|
||||
async def test_config_flow_ping_failed(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test we handle ping failed error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.ping",
|
||||
return_value=False,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: "1.2.3.4",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.ping",
|
||||
return_value=True,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: "1.2.3.4",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "1.2.3.4"
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_config_flow_cannot_connect(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test we handle cannot connect error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.ping",
|
||||
side_effect=aiohttp.ClientError,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: "1.2.3.4",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.ping",
|
||||
return_value=True,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: "1.2.3.4",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "1.2.3.4"
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_config_flow_unknown_error(
|
||||
hass: HomeAssistant, mock_setup_entry: AsyncMock
|
||||
) -> None:
|
||||
"""Test we handle an unknown error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.ping",
|
||||
side_effect=RuntimeError,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: "1.2.3.4",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": "unknown"}
|
||||
|
||||
with patch(
|
||||
"wmspro.webcontrol.WebControlPro.ping",
|
||||
return_value=True,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_HOST: "1.2.3.4",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "1.2.3.4"
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "1.2.3.4",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
226
tests/components/wmspro/test_cover.py
Normal file
226
tests/components/wmspro/test_cover.py
Normal file
@ -0,0 +1,226 @@
|
||||
"""Test the wmspro diagnostics."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.wmspro.const import DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_CLOSE_COVER,
|
||||
SERVICE_OPEN_COVER,
|
||||
SERVICE_SET_COVER_POSITION,
|
||||
SERVICE_STOP_COVER,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from . import setup_config_entry
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_cover_device(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_hub_ping: AsyncMock,
|
||||
mock_hub_configuration_prod: AsyncMock,
|
||||
mock_hub_status_prod_awning: AsyncMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test that a cover device is created correctly."""
|
||||
assert await setup_config_entry(hass, mock_config_entry)
|
||||
assert len(mock_hub_ping.mock_calls) == 1
|
||||
assert len(mock_hub_configuration_prod.mock_calls) == 1
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) == 2
|
||||
|
||||
device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "58717")})
|
||||
assert device_entry is not None
|
||||
assert device_entry == snapshot
|
||||
|
||||
|
||||
async def test_cover_update(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_hub_ping: AsyncMock,
|
||||
mock_hub_configuration_prod: AsyncMock,
|
||||
mock_hub_status_prod_awning: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test that a cover entity is created and updated correctly."""
|
||||
assert await setup_config_entry(hass, mock_config_entry)
|
||||
assert len(mock_hub_ping.mock_calls) == 1
|
||||
assert len(mock_hub_configuration_prod.mock_calls) == 1
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) == 2
|
||||
|
||||
entity = hass.states.get("cover.markise")
|
||||
assert entity is not None
|
||||
assert entity == snapshot
|
||||
|
||||
await async_setup_component(hass, "homeassistant", {})
|
||||
await hass.services.async_call(
|
||||
"homeassistant",
|
||||
"update_entity",
|
||||
{ATTR_ENTITY_ID: entity.entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) == 3
|
||||
|
||||
|
||||
async def test_cover_close_and_open(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_hub_ping: AsyncMock,
|
||||
mock_hub_configuration_prod: AsyncMock,
|
||||
mock_hub_status_prod_awning: AsyncMock,
|
||||
mock_action_call: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that a cover entity is opened and closed correctly."""
|
||||
assert await setup_config_entry(hass, mock_config_entry)
|
||||
assert len(mock_hub_ping.mock_calls) == 1
|
||||
assert len(mock_hub_configuration_prod.mock_calls) == 1
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) >= 1
|
||||
|
||||
entity = hass.states.get("cover.markise")
|
||||
assert entity is not None
|
||||
assert entity.state == "open"
|
||||
assert entity.attributes["current_position"] == 100
|
||||
|
||||
with patch(
|
||||
"wmspro.destination.Destination.refresh",
|
||||
return_value=True,
|
||||
):
|
||||
before = len(mock_hub_status_prod_awning.mock_calls)
|
||||
|
||||
await hass.services.async_call(
|
||||
Platform.COVER,
|
||||
SERVICE_CLOSE_COVER,
|
||||
{ATTR_ENTITY_ID: entity.entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
entity = hass.states.get("cover.markise")
|
||||
assert entity is not None
|
||||
assert entity.state == "closed"
|
||||
assert entity.attributes["current_position"] == 0
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) == before
|
||||
|
||||
with patch(
|
||||
"wmspro.destination.Destination.refresh",
|
||||
return_value=True,
|
||||
):
|
||||
before = len(mock_hub_status_prod_awning.mock_calls)
|
||||
|
||||
await hass.services.async_call(
|
||||
Platform.COVER,
|
||||
SERVICE_OPEN_COVER,
|
||||
{ATTR_ENTITY_ID: entity.entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
entity = hass.states.get("cover.markise")
|
||||
assert entity is not None
|
||||
assert entity.state == "open"
|
||||
assert entity.attributes["current_position"] == 100
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) == before
|
||||
|
||||
|
||||
async def test_cover_move(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_hub_ping: AsyncMock,
|
||||
mock_hub_configuration_prod: AsyncMock,
|
||||
mock_hub_status_prod_awning: AsyncMock,
|
||||
mock_action_call: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that a cover entity is moved and closed correctly."""
|
||||
assert await setup_config_entry(hass, mock_config_entry)
|
||||
assert len(mock_hub_ping.mock_calls) == 1
|
||||
assert len(mock_hub_configuration_prod.mock_calls) == 1
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) >= 1
|
||||
|
||||
entity = hass.states.get("cover.markise")
|
||||
assert entity is not None
|
||||
assert entity.state == "open"
|
||||
assert entity.attributes["current_position"] == 100
|
||||
|
||||
with patch(
|
||||
"wmspro.destination.Destination.refresh",
|
||||
return_value=True,
|
||||
):
|
||||
before = len(mock_hub_status_prod_awning.mock_calls)
|
||||
|
||||
await hass.services.async_call(
|
||||
Platform.COVER,
|
||||
SERVICE_SET_COVER_POSITION,
|
||||
{ATTR_ENTITY_ID: entity.entity_id, "position": 50},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
entity = hass.states.get("cover.markise")
|
||||
assert entity is not None
|
||||
assert entity.state == "open"
|
||||
assert entity.attributes["current_position"] == 50
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) == before
|
||||
|
||||
|
||||
async def test_cover_move_and_stop(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_hub_ping: AsyncMock,
|
||||
mock_hub_configuration_prod: AsyncMock,
|
||||
mock_hub_status_prod_awning: AsyncMock,
|
||||
mock_action_call: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that a cover entity is moved and closed correctly."""
|
||||
assert await setup_config_entry(hass, mock_config_entry)
|
||||
assert len(mock_hub_ping.mock_calls) == 1
|
||||
assert len(mock_hub_configuration_prod.mock_calls) == 1
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) >= 1
|
||||
|
||||
entity = hass.states.get("cover.markise")
|
||||
assert entity is not None
|
||||
assert entity.state == "open"
|
||||
assert entity.attributes["current_position"] == 100
|
||||
|
||||
with patch(
|
||||
"wmspro.destination.Destination.refresh",
|
||||
return_value=True,
|
||||
):
|
||||
before = len(mock_hub_status_prod_awning.mock_calls)
|
||||
|
||||
await hass.services.async_call(
|
||||
Platform.COVER,
|
||||
SERVICE_SET_COVER_POSITION,
|
||||
{ATTR_ENTITY_ID: entity.entity_id, "position": 80},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
entity = hass.states.get("cover.markise")
|
||||
assert entity is not None
|
||||
assert entity.state == "open"
|
||||
assert entity.attributes["current_position"] == 80
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) == before
|
||||
|
||||
with patch(
|
||||
"wmspro.destination.Destination.refresh",
|
||||
return_value=True,
|
||||
):
|
||||
before = len(mock_hub_status_prod_awning.mock_calls)
|
||||
|
||||
await hass.services.async_call(
|
||||
Platform.COVER,
|
||||
SERVICE_STOP_COVER,
|
||||
{ATTR_ENTITY_ID: entity.entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
entity = hass.states.get("cover.markise")
|
||||
assert entity is not None
|
||||
assert entity.state == "open"
|
||||
assert entity.attributes["current_position"] == 80
|
||||
assert len(mock_hub_status_prod_awning.mock_calls) == before
|
38
tests/components/wmspro/test_init.py
Normal file
38
tests/components/wmspro/test_init.py
Normal file
@ -0,0 +1,38 @@
|
||||
"""Test the wmspro initialization."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import aiohttp
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_config_entry
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_config_entry_device_config_ping_failed(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_hub_ping: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that a config entry will be retried due to ConfigEntryNotReady."""
|
||||
mock_hub_ping.side_effect = aiohttp.ClientError
|
||||
await setup_config_entry(hass, mock_config_entry)
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert len(mock_hub_ping.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_config_entry_device_config_refresh_failed(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_hub_ping: AsyncMock,
|
||||
mock_hub_refresh: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that a config entry will be retried due to ConfigEntryNotReady."""
|
||||
mock_hub_refresh.side_effect = aiohttp.ClientError
|
||||
await setup_config_entry(hass, mock_config_entry)
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert len(mock_hub_ping.mock_calls) == 1
|
||||
assert len(mock_hub_refresh.mock_calls) == 1
|
Loading…
x
Reference in New Issue
Block a user