diff --git a/homeassistant/components/hue.py b/homeassistant/components/hue.py index 3dad4429b53..6147f706658 100644 --- a/homeassistant/components/hue.py +++ b/homeassistant/components/hue.py @@ -160,6 +160,8 @@ class HueBridge(object): self.allow_hue_groups = allow_hue_groups self.bridge = None + self.lights = {} + self.lightgroups = {} self.configured = False self.config_request_id = None diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index a454143bcd2..f5c910ea116 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -31,10 +31,6 @@ DEPENDENCIES = ['hue'] _LOGGER = logging.getLogger(__name__) -DATA_KEY = 'hue_lights' -DATA_LIGHTS = 'lights' -DATA_LIGHTGROUPS = 'lightgroups' - MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) @@ -93,8 +89,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if discovery_info is None or 'bridge_id' not in discovery_info: return - setup_data(hass) - if config is not None and len(config) > 0: # Legacy configuration, will be removed in 0.60 config_str = yaml.dump([config]) @@ -110,12 +104,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): unthrottled_update_lights(hass, bridge, add_devices) -def setup_data(hass): - """Initialize internal data. Useful from tests.""" - if DATA_KEY not in hass.data: - hass.data[DATA_KEY] = {DATA_LIGHTS: {}, DATA_LIGHTGROUPS: {}} - - @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update_lights(hass, bridge, add_devices): """Update the Hue light objects with latest info from the bridge.""" @@ -176,18 +164,17 @@ def process_lights(hass, api, bridge, bridge_type, update_lights_cb): new_lights = [] - lights = hass.data[DATA_KEY][DATA_LIGHTS] for light_id, info in api_lights.items(): - if light_id not in lights: - lights[light_id] = HueLight( + if light_id not in bridge.lights: + bridge.lights[light_id] = HueLight( int(light_id), info, bridge, update_lights_cb, bridge_type, bridge.allow_unreachable, bridge.allow_in_emulated_hue) - new_lights.append(lights[light_id]) + new_lights.append(bridge.lights[light_id]) else: - lights[light_id].info = info - lights[light_id].schedule_update_ha_state() + bridge.lights[light_id].info = info + bridge.lights[light_id].schedule_update_ha_state() return new_lights @@ -202,23 +189,22 @@ def process_groups(hass, api, bridge, bridge_type, update_lights_cb): new_lights = [] - groups = hass.data[DATA_KEY][DATA_LIGHTGROUPS] for lightgroup_id, info in api_groups.items(): if 'state' not in info: _LOGGER.warning('Group info does not contain state. ' 'Please update your hub.') return [] - if lightgroup_id not in groups: - groups[lightgroup_id] = HueLight( + if lightgroup_id not in bridge.lightgroups: + bridge.lightgroups[lightgroup_id] = HueLight( int(lightgroup_id), info, bridge, update_lights_cb, bridge_type, bridge.allow_unreachable, bridge.allow_in_emulated_hue, True) - new_lights.append(groups[lightgroup_id]) + new_lights.append(bridge.lightgroups[lightgroup_id]) else: - groups[lightgroup_id].info = info - groups[lightgroup_id].schedule_update_ha_state() + bridge.lightgroups[lightgroup_id].info = info + bridge.lightgroups[lightgroup_id].schedule_update_ha_state() return new_lights diff --git a/tests/components/light/test_hue.py b/tests/components/light/test_hue.py index 5e5bd4f6c7f..7955cecba04 100644 --- a/tests/components/light/test_hue.py +++ b/tests/components/light/test_hue.py @@ -36,27 +36,45 @@ class TestSetup(unittest.TestCase): self.mock_lights = [] self.mock_groups = [] self.mock_add_devices = MagicMock() - hue_light.setup_data(self.hass) def setup_mocks_for_process_lights(self): """Set up all mocks for process_lights tests.""" - self.mock_bridge = MagicMock() + self.mock_bridge = self.create_mock_bridge('host') self.mock_api = MagicMock() self.mock_api.get.return_value = {} self.mock_bridge.get_api.return_value = self.mock_api self.mock_bridge_type = MagicMock() - hue_light.setup_data(self.hass) def setup_mocks_for_process_groups(self): """Set up all mocks for process_groups tests.""" - self.mock_bridge = MagicMock() + self.mock_bridge = self.create_mock_bridge('host') self.mock_bridge.get_group.return_value = { 'name': 'Group 0', 'state': {'any_on': True}} + self.mock_api = MagicMock() self.mock_api.get.return_value = {} self.mock_bridge.get_api.return_value = self.mock_api + self.mock_bridge_type = MagicMock() - hue_light.setup_data(self.hass) + + def create_mock_bridge(self, host, allow_hue_groups=True): + """Return a mock HueBridge with reasonable defaults.""" + mock_bridge = MagicMock() + mock_bridge.host = host + mock_bridge.allow_hue_groups = allow_hue_groups + mock_bridge.lights = {} + mock_bridge.lightgroups = {} + return mock_bridge + + def create_mock_lights(self, lights): + """Return a dict suitable for mocking api.get('lights').""" + mock_bridge_lights = lights + + for light_id, info in mock_bridge_lights.items(): + if 'state' not in info: + info['state'] = {'on': False} + + return mock_bridge_lights def test_setup_platform_no_discovery_info(self): """Test setup_platform without discovery info.""" @@ -211,6 +229,70 @@ class TestSetup(unittest.TestCase): self.mock_add_devices.assert_called_once_with( self.mock_lights) + @MockDependency('phue') + def test_update_lights_with_two_bridges(self, mock_phue): + """Test the update_lights function with two bridges.""" + self.setup_mocks_for_update_lights() + + mock_bridge_one = self.create_mock_bridge('one', False) + mock_bridge_one_lights = self.create_mock_lights( + {1: {'name': 'b1l1'}, 2: {'name': 'b1l2'}}) + + mock_bridge_two = self.create_mock_bridge('two', False) + mock_bridge_two_lights = self.create_mock_lights( + {1: {'name': 'b2l1'}, 3: {'name': 'b2l3'}}) + + with patch('homeassistant.components.light.hue.get_bridge_type', + return_value=self.mock_bridge_type): + with patch('homeassistant.components.light.hue.HueLight.' + 'schedule_update_ha_state'): + mock_api = MagicMock() + mock_api.get.return_value = mock_bridge_one_lights + with patch.object(mock_bridge_one, 'get_api', + return_value=mock_api): + hue_light.unthrottled_update_lights( + self.hass, mock_bridge_one, self.mock_add_devices) + + mock_api = MagicMock() + mock_api.get.return_value = mock_bridge_two_lights + with patch.object(mock_bridge_two, 'get_api', + return_value=mock_api): + hue_light.unthrottled_update_lights( + self.hass, mock_bridge_two, self.mock_add_devices) + + self.assertEquals(sorted(mock_bridge_one.lights.keys()), [1, 2]) + self.assertEquals(sorted(mock_bridge_two.lights.keys()), [1, 3]) + + self.assertEquals(len(self.mock_add_devices.mock_calls), 2) + + # first call + name, args, kwargs = self.mock_add_devices.mock_calls[0] + self.assertEquals(len(args), 1) + self.assertEquals(len(kwargs), 0) + + # one argument, a list of lights in bridge one; each of them is an + # object of type HueLight so we can't straight up compare them + lights = args[0] + self.assertEquals( + lights[0].unique_id, + '{}.b1l1.Light.1'.format(hue_light.HueLight)) + self.assertEquals( + lights[1].unique_id, + '{}.b1l2.Light.2'.format(hue_light.HueLight)) + + # second call works the same + name, args, kwargs = self.mock_add_devices.mock_calls[1] + self.assertEquals(len(args), 1) + self.assertEquals(len(kwargs), 0) + + lights = args[0] + self.assertEquals( + lights[0].unique_id, + '{}.b2l1.Light.1'.format(hue_light.HueLight)) + self.assertEquals( + lights[1].unique_id, + '{}.b2l3.Light.3'.format(hue_light.HueLight)) + def test_process_lights_api_error(self): """Test the process_lights function when the bridge errors out.""" self.setup_mocks_for_process_lights() @@ -221,9 +303,7 @@ class TestSetup(unittest.TestCase): None) self.assertEquals([], ret) - self.assertEquals( - {}, - self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTS]) + self.assertEquals(self.mock_bridge.lights, {}) def test_process_lights_no_lights(self): """Test the process_lights function when bridge returns no lights.""" @@ -234,9 +314,7 @@ class TestSetup(unittest.TestCase): None) self.assertEquals([], ret) - self.assertEquals( - {}, - self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTS]) + self.assertEquals(self.mock_bridge.lights, {}) @patch('homeassistant.components.light.hue.HueLight') def test_process_lights_some_lights(self, mock_hue_light): @@ -260,9 +338,7 @@ class TestSetup(unittest.TestCase): self.mock_bridge_type, self.mock_bridge.allow_unreachable, self.mock_bridge.allow_in_emulated_hue), ]) - self.assertEquals( - len(self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTS]), - 2) + self.assertEquals(len(self.mock_bridge.lights), 2) @patch('homeassistant.components.light.hue.HueLight') def test_process_lights_new_light(self, mock_hue_light): @@ -274,8 +350,7 @@ class TestSetup(unittest.TestCase): self.setup_mocks_for_process_lights() self.mock_api.get.return_value = { 1: {'state': 'on'}, 2: {'state': 'off'}} - self.hass.data[ - hue_light.DATA_KEY][hue_light.DATA_LIGHTS][1] = MagicMock() + self.mock_bridge.lights = {1: MagicMock()} ret = hue_light.process_lights( self.hass, self.mock_api, self.mock_bridge, self.mock_bridge_type, @@ -288,11 +363,9 @@ class TestSetup(unittest.TestCase): self.mock_bridge_type, self.mock_bridge.allow_unreachable, self.mock_bridge.allow_in_emulated_hue), ]) - self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTS][ - 1].schedule_update_ha_state.assert_called_once_with() - self.assertEquals( - len(self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTS]), - 2) + self.assertEquals(len(self.mock_bridge.lights), 2) + self.mock_bridge.lights[1]\ + .schedule_update_ha_state.assert_called_once_with() def test_process_groups_api_error(self): """Test the process_groups function when the bridge errors out.""" @@ -304,9 +377,7 @@ class TestSetup(unittest.TestCase): None) self.assertEquals([], ret) - self.assertEquals( - {}, - self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS]) + self.assertEquals(self.mock_bridge.lightgroups, {}) def test_process_groups_no_state(self): """Test the process_groups function when bridge returns no status.""" @@ -318,9 +389,7 @@ class TestSetup(unittest.TestCase): None) self.assertEquals([], ret) - self.assertEquals( - {}, - self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS]) + self.assertEquals(self.mock_bridge.lightgroups, {}) @patch('homeassistant.components.light.hue.HueLight') def test_process_groups_some_groups(self, mock_hue_light): @@ -344,10 +413,7 @@ class TestSetup(unittest.TestCase): self.mock_bridge_type, self.mock_bridge.allow_unreachable, self.mock_bridge.allow_in_emulated_hue, True), ]) - self.assertEquals( - len(self.hass.data[ - hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS]), - 2) + self.assertEquals(len(self.mock_bridge.lightgroups), 2) @patch('homeassistant.components.light.hue.HueLight') def test_process_groups_new_group(self, mock_hue_light): @@ -359,8 +425,7 @@ class TestSetup(unittest.TestCase): self.setup_mocks_for_process_groups() self.mock_api.get.return_value = { 1: {'state': 'on'}, 2: {'state': 'off'}} - self.hass.data[ - hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS][1] = MagicMock() + self.mock_bridge.lightgroups = {1: MagicMock()} ret = hue_light.process_groups( self.hass, self.mock_api, self.mock_bridge, self.mock_bridge_type, @@ -373,12 +438,9 @@ class TestSetup(unittest.TestCase): self.mock_bridge_type, self.mock_bridge.allow_unreachable, self.mock_bridge.allow_in_emulated_hue, True), ]) - self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS][ - 1].schedule_update_ha_state.assert_called_once_with() - self.assertEquals( - len(self.hass.data[ - hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS]), - 2) + self.assertEquals(len(self.mock_bridge.lightgroups), 2) + self.mock_bridge.lightgroups[1]\ + .schedule_update_ha_state.assert_called_once_with() class TestHueLight(unittest.TestCase):