diff --git a/API.md b/API.md index 5e24bb36c..d90abace3 100644 --- a/API.md +++ b/API.md @@ -52,7 +52,7 @@ The addons from `addons` are only installed one. "slug": "xy", "description": "description", "repository": "12345678|null", - "version": "LAST_VERSION", + "version": "LATEST_VERSION", "installed": "INSTALL_VERSION", "icon": "bool", "logo": "bool", @@ -537,6 +537,7 @@ Get all available addons. "kernel_modules": "bool", "devicetree": "bool", "docker_api": "bool", + "video": "bool", "audio": "bool", "audio_input": "null|0,0", "audio_output": "null|0,0", diff --git a/azure-pipelines-release.yml b/azure-pipelines-release.yml index 313b368e5..69b0e94ac 100644 --- a/azure-pipelines-release.yml +++ b/azure-pipelines-release.yml @@ -3,8 +3,8 @@ trigger: batch: true branches: - exclude: - - "*" + include: + - dev tags: include: - "*" diff --git a/hassio/addons/model.py b/hassio/addons/model.py index 1d933936c..debc0ebe8 100644 --- a/hassio/addons/model.py +++ b/hassio/addons/model.py @@ -57,6 +57,7 @@ from ..const import ( ATTR_UDEV, ATTR_URL, ATTR_VERSION, + ATTR_VIDEO, ATTR_WEBUI, SECURITY_DEFAULT, SECURITY_DISABLE, @@ -393,6 +394,11 @@ class AddonModel(CoreSysAttributes): """Return True if the add-on access to audio.""" return self.data[ATTR_AUDIO] + @property + def with_video(self) -> bool: + """Return True if the add-on access to video.""" + return self.data[ATTR_VIDEO] + @property def homeassistant_version(self) -> Optional[str]: """Return min Home Assistant version they needed by Add-on.""" diff --git a/hassio/addons/validate.py b/hassio/addons/validate.py index dd2f524db..ec42060f7 100644 --- a/hassio/addons/validate.py +++ b/hassio/addons/validate.py @@ -77,6 +77,7 @@ from ..const import ( ATTR_USER, ATTR_UUID, ATTR_VERSION, + ATTR_VIDEO, ATTR_WEBUI, BOOT_AUTO, BOOT_MANUAL, @@ -219,6 +220,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema( vol.Optional(ATTR_APPARMOR, default=True): vol.Boolean(), vol.Optional(ATTR_FULL_ACCESS, default=False): vol.Boolean(), vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(), + vol.Optional(ATTR_VIDEO, default=False): vol.Boolean(), vol.Optional(ATTR_GPIO, default=False): vol.Boolean(), vol.Optional(ATTR_DEVICETREE, default=False): vol.Boolean(), vol.Optional(ATTR_KERNEL_MODULES, default=False): vol.Boolean(), diff --git a/hassio/api/addons.py b/hassio/api/addons.py index 63eb4226e..894b03001 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -82,6 +82,7 @@ from ..const import ( ATTR_UDEV, ATTR_URL, ATTR_VERSION, + ATTR_VIDEO, ATTR_WEBUI, BOOT_AUTO, BOOT_MANUAL, @@ -238,6 +239,7 @@ class APIAddons(CoreSysAttributes): ATTR_DEVICETREE: addon.with_devicetree, ATTR_UDEV: addon.with_udev, ATTR_DOCKER_API: addon.access_docker_api, + ATTR_VIDEO: addon.with_video, ATTR_AUDIO: addon.with_audio, ATTR_AUDIO_INPUT: None, ATTR_AUDIO_OUTPUT: None, diff --git a/hassio/api/utils.py b/hassio/api/utils.py index 5a978b5e9..8432d47ee 100644 --- a/hassio/api/utils.py +++ b/hassio/api/utils.py @@ -25,18 +25,21 @@ _LOGGER: logging.Logger = logging.getLogger(__name__) def excract_supervisor_token(request: web.Request) -> Optional[str]: """Extract Supervisor token from request.""" + supervisor_token = request.headers.get(HEADER_TOKEN) + if supervisor_token: + return supervisor_token + + # Remove with old Hass.io fallback + supervisor_token = request.headers.get(HEADER_TOKEN_OLD) + if supervisor_token: + return supervisor_token + + # API access only supervisor_token = request.headers.get(AUTHORIZATION) if supervisor_token: return supervisor_token.split(" ")[-1] - # Header token handling - supervisor_token = request.headers.get(HEADER_TOKEN) - - # Remove with old Hass.io fallback - if not supervisor_token: - supervisor_token = request.headers.get(HEADER_TOKEN_OLD) - - return supervisor_token + return None def json_loads(data: Any) -> Dict[str, Any]: diff --git a/hassio/const.py b/hassio/const.py index 0cd5fab4c..20651c875 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -3,7 +3,7 @@ from enum import Enum from ipaddress import ip_network from pathlib import Path -HASSIO_VERSION = "198" +HASSIO_VERSION = "199" URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons" @@ -149,6 +149,7 @@ ATTR_TYPE = "type" ATTR_TIMEOUT = "timeout" ATTR_AUTO_UPDATE = "auto_update" ATTR_CUSTOM = "custom" +ATTR_VIDEO = "video" ATTR_AUDIO = "audio" ATTR_AUDIO_INPUT = "audio_input" ATTR_AUDIO_OUTPUT = "audio_output" diff --git a/hassio/docker/addon.py b/hassio/docker/addon.py index a220492e5..d6d8c4e69 100644 --- a/hassio/docker/addon.py +++ b/hassio/docker/addon.py @@ -147,6 +147,11 @@ class DockerAddon(DockerInterface): for device in serial_devs: devices.append(f"{device}:{device}:rwm") + # Use video devices + if self.addon.with_video: + for device in self.sys_hardware.video_devices: + devices.append(f"{device.path!s}:{device.path!s}:rwm") + # Return None if no devices is present return devices or None diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index ebd18a84a..cbfd8f34f 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -243,10 +243,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): _LOGGER.warning("Fails install landingpage, retry after 30sec") await asyncio.sleep(30) else: + self.version = self.instance.version + self.save_data() break - self.version = self.instance.version - self.save_data() + # Start landingpage + _LOGGER.info("Start HomeAssistant landingpage") + with suppress(HomeAssistantError): + await self._start() @process_lock async def install(self) -> None: diff --git a/hassio/misc/hardware.py b/hassio/misc/hardware.py index 800669650..269d1ca47 100644 --- a/hassio/misc/hardware.py +++ b/hassio/misc/hardware.py @@ -4,13 +4,15 @@ from datetime import datetime import logging from pathlib import Path import re -from typing import Any, Dict, Optional, Set +from typing import Any, Dict, List, Optional, Set +import attr import pyudev from ..const import ATTR_DEVICES, ATTR_NAME, ATTR_TYPE, CHAN_ID, CHAN_TYPE from ..exceptions import HardwareNotSupportedError + _LOGGER: logging.Logger = logging.getLogger(__name__) ASOUND_CARDS: Path = Path("/proc/asound/cards") @@ -26,6 +28,17 @@ GPIO_DEVICES: Path = Path("/sys/class/gpio") SOC_DEVICES: Path = Path("/sys/devices/platform/soc") RE_TTY: re.Pattern = re.compile(r"tty[A-Z]+") +RE_VIDEO_DEVICES = re.compile(r"^(?:vchiq|cec)") + + +@attr.s(frozen=True) +class Device: + """Represent a device.""" + + name: str = attr.ib() + path: Path = attr.ib() + links: List[Path] = attr.ib() + class Hardware: """Representation of an interface to procfs, sysfs and udev.""" @@ -34,6 +47,33 @@ class Hardware: """Init hardware object.""" self.context = pyudev.Context() + @property + def devices(self) -> List[Device]: + """Return a list of all available devices.""" + dev_list: List[Device] = [] + + # Exctract all devices + for device in self.context.list_devices(): + dev_list.append( + Device(device.sys_name), + Path(device.device_node), + [Path(node) for node in device.device_links], + ) + + return dev_list + + @property + def video_devices(self) -> List[Device]: + """Return all available video devices.""" + dev_list: List[Device] = [] + + for device in self.devices: + if not RE_VIDEO_DEVICES.match(device.name): + continue + dev_list.append(device) + + return dev_list + @property def serial_devices(self) -> Set[str]: """Return all serial and connected devices.""" diff --git a/scripts/test_env.sh b/scripts/test_env.sh index 6f39aab58..cbfd13aba 100755 --- a/scripts/test_env.sh +++ b/scripts/test_env.sh @@ -75,6 +75,7 @@ function install_cli() { function setup_test_env() { mkdir -p /workspaces/test_hassio + echo "Start Supervisor" docker run --rm --privileged \ --name hassio_supervisor \ --security-opt seccomp=unconfined \ @@ -88,6 +89,10 @@ function setup_test_env() { -e SUPERVISOR_DEV=1 \ -e HOMEASSISTANT_REPOSITORY="homeassistant/qemux86-64-homeassistant" \ homeassistant/amd64-hassio-supervisor:latest + + if docker rm homeassistant 2> /dev/null; then + echo "Cleanup HomeAssistant instance" + fi } echo "Start Test-Env"