From 645c3a67d818f6103bd125b6886d6c1a740a9d53 Mon Sep 17 00:00:00 2001
From: Robert Svensson <Kane610@users.noreply.github.com>
Date: Wed, 29 Aug 2018 23:18:20 +0200
Subject: [PATCH] Fix so that entities are properly unloaded with config entry
 (#16281)

---
 .../components/binary_sensor/deconz.py        |  5 +++
 homeassistant/components/deconz/__init__.py   | 19 +++++++--
 homeassistant/components/light/deconz.py      |  5 +++
 homeassistant/components/scene/deconz.py      |  4 ++
 homeassistant/components/sensor/deconz.py     | 40 ++++++++++++-------
 homeassistant/components/switch/deconz.py     |  5 +++
 requirements_all.txt                          |  2 +-
 requirements_test_all.txt                     |  2 +-
 8 files changed, 62 insertions(+), 20 deletions(-)

diff --git a/homeassistant/components/binary_sensor/deconz.py b/homeassistant/components/binary_sensor/deconz.py
index 1fb62124407..d2ca9e7c5e8 100644
--- a/homeassistant/components/binary_sensor/deconz.py
+++ b/homeassistant/components/binary_sensor/deconz.py
@@ -54,6 +54,11 @@ class DeconzBinarySensor(BinarySensorDevice):
         self._sensor.register_async_callback(self.async_update_callback)
         self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
 
+    async def async_will_remove_from_hass(self) -> None:
+        """Disconnect sensor object when removed."""
+        self._sensor.remove_callback(self.async_update_callback)
+        self._sensor = None
+
     @callback
     def async_update_callback(self, reason):
         """Update the sensor's state.
diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py
index a4edc009ea1..e9f797d95f9 100644
--- a/homeassistant/components/deconz/__init__.py
+++ b/homeassistant/components/deconz/__init__.py
@@ -24,7 +24,7 @@ from .const import (
     CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
     DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
 
-REQUIREMENTS = ['pydeconz==44']
+REQUIREMENTS = ['pydeconz==45']
 
 CONFIG_SCHEMA = vol.Schema({
     DOMAIN: vol.Schema({
@@ -179,15 +179,22 @@ async def async_unload_entry(hass, config_entry):
     deconz = hass.data.pop(DOMAIN)
     hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
     deconz.close()
-    for component in ['binary_sensor', 'light', 'scene', 'sensor']:
+
+    for component in ['binary_sensor', 'light', 'scene', 'sensor', 'switch']:
         await hass.config_entries.async_forward_entry_unload(
             config_entry, component)
+
     dispatchers = hass.data[DATA_DECONZ_UNSUB]
     for unsub_dispatcher in dispatchers:
         unsub_dispatcher()
     hass.data[DATA_DECONZ_UNSUB] = []
-    hass.data[DATA_DECONZ_EVENT] = []
+
+    for event in hass.data[DATA_DECONZ_EVENT]:
+        event.async_will_remove_from_hass()
+        hass.data[DATA_DECONZ_EVENT].remove(event)
+
     hass.data[DATA_DECONZ_ID] = []
+
     return True
 
 
@@ -206,6 +213,12 @@ class DeconzEvent:
         self._event = 'deconz_{}'.format(CONF_EVENT)
         self._id = slugify(self._device.name)
 
+    @callback
+    def async_will_remove_from_hass(self) -> None:
+        """Disconnect event object when removed."""
+        self._device.remove_callback(self.async_update_callback)
+        self._device = None
+
     @callback
     def async_update_callback(self, reason):
         """Fire the event if reason is that state is updated."""
diff --git a/homeassistant/components/light/deconz.py b/homeassistant/components/light/deconz.py
index 412cf8693e5..ff3fe609924 100644
--- a/homeassistant/components/light/deconz.py
+++ b/homeassistant/components/light/deconz.py
@@ -82,6 +82,11 @@ class DeconzLight(Light):
         self._light.register_async_callback(self.async_update_callback)
         self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._light.deconz_id
 
+    async def async_will_remove_from_hass(self) -> None:
+        """Disconnect light object when removed."""
+        self._light.remove_callback(self.async_update_callback)
+        self._light = None
+
     @callback
     def async_update_callback(self, reason):
         """Update the light's state."""
diff --git a/homeassistant/components/scene/deconz.py b/homeassistant/components/scene/deconz.py
index dde78dadc49..5af8f657206 100644
--- a/homeassistant/components/scene/deconz.py
+++ b/homeassistant/components/scene/deconz.py
@@ -38,6 +38,10 @@ class DeconzScene(Scene):
         """Subscribe to sensors events."""
         self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._scene.deconz_id
 
+    async def async_will_remove_from_hass(self) -> None:
+        """Disconnect scene object when removed."""
+        self._scene = None
+
     async def async_activate(self):
         """Activate the scene."""
         await self._scene.async_set_state({})
diff --git a/homeassistant/components/sensor/deconz.py b/homeassistant/components/sensor/deconz.py
index 8cb3915dc46..37fab727299 100644
--- a/homeassistant/components/sensor/deconz.py
+++ b/homeassistant/components/sensor/deconz.py
@@ -64,6 +64,11 @@ class DeconzSensor(Entity):
         self._sensor.register_async_callback(self.async_update_callback)
         self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
 
+    async def async_will_remove_from_hass(self) -> None:
+        """Disconnect sensor object when removed."""
+        self._sensor.remove_callback(self.async_update_callback)
+        self._sensor = None
+
     @callback
     def async_update_callback(self, reason):
         """Update the sensor's state.
@@ -155,16 +160,21 @@ class DeconzSensor(Entity):
 class DeconzBattery(Entity):
     """Battery class for when a device is only represented as an event."""
 
-    def __init__(self, device):
+    def __init__(self, sensor):
         """Register dispatcher callback for update of battery state."""
-        self._device = device
-        self._name = '{} {}'.format(self._device.name, 'Battery Level')
+        self._sensor = sensor
+        self._name = '{} {}'.format(self._sensor.name, 'Battery Level')
         self._unit_of_measurement = "%"
 
     async def async_added_to_hass(self):
         """Subscribe to sensors events."""
-        self._device.register_async_callback(self.async_update_callback)
-        self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._device.deconz_id
+        self._sensor.register_async_callback(self.async_update_callback)
+        self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
+
+    async def async_will_remove_from_hass(self) -> None:
+        """Disconnect sensor object when removed."""
+        self._sensor.remove_callback(self.async_update_callback)
+        self._sensor = None
 
     @callback
     def async_update_callback(self, reason):
@@ -175,7 +185,7 @@ class DeconzBattery(Entity):
     @property
     def state(self):
         """Return the state of the battery."""
-        return self._device.battery
+        return self._sensor.battery
 
     @property
     def name(self):
@@ -185,7 +195,7 @@ class DeconzBattery(Entity):
     @property
     def unique_id(self):
         """Return a unique identifier for the device."""
-        return self._device.uniqueid
+        return self._sensor.uniqueid
 
     @property
     def device_class(self):
@@ -206,22 +216,22 @@ class DeconzBattery(Entity):
     def device_state_attributes(self):
         """Return the state attributes of the battery."""
         attr = {
-            ATTR_EVENT_ID: slugify(self._device.name),
+            ATTR_EVENT_ID: slugify(self._sensor.name),
         }
         return attr
 
     @property
     def device_info(self):
         """Return a device description for device registry."""
-        if (self._device.uniqueid is None or
-                self._device.uniqueid.count(':') != 7):
+        if (self._sensor.uniqueid is None or
+                self._sensor.uniqueid.count(':') != 7):
             return None
-        serial = self._device.uniqueid.split('-', 1)[0]
+        serial = self._sensor.uniqueid.split('-', 1)[0]
         return {
             'connections': {(CONNECTION_ZIGBEE, serial)},
             'identifiers': {(DECONZ_DOMAIN, serial)},
-            'manufacturer': self._device.manufacturer,
-            'model': self._device.modelid,
-            'name': self._device.name,
-            'sw_version': self._device.swversion,
+            'manufacturer': self._sensor.manufacturer,
+            'model': self._sensor.modelid,
+            'name': self._sensor.name,
+            'sw_version': self._sensor.swversion,
         }
diff --git a/homeassistant/components/switch/deconz.py b/homeassistant/components/switch/deconz.py
index 35dbc3ef782..bd8167d89a0 100644
--- a/homeassistant/components/switch/deconz.py
+++ b/homeassistant/components/switch/deconz.py
@@ -55,6 +55,11 @@ class DeconzSwitch(SwitchDevice):
         self._switch.register_async_callback(self.async_update_callback)
         self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._switch.deconz_id
 
+    async def async_will_remove_from_hass(self) -> None:
+        """Disconnect switch object when removed."""
+        self._switch.remove_callback(self.async_update_callback)
+        self._switch = None
+
     @callback
     def async_update_callback(self, reason):
         """Update the switch's state."""
diff --git a/requirements_all.txt b/requirements_all.txt
index c788a242834..54d902f4260 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -820,7 +820,7 @@ pycsspeechtts==1.0.2
 pydaikin==0.4
 
 # homeassistant.components.deconz
-pydeconz==44
+pydeconz==45
 
 # homeassistant.components.zwave
 pydispatcher==2.0.5
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 8e10876cc06..e0d1596db3f 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -139,7 +139,7 @@ py-canary==0.5.0
 pyblackbird==0.5
 
 # homeassistant.components.deconz
-pydeconz==44
+pydeconz==45
 
 # homeassistant.components.zwave
 pydispatcher==2.0.5
-- 
GitLab