Skip to content

Commit dd6486f

Browse files
authored
Add Host APIs from supervisor (#19)
* Add Host APIs from supervisor * Separate RebootOptions and test options * RebootOptions not ShutdownOptions
1 parent 2fae2c5 commit dd6486f

File tree

7 files changed

+365
-0
lines changed

7 files changed

+365
-0
lines changed

aiohasupervisor/host.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Host client for supervisor."""
2+
3+
from .client import _SupervisorComponentClient
4+
from .models.host import (
5+
HostInfo,
6+
HostOptions,
7+
RebootOptions,
8+
Service,
9+
ServiceList,
10+
ShutdownOptions,
11+
)
12+
13+
14+
class HostClient(_SupervisorComponentClient):
15+
"""Handles host access in supervisor."""
16+
17+
async def info(self) -> HostInfo:
18+
"""Get host info."""
19+
result = await self._client.get("host/info")
20+
return HostInfo.from_dict(result.data)
21+
22+
async def reboot(self, options: RebootOptions | None = None) -> None:
23+
"""Reboot host."""
24+
await self._client.post(
25+
"host/reboot", json=options.to_dict() if options else None
26+
)
27+
28+
async def shutdown(self, options: ShutdownOptions | None = None) -> None:
29+
"""Shutdown host."""
30+
await self._client.post(
31+
"host/shutdown", json=options.to_dict() if options else None
32+
)
33+
34+
async def reload(self) -> None:
35+
"""Reload host info cache."""
36+
await self._client.post("host/reload")
37+
38+
async def options(self, options: HostOptions) -> None:
39+
"""Set host options."""
40+
await self._client.post("host/options", json=options.to_dict())
41+
42+
async def services(self) -> list[Service]:
43+
"""Get list of available services on host."""
44+
result = await self._client.get("host/services")
45+
return ServiceList.from_dict(result.data).services
46+
47+
# Omitted for now - Log endpoints

aiohasupervisor/models/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@
5353
HomeAssistantStopOptions,
5454
HomeAssistantUpdateOptions,
5555
)
56+
from aiohasupervisor.models.host import (
57+
HostInfo,
58+
HostOptions,
59+
RebootOptions,
60+
Service,
61+
ServiceState,
62+
ShutdownOptions,
63+
)
5664
from aiohasupervisor.models.network import (
5765
AccessPoint,
5866
AuthMethod,
@@ -212,4 +220,10 @@
212220
"Wifi",
213221
"WifiConfig",
214222
"WifiMode",
223+
"HostInfo",
224+
"HostOptions",
225+
"RebootOptions",
226+
"Service",
227+
"ServiceState",
228+
"ShutdownOptions",
215229
]

aiohasupervisor/models/host.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""Models for host APIs."""
2+
3+
from dataclasses import dataclass
4+
from datetime import datetime
5+
from enum import StrEnum
6+
7+
from .base import Request, ResponseData
8+
from .root import HostFeature
9+
10+
# --- ENUMS ----
11+
12+
13+
class ServiceState(StrEnum):
14+
"""ServiceState type.
15+
16+
The service state is determined by systemd, not supervisor. The list below
17+
is pulled from `systemctl --state=help`. It may be incomplete and it may
18+
change based on the host. Therefore within a list of services there may be
19+
some with a state not in this list parsed as string. If you find this
20+
please create an issue or pr to get the state added.
21+
"""
22+
23+
ACTIVE = "active"
24+
RELOADING = "reloading"
25+
INACTIVE = "inactive"
26+
FAILED = "failed"
27+
ACTIVATING = "activating"
28+
DEACTIVATING = "deactivating"
29+
MAINTENANCE = "maintenance"
30+
31+
32+
# --- OBJECTS ----
33+
34+
35+
@dataclass(frozen=True, slots=True)
36+
class HostInfo(ResponseData):
37+
"""HostInfo model."""
38+
39+
agent_version: str | None
40+
apparmor_version: str | None
41+
chassis: str | None
42+
virtualization: str | None
43+
cpe: str | None
44+
deployment: str | None
45+
disk_free: float
46+
disk_total: float
47+
disk_used: float
48+
disk_life_time: float
49+
features: list[HostFeature]
50+
hostname: str | None
51+
llmnr_hostname: str | None
52+
kernel: str | None
53+
operating_system: str | None
54+
timezone: str | None
55+
dt_utc: datetime | None
56+
dt_synchronized: bool | None
57+
use_ntp: bool | None
58+
startup_time: float | None
59+
boot_timestamp: int | None
60+
broadcast_llmnr: bool | None
61+
broadcast_mdns: bool | None
62+
63+
64+
@dataclass(frozen=True, slots=True)
65+
class ShutdownOptions(Request):
66+
"""ShutdownOptions model."""
67+
68+
force: bool
69+
70+
71+
@dataclass(frozen=True, slots=True)
72+
class RebootOptions(Request):
73+
"""RebootOptions model."""
74+
75+
force: bool
76+
77+
78+
@dataclass(frozen=True, slots=True)
79+
class HostOptions(Request):
80+
"""HostOptions model."""
81+
82+
hostname: str
83+
84+
85+
@dataclass(frozen=True, slots=True)
86+
class Service(ResponseData):
87+
"""Service model."""
88+
89+
name: str
90+
description: str
91+
state: ServiceState | str
92+
93+
94+
@dataclass(frozen=True, slots=True)
95+
class ServiceList(ResponseData):
96+
"""ServiceList model."""
97+
98+
services: list[Service]

aiohasupervisor/root.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .client import _SupervisorClient
1010
from .discovery import DiscoveryClient
1111
from .homeassistant import HomeAssistantClient
12+
from .host import HostClient
1213
from .models.root import AvailableUpdate, AvailableUpdates, RootInfo
1314
from .network import NetworkClient
1415
from .os import OSClient
@@ -34,6 +35,7 @@ def __init__(
3435
self._backups = BackupsClient(self._client)
3536
self._discovery = DiscoveryClient(self._client)
3637
self._network = NetworkClient(self._client)
38+
self._host = HostClient(self._client)
3739
self._resolution = ResolutionClient(self._client)
3840
self._store = StoreClient(self._client)
3941
self._supervisor = SupervisorManagementClient(self._client)
@@ -69,6 +71,11 @@ def network(self) -> NetworkClient:
6971
"""Get network component client."""
7072
return self._network
7173

74+
@property
75+
def host(self) -> HostClient:
76+
"""Get host component client."""
77+
return self._host
78+
7279
@property
7380
def resolution(self) -> ResolutionClient:
7481
"""Get resolution center component client."""

tests/fixtures/host_info.json

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"result": "ok",
3+
"data": {
4+
"agent_version": "1.6.0",
5+
"apparmor_version": "3.1.2",
6+
"chassis": "embedded",
7+
"virtualization": "",
8+
"cpe": "cpe:2.3:o:home-assistant:haos:12.4.dev20240527:*:development:*:*:*:odroid-n2:*",
9+
"deployment": "development",
10+
"disk_free": 20.1,
11+
"disk_total": 27.9,
12+
"disk_used": 6.7,
13+
"disk_life_time": 10.0,
14+
"features": [
15+
"reboot",
16+
"shutdown",
17+
"services",
18+
"network",
19+
"hostname",
20+
"timedate",
21+
"os_agent",
22+
"haos",
23+
"resolved",
24+
"journal",
25+
"disk",
26+
"mount"
27+
],
28+
"hostname": "homeassistant",
29+
"llmnr_hostname": "homeassistant3",
30+
"kernel": "6.6.32-haos",
31+
"operating_system": "Home Assistant OS 12.4.dev20240527",
32+
"timezone": "Etc/UTC",
33+
"dt_utc": "2024-10-03T00:00:00.000000+00:00",
34+
"dt_synchronized": true,
35+
"use_ntp": true,
36+
"startup_time": 1.966311,
37+
"boot_timestamp": 1716927644219811,
38+
"broadcast_llmnr": true,
39+
"broadcast_mdns": true
40+
}
41+
}

tests/fixtures/host_services.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"result": "ok",
3+
"data": {
4+
"services": [
5+
{
6+
"name": "emergency.service",
7+
"description": "Emergency Shell",
8+
"state": "inactive"
9+
},
10+
{
11+
"name": "bluetooth.service",
12+
"description": "Bluetooth service",
13+
"state": "inactive"
14+
},
15+
{
16+
"name": "haos-swapfile.service",
17+
"description": "HAOS swap",
18+
"state": "inactive"
19+
},
20+
{
21+
"name": "hassos-config.service",
22+
"description": "HassOS Configuration Manager",
23+
"state": "inactive"
24+
},
25+
{
26+
"name": "dropbear.service",
27+
"description": "Dropbear SSH daemon",
28+
"state": "active"
29+
},
30+
{
31+
"name": "systemd-time-wait-sync.service",
32+
"description": "Wait Until Kernel Time Synchronized",
33+
"state": "active"
34+
},
35+
{
36+
"name": "systemd-journald.service",
37+
"description": "Journal Service",
38+
"state": "active"
39+
},
40+
{
41+
"name": "systemd-resolved.service",
42+
"description": "Network Name Resolution",
43+
"state": "active"
44+
}
45+
]
46+
}
47+
}

0 commit comments

Comments
 (0)