Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use voluptuous for Honeywell #3298

Merged
merged 1 commit into from
Sep 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 51 additions & 46 deletions homeassistant/components/climate/honeywell.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,67 @@
import logging
import socket

from homeassistant.components.climate import ClimateDevice
import voluptuous as vol

from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv

REQUIREMENTS = ['evohomeclient==0.2.5',
'somecomfort==0.2.1']

_LOGGER = logging.getLogger(__name__)

CONF_AWAY_TEMP = "away_temperature"
DEFAULT_AWAY_TEMP = 16
ATTR_FAN = 'fan'
ATTR_FANMODE = 'fanmode'
ATTR_SYSTEM_MODE = 'system_mode'

CONF_AWAY_TEMPERATURE = 'away_temperature'
CONF_REGION = 'region'

DEFAULT_AWAY_TEMPERATURE = 16
DEFAULT_REGION = 'eu'
REGIONS = ['eu', 'us']

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_AWAY_TEMPERATURE, default=DEFAULT_AWAY_TEMPERATURE):
vol.Coerce(float),
vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS),
})


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the HoneywelL thermostat."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
region = config.get(CONF_REGION)

if region == 'us':
return _setup_us(username, password, config, add_devices)
else:
return _setup_round(username, password, config, add_devices)


def _setup_round(username, password, config, add_devices):
"""Setup rounding function."""
from evohomeclient import EvohomeClient

try:
away_temp = float(config.get(CONF_AWAY_TEMP, DEFAULT_AWAY_TEMP))
except ValueError:
_LOGGER.error("value entered for item %s should convert to a number",
CONF_AWAY_TEMP)
return False

away_temp = config.get(CONF_AWAY_TEMPERATURE)
evo_api = EvohomeClient(username, password)

try:
zones = evo_api.temperatures(force_refresh=True)
for i, zone in enumerate(zones):
add_devices([RoundThermostat(evo_api,
zone['id'],
i == 0,
away_temp)])
add_devices(
[RoundThermostat(evo_api, zone['id'], i == 0, away_temp)]
)
except socket.error:
_LOGGER.error(
"Connection error logging into the honeywell evohome web service"
)
"Connection error logging into the honeywell evohome web service")
return False
return True

Expand Down Expand Up @@ -74,26 +97,6 @@ def _setup_us(username, password, config, add_devices):
return True


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the honeywel thermostat."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
region = config.get('region', 'eu').lower()

if username is None or password is None:
_LOGGER.error("Missing required configuration items %s or %s",
CONF_USERNAME, CONF_PASSWORD)
return False
if region not in ('us', 'eu'):
_LOGGER.error('Region `%s` is invalid (use either us or eu)', region)
return False

if region == 'us':
return _setup_us(username, password, config, add_devices)
else:
return _setup_round(username, password, config, add_devices)


class RoundThermostat(ClimateDevice):
"""Representation of a Honeywell Round Connected thermostat."""

Expand All @@ -103,7 +106,7 @@ def __init__(self, device, zone_id, master, away_temp):
self.device = device
self._current_temperature = None
self._target_temperature = None
self._name = "round connected"
self._name = 'round connected'
self._id = zone_id
self._master = master
self._is_dhw = False
Expand Down Expand Up @@ -143,7 +146,7 @@ def set_temperature(self, **kwargs):
@property
def current_operation(self: ClimateDevice) -> str:
"""Get the current operation of the system."""
return getattr(self.device, 'system_mode', None)
return getattr(self.device, ATTR_SYSTEM_MODE, None)

@property
def is_away_mode_on(self):
Expand All @@ -152,7 +155,7 @@ def is_away_mode_on(self):

