mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 06:37:52 +00:00
Add options flow for AirVisual (#32634)
* Add options flow for AirVisual * Code review Co-Authored-By: Robert Svensson <Kane610@users.noreply.github.com> Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>
This commit is contained in:
parent
a3c55b5e96
commit
21cff003f8
@ -11,13 +11,23 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"api_key": "API Key",
|
"api_key": "API Key",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"longitude": "Longitude",
|
"longitude": "Longitude"
|
||||||
"show_on_map": "Show monitored geography on the map"
|
|
||||||
},
|
},
|
||||||
"description": "Monitor air quality in a geographical location.",
|
"description": "Monitor air quality in a geographical location.",
|
||||||
"title": "Configure AirVisual"
|
"title": "Configure AirVisual"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "AirVisual"
|
"title": "AirVisual"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"show_on_map": "Show monitored geography on the map"
|
||||||
|
},
|
||||||
|
"description": "Set various options for the AirVisual integration.",
|
||||||
|
"title": "Configure AirVisual"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -139,6 +139,8 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
hass, refresh, DEFAULT_SCAN_INTERVAL
|
hass, refresh, DEFAULT_SCAN_INTERVAL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
config_entry.add_update_listener(async_update_options)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -154,6 +156,12 @@ async def async_unload_entry(hass, config_entry):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_update_options(hass, config_entry):
|
||||||
|
"""Handle an options update."""
|
||||||
|
airvisual = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
||||||
|
airvisual.async_update_options(config_entry.options)
|
||||||
|
|
||||||
|
|
||||||
class AirVisualData:
|
class AirVisualData:
|
||||||
"""Define a class to manage data from the AirVisual cloud API."""
|
"""Define a class to manage data from the AirVisual cloud API."""
|
||||||
|
|
||||||
@ -162,7 +170,7 @@ class AirVisualData:
|
|||||||
self._client = client
|
self._client = client
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.show_on_map = config_entry.options[CONF_SHOW_ON_MAP]
|
self.options = config_entry.options
|
||||||
|
|
||||||
self.geographies = {
|
self.geographies = {
|
||||||
async_get_geography_id(geography): geography
|
async_get_geography_id(geography): geography
|
||||||
@ -199,3 +207,9 @@ class AirVisualData:
|
|||||||
|
|
||||||
_LOGGER.debug("Received new data")
|
_LOGGER.debug("Received new data")
|
||||||
async_dispatcher_send(self._hass, TOPIC_UPDATE)
|
async_dispatcher_send(self._hass, TOPIC_UPDATE)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_update_options(self, options):
|
||||||
|
"""Update the data manager's options."""
|
||||||
|
self.options = options
|
||||||
|
async_dispatcher_send(self._hass, TOPIC_UPDATE)
|
||||||
|
@ -6,7 +6,12 @@ from pyairvisual.errors import InvalidKeyError
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
from homeassistant.const import (
|
||||||
|
CONF_API_KEY,
|
||||||
|
CONF_LATITUDE,
|
||||||
|
CONF_LONGITUDE,
|
||||||
|
CONF_SHOW_ON_MAP,
|
||||||
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
|
|
||||||
@ -16,7 +21,7 @@ _LOGGER = logging.getLogger("homeassistant.components.airvisual")
|
|||||||
|
|
||||||
|
|
||||||
class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a AirVisual config flow."""
|
"""Handle an AirVisual config flow."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
@ -48,6 +53,12 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
step_id="user", data_schema=self.cloud_api_schema, errors=errors or {},
|
step_id="user", data_schema=self.cloud_api_schema, errors=errors or {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry):
|
||||||
|
"""Define the config flow to handle options."""
|
||||||
|
return AirVisualOptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
async def async_step_import(self, import_config):
|
async def async_step_import(self, import_config):
|
||||||
"""Import a config entry from configuration.yaml."""
|
"""Import a config entry from configuration.yaml."""
|
||||||
return await self.async_step_user(import_config)
|
return await self.async_step_user(import_config)
|
||||||
@ -85,3 +96,28 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=f"Cloud API (API key: {user_input[CONF_API_KEY][:4]}...)", data=data
|
title=f"Cloud API (API key: {user_input[CONF_API_KEY][:4]}...)", data=data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
"""Handle an AirVisual options flow."""
|
||||||
|
|
||||||
|
def __init__(self, config_entry):
|
||||||
|
"""Initialize."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
"""Manage the options."""
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="init",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_SHOW_ON_MAP,
|
||||||
|
default=self.config_entry.options.get(CONF_SHOW_ON_MAP),
|
||||||
|
): bool
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@ -11,6 +11,7 @@ from homeassistant.const import (
|
|||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
CONF_LATITUDE,
|
CONF_LATITUDE,
|
||||||
CONF_LONGITUDE,
|
CONF_LONGITUDE,
|
||||||
|
CONF_SHOW_ON_MAP,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
@ -110,15 +111,6 @@ class AirVisualSensor(Entity):
|
|||||||
ATTR_COUNTRY: airvisual.data[geography_id].get(CONF_COUNTRY),
|
ATTR_COUNTRY: airvisual.data[geography_id].get(CONF_COUNTRY),
|
||||||
}
|
}
|
||||||
|
|
||||||
geography = airvisual.geographies[geography_id]
|
|
||||||
if geography.get(CONF_LATITUDE):
|
|
||||||
if airvisual.show_on_map:
|
|
||||||
self._attrs[ATTR_LATITUDE] = geography[CONF_LATITUDE]
|
|
||||||
self._attrs[ATTR_LONGITUDE] = geography[CONF_LONGITUDE]
|
|
||||||
else:
|
|
||||||
self._attrs["lati"] = geography[CONF_LATITUDE]
|
|
||||||
self._attrs["long"] = geography[CONF_LONGITUDE]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
@ -199,6 +191,19 @@ class AirVisualSensor(Entity):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
geography = self._airvisual.geographies[self._geography_id]
|
||||||
|
if CONF_LATITUDE in geography:
|
||||||
|
if self._airvisual.options[CONF_SHOW_ON_MAP]:
|
||||||
|
self._attrs[ATTR_LATITUDE] = geography[CONF_LATITUDE]
|
||||||
|
self._attrs[ATTR_LONGITUDE] = geography[CONF_LONGITUDE]
|
||||||
|
self._attrs.pop("lati", None)
|
||||||
|
self._attrs.pop("long", None)
|
||||||
|
else:
|
||||||
|
self._attrs["lati"] = geography[CONF_LATITUDE]
|
||||||
|
self._attrs["long"] = geography[CONF_LONGITUDE]
|
||||||
|
self._attrs.pop(ATTR_LATITUDE, None)
|
||||||
|
self._attrs.pop(ATTR_LONGITUDE, None)
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self) -> None:
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
"""Disconnect dispatcher listener when removed."""
|
"""Disconnect dispatcher listener when removed."""
|
||||||
for cancel in self._async_unsub_dispatcher_connects:
|
for cancel in self._async_unsub_dispatcher_connects:
|
||||||
|
@ -8,8 +8,7 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"api_key": "API Key",
|
"api_key": "API Key",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"longitude": "Longitude",
|
"longitude": "Longitude"
|
||||||
"show_on_map": "Show monitored geography on the map"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -19,5 +18,16 @@
|
|||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "This API key is already in use."
|
"already_configured": "This API key is already in use."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Configure AirVisual",
|
||||||
|
"description": "Set various options for the AirVisual integration.",
|
||||||
|
"data": {
|
||||||
|
"show_on_map": "Show monitored geography on the map"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,12 @@ from pyairvisual.errors import InvalidKeyError
|
|||||||
from homeassistant import data_entry_flow
|
from homeassistant import data_entry_flow
|
||||||
from homeassistant.components.airvisual import CONF_GEOGRAPHIES, DOMAIN
|
from homeassistant.components.airvisual import CONF_GEOGRAPHIES, DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
from homeassistant.const import (
|
||||||
|
CONF_API_KEY,
|
||||||
|
CONF_LATITUDE,
|
||||||
|
CONF_LONGITUDE,
|
||||||
|
CONF_SHOW_ON_MAP,
|
||||||
|
)
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -37,6 +42,34 @@ async def test_invalid_api_key(hass):
|
|||||||
assert result["errors"] == {CONF_API_KEY: "invalid_api_key"}
|
assert result["errors"] == {CONF_API_KEY: "invalid_api_key"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_options_flow(hass):
|
||||||
|
"""Test config flow options."""
|
||||||
|
conf = {CONF_API_KEY: "abcde12345"}
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="abcde12345",
|
||||||
|
data=conf,
|
||||||
|
options={CONF_SHOW_ON_MAP: True},
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.airvisual.async_setup_entry", return_value=True
|
||||||
|
):
|
||||||
|
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={CONF_SHOW_ON_MAP: False}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert config_entry.options == {CONF_SHOW_ON_MAP: False}
|
||||||
|
|
||||||
|
|
||||||
async def test_show_form(hass):
|
async def test_show_form(hass):
|
||||||
"""Test that the form is served with no input."""
|
"""Test that the form is served with no input."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@ -49,10 +82,13 @@ async def test_show_form(hass):
|
|||||||
|
|
||||||
async def test_step_import(hass):
|
async def test_step_import(hass):
|
||||||
"""Test that the import step works."""
|
"""Test that the import step works."""
|
||||||
conf = {CONF_API_KEY: "abcde12345"}
|
conf = {
|
||||||
|
CONF_API_KEY: "abcde12345",
|
||||||
|
CONF_GEOGRAPHIES: [{CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765}],
|
||||||
|
}
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.wwlln.async_setup_entry", return_value=True
|
"homeassistant.components.airvisual.async_setup_entry", return_value=True
|
||||||
), patch("pyairvisual.api.API.nearest_city"):
|
), patch("pyairvisual.api.API.nearest_city"):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
||||||
@ -62,7 +98,7 @@ async def test_step_import(hass):
|
|||||||
assert result["title"] == "Cloud API (API key: abcd...)"
|
assert result["title"] == "Cloud API (API key: abcd...)"
|
||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
CONF_API_KEY: "abcde12345",
|
CONF_API_KEY: "abcde12345",
|
||||||
CONF_GEOGRAPHIES: [{CONF_LATITUDE: 32.87336, CONF_LONGITUDE: -117.22743}],
|
CONF_GEOGRAPHIES: [{CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765}],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -75,7 +111,7 @@ async def test_step_user(hass):
|
|||||||
}
|
}
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.wwlln.async_setup_entry", return_value=True
|
"homeassistant.components.airvisual.async_setup_entry", return_value=True
|
||||||
), patch("pyairvisual.api.API.nearest_city"):
|
), patch("pyairvisual.api.API.nearest_city"):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": SOURCE_USER}, data=conf
|
DOMAIN, context={"source": SOURCE_USER}, data=conf
|
||||||
|
Loading…
x
Reference in New Issue
Block a user