Create Fritz device and connectivity sensor (#49699)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Simone Chemelli 2021-05-04 05:11:21 +02:00 committed by GitHub
parent 0df9454310
commit 55c96ae86f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 138 additions and 45 deletions

View File

@ -330,6 +330,7 @@ omit =
homeassistant/components/freebox/sensor.py
homeassistant/components/freebox/switch.py
homeassistant/components/fritz/__init__.py
homeassistant/components/fritz/binary_sensor.py
homeassistant/components/fritz/common.py
homeassistant/components/fritz/const.py
homeassistant/components/fritz/device_tracker.py

View File

@ -13,7 +13,6 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.typing import ConfigType
from .common import FritzBoxTools, FritzData
from .const import DATA_FRITZ, DOMAIN, PLATFORMS
@ -59,7 +58,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigType) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload FRITZ!Box Tools config entry."""
fritzbox: FritzBoxTools = hass.data[DOMAIN][entry.entry_id]
fritzbox.async_unload()

View File

@ -0,0 +1,87 @@
"""AVM FRITZ!Box connectivitiy sensor."""
import logging
from fritzconnection.core.exceptions import FritzConnectionException
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_CONNECTIVITY,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .common import FritzBoxBaseEntity, FritzBoxTools
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
) -> None:
"""Set up entry."""
_LOGGER.debug("Setting up FRITZ!Box binary sensors")
fritzbox_tools = hass.data[DOMAIN][entry.entry_id]
if "WANIPConn1" in fritzbox_tools.connection.services:
# Only routers are supported at the moment
async_add_entities(
[FritzBoxConnectivitySensor(fritzbox_tools, entry.title)], True
)
class FritzBoxConnectivitySensor(FritzBoxBaseEntity, BinarySensorEntity):
"""Define FRITZ!Box connectivity class."""
def __init__(self, fritzbox_tools: FritzBoxTools, device_friendlyname: str) -> None:
"""Init FRITZ!Box connectivity class."""
self._unique_id = f"{fritzbox_tools.unique_id}-connectivity"
self._name = f"{device_friendlyname} Connectivity"
self._is_on = True
self._is_available = True
super().__init__(fritzbox_tools, device_friendlyname)
@property
def name(self):
"""Return name."""
return self._name
@property
def device_class(self):
"""Return device class."""
return DEVICE_CLASS_CONNECTIVITY
@property
def is_on(self) -> bool:
"""Return status."""
return self._is_on
@property
def unique_id(self):
"""Return unique id."""
return self._unique_id
@property
def available(self) -> bool:
"""Return availability."""
return self._is_available
def update(self) -> None:
"""Update data."""
_LOGGER.debug("Updating FRITZ!Box binary sensors")
self._is_on = True
try:
if "WANCommonInterfaceConfig1" in self._fritzbox_tools.connection.services:
link_props = self._fritzbox_tools.connection.call_action(
"WANCommonInterfaceConfig1", "GetCommonLinkProperties"
)
is_up = link_props["NewPhysicalLinkStatus"]
self._is_on = is_up == "Up"
else:
self._is_on = self._fritzbox_tools.fritzstatus.is_connected
self._is_available = True
except FritzConnectionException:
_LOGGER.error("Error getting the state from the FRITZ!Box", exc_info=True)
self._is_available = False

View File

@ -12,6 +12,7 @@ from fritzconnection.lib.fritzhosts import FritzHosts
from fritzconnection.lib.fritzstatus import FritzStatus
from homeassistant.core import callback
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.util import dt as dt_util
@ -49,7 +50,6 @@ class FritzBoxTools:
):
"""Initialize FritzboxTools class."""
self._cancel_scan = None
self._device_info = None
self._devices: dict[str, Any] = {}
self._unique_id = None
self.connection = None
@ -60,6 +60,9 @@ class FritzBoxTools:
self.password = password
self.port = port
self.username = username
self.mac = None
self.model = None
self.sw_version = None
async def async_setup(self):
"""Wrap up FritzboxTools class setup."""
@ -76,12 +79,13 @@ class FritzBoxTools:
)
self.fritzstatus = FritzStatus(fc=self.connection)
info = self.connection.call_action("DeviceInfo:1", "GetInfo")
if self._unique_id is None:
self._unique_id = self.connection.call_action("DeviceInfo:1", "GetInfo")[
"NewSerialNumber"
]
self._unique_id = info["NewSerialNumber"]
self._device_info = self._fetch_device_info()
self.model = info.get("NewModelName")
self.sw_version = info.get("NewSoftwareVersion")
self.mac = self.unique_id
async def async_start(self):
"""Start FritzHosts connection."""
@ -106,16 +110,6 @@ class FritzBoxTools:
"""Return unique id."""
return self._unique_id
@property
def fritzbox_model(self):
"""Return model."""
return self._device_info["model"].replace("FRITZ!Box ", "")
@property
def device_info(self):
"""Return device info."""
return self._device_info
@property
def devices(self) -> dict[str, Any]:
"""Return devices."""
@ -163,33 +157,13 @@ class FritzBoxTools:
if new_device:
async_dispatcher_send(self.hass, self.signal_device_new)
def _fetch_device_info(self):
"""Fetch device info."""
info = self.connection.call_action("DeviceInfo:1", "GetInfo")
dev_info = {}
dev_info["identifiers"] = {
# Serial numbers are unique identifiers within a specific domain
(DOMAIN, self.unique_id)
}
dev_info["manufacturer"] = "AVM"
if dev_name := info.get("NewName"):
dev_info["name"] = dev_name
if dev_model := info.get("NewModelName"):
dev_info["model"] = dev_model
if dev_sw_ver := info.get("NewSoftwareVersion"):
dev_info["sw_version"] = dev_sw_ver
return dev_info
class FritzData:
"""Storage class for platform global data."""
def __init__(self) -> None:
"""Initialize the data."""
self.tracked = {}
self.tracked: dict = {}
class FritzDevice:
@ -241,3 +215,30 @@ class FritzDevice:
def last_activity(self):
"""Return device last activity."""
return self._last_activity
class FritzBoxBaseEntity:
"""Fritz host entity base class."""
def __init__(self, fritzbox_tools: FritzBoxTools, device_name: str) -> None:
"""Init device info class."""
self._fritzbox_tools = fritzbox_tools
self._device_name = device_name
@property
def mac_address(self) -> str:
"""Return the mac address of the main device."""
return self._fritzbox_tools.mac
@property
def device_info(self):
"""Return the device information."""
return {
"connections": {(CONNECTION_NETWORK_MAC, self.mac_address)},
"identifiers": {(DOMAIN, self._fritzbox_tools.unique_id)},
"name": self._device_name,
"manufacturer": "AVM",
"model": self._fritzbox_tools.model,
"sw_version": self._fritzbox_tools.sw_version,
}

View File

@ -1,4 +1,6 @@
"""Config flow to configure the FRITZ!Box Tools integration."""
from __future__ import annotations
import logging
from urllib.parse import urlparse
@ -65,7 +67,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
return None
async def async_check_configured_entry(self) -> ConfigEntry:
async def async_check_configured_entry(self) -> ConfigEntry | None:
"""Check if entry is configured."""
for entry in self._async_current_entries(include_ignore=False):
if entry.data[CONF_HOST] == self._host:
@ -170,7 +172,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
self._password = user_input[CONF_PASSWORD]
if not (error := await self.fritz_tools_init()):
self._name = self.fritz_tools.device_info["model"]
self._name = self.fritz_tools.model
if await self.async_check_configured_entry():
error = "already_configured"

View File

@ -2,7 +2,7 @@
DOMAIN = "fritz"
PLATFORMS = ["device_tracker"]
PLATFORMS = ["binary_sensor", "device_tracker"]
DATA_FRITZ = "fritz_data"

View File

@ -17,7 +17,6 @@ from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.typing import ConfigType
from .common import FritzBoxTools
@ -116,7 +115,7 @@ class FritzBoxTracker(ScannerEntity):
self._mac = device.mac_address
self._name = device.hostname or DEFAULT_DEVICE_NAME
self._active = False
self._attrs = {}
self._attrs: dict = {}
@property
def is_connected(self):
@ -154,7 +153,7 @@ class FritzBoxTracker(ScannerEntity):
return SOURCE_TYPE_ROUTER
@property
def device_info(self) -> DeviceInfo:
def device_info(self):
"""Return the device information."""
return {
"connections": {(CONNECTION_NETWORK_MAC, self._mac)},
@ -162,6 +161,10 @@ class FritzBoxTracker(ScannerEntity):
"name": self.name,
"manufacturer": "AVM",
"model": "FRITZ!Box Tracked device",
"via_device": (
DOMAIN,
self._router.unique_id,
),
}
@property

View File

@ -49,7 +49,7 @@ class FritzConnectionMock: # pylint: disable=too-few-public-methods
"NewBytesReceived": 12045,
},
("DeviceInfo:1", "GetInfo"): {
"NewSerialNumber": 1234,
"NewSerialNumber": "abcdefgh",
"NewName": "TheName",
"NewModelName": "FRITZ!Box 7490",
},