diff --git a/homeassistant/components/google_weather/__init__.py b/homeassistant/components/google_weather/__init__.py index 97d64ff676f..8f2c5a2b094 100644 --- a/homeassistant/components/google_weather/__init__.py +++ b/homeassistant/components/google_weather/__init__.py @@ -20,7 +20,7 @@ from .coordinator import ( GoogleWeatherSubEntryRuntimeData, ) -_PLATFORMS: list[Platform] = [Platform.WEATHER] +_PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.WEATHER] async def async_setup_entry( diff --git a/homeassistant/components/google_weather/entity.py b/homeassistant/components/google_weather/entity.py index 2d6104d280f..3b3da6e0c5e 100644 --- a/homeassistant/components/google_weather/entity.py +++ b/homeassistant/components/google_weather/entity.py @@ -16,10 +16,15 @@ class GoogleWeatherBaseEntity(Entity): _attr_has_entity_name = True def __init__( - self, config_entry: GoogleWeatherConfigEntry, subentry: ConfigSubentry + self, + config_entry: GoogleWeatherConfigEntry, + subentry: ConfigSubentry, + unique_id_suffix: str | None = None, ) -> None: """Initialize base entity.""" self._attr_unique_id = subentry.subentry_id + if unique_id_suffix is not None: + self._attr_unique_id += f"_{unique_id_suffix.lower()}" self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, subentry.subentry_id)}, name=subentry.title, diff --git a/homeassistant/components/google_weather/icons.json b/homeassistant/components/google_weather/icons.json new file mode 100644 index 00000000000..b8927a1578f --- /dev/null +++ b/homeassistant/components/google_weather/icons.json @@ -0,0 +1,27 @@ +{ + "entity": { + "sensor": { + "cloud_coverage": { + "default": "mdi:weather-cloudy" + }, + "precipitation_probability": { + "default": "mdi:weather-rainy" + }, + "precipitation_qpf": { + "default": "mdi:cup-water" + }, + "thunderstorm_probability": { + "default": "mdi:weather-lightning" + }, + "uv_index": { + "default": "mdi:weather-sunny-alert" + }, + "visibility": { + "default": "mdi:eye" + }, + "weather_condition": { + "default": "mdi:card-text-outline" + } + } + } +} diff --git a/homeassistant/components/google_weather/sensor.py b/homeassistant/components/google_weather/sensor.py new file mode 100644 index 00000000000..12b3b4bcce2 --- /dev/null +++ b/homeassistant/components/google_weather/sensor.py @@ -0,0 +1,233 @@ +"""Support for Google Weather sensors.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from google_weather_api import CurrentConditionsResponse + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigSubentry +from homeassistant.const import ( + DEGREE, + PERCENTAGE, + UV_INDEX, + UnitOfLength, + UnitOfPressure, + UnitOfSpeed, + UnitOfTemperature, + UnitOfVolumetricFlux, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .coordinator import ( + GoogleWeatherConfigEntry, + GoogleWeatherCurrentConditionsCoordinator, +) +from .entity import GoogleWeatherBaseEntity + +PARALLEL_UPDATES = 0 + + +@dataclass(frozen=True, kw_only=True) +class GoogleWeatherSensorDescription(SensorEntityDescription): + """Class describing Google Weather sensor entities.""" + + value_fn: Callable[[CurrentConditionsResponse], str | int | float | None] + + +SENSOR_TYPES: tuple[GoogleWeatherSensorDescription, ...] = ( + GoogleWeatherSensorDescription( + key="temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + value_fn=lambda data: data.temperature.degrees, + ), + GoogleWeatherSensorDescription( + key="feelsLikeTemperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + value_fn=lambda data: data.feels_like_temperature.degrees, + translation_key="apparent_temperature", + ), + GoogleWeatherSensorDescription( + key="dewPoint", + device_class=SensorDeviceClass.TEMPERATURE, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + value_fn=lambda data: data.dew_point.degrees, + translation_key="dew_point", + ), + GoogleWeatherSensorDescription( + key="heatIndex", + device_class=SensorDeviceClass.TEMPERATURE, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + value_fn=lambda data: data.heat_index.degrees, + translation_key="heat_index", + ), + GoogleWeatherSensorDescription( + key="windChill", + device_class=SensorDeviceClass.TEMPERATURE, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + value_fn=lambda data: data.wind_chill.degrees, + translation_key="wind_chill", + ), + GoogleWeatherSensorDescription( + key="relativeHumidity", + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + value_fn=lambda data: data.relative_humidity, + ), + GoogleWeatherSensorDescription( + key="uvIndex", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UV_INDEX, + value_fn=lambda data: data.uv_index, + translation_key="uv_index", + ), + GoogleWeatherSensorDescription( + key="precipitation_probability", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + value_fn=lambda data: data.precipitation.probability.percent, + translation_key="precipitation_probability", + ), + GoogleWeatherSensorDescription( + key="precipitation_qpf", + device_class=SensorDeviceClass.PRECIPITATION_INTENSITY, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR, + value_fn=lambda data: data.precipitation.qpf.quantity, + ), + GoogleWeatherSensorDescription( + key="thunderstormProbability", + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + value_fn=lambda data: data.thunderstorm_probability, + translation_key="thunderstorm_probability", + ), + GoogleWeatherSensorDescription( + key="airPressure", + device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=0, + native_unit_of_measurement=UnitOfPressure.HPA, + value_fn=lambda data: data.air_pressure.mean_sea_level_millibars, + ), + GoogleWeatherSensorDescription( + key="wind_direction", + device_class=SensorDeviceClass.WIND_DIRECTION, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT_ANGLE, + native_unit_of_measurement=DEGREE, + value_fn=lambda data: data.wind.direction.degrees, + ), + GoogleWeatherSensorDescription( + key="wind_speed", + device_class=SensorDeviceClass.WIND_SPEED, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR, + value_fn=lambda data: data.wind.speed.value, + ), + GoogleWeatherSensorDescription( + key="wind_gust", + device_class=SensorDeviceClass.WIND_SPEED, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR, + value_fn=lambda data: data.wind.gust.value, + translation_key="wind_gust_speed", + ), + GoogleWeatherSensorDescription( + key="visibility", + device_class=SensorDeviceClass.DISTANCE, + entity_registry_enabled_default=False, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfLength.KILOMETERS, + value_fn=lambda data: data.visibility.distance, + translation_key="visibility", + ), + GoogleWeatherSensorDescription( + key="cloudCover", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + value_fn=lambda data: data.cloud_cover, + translation_key="cloud_coverage", + ), + GoogleWeatherSensorDescription( + key="weatherCondition", + entity_registry_enabled_default=False, + value_fn=lambda data: data.weather_condition.description.text, + translation_key="weather_condition", + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: GoogleWeatherConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Add Google Weather entities from a config_entry.""" + for subentry in entry.subentries.values(): + subentry_runtime_data = entry.runtime_data.subentries_runtime_data[ + subentry.subentry_id + ] + coordinator = subentry_runtime_data.coordinator_observation + async_add_entities( + ( + GoogleWeatherSensor(coordinator, subentry, description) + for description in SENSOR_TYPES + if description.value_fn(coordinator.data) is not None + ), + config_subentry_id=subentry.subentry_id, + ) + + +class GoogleWeatherSensor( + CoordinatorEntity[GoogleWeatherCurrentConditionsCoordinator], + GoogleWeatherBaseEntity, + SensorEntity, +): + """Define a Google Weather entity.""" + + entity_description: GoogleWeatherSensorDescription + + def __init__( + self, + coordinator: GoogleWeatherCurrentConditionsCoordinator, + subentry: ConfigSubentry, + description: GoogleWeatherSensorDescription, + ) -> None: + """Initialize.""" + super().__init__(coordinator) + GoogleWeatherBaseEntity.__init__( + self, coordinator.config_entry, subentry, description.key + ) + self.entity_description = description + + @property + def native_value(self) -> str | int | float | None: + """Return the state.""" + return self.entity_description.value_fn(self.coordinator.data) diff --git a/homeassistant/components/google_weather/strings.json b/homeassistant/components/google_weather/strings.json index db0531f8015..7f23f297544 100644 --- a/homeassistant/components/google_weather/strings.json +++ b/homeassistant/components/google_weather/strings.json @@ -61,5 +61,42 @@ } } } + }, + "entity": { + "sensor": { + "apparent_temperature": { + "name": "Apparent temperature" + }, + "cloud_coverage": { + "name": "Cloud coverage" + }, + "dew_point": { + "name": "Dew point" + }, + "heat_index": { + "name": "Heat index temperature" + }, + "precipitation_probability": { + "name": "Precipitation probability" + }, + "thunderstorm_probability": { + "name": "Thunderstorm probability" + }, + "uv_index": { + "name": "UV index" + }, + "visibility": { + "name": "Visibility" + }, + "weather_condition": { + "name": "Weather condition" + }, + "wind_chill": { + "name": "Wind chill temperature" + }, + "wind_gust_speed": { + "name": "Wind gust speed" + } + } } } diff --git a/tests/components/google_weather/snapshots/test_sensor.ambr b/tests/components/google_weather/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..ceb02d99754 --- /dev/null +++ b/tests/components/google_weather/snapshots/test_sensor.ambr @@ -0,0 +1,923 @@ +# serializer version: 1 +# name: test_sensor[sensor.home_apparent_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_apparent_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Apparent temperature', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'apparent_temperature', + 'unique_id': 'home-subentry-id_feelsliketemperature', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.home_apparent_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Home Apparent temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_apparent_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '13.1', + }) +# --- +# name: test_sensor[sensor.home_atmospheric_pressure-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_atmospheric_pressure', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Atmospheric pressure', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'home-subentry-id_airpressure', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.home_atmospheric_pressure-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'atmospheric_pressure', + 'friendly_name': 'Home Atmospheric pressure', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_atmospheric_pressure', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1019.16', + }) +# --- +# name: test_sensor[sensor.home_cloud_coverage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_cloud_coverage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Cloud coverage', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cloud_coverage', + 'unique_id': 'home-subentry-id_cloudcover', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor[sensor.home_cloud_coverage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Home Cloud coverage', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.home_cloud_coverage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensor[sensor.home_dew_point-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_dew_point', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Dew point', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dew_point', + 'unique_id': 'home-subentry-id_dewpoint', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.home_dew_point-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Home Dew point', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_dew_point', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1.1', + }) +# --- +# name: test_sensor[sensor.home_heat_index_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_heat_index_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Heat index temperature', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'heat_index', + 'unique_id': 'home-subentry-id_heatindex', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.home_heat_index_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Home Heat index temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_heat_index_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '13.7', + }) +# --- +# name: test_sensor[sensor.home_humidity-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_humidity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Humidity', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'home-subentry-id_relativehumidity', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor[sensor.home_humidity-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'humidity', + 'friendly_name': 'Home Humidity', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.home_humidity', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '42', + }) +# --- +# name: test_sensor[sensor.home_precipitation_intensity-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_precipitation_intensity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Precipitation intensity', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'home-subentry-id_precipitation_qpf', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.home_precipitation_intensity-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'precipitation_intensity', + 'friendly_name': 'Home Precipitation intensity', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_precipitation_intensity', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_sensor[sensor.home_precipitation_probability-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_precipitation_probability', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Precipitation probability', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'precipitation_probability', + 'unique_id': 'home-subentry-id_precipitation_probability', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor[sensor.home_precipitation_probability-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Home Precipitation probability', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.home_precipitation_probability', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensor[sensor.home_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Temperature', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'home-subentry-id_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.home_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Home Temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '13.7', + }) +# --- +# name: test_sensor[sensor.home_thunderstorm_probability-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_thunderstorm_probability', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Thunderstorm probability', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'thunderstorm_probability', + 'unique_id': 'home-subentry-id_thunderstormprobability', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor[sensor.home_thunderstorm_probability-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Home Thunderstorm probability', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.home_thunderstorm_probability', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_sensor[sensor.home_uv_index-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_uv_index', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'UV index', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'uv_index', + 'unique_id': 'home-subentry-id_uvindex', + 'unit_of_measurement': 'UV index', + }) +# --- +# name: test_sensor[sensor.home_uv_index-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Home UV index', + 'state_class': , + 'unit_of_measurement': 'UV index', + }), + 'context': , + 'entity_id': 'sensor.home_uv_index', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1', + }) +# --- +# name: test_sensor[sensor.home_visibility-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_visibility', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Visibility', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'visibility', + 'unique_id': 'home-subentry-id_visibility', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.home_visibility-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'distance', + 'friendly_name': 'Home Visibility', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_visibility', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '16.0', + }) +# --- +# name: test_sensor[sensor.home_weather_condition-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_weather_condition', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Weather condition', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'weather_condition', + 'unique_id': 'home-subentry-id_weathercondition', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[sensor.home_weather_condition-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Home Weather condition', + }), + 'context': , + 'entity_id': 'sensor.home_weather_condition', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'Sunny', + }) +# --- +# name: test_sensor[sensor.home_wind_chill_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_wind_chill_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Wind chill temperature', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'wind_chill', + 'unique_id': 'home-subentry-id_windchill', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.home_wind_chill_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Home Wind chill temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_wind_chill_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '13.1', + }) +# --- +# name: test_sensor[sensor.home_wind_direction-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_wind_direction', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Wind direction', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'home-subentry-id_wind_direction', + 'unit_of_measurement': '°', + }) +# --- +# name: test_sensor[sensor.home_wind_direction-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'wind_direction', + 'friendly_name': 'Home Wind direction', + 'state_class': , + 'unit_of_measurement': '°', + }), + 'context': , + 'entity_id': 'sensor.home_wind_direction', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '335', + }) +# --- +# name: test_sensor[sensor.home_wind_gust_speed-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_wind_gust_speed', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Wind gust speed', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'wind_gust_speed', + 'unique_id': 'home-subentry-id_wind_gust', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.home_wind_gust_speed-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'wind_speed', + 'friendly_name': 'Home Wind gust speed', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_wind_gust_speed', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '18.0', + }) +# --- +# name: test_sensor[sensor.home_wind_speed-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_wind_speed', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Wind speed', + 'platform': 'google_weather', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'home-subentry-id_wind_speed', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.home_wind_speed-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'wind_speed', + 'friendly_name': 'Home Wind speed', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_wind_speed', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '8.0', + }) +# --- diff --git a/tests/components/google_weather/test_sensor.py b/tests/components/google_weather/test_sensor.py new file mode 100644 index 00000000000..e96089b17c7 --- /dev/null +++ b/tests/components/google_weather/test_sensor.py @@ -0,0 +1,168 @@ +"""Test sensor of Google Weather integration.""" + +from datetime import timedelta +from unittest.mock import AsyncMock, patch + +from freezegun.api import FrozenDateTimeFactory +from google_weather_api import GoogleWeatherApiError +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_UNIT_OF_MEASUREMENT, + STATE_UNAVAILABLE, + Platform, + UnitOfLength, + UnitOfPressure, + UnitOfSpeed, + UnitOfTemperature, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.setup import async_setup_component +from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM + +from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_sensor( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, + mock_google_weather_api: AsyncMock, + snapshot: SnapshotAssertion, +) -> None: + """Test states of the sensor.""" + with patch("homeassistant.components.google_weather._PLATFORMS", [Platform.SENSOR]): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_availability( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_google_weather_api: AsyncMock, + freezer: FrozenDateTimeFactory, +) -> None: + """Ensure that we mark the entities unavailable correctly when service is offline.""" + entity_id = "sensor.home_temperature" + await hass.config_entries.async_setup(mock_config_entry.entry_id) + + state = hass.states.get(entity_id) + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "13.7" + + mock_google_weather_api.async_get_current_conditions.side_effect = ( + GoogleWeatherApiError() + ) + + freezer.tick(timedelta(minutes=15)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_UNAVAILABLE + + mock_google_weather_api.async_get_current_conditions.side_effect = None + + freezer.tick(timedelta(minutes=15)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "13.7" + mock_google_weather_api.async_get_current_conditions.assert_called_with( + latitude=10.1, longitude=20.1 + ) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_manual_update_entity( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_google_weather_api: AsyncMock, +) -> None: + """Test manual update entity via service homeassistant/update_entity.""" + await hass.config_entries.async_setup(mock_config_entry.entry_id) + + await async_setup_component(hass, "homeassistant", {}) + + mock_google_weather_api.async_get_current_conditions.assert_called_once_with( + latitude=10.1, longitude=20.1 + ) + + await hass.services.async_call( + "homeassistant", + "update_entity", + {ATTR_ENTITY_ID: ["sensor.home_temperature"]}, + blocking=True, + ) + + assert mock_google_weather_api.async_get_current_conditions.call_count == 2 + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_sensor_imperial_units( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_google_weather_api: AsyncMock, +) -> None: + """Test states of the sensor with imperial units.""" + hass.config.units = US_CUSTOMARY_SYSTEM + await hass.config_entries.async_setup(mock_config_entry.entry_id) + + state = hass.states.get("sensor.home_temperature") + assert state + assert state.state == "56.66" + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfTemperature.FAHRENHEIT + ) + + state = hass.states.get("sensor.home_wind_speed") + assert state + assert float(state.state) == pytest.approx(4.97097) + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfSpeed.MILES_PER_HOUR + + state = hass.states.get("sensor.home_visibility") + assert state + assert float(state.state) == pytest.approx(9.94194) + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfLength.MILES + + state = hass.states.get("sensor.home_atmospheric_pressure") + assert state + assert float(state.state) == pytest.approx(30.09578) + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UnitOfPressure.INHG + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_state_update( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_google_weather_api: AsyncMock, + freezer: FrozenDateTimeFactory, +) -> None: + """Ensure the sensor state changes after updating the data.""" + entity_id = "sensor.home_temperature" + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + + state = hass.states.get(entity_id) + assert state + assert state.state == "13.7" + + mock_google_weather_api.async_get_current_conditions.return_value.temperature.degrees = 15.0 + + freezer.tick(timedelta(minutes=15)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == "15.0"