mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Motion blinds add interface and wait_for_push options (#50067)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
174eaefe61
commit
9bd2115a20
@ -3,7 +3,7 @@ from datetime import timedelta
|
|||||||
import logging
|
import logging
|
||||||
from socket import timeout
|
from socket import timeout
|
||||||
|
|
||||||
from motionblinds import MotionMulticast, ParseException
|
from motionblinds import AsyncMotionMulticast, ParseException
|
||||||
|
|
||||||
from homeassistant import config_entries, core
|
from homeassistant import config_entries, core
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_HOST, EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import CONF_API_KEY, CONF_HOST, EVENT_HOMEASSISTANT_STOP
|
||||||
@ -13,6 +13,10 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_AVAILABLE,
|
ATTR_AVAILABLE,
|
||||||
|
CONF_INTERFACE,
|
||||||
|
CONF_WAIT_FOR_PUSH,
|
||||||
|
DEFAULT_INTERFACE,
|
||||||
|
DEFAULT_WAIT_FOR_PUSH,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
KEY_COORDINATOR,
|
KEY_COORDINATOR,
|
||||||
KEY_GATEWAY,
|
KEY_GATEWAY,
|
||||||
@ -34,7 +38,7 @@ class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator):
|
|||||||
self,
|
self,
|
||||||
hass,
|
hass,
|
||||||
logger,
|
logger,
|
||||||
gateway,
|
coordinator_info,
|
||||||
*,
|
*,
|
||||||
name,
|
name,
|
||||||
update_interval=None,
|
update_interval=None,
|
||||||
@ -49,7 +53,8 @@ class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator):
|
|||||||
update_interval=update_interval,
|
update_interval=update_interval,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._gateway = gateway
|
self._gateway = coordinator_info[KEY_GATEWAY]
|
||||||
|
self._wait_for_push = coordinator_info[CONF_WAIT_FOR_PUSH]
|
||||||
|
|
||||||
def update_gateway(self):
|
def update_gateway(self):
|
||||||
"""Call all updates using one async_add_executor_job."""
|
"""Call all updates using one async_add_executor_job."""
|
||||||
@ -66,7 +71,10 @@ class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator):
|
|||||||
|
|
||||||
for blind in self._gateway.device_list.values():
|
for blind in self._gateway.device_list.values():
|
||||||
try:
|
try:
|
||||||
blind.Update()
|
if self._wait_for_push:
|
||||||
|
blind.Update()
|
||||||
|
else:
|
||||||
|
blind.Update_trigger()
|
||||||
except (timeout, ParseException):
|
except (timeout, ParseException):
|
||||||
# let the error be logged and handled by the motionblinds library
|
# let the error be logged and handled by the motionblinds library
|
||||||
data[blind.mac] = {ATTR_AVAILABLE: False}
|
data[blind.mac] = {ATTR_AVAILABLE: False}
|
||||||
@ -95,13 +103,17 @@ async def async_setup_entry(
|
|||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
host = entry.data[CONF_HOST]
|
host = entry.data[CONF_HOST]
|
||||||
key = entry.data[CONF_API_KEY]
|
key = entry.data[CONF_API_KEY]
|
||||||
|
multicast_interface = entry.data.get(CONF_INTERFACE, DEFAULT_INTERFACE)
|
||||||
|
wait_for_push = entry.options.get(CONF_WAIT_FOR_PUSH, DEFAULT_WAIT_FOR_PUSH)
|
||||||
|
|
||||||
|
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||||
|
|
||||||
# Create multicast Listener
|
# Create multicast Listener
|
||||||
if KEY_MULTICAST_LISTENER not in hass.data[DOMAIN]:
|
if KEY_MULTICAST_LISTENER not in hass.data[DOMAIN]:
|
||||||
multicast = MotionMulticast()
|
multicast = AsyncMotionMulticast(interface=multicast_interface)
|
||||||
hass.data[DOMAIN][KEY_MULTICAST_LISTENER] = multicast
|
hass.data[DOMAIN][KEY_MULTICAST_LISTENER] = multicast
|
||||||
# start listening for local pushes (only once)
|
# start listening for local pushes (only once)
|
||||||
await hass.async_add_executor_job(multicast.Start_listen)
|
await multicast.Start_listen()
|
||||||
|
|
||||||
# register stop callback to shutdown listening for local pushes
|
# register stop callback to shutdown listening for local pushes
|
||||||
def stop_motion_multicast(event):
|
def stop_motion_multicast(event):
|
||||||
@ -117,11 +129,15 @@ async def async_setup_entry(
|
|||||||
if not await connect_gateway_class.async_connect_gateway(host, key):
|
if not await connect_gateway_class.async_connect_gateway(host, key):
|
||||||
raise ConfigEntryNotReady
|
raise ConfigEntryNotReady
|
||||||
motion_gateway = connect_gateway_class.gateway_device
|
motion_gateway = connect_gateway_class.gateway_device
|
||||||
|
coordinator_info = {
|
||||||
|
KEY_GATEWAY: motion_gateway,
|
||||||
|
CONF_WAIT_FOR_PUSH: wait_for_push,
|
||||||
|
}
|
||||||
|
|
||||||
coordinator = DataUpdateCoordinatorMotionBlinds(
|
coordinator = DataUpdateCoordinatorMotionBlinds(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
motion_gateway,
|
coordinator_info,
|
||||||
# Name of the data. For logging purposes.
|
# Name of the data. For logging purposes.
|
||||||
name=entry.title,
|
name=entry.title,
|
||||||
# Polling interval. Will only be polled if there are subscribers.
|
# Polling interval. Will only be polled if there are subscribers.
|
||||||
@ -172,6 +188,13 @@ async def async_unload_entry(
|
|||||||
# No motion gateways left, stop Motion multicast
|
# No motion gateways left, stop Motion multicast
|
||||||
_LOGGER.debug("Shutting down Motion Listener")
|
_LOGGER.debug("Shutting down Motion Listener")
|
||||||
multicast = hass.data[DOMAIN].pop(KEY_MULTICAST_LISTENER)
|
multicast = hass.data[DOMAIN].pop(KEY_MULTICAST_LISTENER)
|
||||||
await hass.async_add_executor_job(multicast.Stop_listen)
|
multicast.Stop_listen()
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def update_listener(
|
||||||
|
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
|
||||||
|
) -> None:
|
||||||
|
"""Handle options update."""
|
||||||
|
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||||
|
@ -1,11 +1,22 @@
|
|||||||
"""Config flow to configure Motion Blinds using their WLAN API."""
|
"""Config flow to configure Motion Blinds using their WLAN API."""
|
||||||
from motionblinds import MotionDiscovery
|
from socket import gaierror
|
||||||
|
|
||||||
|
from motionblinds import AsyncMotionMulticast, MotionDiscovery
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components import network
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_HOST
|
from homeassistant.const import CONF_API_KEY, CONF_HOST
|
||||||
|
from homeassistant.core import callback
|
||||||
|
|
||||||
from .const import DEFAULT_GATEWAY_NAME, DOMAIN
|
from .const import (
|
||||||
|
CONF_INTERFACE,
|
||||||
|
CONF_WAIT_FOR_PUSH,
|
||||||
|
DEFAULT_GATEWAY_NAME,
|
||||||
|
DEFAULT_INTERFACE,
|
||||||
|
DEFAULT_WAIT_FOR_PUSH,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
from .gateway import ConnectMotionGateway
|
from .gateway import ConnectMotionGateway
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
@ -14,11 +25,34 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SETTINGS = vol.Schema(
|
|
||||||
{
|
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
vol.Required(CONF_API_KEY): vol.All(str, vol.Length(min=16, max=16)),
|
"""Options for the component."""
|
||||||
}
|
|
||||||
)
|
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||||
|
"""Init object."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
"""Manage the options."""
|
||||||
|
errors = {}
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
|
settings_schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(
|
||||||
|
CONF_WAIT_FOR_PUSH,
|
||||||
|
default=self.config_entry.options.get(
|
||||||
|
CONF_WAIT_FOR_PUSH, DEFAULT_WAIT_FOR_PUSH
|
||||||
|
),
|
||||||
|
): bool,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="init", data_schema=settings_schema, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
@ -30,6 +64,13 @@ class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
"""Initialize the Motion Blinds flow."""
|
"""Initialize the Motion Blinds flow."""
|
||||||
self._host = None
|
self._host = None
|
||||||
self._ips = []
|
self._ips = []
|
||||||
|
self._config_settings = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry) -> OptionsFlowHandler:
|
||||||
|
"""Get the options flow."""
|
||||||
|
return OptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(self, user_input=None):
|
||||||
"""Handle a flow initialized by the user."""
|
"""Handle a flow initialized by the user."""
|
||||||
@ -70,8 +111,24 @@ class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_connect(self, user_input=None):
|
async def async_step_connect(self, user_input=None):
|
||||||
"""Connect to the Motion Gateway."""
|
"""Connect to the Motion Gateway."""
|
||||||
|
errors = {}
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
key = user_input[CONF_API_KEY]
|
key = user_input[CONF_API_KEY]
|
||||||
|
multicast_interface = user_input[CONF_INTERFACE]
|
||||||
|
|
||||||
|
# check socket interface
|
||||||
|
if multicast_interface != DEFAULT_INTERFACE:
|
||||||
|
motion_multicast = AsyncMotionMulticast(interface=multicast_interface)
|
||||||
|
try:
|
||||||
|
await motion_multicast.Start_listen()
|
||||||
|
motion_multicast.Stop_listen()
|
||||||
|
except gaierror:
|
||||||
|
errors[CONF_INTERFACE] = "invalid_interface"
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="connect",
|
||||||
|
data_schema=self._config_settings,
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
connect_gateway_class = ConnectMotionGateway(self.hass, multicast=None)
|
connect_gateway_class = ConnectMotionGateway(self.hass, multicast=None)
|
||||||
if not await connect_gateway_class.async_connect_gateway(self._host, key):
|
if not await connect_gateway_class.async_connect_gateway(self._host, key):
|
||||||
@ -85,7 +142,45 @@ class MotionBlindsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=DEFAULT_GATEWAY_NAME,
|
title=DEFAULT_GATEWAY_NAME,
|
||||||
data={CONF_HOST: self._host, CONF_API_KEY: key},
|
data={
|
||||||
|
CONF_HOST: self._host,
|
||||||
|
CONF_API_KEY: key,
|
||||||
|
CONF_INTERFACE: multicast_interface,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.async_show_form(step_id="connect", data_schema=CONFIG_SETTINGS)
|
(interfaces, default_interface) = await self.async_get_interfaces()
|
||||||
|
|
||||||
|
self._config_settings = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_API_KEY): vol.All(str, vol.Length(min=16, max=16)),
|
||||||
|
vol.Optional(CONF_INTERFACE, default=default_interface): vol.In(
|
||||||
|
interfaces
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="connect", data_schema=self._config_settings, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_get_interfaces(self):
|
||||||
|
"""Get list of interface to use."""
|
||||||
|
interfaces = [DEFAULT_INTERFACE]
|
||||||
|
enabled_interfaces = []
|
||||||
|
default_interface = DEFAULT_INTERFACE
|
||||||
|
|
||||||
|
adapters = await network.async_get_adapters(self.hass)
|
||||||
|
for adapter in adapters:
|
||||||
|
if ipv4s := adapter["ipv4"]:
|
||||||
|
ip4 = ipv4s[0]["address"]
|
||||||
|
interfaces.append(ip4)
|
||||||
|
if adapter["enabled"]:
|
||||||
|
enabled_interfaces.append(ip4)
|
||||||
|
if adapter["default"]:
|
||||||
|
default_interface = ip4
|
||||||
|
|
||||||
|
if len(enabled_interfaces) == 1:
|
||||||
|
default_interface = enabled_interfaces[0]
|
||||||
|
|
||||||
|
return (interfaces, default_interface)
|
||||||
|
@ -5,6 +5,11 @@ DEFAULT_GATEWAY_NAME = "Motion Blinds Gateway"
|
|||||||
|
|
||||||
PLATFORMS = ["cover", "sensor"]
|
PLATFORMS = ["cover", "sensor"]
|
||||||
|
|
||||||
|
CONF_WAIT_FOR_PUSH = "wait_for_push"
|
||||||
|
CONF_INTERFACE = "interface"
|
||||||
|
DEFAULT_WAIT_FOR_PUSH = False
|
||||||
|
DEFAULT_INTERFACE = "any"
|
||||||
|
|
||||||
KEY_GATEWAY = "gateway"
|
KEY_GATEWAY = "gateway"
|
||||||
KEY_COORDINATOR = "coordinator"
|
KEY_COORDINATOR = "coordinator"
|
||||||
KEY_MULTICAST_LISTENER = "multicast_listener"
|
KEY_MULTICAST_LISTENER = "multicast_listener"
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
|
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
|
||||||
"requirements": ["motionblinds==0.5.7"],
|
"requirements": ["motionblinds==0.5.7"],
|
||||||
|
"dependencies": ["network"],
|
||||||
"codeowners": ["@starkillerOG"],
|
"codeowners": ["@starkillerOG"],
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
"title": "Motion Blinds",
|
"title": "Motion Blinds",
|
||||||
"description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions",
|
"description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions",
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||||
|
"interface": "The network interface to use"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
@ -24,13 +25,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"discovery_error": "Failed to discover a Motion Gateway"
|
"discovery_error": "Failed to discover a Motion Gateway",
|
||||||
|
"invalid_interface": "Invalid network interface"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
"connection_error": "[%key:common::config_flow::error::cannot_connect%]"
|
"connection_error": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Motion Blinds",
|
||||||
|
"description": "Specify optional settings",
|
||||||
|
"data": {
|
||||||
|
"wait_for_push": "Wait for multicast push on update"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,13 +6,15 @@
|
|||||||
"connection_error": "Failed to connect"
|
"connection_error": "Failed to connect"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"discovery_error": "Failed to discover a Motion Gateway"
|
"discovery_error": "Failed to discover a Motion Gateway",
|
||||||
|
"invalid_interface": "Invalid network interface"
|
||||||
},
|
},
|
||||||
"flow_title": "Motion Blinds",
|
"flow_title": "Motion Blinds",
|
||||||
"step": {
|
"step": {
|
||||||
"connect": {
|
"connect": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "API Key"
|
"api_key": "API Key",
|
||||||
|
"interface": "The network interface to use"
|
||||||
},
|
},
|
||||||
"description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions",
|
"description": "You will need the 16 character API Key, see https://www.home-assistant.io/integrations/motion_blinds/#retrieving-the-key for instructions",
|
||||||
"title": "Motion Blinds"
|
"title": "Motion Blinds"
|
||||||
@ -33,5 +35,16 @@
|
|||||||
"title": "Motion Blinds"
|
"title": "Motion Blinds"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Motion Blinds",
|
||||||
|
"description": "Specify optional settings",
|
||||||
|
"data": {
|
||||||
|
"wait_for_push": "Wait for multicast push on update"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,13 +4,16 @@ from unittest.mock import Mock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries, data_entry_flow
|
||||||
|
from homeassistant.components.motion_blinds import const
|
||||||
from homeassistant.components.motion_blinds.config_flow import DEFAULT_GATEWAY_NAME
|
from homeassistant.components.motion_blinds.config_flow import DEFAULT_GATEWAY_NAME
|
||||||
from homeassistant.components.motion_blinds.const import DOMAIN
|
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_HOST
|
from homeassistant.const import CONF_API_KEY, CONF_HOST
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
TEST_HOST = "1.2.3.4"
|
TEST_HOST = "1.2.3.4"
|
||||||
TEST_HOST2 = "5.6.7.8"
|
TEST_HOST2 = "5.6.7.8"
|
||||||
|
TEST_HOST_HA = "9.10.11.12"
|
||||||
TEST_API_KEY = "12ab345c-d67e-8f"
|
TEST_API_KEY = "12ab345c-d67e-8f"
|
||||||
TEST_MAC = "ab:cd:ef:gh"
|
TEST_MAC = "ab:cd:ef:gh"
|
||||||
TEST_MAC2 = "ij:kl:mn:op"
|
TEST_MAC2 = "ij:kl:mn:op"
|
||||||
@ -56,9 +59,13 @@ TEST_DISCOVERY_2 = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_INTERFACES = [
|
||||||
|
{"enabled": True, "default": True, "ipv4": [{"address": TEST_HOST_HA}]}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="motion_blinds_connect", autouse=True)
|
@pytest.fixture(name="motion_blinds_connect", autouse=True)
|
||||||
def motion_blinds_connect_fixture():
|
def motion_blinds_connect_fixture(mock_get_source_ip):
|
||||||
"""Mock motion blinds connection and entry setup."""
|
"""Mock motion blinds connection and entry setup."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.motion_blinds.gateway.MotionGateway.GetDeviceList",
|
"homeassistant.components.motion_blinds.gateway.MotionGateway.GetDeviceList",
|
||||||
@ -72,6 +79,15 @@ def motion_blinds_connect_fixture():
|
|||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.motion_blinds.config_flow.MotionDiscovery.discover",
|
"homeassistant.components.motion_blinds.config_flow.MotionDiscovery.discover",
|
||||||
return_value=TEST_DISCOVERY_1,
|
return_value=TEST_DISCOVERY_1,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.motion_blinds.config_flow.AsyncMotionMulticast.Start_listen",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.motion_blinds.config_flow.AsyncMotionMulticast.Stop_listen",
|
||||||
|
return_value=True,
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.motion_blinds.config_flow.network.async_get_adapters",
|
||||||
|
return_value=TEST_INTERFACES,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.motion_blinds.async_setup_entry", return_value=True
|
"homeassistant.components.motion_blinds.async_setup_entry", return_value=True
|
||||||
):
|
):
|
||||||
@ -81,7 +97,7 @@ def motion_blinds_connect_fixture():
|
|||||||
async def test_config_flow_manual_host_success(hass):
|
async def test_config_flow_manual_host_success(hass):
|
||||||
"""Successful flow manually initialized by the user."""
|
"""Successful flow manually initialized by the user."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
@ -95,7 +111,7 @@ async def test_config_flow_manual_host_success(hass):
|
|||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == "connect"
|
assert result["step_id"] == "connect"
|
||||||
assert result["errors"] is None
|
assert result["errors"] == {}
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
@ -107,13 +123,14 @@ async def test_config_flow_manual_host_success(hass):
|
|||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
CONF_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
CONF_API_KEY: TEST_API_KEY,
|
CONF_API_KEY: TEST_API_KEY,
|
||||||
|
const.CONF_INTERFACE: TEST_HOST_HA,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_config_flow_discovery_1_success(hass):
|
async def test_config_flow_discovery_1_success(hass):
|
||||||
"""Successful flow with 1 gateway discovered."""
|
"""Successful flow with 1 gateway discovered."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
@ -127,7 +144,7 @@ async def test_config_flow_discovery_1_success(hass):
|
|||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == "connect"
|
assert result["step_id"] == "connect"
|
||||||
assert result["errors"] is None
|
assert result["errors"] == {}
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
@ -139,13 +156,14 @@ async def test_config_flow_discovery_1_success(hass):
|
|||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
CONF_HOST: TEST_HOST,
|
CONF_HOST: TEST_HOST,
|
||||||
CONF_API_KEY: TEST_API_KEY,
|
CONF_API_KEY: TEST_API_KEY,
|
||||||
|
const.CONF_INTERFACE: TEST_HOST_HA,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_config_flow_discovery_2_success(hass):
|
async def test_config_flow_discovery_2_success(hass):
|
||||||
"""Successful flow with 2 gateway discovered."""
|
"""Successful flow with 2 gateway discovered."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
@ -176,7 +194,7 @@ async def test_config_flow_discovery_2_success(hass):
|
|||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == "connect"
|
assert result["step_id"] == "connect"
|
||||||
assert result["errors"] is None
|
assert result["errors"] == {}
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
@ -188,13 +206,14 @@ async def test_config_flow_discovery_2_success(hass):
|
|||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
CONF_HOST: TEST_HOST2,
|
CONF_HOST: TEST_HOST2,
|
||||||
CONF_API_KEY: TEST_API_KEY,
|
CONF_API_KEY: TEST_API_KEY,
|
||||||
|
const.CONF_INTERFACE: TEST_HOST_HA,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_config_flow_connection_error(hass):
|
async def test_config_flow_connection_error(hass):
|
||||||
"""Failed flow manually initialized by the user with connection timeout."""
|
"""Failed flow manually initialized by the user with connection timeout."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
@ -208,7 +227,7 @@ async def test_config_flow_connection_error(hass):
|
|||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == "connect"
|
assert result["step_id"] == "connect"
|
||||||
assert result["errors"] is None
|
assert result["errors"] == {}
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.motion_blinds.gateway.MotionGateway.GetDeviceList",
|
"homeassistant.components.motion_blinds.gateway.MotionGateway.GetDeviceList",
|
||||||
@ -226,7 +245,7 @@ async def test_config_flow_connection_error(hass):
|
|||||||
async def test_config_flow_discovery_fail(hass):
|
async def test_config_flow_discovery_fail(hass):
|
||||||
"""Failed flow with no gateways discovered."""
|
"""Failed flow with no gateways discovered."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
@ -245,3 +264,101 @@ async def test_config_flow_discovery_fail(hass):
|
|||||||
assert result["type"] == "form"
|
assert result["type"] == "form"
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
assert result["errors"] == {"base": "discovery_error"}
|
assert result["errors"] == {"base": "discovery_error"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_interface(hass):
|
||||||
|
"""Successful flow manually initialized by the user with interface specified."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "connect"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_API_KEY: TEST_API_KEY, const.CONF_INTERFACE: TEST_HOST_HA},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == DEFAULT_GATEWAY_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_API_KEY: TEST_API_KEY,
|
||||||
|
const.CONF_INTERFACE: TEST_HOST_HA,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_invalid_interface(hass):
|
||||||
|
"""Failed flow manually initialized by the user with invalid interface."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
const.DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_HOST: TEST_HOST},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "connect"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.motion_blinds.config_flow.AsyncMotionMulticast.Start_listen",
|
||||||
|
side_effect=socket.gaierror,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_API_KEY: TEST_API_KEY, const.CONF_INTERFACE: TEST_HOST_HA},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "connect"
|
||||||
|
assert result["errors"] == {const.CONF_INTERFACE: "invalid_interface"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options_flow(hass):
|
||||||
|
"""Test specifying non default settings using options flow."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=const.DOMAIN,
|
||||||
|
unique_id=TEST_MAC,
|
||||||
|
data={
|
||||||
|
CONF_HOST: TEST_HOST,
|
||||||
|
CONF_API_KEY: TEST_API_KEY,
|
||||||
|
},
|
||||||
|
title=DEFAULT_GATEWAY_NAME,
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(config_entry.entry_id)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={const.CONF_WAIT_FOR_PUSH: False},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert config_entry.options == {
|
||||||
|
const.CONF_WAIT_FOR_PUSH: False,
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user