Skip to content
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

Adds AdGuard Home integration #24219

Merged
merged 11 commits into from Jun 2, 2019
@@ -13,6 +13,10 @@ omit =
homeassistant/components/abode/*
homeassistant/components/acer_projector/switch.py
homeassistant/components/actiontec/device_tracker.py
homeassistant/components/adguard/__init__.py
homeassistant/components/adguard/const.py
homeassistant/components/adguard/sensor.py
homeassistant/components/adguard/switch.py
homeassistant/components/ads/*
homeassistant/components/aftership/sensor.py
homeassistant/components/airvisual/sensor.py
@@ -17,6 +17,7 @@ virtualization/Docker/* @home-assistant/docker
homeassistant/scripts/check_config.py @kellerza

# Integrations
homeassistant/components/adguard/* @frenck
homeassistant/components/airvisual/* @bachya
homeassistant/components/alarm_control_panel/* @colinodell
homeassistant/components/alpha_vantage/* @fabaff
@@ -0,0 +1,29 @@
{
"config": {
"title": "AdGuard Home",
"step": {
"user": {
"title": "Link your AdGuard Home.",
"description": "Set up your AdGuard Home instance to allow monitoring and control.",
"data": {
"host": "Host",
"password": "Password",
"port": "Port",
"username": "Username",
"ssl": "AdGuard Home uses a SSL certificate",
"verify_ssl": "AdGuard Home uses a proper certificate"
}
},
"hassio_confirm": {
"title": "AdGuard Home via Hass.io add-on",
"description": "Do you want to configure Home Assistant to connect to the AdGuard Home provided by the Hass.io add-on: {addon}?"
}
},
"error": {
"connection_error": "Failed to connect."
},
"abort": {
"single_instance_allowed": "Only a single configuration of AdGuard Home is allowed."
}
}
}
@@ -0,0 +1,193 @@
"""Support for AdGuard Home."""
import logging
from typing import Any, Dict

import voluptuous as vol

from homeassistant.components.adguard.const import (
CONF_FORCE, DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN,
SERVICE_ADD_URL, SERVICE_DISABLE_URL, SERVICE_ENABLE_URL, SERVICE_REFRESH,
SERVICE_REMOVE_URL)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_URL,
CONF_USERNAME, CONF_VERIFY_SSL)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType, HomeAssistantType

_LOGGER = logging.getLogger(__name__)

SERVICE_URL_SCHEMA = vol.Schema({vol.Required(CONF_URL): cv.url})
SERVICE_ADD_URL_SCHEMA = vol.Schema(
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_URL): cv.url}
)
SERVICE_REFRESH_SCHEMA = vol.Schema(
{vol.Optional(CONF_FORCE, default=False): cv.boolean}
)


async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
"""Set up the AdGuard Home components."""
return True


async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry
) -> bool:
"""Set up AdGuard Home from a config entry."""
from adguardhome import AdGuardHome
This conversation was marked as resolved by frenck

This comment has been minimized.

Copy link
@balloob

balloob Jun 1, 2019

Member

FYI We are allowed to import these at the top now.

This comment has been minimized.

Copy link
@frenck

frenck Jun 1, 2019

Author Member

@balloob, I figured that, because it happened a couple of times now already. The manifest does not require the loading of the actual code anymore to get information about the dependencies, which is great!

Nevertheless, I stuck with the component checklist:

"4. Requirements should only be imported inside functions. This is necessary because requirements are installed on the fly."

https://developers.home-assistant.io/docs/en/creating_component_code_review.html

This comment has been minimized.

Copy link
@frenck

frenck Jun 1, 2019

Author Member

Updated codebase to reflect your review comment. Dependency imports are now at the top.


session = async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL])
adguard = AdGuardHome(
entry.data[CONF_HOST],
port=entry.data[CONF_PORT],
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
tls=entry.data[CONF_SSL],
verify_ssl=entry.data[CONF_VERIFY_SSL],
loop=hass.loop,
session=session,
)

hass.data.setdefault(DOMAIN, {})[DATA_ADGUARD_CLIENT] = adguard

for component in 'sensor', 'switch':
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)

def add_url(call) -> None:
This conversation was marked as resolved by frenck

This comment has been minimized.

Copy link
@balloob

balloob Jun 1, 2019

Member

These should become async service handlers, and then await the calling of add_url. That way we can guarantee to the user that if they call the service with blocking=true, that the method finished calling.

async def add_url(call):
    await adguard.filtering.add_url(name, url)

This comment has been minimized.

Copy link
@frenck

frenck Jun 1, 2019

Author Member

Check, will adjust. 👍

"""Service call to add a new filter subscription to AdGuard Home."""
hass.async_create_task(
adguard.filtering.add_url(
call.data.get(CONF_NAME), call.data.get(CONF_URL)
)
)

def remove_url(call) -> None:
"""Service call to remove a filter subscription from AdGuard Home."""
hass.async_create_task(
adguard.filtering.remove_url(call.data.get(CONF_URL))
)

def enable_url(call) -> None:
"""Service call to enable a filter subscription in AdGuard Home."""
hass.async_create_task(
adguard.filtering.enable_url(call.data.get(CONF_URL))
)

def disable_url(call) -> None:
"""Service call to disable a filter subscription in AdGuard Home."""
hass.async_create_task(
adguard.filtering.disable_url(call.data.get(CONF_URL))
)

def refresh(call) -> None:
"""Service call to refresh the filter subscriptions in AdGuard Home."""
hass.async_create_task(
adguard.filtering.refresh(call.data.get(CONF_FORCE))
)

hass.services.async_register(
DOMAIN, SERVICE_ADD_URL, add_url, schema=SERVICE_ADD_URL_SCHEMA
)
hass.services.async_register(
DOMAIN, SERVICE_REMOVE_URL, remove_url, schema=SERVICE_URL_SCHEMA
)
hass.services.async_register(
DOMAIN, SERVICE_ENABLE_URL, enable_url, schema=SERVICE_URL_SCHEMA
)
hass.services.async_register(
DOMAIN, SERVICE_DISABLE_URL, disable_url, schema=SERVICE_URL_SCHEMA
)
hass.services.async_register(
DOMAIN, SERVICE_REFRESH, refresh, schema=SERVICE_REFRESH_SCHEMA
)

return True


async def async_unload_entry(
hass: HomeAssistantType, entry: ConfigType
) -> bool:
"""Unload AdGuard Home config entry."""
hass.services.async_remove(DOMAIN, SERVICE_ADD_URL)
hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL)
hass.services.async_remove(DOMAIN, SERVICE_ENABLE_URL)
hass.services.async_remove(DOMAIN, SERVICE_DISABLE_URL)
hass.services.async_remove(DOMAIN, SERVICE_REFRESH)

for component in 'sensor', 'switch':
await hass.config_entries.async_forward_entry_unload(entry, component)

del hass.data[DOMAIN]

return True


class AdGuardHomeEntity(Entity):
"""Defines a base AdGuard Home entity."""

def __init__(self, adguard, name: str, icon: str) -> None:
"""Initialize the AdGuard Home entity."""
self._name = name
self._icon = icon
self._available = True
self.adguard = adguard

@property
def name(self) -> str:
"""Return the name of the entity."""
return self._name

@property
def icon(self) -> str:
"""Return the mdi icon of the entity."""
return self._icon

@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._available

async def async_update(self) -> None:
"""Update AdGuard Home entity."""
from adguardhome import AdGuardHomeError

try:
await self._adguard_update()
self._available = True
except AdGuardHomeError:
if self._available:
_LOGGER.debug(
"An error occurred while updating AdGuard Home sensor.",
exc_info=True,
)
self._available = False

async def _adguard_update(self) -> None:
"""Update AdGuard Home entity."""
raise NotImplementedError()


class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
"""Defines a AdGuard Home device entity."""

@property
def device_info(self) -> Dict[str, Any]:
"""Return device information about this AdGuard Home instance."""
return {
'identifiers': {
(
DOMAIN,
self.adguard.host,
self.adguard.port,
self.adguard.base_path,
)
},
'name': 'AdGuard Home',
'manufacturer': 'AdGuard Team',
'sw_version': self.hass.data[DOMAIN].get(DATA_ADGUARD_VERION),
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.