Skip to content

Discover.discover() add selecting network interface [pull request] #78

@dmitryelj

Description

@dmitryelj

Hi, thanks for the cool product.

On my dd-wrt router, the broadcast socket does not work without specifying the network interface name. Please add to the code possibility to specify the network interface name during the discovery process.

I am too lazy to create a full pull-request, so, just a code:

async def discover(*, target="255.255.255.255",
                      on_discovered=None,
                      timeout=5,
                      discovery_packets=3,
                      return_raw=False,
                      interface=None) -> Mapping[str, Union[SmartDevice, Dict]]:
    loop = asyncio.get_event_loop()
    transport, protocol = await loop.create_datagram_endpoint(
        lambda: __DiscoverProtocol(
            target=target,
            on_discovered=on_discovered,
            timeout=timeout,
            discovery_packets=discovery_packets,
            interface=interface
        ),
        local_addr=("0.0.0.0", 0),
    )
    protocol = cast(__DiscoverProtocol, protocol)

    try:
        await asyncio.sleep(5)
    finally:
        transport.close()

    if return_raw:
        return protocol.discovered_devices_raw

    return protocol.discovered_devices


class _DiscoverProtocol(asyncio.DatagramProtocol):
    discovered_devices: Dict[str, SmartDevice]
    discovered_devices_raw: Dict[str, Dict]

    def __init__(
        self,
        *,
        on_discovered: OnDiscoveredCallable = None,
        target: str = "255.255.255.255",
        timeout: int = 5,
        discovery_packets: int = 3,
        interface: bytes = b""
    ):
        self.transport = None
        self.tries = discovery_packets
        self.timeout = timeout
        self.interface = interface
        self.on_discovered = on_discovered
        self.protocol = TPLinkSmartHomeProtocol()
        self.target = (target, Discover.DISCOVERY_PORT)
        self.discovered_devices = {}
        self.discovered_devices_raw = {}

    def connection_made(self, transport) -> None:
        """Set socket options for broadcasting."""
        self.transport = transport
        sock = transport.get_extra_info("socket")
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        if self.interface is not None and len(self.interface) > 0:
            sock.setsockopt(socket.SOL_SOCKET, 25, self.interface)

        self.do_discover()

Usage on my dd-wrt router:

devices = asyncio.run(Discover.discover(interface=b"br0"))

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions