diff --git a/.strict-typing b/.strict-typing index 452a6f647a7..ce06d00c697 100644 --- a/.strict-typing +++ b/.strict-typing @@ -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.* diff --git a/homeassistant/components/opnsense/__init__.py b/homeassistant/components/opnsense/__init__.py index 66f35a51b87..bc085dbfa4d 100644 --- a/homeassistant/components/opnsense/__init__.py +++ b/homeassistant/components/opnsense/__init__.py @@ -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) diff --git a/homeassistant/components/opnsense/const.py b/homeassistant/components/opnsense/const.py new file mode 100644 index 00000000000..62ab16701f4 --- /dev/null +++ b/homeassistant/components/opnsense/const.py @@ -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" diff --git a/homeassistant/components/opnsense/device_tracker.py b/homeassistant/components/opnsense/device_tracker.py index 6357ce38e1d..5f6d8d2d436 100644 --- a/homeassistant/components/opnsense/device_tracker.py +++ b/homeassistant/components/opnsense/device_tracker.py @@ -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} diff --git a/homeassistant/components/opnsense/manifest.json b/homeassistant/components/opnsense/manifest.json index 4dd82216f1a..0a9aecbde25 100644 --- a/homeassistant/components/opnsense/manifest.json +++ b/homeassistant/components/opnsense/manifest.json @@ -1,6 +1,6 @@ { "domain": "opnsense", - "name": "OPNSense", + "name": "OPNsense", "codeowners": ["@mtreinish"], "documentation": "https://www.home-assistant.io/integrations/opnsense", "iot_class": "local_polling", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index 0f7e6e2716c..4e243fb686f 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -4736,7 +4736,7 @@ } }, "opnsense": { - "name": "OPNSense", + "name": "OPNsense", "integration_type": "hub", "config_flow": false, "iot_class": "local_polling" diff --git a/mypy.ini b/mypy.ini index db883045f85..41ab0f88a10 100644 --- a/mypy.ini +++ b/mypy.ini @@ -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