Heiko Thiery 9487bd455a Add AVM fritzbox smarthome component (#10688)
* initial commit

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix failed flake8 tests

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add fritzhome files to .coveragerc

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix wrong module import

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove too general exception

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* incorporate review comments

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove blank line

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix wrong import

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix issue with operations

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* incorporate review comments

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove unused attributes

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* adapt to supported_features

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* change checking of kwargs to canonical way

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove unused self._state

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* Don't overwrite the platform domain

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* Remove parenthesis from import without line break

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* Do not pass hass to the components on init

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* Remove check for available in current_operation

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* Remove redundant logging message

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* Add blank line between standard and hass imports

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* Use states from base climate component

Also add the new state STATE_MANUAL to the base.

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add reconnect when access failed

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add device specific attributes

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* group the imports from the same module

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* change domain data to fritz instance

This let us use the fritz instance to reconnect from platform without accessing
protected attributes.

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix typo

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* rename platform from fritzhome to fritzbox

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* Add device_state_attributes

Add attributes to have compatiblity to fritzdect.

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add support for multiple fritzboxes

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix pylint issues

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fixed pyfritzhome version

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix import

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix component name in requirements_all.txt

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* upgrade pyfritzhome to 0.3.7

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* rename platform/component also in .coveragerc

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* use DEFAULT_HOST when no host is in dict

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add config schema for dict

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove check

The check since since the config scheme takes case.

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add check for empty devices

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* use standard attribute from base class

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove STATE_MANUAL from operation list

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove set DEFAULT_HOST

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* don't pass hass to the SwitchDevice

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove unsed DEFAULT_HOST

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* refactored device attributes

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add info output if no fritzbox is configured

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* small fixes according review comment

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove unneeded default value

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove non required code from try..except block

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* line break for line that is too long

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove too many empty lines

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>
2018-04-17 12:40:36 +02:00

154 lines
4.9 KiB
Python
Executable File

"""
Support for AVM Fritz!Box smarthome thermostate devices.
For more details about this component, please refer to the documentation at
http://home-assistant.io/components/climate.fritzbox/
"""
import logging
import requests
from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN
from homeassistant.components.fritzbox import (
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)
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]
MIN_TEMPERATURE = 8
MAX_TEMPERATURE = 28
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Fritzbox smarthome thermostat platform."""
devices = []
fritz_list = hass.data[FRITZBOX_DOMAIN]
for fritz in fritz_list:
device_list = fritz.get_devices()
for device in device_list:
if device.has_thermostat:
devices.append(FritzboxThermostat(device, fritz))
add_devices(devices)
class FritzboxThermostat(ClimateDevice):
"""The thermostat class for Fritzbox smarthome thermostates."""
def __init__(self, device, fritz):
"""Initialize the thermostat."""
self._device = device
self._fritz = fritz
self._current_temperature = self._device.actual_temperature
self._target_temperature = self._device.target_temperature
self._comfort_temperature = self._device.comfort_temperature
self._eco_temperature = self._device.eco_temperature
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def available(self):
"""Return if thermostat is available."""
return self._device.present
@property
def name(self):
"""Return the name of the device."""
return self._device.name
@property
def temperature_unit(self):
"""Return the unit of measurement that is used."""
return TEMP_CELSIUS
@property
def precision(self):
"""Return precision 0.5."""
return PRECISION_HALVES
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if ATTR_OPERATION_MODE in kwargs:
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
self.set_operation_mode(operation_mode)
elif ATTR_TEMPERATURE in kwargs:
temperature = kwargs.get(ATTR_TEMPERATURE)
self._device.set_target_temperature(temperature)
@property
def current_operation(self):
"""Return the current operation mode."""
if self._target_temperature == self._comfort_temperature:
return STATE_HEAT
elif self._target_temperature == self._eco_temperature:
return STATE_ECO
return STATE_MANUAL
@property
def operation_list(self):
"""Return the list of available operation modes."""
return OPERATION_LIST
def set_operation_mode(self, operation_mode):
"""Set new operation mode."""
if operation_mode == STATE_HEAT:
self.set_temperature(temperature=self._comfort_temperature)
elif operation_mode == STATE_ECO:
self.set_temperature(temperature=self._eco_temperature)
@property
def min_temp(self):
"""Return the minimum temperature."""
return MIN_TEMPERATURE
@property
def max_temp(self):
"""Return the maximum temperature."""
return MAX_TEMPERATURE
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
attrs = {
ATTR_STATE_DEVICE_LOCKED: self._device.device_lock,
ATTR_STATE_LOCKED: self._device.lock,
ATTR_STATE_BATTERY_LOW: self._device.battery_low,
}
return attrs
def update(self):
"""Update the data from the thermostat."""
try:
self._device.update()
self._current_temperature = self._device.actual_temperature
self._target_temperature = self._device.target_temperature
self._comfort_temperature = self._device.comfort_temperature
self._eco_temperature = self._device.eco_temperature
except requests.exceptions.HTTPError as ex:
_LOGGER.warning("Fritzbox connection error: %s", ex)
self._fritz.login()