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.NOTIFY,
|
||||||
Platform.NUMBER,
|
Platform.NUMBER,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
|
Platform.SWITCH,
|
||||||
Platform.WEATHER,
|
Platform.WEATHER,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pyecobee"],
|
"loggers": ["pyecobee"],
|
||||||
"requirements": ["python-ecobee-api==0.2.17"],
|
"requirements": ["python-ecobee-api==0.2.18"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_ecobee._tcp.local."
|
"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
|
python-digitalocean==1.13.2
|
||||||
|
|
||||||
# homeassistant.components.ecobee
|
# homeassistant.components.ecobee
|
||||||
python-ecobee-api==0.2.17
|
python-ecobee-api==0.2.18
|
||||||
|
|
||||||
# homeassistant.components.etherscan
|
# homeassistant.components.etherscan
|
||||||
python-etherscan-api==0.0.3
|
python-etherscan-api==0.0.3
|
||||||
|
|
|
@ -1730,7 +1730,7 @@ python-awair==0.2.4
|
||||||
python-bsblan==0.5.18
|
python-bsblan==0.5.18
|
||||||
|
|
||||||
# homeassistant.components.ecobee
|
# homeassistant.components.ecobee
|
||||||
python-ecobee-api==0.2.17
|
python-ecobee-api==0.2.18
|
||||||
|
|
||||||
# homeassistant.components.fully_kiosk
|
# homeassistant.components.fully_kiosk
|
||||||
python-fullykiosk==0.0.12
|
python-fullykiosk==0.0.12
|
||||||
|
|
|
@ -65,6 +65,9 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = {
|
||||||
"identifier": 8675309,
|
"identifier": 8675309,
|
||||||
"name": "ecobee",
|
"name": "ecobee",
|
||||||
"modelNumber": "athenaSmart",
|
"modelNumber": "athenaSmart",
|
||||||
|
"utcTime": "2022-01-01 10:00:00",
|
||||||
|
"thermostatTime": "2022-01-01 6:00:00",
|
||||||
|
"location": {"timeZone": "America/Toronto"},
|
||||||
"program": {
|
"program": {
|
||||||
"climates": [
|
"climates": [
|
||||||
{"name": "Climate1", "climateRef": "c1"},
|
{"name": "Climate1", "climateRef": "c1"},
|
||||||
|
@ -92,7 +95,8 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = {
|
||||||
"humidifierMode": "manual",
|
"humidifierMode": "manual",
|
||||||
"humidity": "30",
|
"humidity": "30",
|
||||||
"hasHeatPump": True,
|
"hasHeatPump": True,
|
||||||
"ventilatorType": "none",
|
"ventilatorType": "hrv",
|
||||||
|
"ventilatorOffDateTime": "2022-01-01 6:00:00",
|
||||||
},
|
},
|
||||||
"equipmentStatus": "fan",
|
"equipmentStatus": "fan",
|
||||||
"events": [
|
"events": [
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
"identifier": 8675309,
|
"identifier": 8675309,
|
||||||
"name": "ecobee",
|
"name": "ecobee",
|
||||||
"modelNumber": "athenaSmart",
|
"modelNumber": "athenaSmart",
|
||||||
|
"utcTime": "2022-01-01 10:00:00",
|
||||||
|
"thermostatTime": "2022-01-01 6:00:00",
|
||||||
|
"location": {
|
||||||
|
"timeZone": "America/Toronto"
|
||||||
|
},
|
||||||
"program": {
|
"program": {
|
||||||
"climates": [
|
"climates": [
|
||||||
{ "name": "Climate1", "climateRef": "c1" },
|
{ "name": "Climate1", "climateRef": "c1" },
|
||||||
|
@ -30,6 +35,7 @@
|
||||||
"ventilatorType": "hrv",
|
"ventilatorType": "hrv",
|
||||||
"ventilatorMinOnTimeHome": 20,
|
"ventilatorMinOnTimeHome": 20,
|
||||||
"ventilatorMinOnTimeAway": 10,
|
"ventilatorMinOnTimeAway": 10,
|
||||||
|
"ventilatorOffDateTime": "2022-01-01 6:00:00",
|
||||||
"isVentilatorTimerOn": false,
|
"isVentilatorTimerOn": false,
|
||||||
"hasHumidifier": true,
|
"hasHumidifier": true,
|
||||||
"humidifierMode": "manual",
|
"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