diff --git a/homeassistant/components/hydrawise/coordinator.py b/homeassistant/components/hydrawise/coordinator.py index 50caaa0c0de..6cd233eb1df 100644 --- a/homeassistant/components/hydrawise/coordinator.py +++ b/homeassistant/components/hydrawise/coordinator.py @@ -23,7 +23,7 @@ class HydrawiseData: controllers: dict[int, Controller] zones: dict[int, Zone] sensors: dict[int, Sensor] - daily_water_use: dict[int, ControllerWaterUseSummary] + daily_water_summary: dict[int, ControllerWaterUseSummary] class HydrawiseDataUpdateCoordinator(DataUpdateCoordinator[HydrawiseData]): @@ -47,7 +47,7 @@ class HydrawiseDataUpdateCoordinator(DataUpdateCoordinator[HydrawiseData]): controllers = {} zones = {} sensors = {} - daily_water_use: dict[int, ControllerWaterUseSummary] = {} + daily_water_summary: dict[int, ControllerWaterUseSummary] = {} for controller in user.controllers: controllers[controller.id] = controller controller.zones = await self.api.get_zones(controller) @@ -55,22 +55,16 @@ class HydrawiseDataUpdateCoordinator(DataUpdateCoordinator[HydrawiseData]): zones[zone.id] = zone for sensor in controller.sensors: sensors[sensor.id] = sensor - if any( - "flow meter" in sensor.model.name.lower() - for sensor in controller.sensors - ): - daily_water_use[controller.id] = await self.api.get_water_use_summary( - controller, - now().replace(hour=0, minute=0, second=0, microsecond=0), - now(), - ) - else: - daily_water_use[controller.id] = ControllerWaterUseSummary() + daily_water_summary[controller.id] = await self.api.get_water_use_summary( + controller, + now().replace(hour=0, minute=0, second=0, microsecond=0), + now(), + ) return HydrawiseData( user=user, controllers=controllers, zones=zones, sensors=sensors, - daily_water_use=daily_water_use, + daily_water_summary=daily_water_summary, ) diff --git a/homeassistant/components/hydrawise/icons.json b/homeassistant/components/hydrawise/icons.json index 64deab590da..4af4fe75fcc 100644 --- a/homeassistant/components/hydrawise/icons.json +++ b/homeassistant/components/hydrawise/icons.json @@ -4,6 +4,9 @@ "daily_active_water_use": { "default": "mdi:water" }, + "daily_active_water_time": { + "default": "mdi:timelapse" + }, "daily_inactive_water_use": { "default": "mdi:water" }, diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index fe4b33d5851..563af893700 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass -from datetime import datetime +from datetime import datetime, timedelta from typing import Any from homeassistant.components.sensor import ( @@ -44,28 +44,65 @@ def _get_zone_next_cycle(sensor: HydrawiseSensor) -> datetime | None: def _get_zone_daily_active_water_use(sensor: HydrawiseSensor) -> float: """Get active water use for the zone.""" - daily_water_summary = sensor.coordinator.data.daily_water_use[sensor.controller.id] + daily_water_summary = sensor.coordinator.data.daily_water_summary[ + sensor.controller.id + ] return float(daily_water_summary.active_use_by_zone_id.get(sensor.zone.id, 0.0)) +def _get_zone_daily_active_water_time(sensor: HydrawiseSensor) -> float | None: + """Get active water time for the zone.""" + daily_water_summary = sensor.coordinator.data.daily_water_summary[ + sensor.controller.id + ] + return daily_water_summary.active_time_by_zone_id.get( + sensor.zone.id, timedelta() + ).total_seconds() + + def _get_controller_daily_active_water_use(sensor: HydrawiseSensor) -> float | None: """Get active water use for the controller.""" - daily_water_summary = sensor.coordinator.data.daily_water_use[sensor.controller.id] + daily_water_summary = sensor.coordinator.data.daily_water_summary[ + sensor.controller.id + ] return daily_water_summary.total_active_use def _get_controller_daily_inactive_water_use(sensor: HydrawiseSensor) -> float | None: """Get inactive water use for the controller.""" - daily_water_summary = sensor.coordinator.data.daily_water_use[sensor.controller.id] + daily_water_summary = sensor.coordinator.data.daily_water_summary[ + sensor.controller.id + ] return daily_water_summary.total_inactive_use +def _get_controller_daily_active_water_time(sensor: HydrawiseSensor) -> float: + """Get active water time for the controller.""" + daily_water_summary = sensor.coordinator.data.daily_water_summary[ + sensor.controller.id + ] + return daily_water_summary.total_active_time.total_seconds() + + def _get_controller_daily_total_water_use(sensor: HydrawiseSensor) -> float | None: """Get inactive water use for the controller.""" - daily_water_summary = sensor.coordinator.data.daily_water_use[sensor.controller.id] + daily_water_summary = sensor.coordinator.data.daily_water_summary[ + sensor.controller.id + ] return daily_water_summary.total_use +CONTROLLER_SENSORS: tuple[HydrawiseSensorEntityDescription, ...] = ( + HydrawiseSensorEntityDescription( + key="daily_active_water_time", + translation_key="daily_active_water_time", + device_class=SensorDeviceClass.DURATION, + native_unit_of_measurement=UnitOfTime.SECONDS, + value_fn=_get_controller_daily_active_water_time, + ), +) + + FLOW_CONTROLLER_SENSORS: tuple[HydrawiseSensorEntityDescription, ...] = ( HydrawiseSensorEntityDescription( key="daily_total_water_use", @@ -113,6 +150,13 @@ ZONE_SENSORS: tuple[HydrawiseSensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfTime.MINUTES, value_fn=_get_zone_watering_time, ), + HydrawiseSensorEntityDescription( + key="daily_active_water_time", + translation_key="daily_active_water_time", + device_class=SensorDeviceClass.DURATION, + native_unit_of_measurement=UnitOfTime.SECONDS, + value_fn=_get_zone_daily_active_water_time, + ), ) FLOW_MEASUREMENT_KEYS = [x.key for x in FLOW_CONTROLLER_SENSORS] @@ -129,30 +173,31 @@ async def async_setup_entry( ] entities: list[HydrawiseSensor] = [] for controller in coordinator.data.controllers.values(): + entities.extend( + HydrawiseSensor(coordinator, description, controller) + for description in CONTROLLER_SENSORS + ) entities.extend( HydrawiseSensor(coordinator, description, controller, zone_id=zone.id) for zone in controller.zones for description in ZONE_SENSORS ) - entities.extend( - HydrawiseSensor(coordinator, description, controller, sensor_id=sensor.id) - for sensor in controller.sensors - for description in FLOW_CONTROLLER_SENSORS - if "flow meter" in sensor.model.name.lower() - ) - entities.extend( - HydrawiseSensor( - coordinator, - description, - controller, - zone_id=zone.id, - sensor_id=sensor.id, + if coordinator.data.daily_water_summary[controller.id].total_use is not None: + # we have a flow sensor for this controller + entities.extend( + HydrawiseSensor(coordinator, description, controller) + for description in FLOW_CONTROLLER_SENSORS + ) + entities.extend( + HydrawiseSensor( + coordinator, + description, + controller, + zone_id=zone.id, + ) + for zone in controller.zones + for description in FLOW_ZONE_SENSORS ) - for zone in controller.zones - for sensor in controller.sensors - for description in FLOW_ZONE_SENSORS - if "flow meter" in sensor.model.name.lower() - ) async_add_entities(entities) @@ -177,6 +222,7 @@ class HydrawiseSensor(HydrawiseEntity, SensorEntity): """Icon of the entity based on the value.""" if ( self.entity_description.key in FLOW_MEASUREMENT_KEYS + and self.entity_description.device_class == SensorDeviceClass.VOLUME and round(self.state, 2) == 0.0 ): return "mdi:water-outline" diff --git a/homeassistant/components/hydrawise/strings.json b/homeassistant/components/hydrawise/strings.json index 1bc5525c9d9..c455412d1a4 100644 --- a/homeassistant/components/hydrawise/strings.json +++ b/homeassistant/components/hydrawise/strings.json @@ -33,6 +33,9 @@ "daily_total_water_use": { "name": "Daily total water use" }, + "daily_active_water_time": { + "name": "Daily active watering time" + }, "daily_active_water_use": { "name": "Daily active water use" }, @@ -43,7 +46,7 @@ "name": "Next cycle" }, "watering_time": { - "name": "Watering time" + "name": "Remaining watering time" } }, "switch": { diff --git a/tests/components/hydrawise/conftest.py b/tests/components/hydrawise/conftest.py index 0b5327cd7b2..a938322414b 100644 --- a/tests/components/hydrawise/conftest.py +++ b/tests/components/hydrawise/conftest.py @@ -187,6 +187,8 @@ def controller_water_use_summary() -> ControllerWaterUseSummary: total_active_use=332.6, total_inactive_use=13.0, active_use_by_zone_id={5965394: 120.1, 5965395: 0.0}, + total_active_time=timedelta(seconds=123), + active_time_by_zone_id={5965394: timedelta(seconds=123), 5965395: timedelta()}, unit="gal", ) diff --git a/tests/components/hydrawise/snapshots/test_sensor.ambr b/tests/components/hydrawise/snapshots/test_sensor.ambr index 3472de98460..dadf3c44789 100644 --- a/tests/components/hydrawise/snapshots/test_sensor.ambr +++ b/tests/components/hydrawise/snapshots/test_sensor.ambr @@ -54,6 +54,55 @@ 'state': '1259.0279593584', }) # --- +# name: test_all_sensors[sensor.home_controller_daily_active_watering_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.home_controller_daily_active_watering_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Daily active watering time', + 'platform': 'hydrawise', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_active_water_time', + 'unique_id': '52496_daily_active_water_time', + 'unit_of_measurement': , + }) +# --- +# name: test_all_sensors[sensor.home_controller_daily_active_watering_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by hydrawise.com', + 'device_class': 'duration', + 'friendly_name': 'Home Controller Daily active watering time', + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.home_controller_daily_active_watering_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '123.0', + }) +# --- # name: test_all_sensors[sensor.home_controller_daily_inactive_water_use-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -219,6 +268,55 @@ 'state': '454.6279552584', }) # --- +# name: test_all_sensors[sensor.zone_one_daily_active_watering_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.zone_one_daily_active_watering_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Daily active watering time', + 'platform': 'hydrawise', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_active_water_time', + 'unique_id': '5965394_daily_active_water_time', + 'unit_of_measurement': , + }) +# --- +# name: test_all_sensors[sensor.zone_one_daily_active_watering_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by hydrawise.com', + 'device_class': 'duration', + 'friendly_name': 'Zone One Daily active watering time', + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.zone_one_daily_active_watering_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '123.0', + }) +# --- # name: test_all_sensors[sensor.zone_one_next_cycle-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -267,7 +365,7 @@ 'state': '2023-10-04T19:49:57+00:00', }) # --- -# name: test_all_sensors[sensor.zone_one_watering_time-entry] +# name: test_all_sensors[sensor.zone_one_remaining_watering_time-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -279,7 +377,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.zone_one_watering_time', + 'entity_id': 'sensor.zone_one_remaining_watering_time', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -291,7 +389,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Watering time', + 'original_name': 'Remaining watering time', 'platform': 'hydrawise', 'previous_unique_id': None, 'supported_features': 0, @@ -300,15 +398,15 @@ 'unit_of_measurement': , }) # --- -# name: test_all_sensors[sensor.zone_one_watering_time-state] +# name: test_all_sensors[sensor.zone_one_remaining_watering_time-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'attribution': 'Data provided by hydrawise.com', - 'friendly_name': 'Zone One Watering time', + 'friendly_name': 'Zone One Remaining watering time', 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.zone_one_watering_time', + 'entity_id': 'sensor.zone_one_remaining_watering_time', 'last_changed': , 'last_reported': , 'last_updated': , @@ -371,6 +469,55 @@ 'state': '0.0', }) # --- +# name: test_all_sensors[sensor.zone_two_daily_active_watering_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.zone_two_daily_active_watering_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Daily active watering time', + 'platform': 'hydrawise', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_active_water_time', + 'unique_id': '5965395_daily_active_water_time', + 'unit_of_measurement': , + }) +# --- +# name: test_all_sensors[sensor.zone_two_daily_active_watering_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by hydrawise.com', + 'device_class': 'duration', + 'friendly_name': 'Zone Two Daily active watering time', + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.zone_two_daily_active_watering_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_all_sensors[sensor.zone_two_next_cycle-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -419,7 +566,7 @@ 'state': 'unknown', }) # --- -# name: test_all_sensors[sensor.zone_two_watering_time-entry] +# name: test_all_sensors[sensor.zone_two_remaining_watering_time-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -431,7 +578,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.zone_two_watering_time', + 'entity_id': 'sensor.zone_two_remaining_watering_time', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -443,7 +590,7 @@ }), 'original_device_class': None, 'original_icon': None, - 'original_name': 'Watering time', + 'original_name': 'Remaining watering time', 'platform': 'hydrawise', 'previous_unique_id': None, 'supported_features': 0, @@ -452,15 +599,15 @@ 'unit_of_measurement': , }) # --- -# name: test_all_sensors[sensor.zone_two_watering_time-state] +# name: test_all_sensors[sensor.zone_two_remaining_watering_time-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'attribution': 'Data provided by hydrawise.com', - 'friendly_name': 'Zone Two Watering time', + 'friendly_name': 'Zone Two Remaining watering time', 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.zone_two_watering_time', + 'entity_id': 'sensor.zone_two_remaining_watering_time', 'last_changed': , 'last_reported': , 'last_updated': , diff --git a/tests/components/hydrawise/test_sensor.py b/tests/components/hydrawise/test_sensor.py index af75ad69ade..b9ff99f0013 100644 --- a/tests/components/hydrawise/test_sensor.py +++ b/tests/components/hydrawise/test_sensor.py @@ -3,7 +3,7 @@ from collections.abc import Awaitable, Callable from unittest.mock import patch -from pydrawise.schema import Controller, User, Zone +from pydrawise.schema import Controller, ControllerWaterUseSummary, User, Zone import pytest from syrupy.assertion import SnapshotAssertion @@ -53,10 +53,15 @@ async def test_suspended_state( async def test_no_sensor_and_water_state( hass: HomeAssistant, controller: Controller, + controller_water_use_summary: ControllerWaterUseSummary, mock_add_config_entry: Callable[[], Awaitable[MockConfigEntry]], ) -> None: """Test rain sensor, flow sensor, and water use in the absence of flow and rain sensors.""" controller.sensors = [] + controller_water_use_summary.total_use = None + controller_water_use_summary.total_active_use = None + controller_water_use_summary.total_inactive_use = None + controller_water_use_summary.active_use_by_zone_id = {} await mock_add_config_entry() assert hass.states.get("sensor.zone_one_daily_active_water_use") is None @@ -65,6 +70,18 @@ async def test_no_sensor_and_water_state( assert hass.states.get("sensor.home_controller_daily_inactive_water_use") is None assert hass.states.get("binary_sensor.home_controller_rain_sensor") is None + sensor = hass.states.get("sensor.home_controller_daily_active_watering_time") + assert sensor is not None + assert sensor.state == "123.0" + + sensor = hass.states.get("sensor.zone_one_daily_active_watering_time") + assert sensor is not None + assert sensor.state == "123.0" + + sensor = hass.states.get("sensor.zone_two_daily_active_watering_time") + assert sensor is not None + assert sensor.state == "0.0" + sensor = hass.states.get("binary_sensor.home_controller_connectivity") assert sensor is not None assert sensor.state == "on"