Add ability to configure map icons for PurpleAir (#86124)

This commit is contained in:
Aaron Bach 2023-06-30 08:35:19 -06:00 committed by GitHub
parent 522d2496df
commit 39e0662fc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 11 deletions

View File

@ -1,6 +1,9 @@
"""The PurpleAir integration."""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from aiopurpleair.models.sensors import SensorModel
from homeassistant.config_entries import ConfigEntry
@ -9,7 +12,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .const import CONF_SHOW_ON_MAP, DOMAIN
from .coordinator import PurpleAirDataUpdateCoordinator
PLATFORMS = [Platform.SENSOR]
@ -60,16 +63,30 @@ class PurpleAirEntity(CoordinatorEntity[PurpleAirDataUpdateCoordinator]):
self._attr_device_info = DeviceInfo(
configuration_url=self.coordinator.async_get_map_url(sensor_index),
hw_version=self.sensor_data.hardware,
identifiers={(DOMAIN, str(self._sensor_index))},
identifiers={(DOMAIN, str(sensor_index))},
manufacturer="PurpleAir, Inc.",
model=self.sensor_data.model,
name=self.sensor_data.name,
sw_version=self.sensor_data.firmware_version,
)
self._attr_extra_state_attributes = {
ATTR_LATITUDE: self.sensor_data.latitude,
ATTR_LONGITUDE: self.sensor_data.longitude,
}
self._entry = entry
@property
def extra_state_attributes(self) -> Mapping[str, Any]:
"""Return entity specific state attributes."""
attrs = {}
# Displaying the geography on the map relies upon putting the latitude/longitude
# in the entity attributes with "latitude" and "longitude" as the keys.
# Conversely, we can hide the location on the map by using other keys, like
# "lati" and "long":
if self._entry.options.get(CONF_SHOW_ON_MAP):
attrs[ATTR_LATITUDE] = self.sensor_data.latitude
attrs[ATTR_LONGITUDE] = self.sensor_data.longitude
else:
attrs["lati"] = self.sensor_data.latitude
attrs["long"] = self.sensor_data.longitude
return attrs
@property
def sensor_data(self) -> SensorModel:

View File

@ -31,7 +31,7 @@ from homeassistant.helpers.selector import (
SelectSelectorMode,
)
from .const import CONF_SENSOR_INDICES, DOMAIN, LOGGER
from .const import CONF_SENSOR_INDICES, CONF_SHOW_ON_MAP, DOMAIN, LOGGER
CONF_DISTANCE = "distance"
CONF_NEARBY_SENSOR_OPTIONS = "nearby_sensor_options"
@ -318,6 +318,22 @@ class PurpleAirOptionsFlowHandler(config_entries.OptionsFlow):
self._flow_data: dict[str, Any] = {}
self.config_entry = config_entry
@property
def settings_schema(self) -> vol.Schema:
"""Return the settings schema."""
return vol.Schema(
{
vol.Optional(
CONF_SHOW_ON_MAP,
description={
"suggested_value": self.config_entry.options.get(
CONF_SHOW_ON_MAP
)
},
): bool
}
)
async def async_step_add_sensor(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
@ -352,7 +368,7 @@ class PurpleAirOptionsFlowHandler(config_entries.OptionsFlow):
async def async_step_choose_sensor(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the selection of a sensor."""
"""Choose a sensor."""
if user_input is None:
options = self._flow_data.pop(CONF_NEARBY_SENSOR_OPTIONS)
return self.async_show_form(
@ -375,13 +391,13 @@ class PurpleAirOptionsFlowHandler(config_entries.OptionsFlow):
"""Manage the options."""
return self.async_show_menu(
step_id="init",
menu_options=["add_sensor", "remove_sensor"],
menu_options=["add_sensor", "remove_sensor", "settings"],
)
async def async_step_remove_sensor(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Add a sensor."""
"""Remove a sensor."""
if user_input is None:
return self.async_show_form(
step_id="remove_sensor",
@ -437,3 +453,15 @@ class PurpleAirOptionsFlowHandler(config_entries.OptionsFlow):
options[CONF_SENSOR_INDICES].remove(removed_sensor_index)
return self.async_create_entry(data=options)
async def async_step_settings(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage settings."""
if user_input is None:
return self.async_show_form(
step_id="settings", data_schema=self.settings_schema
)
options = deepcopy({**self.config_entry.options})
return self.async_create_entry(data=options | user_input)

View File

@ -7,3 +7,4 @@ LOGGER = logging.getLogger(__package__)
CONF_READ_KEY = "read_key"
CONF_SENSOR_INDICES = "sensor_indices"
CONF_SHOW_ON_MAP = "show_on_map"

View File

@ -79,7 +79,8 @@
"init": {
"menu_options": {
"add_sensor": "Add sensor",
"remove_sensor": "Remove sensor"
"remove_sensor": "Remove sensor",
"settings": "Settings"
}
},
"remove_sensor": {
@ -90,6 +91,12 @@
"data_description": {
"sensor_device_id": "The sensor to remove"
}
},
"settings": {
"title": "Settings",
"data": {
"show_on_map": "Show configured sensor locations on the map"
}
}
},
"error": {

View File

@ -302,3 +302,29 @@ async def test_options_remove_sensor(
# Unload to make sure the update does not run after the
# mock is removed.
await hass.config_entries.async_unload(config_entry.entry_id)
async def test_options_settings(
hass: HomeAssistant, config_entry, setup_config_entry
) -> None:
"""Test setting settings via the options flow."""
result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] == data_entry_flow.FlowResultType.MENU
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={"next_step_id": "settings"}
)
assert result["type"] == data_entry_flow.FlowResultType.FORM
assert result["step_id"] == "settings"
result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input={"show_on_map": True}
)
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"] == {
"sensor_indices": [TEST_SENSOR_INDEX1],
"show_on_map": True,
}
assert config_entry.options["show_on_map"] is True