async def discover_single(host: str) -> SmartDevice: """Discover a single device by the given IP address. :param host: Hostname of device to query :rtype: SmartDevice :return: Object for querying/controlling found device. """ protocol = TPLinkSmartHomeProtocol(host) info = await protocol.query(Discover.DISCOVERY_QUERY) device_class = Discover._get_device_class(info) dev = device_class(host) await dev.update() return dev
def datagram_received(self, data, addr) -> None: """Handle discovery responses.""" ip, port = addr if ip in self.discovered_devices: return if port == 9999: info = json.loads(TPLinkSmartHomeProtocol.decrypt(data)) device_class = Discover._get_device_class(info) device = device_class(ip) else: info = json.loads(data[16:]) device_class = Discover._get_new_device_class(info) owner = Discover._get_new_owner(info) if owner is not None: owner_bin = bytes.fromhex(owner) _LOGGER.debug( "[DISCOVERY] Device owner is %s, empty owner is %s", owner_bin, self.emptyUser, ) if owner is None or owner == "" or owner_bin == self.emptyUser: _LOGGER.debug("[DISCOVERY] Device %s has no owner", ip) device = device_class(ip, Auth()) elif (self.authentication is not None and owner_bin == self.authentication.owner()): _LOGGER.debug("[DISCOVERY] Device %s has authenticated owner", ip) device = device_class(ip, self.authentication) else: _LOGGER.debug("[DISCOVERY] Found %s with unknown owner %s", ip, owner) return _LOGGER.debug("[DISCOVERY] %s << %s", ip, info) asyncio.ensure_future(device.update()) self.discovered_devices[ip] = device self.discovered_devices_raw[ip] = info if device_class is not None: if self.on_discovered is not None: asyncio.ensure_future(self.on_discovered(device)) else: _LOGGER.error("Received invalid response: %s", info)
async def discover_single(host: str) -> SmartDevice: """Discover a single device by the given IP address. :param host: Hostname of device to query :rtype: SmartDevice :return: Object for querying/controlling found device. """ protocol = TPLinkSmartHomeProtocol() info = await protocol.query(host, Discover.DISCOVERY_QUERY) device_class = Discover._get_device_class(info) if device_class is not None: return device_class(host) raise SmartDeviceException("Unable to discover device, received: %s" % info)
def __init__(self, host: str, *, cache_ttl: int = 3) -> None: """Create a new SmartDevice instance. :param str host: host name or ip address on which the device listens :param child_id: optional child ID for context in a parent device """ self.host = host self.protocol = TPLinkSmartHomeProtocol() self.emeter_type = "emeter" self.cache_ttl = timedelta(seconds=cache_ttl) _LOGGER.debug("Initializing %s with cache ttl %s", self.host, self.cache_ttl) self.cache = defaultdict( lambda: defaultdict(lambda: None)) # type: ignore self._device_type = DeviceType.Unknown self._sys_info: Optional[Dict] = None
def read_payloads_from_file(file): """Read the given pcap file and yield json payloads.""" pcap = dpkt.pcap.Reader(file) for ts, pkt in pcap: eth = Ethernet(pkt) if eth.type != ETH_TYPE_IP: continue ip = eth.ip if ip.p == 6: transport = ip.tcp elif ip == 17: transport = ip.udp else: continue if transport.sport != 9999 and transport.dport != 9999: continue data = transport.data try: decrypted = TPLinkSmartHomeProtocol.decrypt(data[4:]) except Exception as ex: click.echo( click.style(f"Unable to decrypt the data, ignoring: {ex}", fg="red") ) continue try: json_payload = json.loads(decrypted) except Exception as ex: click.echo( click.style(f"Unable to parse payload, ignoring: {ex}", fg="red") ) continue if not json_payload: # ignore empty payloads click.echo(click.style("Got empty payload, ignoring", fg="red")) continue yield json_payload
def datagram_received(self, data, addr) -> None: """Handle discovery responses.""" ip, port = addr if ip in self.discovered_devices: return info = json.loads(TPLinkSmartHomeProtocol.decrypt(data)) _LOGGER.debug("[DISCOVERY] %s << %s", ip, info) try: device = Discover._create_device_from_discovery_info(ip, info) except SmartDeviceException as ex: _LOGGER.debug("Unable to find device type from %s: %s", info, ex) return self.discovered_devices[ip] = device if device is not None: if self.on_discovered is not None: asyncio.ensure_future(self.on_discovered(device)) else: _LOGGER.error("Received invalid response: %s", info)
async def discover_single(host: str, complete: bool) -> SmartDevice: """Discover a single device by the given IP address. :param host: Hostname of device to query :param complete: Whether to discover only with get_sysinfo or all options :rtype: SmartDevice :return: Object for querying/controlling found device. """ protocol = TPLinkSmartHomeProtocol() if complete: info = await protocol.query(host, Discover.COMPLETE_DISCOVERY_QUERY) else: info = await protocol.query(host, Discover.DISCOVERY_QUERY) device_class = Discover._get_device_class(info) if device_class is not None: dev = device_class(host) await dev.update() return dev raise SmartDeviceException("Unable to discover device, received: %s" % info)
class _DiscoverProtocol(asyncio.DatagramProtocol): """Implementation of the discovery protocol handler. This is internal class, use :func:Discover.discover: instead. """ 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, ): self.transport = None self.tries = discovery_packets self.timeout = timeout 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) self.do_discover() def do_discover(self) -> None: """Send number of discovery datagrams.""" req = json.dumps(Discover.DISCOVERY_QUERY) _LOGGER.debug("[DISCOVERY] %s >> %s", self.target, Discover.DISCOVERY_QUERY) encrypted_req = self.protocol.encrypt(req) for i in range(self.tries): self.transport.sendto(encrypted_req[4:], self.target) # type: ignore def datagram_received(self, data, addr) -> None: """Handle discovery responses.""" ip, port = addr if ip in self.discovered_devices: return info = json.loads(self.protocol.decrypt(data)) _LOGGER.debug("[DISCOVERY] %s << %s", ip, info) device_class = Discover._get_device_class(info) device = device_class(ip) self.discovered_devices[ip] = device self.discovered_devices_raw[ip] = info if device_class is not None: if self.on_discovered is not None: asyncio.ensure_future(self.on_discovered(device)) else: _LOGGER.error("Received invalid response: %s", info) def error_received(self, ex): """Handle asyncio.Protocol errors.""" _LOGGER.error("Got error: %s", ex) def connection_lost(self, ex): """NOP implementation of connection lost."""