Move SsdpServiceInfo to service_info helpers (#135661)

* Move SsdpServiceInfo to service_info helpers

* docstring

* Move string constants

* Adjust components
This commit is contained in:
epenet 2025-01-15 14:00:27 +01:00 committed by GitHub
parent 4ccc686295
commit 8c13daf6d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 405 additions and 128 deletions

View File

@ -14,6 +14,11 @@ from homeassistant.components import ssdp
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_DEVICE_ID, CONF_HOST, CONF_URL from homeassistant.const import CONF_DEVICE_ID, CONF_HOST, CONF_URL
from homeassistant.data_entry_flow import AbortFlow from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_SERVICE_LIST,
SsdpServiceInfo,
)
from .const import CONF_SOURCE_ID, CONFIG_VERSION, DEFAULT_NAME, DOMAIN from .const import CONF_SOURCE_ID, CONFIG_VERSION, DEFAULT_NAME, DOMAIN
from .util import generate_source_id from .util import generate_source_id
@ -33,7 +38,7 @@ class DlnaDmsFlowHandler(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize flow.""" """Initialize flow."""
self._discoveries: dict[str, ssdp.SsdpServiceInfo] = {} self._discoveries: dict[str, SsdpServiceInfo] = {}
self._location: str | None = None self._location: str | None = None
self._usn: str | None = None self._usn: str | None = None
self._name: str | None = None self._name: str | None = None
@ -60,14 +65,14 @@ class DlnaDmsFlowHandler(ConfigFlow, domain=DOMAIN):
} }
discovery_choices = { discovery_choices = {
host: f"{discovery.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME)} ({host})" host: f"{discovery.upnp.get(ATTR_UPNP_FRIENDLY_NAME)} ({host})"
for host, discovery in self._discoveries.items() for host, discovery in self._discoveries.items()
} }
data_schema = vol.Schema({vol.Optional(CONF_HOST): vol.In(discovery_choices)}) data_schema = vol.Schema({vol.Optional(CONF_HOST): vol.In(discovery_choices)})
return self.async_show_form(step_id="user", data_schema=data_schema) return self.async_show_form(step_id="user", data_schema=data_schema)
async def async_step_ssdp( async def async_step_ssdp(
self, discovery_info: ssdp.SsdpServiceInfo self, discovery_info: SsdpServiceInfo
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Handle a flow initialized by SSDP discovery.""" """Handle a flow initialized by SSDP discovery."""
if LOGGER.isEnabledFor(logging.DEBUG): if LOGGER.isEnabledFor(logging.DEBUG):
@ -81,7 +86,7 @@ class DlnaDmsFlowHandler(ConfigFlow, domain=DOMAIN):
# Abort if the device doesn't support all services required for a DmsDevice. # Abort if the device doesn't support all services required for a DmsDevice.
# Use the discovery_info instead of DmsDevice.is_profile_device to avoid # Use the discovery_info instead of DmsDevice.is_profile_device to avoid
# contacting the device again. # contacting the device again.
discovery_service_list = discovery_info.upnp.get(ssdp.ATTR_UPNP_SERVICE_LIST) discovery_service_list = discovery_info.upnp.get(ATTR_UPNP_SERVICE_LIST)
if not discovery_service_list: if not discovery_service_list:
return self.async_abort(reason="not_dms") return self.async_abort(reason="not_dms")
@ -135,7 +140,7 @@ class DlnaDmsFlowHandler(ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title=self._name, data=data) return self.async_create_entry(title=self._name, data=data)
async def _async_parse_discovery( async def _async_parse_discovery(
self, discovery_info: ssdp.SsdpServiceInfo, raise_on_progress: bool = True self, discovery_info: SsdpServiceInfo, raise_on_progress: bool = True
) -> None: ) -> None:
"""Get required details from an SSDP discovery. """Get required details from an SSDP discovery.
@ -162,15 +167,15 @@ class DlnaDmsFlowHandler(ConfigFlow, domain=DOMAIN):
) )
self._name = ( self._name = (
discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME) discovery_info.upnp.get(ATTR_UPNP_FRIENDLY_NAME)
or urlparse(self._location).hostname or urlparse(self._location).hostname
or DEFAULT_NAME or DEFAULT_NAME
) )
async def _async_get_discoveries(self) -> list[ssdp.SsdpServiceInfo]: async def _async_get_discoveries(self) -> list[SsdpServiceInfo]:
"""Get list of unconfigured DLNA devices discovered by SSDP.""" """Get list of unconfigured DLNA devices discovered by SSDP."""
# Get all compatible devices from ssdp's cache # Get all compatible devices from ssdp's cache
discoveries: list[ssdp.SsdpServiceInfo] = [] discoveries: list[SsdpServiceInfo] = []
for udn_st in DmsDevice.DEVICE_TYPES: for udn_st in DmsDevice.DEVICE_TYPES:
st_discoveries = await ssdp.async_get_discovery_info_by_st( st_discoveries = await ssdp.async_get_discovery_info_by_st(
self.hass, udn_st self.hass, udn_st

View File

@ -23,11 +23,6 @@ from demetriek import (
import voluptuous as vol import voluptuous as vol
from yarl import URL from yarl import URL
from homeassistant.components.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_SERIAL,
SsdpServiceInfo,
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_DEVICE, CONF_HOST, CONF_MAC from homeassistant.const import CONF_API_KEY, CONF_DEVICE, CONF_HOST, CONF_MAC
from homeassistant.data_entry_flow import AbortFlow from homeassistant.data_entry_flow import AbortFlow
@ -44,6 +39,11 @@ from homeassistant.helpers.selector import (
TextSelectorType, TextSelectorType,
) )
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_SERIAL,
SsdpServiceInfo,
)
from homeassistant.util.network import is_link_local from homeassistant.util.network import is_link_local
from .const import DOMAIN, LOGGER from .const import DOMAIN, LOGGER

View File

@ -3,13 +3,13 @@
import logging import logging
from typing import Any from typing import Any
from homeassistant.components.ssdp import ( from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_NAME
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_UDN, ATTR_UPNP_UDN,
SsdpServiceInfo, SsdpServiceInfo,
) )
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_NAME
from .const import DOMAIN from .const import DOMAIN

