diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index d412a7af91f..d95224c9469 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/emulated_hue/ """ import asyncio +import json import logging import voluptuous as vol @@ -24,6 +25,8 @@ DOMAIN = 'emulated_hue' _LOGGER = logging.getLogger(__name__) +NUMBERS_FILE = 'emulated_hue_ids.json' + CONF_HOST_IP = 'host_ip' CONF_LISTEN_PORT = 'listen_port' CONF_UPNP_BIND_MULTICAST = 'upnp_bind_multicast' @@ -63,7 +66,7 @@ ATTR_EMULATED_HUE = 'emulated_hue' def setup(hass, yaml_config): """Activate the emulated_hue component.""" - config = Config(yaml_config.get(DOMAIN, {})) + config = Config(hass, yaml_config.get(DOMAIN, {})) server = HomeAssistantWSGI( hass, @@ -112,10 +115,11 @@ def setup(hass, yaml_config): class Config(object): """Holds configuration variables for the emulated hue bridge.""" - def __init__(self, conf): + def __init__(self, hass, conf): """Initialize the instance.""" + self.hass = hass self.type = conf.get(CONF_TYPE) - self.numbers = {} + self.numbers = None self.cached_states = {} # Get the IP address that will be passed to the Echo during discovery @@ -165,6 +169,9 @@ class Config(object): if self.type == TYPE_ALEXA: return entity_id + if self.numbers is None: + self.numbers = self._load_numbers_json() + # Google Home for number, ent_id in self.numbers.items(): if entity_id == ent_id: @@ -172,6 +179,7 @@ class Config(object): number = str(len(self.numbers) + 1) self.numbers[number] = entity_id + self._save_numbers_json() return number def number_to_entity_id(self, number): @@ -179,6 +187,9 @@ class Config(object): if self.type == TYPE_ALEXA: return number + if self.numbers is None: + self.numbers = self._load_numbers_json() + # Google Home assert isinstance(number, str) return self.numbers.get(number) @@ -205,3 +216,26 @@ class Config(object): domain_exposed_by_default and explicit_expose is not False return is_default_exposed or explicit_expose + + def _load_numbers_json(self): + """Helper method to load numbers json.""" + try: + with open(self.hass.config.path(NUMBERS_FILE), + encoding='utf-8') as fil: + return json.loads(fil.read()) + except (OSError, ValueError) as err: + # OSError if file not found or unaccessible/no permissions + # ValueError if could not parse JSON + if not isinstance(err, FileNotFoundError): + _LOGGER.warning('Failed to open %s: %s', NUMBERS_FILE, err) + return {} + + def _save_numbers_json(self): + """Helper method to save numbers json.""" + try: + with open(self.hass.config.path(NUMBERS_FILE), 'w', + encoding='utf-8') as fil: + fil.write(json.dumps(self.numbers)) + except OSError as err: + # OSError if file write permissions + _LOGGER.warning('Failed to write %s: %s', NUMBERS_FILE, err) diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 0b36b835cd5..7c73e933fd3 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -106,7 +106,7 @@ def hass_hue(loop, hass): def hue_client(loop, hass_hue, test_client): """Create web client for emulated hue api.""" web_app = mock_http_component_app(hass_hue) - config = Config({'type': 'alexa'}) + config = Config(None, {'type': 'alexa'}) HueUsernameView().register(web_app.router) HueAllLightsStateView(config).register(web_app.router) diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 2ee7c385d8d..8c0a6dc4f60 100755 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -1,31 +1,44 @@ """Test the Emulated Hue component.""" -from unittest.mock import patch +import json + +from unittest.mock import patch, Mock, mock_open from homeassistant.components.emulated_hue import Config, _LOGGER def test_config_google_home_entity_id_to_number(): """Test config adheres to the type.""" - conf = Config({ + conf = Config(Mock(), { 'type': 'google_home' }) - number = conf.entity_id_to_number('light.test') - assert number == '1' + mop = mock_open(read_data=json.dumps({'1': 'light.test2'})) + handle = mop() - number = conf.entity_id_to_number('light.test') - assert number == '1' + with patch('homeassistant.components.emulated_hue.open', mop, create=True): + number = conf.entity_id_to_number('light.test') + assert number == '2' + assert handle.write.call_count == 1 + assert json.loads(handle.write.mock_calls[0][1][0]) == { + '1': 'light.test2', + '2': 'light.test', + } - number = conf.entity_id_to_number('light.test2') - assert number == '2' + number = conf.entity_id_to_number('light.test') + assert number == '2' + assert handle.write.call_count == 1 - entity_id = conf.number_to_entity_id('1') - assert entity_id == 'light.test' + number = conf.entity_id_to_number('light.test2') + assert number == '1' + assert handle.write.call_count == 1 + + entity_id = conf.number_to_entity_id('1') + assert entity_id == 'light.test2' def test_config_alexa_entity_id_to_number(): """Test config adheres to the type.""" - conf = Config({ + conf = Config(None, { 'type': 'alexa' }) @@ -45,7 +58,7 @@ def test_config_alexa_entity_id_to_number(): def test_warning_config_google_home_listen_port(): """Test we warn when non-default port is used for Google Home.""" with patch.object(_LOGGER, 'warning') as mock_warn: - Config({ + Config(None, { 'type': 'google_home', 'host_ip': '123.123.123.123', 'listen_port': 8300