From e831a2705e5ed2c240af3ba9e6a6acc8f110c960 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Wolf-Bastian=20P=C3=B6ttner?= <bastian@poettner.de>
Date: Fri, 3 Feb 2017 08:29:18 +0100
Subject: [PATCH] Add support for FRITZ!DECT wireless switches based on
 fritzhome (#5541)

---
 .coveragerc                                  |   1 +
 homeassistant/components/switch/fritzdect.py | 165 +++++++++++++++++++
 requirements_all.txt                         |   3 +
 3 files changed, 169 insertions(+)
 create mode 100644 homeassistant/components/switch/fritzdect.py

diff --git a/.coveragerc b/.coveragerc
index dbccf546c36..093b0e4d526 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -367,6 +367,7 @@ omit =
     homeassistant/components/switch/digitalloggers.py
     homeassistant/components/switch/dlink.py
     homeassistant/components/switch/edimax.py
+    homeassistant/components/switch/fritzdect.py
     homeassistant/components/switch/hdmi_cec.py
     homeassistant/components/switch/hikvisioncam.py
     homeassistant/components/switch/hook.py
diff --git a/homeassistant/components/switch/fritzdect.py b/homeassistant/components/switch/fritzdect.py
new file mode 100644
index 00000000000..fc185c9f6a3
--- /dev/null
+++ b/homeassistant/components/switch/fritzdect.py
@@ -0,0 +1,165 @@
+"""
+Support for FRITZ!DECT Switches.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/switch.fritzdect/
+"""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA)
+from homeassistant.const import (
+    CONF_HOST, CONF_PASSWORD, CONF_USERNAME)
+import homeassistant.helpers.config_validation as cv
+from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN
+
+REQUIREMENTS = ['fritzhome==1.0.2']
+
+_LOGGER = logging.getLogger(__name__)
+
+# Standard Fritz Box IP
+DEFAULT_HOST = 'fritz.box'
+
+ATTR_CURRENT_CONSUMPTION = 'Current Consumption'
+ATTR_CURRENT_CONSUMPTION_UNIT = 'W'
+
+ATTR_TOTAL_CONSUMPTION = 'Total Consumption'
+ATTR_TOTAL_CONSUMPTION_UNIT = 'kWh'
+
+ATTR_TEMPERATURE = 'Temperature'
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+    vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
+    vol.Required(CONF_USERNAME): cv.string,
+    vol.Required(CONF_PASSWORD): cv.string,
+})
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+    """Add all switches connected to Fritz Box."""
+    from fritzhome.fritz import FritzBox
+
+    host = config.get(CONF_HOST)
+    username = config.get(CONF_USERNAME)
+    password = config.get(CONF_PASSWORD)
+
+    # Hack: fritzhome only throws Exception. To prevent pylint from
+    # complaining, we disable the warning here:
+    # pylint: disable=W0703
+
+    # Log into Fritz Box
+    fritz = FritzBox(host, username, password)
+    try:
+        fritz.login()
+    except Exception:
+        _LOGGER.error("Login to Fritz!Box failed")
+        return
+
+    # Add all actors to hass
+    for actor in fritz.get_actors():
+        # Only add devices that support switching
+        if actor.has_switch:
+            data = FritzDectSwitchData(fritz, actor.actor_id)
+            add_devices([FritzDectSwitch(hass, data, actor.name)], True)
+
+
+class FritzDectSwitch(SwitchDevice):
+    """Representation of a FRITZ!DECT switch."""
+
+    def __init__(self, hass, data, name):
+        """Initialize the switch."""
+        self.units = hass.config.units
+        self.data = data
+        self._name = name
+
+    @property
+    def name(self):
+        """Return the name of the FRITZ!DECT switch, if any."""
+        return self._name
+
+    @property
+    def device_state_attributes(self):
+        """Return the state attributes of the device."""
+        attrs = {}
+
+        if self.data.has_powermeter and \
+           self.data.current_consumption != STATE_UNKNOWN and \
+           self.data.total_consumption != STATE_UNKNOWN:
+            attrs[ATTR_CURRENT_CONSUMPTION] = "%.1f %s" % \
+                (self.data.current_consumption, ATTR_CURRENT_CONSUMPTION_UNIT)
+            attrs[ATTR_TOTAL_CONSUMPTION] = "%.3f %s" % \
+                (self.data.total_consumption, ATTR_TOTAL_CONSUMPTION_UNIT)
+
+        if self.data.has_temperature and \
+           self.data.temperature != STATE_UNKNOWN:
+            attrs[ATTR_TEMPERATURE] = "%.1f %s" % \
+              (self.units.temperature(self.data.temperature, TEMP_CELSIUS),
+               self.units.temperature_unit)
+
+        return attrs
+
+    @property
+    def current_power_watt(self):
+        """Return the current power usage in Watt."""
+        try:
+            return float(self.data.current_consumption)
+        except ValueError:
+            return None
+
+    @property
+    def is_on(self):
+        """Return true if switch is on."""
+        return self.data.state
+
+    def turn_on(self, **kwargs):
+        """Turn the switch on."""
+        actor = self.data.fritz.get_actor_by_ain(self.data.ain)
+        actor.switch_on()
+
+    def turn_off(self):
+        """Turn the switch off."""
+        actor = self.data.fritz.get_actor_by_ain(self.data.ain)
+        actor.switch_off()
+
+    def update(self):
+        """Get the latest data from the fritz box and updates the states."""
+        self.data.update()
+
+
+class FritzDectSwitchData(object):
+    """Get the latest data from the fritz box."""
+
+    def __init__(self, fritz, ain):
+        """Initialize the data object."""
+        self.fritz = fritz
+        self.ain = ain
+        self.state = STATE_UNKNOWN
+        self.temperature = STATE_UNKNOWN
+        self.current_consumption = STATE_UNKNOWN
+        self.total_consumption = STATE_UNKNOWN
+        self.has_switch = STATE_UNKNOWN
+        self.has_temperature = STATE_UNKNOWN
+        self.has_powermeter = STATE_UNKNOWN
+
+    def update(self):
+        """Get the latest data from the fritz box."""
+        from requests.exceptions import RequestException
+
+        try:
+            actor = self.fritz.get_actor_by_ain(self.ain)
+            self.state = actor.get_state()
+        except RequestException:
+            _LOGGER.error("Request to actor failed")
+            self.state = STATE_UNKNOWN
+            self.temperature = STATE_UNKNOWN
+            self.current_consumption = STATE_UNKNOWN
+            self.total_consumption = STATE_UNKNOWN
+            return
+
+        self.temperature = actor.temperature
+        self.current_consumption = (actor.get_power() or 0.0) / 1000
+        self.total_consumption = (actor.get_energy() or 0.0) / 100000
+        self.has_switch = actor.has_switch
+        self.has_temperature = actor.has_temperature
+        self.has_powermeter = actor.has_powermeter
diff --git a/requirements_all.txt b/requirements_all.txt
index d2d19cc23b0..957a1c3b75c 100755
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -154,6 +154,9 @@ freesms==0.1.1
 # homeassistant.components.device_tracker.fritz
 # fritzconnection==0.6
 
+# homeassistant.components.switch.fritzdect
+fritzhome==1.0.2
+
 # homeassistant.components.conversation
 fuzzywuzzy==0.14.0
 
-- 
GitLab