Add services to SmartTub for changing filtration settings (#46980)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
pull/51008/head
Matt Zimmerman 2021-05-23 07:00:24 -07:00 committed by GitHub
parent e1b6385b4d
commit 87438dd401
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 204 additions and 60 deletions

View File

@ -1,4 +1,4 @@
"""SmartTub integration."""
"""Base classes for SmartTub entities."""
import logging
import smarttub

View File

@ -2,7 +2,11 @@
from enum import Enum
import logging
import smarttub
import voluptuous as vol
from homeassistant.components.sensor import SensorEntity
from homeassistant.helpers import config_validation as cv, entity_platform
from .const import DOMAIN, SMARTTUB_CONTROLLER
from .entity import SmartTubSensorBase
@ -16,6 +20,25 @@ ATTR_MODE = "mode"
# the hour of the day at which to start the cycle (0-23)
ATTR_START_HOUR = "start_hour"
SET_PRIMARY_FILTRATION_SCHEMA = vol.All(
cv.has_at_least_one_key(ATTR_DURATION, ATTR_START_HOUR),
cv.make_entity_service_schema(
{
vol.Optional(ATTR_DURATION): vol.All(int, vol.Range(min=1, max=24)),
vol.Optional(ATTR_START_HOUR): vol.All(int, vol.Range(min=0, max=23)),
},
),
)
SET_SECONDARY_FILTRATION_SCHEMA = {
vol.Required(ATTR_MODE): vol.In(
{
mode.name.lower()
for mode in smarttub.SpaSecondaryFiltrationCycle.SecondaryFiltrationMode
}
),
}
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up sensor entities for the sensors in the tub."""
@ -45,6 +68,20 @@ async def async_setup_entry(hass, entry, async_add_entities):
async_add_entities(entities)
platform = entity_platform.current_platform.get()
platform.async_register_entity_service(
"set_primary_filtration",
SET_PRIMARY_FILTRATION_SCHEMA,
"async_set_primary_filtration",
)
platform.async_register_entity_service(
"set_secondary_filtration",
SET_SECONDARY_FILTRATION_SCHEMA,
"async_set_secondary_filtration",
)
class SmartTubSensor(SmartTubSensorBase, SensorEntity):
"""Generic class for SmartTub status sensors."""
@ -66,22 +103,33 @@ class SmartTubPrimaryFiltrationCycle(SmartTubSensor):
coordinator, spa, "Primary Filtration Cycle", "primary_filtration"
)
@property
def cycle(self) -> smarttub.SpaPrimaryFiltrationCycle:
"""Return the underlying smarttub.SpaPrimaryFiltrationCycle object."""
return self._state
@property
def state(self) -> str:
"""Return the current state of the sensor."""
return self._state.status.name.lower()
return self.cycle.status.name.lower()
@property
def extra_state_attributes(self):
"""Return the state attributes."""
state = self._state
return {
ATTR_DURATION: state.duration,
ATTR_CYCLE_LAST_UPDATED: state.last_updated.isoformat(),
ATTR_MODE: state.mode.name.lower(),
ATTR_START_HOUR: state.start_hour,
ATTR_DURATION: self.cycle.duration,
ATTR_CYCLE_LAST_UPDATED: self.cycle.last_updated.isoformat(),
ATTR_MODE: self.cycle.mode.name.lower(),
ATTR_START_HOUR: self.cycle.start_hour,
}
async def async_set_primary_filtration(self, **kwargs):
"""Update primary filtration settings."""
await self.cycle.set(
duration=kwargs.get(ATTR_DURATION),
start_hour=kwargs.get(ATTR_START_HOUR),
)
class SmartTubSecondaryFiltrationCycle(SmartTubSensor):
"""The secondary filtration cycle."""
@ -92,16 +140,27 @@ class SmartTubSecondaryFiltrationCycle(SmartTubSensor):
coordinator, spa, "Secondary Filtration Cycle", "secondary_filtration"
)
@property
def cycle(self) -> smarttub.SpaSecondaryFiltrationCycle:
"""Return the underlying smarttub.SpaSecondaryFiltrationCycle object."""
return self._state
@property
def state(self) -> str:
"""Return the current state of the sensor."""
return self._state.status.name.lower()
return self.cycle.status.name.lower()
@property
def extra_state_attributes(self):
"""Return the state attributes."""
state = self._state
return {
ATTR_CYCLE_LAST_UPDATED: state.last_updated.isoformat(),
ATTR_MODE: state.mode.name.lower(),
ATTR_CYCLE_LAST_UPDATED: self.cycle.last_updated.isoformat(),
ATTR_MODE: self.cycle.mode.name.lower(),
}
async def async_set_secondary_filtration(self, **kwargs):
"""Update primary filtration settings."""
mode = smarttub.SpaSecondaryFiltrationCycle.SecondaryFiltrationMode[
kwargs[ATTR_MODE].upper()
]
await self.cycle.set_mode(mode)

View File

@ -0,0 +1,47 @@
set_primary_filtration:
name: Update primary filtration settings
description: Updates the primary filtration settings
target:
entity:
integration: smarttub
domain: sensor
fields:
duration:
name: Duration
description: The desired duration of the primary filtration cycle
default: 8
selector:
number:
min: 1
max: 24
unit_of_measurement: "hours"
mode: slider
example: 8
start_hour:
description: The hour of the day at which to begin the primary filtration cycle
default: 0
example: 2
selector:
number:
min: 0
max: 23
unit_of_measurement: "hour"
set_secondary_filtration:
name: Update secondary filtration settings
description: Updates the secondary filtration settings
target:
entity:
integration: smarttub
domain: sensor
fields:
mode:
description: The secondary filtration mode.
selector:
select:
options:
- "frequent"
- "infrequent"
- "away"
required: true
example: "frequent"

View File

@ -35,13 +35,65 @@ async def setup_component(hass):
@pytest.fixture(name="spa")
def mock_spa():
def mock_spa(spa_state):
"""Mock a smarttub.Spa."""
mock_spa = create_autospec(smarttub.Spa, instance=True)
mock_spa.id = "mockspa1"
mock_spa.brand = "mockbrand1"
mock_spa.model = "mockmodel1"
mock_spa.get_status_full.return_value = spa_state
mock_circulation_pump = create_autospec(smarttub.SpaPump, instance=True)
mock_circulation_pump.id = "CP"
mock_circulation_pump.spa = mock_spa
mock_circulation_pump.state = smarttub.SpaPump.PumpState.OFF
mock_circulation_pump.type = smarttub.SpaPump.PumpType.CIRCULATION
mock_jet_off = create_autospec(smarttub.SpaPump, instance=True)
mock_jet_off.id = "P1"
mock_jet_off.spa = mock_spa
mock_jet_off.state = smarttub.SpaPump.PumpState.OFF
mock_jet_off.type = smarttub.SpaPump.PumpType.JET
mock_jet_on = create_autospec(smarttub.SpaPump, instance=True)
mock_jet_on.id = "P2"
mock_jet_on.spa = mock_spa
mock_jet_on.state = smarttub.SpaPump.PumpState.HIGH
mock_jet_on.type = smarttub.SpaPump.PumpType.JET
spa_state.pumps = [mock_circulation_pump, mock_jet_off, mock_jet_on]
mock_light_off = create_autospec(smarttub.SpaLight, instance=True)
mock_light_off.spa = mock_spa
mock_light_off.zone = 1
mock_light_off.intensity = 0
mock_light_off.mode = smarttub.SpaLight.LightMode.OFF
mock_light_on = create_autospec(smarttub.SpaLight, instance=True)
mock_light_on.spa = mock_spa
mock_light_on.zone = 2
mock_light_on.intensity = 50
mock_light_on.mode = smarttub.SpaLight.LightMode.PURPLE
spa_state.lights = [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
@pytest.fixture(name="spa_state")
def mock_spa_state():
"""Create a smarttub.SpaStateFull with mocks."""
full_status = smarttub.SpaStateFull(
mock_spa,
{
@ -73,51 +125,15 @@ def mock_spa():
"pumps": [],
},
)
mock_spa.get_status_full.return_value = full_status
mock_circulation_pump = create_autospec(smarttub.SpaPump, instance=True)
mock_circulation_pump.id = "CP"
mock_circulation_pump.spa = mock_spa
mock_circulation_pump.state = smarttub.SpaPump.PumpState.OFF
mock_circulation_pump.type = smarttub.SpaPump.PumpType.CIRCULATION
full_status.primary_filtration.set = create_autospec(
smarttub.SpaPrimaryFiltrationCycle, instance=True
).set
full_status.secondary_filtration.set_mode = create_autospec(
smarttub.SpaSecondaryFiltrationCycle, instance=True
).set_mode
mock_jet_off = create_autospec(smarttub.SpaPump, instance=True)
mock_jet_off.id = "P1"
mock_jet_off.spa = mock_spa
mock_jet_off.state = smarttub.SpaPump.PumpState.OFF
mock_jet_off.type = smarttub.SpaPump.PumpType.JET
mock_jet_on = create_autospec(smarttub.SpaPump, instance=True)
mock_jet_on.id = "P2"
mock_jet_on.spa = mock_spa
mock_jet_on.state = smarttub.SpaPump.PumpState.HIGH
mock_jet_on.type = smarttub.SpaPump.PumpType.JET
full_status.pumps = [mock_circulation_pump, mock_jet_off, mock_jet_on]
mock_light_off = create_autospec(smarttub.SpaLight, instance=True)
mock_light_off.spa = mock_spa
mock_light_off.zone = 1
mock_light_off.intensity = 0
mock_light_off.mode = smarttub.SpaLight.LightMode.OFF
mock_light_on = create_autospec(smarttub.SpaLight, instance=True)
mock_light_on.spa = mock_spa
mock_light_on.zone = 2
mock_light_on.intensity = 50
mock_light_on.mode = smarttub.SpaLight.LightMode.PURPLE
full_status.lights = [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
return full_status
@pytest.fixture(name="account")

View File

@ -33,7 +33,7 @@ from homeassistant.const import (
from . import trigger_update
async def test_thermostat_update(spa, setup_entry, hass):
async def test_thermostat_update(spa, spa_state, setup_entry, hass):
"""Test the thermostat entity."""
entity_id = f"climate.{spa.brand}_{spa.model}_thermostat"
@ -42,7 +42,7 @@ async def test_thermostat_update(spa, setup_entry, hass):
assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT
spa.get_status_full.return_value.heater = "OFF"
spa_state.heater = "OFF"
await trigger_update(hass)
state = hass.states.get(entity_id)
@ -85,7 +85,7 @@ async def test_thermostat_update(spa, setup_entry, hass):
)
spa.set_heat_mode.assert_called_with(smarttub.Spa.HeatMode.ECONOMY)
spa.get_status_full.return_value.heat_mode = smarttub.Spa.HeatMode.ECONOMY
spa_state.heat_mode = smarttub.Spa.HeatMode.ECONOMY
await trigger_update(hass)
state = hass.states.get(entity_id)
assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ECO

View File

@ -1,6 +1,7 @@
"""Test the SmartTub sensor platform."""
import pytest
import smarttub
@pytest.mark.parametrize(
@ -23,7 +24,7 @@ async def test_sensor(spa, setup_entry, hass, entity_suffix, expected_state):
assert state.state == expected_state
async def test_primary_filtration(spa, setup_entry, hass):
async def test_primary_filtration(spa, spa_state, setup_entry, hass):
"""Test the primary filtration cycle sensor."""
entity_id = f"sensor.{spa.brand}_{spa.model}_primary_filtration_cycle"
@ -35,8 +36,16 @@ async def test_primary_filtration(spa, setup_entry, hass):
assert state.attributes["mode"] == "normal"
assert state.attributes["start_hour"] == 2
await hass.services.async_call(
"smarttub",
"set_primary_filtration",
{"entity_id": entity_id, "duration": 8, "start_hour": 1},
blocking=True,
)
spa_state.primary_filtration.set.assert_called_with(duration=8, start_hour=1)
async def test_secondary_filtration(spa, setup_entry, hass):
async def test_secondary_filtration(spa, spa_state, setup_entry, hass):
"""Test the secondary filtration cycle sensor."""
entity_id = f"sensor.{spa.brand}_{spa.model}_secondary_filtration_cycle"
@ -45,3 +54,16 @@ async def test_secondary_filtration(spa, setup_entry, hass):
assert state.state == "inactive"
assert state.attributes["cycle_last_updated"] is not None
assert state.attributes["mode"] == "away"
await hass.services.async_call(
"smarttub",
"set_secondary_filtration",
{
"entity_id": entity_id,
"mode": "frequent",
},
blocking=True,
)
spa_state.secondary_filtration.set_mode.assert_called_with(
mode=smarttub.SpaSecondaryFiltrationCycle.SecondaryFiltrationMode.FREQUENT
)