diff --git a/.coveragerc b/.coveragerc index c8958d98178dab3e4d425a57840b2b00a547108e..c51f6bc37cbca0b077b8b416fecb776b57c5b4c0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -397,6 +397,7 @@ omit = homeassistant/components/device_tracker/bt_home_hub_5.py homeassistant/components/device_tracker/cisco_ios.py homeassistant/components/device_tracker/ddwrt.py + homeassistant/components/device_tracker/freebox.py homeassistant/components/device_tracker/fritz.py homeassistant/components/device_tracker/google_maps.py homeassistant/components/device_tracker/gpslogger.py diff --git a/homeassistant/components/device_tracker/freebox.py b/homeassistant/components/device_tracker/freebox.py new file mode 100644 index 0000000000000000000000000000000000000000..67957ca99b9f6889ebf329f06c6f3524a94a13a2 --- /dev/null +++ b/homeassistant/components/device_tracker/freebox.py @@ -0,0 +1,120 @@ +""" +Support for device tracking through Freebox routers. + +This tracker keeps track of the devices connected to the configured Freebox. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.freebox/ +""" +import asyncio +import copy +import logging +import socket +from collections import namedtuple +from datetime import timedelta + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.components.device_tracker import ( + PLATFORM_SCHEMA, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) +from homeassistant.const import ( + CONF_HOST, CONF_PORT) + +REQUIREMENTS = ['aiofreepybox==0.0.3'] + +_LOGGER = logging.getLogger(__name__) + +FREEBOX_CONFIG_FILE = 'freebox.conf' + +PLATFORM_SCHEMA = vol.All( + PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT): cv.port + })) + +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) + + +async def async_setup_scanner(hass, config, async_see, discovery_info=None): + """Set up the Freebox device tracker and start the polling.""" + freebox_config = copy.deepcopy(config) + if discovery_info is not None: + freebox_config[CONF_HOST] = discovery_info['properties']['api_domain'] + freebox_config[CONF_PORT] = discovery_info['properties']['https_port'] + _LOGGER.info("Discovered Freebox server: %s:%s", + freebox_config[CONF_HOST], freebox_config[CONF_PORT]) + + scanner = FreeboxDeviceScanner(hass, freebox_config, async_see) + interval = freebox_config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) + await scanner.async_start(hass, interval) + return True + + +Device = namedtuple('Device', ['id', 'name', 'ip']) + + +def _build_device(device_dict): + return Device( + device_dict['l2ident']['id'], + device_dict['primary_name'], + device_dict['l3connectivities'][0]['addr']) + + +class FreeboxDeviceScanner(object): + """This class scans for devices connected to the Freebox.""" + + def __init__(self, hass, config, async_see): + """Initialize the scanner.""" + from aiofreepybox import Freepybox + + self.host = config[CONF_HOST] + self.port = config[CONF_PORT] + self.token_file = hass.config.path(FREEBOX_CONFIG_FILE) + self.async_see = async_see + + # Hardcode the app description to avoid invalidating the authentication + # file at each new version. + # The version can be changed if we want the user to re-authorize HASS + # on her Freebox. + app_desc = { + 'app_id': 'hass', + 'app_name': 'Home Assistant', + 'app_version': '0.65', + 'device_name': socket.gethostname() + } + + api_version = 'v1' # Use the lowest working version. + self.fbx = Freepybox( + app_desc=app_desc, + token_file=self.token_file, + api_version=api_version) + + async def async_start(self, hass, interval): + """Perform a first update and start polling at the given interval.""" + await self.async_update_info() + interval = max(interval, MIN_TIME_BETWEEN_SCANS) + async_track_time_interval(hass, self.async_update_info, interval) + + async def async_update_info(self, now=None): + """Check the Freebox for devices.""" + from aiofreepybox.exceptions import HttpRequestError + + _LOGGER.info('Scanning devices') + + await self.fbx.open(self.host, self.port) + try: + hosts = await self.fbx.lan.get_hosts_list() + except HttpRequestError: + _LOGGER.exception('Failed to scan devices') + else: + active_devices = [_build_device(device) + for device in hosts + if device['active']] + + if active_devices: + await asyncio.wait([self.async_see(mac=d.id, host_name=d.name) + for d in active_devices]) + + await self.fbx.close() diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 69447b81cd427ec8fccca4002bd2de4a42173002..00d4291539b9367663bc943a868e696d20ebefbe 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -84,6 +84,7 @@ SERVICE_HANDLERS = { 'kodi': ('media_player', 'kodi'), 'volumio': ('media_player', 'volumio'), 'nanoleaf_aurora': ('light', 'nanoleaf_aurora'), + 'freebox': ('device_tracker', 'freebox'), } OPTIONAL_SERVICE_HANDLERS = { diff --git a/requirements_all.txt b/requirements_all.txt index 24c2df99ebb7f9a3950b10c91a9e11339cb24da9..8673f4aa22f504ca20008efca8edc2cba90384b2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -81,6 +81,9 @@ aioautomatic==0.6.5 # homeassistant.components.sensor.dnsip aiodns==1.1.1 +# homeassistant.components.device_tracker.freebox +aiofreepybox==0.0.3 + # homeassistant.components.emulated_hue # homeassistant.components.http aiohttp_cors==0.7.0