Hue: unique ID for groups + remote events (#50748)
This commit is contained in:
parent
c8486879ae
commit
56774a9f63
10 changed files with 76 additions and 39 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue