diff --git a/.coveragerc b/.coveragerc index e7f99d9982e..426114c6576 100644 --- a/.coveragerc +++ b/.coveragerc @@ -520,6 +520,7 @@ omit = homeassistant/components/iperf3/* homeassistant/components/iqvia/* homeassistant/components/irish_rail_transport/sensor.py + homeassistant/components/iss/__init__.py homeassistant/components/iss/binary_sensor.py homeassistant/components/isy994/__init__.py homeassistant/components/isy994/binary_sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index e5b6ed9798a..cb2573eedb8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -467,6 +467,8 @@ tests/components/iqvia/* @bachya homeassistant/components/irish_rail_transport/* @ttroy50 homeassistant/components/islamic_prayer_times/* @engrbm87 tests/components/islamic_prayer_times/* @engrbm87 +homeassistant/components/iss/* @DurgNomis-drol +tests/components/iss/* @DurgNomis-drol homeassistant/components/isy994/* @bdraco @shbatm tests/components/isy994/* @bdraco @shbatm homeassistant/components/izone/* @Swamp-Ig diff --git a/homeassistant/components/iss/__init__.py b/homeassistant/components/iss/__init__.py index 51487bdfaf2..af44e621a7f 100644 --- a/homeassistant/components/iss/__init__.py +++ b/homeassistant/components/iss/__init__.py @@ -1 +1,27 @@ """The iss component.""" +from __future__ import annotations + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant + +from .const import DOMAIN + +PLATFORMS = [Platform.BINARY_SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up this integration using UI.""" + + hass.data.setdefault(DOMAIN, {}) + + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Handle removal of an entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + del hass.data[DOMAIN] + return unload_ok diff --git a/homeassistant/components/iss/binary_sensor.py b/homeassistant/components/iss/binary_sensor.py index eab1294ad10..5272053a517 100644 --- a/homeassistant/components/iss/binary_sensor.py +++ b/homeassistant/components/iss/binary_sensor.py @@ -1,4 +1,4 @@ -"""Support for International Space Station binary sensor.""" +"""Support for iss binary sensor.""" from __future__ import annotations from datetime import timedelta @@ -10,6 +10,7 @@ from requests.exceptions import HTTPError import voluptuous as vol from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, @@ -22,6 +23,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.util import Throttle +from .const import DOMAIN + _LOGGER = logging.getLogger(__name__) ATTR_ISS_NEXT_RISE = "next_rise" @@ -46,22 +49,40 @@ def setup_platform( add_entities: AddEntitiesCallback, discovery_info: DiscoveryInfoType | None = None, ) -> None: - """Set up the ISS binary sensor.""" - if None in (hass.config.latitude, hass.config.longitude): - _LOGGER.error("Latitude or longitude not set in Home Assistant config") - return + """Import ISS configuration from yaml.""" + _LOGGER.warning( + "Configuration of the iss platform in YAML is deprecated and will be " + "removed in Home Assistant 2022.5; Your existing configuration " + "has been imported into the UI automatically and can be safely removed " + "from your configuration.yaml file" + ) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config, + ) + ) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the sensor platform.""" + + name = entry.data.get(CONF_NAME, DEFAULT_NAME) + show_on_map = entry.data.get(CONF_SHOW_ON_MAP, False) try: iss_data = IssData(hass.config.latitude, hass.config.longitude) - iss_data.update() + await hass.async_add_executor_job(iss_data.update) except HTTPError as error: _LOGGER.error(error) return - name = config.get(CONF_NAME) - show_on_map = config.get(CONF_SHOW_ON_MAP) - - add_entities([IssBinarySensor(iss_data, name, show_on_map)], True) + async_add_entities([IssBinarySensor(iss_data, name, show_on_map)], True) class IssBinarySensor(BinarySensorEntity): diff --git a/homeassistant/components/iss/config_flow.py b/homeassistant/components/iss/config_flow.py new file mode 100644 index 00000000000..e1703b54acb --- /dev/null +++ b/homeassistant/components/iss/config_flow.py @@ -0,0 +1,48 @@ +"""Config flow to configure iss component.""" + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_NAME, CONF_SHOW_ON_MAP +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Config flow for iss component.""" + + VERSION = 1 + + async def async_step_user(self, user_input=None) -> FlowResult: + """Handle a flow initialized by the user.""" + # Check if already configured + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + # Check if location have been defined. + if not self.hass.config.latitude and not self.hass.config.longitude: + return self.async_abort(reason="latitude_longitude_not_defined") + + if user_input is not None: + return self.async_create_entry( + title="International Space Station", data=user_input + ) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Optional(CONF_SHOW_ON_MAP, default=False): bool, + } + ), + ) + + async def async_step_import(self, conf: dict) -> FlowResult: + """Import a configuration from configuration.yaml.""" + return await self.async_step_user( + user_input={ + CONF_NAME: conf[CONF_NAME], + CONF_SHOW_ON_MAP: conf[CONF_SHOW_ON_MAP], + } + ) diff --git a/homeassistant/components/iss/const.py b/homeassistant/components/iss/const.py new file mode 100644 index 00000000000..3d240041b67 --- /dev/null +++ b/homeassistant/components/iss/const.py @@ -0,0 +1,3 @@ +"""Constants for iss.""" + +DOMAIN = "iss" diff --git a/homeassistant/components/iss/manifest.json b/homeassistant/components/iss/manifest.json index 740dbbb9ff4..bd1aa967f07 100644 --- a/homeassistant/components/iss/manifest.json +++ b/homeassistant/components/iss/manifest.json @@ -1,9 +1,10 @@ { "domain": "iss", + "config_flow": true, "name": "International Space Station (ISS)", "documentation": "https://www.home-assistant.io/integrations/iss", "requirements": ["pyiss==1.0.1"], - "codeowners": [], + "codeowners": ["@DurgNomis-drol"], "iot_class": "cloud_polling", "loggers": ["pyiss"] } diff --git a/homeassistant/components/iss/strings.json b/homeassistant/components/iss/strings.json new file mode 100644 index 00000000000..cdbaecbeba5 --- /dev/null +++ b/homeassistant/components/iss/strings.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "user": { + "description": "Do you want to configure the Internation Space Station?", + "data": { + "show_on_map": "Show on map?" + } + } + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "latitude_longitude_not_defined": "Latitude and longitude is not defind in Home Assistant." + } + } + } \ No newline at end of file diff --git a/homeassistant/components/iss/translations/en.json b/homeassistant/components/iss/translations/en.json new file mode 100644 index 00000000000..13483418ffa --- /dev/null +++ b/homeassistant/components/iss/translations/en.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "latitude_longitude_not_defined": "Latitude and longitude is not defind in Home Assistant.", + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "step": { + "user": { + "data": { + "show_on_map": "Show on map?" + }, + "description": "Do you want to configure the Internation Space Station?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index bf64bc4a51c..365aa903b09 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -159,6 +159,7 @@ FLOWS = [ "ipp", "iqvia", "islamic_prayer_times", + "iss", "isy994", "izone", "jellyfin", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 83086ebb786..52b93948e43 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -998,6 +998,9 @@ pyipp==0.11.0 # homeassistant.components.iqvia pyiqvia==2021.11.0 +# homeassistant.components.iss +pyiss==1.0.1 + # homeassistant.components.isy994 pyisy==3.0.1 diff --git a/tests/components/iss/__init__.py b/tests/components/iss/__init__.py new file mode 100644 index 00000000000..7fa75a42ccb --- /dev/null +++ b/tests/components/iss/__init__.py @@ -0,0 +1 @@ +"""Tests for the iss component.""" diff --git a/tests/components/iss/test_config_flow.py b/tests/components/iss/test_config_flow.py new file mode 100644 index 00000000000..a20a8729f55 --- /dev/null +++ b/tests/components/iss/test_config_flow.py @@ -0,0 +1,78 @@ +"""Test iss config flow.""" +from unittest.mock import patch + +from homeassistant import data_entry_flow +from homeassistant.components.iss.binary_sensor import DEFAULT_NAME +from homeassistant.components.iss.const import DOMAIN +from homeassistant.config import async_process_ha_core_config +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_NAME, CONF_SHOW_ON_MAP + +from tests.common import MockConfigEntry + + +async def test_import(hass): + """Test entry will be imported.""" + + imported_config = {CONF_NAME: DEFAULT_NAME, CONF_SHOW_ON_MAP: False} + + with patch("homeassistant.components.iss.async_setup_entry", return_value=True): + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=imported_config + ) + assert result.get("type") == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result.get("result").data == imported_config + + +async def test_create_entry(hass): + """Test we can finish a config flow.""" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result.get("type") == data_entry_flow.RESULT_TYPE_FORM + assert result.get("step_id") == SOURCE_USER + + with patch("homeassistant.components.iss.async_setup_entry", return_value=True): + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_SHOW_ON_MAP: True}, + ) + + assert result.get("type") == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result.get("result").data[CONF_SHOW_ON_MAP] is True + + +async def test_integration_already_exists(hass): + """Test we only allow a single config flow.""" + + MockConfigEntry( + domain=DOMAIN, + data={}, + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={CONF_SHOW_ON_MAP: False} + ) + + assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT + assert result.get("reason") == "single_instance_allowed" + + +async def test_abort_no_home(hass): + """Test we don't create an entry if no coordinates are set.""" + + await async_process_ha_core_config( + hass, + {"latitude": 0.0, "longitude": 0.0}, + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={CONF_SHOW_ON_MAP: False} + ) + + assert result.get("type") == data_entry_flow.RESULT_TYPE_ABORT + assert result.get("reason") == "latitude_longitude_not_defined"