Skip to content

Commit

Permalink
Merge branch 'master' into get_historic_data
Browse files Browse the repository at this point in the history
  • Loading branch information
Danielhiversen authored Jan 4, 2025
2 parents 7399eec + 306d4bb commit 36e17ac
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 68 deletions.
2 changes: 1 addition & 1 deletion tibber/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from http import HTTPStatus
from typing import Final

__version__ = "0.30.3"
__version__ = "0.30.8"

API_ENDPOINT: Final = "https://api.tibber.com/v1-beta/gql"
DEFAULT_TIMEOUT: Final = 10
Expand Down
50 changes: 21 additions & 29 deletions tibber/gql_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,35 +101,6 @@
}
}
"""
PRICE_INFO = """
{
viewer {
home(id: "%s") {
currentSubscription {
priceInfo {
current {
energy
tax
total
startsAt
level
}
today {
total
startsAt
level
}
tomorrow {
total
startsAt
level
}
}
}
}
}
}
"""
PUSH_NOTIFICATION = """
mutation{{
sendPushNotification(input: {{
Expand Down Expand Up @@ -295,3 +266,24 @@
}
"""
PRICE_INFO = """
{
viewer {
home(id: "%s") {
currentSubscription {
priceRating {
hourly {
currency
entries {
time
total
energy
level
}
}
}
}
}
}
}
"""
91 changes: 53 additions & 38 deletions tibber/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
LIVE_SUBSCRIBE,
PRICE_INFO,
UPDATE_CURRENT_PRICE,
UPDATE_INFO,
UPDATE_INFO_PRICE,
)

Expand Down Expand Up @@ -86,6 +85,8 @@ def __init__(self, home_id: str, tibber_control: Tibber) -> None:
self._rt_listener: None | asyncio.Task[Any] = None
self._rt_callback: Callable[..., Any] | None = None
self._rt_stopped: bool = True
self._has_real_time_consumption: None | bool = None
self._real_time_consumption_suggested_disabled: dt.datetime | None = None

async def _fetch_data(self, hourly_data: HourlyData) -> None:
"""Update hourly consumption or production data asynchronously."""
Expand Down Expand Up @@ -198,14 +199,40 @@ def hourly_production_data(self) -> list[dict[Any, Any]]:

async def update_info(self) -> None:
"""Update home info and the current price info asynchronously."""
if data := await self._tibber_control.execute(UPDATE_INFO % self._home_id):
self.info = data
await self.update_info_and_price_info()

async def update_info_and_price_info(self) -> None:
"""Update home info and all price info asynchronously."""
if data := await self._tibber_control.execute(UPDATE_INFO_PRICE % self._home_id):
self.info = data
self._process_price_info(self.info)
self._update_has_real_time_consumption()
await self.update_price_info()

def _update_has_real_time_consumption(self) -> None:
try:
_has_real_time_consumption = self.info["viewer"]["home"]["features"]["realTimeConsumptionEnabled"]
except (KeyError, TypeError):
self._has_real_time_consumption = None
return
if self._has_real_time_consumption is None:
self._has_real_time_consumption = _has_real_time_consumption
return

if self._has_real_time_consumption is True and _has_real_time_consumption is False:
now = dt.datetime.now(tz=dt.UTC)
if self._real_time_consumption_suggested_disabled is None:
self._real_time_consumption_suggested_disabled = now
self._has_real_time_consumption = None
elif now - self._real_time_consumption_suggested_disabled > dt.timedelta(hours=1):
self._real_time_consumption_suggested_disabled = None
self._has_real_time_consumption = False
else:
self._has_real_time_consumption = None
return

if _has_real_time_consumption is True:
self._real_time_consumption_suggested_disabled = None
self._has_real_time_consumption = _has_real_time_consumption

async def update_current_price_info(self) -> None:
"""Update just the current price info asynchronously."""
Expand All @@ -224,42 +251,33 @@ async def update_current_price_info(self) -> None:
if price_info:
self._current_price_info = price_info

async def update_price_info(self) -> None:
async def update_price_info(self, retry: bool = True) -> None:
"""Update the current price info, todays price info
and tomorrows price info asynchronously.
"""
if price_info := await self._tibber_control.execute(PRICE_INFO % self.home_id):
self._process_price_info(price_info)

def _process_price_info(self, price_info: dict[str, dict[str, Any]]) -> None:
"""Processes price information retrieved from a GraphQL query.
The information from the provided dictionary is extracted, then the
properties of this TibberHome object is updated with this data.
:param price_info: Price info to retrieve data from.
"""
price_info = await self._tibber_control.execute(PRICE_INFO % self.home_id)
if not price_info:
_LOGGER.error("Could not find price info.")
return
if self.has_active_subscription:
if retry:
_LOGGER.debug("Could not find price info. Retrying...")
return await self.update_price_info(retry=False)
_LOGGER.error("Could not find price info.")
return None
data = price_info["viewer"]["home"]["currentSubscription"]["priceRating"]["hourly"]["entries"]
if not data:
if self.has_active_subscription:
if retry:
_LOGGER.debug("Could not find price info data. Retrying...")
return await self.update_price_info(retry=False)
_LOGGER.error("Could not find price info data. %s", price_info)
return None
self._price_info = {}
self._level_info = {}
for key in ["current", "today", "tomorrow"]:
try:
price_info_k = price_info["viewer"]["home"]["currentSubscription"]["priceInfo"][key]
except (KeyError, TypeError):
_LOGGER.error("Could not find price info for %s.", key)
continue
if key == "current":
self._current_price_info = price_info_k
continue
for data in price_info_k:
self._price_info[data.get("startsAt")] = data.get("total")
self._level_info[data.get("startsAt")] = data.get("level")
if (
not self.last_data_timestamp
or dt.datetime.fromisoformat(data.get("startsAt")) > self.last_data_timestamp
):
self.last_data_timestamp = dt.datetime.fromisoformat(data.get("startsAt"))
for row in data:
self._price_info[row.get("time")] = row.get("total")
self._level_info[row.get("time")] = row.get("level")
self.last_data_timestamp = dt.datetime.fromisoformat(data[-1]["time"])
return None

@property
def current_price_total(self) -> float | None:
Expand Down Expand Up @@ -305,10 +323,7 @@ def has_active_subscription(self) -> bool:
@property
def has_real_time_consumption(self) -> None | bool:
"""Return home id."""
try:
return self.info["viewer"]["home"]["features"]["realTimeConsumptionEnabled"]
except (KeyError, TypeError):
return None
return self._has_real_time_consumption

@property
def has_production(self) -> bool:
Expand Down
2 changes: 2 additions & 0 deletions tibber/response_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ async def extract_response_data(response: ClientResponse) -> dict[Any, Any]:
if error_code == API_ERR_CODE_UNAUTH:
raise InvalidLoginError(response.status, error_message, error_code)

_LOGGER.error("FatalHttpExceptionError %s %s", error_message, error_code)
raise FatalHttpExceptionError(response.status, error_message, error_code)

error_code, error_message = extract_error_details(result.get("errors", []), "N/A")
# if reached here the HTTP response code is not currently handled
_LOGGER.error("FatalHttpExceptionError %s %s", error_message, error_code)
raise FatalHttpExceptionError(response.status, f"Unhandled error: {error_message}", error_code)

0 comments on commit 36e17ac

Please sign in to comment.