-
-
Notifications
You must be signed in to change notification settings - Fork 28.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
349 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
"""Support for using humidifier with ecobee thermostats.""" | ||
from datetime import timedelta | ||
|
||
from homeassistant.components.humidifier import HumidifierEntity | ||
from homeassistant.components.humidifier.const import ( | ||
DEFAULT_MAX_HUMIDITY, | ||
DEFAULT_MIN_HUMIDITY, | ||
DEVICE_CLASS_HUMIDIFIER, | ||
MODE_AUTO, | ||
SUPPORT_MODES, | ||
) | ||
|
||
from .const import DOMAIN | ||
|
||
SCAN_INTERVAL = timedelta(minutes=3) | ||
|
||
MODE_MANUAL = "manual" | ||
MODE_OFF = "off" | ||
|
||
|
||
async def async_setup_entry(hass, config_entry, async_add_entities): | ||
"""Set up the ecobee thermostat humidifier entity.""" | ||
data = hass.data[DOMAIN] | ||
entities = [] | ||
for index in range(len(data.ecobee.thermostats)): | ||
thermostat = data.ecobee.get_thermostat(index) | ||
if thermostat["settings"]["hasHumidifier"]: | ||
entities.append(EcobeeHumidifier(data, index)) | ||
|
||
async_add_entities(entities, True) | ||
|
||
|
||
class EcobeeHumidifier(HumidifierEntity): | ||
"""A humidifier class for an ecobee thermostat with humidifer attached.""" | ||
|
||
def __init__(self, data, thermostat_index): | ||
"""Initialize ecobee humidifier platform.""" | ||
self.data = data | ||
self.thermostat_index = thermostat_index | ||
self.thermostat = self.data.ecobee.get_thermostat(self.thermostat_index) | ||
self._name = self.thermostat["name"] | ||
self._last_humidifier_on_mode = MODE_MANUAL | ||
|
||
self.update_without_throttle = False | ||
|
||
async def async_update(self): | ||
"""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() | ||
self.thermostat = self.data.ecobee.get_thermostat(self.thermostat_index) | ||
if self.mode != MODE_OFF: | ||
self._last_humidifier_on_mode = self.mode | ||
|
||
@property | ||
def available_modes(self): | ||
"""Return the list of available modes.""" | ||
return [MODE_OFF, MODE_AUTO, MODE_MANUAL] | ||
|
||
@property | ||
def device_class(self): | ||
"""Return the device class type.""" | ||
return DEVICE_CLASS_HUMIDIFIER | ||
|
||
@property | ||
def is_on(self): | ||
"""Return True if the humidifier is on.""" | ||
return self.mode != MODE_OFF | ||
|
||
@property | ||
def max_humidity(self): | ||
"""Return the maximum humidity.""" | ||
return DEFAULT_MAX_HUMIDITY | ||
|
||
@property | ||
def min_humidity(self): | ||
"""Return the minimum humidity.""" | ||
return DEFAULT_MIN_HUMIDITY | ||
|
||
@property | ||
def mode(self): | ||
"""Return the current mode, e.g., off, auto, manual.""" | ||
return self.thermostat["settings"]["humidifierMode"] | ||
|
||
@property | ||
def name(self): | ||
"""Return the name of the ecobee thermostat.""" | ||
return self._name | ||
|
||
@property | ||
def supported_features(self): | ||
"""Return the list of supported features.""" | ||
return SUPPORT_MODES | ||
|
||
@property | ||
def target_humidity(self) -> int: | ||
"""Return the desired humidity set point.""" | ||
return int(self.thermostat["runtime"]["desiredHumidity"]) | ||
|
||
def set_mode(self, mode): | ||
"""Set humidifier mode (auto, off, manual).""" | ||
if mode.lower() not in (self.available_modes): | ||
raise ValueError( | ||
f"Invalid mode value: {mode} Valid values are {', '.join(self.available_modes)}." | ||
) | ||
|
||
self.data.ecobee.set_humidifier_mode(self.thermostat_index, mode) | ||
self.update_without_throttle = True | ||
|
||
def set_humidity(self, humidity): | ||
"""Set the humidity level.""" | ||
self.data.ecobee.set_humidity(self.thermostat_index, humidity) | ||
self.update_without_throttle = True | ||
|
||
def turn_off(self, **kwargs): | ||
"""Set humidifier to off mode.""" | ||
self.set_mode(MODE_OFF) | ||
|
||
def turn_on(self, **kwargs): | ||
"""Set humidifier to on mode.""" | ||
self.set_mode(self._last_humidifier_on_mode) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
"""Common methods used across tests for Ecobee.""" | ||
from unittest.mock import patch | ||
|
||
from homeassistant.components.ecobee.const import CONF_REFRESH_TOKEN, DOMAIN | ||
from homeassistant.const import CONF_API_KEY | ||
from homeassistant.setup import async_setup_component | ||
|
||
from tests.common import MockConfigEntry | ||
|
||
|
||
async def setup_platform(hass, platform): | ||
"""Set up the ecobee platform.""" | ||
mock_entry = MockConfigEntry( | ||
domain=DOMAIN, | ||
data={ | ||
CONF_API_KEY: "ABC123", | ||
CONF_REFRESH_TOKEN: "EFG456", | ||
}, | ||
) | ||
mock_entry.add_to_hass(hass) | ||
|
||
with patch("homeassistant.components.ecobee.const.PLATFORMS", [platform]): | ||
assert await async_setup_component(hass, DOMAIN, {}) | ||
|
||
await hass.async_block_till_done() | ||
|
||
return mock_entry |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
"""Fixtures for tests.""" | ||
import pytest | ||
|
||
from tests.common import load_fixture | ||
|
||
|
||
@pytest.fixture(autouse=True) | ||
def requests_mock_fixture(requests_mock): | ||
"""Fixture to provide a requests mocker.""" | ||
requests_mock.get( | ||
"https://api.ecobee.com/1/thermostat", | ||
text=load_fixture("ecobee/ecobee-data.json"), | ||
) | ||
requests_mock.post( | ||
"https://api.ecobee.com/token", | ||
text=load_fixture("ecobee/ecobee-token.json"), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
"""The test for the ecobee thermostat humidifier module.""" | ||
from unittest.mock import patch | ||
|
||
import pytest | ||
|
||
from homeassistant.components.ecobee.humidifier import MODE_MANUAL, MODE_OFF | ||
from homeassistant.components.humidifier import DOMAIN as HUMIDIFIER_DOMAIN | ||
from homeassistant.components.humidifier.const import ( | ||
ATTR_AVAILABLE_MODES, | ||
ATTR_HUMIDITY, | ||
ATTR_MAX_HUMIDITY, | ||
ATTR_MIN_HUMIDITY, | ||
DEFAULT_MAX_HUMIDITY, | ||
DEFAULT_MIN_HUMIDITY, | ||
DEVICE_CLASS_HUMIDIFIER, | ||
MODE_AUTO, | ||
SERVICE_SET_HUMIDITY, | ||
SERVICE_SET_MODE, | ||
SUPPORT_MODES, | ||
) | ||
from homeassistant.const import ( | ||
ATTR_DEVICE_CLASS, | ||
ATTR_ENTITY_ID, | ||
ATTR_FRIENDLY_NAME, | ||
ATTR_MODE, | ||
ATTR_SUPPORTED_FEATURES, | ||
SERVICE_TURN_OFF, | ||
SERVICE_TURN_ON, | ||
STATE_OFF, | ||
) | ||
|
||
from .common import setup_platform | ||
|
||
DEVICE_ID = "humidifier.ecobee" | ||
|
||
|
||
async def test_attributes(hass): | ||
"""Test the humidifier attributes are correct.""" | ||
await setup_platform(hass, HUMIDIFIER_DOMAIN) | ||
|
||
state = hass.states.get(DEVICE_ID) | ||
assert state.state == STATE_OFF | ||
assert state.attributes.get(ATTR_MIN_HUMIDITY) == DEFAULT_MIN_HUMIDITY | ||
assert state.attributes.get(ATTR_MAX_HUMIDITY) == DEFAULT_MAX_HUMIDITY | ||
assert state.attributes.get(ATTR_HUMIDITY) == 40 | ||
assert state.attributes.get(ATTR_AVAILABLE_MODES) == [ | ||
MODE_OFF, | ||
MODE_AUTO, | ||
MODE_MANUAL, | ||
] | ||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "ecobee" | ||
assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_HUMIDIFIER | ||
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == SUPPORT_MODES | ||
|
||
|
||
async def test_turn_on(hass): | ||
"""Test the humidifer can be turned on.""" | ||
with patch("pyecobee.Ecobee.set_humidifier_mode") as mock_turn_on: | ||
await setup_platform(hass, HUMIDIFIER_DOMAIN) | ||
|
||
await hass.services.async_call( | ||
HUMIDIFIER_DOMAIN, | ||
SERVICE_TURN_ON, | ||
{ATTR_ENTITY_ID: DEVICE_ID}, | ||
blocking=True, | ||
) | ||
await hass.async_block_till_done() | ||
mock_turn_on.assert_called_once_with(0, "manual") | ||
|
||
|
||
async def test_turn_off(hass): | ||
"""Test the humidifer can be turned off.""" | ||
with patch("pyecobee.Ecobee.set_humidifier_mode") as mock_turn_off: | ||
await setup_platform(hass, HUMIDIFIER_DOMAIN) | ||
|
||
await hass.services.async_call( | ||
HUMIDIFIER_DOMAIN, | ||
SERVICE_TURN_OFF, | ||
{ATTR_ENTITY_ID: DEVICE_ID}, | ||
blocking=True, | ||
) | ||
await hass.async_block_till_done() | ||
mock_turn_off.assert_called_once_with(0, STATE_OFF) | ||
|
||
|
||
async def test_set_mode(hass): | ||
"""Test the humidifer can change modes.""" | ||
with patch("pyecobee.Ecobee.set_humidifier_mode") as mock_set_mode: | ||
await setup_platform(hass, HUMIDIFIER_DOMAIN) | ||
|
||
await hass.services.async_call( | ||
HUMIDIFIER_DOMAIN, | ||
SERVICE_SET_MODE, | ||
{ATTR_ENTITY_ID: DEVICE_ID, ATTR_MODE: MODE_AUTO}, | ||
blocking=True, | ||
) | ||
await hass.async_block_till_done() | ||
mock_set_mode.assert_called_once_with(0, MODE_AUTO) | ||
|
||
await hass.services.async_call( | ||
HUMIDIFIER_DOMAIN, | ||
SERVICE_SET_MODE, | ||
{ATTR_ENTITY_ID: DEVICE_ID, ATTR_MODE: MODE_MANUAL}, | ||
blocking=True, | ||
) | ||
await hass.async_block_till_done() | ||
mock_set_mode.assert_called_with(0, MODE_MANUAL) | ||
|
||
with pytest.raises(ValueError): | ||
await hass.services.async_call( | ||
HUMIDIFIER_DOMAIN, | ||
SERVICE_SET_MODE, | ||
{ATTR_ENTITY_ID: DEVICE_ID, ATTR_MODE: "ModeThatDoesntExist"}, | ||
blocking=True, | ||
) | ||
|
||
|
||
async def test_set_humidity(hass): | ||
"""Test the humidifer can set humidity level.""" | ||
with patch("pyecobee.Ecobee.set_humidity") as mock_set_humidity: | ||
await setup_platform(hass, HUMIDIFIER_DOMAIN) | ||
|
||
await hass.services.async_call( | ||
HUMIDIFIER_DOMAIN, | ||
SERVICE_SET_HUMIDITY, | ||
{ATTR_ENTITY_ID: DEVICE_ID, ATTR_HUMIDITY: 60}, | ||
blocking=True, | ||
) | ||
await hass.async_block_till_done() | ||
mock_set_humidity.assert_called_once_with(0, 60) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
"thermostatList": [ | ||
{"name": "ecobee", | ||
"program": { | ||
"climates": [ | ||
{"name": "Climate1", "climateRef": "c1"}, | ||
{"name": "Climate2", "climateRef": "c2"} | ||
], | ||
"currentClimateRef": "c1" | ||
}, | ||
"runtime": { | ||
"actualTemperature": 300, | ||
"actualHumidity": 15, | ||
"desiredHeat": 400, | ||
"desiredCool": 200, | ||
"desiredFanMode": "on", | ||
"desiredHumidity": 40 | ||
}, | ||
"settings": { | ||
"hvacMode": "auto", | ||
"heatStages": 1, | ||
"coolStages": 1, | ||
"fanMinOnTime": 10, | ||
"heatCoolMinDelta": 50, | ||
"holdAction": "nextTransition", | ||
"hasHumidifier": true, | ||
"humidifierMode": "off", | ||
"humidity": "30" | ||
}, | ||
"equipmentStatus": "fan", | ||
"events": [ | ||
{ | ||
"name": "Event1", | ||
"running": true, | ||
"type": "hold", | ||
"holdClimateRef": "away", | ||
"endDate": "2022-01-01 10:00:00", | ||
"startDate": "2022-02-02 11:00:00" | ||
} | ||
]} | ||
] | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"access_token": "Rc7JE8P7XUgSCPogLOx2VLMfITqQQrjg", | ||
"token_type": "Bearer", | ||
"expires_in": 3599, | ||
"refresh_token": "og2Obost3ucRo1ofo0EDoslGltmFMe2g", | ||
"scope": "smartWrite" | ||
} |