mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-28 11:36:32 +00:00
Merge 9a1f7543e7c42b90a046d398ea56263604aca301 into 606db3585cb07a7e04a309fbfd540403da1a4e86
This commit is contained in:
commit
4bbef0a4f8
@ -12,6 +12,8 @@ from ..const import (
|
|||||||
ATTR_ACCESSPOINTS,
|
ATTR_ACCESSPOINTS,
|
||||||
ATTR_ADDRESS,
|
ATTR_ADDRESS,
|
||||||
ATTR_AUTH,
|
ATTR_AUTH,
|
||||||
|
ATTR_BAND,
|
||||||
|
ATTR_CHANNEL,
|
||||||
ATTR_CONNECTED,
|
ATTR_CONNECTED,
|
||||||
ATTR_DNS,
|
ATTR_DNS,
|
||||||
ATTR_DOCKER,
|
ATTR_DOCKER,
|
||||||
@ -52,7 +54,7 @@ from ..host.configuration import (
|
|||||||
VlanConfig,
|
VlanConfig,
|
||||||
WifiConfig,
|
WifiConfig,
|
||||||
)
|
)
|
||||||
from ..host.const import AuthMethod, InterfaceType, WifiMode
|
from ..host.const import AuthMethod, InterfaceType, WifiBand, WifiMode
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
_SCHEMA_IPV4_CONFIG = vol.Schema(
|
_SCHEMA_IPV4_CONFIG = vol.Schema(
|
||||||
@ -79,6 +81,8 @@ _SCHEMA_WIFI_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_AUTH): vol.Coerce(AuthMethod),
|
vol.Optional(ATTR_AUTH): vol.Coerce(AuthMethod),
|
||||||
vol.Optional(ATTR_SSID): str,
|
vol.Optional(ATTR_SSID): str,
|
||||||
vol.Optional(ATTR_PSK): str,
|
vol.Optional(ATTR_PSK): str,
|
||||||
|
vol.Optional(ATTR_BAND): vol.Coerce(WifiBand),
|
||||||
|
vol.Optional(ATTR_CHANNEL): vol.Coerce(int),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -112,6 +116,8 @@ def wifi_struct(config: WifiConfig) -> dict[str, Any]:
|
|||||||
ATTR_AUTH: config.auth,
|
ATTR_AUTH: config.auth,
|
||||||
ATTR_SSID: config.ssid,
|
ATTR_SSID: config.ssid,
|
||||||
ATTR_SIGNAL: config.signal,
|
ATTR_SIGNAL: config.signal,
|
||||||
|
ATTR_BAND: config.band,
|
||||||
|
ATTR_CHANNEL: config.channel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -227,6 +233,8 @@ class APINetwork(CoreSysAttributes):
|
|||||||
config.get(ATTR_AUTH, AuthMethod.OPEN),
|
config.get(ATTR_AUTH, AuthMethod.OPEN),
|
||||||
config.get(ATTR_PSK, None),
|
config.get(ATTR_PSK, None),
|
||||||
None,
|
None,
|
||||||
|
config.get(ATTR_BAND, None),
|
||||||
|
config.get(ATTR_CHANNEL, None),
|
||||||
)
|
)
|
||||||
elif key == ATTR_ENABLED:
|
elif key == ATTR_ENABLED:
|
||||||
interface.enabled = config
|
interface.enabled = config
|
||||||
|
@ -119,6 +119,7 @@ ATTR_BACKUP_POST = "backup_post"
|
|||||||
ATTR_BACKUP_PRE = "backup_pre"
|
ATTR_BACKUP_PRE = "backup_pre"
|
||||||
ATTR_BACKUPS = "backups"
|
ATTR_BACKUPS = "backups"
|
||||||
ATTR_BACKUPS_EXCLUDE_DATABASE = "backups_exclude_database"
|
ATTR_BACKUPS_EXCLUDE_DATABASE = "backups_exclude_database"
|
||||||
|
ATTR_BAND = "band"
|
||||||
ATTR_BLK_READ = "blk_read"
|
ATTR_BLK_READ = "blk_read"
|
||||||
ATTR_BLK_WRITE = "blk_write"
|
ATTR_BLK_WRITE = "blk_write"
|
||||||
ATTR_BOARD = "board"
|
ATTR_BOARD = "board"
|
||||||
|
@ -204,6 +204,7 @@ class InterfaceMethod(StrEnum):
|
|||||||
MANUAL = "manual"
|
MANUAL = "manual"
|
||||||
DISABLED = "disabled"
|
DISABLED = "disabled"
|
||||||
LINK_LOCAL = "link-local"
|
LINK_LOCAL = "link-local"
|
||||||
|
SHARED = "shared"
|
||||||
|
|
||||||
|
|
||||||
class ConnectionType(StrEnum):
|
class ConnectionType(StrEnum):
|
||||||
|
@ -33,6 +33,8 @@ class WirelessProperties:
|
|||||||
assigned_mac: str | None
|
assigned_mac: str | None
|
||||||
mode: str | None
|
mode: str | None
|
||||||
powersave: int | None
|
powersave: int | None
|
||||||
|
band: str | None
|
||||||
|
channel: int | None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
|
@ -48,6 +48,8 @@ CONF_ATTR_802_WIRELESS_MODE = "mode"
|
|||||||
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC = "assigned-mac-address"
|
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC = "assigned-mac-address"
|
||||||
CONF_ATTR_802_WIRELESS_SSID = "ssid"
|
CONF_ATTR_802_WIRELESS_SSID = "ssid"
|
||||||
CONF_ATTR_802_WIRELESS_POWERSAVE = "powersave"
|
CONF_ATTR_802_WIRELESS_POWERSAVE = "powersave"
|
||||||
|
CONF_ATTR_802_WIRELESS_BAND = "band"
|
||||||
|
CONF_ATTR_802_WIRELESS_CHANNEL = "channel"
|
||||||
CONF_ATTR_802_WIRELESS_SECURITY_AUTH_ALG = "auth-alg"
|
CONF_ATTR_802_WIRELESS_SECURITY_AUTH_ALG = "auth-alg"
|
||||||
CONF_ATTR_802_WIRELESS_SECURITY_KEY_MGMT = "key-mgmt"
|
CONF_ATTR_802_WIRELESS_SECURITY_KEY_MGMT = "key-mgmt"
|
||||||
CONF_ATTR_802_WIRELESS_SECURITY_PSK = "psk"
|
CONF_ATTR_802_WIRELESS_SECURITY_PSK = "psk"
|
||||||
@ -234,6 +236,8 @@ class NetworkSetting(DBusInterface):
|
|||||||
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_ASSIGNED_MAC),
|
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_ASSIGNED_MAC),
|
||||||
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_MODE),
|
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_MODE),
|
||||||
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_POWERSAVE),
|
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_POWERSAVE),
|
||||||
|
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_BAND),
|
||||||
|
data[CONF_ATTR_802_WIRELESS].get(CONF_ATTR_802_WIRELESS_CHANNEL),
|
||||||
)
|
)
|
||||||
|
|
||||||
if CONF_ATTR_802_WIRELESS_SECURITY in data:
|
if CONF_ATTR_802_WIRELESS_SECURITY in data:
|
||||||
|
@ -15,6 +15,8 @@ from . import (
|
|||||||
CONF_ATTR_802_ETHERNET_ASSIGNED_MAC,
|
CONF_ATTR_802_ETHERNET_ASSIGNED_MAC,
|
||||||
CONF_ATTR_802_WIRELESS,
|
CONF_ATTR_802_WIRELESS,
|
||||||
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC,
|
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC,
|
||||||
|
CONF_ATTR_802_WIRELESS_BAND,
|
||||||
|
CONF_ATTR_802_WIRELESS_CHANNEL,
|
||||||
CONF_ATTR_802_WIRELESS_MODE,
|
CONF_ATTR_802_WIRELESS_MODE,
|
||||||
CONF_ATTR_802_WIRELESS_POWERSAVE,
|
CONF_ATTR_802_WIRELESS_POWERSAVE,
|
||||||
CONF_ATTR_802_WIRELESS_SECURITY,
|
CONF_ATTR_802_WIRELESS_SECURITY,
|
||||||
@ -50,6 +52,19 @@ if TYPE_CHECKING:
|
|||||||
from ....host.configuration import Interface
|
from ....host.configuration import Interface
|
||||||
|
|
||||||
|
|
||||||
|
def _get_address_data(ipv4setting) -> Variant:
|
||||||
|
address_data = []
|
||||||
|
for address in ipv4setting.address:
|
||||||
|
address_data.append(
|
||||||
|
{
|
||||||
|
"address": Variant("s", str(address.ip)),
|
||||||
|
"prefix": Variant("u", int(address.with_prefixlen.split("/")[-1])),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return Variant("aa{sv}", address_data)
|
||||||
|
|
||||||
|
|
||||||
def _get_ipv4_connection_settings(ipv4setting) -> dict:
|
def _get_ipv4_connection_settings(ipv4setting) -> dict:
|
||||||
ipv4 = {}
|
ipv4 = {}
|
||||||
if not ipv4setting or ipv4setting.method == InterfaceMethod.AUTO:
|
if not ipv4setting or ipv4setting.method == InterfaceMethod.AUTO:
|
||||||
@ -58,19 +73,12 @@ def _get_ipv4_connection_settings(ipv4setting) -> dict:
|
|||||||
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "disabled")
|
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "disabled")
|
||||||
elif ipv4setting.method == InterfaceMethod.STATIC:
|
elif ipv4setting.method == InterfaceMethod.STATIC:
|
||||||
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "manual")
|
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "manual")
|
||||||
|
ipv4[CONF_ATTR_IPV4_ADDRESS_DATA] = _get_address_data(ipv4setting)
|
||||||
address_data = []
|
|
||||||
for address in ipv4setting.address:
|
|
||||||
address_data.append(
|
|
||||||
{
|
|
||||||
"address": Variant("s", str(address.ip)),
|
|
||||||
"prefix": Variant("u", int(address.with_prefixlen.split("/")[-1])),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
ipv4[CONF_ATTR_IPV4_ADDRESS_DATA] = Variant("aa{sv}", address_data)
|
|
||||||
if ipv4setting.gateway:
|
if ipv4setting.gateway:
|
||||||
ipv4[CONF_ATTR_IPV4_GATEWAY] = Variant("s", str(ipv4setting.gateway))
|
ipv4[CONF_ATTR_IPV4_GATEWAY] = Variant("s", str(ipv4setting.gateway))
|
||||||
|
elif ipv4setting.method == InterfaceMethod.SHARED:
|
||||||
|
ipv4[CONF_ATTR_IPV4_METHOD] = Variant("s", "shared")
|
||||||
|
ipv4[CONF_ATTR_IPV4_ADDRESS_DATA] = _get_address_data(ipv4setting)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Invalid IPv4 InterfaceMethod")
|
raise RuntimeError("Invalid IPv4 InterfaceMethod")
|
||||||
|
|
||||||
@ -199,13 +207,21 @@ def get_connection_from_interface(
|
|||||||
elif interface.type == InterfaceType.WIRELESS:
|
elif interface.type == InterfaceType.WIRELESS:
|
||||||
wireless = {
|
wireless = {
|
||||||
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC: Variant("s", "preserve"),
|
CONF_ATTR_802_WIRELESS_ASSIGNED_MAC: Variant("s", "preserve"),
|
||||||
CONF_ATTR_802_WIRELESS_MODE: Variant("s", "infrastructure"),
|
CONF_ATTR_802_WIRELESS_MODE: Variant(
|
||||||
|
"s", (interface.wifi and interface.wifi.mode) or "infrastructure"
|
||||||
|
),
|
||||||
CONF_ATTR_802_WIRELESS_POWERSAVE: Variant("i", 1),
|
CONF_ATTR_802_WIRELESS_POWERSAVE: Variant("i", 1),
|
||||||
}
|
}
|
||||||
if interface.wifi and interface.wifi.ssid:
|
if interface.wifi and interface.wifi.ssid:
|
||||||
wireless[CONF_ATTR_802_WIRELESS_SSID] = Variant(
|
wireless[CONF_ATTR_802_WIRELESS_SSID] = Variant(
|
||||||
"ay", interface.wifi.ssid.encode("UTF-8")
|
"ay", interface.wifi.ssid.encode("UTF-8")
|
||||||
)
|
)
|
||||||
|
if interface.wifi and interface.wifi.band:
|
||||||
|
wireless[CONF_ATTR_802_WIRELESS_BAND] = Variant("s", interface.wifi.band)
|
||||||
|
if interface.wifi and interface.wifi.channel:
|
||||||
|
wireless[CONF_ATTR_802_WIRELESS_CHANNEL] = Variant(
|
||||||
|
"u", interface.wifi.channel
|
||||||
|
)
|
||||||
|
|
||||||
conn[CONF_ATTR_802_WIRELESS] = wireless
|
conn[CONF_ATTR_802_WIRELESS] = wireless
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from ..dbus.const import (
|
|||||||
)
|
)
|
||||||
from ..dbus.network.connection import NetworkConnection
|
from ..dbus.network.connection import NetworkConnection
|
||||||
from ..dbus.network.interface import NetworkInterface
|
from ..dbus.network.interface import NetworkInterface
|
||||||
from .const import AuthMethod, InterfaceMethod, InterfaceType, WifiMode
|
from .const import AuthMethod, InterfaceMethod, InterfaceType, WifiBand, WifiMode
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
@ -55,6 +55,8 @@ class WifiConfig:
|
|||||||
auth: AuthMethod
|
auth: AuthMethod
|
||||||
psk: str | None
|
psk: str | None
|
||||||
signal: int | None
|
signal: int | None
|
||||||
|
band: WifiBand | None
|
||||||
|
channel: int | None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
@ -191,6 +193,7 @@ class Interface:
|
|||||||
NMInterfaceMethod.DISABLED: InterfaceMethod.DISABLED,
|
NMInterfaceMethod.DISABLED: InterfaceMethod.DISABLED,
|
||||||
NMInterfaceMethod.MANUAL: InterfaceMethod.STATIC,
|
NMInterfaceMethod.MANUAL: InterfaceMethod.STATIC,
|
||||||
NMInterfaceMethod.LINK_LOCAL: InterfaceMethod.DISABLED,
|
NMInterfaceMethod.LINK_LOCAL: InterfaceMethod.DISABLED,
|
||||||
|
NMInterfaceMethod.SHARED: InterfaceMethod.SHARED,
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapping.get(method, InterfaceMethod.DISABLED)
|
return mapping.get(method, InterfaceMethod.DISABLED)
|
||||||
@ -237,6 +240,12 @@ class Interface:
|
|||||||
if inet.settings.wireless.mode:
|
if inet.settings.wireless.mode:
|
||||||
mode = WifiMode(inet.settings.wireless.mode)
|
mode = WifiMode(inet.settings.wireless.mode)
|
||||||
|
|
||||||
|
# Band and Channel
|
||||||
|
band = channel = None
|
||||||
|
if mode == WifiMode.AP:
|
||||||
|
band = WifiBand(inet.settings.wireless.band)
|
||||||
|
channel = inet.settings.wireless.channel
|
||||||
|
|
||||||
# Signal
|
# Signal
|
||||||
if inet.wireless and inet.wireless.active:
|
if inet.wireless and inet.wireless.active:
|
||||||
signal = inet.wireless.active.strength
|
signal = inet.wireless.active.strength
|
||||||
@ -249,10 +258,12 @@ class Interface:
|
|||||||
auth,
|
auth,
|
||||||
psk,
|
psk,
|
||||||
signal,
|
signal,
|
||||||
|
band,
|
||||||
|
channel,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _map_nm_vlan(inet: NetworkInterface) -> WifiConfig | None:
|
def _map_nm_vlan(inet: NetworkInterface) -> VlanConfig | None:
|
||||||
"""Create mapping to nm vlan property."""
|
"""Create mapping to nm vlan property."""
|
||||||
if inet.type != DeviceType.VLAN or not inet.settings:
|
if inet.type != DeviceType.VLAN or not inet.settings:
|
||||||
return None
|
return None
|
||||||
|
@ -13,6 +13,7 @@ class InterfaceMethod(StrEnum):
|
|||||||
DISABLED = "disabled"
|
DISABLED = "disabled"
|
||||||
STATIC = "static"
|
STATIC = "static"
|
||||||
AUTO = "auto"
|
AUTO = "auto"
|
||||||
|
SHARED = "shared"
|
||||||
|
|
||||||
|
|
||||||
class InterfaceType(StrEnum):
|
class InterfaceType(StrEnum):
|
||||||
@ -31,6 +32,13 @@ class AuthMethod(StrEnum):
|
|||||||
WPA_PSK = "wpa-psk"
|
WPA_PSK = "wpa-psk"
|
||||||
|
|
||||||
|
|
||||||
|
class WifiBand(StrEnum):
|
||||||
|
"""Wifi band."""
|
||||||
|
|
||||||
|
A = "a"
|
||||||
|
BG = "bg"
|
||||||
|
|
||||||
|
|
||||||
class WifiMode(StrEnum):
|
class WifiMode(StrEnum):
|
||||||
"""Wifi mode."""
|
"""Wifi mode."""
|
||||||
|
|
||||||
|
@ -276,6 +276,31 @@ async def test_api_network_interface_update_wifi_error(api_client: TestClient):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_api_network_interface_update_wifi_bad_channel(api_client: TestClient):
|
||||||
|
"""Test network interface WiFi API error handling for bad channel."""
|
||||||
|
# Simulate frontend WiFi interface edit where the user selects a bad channel.
|
||||||
|
resp = await api_client.post(
|
||||||
|
f"/network/interface/{TEST_INTERFACE_WLAN_NAME}/update",
|
||||||
|
json={
|
||||||
|
"enabled": True,
|
||||||
|
"ipv4": {
|
||||||
|
"method": "shared",
|
||||||
|
"address": ["10.42.0.1/24"],
|
||||||
|
},
|
||||||
|
"ipv6": {
|
||||||
|
"method": "auto",
|
||||||
|
},
|
||||||
|
"wifi": {"mode": "ap", "ssid": "HotSpot", "band": "bg", "channel": 17},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
result = await resp.json()
|
||||||
|
assert result["result"] == "error"
|
||||||
|
assert (
|
||||||
|
result["message"]
|
||||||
|
== "Can't create config and activate wlan0: 802-11-wireless.channel: '17' is not a valid channel"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_api_network_interface_update_remove(api_client: TestClient):
|
async def test_api_network_interface_update_remove(api_client: TestClient):
|
||||||
"""Test network manager api."""
|
"""Test network manager api."""
|
||||||
resp = await api_client.post(
|
resp = await api_client.post(
|
||||||
|
@ -240,6 +240,15 @@ class NetworkManager(DBusServiceMock):
|
|||||||
"org.freedesktop.NetworkManager.Device.InvalidConnection",
|
"org.freedesktop.NetworkManager.Device.InvalidConnection",
|
||||||
"A 'wireless' setting with a valid SSID is required if no AP path was given.",
|
"A 'wireless' setting with a valid SSID is required if no AP path was given.",
|
||||||
)
|
)
|
||||||
|
if (
|
||||||
|
"channel" in connection["802-11-wireless"]
|
||||||
|
and connection["802-11-wireless"]["channel"].value > 14
|
||||||
|
):
|
||||||
|
raise DBusError(
|
||||||
|
"org.freedesktop.NetworkManager.Device.InvalidConnection",
|
||||||
|
# this is the actual error from NetworkManager
|
||||||
|
f"802-11-wireless.channel: '{connection['802-11-wireless']['channel'].value}' is not a valid channel",
|
||||||
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"/org/freedesktop/NetworkManager/Settings/1",
|
"/org/freedesktop/NetworkManager/Settings/1",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user