Motion blinds add interface and wait_for_push options (#50067)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
starkillerOG 2021-10-18 23:34:51 +02:00 committed by GitHub
parent 174eaefe61
commit 9bd2115a20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 300 additions and 33 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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"
} }

View File

@ -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"
}
}
}
} }
} }

View File

@ -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"
}
}
}
} }
} }

View File

@ -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,
}