diff --git a/homeassistant/config.py b/homeassistant/config.py index 9e3f1d80663..88abf2ac791 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -50,6 +50,13 @@ FILE_MIGRATION = ( ('ios.conf', '.ios.conf'), ) +CORE_STORAGE_KEY = 'homeassistant.core_config' +CORE_STORAGE_VERSION = 1 + +SOURCE_DISCOVERED = 'discovered' +SOURCE_STORAGE = 'storage' +SOURCE_YAML = 'yaml' + DEFAULT_CORE_CONFIG = ( # Tuples (attribute, default, auto detect property, description) (CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is ' @@ -473,6 +480,42 @@ def _format_config_error(ex: vol.Invalid, domain: str, config: Dict) -> str: return message +def _set_time_zone(hass: HomeAssistant, time_zone_str: Optional[str]) -> None: + """Help to set the time zone.""" + if time_zone_str is None: + return + + time_zone = date_util.get_time_zone(time_zone_str) + + if time_zone: + hass.config.time_zone = time_zone + date_util.set_default_time_zone(time_zone) + else: + _LOGGER.error("Received invalid time zone %s", time_zone_str) + + +async def async_load_ha_core_config(hass: HomeAssistant) -> None: + """Store [homeassistant] core config.""" + store = hass.helpers.storage.Store(CORE_STORAGE_VERSION, CORE_STORAGE_KEY, + private=True) + data = await store.async_load() + if not data: + return + + hac = hass.config + hac.config_source = SOURCE_STORAGE + hac.latitude = data['latitude'] + hac.longitude = data['longitude'] + hac.elevation = data['elevation'] + unit_system = data['unit_system'] + if unit_system == CONF_UNIT_SYSTEM_IMPERIAL: + hac.units = IMPERIAL_SYSTEM + else: + hac.units = METRIC_SYSTEM + hac.location_name = data['location_name'] + _set_time_zone(hass, data['time_zone']) + + async def async_process_ha_core_config( hass: HomeAssistant, config: Dict, api_password: Optional[str] = None, @@ -511,20 +554,14 @@ async def async_process_ha_core_config( auth_conf, mfa_conf)) + await async_load_ha_core_config(hass) + hac = hass.config - def set_time_zone(time_zone_str: Optional[str]) -> None: - """Help to set the time zone.""" - if time_zone_str is None: - return - - time_zone = date_util.get_time_zone(time_zone_str) - - if time_zone: - hac.time_zone = time_zone - date_util.set_default_time_zone(time_zone) - else: - _LOGGER.error("Received invalid time zone %s", time_zone_str) + if any([k in config for k in [ + CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_ELEVATION, + CONF_TIME_ZONE, CONF_UNIT_SYSTEM]]): + hac.config_source = SOURCE_YAML for key, attr in ((CONF_LATITUDE, 'latitude'), (CONF_LONGITUDE, 'longitude'), @@ -533,7 +570,7 @@ async def async_process_ha_core_config( if key in config: setattr(hac, attr, config[key]) - set_time_zone(config.get(CONF_TIME_ZONE)) + _set_time_zone(hass, config.get(CONF_TIME_ZONE)) # Init whitelist external dir hac.whitelist_external_dirs = {hass.config.path('www')} @@ -591,6 +628,7 @@ async def async_process_ha_core_config( # If we miss some of the needed values, auto detect them if None in (hac.latitude, hac.longitude, hac.units, hac.time_zone): + hac.config_source = SOURCE_DISCOVERED info = await loc_util.async_detect_location_info( hass.helpers.aiohttp_client.async_get_clientsession() ) @@ -613,7 +651,7 @@ async def async_process_ha_core_config( discovered.append(('name', info.city)) if hac.time_zone is None: - set_time_zone(info.time_zone) + _set_time_zone(hass, info.time_zone) discovered.append(('time_zone', info.time_zone)) if hac.elevation is None and hac.latitude is not None and \ @@ -630,6 +668,24 @@ async def async_process_ha_core_config( ", ".join('{}: {}'.format(key, val) for key, val in discovered)) +async def async_store_ha_core_config(hass: HomeAssistant) -> None: + """Store [homeassistant] core config.""" + config = hass.config.as_dict() + + data = { + 'latitude': config['latitude'], + 'longitude': config['longitude'], + 'elevation': config['elevation'], + 'unit_system': hass.config.units.name, + 'location_name': config['location_name'], + 'time_zone': config['time_zone'], + } + + store = hass.helpers.storage.Store(CORE_STORAGE_VERSION, CORE_STORAGE_KEY, + private=True) + await store.async_save(data) + + def _log_pkg_error( package: str, component: str, config: Dict, message: str) -> None: """Log an error while merging packages.""" diff --git a/homeassistant/core.py b/homeassistant/core.py index c127e100f11..a02c1b687ab 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1177,6 +1177,8 @@ class Config: self.time_zone = None # type: Optional[datetime.tzinfo] self.units = METRIC_SYSTEM # type: UnitSystem + self.config_source = None # type: Optional[str] + # If True, pip install is skipped for requirements on startup self.skip_pip = False # type: bool @@ -1251,7 +1253,8 @@ class Config: 'components': self.components, 'config_dir': self.config_dir, 'whitelist_external_dirs': self.whitelist_external_dirs, - 'version': __version__ + 'version': __version__, + 'config_source': self.config_source } diff --git a/tests/test_config.py b/tests/test_config.py index 9090e229248..c081d97ed7c 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -414,6 +414,64 @@ def test_migrate_no_file_on_upgrade(mock_os, mock_shutil, hass): assert mock_os.rename.call_count == 0 +async def test_loading_configuration_from_storage(hass, hass_storage): + """Test loading core config onto hass object.""" + hass_storage["homeassistant.core_config"] = { + 'data': { + 'elevation': 10, + 'latitude': 55, + 'location_name': 'Home', + 'longitude': 13, + 'time_zone': 'Europe/Copenhagen', + 'unit_system': 'metric' + }, + 'key': 'homeassistant.core_config', + 'version': 1 + } + await config_util.async_process_ha_core_config( + hass, {'whitelist_external_dirs': '/tmp'}) + + assert hass.config.latitude == 55 + assert hass.config.longitude == 13 + assert hass.config.elevation == 10 + assert hass.config.location_name == 'Home' + assert hass.config.units.name == CONF_UNIT_SYSTEM_METRIC + assert hass.config.time_zone.zone == 'Europe/Copenhagen' + assert len(hass.config.whitelist_external_dirs) == 2 + assert '/tmp' in hass.config.whitelist_external_dirs + assert hass.config.config_source == config_util.SOURCE_STORAGE + + +async def test_override_stored_configuration(hass, hass_storage): + """Test loading core and YAML config onto hass object.""" + hass_storage["homeassistant.core_config"] = { + 'data': { + 'elevation': 10, + 'latitude': 55, + 'location_name': 'Home', + 'longitude': 13, + 'time_zone': 'Europe/Copenhagen', + 'unit_system': 'metric' + }, + 'key': 'homeassistant.core_config', + 'version': 1 + } + await config_util.async_process_ha_core_config(hass, { + 'latitude': 60, + 'whitelist_external_dirs': '/tmp', + }) + + assert hass.config.latitude == 60 + assert hass.config.longitude == 13 + assert hass.config.elevation == 10 + assert hass.config.location_name == 'Home' + assert hass.config.units.name == CONF_UNIT_SYSTEM_METRIC + assert hass.config.time_zone.zone == 'Europe/Copenhagen' + assert len(hass.config.whitelist_external_dirs) == 2 + assert '/tmp' in hass.config.whitelist_external_dirs + assert hass.config.config_source == config_util.SOURCE_YAML + + async def test_loading_configuration(hass): """Test loading core config onto hass object.""" hass.config = mock.Mock() @@ -436,6 +494,7 @@ async def test_loading_configuration(hass): assert hass.config.time_zone.zone == 'America/New_York' assert len(hass.config.whitelist_external_dirs) == 2 assert '/tmp' in hass.config.whitelist_external_dirs + assert hass.config.config_source == config_util.SOURCE_YAML async def test_loading_configuration_temperature_unit(hass): @@ -457,6 +516,7 @@ async def test_loading_configuration_temperature_unit(hass): assert hass.config.location_name == 'Huis' assert hass.config.units.name == CONF_UNIT_SYSTEM_METRIC assert hass.config.time_zone.zone == 'America/New_York' + assert hass.config.config_source == config_util.SOURCE_YAML async def test_loading_configuration_from_packages(hass): @@ -515,6 +575,7 @@ async def test_discovering_configuration(mock_detect, mock_elevation, hass): assert hass.config.units.name == CONF_UNIT_SYSTEM_METRIC assert hass.config.units.is_metric assert hass.config.time_zone.zone == 'America/Los_Angeles' + assert hass.config.config_source == config_util.SOURCE_DISCOVERED @asynctest.mock.patch('homeassistant.util.location.async_detect_location_info', @@ -539,6 +600,7 @@ async def test_discovering_configuration_auto_detect_fails(mock_detect, assert hass.config.time_zone == blankConfig.time_zone assert len(hass.config.whitelist_external_dirs) == 1 assert "/test/config/www" in hass.config.whitelist_external_dirs + assert hass.config.config_source == config_util.SOURCE_DISCOVERED @asynctest.mock.patch( diff --git a/tests/test_core.py b/tests/test_core.py index 1e709ed3a8a..afbbe3e33b2 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -900,6 +900,7 @@ class TestConfig(unittest.TestCase): 'config_dir': '/tmp/ha-config', 'whitelist_external_dirs': set(), 'version': __version__, + 'config_source': None, } assert expected == self.config.as_dict()