mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-09 10:16:29 +00:00
Allow stop/start home-assistant & flow of startup (#182)
* Allow config boot * Read boot settings * Use internal boot time for detect reboot * Check if Home-Assistant need to watch * Make datetime string and parse_datetime * Add api calls * fix lint p1 * Use new datetime parser for sessions and make a real default boot time * fix lint p2 * only start docker if they is running * convert to int (timestamp) * add boot flag
This commit is contained in:
parent
a733886803
commit
5a80be9fd4
5
API.md
5
API.md
@ -283,7 +283,8 @@ Optional:
|
|||||||
"last_version": "LAST_VERSION",
|
"last_version": "LAST_VERSION",
|
||||||
"devices": [""],
|
"devices": [""],
|
||||||
"image": "str",
|
"image": "str",
|
||||||
"custom": "bool -> if custom image"
|
"custom": "bool -> if custom image",
|
||||||
|
"boot": "bool"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -304,6 +305,8 @@ Output is the raw Docker log.
|
|||||||
- POST `/homeassistant/restart`
|
- POST `/homeassistant/restart`
|
||||||
- POST `/homeassistant/options`
|
- POST `/homeassistant/options`
|
||||||
- POST `/homeassistant/check`
|
- POST `/homeassistant/check`
|
||||||
|
- POST `/homeassistant/start`
|
||||||
|
- POST `/homeassistant/stop`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
@ -72,6 +72,8 @@ class RestAPI(object):
|
|||||||
self.webapp.router.add_post('/homeassistant/options', api_hass.options)
|
self.webapp.router.add_post('/homeassistant/options', api_hass.options)
|
||||||
self.webapp.router.add_post('/homeassistant/update', api_hass.update)
|
self.webapp.router.add_post('/homeassistant/update', api_hass.update)
|
||||||
self.webapp.router.add_post('/homeassistant/restart', api_hass.restart)
|
self.webapp.router.add_post('/homeassistant/restart', api_hass.restart)
|
||||||
|
self.webapp.router.add_post('/homeassistant/stop', api_hass.stop)
|
||||||
|
self.webapp.router.add_post('/homeassistant/start', api_hass.start)
|
||||||
self.webapp.router.add_post('/homeassistant/check', api_hass.check)
|
self.webapp.router.add_post('/homeassistant/check', api_hass.check)
|
||||||
|
|
||||||
def register_addons(self, addons):
|
def register_addons(self, addons):
|
||||||
|
@ -7,14 +7,16 @@ import voluptuous as vol
|
|||||||
from .util import api_process, api_process_raw, api_validate
|
from .util import api_process, api_process_raw, api_validate
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_DEVICES, ATTR_IMAGE, ATTR_CUSTOM,
|
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_DEVICES, ATTR_IMAGE, ATTR_CUSTOM,
|
||||||
CONTENT_TYPE_BINARY)
|
ATTR_BOOT, CONTENT_TYPE_BINARY)
|
||||||
from ..validate import HASS_DEVICES
|
from ..validate import HASS_DEVICES
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_OPTIONS = vol.Schema({
|
SCHEMA_OPTIONS = vol.Schema({
|
||||||
vol.Optional(ATTR_DEVICES): HASS_DEVICES,
|
vol.Optional(ATTR_DEVICES): HASS_DEVICES,
|
||||||
|
vol.Optional(ATTR_BOOT): vol.Boolean(),
|
||||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): vol.Any(None, vol.Coerce(str)),
|
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): vol.Any(None, vol.Coerce(str)),
|
||||||
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'):
|
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'):
|
||||||
vol.Any(None, vol.Coerce(str)),
|
vol.Any(None, vol.Coerce(str)),
|
||||||
@ -43,6 +45,7 @@ class APIHomeAssistant(object):
|
|||||||
ATTR_IMAGE: self.homeassistant.image,
|
ATTR_IMAGE: self.homeassistant.image,
|
||||||
ATTR_DEVICES: self.homeassistant.devices,
|
ATTR_DEVICES: self.homeassistant.devices,
|
||||||
ATTR_CUSTOM: self.homeassistant.is_custom_image,
|
ATTR_CUSTOM: self.homeassistant.is_custom_image,
|
||||||
|
ATTR_BOOT: self.homeassistant.boot,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
@ -57,6 +60,9 @@ class APIHomeAssistant(object):
|
|||||||
self.homeassistant.set_custom(
|
self.homeassistant.set_custom(
|
||||||
body[ATTR_IMAGE], body[ATTR_LAST_VERSION])
|
body[ATTR_IMAGE], body[ATTR_LAST_VERSION])
|
||||||
|
|
||||||
|
if ATTR_BOOT in body:
|
||||||
|
self.homeassistant.boot = body[ATTR_BOOT]
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
@ -71,6 +77,16 @@ class APIHomeAssistant(object):
|
|||||||
return await asyncio.shield(
|
return await asyncio.shield(
|
||||||
self.homeassistant.update(version), loop=self.loop)
|
self.homeassistant.update(version), loop=self.loop)
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
def stop(self, request):
|
||||||
|
"""Stop homeassistant."""
|
||||||
|
return asyncio.shield(self.homeassistant.stop(), loop=self.loop)
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
def start(self, request):
|
||||||
|
"""Start homeassistant."""
|
||||||
|
return asyncio.shield(self.homeassistant.run(), loop=self.loop)
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request):
|
def restart(self, request):
|
||||||
"""Restart homeassistant."""
|
"""Restart homeassistant."""
|
||||||
|
@ -7,14 +7,12 @@ from pathlib import Path, PurePath
|
|||||||
from .const import (
|
from .const import (
|
||||||
FILE_HASSIO_CONFIG, HASSIO_DATA, ATTR_SECURITY, ATTR_SESSIONS,
|
FILE_HASSIO_CONFIG, HASSIO_DATA, ATTR_SECURITY, ATTR_SESSIONS,
|
||||||
ATTR_PASSWORD, ATTR_TOTP, ATTR_TIMEZONE, ATTR_ADDONS_CUSTOM_LIST,
|
ATTR_PASSWORD, ATTR_TOTP, ATTR_TIMEZONE, ATTR_ADDONS_CUSTOM_LIST,
|
||||||
ATTR_AUDIO_INPUT, ATTR_AUDIO_OUTPUT)
|
ATTR_AUDIO_INPUT, ATTR_AUDIO_OUTPUT, ATTR_LAST_BOOT)
|
||||||
from .tools import JsonConfig
|
from .tools import JsonConfig, parse_datetime
|
||||||
from .validate import SCHEMA_HASSIO_CONFIG
|
from .validate import SCHEMA_HASSIO_CONFIG
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DATETIME_FORMAT = "%Y%m%d %H:%M:%S"
|
|
||||||
|
|
||||||
HOMEASSISTANT_CONFIG = PurePath("homeassistant")
|
HOMEASSISTANT_CONFIG = PurePath("homeassistant")
|
||||||
|
|
||||||
HASSIO_SSL = PurePath("ssl")
|
HASSIO_SSL = PurePath("ssl")
|
||||||
@ -28,6 +26,8 @@ BACKUP_DATA = PurePath("backup")
|
|||||||
SHARE_DATA = PurePath("share")
|
SHARE_DATA = PurePath("share")
|
||||||
TMP_DATA = PurePath("tmp")
|
TMP_DATA = PurePath("tmp")
|
||||||
|
|
||||||
|
DEFAULT_BOOT_TIME = datetime.utcfromtimestamp(0).isoformat()
|
||||||
|
|
||||||
|
|
||||||
class CoreConfig(JsonConfig):
|
class CoreConfig(JsonConfig):
|
||||||
"""Hold all core config data."""
|
"""Hold all core config data."""
|
||||||
@ -48,6 +48,22 @@ class CoreConfig(JsonConfig):
|
|||||||
self._data[ATTR_TIMEZONE] = value
|
self._data[ATTR_TIMEZONE] = value
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_boot(self):
|
||||||
|
"""Return last boot datetime."""
|
||||||
|
boot_str = self._data.get(ATTR_LAST_BOOT, DEFAULT_BOOT_TIME)
|
||||||
|
|
||||||
|
boot_time = parse_datetime(boot_str)
|
||||||
|
if not boot_time:
|
||||||
|
return datetime.utcfromtimestamp(1)
|
||||||
|
return boot_time
|
||||||
|
|
||||||
|
@last_boot.setter
|
||||||
|
def last_boot(self, value):
|
||||||
|
"""Set last boot datetime."""
|
||||||
|
self._data[ATTR_LAST_BOOT] = value.isoformat()
|
||||||
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_hassio(self):
|
def path_hassio(self):
|
||||||
"""Return hassio data path."""
|
"""Return hassio data path."""
|
||||||
@ -191,14 +207,14 @@ class CoreConfig(JsonConfig):
|
|||||||
def security_sessions(self):
|
def security_sessions(self):
|
||||||
"""Return api sessions."""
|
"""Return api sessions."""
|
||||||
return {
|
return {
|
||||||
session: datetime.strptime(until, DATETIME_FORMAT) for
|
session: parse_datetime(until) for
|
||||||
session, until in self._data[ATTR_SESSIONS].items()
|
session, until in self._data[ATTR_SESSIONS].items()
|
||||||
}
|
}
|
||||||
|
|
||||||
def add_security_session(self, session, valid):
|
def add_security_session(self, session, valid):
|
||||||
"""Set the a new session."""
|
"""Set the a new session."""
|
||||||
self._data[ATTR_SESSIONS].update(
|
self._data[ATTR_SESSIONS].update(
|
||||||
{session: valid.strftime(DATETIME_FORMAT)}
|
{session: valid.isoformat()}
|
||||||
)
|
)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ ATTR_SOURCE = 'source'
|
|||||||
ATTR_FEATURES = 'features'
|
ATTR_FEATURES = 'features'
|
||||||
ATTR_ADDONS = 'addons'
|
ATTR_ADDONS = 'addons'
|
||||||
ATTR_VERSION = 'version'
|
ATTR_VERSION = 'version'
|
||||||
|
ATTR_LAST_BOOT = 'last_boot'
|
||||||
ATTR_LAST_VERSION = 'last_version'
|
ATTR_LAST_VERSION = 'last_version'
|
||||||
ATTR_BETA_CHANNEL = 'beta_channel'
|
ATTR_BETA_CHANNEL = 'beta_channel'
|
||||||
ATTR_NAME = 'name'
|
ATTR_NAME = 'name'
|
||||||
|
@ -142,7 +142,7 @@ class HassIO(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# HomeAssistant is already running / supervisor have only reboot
|
# HomeAssistant is already running / supervisor have only reboot
|
||||||
if await self.homeassistant.is_running():
|
if self.hardware.last_boot == self.config.last_boot:
|
||||||
_LOGGER.info("HassIO reboot detected")
|
_LOGGER.info("HassIO reboot detected")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -153,11 +153,15 @@ class HassIO(object):
|
|||||||
await self.addons.auto_boot(STARTUP_SERVICES)
|
await self.addons.auto_boot(STARTUP_SERVICES)
|
||||||
|
|
||||||
# run HomeAssistant
|
# run HomeAssistant
|
||||||
await self.homeassistant.run()
|
if self.homeassistant.boot:
|
||||||
|
await self.homeassistant.run()
|
||||||
|
|
||||||
# start addon mark as application
|
# start addon mark as application
|
||||||
await self.addons.auto_boot(STARTUP_APPLICATION)
|
await self.addons.auto_boot(STARTUP_APPLICATION)
|
||||||
|
|
||||||
|
# store new last boot
|
||||||
|
self.config.last_boot = self.hardware.last_boot
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# schedule homeassistant watchdog
|
# schedule homeassistant watchdog
|
||||||
self.scheduler.register_task(
|
self.scheduler.register_task(
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
"""Init file for HassIO docker object."""
|
"""Init file for HassIO docker object."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import docker
|
||||||
|
|
||||||
from .interface import DockerInterface
|
from .interface import DockerInterface
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -93,3 +95,19 @@ class DockerHomeAssistant(DockerInterface):
|
|||||||
{'bind': '/ssl', 'mode': 'ro'},
|
{'bind': '/ssl', 'mode': 'ro'},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_initialize(self):
|
||||||
|
"""Return True if docker container exists."""
|
||||||
|
return self.loop.run_in_executor(None, self._is_initialize)
|
||||||
|
|
||||||
|
def _is_initialize(self):
|
||||||
|
"""Return True if docker container exists.
|
||||||
|
|
||||||
|
Need run inside executor.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.docker.containers.get(self.name)
|
||||||
|
except docker.errors.DockerException:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Read hardware info from system."""
|
"""Read hardware info from system."""
|
||||||
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
@ -15,6 +16,9 @@ RE_CARDS = re.compile(r"(\d+) \[(\w*) *\]: (.*\w)")
|
|||||||
ASOUND_DEVICES = Path("/proc/asound/devices")
|
ASOUND_DEVICES = Path("/proc/asound/devices")
|
||||||
RE_DEVICES = re.compile(r"\[.*(\d+)- (\d+).*\]: ([\w ]*)")
|
RE_DEVICES = re.compile(r"\[.*(\d+)- (\d+).*\]: ([\w ]*)")
|
||||||
|
|
||||||
|
PROC_STAT = Path("/proc/stat")
|
||||||
|
RE_BOOT_TIME = re.compile(r"btime (\d+)")
|
||||||
|
|
||||||
|
|
||||||
class Hardware(object):
|
class Hardware(object):
|
||||||
"""Represent a interface to procfs, sysfs and udev."""
|
"""Represent a interface to procfs, sysfs and udev."""
|
||||||
@ -85,3 +89,21 @@ class Hardware(object):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
return audio_list
|
return audio_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_boot(self):
|
||||||
|
"""Return last boot time."""
|
||||||
|
try:
|
||||||
|
with PROC_STAT.open("r") as stat_file:
|
||||||
|
stats = stat_file.read()
|
||||||
|
except OSError as err:
|
||||||
|
_LOGGER.error("Can't read stat data -> %s", err)
|
||||||
|
return
|
||||||
|
|
||||||
|
# parse stat file
|
||||||
|
found = RE_BOOT_TIME.search(stats)
|
||||||
|
if not found:
|
||||||
|
_LOGGER.error("Can't found last boot time!")
|
||||||
|
return
|
||||||
|
|
||||||
|
return datetime.utcfromtimestamp(int(found.group(1)))
|
||||||
|
@ -6,7 +6,7 @@ import re
|
|||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION,
|
FILE_HASSIO_HOMEASSISTANT, ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION,
|
||||||
ATTR_VERSION)
|
ATTR_VERSION, ATTR_BOOT)
|
||||||
from .dock.homeassistant import DockerHomeAssistant
|
from .dock.homeassistant import DockerHomeAssistant
|
||||||
from .tools import JsonConfig, convert_to_ascii
|
from .tools import JsonConfig, convert_to_ascii
|
||||||
from .validate import SCHEMA_HASS_CONFIG
|
from .validate import SCHEMA_HASS_CONFIG
|
||||||
@ -73,6 +73,17 @@ class HomeAssistant(JsonConfig):
|
|||||||
self._data[ATTR_DEVICES] = value
|
self._data[ATTR_DEVICES] = value
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def boot(self):
|
||||||
|
"""Return True if home-assistant boot is enabled."""
|
||||||
|
return self._data[ATTR_BOOT]
|
||||||
|
|
||||||
|
@boot.setter
|
||||||
|
def boot(self, value):
|
||||||
|
"""Set home-assistant boot options."""
|
||||||
|
self._data[ATTR_BOOT] = value
|
||||||
|
self.save()
|
||||||
|
|
||||||
def set_custom(self, image, version):
|
def set_custom(self, image, version):
|
||||||
"""Set a custom image for homeassistant."""
|
"""Set a custom image for homeassistant."""
|
||||||
# reset
|
# reset
|
||||||
@ -119,6 +130,7 @@ class HomeAssistant(JsonConfig):
|
|||||||
async def update(self, version=None):
|
async def update(self, version=None):
|
||||||
"""Update HomeAssistant version."""
|
"""Update HomeAssistant version."""
|
||||||
version = version or self.last_version
|
version = version or self.last_version
|
||||||
|
running = await self.docker.is_running()
|
||||||
|
|
||||||
if version == self.docker.version:
|
if version == self.docker.version:
|
||||||
_LOGGER.warning("Version %s is already installed", version)
|
_LOGGER.warning("Version %s is already installed", version)
|
||||||
@ -127,7 +139,8 @@ class HomeAssistant(JsonConfig):
|
|||||||
try:
|
try:
|
||||||
return await self.docker.update(version)
|
return await self.docker.update(version)
|
||||||
finally:
|
finally:
|
||||||
await self.docker.run()
|
if running:
|
||||||
|
await self.docker.run()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Run HomeAssistant docker.
|
"""Run HomeAssistant docker.
|
||||||
@ -164,6 +177,13 @@ class HomeAssistant(JsonConfig):
|
|||||||
"""
|
"""
|
||||||
return self.docker.is_running()
|
return self.docker.is_running()
|
||||||
|
|
||||||
|
def is_initialize(self):
|
||||||
|
"""Return True if a docker container is exists.
|
||||||
|
|
||||||
|
Return a coroutine.
|
||||||
|
"""
|
||||||
|
return self.docker.is_initialize()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def in_progress(self):
|
def in_progress(self):
|
||||||
"""Return True if a task is in progress."""
|
"""Return True if a task is in progress."""
|
||||||
|
@ -66,6 +66,11 @@ def homeassistant_watchdog(loop, homeassistant):
|
|||||||
"""Create scheduler task for montoring running state."""
|
"""Create scheduler task for montoring running state."""
|
||||||
async def _homeassistant_watchdog():
|
async def _homeassistant_watchdog():
|
||||||
"""Check running state and start if they is close."""
|
"""Check running state and start if they is close."""
|
||||||
|
# if Home-Assistant is active
|
||||||
|
if not await homeassistant.is_initialize():
|
||||||
|
return
|
||||||
|
|
||||||
|
# If Home-Assistant is running
|
||||||
if homeassistant.in_progress or await homeassistant.is_running():
|
if homeassistant.in_progress or await homeassistant.is_running():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
"""Tools file for HassIO."""
|
"""Tools file for HassIO."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta, timezone
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
import pytz
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
@ -17,6 +18,16 @@ FREEGEOIP_URL = "https://freegeoip.io/json/"
|
|||||||
|
|
||||||
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
|
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
|
||||||
|
|
||||||
|
# Copyright (c) Django Software Foundation and individual contributors.
|
||||||
|
# All rights reserved.
|
||||||
|
# https://github.com/django/django/blob/master/LICENSE
|
||||||
|
DATETIME_RE = re.compile(
|
||||||
|
r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})'
|
||||||
|
r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})'
|
||||||
|
r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?'
|
||||||
|
r'(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def write_json_file(jsonfile, data):
|
def write_json_file(jsonfile, data):
|
||||||
"""Write a json file."""
|
"""Write a json file."""
|
||||||
@ -53,6 +64,42 @@ def convert_to_ascii(raw):
|
|||||||
return RE_STRING.sub("", raw.decode())
|
return RE_STRING.sub("", raw.decode())
|
||||||
|
|
||||||
|
|
||||||
|
# Copyright (c) Django Software Foundation and individual contributors.
|
||||||
|
# All rights reserved.
|
||||||
|
# https://github.com/django/django/blob/master/LICENSE
|
||||||
|
def parse_datetime(dt_str):
|
||||||
|
"""Parse a string and return a datetime.datetime.
|
||||||
|
|
||||||
|
This function supports time zone offsets. When the input contains one,
|
||||||
|
the output uses a timezone with a fixed offset from UTC.
|
||||||
|
Raises ValueError if the input is well formatted but not a valid datetime.
|
||||||
|
Returns None if the input isn't well formatted.
|
||||||
|
"""
|
||||||
|
match = DATETIME_RE.match(dt_str)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
kws = match.groupdict() # type: Dict[str, Any]
|
||||||
|
if kws['microsecond']:
|
||||||
|
kws['microsecond'] = kws['microsecond'].ljust(6, '0')
|
||||||
|
tzinfo_str = kws.pop('tzinfo')
|
||||||
|
|
||||||
|
tzinfo = None # type: Optional[dt.tzinfo]
|
||||||
|
if tzinfo_str == 'Z':
|
||||||
|
tzinfo = pytz.utc
|
||||||
|
elif tzinfo_str is not None:
|
||||||
|
offset_mins = int(tzinfo_str[-2:]) if len(tzinfo_str) > 3 else 0
|
||||||
|
offset_hours = int(tzinfo_str[1:3])
|
||||||
|
offset = timedelta(hours=offset_hours, minutes=offset_mins)
|
||||||
|
if tzinfo_str[0] == '-':
|
||||||
|
offset = -offset
|
||||||
|
tzinfo = timezone(offset)
|
||||||
|
else:
|
||||||
|
tzinfo = None
|
||||||
|
kws = {k: int(v) for k, v in kws.items() if v is not None}
|
||||||
|
kws['tzinfo'] = tzinfo
|
||||||
|
return datetime(**kws)
|
||||||
|
|
||||||
|
|
||||||
class JsonConfig(object):
|
class JsonConfig(object):
|
||||||
"""Hass core object for handle it."""
|
"""Hass core object for handle it."""
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from .const import (
|
|||||||
ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_SESSIONS, ATTR_PASSWORD,
|
ATTR_DEVICES, ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_SESSIONS, ATTR_PASSWORD,
|
||||||
ATTR_TOTP, ATTR_SECURITY, ATTR_BETA_CHANNEL, ATTR_TIMEZONE,
|
ATTR_TOTP, ATTR_SECURITY, ATTR_BETA_CHANNEL, ATTR_TIMEZONE,
|
||||||
ATTR_ADDONS_CUSTOM_LIST, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT,
|
ATTR_ADDONS_CUSTOM_LIST, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT,
|
||||||
ATTR_HOMEASSISTANT, ATTR_HASSIO)
|
ATTR_HOMEASSISTANT, ATTR_HASSIO, ATTR_BOOT, ATTR_LAST_BOOT)
|
||||||
|
|
||||||
|
|
||||||
NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
|
NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535))
|
||||||
@ -55,8 +55,10 @@ DOCKER_PORTS = vol.Schema({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_HASS_CONFIG = vol.Schema({
|
SCHEMA_HASS_CONFIG = vol.Schema({
|
||||||
vol.Optional(ATTR_DEVICES, default=[]): HASS_DEVICES,
|
vol.Optional(ATTR_DEVICES, default=[]): HASS_DEVICES,
|
||||||
|
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
||||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): vol.Coerce(str),
|
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): vol.Coerce(str),
|
||||||
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str),
|
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Coerce(str),
|
||||||
})
|
})
|
||||||
@ -73,6 +75,7 @@ SCHEMA_UPDATER_CONFIG = vol.Schema({
|
|||||||
# 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_ADDONS_CUSTOM_LIST, default=[]): [vol.Url()],
|
vol.Optional(ATTR_ADDONS_CUSTOM_LIST, default=[]): [vol.Url()],
|
||||||
vol.Optional(ATTR_SECURITY, default=False): vol.Boolean(),
|
vol.Optional(ATTR_SECURITY, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_TOTP): vol.Coerce(str),
|
vol.Optional(ATTR_TOTP): vol.Coerce(str),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user