mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-18 06:36:30 +00:00
Group all homeassistant/core into a module (#1950)
* Group all homeassistant/core into a module * fix references * fix lint * streamline object property protection * Fix api
This commit is contained in:
parent
b3308ecbe0
commit
2411b4287d
@ -359,7 +359,7 @@ class Addon(AddonModel):
|
|||||||
options = self.options
|
options = self.options
|
||||||
|
|
||||||
# Update secrets for validation
|
# Update secrets for validation
|
||||||
await self.sys_secrets.reload()
|
await self.sys_homeassistant.secrets.reload()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
options = schema(options)
|
options = schema(options)
|
||||||
|
@ -282,7 +282,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
addon = self._extract_addon_installed(request)
|
addon = self._extract_addon_installed(request)
|
||||||
|
|
||||||
# Update secrets for validation
|
# Update secrets for validation
|
||||||
await self.sys_secrets.reload()
|
await self.sys_homeassistant.secrets.reload()
|
||||||
|
|
||||||
# Extend schema with add-on specific validation
|
# Extend schema with add-on specific validation
|
||||||
addon_schema = SCHEMA_OPTIONS.extend(
|
addon_schema = SCHEMA_OPTIONS.extend(
|
||||||
|
@ -117,7 +117,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def stats(self, request: web.Request) -> Dict[Any, str]:
|
async def stats(self, request: web.Request) -> Dict[Any, str]:
|
||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_homeassistant.stats()
|
stats = await self.sys_homeassistant.core.stats()
|
||||||
if not stats:
|
if not stats:
|
||||||
raise APIError("No stats available")
|
raise APIError("No stats available")
|
||||||
|
|
||||||
@ -138,36 +138,36 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_homeassistant.latest_version)
|
version = body.get(ATTR_VERSION, self.sys_homeassistant.latest_version)
|
||||||
|
|
||||||
await asyncio.shield(self.sys_homeassistant.update(version))
|
await asyncio.shield(self.sys_homeassistant.core.update(version))
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def stop(self, request: web.Request) -> Awaitable[None]:
|
def stop(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Stop Home Assistant."""
|
"""Stop Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.stop())
|
return asyncio.shield(self.sys_homeassistant.core.stop())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def start(self, request: web.Request) -> Awaitable[None]:
|
def start(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Start Home Assistant."""
|
"""Start Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.start())
|
return asyncio.shield(self.sys_homeassistant.core.start())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request: web.Request) -> Awaitable[None]:
|
def restart(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Restart Home Assistant."""
|
"""Restart Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.restart())
|
return asyncio.shield(self.sys_homeassistant.core.restart())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def rebuild(self, request: web.Request) -> Awaitable[None]:
|
def rebuild(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Rebuild Home Assistant."""
|
"""Rebuild Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.rebuild())
|
return asyncio.shield(self.sys_homeassistant.core.rebuild())
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
||||||
"""Return Home Assistant Docker logs."""
|
"""Return Home Assistant Docker logs."""
|
||||||
return self.sys_homeassistant.logs()
|
return self.sys_homeassistant.core.logs()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def check(self, request: web.Request) -> None:
|
async def check(self, request: web.Request) -> None:
|
||||||
"""Check configuration of Home Assistant."""
|
"""Check configuration of Home Assistant."""
|
||||||
result = await self.sys_homeassistant.check_config()
|
result = await self.sys_homeassistant.core.check_config()
|
||||||
if not result.valid:
|
if not result.valid:
|
||||||
raise APIError(result.log)
|
raise APIError(result.log)
|
||||||
|
@ -45,7 +45,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
async def _api_client(self, request: web.Request, path: str, timeout: int = 300):
|
async def _api_client(self, request: web.Request, path: str, timeout: int = 300):
|
||||||
"""Return a client request with proxy origin for Home Assistant."""
|
"""Return a client request with proxy origin for Home Assistant."""
|
||||||
try:
|
try:
|
||||||
async with self.sys_homeassistant.make_request(
|
async with self.sys_homeassistant.api.make_request(
|
||||||
request.method.lower(),
|
request.method.lower(),
|
||||||
f"api/{path}",
|
f"api/{path}",
|
||||||
headers={
|
headers={
|
||||||
@ -75,7 +75,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
async def stream(self, request: web.Request):
|
async def stream(self, request: web.Request):
|
||||||
"""Proxy HomeAssistant EventStream Requests."""
|
"""Proxy HomeAssistant EventStream Requests."""
|
||||||
self._check_access(request)
|
self._check_access(request)
|
||||||
if not await self.sys_homeassistant.check_api_state():
|
if not await self.sys_homeassistant.api.check_api_state():
|
||||||
raise HTTPBadGateway()
|
raise HTTPBadGateway()
|
||||||
|
|
||||||
_LOGGER.info("Home Assistant EventStream start")
|
_LOGGER.info("Home Assistant EventStream start")
|
||||||
@ -96,7 +96,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
async def api(self, request: web.Request):
|
async def api(self, request: web.Request):
|
||||||
"""Proxy Home Assistant API Requests."""
|
"""Proxy Home Assistant API Requests."""
|
||||||
self._check_access(request)
|
self._check_access(request)
|
||||||
if not await self.sys_homeassistant.check_api_state():
|
if not await self.sys_homeassistant.api.check_api_state():
|
||||||
raise HTTPBadGateway()
|
raise HTTPBadGateway()
|
||||||
|
|
||||||
# Normal request
|
# Normal request
|
||||||
@ -130,7 +130,10 @@ class APIProxy(CoreSysAttributes):
|
|||||||
# Auth session
|
# Auth session
|
||||||
await self.sys_homeassistant.ensure_access_token()
|
await self.sys_homeassistant.ensure_access_token()
|
||||||
await client.send_json(
|
await client.send_json(
|
||||||
{"type": "auth", "access_token": self.sys_homeassistant.access_token}
|
{
|
||||||
|
"type": "auth",
|
||||||
|
"access_token": self.sys_homeassistant.api.access_token,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
data = await client.receive_json()
|
data = await client.receive_json()
|
||||||
@ -143,7 +146,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
data.get("type") == "invalid_auth"
|
data.get("type") == "invalid_auth"
|
||||||
and self.sys_homeassistant.refresh_token
|
and self.sys_homeassistant.refresh_token
|
||||||
):
|
):
|
||||||
self.sys_homeassistant.access_token = None
|
self.sys_homeassistant.api.access_token = None
|
||||||
return await self._websocket_client()
|
return await self._websocket_client()
|
||||||
|
|
||||||
raise HomeAssistantAuthError()
|
raise HomeAssistantAuthError()
|
||||||
@ -157,7 +160,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
|
|
||||||
async def websocket(self, request: web.Request):
|
async def websocket(self, request: web.Request):
|
||||||
"""Initialize a WebSocket API connection."""
|
"""Initialize a WebSocket API connection."""
|
||||||
if not await self.sys_homeassistant.check_api_state():
|
if not await self.sys_homeassistant.api.check_api_state():
|
||||||
raise HTTPBadGateway()
|
raise HTTPBadGateway()
|
||||||
_LOGGER.info("Home Assistant WebSocket API request initialize")
|
_LOGGER.info("Home Assistant WebSocket API request initialize")
|
||||||
|
|
||||||
|
@ -176,7 +176,9 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
def reload(self, request: web.Request) -> Awaitable[None]:
|
def reload(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Reload add-ons, configuration, etc."""
|
"""Reload add-ons, configuration, etc."""
|
||||||
return asyncio.shield(
|
return asyncio.shield(
|
||||||
asyncio.wait([self.sys_updater.reload(), self.sys_secrets.reload()])
|
asyncio.wait(
|
||||||
|
[self.sys_updater.reload(), self.sys_homeassistant.secrets.reload()]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@ -62,12 +62,12 @@ class Auth(JsonConfig, CoreSysAttributes):
|
|||||||
_LOGGER.info("Auth request from %s for %s", addon.slug, username)
|
_LOGGER.info("Auth request from %s for %s", addon.slug, username)
|
||||||
|
|
||||||
# Check API state
|
# Check API state
|
||||||
if not await self.sys_homeassistant.check_api_state():
|
if not await self.sys_homeassistant.api.check_api_state():
|
||||||
_LOGGER.info("Home Assistant not running, check cache")
|
_LOGGER.info("Home Assistant not running, check cache")
|
||||||
return self._check_cache(username, password)
|
return self._check_cache(username, password)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with self.sys_homeassistant.make_request(
|
async with self.sys_homeassistant.api.make_request(
|
||||||
"post",
|
"post",
|
||||||
"api/hassio_auth",
|
"api/hassio_auth",
|
||||||
json={
|
json={
|
||||||
@ -93,7 +93,7 @@ class Auth(JsonConfig, CoreSysAttributes):
|
|||||||
async def change_password(self, username: str, password: str) -> None:
|
async def change_password(self, username: str, password: str) -> None:
|
||||||
"""Change user password login."""
|
"""Change user password login."""
|
||||||
try:
|
try:
|
||||||
async with self.sys_homeassistant.make_request(
|
async with self.sys_homeassistant.api.make_request(
|
||||||
"post",
|
"post",
|
||||||
"api/hassio_auth/password_reset",
|
"api/hassio_auth/password_reset",
|
||||||
json={ATTR_USERNAME: username, ATTR_PASSWORD: password},
|
json={ATTR_USERNAME: username, ATTR_PASSWORD: password},
|
||||||
|
@ -37,7 +37,6 @@ from .ingress import Ingress
|
|||||||
from .misc.filter import filter_data
|
from .misc.filter import filter_data
|
||||||
from .misc.hwmon import HwMonitor
|
from .misc.hwmon import HwMonitor
|
||||||
from .misc.scheduler import Scheduler
|
from .misc.scheduler import Scheduler
|
||||||
from .misc.secrets import SecretsManager
|
|
||||||
from .misc.tasks import Tasks
|
from .misc.tasks import Tasks
|
||||||
from .plugins import PluginManager
|
from .plugins import PluginManager
|
||||||
from .services import ServiceManager
|
from .services import ServiceManager
|
||||||
@ -74,7 +73,6 @@ async def initialize_coresys() -> CoreSys:
|
|||||||
coresys.discovery = Discovery(coresys)
|
coresys.discovery = Discovery(coresys)
|
||||||
coresys.dbus = DBusManager(coresys)
|
coresys.dbus = DBusManager(coresys)
|
||||||
coresys.hassos = HassOS(coresys)
|
coresys.hassos = HassOS(coresys)
|
||||||
coresys.secrets = SecretsManager(coresys)
|
|
||||||
coresys.scheduler = Scheduler(coresys)
|
coresys.scheduler = Scheduler(coresys)
|
||||||
|
|
||||||
# diagnostics
|
# diagnostics
|
||||||
|
@ -122,9 +122,6 @@ class Core(CoreSysAttributes):
|
|||||||
# Load ingress
|
# Load ingress
|
||||||
await self.sys_ingress.load()
|
await self.sys_ingress.load()
|
||||||
|
|
||||||
# Load secrets
|
|
||||||
await self.sys_secrets.load()
|
|
||||||
|
|
||||||
# Check supported OS
|
# Check supported OS
|
||||||
if not self.sys_hassos.available:
|
if not self.sys_hassos.available:
|
||||||
if self.sys_host.info.operating_system not in SUPERVISED_SUPPORTED_OS:
|
if self.sys_host.info.operating_system not in SUPERVISED_SUPPORTED_OS:
|
||||||
@ -204,10 +201,10 @@ class Core(CoreSysAttributes):
|
|||||||
# run HomeAssistant
|
# run HomeAssistant
|
||||||
if (
|
if (
|
||||||
self.sys_homeassistant.boot
|
self.sys_homeassistant.boot
|
||||||
and not await self.sys_homeassistant.is_running()
|
and not await self.sys_homeassistant.core.is_running()
|
||||||
):
|
):
|
||||||
with suppress(HomeAssistantError):
|
with suppress(HomeAssistantError):
|
||||||
await self.sys_homeassistant.start()
|
await self.sys_homeassistant.core.start()
|
||||||
else:
|
else:
|
||||||
_LOGGER.info("Skip start of Home Assistant")
|
_LOGGER.info("Skip start of Home Assistant")
|
||||||
|
|
||||||
@ -223,7 +220,7 @@ class Core(CoreSysAttributes):
|
|||||||
|
|
||||||
# If landingpage / run upgrade in background
|
# If landingpage / run upgrade in background
|
||||||
if self.sys_homeassistant.version == "landingpage":
|
if self.sys_homeassistant.version == "landingpage":
|
||||||
self.sys_create_task(self.sys_homeassistant.install())
|
self.sys_create_task(self.sys_homeassistant.core.install())
|
||||||
|
|
||||||
# Start observe the host Hardware
|
# Start observe the host Hardware
|
||||||
await self.sys_hwmonitor.load()
|
await self.sys_hwmonitor.load()
|
||||||
@ -278,7 +275,7 @@ class Core(CoreSysAttributes):
|
|||||||
|
|
||||||
# Close Home Assistant
|
# Close Home Assistant
|
||||||
with suppress(HassioError):
|
with suppress(HassioError):
|
||||||
await self.sys_homeassistant.stop()
|
await self.sys_homeassistant.core.stop()
|
||||||
|
|
||||||
# Shutdown System Add-ons
|
# Shutdown System Add-ons
|
||||||
await self.sys_addons.shutdown(AddonStartup.SERVICES)
|
await self.sys_addons.shutdown(AddonStartup.SERVICES)
|
||||||
@ -304,7 +301,7 @@ class Core(CoreSysAttributes):
|
|||||||
|
|
||||||
# Restore core functionality
|
# Restore core functionality
|
||||||
await self.sys_addons.repair()
|
await self.sys_addons.repair()
|
||||||
await self.sys_homeassistant.repair()
|
await self.sys_homeassistant.core.repair()
|
||||||
|
|
||||||
# Tag version for latest
|
# Tag version for latest
|
||||||
await self.sys_supervisor.repair()
|
await self.sys_supervisor.repair()
|
||||||
|
@ -23,7 +23,6 @@ if TYPE_CHECKING:
|
|||||||
from .hassos import HassOS
|
from .hassos import HassOS
|
||||||
from .misc.scheduler import Scheduler
|
from .misc.scheduler import Scheduler
|
||||||
from .misc.hwmon import HwMonitor
|
from .misc.hwmon import HwMonitor
|
||||||
from .misc.secrets import SecretsManager
|
|
||||||
from .misc.tasks import Tasks
|
from .misc.tasks import Tasks
|
||||||
from .homeassistant import HomeAssistant
|
from .homeassistant import HomeAssistant
|
||||||
from .host import HostManager
|
from .host import HostManager
|
||||||
@ -76,7 +75,6 @@ class CoreSys:
|
|||||||
self._dbus: Optional[DBusManager] = None
|
self._dbus: Optional[DBusManager] = None
|
||||||
self._hassos: Optional[HassOS] = None
|
self._hassos: Optional[HassOS] = None
|
||||||
self._services: Optional[ServiceManager] = None
|
self._services: Optional[ServiceManager] = None
|
||||||
self._secrets: Optional[SecretsManager] = None
|
|
||||||
self._scheduler: Optional[Scheduler] = None
|
self._scheduler: Optional[Scheduler] = None
|
||||||
self._store: Optional[StoreManager] = None
|
self._store: Optional[StoreManager] = None
|
||||||
self._discovery: Optional[Discovery] = None
|
self._discovery: Optional[Discovery] = None
|
||||||
@ -246,20 +244,6 @@ class CoreSys:
|
|||||||
raise RuntimeError("Updater already set!")
|
raise RuntimeError("Updater already set!")
|
||||||
self._updater = value
|
self._updater = value
|
||||||
|
|
||||||
@property
|
|
||||||
def secrets(self) -> SecretsManager:
|
|
||||||
"""Return SecretsManager object."""
|
|
||||||
if self._secrets is None:
|
|
||||||
raise RuntimeError("SecretsManager not set!")
|
|
||||||
return self._secrets
|
|
||||||
|
|
||||||
@secrets.setter
|
|
||||||
def secrets(self, value: SecretsManager) -> None:
|
|
||||||
"""Set a Updater object."""
|
|
||||||
if self._secrets:
|
|
||||||
raise RuntimeError("SecretsManager already set!")
|
|
||||||
self._secrets = value
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def addons(self) -> AddonManager:
|
def addons(self) -> AddonManager:
|
||||||
"""Return AddonManager object."""
|
"""Return AddonManager object."""
|
||||||
@ -529,11 +513,6 @@ class CoreSysAttributes:
|
|||||||
"""Return Updater object."""
|
"""Return Updater object."""
|
||||||
return self.coresys.updater
|
return self.coresys.updater
|
||||||
|
|
||||||
@property
|
|
||||||
def sys_secrets(self) -> SecretsManager:
|
|
||||||
"""Return SecretsManager object."""
|
|
||||||
return self.coresys.secrets
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sys_addons(self) -> AddonManager:
|
def sys_addons(self) -> AddonManager:
|
||||||
"""Return AddonManager object."""
|
"""Return AddonManager object."""
|
||||||
|
@ -117,7 +117,7 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
|||||||
|
|
||||||
async def _push_discovery(self, message: Message, command: str) -> None:
|
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.api.check_api_state():
|
||||||
_LOGGER.info("Discovery %s message ignore", message.uuid)
|
_LOGGER.info("Discovery %s message ignore", message.uuid)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
|||||||
data.pop(ATTR_CONFIG)
|
data.pop(ATTR_CONFIG)
|
||||||
|
|
||||||
with suppress(HomeAssistantAPIError):
|
with suppress(HomeAssistantAPIError):
|
||||||
async with self.sys_homeassistant.make_request(
|
async with self.sys_homeassistant.api.make_request(
|
||||||
command,
|
command,
|
||||||
f"api/hassio_push/discovery/{message.uuid}",
|
f"api/hassio_push/discovery/{message.uuid}",
|
||||||
json=data,
|
json=data,
|
||||||
|
241
supervisor/homeassistant/__init__.py
Normal file
241
supervisor/homeassistant/__init__.py
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
"""Home Assistant control object."""
|
||||||
|
import asyncio
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
from typing import Optional
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from ..const import (
|
||||||
|
ATTR_ACCESS_TOKEN,
|
||||||
|
ATTR_AUDIO_INPUT,
|
||||||
|
ATTR_AUDIO_OUTPUT,
|
||||||
|
ATTR_BOOT,
|
||||||
|
ATTR_IMAGE,
|
||||||
|
ATTR_PORT,
|
||||||
|
ATTR_REFRESH_TOKEN,
|
||||||
|
ATTR_SSL,
|
||||||
|
ATTR_UUID,
|
||||||
|
ATTR_VERSION,
|
||||||
|
ATTR_WAIT_BOOT,
|
||||||
|
ATTR_WATCHDOG,
|
||||||
|
FILE_HASSIO_HOMEASSISTANT,
|
||||||
|
)
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
from ..utils.json import JsonConfig
|
||||||
|
from ..validate import SCHEMA_HASS_CONFIG
|
||||||
|
from .api import HomeAssistantAPI
|
||||||
|
from .core import HomeAssistantCore
|
||||||
|
from .secrets import HomeAssistantSecrets
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||||
|
"""Home Assistant core object for handle it."""
|
||||||
|
|
||||||
|
def __init__(self, coresys: CoreSys):
|
||||||
|
"""Initialize Home Assistant object."""
|
||||||
|
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
||||||
|
self.coresys: CoreSys = coresys
|
||||||
|
self._api: HomeAssistantAPI = HomeAssistantAPI(coresys)
|
||||||
|
self._core: HomeAssistantCore = HomeAssistantCore(coresys)
|
||||||
|
self._secrets: HomeAssistantSecrets = HomeAssistantSecrets(coresys)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api(self) -> HomeAssistantAPI:
|
||||||
|
"""Return API handler for core."""
|
||||||
|
return self._api
|
||||||
|
|
||||||
|
@property
|
||||||
|
def core(self) -> HomeAssistantCore:
|
||||||
|
"""Return Core handler for docker."""
|
||||||
|
return self._core
|
||||||
|
|
||||||
|
@property
|
||||||
|
def secrets(self) -> HomeAssistantSecrets:
|
||||||
|
"""Return Secrets Manager for core."""
|
||||||
|
return self._secrets
|
||||||
|
|
||||||
|
@property
|
||||||
|
def machine(self) -> str:
|
||||||
|
"""Return the system machines."""
|
||||||
|
return self.core.instance.machine
|
||||||
|
|
||||||
|
@property
|
||||||
|
def arch(self) -> str:
|
||||||
|
"""Return arch of running Home Assistant."""
|
||||||
|
return self.core.instance.arch
|
||||||
|
|
||||||
|
@property
|
||||||
|
def error_state(self) -> bool:
|
||||||
|
"""Return True if system is in error."""
|
||||||
|
return self.core.error_state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ip_address(self) -> IPv4Address:
|
||||||
|
"""Return IP of Home Assistant instance."""
|
||||||
|
return self.core.instance.ip_address
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_port(self) -> int:
|
||||||
|
"""Return network port to Home Assistant instance."""
|
||||||
|
return self._data[ATTR_PORT]
|
||||||
|
|
||||||
|
@api_port.setter
|
||||||
|
def api_port(self, value: int) -> None:
|
||||||
|
"""Set network port for Home Assistant instance."""
|
||||||
|
self._data[ATTR_PORT] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_ssl(self) -> bool:
|
||||||
|
"""Return if we need ssl to Home Assistant instance."""
|
||||||
|
return self._data[ATTR_SSL]
|
||||||
|
|
||||||
|
@api_ssl.setter
|
||||||
|
def api_ssl(self, value: bool):
|
||||||
|
"""Set SSL for Home Assistant instance."""
|
||||||
|
self._data[ATTR_SSL] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_url(self) -> str:
|
||||||
|
"""Return API url to Home Assistant."""
|
||||||
|
return "{}://{}:{}".format(
|
||||||
|
"https" if self.api_ssl else "http", self.ip_address, self.api_port
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def watchdog(self) -> bool:
|
||||||
|
"""Return True if the watchdog should protect Home Assistant."""
|
||||||
|
return self._data[ATTR_WATCHDOG]
|
||||||
|
|
||||||
|
@watchdog.setter
|
||||||
|
def watchdog(self, value: bool):
|
||||||
|
"""Return True if the watchdog should protect Home Assistant."""
|
||||||
|
self._data[ATTR_WATCHDOG] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wait_boot(self) -> int:
|
||||||
|
"""Return time to wait for Home Assistant startup."""
|
||||||
|
return self._data[ATTR_WAIT_BOOT]
|
||||||
|
|
||||||
|
@wait_boot.setter
|
||||||
|
def wait_boot(self, value: int):
|
||||||
|
"""Set time to wait for Home Assistant startup."""
|
||||||
|
self._data[ATTR_WAIT_BOOT] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def latest_version(self) -> str:
|
||||||
|
"""Return last available version of Home Assistant."""
|
||||||
|
return self.sys_updater.version_homeassistant
|
||||||
|
|
||||||
|
@property
|
||||||
|
def image(self) -> str:
|
||||||
|
"""Return image name of the Home Assistant container."""
|
||||||
|
if self._data.get(ATTR_IMAGE):
|
||||||
|
return self._data[ATTR_IMAGE]
|
||||||
|
return f"homeassistant/{self.sys_machine}-homeassistant"
|
||||||
|
|
||||||
|
@image.setter
|
||||||
|
def image(self, value: str) -> None:
|
||||||
|
"""Set image name of Home Assistant container."""
|
||||||
|
self._data[ATTR_IMAGE] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self) -> Optional[str]:
|
||||||
|
"""Return version of local version."""
|
||||||
|
return self._data.get(ATTR_VERSION)
|
||||||
|
|
||||||
|
@version.setter
|
||||||
|
def version(self, value: str) -> None:
|
||||||
|
"""Set installed version."""
|
||||||
|
self._data[ATTR_VERSION] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def boot(self) -> bool:
|
||||||
|
"""Return True if Home Assistant boot is enabled."""
|
||||||
|
return self._data[ATTR_BOOT]
|
||||||
|
|
||||||
|
@boot.setter
|
||||||
|
def boot(self, value: bool):
|
||||||
|
"""Set Home Assistant boot options."""
|
||||||
|
self._data[ATTR_BOOT] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uuid(self) -> UUID:
|
||||||
|
"""Return a UUID of this Home Assistant instance."""
|
||||||
|
return self._data[ATTR_UUID]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supervisor_token(self) -> Optional[str]:
|
||||||
|
"""Return an access token for the Supervisor API."""
|
||||||
|
return self._data.get(ATTR_ACCESS_TOKEN)
|
||||||
|
|
||||||
|
@supervisor_token.setter
|
||||||
|
def supervisor_token(self, value: str) -> None:
|
||||||
|
"""Set the access token for the Supervisor API."""
|
||||||
|
self._data[ATTR_ACCESS_TOKEN] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def refresh_token(self) -> Optional[str]:
|
||||||
|
"""Return the refresh token to authenticate with Home Assistant."""
|
||||||
|
return self._data.get(ATTR_REFRESH_TOKEN)
|
||||||
|
|
||||||
|
@refresh_token.setter
|
||||||
|
def refresh_token(self, value: str):
|
||||||
|
"""Set Home Assistant refresh_token."""
|
||||||
|
self._data[ATTR_REFRESH_TOKEN] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path_pulse(self):
|
||||||
|
"""Return path to asound config."""
|
||||||
|
return Path(self.sys_config.path_tmp, "homeassistant_pulse")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path_extern_pulse(self):
|
||||||
|
"""Return path to asound config for Docker."""
|
||||||
|
return Path(self.sys_config.path_extern_tmp, "homeassistant_pulse")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def audio_output(self) -> Optional[str]:
|
||||||
|
"""Return a pulse profile for output or None."""
|
||||||
|
return self._data[ATTR_AUDIO_OUTPUT]
|
||||||
|
|
||||||
|
@audio_output.setter
|
||||||
|
def audio_output(self, value: Optional[str]):
|
||||||
|
"""Set audio output profile settings."""
|
||||||
|
self._data[ATTR_AUDIO_OUTPUT] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def audio_input(self) -> Optional[str]:
|
||||||
|
"""Return pulse profile for input or None."""
|
||||||
|
return self._data[ATTR_AUDIO_INPUT]
|
||||||
|
|
||||||
|
@audio_input.setter
|
||||||
|
def audio_input(self, value: Optional[str]):
|
||||||
|
"""Set audio input settings."""
|
||||||
|
self._data[ATTR_AUDIO_INPUT] = value
|
||||||
|
|
||||||
|
async def load(self) -> None:
|
||||||
|
"""Prepare Home Assistant object."""
|
||||||
|
await asyncio.wait([self.secrets.load(), self.core.load()])
|
||||||
|
|
||||||
|
def write_pulse(self):
|
||||||
|
"""Write asound config to file and return True on success."""
|
||||||
|
pulse_config = self.sys_plugins.audio.pulse_client(
|
||||||
|
input_profile=self.audio_input, output_profile=self.audio_output
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cleanup wrong maps
|
||||||
|
if self.path_pulse.is_dir():
|
||||||
|
shutil.rmtree(self.path_pulse, ignore_errors=True)
|
||||||
|
|
||||||
|
# Write pulse config
|
||||||
|
try:
|
||||||
|
with self.path_pulse.open("w") as config_file:
|
||||||
|
config_file.write(pulse_config)
|
||||||
|
except OSError as err:
|
||||||
|
_LOGGER.error("Home Assistant can't write pulse/client.config: %s", err)
|
||||||
|
else:
|
||||||
|
_LOGGER.info("Update pulse/client.config: %s", self.path_pulse)
|
122
supervisor/homeassistant/api.py
Normal file
122
supervisor/homeassistant/api.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
"""Home Assistant control object."""
|
||||||
|
import asyncio
|
||||||
|
from contextlib import asynccontextmanager, suppress
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import logging
|
||||||
|
from typing import Any, AsyncContextManager, Dict, Optional
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
from aiohttp import hdrs
|
||||||
|
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
from ..exceptions import HomeAssistantAPIError, HomeAssistantAuthError
|
||||||
|
from ..utils import check_port
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HomeAssistantAPI(CoreSysAttributes):
|
||||||
|
"""Home Assistant core object for handle it."""
|
||||||
|
|
||||||
|
def __init__(self, coresys: CoreSys):
|
||||||
|
"""Initialize Home Assistant object."""
|
||||||
|
self.coresys: CoreSys = coresys
|
||||||
|
|
||||||
|
# We don't persist access tokens. Instead we fetch new ones when needed
|
||||||
|
self.access_token: Optional[str] = None
|
||||||
|
self._access_token_expires: Optional[datetime] = None
|
||||||
|
|
||||||
|
async def _ensure_access_token(self) -> None:
|
||||||
|
"""Ensure there is an access token."""
|
||||||
|
if (
|
||||||
|
self.access_token is not None
|
||||||
|
and self._access_token_expires > datetime.utcnow()
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
with suppress(asyncio.TimeoutError, aiohttp.ClientError):
|
||||||
|
async with self.sys_websession_ssl.post(
|
||||||
|
f"{self.sys_homeassistant.api_url}/auth/token",
|
||||||
|
timeout=30,
|
||||||
|
data={
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
"refresh_token": self.sys_homeassistant.refresh_token,
|
||||||
|
},
|
||||||
|
) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
_LOGGER.error("Can't update Home Assistant access token!")
|
||||||
|
raise HomeAssistantAuthError()
|
||||||
|
|
||||||
|
_LOGGER.info("Updated Home Assistant API token")
|
||||||
|
tokens = await resp.json()
|
||||||
|
self.access_token = tokens["access_token"]
|
||||||
|
self._access_token_expires = datetime.utcnow() + timedelta(
|
||||||
|
seconds=tokens["expires_in"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def make_request(
|
||||||
|
self,
|
||||||
|
method: str,
|
||||||
|
path: str,
|
||||||
|
json: Optional[Dict[str, Any]] = None,
|
||||||
|
content_type: Optional[str] = None,
|
||||||
|
data: Any = None,
|
||||||
|
timeout: int = 30,
|
||||||
|
params: Optional[Dict[str, str]] = None,
|
||||||
|
headers: Optional[Dict[str, str]] = None,
|
||||||
|
) -> AsyncContextManager[aiohttp.ClientResponse]:
|
||||||
|
"""Async context manager to make a request with right auth."""
|
||||||
|
url = f"{self.sys_homeassistant.api_url}/{path}"
|
||||||
|
headers = headers or {}
|
||||||
|
|
||||||
|
# Passthrough content type
|
||||||
|
if content_type is not None:
|
||||||
|
headers[hdrs.CONTENT_TYPE] = content_type
|
||||||
|
|
||||||
|
for _ in (1, 2):
|
||||||
|
await self._ensure_access_token()
|
||||||
|
headers[hdrs.AUTHORIZATION] = f"Bearer {self.access_token}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with getattr(self.sys_websession_ssl, method)(
|
||||||
|
url,
|
||||||
|
data=data,
|
||||||
|
timeout=timeout,
|
||||||
|
json=json,
|
||||||
|
headers=headers,
|
||||||
|
params=params,
|
||||||
|
) as resp:
|
||||||
|
# Access token expired
|
||||||
|
if resp.status == 401:
|
||||||
|
self.access_token = None
|
||||||
|
continue
|
||||||
|
yield resp
|
||||||
|
return
|
||||||
|
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
|
||||||
|
_LOGGER.error("Error on call %s: %s", url, err)
|
||||||
|
break
|
||||||
|
|
||||||
|
raise HomeAssistantAPIError()
|
||||||
|
|
||||||
|
async def check_api_state(self) -> bool:
|
||||||
|
"""Return True if Home Assistant up and running."""
|
||||||
|
# Check if port is up
|
||||||
|
if not await self.sys_run_in_executor(
|
||||||
|
check_port,
|
||||||
|
self.sys_homeassistant.ip_address,
|
||||||
|
self.sys_homeassistant.api_port,
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if API is up
|
||||||
|
with suppress(HomeAssistantAPIError):
|
||||||
|
async with self.make_request("get", "api/config") as resp:
|
||||||
|
if resp.status in (200, 201):
|
||||||
|
data = await resp.json()
|
||||||
|
if data.get("state", "RUNNING") == "RUNNING":
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
_LOGGER.debug("Home Assistant API return: %d", resp.status)
|
||||||
|
|
||||||
|
return False
|
@ -1,50 +1,22 @@
|
|||||||
"""Home Assistant control object."""
|
"""Home Assistant control object."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import asynccontextmanager, suppress
|
from contextlib import suppress
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from ipaddress import IPv4Address
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
from typing import Any, AsyncContextManager, Awaitable, Dict, Optional
|
from typing import Awaitable, Optional
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
from aiohttp import hdrs
|
|
||||||
import attr
|
import attr
|
||||||
from packaging import version as pkg_version
|
from packaging import version as pkg_version
|
||||||
|
|
||||||
from .const import (
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
ATTR_ACCESS_TOKEN,
|
from ..docker.homeassistant import DockerHomeAssistant
|
||||||
ATTR_AUDIO_INPUT,
|
from ..docker.stats import DockerStats
|
||||||
ATTR_AUDIO_OUTPUT,
|
from ..exceptions import DockerAPIError, HomeAssistantError, HomeAssistantUpdateError
|
||||||
ATTR_BOOT,
|
from ..utils import convert_to_ascii, process_lock
|
||||||
ATTR_IMAGE,
|
|
||||||
ATTR_PORT,
|
|
||||||
ATTR_REFRESH_TOKEN,
|
|
||||||
ATTR_SSL,
|
|
||||||
ATTR_UUID,
|
|
||||||
ATTR_VERSION,
|
|
||||||
ATTR_WAIT_BOOT,
|
|
||||||
ATTR_WATCHDOG,
|
|
||||||
FILE_HASSIO_HOMEASSISTANT,
|
|
||||||
)
|
|
||||||
from .coresys import CoreSys, CoreSysAttributes
|
|
||||||
from .docker.homeassistant import DockerHomeAssistant
|
|
||||||
from .docker.stats import DockerStats
|
|
||||||
from .exceptions import (
|
|
||||||
DockerAPIError,
|
|
||||||
HomeAssistantAPIError,
|
|
||||||
HomeAssistantAuthError,
|
|
||||||
HomeAssistantError,
|
|
||||||
HomeAssistantUpdateError,
|
|
||||||
)
|
|
||||||
from .utils import check_port, convert_to_ascii, process_lock
|
|
||||||
from .utils.json import JsonConfig
|
|
||||||
from .validate import SCHEMA_HASS_CONFIG
|
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -61,192 +33,40 @@ class ConfigResult:
|
|||||||
log = attr.ib()
|
log = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
class HomeAssistant(JsonConfig, CoreSysAttributes):
|
class HomeAssistantCore(CoreSysAttributes):
|
||||||
"""Home Assistant core object for handle it."""
|
"""Home Assistant core object for handle it."""
|
||||||
|
|
||||||
def __init__(self, coresys: CoreSys):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize Home Assistant object."""
|
"""Initialize Home Assistant object."""
|
||||||
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
|
||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.instance: DockerHomeAssistant = DockerHomeAssistant(coresys)
|
self.instance: DockerHomeAssistant = DockerHomeAssistant(coresys)
|
||||||
self.lock: asyncio.Lock = asyncio.Lock()
|
self.lock: asyncio.Lock = asyncio.Lock()
|
||||||
self._error_state: bool = False
|
self._error_state: bool = False
|
||||||
|
|
||||||
# We don't persist access tokens. Instead we fetch new ones when needed
|
|
||||||
self.access_token: Optional[str] = None
|
|
||||||
self._access_token_expires: Optional[datetime] = None
|
|
||||||
|
|
||||||
async def load(self) -> None:
|
|
||||||
"""Prepare Home Assistant object."""
|
|
||||||
try:
|
|
||||||
# Evaluate Version if we lost this information
|
|
||||||
if not self.version:
|
|
||||||
self.version = await self.instance.get_latest_version(
|
|
||||||
key=pkg_version.parse
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.instance.attach(tag=self.version)
|
|
||||||
except DockerAPIError:
|
|
||||||
_LOGGER.info("No Home Assistant Docker image %s found.", self.image)
|
|
||||||
await self.install_landingpage()
|
|
||||||
else:
|
|
||||||
self.version = self.instance.version
|
|
||||||
self.image = self.instance.image
|
|
||||||
self.save_data()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def machine(self) -> str:
|
|
||||||
"""Return the system machines."""
|
|
||||||
return self.instance.machine
|
|
||||||
|
|
||||||
@property
|
|
||||||
def arch(self) -> str:
|
|
||||||
"""Return arch of running Home Assistant."""
|
|
||||||
return self.instance.arch
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def error_state(self) -> bool:
|
def error_state(self) -> bool:
|
||||||
"""Return True if system is in error."""
|
"""Return True if system is in error."""
|
||||||
return self._error_state
|
return self._error_state
|
||||||
|
|
||||||
@property
|
async def load(self) -> None:
|
||||||
def ip_address(self) -> IPv4Address:
|
"""Prepare Home Assistant object."""
|
||||||
"""Return IP of Home Assistant instance."""
|
try:
|
||||||
return self.instance.ip_address
|
# Evaluate Version if we lost this information
|
||||||
|
if not self.sys_homeassistant.version:
|
||||||
|
self.sys_homeassistant.version = await self.instance.get_latest_version(
|
||||||
|
key=pkg_version.parse
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
await self.instance.attach(tag=self.sys_homeassistant.version)
|
||||||
def api_port(self) -> int:
|
except DockerAPIError:
|
||||||
"""Return network port to Home Assistant instance."""
|
_LOGGER.info(
|
||||||
return self._data[ATTR_PORT]
|
"No Home Assistant Docker image %s found.", self.sys_homeassistant.image
|
||||||
|
)
|
||||||
@api_port.setter
|
await self.install_landingpage()
|
||||||
def api_port(self, value: int) -> None:
|
else:
|
||||||
"""Set network port for Home Assistant instance."""
|
self.sys_homeassistant.version = self.instance.version
|
||||||
self._data[ATTR_PORT] = value
|
self.sys_homeassistant.image = self.instance.image
|
||||||
|
self.sys_homeassistant.save_data()
|
||||||
@property
|
|
||||||
def api_ssl(self) -> bool:
|
|
||||||
"""Return if we need ssl to Home Assistant instance."""
|
|
||||||
return self._data[ATTR_SSL]
|
|
||||||
|
|
||||||
@api_ssl.setter
|
|
||||||
def api_ssl(self, value: bool):
|
|
||||||
"""Set SSL for Home Assistant instance."""
|
|
||||||
self._data[ATTR_SSL] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def api_url(self) -> str:
|
|
||||||
"""Return API url to Home Assistant."""
|
|
||||||
return "{}://{}:{}".format(
|
|
||||||
"https" if self.api_ssl else "http", self.ip_address, self.api_port
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def watchdog(self) -> bool:
|
|
||||||
"""Return True if the watchdog should protect Home Assistant."""
|
|
||||||
return self._data[ATTR_WATCHDOG]
|
|
||||||
|
|
||||||
@watchdog.setter
|
|
||||||
def watchdog(self, value: bool):
|
|
||||||
"""Return True if the watchdog should protect Home Assistant."""
|
|
||||||
self._data[ATTR_WATCHDOG] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def wait_boot(self) -> int:
|
|
||||||
"""Return time to wait for Home Assistant startup."""
|
|
||||||
return self._data[ATTR_WAIT_BOOT]
|
|
||||||
|
|
||||||
@wait_boot.setter
|
|
||||||
def wait_boot(self, value: int):
|
|
||||||
"""Set time to wait for Home Assistant startup."""
|
|
||||||
self._data[ATTR_WAIT_BOOT] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def latest_version(self) -> str:
|
|
||||||
"""Return last available version of Home Assistant."""
|
|
||||||
return self.sys_updater.version_homeassistant
|
|
||||||
|
|
||||||
@property
|
|
||||||
def image(self) -> str:
|
|
||||||
"""Return image name of the Home Assistant container."""
|
|
||||||
if self._data.get(ATTR_IMAGE):
|
|
||||||
return self._data[ATTR_IMAGE]
|
|
||||||
return f"homeassistant/{self.sys_machine}-homeassistant"
|
|
||||||
|
|
||||||
@image.setter
|
|
||||||
def image(self, value: str) -> None:
|
|
||||||
"""Set image name of Home Assistant container."""
|
|
||||||
self._data[ATTR_IMAGE] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def version(self) -> Optional[str]:
|
|
||||||
"""Return version of local version."""
|
|
||||||
return self._data.get(ATTR_VERSION)
|
|
||||||
|
|
||||||
@version.setter
|
|
||||||
def version(self, value: str) -> None:
|
|
||||||
"""Set installed version."""
|
|
||||||
self._data[ATTR_VERSION] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def boot(self) -> bool:
|
|
||||||
"""Return True if Home Assistant boot is enabled."""
|
|
||||||
return self._data[ATTR_BOOT]
|
|
||||||
|
|
||||||
@boot.setter
|
|
||||||
def boot(self, value: bool):
|
|
||||||
"""Set Home Assistant boot options."""
|
|
||||||
self._data[ATTR_BOOT] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def uuid(self) -> UUID:
|
|
||||||
"""Return a UUID of this Home Assistant instance."""
|
|
||||||
return self._data[ATTR_UUID]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def supervisor_token(self) -> Optional[str]:
|
|
||||||
"""Return an access token for the Supervisor API."""
|
|
||||||
return self._data.get(ATTR_ACCESS_TOKEN)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def refresh_token(self) -> Optional[str]:
|
|
||||||
"""Return the refresh token to authenticate with Home Assistant."""
|
|
||||||
return self._data.get(ATTR_REFRESH_TOKEN)
|
|
||||||
|
|
||||||
@refresh_token.setter
|
|
||||||
def refresh_token(self, value: str):
|
|
||||||
"""Set Home Assistant refresh_token."""
|
|
||||||
self._data[ATTR_REFRESH_TOKEN] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path_pulse(self):
|
|
||||||
"""Return path to asound config."""
|
|
||||||
return Path(self.sys_config.path_tmp, "homeassistant_pulse")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def path_extern_pulse(self):
|
|
||||||
"""Return path to asound config for Docker."""
|
|
||||||
return Path(self.sys_config.path_extern_tmp, "homeassistant_pulse")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def audio_output(self) -> Optional[str]:
|
|
||||||
"""Return a pulse profile for output or None."""
|
|
||||||
return self._data[ATTR_AUDIO_OUTPUT]
|
|
||||||
|
|
||||||
@audio_output.setter
|
|
||||||
def audio_output(self, value: Optional[str]):
|
|
||||||
"""Set audio output profile settings."""
|
|
||||||
self._data[ATTR_AUDIO_OUTPUT] = value
|
|
||||||
|
|
||||||
@property
|
|
||||||
def audio_input(self) -> Optional[str]:
|
|
||||||
"""Return pulse profile for input or None."""
|
|
||||||
return self._data[ATTR_AUDIO_INPUT]
|
|
||||||
|
|
||||||
@audio_input.setter
|
|
||||||
def audio_input(self, value: Optional[str]):
|
|
||||||
"""Set audio input settings."""
|
|
||||||
self._data[ATTR_AUDIO_INPUT] = value
|
|
||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
async def install_landingpage(self) -> None:
|
async def install_landingpage(self) -> None:
|
||||||
@ -271,9 +91,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
except Exception as err: # pylint: disable=broad-except
|
except Exception as err: # pylint: disable=broad-except
|
||||||
self.sys_capture_exception(err)
|
self.sys_capture_exception(err)
|
||||||
else:
|
else:
|
||||||
self.version = self.instance.version
|
self.sys_homeassistant.version = self.instance.version
|
||||||
self.image = self.sys_updater.image_homeassistant
|
self.sys_homeassistant.image = self.sys_updater.image_homeassistant
|
||||||
self.save_data()
|
self.sys_homeassistant.save_data()
|
||||||
break
|
break
|
||||||
|
|
||||||
# Start landingpage
|
# Start landingpage
|
||||||
@ -287,10 +107,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
_LOGGER.info("Setup Home Assistant")
|
_LOGGER.info("Setup Home Assistant")
|
||||||
while True:
|
while True:
|
||||||
# read homeassistant tag and install it
|
# read homeassistant tag and install it
|
||||||
if not self.latest_version:
|
if not self.sys_homeassistant.latest_version:
|
||||||
await self.sys_updater.reload()
|
await self.sys_updater.reload()
|
||||||
|
|
||||||
tag = self.latest_version
|
tag = self.sys_homeassistant.latest_version
|
||||||
if tag:
|
if tag:
|
||||||
try:
|
try:
|
||||||
await self.instance.update(
|
await self.instance.update(
|
||||||
@ -306,9 +126,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
await asyncio.sleep(30)
|
await asyncio.sleep(30)
|
||||||
|
|
||||||
_LOGGER.info("Home Assistant docker now installed")
|
_LOGGER.info("Home Assistant docker now installed")
|
||||||
self.version = self.instance.version
|
self.sys_homeassistant.version = self.instance.version
|
||||||
self.image = self.sys_updater.image_homeassistant
|
self.sys_homeassistant.image = self.sys_updater.image_homeassistant
|
||||||
self.save_data()
|
self.sys_homeassistant.save_data()
|
||||||
|
|
||||||
# finishing
|
# finishing
|
||||||
try:
|
try:
|
||||||
@ -324,9 +144,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
@process_lock
|
@process_lock
|
||||||
async def update(self, version: Optional[str] = None) -> None:
|
async def update(self, version: Optional[str] = None) -> None:
|
||||||
"""Update HomeAssistant version."""
|
"""Update HomeAssistant version."""
|
||||||
version = version or self.latest_version
|
version = version or self.sys_homeassistant.latest_version
|
||||||
old_image = self.image
|
old_image = self.sys_homeassistant.image
|
||||||
rollback = self.version if not self.error_state else None
|
rollback = self.sys_homeassistant.version if not self.error_state else None
|
||||||
running = await self.instance.is_running()
|
running = await self.instance.is_running()
|
||||||
exists = await self.instance.exists()
|
exists = await self.instance.exists()
|
||||||
|
|
||||||
@ -346,15 +166,15 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
_LOGGER.warning("Update Home Assistant image fails")
|
_LOGGER.warning("Update Home Assistant image fails")
|
||||||
raise HomeAssistantUpdateError()
|
raise HomeAssistantUpdateError()
|
||||||
else:
|
else:
|
||||||
self.version = self.instance.version
|
self.sys_homeassistant.version = self.instance.version
|
||||||
self.image = self.sys_updater.image_homeassistant
|
self.sys_homeassistant.image = self.sys_updater.image_homeassistant
|
||||||
|
|
||||||
if running:
|
if running:
|
||||||
await self._start()
|
await self._start()
|
||||||
_LOGGER.info("Successful run Home Assistant %s", to_version)
|
_LOGGER.info("Successful run Home Assistant %s", to_version)
|
||||||
|
|
||||||
# Successfull - last step
|
# Successfull - last step
|
||||||
self.save_data()
|
self.sys_homeassistant.save_data()
|
||||||
with suppress(DockerAPIError):
|
with suppress(DockerAPIError):
|
||||||
await self.instance.cleanup(old_image=old_image)
|
await self.instance.cleanup(old_image=old_image)
|
||||||
|
|
||||||
@ -384,18 +204,18 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
async def _start(self) -> None:
|
async def _start(self) -> None:
|
||||||
"""Start Home Assistant Docker & wait."""
|
"""Start Home Assistant Docker & wait."""
|
||||||
# Create new API token
|
# Create new API token
|
||||||
self._data[ATTR_ACCESS_TOKEN] = secrets.token_hex(56)
|
self.sys_homeassistant.supervisor_token = secrets.token_hex(56)
|
||||||
self.save_data()
|
self.sys_homeassistant.save_data()
|
||||||
|
|
||||||
# Write audio settings
|
# Write audio settings
|
||||||
self.write_pulse()
|
self.sys_homeassistant.write_pulse()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.instance.run()
|
await self.instance.run()
|
||||||
except DockerAPIError:
|
except DockerAPIError:
|
||||||
raise HomeAssistantError()
|
raise HomeAssistantError()
|
||||||
|
|
||||||
await self._block_till_run(self.version)
|
await self._block_till_run(self.sys_homeassistant.version)
|
||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
@ -411,7 +231,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
except DockerAPIError:
|
except DockerAPIError:
|
||||||
raise HomeAssistantError()
|
raise HomeAssistantError()
|
||||||
|
|
||||||
await self._block_till_run(self.version)
|
await self._block_till_run(self.sys_homeassistant.version)
|
||||||
# No Instance/Container found, extended start
|
# No Instance/Container found, extended start
|
||||||
else:
|
else:
|
||||||
await self._start()
|
await self._start()
|
||||||
@ -435,7 +255,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
except DockerAPIError:
|
except DockerAPIError:
|
||||||
raise HomeAssistantError()
|
raise HomeAssistantError()
|
||||||
|
|
||||||
await self._block_till_run(self.version)
|
await self._block_till_run(self.sys_homeassistant.version)
|
||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
async def rebuild(self) -> None:
|
async def rebuild(self) -> None:
|
||||||
@ -503,101 +323,6 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
_LOGGER.info("Home Assistant config is valid")
|
_LOGGER.info("Home Assistant config is valid")
|
||||||
return ConfigResult(True, log)
|
return ConfigResult(True, log)
|
||||||
|
|
||||||
async def ensure_access_token(self) -> None:
|
|
||||||
"""Ensure there is an access token."""
|
|
||||||
if (
|
|
||||||
self.access_token is not None
|
|
||||||
and self._access_token_expires > datetime.utcnow()
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
with suppress(asyncio.TimeoutError, aiohttp.ClientError):
|
|
||||||
async with self.sys_websession_ssl.post(
|
|
||||||
f"{self.api_url}/auth/token",
|
|
||||||
timeout=30,
|
|
||||||
data={
|
|
||||||
"grant_type": "refresh_token",
|
|
||||||
"refresh_token": self.refresh_token,
|
|
||||||
},
|
|
||||||
) as resp:
|
|
||||||
if resp.status != 200:
|
|
||||||
_LOGGER.error("Can't update Home Assistant access token!")
|
|
||||||
raise HomeAssistantAuthError()
|
|
||||||
|
|
||||||
_LOGGER.info("Updated Home Assistant API token")
|
|
||||||
tokens = await resp.json()
|
|
||||||
self.access_token = tokens["access_token"]
|
|
||||||
self._access_token_expires = datetime.utcnow() + timedelta(
|
|
||||||
seconds=tokens["expires_in"]
|
|
||||||
)
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def make_request(
|
|
||||||
self,
|
|
||||||
method: str,
|
|
||||||
path: str,
|
|
||||||
json: Optional[Dict[str, Any]] = None,
|
|
||||||
content_type: Optional[str] = None,
|
|
||||||
data: Any = None,
|
|
||||||
timeout: int = 30,
|
|
||||||
params: Optional[Dict[str, str]] = None,
|
|
||||||
headers: Optional[Dict[str, str]] = None,
|
|
||||||
) -> AsyncContextManager[aiohttp.ClientResponse]:
|
|
||||||
"""Async context manager to make a request with right auth."""
|
|
||||||
url = f"{self.api_url}/{path}"
|
|
||||||
headers = headers or {}
|
|
||||||
|
|
||||||
# Passthrough content type
|
|
||||||
if content_type is not None:
|
|
||||||
headers[hdrs.CONTENT_TYPE] = content_type
|
|
||||||
|
|
||||||
for _ in (1, 2):
|
|
||||||
# Prepare Access token
|
|
||||||
if self.refresh_token:
|
|
||||||
await self.ensure_access_token()
|
|
||||||
headers[hdrs.AUTHORIZATION] = f"Bearer {self.access_token}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with getattr(self.sys_websession_ssl, method)(
|
|
||||||
url,
|
|
||||||
data=data,
|
|
||||||
timeout=timeout,
|
|
||||||
json=json,
|
|
||||||
headers=headers,
|
|
||||||
params=params,
|
|
||||||
) as resp:
|
|
||||||
# Access token expired
|
|
||||||
if resp.status == 401 and self.refresh_token:
|
|
||||||
self.access_token = None
|
|
||||||
continue
|
|
||||||
yield resp
|
|
||||||
return
|
|
||||||
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
|
|
||||||
_LOGGER.error("Error on call %s: %s", url, err)
|
|
||||||
break
|
|
||||||
|
|
||||||
raise HomeAssistantAPIError()
|
|
||||||
|
|
||||||
async def check_api_state(self) -> bool:
|
|
||||||
"""Return True if Home Assistant up and running."""
|
|
||||||
# Check if port is up
|
|
||||||
if not await self.sys_run_in_executor(
|
|
||||||
check_port, self.ip_address, self.api_port
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check if API is up
|
|
||||||
with suppress(HomeAssistantAPIError):
|
|
||||||
async with self.make_request("get", "api/config") as resp:
|
|
||||||
if resp.status in (200, 201):
|
|
||||||
data = await resp.json()
|
|
||||||
if data.get("state", "RUNNING") == "RUNNING":
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
_LOGGER.debug("Home Assistant API return: %d", resp.status)
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def _block_till_run(self, version: str) -> None:
|
async def _block_till_run(self, version: str) -> None:
|
||||||
"""Block until Home-Assistant is booting up or startup timeout."""
|
"""Block until Home-Assistant is booting up or startup timeout."""
|
||||||
# Skip landingpage
|
# Skip landingpage
|
||||||
@ -631,7 +356,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# 2: Check if API response
|
# 2: Check if API response
|
||||||
if await self.check_api_state():
|
if await self.sys_homeassistant.api.check_api_state():
|
||||||
_LOGGER.info("Detect a running Home Assistant instance")
|
_LOGGER.info("Detect a running Home Assistant instance")
|
||||||
self._error_state = False
|
self._error_state = False
|
||||||
return
|
return
|
||||||
@ -659,7 +384,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
_LOGGER.info("Home Assistant pip installation done")
|
_LOGGER.info("Home Assistant pip installation done")
|
||||||
|
|
||||||
# 5: Timeout
|
# 5: Timeout
|
||||||
if timeout and time.monotonic() - start_time > self.wait_boot:
|
if (
|
||||||
|
timeout
|
||||||
|
and time.monotonic() - start_time > self.sys_homeassistant.wait_boot
|
||||||
|
):
|
||||||
_LOGGER.warning("Don't wait anymore on Home Assistant startup!")
|
_LOGGER.warning("Don't wait anymore on Home Assistant startup!")
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -671,32 +399,13 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
if await self.instance.exists():
|
if await self.instance.exists():
|
||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.info("Repair Home Assistant %s", self.version)
|
_LOGGER.info("Repair Home Assistant %s", self.sys_homeassistant.version)
|
||||||
await self.sys_run_in_executor(
|
await self.sys_run_in_executor(
|
||||||
self.sys_docker.network.stale_cleanup, self.instance.name
|
self.sys_docker.network.stale_cleanup, self.instance.name
|
||||||
)
|
)
|
||||||
|
|
||||||
# Pull image
|
# Pull image
|
||||||
try:
|
try:
|
||||||
await self.instance.install(self.version)
|
await self.instance.install(self.sys_homeassistant.version)
|
||||||
except DockerAPIError:
|
except DockerAPIError:
|
||||||
_LOGGER.error("Repairing of Home Assistant fails")
|
_LOGGER.error("Repairing of Home Assistant fails")
|
||||||
|
|
||||||
def write_pulse(self):
|
|
||||||
"""Write asound config to file and return True on success."""
|
|
||||||
pulse_config = self.sys_plugins.audio.pulse_client(
|
|
||||||
input_profile=self.audio_input, output_profile=self.audio_output
|
|
||||||
)
|
|
||||||
|
|
||||||
# Cleanup wrong maps
|
|
||||||
if self.path_pulse.is_dir():
|
|
||||||
shutil.rmtree(self.path_pulse, ignore_errors=True)
|
|
||||||
|
|
||||||
# Write pulse config
|
|
||||||
try:
|
|
||||||
with self.path_pulse.open("w") as config_file:
|
|
||||||
config_file.write(pulse_config)
|
|
||||||
except OSError as err:
|
|
||||||
_LOGGER.error("Home Assistant can't write pulse/client.config: %s", err)
|
|
||||||
else:
|
|
||||||
_LOGGER.info("Update pulse/client.config: %s", self.path_pulse)
|
|
@ -12,7 +12,7 @@ from ..utils import AsyncThrottle
|
|||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SecretsManager(CoreSysAttributes):
|
class HomeAssistantSecrets(CoreSysAttributes):
|
||||||
"""Manage Home Assistant secrets."""
|
"""Manage Home Assistant secrets."""
|
||||||
|
|
||||||
def __init__(self, coresys: CoreSys):
|
def __init__(self, coresys: CoreSys):
|
@ -156,13 +156,13 @@ class Ingress(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
async def update_hass_panel(self, addon: Addon):
|
async def update_hass_panel(self, addon: Addon):
|
||||||
"""Return True if Home Assistant up and running."""
|
"""Return True if Home Assistant up and running."""
|
||||||
if not await self.sys_homeassistant.is_running():
|
if not await self.sys_homeassistant.core.is_running():
|
||||||
_LOGGER.debug("Ignore panel update on Core")
|
_LOGGER.debug("Ignore panel update on Core")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update UI
|
# Update UI
|
||||||
method = "post" if addon.ingress_panel else "delete"
|
method = "post" if addon.ingress_panel else "delete"
|
||||||
async with self.sys_homeassistant.make_request(
|
async with self.sys_homeassistant.api.make_request(
|
||||||
method, f"api/hassio_push/panel/{addon.slug}"
|
method, f"api/hassio_push/panel/{addon.slug}"
|
||||||
) as resp:
|
) as resp:
|
||||||
if resp.status in (200, 201):
|
if resp.status in (200, 201):
|
||||||
|
@ -169,7 +169,7 @@ class Tasks(CoreSysAttributes):
|
|||||||
"""Check running state of Docker and start if they is close."""
|
"""Check running state of Docker and start if they is close."""
|
||||||
# if Home Assistant is active
|
# if Home Assistant is active
|
||||||
if (
|
if (
|
||||||
not await self.sys_homeassistant.is_fails()
|
not await self.sys_homeassistant.core.is_fails()
|
||||||
or not self.sys_homeassistant.watchdog
|
or not self.sys_homeassistant.watchdog
|
||||||
or self.sys_homeassistant.error_state
|
or self.sys_homeassistant.error_state
|
||||||
):
|
):
|
||||||
@ -177,14 +177,14 @@ class Tasks(CoreSysAttributes):
|
|||||||
|
|
||||||
# if Home Assistant is running
|
# if Home Assistant is running
|
||||||
if (
|
if (
|
||||||
self.sys_homeassistant.in_progress
|
self.sys_homeassistant.core.in_progress
|
||||||
or await self.sys_homeassistant.is_running()
|
or await self.sys_homeassistant.core.is_running()
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.warning("Watchdog found a problem with Home Assistant Docker!")
|
_LOGGER.warning("Watchdog found a problem with Home Assistant Docker!")
|
||||||
try:
|
try:
|
||||||
await self.sys_homeassistant.start()
|
await self.sys_homeassistant.core.start()
|
||||||
except HomeAssistantError:
|
except HomeAssistantError:
|
||||||
_LOGGER.error("Watchdog Home Assistant reanimation fails!")
|
_LOGGER.error("Watchdog Home Assistant reanimation fails!")
|
||||||
|
|
||||||
@ -196,7 +196,7 @@ class Tasks(CoreSysAttributes):
|
|||||||
"""
|
"""
|
||||||
# If Home-Assistant is active
|
# If Home-Assistant is active
|
||||||
if (
|
if (
|
||||||
not await self.sys_homeassistant.is_fails()
|
not await self.sys_homeassistant.core.is_fails()
|
||||||
or not self.sys_homeassistant.watchdog
|
or not self.sys_homeassistant.watchdog
|
||||||
or self.sys_homeassistant.error_state
|
or self.sys_homeassistant.error_state
|
||||||
):
|
):
|
||||||
@ -207,8 +207,8 @@ class Tasks(CoreSysAttributes):
|
|||||||
|
|
||||||
# If Home-Assistant API is up
|
# If Home-Assistant API is up
|
||||||
if (
|
if (
|
||||||
self.sys_homeassistant.in_progress
|
self.sys_homeassistant.core.in_progress
|
||||||
or await self.sys_homeassistant.check_api_state()
|
or await self.sys_homeassistant.api.check_api_state()
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -221,7 +221,7 @@ class Tasks(CoreSysAttributes):
|
|||||||
|
|
||||||
_LOGGER.error("Watchdog found a problem with Home Assistant API!")
|
_LOGGER.error("Watchdog found a problem with Home Assistant API!")
|
||||||
try:
|
try:
|
||||||
await self.sys_homeassistant.restart()
|
await self.sys_homeassistant.core.restart()
|
||||||
except HomeAssistantError:
|
except HomeAssistantError:
|
||||||
_LOGGER.error("Watchdog Home Assistant reanimation fails!")
|
_LOGGER.error("Watchdog Home Assistant reanimation fails!")
|
||||||
finally:
|
finally:
|
||||||
|
@ -231,7 +231,7 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
_LOGGER.info("Restore %s run Home-Assistant", snapshot.slug)
|
_LOGGER.info("Restore %s run Home-Assistant", snapshot.slug)
|
||||||
snapshot.restore_homeassistant()
|
snapshot.restore_homeassistant()
|
||||||
task_hass = self.sys_create_task(
|
task_hass = self.sys_create_task(
|
||||||
self.sys_homeassistant.update(snapshot.homeassistant_version)
|
self.sys_homeassistant.core.update(snapshot.homeassistant_version)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Restore repositories
|
# Restore repositories
|
||||||
@ -295,7 +295,7 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
async with snapshot:
|
async with snapshot:
|
||||||
# Stop Home-Assistant for config restore
|
# Stop Home-Assistant for config restore
|
||||||
if FOLDER_HOMEASSISTANT in folders:
|
if FOLDER_HOMEASSISTANT in folders:
|
||||||
await self.sys_homeassistant.stop()
|
await self.sys_homeassistant.core.stop()
|
||||||
snapshot.restore_homeassistant()
|
snapshot.restore_homeassistant()
|
||||||
|
|
||||||
# Process folders
|
# Process folders
|
||||||
@ -308,7 +308,9 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
if homeassistant:
|
if homeassistant:
|
||||||
_LOGGER.info("Restore %s run Home-Assistant", snapshot.slug)
|
_LOGGER.info("Restore %s run Home-Assistant", snapshot.slug)
|
||||||
task_hass = self.sys_create_task(
|
task_hass = self.sys_create_task(
|
||||||
self.sys_homeassistant.update(snapshot.homeassistant_version)
|
self.sys_homeassistant.core.update(
|
||||||
|
snapshot.homeassistant_version
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if addons:
|
if addons:
|
||||||
@ -324,13 +326,13 @@ class SnapshotManager(CoreSysAttributes):
|
|||||||
await task_hass
|
await task_hass
|
||||||
|
|
||||||
# Do we need start HomeAssistant?
|
# Do we need start HomeAssistant?
|
||||||
if not await self.sys_homeassistant.is_running():
|
if not await self.sys_homeassistant.core.is_running():
|
||||||
await self.sys_homeassistant.start()
|
await self.sys_homeassistant.start()
|
||||||
|
|
||||||
# Check If we can access to API / otherwise restart
|
# Check If we can access to API / otherwise restart
|
||||||
if not await self.sys_homeassistant.check_api_state():
|
if not await self.sys_homeassistant.api.check_api_state():
|
||||||
_LOGGER.warning("Need restart HomeAssistant for API")
|
_LOGGER.warning("Need restart HomeAssistant for API")
|
||||||
await self.sys_homeassistant.restart()
|
await self.sys_homeassistant.core.restart()
|
||||||
|
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Restore %s error", snapshot.slug)
|
_LOGGER.exception("Restore %s error", snapshot.slug)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user