60 lines
2.0 KiB
Python
60 lines
2.0 KiB
Python
|
|
"""Service discovery on the local network (DNS-SD).
|
||
|
|
|
||
|
|
browse(service_type=None, duration=3.0) -> list of dicts
|
||
|
|
"""
|
||
|
|
import time
|
||
|
|
|
||
|
|
from zeroconf import Zeroconf, ServiceBrowser, ServiceListener, ZeroconfServiceTypes
|
||
|
|
|
||
|
|
|
||
|
|
class _Collector(ServiceListener):
|
||
|
|
def __init__(self) -> None:
|
||
|
|
self.items: dict[str, dict] = {}
|
||
|
|
|
||
|
|
def add_service(self, zc: Zeroconf, type_: str, name: str) -> None: # noqa: N802
|
||
|
|
info = zc.get_service_info(type_, name, timeout=1500)
|
||
|
|
if info is None:
|
||
|
|
return
|
||
|
|
addrs = [a for a in (info.parsed_addresses() or [])]
|
||
|
|
self.items[name] = {
|
||
|
|
"name": name,
|
||
|
|
"type": type_,
|
||
|
|
"server": (info.server or "").rstrip("."),
|
||
|
|
"port": info.port,
|
||
|
|
"addresses": addrs,
|
||
|
|
"txt": {
|
||
|
|
k.decode(errors="replace"):
|
||
|
|
(v.decode(errors="replace") if isinstance(v, (bytes, bytearray)) else v)
|
||
|
|
for k, v in (info.properties or {}).items() if k
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
def update_service(self, *a, **k) -> None: # noqa: N802
|
||
|
|
return
|
||
|
|
|
||
|
|
def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None: # noqa: N802
|
||
|
|
self.items.pop(name, None)
|
||
|
|
|
||
|
|
|
||
|
|
def list_types(duration: float = 3.0) -> list[str]:
|
||
|
|
"""Return all service types advertised on the LAN."""
|
||
|
|
return sorted(ZeroconfServiceTypes.find(timeout=duration))
|
||
|
|
|
||
|
|
|
||
|
|
def browse(service_type: str | None = None, duration: float = 3.0) -> list[dict]:
|
||
|
|
"""Discover services. If service_type is None, discover all types first."""
|
||
|
|
zc = Zeroconf()
|
||
|
|
try:
|
||
|
|
types = [service_type] if service_type else list_types(duration=duration / 2)
|
||
|
|
if not types:
|
||
|
|
return []
|
||
|
|
listener = _Collector()
|
||
|
|
browsers = [ServiceBrowser(zc, t, listener) for t in types]
|
||
|
|
time.sleep(duration if not service_type else duration)
|
||
|
|
for b in browsers:
|
||
|
|
b.cancel()
|
||
|
|
return sorted(listener.items.values(),
|
||
|
|
key=lambda x: (x.get("type", ""), x.get("name", "")))
|
||
|
|
finally:
|
||
|
|
zc.close()
|