Hue: unique ID for groups + remote events (#50748)

This commit is contained in:
Paulus Schoutsen 2021-05-17 08:07:25 -07:00 committed by GitHub
parent c8486879ae
commit 56774a9f63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 76 additions and 39 deletions

View file

@ -1,4 +1,6 @@
"""Code to handle a Hue bridge."""
from __future__ import annotations
import asyncio
from functools import partial
import logging
@ -241,7 +243,8 @@ class HueBridge:
key = (updated_object.ITEM_TYPE, updated_object.id)
if key in self._update_callbacks:
self._update_callbacks[key]()
for callback in self._update_callbacks[key]:
callback()
except GeneratorExit:
pass
@ -249,18 +252,20 @@ class HueBridge:
@core.callback
def listen_updates(self, item_type, item_id, update_callback):
"""Listen to updates."""
callbacks = self._update_callbacks
key = (item_type, item_id)
callbacks: list[core.CALLBACK_TYPE] | None = self._update_callbacks.get(key)
if key in callbacks:
_LOGGER.warning("Overwriting update callback for %s", key)
if callbacks is None:
callbacks = self._update_callbacks[key] = []
callbacks[key] = update_callback
callbacks.append(update_callback)
@core.callback
def unsub():
if callbacks.get(key) == update_callback:
callbacks.pop(key)
try:
callbacks.remove(update_callback)
except ValueError:
pass
return unsub

View file

@ -5,7 +5,7 @@ from aiohue.sensors import TYPE_ZGP_SWITCH, TYPE_ZLL_ROTARY, TYPE_ZLL_SWITCH
from homeassistant.const import CONF_EVENT, CONF_ID, CONF_UNIQUE_ID
from homeassistant.core import callback
from homeassistant.util import slugify
from homeassistant.util import dt as dt_util, slugify
from .sensor_device import GenericHueDevice
@ -48,7 +48,13 @@ class HueEvent(GenericHueDevice):
@callback
def async_update_callback(self):
"""Fire the event if reason is that state is updated."""
if self.sensor.state == self._last_state:
if (
self.sensor.state == self._last_state
or
# Filter out old states. Can happen when events fire while refreshing
dt_util.parse_datetime(self.sensor.state["lastupdated"])
<= dt_util.parse_datetime(self._last_state["lastupdated"])
):
return
# Extract the press code as state

View file

@ -306,7 +306,11 @@ class HueLight(CoordinatorEntity, LightEntity):
@property
def unique_id(self):
"""Return the unique ID of this Hue light."""
return self.light.uniqueid
unique_id = self.light.uniqueid
if not unique_id and self.is_group and self.light.room:
unique_id = self.light.room["id"]
return unique_id
@property
def device_id(self):

View file

@ -3,7 +3,7 @@
"name": "Philips Hue",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/hue",
"requirements": ["aiohue==2.4.2"],
"requirements": ["aiohue==2.5.0"],
"ssdp": [
{
"manufacturer": "Royal Philips Electronics",

View file

@ -182,7 +182,7 @@ aiohomekit==0.2.61
aiohttp_cors==0.7.0
# homeassistant.components.hue
aiohue==2.4.2
aiohue==2.5.0
# homeassistant.components.imap
aioimaplib==0.7.15

View file

@ -119,7 +119,7 @@ aiohomekit==0.2.61
aiohttp_cors==0.7.0
# homeassistant.components.hue
aiohue==2.4.2
aiohue==2.5.0
# homeassistant.components.apache_kafka
aiokafka==0.6.0

View file

@ -81,10 +81,10 @@ def create_mock_api(hass):
logger = logging.getLogger(__name__)
api.config.apiversion = "9.9.9"
api.lights = Lights(logger, {}, mock_request)
api.groups = Groups(logger, {}, mock_request)
api.sensors = Sensors(logger, {}, mock_request)
api.scenes = Scenes(logger, {}, mock_request)
api.lights = Lights(logger, {}, [], mock_request)
api.groups = Groups(logger, {}, [], mock_request)
api.sensors = Sensors(logger, {}, [], mock_request)
api.scenes = Scenes(logger, {}, [], mock_request)
return api

View file

@ -351,12 +351,11 @@ async def test_event_updates(hass, caplog):
unsub = hue_bridge.listen_updates("lights", "2", obj_updated)
unsub_false = hue_bridge.listen_updates("lights", "2", obj_updated_false)
assert "Overwriting update callback" in caplog.text
events.put_nowait(Mock(ITEM_TYPE="lights", id="2"))
await wait_empty_queue()
assert len(calls) == 2
assert len(calls) == 3
assert calls[-2] is True
assert calls[-1] is False
# Also call multiple times to make sure that works.
@ -368,7 +367,7 @@ async def test_event_updates(hass, caplog):
events.put_nowait(Mock(ITEM_TYPE="lights", id="2"))
await wait_empty_queue()
assert len(calls) == 2
assert len(calls) == 3
events.put_nowait(None)
await subscription_task

View file

@ -269,6 +269,10 @@ async def test_groups(hass, mock_bridge):
mock_bridge.allow_groups = True
mock_bridge.mock_light_responses.append({})
mock_bridge.mock_group_responses.append(GROUP_RESPONSE)
mock_bridge.api.groups._v2_resources = [
{"id_v1": "/groups/1", "id": "group-1-mock-id", "type": "room"},
{"id_v1": "/groups/2", "id": "group-2-mock-id", "type": "room"},
]
await setup_bridge(hass, mock_bridge)
assert len(mock_bridge.mock_requests) == 2
@ -285,6 +289,10 @@ async def test_groups(hass, mock_bridge):
assert lamp_2 is not None
assert lamp_2.state == "on"
ent_reg = er.async_get(hass)
assert ent_reg.async_get("light.group_1").unique_id == "group-1-mock-id"
assert ent_reg.async_get("light.group_2").unique_id == "group-2-mock-id"
async def test_new_group_discovered(hass, mock_bridge):
"""Test if 2nd update has a new group."""

View file

@ -8,6 +8,8 @@ from homeassistant.components.hue.hue_event import CONF_HUE_EVENT
from .conftest import create_mock_bridge, setup_bridge_for_sensors as setup_bridge
from tests.common import async_capture_events
PRESENCE_SENSOR_1_PRESENT = {
"state": {"presence": True, "lastupdated": "2019-01-01T01:00:00"},
"swupdate": {"state": "noupdates", "lastinstall": "2019-01-01T00:00:00"},
@ -435,18 +437,17 @@ async def test_hue_events(hass, mock_bridge):
"""Test that hue remotes fire events when pressed."""
mock_bridge.mock_sensor_responses.append(SENSOR_RESPONSE)
mock_listener = Mock()
unsub = hass.bus.async_listen(CONF_HUE_EVENT, mock_listener)
events = async_capture_events(hass, CONF_HUE_EVENT)
await setup_bridge(hass, mock_bridge)
assert len(mock_bridge.mock_requests) == 1
assert len(hass.states.async_all()) == 7
assert len(mock_listener.mock_calls) == 0
assert len(events) == 0
new_sensor_response = dict(SENSOR_RESPONSE)
new_sensor_response["7"]["state"] = {
"buttonevent": 18,
"lastupdated": "2019-12-28T22:58:02",
"lastupdated": "2019-12-28T22:58:03",
}
mock_bridge.mock_sensor_responses.append(new_sensor_response)
@ -456,18 +457,18 @@ async def test_hue_events(hass, mock_bridge):
assert len(mock_bridge.mock_requests) == 2
assert len(hass.states.async_all()) == 7
assert len(mock_listener.mock_calls) == 1
assert mock_listener.mock_calls[0][1][0].data == {
assert len(events) == 1
assert events[-1].data == {
"id": "hue_tap",
"unique_id": "00:00:00:00:00:44:23:08-f2",
"event": 18,
"last_updated": "2019-12-28T22:58:02",
"last_updated": "2019-12-28T22:58:03",
}
new_sensor_response = dict(new_sensor_response)
new_sensor_response["8"]["state"] = {
"buttonevent": 3002,
"lastupdated": "2019-12-28T22:58:01",
"lastupdated": "2019-12-28T22:58:03",
}
mock_bridge.mock_sensor_responses.append(new_sensor_response)
@ -477,14 +478,30 @@ async def test_hue_events(hass, mock_bridge):
assert len(mock_bridge.mock_requests) == 3
assert len(hass.states.async_all()) == 7
assert len(mock_listener.mock_calls) == 2
assert mock_listener.mock_calls[1][1][0].data == {
assert len(events) == 2
assert events[-1].data == {
"id": "hue_dimmer_switch_1",
"unique_id": "00:17:88:01:10:3e:3a:dc-02-fc00",
"event": 3002,
"last_updated": "2019-12-28T22:58:01",
"last_updated": "2019-12-28T22:58:03",
}
# Fire old event, it should be ignored
new_sensor_response = dict(new_sensor_response)
new_sensor_response["8"]["state"] = {
"buttonevent": 18,
"lastupdated": "2019-12-28T22:58:02",
}
mock_bridge.mock_sensor_responses.append(new_sensor_response)
# Force updates to run again
await mock_bridge.sensor_manager.coordinator.async_refresh()
await hass.async_block_till_done()
assert len(mock_bridge.mock_requests) == 4
assert len(hass.states.async_all()) == 7
assert len(events) == 2
# Add a new remote. In discovery the new event is registered **but not fired**
new_sensor_response = dict(new_sensor_response)
new_sensor_response["21"] = {
@ -524,9 +541,9 @@ async def test_hue_events(hass, mock_bridge):
await mock_bridge.sensor_manager.coordinator.async_refresh()
await hass.async_block_till_done()
assert len(mock_bridge.mock_requests) == 4
assert len(mock_bridge.mock_requests) == 5
assert len(hass.states.async_all()) == 8
assert len(mock_listener.mock_calls) == 2
assert len(events) == 2
# A new press fires the event
new_sensor_response["21"]["state"]["lastupdated"] = "2020-01-31T15:57:19"
@ -536,14 +553,12 @@ async def test_hue_events(hass, mock_bridge):
await mock_bridge.sensor_manager.coordinator.async_refresh()
await hass.async_block_till_done()
assert len(mock_bridge.mock_requests) == 5
assert len(mock_bridge.mock_requests) == 6
assert len(hass.states.async_all()) == 8
assert len(mock_listener.mock_calls) == 3
assert mock_listener.mock_calls[2][1][0].data == {
assert len(events) == 3
assert events[-1].data == {
"id": "lutron_aurora_1",
"unique_id": "ff:ff:00:0f:e7:fd:bc:b7-01-fc00-0014",
"event": 2,
"last_updated": "2020-01-31T15:57:19",
}
unsub()