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_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
or not sensor.type.startswith("CLIP")
)
and sensor.deconz_id not in gateway.deconz_ids.values()
):
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
or not sensor.type.startswith("CLIP")
)
and sensor.deconz_id not in gateway.deconz_ids.values()
):
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.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:
"""Disconnect device object when removed."""
@ -85,6 +90,15 @@ class DeconzDevice(DeconzBase, Entity):
for unsub_dispatcher in self.listeners:
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
def async_update_callback(self, force_update=False, ignore_update=False):
"""Update the device's state."""

View File

@ -20,6 +20,8 @@ from .const import (
DOMAIN,
LOGGER,
NEW_DEVICE,
NEW_GROUP,
NEW_SENSOR,
SUPPORTED_PLATFORMS,
)
from .errors import AuthenticationRequired, CannotConnect
@ -45,6 +47,9 @@ class DeconzGateway:
self.events = []
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
def bridgeid(self) -> str:
"""Return the unique identifier of the gateway."""
@ -108,22 +113,64 @@ class DeconzGateway:
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
@staticmethod
async def async_new_address(hass, entry) -> None:
"""Handle signals of gateway getting new address.
async def async_config_entry_updated(hass, entry) -> None:
"""Handle signals of config entry being updated.
This is a static method because a class method (bound method),
can not be used with weak references.
This is a static method because a class method (bound method), 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)
if gateway.api.host != entry.data[CONF_HOST]:
gateway.api.close()
gateway.api.host = entry.data[CONF_HOST]
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
def signal_reachable(self) -> str:
@ -141,6 +188,11 @@ class DeconzGateway:
"""Gateway specific event to signal new device."""
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
def async_add_device_callback(self, device_type, device) -> None:
"""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 = []
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))
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
or not sensor.type.startswith("CLIP")
)
and sensor.deconz_id not in gateway.deconz_ids.values()
):
entities.append(DeconzSensor(sensor, gateway))

View File

@ -14,20 +14,9 @@
"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"
},
"options": {
"title": "Extra configuration options for deCONZ",
"data": {
"allow_clip_sensor": "Allow importing virtual sensors",
"allow_deconz_groups": "Allow importing deCONZ groups"
}
},
"hassio_confirm": {
"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}?",
"data": {
"allow_clip_sensor": "Allow importing virtual sensors",
"allow_deconz_groups": "Allow importing deCONZ groups"
}
"description": "Do you want to configure Home Assistant to connect to the deCONZ gateway provided by the Hass.io add-on {addon}?"
}
},
"error": {
@ -45,11 +34,12 @@
"options": {
"step": {
"deconz_devices": {
"description": "Configure visibility of deCONZ device types",
"data": {
"allow_clip_sensor": "Allow deCONZ CLIP sensors",
"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"
}
}
}
}

View File

@ -134,6 +134,28 @@ async def test_allow_clip_sensor(hass):
vibration_sensor = hass.states.get("binary_sensor.vibration_sensor")
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):
"""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")
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):
"""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")
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")
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):
"""Test that adding a new sensor works."""