Add binary sensor for smarttub errors (#49364)

This commit is contained in:
Matt Zimmerman 2021-05-26 09:25:47 -07:00 committed by GitHub
parent 64661ee2b7
commit ffb9ab21c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 4 deletions

View File

@ -1,7 +1,7 @@
"""Platform for binary sensor integration.""" """Platform for binary sensor integration."""
import logging import logging
from smarttub import SpaReminder from smarttub import SpaError, SpaReminder
import voluptuous as vol import voluptuous as vol
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
@ -11,7 +11,7 @@ from homeassistant.components.binary_sensor import (
) )
from homeassistant.helpers import entity_platform from homeassistant.helpers import entity_platform
from .const import ATTR_REMINDERS, DOMAIN, SMARTTUB_CONTROLLER from .const import ATTR_ERRORS, ATTR_REMINDERS, DOMAIN, SMARTTUB_CONTROLLER
from .entity import SmartTubEntity, SmartTubSensorBase from .entity import SmartTubEntity, SmartTubSensorBase
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -19,6 +19,13 @@ _LOGGER = logging.getLogger(__name__)
# whether the reminder has been snoozed (bool) # whether the reminder has been snoozed (bool)
ATTR_REMINDER_SNOOZED = "snoozed" ATTR_REMINDER_SNOOZED = "snoozed"
ATTR_ERROR_CODE = "error_code"
ATTR_ERROR_TITLE = "error_title"
ATTR_ERROR_DESCRIPTION = "error_description"
ATTR_ERROR_TYPE = "error_type"
ATTR_CREATED_AT = "created_at"
ATTR_UPDATED_AT = "updated_at"
# how many days to snooze the reminder for # how many days to snooze the reminder for
ATTR_SNOOZE_DAYS = "days" ATTR_SNOOZE_DAYS = "days"
SNOOZE_REMINDER_SCHEMA = { SNOOZE_REMINDER_SCHEMA = {
@ -34,6 +41,7 @@ async def async_setup_entry(hass, entry, async_add_entities):
entities = [] entities = []
for spa in controller.spas: for spa in controller.spas:
entities.append(SmartTubOnline(controller.coordinator, spa)) entities.append(SmartTubOnline(controller.coordinator, spa))
entities.append(SmartTubError(controller.coordinator, spa))
entities.extend( entities.extend(
SmartTubReminder(controller.coordinator, spa, reminder) SmartTubReminder(controller.coordinator, spa, reminder)
for reminder in controller.coordinator.data[spa.id][ATTR_REMINDERS].values() for reminder in controller.coordinator.data[spa.id][ATTR_REMINDERS].values()
@ -119,3 +127,54 @@ class SmartTubReminder(SmartTubEntity, BinarySensorEntity):
"""Snooze this reminder for the specified number of days.""" """Snooze this reminder for the specified number of days."""
await self.reminder.snooze(days) await self.reminder.snooze(days)
await self.coordinator.async_request_refresh() await self.coordinator.async_request_refresh()
class SmartTubError(SmartTubEntity, BinarySensorEntity):
"""Indicates whether an error code is present.
There may be 0 or more errors. If there are >0, we show the first one.
"""
def __init__(self, coordinator, spa):
"""Initialize the entity."""
super().__init__(
coordinator,
spa,
"Error",
)
@property
def error(self) -> SpaError:
"""Return the underlying SpaError object for this entity."""
errors = self.coordinator.data[self.spa.id][ATTR_ERRORS]
if len(errors) == 0:
return None
return errors[0]
@property
def is_on(self) -> bool:
"""Return true if an error is signaled."""
return self.error is not None
@property
def extra_state_attributes(self):
"""Return the state attributes."""
error = self.error
if error is None:
return {}
return {
ATTR_ERROR_CODE: error.code,
ATTR_ERROR_TITLE: error.title,
ATTR_ERROR_DESCRIPTION: error.description,
ATTR_ERROR_TYPE: error.error_type,
ATTR_CREATED_AT: error.created_at.isoformat(),
ATTR_UPDATED_AT: error.updated_at.isoformat(),
}
@property
def device_class(self) -> str:
"""Return the device class for this entity."""
return DEVICE_CLASS_PROBLEM

View File

@ -21,6 +21,7 @@ DEFAULT_LIGHT_EFFECT = "purple"
# default to 50% brightness # default to 50% brightness
DEFAULT_LIGHT_BRIGHTNESS = 128 DEFAULT_LIGHT_BRIGHTNESS = 128
ATTR_ERRORS = "errors"
ATTR_LIGHTS = "lights" ATTR_LIGHTS = "lights"
ATTR_PUMPS = "pumps" ATTR_PUMPS = "pumps"
ATTR_REMINDERS = "reminders" ATTR_REMINDERS = "reminders"

View File

@ -16,6 +16,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import ( from .const import (
ATTR_ERRORS,
ATTR_LIGHTS, ATTR_LIGHTS,
ATTR_PUMPS, ATTR_PUMPS,
ATTR_REMINDERS, ATTR_REMINDERS,
@ -92,15 +93,17 @@ class SmartTubController:
return data return data
async def _get_spa_data(self, spa): async def _get_spa_data(self, spa):
full_status, reminders = await asyncio.gather( full_status, reminders, errors = await asyncio.gather(
spa.get_status_full(), spa.get_status_full(),
spa.get_reminders(), spa.get_reminders(),
spa.get_errors(),
) )
return { return {
ATTR_STATUS: full_status, ATTR_STATUS: full_status,
ATTR_PUMPS: {pump.id: pump for pump in full_status.pumps}, ATTR_PUMPS: {pump.id: pump for pump in full_status.pumps},
ATTR_LIGHTS: {light.zone: light for light in full_status.lights}, ATTR_LIGHTS: {light.zone: light for light in full_status.lights},
ATTR_REMINDERS: {reminder.id: reminder for reminder in reminders}, ATTR_REMINDERS: {reminder.id: reminder for reminder in reminders},
ATTR_ERRORS: errors,
} }
async def async_register_devices(self, entry): async def async_register_devices(self, entry):

View File

@ -87,6 +87,8 @@ def mock_spa(spa_state):
mock_spa.get_reminders.return_value = [mock_filter_reminder] mock_spa.get_reminders.return_value = [mock_filter_reminder]
mock_spa.get_errors.return_value = []
return mock_spa return mock_spa

View File

@ -1,5 +1,11 @@
"""Test the SmartTub binary sensor platform.""" """Test the SmartTub binary sensor platform."""
from homeassistant.components.binary_sensor import STATE_OFF from datetime import datetime
from unittest.mock import create_autospec
import pytest
import smarttub
from homeassistant.components.binary_sensor import STATE_OFF, STATE_ON
async def test_binary_sensors(spa, setup_entry, hass): async def test_binary_sensors(spa, setup_entry, hass):
@ -10,6 +16,11 @@ async def test_binary_sensors(spa, setup_entry, hass):
# disabled by default # disabled by default
assert state is None assert state is None
entity_id = f"binary_sensor.{spa.brand}_{spa.model}_error"
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_OFF
async def test_reminders(spa, setup_entry, hass): async def test_reminders(spa, setup_entry, hass):
"""Test the reminder sensor.""" """Test the reminder sensor."""
@ -21,6 +32,37 @@ async def test_reminders(spa, setup_entry, hass):
assert state.attributes["snoozed"] is False assert state.attributes["snoozed"] is False
@pytest.fixture
def mock_error(spa):
"""Mock error."""
error = create_autospec(smarttub.SpaError, instance=True)
error.code = 11
error.title = "Flow Switch Stuck Open"
error.description = None
error.active = True
error.created_at = datetime.now()
error.updated_at = datetime.now()
error.error_type = "TUB_ERROR"
return error
async def test_error(spa, hass, config_entry, mock_error):
"""Test the error sensor."""
spa.get_errors.return_value = [mock_error]
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
entity_id = f"binary_sensor.{spa.brand}_{spa.model}_error"
state = hass.states.get(entity_id)
assert state is not None
assert state.state == STATE_ON
assert state.attributes["error_code"] == 11
async def test_snooze(spa, setup_entry, hass): async def test_snooze(spa, setup_entry, hass):
"""Test snoozing a reminder.""" """Test snoozing a reminder."""