deCONZ - Directly reflect changes to config entry options (#31661)

* Directly reflect changes to config entry options

* Remove print

* Add tests

* Add title to config options
pull/31963/head
Robert Svensson 2020-02-18 22:24:25 +01:00 committed by GitHub
parent 17f3332c89
commit b5df2ba853
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 188 additions and 22 deletions

View File

@ -108,7 +108,8 @@
"allow_clip_sensor": "Allow deCONZ CLIP sensors", "allow_clip_sensor": "Allow deCONZ CLIP sensors",
"allow_deconz_groups": "Allow deCONZ light groups" "allow_deconz_groups": "Allow deCONZ light groups"
}, },
"description": "Configure visibility of deCONZ device types" "description": "Configure visibility of deCONZ device types",
"title": "deCONZ options"
} }
} }
} }

View File

@ -37,6 +37,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
gateway.option_allow_clip_sensor gateway.option_allow_clip_sensor
or not sensor.type.startswith("CLIP") or not sensor.type.startswith("CLIP")
) )
and sensor.deconz_id not in gateway.deconz_ids.values()
): ):
entities.append(DeconzBinarySensor(sensor, gateway)) entities.append(DeconzBinarySensor(sensor, gateway))

View File

@ -44,6 +44,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
gateway.option_allow_clip_sensor gateway.option_allow_clip_sensor
or not sensor.type.startswith("CLIP") or not sensor.type.startswith("CLIP")
) )
and sensor.deconz_id not in gateway.deconz_ids.values()
): ):
entities.append(DeconzThermostat(sensor, gateway)) entities.append(DeconzThermostat(sensor, gateway))

View File

@ -77,6 +77,11 @@ class DeconzDevice(DeconzBase, Entity):
self.hass, self.gateway.signal_reachable, self.async_update_callback self.hass, self.gateway.signal_reachable, self.async_update_callback
) )
) )
self.listeners.append(
async_dispatcher_connect(
self.hass, self.gateway.signal_remove_entity, self.async_remove_self
)
)
async def async_will_remove_from_hass(self) -> None: async def async_will_remove_from_hass(self) -> None:
"""Disconnect device object when removed.""" """Disconnect device object when removed."""
@ -85,6 +90,15 @@ class DeconzDevice(DeconzBase, Entity):
for unsub_dispatcher in self.listeners: for unsub_dispatcher in self.listeners:
unsub_dispatcher() unsub_dispatcher()
async def async_remove_self(self, deconz_ids: list) -> None:
"""Schedule removal of this entity.
Called by signal_remove_entity scheduled by async_added_to_hass.
"""
if self._device.deconz_id not in deconz_ids:
return
await self.async_remove()
@callback @callback
def async_update_callback(self, force_update=False, ignore_update=False): def async_update_callback(self, force_update=False, ignore_update=False):
"""Update the device's state.""" """Update the device's state."""

View File

