From 73c6728e98e2e853afa9bd80b3a689c2d56e49e3 Mon Sep 17 00:00:00 2001 From: Matt Zimmerman Date: Tue, 16 Mar 2021 09:38:09 -0700 Subject: [PATCH] Add binary_sensor entities for SmartTub reminders (#47583) --- .../components/smarttub/binary_sensor.py | 63 ++++++++++++++++++- homeassistant/components/smarttub/climate.py | 2 +- homeassistant/components/smarttub/const.py | 5 +- .../components/smarttub/controller.py | 5 +- tests/components/smarttub/conftest.py | 8 +++ .../components/smarttub/test_binary_sensor.py | 22 ++++++- 6 files changed, 96 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/smarttub/binary_sensor.py b/homeassistant/components/smarttub/binary_sensor.py index 52dbfd71a37..04a6bb9b72a 100644 --- a/homeassistant/components/smarttub/binary_sensor.py +++ b/homeassistant/components/smarttub/binary_sensor.py @@ -1,23 +1,38 @@ """Platform for binary sensor integration.""" +from datetime import datetime, timedelta import logging +from smarttub import SpaReminder + from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_PROBLEM, BinarySensorEntity, ) -from .const import DOMAIN, SMARTTUB_CONTROLLER -from .entity import SmartTubSensorBase +from .const import ATTR_REMINDERS, DOMAIN, SMARTTUB_CONTROLLER +from .entity import SmartTubEntity, SmartTubSensorBase _LOGGER = logging.getLogger(__name__) +# whether the reminder has been snoozed (bool) +ATTR_REMINDER_SNOOZED = "snoozed" +# the date at which the reminder will be activated +ATTR_REMINDER_DATE = "date" + async def async_setup_entry(hass, entry, async_add_entities): """Set up binary sensor entities for the binary sensors in the tub.""" controller = hass.data[DOMAIN][entry.entry_id][SMARTTUB_CONTROLLER] - entities = [SmartTubOnline(controller.coordinator, spa) for spa in controller.spas] + entities = [] + for spa in controller.spas: + entities.append(SmartTubOnline(controller.coordinator, spa)) + entities.extend( + SmartTubReminder(controller.coordinator, spa, reminder) + for reminder in controller.coordinator.data[spa.id][ATTR_REMINDERS].values() + ) async_add_entities(entities) @@ -38,3 +53,45 @@ class SmartTubOnline(SmartTubSensorBase, BinarySensorEntity): def device_class(self) -> str: """Return the device class for this entity.""" return DEVICE_CLASS_CONNECTIVITY + + +class SmartTubReminder(SmartTubEntity, BinarySensorEntity): + """Reminders for maintenance actions.""" + + def __init__(self, coordinator, spa, reminder): + """Initialize the entity.""" + super().__init__( + coordinator, + spa, + f"{reminder.name.title()} Reminder", + ) + self.reminder_id = reminder.id + + @property + def unique_id(self): + """Return a unique id for this sensor.""" + return f"{self.spa.id}-reminder-{self.reminder_id}" + + @property + def reminder(self) -> SpaReminder: + """Return the underlying SpaReminder object for this entity.""" + return self.coordinator.data[self.spa.id]["reminders"][self.reminder_id] + + @property + def is_on(self) -> bool: + """Return whether the specified maintenance action needs to be taken.""" + return self.reminder.remaining_days == 0 + + @property + def device_state_attributes(self): + """Return the state attributes.""" + when = datetime.now() + timedelta(days=self.reminder.remaining_days) + return { + ATTR_REMINDER_SNOOZED: self.reminder.snoozed, + ATTR_REMINDER_DATE: when.date().isoformat(), + } + + @property + def device_class(self) -> str: + """Return the device class for this entity.""" + return DEVICE_CLASS_PROBLEM diff --git a/homeassistant/components/smarttub/climate.py b/homeassistant/components/smarttub/climate.py index 3e18bc12672..be564c84a94 100644 --- a/homeassistant/components/smarttub/climate.py +++ b/homeassistant/components/smarttub/climate.py @@ -54,7 +54,7 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity): def __init__(self, coordinator, spa): """Initialize the entity.""" - super().__init__(coordinator, spa, "thermostat") + super().__init__(coordinator, spa, "Thermostat") @property def temperature_unit(self): diff --git a/homeassistant/components/smarttub/const.py b/homeassistant/components/smarttub/const.py index 082d46cb26a..23bd8bd8ec0 100644 --- a/homeassistant/components/smarttub/const.py +++ b/homeassistant/components/smarttub/const.py @@ -21,6 +21,7 @@ DEFAULT_LIGHT_EFFECT = "purple" # default to 50% brightness DEFAULT_LIGHT_BRIGHTNESS = 128 -ATTR_STATUS = "status" -ATTR_PUMPS = "pumps" ATTR_LIGHTS = "lights" +ATTR_PUMPS = "pumps" +ATTR_REMINDERS = "reminders" +ATTR_STATUS = "status" diff --git a/homeassistant/components/smarttub/controller.py b/homeassistant/components/smarttub/controller.py index e5246446665..8139c72ab6e 100644 --- a/homeassistant/components/smarttub/controller.py +++ b/homeassistant/components/smarttub/controller.py @@ -18,6 +18,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import ( ATTR_LIGHTS, ATTR_PUMPS, + ATTR_REMINDERS, ATTR_STATUS, DOMAIN, POLLING_TIMEOUT, @@ -93,15 +94,17 @@ class SmartTubController: return data async def _get_spa_data(self, spa): - status, pumps, lights = await asyncio.gather( + status, pumps, lights, reminders = await asyncio.gather( spa.get_status(), spa.get_pumps(), spa.get_lights(), + spa.get_reminders(), ) return { ATTR_STATUS: status, ATTR_PUMPS: {pump.id: pump for pump in pumps}, ATTR_LIGHTS: {light.zone: light for light in lights}, + ATTR_REMINDERS: {reminder.id: reminder for reminder in reminders}, } async def async_register_devices(self, entry): diff --git a/tests/components/smarttub/conftest.py b/tests/components/smarttub/conftest.py index b7c90b5ad3e..7f23c2355bc 100644 --- a/tests/components/smarttub/conftest.py +++ b/tests/components/smarttub/conftest.py @@ -105,6 +105,14 @@ def mock_spa(): mock_spa.get_lights.return_value = [mock_light_off, mock_light_on] + mock_filter_reminder = create_autospec(smarttub.SpaReminder, instance=True) + mock_filter_reminder.id = "FILTER01" + mock_filter_reminder.name = "MyFilter" + mock_filter_reminder.remaining_days = 2 + mock_filter_reminder.snoozed = False + + mock_spa.get_reminders.return_value = [mock_filter_reminder] + return mock_spa diff --git a/tests/components/smarttub/test_binary_sensor.py b/tests/components/smarttub/test_binary_sensor.py index b2624369e96..8229372e904 100644 --- a/tests/components/smarttub/test_binary_sensor.py +++ b/tests/components/smarttub/test_binary_sensor.py @@ -1,13 +1,31 @@ """Test the SmartTub binary sensor platform.""" +from datetime import date, timedelta -from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY, STATE_ON +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + STATE_OFF, + STATE_ON, +) async def test_binary_sensors(spa, setup_entry, hass): - """Test the binary sensors.""" + """Test simple binary sensors.""" entity_id = f"binary_sensor.{spa.brand}_{spa.model}_online" state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_ON assert state.attributes.get("device_class") == DEVICE_CLASS_CONNECTIVITY + + +async def test_reminders(spa, setup_entry, hass): + """Test the reminder sensor.""" + + entity_id = f"binary_sensor.{spa.brand}_{spa.model}_myfilter_reminder" + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_OFF + assert date.fromisoformat(state.attributes["date"]) <= date.today() + timedelta( + days=2 + ) + assert state.attributes["snoozed"] is False