Skip to content

Commit

Permalink
Add network APIs from supervisor (#20)
Browse files Browse the repository at this point in the history
* Add network APIs from supervisor

* TypeError to ValueError and test none for vlan config
  • Loading branch information
mdegat01 authored Oct 10, 2024
1 parent 03114e5 commit 2fae2c5
Show file tree
Hide file tree
Showing 8 changed files with 537 additions and 0 deletions.
36 changes: 36 additions & 0 deletions aiohasupervisor/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,25 @@
HomeAssistantStopOptions,
HomeAssistantUpdateOptions,
)
from aiohasupervisor.models.network import (
AccessPoint,
AuthMethod,
DockerNetwork,
InterfaceMethod,
InterfaceType,
IPv4,
IPv4Config,
IPv6,
IPv6Config,
NetworkInfo,
NetworkInterface,
NetworkInterfaceConfig,
Vlan,
VlanConfig,
Wifi,
WifiConfig,
WifiMode,
)
from aiohasupervisor.models.os import (
BootSlot,
BootSlotName,
Expand Down Expand Up @@ -176,4 +195,21 @@
"PartialRestoreOptions",
"Discovery",
"DiscoveryConfig",
"AccessPoint",
"AuthMethod",
"DockerNetwork",
"InterfaceMethod",
"InterfaceType",
"IPv4",
"IPv4Config",
"IPv6",
"IPv6Config",
"NetworkInfo",
"NetworkInterface",
"NetworkInterfaceConfig",
"Vlan",
"VlanConfig",
"Wifi",
"WifiConfig",
"WifiMode",
]
198 changes: 198 additions & 0 deletions aiohasupervisor/models/network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
"""Models for supervisor network."""

from abc import ABC
from dataclasses import dataclass
from enum import StrEnum
from ipaddress import (
IPv4Address,
IPv4Interface,
IPv4Network,
IPv6Address,
IPv6Interface,
)

from .base import Options, Request, ResponseData

# --- ENUMS ----


class InterfaceType(StrEnum):
"""InterfaceType type."""

ETHERNET = "ethernet"
WIRELESS = "wireless"
VLAN = "vlan"


class InterfaceMethod(StrEnum):
"""InterfaceMethod type."""

DISABLED = "disabled"
STATIC = "static"
AUTO = "auto"


class WifiMode(StrEnum):
"""WifiMode type."""

INFRASTRUCTURE = "infrastructure"
MESH = "mesh"
ADHOC = "adhoc"
AP = "ap"


class AuthMethod(StrEnum):
"""AuthMethod type."""

OPEN = "open"
WEP = "wep"
WPA_PSK = "wpa-psk"


# --- OBJECTS ----


@dataclass(frozen=True)
class IpBase(ABC):
"""IpBase ABC type."""

method: InterfaceMethod
ready: bool | None


@dataclass(frozen=True, slots=True)
class IPv4(IpBase, ResponseData):
"""IPv4 model."""

address: list[IPv4Interface]
nameservers: list[IPv4Address]
gateway: IPv4Address | None


@dataclass(frozen=True, slots=True)
class IPv6(IpBase, ResponseData):
"""IPv6 model."""

address: list[IPv6Interface]
nameservers: list[IPv6Address]
gateway: IPv6Address | None


@dataclass(frozen=True, slots=True)
class Wifi(ResponseData):
"""Wifi model."""

mode: WifiMode
auth: AuthMethod
ssid: str
signal: int | None


@dataclass(frozen=True, slots=True)
class Vlan(ResponseData):
"""Vlan model."""

id: int
interface: str


@dataclass(frozen=True, slots=True)
class NetworkInterface(ResponseData):
"""NetworkInterface model."""

interface: str
type: InterfaceType
enabled: bool
connected: bool
primary: bool
mac: str
ipv4: IPv4
ipv6: IPv6
wifi: Wifi | None
vlan: Vlan | None


@dataclass(frozen=True, slots=True)
class DockerNetwork(ResponseData):
"""DockerNetwork model."""

interface: str
address: IPv4Network
gateway: IPv4Address
dns: IPv4Address


@dataclass(frozen=True, slots=True)
class NetworkInfo(ResponseData):
"""NetworkInfo model."""

interfaces: list[NetworkInterface]
docker: DockerNetwork
host_internet: bool | None
supervisor_internet: bool


@dataclass(frozen=True, slots=True)
class IPv4Config(Request):
"""IPv4Config model."""

address: list[IPv4Interface] | None = None
method: InterfaceMethod | None = None
gateway: IPv4Address | None = None
nameservers: list[IPv4Address] | None = None


@dataclass(frozen=True, slots=True)
class IPv6Config(Request):
"""IPv6Config model."""

address: list[IPv6Interface] | None = None
method: InterfaceMethod | None = None
gateway: IPv6Address | None = None
nameservers: list[IPv6Address] | None = None


@dataclass(frozen=True, slots=True)
class WifiConfig(Request):
"""WifiConfig model."""

