Adds AdGuard Home integration (#24219)
* Adds AdGuard Home integration * 👕 Addresses linting warnings * 🚑 Fixes typehint in async_setup_entry * 👕 Take advantage of Python's coalescing operators * 👕 Use adguard instance from outer scope directly in service calls * 👕 Use more sensible scan_interval default for sensors * 👕 Adds specific files to .coveragerc * ☔ Added tests and small changes to improve coverage * 🔨 Import adguardhome dependencies at the top * 🚑 Converted service handlers to be async * 🔥 Removed init step from config flow
This commit is contained in:
parent
7be7d3ffac
commit
9220270948
17 changed files with 1095 additions and 0 deletions
|
@ -13,6 +13,10 @@ omit =
|
||||||
homeassistant/components/abode/*
|
homeassistant/components/abode/*
|
||||||
homeassistant/components/acer_projector/switch.py
|
homeassistant/components/acer_projector/switch.py
|
||||||
homeassistant/components/actiontec/device_tracker.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/ads/*
|
||||||
homeassistant/components/aftership/sensor.py
|
homeassistant/components/aftership/sensor.py
|
||||||
homeassistant/components/airvisual/sensor.py
|
homeassistant/components/airvisual/sensor.py
|
||||||
|
|
|
@ -17,6 +17,7 @@ virtualization/Docker/* @home-assistant/docker
|
||||||
homeassistant/scripts/check_config.py @kellerza
|
homeassistant/scripts/check_config.py @kellerza
|
||||||
|
|
||||||
# Integrations
|
# Integrations
|
||||||
|
homeassistant/components/adguard/* @frenck
|
||||||
homeassistant/components/airvisual/* @bachya
|
homeassistant/components/airvisual/* @bachya
|
||||||
homeassistant/components/alarm_control_panel/* @colinodell
|
homeassistant/components/alarm_control_panel/* @colinodell
|
||||||
homeassistant/components/alpha_vantage/* @fabaff
|
homeassistant/components/alpha_vantage/* @fabaff
|
||||||
|
|
29
homeassistant/components/adguard/.translations/en.json
Normal file
29
homeassistant/components/adguard/.translations/en.json
Normal file
|
@ -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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
180
homeassistant/components/adguard/__init__.py
Normal file
180
homeassistant/components/adguard/__init__.py
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
"""Support for AdGuard Home."""
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from adguardhome import AdGuardHome, AdGuardHomeError
|
||||||
|
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."""
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def add_url(call) -> None:
|
||||||
|
"""Service call to add a new filter subscription to AdGuard Home."""
|
||||||
|
await adguard.filtering.add_url(
|
||||||
|
call.data.get(CONF_NAME), call.data.get(CONF_URL)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def remove_url(call) -> None:
|
||||||
|
"""Service call to remove a filter subscription from AdGuard Home."""
|
||||||
|
await adguard.filtering.remove_url(call.data.get(CONF_URL))
|
||||||
|
|
||||||
|
async def enable_url(call) -> None:
|
||||||
|
"""Service call to enable a filter subscription in AdGuard Home."""
|
||||||
|
await adguard.filtering.enable_url(call.data.get(CONF_URL))
|
||||||
|
|
||||||
|
async def disable_url(call) -> None:
|
||||||
|
"""Service call to disable a filter subscription in AdGuard Home."""
|
||||||
|
await adguard.filtering.disable_url(call.data.get(CONF_URL))
|
||||||
|
|
||||||
|
async def refresh(call) -> None:
|
||||||
|
"""Service call to refresh the filter subscriptions in AdGuard Home."""
|
||||||
|
await 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."""
|
||||||
|
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),
|
||||||
|
}
|
147
homeassistant/components/adguard/config_flow.py
Normal file
147
homeassistant/components/adguard/config_flow.py
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
"""Config flow to configure the AdGuard Home integration."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.adguard.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigFlow
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME,
|
||||||
|
CONF_VERIFY_SSL)
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@config_entries.HANDLERS.register(DOMAIN)
|
||||||
|
class AdGuardHomeFlowHandler(ConfigFlow):
|
||||||
|
"""Handle a AdGuard Home config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
|
||||||
|
_hassio_discovery = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize AgGuard Home flow."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _show_setup_form(self, errors=None):
|
||||||
|
"""Show the setup form to the user."""
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id='user',
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_HOST): str,
|
||||||
|
vol.Required(CONF_PORT, default=3000): vol.Coerce(int),
|
||||||
|
vol.Optional(CONF_USERNAME): str,
|
||||||
|
vol.Optional(CONF_PASSWORD): str,
|
||||||
|
vol.Required(CONF_SSL, default=True): bool,
|
||||||
|
vol.Required(CONF_VERIFY_SSL, default=True): bool,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors=errors or {},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _show_hassio_form(self, errors=None):
|
||||||
|
"""Show the Hass.io confirmation form to the user."""
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id='hassio_confirm',
|
||||||
|
description_placeholders={
|
||||||
|
'addon': self._hassio_discovery['addon']
|
||||||
|
},
|
||||||
|
data_schema=vol.Schema({}),
|
||||||
|
errors=errors or {},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle a flow initiated by the user."""
|
||||||
|
if self._async_current_entries():
|
||||||
|
return self.async_abort(reason='single_instance_allowed')
|
||||||
|
|
||||||
|
if user_input is None:
|
||||||
|
return await self._show_setup_form(user_input)
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
session = async_get_clientsession(
|
||||||
|
self.hass, user_input[CONF_VERIFY_SSL]
|
||||||
|
)
|
||||||
|
|
||||||
|
adguard = AdGuardHome(
|
||||||
|
user_input[CONF_HOST],
|
||||||
|
port=user_input[CONF_PORT],
|
||||||
|
username=user_input.get(CONF_USERNAME),
|
||||||
|
password=user_input.get(CONF_PASSWORD),
|
||||||
|
tls=user_input[CONF_SSL],
|
||||||
|
verify_ssl=user_input[CONF_VERIFY_SSL],
|
||||||
|
loop=self.hass.loop,
|
||||||
|
session=session,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await adguard.version()
|
||||||
|
except AdGuardHomeConnectionError:
|
||||||
|
errors['base'] = 'connection_error'
|
||||||
|
return await self._show_setup_form(errors)
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=user_input[CONF_HOST],
|
||||||
|
data={
|
||||||
|
CONF_HOST: user_input[CONF_HOST],
|
||||||
|
CONF_PASSWORD: user_input.get(CONF_PASSWORD),
|
||||||
|
CONF_PORT: user_input[CONF_PORT],
|
||||||
|
CONF_SSL: user_input[CONF_SSL],
|
||||||
|
CONF_USERNAME: user_input.get(CONF_USERNAME),
|
||||||
|
CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_hassio(self, user_input=None):
|
||||||
|
"""Prepare configuration for a Hass.io AdGuard Home add-on.
|
||||||
|
|
||||||
|
This flow is triggered by the discovery component.
|
||||||
|
"""
|
||||||
|
if self._async_current_entries():
|
||||||
|
return self.async_abort(reason='single_instance_allowed')
|
||||||
|
|
||||||
|
self._hassio_discovery = user_input
|
||||||
|
|
||||||
|
return await self.async_step_hassio_confirm()
|
||||||
|
|
||||||
|
async def async_step_hassio_confirm(self, user_input=None):
|
||||||
|
"""Confirm Hass.io discovery."""
|
||||||
|
if user_input is None:
|
||||||
|
return await self._show_hassio_form()
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
session = async_get_clientsession(self.hass, False)
|
||||||
|
|
||||||
|
adguard = AdGuardHome(
|
||||||
|
self._hassio_discovery[CONF_HOST],
|
||||||
|
port=self._hassio_discovery[CONF_PORT],
|
||||||
|
tls=False,
|
||||||
|
loop=self.hass.loop,
|
||||||
|
session=session,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await adguard.version()
|
||||||
|
except AdGuardHomeConnectionError:
|
||||||
|
errors['base'] = 'connection_error'
|
||||||
|
return await self._show_hassio_form(errors)
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=self._hassio_discovery['addon'],
|
||||||
|
data={
|
||||||
|
CONF_HOST: self._hassio_discovery[CONF_HOST],
|
||||||
|
CONF_PORT: self._hassio_discovery[CONF_PORT],
|
||||||
|
CONF_PASSWORD: None,
|
||||||
|
CONF_SSL: False,
|
||||||
|
CONF_USERNAME: None,
|
||||||
|
CONF_VERIFY_SSL: True,
|
||||||
|
},
|
||||||
|
)
|
14
homeassistant/components/adguard/const.py
Normal file
14
homeassistant/components/adguard/const.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
"""Constants for the AdGuard Home integration."""
|
||||||
|
|
||||||
|
DOMAIN = 'adguard'
|
||||||
|
|
||||||
|
DATA_ADGUARD_CLIENT = 'adguard_client'
|
||||||
|
DATA_ADGUARD_VERION = 'adguard_version'
|
||||||
|
|
||||||
|
CONF_FORCE = 'force'
|
||||||
|
|
||||||
|
SERVICE_ADD_URL = 'add_url'
|
||||||
|
SERVICE_DISABLE_URL = 'disable_url'
|
||||||
|
SERVICE_ENABLE_URL = 'enable_url'
|
||||||
|
SERVICE_REFRESH = 'refresh'
|
||||||
|
SERVICE_REMOVE_URL = 'remove_url'
|
13
homeassistant/components/adguard/manifest.json
Normal file
13
homeassistant/components/adguard/manifest.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"domain": "adguard",
|
||||||
|
"name": "AdGuard Home",
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/components/adguard",
|
||||||
|
"requirements": [
|
||||||
|
"adguardhome==0.2.0"
|
||||||
|
],
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": [
|
||||||
|
"@frenck"
|
||||||
|
]
|
||||||
|
}
|
232
homeassistant/components/adguard/sensor.py
Normal file
232
homeassistant/components/adguard/sensor.py
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
"""Support for AdGuard Home sensors."""
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from adguardhome import AdGuardHomeConnectionError
|
||||||
|
|
||||||
|
from homeassistant.components.adguard import AdGuardHomeDeviceEntity
|
||||||
|
from homeassistant.components.adguard.const import (
|
||||||
|
DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=300)
|
||||||
|
PARALLEL_UPDATES = 4
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
||||||
|
) -> None:
|
||||||
|
"""Set up AdGuard Home sensor based on a config entry."""
|
||||||
|
adguard = hass.data[DOMAIN][DATA_ADGUARD_CLIENT]
|
||||||
|
|
||||||
|
try:
|
||||||
|
version = await adguard.version()
|
||||||
|
except AdGuardHomeConnectionError as exception:
|
||||||
|
raise PlatformNotReady from exception
|
||||||
|
|
||||||
|
hass.data[DOMAIN][DATA_ADGUARD_VERION] = version
|
||||||
|
|
||||||
|
sensors = [
|
||||||
|
AdGuardHomeDNSQueriesSensor(adguard),
|
||||||
|
AdGuardHomeBlockedFilteringSensor(adguard),
|
||||||
|
AdGuardHomePercentageBlockedSensor(adguard),
|
||||||
|
AdGuardHomeReplacedParentalSensor(adguard),
|
||||||
|
AdGuardHomeReplacedSafeBrowsingSensor(adguard),
|
||||||
|
AdGuardHomeReplacedSafeSearchSensor(adguard),
|
||||||
|
AdGuardHomeAverageProcessingTimeSensor(adguard),
|
||||||
|
AdGuardHomeRulesCountSensor(adguard),
|
||||||
|
]
|
||||||
|
|
||||||
|
async_add_entities(sensors, True)
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeSensor(AdGuardHomeDeviceEntity):
|
||||||
|
"""Defines a AdGuard Home sensor."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
adguard,
|
||||||
|
name: str,
|
||||||
|
icon: str,
|
||||||
|
measurement: str,
|
||||||
|
unit_of_measurement: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize AdGuard Home sensor."""
|
||||||
|
self._state = None
|
||||||
|
self._unit_of_measurement = unit_of_measurement
|
||||||
|
self.measurement = measurement
|
||||||
|
|
||||||
|
super().__init__(adguard, name, icon)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return the unique ID for this sensor."""
|
||||||
|
return '_'.join(
|
||||||
|
[
|
||||||
|
DOMAIN,
|
||||||
|
self.adguard.host,
|
||||||
|
str(self.adguard.port),
|
||||||
|
'sensor',
|
||||||
|
self.measurement,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self) -> str:
|
||||||
|
"""Return the unit this state is expressed in."""
|
||||||
|
return self._unit_of_measurement
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeDNSQueriesSensor(AdGuardHomeSensor):
|
||||||
|
"""Defines a AdGuard Home DNS Queries sensor."""
|
||||||
|
|
||||||
|
def __init__(self, adguard):
|
||||||
|
"""Initialize AdGuard Home sensor."""
|
||||||
|
super().__init__(
|
||||||
|
adguard,
|
||||||
|
'AdGuard DNS Queries',
|
||||||
|
'mdi:magnify',
|
||||||
|
'dns_queries',
|
||||||
|
'queries',
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.stats.dns_queries()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeBlockedFilteringSensor(AdGuardHomeSensor):
|
||||||
|
"""Defines a AdGuard Home blocked by filtering sensor."""
|
||||||
|
|
||||||
|
def __init__(self, adguard):
|
||||||
|
"""Initialize AdGuard Home sensor."""
|
||||||
|
super().__init__(
|
||||||
|
adguard,
|
||||||
|
'AdGuard DNS Queries Blocked',
|
||||||
|
'mdi:magnify-close',
|
||||||
|
'blocked_filtering',
|
||||||
|
'queries',
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.stats.blocked_filtering()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor):
|
||||||
|
"""Defines a AdGuard Home blocked percentage sensor."""
|
||||||
|
|
||||||
|
def __init__(self, adguard):
|
||||||
|
"""Initialize AdGuard Home sensor."""
|
||||||
|
super().__init__(
|
||||||
|
adguard,
|
||||||
|
'AdGuard DNS Queries Blocked Ratio',
|
||||||
|
'mdi:magnify-close',
|
||||||
|
'blocked_percentage',
|
||||||
|
'%',
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
percentage = await self.adguard.stats.blocked_percentage()
|
||||||
|
self._state = "{:.2f}".format(percentage)
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor):
|
||||||
|
"""Defines a AdGuard Home replaced by parental control sensor."""
|
||||||
|
|
||||||
|
def __init__(self, adguard):
|
||||||
|
"""Initialize AdGuard Home sensor."""
|
||||||
|
super().__init__(
|
||||||
|
adguard,
|
||||||
|
'AdGuard Parental Control Blocked',
|
||||||
|
'mdi:human-male-girl',
|
||||||
|
'blocked_parental',
|
||||||
|
'requests',
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.stats.replaced_parental()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeReplacedSafeBrowsingSensor(AdGuardHomeSensor):
|
||||||
|
"""Defines a AdGuard Home replaced by safe browsing sensor."""
|
||||||
|
|
||||||
|
def __init__(self, adguard):
|
||||||
|
"""Initialize AdGuard Home sensor."""
|
||||||
|
super().__init__(
|
||||||
|
adguard,
|
||||||
|
'AdGuard Safe Browsing Blocked',
|
||||||
|
'mdi:shield-half-full',
|
||||||
|
'blocked_safebrowsing',
|
||||||
|
'requests',
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.stats.replaced_safebrowsing()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor):
|
||||||
|
"""Defines a AdGuard Home replaced by safe search sensor."""
|
||||||
|
|
||||||
|
def __init__(self, adguard):
|
||||||
|
"""Initialize AdGuard Home sensor."""
|
||||||
|
super().__init__(
|
||||||
|
adguard,
|
||||||
|
'Searches Safe Search Enforced',
|
||||||
|
'mdi:shield-search',
|
||||||
|
'enforced_safesearch',
|
||||||
|
'requests',
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.stats.replaced_safesearch()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor):
|
||||||
|
"""Defines a AdGuard Home average processing time sensor."""
|
||||||
|
|
||||||
|
def __init__(self, adguard):
|
||||||
|
"""Initialize AdGuard Home sensor."""
|
||||||
|
super().__init__(
|
||||||
|
adguard,
|
||||||
|
'AdGuard Average Processing Speed',
|
||||||
|
'mdi:speedometer',
|
||||||
|
'average_speed',
|
||||||
|
'ms',
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
average = await self.adguard.stats.avg_processing_time()
|
||||||
|
self._state = "{:.2f}".format(average)
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeRulesCountSensor(AdGuardHomeSensor):
|
||||||
|
"""Defines a AdGuard Home rules count sensor."""
|
||||||
|
|
||||||
|
def __init__(self, adguard):
|
||||||
|
"""Initialize AdGuard Home sensor."""
|
||||||
|
super().__init__(
|
||||||
|
adguard,
|
||||||
|
'AdGuard Rules Count',
|
||||||
|
'mdi:counter',
|
||||||
|
'rules_count',
|
||||||
|
'rules',
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.filtering.rules_count()
|
37
homeassistant/components/adguard/services.yaml
Normal file
37
homeassistant/components/adguard/services.yaml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
add_url:
|
||||||
|
description: Add a new filter subscription to AdGuard Home.
|
||||||
|
fields:
|
||||||
|
name:
|
||||||
|
description: The name of the filter subscription.
|
||||||
|
example: Example
|
||||||
|
url:
|
||||||
|
description: The filter URL to subscribe to, containing the filter rules.
|
||||||
|
example: https://www.example.com/filter/1.txt
|
||||||
|
|
||||||
|
remove_url:
|
||||||
|
description: Removes a filter subscription from AdGuard Home.
|
||||||
|
fields:
|
||||||
|
url:
|
||||||
|
description: The filter subscription URL to remove.
|
||||||
|
example: https://www.example.com/filter/1.txt
|
||||||
|
|
||||||
|
enable_url:
|
||||||
|
description: Enables a filter subscription in AdGuard Home.
|
||||||
|
fields:
|
||||||
|
url:
|
||||||
|
description: The filter subscription URL to enable.
|
||||||
|
example: https://www.example.com/filter/1.txt
|
||||||
|
|
||||||
|
disable_url:
|
||||||
|
description: Disables a filter subscription in AdGuard Home.
|
||||||
|
fields:
|
||||||
|
url:
|
||||||
|
description: The filter subscription URL to disable.
|
||||||
|
example: https://www.example.com/filter/1.txt
|
||||||
|
|
||||||
|
refresh:
|
||||||
|
description: Refresh all filter subscriptions in AdGuard Home.
|
||||||
|
fields:
|
||||||
|
force:
|
||||||
|
description: Force update (by passes AdGuard Home throttling).
|
||||||
|
example: '"true" to force, "false" or omit for a regular refresh.'
|
29
homeassistant/components/adguard/strings.json
Normal file
29
homeassistant/components/adguard/strings.json
Normal file
|
@ -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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
233
homeassistant/components/adguard/switch.py
Normal file
233
homeassistant/components/adguard/switch.py
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
"""Support for AdGuard Home switches."""
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from adguardhome import AdGuardHomeConnectionError, AdGuardHomeError
|
||||||
|
|
||||||
|
from homeassistant.components.adguard import AdGuardHomeDeviceEntity
|
||||||
|
from homeassistant.components.adguard.const import (
|
||||||
|
DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERION, DOMAIN)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=10)
|
||||||
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
||||||
|
) -> None:
|
||||||
|
"""Set up AdGuard Home switch based on a config entry."""
|
||||||
|
adguard = hass.data[DOMAIN][DATA_ADGUARD_CLIENT]
|
||||||
|
|
||||||
|
try:
|
||||||
|
version = await adguard.version()
|
||||||
|
except AdGuardHomeConnectionError as exception:
|
||||||
|
raise PlatformNotReady from exception
|
||||||
|
|
||||||
|
hass.data[DOMAIN][DATA_ADGUARD_VERION] = version
|
||||||
|
|
||||||
|
switches = [
|
||||||
|
AdGuardHomeProtectionSwitch(adguard),
|
||||||
|
AdGuardHomeFilteringSwitch(adguard),
|
||||||
|
AdGuardHomeParentalSwitch(adguard),
|
||||||
|
AdGuardHomeSafeBrowsingSwitch(adguard),
|
||||||
|
AdGuardHomeSafeSearchSwitch(adguard),
|
||||||
|
AdGuardHomeQueryLogSwitch(adguard),
|
||||||
|
]
|
||||||
|
async_add_entities(switches, True)
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeSwitch(ToggleEntity, AdGuardHomeDeviceEntity):
|
||||||
|
"""Defines a AdGuard Home switch."""
|
||||||
|
|
||||||
|
def __init__(self, adguard, name: str, icon: str, key: str):
|
||||||
|
"""Initialize AdGuard Home switch."""
|
||||||
|
self._state = False
|
||||||
|
self._key = key
|
||||||
|
super().__init__(adguard, name, icon)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return the unique ID for this sensor."""
|
||||||
|
return '_'.join(
|
||||||
|
[
|
||||||
|
DOMAIN,
|
||||||
|
self.adguard.host,
|
||||||
|
str(self.adguard.port),
|
||||||
|
'switch',
|
||||||
|
self._key,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return the state of the switch."""
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs) -> None:
|
||||||
|
"""Turn off the switch."""
|
||||||
|
try:
|
||||||
|
await self._adguard_turn_off()
|
||||||
|
except AdGuardHomeError:
|
||||||
|
_LOGGER.error(
|
||||||
|
"An error occurred while turning off AdGuard Home switch."
|
||||||
|
)
|
||||||
|
self._available = False
|
||||||
|
|
||||||
|
async def _adguard_turn_off(self) -> None:
|
||||||
|
"""Turn off the switch."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs) -> None:
|
||||||
|
"""Turn on the switch."""
|
||||||
|
try:
|
||||||
|
await self._adguard_turn_on()
|
||||||
|
except AdGuardHomeError:
|
||||||
|
_LOGGER.error(
|
||||||
|
"An error occurred while turning on AdGuard Home switch."
|
||||||
|
)
|
||||||
|
self._available = False
|
||||||
|
|
||||||
|
async def _adguard_turn_on(self) -> None:
|
||||||
|
"""Turn on the switch."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeProtectionSwitch(AdGuardHomeSwitch):
|
||||||
|
"""Defines a AdGuard Home protection switch."""
|
||||||
|
|
||||||
|
def __init__(self, adguard) -> None:
|
||||||
|
"""Initialize AdGuard Home switch."""
|
||||||
|
super().__init__(
|
||||||
|
adguard, "AdGuard Protection", 'mdi:shield-check', 'protection'
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_turn_off(self) -> None:
|
||||||
|
"""Turn off the switch."""
|
||||||
|
await self.adguard.disable_protection()
|
||||||
|
|
||||||
|
async def _adguard_turn_on(self) -> None:
|
||||||
|
"""Turn on the switch."""
|
||||||
|
await self.adguard.enable_protection()
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.protection_enabled()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeParentalSwitch(AdGuardHomeSwitch):
|
||||||
|
"""Defines a AdGuard Home parental control switch."""
|
||||||
|
|
||||||
|
def __init__(self, adguard) -> None:
|
||||||
|
"""Initialize AdGuard Home switch."""
|
||||||
|
super().__init__(
|
||||||
|
adguard, "AdGuard Parental Control", 'mdi:shield-check', 'parental'
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_turn_off(self) -> None:
|
||||||
|
"""Turn off the switch."""
|
||||||
|
await self.adguard.parental.disable()
|
||||||
|
|
||||||
|
async def _adguard_turn_on(self) -> None:
|
||||||
|
"""Turn on the switch."""
|
||||||
|
await self.adguard.parental.enable()
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.parental.enabled()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeSafeSearchSwitch(AdGuardHomeSwitch):
|
||||||
|
"""Defines a AdGuard Home safe search switch."""
|
||||||
|
|
||||||
|
def __init__(self, adguard) -> None:
|
||||||
|
"""Initialize AdGuard Home switch."""
|
||||||
|
super().__init__(
|
||||||
|
adguard, "AdGuard Safe Search", 'mdi:shield-check', 'safesearch'
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_turn_off(self) -> None:
|
||||||
|
"""Turn off the switch."""
|
||||||
|
await self.adguard.safesearch.disable()
|
||||||
|
|
||||||
|
async def _adguard_turn_on(self) -> None:
|
||||||
|
"""Turn on the switch."""
|
||||||
|
await self.adguard.safesearch.enable()
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.safesearch.enabled()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeSafeBrowsingSwitch(AdGuardHomeSwitch):
|
||||||
|
"""Defines a AdGuard Home safe search switch."""
|
||||||
|
|
||||||
|
def __init__(self, adguard) -> None:
|
||||||
|
"""Initialize AdGuard Home switch."""
|
||||||
|
super().__init__(
|
||||||
|
adguard,
|
||||||
|
"AdGuard Safe Browsing",
|
||||||
|
'mdi:shield-check',
|
||||||
|
'safebrowsing',
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_turn_off(self) -> None:
|
||||||
|
"""Turn off the switch."""
|
||||||
|
await self.adguard.safebrowsing.disable()
|
||||||
|
|
||||||
|
async def _adguard_turn_on(self) -> None:
|
||||||
|
"""Turn on the switch."""
|
||||||
|
await self.adguard.safebrowsing.enable()
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.safebrowsing.enabled()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeFilteringSwitch(AdGuardHomeSwitch):
|
||||||
|
"""Defines a AdGuard Home filtering switch."""
|
||||||
|
|
||||||
|
def __init__(self, adguard) -> None:
|
||||||
|
"""Initialize AdGuard Home switch."""
|
||||||
|
super().__init__(
|
||||||
|
adguard, "AdGuard Filtering", 'mdi:shield-check', 'filtering'
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_turn_off(self) -> None:
|
||||||
|
"""Turn off the switch."""
|
||||||
|
await self.adguard.filtering.disable()
|
||||||
|
|
||||||
|
async def _adguard_turn_on(self) -> None:
|
||||||
|
"""Turn on the switch."""
|
||||||
|
await self.adguard.filtering.enable()
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.filtering.enabled()
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeQueryLogSwitch(AdGuardHomeSwitch):
|
||||||
|
"""Defines a AdGuard Home query log switch."""
|
||||||
|
|
||||||
|
def __init__(self, adguard) -> None:
|
||||||
|
"""Initialize AdGuard Home switch."""
|
||||||
|
super().__init__(
|
||||||
|
adguard, "AdGuard Query Log", 'mdi:shield-check', 'querylog'
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_turn_off(self) -> None:
|
||||||
|
"""Turn off the switch."""
|
||||||
|
await self.adguard.querylog.disable()
|
||||||
|
|
||||||
|
async def _adguard_turn_on(self) -> None:
|
||||||
|
"""Turn on the switch."""
|
||||||
|
await self.adguard.querylog.enable()
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
self._state = await self.adguard.querylog.enabled()
|
|
@ -5,6 +5,7 @@ To update, run python3 -m script.hassfest
|
||||||
|
|
||||||
|
|
||||||
FLOWS = [
|
FLOWS = [
|
||||||
|
"adguard",
|
||||||
"ambiclimate",
|
"ambiclimate",
|
||||||
"ambient_station",
|
"ambient_station",
|
||||||
"axis",
|
"axis",
|
||||||
|
|
|
@ -107,6 +107,9 @@ adafruit-blinka==1.2.1
|
||||||
# homeassistant.components.mcp23017
|
# homeassistant.components.mcp23017
|
||||||
adafruit-circuitpython-mcp230xx==1.1.2
|
adafruit-circuitpython-mcp230xx==1.1.2
|
||||||
|
|
||||||
|
# homeassistant.components.adguard
|
||||||
|
adguardhome==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.frontier_silicon
|
# homeassistant.components.frontier_silicon
|
||||||
afsapi==0.0.4
|
afsapi==0.0.4
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,9 @@ PyTransportNSW==0.1.1
|
||||||
# homeassistant.components.yessssms
|
# homeassistant.components.yessssms
|
||||||
YesssSMS==0.2.3
|
YesssSMS==0.2.3
|
||||||
|
|
||||||
|
# homeassistant.components.adguard
|
||||||
|
adguardhome==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
aioambient==0.3.0
|
aioambient==0.3.0
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ COMMENT_REQUIREMENTS = (
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_REQUIREMENTS = (
|
TEST_REQUIREMENTS = (
|
||||||
|
'adguardhome',
|
||||||
'ambiclimate',
|
'ambiclimate',
|
||||||
'aioambient',
|
'aioambient',
|
||||||
'aioautomatic',
|
'aioautomatic',
|
||||||
|
|
1
tests/components/adguard/__init__.py
Normal file
1
tests/components/adguard/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for the AdGuard Home component."""
|
167
tests/components/adguard/test_config_flow.py
Normal file
167
tests/components/adguard/test_config_flow.py
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
"""Tests for the AdGuard Home config flow."""
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
|
from homeassistant.components.adguard import config_flow
|
||||||
|
from homeassistant.components.adguard.const import DOMAIN
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME,
|
||||||
|
CONF_VERIFY_SSL)
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
FIXTURE_USER_INPUT = {
|
||||||
|
CONF_HOST: '127.0.0.1',
|
||||||
|
CONF_PORT: 3000,
|
||||||
|
CONF_USERNAME: 'user',
|
||||||
|
CONF_PASSWORD: 'pass',
|
||||||
|
CONF_SSL: True,
|
||||||
|
CONF_VERIFY_SSL: True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_show_authenticate_form(hass):
|
||||||
|
"""Test that the setup form is served."""
|
||||||
|
flow = config_flow.AdGuardHomeFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
result = await flow.async_step_user(user_input=None)
|
||||||
|
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result['step_id'] == 'user'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_connection_error(hass, aioclient_mock):
|
||||||
|
"""Test we show user form on AdGuard Home connection error."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
"{}://{}:{}/control/status".format(
|
||||||
|
'https' if FIXTURE_USER_INPUT[CONF_SSL] else 'http',
|
||||||
|
FIXTURE_USER_INPUT[CONF_HOST],
|
||||||
|
FIXTURE_USER_INPUT[CONF_PORT],
|
||||||
|
),
|
||||||
|
exc=aiohttp.ClientError,
|
||||||
|
)
|
||||||
|
|
||||||
|
flow = config_flow.AdGuardHomeFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
result = await flow.async_step_user(user_input=FIXTURE_USER_INPUT)
|
||||||
|
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result['step_id'] == 'user'
|
||||||
|
assert result['errors'] == {'base': 'connection_error'}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_full_flow_implementation(hass, aioclient_mock):
|
||||||
|
"""Test registering an integration and finishing flow works."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
"{}://{}:{}/control/status".format(
|
||||||
|
'https' if FIXTURE_USER_INPUT[CONF_SSL] else 'http',
|
||||||
|
FIXTURE_USER_INPUT[CONF_HOST],
|
||||||
|
FIXTURE_USER_INPUT[CONF_PORT],
|
||||||
|
),
|
||||||
|
json={'version': '1.0'},
|
||||||
|
headers={'Content-Type': 'application/json'},
|
||||||
|
)
|
||||||
|
|
||||||
|
flow = config_flow.AdGuardHomeFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
result = await flow.async_step_user(user_input=None)
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result['step_id'] == 'user'
|
||||||
|
|
||||||
|
result = await flow.async_step_user(user_input=FIXTURE_USER_INPUT)
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result['title'] == FIXTURE_USER_INPUT[CONF_HOST]
|
||||||
|
assert result['data'][CONF_HOST] == FIXTURE_USER_INPUT[CONF_HOST]
|
||||||
|
assert result['data'][CONF_PASSWORD] == FIXTURE_USER_INPUT[CONF_PASSWORD]
|
||||||
|
assert result['data'][CONF_PORT] == FIXTURE_USER_INPUT[CONF_PORT]
|
||||||
|
assert result['data'][CONF_SSL] == FIXTURE_USER_INPUT[CONF_SSL]
|
||||||
|
assert result['data'][CONF_USERNAME] == FIXTURE_USER_INPUT[CONF_USERNAME]
|
||||||
|
assert (
|
||||||
|
result['data'][CONF_VERIFY_SSL] == FIXTURE_USER_INPUT[CONF_VERIFY_SSL]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_integration_already_exists(hass):
|
||||||
|
"""Test we only allow a single config flow."""
|
||||||
|
MockConfigEntry(domain=DOMAIN).add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={'source': 'user'}
|
||||||
|
)
|
||||||
|
assert result['type'] == 'abort'
|
||||||
|
assert result['reason'] == 'single_instance_allowed'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_hassio_single_instance(hass):
|
||||||
|
"""Test we only allow a single config flow."""
|
||||||
|
MockConfigEntry(domain='adguard', data={'host': '1.2.3.4'}).add_to_hass(
|
||||||
|
hass
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
'adguard', context={'source': 'hassio'}
|
||||||
|
)
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result['reason'] == 'single_instance_allowed'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_hassio_confirm(hass, aioclient_mock):
|
||||||
|
"""Test we can finish a config flow."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://mock-adguard:3000/control/status",
|
||||||
|
json={'version': '1.0'},
|
||||||
|
headers={'Content-Type': 'application/json'},
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
'adguard',
|
||||||
|
data={
|
||||||
|
'addon': 'AdGuard Home Addon',
|
||||||
|
'host': 'mock-adguard',
|
||||||
|
'port': 3000,
|
||||||
|
},
|
||||||
|
context={'source': 'hassio'},
|
||||||
|
)
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result['step_id'] == 'hassio_confirm'
|
||||||
|
assert result['description_placeholders'] == {
|
||||||
|
'addon': 'AdGuard Home Addon'
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result['flow_id'], {}
|
||||||
|
)
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result['title'] == 'AdGuard Home Addon'
|
||||||
|
assert result['data'][CONF_HOST] == 'mock-adguard'
|
||||||
|
assert result['data'][CONF_PASSWORD] is None
|
||||||
|
assert result['data'][CONF_PORT] == 3000
|
||||||
|
assert result['data'][CONF_SSL] is False
|
||||||
|
assert result['data'][CONF_USERNAME] is None
|
||||||
|
assert result['data'][CONF_VERIFY_SSL]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_hassio_connection_error(hass, aioclient_mock):
|
||||||
|
"""Test we show hassio confirm form on AdGuard Home connection error."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://mock-adguard:3000/control/status",
|
||||||
|
exc=aiohttp.ClientError,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
'adguard',
|
||||||
|
data={
|
||||||
|
'addon': 'AdGuard Home Addon',
|
||||||
|
'host': 'mock-adguard',
|
||||||
|
'port': 3000,
|
||||||
|
},
|
||||||
|
context={'source': 'hassio'},
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result['flow_id'], {}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result['step_id'] == 'hassio_confirm'
|
||||||
|
assert result['errors'] == {'base': 'connection_error'}
|
Loading…
Add table
Add a link
Reference in a new issue