mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
Hue: unique ID for groups + remote events (#50748)
This commit is contained in:
parent
c8486879ae
commit
56774a9f63
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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."""
|
||||||
|
@ -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()
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user