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.""" """Code to handle a Hue bridge."""
from __future__ import annotations
import asyncio import asyncio
from functools import partial from functools import partial
import logging import logging
@ -241,7 +243,8 @@ class HueBridge:
key = (updated_object.ITEM_TYPE, updated_object.id) key = (updated_object.ITEM_TYPE, updated_object.id)
if key in self._update_callbacks: if key in self._update_callbacks:
self._update_callbacks[key]() for callback in self._update_callbacks[key]:
callback()
except GeneratorExit: except GeneratorExit:
pass pass
@ -249,18 +252,20 @@ class HueBridge:
@core.callback @core.callback
def listen_updates(self, item_type, item_id, update_callback): def listen_updates(self, item_type, item_id, update_callback):
"""Listen to updates.""" """Listen to updates."""
callbacks = self._update_callbacks
key = (item_type, item_id) key = (item_type, item_id)
callbacks: list[core.CALLBACK_TYPE] | None = self._update_callbacks.get(key)
if key in callbacks: if callbacks is None:
_LOGGER.warning("Overwriting update callback for %s", key) callbacks = self._update_callbacks[key] = []
callbacks[key] = update_callback callbacks.append(update_callback)
@core.callback @core.callback
def unsub(): def unsub():
if callbacks.get(key) == update_callback: try:
callbacks.pop(key) callbacks.remove(update_callback)
except ValueError:
pass
return unsub 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.const import CONF_EVENT, CONF_ID, CONF_UNIQUE_ID
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.util import slugify from homeassistant.util import dt as dt_util, slugify
from .sensor_device import GenericHueDevice from .sensor_device import GenericHueDevice
@ -48,7 +48,13 @@ class HueEvent(GenericHueDevice):
@callback @callback
def async_update_callback(self): def async_update_callback(self):
"""Fire the event if reason is that state is updated.""" """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 return
# Extract the press code as state # Extract the press code as state

View File

@ -306,7 +306,11 @@ class HueLight(CoordinatorEntity, LightEntity):
@property @property
def unique_id(self): def unique_id(self):
"""Return the unique ID of this Hue light.""" """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 @property
def device_id(self): def device_id(self):

View File

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

View File

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

View File

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

View File

@ -81,10 +81,10 @@ def create_mock_api(hass):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
api.config.apiversion = "9.9.9" api.config.apiversion = "9.9.9"
api.lights = Lights(logger, {}, mock_request) api.lights = Lights(logger, {}, [], mock_request)
api.groups = Groups(logger, {}, mock_request) api.groups = Groups(logger, {}, [], mock_request)
api.sensors = Sensors(logger, {}, mock_request) api.sensors = Sensors(logger, {}, [], mock_request)
api.scenes = Scenes(logger, {}, mock_request) api.scenes = Scenes(logger, {}, [], mock_request)
return api 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 = hue_bridge.listen_updates("lights", "2", obj_updated)
unsub_false = hue_bridge.listen_updates("lights", "2", obj_updated_false) 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")) events.put_nowait(Mock(ITEM_TYPE="lights", id="2"))
await wait_empty_queue() await wait_empty_queue()
assert len(calls) == 2 assert len(calls) == 3
assert calls[-2] is True
assert calls[-1] is False assert calls[-1] is False
# Also call multiple times to make sure that works. # 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")) events.put_nowait(Mock(ITEM_TYPE="lights", id="2"))
await wait_empty_queue() await wait_empty_queue()
assert len(calls) == 2 assert len(calls) == 3
events.put_nowait(None) events.put_nowait(None)
await subscription_task await subscription_task

View File

@ -269,6 +269,10 @@ async def test_groups(hass, mock_bridge):
mock_bridge.allow_groups = True mock_bridge.allow_groups = True
mock_bridge.mock_light_responses.append({}) mock_bridge.mock_light_responses.append({})
mock_bridge.mock_group_responses.append(GROUP_RESPONSE) 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) await setup_bridge(hass, mock_bridge)
assert len(mock_bridge.mock_requests) == 2 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 is not None
assert lamp_2.state == "on" 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): async def test_new_group_discovered(hass, mock_bridge):
"""Test if 2nd update has a new group.""" """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 .conftest import create_mock_bridge, setup_bridge_for_sensors as setup_bridge
from tests.common import async_capture_events
PRESENCE_SENSOR_1_PRESENT = { PRESENCE_SENSOR_1_PRESENT = {
"state": {"presence": True, "lastupdated": "2019-01-01T01:00:00"}, "state": {"presence": True, "lastupdated": "2019-01-01T01:00:00"},
"swupdate": {"state": "noupdates", "lastinstall": "2019-01-01T00: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.""" """Test that hue remotes fire events when pressed."""
mock_bridge.mock_sensor_responses.append(SENSOR_RESPONSE) mock_bridge.mock_sensor_responses.append(SENSOR_RESPONSE)
mock_listener = Mock() events = async_capture_events(hass, CONF_HUE_EVENT)
unsub = hass.bus.async_listen(CONF_HUE_EVENT, mock_listener)
await setup_bridge(hass, mock_bridge) await setup_bridge(hass, mock_bridge)
assert len(mock_bridge.mock_requests) == 1 assert len(mock_bridge.mock_requests) == 1
assert len(hass.states.async_all()) == 7 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 = dict(SENSOR_RESPONSE)
new_sensor_response["7"]["state"] = { new_sensor_response["7"]["state"] = {
"buttonevent": 18, "buttonevent": 18,
"lastupdated": "2019-12-28T22:58:02", "lastupdated": "2019-12-28T22:58:03",
} }
mock_bridge.mock_sensor_responses.append(new_sensor_response) 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(mock_bridge.mock_requests) == 2
assert len(hass.states.async_all()) == 7 assert len(hass.states.async_all()) == 7
assert len(mock_listener.mock_calls) == 1 assert len(events) == 1
assert mock_listener.mock_calls[0][1][0].data == { assert events[-1].data == {
"id": "hue_tap", "id": "hue_tap",
"unique_id": "00:00:00:00:00:44:23:08-f2", "unique_id": "00:00:00:00:00:44:23:08-f2",
"event": 18, "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 = dict(new_sensor_response)
new_sensor_response["8"]["state"] = { new_sensor_response["8"]["state"] = {
"buttonevent": 3002, "buttonevent": 3002,
"lastupdated": "2019-12-28T22:58:01", "lastupdated": "2019-12-28T22:58:03",
} }
mock_bridge.mock_sensor_responses.append(new_sensor_response) 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(mock_bridge.mock_requests) == 3
assert len(hass.states.async_all()) == 7 assert len(hass.states.async_all()) == 7
assert len(mock_listener.mock_calls) == 2 assert len(events) == 2
assert mock_listener.mock_calls[1][1][0].data == { assert events[-1].data == {
"id": "hue_dimmer_switch_1", "id": "hue_dimmer_switch_1",
"unique_id": "00:17:88:01:10:3e:3a:dc-02-fc00", "unique_id": "00:17:88:01:10:3e:3a:dc-02-fc00",
"event": 3002, "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** # Add a new remote. In discovery the new event is registered **but not fired**
new_sensor_response = dict(new_sensor_response) new_sensor_response = dict(new_sensor_response)
new_sensor_response["21"] = { 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 mock_bridge.sensor_manager.coordinator.async_refresh()
await hass.async_block_till_done() 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(hass.states.async_all()) == 8
assert len(mock_listener.mock_calls) == 2 assert len(events) == 2
# A new press fires the event # A new press fires the event
new_sensor_response["21"]["state"]["lastupdated"] = "2020-01-31T15:57:19" 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 mock_bridge.sensor_manager.coordinator.async_refresh()
await hass.async_block_till_done() 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(hass.states.async_all()) == 8
assert len(mock_listener.mock_calls) == 3 assert len(events) == 3
assert mock_listener.mock_calls[2][1][0].data == { assert events[-1].data == {
"id": "lutron_aurora_1", "id": "lutron_aurora_1",
"unique_id": "ff:ff:00:0f:e7:fd:bc:b7-01-fc00-0014", "unique_id": "ff:ff:00:0f:e7:fd:bc:b7-01-fc00-0014",
"event": 2, "event": 2,
"last_updated": "2020-01-31T15:57:19", "last_updated": "2020-01-31T15:57:19",
} }
unsub()