Example #1
0
    async def __open(
        self,
        url: Text,
        method: Text = "get",
        headers=None,
        cookies=None,
        data=None,
        baseurl: Text = "",
    ) -> None:
        """Open url."""
        headers = headers or {}
        cookies = cookies or {}
        if not baseurl:
            baseurl = self.baseurl
        url: URL = URL(baseurl + url)

        _LOGGER.debug("%s: %s %s", method, url, data)

        try:
            if data:
                resp = await getattr(self.websession, method)(
                    str(url), data=data, headers=headers, cookies=cookies
                )
            else:
                resp = await getattr(self.websession, method)(
                    str(url), headers=headers, cookies=cookies
                )
            data = resp.json()
            _LOGGER.debug("%s: %s", resp.status_code, json.dumps(data))
            if resp.status_code > 299:
                if resp.status_code == 401:
                    if data and data.get("error") == "invalid_token":
                        raise TeslaException(resp.status_code, "invalid_token")
                elif resp.status_code == 408:
                    raise TeslaException(resp.status_code, "vehicle_unavailable")
                raise TeslaException(resp.status_code)
            if data.get("error"):
                # known errors:
                #     'vehicle unavailable: {:error=>"vehicle unavailable:"}',
                #     "upstream_timeout", "vehicle is curently in service"
                _LOGGER.debug(
                    "Raising exception for : %s",
                    f'{data.get("error")}:{data.get("error_description")}',
                )
                raise TeslaException(
                    f'{data.get("error")}:{data.get("error_description")}'
                )
        except httpx.HTTPStatusError as exception_:
            raise TeslaException(exception_.request.status_code) from exception_
        return data
Example #2
0
 async def _process_messages() -> None:
     """Start Async WebSocket Listener."""
     nonlocal last_message_time
     nonlocal disconnected
     async for msg in self.websocket:
         _LOGGER.debug("msg: %s", msg)
         if msg.type == aiohttp.WSMsgType.BINARY:
             msg_json = json.loads(msg.data)
             if msg_json["msg_type"] == "control:hello":
                 _LOGGER.debug(
                     "%s:Succesfully connected to websocket %s",
                     vin[-5:],
                     self.websocket_url,
                 )
             if msg_json["msg_type"] == "data:update":
                 last_message_time = time.time()
             if (msg_json["msg_type"] == "data:error"
                     and msg_json["value"] == "Can't validate token. "):
                 raise TeslaException(
                     "Can't validate token for websocket connection.")
             if (msg_json["msg_type"] == "data:error"
                     and msg_json["value"] == "disconnected"):
                 if kwargs.get("on_disconnect"):
                     kwargs.get("on_disconnect")(msg_json)
                 disconnected = True
             if kwargs.get("on_message"):
                 kwargs.get("on_message")(msg_json)
         elif msg.type == aiohttp.WSMsgType.ERROR:
             _LOGGER.debug("WSMsgType error")
             break
Example #3
0
    def __open(self, url, headers=None, data=None, baseurl=""):
        """Use raw urlopen command."""
        headers = headers or {}
        if not baseurl:
            baseurl = self.baseurl
        req = Request("%s%s" % (baseurl, url), headers=headers)
        _LOGGER.debug(url)

        try:
            req.data = urlencode(data).encode('utf-8')
        except TypeError:
            pass
        opener = build_opener()

        try:
            resp = opener.open(req)
            charset = resp.info().get('charset', 'utf-8')
            data = json.loads(resp.read().decode(charset))
            opener.close()
            _LOGGER.debug(json.dumps(data))
            return data
        except HTTPError as exception_:
            if exception_.code == 408:
                _LOGGER.debug("%s", exception_)
                return False
            raise TeslaException(exception_.code)
Example #4
0
    async def __open(
        self,
        url: Text,
        method: Text = "get",
        headers=None,
        data=None,
        baseurl: Text = "",
    ) -> None:
        """Open url."""
        headers = headers or {}
        if not baseurl:
            baseurl = self.baseurl
        url: URL = URL(baseurl + url)

        _LOGGER.debug("%s: %s %s", method, url, data)

        try:
            resp = await getattr(self.websession, method)(url,
                                                          headers=headers,
                                                          data=data)
            data = await resp.json()
            _LOGGER.debug("%s: %s", resp.status, json.dumps(data))
            if resp.status > 299:
                if resp.status == 401:
                    if data and data.get("error") == "invalid_token":
                        raise TeslaException(resp.status, "invalid_token")
                elif resp.status == 408:
                    raise TeslaException(resp.status, "vehicle_unavailable")
                raise TeslaException(resp.status)
            if data.get("error"):
                # known errors:
                #     'vehicle unavailable: {:error=>"vehicle unavailable:"}',
                #     "upstream_timeout", "vehicle is curently in service"
                _LOGGER.debug(
                    "Raising exception for : %s",
                    f'{data.get("error")}:{data.get("error_description")}',
                )
                raise TeslaException(
                    f'{data.get("error")}:{data.get("error_description")}')
        except aiohttp.ClientResponseError as exception_:
            raise TeslaException(exception_.status)
        return data
Example #5
0
async def test_form_invalid_auth(hass):
    """Test we handle invalid auth."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )

    with patch(
        "custom_components.tesla_custom.config_flow.TeslaAPI.connect",
        side_effect=TeslaException(401),
    ):
        result2 = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {CONF_USERNAME: TEST_USERNAME, CONF_TOKEN: TEST_TOKEN},
        )

    assert result2["type"] == "form"
    assert result2["errors"] == {"base": "invalid_auth"}
Example #6
0
async def test_form_cannot_connect(hass):
    """Test we handle cannot connect error."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )

    with patch(
        "custom_components.tesla_custom.config_flow.TeslaAPI.connect",
        side_effect=TeslaException(code=HTTPStatus.NOT_FOUND),
    ):
        result2 = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            {CONF_TOKEN: TEST_TOKEN, CONF_USERNAME: TEST_USERNAME},
        )

    assert result2["type"] == "form"
    assert result2["errors"] == {"base": "cannot_connect"}
Example #7
0
async def test_form_cannot_connect(opp):
    """Test we handle cannot connect error."""
    result = await opp.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER})

    with patch(
            "openpeerpower.components.tesla.config_flow.TeslaAPI.connect",
            side_effect=TeslaException(code=HTTP_NOT_FOUND),
    ):
        result2 = await opp.config_entries.flow.async_configure(
            result["flow_id"],
            {
                CONF_PASSWORD: TEST_PASSWORD,
                CONF_USERNAME: TEST_USERNAME
            },
        )

    assert result2["type"] == "form"
    assert result2["errors"] == {"base": "cannot_connect"}
Example #8
0
async def wake_up(wrapped, instance, args, kwargs) -> Callable:
    # pylint: disable=protected-access
    """Wrap a API func so it will attempt to wake the vehicle if asleep.

    The command wrapped is run once if the car_id was last reported
    online. If wrapped detects the car_id is offline, five attempts
    will be made to wake the vehicle to retry the command.

    Raises
        RetryLimitError: The wake_up has exceeded the 5 attempts.
        TeslaException: Tesla connection errors

    Returns
        Callable: Wrapped function that will wake_up

    """
    def valid_result(result):
        """Check if TeslaAPI result succesful.

        Parameters
        ----------
        result : tesla API result
            This is the result of a Tesla Rest API call.

        Returns
        -------
        bool
            Tesla API failure can be checked in a dict with a bool in
            ['response']['result'], a bool, or None or
            ['response']['reason'] == 'could_not_wake_buses'
            Returns true when a failure state not detected.

        """
        try:
            return (result is not None and result is not False
                    and (result is True or
                         (isinstance(result, dict)
                          and isinstance(result["response"], dict) and
                          (result["response"].get("result") is True
                           or result["response"].get("reason") !=
                           "could_not_wake_buses"))))
        except TypeError as exception:
            _LOGGER.error("Result: %s, %s", result, exception)
            return False

    retries = 0
    sleep_delay = 2
    car_id = args[0]
    is_wake_command = len(args) >= 2 and args[1] == "wake_up"
    result = None
    if instance.car_online.get(instance._id_to_vin(car_id)) or is_wake_command:
        try:
            result = await wrapped(*args, **kwargs)
        except TeslaException as ex:
            _LOGGER.debug("Exception: %s\n%s(%s %s)", str(ex),
                          wrapped.__name__, args, kwargs)
            raise
    if valid_result(result) or is_wake_command:
        return result
    _LOGGER.debug(
        "wake_up needed for %s -> %s \n"
        "Info: args:%s, kwargs:%s, "
        "VIN:%s, car_online:%s",
        wrapped.__name__,
        result,
        args,
        kwargs,
        instance._id_to_vin(car_id)[-5:] if car_id else None,
        instance.car_online,
    )
    instance.car_online[instance._id_to_vin(car_id)] = False
    while (kwargs.get("wake_if_asleep") and
           # Check online state
           (car_id is None or
            (not instance._id_to_vin(car_id)
             or not instance.car_online.get(instance._id_to_vin(car_id))))):
        _LOGGER.debug("Attempting to wake up")
        result = await instance._wake_up(car_id)
        _LOGGER.debug(
            "%s(%s): Wake Attempt(%s): %s",
            wrapped.__name__,
            instance._id_to_vin(car_id)[-5:],
            retries,
            result,
        )
        if not result:
            if retries < 5:
                await asyncio.sleep(15 + sleep_delay**(retries + 2))
                retries += 1
                continue
            instance.car_online[instance._id_to_vin(car_id)] = False
            raise RetryLimitError("Reached retry limit; aborting wake up")
        break
    instance.car_online[instance._id_to_vin(car_id)] = True
    # retry function
    _LOGGER.debug("Retrying %s(%s %s)", wrapped.__name__, args, kwargs)
    try:
        result = await wrapped(*args, **kwargs)
        _LOGGER.debug(
            "Retry after wake up succeeded: %s",
            "True" if valid_result(result) else result,
        )
    except TeslaException as ex:
        _LOGGER.debug("Exception: %s\n%s(%s %s)", str(ex), wrapped.__name__,
                      args, kwargs)
        raise
    if valid_result(result):
        return result
    raise TeslaException("could_not_wake_buses")