mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
Add consider_home option to Fritz device_tracker (#50741)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
2ae91bf0ea
commit
987e8ed5ed
@ -34,7 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
try:
|
||||
await fritz_tools.async_setup()
|
||||
await fritz_tools.async_start()
|
||||
await fritz_tools.async_start(entry.options)
|
||||
except FritzSecurityError as ex:
|
||||
raise ConfigEntryAuthFailed from ex
|
||||
except FritzConnectionException as ex:
|
||||
@ -53,6 +53,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_unload)
|
||||
)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
# Load the other platforms like switch
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
|
||||
@ -79,3 +81,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
await async_unload_services(hass)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Update when config_entry options update."""
|
||||
if entry.options:
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
@ -16,6 +16,7 @@ from fritzconnection.core.exceptions import (
|
||||
from fritzconnection.lib.fritzhosts import FritzHosts
|
||||
from fritzconnection.lib.fritzstatus import FritzStatus
|
||||
|
||||
from homeassistant.components.device_tracker.const import CONF_CONSIDER_HOME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
@ -59,6 +60,7 @@ class FritzBoxTools:
|
||||
"""Initialize FritzboxTools class."""
|
||||
self._cancel_scan = None
|
||||
self._devices: dict[str, Any] = {}
|
||||
self._options = None
|
||||
self._unique_id = None
|
||||
self.connection = None
|
||||
self.fritz_hosts = None
|
||||
@ -95,10 +97,10 @@ class FritzBoxTools:
|
||||
self.sw_version = info.get("NewSoftwareVersion")
|
||||
self.mac = self.unique_id
|
||||
|
||||
async def async_start(self):
|
||||
async def async_start(self, options):
|
||||
"""Start FritzHosts connection."""
|
||||
self.fritz_hosts = FritzHosts(fc=self.connection)
|
||||
|
||||
self._options = options
|
||||
await self.hass.async_add_executor_job(self.scan_devices)
|
||||
|
||||
self._cancel_scan = async_track_time_interval(
|
||||
@ -141,6 +143,8 @@ class FritzBoxTools:
|
||||
"""Scan for new devices and return a list of found device ids."""
|
||||
_LOGGER.debug("Checking devices for FRITZ!Box router %s", self.host)
|
||||
|
||||
consider_home = self._options[CONF_CONSIDER_HOME]
|
||||
|
||||
new_device = False
|
||||
for known_host in self._update_info():
|
||||
if not known_host.get("mac"):
|
||||
@ -154,10 +158,10 @@ class FritzBoxTools:
|
||||
dev_info = Device(dev_mac, dev_ip, dev_name)
|
||||
|
||||
if dev_mac in self._devices:
|
||||
self._devices[dev_mac].update(dev_info, dev_home)
|
||||
self._devices[dev_mac].update(dev_info, dev_home, consider_home)
|
||||
else:
|
||||
device = FritzDevice(dev_mac)
|
||||
device.update(dev_info, dev_home)
|
||||
device.update(dev_info, dev_home, consider_home)
|
||||
self._devices[dev_mac] = device
|
||||
new_device = True
|
||||
|
||||
@ -204,19 +208,25 @@ class FritzDevice:
|
||||
self._last_activity = None
|
||||
self._connected = False
|
||||
|
||||
def update(self, dev_info, dev_home):
|
||||
def update(self, dev_info, dev_home, consider_home):
|
||||
"""Update device info."""
|
||||
utc_point_in_time = dt_util.utcnow()
|
||||
|
||||
if not self._name:
|
||||
self._name = dev_info.name or self._mac.replace(":", "_")
|
||||
self._connected = dev_home
|
||||
|
||||
if not self._connected:
|
||||
if not dev_home and self._last_activity:
|
||||
self._connected = (
|
||||
utc_point_in_time - self._last_activity
|
||||
).total_seconds() < consider_home
|
||||
else:
|
||||
self._connected = dev_home
|
||||
|
||||
if self._connected:
|
||||
self._ip_address = dev_info.ip_address
|
||||
self._last_activity = utc_point_in_time
|
||||
else:
|
||||
self._ip_address = None
|
||||
return
|
||||
|
||||
self._last_activity = utc_point_in_time
|
||||
self._ip_address = dev_info.ip_address
|
||||
|
||||
@property
|
||||
def is_connected(self):
|
||||
|
@ -2,19 +2,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from fritzconnection.core.exceptions import FritzConnectionException, FritzSecurityError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_tracker.const import (
|
||||
CONF_CONSIDER_HOME,
|
||||
DEFAULT_CONSIDER_HOME,
|
||||
)
|
||||
from homeassistant.components.ssdp import (
|
||||
ATTR_SSDP_LOCATION,
|
||||
ATTR_UPNP_FRIENDLY_NAME,
|
||||
ATTR_UPNP_UDN,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
||||
from .common import FritzBoxTools
|
||||
from .const import (
|
||||
@ -34,6 +40,12 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
"""Get the options flow for this handler."""
|
||||
return FritzBoxToolsOptionsFlowHandler(config_entry)
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize FRITZ!Box Tools flow."""
|
||||
self._host = None
|
||||
@ -85,6 +97,9 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
CONF_PORT: self.fritz_tools.port,
|
||||
CONF_USERNAME: self.fritz_tools.username,
|
||||
},
|
||||
options={
|
||||
CONF_CONSIDER_HOME: DEFAULT_CONSIDER_HOME.total_seconds(),
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_ssdp(self, discovery_info):
|
||||
@ -244,3 +259,31 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
CONF_PORT: import_config.get(CONF_PORT, DEFAULT_PORT),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class FritzBoxToolsOptionsFlowHandler(OptionsFlow):
|
||||
"""Handle a option flow."""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle options flow."""
|
||||
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_CONSIDER_HOME,
|
||||
default=self.config_entry.options.get(
|
||||
CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME.total_seconds()
|
||||
),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=0, max=900)),
|
||||
}
|
||||
)
|
||||
return self.async_show_form(step_id="init", data_schema=data_schema)
|
||||
|
@ -116,6 +116,7 @@ class FritzBoxTracker(ScannerEntity):
|
||||
self._router = router
|
||||
self._mac = device.mac_address
|
||||
self._name = device.hostname or DEFAULT_DEVICE_NAME
|
||||
self._last_activity = device.last_activity
|
||||
self._active = False
|
||||
self._attrs: dict = {}
|
||||
|
||||
@ -186,16 +187,22 @@ class FritzBoxTracker(ScannerEntity):
|
||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return the attributes."""
|
||||
attrs: dict[str, str] = {}
|
||||
if self._last_activity is not None:
|
||||
attrs["last_time_reachable"] = self._last_activity.isoformat(
|
||||
timespec="seconds"
|
||||
)
|
||||
return attrs
|
||||
|
||||
@callback
|
||||
def async_process_update(self) -> None:
|
||||
"""Update device."""
|
||||
device = self._router.devices[self._mac]
|
||||
device: FritzDevice = self._router.devices[self._mac]
|
||||
self._active = device.is_connected
|
||||
|
||||
if device.last_activity:
|
||||
self._attrs["last_time_reachable"] = device.last_activity.isoformat(
|
||||
timespec="seconds"
|
||||
)
|
||||
self._last_activity = device.last_activity
|
||||
|
||||
@callback
|
||||
def async_on_demand_update(self):
|
||||
|
@ -40,5 +40,14 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"consider_home": "Seconds to consider a device at 'home'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,5 +51,14 @@
|
||||
"title": "Setup FRITZ!Box Tools"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"consider_home": "Seconds to consider a device at 'home'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,10 @@ from unittest.mock import patch
|
||||
from fritzconnection.core.exceptions import FritzConnectionException, FritzSecurityError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.device_tracker.const import (
|
||||
CONF_CONSIDER_HOME,
|
||||
DEFAULT_CONSIDER_HOME,
|
||||
)
|
||||
from homeassistant.components.fritz.const import (
|
||||
DOMAIN,
|
||||
ERROR_AUTH_INVALID,
|
||||
@ -83,6 +87,10 @@ async def test_user(hass: HomeAssistant, fc_class_mock):
|
||||
assert result["data"][CONF_HOST] == "fake_host"
|
||||
assert result["data"][CONF_PASSWORD] == "fake_pass"
|
||||
assert result["data"][CONF_USERNAME] == "fake_user"
|
||||
assert (
|
||||
result["options"][CONF_CONSIDER_HOME]
|
||||
== DEFAULT_CONSIDER_HOME.total_seconds()
|
||||
)
|
||||
assert not result["result"].unique_id
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -416,3 +424,30 @@ async def test_import(hass: HomeAssistant, fc_class_mock):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_setup_entry.called
|
||||
|
||||
|
||||
async def test_options_flow(hass: HomeAssistant, fc_class_mock):
|
||||
"""Test options flow."""
|
||||
|
||||
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
mock_config.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.fritz.common.FritzConnection",
|
||||
side_effect=fc_class_mock,
|
||||
), patch("homeassistant.components.fritz.common.FritzStatus"), patch(
|
||||
"homeassistant.components.fritz.common.FritzBoxTools"
|
||||
):
|
||||
result = await hass.config_entries.options.async_init(mock_config.entry_id)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
result = await hass.config_entries.options.async_init(mock_config.entry_id)
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_CONSIDER_HOME: 37,
|
||||
},
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert mock_config.options[CONF_CONSIDER_HOME] == 37
|
||||
|
Loading…
x
Reference in New Issue
Block a user