def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the HVAC mode for the thermostat."""
if hasattr(self.device, 'system_mode'):
if hasattr(self.device, ATTR_SYSTEM_MODE):
self.device.system_mode = operation_mode

def turn_away_mode_on(self):
Expand Down Expand Up @@ -186,8 +189,8 @@ def update(self):

self._current_temperature = data['temp']
self._target_temperature = data['setpoint']
if data['thermostat'] == "DOMESTIC_HOT_WATER":
self._name = "Hot Water"
if data['thermostat'] == 'DOMESTIC_HOT_WATER':
self._name = 'Hot Water'
self._is_dhw = True
else:
self._name = data['name']
Expand Down Expand Up @@ -236,7 +239,7 @@ def target_temperature(self):
@property
def current_operation(self: ClimateDevice) -> str:
"""Return current operation ie. heat, cool, idle."""
return getattr(self._device, 'system_mode', None)
return getattr(self._device, ATTR_SYSTEM_MODE, None)

def set_temperature(self, **kwargs):
"""Set target temperature."""
Expand All @@ -255,9 +258,11 @@ def set_temperature(self, **kwargs):
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {'fan': (self.is_fan_on and 'running' or 'idle'),
'fanmode': self._device.fan_mode,
'system_mode': self._device.system_mode}
return {
ATTR_FAN: (self.is_fan_on and 'running' or 'idle'),
ATTR_FANMODE: self._device.fan_mode,
ATTR_SYSTEM_MODE: self._device.system_mode,
}

def turn_away_mode_on(self):
"""Turn away on."""
Expand All @@ -269,5 +274,5 @@ def turn_away_mode_off(self):

def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the system mode (Cool, Heat, etc)."""
if hasattr(self._device, 'system_mode'):
if hasattr(self._device, ATTR_SYSTEM_MODE):
self._device.system_mode = operation_mode
77 changes: 46 additions & 31 deletions tests/components/climate/test_honeywell.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import unittest
from unittest import mock

import voluptuous as vol
import somecomfort

from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD,
TEMP_CELSIUS, TEMP_FAHRENHEIT)
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, TEMP_CELSIUS, TEMP_FAHRENHEIT)
import homeassistant.components.climate.honeywell as honeywell


Expand All @@ -21,17 +22,30 @@ def test_setup_us(self, mock_ht, mock_sc):
config = {
CONF_USERNAME: 'user',
CONF_PASSWORD: 'pass',
'region': 'us',
honeywell.CONF_REGION: 'us',
}
bad_pass_config = {
CONF_USERNAME: 'user',
'region': 'us',
honeywell.CONF_REGION: 'us',
}
bad_region_config = {
CONF_USERNAME: 'user',
CONF_PASSWORD: 'pass',
'region': 'un',
honeywell.CONF_REGION: 'un',
}

with self.assertRaises(vol.Invalid):
honeywell.PLATFORM_SCHEMA(None)

with self.assertRaises(vol.Invalid):
honeywell.PLATFORM_SCHEMA({})

with self.assertRaises(vol.Invalid):
honeywell.PLATFORM_SCHEMA(bad_pass_config)

with self.assertRaises(vol.Invalid):
honeywell.PLATFORM_SCHEMA(bad_region_config)

hass = mock.MagicMock()
add_devices = mock.MagicMock()

Expand All @@ -46,10 +60,6 @@ def test_setup_us(self, mock_ht, mock_sc):
locations[0].devices_by_id.values.return_value = devices_1
locations[1].devices_by_id.values.return_value = devices_2

result = honeywell.setup_platform(hass, bad_pass_config, add_devices)
self.assertFalse(result)
result = honeywell.setup_platform(hass, bad_region_config, add_devices)
self.assertFalse(result)
result = honeywell.setup_platform(hass, config, add_devices)
self.assertTrue(result)
mock_sc.assert_called_once_with('user', 'pass')
Expand All @@ -67,7 +77,7 @@ def test_setup_us_failures(self, mock_sc):
config = {
CONF_USERNAME: 'user',
CONF_PASSWORD: 'pass',
'region': 'us',
honeywell.CONF_REGION: 'us',
}

