Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions homeassistant/components/hue/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def __init__(self, hass, config_entry):
# Jobs to be executed when API is reset.
self.reset_jobs = []
self.sensor_manager = None
self.unsub_config_entry_listener = None
self._update_callbacks = {}

@property
def host(self):
Expand Down Expand Up @@ -111,9 +111,8 @@ async def async_setup(self, tries=0):
3 if self.api.config.modelid == "BSB001" else 10
)

self.unsub_config_entry_listener = self.config_entry.add_update_listener(
_update_listener
)
self.reset_jobs.append(self.config_entry.add_update_listener(_update_listener))
self.reset_jobs.append(asyncio.create_task(self._subscribe_events()).cancel)

self.authorized = True
return True
Expand Down Expand Up @@ -168,8 +167,7 @@ async def async_reset(self):
while self.reset_jobs:
self.reset_jobs.pop()()

if self.unsub_config_entry_listener is not None:
self.unsub_config_entry_listener()
self._update_callbacks = {}

# If setup was successful, we set api variable, forwarded entry and
# register service
Expand Down Expand Up @@ -236,6 +234,36 @@ async def handle_unauthorized_error(self):
self.authorized = False
create_config_flow(self.hass, self.host)

async def _subscribe_events(self):
"""Subscribe to Hue events."""
try:
async for updated_object in self.api.listen_events():
key = (updated_object.ITEM_TYPE, updated_object.id)

if key in self._update_callbacks:
self._update_callbacks[key]()

except GeneratorExit:
pass

@core.callback
def listen_updates(self, item_type, item_id, update_callback):
"""Listen to updates."""
callbacks = self._update_callbacks
key = (item_type, item_id)

if key in callbacks:
_LOGGER.warning("Overwriting update callback for %s", key)

callbacks[key] = update_callback

@core.callback
def unsub():
if callbacks.get(key) == update_callback:
callbacks.pop(key)

return unsub


async def authenticate_bridge(hass: core.HomeAssistant, bridge: aiohue.Bridge):
"""Create a bridge object and verify authentication."""
Expand Down
6 changes: 5 additions & 1 deletion homeassistant/components/hue/hue_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ def __init__(self, sensor, name, bridge, primary_sensor=None):
self.async_update_callback
)
)
_LOGGER.debug("Hue event created: %s", self.event_id)
self.bridge.reset_jobs.append(
self.bridge.listen_updates(
self.sensor.ITEM_TYPE, self.sensor.id, self.async_update_callback
)
)

@callback
def async_update_callback(self):
Expand Down
9 changes: 9 additions & 0 deletions homeassistant/components/hue/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,15 @@ def device_info(self):

return info

async def async_added_to_hass(self) -> None:
"""Handle entity being added to Home Assistant."""
self.async_on_remove(
self.bridge.listen_updates(
self.light.ITEM_TYPE, self.light.id, self.async_write_ha_state
)
)
await super().async_added_to_hass()

