mirror of https://github.com/home-assistant/core
Add support for SmartTub filtration cycles (#46868)
This commit is contained in:
parent
b1a24c8bbb
commit
5d8390fd9b
|
@ -1,6 +1,8 @@
|
|||
"""Platform for climate integration."""
|
||||
import logging
|
||||
|
||||
from smarttub import Spa
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
CURRENT_HVAC_HEAT,
|
||||
|
@ -38,9 +40,9 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity):
|
|||
"""The target water temperature for the spa."""
|
||||
|
||||
PRESET_MODES = {
|
||||
"AUTO": PRESET_NONE,
|
||||
"ECO": PRESET_ECO,
|
||||
"DAY": PRESET_DAY,
|
||||
Spa.HeatMode.AUTO: PRESET_NONE,
|
||||
Spa.HeatMode.ECONOMY: PRESET_ECO,
|
||||
Spa.HeatMode.DAY: PRESET_DAY,
|
||||
}
|
||||
|
||||
HEAT_MODES = {v: k for k, v in PRESET_MODES.items()}
|
||||
|
@ -62,7 +64,7 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity):
|
|||
@property
|
||||
def hvac_action(self):
|
||||
"""Return the current running hvac operation."""
|
||||
return self.HVAC_ACTIONS.get(self.get_spa_status("heater"))
|
||||
return self.HVAC_ACTIONS.get(self.spa_status.heater)
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
|
@ -110,7 +112,7 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity):
|
|||
@property
|
||||
def preset_mode(self):
|
||||
"""Return the current preset mode."""
|
||||
return self.PRESET_MODES[self.get_spa_status("heatMode")]
|
||||
return self.PRESET_MODES[self.spa_status.heat_mode]
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
|
@ -120,12 +122,12 @@ class SmartTubThermostat(SmartTubEntity, ClimateEntity):
|
|||
@property
|
||||
def current_temperature(self):
|
||||
"""Return the current water temperature."""
|
||||
return self.get_spa_status("water.temperature")
|
||||
return self.spa_status.water.temperature
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
"""Return the target water temperature."""
|
||||
return self.get_spa_status("setTemperature")
|
||||
return self.spa_status.set_temperature
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
"""Set new target temperature."""
|
||||
|
|
|
@ -52,18 +52,8 @@ class SmartTubEntity(CoordinatorEntity):
|
|||
spa_name = get_spa_name(self.spa)
|
||||
return f"{spa_name} {self._entity_type}"
|
||||
|
||||
def get_spa_status(self, path):
|
||||
"""Retrieve a value from the data returned by Spa.get_status().
|
||||
@property
|
||||
def spa_status(self) -> smarttub.SpaState:
|
||||
"""Retrieve the result of Spa.get_status()."""
|
||||
|
||||
Nested keys can be specified by a dotted path, e.g.
|
||||
status['foo']['bar'] is 'foo.bar'.
|
||||
"""
|
||||
|
||||
status = self.coordinator.data[self.spa.id].get("status")
|
||||
if status is None:
|
||||
return None
|
||||
|
||||
for key in path.split("."):
|
||||
status = status[key]
|
||||
|
||||
return status
|
||||
return self.coordinator.data[self.spa.id].get("status")
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"dependencies": [],
|
||||
"codeowners": ["@mdz"],
|
||||
"requirements": [
|
||||
"python-smarttub==0.0.12"
|
||||
"python-smarttub==0.0.17"
|
||||
],
|
||||
"quality_scale": "platinum"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Platform for sensor integration."""
|
||||
from enum import Enum
|
||||
import logging
|
||||
|
||||
from .const import DOMAIN, SMARTTUB_CONTROLLER
|
||||
|
@ -6,6 +7,11 @@ from .entity import SmartTubEntity
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_DURATION = "duration"
|
||||
ATTR_LAST_UPDATED = "last_updated"
|
||||
ATTR_MODE = "mode"
|
||||
ATTR_START_HOUR = "start_hour"
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up sensor entities for the sensors in the tub."""
|
||||
|
@ -18,15 +24,17 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||
[
|
||||
SmartTubSensor(controller.coordinator, spa, "State", "state"),
|
||||
SmartTubSensor(
|
||||
controller.coordinator, spa, "Flow Switch", "flowSwitch"
|
||||
controller.coordinator, spa, "Flow Switch", "flow_switch"
|
||||
),
|
||||
SmartTubSensor(controller.coordinator, spa, "Ozone", "ozone"),
|
||||
SmartTubSensor(
|
||||
controller.coordinator, spa, "Blowout Cycle", "blowoutCycle"
|
||||
controller.coordinator, spa, "Blowout Cycle", "blowout_cycle"
|
||||
),
|
||||
SmartTubSensor(
|
||||
controller.coordinator, spa, "Cleanup Cycle", "cleanupCycle"
|
||||
controller.coordinator, spa, "Cleanup Cycle", "cleanup_cycle"
|
||||
),
|
||||
SmartTubPrimaryFiltrationCycle(controller.coordinator, spa),
|
||||
SmartTubSecondaryFiltrationCycle(controller.coordinator, spa),
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -36,17 +44,69 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||
class SmartTubSensor(SmartTubEntity):
|
||||
"""Generic and base class for SmartTub sensors."""
|
||||
|
||||
def __init__(self, coordinator, spa, sensor_name, spa_status_key):
|
||||
def __init__(self, coordinator, spa, sensor_name, attr_name):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator, spa, sensor_name)
|
||||
self._spa_status_key = spa_status_key
|
||||
self._attr_name = attr_name
|
||||
|
||||
@property
|
||||
def _state(self):
|
||||
"""Retrieve the underlying state from the spa."""
|
||||
return self.get_spa_status(self._spa_status_key)
|
||||
return getattr(self.spa_status, self._attr_name)
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
"""Return the current state of the sensor."""
|
||||
if isinstance(self._state, Enum):
|
||||
return self._state.name.lower()
|
||||
return self._state.lower()
|
||||
|
||||
|
||||
class SmartTubPrimaryFiltrationCycle(SmartTubSensor):
|
||||
"""The primary filtration cycle."""
|
||||
|
||||
def __init__(self, coordinator, spa):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(
|
||||
coordinator, spa, "primary filtration cycle", "primary_filtration"
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
"""Return the current state of the sensor."""
|
||||
return self._state.status.name.lower()
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
state = self._state
|
||||
return {
|
||||
ATTR_DURATION: state.duration,
|
||||
ATTR_LAST_UPDATED: state.last_updated.isoformat(),
|
||||
ATTR_MODE: state.mode.name.lower(),
|
||||
ATTR_START_HOUR: state.start_hour,
|
||||
}
|
||||
|
||||
|
||||
class SmartTubSecondaryFiltrationCycle(SmartTubSensor):
|
||||
"""The secondary filtration cycle."""
|
||||
|
||||
def __init__(self, coordinator, spa):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(
|
||||
coordinator, spa, "Secondary Filtration Cycle", "secondary_filtration"
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
"""Return the current state of the sensor."""
|
||||
return self._state.status.name.lower()
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
state = self._state
|
||||
return {
|
||||
ATTR_LAST_UPDATED: state.last_updated.isoformat(),
|
||||
ATTR_MODE: state.mode.name.lower(),
|
||||
}
|
||||
|
|
|
@ -1817,7 +1817,7 @@ python-qbittorrent==0.4.2
|
|||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.12
|
||||
python-smarttub==0.0.17
|
||||
|
||||
# homeassistant.components.sochain
|
||||
python-sochain-api==0.0.2
|
||||
|
|
|
@ -942,7 +942,7 @@ python-nest==4.1.0
|
|||
python-openzwave-mqtt[mqtt-client]==1.4.0
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.12
|
||||
python-smarttub==0.0.17
|
||||
|
||||
# homeassistant.components.songpal
|
||||
python-songpal==0.12
|
||||
|
|
|
@ -42,32 +42,34 @@ def mock_spa():
|
|||
mock_spa.id = "mockspa1"
|
||||
mock_spa.brand = "mockbrand1"
|
||||
mock_spa.model = "mockmodel1"
|
||||
mock_spa.get_status.return_value = {
|
||||
"setTemperature": 39,
|
||||
"water": {"temperature": 38},
|
||||
"heater": "ON",
|
||||
"heatMode": "AUTO",
|
||||
"state": "NORMAL",
|
||||
"primaryFiltration": {
|
||||
"cycle": 1,
|
||||
"duration": 4,
|
||||
"lastUpdated": "2021-01-20T11:38:57.014Z",
|
||||
"mode": "NORMAL",
|
||||
"startHour": 2,
|
||||
"status": "INACTIVE",
|
||||
mock_spa.get_status.return_value = smarttub.SpaState(
|
||||
mock_spa,
|
||||
**{
|
||||
"setTemperature": 39,
|
||||
"water": {"temperature": 38},
|
||||
"heater": "ON",
|
||||
"heatMode": "AUTO",
|
||||
"state": "NORMAL",
|
||||
"primaryFiltration": {
|
||||
"cycle": 1,
|
||||
"duration": 4,
|
||||
"lastUpdated": "2021-01-20T11:38:57.014Z",
|
||||
"mode": "NORMAL",
|
||||
"startHour": 2,
|
||||
"status": "INACTIVE",
|
||||
},
|
||||
"secondaryFiltration": {
|
||||
"lastUpdated": "2020-07-09T19:39:52.961Z",
|
||||
"mode": "AWAY",
|
||||
"status": "INACTIVE",
|
||||
},
|
||||
"flowSwitch": "OPEN",
|
||||
"ozone": "OFF",
|
||||
"uv": "OFF",
|
||||
"blowoutCycle": "INACTIVE",
|
||||
"cleanupCycle": "INACTIVE",
|
||||
},
|
||||
"secondaryFiltration": {
|
||||
"lastUpdated": "2020-07-09T19:39:52.961Z",
|
||||
"mode": "AWAY",
|
||||
"status": "INACTIVE",
|
||||
},
|
||||
"flowSwitch": "OPEN",
|
||||
"ozone": "OFF",
|
||||
"uv": "OFF",
|
||||
"blowoutCycle": "INACTIVE",
|
||||
"cleanupCycle": "INACTIVE",
|
||||
}
|
||||
|
||||
)
|
||||
mock_circulation_pump = create_autospec(smarttub.SpaPump, instance=True)
|
||||
mock_circulation_pump.id = "CP"
|
||||
mock_circulation_pump.spa = mock_spa
|
||||
|
|
|
@ -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.return_value["heater"] = "OFF"
|
||||
spa.get_status.return_value.heater = "OFF"
|
||||
await trigger_update(hass)
|
||||
state = hass.states.get(entity_id)
|
||||
|
||||
|
@ -83,9 +83,9 @@ async def test_thermostat_update(spa, setup_entry, hass):
|
|||
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_ECO},
|
||||
blocking=True,
|
||||
)
|
||||
spa.set_heat_mode.assert_called_with("ECO")
|
||||
spa.set_heat_mode.assert_called_with(smarttub.Spa.HeatMode.ECONOMY)
|
||||
|
||||
spa.get_status.return_value["heatMode"] = "ECO"
|
||||
spa.get_status.return_value.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
|
||||
|
|
|
@ -11,7 +11,7 @@ async def test_sensors(spa, setup_entry, hass):
|
|||
assert state is not None
|
||||
assert state.state == "normal"
|
||||
|
||||
spa.get_status.return_value["state"] = "BAD"
|
||||
spa.get_status.return_value.state = "BAD"
|
||||
await trigger_update(hass)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
|
@ -36,3 +36,19 @@ async def test_sensors(spa, setup_entry, hass):
|
|||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "inactive"
|
||||
|
||||
entity_id = f"sensor.{spa.brand}_{spa.model}_primary_filtration_cycle"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "inactive"
|
||||
assert state.attributes["duration"] == 4
|
||||
assert state.attributes["last_updated"] is not None
|
||||
assert state.attributes["mode"] == "normal"
|
||||
assert state.attributes["start_hour"] == 2
|
||||
|
||||
entity_id = f"sensor.{spa.brand}_{spa.model}_secondary_filtration_cycle"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "inactive"
|
||||
assert state.attributes["last_updated"] is not None
|
||||
assert state.attributes["mode"] == "away"
|
||||
|
|
Loading…
Reference in New Issue