mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
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
@ -112,6 +112,9 @@ omit =
|
|||||||
homeassistant/components/evohome.py
|
homeassistant/components/evohome.py
|
||||||
homeassistant/components/*/evohome.py
|
homeassistant/components/*/evohome.py
|
||||||
|
|
||||||
|
homeassistant/components/freebox.py
|
||||||
|
homeassistant/components/*/freebox.py
|
||||||
|
|
||||||
homeassistant/components/fritzbox.py
|
homeassistant/components/fritzbox.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/bt_smarthub.py
|
||||||
homeassistant/components/device_tracker/cisco_ios.py
|
homeassistant/components/device_tracker/cisco_ios.py
|
||||||
homeassistant/components/device_tracker/ddwrt.py
|
homeassistant/components/device_tracker/ddwrt.py
|
||||||
homeassistant/components/device_tracker/freebox.py
|
|
||||||
homeassistant/components/device_tracker/fritz.py
|
homeassistant/components/device_tracker/fritz.py
|
||||||
homeassistant/components/device_tracker/google_maps.py
|
homeassistant/components/device_tracker/google_maps.py
|
||||||
homeassistant/components/device_tracker/googlehome.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 component, please refer to the documentation at
|
||||||
|
|
||||||
For more details about this platform, please refer to the documentation at
|
|
||||||
https://home-assistant.io/components/device_tracker.freebox/
|
https://home-assistant.io/components/device_tracker.freebox/
|
||||||
"""
|
"""
|
||||||
import asyncio
|
|
||||||
import copy
|
|
||||||
import logging
|
|
||||||
import socket
|
|
||||||
from collections import namedtuple
|
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
|
DEPENDENCIES = ['freebox']
|
||||||
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']
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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'])
|
Device = namedtuple('Device', ['id', 'name', 'ip'])
|
||||||
|
|
||||||
@ -62,59 +31,41 @@ def _build_device(device_dict):
|
|||||||
device_dict['l3connectivities'][0]['addr'])
|
device_dict['l3connectivities'][0]['addr'])
|
||||||
|
|
||||||
|
|
||||||
class FreeboxDeviceScanner:
|
class FreeboxDeviceScanner(DeviceScanner):
|
||||||
"""This class scans for devices connected to the Freebox."""
|
"""Queries the Freebox device."""
|
||||||
|
|
||||||
def __init__(self, hass, config, async_see):
|
def __init__(self, fbx):
|
||||||
"""Initialize the scanner."""
|
"""Initialize the scanner."""
|
||||||
from aiofreepybox import Freepybox
|
self.last_results = {}
|
||||||
|
self.success_init = False
|
||||||
|
self.connection = fbx
|
||||||
|
|
||||||
self.host = config[CONF_HOST]
|
async def async_connect(self):
|
||||||
self.port = config[CONF_PORT]
|
"""Initialize connection to the router."""
|
||||||
self.token_file = hass.config.path(FREEBOX_CONFIG_FILE)
|
# Test the router is accessible.
|
||||||
self.async_see = async_see
|
data = await self.connection.lan.get_hosts_list()
|
||||||
|
self.success_init = data is not None
|
||||||
|
|
||||||
# Hardcode the app description to avoid invalidating the authentication
|
async def async_scan_devices(self):
|
||||||
# file at each new version.
|
"""Scan for new devices and return a list with found device IDs."""
|
||||||
# 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()
|
await self.async_update_info()
|
||||||
interval = max(interval, MIN_TIME_BETWEEN_SCANS)
|
return [device.id for device in self.last_results]
|
||||||
async_track_time_interval(hass, self.async_update_info, interval)
|
|
||||||
|
|
||||||
async def async_update_info(self, now=None):
|
async def get_device_name(self, device):
|
||||||
"""Check the Freebox for devices."""
|
"""Return the name of the given device or None if we don't know."""
|
||||||
from aiofreepybox.exceptions import HttpRequestError
|
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)
|
hosts = await self.connection.lan.get_hosts_list()
|
||||||
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:
|
last_results = [_build_device(device)
|
||||||
await asyncio.wait([self.async_see(mac=d.id, host_name=d.name)
|
for device in hosts
|
||||||
for d in active_devices])
|
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_SAMSUNG_PRINTER = 'samsung_printer'
|
||||||
SERVICE_HOMEKIT = 'homekit'
|
SERVICE_HOMEKIT = 'homekit'
|
||||||
SERVICE_OCTOPRINT = 'octoprint'
|
SERVICE_OCTOPRINT = 'octoprint'
|
||||||
|
SERVICE_FREEBOX = 'freebox'
|
||||||
SERVICE_IGD = 'igd'
|
SERVICE_IGD = 'igd'
|
||||||
SERVICE_DLNA_DMR = 'dlna_dmr'
|
SERVICE_DLNA_DMR = 'dlna_dmr'
|
||||||
|
|
||||||
@ -71,6 +72,7 @@ SERVICE_HANDLERS = {
|
|||||||
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
|
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
|
||||||
SERVICE_KONNECTED: ('konnected', None),
|
SERVICE_KONNECTED: ('konnected', None),
|
||||||
SERVICE_OCTOPRINT: ('octoprint', None),
|
SERVICE_OCTOPRINT: ('octoprint', None),
|
||||||
|
SERVICE_FREEBOX: ('freebox', None),
|
||||||
'panasonic_viera': ('media_player', 'panasonic_viera'),
|
'panasonic_viera': ('media_player', 'panasonic_viera'),
|
||||||
'plex_mediaserver': ('media_player', 'plex'),
|
'plex_mediaserver': ('media_player', 'plex'),
|
||||||
'roku': ('media_player', 'roku'),
|
'roku': ('media_player', 'roku'),
|
||||||
@ -90,7 +92,6 @@ SERVICE_HANDLERS = {
|
|||||||
'volumio': ('media_player', 'volumio'),
|
'volumio': ('media_player', 'volumio'),
|
||||||
'lg_smart_device': ('media_player', 'lg_soundbar'),
|
'lg_smart_device': ('media_player', 'lg_soundbar'),
|
||||||
'nanoleaf_aurora': ('light', 'nanoleaf_aurora'),
|
'nanoleaf_aurora': ('light', 'nanoleaf_aurora'),
|
||||||
'freebox': ('device_tracker', 'freebox'),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OPTIONAL_SERVICE_HANDLERS = {
|
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
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==1.3.0
|
aioesphomeapi==1.3.0
|
||||||
|
|
||||||
# homeassistant.components.device_tracker.freebox
|
# homeassistant.components.freebox
|
||||||
aiofreepybox==0.0.5
|
aiofreepybox==0.0.6
|
||||||
|
|
||||||
# homeassistant.components.camera.yi
|
# homeassistant.components.camera.yi
|
||||||
aioftp==0.12.0
|
aioftp==0.12.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user