@ -20,6 +20,8 @@ from .const import (
DOMAIN, DOMAIN,
LOGGER, LOGGER,
NEW_DEVICE, NEW_DEVICE,
NEW_GROUP,
NEW_SENSOR,
SUPPORTED_PLATFORMS, SUPPORTED_PLATFORMS,
) )
from .errors import AuthenticationRequired, CannotConnect from .errors import AuthenticationRequired, CannotConnect
@ -45,6 +47,9 @@ class DeconzGateway:
self.events = [] self.events = []
self.listeners = [] self.listeners = []
self._current_option_allow_clip_sensor = self.option_allow_clip_sensor
self._current_option_allow_deconz_groups = self.option_allow_deconz_groups
@property @property
def bridgeid(self) -> str: def bridgeid(self) -> str:
"""Return the unique identifier of the gateway.""" """Return the unique identifier of the gateway."""
@ -108,22 +113,64 @@ class DeconzGateway:
self.api.start() self.api.start()
self.config_entry.add_update_listener(self.async_new_address) self.config_entry.add_update_listener(self.async_config_entry_updated)
return True return True
@staticmethod @staticmethod
async def async_new_address(hass, entry) -> None: async def async_config_entry_updated(hass, entry) -> None:
"""Handle signals of gateway getting new address. """Handle signals of config entry being updated.
This is a static method because a class method (bound method), This is a static method because a class method (bound method), can not be used with weak references.
can not be used with weak references. Causes for this is either discovery updating host address or config entry options changing.
""" """
gateway = get_gateway_from_config_entry(hass, entry) gateway = get_gateway_from_config_entry(hass, entry)
if gateway.api.host != entry.data[CONF_HOST]: if gateway.api.host != entry.data[CONF_HOST]:
gateway.api.close() gateway.api.close()
gateway.api.host = entry.data[CONF_HOST] gateway.api.host = entry.data[CONF_HOST]
gateway.api.start() gateway.api.start()
return
await gateway.options_updated()
async def options_updated(self):
"""Manage entities affected by config entry options."""
deconz_ids = []
if self._current_option_allow_clip_sensor != self.option_allow_clip_sensor:
self._current_option_allow_clip_sensor = self.option_allow_clip_sensor
sensors = [
sensor
for sensor in self.api.sensors.values()
if sensor.type.startswith("CLIP")
]
if self.option_allow_clip_sensor:
self.async_add_device_callback(NEW_SENSOR, sensors)
else:
deconz_ids += [sensor.deconz_id for sensor in sensors]
if self._current_option_allow_deconz_groups != self.option_allow_deconz_groups:
self._current_option_allow_deconz_groups = self.option_allow_deconz_groups
groups = list(self.api.groups.values())
if self.option_allow_deconz_groups:
self.async_add_device_callback(NEW_GROUP, groups)
else:
deconz_ids += [group.deconz_id for group in groups]
if deconz_ids:
async_dispatcher_send(self.hass, self.signal_remove_entity, deconz_ids)
entity_registry = await self.hass.helpers.entity_registry.async_get_registry()
for entity_id, deconz_id in self.deconz_ids.items():
if deconz_id in deconz_ids and entity_registry.async_is_registered(
entity_id
):
entity_registry.async_remove(entity_id)
@property @property
def signal_reachable(self) -> str: def signal_reachable(self) -> str:
@ -141,6 +188,11 @@ class DeconzGateway:
"""Gateway specific event to signal new device.""" """Gateway specific event to signal new device."""
return NEW_DEVICE[device_type].format(self.bridgeid) return NEW_DEVICE[device_type].format(self.bridgeid)
@property
def signal_remove_entity(self) -> str:
"""Gateway specific event to signal removal of entity."""
return f"deconz-remove-{self.bridgeid}"
@callback @callback
def async_add_device_callback(self, device_type, device) -> None: def async_add_device_callback(self, device_type, device) -> None:
"""Handle event of new device creation in deCONZ.""" """Handle event of new device creation in deCONZ."""

View File

@ -67,7 +67,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities = [] entities = []
for group in groups: for group in groups:
if group.lights: if group.lights and group.deconz_id not in gateway.deconz_ids.values():
entities.append(DeconzGroup(group, gateway)) entities.append(DeconzGroup(group, gateway))
async_add_entities(entities, True) async_add_entities(entities, True)

View File

@ -68,6 +68,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
gateway.option_allow_clip_sensor gateway.option_allow_clip_sensor
or not sensor.type.startswith("CLIP") or not sensor.type.startswith("CLIP")
) )
and sensor.deconz_id not in gateway.deconz_ids.values()
): ):
entities.append(DeconzSensor(sensor, gateway)) entities.append(DeconzSensor(sensor, gateway))

View File

