Skip to content

Commit

Permalink
Merge pull request #418 from zabuldon/dev
Browse files Browse the repository at this point in the history
chore: release 2023-07-24
  • Loading branch information
alandtse authored Jul 25, 2023
2 parents 1c7bc30 + 5300af9 commit 2dc54e9
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 27 deletions.
4 changes: 2 additions & 2 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
aiohttp==3.8.3; python_version >= "3.6"
aiohttp==3.8.5; python_version >= "3.6"
aiosignal==1.2.0; python_version >= "3.7" and python_version < "4.0"
alabaster==0.7.12; python_version >= "3.7"
anyio==3.6.2; python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.6.2"
Expand Down Expand Up @@ -48,7 +48,7 @@ py==1.11.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_
pycodestyle==2.7.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
pydocstyle==6.1.1; python_version >= "3.6"
pyflakes==2.3.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
pygments==2.13.0; python_version >= "3.7"
pygments==2.15.0; python_version >= "3.7"
pylint==2.13.9; python_full_version >= "3.6.2"
pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.7"
pytest-asyncio==0.20.1; python_version >= "3.7"
Expand Down
7 changes: 4 additions & 3 deletions teslajsonpy/car.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,8 +987,7 @@ async def remote_auto_steering_wheel_heat_climate_request(
"""

data = await self._send_command(
"REMOTE_AUTO_STEERING_WHEEL_HEAT_CLIMATE_REQUEST",
on=enable
"REMOTE_AUTO_STEERING_WHEEL_HEAT_CLIMATE_REQUEST", on=enable
)
if data and data["response"]["result"] is True:
params = {"auto_steering_wheel_heat": enable}
Expand Down Expand Up @@ -1017,7 +1016,9 @@ async def set_heated_steering_wheel_level(self, level: int) -> None:
def get_heated_steering_wheel_level(self) -> int:
"""Return the status of the heated steering wheel."""
if self.data_available:
return self._vehicle_data.get("climate_state", {}).get("steering_wheel_heat_level")
return self._vehicle_data.get("climate_state", {}).get(
"steering_wheel_heat_level"
)
return None

async def set_hvac_mode(self, value: str) -> None:
Expand Down
23 changes: 13 additions & 10 deletions teslajsonpy/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,35 +188,38 @@ async def __open(
if not baseurl:
baseurl = self.baseurl
url: URL = URL(baseurl).join(URL(url))

_LOGGER.debug("%s: %s %s", method, url, data)
debug = _LOGGER.isEnabledFor(logging.DEBUG)
if debug:
_LOGGER.debug("%s: %s %s", method, url, data)

try:
if data:
resp = await getattr(self.websession, method)(
resp: httpx.Response = await getattr(self.websession, method)(
str(url), json=data, headers=headers, cookies=cookies
)
else:
resp = await getattr(self.websession, method)(
resp: httpx.Response = await getattr(self.websession, method)(
str(url), headers=headers, cookies=cookies
)
_LOGGER.debug("%s: %s", resp.status_code, resp.text)
if debug:
_LOGGER.debug("%s: %s", resp.status_code, resp.text)
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)
data = orjson.loads(resp.text) # pylint: disable=no-member
data = orjson.loads(resp.content) # pylint: disable=no-member
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")}',
)
if debug:
_LOGGER.debug(
"Raising exception for : %s",
f'{data.get("error")}:{data.get("error_description")}',
)
raise TeslaException(
f'{data.get("error")}:{data.get("error_description")}'
)
Expand Down
41 changes: 37 additions & 4 deletions teslajsonpy/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@
WAKE_TIMEOUT,
)
from teslajsonpy.energy import EnergySite, PowerwallSite, SolarPowerwallSite, SolarSite
from teslajsonpy.exceptions import TeslaException, custom_retry, custom_wait
from teslajsonpy.exceptions import (
TeslaException,
custom_retry,
custom_retry_except_unavailable,
custom_wait,
)

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -743,7 +748,11 @@ async def _get_and_process_battery_summary(
cur_time - last_update,
ONLINE_INTERVAL,
)
if force or cur_time - last_update >= ONLINE_INTERVAL and update_vehicles:
if (
force
or cur_time - last_update >= ONLINE_INTERVAL
and update_vehicles
):
cars = await self.get_vehicles()
for car in cars:
self.set_id_vin(car_id=car["id"], vin=car["vin"])
Expand Down Expand Up @@ -1333,7 +1342,11 @@ async def api(
return response

# Perform request using given keyword arguments as parameters
return await self.__post_with_retries("", method=method, data=kwargs, url=uri)
# wake_if_asleep is False so we do not retry if the car is asleep
# or if the car is unavailable
return await self.__post_with_retries_except_unavailable(
"", method=method, data=kwargs, url=uri
)

@retry(
wait=custom_wait,
Expand All @@ -1342,5 +1355,25 @@ async def api(
reraise=True,
)
async def __post_with_retries(self, command, method="post", data=None, url=""):
"""Call connection.post with retries for common exceptions."""
"""Call connection.post with retries for common exceptions.
Retries if the car is unavailable.
"""
return await self.__connection.post(command, method=method, data=data, url=url)

@retry(
wait=custom_wait,
retry=custom_retry_except_unavailable,
stop=stop_after_delay(MAX_API_RETRY_TIME),
reraise=True,
)
async def __post_with_retries_except_unavailable(
self, command, method="post", data=None, url=""
):
"""Call connection.post with retries for common exceptions.
Does not retry if the car is unavailable. This should be
used when wake_if_asleep is False since its unlikely the
car will suddenly become available if its offline/sleep.
"""
return await self.__connection.post(command, method=method, data=data, url=url)
16 changes: 8 additions & 8 deletions teslajsonpy/energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,17 @@ def energysite_id(self) -> int:
@property
def has_load_meter(self) -> bool:
"""Return True if energy site has a load meter."""
return self._energysite.get("components").get("load_meter")
return self._energysite.get("components", {}).get("load_meter")

@property
def has_battery(self) -> bool:
"""Return True if energy site has battery."""
return self._energysite.get("components").get("battery")
return self._energysite.get("components", {}).get("battery")

@property
def has_solar(self) -> bool:
"""Return True if energy site has solar."""
return self._energysite.get("components").get("solar")
return self._energysite.get("components", {}).get("solar")

@property
def id(self) -> str:
Expand Down Expand Up @@ -108,7 +108,7 @@ def solar_power(self) -> float:
@property
def solar_type(self) -> str:
"""Return type of solar (e.g. pv_panels or roof)."""
return self._energysite.get("components").get("solar_type")
return self._energysite.get("components", {}).get("solar_type")


class PowerwallSite(EnergySite):
Expand All @@ -134,7 +134,7 @@ def __init__(
@property
def backup_reserve_percent(self) -> int:
"""Return backup reserve percentage."""
return self._battery_data.get("backup").get("backup_reserve_percent")
return self._battery_data.get("backup", {}).get("backup_reserve_percent")

@property
def battery_power(self) -> float:
Expand Down Expand Up @@ -238,22 +238,22 @@ class SolarPowerwallSite(PowerwallSite):
@property
def export_rule(self) -> str:
"""Return energy export rule setting."""
return self._battery_data.get("components").get(
return self._battery_data.get("components", {}).get(
"customer_preferred_export_rule"
)

@property
def grid_charging(self) -> bool:
"""Return grid charging."""
# Key is missing from battery_data when False
return not self._battery_data.get("components").get(
return not self._battery_data.get("components", {}).get(
"disallow_charge_from_grid_with_solar_installed", False
)

@property
def solar_type(self) -> str:
"""Return type of solar (e.g. pv_panels or roof)."""
return self._battery_data.get("components").get("solar_type")
return self._battery_data.get("components", {}).get("solar_type")

async def set_grid_charging(self, value: bool) -> None:
"""Set grid charging setting of Powerwall."""
Expand Down
19 changes: 19 additions & 0 deletions teslajsonpy/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@ class HomelinkError(TeslaException):
pass


def custom_retry_except_unavailable(retry_state: RetryCallState) -> bool:
"""Determine whether Tenacity should retry.
Args
retry_state (RetryCallState): Provided by Tenacity
Returns
bool: whether or not to retry
"""
if not custom_retry(retry_state):
return False
ex = retry_state.outcome.exception()
if isinstance(ex, TeslaException):
if ex.code == 408: # "VEHICLE_UNAVAILABLE"
return False
return True


def custom_retry(retry_state: RetryCallState) -> bool:
"""Determine whether Tenacity should retry.
Expand Down

0 comments on commit 2dc54e9

Please sign in to comment.