Fix WUnderground duplicate entity ids (#13285)

* Fix WUnderground duplicate entity ids

* Entity Namespace
This commit is contained in:
Otto Winter 2018-03-17 13:14:53 +01:00 committed by GitHub
parent f5093b474a
commit 3442b6741d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 45 additions and 8 deletions

View File

@ -14,11 +14,12 @@ import async_timeout
import voluptuous as vol
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.components import sensor
from homeassistant.components.sensor import PLATFORM_SCHEMA, ENTITY_ID_FORMAT
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE,
TEMP_FAHRENHEIT, TEMP_CELSIUS, LENGTH_INCHES, LENGTH_KILOMETERS,
LENGTH_MILES, LENGTH_FEET, ATTR_ATTRIBUTION)
LENGTH_MILES, LENGTH_FEET, ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -617,6 +618,8 @@ LANG_CODES = [
'CY', 'SN', 'JI', 'YI',
]
DEFAULT_ENTITY_NAMESPACE = 'pws'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_PWS_ID): cv.string,
@ -627,22 +630,31 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
'Latitude and longitude must exist together'): cv.longitude,
vol.Required(CONF_MONITORED_CONDITIONS):
vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES)]),
vol.Optional(CONF_ENTITY_NAMESPACE,
default=DEFAULT_ENTITY_NAMESPACE): cv.string,
})
# Stores a list of entity ids we added in order to support multiple stations
# at once.
ADDED_ENTITY_IDS_KEY = 'wunderground_added_entity_ids'
@asyncio.coroutine
def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
async_add_devices, discovery_info=None):
"""Set up the WUnderground sensor."""
hass.data.setdefault(ADDED_ENTITY_IDS_KEY, set())
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
namespace = config.get(CONF_ENTITY_NAMESPACE)
rest = WUndergroundData(
hass, config.get(CONF_API_KEY), config.get(CONF_PWS_ID),
config.get(CONF_LANG), latitude, longitude)
sensors = []
for variable in config[CONF_MONITORED_CONDITIONS]:
sensors.append(WUndergroundSensor(hass, rest, variable))
sensors.append(WUndergroundSensor(hass, rest, variable, namespace))
yield from rest.async_update()
if not rest.data:
@ -654,7 +666,8 @@ def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
class WUndergroundSensor(Entity):
"""Implementing the WUnderground sensor."""
def __init__(self, hass: HomeAssistantType, rest, condition):
def __init__(self, hass: HomeAssistantType, rest, condition,
namespace: str):
"""Initialize the sensor."""
self.rest = rest
self._condition = condition
@ -666,8 +679,12 @@ class WUndergroundSensor(Entity):
self._entity_picture = None
self._unit_of_measurement = self._cfg_expand("unit_of_measurement")
self.rest.request_feature(SENSOR_TYPES[condition].feature)
current_ids = set(hass.states.async_entity_ids(sensor.DOMAIN))
current_ids |= hass.data[ADDED_ENTITY_IDS_KEY]
self.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, "pws_" + condition, hass=hass)
ENTITY_ID_FORMAT, "{} {}".format(namespace, condition),
current_ids=current_ids)
hass.data[ADDED_ENTITY_IDS_KEY].add(self.entity_id)
def _cfg_expand(self, what, default=None):
"""Parse and return sensor data."""

View File

@ -4,7 +4,7 @@ import logging
import functools as ft
from timeit import default_timer as timer
from typing import Optional, List
from typing import Optional, List, Iterable
from homeassistant.const import (
ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ICON,
@ -42,7 +42,7 @@ def generate_entity_id(entity_id_format: str, name: Optional[str],
@callback
def async_generate_entity_id(entity_id_format: str, name: Optional[str],
current_ids: Optional[List[str]] = None,
current_ids: Optional[Iterable[str]] = None,
hass: Optional[HomeAssistant] = None) -> str:
"""Generate a unique entity ID based on given entity IDs or used IDs."""
if current_ids is None:

View File

@ -13,7 +13,7 @@ from functools import wraps
from types import MappingProxyType
from unicodedata import normalize
from typing import Any, Optional, TypeVar, Callable, Sequence, KeysView, Union
from typing import Any, Optional, TypeVar, Callable, KeysView, Union, Iterable
from .dt import as_local, utcnow
@ -72,7 +72,7 @@ def convert(value: T, to_type: Callable[[T], U],
def ensure_unique_string(preferred_string: str, current_strings:
Union[Sequence[str], KeysView[str]]) -> str:
Union[Iterable[str], KeysView[str]]) -> str:
"""Return a string that is not present in current_strings.
If preferred string exists will append _2, _3, ..

View File

@ -143,3 +143,23 @@ def test_invalid_data(hass, aioclient_mock):
for condition in VALID_CONFIG['monitored_conditions']:
state = hass.states.get('sensor.pws_' + condition)
assert state.state == STATE_UNKNOWN
async def test_entity_id_with_multiple_stations(hass, aioclient_mock):
"""Test not generating duplicate entity ids with multiple stations."""
aioclient_mock.get(URL, text=load_fixture('wunderground-valid.json'))
config = [
VALID_CONFIG,
{**VALID_CONFIG, 'entity_namespace': 'hi'}
]
await async_setup_component(hass, 'sensor', {'sensor': config})
await hass.async_block_till_done()
state = hass.states.get('sensor.pws_weather')
assert state is not None
assert state.state == 'Clear'
state = hass.states.get('sensor.hi_weather')
assert state is not None
assert state.state == 'Clear'