SSDP / UPnP Discovery¶
SSDPDiscoveryService¶
Available as self.dependencies.ssdp_discovery_service.
register¶
def register(
self,
listener: SSDPDiscoveryListener,
search_target: str,
mcast_group: str = "239.255.255.250",
port: int = 1900,
) -> Callable[[], None]: ...
| Arg | Type | Default | Description |
|---|---|---|---|
listener |
SSDPDiscoveryListener |
required | Receives discovery events. |
search_target |
str |
required | SSDP ST field. See common templates below. |
mcast_group |
str |
"239.255.255.250" |
Multicast group. Change only if necessary. |
port |
int |
1900 |
SSDP port. Change only if necessary. |
Returns a cancel closure — call it in stop() to deregister.
Common search_target values:
| ST | Discovers |
|---|---|
"upnp:rootdevice" |
All UPnP root devices |
"ssdp:all" |
All UPnP devices and services |
"urn:schemas-upnp-org:device:MediaServer:1" |
UPnP MediaServer devices |
"uuid:<device-UUID>" |
A specific device by UUID |
SSDPDiscoveryListener (Protocol)¶
class SSDPDiscoveryListener(Protocol):
async def ssdp_did_discover_service(self, ssdp: SSDPDiscoveryService, info: SSDPDiscoveryInfo): ...
async def ssdp_did_update_service(self, ssdp: SSDPDiscoveryService, info: SSDPDiscoveryInfo): ...
async def ssdp_did_remove_service(self, ssdp: SSDPDiscoveryService, info: SSDPDiscoveryInfo): ...
ssdp_did_remove_service fires when a device sends a ssdp:byebye NOTIFY, or when a device fails to respond to several consecutive M-SEARCH bursts (controlled by _missed_scans_evict, default 3 missed scans).
SSDPDiscoveryInfo¶
@dataclass
class SSDPDiscoveryInfo:
addr: str
host: str | None
search_target: str | None
service_name: str | None # USN (Unique Service Name)
server: str | None
cache_control: str | None
location: str | None # URL to the device description XML
response: dict[str, str] # full raw response headers
Example¶
# in Controller
...
async def start(self):
self._cancels: set[Callable[[], None]] = set()
cancel = self.dependencies.ssdp_discovery_service.register(
self, # self implements SSDPDiscoveryListener
"urn:schemas-upnp-org:device:MediaServer:1",
)
self._cancels.add(cancel)
async def stop(self):
for cancel in self._cancels:
cancel()
# SSDPDiscoveryListener Implementation
async def ssdp_did_discover_service(self, ssdp: SSDPDiscoveryService, info: SSDPDiscoveryInfo):
if discovery := self._mapper.map_to_discovery(info):
self._discoveries[discovery.id] = discovery
await self.dependencies.output.controller_did_receive_discovery(self, discovery)
async def ssdp_did_update_service(self, ssdp: SSDPDiscoveryService, info: SSDPDiscoveryInfo):
if discovery := self._mapper.map_to_discovery(info):
self._discoveries[discovery.id] = discovery
await self.dependencies.output.controller_did_update_discovery(self, discovery)
async def ssdp_did_remove_service(self, ssdp: SSDPDiscoveryService, info: SSDPDiscoveryInfo):
discovery_id = self.device_uuid(info.service_name or info.addr)
if discovery_id in self._discoveries:
self._discoveries.pop(discovery_id)
await self.dependencies.output.controller_did_lose_discovery(self, discovery_id)
...