* Honeywell fixes and improvements Give the Honeywell device a state ('On', 'Off', etc) that can be displayed to user and understood by other components. Previously this was always 'Unknown'. Update also raises a state_changed event when a new temperature is polled. These two together fix an issue (#8688) where Honeywell climate data couldn't be logged in InfluxDB. * Roll back some changes These were not necessary to achieve the result I wanted. * Renamed RoundThermostat's 'device' member for greater clarity Now called 'client' * Improve and simplify discovering thermostat mode Per code review, this is a rather neater way to discover the thermostat mode * Update tests for compatibility with new component The tests previously relied upon the update() method being called in the constructor. This is no longer the case. * Address formatting review comment Parens not necessary * This system mode is not certain to apply to domestic hot water Moved the mode lookup to only happen on update of radiator devices, since hot water devices seem to be treated differently and I can't test.
446 lines
17 KiB
Python
446 lines
17 KiB
Python
"""The test the Honeywell thermostat module."""
|
|
import socket
|
|
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.components.climate import (
|
|
ATTR_FAN_MODE, ATTR_OPERATION_MODE, ATTR_FAN_LIST, ATTR_OPERATION_LIST)
|
|
|
|
import homeassistant.components.climate.honeywell as honeywell
|
|
|
|
|
|
class TestHoneywell(unittest.TestCase):
|
|
"""A test class for Honeywell themostats."""
|
|
|
|
@mock.patch('somecomfort.SomeComfort')
|
|
@mock.patch('homeassistant.components.climate.'
|
|
'honeywell.HoneywellUSThermostat')
|
|
def test_setup_us(self, mock_ht, mock_sc):
|
|
"""Test for the US setup."""
|
|
config = {
|
|
CONF_USERNAME: 'user',
|
|
CONF_PASSWORD: 'pass',
|
|
honeywell.CONF_COOL_AWAY_TEMPERATURE: 18,
|
|
honeywell.CONF_HEAT_AWAY_TEMPERATURE: 28,
|
|
honeywell.CONF_REGION: 'us',
|
|
}
|
|
bad_pass_config = {
|
|
CONF_USERNAME: 'user',
|
|
honeywell.CONF_COOL_AWAY_TEMPERATURE: 18,
|
|
honeywell.CONF_HEAT_AWAY_TEMPERATURE: 28,
|
|
honeywell.CONF_REGION: 'us',
|
|
}
|
|
bad_region_config = {
|
|
CONF_USERNAME: 'user',
|
|
CONF_PASSWORD: 'pass',
|
|
honeywell.CONF_COOL_AWAY_TEMPERATURE: 18,
|
|
honeywell.CONF_HEAT_AWAY_TEMPERATURE: 28,
|
|
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()
|
|
|
|
locations = [
|
|
mock.MagicMock(),
|
|
mock.MagicMock(),
|
|
]
|
|
devices_1 = [mock.MagicMock()]
|
|
devices_2 = [mock.MagicMock(), mock.MagicMock]
|
|
mock_sc.return_value.locations_by_id.values.return_value = \
|
|
locations
|
|
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, config, add_devices)
|
|
self.assertTrue(result)
|
|
self.assertEqual(mock_sc.call_count, 1)
|
|
self.assertEqual(mock_sc.call_args, mock.call('user', 'pass'))
|
|
mock_ht.assert_has_calls([
|
|
mock.call(mock_sc.return_value, devices_1[0], 18, 28,
|
|
'user', 'pass'),
|
|
mock.call(mock_sc.return_value, devices_2[0], 18, 28,
|
|
'user', 'pass'),
|
|
mock.call(mock_sc.return_value, devices_2[1], 18, 28,
|
|
'user', 'pass'),
|
|
])
|
|
|
|
@mock.patch('somecomfort.SomeComfort')
|
|
def test_setup_us_failures(self, mock_sc):
|
|
"""Test the US setup."""
|
|
hass = mock.MagicMock()
|
|
add_devices = mock.MagicMock()
|
|
config = {
|
|
CONF_USERNAME: 'user',
|
|
CONF_PASSWORD: 'pass',
|
|
honeywell.CONF_REGION: 'us',
|
|
}
|
|
|
|
mock_sc.side_effect = somecomfort.AuthError
|
|
result = honeywell.setup_platform(hass, config, add_devices)
|
|
self.assertFalse(result)
|
|
self.assertFalse(add_devices.called)
|
|
|
|
mock_sc.side_effect = somecomfort.SomeComfortError
|
|
result = honeywell.setup_platform(hass, config, add_devices)
|
|
self.assertFalse(result)
|
|
self.assertFalse(add_devices.called)
|
|
|
|
@mock.patch('somecomfort.SomeComfort')
|
|
@mock.patch('homeassistant.components.climate.'
|
|
'honeywell.HoneywellUSThermostat')
|
|
def _test_us_filtered_devices(self, mock_ht, mock_sc, loc=None, dev=None):
|
|
"""Test for US filtered thermostats."""
|
|
config = {
|
|
CONF_USERNAME: 'user',
|
|
CONF_PASSWORD: 'pass',
|
|
honeywell.CONF_REGION: 'us',
|
|
'location': loc,
|
|
'thermostat': dev,
|
|
}
|
|
locations = {
|
|
1: mock.MagicMock(locationid=mock.sentinel.loc1,
|
|
devices_by_id={
|
|
11: mock.MagicMock(
|
|
deviceid=mock.sentinel.loc1dev1),
|
|
12: mock.MagicMock(
|
|
deviceid=mock.sentinel.loc1dev2),
|
|
}),
|
|
2: mock.MagicMock(locationid=mock.sentinel.loc2,
|
|
devices_by_id={
|
|
21: mock.MagicMock(
|
|
deviceid=mock.sentinel.loc2dev1),
|
|
}),
|
|
3: mock.MagicMock(locationid=mock.sentinel.loc3,
|
|
devices_by_id={
|
|
31: mock.MagicMock(
|
|
deviceid=mock.sentinel.loc3dev1),
|
|
}),
|
|
}
|
|
mock_sc.return_value = mock.MagicMock(locations_by_id=locations)
|
|
hass = mock.MagicMock()
|
|
add_devices = mock.MagicMock()
|
|
self.assertEqual(True,
|
|
honeywell.setup_platform(hass, config, add_devices))
|
|
|
|
return mock_ht.call_args_list, mock_sc
|
|
|
|
def test_us_filtered_thermostat_1(self):
|
|
"""Test for US filtered thermostats."""
|
|
result, client = self._test_us_filtered_devices(
|
|
dev=mock.sentinel.loc1dev1)
|
|
devices = [x[0][1].deviceid for x in result]
|
|
self.assertEqual([mock.sentinel.loc1dev1], devices)
|
|
|
|
def test_us_filtered_thermostat_2(self):
|
|
"""Test for US filtered location."""
|
|
result, client = self._test_us_filtered_devices(
|
|
dev=mock.sentinel.loc2dev1)
|
|
devices = [x[0][1].deviceid for x in result]
|
|
self.assertEqual([mock.sentinel.loc2dev1], devices)
|
|
|
|
def test_us_filtered_location_1(self):
|
|
"""Test for US filtered locations."""
|
|
result, client = self._test_us_filtered_devices(
|
|
loc=mock.sentinel.loc1)
|
|
devices = [x[0][1].deviceid for x in result]
|
|
self.assertEqual([mock.sentinel.loc1dev1,
|
|
mock.sentinel.loc1dev2], devices)
|
|
|
|
def test_us_filtered_location_2(self):
|
|
"""Test for US filtered locations."""
|
|
result, client = self._test_us_filtered_devices(
|
|
loc=mock.sentinel.loc2)
|
|
devices = [x[0][1].deviceid for x in result]
|
|
self.assertEqual([mock.sentinel.loc2dev1], devices)
|
|
|
|
@mock.patch('evohomeclient.EvohomeClient')
|
|
@mock.patch('homeassistant.components.climate.honeywell.'
|
|
'RoundThermostat')
|
|
def test_eu_setup_full_config(self, mock_round, mock_evo):
|
|
"""Test the EU setup with complete configuration."""
|
|
config = {
|
|
CONF_USERNAME: 'user',
|
|
CONF_PASSWORD: 'pass',
|
|
honeywell.CONF_AWAY_TEMPERATURE: 20.0,
|
|
honeywell.CONF_REGION: 'eu',
|
|
}
|
|
mock_evo.return_value.temperatures.return_value = [
|
|
{'id': 'foo'}, {'id': 'bar'}]
|
|
hass = mock.MagicMock()
|
|
add_devices = mock.MagicMock()
|
|
self.assertTrue(honeywell.setup_platform(hass, config, add_devices))
|
|
self.assertEqual(mock_evo.call_count, 1)
|
|
self.assertEqual(mock_evo.call_args, mock.call('user', 'pass'))
|
|
self.assertEqual(mock_evo.return_value.temperatures.call_count, 1)
|
|
self.assertEqual(
|
|
mock_evo.return_value.temperatures.call_args,
|
|
mock.call(force_refresh=True)
|
|
)
|
|
mock_round.assert_has_calls([
|
|
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)
|
|
|
|
@mock.patch('evohomeclient.EvohomeClient')
|
|
@mock.patch('homeassistant.components.climate.honeywell.'
|
|
'RoundThermostat')
|
|
def test_eu_setup_partial_config(self, mock_round, mock_evo):
|
|
"""Test the EU setup with partial configuration."""
|
|
config = {
|
|
CONF_USERNAME: 'user',
|
|
CONF_PASSWORD: 'pass',
|
|
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))
|
|
mock_round.assert_has_calls([
|
|
mock.call(mock_evo.return_value, 'foo', True, 16),
|
|
mock.call(mock_evo.return_value, 'bar', False, 16),
|
|
])
|
|
|
|
@mock.patch('evohomeclient.EvohomeClient')
|
|
@mock.patch('homeassistant.components.climate.honeywell.'
|
|
'RoundThermostat')
|
|
def test_eu_setup_bad_temp(self, mock_round, mock_evo):
|
|
"""Test the EU setup with invalid temperature."""
|
|
config = {
|
|
CONF_USERNAME: 'user',
|
|
CONF_PASSWORD: 'pass',
|
|
honeywell.CONF_AWAY_TEMPERATURE: 'ponies',
|
|
honeywell.CONF_REGION: 'eu',
|
|
}
|
|
|
|
with self.assertRaises(vol.Invalid):
|
|
honeywell.PLATFORM_SCHEMA(config)
|
|
|
|
@mock.patch('evohomeclient.EvohomeClient')
|
|
@mock.patch('homeassistant.components.climate.honeywell.'
|
|
'RoundThermostat')
|
|
def test_eu_setup_error(self, mock_round, mock_evo):
|
|
"""Test the EU setup with errors."""
|
|
config = {
|
|
CONF_USERNAME: 'user',
|
|
CONF_PASSWORD: 'pass',
|
|
honeywell.CONF_AWAY_TEMPERATURE: 20,
|
|
honeywell.CONF_REGION: 'eu',
|
|
}
|
|
mock_evo.return_value.temperatures.side_effect = socket.error
|
|
add_devices = mock.MagicMock()
|
|
hass = mock.MagicMock()
|
|
self.assertFalse(honeywell.setup_platform(hass, config, add_devices))
|
|
|
|
|
|
class TestHoneywellRound(unittest.TestCase):
|
|
"""A test class for Honeywell Round thermostats."""
|
|
|
|
def setup_method(self, method):
|
|
"""Test the setup method."""
|
|
def fake_temperatures(force_refresh=None):
|
|
"""Create fake temperatures."""
|
|
temps = [
|
|
{'id': '1', 'temp': 20, 'setpoint': 21,
|
|
'thermostat': 'main', 'name': 'House'},
|
|
{'id': '2', 'temp': 21, 'setpoint': 22,
|
|
'thermostat': 'DOMESTIC_HOT_WATER'},
|
|
]
|
|
return temps
|
|
|
|
self.device = mock.MagicMock()
|
|
self.device.temperatures.side_effect = fake_temperatures
|
|
self.round1 = honeywell.RoundThermostat(self.device, '1',
|
|
True, 16)
|
|
self.round1.update()
|
|
self.round2 = honeywell.RoundThermostat(self.device, '2',
|
|
False, 17)
|
|
self.round2.update()
|
|
|
|
def test_attributes(self):
|
|
"""Test the attributes."""
|
|
self.assertEqual('House', self.round1.name)
|
|
self.assertEqual(TEMP_CELSIUS, self.round1.temperature_unit)
|
|
self.assertEqual(20, self.round1.current_temperature)
|
|
self.assertEqual(21, self.round1.target_temperature)
|
|
self.assertFalse(self.round1.is_away_mode_on)
|
|
|
|
self.assertEqual('Hot Water', self.round2.name)
|
|
self.assertEqual(TEMP_CELSIUS, self.round2.temperature_unit)
|
|
self.assertEqual(21, self.round2.current_temperature)
|
|
self.assertEqual(None, self.round2.target_temperature)
|
|
self.assertFalse(self.round2.is_away_mode_on)
|
|
|
|
def test_away_mode(self):
|
|
"""Test setting the away mode."""
|
|
self.assertFalse(self.round1.is_away_mode_on)
|
|
self.round1.turn_away_mode_on()
|
|
self.assertTrue(self.round1.is_away_mode_on)
|
|
self.assertEqual(self.device.set_temperature.call_count, 1)
|
|
self.assertEqual(
|
|
self.device.set_temperature.call_args, mock.call('House', 16)
|
|
)
|
|
|
|
self.device.set_temperature.reset_mock()
|
|
self.round1.turn_away_mode_off()
|
|
self.assertFalse(self.round1.is_away_mode_on)
|
|
self.assertEqual(self.device.cancel_temp_override.call_count, 1)
|
|
self.assertEqual(
|
|
self.device.cancel_temp_override.call_args, mock.call('House')
|
|
)
|
|
|
|
def test_set_temperature(self):
|
|
"""Test setting the temperature."""
|
|
self.round1.set_temperature(temperature=25)
|
|
self.assertEqual(self.device.set_temperature.call_count, 1)
|
|
self.assertEqual(
|
|
self.device.set_temperature.call_args, mock.call('House', 25)
|
|
)
|
|
|
|
def test_set_operation_mode(self: unittest.TestCase) -> None:
|
|
"""Test setting the system operation."""
|
|
self.round1.set_operation_mode('cool')
|
|
self.assertEqual('cool', self.round1.current_operation)
|
|
self.assertEqual('cool', self.device.system_mode)
|
|
|
|
self.round1.set_operation_mode('heat')
|
|
self.assertEqual('heat', self.round1.current_operation)
|
|
self.assertEqual('heat', self.device.system_mode)
|
|
|
|
|
|
class TestHoneywellUS(unittest.TestCase):
|
|
"""A test class for Honeywell US thermostats."""
|
|
|
|
def setup_method(self, method):
|
|
"""Test the setup method."""
|
|
self.client = mock.MagicMock()
|
|
self.device = mock.MagicMock()
|
|
self.cool_away_temp = 18
|
|
self.heat_away_temp = 28
|
|
self.honeywell = honeywell.HoneywellUSThermostat(
|
|
self.client, self.device,
|
|
self.cool_away_temp, self.heat_away_temp,
|
|
'user', 'password')
|
|
|
|
self.device.fan_running = True
|
|
self.device.name = 'test'
|
|
self.device.temperature_unit = 'F'
|
|
self.device.current_temperature = 72
|
|
self.device.setpoint_cool = 78
|
|
self.device.setpoint_heat = 65
|
|
self.device.system_mode = 'heat'
|
|
self.device.fan_mode = 'auto'
|
|
|
|
def test_properties(self):
|
|
"""Test the properties."""
|
|
self.assertTrue(self.honeywell.is_fan_on)
|
|
self.assertEqual('test', self.honeywell.name)
|
|
self.assertEqual(72, self.honeywell.current_temperature)
|
|
|
|
def test_unit_of_measurement(self):
|
|
"""Test the unit of measurement."""
|
|
self.assertEqual(TEMP_FAHRENHEIT, self.honeywell.temperature_unit)
|
|
self.device.temperature_unit = 'C'
|
|
self.assertEqual(TEMP_CELSIUS, self.honeywell.temperature_unit)
|
|
|
|
def test_target_temp(self):
|
|
"""Test the target temperature."""
|
|
self.assertEqual(65, self.honeywell.target_temperature)
|
|
self.device.system_mode = 'cool'
|
|
self.assertEqual(78, self.honeywell.target_temperature)
|
|
|
|
def test_set_temp(self):
|
|
"""Test setting the temperature."""
|
|
self.honeywell.set_temperature(temperature=70)
|
|
self.assertEqual(70, self.device.setpoint_heat)
|
|
self.assertEqual(70, self.honeywell.target_temperature)
|
|
|
|
self.device.system_mode = 'cool'
|
|
self.assertEqual(78, self.honeywell.target_temperature)
|
|
self.honeywell.set_temperature(temperature=74)
|
|
self.assertEqual(74, self.device.setpoint_cool)
|
|
self.assertEqual(74, self.honeywell.target_temperature)
|
|
|
|
def test_set_operation_mode(self: unittest.TestCase) -> None:
|
|
"""Test setting the operation mode."""
|
|
self.honeywell.set_operation_mode('cool')
|
|
self.assertEqual('cool', self.device.system_mode)
|
|
|
|
self.honeywell.set_operation_mode('heat')
|
|
self.assertEqual('heat', self.device.system_mode)
|
|
|
|
def test_set_temp_fail(self):
|
|
"""Test if setting the temperature fails."""
|
|
self.device.setpoint_heat = mock.MagicMock(
|
|
side_effect=somecomfort.SomeComfortError)
|
|
self.honeywell.set_temperature(temperature=123)
|
|
|
|
def test_attributes(self):
|
|
"""Test the attributes."""
|
|
expected = {
|
|
honeywell.ATTR_FAN: 'running',
|
|
ATTR_FAN_MODE: 'auto',
|
|
ATTR_OPERATION_MODE: 'heat',
|
|
ATTR_FAN_LIST: somecomfort.FAN_MODES,
|
|
ATTR_OPERATION_LIST: somecomfort.SYSTEM_MODES,
|
|
}
|
|
self.assertEqual(expected, self.honeywell.device_state_attributes)
|
|
expected['fan'] = 'idle'
|
|
self.device.fan_running = False
|
|
self.assertEqual(expected, self.honeywell.device_state_attributes)
|
|
|
|
def test_with_no_fan(self):
|
|
"""Test if there is on fan."""
|
|
self.device.fan_running = False
|
|
self.device.fan_mode = None
|
|
expected = {
|
|
honeywell.ATTR_FAN: 'idle',
|
|
ATTR_FAN_MODE: None,
|
|
ATTR_OPERATION_MODE: 'heat',
|
|
ATTR_FAN_LIST: somecomfort.FAN_MODES,
|
|
ATTR_OPERATION_LIST: somecomfort.SYSTEM_MODES,
|
|
}
|
|
self.assertEqual(expected, self.honeywell.device_state_attributes)
|
|
|
|
def test_heat_away_mode(self):
|
|
"""Test setting the heat away mode."""
|
|
self.honeywell.set_operation_mode('heat')
|
|
self.assertFalse(self.honeywell.is_away_mode_on)
|
|
self.honeywell.turn_away_mode_on()
|
|
self.assertTrue(self.honeywell.is_away_mode_on)
|
|
self.assertEqual(self.device.setpoint_heat, self.heat_away_temp)
|
|
self.assertEqual(self.device.hold_heat, True)
|
|
|
|
self.honeywell.turn_away_mode_off()
|
|
self.assertFalse(self.honeywell.is_away_mode_on)
|
|
self.assertEqual(self.device.hold_heat, False)
|
|
|
|
@mock.patch('somecomfort.SomeComfort')
|
|
def test_retry(self, test_somecomfort):
|
|
"""Test retry connection."""
|
|
old_device = self.honeywell._device
|
|
self.honeywell._retry()
|
|
self.assertEqual(self.honeywell._device, old_device)
|