-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial version of Vilfo Router integration (#31177)
* Initial implementation of Vilfo router integration. This commit is a combination of several commits, with commit messages in bullet form below. * Added additional files to Vilfo integration. * Added generated files. * Fixed alphabetic order in generated config_flows. * Continued implementation of config flow for Vilfo integration. * Continued work on config_flow for Vilfo. * Updated requirements in manifest for Vilfo Router integration. * Some strings added to Vilfo Router integration. * Vilfo Router integration updated with sensor support. * Code style cleanup. * Additional cleanup of config flow. * Added additional UI strings for Vilfo Router * Updated tests of config flow and fixed formatting * Updated requirement upon vilfo-api-client. * Sensor refactoring including support for icons * Code style changes for Vilfo Router integration * Code cleanup * Fixed linting issues in Vilfo Router integration * Fixed import order in test for Vilfo integration. * Updates to Vilfo Router integration based on feedback Based on the feedback received, updates have been made to the Vilfo Router integration. A couple of the points mentioned have not been addressed yet, since the appropriate action has not yet been determined. These are: * #31177 (comment) * #31177 (comment) This commit consists of: * Removed unused folder/submodule * Fixes to __init__ * Fixes to config_flow * Fixes to const * Refactored sensors and applied fixes * Fix issue with wrong exception type in config flow * Updated tests for Vilfo integration config_flow * Updated dependency upon vilfo-api-client to improve testability * Import order fixes in test * Use constants instead of strings in tests * Updated the VilfoRouterData class to only use the hostname as unique_id when it is the default one (admin.vilfo.com). * Refactored based on feedback during review. * Changes to constant names, * Blocking IO separated to executor job, * Data for uptime sensor changed from being computed to being a timestamp, * Started refactoring uptime sensor in terms of naming and unit. * Updated constants for boot time (previously uptime) sensor. * Refactored test of Vilfo config flow to avoid patching code under test. * UI naming fixes and better exception handling. * Removed unused exception class. * Various changes to Vilfo Router integration. * Removed unit of measurement for boot time sensor, * Added support for a sensor not having a unit, * Updated the config_flow to handle when the integration is already configured, * Updated tests to avoid mocking the code under test and also to cover the aforementioned changes. * Exception handling in Vilfo Router config flow refactored to be more readable. * Refactored constant usage, fixed sensor availability and fix API client library doing I/O in async context. * Updated signature with hass first * Update call to constructor with changed order of arguments
- Loading branch information
Showing
13 changed files
with
630 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
"""The Vilfo Router integration.""" | ||
import asyncio | ||
from datetime import timedelta | ||
import logging | ||
|
||
from vilfo import Client as VilfoClient | ||
from vilfo.exceptions import VilfoException | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import ConfigEntryNotReady | ||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType | ||
from homeassistant.util import Throttle | ||
|
||
from .const import ATTR_BOOT_TIME, ATTR_LOAD, DOMAIN, ROUTER_DEFAULT_HOST | ||
|
||
PLATFORMS = ["sensor"] | ||
|
||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=30) | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
async def async_setup(hass: HomeAssistantType, config: ConfigType): | ||
"""Set up the Vilfo Router component.""" | ||
hass.data.setdefault(DOMAIN, {}) | ||
return True | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
"""Set up Vilfo Router from a config entry.""" | ||
host = entry.data[CONF_HOST] | ||
access_token = entry.data[CONF_ACCESS_TOKEN] | ||
|
||
vilfo_router = VilfoRouterData(hass, host, access_token) | ||
|
||
await vilfo_router.async_update() | ||
|
||
if not vilfo_router.available: | ||
raise ConfigEntryNotReady | ||
|
||
hass.data[DOMAIN][entry.entry_id] = vilfo_router | ||
|
||
for platform in PLATFORMS: | ||
hass.async_create_task( | ||
hass.config_entries.async_forward_entry_setup(entry, platform) | ||
) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
"""Unload a config entry.""" | ||
unload_ok = all( | ||
await asyncio.gather( | ||
*[ | ||
hass.config_entries.async_forward_entry_unload(entry, platform) | ||
for platform in PLATFORMS | ||
] | ||
) | ||
) | ||
if unload_ok: | ||
hass.data[DOMAIN].pop(entry.entry_id) | ||
|
||
return unload_ok | ||
|
||
|
||
class VilfoRouterData: | ||
"""Define an object to hold sensor data.""" | ||
|
||
def __init__(self, hass, host, access_token): | ||
"""Initialize.""" | ||
self._vilfo = VilfoClient(host, access_token) | ||
self.hass = hass | ||
self.host = host | ||
self.available = False | ||
self.firmware_version = None | ||
self.mac_address = self._vilfo.mac | ||
self.data = {} | ||
self._unavailable_logged = False | ||
|
||
@property | ||
def unique_id(self): | ||
"""Get the unique_id for the Vilfo Router.""" | ||
if self.mac_address: | ||
return self.mac_address | ||
|
||
if self.host == ROUTER_DEFAULT_HOST: | ||
return self.host | ||
|
||
return self.host | ||
|
||
def _fetch_data(self): | ||
board_information = self._vilfo.get_board_information() | ||
load = self._vilfo.get_load() | ||
|
||
return { | ||
"board_information": board_information, | ||
"load": load, | ||
} | ||
|
||
@Throttle(DEFAULT_SCAN_INTERVAL) | ||
async def async_update(self): | ||
"""Update data using calls to VilfoClient library.""" | ||
try: | ||
data = await self.hass.async_add_executor_job(self._fetch_data) | ||
|
||
self.firmware_version = data["board_information"]["version"] | ||
self.data[ATTR_BOOT_TIME] = data["board_information"]["bootTime"] | ||
self.data[ATTR_LOAD] = data["load"] | ||
|
||
self.available = True | ||
except VilfoException as error: | ||
if not self._unavailable_logged: | ||
_LOGGER.error( | ||
"Could not fetch data from %s, error: %s", self.host, error | ||
) | ||
self._unavailable_logged = True | ||
self.available = False | ||
return | ||
|
||
if self.available and self._unavailable_logged: | ||
_LOGGER.info("Vilfo Router %s is available again", self.host) | ||
self._unavailable_logged = False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
"""Config flow for Vilfo Router integration.""" | ||
import ipaddress | ||
import logging | ||
import re | ||
|
||
from vilfo import Client as VilfoClient | ||
from vilfo.exceptions import ( | ||
AuthenticationException as VilfoAuthenticationException, | ||
VilfoException, | ||
) | ||
import voluptuous as vol | ||
|
||
from homeassistant import config_entries, core, exceptions | ||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_ID, CONF_MAC | ||
|
||
from .const import DOMAIN # pylint:disable=unused-import | ||
from .const import ROUTER_DEFAULT_HOST | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DATA_SCHEMA = vol.Schema( | ||
{ | ||
vol.Required(CONF_HOST, default=ROUTER_DEFAULT_HOST): str, | ||
vol.Required(CONF_ACCESS_TOKEN, default=""): str, | ||
} | ||
) | ||
|
||
RESULT_SUCCESS = "success" | ||
RESULT_CANNOT_CONNECT = "cannot_connect" | ||
RESULT_INVALID_AUTH = "invalid_auth" | ||
|
||
|
||
def host_valid(host): | ||
"""Return True if hostname or IP address is valid.""" | ||
try: | ||
if ipaddress.ip_address(host).version == (4 or 6): | ||
return True | ||
except ValueError: | ||
disallowed = re.compile(r"[^a-zA-Z\d\-]") | ||
return all(x and not disallowed.search(x) for x in host.split(".")) | ||
|
||
|
||
def _try_connect_and_fetch_basic_info(host, token): | ||
"""Attempt to connect and call the ping endpoint and, if successful, fetch basic information.""" | ||
|
||
# Perform the ping. This doesn't validate authentication. | ||
controller = VilfoClient(host=host, token=token) | ||
result = {"type": None, "data": {}} | ||
|
||
try: | ||
controller.ping() | ||
except VilfoException: | ||
result["type"] = RESULT_CANNOT_CONNECT | ||
result["data"] = CannotConnect | ||
return result | ||
|
||
# Perform a call that requires authentication. | ||
try: | ||
controller.get_board_information() | ||
except VilfoAuthenticationException: | ||
result["type"] = RESULT_INVALID_AUTH | ||
result["data"] = InvalidAuth | ||
return result | ||
|
||
if controller.mac: | ||
result["data"][CONF_ID] = controller.mac | ||
result["data"][CONF_MAC] = controller.mac | ||
else: | ||
result["data"][CONF_ID] = host | ||
result["data"][CONF_MAC] = None | ||
|
||
result["type"] = RESULT_SUCCESS | ||
|
||
return result | ||
|
||
|
||
async def validate_input(hass: core.HomeAssistant, data): | ||
"""Validate the user input allows us to connect. | ||
Data has the keys from DATA_SCHEMA with values provided by the user. | ||
""" | ||
|
||
# Validate the host before doing anything else. | ||
if not host_valid(data[CONF_HOST]): | ||
raise InvalidHost | ||
|
||
config = {} | ||
|
||
result = await hass.async_add_executor_job( | ||
_try_connect_and_fetch_basic_info, data[CONF_HOST], data[CONF_ACCESS_TOKEN] | ||
) | ||
|
||
if result["type"] != RESULT_SUCCESS: | ||
raise result["data"] | ||
|
||
# Return some info we want to store in the config entry. | ||
result_data = result["data"] | ||
config["title"] = f"{data[CONF_HOST]}" | ||
config[CONF_MAC] = result_data[CONF_MAC] | ||
config[CONF_HOST] = data[CONF_HOST] | ||
config[CONF_ID] = result_data[CONF_ID] | ||
|
||
return config | ||
|
||
|
||
class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||
"""Handle a config flow for Vilfo Router.""" | ||
|
||
VERSION = 1 | ||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL | ||
|
||
async def async_step_user(self, user_input=None): | ||
"""Handle the initial step.""" | ||
errors = {} | ||
if user_input is not None: | ||
try: | ||
info = await validate_input(self.hass, user_input) | ||
except InvalidHost: | ||
errors[CONF_HOST] = "wrong_host" | ||
except CannotConnect: | ||
errors["base"] = "cannot_connect" | ||
except InvalidAuth: | ||
errors["base"] = "invalid_auth" | ||
except Exception as err: # pylint: disable=broad-except | ||
_LOGGER.error("Unexpected exception: %s", err) | ||
errors["base"] = "unknown" | ||
else: | ||
await self.async_set_unique_id(info[CONF_ID]) | ||
self._abort_if_unique_id_configured() | ||
|
||
return self.async_create_entry(title=info["title"], data=user_input) | ||
|
||
return self.async_show_form( | ||
step_id="user", data_schema=DATA_SCHEMA, errors=errors | ||
) | ||
|
||
|
||
class CannotConnect(exceptions.HomeAssistantError): | ||
"""Error to indicate we cannot connect.""" | ||
|
||
|
||
class InvalidAuth(exceptions.HomeAssistantError): | ||
"""Error to indicate there is invalid auth.""" | ||
|
||
|
||
class InvalidHost(exceptions.HomeAssistantError): | ||
"""Error to indicate that hostname/IP address is invalid.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
"""Constants for the Vilfo Router integration.""" | ||
from homeassistant.const import DEVICE_CLASS_TIMESTAMP | ||
|
||
DOMAIN = "vilfo" | ||
|
||
ATTR_API_DATA_FIELD = "api_data_field" | ||
ATTR_API_DATA_FIELD_LOAD = "load" | ||
ATTR_API_DATA_FIELD_BOOT_TIME = "boot_time" | ||
ATTR_DEVICE_CLASS = "device_class" | ||
ATTR_ICON = "icon" | ||
ATTR_LABEL = "label" | ||
ATTR_LOAD = "load" | ||
ATTR_UNIT = "unit" | ||
ATTR_BOOT_TIME = "boot_time" | ||
|
||
ROUTER_DEFAULT_HOST = "admin.vilfo.com" | ||
ROUTER_DEFAULT_MODEL = "Vilfo Router" | ||
ROUTER_DEFAULT_NAME = "Vilfo Router" | ||
ROUTER_MANUFACTURER = "Vilfo AB" | ||
|
||
UNIT_PERCENT = "%" | ||
|
||
SENSOR_TYPES = { | ||
ATTR_LOAD: { | ||
ATTR_LABEL: "Load", | ||
ATTR_UNIT: UNIT_PERCENT, | ||
ATTR_ICON: "mdi:memory", | ||
ATTR_API_DATA_FIELD: ATTR_API_DATA_FIELD_LOAD, | ||
}, | ||
ATTR_BOOT_TIME: { | ||
ATTR_LABEL: "Boot time", | ||
ATTR_ICON: "mdi:timer", | ||
ATTR_API_DATA_FIELD: ATTR_API_DATA_FIELD_BOOT_TIME, | ||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TIMESTAMP, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"domain": "vilfo", | ||
"name": "Vilfo Router", | ||
"config_flow": true, | ||
"documentation": "https://www.home-assistant.io/integrations/vilfo", | ||
"requirements": ["vilfo-api-client==0.3.2"], | ||
"dependencies": [], | ||
"codeowners": ["@ManneW"] | ||
} |
Oops, something went wrong.