@ -14,20 +14,9 @@
"title": "Link with deCONZ", "title": "Link with deCONZ",
"description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ Settings -> Gateway -> Advanced\n2. Press \"Authenticate app\" button" "description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ Settings -> Gateway -> Advanced\n2. Press \"Authenticate app\" button"
}, },
"options": {
"title": "Extra configuration options for deCONZ",
"data": {
"allow_clip_sensor": "Allow importing virtual sensors",
"allow_deconz_groups": "Allow importing deCONZ groups"
}
},
"hassio_confirm": { "hassio_confirm": {
"title": "deCONZ Zigbee gateway via Hass.io add-on", "title": "deCONZ Zigbee gateway via Hass.io add-on",
"description": "Do you want to configure Home Assistant to connect to the deCONZ gateway provided by the Hass.io add-on {addon}?", "description": "Do you want to configure Home Assistant to connect to the deCONZ gateway provided by the Hass.io add-on {addon}?"
"data": {
"allow_clip_sensor": "Allow importing virtual sensors",
"allow_deconz_groups": "Allow importing deCONZ groups"
}
} }
}, },
"error": { "error": {
@ -45,11 +34,12 @@
"options": { "options": {
"step": { "step": {
"deconz_devices": { "deconz_devices": {
"description": "Configure visibility of deCONZ device types",
"data": { "data": {
"allow_clip_sensor": "Allow deCONZ CLIP sensors", "allow_clip_sensor": "Allow deCONZ CLIP sensors",
"allow_deconz_groups": "Allow deCONZ light groups" "allow_deconz_groups": "Allow deCONZ light groups"
} },
"description": "Configure visibility of deCONZ device types",
"title": "deCONZ options"
} }
} }
}, },
@ -105,4 +95,4 @@
"side_6": "Side 6" "side_6": "Side 6"
} }
} }
} }

View File

@ -134,6 +134,28 @@ async def test_allow_clip_sensor(hass):
vibration_sensor = hass.states.get("binary_sensor.vibration_sensor") vibration_sensor = hass.states.get("binary_sensor.vibration_sensor")
assert vibration_sensor.state == "on" assert vibration_sensor.state == "on"
hass.config_entries.async_update_entry(
gateway.config_entry, options={deconz.gateway.CONF_ALLOW_CLIP_SENSOR: False}
)
await hass.async_block_till_done()
assert "binary_sensor.presence_sensor" in gateway.deconz_ids
assert "binary_sensor.temperature_sensor" not in gateway.deconz_ids
assert "binary_sensor.clip_presence_sensor" not in gateway.deconz_ids
assert "binary_sensor.vibration_sensor" in gateway.deconz_ids
assert len(hass.states.async_all()) == 3
hass.config_entries.async_update_entry(
gateway.config_entry, options={deconz.gateway.CONF_ALLOW_CLIP_SENSOR: True}
)
await hass.async_block_till_done()
assert "binary_sensor.presence_sensor" in gateway.deconz_ids
assert "binary_sensor.temperature_sensor" not in gateway.deconz_ids
assert "binary_sensor.clip_presence_sensor" in gateway.deconz_ids
assert "binary_sensor.vibration_sensor" in gateway.deconz_ids
assert len(hass.states.async_all()) == 4
async def test_add_new_binary_sensor(hass): async def test_add_new_binary_sensor(hass):
"""Test that adding a new binary sensor works.""" """Test that adding a new binary sensor works."""

View File

@ -214,6 +214,30 @@ async def test_clip_climate_device(hass):
clip_thermostat = hass.states.get("climate.clip_thermostat") clip_thermostat = hass.states.get("climate.clip_thermostat")
assert clip_thermostat.state == "heat" assert clip_thermostat.state == "heat"
hass.config_entries.async_update_entry(
gateway.config_entry, options={deconz.gateway.CONF_ALLOW_CLIP_SENSOR: False}
)
await hass.async_block_till_done()
assert "climate.thermostat" in gateway.deconz_ids
assert "sensor.thermostat" not in gateway.deconz_ids
assert "sensor.thermostat_battery_level" in gateway.deconz_ids
assert "climate.presence_sensor" not in gateway.deconz_ids
assert "climate.clip_thermostat" not in gateway.deconz_ids
assert len(hass.states.async_all()) == 3
hass.config_entries.async_update_entry(
gateway.config_entry, options={deconz.gateway.CONF_ALLOW_CLIP_SENSOR: True}
)
await hass.async_block_till_done()
assert "climate.thermostat" in gateway.deconz_ids
assert "sensor.thermostat" not in gateway.deconz_ids
assert "sensor.thermostat_battery_level" in gateway.deconz_ids
assert "climate.presence_sensor" not in gateway.deconz_ids
assert "climate.clip_thermostat" in gateway.deconz_ids
assert len(hass.states.async_all()) == 4
async def test_verify_state_update(hass): async def test_verify_state_update(hass):
"""Test that state update properly.""" """Test that state update properly."""

