mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-11 11:16:30 +00:00
Support for deconz discovery & cleanup (#974)
* Support for deconz discovery & cleanup * Split discovery * Fix lint * Fix lint / import
This commit is contained in:
parent
b52f90187b
commit
b12175ab9a
@ -21,7 +21,7 @@ from ..const import (
|
|||||||
ATTR_UUID, ATTR_VERSION, ATTR_WEBUI, BOOT_AUTO, BOOT_MANUAL,
|
ATTR_UUID, ATTR_VERSION, ATTR_WEBUI, BOOT_AUTO, BOOT_MANUAL,
|
||||||
PRIVILEGED_ALL, ROLE_ALL, ROLE_DEFAULT, STARTUP_ALL, STARTUP_APPLICATION,
|
PRIVILEGED_ALL, ROLE_ALL, ROLE_DEFAULT, STARTUP_ALL, STARTUP_APPLICATION,
|
||||||
STARTUP_SERVICES, STATE_STARTED, STATE_STOPPED)
|
STARTUP_SERVICES, STATE_STARTED, STATE_STOPPED)
|
||||||
from ..services.validate import DISCOVERY_SERVICES
|
from ..discovery.validate import valid_discovery_service
|
||||||
from ..validate import (
|
from ..validate import (
|
||||||
ALSA_DEVICE, DOCKER_PORTS, NETWORK_PORT, SHA256, UUID_MATCH)
|
ALSA_DEVICE, DOCKER_PORTS, NETWORK_PORT, SHA256, UUID_MATCH)
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_DOCKER_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_DOCKER_API, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_AUTH_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUTH_API, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
|
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
|
||||||
vol.Optional(ATTR_DISCOVERY): [vol.In(DISCOVERY_SERVICES)],
|
vol.Optional(ATTR_DISCOVERY): [valid_discovery_service],
|
||||||
vol.Required(ATTR_OPTIONS): dict,
|
vol.Required(ATTR_OPTIONS): dict,
|
||||||
vol.Required(ATTR_SCHEMA): vol.Any(vol.Schema({
|
vol.Required(ATTR_SCHEMA): vol.Any(vol.Schema({
|
||||||
vol.Coerce(str): vol.Any(SCHEMA_ELEMENT, [
|
vol.Coerce(str): vol.Any(SCHEMA_ELEMENT, [
|
||||||
|
@ -3,17 +3,24 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_ADDON, ATTR_UUID, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_SERVICE,
|
ATTR_ADDON,
|
||||||
REQUEST_FROM)
|
ATTR_UUID,
|
||||||
|
ATTR_CONFIG,
|
||||||
|
ATTR_DISCOVERY,
|
||||||
|
ATTR_SERVICE,
|
||||||
|
REQUEST_FROM,
|
||||||
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError, APIForbidden
|
from ..exceptions import APIError, APIForbidden
|
||||||
from ..validate import SERVICE_ALL
|
from ..discovery.validate import valid_discovery_service
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_DISCOVERY = vol.Schema({
|
SCHEMA_DISCOVERY = vol.Schema(
|
||||||
vol.Required(ATTR_SERVICE): SERVICE_ALL,
|
{
|
||||||
|
vol.Required(ATTR_SERVICE): valid_discovery_service,
|
||||||
vol.Optional(ATTR_CONFIG): vol.Maybe(dict),
|
vol.Optional(ATTR_CONFIG): vol.Maybe(dict),
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class APIDiscovery(CoreSysAttributes):
|
class APIDiscovery(CoreSysAttributes):
|
||||||
@ -21,7 +28,7 @@ class APIDiscovery(CoreSysAttributes):
|
|||||||
|
|
||||||
def _extract_message(self, request):
|
def _extract_message(self, request):
|
||||||
"""Extract discovery message from URL."""
|
"""Extract discovery message from URL."""
|
||||||
message = self.sys_discovery.get(request.match_info.get('uuid'))
|
message = self.sys_discovery.get(request.match_info.get("uuid"))
|
||||||
if not message:
|
if not message:
|
||||||
raise APIError("Discovery message not found")
|
raise APIError("Discovery message not found")
|
||||||
return message
|
return message
|
||||||
@ -38,12 +45,14 @@ class APIDiscovery(CoreSysAttributes):
|
|||||||
|
|
||||||
discovery = []
|
discovery = []
|
||||||
for message in self.sys_discovery.list_messages:
|
for message in self.sys_discovery.list_messages:
|
||||||
discovery.append({
|
discovery.append(
|
||||||
|
{
|
||||||
ATTR_ADDON: message.addon,
|
ATTR_ADDON: message.addon,
|
||||||
ATTR_SERVICE: message.service,
|
ATTR_SERVICE: message.service,
|
||||||
ATTR_UUID: message.uuid,
|
ATTR_UUID: message.uuid,
|
||||||
ATTR_CONFIG: message.config,
|
ATTR_CONFIG: message.config,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return {ATTR_DISCOVERY: discovery}
|
return {ATTR_DISCOVERY: discovery}
|
||||||
|
|
||||||
|
@ -159,7 +159,6 @@ ATTR_ADDON = "addon"
|
|||||||
ATTR_AVAILABLE = "available"
|
ATTR_AVAILABLE = "available"
|
||||||
ATTR_HOST = "host"
|
ATTR_HOST = "host"
|
||||||
ATTR_USERNAME = "username"
|
ATTR_USERNAME = "username"
|
||||||
ATTR_PROTOCOL = "protocol"
|
|
||||||
ATTR_DISCOVERY = "discovery"
|
ATTR_DISCOVERY = "discovery"
|
||||||
ATTR_CONFIG = "config"
|
ATTR_CONFIG = "config"
|
||||||
ATTR_SERVICES = "services"
|
ATTR_SERVICES = "services"
|
||||||
@ -189,7 +188,6 @@ ATTR_AUTH_API = "auth_api"
|
|||||||
ATTR_KERNEL_MODULES = "kernel_modules"
|
ATTR_KERNEL_MODULES = "kernel_modules"
|
||||||
ATTR_SUPPORTED_ARCH = "supported_arch"
|
ATTR_SUPPORTED_ARCH = "supported_arch"
|
||||||
|
|
||||||
SERVICE_MQTT = "mqtt"
|
|
||||||
PROVIDE_SERVICE = "provide"
|
PROVIDE_SERVICE = "provide"
|
||||||
NEED_SERVICE = "need"
|
NEED_SERVICE = "need"
|
||||||
WANT_SERVICE = "want"
|
WANT_SERVICE = "want"
|
||||||
|
@ -1,35 +1,50 @@
|
|||||||
"""Handle discover message for Home Assistant."""
|
"""Handle discover message for Home Assistant."""
|
||||||
import logging
|
from __future__ import annotations
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from uuid import uuid4
|
import logging
|
||||||
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
||||||
|
from uuid import uuid4, UUID
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
from .const import FILE_HASSIO_DISCOVERY, ATTR_CONFIG, ATTR_DISCOVERY
|
from ..const import ATTR_CONFIG, ATTR_DISCOVERY, FILE_HASSIO_DISCOVERY
|
||||||
from .coresys import CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from .exceptions import DiscoveryError, HomeAssistantAPIError
|
from ..exceptions import DiscoveryError, HomeAssistantAPIError
|
||||||
from .validate import SCHEMA_DISCOVERY_CONFIG
|
from ..utils.json import JsonConfig
|
||||||
from .utils.json import JsonConfig
|
from .validate import SCHEMA_DISCOVERY_CONFIG, valid_discovery_config
|
||||||
from .services.validate import DISCOVERY_SERVICES
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..addons.addon import Addon
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CMD_NEW = 'post'
|
CMD_NEW = "post"
|
||||||
CMD_DEL = 'delete'
|
CMD_DEL = "delete"
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class Message:
|
||||||
|
"""Represent a single Discovery message."""
|
||||||
|
|
||||||
|
addon: str = attr.ib()
|
||||||
|
service: str = attr.ib()
|
||||||
|
config: Dict[str, Any] = attr.ib(cmp=False)
|
||||||
|
uuid: UUID = attr.ib(factory=lambda: uuid4().hex, cmp=False)
|
||||||
|
|
||||||
|
|
||||||
class Discovery(CoreSysAttributes, JsonConfig):
|
class Discovery(CoreSysAttributes, JsonConfig):
|
||||||
"""Home Assistant Discovery handler."""
|
"""Home Assistant Discovery handler."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize discovery handler."""
|
"""Initialize discovery handler."""
|
||||||
super().__init__(FILE_HASSIO_DISCOVERY, SCHEMA_DISCOVERY_CONFIG)
|
super().__init__(FILE_HASSIO_DISCOVERY, SCHEMA_DISCOVERY_CONFIG)
|
||||||
self.coresys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.message_obj = {}
|
self.message_obj: Dict[str, Message] = {}
|
||||||
|
|
||||||
async def load(self):
|
async def load(self) -> None:
|
||||||
"""Load exists discovery message into storage."""
|
"""Load exists discovery message into storage."""
|
||||||
messages = {}
|
messages = {}
|
||||||
for message in self._data[ATTR_DISCOVERY]:
|
for message in self._data[ATTR_DISCOVERY]:
|
||||||
@ -39,9 +54,9 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
|||||||
_LOGGER.info("Load %d messages", len(messages))
|
_LOGGER.info("Load %d messages", len(messages))
|
||||||
self.message_obj = messages
|
self.message_obj = messages
|
||||||
|
|
||||||
def save(self):
|
def save(self) -> None:
|
||||||
"""Write discovery message into data file."""
|
"""Write discovery message into data file."""
|
||||||
messages = []
|
messages: List[Dict[str, Any]] = []
|
||||||
for message in self.list_messages:
|
for message in self.list_messages:
|
||||||
messages.append(attr.asdict(message))
|
messages.append(attr.asdict(message))
|
||||||
|
|
||||||
@ -49,22 +64,21 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
|||||||
self._data[ATTR_DISCOVERY].extend(messages)
|
self._data[ATTR_DISCOVERY].extend(messages)
|
||||||
self.save_data()
|
self.save_data()
|
||||||
|
|
||||||
def get(self, uuid):
|
def get(self, uuid: str) -> Optional[Message]:
|
||||||
"""Return discovery message."""
|
"""Return discovery message."""
|
||||||
return self.message_obj.get(uuid)
|
return self.message_obj.get(uuid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def list_messages(self):
|
def list_messages(self) -> List[Message]:
|
||||||
"""Return list of available discovery messages."""
|
"""Return list of available discovery messages."""
|
||||||
return list(self.message_obj.values())
|
return list(self.message_obj.values())
|
||||||
|
|
||||||
def send(self, addon, service, config):
|
def send(self, addon: Addon, service: str, config: Dict[str, Any]) -> Message:
|
||||||
"""Send a discovery message to Home Assistant."""
|
"""Send a discovery message to Home Assistant."""
|
||||||
try:
|
try:
|
||||||
config = DISCOVERY_SERVICES[service](config)
|
config = valid_discovery_config(service, config)
|
||||||
except vol.Invalid as err:
|
except vol.Invalid as err:
|
||||||
_LOGGER.error(
|
_LOGGER.error("Invalid discovery %s config", humanize_error(config, err))
|
||||||
"Invalid discovery %s config", humanize_error(config, err))
|
|
||||||
raise DiscoveryError() from None
|
raise DiscoveryError() from None
|
||||||
|
|
||||||
# Create message
|
# Create message
|
||||||
@ -77,24 +91,26 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
|||||||
_LOGGER.info("Duplicate discovery message from %s", addon.slug)
|
_LOGGER.info("Duplicate discovery message from %s", addon.slug)
|
||||||
return old_message
|
return old_message
|
||||||
|
|
||||||
_LOGGER.info("Send discovery to Home Assistant %s from %s",
|
_LOGGER.info("Send discovery to Home Assistant %s from %s", service, addon.slug)
|
||||||
service, addon.slug)
|
|
||||||
self.message_obj[message.uuid] = message
|
self.message_obj[message.uuid] = message
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
self.sys_create_task(self._push_discovery(message, CMD_NEW))
|
self.sys_create_task(self._push_discovery(message, CMD_NEW))
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def remove(self, message):
|
def remove(self, message: Message) -> None:
|
||||||
"""Remove a discovery message from Home Assistant."""
|
"""Remove a discovery message from Home Assistant."""
|
||||||
self.message_obj.pop(message.uuid, None)
|
self.message_obj.pop(message.uuid, None)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
_LOGGER.info("Delete discovery to Home Assistant %s from %s",
|
_LOGGER.info(
|
||||||
message.service, message.addon)
|
"Delete discovery to Home Assistant %s from %s",
|
||||||
|
message.service,
|
||||||
|
message.addon,
|
||||||
|
)
|
||||||
self.sys_create_task(self._push_discovery(message, CMD_DEL))
|
self.sys_create_task(self._push_discovery(message, CMD_DEL))
|
||||||
|
|
||||||
async def _push_discovery(self, message, command):
|
async def _push_discovery(self, message: Message, command: str) -> None:
|
||||||
"""Send a discovery request."""
|
"""Send a discovery request."""
|
||||||
if not await self.sys_homeassistant.check_api_state():
|
if not await self.sys_homeassistant.check_api_state():
|
||||||
_LOGGER.info("Discovery %s mesage ignore", message.uuid)
|
_LOGGER.info("Discovery %s mesage ignore", message.uuid)
|
||||||
@ -105,18 +121,12 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
|||||||
|
|
||||||
with suppress(HomeAssistantAPIError):
|
with suppress(HomeAssistantAPIError):
|
||||||
async with self.sys_homeassistant.make_request(
|
async with self.sys_homeassistant.make_request(
|
||||||
command, f"api/hassio_push/discovery/{message.uuid}",
|
command,
|
||||||
json=data, timeout=10):
|
f"api/hassio_push/discovery/{message.uuid}",
|
||||||
|
json=data,
|
||||||
|
timeout=10,
|
||||||
|
):
|
||||||
_LOGGER.info("Discovery %s message send", message.uuid)
|
_LOGGER.info("Discovery %s message send", message.uuid)
|
||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.warning("Discovery %s message fail", message.uuid)
|
_LOGGER.warning("Discovery %s message fail", message.uuid)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
|
||||||
class Message:
|
|
||||||
"""Represent a single Discovery message."""
|
|
||||||
addon = attr.ib()
|
|
||||||
service = attr.ib()
|
|
||||||
config = attr.ib(cmp=False)
|
|
||||||
uuid = attr.ib(factory=lambda: uuid4().hex, cmp=False)
|
|
8
hassio/discovery/const.py
Normal file
8
hassio/discovery/const.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
"""Discovery static data."""
|
||||||
|
|
||||||
|
ATTR_HOST = "host"
|
||||||
|
ATTR_PASSWORD = "password"
|
||||||
|
ATTR_PORT = "port"
|
||||||
|
ATTR_PROTOCOL = "protocol"
|
||||||
|
ATTR_SSL = "ssl"
|
||||||
|
ATTR_USERNAME = "username"
|
1
hassio/discovery/services/__init__.py
Normal file
1
hassio/discovery/services/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Discovery service modules."""
|
11
hassio/discovery/services/deconz.py
Normal file
11
hassio/discovery/services/deconz.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"""Discovery service for MQTT."""
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from hassio.validate import NETWORK_PORT
|
||||||
|
|
||||||
|
from ..const import ATTR_HOST, ATTR_PORT
|
||||||
|
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
|
SCHEMA = vol.Schema(
|
||||||
|
{vol.Required(ATTR_HOST): vol.Coerce(str), vol.Required(ATTR_PORT): NETWORK_PORT}
|
||||||
|
)
|
27
hassio/discovery/services/mqtt.py
Normal file
27
hassio/discovery/services/mqtt.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"""Discovery service for MQTT."""
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from hassio.validate import NETWORK_PORT
|
||||||
|
|
||||||
|
from ..const import (
|
||||||
|
ATTR_HOST,
|
||||||
|
ATTR_PASSWORD,
|
||||||
|
ATTR_PORT,
|
||||||
|
ATTR_PROTOCOL,
|
||||||
|
ATTR_SSL,
|
||||||
|
ATTR_USERNAME,
|
||||||
|
)
|
||||||
|
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
|
SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_HOST): vol.Coerce(str),
|
||||||
|
vol.Required(ATTR_PORT): NETWORK_PORT,
|
||||||
|
vol.Optional(ATTR_USERNAME): vol.Coerce(str),
|
||||||
|
vol.Optional(ATTR_PASSWORD): vol.Coerce(str),
|
||||||
|
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
||||||
|
vol.Optional(ATTR_PROTOCOL, default="3.1.1"): vol.All(
|
||||||
|
vol.Coerce(str), vol.In(["3.1", "3.1.1"])
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
47
hassio/discovery/validate.py
Normal file
47
hassio/discovery/validate.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
"""Validate services schema."""
|
||||||
|
from pathlib import Path
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from ..const import ATTR_ADDON, ATTR_CONFIG, ATTR_DISCOVERY, ATTR_SERVICE, ATTR_UUID
|
||||||
|
from ..utils.validate import schema_or
|
||||||
|
from ..validate import UUID_MATCH
|
||||||
|
|
||||||
|
|
||||||
|
def valid_discovery_service(service):
|
||||||
|
"""Validate service name."""
|
||||||
|
service_file = Path(__file__).parent.joinpath(f"services/{service}.py")
|
||||||
|
if not service_file.exists():
|
||||||
|
raise vol.Invalid(f"Service {service} not found")
|
||||||
|
return service
|
||||||
|
|
||||||
|
|
||||||
|
def valid_discovery_config(service, config):
|
||||||
|
"""Validate service name."""
|
||||||
|
try:
|
||||||
|
service_mod = import_module(f".services.{service}", "hassio.discovery")
|
||||||
|
except ImportError:
|
||||||
|
raise vol.Invalid(f"Service {service} not found")
|
||||||
|
|
||||||
|
return service_mod.SCHEMA(config)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_DISCOVERY = vol.Schema(
|
||||||
|
[
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_UUID): UUID_MATCH,
|
||||||
|
vol.Required(ATTR_ADDON): vol.Coerce(str),
|
||||||
|
vol.Required(ATTR_SERVICE): valid_discovery_service,
|
||||||
|
vol.Required(ATTR_CONFIG): vol.Maybe(dict),
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
SCHEMA_DISCOVERY_CONFIG = vol.Schema(
|
||||||
|
{vol.Optional(ATTR_DISCOVERY, default=list): schema_or(SCHEMA_DISCOVERY)},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
@ -1,38 +1,38 @@
|
|||||||
"""Handle internal services discovery."""
|
"""Handle internal services discovery."""
|
||||||
from .mqtt import MQTTService
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
from .const import SERVICE_MQTT
|
||||||
from .data import ServicesData
|
from .data import ServicesData
|
||||||
from ..const import SERVICE_MQTT
|
from .interface import ServiceInterface
|
||||||
from ..coresys import CoreSysAttributes
|
from .modules.mqtt import MQTTService
|
||||||
|
|
||||||
|
AVAILABLE_SERVICES = {SERVICE_MQTT: MQTTService}
|
||||||
AVAILABLE_SERVICES = {
|
|
||||||
SERVICE_MQTT: MQTTService
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceManager(CoreSysAttributes):
|
class ServiceManager(CoreSysAttributes):
|
||||||
"""Handle internal services discovery."""
|
"""Handle internal services discovery."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize Services handler."""
|
"""Initialize Services handler."""
|
||||||
self.coresys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.data = ServicesData()
|
self.data: ServicesData = ServicesData()
|
||||||
self.services_obj = {}
|
self.services_obj: Dict[str, ServiceInterface] = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def list_services(self):
|
def list_services(self) -> List[ServiceInterface]:
|
||||||
"""Return a list of services."""
|
"""Return a list of services."""
|
||||||
return list(self.services_obj.values())
|
return list(self.services_obj.values())
|
||||||
|
|
||||||
def get(self, slug):
|
def get(self, slug: str) -> ServiceInterface:
|
||||||
"""Return service object from slug."""
|
"""Return service object from slug."""
|
||||||
return self.services_obj.get(slug)
|
return self.services_obj.get(slug)
|
||||||
|
|
||||||
async def load(self):
|
async def load(self) -> None:
|
||||||
"""Load available services."""
|
"""Load available services."""
|
||||||
for slug, service in AVAILABLE_SERVICES.items():
|
for slug, service in AVAILABLE_SERVICES.items():
|
||||||
self.services_obj[slug] = service(self.coresys)
|
self.services_obj[slug] = service(self.coresys)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self) -> None:
|
||||||
"""Reset available data."""
|
"""Reset available data."""
|
||||||
self.data.reset_data()
|
self.data.reset_data()
|
||||||
|
11
hassio/services/const.py
Normal file
11
hassio/services/const.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"""Service API static data."""
|
||||||
|
|
||||||
|
ATTR_ADDON = "addon"
|
||||||
|
ATTR_HOST = "host"
|
||||||
|
ATTR_PASSWORD = "password"
|
||||||
|
ATTR_PORT = "port"
|
||||||
|
ATTR_PROTOCOL = "protocol"
|
||||||
|
ATTR_SSL = "ssl"
|
||||||
|
ATTR_USERNAME = "username"
|
||||||
|
|
||||||
|
SERVICE_MQTT = "mqtt"
|
@ -1,8 +1,10 @@
|
|||||||
"""Handle service data for persistent supervisor reboot."""
|
"""Handle service data for persistent supervisor reboot."""
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
from .validate import SCHEMA_SERVICES_CONFIG
|
from ..const import FILE_HASSIO_SERVICES
|
||||||
from ..const import FILE_HASSIO_SERVICES, SERVICE_MQTT
|
|
||||||
from ..utils.json import JsonConfig
|
from ..utils.json import JsonConfig
|
||||||
|
from .const import SERVICE_MQTT
|
||||||
|
from .validate import SCHEMA_SERVICES_CONFIG
|
||||||
|
|
||||||
|
|
||||||
class ServicesData(JsonConfig):
|
class ServicesData(JsonConfig):
|
||||||
@ -13,6 +15,6 @@ class ServicesData(JsonConfig):
|
|||||||
super().__init__(FILE_HASSIO_SERVICES, SCHEMA_SERVICES_CONFIG)
|
super().__init__(FILE_HASSIO_SERVICES, SCHEMA_SERVICES_CONFIG)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mqtt(self):
|
def mqtt(self) -> Dict[str, Any]:
|
||||||
"""Return settings for MQTT service."""
|
"""Return settings for MQTT service."""
|
||||||
return self._data[SERVICE_MQTT]
|
return self._data[SERVICE_MQTT]
|
||||||
|
@ -1,33 +1,37 @@
|
|||||||
"""Interface for single service."""
|
"""Interface for single service."""
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from ..coresys import CoreSysAttributes
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from ..addons.addon import Addon
|
||||||
from ..const import PROVIDE_SERVICE
|
from ..const import PROVIDE_SERVICE
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
|
||||||
|
|
||||||
class ServiceInterface(CoreSysAttributes):
|
class ServiceInterface(CoreSysAttributes):
|
||||||
"""Interface class for service integration."""
|
"""Interface class for service integration."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize service interface."""
|
"""Initialize service interface."""
|
||||||
self.coresys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slug(self):
|
def slug(self) -> str:
|
||||||
"""Return slug of this service."""
|
"""Return slug of this service."""
|
||||||
return None
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _data(self):
|
def _data(self) -> Dict[str, Any]:
|
||||||
"""Return data of this service."""
|
"""Return data of this service."""
|
||||||
return None
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def schema(self):
|
def schema(self) -> vol.Schema:
|
||||||
"""Return data schema of this service."""
|
"""Return data schema of this service."""
|
||||||
return None
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def providers(self):
|
def providers(self) -> List[str]:
|
||||||
"""Return name of service providers addon."""
|
"""Return name of service providers addon."""
|
||||||
addons = []
|
addons = []
|
||||||
for addon in self.sys_addons.list_installed:
|
for addon in self.sys_addons.list_installed:
|
||||||
@ -36,24 +40,24 @@ class ServiceInterface(CoreSysAttributes):
|
|||||||
return addons
|
return addons
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self):
|
def enabled(self) -> bool:
|
||||||
"""Return True if the service is in use."""
|
"""Return True if the service is in use."""
|
||||||
return bool(self._data)
|
return bool(self._data)
|
||||||
|
|
||||||
def save(self):
|
def save(self) -> None:
|
||||||
"""Save changes."""
|
"""Save changes."""
|
||||||
self.sys_services.data.save_data()
|
self.sys_services.data.save_data()
|
||||||
|
|
||||||
def get_service_data(self):
|
def get_service_data(self) -> Optional[Dict[str, Any]]:
|
||||||
"""Return the requested service data."""
|
"""Return the requested service data."""
|
||||||
if self.enabled:
|
if self.enabled:
|
||||||
return self._data
|
return self._data
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_service_data(self, addon, data):
|
def set_service_data(self, addon: Addon, data: Dict[str, Any]) -> None:
|
||||||
"""Write the data into service object."""
|
"""Write the data into service object."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def del_service_data(self, addon):
|
def del_service_data(self, addon: Addon) -> None:
|
||||||
"""Remove the data from service object."""
|
"""Remove the data from service object."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
1
hassio/services/modules/__init__.py
Normal file
1
hassio/services/modules/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Services modules."""
|
81
hassio/services/modules/mqtt.py
Normal file
81
hassio/services/modules/mqtt.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
"""Provide the MQTT Service."""
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from hassio.addons.addon import Addon
|
||||||
|
from hassio.exceptions import ServicesError
|
||||||
|
from hassio.validate import NETWORK_PORT
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from ..const import (
|
||||||
|
ATTR_ADDON,
|
||||||
|
ATTR_HOST,
|
||||||
|
ATTR_PASSWORD,
|
||||||
|
ATTR_PORT,
|
||||||
|
ATTR_PROTOCOL,
|
||||||
|
ATTR_SSL,
|
||||||
|
ATTR_USERNAME,
|
||||||
|
SERVICE_MQTT,
|
||||||
|
)
|
||||||
|
from ..interface import ServiceInterface
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
|
SCHEMA_SERVICE_MQTT = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_HOST): vol.Coerce(str),
|
||||||
|
vol.Required(ATTR_PORT): NETWORK_PORT,
|
||||||
|
vol.Optional(ATTR_USERNAME): vol.Coerce(str),
|
||||||
|
vol.Optional(ATTR_PASSWORD): vol.Coerce(str),
|
||||||
|
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
||||||
|
vol.Optional(ATTR_PROTOCOL, default="3.1.1"): vol.All(
|
||||||
|
vol.Coerce(str), vol.In(["3.1", "3.1.1"])
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SCHEMA_CONFIG_MQTT = SCHEMA_SERVICE_MQTT.extend(
|
||||||
|
{vol.Required(ATTR_ADDON): vol.Coerce(str)}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MQTTService(ServiceInterface):
|
||||||
|
"""Provide MQTT services."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def slug(self) -> str:
|
||||||
|
"""Return slug of this service."""
|
||||||
|
return SERVICE_MQTT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _data(self) -> Dict[str, Any]:
|
||||||
|
"""Return data of this service."""
|
||||||
|
return self.sys_services.data.mqtt
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schema(self) -> vol.Schema:
|
||||||
|
"""Return data schema of this service."""
|
||||||
|
return SCHEMA_SERVICE_MQTT
|
||||||
|
|
||||||
|
def set_service_data(self, addon: Addon, data: Dict[str, Any]) -> None:
|
||||||
|
"""Write the data into service object."""
|
||||||
|
if self.enabled:
|
||||||
|
_LOGGER.error("It is already a MQTT in use from %s", self._data[ATTR_ADDON])
|
||||||
|
raise ServicesError()
|
||||||
|
|
||||||
|
self._data.update(data)
|
||||||
|
self._data[ATTR_ADDON] = addon.slug
|
||||||
|
|
||||||
|
_LOGGER.info("Set %s as service provider for mqtt", addon.slug)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def del_service_data(self, addon: Addon) -> None:
|
||||||
|
"""Remove the data from service object."""
|
||||||
|
if not self.enabled:
|
||||||
|
_LOGGER.warning("Can't remove not exists services")
|
||||||
|
raise ServicesError()
|
||||||
|
|
||||||
|
self._data.clear()
|
||||||
|
self.save()
|
@ -1,50 +0,0 @@
|
|||||||
"""Provide the MQTT Service."""
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from .interface import ServiceInterface
|
|
||||||
from .validate import SCHEMA_SERVICE_MQTT
|
|
||||||
from ..const import ATTR_ADDON, SERVICE_MQTT
|
|
||||||
from ..exceptions import ServicesError
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class MQTTService(ServiceInterface):
|
|
||||||
"""Provide MQTT services."""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def slug(self):
|
|
||||||
"""Return slug of this service."""
|
|
||||||
return SERVICE_MQTT
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _data(self):
|
|
||||||
"""Return data of this service."""
|
|
||||||
return self.sys_services.data.mqtt
|
|
||||||
|
|
||||||
@property
|
|
||||||
def schema(self):
|
|
||||||
"""Return data schema of this service."""
|
|
||||||
return SCHEMA_SERVICE_MQTT
|
|
||||||
|
|
||||||
def set_service_data(self, addon, data):
|
|
||||||
"""Write the data into service object."""
|
|
||||||
if self.enabled:
|
|
||||||
_LOGGER.error(
|
|
||||||
"It is already a MQTT in use from %s", self._data[ATTR_ADDON])
|
|
||||||
raise ServicesError()
|
|
||||||
|
|
||||||
self._data.update(data)
|
|
||||||
self._data[ATTR_ADDON] = addon.slug
|
|
||||||
|
|
||||||
_LOGGER.info("Set %s as service provider for mqtt", addon.slug)
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def del_service_data(self, addon):
|
|
||||||
"""Remove the data from service object."""
|
|
||||||
if not self.enabled:
|
|
||||||
_LOGGER.warning("Can't remove not exists services")
|
|
||||||
raise ServicesError()
|
|
||||||
|
|
||||||
self._data.clear()
|
|
||||||
self.save()
|
|
@ -1,35 +1,12 @@
|
|||||||
"""Validate services schema."""
|
"""Validate services schema."""
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from ..const import (
|
|
||||||
SERVICE_MQTT, ATTR_HOST, ATTR_PORT, ATTR_PASSWORD, ATTR_USERNAME, ATTR_SSL,
|
|
||||||
ATTR_ADDON, ATTR_PROTOCOL)
|
|
||||||
from ..validate import NETWORK_PORT
|
|
||||||
from ..utils.validate import schema_or
|
from ..utils.validate import schema_or
|
||||||
|
from .const import SERVICE_MQTT
|
||||||
|
from .modules.mqtt import SCHEMA_CONFIG_MQTT
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
SCHEMA_SERVICES_CONFIG = vol.Schema(
|
||||||
SCHEMA_SERVICE_MQTT = vol.Schema({
|
{vol.Optional(SERVICE_MQTT, default=dict): schema_or(SCHEMA_CONFIG_MQTT)},
|
||||||
vol.Required(ATTR_HOST): vol.Coerce(str),
|
extra=vol.REMOVE_EXTRA,
|
||||||
vol.Required(ATTR_PORT): NETWORK_PORT,
|
)
|
||||||
vol.Optional(ATTR_USERNAME): vol.Coerce(str),
|
|
||||||
vol.Optional(ATTR_PASSWORD): vol.Coerce(str),
|
|
||||||
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
|
||||||
vol.Optional(ATTR_PROTOCOL, default='3.1.1'):
|
|
||||||
vol.All(vol.Coerce(str), vol.In(['3.1', '3.1.1'])),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_CONFIG_MQTT = SCHEMA_SERVICE_MQTT.extend({
|
|
||||||
vol.Required(ATTR_ADDON): vol.Coerce(str),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_SERVICES_CONFIG = vol.Schema({
|
|
||||||
vol.Optional(SERVICE_MQTT, default=dict): schema_or(SCHEMA_CONFIG_MQTT),
|
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
|
||||||
|
|
||||||
|
|
||||||
DISCOVERY_SERVICES = {
|
|
||||||
SERVICE_MQTT: SCHEMA_SERVICE_MQTT,
|
|
||||||
}
|
|
||||||
|
@ -5,14 +5,30 @@ import re
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_CHANNEL, ATTR_TIMEZONE, ATTR_HASSOS,
|
ATTR_IMAGE,
|
||||||
ATTR_ADDONS_CUSTOM_LIST, ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO,
|
ATTR_LAST_VERSION,
|
||||||
ATTR_BOOT, ATTR_LAST_BOOT, ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_CONFIG,
|
ATTR_CHANNEL,
|
||||||
ATTR_WAIT_BOOT, ATTR_UUID, ATTR_REFRESH_TOKEN, ATTR_HASSOS_CLI,
|
ATTR_TIMEZONE,
|
||||||
ATTR_ACCESS_TOKEN, ATTR_DISCOVERY, ATTR_ADDON, ATTR_SERVICE,
|
ATTR_HASSOS,
|
||||||
SERVICE_MQTT,
|
ATTR_ADDONS_CUSTOM_LIST,
|
||||||
CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV)
|
ATTR_PASSWORD,
|
||||||
from .utils.validate import schema_or, validate_timezone
|
ATTR_HOMEASSISTANT,
|
||||||
|
ATTR_HASSIO,
|
||||||
|
ATTR_BOOT,
|
||||||
|
ATTR_LAST_BOOT,
|
||||||
|
ATTR_SSL,
|
||||||
|
ATTR_PORT,
|
||||||
|
ATTR_WATCHDOG,
|
||||||
|
ATTR_WAIT_BOOT,
|
||||||
|
ATTR_UUID,
|
||||||
|
ATTR_REFRESH_TOKEN,
|
||||||
|
ATTR_HASSOS_CLI,
|
||||||
|
ATTR_ACCESS_TOKEN,
|
||||||
|
CHANNEL_STABLE,
|
||||||
|
CHANNEL_BETA,
|
||||||
|
CHANNEL_DEV,
|
||||||
|
)
|
||||||
|
from .utils.validate import validate_timezone
|
||||||
|
|
||||||
|
|
||||||
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
|
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
|
||||||
@ -24,7 +40,6 @@ ALSA_DEVICE = vol.Maybe(vol.Match(r"\d+,\d+"))
|
|||||||
CHANNELS = vol.In([CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV])
|
CHANNELS = vol.In([CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV])
|
||||||
UUID_MATCH = vol.Match(r"^[0-9a-f]{32}$")
|
UUID_MATCH = vol.Match(r"^[0-9a-f]{32}$")
|
||||||
SHA256 = vol.Match(r"^[0-9a-f]{64}$")
|
SHA256 = vol.Match(r"^[0-9a-f]{64}$")
|
||||||
SERVICE_ALL = vol.In([SERVICE_MQTT])
|
|
||||||
|
|
||||||
|
|
||||||
def validate_repository(repository):
|
def validate_repository(repository):
|
||||||
@ -35,7 +50,7 @@ def validate_repository(repository):
|
|||||||
|
|
||||||
# Validate URL
|
# Validate URL
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
vol.Url()(data.group('url'))
|
vol.Url()(data.group("url"))
|
||||||
|
|
||||||
return repository
|
return repository
|
||||||
|
|
||||||
@ -66,63 +81,61 @@ def convert_to_docker_ports(data):
|
|||||||
raise vol.Invalid("Can't validate Docker host settings")
|
raise vol.Invalid("Can't validate Docker host settings")
|
||||||
|
|
||||||
|
|
||||||
DOCKER_PORTS = vol.Schema({
|
DOCKER_PORTS = vol.Schema(
|
||||||
vol.All(vol.Coerce(str), vol.Match(r"^\d+(?:/tcp|/udp)?$")):
|
{
|
||||||
convert_to_docker_ports,
|
vol.All(
|
||||||
})
|
vol.Coerce(str), vol.Match(r"^\d+(?:/tcp|/udp)?$")
|
||||||
|
): convert_to_docker_ports
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_HASS_CONFIG = vol.Schema({
|
SCHEMA_HASS_CONFIG = vol.Schema(
|
||||||
|
{
|
||||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): UUID_MATCH,
|
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): UUID_MATCH,
|
||||||
vol.Optional(ATTR_ACCESS_TOKEN): SHA256,
|
vol.Optional(ATTR_ACCESS_TOKEN): SHA256,
|
||||||
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
||||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): DOCKER_IMAGE,
|
vol.Inclusive(ATTR_IMAGE, "custom_hass"): DOCKER_IMAGE,
|
||||||
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str),
|
vol.Inclusive(ATTR_LAST_VERSION, "custom_hass"): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT,
|
vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT,
|
||||||
vol.Optional(ATTR_PASSWORD): vol.Maybe(vol.Coerce(str)),
|
vol.Optional(ATTR_PASSWORD): vol.Maybe(vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)),
|
vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
vol.Optional(ATTR_SSL, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_WATCHDOG, default=True): vol.Boolean(),
|
vol.Optional(ATTR_WATCHDOG, default=True): vol.Boolean(),
|
||||||
vol.Optional(ATTR_WAIT_BOOT, default=600):
|
vol.Optional(ATTR_WAIT_BOOT, default=600): vol.All(
|
||||||
vol.All(vol.Coerce(int), vol.Range(min=60)),
|
vol.Coerce(int), vol.Range(min=60)
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
),
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_UPDATER_CONFIG = vol.Schema({
|
SCHEMA_UPDATER_CONFIG = vol.Schema(
|
||||||
|
{
|
||||||
vol.Optional(ATTR_CHANNEL, default=CHANNEL_STABLE): CHANNELS,
|
vol.Optional(ATTR_CHANNEL, default=CHANNEL_STABLE): CHANNELS,
|
||||||
vol.Optional(ATTR_HOMEASSISTANT): vol.Coerce(str),
|
vol.Optional(ATTR_HOMEASSISTANT): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_HASSIO): vol.Coerce(str),
|
vol.Optional(ATTR_HASSIO): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_HASSOS): vol.Coerce(str),
|
vol.Optional(ATTR_HASSOS): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_HASSOS_CLI): vol.Coerce(str),
|
vol.Optional(ATTR_HASSOS_CLI): vol.Coerce(str),
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_HASSIO_CONFIG = vol.Schema({
|
SCHEMA_HASSIO_CONFIG = vol.Schema(
|
||||||
vol.Optional(ATTR_TIMEZONE, default='UTC'): validate_timezone,
|
{
|
||||||
|
vol.Optional(ATTR_TIMEZONE, default="UTC"): validate_timezone,
|
||||||
vol.Optional(ATTR_LAST_BOOT): vol.Coerce(str),
|
vol.Optional(ATTR_LAST_BOOT): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_ADDONS_CUSTOM_LIST, default=[
|
vol.Optional(
|
||||||
"https://github.com/hassio-addons/repository",
|
ATTR_ADDONS_CUSTOM_LIST,
|
||||||
]): REPOSITORIES,
|
default=["https://github.com/hassio-addons/repository"],
|
||||||
|
): REPOSITORIES,
|
||||||
vol.Optional(ATTR_WAIT_BOOT, default=5): WAIT_BOOT,
|
vol.Optional(ATTR_WAIT_BOOT, default=5): WAIT_BOOT,
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_DISCOVERY = vol.Schema([
|
SCHEMA_AUTH_CONFIG = vol.Schema({SHA256: SHA256})
|
||||||
vol.Schema({
|
|
||||||
vol.Required(ATTR_UUID): UUID_MATCH,
|
|
||||||
vol.Required(ATTR_ADDON): vol.Coerce(str),
|
|
||||||
vol.Required(ATTR_SERVICE): SERVICE_ALL,
|
|
||||||
vol.Required(ATTR_CONFIG): vol.Maybe(dict),
|
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
|
||||||
])
|
|
||||||
|
|
||||||
SCHEMA_DISCOVERY_CONFIG = vol.Schema({
|
|
||||||
vol.Optional(ATTR_DISCOVERY, default=list): schema_or(SCHEMA_DISCOVERY),
|
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_AUTH_CONFIG = vol.Schema({
|
|
||||||
SHA256: SHA256
|
|
||||||
})
|
|
||||||
|
1
tests/discovery/__init__.py
Normal file
1
tests/discovery/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for discovery."""
|
19
tests/discovery/test_deconz.py
Normal file
19
tests/discovery/test_deconz.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
"""Test DeConz discovery."""
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from hassio.discovery.validate import valid_discovery_config
|
||||||
|
|
||||||
|
|
||||||
|
def test_good_config():
|
||||||
|
"""Test good deconz config."""
|
||||||
|
|
||||||
|
valid_discovery_config("deconz", {"host": "test", "port": 3812})
|
||||||
|
|
||||||
|
|
||||||
|
def test_bad_config():
|
||||||
|
"""Test good deconz config."""
|
||||||
|
|
||||||
|
with pytest.raises(vol.Invalid):
|
||||||
|
valid_discovery_config("deconz", {"host": "test"})
|
21
tests/discovery/test_mqtt.py
Normal file
21
tests/discovery/test_mqtt.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
"""Test MQTT discovery."""
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from hassio.discovery.validate import valid_discovery_config
|
||||||
|
|
||||||
|
|
||||||
|
def test_good_config():
|
||||||
|
"""Test good mqtt config."""
|
||||||
|
|
||||||
|
valid_discovery_config(
|
||||||
|
"mqtt", {"host": "test", "port": 3812, "username": "bla", "ssl": True}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bad_config():
|
||||||
|
"""Test good mqtt config."""
|
||||||
|
|
||||||
|
with pytest.raises(vol.Invalid):
|
||||||
|
valid_discovery_config("mqtt", {"host": "test", "username": "bla", "ssl": True})
|
21
tests/discovery/test_validate.py
Normal file
21
tests/discovery/test_validate.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
"""Test validate of discovery."""
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from hassio.discovery import validate
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_services():
|
||||||
|
"""Validate that service is valid."""
|
||||||
|
|
||||||
|
for service in ("mqtt", "deconz"):
|
||||||
|
validate.valid_discovery_service(service)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_services():
|
||||||
|
"""Test that validate is invalid for a service."""
|
||||||
|
|
||||||
|
for service in ("fadsfasd", "203432"):
|
||||||
|
with pytest.raises(vol.Invalid):
|
||||||
|
validate.valid_discovery_service(service)
|
Loading…
x
Reference in New Issue
Block a user