1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-19 18:38:58 +00:00

Add strict typing, shared constants, and fix OPNsense name casing (#151599)

This commit is contained in:
Kevin McCormack
2025-09-04 05:20:16 -04:00
committed by GitHub
parent 8d945d89de
commit eae1fe4a56
7 changed files with 63 additions and 35 deletions

View File

@@ -383,6 +383,7 @@ homeassistant.components.openai_conversation.*
homeassistant.components.openexchangerates.*
homeassistant.components.opensky.*
homeassistant.components.openuv.*
homeassistant.components.opnsense.*
homeassistant.components.opower.*
homeassistant.components.oralb.*
homeassistant.components.otbr.*

View File

@@ -1,4 +1,4 @@
"""Support for OPNSense Routers."""
"""Support for OPNsense Routers."""
import logging
@@ -12,15 +12,16 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.typing import ConfigType
from .const import (
CONF_API_SECRET,
CONF_INTERFACE_CLIENT,
CONF_TRACKER_INTERFACES,
DOMAIN,
OPNSENSE_DATA,
)
_LOGGER = logging.getLogger(__name__)
CONF_API_SECRET = "api_secret"
CONF_TRACKER_INTERFACE = "tracker_interfaces"
DOMAIN = "opnsense"
OPNSENSE_DATA = DOMAIN
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
@@ -29,7 +30,7 @@ CONFIG_SCHEMA = vol.Schema(
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_API_SECRET): cv.string,
vol.Optional(CONF_VERIFY_SSL, default=False): cv.boolean,
vol.Optional(CONF_TRACKER_INTERFACE, default=[]): vol.All(
vol.Optional(CONF_TRACKER_INTERFACES, default=[]): vol.All(
cv.ensure_list, [cv.string]
),
}
@@ -47,7 +48,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
api_key = conf[CONF_API_KEY]
api_secret = conf[CONF_API_SECRET]
verify_ssl = conf[CONF_VERIFY_SSL]
tracker_interfaces = conf[CONF_TRACKER_INTERFACE]
tracker_interfaces = conf[CONF_TRACKER_INTERFACES]
interfaces_client = diagnostics.InterfaceClient(
api_key, api_secret, url, verify_ssl, timeout=20
@@ -72,8 +73,8 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
return False
hass.data[OPNSENSE_DATA] = {
"interfaces": interfaces_client,
CONF_TRACKER_INTERFACE: tracker_interfaces,
CONF_INTERFACE_CLIENT: interfaces_client,
CONF_TRACKER_INTERFACES: tracker_interfaces,
}
load_platform(hass, Platform.DEVICE_TRACKER, DOMAIN, tracker_interfaces, config)

View File

@@ -0,0 +1,8 @@
"""Constants for OPNsense component."""
DOMAIN = "opnsense"
OPNSENSE_DATA = DOMAIN
CONF_API_SECRET = "api_secret"
CONF_INTERFACE_CLIENT = "interface_client"
CONF_TRACKER_INTERFACES = "tracker_interfaces"

View File

@@ -1,34 +1,41 @@
"""Device tracker support for OPNSense routers."""
"""Device tracker support for OPNsense routers."""
from __future__ import annotations
from typing import Any, NewType
from pyopnsense import diagnostics
from homeassistant.components.device_tracker import DeviceScanner
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
from . import CONF_TRACKER_INTERFACE, OPNSENSE_DATA
from .const import CONF_INTERFACE_CLIENT, CONF_TRACKER_INTERFACES, OPNSENSE_DATA
DeviceDetails = NewType("DeviceDetails", dict[str, Any])
DeviceDetailsByMAC = NewType("DeviceDetailsByMAC", dict[str, DeviceDetails])
async def async_get_scanner(
hass: HomeAssistant, config: ConfigType
) -> OPNSenseDeviceScanner:
"""Configure the OPNSense device_tracker."""
interface_client = hass.data[OPNSENSE_DATA]["interfaces"]
return OPNSenseDeviceScanner(
interface_client, hass.data[OPNSENSE_DATA][CONF_TRACKER_INTERFACE]
) -> DeviceScanner | None:
"""Configure the OPNsense device_tracker."""
return OPNsenseDeviceScanner(
hass.data[OPNSENSE_DATA][CONF_INTERFACE_CLIENT],
hass.data[OPNSENSE_DATA][CONF_TRACKER_INTERFACES],
)
class OPNSenseDeviceScanner(DeviceScanner):
"""Class which queries a router running OPNsense."""
class OPNsenseDeviceScanner(DeviceScanner):
"""This class queries a router running OPNsense."""
def __init__(self, client, interfaces):
def __init__(
self, client: diagnostics.InterfaceClient, interfaces: list[str]
) -> None:
"""Initialize the scanner."""
self.last_results = {}
self.last_results: dict[str, Any] = {}
self.client = client
self.interfaces = interfaces
def _get_mac_addrs(self, devices):
def _get_mac_addrs(self, devices: list[DeviceDetails]) -> DeviceDetailsByMAC | dict:
"""Create dict with mac address keys from list of devices."""
out_devices = {}
for device in devices:
@@ -36,30 +43,31 @@ class OPNSenseDeviceScanner(DeviceScanner):
out_devices[device["mac"]] = device
return out_devices
def scan_devices(self):
def scan_devices(self) -> list[str]:
"""Scan for new devices and return a list with found device IDs."""
self.update_info()
return list(self.last_results)
def get_device_name(self, device):
def get_device_name(self, device: str) -> str | None:
"""Return the name of the given device or None if we don't know."""
if device not in self.last_results:
return None
return self.last_results[device].get("hostname") or None
def update_info(self):
"""Ensure the information from the OPNSense router is up to date.
def update_info(self) -> bool:
"""Ensure the information from the OPNsense router is up to date.
Return boolean if scanning successful.
"""
devices = self.client.get_arp()
self.last_results = self._get_mac_addrs(devices)
return True
def get_extra_attributes(self, device):
def get_extra_attributes(self, device: str) -> dict[Any, Any]:
"""Return the extra attrs of the given device."""
if device not in self.last_results:
return None
if not (mfg := self.last_results[device].get("manufacturer")):
return {}
mfg = self.last_results[device].get("manufacturer")
if not mfg:
return {}
return {"manufacturer": mfg}

View File

@@ -1,6 +1,6 @@
{
"domain": "opnsense",
"name": "OPNSense",
"name": "OPNsense",
"codeowners": ["@mtreinish"],
"documentation": "https://www.home-assistant.io/integrations/opnsense",
"iot_class": "local_polling",

View File

@@ -4736,7 +4736,7 @@
}
},
"opnsense": {
"name": "OPNSense",
"name": "OPNsense",
"integration_type": "hub",
"config_flow": false,
"iot_class": "local_polling"

10
mypy.ini generated
View File

@@ -3586,6 +3586,16 @@ disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.opnsense.*]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.opower.*]
check_untyped_defs = true
disallow_incomplete_defs = true