View File

@ -245,3 +245,29 @@ async def test_disable_light_groups(hass):
empty_group = hass.states.get("light.empty_group") empty_group = hass.states.get("light.empty_group")
assert empty_group is None assert empty_group is None
hass.config_entries.async_update_entry(
gateway.config_entry, options={deconz.gateway.CONF_ALLOW_DECONZ_GROUPS: True}
)
await hass.async_block_till_done()
assert "light.rgb_light" in gateway.deconz_ids
assert "light.tunable_white_light" in gateway.deconz_ids
assert "light.light_group" in gateway.deconz_ids
assert "light.empty_group" not in gateway.deconz_ids
assert "light.on_off_switch" not in gateway.deconz_ids
# 3 entities
assert len(hass.states.async_all()) == 5
hass.config_entries.async_update_entry(
gateway.config_entry, options={deconz.gateway.CONF_ALLOW_DECONZ_GROUPS: False}
)
await hass.async_block_till_done()
assert "light.rgb_light" in gateway.deconz_ids
assert "light.tunable_white_light" in gateway.deconz_ids
assert "light.light_group" not in gateway.deconz_ids
assert "light.empty_group" not in gateway.deconz_ids
assert "light.on_off_switch" not in gateway.deconz_ids
# 3 entities
assert len(hass.states.async_all()) == 4

View File

@ -218,6 +218,40 @@ async def test_allow_clip_sensors(hass):
clip_light_level_sensor = hass.states.get("sensor.clip_light_level_sensor") clip_light_level_sensor = hass.states.get("sensor.clip_light_level_sensor")
assert clip_light_level_sensor.state == "999.8" assert clip_light_level_sensor.state == "999.8"
hass.config_entries.async_update_entry(
gateway.config_entry, options={deconz.gateway.CONF_ALLOW_CLIP_SENSOR: False}
)
await hass.async_block_till_done()
assert "sensor.light_level_sensor" in gateway.deconz_ids
assert "sensor.presence_sensor" not in gateway.deconz_ids
assert "sensor.switch_1" not in gateway.deconz_ids
assert "sensor.switch_1_battery_level" not in gateway.deconz_ids
assert "sensor.switch_2" not in gateway.deconz_ids
assert "sensor.switch_2_battery_level" in gateway.deconz_ids
assert "sensor.daylight_sensor" not in gateway.deconz_ids
assert "sensor.power_sensor" in gateway.deconz_ids
assert "sensor.consumption_sensor" in gateway.deconz_ids
assert "sensor.clip_light_level_sensor" not in gateway.deconz_ids
assert len(hass.states.async_all()) == 5
hass.config_entries.async_update_entry(
gateway.config_entry, options={deconz.gateway.CONF_ALLOW_CLIP_SENSOR: True}
)
await hass.async_block_till_done()
assert "sensor.light_level_sensor" in gateway.deconz_ids
assert "sensor.presence_sensor" not in gateway.deconz_ids
assert "sensor.switch_1" not in gateway.deconz_ids
assert "sensor.switch_1_battery_level" not in gateway.deconz_ids
assert "sensor.switch_2" not in gateway.deconz_ids
assert "sensor.switch_2_battery_level" in gateway.deconz_ids
assert "sensor.daylight_sensor" not in gateway.deconz_ids
assert "sensor.power_sensor" in gateway.deconz_ids
assert "sensor.consumption_sensor" in gateway.deconz_ids
assert "sensor.clip_light_level_sensor" in gateway.deconz_ids
assert len(hass.states.async_all()) == 6
async def test_add_new_sensor(hass): async def test_add_new_sensor(hass):
"""Test that adding a new sensor works.""" """Test that adding a new sensor works."""