Add Freebox component with sensors and device tracker (#18472)

* Add freebox component with sensor and device tracker

* script/gen_requirements_all passed and pylint fixes

* Fix docstring in wrong place

* Fix indentation

* Lint fixes

* More lint fixes

* Lint fixes again

* Pylint fixes

* Bump aiopyfreebox version

* Close freebox connection on HA Stop

* Fixed docstring

* Fixed ident

* Lint fixes

* Fix cloing session when HA stop

* Fix URL

* Fix URL

* Fix double look up in discovery datas

* Fix logging level

* Fix get_device_name

Thx for the hint Martin

* Fix async_update_info

* Update requirements_all.txt
This commit is contained in:
SNoof85 2018-12-28 00:26:09 +01:00 committed by Martin Hjelmare
parent fe14be53e3
commit 699a38de52
6 changed files with 223 additions and 92 deletions

View file

@ -112,6 +112,9 @@ omit =
homeassistant/components/evohome.py
homeassistant/components/*/evohome.py
homeassistant/components/freebox.py
homeassistant/components/*/freebox.py
homeassistant/components/fritzbox.py
homeassistant/components/*/fritzbox.py
@ -508,7 +511,6 @@ omit =
homeassistant/components/device_tracker/bt_smarthub.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/googlehome.py

View file

@ -1,56 +1,25 @@
"""
Support for device tracking through Freebox routers.
Support for Freebox devices (Freebox v6 and Freebox mini 4K).
This tracker keeps track of the devices connected to the configured Freebox.
For more details about this platform, please refer to the documentation at
For more details about this component, 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 logging
import voluptuous as vol
from homeassistant.components.device_tracker import DeviceScanner
from homeassistant.components.freebox import DATA_FREEBOX
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.5']
DEPENDENCIES = ['freebox']
_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
async def async_get_scanner(hass, config):
"""Validate the configuration and return a Freebox scanner."""
scanner = FreeboxDeviceScanner(hass.data[DATA_FREEBOX])
await scanner.async_connect()
return scanner if scanner.success_init else None
Device = namedtuple('Device', ['id', 'name', 'ip'])
@ -62,59 +31,41 @@ def _build_device(device_dict):
device_dict['l3connectivities'][0]['addr'])
class FreeboxDeviceScanner:
"""This class scans for devices connected to the Freebox."""
class FreeboxDeviceScanner(DeviceScanner):
"""Queries the Freebox device."""
def __init__(self, hass, config, async_see):
def __init__(self, fbx):
"""Initialize the scanner."""
from aiofreepybox import Freepybox
self.last_results = {}
self.success_init = False
self.connection = fbx
self.host = config[CONF_HOST]
self.port = config[CONF_PORT]
self.token_file = hass.config.path(FREEBOX_CONFIG_FILE)
self.async_see = async_see
async def async_connect(self):
"""Initialize connection to the router."""
# Test the router is accessible.
data = await self.connection.lan.get_hosts_list()
self.success_init = data is not None
# 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."""
async def async_scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
await self.async_update_info()
interval = max(interval, MIN_TIME_BETWEEN_SCANS)
async_track_time_interval(hass, self.async_update_info, interval)
return [device.id for device in self.last_results]
async def async_update_info(self, now=None):
"""Check the Freebox for devices."""
from aiofreepybox.exceptions import HttpRequestError
async def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
name = next((
result.name for result in self.last_results
if result.id == device), None)
return name
_LOGGER.info('Scanning devices')
async def async_update_info(self):
"""Ensure the information from the Freebox router is up to date."""
_LOGGER.debug('Checking 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']]
hosts = await self.connection.lan.get_hosts_list()
if active_devices:
await asyncio.wait([self.async_see(mac=d.id, host_name=d.name)
for d in active_devices])
last_results = [_build_device(device)
for device in hosts
if device['active']]
await self.fbx.close()
self.last_results = last_results

View file

@ -44,6 +44,7 @@ SERVICE_SABNZBD = 'sabnzbd'
SERVICE_SAMSUNG_PRINTER = 'samsung_printer'
SERVICE_HOMEKIT = 'homekit'
SERVICE_OCTOPRINT = 'octoprint'
SERVICE_FREEBOX = 'freebox'
SERVICE_IGD = 'igd'
SERVICE_DLNA_DMR = 'dlna_dmr'
@ -71,6 +72,7 @@ SERVICE_HANDLERS = {
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
SERVICE_KONNECTED: ('konnected', None),
SERVICE_OCTOPRINT: ('octoprint', None),
SERVICE_FREEBOX: ('freebox', None),
'panasonic_viera': ('media_player', 'panasonic_viera'),
'plex_mediaserver': ('media_player', 'plex'),
'roku': ('media_player', 'roku'),
@ -90,7 +92,6 @@ SERVICE_HANDLERS = {
'volumio': ('media_player', 'volumio'),
'lg_smart_device': ('media_player', 'lg_soundbar'),
'nanoleaf_aurora': ('light', 'nanoleaf_aurora'),
'freebox': ('device_tracker', 'freebox'),
}
OPTIONAL_SERVICE_HANDLERS = {

View file

@ -0,0 +1,91 @@
"""
Support for Freebox devices (Freebox v6 and Freebox mini 4K).
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/freebox/
"""
import logging
import socket
import voluptuous as vol
from homeassistant.components.discovery import SERVICE_FREEBOX
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.discovery import async_load_platform
REQUIREMENTS = ['aiofreepybox==0.0.6']
_LOGGER = logging.getLogger(__name__)
DOMAIN = "freebox"
DATA_FREEBOX = DOMAIN
FREEBOX_CONFIG_FILE = 'freebox.conf'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT): cv.port
})
}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass, config):
"""Set up the Freebox component."""
conf = config.get(DOMAIN)
async def discovery_dispatch(service, discovery_info):
if conf is None:
host = discovery_info.get('properties', {}).get('api_domain')
port = discovery_info.get('properties', {}).get('https_port')
_LOGGER.info("Discovered Freebox server: %s:%s", host, port)
await async_setup_freebox(hass, config, host, port)
discovery.async_listen(hass, SERVICE_FREEBOX, discovery_dispatch)
if conf is not None:
host = conf.get(CONF_HOST)
port = conf.get(CONF_PORT)
await async_setup_freebox(hass, config, host, port)
return True
async def async_setup_freebox(hass, config, host, port):
"""Start up the Freebox component platforms."""
from aiofreepybox import Freepybox
from aiofreepybox.exceptions import HttpRequestError
app_desc = {
'app_id': 'hass',
'app_name': 'Home Assistant',
'app_version': '0.65',
'device_name': socket.gethostname()
}
token_file = hass.config.path(FREEBOX_CONFIG_FILE)
api_version = 'v1'
fbx = Freepybox(
app_desc=app_desc,
token_file=token_file,
api_version=api_version)
try:
await fbx.open(host, port)
except HttpRequestError:
_LOGGER.exception('Failed to connect to Freebox')
else:
hass.data[DATA_FREEBOX] = fbx
hass.async_create_task(async_load_platform(
hass, 'sensor', DOMAIN, {}, config))
hass.async_create_task(async_load_platform(
hass, 'device_tracker', DOMAIN, {}, config))
async def close_fbx(event):
"""Close Freebox connection on HA Stop."""
await fbx.close()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_fbx)

View file

@ -0,0 +1,86 @@
"""
Support for Freebox devices (Freebox v6 and Freebox mini 4K).
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sensor.freebox/
"""
import logging
from homeassistant.components.freebox import DATA_FREEBOX
from homeassistant.helpers.entity import Entity
DEPENDENCIES = ['freebox']
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass, config, add_entities, discovery_info=None):
"""Set up the sensors."""
fbx = hass.data[DATA_FREEBOX]
add_entities([
FbxRXSensor(fbx),
FbxTXSensor(fbx)
])
class FbxSensor(Entity):
"""Representation of a freebox sensor."""
_name = 'generic'
def __init__(self, fbx):
"""Initialize the sensor."""
self._fbx = fbx
self._state = None
self._datas = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self._state
async def async_update(self):
"""Fetch status from freebox."""
self._datas = await self._fbx.connection.get_status()
class FbxRXSensor(FbxSensor):
"""Update the Freebox RxSensor."""
_name = 'Freebox download speed'
_unit = 'KB/s'
@property
def unit_of_measurement(self):
"""Define the unit."""
return self._unit
async def async_update(self):
"""Get the value from fetched datas."""
await super().async_update()
if self._datas is not None:
self._state = round(self._datas['rate_down'] / 1000, 2)
class FbxTXSensor(FbxSensor):
"""Update the Freebox TxSensor."""
_name = 'Freebox upload speed'
_unit = 'KB/s'
@property
def unit_of_measurement(self):
"""Define the unit."""
return self._unit
async def async_update(self):
"""Get the value from fetched datas."""
await super().async_update()
if self._datas is not None:
self._state = round(self._datas['rate_up'] / 1000, 2)

View file

@ -98,8 +98,8 @@ aiodns==1.1.1
# homeassistant.components.esphome
aioesphomeapi==1.3.0
# homeassistant.components.device_tracker.freebox
aiofreepybox==0.0.5
# homeassistant.components.freebox
aiofreepybox==0.0.6
# homeassistant.components.camera.yi
aioftp==0.12.0