Skip to content
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
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ omit =
homeassistant/components/*/envisalink.py

homeassistant/components/fritzbox.py
homeassistant/components/*/fritzbox.py
homeassistant/components/switch/fritzbox.py

homeassistant/components/eufy.py
homeassistant/components/*/eufy.py
Expand Down
25 changes: 21 additions & 4 deletions homeassistant/components/climate/fritzbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,27 @@
ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_LOCKED)
from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
STATE_OFF, STATE_ON, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)

DEPENDENCIES = ['fritzbox']

_LOGGER = logging.getLogger(__name__)

SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE)

OPERATION_LIST = [STATE_HEAT, STATE_ECO]
OPERATION_LIST = [STATE_HEAT, STATE_ECO, STATE_OFF, STATE_ON]

MIN_TEMPERATURE = 8
MAX_TEMPERATURE = 28

# special temperatures for on/off in Fritz!Box API (modified by pyfritzhome)
ON_API_TEMPERATURE = 127.0
OFF_API_TEMPERATURE = 126.5
ON_REPORT_SET_TEMPERATURE = 30.0
OFF_REPORT_SET_TEMPERATURE = 0.0


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Fritzbox smarthome thermostat platform."""
Expand Down Expand Up @@ -88,6 +94,9 @@ def current_temperature(self):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._target_temperature in (ON_API_TEMPERATURE,
OFF_API_TEMPERATURE):
return None
return self._target_temperature

def set_temperature(self, **kwargs):
Expand All @@ -102,9 +111,13 @@ def set_temperature(self, **kwargs):
@property
def current_operation(self):
"""Return the current operation mode."""
if self._target_temperature == ON_API_TEMPERATURE:
return STATE_ON
if self._target_temperature == OFF_API_TEMPERATURE:
return STATE_OFF
if self._target_temperature == self._comfort_temperature:
return STATE_HEAT
elif self._target_temperature == self._eco_temperature:
if self._target_temperature == self._eco_temperature:
return STATE_ECO
return STATE_MANUAL

Expand All @@ -119,6 +132,10 @@ def set_operation_mode(self, operation_mode):
self.set_temperature(temperature=self._comfort_temperature)
elif operation_mode == STATE_ECO:
self.set_temperature(temperature=self._eco_temperature)
elif operation_mode == STATE_OFF:
self.set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
elif operation_mode == STATE_ON:
self.set_temperature(temperature=ON_REPORT_SET_TEMPERATURE)

@property
def min_temp(self):
Expand Down
172 changes: 172 additions & 0 deletions tests/components/climate/test_fritzbox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
"""The tests for the demo climate component."""
import unittest
from unittest.mock import Mock, patch

import requests

from homeassistant.components.climate.fritzbox import FritzboxThermostat


class TestFritzboxClimate(unittest.TestCase):
"""Test Fritz!Box heating thermostats."""

def setUp(self):
"""Create a mock device to test on."""
self.device = Mock()
self.device.name = 'Test Thermostat'
self.device.actual_temperature = 18.0
self.device.target_temperature = 19.5
self.device.comfort_temperature = 22.0
self.device.eco_temperature = 16.0
self.device.present = True
self.device.device_lock = True
self.device.lock = False
self.device.battery_low = True
self.device.set_target_temperature = Mock()
self.device.update = Mock()
mock_fritz = Mock()
mock_fritz.login = Mock()
self.thermostat = FritzboxThermostat(self.device, mock_fritz)

def test_init(self):
"""Test instance creation."""
self.assertEqual(18.0, self.thermostat._current_temperature)
self.assertEqual(19.5, self.thermostat._target_temperature)
self.assertEqual(22.0, self.thermostat._comfort_temperature)
self.assertEqual(16.0, self.thermostat._eco_temperature)

def test_supported_features(self):
"""Test supported features property."""
self.assertEqual(129, self.thermostat.supported_features)

def test_available(self):
"""Test available property."""
self.assertTrue(self.thermostat.available)
self.thermostat._device.present = False
self.assertFalse(self.thermostat.available)

def test_name(self):
"""Test name property."""
self.assertEqual('Test Thermostat', self.thermostat.name)

def test_temperature_unit(self):
"""Test temperature_unit property."""
self.assertEqual('°C', self.thermostat.temperature_unit)

def test_precision(self):
"""Test precision property."""
self.assertEqual(0.5, self.thermostat.precision)

def test_current_temperature(self):
"""Test current_temperature property incl. special temperatures."""
self.assertEqual(18, self.thermostat.current_temperature)

def test_target_temperature(self):
"""Test target_temperature property."""
self.assertEqual(19.5, self.thermostat.target_temperature)

self.thermostat._target_temperature = 126.5
self.assertEqual(None, self.thermostat.target_temperature)

self.thermostat._target_temperature = 127.0
self.assertEqual(None, self.thermostat.target_temperature)

@patch.object(FritzboxThermostat, 'set_operation_mode')
def test_set_temperature_operation_mode(self, mock_set_op):
"""Test set_temperature by operation_mode."""
self.thermostat.set_temperature(operation_mode='test_mode')
mock_set_op.assert_called_once_with('test_mode')

def test_set_temperature_temperature(self):
"""Test set_temperature by temperature."""
self.thermostat.set_temperature(temperature=23.0)
self.thermostat._device.set_target_temperature.\
assert_called_once_with(23.0)

@patch.object(FritzboxThermostat, 'set_operation_mode')
def test_set_temperature_none(self, mock_set_op):
"""Test set_temperature with no arguments."""
self.thermostat.set_temperature()
mock_set_op.assert_not_called()
self.thermostat._device.set_target_temperature.assert_not_called()

@patch.object(FritzboxThermostat, 'set_operation_mode')
def test_set_temperature_operation_mode_precedence(self, mock_set_op):
"""Test set_temperature for precedence of operation_mode arguement."""
self.thermostat.set_temperature(operation_mode='test_mode',
temperature=23.0)
mock_set_op.assert_called_once_with('test_mode')
self.thermostat._device.set_target_temperature.assert_not_called()

def test_current_operation(self):
"""Test operation mode property for different temperatures."""
self.thermostat._target_temperature = 127.0
self.assertEqual('on', self.thermostat.current_operation)
self.thermostat._target_temperature = 126.5
self.assertEqual('off', self.thermostat.current_operation)
self.thermostat._target_temperature = 22.0
self.assertEqual('heat', self.thermostat.current_operation)
self.thermostat._target_temperature = 16.0
self.assertEqual('eco', self.thermostat.current_operation)
self.thermostat._target_temperature = 12.5
self.assertEqual('manual', self.thermostat.current_operation)

def test_operation_list(self):
"""Test operation_list property."""
self.assertEqual(['heat', 'eco', 'off', 'on'],
self.thermostat.operation_list)

@patch.object(FritzboxThermostat, 'set_temperature')
def test_set_operation_mode(self, mock_set_temp):
"""Test set_operation_mode by all modes and with a non-existing one."""
values = {
'heat': 22.0,
'eco': 16.0,
'on': 30.0,
'off': 0.0}
for mode, temp in values.items():
print(mode, temp)

mock_set_temp.reset_mock()
self.thermostat.set_operation_mode(mode)
mock_set_temp.assert_called_once_with(temperature=temp)

mock_set_temp.reset_mock()
self.thermostat.set_operation_mode('non_existing_mode')
mock_set_temp.assert_not_called()

def test_min_max_temperature(self):
"""Test min_temp and max_temp properties."""
self.assertEqual(8.0, self.thermostat.min_temp)
self.assertEqual(28.0, self.thermostat.max_temp)

def test_device_state_attributes(self):
"""Test device_state property."""
attr = self.thermostat.device_state_attributes
self.assertEqual(attr['device_locked'], True)
self.assertEqual(attr['locked'], False)
self.assertEqual(attr['battery_low'], True)

def test_update(self):
"""Test update function."""
device = Mock()
device.update = Mock()
device.actual_temperature = 10.0
device.target_temperature = 11.0
device.comfort_temperature = 12.0
device.eco_temperature = 13.0
self.thermostat._device = device

self.thermostat.update()

device.update.assert_called_once_with()
self.assertEqual(10.0, self.thermostat._current_temperature)
self.assertEqual(11.0, self.thermostat._target_temperature)
self.assertEqual(12.0, self.thermostat._comfort_temperature)
self.assertEqual(13.0, self.thermostat._eco_temperature)

def test_update_http_error(self):
"""Test exception handling of update function."""
self.device.update.side_effect = requests.exceptions.HTTPError
self.thermostat.update()
self.thermostat._fritz.login.assert_called_once_with()