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
parent
e1b6385b4d
commit
87438dd401
|
@ -1,4 +1,4 @@
|
|||
"""SmartTub integration."""
|
||||
"""Base classes for SmartTub entities."""
|
||||
import logging
|
||||
|
||||
import smarttub
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue