Ejemplo n.º 1
0
async def resolve_ip_address_getaddrinfo(
        eventloop: asyncio.events.AbstractEventLoop, host: str,
        port: int) -> Tuple[Any, ...]:

    try:
        socket.inet_aton(host)
    except OSError:
        pass
    else:
        return (host, port)

    try:
        res = await eventloop.getaddrinfo(host,
                                          port,
                                          family=socket.AF_INET,
                                          proto=socket.IPPROTO_TCP)
    except OSError as err:
        raise APIConnectionError("Error resolving IP address: {}".format(err))

    if not res:
        raise APIConnectionError("Error resolving IP address: No matches!")

    _, _, _, _, sockaddr = res[0]

    return sockaddr
Ejemplo n.º 2
0
    async def send_message_await_response_complex(self, send_msg: message.Message,
                                                  do_append: Callable[[Any], bool],
                                                  do_stop: Callable[[Any], bool],
                                                  timeout: float = 5.0) -> List[Any]:
        fut = self._params.eventloop.create_future()
        responses = []

        def on_message(resp):
            if fut.done():
                return
            if do_append(resp):
                responses.append(resp)
            if do_stop(resp):
                fut.set_result(responses)

        self._message_handlers.append(on_message)
        await self.send_message(send_msg)

        try:
            await asyncio.wait_for(fut, timeout)
        except asyncio.TimeoutError:
            if self._stopped:
                raise APIConnectionError(
                    "Disconnected while waiting for API response!")
            raise APIConnectionError("Timeout while waiting for API response!")

        try:
            self._message_handlers.remove(on_message)
        except ValueError:
            pass

        return responses
Ejemplo n.º 3
0
    async def _run_once(self) -> None:
        preamble = await self._recv(1)
        if preamble[0] != 0x00:
            raise APIConnectionError("Invalid preamble")

        length = await self._recv_varint()
        msg_type = await self._recv_varint()

        raw_msg = await self._recv(length)
        if msg_type not in MESSAGE_TYPE_TO_PROTO:
            _LOGGER.debug("%s: Skipping message type %s", self._params.address,
                          msg_type)
            return

        msg = MESSAGE_TYPE_TO_PROTO[msg_type]()
        try:
            msg.ParseFromString(raw_msg)
        except Exception as e:
            raise APIConnectionError("Invalid protobuf message: {}".format(e))
        _LOGGER.debug("%s: Got message of type %s: %s", self._params.address,
                      type(msg), msg)
        for msg_handler in self._message_handlers[:]:
            msg_handler(msg)
        await self._handle_internal_messages(msg)
        self._last_traffic = datetime.now()
Ejemplo n.º 4
0
    async def connect(self, on_stop=None, login=False):
        if self._connection is not None:
            raise APIConnectionError("Already connected!")

        connected = False
        stopped = False

        async def _on_stop():
            nonlocal stopped

            if stopped:
                return
            stopped = True
            self._connection = None
            if connected and on_stop is not None:
                await on_stop()

        self._connection = APIConnection(self._params, _on_stop)

        try:
            await self._connection.connect()
            if login:
                await self._connection.login()
        except APIConnectionError:
            await _on_stop()
            raise
        except Exception as e:
            await _on_stop()
            raise APIConnectionError(
                "Unexpected error while connecting: {}".format(e))

        connected = True
Ejemplo n.º 5
0
def resolve_host(
    host: str,
    timeout: float = 3.0,
    zeroconf_instance: Optional[zeroconf.Zeroconf] = None,
) -> str:
    from aioesphomeapi.core import APIConnectionError

    try:
        zc = zeroconf_instance or zeroconf.Zeroconf()
    except Exception:
        raise APIConnectionError(
            "Cannot start mDNS sockets, is this a docker container without "
            "host network mode?"
        )

    try:
        info = HostResolver(host + ".")
        assert info.address is not None
        address = None
        if info.request(zc, timeout):
            address = socket.inet_ntoa(info.address)
    except Exception as err:
        raise APIConnectionError(
            "Error resolving mDNS hostname: {}".format(err)
        ) from err
    finally:
        if not zeroconf_instance:
            zc.close()

    if address is None:
        raise APIConnectionError(
            "Error resolving address with mDNS: Did not respond. "
            "Maybe the device is offline."
        )
    return address
Ejemplo n.º 6
0
 async def _write(self, data: bytes) -> None:
     # _LOGGER.debug("%s: Write: %s", self._params.address,
     #               ' '.join('{:02X}'.format(x) for x in data))
     if not self._socket_connected:
         raise APIConnectionError("Socket is not connected")
     try:
         async with self._write_lock:
             self._socket_writer.write(data)
             await self._socket_writer.drain()
     except OSError as err:
         await self._on_error()
         raise APIConnectionError(
             "Error while writing data: {}".format(err))
