diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 236fde6fb3f..a05b57cff33 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -46,7 +46,6 @@ CONF_TRACK_NEW = 'track_new_devices' DEFAULT_TRACK_NEW = True CONF_CONSIDER_HOME = 'consider_home' -DEFAULT_CONSIDER_HOME = 180 # seconds CONF_SCAN_INTERVAL = 'interval_seconds' DEFAULT_SCAN_INTERVAL = 12 @@ -70,8 +69,10 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ _CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.All(cv.ensure_list, [ vol.Schema({ - vol.Optional(CONF_TRACK_NEW): cv.boolean, - vol.Optional(CONF_CONSIDER_HOME): cv.positive_int # seconds + vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean, + vol.Optional( + CONF_CONSIDER_HOME, default=timedelta(seconds=180)): vol.All( + cv.time_period, cv.positive_timedelta) }, extra=vol.ALLOW_EXTRA)])}, extra=vol.ALLOW_EXTRA) DISCOVERY_PLATFORMS = { @@ -118,9 +119,8 @@ def setup(hass: HomeAssistantType, config: ConfigType): return False else: conf = conf[0] if len(conf) > 0 else {} - consider_home = timedelta( - seconds=conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME)) - track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) + consider_home = conf[CONF_CONSIDER_HOME] + track_new = conf[CONF_TRACK_NEW] devices = load_config(yaml_path, hass, consider_home) @@ -282,7 +282,7 @@ class Device(Entity): def __init__(self, hass: HomeAssistantType, consider_home: timedelta, track: bool, dev_id: str, mac: str, name: str=None, picture: str=None, gravatar: str=None, - away_hide: bool=False) -> None: + hide_if_away: bool=False) -> None: """Initialize a device.""" self.hass = hass self.entity_id = ENTITY_ID_FORMAT.format(dev_id) @@ -307,7 +307,7 @@ class Device(Entity): else: self.config_picture = picture - self.away_hide = away_hide + self.away_hide = hide_if_away @property def name(self): @@ -398,15 +398,29 @@ class Device(Entity): def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta): """Load devices from YAML configuration file.""" + dev_schema = vol.Schema({ + vol.Required('name'): cv.string, + vol.Optional('track', default=False): cv.boolean, + vol.Optional('mac', default=None): vol.Any(None, vol.All(cv.string, + vol.Upper)), + vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean, + vol.Optional('gravatar', default=None): vol.Any(None, cv.string), + vol.Optional('picture', default=None): vol.Any(None, cv.string), + vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All( + cv.time_period, cv.positive_timedelta) + }) try: - return [ - Device(hass, consider_home, device.get('track', False), - str(dev_id).lower(), None if device.get('mac') is None - else str(device.get('mac')).upper(), - device.get('name'), device.get('picture'), - device.get('gravatar'), - device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE)) - for dev_id, device in load_yaml_config_file(path).items()] + result = [] + devices = load_yaml_config_file(path) + for dev_id, device in devices.items(): + try: + device = dev_schema(device) + device['dev_id'] = cv.slug(dev_id) + except vol.Invalid as exp: + log_exception(exp, dev_id, devices) + else: + result.append(Device(hass, **device)) + return result except (HomeAssistantError, FileNotFoundError): # When YAML file could not be loaded/did not contain a dict return [] diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 0981c20f407..7236debbe88 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -167,7 +167,16 @@ def time_period_str(value: str) -> timedelta: return offset -time_period = vol.Any(time_period_str, timedelta, time_period_dict) +def time_period_seconds(value: Union[int, str]) -> timedelta: + """Validate and transform seconds to a time offset.""" + try: + return timedelta(seconds=int(value)) + except (ValueError, TypeError): + raise vol.Invalid('Expected seconds, got {}'.format(value)) + + +time_period = vol.Any(time_period_str, time_period_seconds, timedelta, + time_period_dict) def match_all(value): diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 9490938a646..f195712285a 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -59,15 +59,13 @@ class TestComponentsDeviceTracker(unittest.TestCase): def test_reading_broken_yaml_config(self): # pylint: disable=no-self-use """Test when known devices contains invalid data.""" files = {'empty.yaml': '', - 'bad.yaml': '100', - 'ok.yaml': 'my_device:\n name: Device'} + 'nodict.yaml': '100', + 'allok.yaml': 'my_device:\n name: Device'} + args = {'hass': self.hass, 'consider_home': timedelta(seconds=60)} with patch_yaml_files(files): - # File is empty - assert device_tracker.load_config('empty.yaml', None, False) == [] - # File contains a non-dict format - assert device_tracker.load_config('bad.yaml', None, False) == [] - # A file that works fine - assert len(device_tracker.load_config('ok.yaml', None, False)) == 1 + assert device_tracker.load_config('empty.yaml', **args) == [] + assert device_tracker.load_config('nodict.yaml', **args) == [] + assert len(device_tracker.load_config('allok.yaml', **args)) == 1 def test_reading_yaml_config(self): """Test the rendering of the YAML configuration.""" @@ -75,7 +73,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): device = device_tracker.Device( self.hass, timedelta(seconds=180), True, dev_id, 'AB:CD:EF:GH:IJ', 'Test name', picture='http://test.picture', - away_hide=True) + hide_if_away=True) device_tracker.update_config(self.yaml_devices, dev_id, device) self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, TEST_PLATFORM)) @@ -211,7 +209,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): device = device_tracker.Device( self.hass, timedelta(seconds=180), True, dev_id, None, - friendly_name, picture, away_hide=True) + friendly_name, picture, hide_if_away=True) device_tracker.update_config(self.yaml_devices, dev_id, device) self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, @@ -228,7 +226,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id) device = device_tracker.Device( self.hass, timedelta(seconds=180), True, dev_id, None, - away_hide=True) + hide_if_away=True) device_tracker.update_config(self.yaml_devices, dev_id, device) scanner = get_component('device_tracker.test').SCANNER @@ -246,7 +244,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id) device = device_tracker.Device( self.hass, timedelta(seconds=180), True, dev_id, None, - away_hide=True) + hide_if_away=True) device_tracker.update_config(self.yaml_devices, dev_id, device) scanner = get_component('device_tracker.test').SCANNER diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 76143755220..9f929244888 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -200,17 +200,18 @@ def test_time_period(): schema = vol.Schema(cv.time_period) for value in ( - None, '', 1234, 'hello:world', '12:', '12:34:56:78', + None, '', 'hello:world', '12:', '12:34:56:78', {}, {'wrong_key': -10} ): with pytest.raises(vol.MultipleInvalid): schema(value) for value in ( - '8:20', '23:59', '-8:20', '-23:59:59', '-48:00', {'minutes': 5} + '8:20', '23:59', '-8:20', '-23:59:59', '-48:00', {'minutes': 5}, 1, '5' ): schema(value) + assert timedelta(seconds=180) == schema('180') assert timedelta(hours=23, minutes=59) == schema('23:59') assert -1 * timedelta(hours=1, minutes=15) == schema('-1:15')