async def async_turn_on(self, **kwargs):
"""Turn the specified or all lights on."""
command = {"on": True}
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/hue/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Philips Hue",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/hue",
"requirements": ["aiohue==2.3.1"],
"requirements": ["aiohue==2.4.2"],
"ssdp": [
{
"manufacturer": "Royal Philips Electronics",
Expand Down
3 changes: 0 additions & 3 deletions homeassistant/components/hue/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class GenericHueGaugeSensorEntity(GenericZLLSensor, SensorEntity):
"""Parent class for all 'gauge' Hue device sensors."""

async def _async_update_ha_state(self, *args, **kwargs):
await self.async_update_ha_state(self, *args, **kwargs)


class HueLightLevel(GenericHueGaugeSensorEntity):
"""The light level sensor entity for a Hue motion sensor device."""
Expand Down
4 changes: 1 addition & 3 deletions homeassistant/components/hue/sensor_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,6 @@ class GenericHueSensor(GenericHueDevice, entity.Entity):

should_poll = False

async def _async_update_ha_state(self, *args, **kwargs):
raise NotImplementedError

@property
def available(self):
"""Return if sensor is available."""
Expand All @@ -185,6 +182,7 @@ def state_class(self):

async def async_added_to_hass(self):
"""When entity is added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
self.bridge.sensor_manager.coordinator.async_add_listener(
self.async_write_ha_state
Expand Down
13 changes: 12 additions & 1 deletion homeassistant/components/hue/sensor_device.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Support for the Philips Hue sensor devices."""
from homeassistant.helpers import entity

from .const import DOMAIN as HUE_DOMAIN


class GenericHueDevice:
class GenericHueDevice(entity.Entity):
"""Representation of a Hue device."""

def __init__(self, sensor, name, bridge, primary_sensor=None):
Expand Down Expand Up @@ -51,3 +53,12 @@ def device_info(self):
"sw_version": self.primary_sensor.swversion,
"via_device": (HUE_DOMAIN, self.bridge.api.config.bridgeid),
}

async def async_added_to_hass(self) -> None:
"""Handle entity being added to Home Assistant."""
self.async_on_remove(
self.bridge.listen_updates(
self.sensor.ITEM_TYPE, self.sensor.id, self.async_write_ha_state
)
)
await super().async_added_to_hass()
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ aiohomekit==0.2.61
aiohttp_cors==0.7.0

# homeassistant.components.hue
aiohue==2.3.1
aiohue==2.4.2

# homeassistant.components.imap
aioimaplib==0.7.15
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ aiohomekit==0.2.61
aiohttp_cors==0.7.0

# homeassistant.components.hue
aiohue==2.3.1
aiohue==2.4.2

# homeassistant.components.apache_kafka
aiokafka==0.6.0
Expand Down
46 changes: 17 additions & 29 deletions tests/components/hue/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test helpers for Hue."""
from collections import deque
import logging
from unittest.mock import AsyncMock, Mock, patch

from aiohue.groups import Groups
Expand Down Expand Up @@ -30,46 +31,31 @@ def create_mock_bridge(hass):
authorized=True,
allow_unreachable=False,
allow_groups=False,
api=Mock(),
api=create_mock_api(hass),
reset_jobs=[],
spec=hue.HueBridge,
)
bridge.sensor_manager = hue_sensor_base.SensorManager(bridge)
bridge.mock_requests = []
# We're using a deque so we can schedule multiple responses
# and also means that `popleft()` will blow up if we get more updates
# than expected.
bridge.mock_light_responses = deque()
bridge.mock_group_responses = deque()
bridge.mock_sensor_responses = deque()

async def mock_request(method, path, **kwargs):
kwargs["method"] = method
kwargs["path"] = path
bridge.mock_requests.append(kwargs)

if path == "lights":
return bridge.mock_light_responses.popleft()
if path == "groups":
return bridge.mock_group_responses.popleft()
if path == "sensors":
return bridge.mock_sensor_responses.popleft()
return None
bridge.mock_requests = bridge.api.mock_requests
bridge.mock_light_responses = bridge.api.mock_light_responses
bridge.mock_group_responses = bridge.api.mock_group_responses
bridge.mock_sensor_responses = bridge.api.mock_sensor_responses

async def async_request_call(task):
await task()

bridge.async_request_call = async_request_call
bridge.api.config.apiversion = "9.9.9"
bridge.api.lights = Lights({}, mock_request)
bridge.api.groups = Groups({}, mock_request)
bridge.api.sensors = Sensors({}, mock_request)
return bridge


@pytest.fixture
def mock_api(hass):
"""Mock the Hue api."""
return create_mock_api(hass)


def create_mock_api(hass):
"""Create a mock API."""
api = Mock(initialize=AsyncMock())
api.mock_requests = []
api.mock_light_responses = deque()
Expand All @@ -92,11 +78,13 @@ async def mock_request(method, path, **kwargs):
return api.mock_scene_responses.popleft()
return None

logger = logging.getLogger(__name__)

api.config.apiversion = "9.9.9"
api.lights = Lights({}, mock_request)
api.groups = Groups({}, mock_request)
api.sensors = Sensors({}, mock_request)
api.scenes = Scenes({}, 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


Expand Down
Loading