Ejemplo n.º 7
0
    async def login(self) -> None:
        self._check_connected()
        if self._authenticated:
            raise APIConnectionError("Already logged in!")

        connect = pb.ConnectRequest()
        if self._params.password is not None:
            connect.password = self._params.password
        resp = await self.send_message_await_response(connect, pb.ConnectResponse)
        if resp.invalid_password:
            raise APIConnectionError("Invalid password!")

        self._authenticated = True
Ejemplo n.º 8
0
    async def _recv(self, amount: int) -> bytes:
        if amount == 0:
            return bytes()

        try:
            ret = await self._socket_reader.readexactly(amount)
        except (asyncio.IncompleteReadError, OSError, TimeoutError) as err:
            raise APIConnectionError(
                "Error while receiving data: {}".format(err))

        return ret
Ejemplo n.º 9
0
    async def connect(self) -> None:
        if self._stopped:
            raise APIConnectionError("Connection is closed!")
        if self._connected:
            raise APIConnectionError("Already connected!")

        try:
            coro = resolve_ip_address(self._params.eventloop, self._params.address,
                                      self._params.port, self._params.zeroconf_instance)
            sockaddr = await asyncio.wait_for(coro, 30.0)
        except APIConnectionError as err:
            await self._on_error()
            raise err
        except asyncio.TimeoutError:
            await self._on_error()
            raise APIConnectionError("Timeout while resolving IP address")

        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._socket.setblocking(False)
        self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

        _LOGGER.debug("%s: Connecting to %s:%s (%s)", self._params.address,
                      self._params.address, self._params.port, sockaddr)
        try:
            coro = self._params.eventloop.sock_connect(self._socket, sockaddr)
            await asyncio.wait_for(coro, 30.0)
        except OSError as err:
            await self._on_error()
            raise APIConnectionError(
                "Error connecting to {}: {}".format(sockaddr, err))
        except asyncio.TimeoutError:
            await self._on_error()
            raise APIConnectionError(
                "Timeout while connecting to {}".format(sockaddr))

        _LOGGER.debug("%s: Opened socket for", self._params.address)
        self._socket_reader, self._socket_writer = await asyncio.open_connection(sock=self._socket)
        self._socket_connected = True
        self._params.eventloop.create_task(self.run_forever())

        hello = pb.HelloRequest()
        hello.client_info = self._params.client_info
        try:
            resp = await self.send_message_await_response(hello, pb.HelloResponse)
        except APIConnectionError as err:
            await self._on_error()
            raise err
        _LOGGER.debug("%s: Successfully connected ('%s' API=%s.%s)",
                      self._params.address, resp.server_info, resp.api_version_major,
                      resp.api_version_minor)
        self._api_version = APIVersion(
            resp.api_version_major, resp.api_version_minor)
        if self._api_version.major > 2:
            _LOGGER.error("%s: Incompatible version %s! Closing connection",
                          self._params.address, self._api_version.major)
            await self._on_error()
            raise APIConnectionError("Incompatible API version.")
        self._connected = True

        self._start_ping()
Ejemplo n.º 10
0
    async def send_message_await_response(self,
                                          send_msg: message.Message,
                                          response_type: Any, timeout: float = 5.0) -> Any:
        def is_response(msg):
            return isinstance(msg, response_type)

        res = await self.send_message_await_response_complex(
            send_msg, is_response, is_response, timeout=timeout)
        if len(res) != 1:
            raise APIConnectionError(
                "Expected one result, got {}".format(len(res)))

        return res[0]
Ejemplo n.º 11
0
 def _check_authenticated(self) -> None:
     if not self._authenticated:
         raise APIConnectionError("Must login first!")
Ejemplo n.º 12
0
 def _check_connected(self) -> None:
     if not self._connected:
         raise APIConnectionError("Must be connected!")
Ejemplo n.º 13
0
 def _check_authenticated(self) -> None:
     self._check_connected()
     assert self._connection is not None
     if not self._connection.is_authenticated:
         raise APIConnectionError("Not authenticated!")
Ejemplo n.º 14
0
 def _check_connected(self) -> None:
     if self._connection is None:
         raise APIConnectionError("Not connected!")
     if not self._connection.is_connected:
         raise APIConnectionError("Connection not done!")
Ejemplo n.º 15
0
 def _check_authenticated(self):
     self._check_connected()
     if not self._connection.is_authenticated:
         raise APIConnectionError("Not authenticated!")