BIN
.gitea/.DS_Store
vendored
Normal file
BIN
.gitea/.DS_Store
vendored
Normal file
Binary file not shown.
54
.gitea/workflows/release.yml
Normal file
54
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements_test.txt
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
python -m pytest tests/ -v
|
||||||
|
|
||||||
|
- name: Update version in manifest
|
||||||
|
run: |
|
||||||
|
python -c "
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
with open('custom_components/adguard_control_hub/manifest.json', 'r') as f:
|
||||||
|
manifest = json.load(f)
|
||||||
|
manifest['version'] = os.environ['GITHUB_REF_NAME'].lstrip('v')
|
||||||
|
with open('custom_components/adguard_control_hub/manifest.json', 'w') as f:
|
||||||
|
json.dump(manifest, f, indent=2)
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Create ZIP archive
|
||||||
|
run: |
|
||||||
|
cd custom_components/adguard_control_hub/
|
||||||
|
zip -r ../../adguard-control-hub.zip .
|
||||||
|
cd ../..
|
||||||
|
|
||||||
|
- name: Upload release asset
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ github.event.release.upload_url }}
|
||||||
|
asset_path: ./adguard-control-hub.zip
|
||||||
|
asset_name: adguard-control-hub.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
80
.gitea/workflows/test.yml
Normal file
80
.gitea/workflows/test.yml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ['3.13']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements_test.txt
|
||||||
|
|
||||||
|
- name: Run pytest
|
||||||
|
run: |
|
||||||
|
python -m pytest tests/ -v --tb=short
|
||||||
|
|
||||||
|
- name: Validate manifest
|
||||||
|
run: |
|
||||||
|
python -c "import json; json.load(open('custom_components/adguard_control_hub/manifest.json'))"
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install black isort flake8 mypy
|
||||||
|
|
||||||
|
- name: Run black
|
||||||
|
run: |
|
||||||
|
black --check --diff custom_components/
|
||||||
|
|
||||||
|
- name: Run isort
|
||||||
|
run: |
|
||||||
|
isort --check-only --diff custom_components/
|
||||||
|
|
||||||
|
- name: Run flake8
|
||||||
|
run: |
|
||||||
|
flake8 custom_components/ --max-line-length=88 --extend-ignore=E203,W503
|
||||||
|
|
||||||
|
- name: Run mypy
|
||||||
|
run: |
|
||||||
|
mypy custom_components/
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
hacs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: HACS validation
|
||||||
|
uses: hacs/action@main
|
||||||
|
with:
|
||||||
|
category: integration
|
||||||
127
README.md
Normal file
127
README.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# AdGuard Control Hub
|
||||||
|
|
||||||
|
[![GitHub Release][releases-shield]][releases]
|
||||||
|
[![GitHub Activity][commits-shield]][commits]
|
||||||
|
[![License][license-shield]](LICENSE)
|
||||||
|
[![hacs][hacsbadge]][hacs]
|
||||||
|
|
||||||
|
A comprehensive Home Assistant integration for managing your AdGuard Home instance with advanced control and monitoring capabilities.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Switches
|
||||||
|
- **AdGuard Protection**: Master switch that controls all AdGuard features
|
||||||
|
- **DNS Filtering**: Enables DNS filtering using blocklists
|
||||||
|
- **Safe Browsing**: Blocks known phishing and malware sites
|
||||||
|
- **Parental Control**: Blocks adult content
|
||||||
|
- **Safe Search**: Enforces safe search on search engines
|
||||||
|
- **Query Log**: Records DNS queries for statistics
|
||||||
|
|
||||||
|
### Sensors
|
||||||
|
- **DNS Queries**: Total number of DNS queries processed
|
||||||
|
- **Blocked Queries**: Number of queries blocked by filtering
|
||||||
|
- **Blocked Percentage**: Percentage of queries that were blocked
|
||||||
|
- **Active Filter Rules**: Number of active filtering rules loaded
|
||||||
|
- **Average Processing Time**: Average DNS query processing time
|
||||||
|
|
||||||
|
### Binary Sensors
|
||||||
|
- **AdGuard Home Running**: Shows if AdGuard Home is responsive
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### HACS (Recommended)
|
||||||
|
|
||||||
|
1. Install [HACS](https://hacs.xyz/) if you haven't already
|
||||||
|
2. In HACS, go to "Integrations"
|
||||||
|
3. Click the "+" button and search for "AdGuard Control Hub"
|
||||||
|
4. Click "Install"
|
||||||
|
5. Restart Home Assistant
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
1. Download the latest release from the [releases page](https://github.com/your-username/adguard-control-hub/releases)
|
||||||
|
2. Extract the `adguard-control-hub.zip` file
|
||||||
|
3. Copy the `custom_components/adguard_control_hub` folder to your Home Assistant's `custom_components` directory
|
||||||
|
4. Restart Home Assistant
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
1. In Home Assistant, go to **Settings** → **Devices & Services**
|
||||||
|
2. Click **"+ ADD INTEGRATION"**
|
||||||
|
3. Search for "AdGuard Control Hub" and select it
|
||||||
|
4. Fill in your AdGuard Home connection details:
|
||||||
|
- **Host**: IP address or hostname of your AdGuard Home instance
|
||||||
|
- **Port**: Port number (default: 3000)
|
||||||
|
- **Username**: Admin username (if authentication is enabled)
|
||||||
|
- **Password**: Admin password (if authentication is enabled)
|
||||||
|
- **Use HTTPS**: Enable if using HTTPS
|
||||||
|
- **Verify SSL**: Verify SSL certificates
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- AdGuard Home v0.107.0 or newer
|
||||||
|
- Home Assistant 2023.5.0 or newer
|
||||||
|
- Network access to your AdGuard Home instance
|
||||||
|
|
||||||
|
## Supported AdGuard Home Versions
|
||||||
|
|
||||||
|
This integration has been tested with:
|
||||||
|
- AdGuard Home v0.107.50+
|
||||||
|
- AdGuard Home v0.108.x
|
||||||
|
- AdGuard Home v0.109.x
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Connection Issues
|
||||||
|
- Ensure AdGuard Home is running and accessible from Home Assistant
|
||||||
|
- Check firewall settings on the AdGuard Home host
|
||||||
|
- Verify the correct port is being used
|
||||||
|
- Test connection manually using curl: `curl http://your-adguard-ip:port/control/status`
|
||||||
|
|
||||||
|
### Authentication Issues
|
||||||
|
- Verify username and password are correct
|
||||||
|
- Ensure the user account has admin privileges in AdGuard Home
|
||||||
|
- Try connecting without authentication first to isolate the issue
|
||||||
|
|
||||||
|
### Feature Not Working
|
||||||
|
- Check AdGuard Home logs for any error messages
|
||||||
|
- Verify the specific feature is enabled in AdGuard Home
|
||||||
|
- Some features require specific AdGuard Home configurations
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Setting up Development Environment
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
2. Install development dependencies: `pip install -r requirements_test.txt`
|
||||||
|
3. Run tests: `pytest tests/`
|
||||||
|
4. Run linting: `black custom_components/ && isort custom_components/`
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Make your changes
|
||||||
|
4. Add tests for new functionality
|
||||||
|
5. Ensure all tests pass
|
||||||
|
6. Submit a pull request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
- AdGuard Team for creating AdGuard Home
|
||||||
|
- Home Assistant community for the integration framework
|
||||||
|
- All contributors and testers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[releases-shield]: https://img.shields.io/github/release/your-username/adguard-control-hub.svg?style=for-the-badge
|
||||||
|
[releases]: https://github.com/your-username/adguard-control-hub/releases
|
||||||
|
[commits-shield]: https://img.shields.io/github/commit-activity/y/your-username/adguard-control-hub.svg?style=for-the-badge
|
||||||
|
[commits]: https://github.com/your-username/adguard-control-hub/commits/main
|
||||||
|
[license-shield]: https://img.shields.io/github/license/your-username/adguard-control-hub.svg?style=for-the-badge
|
||||||
|
[hacs]: https://github.com/hacs/integration
|
||||||
|
[hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge
|
||||||
60
custom_components/adguard_control_hub/__init__.py
Normal file
60
custom_components/adguard_control_hub/__init__.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
"""The AdGuard Control Hub integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import AdGuardDataUpdateCoordinator
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PLATFORMS: list[Platform] = [
|
||||||
|
Platform.SWITCH,
|
||||||
|
Platform.SENSOR,
|
||||||
|
Platform.BINARY_SENSOR,
|
||||||
|
]
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=30)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up AdGuard Control Hub from a config entry."""
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
|
||||||
|
coordinator = AdGuardDataUpdateCoordinator(
|
||||||
|
hass,
|
||||||
|
session,
|
||||||
|
entry.data,
|
||||||
|
SCAN_INTERVAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test connection
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||||
|
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
|
"""Reload config entry."""
|
||||||
|
await async_unload_entry(hass, entry)
|
||||||
|
await async_setup_entry(hass, entry)
|
||||||
154
custom_components/adguard_control_hub/api.py
Normal file
154
custom_components/adguard_control_hub/api.py
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
"""AdGuard Home API client."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import async_timeout
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
API_CLIENTS,
|
||||||
|
API_DNS_CONFIG,
|
||||||
|
API_STATUS,
|
||||||
|
API_STATS,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeApiError(Exception):
|
||||||
|
"""Exception to indicate a general API error."""
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeConnectionError(AdGuardHomeApiError):
|
||||||
|
"""Exception to indicate a connection error."""
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeAuthError(AdGuardHomeApiError):
|
||||||
|
"""Exception to indicate an authentication error."""
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeAPI:
|
||||||
|
"""AdGuard Home API client."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
session: aiohttp.ClientSession,
|
||||||
|
host: str,
|
||||||
|
port: int,
|
||||||
|
username: str | None = None,
|
||||||
|
password: str | None = None,
|
||||||
|
ssl: bool = False,
|
||||||
|
verify_ssl: bool = True,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the API client."""
|
||||||
|
self._session = session
|
||||||
|
self._host = host
|
||||||
|
self._port = port
|
||||||
|
self._username = username
|
||||||
|
self._password = password
|
||||||
|
self._ssl = ssl
|
||||||
|
self._verify_ssl = verify_ssl
|
||||||
|
|
||||||
|
protocol = "https" if ssl else "http"
|
||||||
|
self._base_url = f"{protocol}://{host}:{port}"
|
||||||
|
|
||||||
|
async def _request(
|
||||||
|
self,
|
||||||
|
method: str,
|
||||||
|
endpoint: str,
|
||||||
|
data: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Make a request to the AdGuard Home API."""
|
||||||
|
url = f"{self._base_url}{endpoint}"
|
||||||
|
auth = None
|
||||||
|
|
||||||
|
if self._username and self._password:
|
||||||
|
auth = aiohttp.BasicAuth(self._username, self._password)
|
||||||
|
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(10):
|
||||||
|
async with self._session.request(
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
json=data,
|
||||||
|
auth=auth,
|
||||||
|
headers=headers,
|
||||||
|
ssl=self._verify_ssl,
|
||||||
|
) as response:
|
||||||
|
if response.status == 401:
|
||||||
|
raise AdGuardHomeAuthError("Authentication failed")
|
||||||
|
|
||||||
|
if response.status == 403:
|
||||||
|
raise AdGuardHomeAuthError("Access forbidden")
|
||||||
|
|
||||||
|
if response.status not in (200, 204):
|
||||||
|
text = await response.text()
|
||||||
|
raise AdGuardHomeApiError(
|
||||||
|
f"Request failed with status {response.status}: {text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status == 204:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
except asyncio.TimeoutError as err:
|
||||||
|
raise AdGuardHomeConnectionError("Timeout connecting to AdGuard Home") from err
|
||||||
|
except aiohttp.ClientError as err:
|
||||||
|
raise AdGuardHomeConnectionError(f"Error connecting to AdGuard Home: {err}") from err
|
||||||
|
|
||||||
|
async def get_status(self) -> dict[str, Any]:
|
||||||
|
"""Get AdGuard Home status."""
|
||||||
|
return await self._request("GET", API_STATUS)
|
||||||
|
|
||||||
|
async def get_stats(self) -> dict[str, Any]:
|
||||||
|
"""Get AdGuard Home statistics."""
|
||||||
|
return await self._request("GET", API_STATS)
|
||||||
|
|
||||||
|
async def get_clients(self) -> dict[str, Any]:
|
||||||
|
"""Get AdGuard Home clients."""
|
||||||
|
return await self._request("GET", API_CLIENTS)
|
||||||
|
|
||||||
|
async def set_protection(self, enabled: bool) -> None:
|
||||||
|
"""Enable or disable protection."""
|
||||||
|
data = {"protection_enabled": enabled}
|
||||||
|
await self._request("POST", API_DNS_CONFIG, data)
|
||||||
|
|
||||||
|
async def set_filtering(self, enabled: bool) -> None:
|
||||||
|
"""Enable or disable filtering."""
|
||||||
|
data = {"filtering_enabled": enabled}
|
||||||
|
await self._request("POST", API_DNS_CONFIG, data)
|
||||||
|
|
||||||
|
async def set_safebrowsing(self, enabled: bool) -> None:
|
||||||
|
"""Enable or disable safe browsing."""
|
||||||
|
data = {"safebrowsing_enabled": enabled}
|
||||||
|
await self._request("POST", API_DNS_CONFIG, data)
|
||||||
|
|
||||||
|
async def set_parental_control(self, enabled: bool) -> None:
|
||||||
|
"""Enable or disable parental control."""
|
||||||
|
data = {"parental_enabled": enabled}
|
||||||
|
await self._request("POST", API_DNS_CONFIG, data)
|
||||||
|
|
||||||
|
async def set_safe_search(self, enabled: bool) -> None:
|
||||||
|
"""Enable or disable safe search."""
|
||||||
|
data = {"safesearch_enabled": enabled}
|
||||||
|
await self._request("POST", API_DNS_CONFIG, data)
|
||||||
|
|
||||||
|
async def set_query_log(self, enabled: bool) -> None:
|
||||||
|
"""Enable or disable query log."""
|
||||||
|
data = {"querylog_enabled": enabled}
|
||||||
|
await self._request("POST", API_DNS_CONFIG, data)
|
||||||
|
|
||||||
|
async def test_connection(self) -> bool:
|
||||||
|
"""Test connection to AdGuard Home."""
|
||||||
|
try:
|
||||||
|
await self.get_status()
|
||||||
|
return True
|
||||||
|
except AdGuardHomeApiError:
|
||||||
|
return False
|
||||||
80
custom_components/adguard_control_hub/binary_sensor.py
Normal file
80
custom_components/adguard_control_hub/binary_sensor.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
"""AdGuard Control Hub binary sensor platform."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import AdGuardDataUpdateCoordinator
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up AdGuard Control Hub binary sensor based on a config entry."""
|
||||||
|
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
entities = [
|
||||||
|
AdGuardBinarySensor(
|
||||||
|
coordinator,
|
||||||
|
entry,
|
||||||
|
"running",
|
||||||
|
"AdGuard Home Running",
|
||||||
|
"mdi:shield-check-outline",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardBinarySensor(CoordinatorEntity[AdGuardDataUpdateCoordinator], BinarySensorEntity):
|
||||||
|
"""Representation of an AdGuard Control Hub binary sensor."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: AdGuardDataUpdateCoordinator,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
sensor_type: str,
|
||||||
|
name: str,
|
||||||
|
icon: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the binary sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._sensor_type = sensor_type
|
||||||
|
self._entry = entry
|
||||||
|
|
||||||
|
self._attr_name = name
|
||||||
|
self._attr_icon = icon
|
||||||
|
self._attr_unique_id = f"{entry.entry_id}_{sensor_type}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self) -> DeviceInfo:
|
||||||
|
"""Return device information about this AdGuard Home instance."""
|
||||||
|
return DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self._entry.entry_id)},
|
||||||
|
name="AdGuard Control Hub",
|
||||||
|
manufacturer="AdGuard",
|
||||||
|
model="AdGuard Home",
|
||||||
|
sw_version=self.coordinator.data.get("status", {}).get("version"),
|
||||||
|
configuration_url=f"http://{self._entry.data['host']}:{self._entry.data['port']}",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool | None:
|
||||||
|
"""Return True if the binary sensor is on."""
|
||||||
|
if self.coordinator.data is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# If we can fetch data, AdGuard Home is running
|
||||||
|
return self.coordinator.data.get("status") is not None
|
||||||
100
custom_components/adguard_control_hub/config_flow.py
Normal file
100
custom_components/adguard_control_hub/config_flow.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"""Config flow for AdGuard Control Hub integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .api import AdGuardHomeAPI, AdGuardHomeApiError, AdGuardHomeAuthError, AdGuardHomeConnectionError
|
||||||
|
from .const import CONF_SSL, CONF_VERIFY_SSL, DEFAULT_NAME, DEFAULT_PORT, DEFAULT_SSL, DEFAULT_VERIFY_SSL, DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_HOST): str,
|
||||||
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
|
||||||
|
vol.Optional(CONF_USERNAME): str,
|
||||||
|
vol.Optional(CONF_PASSWORD): str,
|
||||||
|
vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool,
|
||||||
|
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Validate the user input allows us to connect."""
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
|
||||||
|
api = AdGuardHomeAPI(
|
||||||
|
session,
|
||||||
|
data[CONF_HOST],
|
||||||
|
data[CONF_PORT],
|
||||||
|
data.get(CONF_USERNAME),
|
||||||
|
data.get(CONF_PASSWORD),
|
||||||
|
data.get(CONF_SSL, DEFAULT_SSL),
|
||||||
|
data.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
status = await api.get_status()
|
||||||
|
return {"title": f"AdGuard Home ({data[CONF_HOST]})", "version": status.get("version", "Unknown")}
|
||||||
|
except AdGuardHomeConnectionError as err:
|
||||||
|
raise CannotConnect from err
|
||||||
|
except AdGuardHomeAuthError as err:
|
||||||
|
raise InvalidAuth from err
|
||||||
|
except AdGuardHomeApiError as err:
|
||||||
|
raise CannotConnect from err
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for AdGuard Control Hub."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle the initial step."""
|
||||||
|
if user_input is None:
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
|
||||||
|
)
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
info = await validate_input(self.hass, user_input)
|
||||||
|
except CannotConnect:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except InvalidAuth:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
# Create unique ID from host and port
|
||||||
|
unique_id = f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
|
||||||
|
await self.async_set_unique_id(unique_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=STEP_USER_DATA_SCHEMA, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CannotConnect(HomeAssistantError):
|
||||||
|
"""Error to indicate we cannot connect."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAuth(HomeAssistantError):
|
||||||
|
"""Error to indicate there is invalid auth."""
|
||||||
108
custom_components/adguard_control_hub/const.py
Normal file
108
custom_components/adguard_control_hub/const.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
"""Constants for the AdGuard Control Hub integration."""
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
|
DOMAIN = "adguard_control_hub"
|
||||||
|
DEFAULT_NAME = "AdGuard Control Hub"
|
||||||
|
DEFAULT_PORT = 3000
|
||||||
|
DEFAULT_SSL = False
|
||||||
|
DEFAULT_VERIFY_SSL = True
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
CONF_HOST = "host"
|
||||||
|
CONF_PORT = "port"
|
||||||
|
CONF_USERNAME = "username"
|
||||||
|
CONF_PASSWORD = "password"
|
||||||
|
CONF_SSL = "ssl"
|
||||||
|
CONF_VERIFY_SSL = "verify_ssl"
|
||||||
|
|
||||||
|
# AdGuard API endpoints
|
||||||
|
API_STATUS = "/control/status"
|
||||||
|
API_DNS_CONFIG = "/control/dns_config"
|
||||||
|
API_STATS = "/control/stats"
|
||||||
|
API_CLIENTS = "/control/clients"
|
||||||
|
API_REWRITE = "/control/rewrite"
|
||||||
|
API_FILTERING = "/control/filtering"
|
||||||
|
|
||||||
|
# Switch types
|
||||||
|
SWITCH_PROTECTION = "protection"
|
||||||
|
SWITCH_FILTERING = "filtering"
|
||||||
|
SWITCH_SAFEBROWSING = "safebrowsing"
|
||||||
|
SWITCH_PARENTAL = "parental"
|
||||||
|
SWITCH_SAFESEARCH = "safesearch"
|
||||||
|
SWITCH_QUERY_LOG = "query_log"
|
||||||
|
|
||||||
|
SWITCHES = {
|
||||||
|
SWITCH_PROTECTION: {
|
||||||
|
"name": "AdGuard Protection",
|
||||||
|
"icon": "mdi:shield-check",
|
||||||
|
"api_key": "protection_enabled",
|
||||||
|
},
|
||||||
|
SWITCH_FILTERING: {
|
||||||
|
"name": "DNS Filtering",
|
||||||
|
"icon": "mdi:filter",
|
||||||
|
"api_key": "filtering_enabled",
|
||||||
|
},
|
||||||
|
SWITCH_SAFEBROWSING: {
|
||||||
|
"name": "Safe Browsing",
|
||||||
|
"icon": "mdi:shield-bug",
|
||||||
|
"api_key": "safebrowsing_enabled",
|
||||||
|
},
|
||||||
|
SWITCH_PARENTAL: {
|
||||||
|
"name": "Parental Control",
|
||||||
|
"icon": "mdi:account-child-circle",
|
||||||
|
"api_key": "parental_enabled",
|
||||||
|
},
|
||||||
|
SWITCH_SAFESEARCH: {
|
||||||
|
"name": "Safe Search",
|
||||||
|
"icon": "mdi:shield-search",
|
||||||
|
"api_key": "safesearch_enabled",
|
||||||
|
},
|
||||||
|
SWITCH_QUERY_LOG: {
|
||||||
|
"name": "Query Log",
|
||||||
|
"icon": "mdi:file-document-multiple",
|
||||||
|
"api_key": "querylog_enabled",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sensor types
|
||||||
|
SENSOR_DNS_QUERIES = "dns_queries"
|
||||||
|
SENSOR_BLOCKED_QUERIES = "blocked_queries"
|
||||||
|
SENSOR_BLOCKED_PERCENTAGE = "blocked_percentage"
|
||||||
|
SENSOR_ACTIVE_FILTERS = "active_filters"
|
||||||
|
SENSOR_AVG_PROCESSING_TIME = "avg_processing_time"
|
||||||
|
|
||||||
|
SENSORS = {
|
||||||
|
SENSOR_DNS_QUERIES: {
|
||||||
|
"name": "DNS Queries",
|
||||||
|
"icon": "mdi:dns",
|
||||||
|
"unit": "queries",
|
||||||
|
"api_key": "num_dns_queries",
|
||||||
|
},
|
||||||
|
SENSOR_BLOCKED_QUERIES: {
|
||||||
|
"name": "Blocked Queries",
|
||||||
|
"icon": "mdi:shield-check",
|
||||||
|
"unit": "queries",
|
||||||
|
"api_key": "num_blocked_filtering",
|
||||||
|
},
|
||||||
|
SENSOR_BLOCKED_PERCENTAGE: {
|
||||||
|
"name": "Blocked Percentage",
|
||||||
|
"icon": "mdi:percent",
|
||||||
|
"unit": "%",
|
||||||
|
"api_key": "blocked_percentage",
|
||||||
|
},
|
||||||
|
SENSOR_ACTIVE_FILTERS: {
|
||||||
|
"name": "Active Filter Rules",
|
||||||
|
"icon": "mdi:filter-check",
|
||||||
|
"unit": "rules",
|
||||||
|
"api_key": "num_replaced_safebrowsing",
|
||||||
|
},
|
||||||
|
SENSOR_AVG_PROCESSING_TIME: {
|
||||||
|
"name": "Average Processing Time",
|
||||||
|
"icon": "mdi:clock-fast",
|
||||||
|
"unit": "ms",
|
||||||
|
"api_key": "avg_processing_time",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update interval
|
||||||
|
DEFAULT_SCAN_INTERVAL = 30
|
||||||
100
custom_components/adguard_control_hub/coordinator.py
Normal file
100
custom_components/adguard_control_hub/coordinator.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"""AdGuard Control Hub data update coordinator."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .api import AdGuardHomeAPI, AdGuardHomeApiError, AdGuardHomeAuthError, AdGuardHomeConnectionError
|
||||||
|
from .const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL, DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||||
|
"""Class to manage fetching AdGuard Home data from the API."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
session,
|
||||||
|
config: dict[str, Any],
|
||||||
|
update_interval: timedelta,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the data update coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name=DOMAIN,
|
||||||
|
update_interval=update_interval,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.api = AdGuardHomeAPI(
|
||||||
|
session,
|
||||||
|
config[CONF_HOST],
|
||||||
|
config[CONF_PORT],
|
||||||
|
config.get(CONF_USERNAME),
|
||||||
|
config.get(CONF_PASSWORD),
|
||||||
|
config.get(CONF_SSL, False),
|
||||||
|
config.get(CONF_VERIFY_SSL, True),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._config = config
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
|
"""Update data via library."""
|
||||||
|
try:
|
||||||
|
# Get status and stats data
|
||||||
|
status_data = await self.api.get_status()
|
||||||
|
stats_data = await self.api.get_stats()
|
||||||
|
clients_data = await self.api.get_clients()
|
||||||
|
|
||||||
|
# Combine all data
|
||||||
|
data = {
|
||||||
|
"status": status_data,
|
||||||
|
"stats": stats_data,
|
||||||
|
"clients": clients_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Calculate blocked percentage
|
||||||
|
if "stats" in data and "num_dns_queries" in data["stats"]:
|
||||||
|
queries = data["stats"].get("num_dns_queries", 0)
|
||||||
|
blocked = data["stats"].get("num_blocked_filtering", 0)
|
||||||
|
if queries > 0:
|
||||||
|
data["stats"]["blocked_percentage"] = round((blocked / queries) * 100, 2)
|
||||||
|
else:
|
||||||
|
data["stats"]["blocked_percentage"] = 0.0
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
except AdGuardHomeAuthError as err:
|
||||||
|
raise ConfigEntryAuthFailed("Authentication failed") from err
|
||||||
|
except (AdGuardHomeConnectionError, AdGuardHomeApiError) as err:
|
||||||
|
raise UpdateFailed(f"Error communicating with AdGuard Home: {err}") from err
|
||||||
|
|
||||||
|
async def async_set_switch(self, switch_type: str, state: bool) -> None:
|
||||||
|
"""Set switch state."""
|
||||||
|
try:
|
||||||
|
if switch_type == "protection":
|
||||||
|
await self.api.set_protection(state)
|
||||||
|
elif switch_type == "filtering":
|
||||||
|
await self.api.set_filtering(state)
|
||||||
|
elif switch_type == "safebrowsing":
|
||||||
|
await self.api.set_safebrowsing(state)
|
||||||
|
elif switch_type == "parental":
|
||||||
|
await self.api.set_parental_control(state)
|
||||||
|
elif switch_type == "safesearch":
|
||||||
|
await self.api.set_safe_search(state)
|
||||||
|
elif switch_type == "query_log":
|
||||||
|
await self.api.set_query_log(state)
|
||||||
|
|
||||||
|
# Refresh data after changing state
|
||||||
|
await self.async_request_refresh()
|
||||||
|
|
||||||
|
except (AdGuardHomeConnectionError, AdGuardHomeApiError) as err:
|
||||||
|
raise UpdateFailed(f"Error setting switch {switch_type}: {err}") from err
|
||||||
16
custom_components/adguard_control_hub/manifest.json
Normal file
16
custom_components/adguard_control_hub/manifest.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"domain": "adguard_control_hub",
|
||||||
|
"name": "AdGuard Control Hub",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"documentation": "https://github.com/your-username/adguard-control-hub",
|
||||||
|
"issue_tracker": "https://github.com/your-username/adguard-control-hub/issues",
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": [
|
||||||
|
"@your-username"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
"aiohttp>=3.8.0"
|
||||||
|
],
|
||||||
|
"config_flow": true,
|
||||||
|
"iot_class": "local_polling"
|
||||||
|
}
|
||||||
98
custom_components/adguard_control_hub/sensor.py
Normal file
98
custom_components/adguard_control_hub/sensor.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
"""AdGuard Control Hub sensor platform."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorEntity, SensorStateClass
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from .const import DOMAIN, SENSORS
|
||||||
|
from .coordinator import AdGuardDataUpdateCoordinator
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up AdGuard Control Hub sensor based on a config entry."""
|
||||||
|
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
entities = []
|
||||||
|
for sensor_type, sensor_info in SENSORS.items():
|
||||||
|
entities.append(
|
||||||
|
AdGuardSensor(
|
||||||
|
coordinator,
|
||||||
|
entry,
|
||||||
|
sensor_type,
|
||||||
|
sensor_info,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardSensor(CoordinatorEntity[AdGuardDataUpdateCoordinator], SensorEntity):
|
||||||
|
"""Representation of an AdGuard Control Hub sensor."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: AdGuardDataUpdateCoordinator,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
sensor_type: str,
|
||||||
|
sensor_info: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._sensor_type = sensor_type
|
||||||
|
self._sensor_info = sensor_info
|
||||||
|
self._entry = entry
|
||||||
|
|
||||||
|
self._attr_name = sensor_info["name"]
|
||||||
|
self._attr_icon = sensor_info["icon"]
|
||||||
|
self._attr_native_unit_of_measurement = sensor_info.get("unit")
|
||||||
|
self._attr_unique_id = f"{entry.entry_id}_{sensor_type}"
|
||||||
|
|
||||||
|
# Set state class for numeric sensors
|
||||||
|
if sensor_info.get("unit") in ["queries", "rules", "ms"]:
|
||||||
|
self._attr_state_class = SensorStateClass.MEASUREMENT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self) -> DeviceInfo:
|
||||||
|
"""Return device information about this AdGuard Home instance."""
|
||||||
|
return DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self._entry.entry_id)},
|
||||||
|
name="AdGuard Control Hub",
|
||||||
|
manufacturer="AdGuard",
|
||||||
|
model="AdGuard Home",
|
||||||
|
sw_version=self.coordinator.data.get("status", {}).get("version"),
|
||||||
|
configuration_url=f"http://{self._entry.data['host']}:{self._entry.data['port']}",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> str | int | float | None:
|
||||||
|
"""Return the native value of the sensor."""
|
||||||
|
if self.coordinator.data is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
stats_data = self.coordinator.data.get("stats", {})
|
||||||
|
api_key = self._sensor_info["api_key"]
|
||||||
|
|
||||||
|
value = stats_data.get(api_key)
|
||||||
|
|
||||||
|
# Handle special cases
|
||||||
|
if self._sensor_type == "blocked_percentage":
|
||||||
|
return value
|
||||||
|
elif self._sensor_type == "avg_processing_time":
|
||||||
|
# Convert to milliseconds if needed
|
||||||
|
if value is not None:
|
||||||
|
return round(value, 2)
|
||||||
|
|
||||||
|
return value
|
||||||
26
custom_components/adguard_control_hub/strings.json
Normal file
26
custom_components/adguard_control_hub/strings.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "AdGuard Control Hub Setup",
|
||||||
|
"description": "Configure connection to your AdGuard Home instance",
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Port",
|
||||||
|
"username": "Username (optional)",
|
||||||
|
"password": "Password (optional)",
|
||||||
|
"ssl": "Use HTTPS",
|
||||||
|
"verify_ssl": "Verify SSL certificate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect to AdGuard Home",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error occurred"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "AdGuard Home instance already configured"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
91
custom_components/adguard_control_hub/switch.py
Normal file
91
custom_components/adguard_control_hub/switch.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
"""AdGuard Control Hub switch platform."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.switch import SwitchEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from .const import DOMAIN, SWITCHES
|
||||||
|
from .coordinator import AdGuardDataUpdateCoordinator
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up AdGuard Control Hub switch based on a config entry."""
|
||||||
|
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
entities = []
|
||||||
|
for switch_type, switch_info in SWITCHES.items():
|
||||||
|
entities.append(
|
||||||
|
AdGuardSwitch(
|
||||||
|
coordinator,
|
||||||
|
entry,
|
||||||
|
switch_type,
|
||||||
|
switch_info,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardSwitch(CoordinatorEntity[AdGuardDataUpdateCoordinator], SwitchEntity):
|
||||||
|
"""Representation of an AdGuard Control Hub switch."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: AdGuardDataUpdateCoordinator,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
switch_type: str,
|
||||||
|
switch_info: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the switch."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._switch_type = switch_type
|
||||||
|
self._switch_info = switch_info
|
||||||
|
self._entry = entry
|
||||||
|
|
||||||
|
self._attr_name = switch_info["name"]
|
||||||
|
self._attr_icon = switch_info["icon"]
|
||||||
|
self._attr_unique_id = f"{entry.entry_id}_{switch_type}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self) -> DeviceInfo:
|
||||||
|
"""Return device information about this AdGuard Home instance."""
|
||||||
|
return DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, self._entry.entry_id)},
|
||||||
|
name="AdGuard Control Hub",
|
||||||
|
manufacturer="AdGuard",
|
||||||
|
model="AdGuard Home",
|
||||||
|
sw_version=self.coordinator.data.get("status", {}).get("version"),
|
||||||
|
configuration_url=f"http://{self._entry.data['host']}:{self._entry.data['port']}",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool | None:
|
||||||
|
"""Return True if entity is on."""
|
||||||
|
if self.coordinator.data is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
status_data = self.coordinator.data.get("status", {})
|
||||||
|
api_key = self._switch_info["api_key"]
|
||||||
|
|
||||||
|
return status_data.get(api_key, False)
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity on."""
|
||||||
|
await self.coordinator.async_set_switch(self._switch_type, True)
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity off."""
|
||||||
|
await self.coordinator.async_set_switch(self._switch_type, False)
|
||||||
36
custom_components/adguard_control_hub/translations/en.json
Normal file
36
custom_components/adguard_control_hub/translations/en.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "AdGuard Control Hub Setup",
|
||||||
|
"description": "Configure connection to your AdGuard Home instance",
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Port",
|
||||||
|
"username": "Username (optional)",
|
||||||
|
"password": "Password (optional)",
|
||||||
|
"ssl": "Use HTTPS",
|
||||||
|
"verify_ssl": "Verify SSL certificate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect to AdGuard Home",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error occurred"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "AdGuard Home instance already configured"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "AdGuard Control Hub Options",
|
||||||
|
"data": {
|
||||||
|
"scan_interval": "Update interval (seconds)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
hacs.json
Normal file
9
hacs.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "AdGuard Control Hub",
|
||||||
|
"render_readme": true,
|
||||||
|
"domains": [
|
||||||
|
"switch",
|
||||||
|
"sensor",
|
||||||
|
"binary_sensor"
|
||||||
|
]
|
||||||
|
}
|
||||||
5
requirements_test.txt
Normal file
5
requirements_test.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pytest>=7.4.0
|
||||||
|
pytest-homeassistant-custom-component>=0.13.0
|
||||||
|
pytest-asyncio>=0.21.1
|
||||||
|
aiohttp>=3.8.0
|
||||||
|
voluptuous>=0.13.1
|
||||||
69
tests/conftest.py
Normal file
69
tests/conftest.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
"""Test configuration for AdGuard Control Hub integration."""
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from custom_components.adguard_control_hub.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_SSL,
|
||||||
|
CONF_USERNAME,
|
||||||
|
CONF_VERIFY_SSL,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_adguard_api():
|
||||||
|
"""Mock AdGuard API client."""
|
||||||
|
mock_api = MagicMock()
|
||||||
|
mock_api.get_status = AsyncMock(return_value={
|
||||||
|
"version": "0.107.50",
|
||||||
|
"protection_enabled": True,
|
||||||
|
"filtering_enabled": True,
|
||||||
|
"safebrowsing_enabled": True,
|
||||||
|
"parental_enabled": True,
|
||||||
|
"safesearch_enabled": True,
|
||||||
|
"querylog_enabled": True,
|
||||||
|
})
|
||||||
|
mock_api.get_stats = AsyncMock(return_value={
|
||||||
|
"num_dns_queries": 1000,
|
||||||
|
"num_blocked_filtering": 200,
|
||||||
|
"avg_processing_time": 1.5,
|
||||||
|
"num_replaced_safebrowsing": 1500,
|
||||||
|
})
|
||||||
|
mock_api.get_clients = AsyncMock(return_value={
|
||||||
|
"clients": [],
|
||||||
|
"auto_clients": [],
|
||||||
|
})
|
||||||
|
mock_api.set_protection = AsyncMock()
|
||||||
|
mock_api.set_filtering = AsyncMock()
|
||||||
|
mock_api.set_safebrowsing = AsyncMock()
|
||||||
|
mock_api.set_parental_control = AsyncMock()
|
||||||
|
mock_api.set_safe_search = AsyncMock()
|
||||||
|
mock_api.set_query_log = AsyncMock()
|
||||||
|
mock_api.test_connection = AsyncMock(return_value=True)
|
||||||
|
return mock_api
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_config_entry():
|
||||||
|
"""Mock config entry."""
|
||||||
|
return {
|
||||||
|
CONF_HOST: "192.168.1.100",
|
||||||
|
CONF_PORT: 3000,
|
||||||
|
CONF_USERNAME: "admin",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_SSL: False,
|
||||||
|
CONF_VERIFY_SSL: True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_setup_entry():
|
||||||
|
"""Mock setup entry."""
|
||||||
|
with patch("custom_components.adguard_control_hub.AdGuardHomeAPI") as mock_api_class:
|
||||||
|
yield mock_api_class
|
||||||
104
tests/test_config_flow.py
Normal file
104
tests/test_config_flow.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
"""Test AdGuard Control Hub config flow."""
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from custom_components.adguard_control_hub.config_flow import (
|
||||||
|
CannotConnect,
|
||||||
|
InvalidAuth,
|
||||||
|
)
|
||||||
|
from custom_components.adguard_control_hub.const import CONF_SSL, CONF_VERIFY_SSL, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we get the form."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_valid_connection(hass: HomeAssistant, mock_adguard_api) -> None:
|
||||||
|
"""Test we get the form with valid connection."""
|
||||||
|
with patch(
|
||||||
|
"custom_components.adguard_control_hub.config_flow.AdGuardHomeAPI"
|
||||||
|
) as mock_api_class:
|
||||||
|
mock_api_class.return_value = mock_adguard_api
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: "192.168.1.100",
|
||||||
|
CONF_PORT: 3000,
|
||||||
|
CONF_USERNAME: "admin",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_SSL: False,
|
||||||
|
CONF_VERIFY_SSL: True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] == "create_entry"
|
||||||
|
assert result2["title"] == "AdGuard Home (192.168.1.100)"
|
||||||
|
assert result2["data"] == {
|
||||||
|
CONF_HOST: "192.168.1.100",
|
||||||
|
CONF_PORT: 3000,
|
||||||
|
CONF_USERNAME: "admin",
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
CONF_SSL: False,
|
||||||
|
CONF_VERIFY_SSL: True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we handle cannot connect error."""
|
||||||
|
with patch(
|
||||||
|
"custom_components.adguard_control_hub.config_flow.validate_input",
|
||||||
|
side_effect=CannotConnect,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: "192.168.1.100",
|
||||||
|
CONF_PORT: 3000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we handle invalid auth error."""
|
||||||
|
with patch(
|
||||||
|
"custom_components.adguard_control_hub.config_flow.validate_input",
|
||||||
|
side_effect=InvalidAuth,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: "192.168.1.100",
|
||||||
|
CONF_PORT: 3000,
|
||||||
|
CONF_USERNAME: "admin",
|
||||||
|
CONF_PASSWORD: "wrong",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {"base": "invalid_auth"}
|
||||||
47
tests/test_init.py
Normal file
47
tests/test_init.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"""Test AdGuard Control Hub initialization."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from custom_components.adguard_control_hub.const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry(hass: HomeAssistant, mock_config_entry, mock_adguard_api) -> None:
|
||||||
|
"""Test successful setup of entry."""
|
||||||
|
with patch(
|
||||||
|
"custom_components.adguard_control_hub.AdGuardHomeAPI"
|
||||||
|
) as mock_api_class:
|
||||||
|
mock_api_class.return_value = mock_adguard_api
|
||||||
|
|
||||||
|
entry = hass.config_entries.async_add(mock_config_entry)
|
||||||
|
|
||||||
|
with patch("custom_components.adguard_control_hub.PLATFORMS", []):
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
assert DOMAIN in hass.data
|
||||||
|
|
||||||
|
|
||||||
|
async def test_unload_entry(hass: HomeAssistant, mock_config_entry, mock_adguard_api) -> None:
|
||||||
|
"""Test successful unload of entry."""
|
||||||
|
with patch(
|
||||||
|
"custom_components.adguard_control_hub.AdGuardHomeAPI"
|
||||||
|
) as mock_api_class:
|
||||||
|
mock_api_class.return_value = mock_adguard_api
|
||||||
|
|
||||||
|
entry = hass.config_entries.async_add(mock_config_entry)
|
||||||
|
|
||||||
|
with patch("custom_components.adguard_control_hub.PLATFORMS", []):
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state == ConfigEntryState.NOT_LOADED
|
||||||
527
wiki/Advanced-Usage.md
Normal file
527
wiki/Advanced-Usage.md
Normal file
@@ -0,0 +1,527 @@
|
|||||||
|
# Advanced Usage
|
||||||
|
|
||||||
|
This guide covers advanced features, automations, and customizations for AdGuard Control Hub.
|
||||||
|
|
||||||
|
## Lovelace Dashboard Cards
|
||||||
|
|
||||||
|
Create informative dashboard cards to monitor and control your AdGuard Home instance.
|
||||||
|
|
||||||
|
### Protection Status Card
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
type: entities
|
||||||
|
title: AdGuard Protection
|
||||||
|
entities:
|
||||||
|
- switch.adguard_protection
|
||||||
|
- switch.adguard_dns_filtering
|
||||||
|
- switch.adguard_safe_browsing
|
||||||
|
- switch.adguard_parental_control
|
||||||
|
- switch.adguard_safe_search
|
||||||
|
- switch.adguard_query_log
|
||||||
|
show_header_toggle: true
|
||||||
|
state_color: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Statistics Overview Card
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
type: glance
|
||||||
|
title: AdGuard Statistics
|
||||||
|
entities:
|
||||||
|
- entity: sensor.adguard_dns_queries
|
||||||
|
name: Total Queries
|
||||||
|
- entity: sensor.adguard_blocked_queries
|
||||||
|
name: Blocked
|
||||||
|
- entity: sensor.adguard_blocked_percentage
|
||||||
|
name: Blocked %
|
||||||
|
- entity: sensor.adguard_average_processing_time
|
||||||
|
name: Avg Time
|
||||||
|
columns: 2
|
||||||
|
show_state: true
|
||||||
|
show_name: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Historical Chart Card
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
type: history-graph
|
||||||
|
title: DNS Activity (24h)
|
||||||
|
entities:
|
||||||
|
- sensor.adguard_dns_queries
|
||||||
|
- sensor.adguard_blocked_queries
|
||||||
|
hours_to_show: 24
|
||||||
|
refresh_interval: 60
|
||||||
|
```
|
||||||
|
|
||||||
|
### Protection Control Card
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
type: custom:mushroom-chips-card
|
||||||
|
chips:
|
||||||
|
- type: entity
|
||||||
|
entity: switch.adguard_protection
|
||||||
|
icon_color: green
|
||||||
|
tap_action:
|
||||||
|
action: toggle
|
||||||
|
- type: entity
|
||||||
|
entity: switch.adguard_parental_control
|
||||||
|
icon_color: blue
|
||||||
|
tap_action:
|
||||||
|
action: toggle
|
||||||
|
- type: entity
|
||||||
|
entity: binary_sensor.adguard_home_running
|
||||||
|
icon_color: red
|
||||||
|
```
|
||||||
|
|
||||||
|
## Automations
|
||||||
|
|
||||||
|
### Basic Automations
|
||||||
|
|
||||||
|
#### Enable Parental Controls During School Hours
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "AdGuard: Enable Parental Controls - School Hours"
|
||||||
|
trigger:
|
||||||
|
- platform: time
|
||||||
|
at: "08:00:00"
|
||||||
|
condition:
|
||||||
|
- condition: time
|
||||||
|
weekday:
|
||||||
|
- mon
|
||||||
|
- tue
|
||||||
|
- wed
|
||||||
|
- thu
|
||||||
|
- fri
|
||||||
|
action:
|
||||||
|
- service: switch.turn_on
|
||||||
|
target:
|
||||||
|
entity_id: switch.adguard_parental_control
|
||||||
|
- service: notify.mobile_app_your_phone
|
||||||
|
data:
|
||||||
|
title: "AdGuard"
|
||||||
|
message: "Parental controls enabled for school hours"
|
||||||
|
|
||||||
|
- alias: "AdGuard: Disable Parental Controls - After School"
|
||||||
|
trigger:
|
||||||
|
- platform: time
|
||||||
|
at: "16:00:00"
|
||||||
|
condition:
|
||||||
|
- condition: time
|
||||||
|
weekday:
|
||||||
|
- mon
|
||||||
|
- tue
|
||||||
|
- wed
|
||||||
|
- thu
|
||||||
|
- fri
|
||||||
|
action:
|
||||||
|
- service: switch.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: switch.adguard_parental_control
|
||||||
|
- service: notify.mobile_app_your_phone
|
||||||
|
data:
|
||||||
|
title: "AdGuard"
|
||||||
|
message: "Parental controls disabled - school day ended"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Temporarily Disable Filtering
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "AdGuard: Temporary Disable Protection"
|
||||||
|
trigger:
|
||||||
|
- platform: event
|
||||||
|
event_type: call_service
|
||||||
|
event_data:
|
||||||
|
domain: script
|
||||||
|
service: disable_adguard_temporarily
|
||||||
|
action:
|
||||||
|
- service: switch.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: switch.adguard_protection
|
||||||
|
- delay: "00:10:00" # 10 minutes
|
||||||
|
- service: switch.turn_on
|
||||||
|
target:
|
||||||
|
entity_id: switch.adguard_protection
|
||||||
|
- service: notify.mobile_app_your_phone
|
||||||
|
data:
|
||||||
|
title: "AdGuard"
|
||||||
|
message: "Protection re-enabled after temporary disable"
|
||||||
|
|
||||||
|
script:
|
||||||
|
disable_adguard_temporarily:
|
||||||
|
alias: "Disable AdGuard for 10 minutes"
|
||||||
|
sequence:
|
||||||
|
- event: call_service
|
||||||
|
event_data:
|
||||||
|
domain: script
|
||||||
|
service: disable_adguard_temporarily
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Automations
|
||||||
|
|
||||||
|
#### Dynamic Protection Based on Network Activity
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "AdGuard: High Activity Alert"
|
||||||
|
trigger:
|
||||||
|
- platform: numeric_state
|
||||||
|
entity_id: sensor.adguard_dns_queries
|
||||||
|
above: 5000
|
||||||
|
for: "00:05:00"
|
||||||
|
action:
|
||||||
|
- service: notify.mobile_app_your_phone
|
||||||
|
data:
|
||||||
|
title: "AdGuard Alert"
|
||||||
|
message: "High DNS activity detected: {{ states('sensor.adguard_dns_queries') }} queries"
|
||||||
|
- service: switch.turn_on
|
||||||
|
target:
|
||||||
|
entity_id: switch.adguard_safe_browsing
|
||||||
|
- service: switch.turn_on
|
||||||
|
target:
|
||||||
|
entity_id: switch.adguard_dns_filtering
|
||||||
|
|
||||||
|
- alias: "AdGuard: Performance Monitoring"
|
||||||
|
trigger:
|
||||||
|
- platform: numeric_state
|
||||||
|
entity_id: sensor.adguard_average_processing_time
|
||||||
|
above: 100
|
||||||
|
for: "00:02:00"
|
||||||
|
action:
|
||||||
|
- service: persistent_notification.create
|
||||||
|
data:
|
||||||
|
title: "AdGuard Performance Warning"
|
||||||
|
message: |
|
||||||
|
DNS response time is high: {{ states('sensor.adguard_average_processing_time') }}ms
|
||||||
|
Current queries: {{ states('sensor.adguard_dns_queries') }}
|
||||||
|
Active rules: {{ states('sensor.adguard_active_filter_rules') }}
|
||||||
|
notification_id: adguard_performance
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Guest Network Protection
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "AdGuard: Guest Network - Enable Strict Filtering"
|
||||||
|
trigger:
|
||||||
|
- platform: device_tracker
|
||||||
|
entity_id: device_tracker.guest_device
|
||||||
|
to: "home"
|
||||||
|
action:
|
||||||
|
- service: switch.turn_on
|
||||||
|
target:
|
||||||
|
entity_id:
|
||||||
|
- switch.adguard_safe_browsing
|
||||||
|
- switch.adguard_parental_control
|
||||||
|
- switch.adguard_safe_search
|
||||||
|
- service: notify.mobile_app_your_phone
|
||||||
|
data:
|
||||||
|
title: "AdGuard"
|
||||||
|
message: "Guest device connected - strict filtering enabled"
|
||||||
|
|
||||||
|
- alias: "AdGuard: Guest Network - Restore Normal Settings"
|
||||||
|
trigger:
|
||||||
|
- platform: device_tracker
|
||||||
|
entity_id: device_tracker.guest_device
|
||||||
|
to: "not_home"
|
||||||
|
for: "00:05:00"
|
||||||
|
action:
|
||||||
|
- service: switch.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: switch.adguard_parental_control
|
||||||
|
- service: notify.mobile_app_your_phone
|
||||||
|
data:
|
||||||
|
title: "AdGuard"
|
||||||
|
message: "Guest device disconnected - normal filtering restored"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitoring and Alerting
|
||||||
|
|
||||||
|
#### AdGuard Home Downtime Alert
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "AdGuard: Downtime Alert"
|
||||||
|
trigger:
|
||||||
|
- platform: state
|
||||||
|
entity_id: binary_sensor.adguard_home_running
|
||||||
|
to: "off"
|
||||||
|
for: "00:02:00"
|
||||||
|
action:
|
||||||
|
- service: notify.mobile_app_your_phone
|
||||||
|
data:
|
||||||
|
title: "AdGuard Home Down"
|
||||||
|
message: "AdGuard Home is not responding. Check the service status."
|
||||||
|
data:
|
||||||
|
priority: high
|
||||||
|
color: red
|
||||||
|
|
||||||
|
- alias: "AdGuard: Service Restored"
|
||||||
|
trigger:
|
||||||
|
- platform: state
|
||||||
|
entity_id: binary_sensor.adguard_home_running
|
||||||
|
to: "on"
|
||||||
|
condition:
|
||||||
|
- condition: state
|
||||||
|
entity_id: binary_sensor.adguard_home_running
|
||||||
|
state: "off"
|
||||||
|
for: "00:01:00"
|
||||||
|
action:
|
||||||
|
- service: notify.mobile_app_your_phone
|
||||||
|
data:
|
||||||
|
title: "AdGuard Home Restored"
|
||||||
|
message: "AdGuard Home is responding again."
|
||||||
|
data:
|
||||||
|
color: green
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
### Quick Control Scripts
|
||||||
|
|
||||||
|
#### Emergency Bypass Script
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
script:
|
||||||
|
adguard_emergency_bypass:
|
||||||
|
alias: "AdGuard Emergency Bypass"
|
||||||
|
description: "Quickly disable all protection for troubleshooting"
|
||||||
|
sequence:
|
||||||
|
- service: switch.turn_off
|
||||||
|
target:
|
||||||
|
entity_id: switch.adguard_protection
|
||||||
|
- service: timer.start
|
||||||
|
target:
|
||||||
|
entity_id: timer.adguard_bypass
|
||||||
|
data:
|
||||||
|
duration: "00:15:00"
|
||||||
|
- service: persistent_notification.create
|
||||||
|
data:
|
||||||
|
title: "AdGuard Bypass Active"
|
||||||
|
message: "All protection disabled for 15 minutes"
|
||||||
|
notification_id: adguard_bypass
|
||||||
|
|
||||||
|
timer:
|
||||||
|
adguard_bypass:
|
||||||
|
duration: "00:15:00"
|
||||||
|
restore: true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Weekly Statistics Report
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
script:
|
||||||
|
adguard_weekly_report:
|
||||||
|
alias: "AdGuard Weekly Report"
|
||||||
|
sequence:
|
||||||
|
- service: notify.mobile_app_your_phone
|
||||||
|
data:
|
||||||
|
title: "AdGuard Weekly Report"
|
||||||
|
message: |
|
||||||
|
Total Queries: {{ states('sensor.adguard_dns_queries') }}
|
||||||
|
Blocked: {{ states('sensor.adguard_blocked_queries') }}
|
||||||
|
Block Rate: {{ states('sensor.adguard_blocked_percentage') }}%
|
||||||
|
Avg Response Time: {{ states('sensor.adguard_average_processing_time') }}ms
|
||||||
|
Filter Rules: {{ states('sensor.adguard_active_filter_rules') }}
|
||||||
|
|
||||||
|
automation:
|
||||||
|
- alias: "AdGuard: Weekly Report"
|
||||||
|
trigger:
|
||||||
|
- platform: time
|
||||||
|
at: "09:00:00"
|
||||||
|
condition:
|
||||||
|
- condition: time
|
||||||
|
weekday:
|
||||||
|
- sun
|
||||||
|
action:
|
||||||
|
- service: script.adguard_weekly_report
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Templates
|
||||||
|
|
||||||
|
### Template Sensors
|
||||||
|
|
||||||
|
#### Protection Status Summary
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
template:
|
||||||
|
- sensor:
|
||||||
|
- name: "AdGuard Protection Summary"
|
||||||
|
state: >
|
||||||
|
{% set protection = states('switch.adguard_protection') %}
|
||||||
|
{% set filtering = states('switch.adguard_dns_filtering') %}
|
||||||
|
{% set safebrowsing = states('switch.adguard_safe_browsing') %}
|
||||||
|
{% set parental = states('switch.adguard_parental_control') %}
|
||||||
|
|
||||||
|
{% if protection == 'off' %}
|
||||||
|
Disabled
|
||||||
|
{% else %}
|
||||||
|
{% set active = [filtering, safebrowsing, parental] | select('eq', 'on') | list | length %}
|
||||||
|
{% if active == 3 %}
|
||||||
|
Full Protection
|
||||||
|
{% elif active == 0 %}
|
||||||
|
Basic Protection
|
||||||
|
{% else %}
|
||||||
|
Partial Protection ({{ active }}/3)
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
icon: >
|
||||||
|
{% set protection = states('switch.adguard_protection') %}
|
||||||
|
{% if protection == 'off' %}
|
||||||
|
mdi:shield-off
|
||||||
|
{% else %}
|
||||||
|
mdi:shield-check
|
||||||
|
{% endif %}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Daily Block Statistics
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
template:
|
||||||
|
- sensor:
|
||||||
|
- name: "AdGuard Daily Block Rate"
|
||||||
|
state: >
|
||||||
|
{% set queries = states('sensor.adguard_dns_queries') | int(0) %}
|
||||||
|
{% set blocked = states('sensor.adguard_blocked_queries') | int(0) %}
|
||||||
|
{% if queries > 0 %}
|
||||||
|
{{ ((blocked / queries) * 100) | round(1) }}
|
||||||
|
{% else %}
|
||||||
|
0
|
||||||
|
{% endif %}
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
device_class: None
|
||||||
|
attributes:
|
||||||
|
daily_queries: "{{ states('sensor.adguard_dns_queries') }}"
|
||||||
|
daily_blocked: "{{ states('sensor.adguard_blocked_queries') }}"
|
||||||
|
effectiveness: >
|
||||||
|
{% set rate = states('sensor.adguard_daily_block_rate') | float(0) %}
|
||||||
|
{% if rate > 20 %}
|
||||||
|
High
|
||||||
|
{% elif rate > 10 %}
|
||||||
|
Medium
|
||||||
|
{% elif rate > 5 %}
|
||||||
|
Low
|
||||||
|
{% else %}
|
||||||
|
Very Low
|
||||||
|
{% endif %}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Node-RED Integration
|
||||||
|
|
||||||
|
### Flow Examples
|
||||||
|
|
||||||
|
#### AdGuard Control Flow
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "adguard-control",
|
||||||
|
"type": "api-call-service",
|
||||||
|
"name": "Toggle AdGuard Protection",
|
||||||
|
"service_domain": "switch",
|
||||||
|
"service": "toggle",
|
||||||
|
"entityId": "switch.adguard_protection",
|
||||||
|
"data": {},
|
||||||
|
"wires": [["notification-node"]]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "notification-node",
|
||||||
|
"type": "api-call-service",
|
||||||
|
"name": "Send Notification",
|
||||||
|
"service_domain": "notify",
|
||||||
|
"service": "mobile_app_your_phone",
|
||||||
|
"data": {
|
||||||
|
"title": "AdGuard",
|
||||||
|
"message": "Protection toggled via Node-RED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Integration
|
||||||
|
|
||||||
|
### REST Commands
|
||||||
|
|
||||||
|
Add REST commands to your configuration.yaml for direct API access:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
rest_command:
|
||||||
|
adguard_enable_protection:
|
||||||
|
url: "http://192.168.1.100:3000/control/dns_config"
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: "Basic {{ ('admin:password') | b64encode }}"
|
||||||
|
payload: '{"protection_enabled": true}'
|
||||||
|
|
||||||
|
adguard_get_status:
|
||||||
|
url: "http://192.168.1.100:3000/control/status"
|
||||||
|
method: GET
|
||||||
|
headers:
|
||||||
|
Authorization: "Basic {{ ('admin:password') | b64encode }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting Advanced Features
|
||||||
|
|
||||||
|
### Automation Issues
|
||||||
|
|
||||||
|
**Automations not triggering:**
|
||||||
|
1. Check automation conditions and triggers
|
||||||
|
2. Verify entity IDs are correct
|
||||||
|
3. Check timezone settings
|
||||||
|
4. Review automation traces in Developer Tools
|
||||||
|
|
||||||
|
**State changes not detected:**
|
||||||
|
1. Verify update intervals
|
||||||
|
2. Check network connectivity
|
||||||
|
3. Review entity history
|
||||||
|
4. Confirm AdGuard Home is responding
|
||||||
|
|
||||||
|
### Template Issues
|
||||||
|
|
||||||
|
**Templates showing unavailable:**
|
||||||
|
1. Check template syntax
|
||||||
|
2. Verify referenced entities exist
|
||||||
|
3. Test templates in Developer Tools → Template
|
||||||
|
4. Review Home Assistant logs for template errors
|
||||||
|
|
||||||
|
### Performance Optimization
|
||||||
|
|
||||||
|
**High resource usage:**
|
||||||
|
1. Reduce automation frequency
|
||||||
|
2. Optimize template sensors
|
||||||
|
3. Use conditions to prevent unnecessary actions
|
||||||
|
4. Monitor Home Assistant performance
|
||||||
|
|
||||||
|
**Slow response times:**
|
||||||
|
1. Check AdGuard Home performance
|
||||||
|
2. Verify network latency
|
||||||
|
3. Reduce update frequency if needed
|
||||||
|
4. Monitor integration logs
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Use HTTPS when possible
|
||||||
|
- Store credentials in secrets.yaml
|
||||||
|
- Limit API access to required functions
|
||||||
|
- Regular security updates
|
||||||
|
|
||||||
|
### Reliability
|
||||||
|
- Add error handling to automations
|
||||||
|
- Use conditions to prevent conflicts
|
||||||
|
- Monitor integration health
|
||||||
|
- Have fallback procedures
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- Balance update frequency with resources
|
||||||
|
- Use efficient templates
|
||||||
|
- Avoid excessive API calls
|
||||||
|
- Monitor system resources
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Review troubleshooting guide](Troubleshooting.md)
|
||||||
|
- [Learn about API endpoints](API-Reference.md)
|
||||||
|
- [Contribute to development](Development.md)
|
||||||
244
wiki/Configuration.md
Normal file
244
wiki/Configuration.md
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
# Configuration Guide
|
||||||
|
|
||||||
|
This guide covers all configuration options for AdGuard Control Hub integration.
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
### Basic Configuration
|
||||||
|
|
||||||
|
When adding the integration for the first time, you'll need to provide:
|
||||||
|
|
||||||
|
| Field | Description | Required | Default |
|
||||||
|
|-------|-------------|----------|---------|
|
||||||
|
| **Host** | IP address or hostname of AdGuard Home | ✅ | - |
|
||||||
|
| **Port** | Port number AdGuard Home is running on | ✅ | 3000 |
|
||||||
|
| **Username** | Admin username (if authentication enabled) | ❌ | - |
|
||||||
|
| **Password** | Admin password (if authentication enabled) | ❌ | - |
|
||||||
|
| **Use HTTPS** | Enable HTTPS connection | ❌ | false |
|
||||||
|
| **Verify SSL** | Verify SSL certificates | ❌ | true |
|
||||||
|
|
||||||
|
### Step-by-Step Configuration
|
||||||
|
|
||||||
|
1. **Navigate to Integration Setup**
|
||||||
|
- Go to **Settings** → **Devices & Services**
|
||||||
|
- Click **"+ ADD INTEGRATION"**
|
||||||
|
- Search for **"AdGuard Control Hub"**
|
||||||
|
|
||||||
|
2. **Enter Connection Details**
|
||||||
|
```
|
||||||
|
Host: 192.168.1.100
|
||||||
|
Port: 3000
|
||||||
|
Username: admin (optional)
|
||||||
|
Password: your-password (optional)
|
||||||
|
Use HTTPS: ☐ (check if using HTTPS)
|
||||||
|
Verify SSL: ☑ (recommended)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test Connection**
|
||||||
|
- Click **"SUBMIT"**
|
||||||
|
- Integration will test the connection
|
||||||
|
- If successful, you'll see a success message
|
||||||
|
|
||||||
|
4. **Complete Setup**
|
||||||
|
- Integration will create all entities
|
||||||
|
- Device will appear in **Devices & Services**
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
|
||||||
|
### HTTPS Setup
|
||||||
|
|
||||||
|
If your AdGuard Home uses HTTPS:
|
||||||
|
|
||||||
|
1. **Enable HTTPS in AdGuard Home**
|
||||||
|
- Open AdGuard Home web interface
|
||||||
|
- Go to **Settings** → **Encryption**
|
||||||
|
- Configure SSL certificate
|
||||||
|
|
||||||
|
2. **Configure Integration**
|
||||||
|
- Check **"Use HTTPS"** during setup
|
||||||
|
- Use HTTPS port (usually 443 or custom)
|
||||||
|
|
||||||
|
3. **SSL Certificate Verification**
|
||||||
|
- **Verify SSL: ON** - Recommended for production
|
||||||
|
- **Verify SSL: OFF** - Only for self-signed certificates
|
||||||
|
|
||||||
|
### Authentication Configuration
|
||||||
|
|
||||||
|
AdGuard Home supports optional authentication:
|
||||||
|
|
||||||
|
#### Without Authentication
|
||||||
|
- Leave **Username** and **Password** empty
|
||||||
|
- AdGuard Home must have authentication disabled
|
||||||
|
|
||||||
|
#### With Authentication
|
||||||
|
- Enter your AdGuard Home admin credentials
|
||||||
|
- User must have admin privileges
|
||||||
|
- Credentials are stored securely in Home Assistant
|
||||||
|
|
||||||
|
### Network Configuration Examples
|
||||||
|
|
||||||
|
#### Local Network
|
||||||
|
```
|
||||||
|
Host: 192.168.1.100
|
||||||
|
Port: 3000
|
||||||
|
Use HTTPS: false
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Remote Server with SSL
|
||||||
|
```
|
||||||
|
Host: adguard.example.com
|
||||||
|
Port: 443
|
||||||
|
Use HTTPS: true
|
||||||
|
Verify SSL: true
|
||||||
|
Username: admin
|
||||||
|
Password: secure-password
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker Installation
|
||||||
|
```
|
||||||
|
Host: adguard-home.local
|
||||||
|
Port: 3000
|
||||||
|
Use HTTPS: false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multiple Instances
|
||||||
|
|
||||||
|
You can configure multiple AdGuard Home instances:
|
||||||
|
|
||||||
|
1. **Add each instance separately**
|
||||||
|
- Each gets its own configuration entry
|
||||||
|
- Entities are prefixed with instance name
|
||||||
|
- All instances appear as separate devices
|
||||||
|
|
||||||
|
2. **Naming Convention**
|
||||||
|
- Integration automatically names based on hostname
|
||||||
|
- Example: "AdGuard Home (192.168.1.100)"
|
||||||
|
- Example: "AdGuard Home (adguard.example.com)"
|
||||||
|
|
||||||
|
## Entity Configuration
|
||||||
|
|
||||||
|
### Entity Naming
|
||||||
|
|
||||||
|
Entities are automatically created with descriptive names:
|
||||||
|
|
||||||
|
**Switches:**
|
||||||
|
- `switch.adguard_protection`
|
||||||
|
- `switch.adguard_dns_filtering`
|
||||||
|
- `switch.adguard_safe_browsing`
|
||||||
|
- `switch.adguard_parental_control`
|
||||||
|
- `switch.adguard_safe_search`
|
||||||
|
- `switch.adguard_query_log`
|
||||||
|
|
||||||
|
**Sensors:**
|
||||||
|
- `sensor.adguard_dns_queries`
|
||||||
|
- `sensor.adguard_blocked_queries`
|
||||||
|
- `sensor.adguard_blocked_percentage`
|
||||||
|
- `sensor.adguard_active_filter_rules`
|
||||||
|
- `sensor.adguard_average_processing_time`
|
||||||
|
|
||||||
|
**Binary Sensors:**
|
||||||
|
- `binary_sensor.adguard_home_running`
|
||||||
|
|
||||||
|
### Customizing Entities
|
||||||
|
|
||||||
|
You can customize entity names and icons:
|
||||||
|
|
||||||
|
1. **Entity Settings**
|
||||||
|
- Go to **Settings** → **Devices & Services**
|
||||||
|
- Find AdGuard Control Hub device
|
||||||
|
- Click on any entity
|
||||||
|
- Click gear icon (⚙️) to edit
|
||||||
|
|
||||||
|
2. **Available Customizations**
|
||||||
|
- Entity ID
|
||||||
|
- Friendly name
|
||||||
|
- Icon
|
||||||
|
- Device class
|
||||||
|
- Area assignment
|
||||||
|
|
||||||
|
## Update Interval
|
||||||
|
|
||||||
|
The integration updates every 30 seconds by default. This provides a good balance between responsiveness and resource usage.
|
||||||
|
|
||||||
|
**Note:** Query log must be enabled in AdGuard Home for statistics to update. Disabling query log will stop sensor updates.
|
||||||
|
|
||||||
|
## Options Configuration
|
||||||
|
|
||||||
|
Currently, there are no additional options to configure after initial setup. All configuration is done during the initial integration setup.
|
||||||
|
|
||||||
|
## Configuration Troubleshooting
|
||||||
|
|
||||||
|
### Connection Issues
|
||||||
|
|
||||||
|
**Cannot connect to AdGuard Home:**
|
||||||
|
1. Verify AdGuard Home is running
|
||||||
|
2. Check network connectivity: `ping your-adguard-ip`
|
||||||
|
3. Test API manually: `curl http://your-adguard-ip:port/control/status`
|
||||||
|
4. Verify firewall allows connection
|
||||||
|
|
||||||
|
**SSL/HTTPS Issues:**
|
||||||
|
1. Verify certificate is valid
|
||||||
|
2. Check certificate matches hostname
|
||||||
|
3. Try with SSL verification disabled temporarily
|
||||||
|
4. Ensure correct port for HTTPS
|
||||||
|
|
||||||
|
### Authentication Issues
|
||||||
|
|
||||||
|
**Authentication failed:**
|
||||||
|
1. Verify credentials in AdGuard Home
|
||||||
|
2. Check user has admin privileges
|
||||||
|
3. Test login in AdGuard Home web interface
|
||||||
|
4. Ensure password doesn't contain special characters that need escaping
|
||||||
|
|
||||||
|
**Forbidden access:**
|
||||||
|
1. User account may not have sufficient privileges
|
||||||
|
2. Check AdGuard Home access control settings
|
||||||
|
3. Verify IP is not blocked by AdGuard Home
|
||||||
|
|
||||||
|
### Entity Issues
|
||||||
|
|
||||||
|
**Entities not created:**
|
||||||
|
1. Check Home Assistant logs for errors
|
||||||
|
2. Verify AdGuard Home API is responding
|
||||||
|
3. Restart Home Assistant
|
||||||
|
4. Check entity registry for conflicts
|
||||||
|
|
||||||
|
**Entities showing unavailable:**
|
||||||
|
1. Check AdGuard Home is running
|
||||||
|
2. Verify network connectivity
|
||||||
|
3. Check authentication status
|
||||||
|
4. Review integration logs
|
||||||
|
|
||||||
|
## Configuration Files
|
||||||
|
|
||||||
|
### Example YAML (for reference only)
|
||||||
|
*Configuration is done via UI, not YAML*
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# This is for reference only - actual configuration is done via UI
|
||||||
|
adguard_control_hub:
|
||||||
|
host: 192.168.1.100
|
||||||
|
port: 3000
|
||||||
|
username: admin
|
||||||
|
password: !secret adguard_password
|
||||||
|
ssl: false
|
||||||
|
verify_ssl: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Secrets Management
|
||||||
|
|
||||||
|
Store sensitive information in `secrets.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# secrets.yaml
|
||||||
|
adguard_password: "your-secure-password"
|
||||||
|
adguard_host: "192.168.1.100"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After configuration:
|
||||||
|
- [Explore available features](Features.md)
|
||||||
|
- [Set up Lovelace cards](Advanced-Usage.md#lovelace-cards)
|
||||||
|
- [Create automations](Advanced-Usage.md#automations)
|
||||||
|
- [Monitor performance](Features.md#sensors)
|
||||||
294
wiki/Features.md
Normal file
294
wiki/Features.md
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
# Features Overview
|
||||||
|
|
||||||
|
AdGuard Control Hub provides comprehensive control and monitoring of your AdGuard Home instance through Home Assistant.
|
||||||
|
|
||||||
|
## Switches
|
||||||
|
|
||||||
|
All switches provide instant control over AdGuard Home's protection features with real-time status updates.
|
||||||
|
|
||||||
|
### AdGuard Protection
|
||||||
|
- **Entity ID:** `switch.adguard_protection`
|
||||||
|
- **Function:** Master switch that controls all AdGuard features
|
||||||
|
- **Icon:** `mdi:shield-check`
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **ON:** All AdGuard features are active
|
||||||
|
- **OFF:** Bypasses all AdGuard features (DNS queries pass through without filtering)
|
||||||
|
- Acts as a master override for all other protection features
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
- Temporarily disable all filtering for troubleshooting
|
||||||
|
- Emergency bypass for important downloads
|
||||||
|
- Scheduled maintenance windows
|
||||||
|
|
||||||
|
### DNS Filtering
|
||||||
|
- **Entity ID:** `switch.adguard_dns_filtering`
|
||||||
|
- **Function:** Enables DNS filtering using configured blocklists
|
||||||
|
- **Icon:** `mdi:filter`
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **ON:** DNS queries are filtered against blocklists
|
||||||
|
- **OFF:** DNS filtering is disabled, but other features remain active
|
||||||
|
- Only affects blocklist-based filtering
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
- Disable filtering for specific time periods
|
||||||
|
- Allow access to falsely blocked domains temporarily
|
||||||
|
- Maintenance of filter lists
|
||||||
|
|
||||||
|
### Safe Browsing
|
||||||
|
- **Entity ID:** `switch.adguard_safe_browsing`
|
||||||
|
- **Function:** Blocks known phishing and malware sites
|
||||||
|
- **Icon:** `mdi:shield-bug`
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **ON:** Queries to malicious domains are blocked
|
||||||
|
- **OFF:** Safe browsing protection is disabled
|
||||||
|
- Uses AdGuard's malware and phishing database
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
- Enhanced security for family networks
|
||||||
|
- Protection for less tech-savvy users
|
||||||
|
- Corporate security policies
|
||||||
|
|
||||||
|
### Parental Control
|
||||||
|
- **Entity ID:** `switch.adguard_parental_control`
|
||||||
|
- **Function:** Blocks adult content and inappropriate websites
|
||||||
|
- **Icon:** `mdi:account-child-circle`
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **ON:** Adult content domains are blocked
|
||||||
|
- **OFF:** Parental controls are disabled
|
||||||
|
- Blocks based on content category classification
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
- Protect children from inappropriate content
|
||||||
|
- School or educational environments
|
||||||
|
- Time-based content filtering
|
||||||
|
|
||||||
|
### Safe Search
|
||||||
|
- **Entity ID:** `switch.adguard_safe_search`
|
||||||
|
- **Function:** Enforces safe search on major search engines
|
||||||
|
- **Icon:** `mdi:shield-search`
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **ON:** Forces safe search mode on Google, Bing, Yandex, etc.
|
||||||
|
- **OFF:** Search engines operate in normal mode
|
||||||
|
- Redirects search queries to safe search variants
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
- Additional layer of content protection
|
||||||
|
- Compliance with content policies
|
||||||
|
- Educational institution requirements
|
||||||
|
|
||||||
|
### Query Log
|
||||||
|
- **Entity ID:** `switch.adguard_query_log`
|
||||||
|
- **Function:** Records DNS queries for statistics and analysis
|
||||||
|
- **Icon:** `mdi:file-document-multiple`
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- **ON:** All DNS queries are logged and stored
|
||||||
|
- **OFF:** Query logging is disabled, statistics stop updating
|
||||||
|
- Required for sensor data and analytics
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
- Monitor network activity
|
||||||
|
- Troubleshoot DNS issues
|
||||||
|
- Privacy mode (disable logging)
|
||||||
|
|
||||||
|
## Sensors
|
||||||
|
|
||||||
|
Sensors provide real-time statistics and monitoring data from your AdGuard Home instance.
|
||||||
|
|
||||||
|
### DNS Queries
|
||||||
|
- **Entity ID:** `sensor.adguard_dns_queries`
|
||||||
|
- **Unit:** queries
|
||||||
|
- **Function:** Total number of DNS queries processed
|
||||||
|
- **Icon:** `mdi:dns`
|
||||||
|
|
||||||
|
**Data Points:**
|
||||||
|
- Total queries since last statistics reset
|
||||||
|
- Updates every 30 seconds
|
||||||
|
- Includes all query types (A, AAAA, CNAME, etc.)
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
- Monitor network activity levels
|
||||||
|
- Track DNS server load
|
||||||
|
- Historical trending
|
||||||
|
|
||||||
|
### Blocked Queries
|
||||||
|
- **Entity ID:** `sensor.adguard_blocked_queries`
|
||||||
|
- **Unit:** queries
|
||||||
|
- **Function:** Number of queries blocked by filtering
|
||||||
|
- **Icon:** `mdi:shield-check`
|
||||||
|
|
||||||
|
**Data Points:**
|
||||||
|
- Queries blocked by all filtering mechanisms
|
||||||
|
- Includes blocklist, parental control, and safe browsing blocks
|
||||||
|
- Updates every 30 seconds
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
- Measure filtering effectiveness
|
||||||
|
- Identify potential threats blocked
|
||||||
|
- Compare blocking trends
|
||||||
|
|
||||||
|
### Blocked Percentage
|
||||||
|
- **Entity ID:** `sensor.adguard_blocked_percentage`
|
||||||
|
- **Unit:** %
|
||||||
|
- **Function:** Percentage of queries that were blocked
|
||||||
|
- **Icon:** `mdi:percent`
|
||||||
|
|
||||||
|
**Calculation:**
|
||||||
|
- `(Blocked Queries / Total Queries) × 100`
|
||||||
|
- Automatically calculated by integration
|
||||||
|
- Provides ratio view of filtering activity
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
- Quick assessment of filtering impact
|
||||||
|
- Performance monitoring
|
||||||
|
- Historical comparison
|
||||||
|
|
||||||
|
### Active Filter Rules
|
||||||
|
- **Entity ID:** `sensor.adguard_active_filter_rules`
|
||||||
|
- **Unit:** rules
|
||||||
|
- **Function:** Number of active filtering rules loaded
|
||||||
|
- **Icon:** `mdi:filter-check`
|
||||||
|
|
||||||
|
**Data Points:**
|
||||||
|
- Total rules from all enabled filter lists
|
||||||
|
- Includes custom rules
|
||||||
|
- Updated when filter lists are refreshed
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
- Monitor filter list size and complexity
|
||||||
|
- Track filter performance impact
|
||||||
|
- Troubleshoot over-blocking
|
||||||
|
|
||||||
|
### Average Processing Time
|
||||||
|
- **Entity ID:** `sensor.adguard_average_processing_time`
|
||||||
|
- **Unit:** ms
|
||||||
|
- **Function:** Average DNS query processing time
|
||||||
|
- **Icon:** `mdi:clock-fast`
|
||||||
|
|
||||||
|
**Measurement:**
|
||||||
|
- Time from query receipt to response
|
||||||
|
- Rolling average over recent queries
|
||||||
|
- Includes upstream server response time
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
- Monitor DNS performance
|
||||||
|
- Identify performance bottlenecks
|
||||||
|
- Compare different upstream servers
|
||||||
|
|
||||||
|
## Binary Sensors
|
||||||
|
|
||||||
|
Binary sensors provide simple on/off status information.
|
||||||
|
|
||||||
|
### AdGuard Home Running
|
||||||
|
- **Entity ID:** `binary_sensor.adguard_home_running`
|
||||||
|
- **Function:** Shows if AdGuard Home is responsive
|
||||||
|
- **Icon:** `mdi:shield-check-outline`
|
||||||
|
|
||||||
|
**States:**
|
||||||
|
- **ON:** AdGuard Home is responding to API requests
|
||||||
|
- **OFF:** AdGuard Home is unreachable or not responding
|
||||||
|
- **UNAVAILABLE:** Integration cannot determine status
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
- Monitor AdGuard Home availability
|
||||||
|
- Trigger alerts for downtime
|
||||||
|
- Automation conditions
|
||||||
|
|
||||||
|
## Device Information
|
||||||
|
|
||||||
|
The integration creates a single device representing your AdGuard Home instance:
|
||||||
|
|
||||||
|
**Device Attributes:**
|
||||||
|
- **Name:** "AdGuard Control Hub"
|
||||||
|
- **Manufacturer:** "AdGuard"
|
||||||
|
- **Model:** "AdGuard Home"
|
||||||
|
- **Software Version:** Detected from AdGuard Home API
|
||||||
|
- **Configuration URL:** Direct link to AdGuard Home web interface
|
||||||
|
|
||||||
|
**Device Identifiers:**
|
||||||
|
- Unique identifier based on host and port
|
||||||
|
- Allows multiple instances without conflicts
|
||||||
|
- Persistent across restarts
|
||||||
|
|
||||||
|
## Entity Attributes
|
||||||
|
|
||||||
|
Each entity provides additional information in its attributes:
|
||||||
|
|
||||||
|
### Switch Attributes
|
||||||
|
```yaml
|
||||||
|
friendly_name: "AdGuard Protection"
|
||||||
|
device_class: switch
|
||||||
|
icon: "mdi:shield-check"
|
||||||
|
state: "on"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sensor Attributes
|
||||||
|
```yaml
|
||||||
|
friendly_name: "DNS Queries"
|
||||||
|
device_class: null
|
||||||
|
unit_of_measurement: "queries"
|
||||||
|
state_class: "measurement"
|
||||||
|
icon: "mdi:dns"
|
||||||
|
state: 1247
|
||||||
|
```
|
||||||
|
|
||||||
|
## Feature Dependencies
|
||||||
|
|
||||||
|
Understanding feature relationships:
|
||||||
|
|
||||||
|
1. **Query Log Dependency**
|
||||||
|
- Most sensors require query log to be enabled
|
||||||
|
- Disabling query log stops statistics updates
|
||||||
|
- Binary sensor still works without query log
|
||||||
|
|
||||||
|
2. **Master Protection Switch**
|
||||||
|
- When protection is OFF, individual switches show their configured state
|
||||||
|
- But filtering is bypassed regardless of individual switch states
|
||||||
|
- Protection switch overrides all other filtering
|
||||||
|
|
||||||
|
3. **Network Connectivity**
|
||||||
|
- All features require network access to AdGuard Home
|
||||||
|
- Connection loss makes all entities unavailable
|
||||||
|
- Integration automatically reconnects when connection is restored
|
||||||
|
|
||||||
|
## Feature Limitations
|
||||||
|
|
||||||
|
**Current Limitations:**
|
||||||
|
- Client-specific controls not yet implemented
|
||||||
|
- Custom filter list management not available
|
||||||
|
- DNS rewrite rules not supported
|
||||||
|
- DHCP settings not exposed
|
||||||
|
|
||||||
|
**Planned Features:**
|
||||||
|
- Individual client management
|
||||||
|
- Custom filtering rules via HA
|
||||||
|
- DNS rewrite configuration
|
||||||
|
- Advanced statistics and reporting
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
**Update Frequency:**
|
||||||
|
- Default: 30-second intervals
|
||||||
|
- Balances responsiveness vs. resource usage
|
||||||
|
- Configurable in future versions
|
||||||
|
|
||||||
|
**Resource Usage:**
|
||||||
|
- Minimal CPU impact on AdGuard Home
|
||||||
|
- Uses standard AdGuard Home API endpoints
|
||||||
|
- No additional logging or processing required
|
||||||
|
|
||||||
|
**Network Traffic:**
|
||||||
|
- Small API requests every 30 seconds
|
||||||
|
- Typical response size: < 1KB per request
|
||||||
|
- Negligible bandwidth impact
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Set up advanced automations](Advanced-Usage.md)
|
||||||
|
- [Troubleshoot issues](Troubleshooting.md)
|
||||||
|
- [Learn about the API](API-Reference.md)
|
||||||
82
wiki/Home.md
Normal file
82
wiki/Home.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# AdGuard Control Hub Wiki
|
||||||
|
|
||||||
|
Welcome to the AdGuard Control Hub integration wiki! This comprehensive guide will help you get the most out of your AdGuard Home integration with Home Assistant.
|
||||||
|
|
||||||
|
## Quick Navigation
|
||||||
|
|
||||||
|
- [Installation Guide](Installation.md) - Step-by-step installation instructions
|
||||||
|
- [Configuration](Configuration.md) - Detailed configuration options
|
||||||
|
- [Features Overview](Features.md) - Complete feature documentation
|
||||||
|
- [API Reference](API-Reference.md) - AdGuard Home API information
|
||||||
|
- [Troubleshooting](Troubleshooting.md) - Common issues and solutions
|
||||||
|
- [Advanced Usage](Advanced-Usage.md) - Automation examples and advanced features
|
||||||
|
- [Development](Development.md) - Contributing and development setup
|
||||||
|
|
||||||
|
## What is AdGuard Control Hub?
|
||||||
|
|
||||||
|
AdGuard Control Hub is a comprehensive Home Assistant integration that provides complete control over your AdGuard Home DNS server. It allows you to:
|
||||||
|
|
||||||
|
- Monitor DNS statistics and performance
|
||||||
|
- Control filtering and protection features
|
||||||
|
- Manage clients and their settings
|
||||||
|
- Create powerful automations based on DNS activity
|
||||||
|
- View detailed analytics and reports
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### 🛡️ Complete Protection Control
|
||||||
|
- Master protection switch
|
||||||
|
- DNS filtering management
|
||||||
|
- Safe browsing controls
|
||||||
|
- Parental control settings
|
||||||
|
- Safe search enforcement
|
||||||
|
- Query logging control
|
||||||
|
|
||||||
|
### 📊 Comprehensive Monitoring
|
||||||
|
- Real-time DNS query statistics
|
||||||
|
- Blocked query tracking
|
||||||
|
- Performance metrics
|
||||||
|
- Filter rule counts
|
||||||
|
- Client activity monitoring
|
||||||
|
|
||||||
|
### 🏠 Home Assistant Integration
|
||||||
|
- Native Home Assistant entities
|
||||||
|
- Lovelace dashboard cards
|
||||||
|
- Automation support
|
||||||
|
- Service calls
|
||||||
|
- Event triggering
|
||||||
|
|
||||||
|
### 🔧 Easy Configuration
|
||||||
|
- GUI-based setup flow
|
||||||
|
- Automatic discovery
|
||||||
|
- Connection validation
|
||||||
|
- Error handling
|
||||||
|
- Secure credential storage
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. **Prerequisites**: Ensure you have AdGuard Home v0.107.0+ running
|
||||||
|
2. **Installation**: Follow the [Installation Guide](Installation.md)
|
||||||
|
3. **Configuration**: Set up your connection using the [Configuration Guide](Configuration.md)
|
||||||
|
4. **Features**: Explore available features in the [Features Overview](Features.md)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you encounter any issues:
|
||||||
|
|
||||||
|
1. Check the [Troubleshooting Guide](Troubleshooting.md)
|
||||||
|
2. Review the [GitHub Issues](https://github.com/your-username/adguard-control-hub/issues)
|
||||||
|
3. Join the discussion in [GitHub Discussions](https://github.com/your-username/adguard-control-hub/discussions)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We welcome contributions! See the [Development Guide](Development.md) for information on:
|
||||||
|
|
||||||
|
- Setting up a development environment
|
||||||
|
- Running tests
|
||||||
|
- Submitting pull requests
|
||||||
|
- Reporting bugs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This integration is not officially affiliated with AdGuard. AdGuard Home is a trademark of AdGuard Software Ltd.*
|
||||||
139
wiki/Installation.md
Normal file
139
wiki/Installation.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# Installation Guide
|
||||||
|
|
||||||
|
This guide covers all installation methods for AdGuard Control Hub integration.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before installing AdGuard Control Hub, ensure you have:
|
||||||
|
|
||||||
|
- **Home Assistant** 2023.5.0 or newer
|
||||||
|
- **AdGuard Home** v0.107.0 or newer
|
||||||
|
- Network connectivity between Home Assistant and AdGuard Home
|
||||||
|
- (Optional) Admin credentials for AdGuard Home if authentication is enabled
|
||||||
|
|
||||||
|
## Installation Methods
|
||||||
|
|
||||||
|
### Method 1: HACS Installation (Recommended)
|
||||||
|
|
||||||
|
HACS (Home Assistant Community Store) is the easiest way to install and manage custom integrations.
|
||||||
|
|
||||||
|
#### Step 1: Install HACS
|
||||||
|
If you don't have HACS installed:
|
||||||
|
|
||||||
|
1. Follow the [official HACS installation guide](https://hacs.xyz/docs/setup/prerequisites)
|
||||||
|
2. Restart Home Assistant after installation
|
||||||
|
|
||||||
|
#### Step 2: Add AdGuard Control Hub
|
||||||
|
|
||||||
|
1. Open Home Assistant web interface
|
||||||
|
2. Navigate to **HACS** → **Integrations**
|
||||||
|
3. Click the **"+ EXPLORE & DOWNLOAD REPOSITORIES"** button
|
||||||
|
4. Search for **"AdGuard Control Hub"**
|
||||||
|
5. Click on the integration and then **"DOWNLOAD"**
|
||||||
|
6. Select the latest version and click **"DOWNLOAD"**
|
||||||
|
7. Restart Home Assistant
|
||||||
|
|
||||||
|
#### Step 3: Add Integration
|
||||||
|
|
||||||
|
1. Navigate to **Settings** → **Devices & Services**
|
||||||
|
2. Click **"+ ADD INTEGRATION"**
|
||||||
|
3. Search for **"AdGuard Control Hub"**
|
||||||
|
4. Follow the configuration steps
|
||||||
|
|
||||||
|
### Method 2: Manual Installation
|
||||||
|
|
||||||
|
For users who prefer manual installation or cannot use HACS.
|
||||||
|
|
||||||
|
#### Step 1: Download Integration
|
||||||
|
|
||||||
|
1. Go to the [latest release page](https://github.com/your-username/adguard-control-hub/releases/latest)
|
||||||
|
2. Download the `adguard-control-hub.zip` file
|
||||||
|
3. Extract the ZIP file
|
||||||
|
|
||||||
|
#### Step 2: Copy Files
|
||||||
|
|
||||||
|
1. Copy the extracted `custom_components/adguard_control_hub` folder
|
||||||
|
2. Place it in your Home Assistant's `config/custom_components/` directory
|
||||||
|
3. The final structure should look like:
|
||||||
|
```
|
||||||
|
config/
|
||||||
|
└── custom_components/
|
||||||
|
└── adguard_control_hub/
|
||||||
|
├── __init__.py
|
||||||
|
├── manifest.json
|
||||||
|
├── config_flow.py
|
||||||
|
└── ... (other files)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Restart and Configure
|
||||||
|
|
||||||
|
1. Restart Home Assistant
|
||||||
|
2. Navigate to **Settings** → **Devices & Services**
|
||||||
|
3. Click **"+ ADD INTEGRATION"**
|
||||||
|
4. Search for **"AdGuard Control Hub"**
|
||||||
|
5. Follow the configuration steps
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
After installation, verify everything is working:
|
||||||
|
|
||||||
|
1. Check **Settings** → **Devices & Services** for the AdGuard Control Hub integration
|
||||||
|
2. Verify entities are created under the AdGuard device
|
||||||
|
3. Test switching protection on/off
|
||||||
|
4. Check the Home Assistant logs for any errors
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Configure the integration](Configuration.md)
|
||||||
|
- [Explore available features](Features.md)
|
||||||
|
- [Set up automations](Advanced-Usage.md)
|
||||||
|
|
||||||
|
## Troubleshooting Installation
|
||||||
|
|
||||||
|
### HACS Issues
|
||||||
|
|
||||||
|
**Integration not found in HACS:**
|
||||||
|
- Ensure you're searching in the "Integrations" section
|
||||||
|
- Check if HACS is up to date
|
||||||
|
- Clear HACS cache and refresh
|
||||||
|
|
||||||
|
**Download fails:**
|
||||||
|
- Check your internet connection
|
||||||
|
- Verify HACS has proper GitHub access
|
||||||
|
- Try downloading manually and installing via Method 2
|
||||||
|
|
||||||
|
### Manual Installation Issues
|
||||||
|
|
||||||
|
**Integration not showing up:**
|
||||||
|
- Verify folder structure is correct
|
||||||
|
- Check file permissions
|
||||||
|
- Restart Home Assistant completely (not just config reload)
|
||||||
|
- Check Home Assistant logs for errors
|
||||||
|
|
||||||
|
**Permission errors:**
|
||||||
|
- Ensure Home Assistant has read/write access to custom_components
|
||||||
|
- Check file ownership matches Home Assistant user
|
||||||
|
|
||||||
|
### General Issues
|
||||||
|
|
||||||
|
**Integration fails to load:**
|
||||||
|
- Check Home Assistant logs: **Settings** → **System** → **Logs**
|
||||||
|
- Verify Python requirements are met
|
||||||
|
- Ensure Home Assistant version compatibility
|
||||||
|
|
||||||
|
**Cannot add integration:**
|
||||||
|
- Clear browser cache and cookies
|
||||||
|
- Try incognito/private browsing mode
|
||||||
|
- Check for JavaScript errors in browser console
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
If you're still having issues:
|
||||||
|
|
||||||
|
1. Check the [Troubleshooting Guide](Troubleshooting.md)
|
||||||
|
2. Review [GitHub Issues](https://github.com/your-username/adguard-control-hub/issues)
|
||||||
|
3. Create a new issue with:
|
||||||
|
- Installation method used
|
||||||
|
- Home Assistant version
|
||||||
|
- Error messages from logs
|
||||||
|
- Steps you've already tried
|
||||||
Reference in New Issue
Block a user