From 14c7fa888225e48280ffa7b1e77253467ef8f8e6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 2 May 2018 20:23:07 +0200 Subject: [PATCH] WUnderground unique ids (#13311) * WUnderground unique_id * Remove async_generate_entity_id * Lint * Address comment --- .../components/sensor/wunderground.py | 48 +++++++++---------- tests/components/sensor/test_wunderground.py | 24 +++++++++- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py index bbee167d4b0..7f2df4bcda9 100644 --- a/homeassistant/components/sensor/wunderground.py +++ b/homeassistant/components/sensor/wunderground.py @@ -15,13 +15,13 @@ 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.components.sensor import PLATFORM_SCHEMA 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, CONF_ENTITY_NAMESPACE) + LENGTH_MILES, LENGTH_FEET, ATTR_ATTRIBUTION) from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.entity import Entity, async_generate_entity_id +from homeassistant.helpers.entity import Entity from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -618,8 +618,6 @@ 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, @@ -629,31 +627,30 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Inclusive(CONF_LONGITUDE, 'coordinates', '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, + vol.All(cv.ensure_list, vol.Length(min=1), [vol.In(SENSOR_TYPES)]) }) -# Stores a list of entity ids we added in order to support multiple stations -# at once. -ADDED_ENTITY_IDS_KEY = 'wunderground_added_entity_ids' - async 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) + pws_id = config.get(CONF_PWS_ID) rest = WUndergroundData( - hass, config.get(CONF_API_KEY), config.get(CONF_PWS_ID), + hass, config.get(CONF_API_KEY), pws_id, config.get(CONF_LANG), latitude, longitude) + + if pws_id is None: + unique_id_base = "@{:06f},{:06f}".format(longitude, latitude) + else: + # Manually specified weather station, use that for unique_id + unique_id_base = pws_id sensors = [] for variable in config[CONF_MONITORED_CONDITIONS]: - sensors.append(WUndergroundSensor(hass, rest, variable, namespace)) + sensors.append(WUndergroundSensor(hass, rest, variable, + unique_id_base)) await rest.async_update() if not rest.data: @@ -666,7 +663,7 @@ class WUndergroundSensor(Entity): """Implementing the WUnderground sensor.""" def __init__(self, hass: HomeAssistantType, rest, condition, - namespace: str): + unique_id_base: str): """Initialize the sensor.""" self.rest = rest self._condition = condition @@ -678,12 +675,10 @@ 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, "{} {}".format(namespace, condition), - current_ids=current_ids) - hass.data[ADDED_ENTITY_IDS_KEY].add(self.entity_id) + # This is only the suggested entity id, it might get changed by + # the entity registry later. + self.entity_id = sensor.ENTITY_ID_FORMAT.format('pws_' + condition) + self._unique_id = "{},{}".format(unique_id_base, condition) def _cfg_expand(self, what, default=None): """Parse and return sensor data.""" @@ -763,6 +758,11 @@ class WUndergroundSensor(Entity): self._entity_picture = re.sub(r'^http://', 'https://', url, flags=re.IGNORECASE) + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return self._unique_id + class WUndergroundData(object): """Get data from WUnderground.""" diff --git a/tests/components/sensor/test_wunderground.py b/tests/components/sensor/test_wunderground.py index 65526e2d938..3f490b4ab12 100644 --- a/tests/components/sensor/test_wunderground.py +++ b/tests/components/sensor/test_wunderground.py @@ -148,10 +148,11 @@ def test_invalid_data(hass, aioclient_mock): 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')) + aioclient_mock.get(PWS_URL, text=load_fixture('wunderground-valid.json')) config = [ VALID_CONFIG, - {**VALID_CONFIG, 'entity_namespace': 'hi'} + {**VALID_CONFIG_PWS, 'entity_namespace': 'hi'} ] await async_setup_component(hass, 'sensor', {'sensor': config}) await hass.async_block_till_done() @@ -160,6 +161,25 @@ async def test_entity_id_with_multiple_stations(hass, aioclient_mock): assert state is not None assert state.state == 'Clear' - state = hass.states.get('sensor.hi_weather') + state = hass.states.get('sensor.hi_pws_weather') assert state is not None assert state.state == 'Clear' + + +async def test_fails_because_of_unique_id(hass, aioclient_mock): + """Test same config twice fails because of unique_id.""" + aioclient_mock.get(URL, text=load_fixture('wunderground-valid.json')) + aioclient_mock.get(PWS_URL, text=load_fixture('wunderground-valid.json')) + + config = [ + VALID_CONFIG, + {**VALID_CONFIG, 'entity_namespace': 'hi'}, + VALID_CONFIG_PWS + ] + await async_setup_component(hass, 'sensor', {'sensor': config}) + await hass.async_block_till_done() + + states = hass.states.async_all() + expected = len(VALID_CONFIG['monitored_conditions']) + \ + len(VALID_CONFIG_PWS['monitored_conditions']) + assert len(states) == expected