diff --git a/homeassistant/components/fyta/__init__.py b/homeassistant/components/fyta/__init__.py index 1969ebfffe9cb7..77724e3f673cd6 100644 --- a/homeassistant/components/fyta/__init__.py +++ b/homeassistant/components/fyta/__init__.py @@ -24,6 +24,7 @@ _LOGGER = logging.getLogger(__name__) PLATFORMS = [ + Platform.BINARY_SENSOR, Platform.SENSOR, ] type FytaConfigEntry = ConfigEntry[FytaCoordinator] diff --git a/homeassistant/components/fyta/binary_sensor.py b/homeassistant/components/fyta/binary_sensor.py new file mode 100644 index 00000000000000..bcef609d01aa3e --- /dev/null +++ b/homeassistant/components/fyta/binary_sensor.py @@ -0,0 +1,117 @@ +"""Binary sensors for Fyta.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Final + +from fyta_cli.fyta_models import Plant + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import FytaConfigEntry +from .entity import FytaPlantEntity + + +@dataclass(frozen=True, kw_only=True) +class FytaBinarySensorEntityDescription(BinarySensorEntityDescription): + """Describes Fyta binary sensor entity.""" + + value_fn: Callable[[Plant], bool] + + +BINARY_SENSORS: Final[list[FytaBinarySensorEntityDescription]] = [ + FytaBinarySensorEntityDescription( + key="low_battery", + device_class=BinarySensorDeviceClass.BATTERY, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda plant: plant.low_battery, + ), + FytaBinarySensorEntityDescription( + key="notification_light", + translation_key="notification_light", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + value_fn=lambda plant: plant.notification_light, + ), + FytaBinarySensorEntityDescription( + key="notification_nutrition", + translation_key="notification_nutrition", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + value_fn=lambda plant: plant.notification_nutrition, + ), + FytaBinarySensorEntityDescription( + key="notification_temperature", + translation_key="notification_temperature", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + value_fn=lambda plant: plant.notification_temperature, + ), + FytaBinarySensorEntityDescription( + key="notification_water", + translation_key="notification_water", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + value_fn=lambda plant: plant.notification_water, + ), + FytaBinarySensorEntityDescription( + key="sensor_update_available", + device_class=BinarySensorDeviceClass.UPDATE, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda plant: plant.sensor_update_available, + ), + FytaBinarySensorEntityDescription( + key="productive_plant", + translation_key="productive_plant", + value_fn=lambda plant: plant.productive_plant, + ), + FytaBinarySensorEntityDescription( + key="repotted", + translation_key="repotted", + value_fn=lambda plant: plant.repotted, + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, entry: FytaConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the FYTA binary sensors.""" + coordinator = entry.runtime_data + + async_add_entities( + FytaPlantBinarySensor(coordinator, entry, sensor, plant_id) + for plant_id in coordinator.fyta.plant_list + for sensor in BINARY_SENSORS + if sensor.key in dir(coordinator.data.get(plant_id)) + ) + + def _async_add_new_device(plant_id: int) -> None: + async_add_entities( + FytaPlantBinarySensor(coordinator, entry, sensor, plant_id) + for sensor in BINARY_SENSORS + if sensor.key in dir(coordinator.data.get(plant_id)) + ) + + coordinator.new_device_callbacks.append(_async_add_new_device) + + +class FytaPlantBinarySensor(FytaPlantEntity, BinarySensorEntity): + """Represents a Fyta binary sensor.""" + + entity_description: FytaBinarySensorEntityDescription + + @property + def is_on(self) -> bool: + """Return value of the binary sensor.""" + + return self.entity_description.value_fn(self.plant) diff --git a/homeassistant/components/fyta/entity.py b/homeassistant/components/fyta/entity.py index 4c078098ec1301..0d0ec533c44d39 100644 --- a/homeassistant/components/fyta/entity.py +++ b/homeassistant/components/fyta/entity.py @@ -2,8 +2,8 @@ from fyta_cli.fyta_models import Plant -from homeassistant.components.sensor import SensorEntityDescription from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import FytaConfigEntry @@ -20,7 +20,7 @@ def __init__( self, coordinator: FytaCoordinator, entry: FytaConfigEntry, - description: SensorEntityDescription, + description: EntityDescription, plant_id: int, ) -> None: """Initialize the Fyta sensor.""" diff --git a/homeassistant/components/fyta/icons.json b/homeassistant/components/fyta/icons.json index 8bb61c63464a59..5b6380196f4c34 100644 --- a/homeassistant/components/fyta/icons.json +++ b/homeassistant/components/fyta/icons.json @@ -1,5 +1,25 @@ { "entity": { + "binary_sensor": { + "notification_light": { + "default": "mdi:lightbulb-alert-outline" + }, + "notification_nutrition": { + "default": "mdi:beaker-alert-outline" + }, + "notification_temperature": { + "default": "mdi:thermometer-alert" + }, + "notification_water": { + "default": "mdi:watering-can-outline" + }, + "productive_plant": { + "default": "mdi:fruit-grapes" + }, + "repotted": { + "default": "mdi:shovel" + } + }, "sensor": { "status": { "default": "mdi:flower" diff --git a/homeassistant/components/fyta/strings.json b/homeassistant/components/fyta/strings.json index d59b79bf92cd29..1a25f654e19e6a 100644 --- a/homeassistant/components/fyta/strings.json +++ b/homeassistant/components/fyta/strings.json @@ -38,6 +38,29 @@ } }, "entity": { + "binary_sensor": { + "notification_light": { + "name": "Light notification" + }, + "notification_nutrition": { + "name": "Nutrition notification" + }, + "notification_temperature": { + "name": "Temperature notification" + }, + "notification_water": { + "name": "Water notification" + }, + "productive_plant": { + "name": "Productive plant" + }, + "repotted": { + "name": "Repotted" + }, + "sensor_update_available": { + "name": "Sensor update available" + } + }, "sensor": { "scientific_name": { "name": "Scientific name" diff --git a/tests/components/fyta/fixtures/plant_status1.json b/tests/components/fyta/fixtures/plant_status1.json index 600fc46608cd6b..ca5662714a00f2 100644 --- a/tests/components/fyta/fixtures/plant_status1.json +++ b/tests/components/fyta/fixtures/plant_status1.json @@ -1,6 +1,9 @@ { "battery_level": 80, - "low_battery": true, + "fertilisation": { + "was_repotted": true + }, + "low_battery": false, "last_updated": "2023-01-10 10:10:00", "light": 2, "light_status": 3, @@ -10,7 +13,7 @@ "moisture_status": 3, "sensor_available": true, "sensor_id": "FD:1D:B7:E3:D0:E2", - "sensor_update_available": false, + "sensor_update_available": true, "sw_version": "1.0", "status": 1, "online": true, diff --git a/tests/components/fyta/fixtures/plant_status2.json b/tests/components/fyta/fixtures/plant_status2.json index c39e2ac8685a8f..bf90ab1e50d71d 100644 --- a/tests/components/fyta/fixtures/plant_status2.json +++ b/tests/components/fyta/fixtures/plant_status2.json @@ -1,5 +1,8 @@ { "battery_level": 80, + "fertilisation": { + "was_repotted": true + }, "low_battery": true, "last_updated": "2023-01-02 10:10:00", "light": 2, diff --git a/tests/components/fyta/fixtures/plant_status3.json b/tests/components/fyta/fixtures/plant_status3.json index 58e3e1b86a0b49..2bedd196fe1c2e 100644 --- a/tests/components/fyta/fixtures/plant_status3.json +++ b/tests/components/fyta/fixtures/plant_status3.json @@ -1,5 +1,8 @@ { "battery_level": 80, + "fertilisation": { + "was_repotted": false + }, "low_battery": true, "last_updated": "2023-01-02 10:10:00", "light": 2, diff --git a/tests/components/fyta/snapshots/test_binary_sensor.ambr b/tests/components/fyta/snapshots/test_binary_sensor.ambr new file mode 100644 index 00000000000000..c90db22bc7ff7a --- /dev/null +++ b/tests/components/fyta/snapshots/test_binary_sensor.ambr @@ -0,0 +1,741 @@ +# serializer version: 1 +# name: test_all_entities[binary_sensor.gummibaum_battery-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.gummibaum_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'ce5f5431554d101905d31797e1232da8-0-low_battery', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Gummibaum Battery', + }), + 'context': , + 'entity_id': 'binary_sensor.gummibaum_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_light_notification-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.gummibaum_light_notification', + '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': 'Light notification', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'notification_light', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-0-notification_light', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_light_notification-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Gummibaum Light notification', + }), + 'context': , + 'entity_id': 'binary_sensor.gummibaum_light_notification', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_nutrition_notification-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.gummibaum_nutrition_notification', + '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': 'Nutrition notification', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'notification_nutrition', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-0-notification_nutrition', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_nutrition_notification-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Gummibaum Nutrition notification', + }), + 'context': , + 'entity_id': 'binary_sensor.gummibaum_nutrition_notification', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_productive_plant-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.gummibaum_productive_plant', + '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': 'Productive plant', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'productive_plant', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-0-productive_plant', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_productive_plant-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Gummibaum Productive plant', + }), + 'context': , + 'entity_id': 'binary_sensor.gummibaum_productive_plant', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_repotted-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.gummibaum_repotted', + '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': 'Repotted', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'repotted', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-0-repotted', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_repotted-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Gummibaum Repotted', + }), + 'context': , + 'entity_id': 'binary_sensor.gummibaum_repotted', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_temperature_notification-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.gummibaum_temperature_notification', + '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': 'Temperature notification', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'notification_temperature', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-0-notification_temperature', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_temperature_notification-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Gummibaum Temperature notification', + }), + 'context': , + 'entity_id': 'binary_sensor.gummibaum_temperature_notification', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_update-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.gummibaum_update', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Update', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'ce5f5431554d101905d31797e1232da8-0-sensor_update_available', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_update-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'update', + 'friendly_name': 'Gummibaum Update', + }), + 'context': , + 'entity_id': 'binary_sensor.gummibaum_update', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_water_notification-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.gummibaum_water_notification', + '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': 'Water notification', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'notification_water', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-0-notification_water', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.gummibaum_water_notification-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Gummibaum Water notification', + }), + 'context': , + 'entity_id': 'binary_sensor.gummibaum_water_notification', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_battery-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.kakaobaum_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'ce5f5431554d101905d31797e1232da8-1-low_battery', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Kakaobaum Battery', + }), + 'context': , + 'entity_id': 'binary_sensor.kakaobaum_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_light_notification-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.kakaobaum_light_notification', + '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': 'Light notification', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'notification_light', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-1-notification_light', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_light_notification-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kakaobaum Light notification', + }), + 'context': , + 'entity_id': 'binary_sensor.kakaobaum_light_notification', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_nutrition_notification-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.kakaobaum_nutrition_notification', + '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': 'Nutrition notification', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'notification_nutrition', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-1-notification_nutrition', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_nutrition_notification-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kakaobaum Nutrition notification', + }), + 'context': , + 'entity_id': 'binary_sensor.kakaobaum_nutrition_notification', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_productive_plant-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.kakaobaum_productive_plant', + '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': 'Productive plant', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'productive_plant', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-1-productive_plant', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_productive_plant-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kakaobaum Productive plant', + }), + 'context': , + 'entity_id': 'binary_sensor.kakaobaum_productive_plant', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_repotted-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.kakaobaum_repotted', + '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': 'Repotted', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'repotted', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-1-repotted', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_repotted-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kakaobaum Repotted', + }), + 'context': , + 'entity_id': 'binary_sensor.kakaobaum_repotted', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_temperature_notification-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.kakaobaum_temperature_notification', + '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': 'Temperature notification', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'notification_temperature', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-1-notification_temperature', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_temperature_notification-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kakaobaum Temperature notification', + }), + 'context': , + 'entity_id': 'binary_sensor.kakaobaum_temperature_notification', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_update-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.kakaobaum_update', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Update', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'ce5f5431554d101905d31797e1232da8-1-sensor_update_available', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_update-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'update', + 'friendly_name': 'Kakaobaum Update', + }), + 'context': , + 'entity_id': 'binary_sensor.kakaobaum_update', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_water_notification-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.kakaobaum_water_notification', + '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': 'Water notification', + 'platform': 'fyta', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'notification_water', + 'unique_id': 'ce5f5431554d101905d31797e1232da8-1-notification_water', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.kakaobaum_water_notification-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kakaobaum Water notification', + }), + 'context': , + 'entity_id': 'binary_sensor.kakaobaum_water_notification', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/fyta/snapshots/test_diagnostics.ambr b/tests/components/fyta/snapshots/test_diagnostics.ambr index eb19797e5b1a79..b4da0238db09f3 100644 --- a/tests/components/fyta/snapshots/test_diagnostics.ambr +++ b/tests/components/fyta/snapshots/test_diagnostics.ambr @@ -31,7 +31,7 @@ 'last_updated': '2023-01-10T10:10:00', 'light': 2.0, 'light_status': 3, - 'low_battery': True, + 'low_battery': False, 'moisture': 61.0, 'moisture_status': 3, 'name': 'Gummibaum', @@ -46,14 +46,14 @@ 'plant_origin_path': '', 'plant_thumb_path': '', 'productive_plant': False, - 'repotted': False, + 'repotted': True, 'salinity': 1.0, 'salinity_status': 4, 'scientific_name': 'Ficus elastica', 'sensor_available': True, 'sensor_id': 'FD:1D:B7:E3:D0:E2', 'sensor_status': 0, - 'sensor_update_available': False, + 'sensor_update_available': True, 'status': 1, 'sw_version': '1.0', 'temperature': 25.2, @@ -81,7 +81,7 @@ 'plant_origin_path': '', 'plant_thumb_path': '', 'productive_plant': False, - 'repotted': False, + 'repotted': True, 'salinity': 1.0, 'salinity_status': 4, 'scientific_name': 'Theobroma cacao', diff --git a/tests/components/fyta/test_binary_sensor.py b/tests/components/fyta/test_binary_sensor.py new file mode 100644 index 00000000000000..9d6a4ae3b0e7a9 --- /dev/null +++ b/tests/components/fyta/test_binary_sensor.py @@ -0,0 +1,95 @@ +"""Test the Home Assistant fyta binary sensor module.""" + +from datetime import timedelta +from unittest.mock import AsyncMock + +from freezegun.api import FrozenDateTimeFactory +from fyta_cli.fyta_exceptions import FytaConnectionError, FytaPlantError +from fyta_cli.fyta_models import Plant +import pytest +from syrupy import SnapshotAssertion + +from homeassistant.components.fyta.const import DOMAIN as FYTA_DOMAIN +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import setup_platform + +from tests.common import ( + MockConfigEntry, + async_fire_time_changed, + load_json_object_fixture, + snapshot_platform, +) + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +async def test_all_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + mock_fyta_connector: AsyncMock, + mock_config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test all entities.""" + + await setup_platform(hass, mock_config_entry, [Platform.BINARY_SENSOR]) + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +@pytest.mark.parametrize( + "exception", + [ + FytaConnectionError, + FytaPlantError, + ], +) +async def test_connection_error( + hass: HomeAssistant, + exception: Exception, + mock_fyta_connector: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test connection error.""" + await setup_platform(hass, mock_config_entry, [Platform.BINARY_SENSOR]) + + mock_fyta_connector.update_all_plants.side_effect = exception + + freezer.tick(delta=timedelta(hours=12)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert ( + hass.states.get("binary_sensor.gummibaum_repotted").state == STATE_UNAVAILABLE + ) + + +async def test_add_remove_entities( + hass: HomeAssistant, + mock_fyta_connector: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test if entities are added and old are removed.""" + await setup_platform(hass, mock_config_entry, [Platform.BINARY_SENSOR]) + + assert hass.states.get("binary_sensor.gummibaum_repotted").state == STATE_ON + + plants: dict[int, Plant] = { + 0: Plant.from_dict(load_json_object_fixture("plant_status1.json", FYTA_DOMAIN)), + 2: Plant.from_dict(load_json_object_fixture("plant_status3.json", FYTA_DOMAIN)), + } + mock_fyta_connector.update_all_plants.return_value = plants + mock_fyta_connector.plant_list = { + 0: "Kautschukbaum", + 2: "Tomatenpflanze", + } + + freezer.tick(delta=timedelta(minutes=10)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert hass.states.get("binary_sensor.kakaobaum_repotted") is None + assert hass.states.get("binary_sensor.tomatenpflanze_repotted").state == STATE_OFF