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:
parent
fe14be53e3
commit
699a38de52
6 changed files with 223 additions and 92 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
91
homeassistant/components/freebox.py
Normal file
91
homeassistant/components/freebox.py
Normal 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)
|
86
homeassistant/components/sensor/freebox.py
Normal file
86
homeassistant/components/sensor/freebox.py
Normal 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)
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue