diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 9b2d44fd94b..1e1538420b5 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -25,7 +25,7 @@ from homeassistant.util import convert, slugify from homeassistant.util.dt import utc_from_timestamp from .common import ControllerData, get_configured_platforms -from .config_flow import new_options +from .config_flow import fix_device_id_list, new_options from .const import ( ATTR_CURRENT_ENERGY_KWH, ATTR_CURRENT_POWER_W, @@ -81,9 +81,18 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b ), ) + saved_light_ids = config_entry.options.get(CONF_LIGHTS, []) + saved_exclude_ids = config_entry.options.get(CONF_EXCLUDE, []) + base_url = config_entry.data[CONF_CONTROLLER] - light_ids = config_entry.options.get(CONF_LIGHTS, []) - exclude_ids = config_entry.options.get(CONF_EXCLUDE, []) + light_ids = fix_device_id_list(saved_light_ids) + exclude_ids = fix_device_id_list(saved_exclude_ids) + + # If the ids were corrected. Update the config entry. + if light_ids != saved_light_ids or exclude_ids != saved_exclude_ids: + hass.config_entries.async_update_entry( + entry=config_entry, options=new_options(light_ids, exclude_ids) + ) # Initialize the Vera controller. controller = veraApi.VeraController(base_url) diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index 3d2b30f1079..cac17951cc1 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -1,7 +1,7 @@ """Config flow for Vera.""" import logging import re -from typing import List, cast +from typing import Any, List import pyvera as pv from requests.exceptions import RequestException @@ -17,20 +17,22 @@ LIST_REGEX = re.compile("[^0-9]+") _LOGGER = logging.getLogger(__name__) -def str_to_int_list(data: str) -> List[str]: +def fix_device_id_list(data: List[Any]) -> List[int]: + """Fix the id list by converting it to a supported int list.""" + return str_to_int_list(list_to_str(data)) + + +def str_to_int_list(data: str) -> List[int]: """Convert a string to an int list.""" - if isinstance(str, list): - return cast(List[str], data) - - return [s for s in LIST_REGEX.split(data) if len(s) > 0] + return [int(s) for s in LIST_REGEX.split(data) if len(s) > 0] -def int_list_to_str(data: List[str]) -> str: +def list_to_str(data: List[Any]) -> str: """Convert an int list to a string.""" return " ".join([str(i) for i in data]) -def new_options(lights: List[str], exclude: List[str]) -> dict: +def new_options(lights: List[int], exclude: List[int]) -> dict: """Create a standard options object.""" return {CONF_LIGHTS: lights, CONF_EXCLUDE: exclude} @@ -40,10 +42,10 @@ def options_schema(options: dict = None) -> dict: options = options or {} return { vol.Optional( - CONF_LIGHTS, default=int_list_to_str(options.get(CONF_LIGHTS, [])), + CONF_LIGHTS, default=list_to_str(options.get(CONF_LIGHTS, [])), ): str, vol.Optional( - CONF_EXCLUDE, default=int_list_to_str(options.get(CONF_EXCLUDE, [])), + CONF_EXCLUDE, default=list_to_str(options.get(CONF_EXCLUDE, [])), ): str, } diff --git a/tests/components/vera/test_config_flow.py b/tests/components/vera/test_config_flow.py index 52ba55b509c..5f4536decec 100644 --- a/tests/components/vera/test_config_flow.py +++ b/tests/components/vera/test_config_flow.py @@ -44,8 +44,8 @@ async def test_async_step_user_success(hass: HomeAssistant) -> None: assert result["data"] == { CONF_CONTROLLER: "http://127.0.0.1:123", CONF_SOURCE: config_entries.SOURCE_USER, - CONF_LIGHTS: ["12", "13"], - CONF_EXCLUDE: ["14", "15"], + CONF_LIGHTS: [12, 13], + CONF_EXCLUDE: [14, 15], } assert result["result"].unique_id == controller.serial_number @@ -154,6 +154,6 @@ async def test_options(hass): ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"] == { - CONF_LIGHTS: ["1", "2", "3", "4", "5", "6", "7"], - CONF_EXCLUDE: ["8", "9", "10", "11", "12", "13", "14"], + CONF_LIGHTS: [1, 2, 3, 4, 5, 6, 7], + CONF_EXCLUDE: [8, 9, 10, 11, 12, 13, 14], } diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py index f1e13a5f208..210037a2ca3 100644 --- a/tests/components/vera/test_init.py +++ b/tests/components/vera/test_init.py @@ -1,8 +1,14 @@ """Vera tests.""" +import pytest import pyvera as pv from requests.exceptions import RequestException -from homeassistant.components.vera import CONF_CONTROLLER, DOMAIN +from homeassistant.components.vera import ( + CONF_CONTROLLER, + CONF_EXCLUDE, + CONF_LIGHTS, + DOMAIN, +) from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED from homeassistant.core import HomeAssistant @@ -110,3 +116,71 @@ async def test_async_setup_entry_error( entry.add_to_hass(hass) assert not await hass.config_entries.async_setup(entry.entry_id) + + +@pytest.mark.parametrize( + ["options"], + [ + [{CONF_LIGHTS: [4, 10, 12, "AAA"], CONF_EXCLUDE: [1, "BBB"]}], + [{CONF_LIGHTS: ["4", "10", "12", "AAA"], CONF_EXCLUDE: ["1", "BBB"]}], + ], +) +async def test_exclude_and_light_ids( + hass: HomeAssistant, vera_component_factory: ComponentFactory, options +) -> None: + """Test device exclusion, marking switches as lights and fixing the data type.""" + vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device1.device_id = 1 + vera_device1.vera_device_id = 1 + vera_device1.name = "dev1" + vera_device1.is_tripped = False + entity_id1 = "binary_sensor.dev1_1" + + vera_device2 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor + vera_device2.device_id = 2 + vera_device2.vera_device_id = 2 + vera_device2.name = "dev2" + vera_device2.is_tripped = False + entity_id2 = "binary_sensor.dev2_2" + + vera_device3 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch + vera_device3.device_id = 3 + vera_device3.name = "dev3" + vera_device3.category = pv.CATEGORY_SWITCH + vera_device3.is_switched_on = MagicMock(return_value=False) + entity_id3 = "switch.dev3_3" + + vera_device4 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch + vera_device4.device_id = 4 + vera_device4.name = "dev4" + vera_device4.category = pv.CATEGORY_SWITCH + vera_device4.is_switched_on = MagicMock(return_value=False) + entity_id4 = "light.dev4_4" + + component_data = await vera_component_factory.configure_component( + hass=hass, + controller_config=new_simple_controller_config( + devices=(vera_device1, vera_device2, vera_device3, vera_device4), + config={**{CONF_CONTROLLER: "http://127.0.0.1:123"}, **options}, + ), + ) + + # Assert the entries were setup correctly. + config_entry = next(iter(hass.config_entries.async_entries(DOMAIN))) + assert config_entry.options == { + CONF_LIGHTS: [4, 10, 12], + CONF_EXCLUDE: [1], + } + + update_callback = component_data.controller_data.update_callback + + update_callback(vera_device1) + update_callback(vera_device2) + update_callback(vera_device3) + update_callback(vera_device4) + await hass.async_block_till_done() + + assert hass.states.get(entity_id1) is None + assert hass.states.get(entity_id2) is not None + assert hass.states.get(entity_id3) is not None + assert hass.states.get(entity_id4) is not None