Hardware integration MVP (#71677)
This commit is contained in:
parent
f166fc009a
commit
2bc093a04d
22 changed files with 535 additions and 3 deletions
|
@ -416,6 +416,8 @@ build.json @home-assistant/supervisor
|
||||||
/tests/components/guardian/ @bachya
|
/tests/components/guardian/ @bachya
|
||||||
/homeassistant/components/habitica/ @ASMfreaK @leikoilja
|
/homeassistant/components/habitica/ @ASMfreaK @leikoilja
|
||||||
/tests/components/habitica/ @ASMfreaK @leikoilja
|
/tests/components/habitica/ @ASMfreaK @leikoilja
|
||||||
|
/homeassistant/components/hardware/ @home-assistant/core
|
||||||
|
/tests/components/hardware/ @home-assistant/core
|
||||||
/homeassistant/components/harmony/ @ehendrix23 @bramkragten @bdraco @mkeesey @Aohzan
|
/homeassistant/components/harmony/ @ehendrix23 @bramkragten @bdraco @mkeesey @Aohzan
|
||||||
/tests/components/harmony/ @ehendrix23 @bramkragten @bdraco @mkeesey @Aohzan
|
/tests/components/harmony/ @ehendrix23 @bramkragten @bdraco @mkeesey @Aohzan
|
||||||
/homeassistant/components/hassio/ @home-assistant/supervisor
|
/homeassistant/components/hassio/ @home-assistant/supervisor
|
||||||
|
@ -829,6 +831,8 @@ build.json @home-assistant/supervisor
|
||||||
/tests/components/rainmachine/ @bachya
|
/tests/components/rainmachine/ @bachya
|
||||||
/homeassistant/components/random/ @fabaff
|
/homeassistant/components/random/ @fabaff
|
||||||
/tests/components/random/ @fabaff
|
/tests/components/random/ @fabaff
|
||||||
|
/homeassistant/components/raspberry_pi/ @home-assistant/core
|
||||||
|
/tests/components/raspberry_pi/ @home-assistant/core
|
||||||
/homeassistant/components/rdw/ @frenck
|
/homeassistant/components/rdw/ @frenck
|
||||||
/tests/components/rdw/ @frenck
|
/tests/components/rdw/ @frenck
|
||||||
/homeassistant/components/recollect_waste/ @bachya
|
/homeassistant/components/recollect_waste/ @bachya
|
||||||
|
|
17
homeassistant/components/hardware/__init__.py
Normal file
17
homeassistant/components/hardware/__init__.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
"""The Hardware integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
|
from . import websocket_api
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
|
"""Set up Hardware."""
|
||||||
|
hass.data[DOMAIN] = {}
|
||||||
|
|
||||||
|
websocket_api.async_setup(hass)
|
||||||
|
|
||||||
|
return True
|
3
homeassistant/components/hardware/const.py
Normal file
3
homeassistant/components/hardware/const.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"""Constants for the Hardware integration."""
|
||||||
|
|
||||||
|
DOMAIN = "hardware"
|
31
homeassistant/components/hardware/hardware.py
Normal file
31
homeassistant/components/hardware/hardware.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
"""The Hardware integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.integration_platform import (
|
||||||
|
async_process_integration_platforms,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .models import HardwareProtocol
|
||||||
|
|
||||||
|
|
||||||
|
async def async_process_hardware_platforms(hass: HomeAssistant):
|
||||||
|
"""Start processing hardware platforms."""
|
||||||
|
hass.data[DOMAIN]["hardware_platform"] = {}
|
||||||
|
|
||||||
|
await async_process_integration_platforms(hass, DOMAIN, _register_hardware_platform)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def _register_hardware_platform(
|
||||||
|
hass: HomeAssistant, integration_domain: str, platform: HardwareProtocol
|
||||||
|
):
|
||||||
|
"""Register a hardware platform."""
|
||||||
|
if integration_domain == DOMAIN:
|
||||||
|
return
|
||||||
|
if not hasattr(platform, "async_info"):
|
||||||
|
raise HomeAssistantError(f"Invalid hardware platform {platform}")
|
||||||
|
hass.data[DOMAIN]["hardware_platform"][integration_domain] = platform
|
7
homeassistant/components/hardware/manifest.json
Normal file
7
homeassistant/components/hardware/manifest.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"domain": "hardware",
|
||||||
|
"name": "Hardware",
|
||||||
|
"config_flow": false,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/hardware",
|
||||||
|
"codeowners": ["@home-assistant/core"]
|
||||||
|
}
|
34
homeassistant/components/hardware/models.py
Normal file
34
homeassistant/components/hardware/models.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
"""Models for Hardware."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BoardInfo:
|
||||||
|
"""Board info type."""
|
||||||
|
|
||||||
|
hassio_board_id: str | None
|
||||||
|
manufacturer: str
|
||||||
|
model: str | None
|
||||||
|
revision: str | None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HardwareInfo:
|
||||||
|
"""Hardware info type."""
|
||||||
|
|
||||||
|
name: str | None
|
||||||
|
board: BoardInfo | None
|
||||||
|
url: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class HardwareProtocol(Protocol):
|
||||||
|
"""Define the format of hardware platforms."""
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_info(self, hass: HomeAssistant) -> HardwareInfo:
|
||||||
|
"""Return info."""
|
47
homeassistant/components/hardware/websocket_api.py
Normal file
47
homeassistant/components/hardware/websocket_api.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
"""The Hardware websocket API."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
from dataclasses import asdict
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components import websocket_api
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .hardware import async_process_hardware_platforms
|
||||||
|
from .models import HardwareProtocol
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_setup(hass: HomeAssistant) -> None:
|
||||||
|
"""Set up the hardware websocket API."""
|
||||||
|
websocket_api.async_register_command(hass, ws_info)
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.websocket_command(
|
||||||
|
{
|
||||||
|
vol.Required("type"): "hardware/info",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@websocket_api.async_response
|
||||||
|
async def ws_info(
|
||||||
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
||||||
|
) -> None:
|
||||||
|
"""Return hardware info."""
|
||||||
|
hardware_info = []
|
||||||
|
|
||||||
|
if "hardware_platform" not in hass.data[DOMAIN]:
|
||||||
|
await async_process_hardware_platforms(hass)
|
||||||
|
|
||||||
|
hardware_platform: dict[str, HardwareProtocol] = hass.data[DOMAIN][
|
||||||
|
"hardware_platform"
|
||||||
|
]
|
||||||
|
for platform in hardware_platform.values():
|
||||||
|
if hasattr(platform, "async_info"):
|
||||||
|
with contextlib.suppress(HomeAssistantError):
|
||||||
|
hardware_info.append(asdict(platform.async_info(hass)))
|
||||||
|
|
||||||
|
connection.send_result(msg["id"], {"hardware": hardware_info})
|
|
@ -205,6 +205,10 @@ MAP_SERVICE_API = {
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HARDWARE_INTEGRATIONS = {
|
||||||
|
"rpi": "raspberry_pi",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
async def async_get_addon_info(hass: HomeAssistant, slug: str) -> dict:
|
async def async_get_addon_info(hass: HomeAssistant, slug: str) -> dict:
|
||||||
|
@ -705,6 +709,29 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
|
||||||
# Init add-on ingress panels
|
# Init add-on ingress panels
|
||||||
await async_setup_addon_panel(hass, hassio)
|
await async_setup_addon_panel(hass, hassio)
|
||||||
|
|
||||||
|
# Setup hardware integration for the detected board type
|
||||||
|
async def _async_setup_hardware_integration(hass):
|
||||||
|
"""Set up hardaware integration for the detected board type."""
|
||||||
|
if (os_info := get_os_info(hass)) is None:
|
||||||
|
# os info not yet fetched from supervisor, retry later
|
||||||
|
async_track_point_in_utc_time(
|
||||||
|
hass,
|
||||||
|
_async_setup_hardware_integration,
|
||||||
|
utcnow() + HASSIO_UPDATE_INTERVAL,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if (board := os_info.get("board")) is None:
|
||||||
|
return
|
||||||
|
if (hw_integration := HARDWARE_INTEGRATIONS.get(board)) is None:
|
||||||
|
return
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
hw_integration, context={"source": "system"}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await _async_setup_hardware_integration(hass)
|
||||||
|
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"})
|
hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"})
|
||||||
)
|
)
|
||||||
|
|
26
homeassistant/components/raspberry_pi/__init__.py
Normal file
26
homeassistant/components/raspberry_pi/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
"""The Raspberry Pi integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.components.hassio import get_os_info
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up a Raspberry Pi config entry."""
|
||||||
|
if (os_info := get_os_info(hass)) is None:
|
||||||
|
# The hassio integration has not yet fetched data from the supervisor
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
board: str
|
||||||
|
if (board := os_info.get("board")) is None or not board.startswith("rpi"):
|
||||||
|
# Not running on a Raspberry Pi, Home Assistant may have been migrated
|
||||||
|
hass.async_create_task(hass.config_entries.async_remove(entry.entry_id))
|
||||||
|
return False
|
||||||
|
|
||||||
|
await hass.config_entries.flow.async_init(
|
||||||
|
"rpi_power", context={"source": "onboarding"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
22
homeassistant/components/raspberry_pi/config_flow.py
Normal file
22
homeassistant/components/raspberry_pi/config_flow.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
"""Config flow for the Raspberry Pi integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigFlow
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
class RaspberryPiConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Raspberry Pi."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_system(self, data: dict[str, Any] | None = None) -> FlowResult:
|
||||||
|
"""Handle the initial step."""
|
||||||
|
if self._async_current_entries():
|
||||||
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
|
return self.async_create_entry(title="Raspberry Pi", data={})
|
3
homeassistant/components/raspberry_pi/const.py
Normal file
3
homeassistant/components/raspberry_pi/const.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
"""Constants for the Raspberry Pi integration."""
|
||||||
|
|
||||||
|
DOMAIN = "raspberry_pi"
|
54
homeassistant/components/raspberry_pi/hardware.py
Normal file
54
homeassistant/components/raspberry_pi/hardware.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
"""The Raspberry Pi hardware platform."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.components.hardware.models import BoardInfo, HardwareInfo
|
||||||
|
from homeassistant.components.hassio import get_os_info
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
BOARD_NAMES = {
|
||||||
|
"rpi": "Raspberry Pi",
|
||||||
|
"rpi0": "Raspberry Pi Zero",
|
||||||
|
"rpi0-w": "Raspberry Pi Zero W",
|
||||||
|
"rpi2": "Raspberry Pi 2",
|
||||||
|
"rpi3": "Raspberry Pi 3 (32-bit)",
|
||||||
|
"rpi3-64": "Raspberry Pi 3",
|
||||||
|
"rpi4": "Raspberry Pi 4 (32-bit)",
|
||||||
|
"rpi4-64": "Raspberry Pi 4",
|
||||||
|
}
|
||||||
|
|
||||||
|
MODELS = {
|
||||||
|
"rpi": "1",
|
||||||
|
"rpi0": "zero",
|
||||||
|
"rpi0-w": "zero_w",
|
||||||
|
"rpi2": "2",
|
||||||
|
"rpi3": "3",
|
||||||
|
"rpi3-64": "3",
|
||||||
|
"rpi4": "4",
|
||||||
|
"rpi4-64": "4",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_info(hass: HomeAssistant) -> HardwareInfo:
|
||||||
|
"""Return board info."""
|
||||||
|
if (os_info := get_os_info(hass)) is None:
|
||||||
|
raise HomeAssistantError
|
||||||
|
board: str
|
||||||
|
if (board := os_info.get("board")) is None:
|
||||||
|
raise HomeAssistantError
|
||||||
|
if not board.startswith("rpi"):
|
||||||
|
raise HomeAssistantError
|
||||||
|
|
||||||
|
return HardwareInfo(
|
||||||
|
board=BoardInfo(
|
||||||
|
hassio_board_id=board,
|
||||||
|
manufacturer=DOMAIN,
|
||||||
|
model=MODELS.get(board),
|
||||||
|
revision=None,
|
||||||
|
),
|
||||||
|
name=BOARD_NAMES.get(board, f"Unknown Raspberry Pi model '{board}'"),
|
||||||
|
url=None,
|
||||||
|
)
|
9
homeassistant/components/raspberry_pi/manifest.json
Normal file
9
homeassistant/components/raspberry_pi/manifest.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"domain": "raspberry_pi",
|
||||||
|
"name": "Raspberry Pi",
|
||||||
|
"config_flow": false,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/raspberry_pi",
|
||||||
|
"dependencies": ["hardware", "hassio"],
|
||||||
|
"codeowners": ["@home-assistant/core"],
|
||||||
|
"integration_type": "hardware"
|
||||||
|
}
|
|
@ -36,6 +36,8 @@ class RPiPowerFlow(DiscoveryFlowHandler[Awaitable[bool]], domain=DOMAIN):
|
||||||
self, data: dict[str, Any] | None = None
|
self, data: dict[str, Any] | None = None
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
"""Handle a flow initialized by onboarding."""
|
"""Handle a flow initialized by onboarding."""
|
||||||
|
if self._async_current_entries():
|
||||||
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
has_devices = await self._discovery_function(self.hass)
|
has_devices = await self._discovery_function(self.hass)
|
||||||
|
|
||||||
if not has_devices:
|
if not has_devices:
|
||||||
|
|
|
@ -52,6 +52,7 @@ NO_IOT_CLASS = [
|
||||||
"downloader",
|
"downloader",
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"frontend",
|
"frontend",
|
||||||
|
"hardware",
|
||||||
"history",
|
"history",
|
||||||
"homeassistant",
|
"homeassistant",
|
||||||
"image",
|
"image",
|
||||||
|
@ -76,6 +77,7 @@ NO_IOT_CLASS = [
|
||||||
"profiler",
|
"profiler",
|
||||||
"proxy",
|
"proxy",
|
||||||
"python_script",
|
"python_script",
|
||||||
|
"raspberry_pi",
|
||||||
"safe_mode",
|
"safe_mode",
|
||||||
"script",
|
"script",
|
||||||
"search",
|
"search",
|
||||||
|
@ -153,7 +155,7 @@ MANIFEST_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required("domain"): str,
|
vol.Required("domain"): str,
|
||||||
vol.Required("name"): str,
|
vol.Required("name"): str,
|
||||||
vol.Optional("integration_type"): "helper",
|
vol.Optional("integration_type"): vol.In(["hardware", "helper"]),
|
||||||
vol.Optional("config_flow"): bool,
|
vol.Optional("config_flow"): bool,
|
||||||
vol.Optional("mqtt"): [str],
|
vol.Optional("mqtt"): [str],
|
||||||
vol.Optional("zeroconf"): [
|
vol.Optional("zeroconf"): [
|
||||||
|
|
1
tests/components/hardware/__init__.py
Normal file
1
tests/components/hardware/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for the Hardware integration."""
|
18
tests/components/hardware/test_websocket_api.py
Normal file
18
tests/components/hardware/test_websocket_api.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
"""Test the hardware websocket API."""
|
||||||
|
from homeassistant.components.hardware.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
|
||||||
|
async def test_board_info(hass: HomeAssistant, hass_ws_client) -> None:
|
||||||
|
"""Test we can get the board info."""
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
await client.send_json({"id": 1, "type": "hardware/info"})
|
||||||
|
msg = await client.receive_json()
|
||||||
|
|
||||||
|
assert msg["id"] == 1
|
||||||
|
assert msg["success"]
|
||||||
|
assert msg["result"] == {"hardware": []}
|
|
@ -20,8 +20,19 @@ from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"}
|
MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def os_info():
|
||||||
|
"""Mock os/info."""
|
||||||
|
return {
|
||||||
|
"json": {
|
||||||
|
"result": "ok",
|
||||||
|
"data": {"version_latest": "1.0.0", "version": "1.0.0"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_all(aioclient_mock, request):
|
def mock_all(aioclient_mock, request, os_info):
|
||||||
"""Mock all setup requests."""
|
"""Mock all setup requests."""
|
||||||
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
|
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
|
||||||
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"})
|
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"})
|
||||||
|
@ -64,7 +75,7 @@ def mock_all(aioclient_mock, request):
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
"http://127.0.0.1/os/info",
|
"http://127.0.0.1/os/info",
|
||||||
json={"result": "ok", "data": {"version_latest": "1.0.0", "version": "1.0.0"}},
|
**os_info,
|
||||||
)
|
)
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
"http://127.0.0.1/supervisor/info",
|
"http://127.0.0.1/supervisor/info",
|
||||||
|
@ -701,3 +712,29 @@ async def test_coordinator_updates(hass, caplog):
|
||||||
)
|
)
|
||||||
assert refresh_updates_mock.call_count == 1
|
assert refresh_updates_mock.call_count == 1
|
||||||
assert "Error on Supervisor API: Unknown" in caplog.text
|
assert "Error on Supervisor API: Unknown" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"os_info",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"result": "ok",
|
||||||
|
"data": {"version_latest": "1.0.0", "version": "1.0.0", "board": "rpi"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_setup_hardware_integration(hass, aioclient_mock):
|
||||||
|
"""Test setup initiates hardware integration."""
|
||||||
|
|
||||||
|
with patch.dict(os.environ, MOCK_ENVIRON), patch(
|
||||||
|
"homeassistant.components.raspberry_pi.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await async_setup_component(hass, "hassio", {"hassio": {}})
|
||||||
|
assert result
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert aioclient_mock.call_count == 15
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
1
tests/components/raspberry_pi/__init__.py
Normal file
1
tests/components/raspberry_pi/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for the Raspberry Pi integration."""
|
58
tests/components/raspberry_pi/test_config_flow.py
Normal file
58
tests/components/raspberry_pi/test_config_flow.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
"""Test the Raspberry Pi config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant.components.raspberry_pi.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import RESULT_TYPE_ABORT, RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, MockModule, mock_integration
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow(hass: HomeAssistant) -> None:
|
||||||
|
"""Test the config flow."""
|
||||||
|
mock_integration(hass, MockModule("hassio"))
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.raspberry_pi.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": "system"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result["title"] == "Raspberry Pi"
|
||||||
|
assert result["data"] == {}
|
||||||
|
assert result["options"] == {}
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||||
|
assert config_entry.data == {}
|
||||||
|
assert config_entry.options == {}
|
||||||
|
assert config_entry.title == "Raspberry Pi"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_flow_single_entry(hass: HomeAssistant) -> None:
|
||||||
|
"""Test only a single entry is allowed."""
|
||||||
|
mock_integration(hass, MockModule("hassio"))
|
||||||
|
|
||||||
|
# Setup the config entry
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
data={},
|
||||||
|
domain=DOMAIN,
|
||||||
|
options={},
|
||||||
|
title="Raspberry Pi",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.raspberry_pi.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": "system"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "single_instance_allowed"
|
||||||
|
mock_setup_entry.assert_not_called()
|
57
tests/components/raspberry_pi/test_hardware.py
Normal file
57
tests/components/raspberry_pi/test_hardware.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
"""Test the Raspberry Pi hardware platform."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.hassio import DATA_OS_INFO
|
||||||
|
from homeassistant.components.raspberry_pi.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.common import MockModule, mock_integration
|
||||||
|
|
||||||
|
|
||||||
|
async def test_hardware_info(hass: HomeAssistant, hass_ws_client) -> None:
|
||||||
|
"""Test we can get the board info."""
|
||||||
|
mock_integration(hass, MockModule("hassio"))
|
||||||
|
hass.data[DATA_OS_INFO] = {"board": "rpi"}
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
await client.send_json({"id": 1, "type": "hardware/info"})
|
||||||
|
msg = await client.receive_json()
|
||||||
|
|
||||||
|
assert msg["id"] == 1
|
||||||
|
assert msg["success"]
|
||||||
|
assert msg["result"] == {
|
||||||
|
"hardware": [
|
||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"hassio_board_id": "rpi",
|
||||||
|
"manufacturer": "raspberry_pi",
|
||||||
|
"model": "1",
|
||||||
|
"revision": None,
|
||||||
|
},
|
||||||
|
"name": "Raspberry Pi",
|
||||||
|
"url": None,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("os_info", [None, {"board": None}, {"board": "other"}])
|
||||||
|
async def test_hardware_info_fail(hass: HomeAssistant, hass_ws_client, os_info) -> None:
|
||||||
|
"""Test async_info raises if os_info is not as expected."""
|
||||||
|
mock_integration(hass, MockModule("hassio"))
|
||||||
|
hass.data[DATA_OS_INFO] = os_info
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, DOMAIN, {})
|
||||||
|
|
||||||
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
await client.send_json({"id": 1, "type": "hardware/info"})
|
||||||
|
msg = await client.receive_json()
|
||||||
|
|
||||||
|
assert msg["id"] == 1
|
||||||
|
assert msg["success"]
|
||||||
|
assert msg["result"] == {"hardware": []}
|
72
tests/components/raspberry_pi/test_init.py
Normal file
72
tests/components/raspberry_pi/test_init.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
"""Test the Raspberry Pi integration."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant.components.raspberry_pi.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, MockModule, mock_integration
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry(hass: HomeAssistant) -> None:
|
||||||
|
"""Test setup of a config entry."""
|
||||||
|
mock_integration(hass, MockModule("hassio"))
|
||||||
|
|
||||||
|
# Setup the config entry
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
data={},
|
||||||
|
domain=DOMAIN,
|
||||||
|
options={},
|
||||||
|
title="Raspberry Pi",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.raspberry_pi.get_os_info",
|
||||||
|
return_value={"board": "rpi"},
|
||||||
|
) as mock_get_os_info:
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_get_os_info.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry_wrong_board(hass: HomeAssistant) -> None:
|
||||||
|
"""Test setup of a config entry with wrong board type."""
|
||||||
|
mock_integration(hass, MockModule("hassio"))
|
||||||
|
|
||||||
|
# Setup the config entry
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
data={},
|
||||||
|
domain=DOMAIN,
|
||||||
|
options={},
|
||||||
|
title="Raspberry Pi",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.raspberry_pi.get_os_info",
|
||||||
|
return_value={"board": "generic-x86-64"},
|
||||||
|
) as mock_get_os_info:
|
||||||
|
assert not await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_get_os_info.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry_wait_hassio(hass: HomeAssistant) -> None:
|
||||||
|
"""Test setup of a config entry when hassio has not fetched os_info."""
|
||||||
|
mock_integration(hass, MockModule("hassio"))
|
||||||
|
|
||||||
|
# Setup the config entry
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
data={},
|
||||||
|
domain=DOMAIN,
|
||||||
|
options={},
|
||||||
|
title="Raspberry Pi",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.raspberry_pi.get_os_info",
|
||||||
|
return_value=None,
|
||||||
|
) as mock_get_os_info:
|
||||||
|
assert not await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_get_os_info.mock_calls) == 1
|
||||||
|
assert config_entry.state == ConfigEntryState.SETUP_RETRY
|
Loading…
Add table
Add a link
Reference in a new issue