mock_sc.side_effect = somecomfort.AuthError
Expand All @@ -88,7 +98,7 @@ def _test_us_filtered_devices(self, mock_ht, mock_sc, loc=None, dev=None):
config = {
CONF_USERNAME: 'user',
CONF_PASSWORD: 'pass',
'region': 'us',
honeywell.CONF_REGION: 'us',
'location': loc,
'thermostat': dev,
}
Expand Down Expand Up @@ -152,12 +162,12 @@ def test_us_filtered_location_2(self):
@mock.patch('homeassistant.components.climate.honeywell.'
'RoundThermostat')
def test_eu_setup_full_config(self, mock_round, mock_evo):
"""Test the EU setup wwith complete configuration."""
"""Test the EU setup with complete configuration."""
config = {
CONF_USERNAME: 'user',
CONF_PASSWORD: 'pass',
honeywell.CONF_AWAY_TEMP: 20,
'region': 'eu',
honeywell.CONF_AWAY_TEMPERATURE: 20.0,
honeywell.CONF_REGION: 'eu',
}
mock_evo.return_value.temperatures.return_value = [
{'id': 'foo'}, {'id': 'bar'}]
Expand All @@ -168,8 +178,8 @@ def test_eu_setup_full_config(self, mock_round, mock_evo):
mock_evo.return_value.temperatures.assert_called_once_with(
force_refresh=True)
mock_round.assert_has_calls([
mock.call(mock_evo.return_value, 'foo', True, 20),
mock.call(mock_evo.return_value, 'bar', False, 20),
mock.call(mock_evo.return_value, 'foo', True, 20.0),
mock.call(mock_evo.return_value, 'bar', False, 20.0),
])
self.assertEqual(2, add_devices.call_count)

Expand All @@ -181,17 +191,20 @@ def test_eu_setup_partial_config(self, mock_round, mock_evo):
config = {
CONF_USERNAME: 'user',
CONF_PASSWORD: 'pass',
'region': 'eu',
honeywell.CONF_REGION: 'eu',
}

mock_evo.return_value.temperatures.return_value = [
{'id': 'foo'}, {'id': 'bar'}]
config[honeywell.CONF_AWAY_TEMPERATURE] = \
honeywell.DEFAULT_AWAY_TEMPERATURE

hass = mock.MagicMock()
add_devices = mock.MagicMock()
self.assertTrue(honeywell.setup_platform(hass, config, add_devices))
default = honeywell.DEFAULT_AWAY_TEMP
mock_round.assert_has_calls([
mock.call(mock_evo.return_value, 'foo', True, default),
mock.call(mock_evo.return_value, 'bar', False, default),
mock.call(mock_evo.return_value, 'foo', True, 16),
mock.call(mock_evo.return_value, 'bar', False, 16),
])

@mock.patch('evohomeclient.EvohomeClient')
Expand All @@ -202,10 +215,12 @@ def test_eu_setup_bad_temp(self, mock_round, mock_evo):
config = {
CONF_USERNAME: 'user',
CONF_PASSWORD: 'pass',
honeywell.CONF_AWAY_TEMP: 'ponies',
'region': 'eu',
honeywell.CONF_AWAY_TEMPERATURE: 'ponies',
honeywell.CONF_REGION: 'eu',
}
self.assertFalse(honeywell.setup_platform(None, config, None))

with self.assertRaises(vol.Invalid):
honeywell.PLATFORM_SCHEMA(config)

@mock.patch('evohomeclient.EvohomeClient')
@mock.patch('homeassistant.components.climate.honeywell.'
Expand All @@ -215,8 +230,8 @@ def test_eu_setup_error(self, mock_round, mock_evo):
config = {
CONF_USERNAME: 'user',
CONF_PASSWORD: 'pass',
honeywell.CONF_AWAY_TEMP: 20,
'region': 'eu',
honeywell.CONF_AWAY_TEMPERATURE: 20,
honeywell.CONF_REGION: 'eu',
}
mock_evo.return_value.temperatures.side_effect = socket.error
add_devices = mock.MagicMock()
Expand Down Expand Up @@ -356,9 +371,9 @@ def test_set_temp_fail(self):
def test_attributes(self):
"""Test the attributes."""
expected = {
'fan': 'running',
'fanmode': 'auto',
'system_mode': 'heat',
honeywell.ATTR_FAN: 'running',
honeywell.ATTR_FANMODE: 'auto',
honeywell.ATTR_SYSTEM_MODE: 'heat',
}
self.assertEqual(expected, self.honeywell.device_state_attributes)
expected['fan'] = 'idle'
Expand All @@ -370,8 +385,8 @@ def test_with_no_fan(self):
self.device.fan_running = False
self.device.fan_mode = None
expected = {
'fan': 'idle',
'fanmode': None,
'system_mode': 'heat',
honeywell.ATTR_FAN: 'idle',
honeywell.ATTR_FANMODE: None,
honeywell.ATTR_SYSTEM_MODE: 'heat',
}
self.assertEqual(expected, self.honeywell.device_state_attributes)