Add ecobee ventilator 20 min timer (#115969)
* add 20 min timer Ecobee * modify local value with estimated time * add ecobee test switch * removed manual setting of data * Add no throttle updates * add more test cases * move timezone calculation in update function * update attribute based on feedback * use timezone for time comparaison * add location data to tests * remove is_on function * update python-ecobee-api lib * remove uncessary checks --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
a3791fde09
commit
0e23d0439b
8 changed files with 220 additions and 4 deletions
|
@ -49,6 +49,7 @@ PLATFORMS = [
|
|||
Platform.NOTIFY,
|
||||
Platform.NUMBER,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.WEATHER,
|
||||
]
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyecobee"],
|
||||
"requirements": ["python-ecobee-api==0.2.17"],
|
||||
"requirements": ["python-ecobee-api==0.2.18"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_ecobee._tcp.local."
|
||||
|
|
90
homeassistant/components/ecobee/switch.py
Normal file
90
homeassistant/components/ecobee/switch.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
"""Support for using switch with ecobee thermostats."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import EcobeeData
|
||||
from .const import DOMAIN
|
||||
from .entity import EcobeeBaseEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the ecobee thermostat switch entity."""
|
||||
data: EcobeeData = hass.data[DOMAIN]
|
||||
|
||||
async_add_entities(
|
||||
(
|
||||
EcobeeVentilator20MinSwitch(data, index)
|
||||
for index, thermostat in enumerate(data.ecobee.thermostats)
|
||||
if thermostat["settings"]["ventilatorType"] != "none"
|
||||
),
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
class EcobeeVentilator20MinSwitch(EcobeeBaseEntity, SwitchEntity):
|
||||
"""A Switch class, representing 20 min timer for an ecobee thermostat with ventilator attached."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = "Ventilator 20m Timer"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: EcobeeData,
|
||||
thermostat_index: int,
|
||||
) -> None:
|
||||
"""Initialize ecobee ventilator platform."""
|
||||
super().__init__(data, thermostat_index)
|
||||
self._attr_unique_id = f"{self.base_unique_id}_ventilator_20m_timer"
|
||||
self._attr_is_on = False
|
||||
self.update_without_throttle = False
|
||||
self._operating_timezone = dt_util.get_time_zone(
|
||||
self.thermostat["location"]["timeZone"]
|
||||
)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest state from the thermostat."""
|
||||
|
||||
if self.update_without_throttle:
|
||||
await self.data.update(no_throttle=True)
|
||||
self.update_without_throttle = False
|
||||
else:
|
||||
await self.data.update()
|
||||
|
||||
ventilator_off_date_time = self.thermostat["settings"]["ventilatorOffDateTime"]
|
||||
|
||||
self._attr_is_on = ventilator_off_date_time and dt_util.parse_datetime(
|
||||
ventilator_off_date_time, raise_on_error=True
|
||||
).replace(tzinfo=self._operating_timezone) >= dt_util.now(
|
||||
self._operating_timezone
|
||||
)
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Set ventilator 20 min timer on."""
|
||||
await self.hass.async_add_executor_job(
|
||||
self.data.ecobee.set_ventilator_timer, self.thermostat_index, True
|
||||
)
|
||||
self.update_without_throttle = True
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Set ventilator 20 min timer off."""
|
||||
await self.hass.async_add_executor_job(
|
||||
self.data.ecobee.set_ventilator_timer, self.thermostat_index, False
|
||||
)
|
||||
self.update_without_throttle = True
|
|
@ -2215,7 +2215,7 @@ python-clementine-remote==1.0.1
|
|||
python-digitalocean==1.13.2
|
||||
|
||||
# homeassistant.components.ecobee
|
||||
python-ecobee-api==0.2.17
|
||||
python-ecobee-api==0.2.18
|
||||
|
||||
# homeassistant.components.etherscan
|
||||
python-etherscan-api==0.0.3
|
||||
|
|
|
@ -1730,7 +1730,7 @@ python-awair==0.2.4
|
|||
python-bsblan==0.5.18
|
||||
|
||||
# homeassistant.components.ecobee
|
||||
python-ecobee-api==0.2.17
|
||||
python-ecobee-api==0.2.18
|
||||
|
||||
# homeassistant.components.fully_kiosk
|
||||
python-fullykiosk==0.0.12
|
||||
|
|
|
@ -65,6 +65,9 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = {
|
|||
"identifier": 8675309,
|
||||
"name": "ecobee",
|
||||
"modelNumber": "athenaSmart",
|
||||
"utcTime": "2022-01-01 10:00:00",
|
||||
"thermostatTime": "2022-01-01 6:00:00",
|
||||
"location": {"timeZone": "America/Toronto"},
|
||||
"program": {
|
||||
"climates": [
|
||||
{"name": "Climate1", "climateRef": "c1"},
|
||||
|
@ -92,7 +95,8 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = {
|
|||
"humidifierMode": "manual",
|
||||
"humidity": "30",
|
||||
"hasHeatPump": True,
|
||||
"ventilatorType": "none",
|
||||
"ventilatorType": "hrv",
|
||||
"ventilatorOffDateTime": "2022-01-01 6:00:00",
|
||||
},
|
||||
"equipmentStatus": "fan",
|
||||
"events": [
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
"identifier": 8675309,
|
||||
"name": "ecobee",
|
||||
"modelNumber": "athenaSmart",
|
||||
"utcTime": "2022-01-01 10:00:00",
|
||||
"thermostatTime": "2022-01-01 6:00:00",
|
||||
"location": {
|
||||
"timeZone": "America/Toronto"
|
||||
},
|
||||
"program": {
|
||||
"climates": [
|
||||
{ "name": "Climate1", "climateRef": "c1" },
|
||||
|
@ -30,6 +35,7 @@
|
|||
"ventilatorType": "hrv",
|
||||
"ventilatorMinOnTimeHome": 20,
|
||||
"ventilatorMinOnTimeAway": 10,
|
||||
"ventilatorOffDateTime": "2022-01-01 6:00:00",
|
||||
"isVentilatorTimerOn": false,
|
||||
"hasHumidifier": true,
|
||||
"humidifierMode": "manual",
|
||||
|
|
115
tests/components/ecobee/test_switch.py
Normal file
115
tests/components/ecobee/test_switch.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
"""The test for the ecobee thermostat switch module."""
|
||||
|
||||
import copy
|
||||
from datetime import datetime, timedelta
|
||||
from unittest import mock
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.ecobee.switch import DATE_FORMAT
|
||||
from homeassistant.components.switch import DOMAIN, SERVICE_TURN_OFF, SERVICE_TURN_ON
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .common import setup_platform
|
||||
|
||||
from tests.components.ecobee import GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP
|
||||
|
||||
VENTILATOR_20MIN_ID = "switch.ecobee_ventilator_20m_timer"
|
||||
THERMOSTAT_ID = 0
|
||||
|
||||
|
||||
@pytest.fixture(name="data")
|
||||
def data_fixture():
|
||||
"""Set up data mock."""
|
||||
data = mock.Mock()
|
||||
data.return_value = copy.deepcopy(GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP)
|
||||
return data
|
||||
|
||||
|
||||
async def test_ventilator_20min_attributes(hass: HomeAssistant) -> None:
|
||||
"""Test the ventilator switch on home attributes are correct."""
|
||||
await setup_platform(hass, DOMAIN)
|
||||
|
||||
state = hass.states.get(VENTILATOR_20MIN_ID)
|
||||
assert state.state == "off"
|
||||
|
||||
|
||||
async def test_ventilator_20min_when_on(hass: HomeAssistant, data) -> None:
|
||||
"""Test the ventilator switch goes on."""
|
||||
|
||||
data.return_value["settings"]["ventilatorOffDateTime"] = (
|
||||
datetime.now() + timedelta(days=1)
|
||||
).strftime(DATE_FORMAT)
|
||||
with mock.patch("pyecobee.Ecobee.get_thermostat", data):
|
||||
await setup_platform(hass, DOMAIN)
|
||||
|
||||
state = hass.states.get(VENTILATOR_20MIN_ID)
|
||||
assert state.state == "on"
|
||||
|
||||
data.reset_mock()
|
||||
|
||||
|
||||
async def test_ventilator_20min_when_off(hass: HomeAssistant, data) -> None:
|
||||
"""Test the ventilator switch goes on."""
|
||||
|
||||
data.return_value["settings"]["ventilatorOffDateTime"] = (
|
||||
datetime.now() - timedelta(days=1)
|
||||
).strftime(DATE_FORMAT)
|
||||
with mock.patch("pyecobee.Ecobee.get_thermostat", data):
|
||||
await setup_platform(hass, DOMAIN)
|
||||
|
||||
state = hass.states.get(VENTILATOR_20MIN_ID)
|
||||
assert state.state == "off"
|
||||
|
||||
data.reset_mock()
|
||||
|
||||
|
||||
async def test_ventilator_20min_when_empty(hass: HomeAssistant, data) -> None:
|
||||
"""Test the ventilator switch goes on."""
|
||||
|
||||
data.return_value["settings"]["ventilatorOffDateTime"] = ""
|
||||
with mock.patch("pyecobee.Ecobee.get_thermostat", data):
|
||||
await setup_platform(hass, DOMAIN)
|
||||
|
||||
state = hass.states.get(VENTILATOR_20MIN_ID)
|
||||
assert state.state == "off"
|
||||
|
||||
data.reset_mock()
|
||||
|
||||
|
||||
async def test_turn_on_20min_ventilator(hass: HomeAssistant) -> None:
|
||||
"""Test the switch 20 min timer (On)."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.ecobee.Ecobee.set_ventilator_timer"
|
||||
) as mock_set_20min_ventilator:
|
||||
await setup_platform(hass, DOMAIN)
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: VENTILATOR_20MIN_ID},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_set_20min_ventilator.assert_called_once_with(THERMOSTAT_ID, True)
|
||||
|
||||
|
||||
async def test_turn_off_20min_ventilator(hass: HomeAssistant) -> None:
|
||||
"""Test the switch 20 min timer (off)."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.ecobee.Ecobee.set_ventilator_timer"
|
||||
) as mock_set_20min_ventilator:
|
||||
await setup_platform(hass, DOMAIN)
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: VENTILATOR_20MIN_ID},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_set_20min_ventilator.assert_called_once_with(THERMOSTAT_ID, False)
|
Loading…
Add table
Reference in a new issue