Skip to content

Commit

Permalink
Moving from dev to stable (#110)
Browse files Browse the repository at this point in the history
* Check for cached consumption first

If a Miele device is temporarily disconnected from the Miele cloud while
running, the Miele API returns a "not connected" state for the device. If the
"not running" check is done first, it causes the energy/water consumption to be
reset to 0. This in turn means that the statistics generated by Home Assistant
will be messed up, it will look like more energy/water was consumed than in
reality.

* Fix missing ecoFeedback object

* fix the coroutine error (#108)

* Bump version

* Switching back to manual versioning

* fix the coroutine error

* warn if unable to delete the cached token
check if authorized before creating a new session

Co-authored-by: sebltm
Co-authored-by: kloknibor <kloknibor@users.noreply.github.com>

Co-authored-by: Sebastian Lövdahl <slovdahl@hibox.fi>
Co-authored-by: mundschenk_at <github@mundschenk.at>
Co-authored-by: Sébastien Michel <26392528+sebltm@users.noreply.github.com>
Co-authored-by: kloknibor <kloknibor@users.noreply.github.com>
  • Loading branch information
5 people committed Apr 7, 2022
1 parent 1ac1b91 commit 1972140
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 16 deletions.
2 changes: 2 additions & 0 deletions custom_components/miele/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ async def miele_configuration_callback(callback_data):

await async_setup(hass, config)

_LOGGER.info("Requesting authorization...")
configurator = hass.components.configurator
_CONFIGURING[DOMAIN] = configurator.async_request_config(
DEFAULT_NAME,
Expand Down Expand Up @@ -127,6 +128,7 @@ async def async_setup(hass, config):
CONF_CACHE_PATH, hass.config.path(STORAGE_DIR, f".miele-token-cache")
)
hass.data[DOMAIN][DATA_OAUTH] = MieleOAuth(
hass,
config[DOMAIN].get(CONF_CLIENT_ID),
config[DOMAIN].get(CONF_CLIENT_SECRET),
redirect_uri=callback_url,
Expand Down
57 changes: 47 additions & 10 deletions custom_components/miele/miele_at_home.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import functools
import json
import logging
import os
from datetime import timedelta

from requests.exceptions import ConnectionError
Expand Down Expand Up @@ -28,8 +29,8 @@ async def _get_devices_raw(self, lang):
devices = await self.hass.async_add_executor_job(func)
if devices.status_code == 401:
_LOGGER.info("Request unauthorized - attempting token refresh")
if self._session.refresh_token():
return self._get_devices_raw(lang)
if await self._session.refresh_token(self.hass):
return await self._get_devices_raw(lang)

if devices.status_code != 200:
_LOGGER.debug(
Expand Down Expand Up @@ -77,8 +78,14 @@ async def action(self, device_id, body):
result = await self.hass.async_add_executor_job(func)
if result.status_code == 401:
_LOGGER.info("Request unauthorized - attempting token refresh")
if self._session.refresh_token():
return self.action(device_id, body)

if await self._session.refresh_token(self.hass):
if self._session.authorized:
return self.action(device_id, body)
else:
self._session._delete_token()
self._session.new_session()
return self.action(device_id, body)

if result.status_code == 200:
return result.json()
Expand All @@ -105,10 +112,11 @@ class MieleOAuth(object):
OAUTH_AUTHORIZE_URL = "https://api.mcs3.miele.com/thirdparty/login"
OAUTH_TOKEN_URL = "https://api.mcs3.miele.com/thirdparty/token"

def __init__(self, client_id, client_secret, redirect_uri, cache_path=None):
def __init__(self, hass, client_id, client_secret, redirect_uri, cache_path=None):
self._client_id = client_id
self._client_secret = client_secret
self._cache_path = cache_path
self._redirect_uri = redirect_uri

self._token = self._get_cached_token()

Expand All @@ -127,7 +135,7 @@ def __init__(self, client_id, client_secret, redirect_uri, cache_path=None):
)

if self.authorized:
self.refresh_token()
self.refresh_token(hass)

@property
def authorized(self):
Expand All @@ -150,17 +158,23 @@ def get_access_token(self, client_code):

return token

async def refresh_token(self):
async def refresh_token(self, hass):
body = "client_id={}&client_secret={}&".format(
self._client_id, self._client_secret
)
self._token = await self._session.refresh_token(
self._token = await hass.async_add_executor_job(
self.sync_refresh_token,
MieleOAuth.OAUTH_TOKEN_URL,
body=body,
refresh_token=self._token["refresh_token"],
body,
self._token["refresh_token"],
)
self._save_token(self._token)

def sync_refresh_token(self, token_url, body, refresh_token):
return self._session.refresh_token(
token_url, body=body, refresh_token=refresh_token
)

def _get_cached_token(self):
token = None
if self._cache_path:
Expand All @@ -175,6 +189,29 @@ def _get_cached_token(self):

return token

def _delete_token(self):
if self._cache_path:
try:
os.remove(self._cache_path)

except IOError:
_LOGGER.warn("Unable to delete cached token")

self._token = None

def _new_session(self, redirect_uri):
self._session = OAuth2Session(
self._client_id,
auto_refresh_url=MieleOAuth.OAUTH_TOKEN_URL,
redirect_uri=self._redirect_uri,
token=self._token,
token_updater=self._save_token,
auto_refresh_kwargs=self._extra,
)

if self.authorized:
self.refresh_token()

def _save_token(self, token):
_LOGGER.debug("trying to save new token")
if self._cache_path:
Expand Down
25 changes: 19 additions & 6 deletions custom_components/miele/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,13 +631,26 @@ def state(self):
self._cached_consumption = -1
return 0

if self._cached_consumption >= 0:
if (
"ecoFeedback" not in device_state
or device_state["ecoFeedback"] is None
or device_status_value == STATUS_NOT_CONNECTED
):
# Sometimes the Miele API seems to return a null ecoFeedback
# object even though the Miele device is running. Or if the the
# Miele device has lost the connection to the Miele cloud, the
# status is "not connected". Either way, we need to return the
# last known value until the API starts returning something
# sane again, otherwise the statistics generated from this
# sensor would be messed up.
if (
"ecoFeedback" not in device_state
or device_state["ecoFeedback"] is None
or device_status_value == STATUS_NOT_CONNECTED
):
if self._cached_consumption > 0:
return self._cached_consumption
else:
return 0

if not _is_running(device_status_value):
self._cached_consumption = -1
return 0

consumption = 0
if self._key == "energyConsumption":
Expand Down

0 comments on commit 1972140

Please sign in to comment.