Skip to content
Snippets Groups Projects
Commit 9487bd45 authored by Heiko Thiery's avatar Heiko Thiery Committed by Pascal Vizeli
Browse files

Add AVM fritzbox smarthome component (#10688)


* initial commit

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

* fix failed flake8 tests

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

* add fritzhome files to .coveragerc

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

* fix wrong module import

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

* remove too general exception

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

* incorporate review comments

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

* remove blank line

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

* fix wrong import

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

* fix issue with operations

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

* incorporate review comments

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

* remove unused attributes

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

* adapt to supported_features

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

* change checking of kwargs to canonical way

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

* remove unused self._state

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

* Don't overwrite the platform domain

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

* Remove parenthesis from import without line break

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

* Do not pass hass to the components on init

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

* Remove check for available in current_operation

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

* Remove redundant logging message

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

* Add blank line between standard and hass imports

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

* Use states from base climate component

Also add the new state STATE_MANUAL to the base.

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

* add reconnect when access failed

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

* add device specific attributes

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

* group the imports from the same module

Signed-off-by: default avatarHeiko 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: default avatarHeiko Thiery <heiko.thiery@gmail.com>

* fix typo

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

* rename platform from fritzhome to fritzbox

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

* Add device_state_attributes

Add attributes to have compatiblity to fritzdect.

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

* add support for multiple fritzboxes

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

* fix pylint issues

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

* fixed pyfritzhome version

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

* fix import

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

* fix component name in requirements_all.txt

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

* upgrade pyfritzhome to 0.3.7

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

* rename platform/component also in .coveragerc

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

* use DEFAULT_HOST when no host is in dict

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

* add config schema for dict

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

* remove check

The check since since the config scheme takes case.

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

* add check for empty devices

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

* use standard attribute from base class

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

* remove STATE_MANUAL from operation list

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

* remove set DEFAULT_HOST

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

* don't pass hass to the SwitchDevice

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

* remove unsed DEFAULT_HOST

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

* refactored device attributes

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

* add info output if no fritzbox is configured

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

* small fixes according review comment

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

* remove unneeded default value

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

* remove non required code from try..except block

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

* line break for line that is too long

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

* remove too many empty lines

Signed-off-by: default avatarHeiko Thiery <heiko.thiery@gmail.com>
parent f2d4dd25
No related branches found
No related tags found
No related merge requests found
...@@ -94,6 +94,9 @@ omit = ...@@ -94,6 +94,9 @@ omit =
homeassistant/components/envisalink.py homeassistant/components/envisalink.py
homeassistant/components/*/envisalink.py homeassistant/components/*/envisalink.py
homeassistant/components/fritzbox.py
homeassistant/components/*/fritzbox.py
homeassistant/components/eufy.py homeassistant/components/eufy.py
homeassistant/components/*/eufy.py homeassistant/components/*/eufy.py
......
...@@ -40,6 +40,7 @@ STATE_HEAT = 'heat' ...@@ -40,6 +40,7 @@ STATE_HEAT = 'heat'
STATE_COOL = 'cool' STATE_COOL = 'cool'
STATE_IDLE = 'idle' STATE_IDLE = 'idle'
STATE_AUTO = 'auto' STATE_AUTO = 'auto'
STATE_MANUAL = 'manual'
STATE_DRY = 'dry' STATE_DRY = 'dry'
STATE_FAN_ONLY = 'fan_only' STATE_FAN_ONLY = 'fan_only'
STATE_ECO = 'eco' STATE_ECO = 'eco'
......
"""
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()
"""
Support for AVM Fritz!Box smarthome devices.
For more details about this component, please refer to the documentation at
http://home-assistant.io/components/fritzbox/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import discovery
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyfritzhome==0.3.7']
SUPPORTED_DOMAINS = ['climate', 'switch']
DOMAIN = 'fritzbox'
ATTR_STATE_DEVICE_LOCKED = 'device_locked'
ATTR_STATE_LOCKED = 'locked'
ATTR_STATE_BATTERY_LOW = 'battery_low'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_DEVICES):
vol.All(cv.ensure_list, [
vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
}),
]),
})
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up the fritzbox component."""
from pyfritzhome import Fritzhome, LoginError
fritz_list = []
configured_devices = config[DOMAIN].get(CONF_DEVICES)
for device in configured_devices:
host = device.get(CONF_HOST)
username = device.get(CONF_USERNAME)
password = device.get(CONF_PASSWORD)
fritzbox = Fritzhome(host=host, user=username,
password=password)
try:
fritzbox.login()
_LOGGER.info("Connected to device %s", device)
except LoginError:
_LOGGER.warning("Login to Fritz!Box %s as %s failed",
host, username)
continue
fritz_list.append(fritzbox)
if not fritz_list:
_LOGGER.info("No fritzboxes configured")
return False
hass.data[DOMAIN] = fritz_list
def logout_fritzboxes(event):
"""Close all connections to the fritzboxes."""
for fritz in fritz_list:
fritz.logout()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, logout_fritzboxes)
for domain in SUPPORTED_DOMAINS:
discovery.load_platform(hass, domain, DOMAIN, {}, config)
return True
"""
Support for AVM Fritz!Box smarthome switch devices.
For more details about this component, please refer to the documentation at
http://home-assistant.io/components/switch.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_LOCKED)
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
DEPENDENCIES = ['fritzbox']
_LOGGER = logging.getLogger(__name__)
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
ATTR_TOTAL_CONSUMPTION_UNIT = 'total_consumption_unit'
ATTR_TOTAL_CONSUMPTION_UNIT_VALUE = 'kWh'
ATTR_TEMPERATURE_UNIT = 'temperature_unit'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Fritzbox smarthome switch 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_switch:
devices.append(FritzboxSwitch(device, fritz))
add_devices(devices)
class FritzboxSwitch(SwitchDevice):
"""The switch class for Fritzbox switches."""
def __init__(self, device, fritz):
"""Initialize the switch."""
self._device = device
self._fritz = fritz
@property
def available(self):
"""Return if switch is available."""
return self._device.present
@property
def name(self):
"""Return the name of the device."""
return self._device.name
@property
def is_on(self):
"""Return true if the switch is on."""
return self._device.switch_state
def turn_on(self, **kwargs):
"""Turn the switch on."""
self._device.set_switch_state_on()
def turn_off(self, **kwargs):
"""Turn the switch off."""
self._device.set_switch_state_off()
def update(self):
"""Get latest data and states from the device."""
try:
self._device.update()
except requests.exceptions.HTTPError as ex:
_LOGGER.warning("Fritzhome connection error: %s", ex)
self._fritz.login()
@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
attrs = {}
attrs[ATTR_STATE_DEVICE_LOCKED] = self._device.device_lock
attrs[ATTR_STATE_LOCKED] = self._device.lock
if self._device.has_powermeter:
attrs[ATTR_TOTAL_CONSUMPTION] = "{:.3f}".format(
(self._device.energy or 0.0) / 100000)
attrs[ATTR_TOTAL_CONSUMPTION_UNIT] = \
ATTR_TOTAL_CONSUMPTION_UNIT_VALUE
if self._device.has_temperature_sensor:
attrs[ATTR_TEMPERATURE] = \
str(self.hass.config.units.temperature(
self._device.temperature, TEMP_CELSIUS))
attrs[ATTR_TEMPERATURE_UNIT] = \
self.hass.config.units.temperature_unit
return attrs
@property
def current_power_w(self):
"""Return the current power usage in W."""
return self._device.power / 1000
...@@ -765,6 +765,9 @@ pyfido==2.1.1 ...@@ -765,6 +765,9 @@ pyfido==2.1.1
# homeassistant.components.climate.flexit # homeassistant.components.climate.flexit
pyflexit==0.3 pyflexit==0.3
# homeassistant.components.fritzbox
pyfritzhome==0.3.7
# homeassistant.components.ifttt # homeassistant.components.ifttt
pyfttt==0.3 pyfttt==0.3
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment