-
-
Notifications
You must be signed in to change notification settings - Fork 37.5k
Add initial version of Vilfo Router integration #31177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
0550821
Initial implementation of Vilfo router integration.
ManneW dbf9c7b
Updates to Vilfo Router integration based on feedback
ManneW 677676e
Updated the VilfoRouterData class to only use the hostname as unique_…
ManneW bdc11ae
Refactored based on feedback during review.
ManneW 121e86a
Removed unused exception class.
ManneW 753fc1a
Various changes to Vilfo Router integration.
ManneW 6cffd91
Exception handling in Vilfo Router config flow refactored to be more …
ManneW 81c48c2
Refactored constant usage, fixed sensor availability and fix API clie…
ManneW 7f87cc7
Updated signature with hass first
ManneW 4cb69bc
Update call to constructor with changed order of arguments
ManneW File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.