mode: WifiMode | None = None
method: AuthMethod | None = None
ssid: str | None = None
psk: str | None = None


@dataclass(frozen=True, slots=True)
class NetworkInterfaceConfig(Options):
"""NetworkInterfaceConfig model."""

ipv4: IPv4Config | None = None
ipv6: IPv6Config | None = None
wifi: WifiConfig | None = None
enabled: bool | None = None


@dataclass(frozen=True, slots=True)
class AccessPoint(ResponseData):
"""AccessPoint model."""

mode: WifiMode
ssid: str
frequency: int
signal: int
mac: str


@dataclass(frozen=True, slots=True)
class AccessPointList(ResponseData):
"""AccessPointList model."""

accesspoints: list[AccessPoint]


@dataclass(frozen=True, slots=True)
class VlanConfig(Options):
"""VlanConfig model."""

ipv4: IPv4Config | None = None
ipv6: IPv6Config | None = None
51 changes: 51 additions & 0 deletions aiohasupervisor/network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Network client for supervisor."""

from .client import _SupervisorComponentClient
from .models.network import (
AccessPoint,
AccessPointList,
NetworkInfo,
NetworkInterface,
NetworkInterfaceConfig,
VlanConfig,
)


class NetworkClient(_SupervisorComponentClient):
"""Handles network access in supervisor."""

async def info(self) -> NetworkInfo:
"""Get network info."""
result = await self._client.get("network/info")
return NetworkInfo.from_dict(result.data)

async def reload(self) -> None:
"""Reload network info caches."""
await self._client.post("network/reload")

async def interface_info(self, interface: str) -> NetworkInterface:
"""Get network interface info."""
result = await self._client.get(f"network/interface/{interface}/info")
return NetworkInterface.from_dict(result.data)

async def update_interface(
self, interface: str, config: NetworkInterfaceConfig
) -> None:
"""Update a network interface."""
await self._client.post(
f"network/interface/{interface}/update", json=config.to_dict()
)

async def access_points(self, interface: str) -> list[AccessPoint]:
"""Get access points visible to a wireless interface."""
result = await self._client.get(f"network/interface/{interface}/accesspoints")
return AccessPointList.from_dict(result.data).accesspoints

async def save_vlan(
self, interface: str, vlan: int, config: VlanConfig | None = None
) -> None:
"""Create or update a vlan for an ethernet interface."""
await self._client.post(
f"network/interface/{interface}/vlan/{vlan}",
json=config.to_dict() if config else None,
)
7 changes: 7 additions & 0 deletions aiohasupervisor/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .discovery import DiscoveryClient
from .homeassistant import HomeAssistantClient
from .models.root import AvailableUpdate, AvailableUpdates, RootInfo
from .network import NetworkClient
from .os import OSClient
from .resolution import ResolutionClient
from .store import StoreClient
Expand All @@ -32,6 +33,7 @@ def __init__(
self._os = OSClient(self._client)
self._backups = BackupsClient(self._client)
self._discovery = DiscoveryClient(self._client)
self._network = NetworkClient(self._client)
self._resolution = ResolutionClient(self._client)
self._store = StoreClient(self._client)
self._supervisor = SupervisorManagementClient(self._client)
Expand Down Expand Up @@ -62,6 +64,11 @@ def discovery(self) -> DiscoveryClient:
"""Get discovery component client."""
return self._discovery

@property
def network(self) -> NetworkClient:
"""Get network component client."""
return self._network

@property
def resolution(self) -> ResolutionClient:
"""Get resolution center component client."""
Expand Down
21 changes: 21 additions & 0 deletions tests/fixtures/network_access_points.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"result": "ok",
"data": {
"accesspoints": [
{
"mode": "infrastructure",
"ssid": "UPC4814466",
"frequency": 2462,
"signal": 47,
"mac": "AA:BB:CC:DD:EE:FF"
},
{
"mode": "infrastructure",
"ssid": "VQ@35(55720",
"frequency": 5660,
"signal": 63,
"mac": "FF:EE:DD:CC:BB:AA"
}
]
}
}
39 changes: 39 additions & 0 deletions tests/fixtures/network_info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"result": "ok",
"data": {
"interfaces": [
{
"interface": "end0",
"type": "ethernet",
"enabled": true,
"connected": true,
"primary": true,
"mac": "00:11:22:33:44:55",
"ipv4": {
"method": "static",
"address": ["192.168.1.2/24"],
"nameservers": ["192.168.1.1"],
"gateway": "192.168.1.1",
"ready": true
},
"ipv6": {
"method": "disabled",
"address": ["fe80::819d:c479:d712:7a77/64"],
"nameservers": [],
"gateway": null,
"ready": true
},
"wifi": null,
"vlan": null
}
],
"docker": {
"interface": "hassio",
"address": "172.30.32.0/23",
"gateway": "172.30.32.1",
"dns": "172.30.32.3"
},
"host_internet": true,
"supervisor_internet": true
}
}
Loading

0 comments on commit 2fae2c5

Please sign in to comment.