217 lines
7.0 KiB
Python
217 lines
7.0 KiB
Python
"""Represent the Freebox router and its devices and sensors."""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timedelta
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from freebox_api import Freepybox
|
|
from freebox_api.api.wifi import Wifi
|
|
from freebox_api.exceptions import HttpRequestError
|
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
|
from homeassistant.exceptions import ConfigEntryNotReady
|
|
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
|
from homeassistant.helpers.event import async_track_time_interval
|
|
from homeassistant.helpers.typing import HomeAssistantType
|
|
from homeassistant.util import slugify
|
|
|
|
from .const import (
|
|
API_VERSION,
|
|
APP_DESC,
|
|
CONNECTION_SENSORS,
|
|
DOMAIN,
|
|
STORAGE_KEY,
|
|
STORAGE_VERSION,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
SCAN_INTERVAL = timedelta(seconds=30)
|
|
|
|
|
|
async def get_api(hass: HomeAssistantType, host: str) -> Freepybox:
|
|
"""Get the Freebox API."""
|
|
freebox_path = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY).path
|
|
|
|
if not os.path.exists(freebox_path):
|
|
await hass.async_add_executor_job(os.makedirs, freebox_path)
|
|
|
|
token_file = Path(f"{freebox_path}/{slugify(host)}.conf")
|
|
|
|
return Freepybox(APP_DESC, token_file, API_VERSION)
|
|
|
|
|
|
class FreeboxRouter:
|
|
"""Representation of a Freebox router."""
|
|
|
|
def __init__(self, hass: HomeAssistantType, entry: ConfigEntry) -> None:
|
|
"""Initialize a Freebox router."""
|
|
self.hass = hass
|
|
self._entry = entry
|
|
self._host = entry.data[CONF_HOST]
|
|
self._port = entry.data[CONF_PORT]
|
|
|
|
self._api: Freepybox = None
|
|
self.name = None
|
|
self.mac = None
|
|
self._sw_v = None
|
|
self._attrs = {}
|
|
|
|
self.devices: dict[str, dict[str, Any]] = {}
|
|
self.disks: dict[int, dict[str, Any]] = {}
|
|
self.sensors_temperature: dict[str, int] = {}
|
|
self.sensors_connection: dict[str, float] = {}
|
|
self.call_list: list[dict[str, Any]] = []
|
|
|
|
self._unsub_dispatcher = None
|
|
self.listeners = []
|
|
|
|
async def setup(self) -> None:
|
|
"""Set up a Freebox router."""
|
|
self._api = await get_api(self.hass, self._host)
|
|
|
|
try:
|
|
await self._api.open(self._host, self._port)
|
|
except HttpRequestError:
|
|
_LOGGER.exception("Failed to connect to Freebox")
|
|
return ConfigEntryNotReady
|
|
|
|
# System
|
|
fbx_config = await self._api.system.get_config()
|
|
self.mac = fbx_config["mac"]
|
|
self.name = fbx_config["model_info"]["pretty_name"]
|
|
self._sw_v = fbx_config["firmware_version"]
|
|
|
|
# Devices & sensors
|
|
await self.update_all()
|
|
self._unsub_dispatcher = async_track_time_interval(
|
|
self.hass, self.update_all, SCAN_INTERVAL
|
|
)
|
|
|
|
async def update_all(self, now: datetime | None = None) -> None:
|
|
"""Update all Freebox platforms."""
|
|
await self.update_device_trackers()
|
|
await self.update_sensors()
|
|
|
|
async def update_device_trackers(self) -> None:
|
|
"""Update Freebox devices."""
|
|
new_device = False
|
|
fbx_devices: [dict[str, Any]] = await self._api.lan.get_hosts_list()
|
|
|
|
# Adds the Freebox itself
|
|
fbx_devices.append(
|
|
{
|
|
"primary_name": self.name,
|
|
"l2ident": {"id": self.mac},
|
|
"vendor_name": "Freebox SAS",
|
|
"host_type": "router",
|
|
"active": True,
|
|
"attrs": self._attrs,
|
|
}
|
|
)
|
|
|
|
for fbx_device in fbx_devices:
|
|
device_mac = fbx_device["l2ident"]["id"]
|
|
|
|
if self.devices.get(device_mac) is None:
|
|
new_device = True
|
|
|
|
self.devices[device_mac] = fbx_device
|
|
|
|
async_dispatcher_send(self.hass, self.signal_device_update)
|
|
|
|
if new_device:
|
|
async_dispatcher_send(self.hass, self.signal_device_new)
|
|
|
|
async def update_sensors(self) -> None:
|
|
"""Update Freebox sensors."""
|
|
# System sensors
|
|
syst_datas: dict[str, Any] = await self._api.system.get_config()
|
|
|
|
# According to the doc `syst_datas["sensors"]` is temperature sensors in celsius degree.
|
|
# Name and id of sensors may vary under Freebox devices.
|
|
for sensor in syst_datas["sensors"]:
|
|
self.sensors_temperature[sensor["name"]] = sensor["value"]
|
|
|
|
# Connection sensors
|
|
connection_datas: dict[str, Any] = await self._api.connection.get_status()
|
|
for sensor_key in CONNECTION_SENSORS:
|
|
self.sensors_connection[sensor_key] = connection_datas[sensor_key]
|
|
|
|
self._attrs = {
|
|
"IPv4": connection_datas.get("ipv4"),
|
|
"IPv6": connection_datas.get("ipv6"),
|
|
"connection_type": connection_datas["media"],
|
|
"uptime": datetime.fromtimestamp(
|
|
round(datetime.now().timestamp()) - syst_datas["uptime_val"]
|
|
),
|
|
"firmware_version": self._sw_v,
|
|
"serial": syst_datas["serial"],
|
|
}
|
|
|
|
self.call_list = await self._api.call.get_calls_log()
|
|
|
|
await self._update_disks_sensors()
|
|
|
|
async_dispatcher_send(self.hass, self.signal_sensor_update)
|
|
|
|
async def _update_disks_sensors(self) -> None:
|
|
"""Update Freebox disks."""
|
|
# None at first request
|
|
fbx_disks: [dict[str, Any]] = await self._api.storage.get_disks() or []
|
|
|
|
for fbx_disk in fbx_disks:
|
|
self.disks[fbx_disk["id"]] = fbx_disk
|
|
|
|
async def reboot(self) -> None:
|
|
"""Reboot the Freebox."""
|
|
await self._api.system.reboot()
|
|
|
|
async def close(self) -> None:
|
|
"""Close the connection."""
|
|
if self._api is not None:
|
|
await self._api.close()
|
|
self._unsub_dispatcher()
|
|
self._api = None
|
|
|
|
@property
|
|
def device_info(self) -> dict[str, Any]:
|
|
"""Return the device information."""
|
|
return {
|
|
"connections": {(CONNECTION_NETWORK_MAC, self.mac)},
|
|
"identifiers": {(DOMAIN, self.mac)},
|
|
"name": self.name,
|
|
"manufacturer": "Freebox SAS",
|
|
"sw_version": self._sw_v,
|
|
}
|
|
|
|
@property
|
|
def signal_device_new(self) -> str:
|
|
"""Event specific per Freebox entry to signal new device."""
|
|
return f"{DOMAIN}-{self._host}-device-new"
|
|
|
|
@property
|
|
def signal_device_update(self) -> str:
|
|
"""Event specific per Freebox entry to signal updates in devices."""
|
|
return f"{DOMAIN}-{self._host}-device-update"
|
|
|
|
@property
|
|
def signal_sensor_update(self) -> str:
|
|
"""Event specific per Freebox entry to signal updates in sensors."""
|
|
return f"{DOMAIN}-{self._host}-sensor-update"
|
|
|
|
@property
|
|
def sensors(self) -> dict[str, Any]:
|
|
"""Return sensors."""
|
|
return {**self.sensors_temperature, **self.sensors_connection}
|
|
|
|
@property
|
|
def wifi(self) -> Wifi:
|
|
"""Return the wifi."""
|
|
return self._api.wifi
|