Source code for gatenet.discovery.mdns
from typing import List, Dict, Any
import time
from zeroconf import Zeroconf, ServiceBrowser, ServiceListener
[docs]
class MDNSListener(ServiceListener):
"""
Listener for mDNS service discovery.
"""
def __init__(self) -> None:
"""
Initialize the mDNS listener.
"""
self.services: List[Dict[str, str]] = []
[docs]
def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
"""
Called when a service is discovered.
Parameters
----------
zc : Zeroconf
Zeroconf instance.
type_ : str
Service type.
name : str
Service name.
"""
try:
info = zc.get_service_info(type_, name)
if not info:
return
service_data = {
"name": name,
"type": type_,
"address": str(info.parsed_addresses()[0]) if info.parsed_addresses() else "unknown",
"port": str(info.port),
"server": info.server if info.server else "unknown"
}
if info.properties:
service_data["properties"] = str(self._decode_properties(info.properties))
self.services.append(service_data)
except Exception as e:
import logging
logging.error(f"Error processing service {name}: {e}")
def _decode_properties(self, properties: Any) -> Dict[str, str]:
"""
Helper to decode service properties.
Parameters
----------
properties : Any
Properties dictionary from Zeroconf.
Returns
-------
Dict[str, str]
Decoded properties as a dictionary.
"""
decoded = {}
for key, value in properties.items():
try:
key_str = key.decode('utf-8')
if value is None:
decoded[key_str] = ""
elif isinstance(value, bytes):
decoded[key_str] = value.decode('utf-8')
else:
decoded[key_str] = str(value)
except (UnicodeDecodeError, AttributeError):
continue
return decoded
[docs]
def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:
"""
Called when a service is removed.
Parameters
----------
zc : Zeroconf
Zeroconf instance.
type_ : str
Service type.
name : str
Service name.
"""
pass
[docs]
def update_service(self, zc: Zeroconf, type_: str, name: str) -> None:
"""
Called when a service is updated.
Parameters
----------
zc : Zeroconf
Zeroconf instance.
type_ : str
Service type.
name : str
Service name.
"""
pass
[docs]
def discover_mdns_services(timeout: float = 2.0) -> List[Dict[str, str]]:
"""
Discover mDNS / Bonjour services on the local network.
Parameters
----------
timeout : float, optional
Time in seconds to wait for service discovery (default is 2.0).
Returns
-------
List[Dict[str, str]]
List of discovered service dictionaries.
Example
-------
>>> from gatenet.discovery.mdns import discover_mdns_services
>>> services = discover_mdns_services(service_type="_http._tcp.local.", timeout=1.0)
>>> for svc in services:
... print(svc)
{'name': 'My Service', 'address': '192.168.1.10', 'port': 8080, ...}
"""
import logging
zeroconf = None
try:
zeroconf = Zeroconf()
listener = MDNSListener()
_ = ServiceBrowser(zeroconf, "_services._dns-sd._udp.local.", listener)
time.sleep(timeout) # Allow time for discovery
return listener.services
except Exception as e:
logging.error(f"Error during mDNS discovery: {e}")
return [{"error": str(e)}]
finally:
if zeroconf:
zeroconf.close()