View File

@ -4,7 +4,6 @@ from __future__ import annotations
import asyncio import asyncio
from collections.abc import Callable, Coroutine, Mapping from collections.abc import Callable, Coroutine, Mapping
from dataclasses import dataclass, field
from datetime import timedelta from datetime import timedelta
from enum import Enum from enum import Enum
from functools import partial from functools import partial
@ -44,13 +43,36 @@ from homeassistant.const import (
__version__ as current_version, __version__ as current_version,
) )
from homeassistant.core import Event, HassJob, HomeAssistant, callback as core_callback from homeassistant.core import Event, HassJob, HomeAssistant, callback as core_callback
from homeassistant.data_entry_flow import BaseServiceInfo
from homeassistant.helpers import config_validation as cv, discovery_flow from homeassistant.helpers import config_validation as cv, discovery_flow
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.deprecation import (
DeprecatedConstant,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.instance_id import async_get as async_get_instance_id from homeassistant.helpers.instance_id import async_get as async_get_instance_id
from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.network import NoURLAvailableError, get_url
from homeassistant.helpers.service_info.ssdp import (
ATTR_NT as _ATTR_NT,
ATTR_ST as _ATTR_ST,
ATTR_UPNP_DEVICE_TYPE as _ATTR_UPNP_DEVICE_TYPE,
ATTR_UPNP_FRIENDLY_NAME as _ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_MANUFACTURER as _ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MANUFACTURER_URL as _ATTR_UPNP_MANUFACTURER_URL,
ATTR_UPNP_MODEL_DESCRIPTION as _ATTR_UPNP_MODEL_DESCRIPTION,
ATTR_UPNP_MODEL_NAME as _ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_MODEL_NUMBER as _ATTR_UPNP_MODEL_NUMBER,
ATTR_UPNP_MODEL_URL as _ATTR_UPNP_MODEL_URL,
ATTR_UPNP_PRESENTATION_URL as _ATTR_UPNP_PRESENTATION_URL,
ATTR_UPNP_SERIAL as _ATTR_UPNP_SERIAL,
ATTR_UPNP_SERVICE_LIST as _ATTR_UPNP_SERVICE_LIST,
ATTR_UPNP_UDN as _ATTR_UPNP_UDN,
ATTR_UPNP_UPC as _ATTR_UPNP_UPC,
SsdpServiceInfo as _SsdpServiceInfo,
)
from homeassistant.helpers.system_info import async_get_system_info from homeassistant.helpers.system_info import async_get_system_info
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import async_get_ssdp, bind_hass from homeassistant.loader import async_get_ssdp, bind_hass
@ -77,30 +99,90 @@ ATTR_SSDP_SERVER = "ssdp_server"
ATTR_SSDP_BOOTID = "BOOTID.UPNP.ORG" ATTR_SSDP_BOOTID = "BOOTID.UPNP.ORG"
ATTR_SSDP_NEXTBOOTID = "NEXTBOOTID.UPNP.ORG" ATTR_SSDP_NEXTBOOTID = "NEXTBOOTID.UPNP.ORG"
# Attributes for accessing info from retrieved UPnP device description # Attributes for accessing info from retrieved UPnP device description
ATTR_ST = "st" _DEPRECATED_ATTR_ST = DeprecatedConstant(
ATTR_NT = "nt" _ATTR_ST,
ATTR_UPNP_DEVICE_TYPE = "deviceType" "homeassistant.helpers.service_info.ssdp.ATTR_ST",
ATTR_UPNP_FRIENDLY_NAME = "friendlyName" "2026.2",
ATTR_UPNP_MANUFACTURER = "manufacturer" )
ATTR_UPNP_MANUFACTURER_URL = "manufacturerURL" _DEPRECATED_ATTR_NT = DeprecatedConstant(
ATTR_UPNP_MODEL_DESCRIPTION = "modelDescription" _ATTR_NT,
ATTR_UPNP_MODEL_NAME = "modelName" "homeassistant.helpers.service_info.ssdp.ATTR_NT",
ATTR_UPNP_MODEL_NUMBER = "modelNumber" "2026.2",
ATTR_UPNP_MODEL_URL = "modelURL" )
ATTR_UPNP_SERIAL = "serialNumber" _DEPRECATED_ATTR_UPNP_DEVICE_TYPE = DeprecatedConstant(
ATTR_UPNP_SERVICE_LIST = "serviceList" _ATTR_UPNP_DEVICE_TYPE,
ATTR_UPNP_UDN = "UDN" "homeassistant.helpers.service_info.ssdp.ATTR_UPNP_DEVICE_TYPE",
ATTR_UPNP_UPC = "UPC" "2026.2",
ATTR_UPNP_PRESENTATION_URL = "presentationURL" )
_DEPRECATED_ATTR_UPNP_FRIENDLY_NAME = DeprecatedConstant(
_ATTR_UPNP_FRIENDLY_NAME,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_FRIENDLY_NAME",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_MANUFACTURER = DeprecatedConstant(
_ATTR_UPNP_MANUFACTURER,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MANUFACTURER",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_MANUFACTURER_URL = DeprecatedConstant(
_ATTR_UPNP_MANUFACTURER_URL,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MANUFACTURER_URL",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_MODEL_DESCRIPTION = DeprecatedConstant(
_ATTR_UPNP_MODEL_DESCRIPTION,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MODEL_DESCRIPTION",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_MODEL_NAME = DeprecatedConstant(
_ATTR_UPNP_MODEL_NAME,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MODEL_NAME",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_MODEL_NUMBER = DeprecatedConstant(
_ATTR_UPNP_MODEL_NUMBER,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MODEL_NUMBER",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_MODEL_URL = DeprecatedConstant(
_ATTR_UPNP_MODEL_URL,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MODEL_URL",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_SERIAL = DeprecatedConstant(
_ATTR_UPNP_SERIAL,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_SERIAL",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_SERVICE_LIST = DeprecatedConstant(
_ATTR_UPNP_SERVICE_LIST,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_SERVICE_LIST",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_UDN = DeprecatedConstant(
_ATTR_UPNP_UDN,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_UDN",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_UPC = DeprecatedConstant(
_ATTR_UPNP_UPC,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_UPC",
"2026.2",
)
_DEPRECATED_ATTR_UPNP_PRESENTATION_URL = DeprecatedConstant(
_ATTR_UPNP_PRESENTATION_URL,
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_PRESENTATION_URL",
"2026.2",
)
# Attributes for accessing info added by Home Assistant # Attributes for accessing info added by Home Assistant
ATTR_HA_MATCHING_DOMAINS = "x_homeassistant_matching_domains" ATTR_HA_MATCHING_DOMAINS = "x_homeassistant_matching_domains"
PRIMARY_MATCH_KEYS = [ PRIMARY_MATCH_KEYS = [
ATTR_UPNP_MANUFACTURER, _ATTR_UPNP_MANUFACTURER,
ATTR_ST, _ATTR_ST,
ATTR_UPNP_DEVICE_TYPE, _ATTR_UPNP_DEVICE_TYPE,
ATTR_NT, _ATTR_NT,
ATTR_UPNP_MANUFACTURER_URL, _ATTR_UPNP_MANUFACTURER_URL,
] ]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -108,27 +190,16 @@ _LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
_DEPRECATED_SsdpServiceInfo = DeprecatedConstant(
@dataclass(slots=True) _SsdpServiceInfo,
class SsdpServiceInfo(BaseServiceInfo): "homeassistant.helpers.service_info.ssdp.SsdpServiceInfo",
"""Prepared info from ssdp/upnp entries.""" "2026.2",
)
ssdp_usn: str
ssdp_st: str
upnp: Mapping[str, Any]
ssdp_location: str | None = None
ssdp_nt: str | None = None
ssdp_udn: str | None = None
ssdp_ext: str | None = None
ssdp_server: str | None = None
ssdp_headers: Mapping[str, Any] = field(default_factory=dict)
ssdp_all_locations: set[str] = field(default_factory=set)
x_homeassistant_matching_domains: set[str] = field(default_factory=set)
SsdpChange = Enum("SsdpChange", "ALIVE BYEBYE UPDATE") SsdpChange = Enum("SsdpChange", "ALIVE BYEBYE UPDATE")
type SsdpHassJobCallback = HassJob[ type SsdpHassJobCallback = HassJob[
[SsdpServiceInfo, SsdpChange], Coroutine[Any, Any, None] | None [_SsdpServiceInfo, SsdpChange], Coroutine[Any, Any, None] | None
] ]
SSDP_SOURCE_SSDP_CHANGE_MAPPING: Mapping[SsdpSource, SsdpChange] = { SSDP_SOURCE_SSDP_CHANGE_MAPPING: Mapping[SsdpSource, SsdpChange] = {
@ -148,7 +219,9 @@ def _format_err(name: str, *args: Any) -> str:
@bind_hass @bind_hass
async def async_register_callback( async def async_register_callback(
hass: HomeAssistant, hass: HomeAssistant,
callback: Callable[[SsdpServiceInfo, SsdpChange], Coroutine[Any, Any, None] | None], callback: Callable[
[_SsdpServiceInfo, SsdpChange], Coroutine[Any, Any, None] | None
],
match_dict: dict[str, str] | None = None, match_dict: dict[str, str] | None = None,
) -> Callable[[], None]: ) -> Callable[[], None]:
"""Register to receive a callback on ssdp broadcast. """Register to receive a callback on ssdp broadcast.
@ -169,7 +242,7 @@ async def async_register_callback(
@bind_hass @bind_hass
async def async_get_discovery_info_by_udn_st( async def async_get_discovery_info_by_udn_st(
hass: HomeAssistant, udn: str, st: str hass: HomeAssistant, udn: str, st: str
) -> SsdpServiceInfo | None: ) -> _SsdpServiceInfo | None:
"""Fetch the discovery info cache.""" """Fetch the discovery info cache."""
scanner: Scanner = hass.data[DOMAIN][SSDP_SCANNER] scanner: Scanner = hass.data[DOMAIN][SSDP_SCANNER]
return await scanner.async_get_discovery_info_by_udn_st(udn, st) return await scanner.async_get_discovery_info_by_udn_st(udn, st)
@ -178,7 +251,7 @@ async def async_get_discovery_info_by_udn_st(
@bind_hass @bind_hass
async def async_get_discovery_info_by_st( async def async_get_discovery_info_by_st(
hass: HomeAssistant, st: str hass: HomeAssistant, st: str
) -> list[SsdpServiceInfo]: ) -> list[_SsdpServiceInfo]:
"""Fetch all the entries matching the st.""" """Fetch all the entries matching the st."""
scanner: Scanner = hass.data[DOMAIN][SSDP_SCANNER] scanner: Scanner = hass.data[DOMAIN][SSDP_SCANNER]
return await scanner.async_get_discovery_info_by_st(st) return await scanner.async_get_discovery_info_by_st(st)
@ -187,7 +260,7 @@ async def async_get_discovery_info_by_st(
@bind_hass @bind_hass
async def async_get_discovery_info_by_udn( async def async_get_discovery_info_by_udn(
hass: HomeAssistant, udn: str hass: HomeAssistant, udn: str
) -> list[SsdpServiceInfo]: ) -> list[_SsdpServiceInfo]:
"""Fetch all the entries matching the udn.""" """Fetch all the entries matching the udn."""
scanner: Scanner = hass.data[DOMAIN][SSDP_SCANNER] scanner: Scanner = hass.data[DOMAIN][SSDP_SCANNER]
return await scanner.async_get_discovery_info_by_udn(udn) return await scanner.async_get_discovery_info_by_udn(udn)
@ -227,7 +300,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
def _async_process_callbacks( def _async_process_callbacks(
hass: HomeAssistant, hass: HomeAssistant,
callbacks: list[SsdpHassJobCallback], callbacks: list[SsdpHassJobCallback],
discovery_info: SsdpServiceInfo, discovery_info: _SsdpServiceInfo,
ssdp_change: SsdpChange, ssdp_change: SsdpChange,
) -> None: ) -> None:
for callback in callbacks: for callback in callbacks:
@ -562,11 +635,11 @@ class Scanner:
) )
def _async_dismiss_discoveries( def _async_dismiss_discoveries(
self, byebye_discovery_info: SsdpServiceInfo self, byebye_discovery_info: _SsdpServiceInfo
) -> None: ) -> None:
"""Dismiss all discoveries for the given address.""" """Dismiss all discoveries for the given address."""
for flow in self.hass.config_entries.flow.async_progress_by_init_data_type( for flow in self.hass.config_entries.flow.async_progress_by_init_data_type(
SsdpServiceInfo, _SsdpServiceInfo,
lambda service_info: bool( lambda service_info: bool(
service_info.ssdp_st == byebye_discovery_info.ssdp_st service_info.ssdp_st == byebye_discovery_info.ssdp_st
and service_info.ssdp_location == byebye_discovery_info.ssdp_location and service_info.ssdp_location == byebye_discovery_info.ssdp_location
@ -589,7 +662,7 @@ class Scanner:
async def _async_headers_to_discovery_info( async def _async_headers_to_discovery_info(
self, ssdp_device: SsdpDevice, headers: CaseInsensitiveDict self, ssdp_device: SsdpDevice, headers: CaseInsensitiveDict
) -> SsdpServiceInfo: ) -> _SsdpServiceInfo:
"""Combine the headers and description into discovery_info. """Combine the headers and description into discovery_info.
Building this is a bit expensive so we only do it on demand. Building this is a bit expensive so we only do it on demand.
@ -602,7 +675,7 @@ class Scanner:
async def async_get_discovery_info_by_udn_st( async def async_get_discovery_info_by_udn_st(
self, udn: str, st: str self, udn: str, st: str
) -> SsdpServiceInfo | None: ) -> _SsdpServiceInfo | None:
"""Return discovery_info for a udn and st.""" """Return discovery_info for a udn and st."""
for ssdp_device in self._ssdp_devices: for ssdp_device in self._ssdp_devices:
if ssdp_device.udn == udn: if ssdp_device.udn == udn:
@ -612,7 +685,7 @@ class Scanner:
) )
return None return None
async def async_get_discovery_info_by_st(self, st: str) -> list[SsdpServiceInfo]: async def async_get_discovery_info_by_st(self, st: str) -> list[_SsdpServiceInfo]:
"""Return matching discovery_infos for a st.""" """Return matching discovery_infos for a st."""
return [ return [
await self._async_headers_to_discovery_info(ssdp_device, headers) await self._async_headers_to_discovery_info(ssdp_device, headers)
@ -620,7 +693,7 @@ class Scanner:
if (headers := ssdp_device.combined_headers(st)) if (headers := ssdp_device.combined_headers(st))
] ]
async def async_get_discovery_info_by_udn(self, udn: str) -> list[SsdpServiceInfo]: async def async_get_discovery_info_by_udn(self, udn: str) -> list[_SsdpServiceInfo]:
"""Return matching discovery_infos for a udn.""" """Return matching discovery_infos for a udn."""
return [ return [
await self._async_headers_to_discovery_info(ssdp_device, headers) await self._async_headers_to_discovery_info(ssdp_device, headers)
@ -665,7 +738,7 @@ def discovery_info_from_headers_and_description(
ssdp_device: SsdpDevice, ssdp_device: SsdpDevice,
combined_headers: CaseInsensitiveDict, combined_headers: CaseInsensitiveDict,
info_desc: Mapping[str, Any], info_desc: Mapping[str, Any],
) -> SsdpServiceInfo: ) -> _SsdpServiceInfo:
"""Convert headers and description to discovery_info.""" """Convert headers and description to discovery_info."""
ssdp_usn = combined_headers["usn"] ssdp_usn = combined_headers["usn"]
ssdp_st = combined_headers.get_lower("st") ssdp_st = combined_headers.get_lower("st")
@ -681,11 +754,11 @@ def discovery_info_from_headers_and_description(
ssdp_st = combined_headers["nt"] ssdp_st = combined_headers["nt"]
# Ensure UPnP "udn" is set # Ensure UPnP "udn" is set
if ATTR_UPNP_UDN not in upnp_info: if _ATTR_UPNP_UDN not in upnp_info:
if udn := _udn_from_usn(ssdp_usn): if udn := _udn_from_usn(ssdp_usn):
upnp_info[ATTR_UPNP_UDN] = udn upnp_info[_ATTR_UPNP_UDN] = udn
return SsdpServiceInfo( return _SsdpServiceInfo(
ssdp_usn=ssdp_usn, ssdp_usn=ssdp_usn,
ssdp_st=ssdp_st, ssdp_st=ssdp_st,
ssdp_ext=combined_headers.get_lower("ext"), ssdp_ext=combined_headers.get_lower("ext"),
@ -887,3 +960,11 @@ class Server:
"""Stop UPnP/SSDP servers.""" """Stop UPnP/SSDP servers."""
for server in self._upnp_servers: for server in self._upnp_servers:
await server.async_stop() await server.async_stop()
# These can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@ -9,7 +9,6 @@ from urllib.parse import urlparse
import voluptuous as vol import voluptuous as vol
from homeassistant.components import ssdp from homeassistant.components import ssdp
from homeassistant.components.ssdp import SsdpServiceInfo
from homeassistant.config_entries import ( from homeassistant.config_entries import (
SOURCE_IGNORE, SOURCE_IGNORE,
ConfigEntry, ConfigEntry,
@ -18,6 +17,12 @@ from homeassistant.config_entries import (
OptionsFlow, OptionsFlow,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_DEVICE_TYPE,
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_MODEL_NAME,
SsdpServiceInfo,
)
from .const import ( from .const import (
CONFIG_ENTRY_FORCE_POLL, CONFIG_ENTRY_FORCE_POLL,
@ -37,17 +42,17 @@ from .const import (
from .device import async_get_mac_address_from_host, get_preferred_location from .device import async_get_mac_address_from_host, get_preferred_location
def _friendly_name_from_discovery(discovery_info: ssdp.SsdpServiceInfo) -> str: def _friendly_name_from_discovery(discovery_info: SsdpServiceInfo) -> str:
"""Extract user-friendly name from discovery.""" """Extract user-friendly name from discovery."""
return cast( return cast(
str, str,
discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME) discovery_info.upnp.get(ATTR_UPNP_FRIENDLY_NAME)
or discovery_info.upnp.get(ssdp.ATTR_UPNP_MODEL_NAME) or discovery_info.upnp.get(ATTR_UPNP_MODEL_NAME)
or discovery_info.ssdp_headers.get("_host", ""), or discovery_info.ssdp_headers.get("_host", ""),
) )
def _is_complete_discovery(discovery_info: ssdp.SsdpServiceInfo) -> bool: def _is_complete_discovery(discovery_info: SsdpServiceInfo) -> bool:
"""Test if discovery is complete and usable.""" """Test if discovery is complete and usable."""
return bool( return bool(
discovery_info.ssdp_udn discovery_info.ssdp_udn
@ -59,7 +64,7 @@ def _is_complete_discovery(discovery_info: ssdp.SsdpServiceInfo) -> bool:
async def _async_discovered_igd_devices( async def _async_discovered_igd_devices(
hass: HomeAssistant, hass: HomeAssistant,
) -> list[ssdp.SsdpServiceInfo]: ) -> list[SsdpServiceInfo]:
"""Discovery IGD devices.""" """Discovery IGD devices."""
return await ssdp.async_get_discovery_info_by_st( return await ssdp.async_get_discovery_info_by_st(
hass, ST_IGD_V1 hass, ST_IGD_V1
@ -76,10 +81,10 @@ async def _async_mac_address_from_discovery(
return await async_get_mac_address_from_host(hass, host) return await async_get_mac_address_from_host(hass, host)
def _is_igd_device(discovery_info: ssdp.SsdpServiceInfo) -> bool: def _is_igd_device(discovery_info: SsdpServiceInfo) -> bool:
"""Test if discovery is a complete IGD device.""" """Test if discovery is a complete IGD device."""
root_device_info = discovery_info.upnp root_device_info = discovery_info.upnp
return root_device_info.get(ssdp.ATTR_UPNP_DEVICE_TYPE) in {ST_IGD_V1, ST_IGD_V2} return root_device_info.get(ATTR_UPNP_DEVICE_TYPE) in {ST_IGD_V1, ST_IGD_V2}
class UpnpFlowHandler(ConfigFlow, domain=DOMAIN): class UpnpFlowHandler(ConfigFlow, domain=DOMAIN):
@ -167,7 +172,7 @@ class UpnpFlowHandler(ConfigFlow, domain=DOMAIN):
) )
async def async_step_ssdp( async def async_step_ssdp(
self, discovery_info: ssdp.SsdpServiceInfo self, discovery_info: SsdpServiceInfo
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Handle a discovered UPnP/IGD device. """Handle a discovered UPnP/IGD device.

View File

@ -10,10 +10,14 @@ from aiohttp import ClientConnectorError
from aiomusiccast import MusicCastConnectionException, MusicCastDevice from aiomusiccast import MusicCastConnectionException, MusicCastDevice
import voluptuous as vol import voluptuous as vol
from homeassistant.components import ssdp
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_SERIAL,
SsdpServiceInfo,
)
from . import get_upnp_desc from . import get_upnp_desc
from .const import CONF_SERIAL, CONF_UPNP_DESC, DOMAIN from .const import CONF_SERIAL, CONF_UPNP_DESC, DOMAIN
@ -81,7 +85,7 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN):
) )
async def async_step_ssdp( async def async_step_ssdp(
self, discovery_info: ssdp.SsdpServiceInfo self, discovery_info: SsdpServiceInfo
) -> ConfigFlowResult: ) -> ConfigFlowResult:
"""Handle ssdp discoveries.""" """Handle ssdp discoveries."""
if not await MusicCastDevice.check_yamaha_ssdp( if not await MusicCastDevice.check_yamaha_ssdp(
@ -89,7 +93,7 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN):
): ):
return self.async_abort(reason="yxc_control_url_missing") return self.async_abort(reason="yxc_control_url_missing")
self.serial_number = discovery_info.upnp[ssdp.ATTR_UPNP_SERIAL] self.serial_number = discovery_info.upnp[ATTR_UPNP_SERIAL]
self.upnp_description = discovery_info.ssdp_location self.upnp_description = discovery_info.ssdp_location
# ssdp_location and hostname have been checked in check_yamaha_ssdp so it is safe to ignore type assignment # ssdp_location and hostname have been checked in check_yamaha_ssdp so it is safe to ignore type assignment
@ -105,9 +109,7 @@ class MusicCastFlowHandler(ConfigFlow, domain=DOMAIN):
self.context.update( self.context.update(
{ {
"title_placeholders": { "title_placeholders": {
"name": discovery_info.upnp.get( "name": discovery_info.upnp.get(ATTR_UPNP_FRIENDLY_NAME, self.host)
ssdp.ATTR_UPNP_FRIENDLY_NAME, self.host
)
} }
} }
) )

View File

@ -87,11 +87,11 @@ from .util.enum import try_parse_enum
if TYPE_CHECKING: if TYPE_CHECKING:
from .components.bluetooth import BluetoothServiceInfoBleak from .components.bluetooth import BluetoothServiceInfoBleak
from .components.ssdp import SsdpServiceInfo
from .components.usb import UsbServiceInfo from .components.usb import UsbServiceInfo
from .helpers.service_info.dhcp import DhcpServiceInfo from .helpers.service_info.dhcp import DhcpServiceInfo
from .helpers.service_info.hassio import HassioServiceInfo from .helpers.service_info.hassio import HassioServiceInfo
from .helpers.service_info.mqtt import MqttServiceInfo from .helpers.service_info.mqtt import MqttServiceInfo
from .helpers.service_info.ssdp import SsdpServiceInfo
from .helpers.service_info.zeroconf import ZeroconfServiceInfo from .helpers.service_info.zeroconf import ZeroconfServiceInfo

View File

@ -16,10 +16,10 @@ if TYPE_CHECKING:
import asyncio import asyncio
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
from homeassistant.components.ssdp import SsdpServiceInfo
from .service_info.dhcp import DhcpServiceInfo from .service_info.dhcp import DhcpServiceInfo
from .service_info.mqtt import MqttServiceInfo from .service_info.mqtt import MqttServiceInfo
from .service_info.ssdp import SsdpServiceInfo
from .service_info.zeroconf import ZeroconfServiceInfo from .service_info.zeroconf import ZeroconfServiceInfo
type DiscoveryFunctionType[_R] = Callable[[HomeAssistant], _R] type DiscoveryFunctionType[_R] = Callable[[HomeAssistant], _R]

View File

@ -0,0 +1,41 @@
"""SSDP discovery data."""
from collections.abc import Mapping
from dataclasses import dataclass, field
from typing import Any, Final
from homeassistant.data_entry_flow import BaseServiceInfo
# Attributes for accessing info from retrieved UPnP device description
ATTR_ST: Final = "st"
ATTR_NT: Final = "nt"
ATTR_UPNP_DEVICE_TYPE: Final = "deviceType"
ATTR_UPNP_FRIENDLY_NAME: Final = "friendlyName"
ATTR_UPNP_MANUFACTURER: Final = "manufacturer"
ATTR_UPNP_MANUFACTURER_URL: Final = "manufacturerURL"
ATTR_UPNP_MODEL_DESCRIPTION: Final = "modelDescription"
ATTR_UPNP_MODEL_NAME: Final = "modelName"
ATTR_UPNP_MODEL_NUMBER: Final = "modelNumber"
ATTR_UPNP_MODEL_URL: Final = "modelURL"
ATTR_UPNP_SERIAL: Final = "serialNumber"
ATTR_UPNP_SERVICE_LIST: Final = "serviceList"
ATTR_UPNP_UDN: Final = "UDN"
ATTR_UPNP_UPC: Final = "UPC"
ATTR_UPNP_PRESENTATION_URL: Final = "presentationURL"
@dataclass(slots=True)
class SsdpServiceInfo(BaseServiceInfo):
"""Prepared info from ssdp/upnp entries."""
ssdp_usn: str
ssdp_st: str
upnp: Mapping[str, Any]
ssdp_location: str | None = None
ssdp_nt: str | None = None
ssdp_udn: str | None = None
ssdp_ext: str | None = None
ssdp_server: str | None = None
ssdp_headers: Mapping[str, Any] = field(default_factory=dict)
ssdp_all_locations: set[str] = field(default_factory=set)
x_homeassistant_matching_domains: set[str] = field(default_factory=set)

View File

@ -20,12 +20,15 @@ from homeassistant.components.deconz.const import (
DOMAIN as DECONZ_DOMAIN, DOMAIN as DECONZ_DOMAIN,
HASSIO_CONFIGURATION_URL, HASSIO_CONFIGURATION_URL,
) )
from homeassistant.components.ssdp import ATTR_UPNP_MANUFACTURER_URL, ATTR_UPNP_SERIAL
from homeassistant.config_entries import SOURCE_HASSIO, SOURCE_SSDP, SOURCE_USER from homeassistant.config_entries import SOURCE_HASSIO, SOURCE_SSDP, SOURCE_USER
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT, CONTENT_TYPE_JSON
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.hassio import HassioServiceInfo from homeassistant.helpers.service_info.hassio import HassioServiceInfo
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_MANUFACTURER_URL,
ATTR_UPNP_SERIAL,
)
from .conftest import API_KEY, BRIDGE_ID from .conftest import API_KEY, BRIDGE_ID

View File

@ -9,15 +9,15 @@ from syrupy import SnapshotAssertion
from homeassistant.components import ssdp from homeassistant.components import ssdp
from homeassistant.components.deconz.config_flow import DECONZ_MANUFACTURERURL from homeassistant.components.deconz.config_flow import DECONZ_MANUFACTURERURL
from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN
from homeassistant.components.ssdp import (
ATTR_UPNP_MANUFACTURER_URL,
ATTR_UPNP_SERIAL,
ATTR_UPNP_UDN,
)
from homeassistant.config_entries import SOURCE_SSDP from homeassistant.config_entries import SOURCE_SSDP
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_MANUFACTURER_URL,
ATTR_UPNP_SERIAL,
ATTR_UPNP_UDN,
)
from .conftest import BRIDGE_ID from .conftest import BRIDGE_ID

View File

@ -6,11 +6,11 @@ from unittest.mock import patch
from aiohttp import ClientError as HTTPClientError from aiohttp import ClientError as HTTPClientError
from homeassistant.components.directv.const import CONF_RECEIVER_ID, DOMAIN from homeassistant.components.directv.const import CONF_RECEIVER_ID, DOMAIN
from homeassistant.components.ssdp import ATTR_UPNP_SERIAL
from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_SOURCE from homeassistant.const import CONF_HOST, CONF_NAME, CONF_SOURCE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.ssdp import ATTR_UPNP_SERIAL
from . import ( from . import (
HOST, HOST,

View File

@ -2,7 +2,6 @@
from homeassistant.components import ssdp from homeassistant.components import ssdp
from homeassistant.components.fritz.const import DOMAIN from homeassistant.components.fritz.const import DOMAIN
from homeassistant.components.ssdp import ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_UDN
from homeassistant.const import ( from homeassistant.const import (
CONF_DEVICES, CONF_DEVICES,
CONF_HOST, CONF_HOST,
@ -11,6 +10,10 @@ from homeassistant.const import (
CONF_SSL, CONF_SSL,
CONF_USERNAME, CONF_USERNAME,
) )
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_UDN,
)
ATTR_HOST = "host" ATTR_HOST = "host"
ATTR_NEW_SERIAL_NUMBER = "NewSerialNumber" ATTR_NEW_SERIAL_NUMBER = "NewSerialNumber"

View File

@ -11,11 +11,14 @@ from requests.exceptions import HTTPError
from homeassistant.components import ssdp from homeassistant.components import ssdp
from homeassistant.components.fritzbox.const import DOMAIN from homeassistant.components.fritzbox.const import DOMAIN
from homeassistant.components.ssdp import ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_UDN
from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER
from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_UDN,
)
from .const import CONF_FAKE_NAME, MOCK_CONFIG from .const import CONF_FAKE_NAME, MOCK_CONFIG

View File

@ -1,7 +1,10 @@
"""Tests for Kaleidescape integration.""" """Tests for Kaleidescape integration."""
from homeassistant.components import ssdp from homeassistant.components import ssdp
from homeassistant.components.ssdp import ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_SERIAL from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_SERIAL,
)
MOCK_HOST = "127.0.0.1" MOCK_HOST = "127.0.0.1"
MOCK_SERIAL = "123456" MOCK_SERIAL = "123456"

View File

@ -14,17 +14,17 @@ from demetriek import (
import pytest import pytest
from homeassistant.components.lametric.const import DOMAIN from homeassistant.components.lametric.const import DOMAIN
from homeassistant.components.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_SERIAL,
SsdpServiceInfo,
)
from homeassistant.config_entries import SOURCE_DHCP, SOURCE_SSDP, SOURCE_USER from homeassistant.config_entries import SOURCE_DHCP, SOURCE_SSDP, SOURCE_USER
from homeassistant.const import CONF_API_KEY, CONF_DEVICE, CONF_HOST, CONF_MAC from homeassistant.const import CONF_API_KEY, CONF_DEVICE, CONF_HOST, CONF_MAC
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_SERIAL,
SsdpServiceInfo,
)
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker

View File

@ -1,12 +1,15 @@
"""Tests for the Openhome config flow module.""" """Tests for the Openhome config flow module."""
from homeassistant.components import ssdp
from homeassistant.components.openhome.const import DOMAIN from homeassistant.components.openhome.const import DOMAIN
from homeassistant.components.ssdp import ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_UDN
from homeassistant.config_entries import SOURCE_SSDP from homeassistant.config_entries import SOURCE_SSDP
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_SOURCE from homeassistant.const import CONF_HOST, CONF_NAME, CONF_SOURCE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_UDN,
SsdpServiceInfo,
)
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -14,7 +17,7 @@ MOCK_UDN = "uuid:4c494e4e-1234-ab12-abcd-01234567819f"
MOCK_FRIENDLY_NAME = "Test Client" MOCK_FRIENDLY_NAME = "Test Client"
MOCK_SSDP_LOCATION = "http://device:12345/description.xml" MOCK_SSDP_LOCATION = "http://device:12345/description.xml"
MOCK_DISCOVER = ssdp.SsdpServiceInfo( MOCK_DISCOVER = SsdpServiceInfo(
ssdp_usn="usn", ssdp_usn="usn",
ssdp_st="st", ssdp_st="st",
ssdp_location=MOCK_SSDP_LOCATION, ssdp_location=MOCK_SSDP_LOCATION,
@ -60,7 +63,7 @@ async def test_device_exists(hass: HomeAssistant) -> None:
async def test_missing_udn(hass: HomeAssistant) -> None: async def test_missing_udn(hass: HomeAssistant) -> None:
"""Test a ssdp import where discovery is missing udn.""" """Test a ssdp import where discovery is missing udn."""
broken_discovery = ssdp.SsdpServiceInfo( broken_discovery = SsdpServiceInfo(
ssdp_usn="usn", ssdp_usn="usn",
ssdp_st="st", ssdp_st="st",
ssdp_location=MOCK_SSDP_LOCATION, ssdp_location=MOCK_SSDP_LOCATION,
@ -79,7 +82,7 @@ async def test_missing_udn(hass: HomeAssistant) -> None:
async def test_missing_ssdp_location(hass: HomeAssistant) -> None: async def test_missing_ssdp_location(hass: HomeAssistant) -> None:
"""Test a ssdp import where discovery is missing udn.""" """Test a ssdp import where discovery is missing udn."""
broken_discovery = ssdp.SsdpServiceInfo( broken_discovery = SsdpServiceInfo(
ssdp_usn="usn", ssdp_usn="usn",
ssdp_st="st", ssdp_st="st",
ssdp_location="", ssdp_location="",

View File

@ -3,7 +3,10 @@
from ipaddress import ip_address from ipaddress import ip_address
from homeassistant.components import ssdp, zeroconf from homeassistant.components import ssdp, zeroconf
from homeassistant.components.ssdp import ATTR_UPNP_FRIENDLY_NAME, ATTR_UPNP_SERIAL from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_SERIAL,
)
NAME = "Roku 3" NAME = "Roku 3"
NAME_ROKUTV = '58" Onn Roku TV' NAME_ROKUTV = '58" Onn Roku TV'

View File

@ -8,12 +8,6 @@ from homeassistant.components.samsungtv.const import (
METHOD_LEGACY, METHOD_LEGACY,
METHOD_WEBSOCKET, METHOD_WEBSOCKET,
) )
from homeassistant.components.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_UDN,
)
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_IP_ADDRESS, CONF_IP_ADDRESS,
@ -24,6 +18,12 @@ from homeassistant.const import (
CONF_PORT, CONF_PORT,
CONF_TOKEN, CONF_TOKEN,
) )
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_UDN,
)
MOCK_CONFIG = { MOCK_CONFIG = {
CONF_HOST: "fake_host", CONF_HOST: "fake_host",

View File

@ -33,13 +33,6 @@ from homeassistant.components.samsungtv.const import (
TIMEOUT_REQUEST, TIMEOUT_REQUEST,
TIMEOUT_WEBSOCKET, TIMEOUT_WEBSOCKET,
) )
from homeassistant.components.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_UDN,
SsdpServiceInfo,
)
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
@ -54,6 +47,13 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import BaseServiceInfo, FlowResultType from homeassistant.data_entry_flow import BaseServiceInfo, FlowResultType
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_UDN,
SsdpServiceInfo,
)
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from .const import ( from .const import (

View File

@ -2,6 +2,7 @@
from datetime import datetime from datetime import datetime
from ipaddress import IPv4Address from ipaddress import IPv4Address
from typing import Any
from unittest.mock import ANY, AsyncMock, patch from unittest.mock import ANY, AsyncMock, patch
from async_upnp_client.server import UpnpServer from async_upnp_client.server import UpnpServer
@ -19,6 +20,24 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.discovery_flow import DiscoveryKey from homeassistant.helpers.discovery_flow import DiscoveryKey
from homeassistant.helpers.service_info.ssdp import (
ATTR_NT,
ATTR_ST,
ATTR_UPNP_DEVICE_TYPE,
ATTR_UPNP_FRIENDLY_NAME,
ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MANUFACTURER_URL,
ATTR_UPNP_MODEL_DESCRIPTION,
ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_MODEL_NUMBER,
ATTR_UPNP_MODEL_URL,
ATTR_UPNP_PRESENTATION_URL,
ATTR_UPNP_SERIAL,
ATTR_UPNP_SERVICE_LIST,
ATTR_UPNP_UDN,
ATTR_UPNP_UPC,
SsdpServiceInfo,
)
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -26,6 +45,7 @@ from tests.common import (
MockConfigEntry, MockConfigEntry,
MockModule, MockModule,
async_fire_time_changed, async_fire_time_changed,
import_and_test_deprecated_constant,
mock_integration, mock_integration,
) )
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
@ -1094,3 +1114,105 @@ async def test_ssdp_rediscover_no_match(
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(mock_flow_init.mock_calls) == 1 assert len(mock_flow_init.mock_calls) == 1
@pytest.mark.parametrize(
("constant_name", "replacement_name", "replacement"),
[
(
"SsdpServiceInfo",
"homeassistant.helpers.service_info.ssdp.SsdpServiceInfo",
SsdpServiceInfo,
),
(
"ATTR_ST",
"homeassistant.helpers.service_info.ssdp.ATTR_ST",
ATTR_ST,
),
(
"ATTR_NT",
"homeassistant.helpers.service_info.ssdp.ATTR_NT",
ATTR_NT,
),
(
"ATTR_UPNP_DEVICE_TYPE",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_DEVICE_TYPE",
ATTR_UPNP_DEVICE_TYPE,
),
(
"ATTR_UPNP_FRIENDLY_NAME",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_FRIENDLY_NAME",
ATTR_UPNP_FRIENDLY_NAME,
),
(
"ATTR_UPNP_MANUFACTURER",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MANUFACTURER",
ATTR_UPNP_MANUFACTURER,
),
(
"ATTR_UPNP_MANUFACTURER_URL",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MANUFACTURER_URL",
ATTR_UPNP_MANUFACTURER_URL,
),
(
"ATTR_UPNP_MODEL_DESCRIPTION",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MODEL_DESCRIPTION",
ATTR_UPNP_MODEL_DESCRIPTION,
),
(
"ATTR_UPNP_MODEL_NAME",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MODEL_NAME",
ATTR_UPNP_MODEL_NAME,
),
(
"ATTR_UPNP_MODEL_NUMBER",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MODEL_NUMBER",
ATTR_UPNP_MODEL_NUMBER,
),
(
"ATTR_UPNP_MODEL_URL",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_MODEL_URL",
ATTR_UPNP_MODEL_URL,
),
(
"ATTR_UPNP_SERIAL",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_SERIAL",
ATTR_UPNP_SERIAL,
),
(
"ATTR_UPNP_SERVICE_LIST",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_SERVICE_LIST",
ATTR_UPNP_SERVICE_LIST,
),
(
"ATTR_UPNP_UDN",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_UDN",
ATTR_UPNP_UDN,
),
(
"ATTR_UPNP_UPC",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_UPC",
ATTR_UPNP_UPC,
),
(
"ATTR_UPNP_PRESENTATION_URL",
"homeassistant.helpers.service_info.ssdp.ATTR_UPNP_PRESENTATION_URL",
ATTR_UPNP_PRESENTATION_URL,
),
],
)
def test_deprecated_constants(
caplog: pytest.LogCaptureFixture,
constant_name: str,
replacement_name: str,
replacement: Any,
) -> None:
"""Test deprecated automation constants."""
import_and_test_deprecated_constant(
caplog,
ssdp,
constant_name,
replacement_name,
replacement,
"2026.2",
)

View File

@ -3,18 +3,18 @@
from pywilight.const import DOMAIN from pywilight.const import DOMAIN
from homeassistant.components import ssdp from homeassistant.components import ssdp
from homeassistant.components.ssdp import (
ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_MODEL_NUMBER,
ATTR_UPNP_SERIAL,
)
from homeassistant.components.wilight.config_flow import ( from homeassistant.components.wilight.config_flow import (
CONF_MODEL_NAME, CONF_MODEL_NAME,
CONF_SERIAL_NUMBER, CONF_SERIAL_NUMBER,
) )
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_MANUFACTURER,
ATTR_UPNP_MODEL_NAME,
ATTR_UPNP_MODEL_NUMBER,
ATTR_UPNP_SERIAL,
)
from tests.common import MockConfigEntry from tests.common import MockConfigEntry

View File

@ -22,7 +22,6 @@ import zigpy.types
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import ssdp, usb, zeroconf from homeassistant.components import ssdp, usb, zeroconf
from homeassistant.components.hassio import AddonError, AddonState from homeassistant.components.hassio import AddonError, AddonState
from homeassistant.components.ssdp import ATTR_UPNP_MANUFACTURER_URL, ATTR_UPNP_SERIAL
from homeassistant.components.zha import config_flow, radio_manager from homeassistant.components.zha import config_flow, radio_manager
from homeassistant.components.zha.const import ( from homeassistant.components.zha.const import (
CONF_BAUDRATE, CONF_BAUDRATE,
@ -43,6 +42,10 @@ from homeassistant.config_entries import (
from homeassistant.const import CONF_SOURCE from homeassistant.const import CONF_SOURCE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.ssdp import (
ATTR_UPNP_MANUFACTURER_URL,
ATTR_UPNP_SERIAL,
)
from tests.common import MockConfigEntry from tests.common import MockConfigEntry