From dbc5c83c06170a257affd1ae2eea9cc936820a57 Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 11:29:47 +0300 Subject: [PATCH 01/15] Added automatic fetch of refresh_token --- __init__.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index e4d3351..ae1d0a4 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,9 @@ import logging import asyncio import collections +import requests +from lxml import html +import json import homeassistant.helpers.config_validation as cv import voluptuous as vol @@ -11,12 +14,14 @@ DOMAIN = 'grohe_sense' -CONF_REFRESH_TOKEN = 'refresh_token' +CONF_USERNAME = 'username' +CONF_PASSWORD = 'password' CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema({ - vol.Required(CONF_REFRESH_TOKEN): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, }), }, extra=vol.ALLOW_EXTRA, @@ -29,10 +34,52 @@ GroheDevice = collections.namedtuple('GroheDevice', ['locationId', 'roomId', 'applianceId', 'type', 'name']) +def get_token(username, password): + cookie = None + config = { + "username": username, + "password": password, + } + + try: + session = requests.session() + response = session.get(url = BASE_URL + 'oidc/login') + except Exception as e: + _LOGGER.e('Get Refresh Token Exception %s', str(e)) + else: + cookie = response.cookies + tree = html.fromstring(response.content) + + name = tree.xpath("//html/body/div/div/div/div/div/div/div/form") + action = name[0].action + + payload = { + 'username': config['username'], + 'password': config['password'], + 'Content-Type': 'application/x-www-form-urlencoded', + 'origin': BASE_URL, + 'referer': BASE_URL + 'oidc/login', + 'X-Requested-With': 'XMLHttpRequest', + } + try: + response = session.post(url = action, data = payload, cookies = cookie, allow_redirects=False) + except Exception as e: + _LOGGER.e('Get Refresh Token Action Exception %s', str(e)) + else: + ondus_url = response.next.url.replace('ondus', 'https') + try: + response = session.get(url = ondus_url, cookies = cookie) + except Exception as e: + _LOGGER.e('Get Refresh Token Response Exception %s', str(e)) + else: + json = json.loads(response.text) + + return json['refresh_token'] + async def async_setup(hass, config): _LOGGER.debug("Loading Grohe Sense") - await initialize_shared_objects(hass, config.get(DOMAIN).get(CONF_REFRESH_TOKEN)) + await initialize_shared_objects(hass, get_token(config.get(DOMAIN).get(CONF_USERNAME), config.get(DOMAIN).get(CONF_PASSWORD))) await hass.helpers.discovery.async_load_platform('sensor', DOMAIN, {}, config) await hass.helpers.discovery.async_load_platform('switch', DOMAIN, {}, config) From cf1d0527055fdba3b272d160362f3200dbdc7e84 Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 12:07:08 +0300 Subject: [PATCH 02/15] Updates --- manifest.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index f24cf14..e263d1b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,9 @@ { "domain": "grohe_sense", + "version": "0.0.1", "name": "Grohe Sense", - "documentation": "https://github.com/gkreitz/homeassistant-grohe_sense", + "documentation": "https://github.com/rusucosmin/homeassistant-grohe_sense", "dependencies": [], "requirements": [], - "codeowners": ["@gkreitz"] + "codeowners": ["@gkreitz", "@rusucosmin"] } From 27d715f671637fe669d8bcd5c424c5cf93c1cad1 Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 13:01:22 +0300 Subject: [PATCH 03/15] Use async/await --- __init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/__init__.py b/__init__.py index ae1d0a4..60c4fee 100644 --- a/__init__.py +++ b/__init__.py @@ -34,7 +34,7 @@ GroheDevice = collections.namedtuple('GroheDevice', ['locationId', 'roomId', 'applianceId', 'type', 'name']) -def get_token(username, password): +async def get_token(username, password): cookie = None config = { "username": username, @@ -43,7 +43,7 @@ def get_token(username, password): try: session = requests.session() - response = session.get(url = BASE_URL + 'oidc/login') + response = await session.get(url = BASE_URL + 'oidc/login') except Exception as e: _LOGGER.e('Get Refresh Token Exception %s', str(e)) else: @@ -62,13 +62,13 @@ def get_token(username, password): 'X-Requested-With': 'XMLHttpRequest', } try: - response = session.post(url = action, data = payload, cookies = cookie, allow_redirects=False) + response = await session.post(url = action, data = payload, cookies = cookie, allow_redirects=False) except Exception as e: _LOGGER.e('Get Refresh Token Action Exception %s', str(e)) else: ondus_url = response.next.url.replace('ondus', 'https') try: - response = session.get(url = ondus_url, cookies = cookie) + response = await session.get(url = ondus_url, cookies = cookie) except Exception as e: _LOGGER.e('Get Refresh Token Response Exception %s', str(e)) else: @@ -79,7 +79,7 @@ def get_token(username, password): async def async_setup(hass, config): _LOGGER.debug("Loading Grohe Sense") - await initialize_shared_objects(hass, get_token(config.get(DOMAIN).get(CONF_USERNAME), config.get(DOMAIN).get(CONF_PASSWORD))) + await initialize_shared_objects(hass, await get_token(config.get(DOMAIN).get(CONF_USERNAME), config.get(DOMAIN).get(CONF_PASSWORD))) await hass.helpers.discovery.async_load_platform('sensor', DOMAIN, {}, config) await hass.helpers.discovery.async_load_platform('switch', DOMAIN, {}, config) From c802de2bde2cd35b55bb6067f259737e90b53513 Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 13:57:00 +0300 Subject: [PATCH 04/15] Use hacs --- __init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/__init__.py b/__init__.py index 60c4fee..beab8f4 100644 --- a/__init__.py +++ b/__init__.py @@ -34,7 +34,7 @@ GroheDevice = collections.namedtuple('GroheDevice', ['locationId', 'roomId', 'applianceId', 'type', 'name']) -async def get_token(username, password): +async def get_token(hass, username, password): cookie = None config = { "username": username, @@ -42,8 +42,8 @@ async def get_token(username, password): } try: - session = requests.session() - response = await session.get(url = BASE_URL + 'oidc/login') + session = aiohttp_client.async_get_clientsession(hass) + response = await session.get(BASE_URL + 'oidc/login') except Exception as e: _LOGGER.e('Get Refresh Token Exception %s', str(e)) else: @@ -79,7 +79,7 @@ async def get_token(username, password): async def async_setup(hass, config): _LOGGER.debug("Loading Grohe Sense") - await initialize_shared_objects(hass, await get_token(config.get(DOMAIN).get(CONF_USERNAME), config.get(DOMAIN).get(CONF_PASSWORD))) + await initialize_shared_objects(hass, await get_token(hass, config.get(DOMAIN).get(CONF_USERNAME), config.get(DOMAIN).get(CONF_PASSWORD))) await hass.helpers.discovery.async_load_platform('sensor', DOMAIN, {}, config) await hass.helpers.discovery.async_load_platform('switch', DOMAIN, {}, config) From dfddb48fe35719574e972cdc5844f8cc3e6667cf Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 15:32:30 +0300 Subject: [PATCH 05/15] Small changes to get the integration to work --- __init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/__init__.py b/__init__.py index beab8f4..cf36cbb 100644 --- a/__init__.py +++ b/__init__.py @@ -48,7 +48,9 @@ async def get_token(hass, username, password): _LOGGER.e('Get Refresh Token Exception %s', str(e)) else: cookie = response.cookies - tree = html.fromstring(response.content) + _LOGGER.log("Try to parse the response") + tree = html.fromstring(await response.text()) + _LOGGER.log("Got tree") name = tree.xpath("//html/body/div/div/div/div/div/div/div/form") action = name[0].action @@ -66,15 +68,19 @@ async def get_token(hass, username, password): except Exception as e: _LOGGER.e('Get Refresh Token Action Exception %s', str(e)) else: - ondus_url = response.next.url.replace('ondus', 'https') + headers = response.headers + _LOGGER.e('Headers', headers) + for k in headers: + _LOGGER.debug('%s %s', k, headers[k]) + ondus_url = response.headers['Location'].url.replace('ondus', 'https') try: response = await session.get(url = ondus_url, cookies = cookie) except Exception as e: _LOGGER.e('Get Refresh Token Response Exception %s', str(e)) else: - json = json.loads(response.text) + response_json = json.loads(response.text()) - return json['refresh_token'] + return response_json['refresh_token'] async def async_setup(hass, config): _LOGGER.debug("Loading Grohe Sense") From 13291e0874e406bdb20f42be766eeb88a53af464 Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 15:57:10 +0300 Subject: [PATCH 06/15] Refactor --- __init__.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/__init__.py b/__init__.py index cf36cbb..0d66202 100644 --- a/__init__.py +++ b/__init__.py @@ -35,29 +35,21 @@ GroheDevice = collections.namedtuple('GroheDevice', ['locationId', 'roomId', 'applianceId', 'type', 'name']) async def get_token(hass, username, password): - cookie = None - config = { - "username": username, - "password": password, - } - try: session = aiohttp_client.async_get_clientsession(hass) response = await session.get(BASE_URL + 'oidc/login') except Exception as e: - _LOGGER.e('Get Refresh Token Exception %s', str(e)) + _LOGGER.error('Get Refresh Token Exception %s', str(e)) else: cookie = response.cookies - _LOGGER.log("Try to parse the response") tree = html.fromstring(await response.text()) - _LOGGER.log("Got tree") name = tree.xpath("//html/body/div/div/div/div/div/div/div/form") action = name[0].action payload = { - 'username': config['username'], - 'password': config['password'], + 'username': username, + 'password': password, 'Content-Type': 'application/x-www-form-urlencoded', 'origin': BASE_URL, 'referer': BASE_URL + 'oidc/login', @@ -66,17 +58,13 @@ async def get_token(hass, username, password): try: response = await session.post(url = action, data = payload, cookies = cookie, allow_redirects=False) except Exception as e: - _LOGGER.e('Get Refresh Token Action Exception %s', str(e)) + _LOGGER.error('Get Refresh Token Action Exception %s', str(e)) else: - headers = response.headers - _LOGGER.e('Headers', headers) - for k in headers: - _LOGGER.debug('%s %s', k, headers[k]) ondus_url = response.headers['Location'].url.replace('ondus', 'https') try: response = await session.get(url = ondus_url, cookies = cookie) except Exception as e: - _LOGGER.e('Get Refresh Token Response Exception %s', str(e)) + _LOGGER.error('Get Refresh Token Response Exception %s', str(e)) else: response_json = json.loads(response.text()) From ead6a5620748824b067bec3b2a52b50e9a47fa9c Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 15:58:35 +0300 Subject: [PATCH 07/15] Rollback codeowners and documentation --- manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index e263d1b..feae5b0 100644 --- a/manifest.json +++ b/manifest.json @@ -2,8 +2,8 @@ "domain": "grohe_sense", "version": "0.0.1", "name": "Grohe Sense", - "documentation": "https://github.com/rusucosmin/homeassistant-grohe_sense", + "documentation": "https://github.com/gkreitz/homeassistant-grohe_sense", "dependencies": [], "requirements": [], - "codeowners": ["@gkreitz", "@rusucosmin"] + "codeowners": ["@gkreitz"] } From 76c46e8d49fd2348eed73207e0dd785942211fa7 Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 15:59:38 +0300 Subject: [PATCH 08/15] Updated readme --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d36fc31..b27855e 100644 --- a/README.md +++ b/README.md @@ -36,16 +36,12 @@ Graphing water consumption is also nice. Note that the data returned by Grohe's ## Installation - Ensure everything is set up and working in Grohe's Ondus app - Copy this folder to `/custom_components/grohe_sense/` -- Go to https://idp2-apigw.cloud.grohe.com/v3/iot/oidc/login -- Bring up developer tools -- Log in, that'll try redirecting your browser with a 302 to an url starting with `ondus://idp2-apigw.cloud.grohe.com/v3/iot/oidc/token`, which an off-the-shelf Chrome will ignore -- You should see this failed redirect in your developer tools. Copy out the full URL and replace `ondus` with `https` and visit that URL (will likely only work once, and will expire, so don't be too slow). -- This gives you a json response. Save it and extract refresh_token from it (manually, or `jq .refresh_token < file.json`) Put the following in your home assistant config (N.B., format has changed, this component is no longer configured as a sensor platform) ``` grohe_sense: - refresh_token: "YOUR_VERY_VERY_LONG_REFRESH_TOKEN" + username: "YOUR_GROHE_EMAIL" + password: "YOUR_GROHE_PASSWORD" ``` ## Remarks on the "API" From 104b54513b31e1f3c83018facbbd7c9e299fac00 Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 16:04:22 +0300 Subject: [PATCH 09/15] Updated to bar --- sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sensor.py b/sensor.py index 5537518..00ac095 100644 --- a/sensor.py +++ b/sensor.py @@ -5,7 +5,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -from homeassistant.const import (STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE, PERCENTAGE, DEVICE_CLASS_HUMIDITY, VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, PRESSURE_MBAR, DEVICE_CLASS_PRESSURE, TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE, VOLUME_LITERS) +from homeassistant.const import (STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE, PERCENTAGE, DEVICE_CLASS_HUMIDITY, VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, PRESSURE_BAR, DEVICE_CLASS_PRESSURE, TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE, VOLUME_LITERS) from homeassistant.helpers import aiohttp_client @@ -21,7 +21,7 @@ 'temperature': SensorType(TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE, lambda x : x), 'humidity': SensorType(PERCENTAGE, DEVICE_CLASS_HUMIDITY, lambda x : x), 'flowrate': SensorType(VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, None, lambda x : x * 3.6), - 'pressure': SensorType(PRESSURE_MBAR, DEVICE_CLASS_PRESSURE, lambda x : x * 1000), + 'pressure': SensorType(PRESSURE_BAR, DEVICE_CLASS_PRESSURE, lambda x : x * 1000), 'temperature_guard': SensorType(TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE, lambda x : x), } From c541b247809ccaf3b1a9ece620b81bfd98102d1c Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 16:57:00 +0300 Subject: [PATCH 10/15] Small fix for URL --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 0d66202..850cdbb 100644 --- a/__init__.py +++ b/__init__.py @@ -60,7 +60,7 @@ async def get_token(hass, username, password): except Exception as e: _LOGGER.error('Get Refresh Token Action Exception %s', str(e)) else: - ondus_url = response.headers['Location'].url.replace('ondus', 'https') + ondus_url = response.headers['Location'].replace('ondus', 'https') try: response = await session.get(url = ondus_url, cookies = cookie) except Exception as e: From 5a162e1e83497f593ab1fa82039e89e3ec4a40a9 Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sat, 27 May 2023 17:12:39 +0300 Subject: [PATCH 11/15] Await --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 850cdbb..41d2ffd 100644 --- a/__init__.py +++ b/__init__.py @@ -66,7 +66,7 @@ async def get_token(hass, username, password): except Exception as e: _LOGGER.error('Get Refresh Token Response Exception %s', str(e)) else: - response_json = json.loads(response.text()) + response_json = json.loads(await response.text()) return response_json['refresh_token'] From 7d335a13455895034a4e5363b5e0b6f928db42d8 Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sun, 16 Jul 2023 23:38:14 +0300 Subject: [PATCH 12/15] Attempt to fix stuff --- __init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/__init__.py b/__init__.py index 41d2ffd..d8998b6 100644 --- a/__init__.py +++ b/__init__.py @@ -34,9 +34,8 @@ GroheDevice = collections.namedtuple('GroheDevice', ['locationId', 'roomId', 'applianceId', 'type', 'name']) -async def get_token(hass, username, password): +async def get_token(session, username, password): try: - session = aiohttp_client.async_get_clientsession(hass) response = await session.get(BASE_URL + 'oidc/login') except Exception as e: _LOGGER.error('Get Refresh Token Exception %s', str(e)) @@ -73,15 +72,15 @@ async def get_token(hass, username, password): async def async_setup(hass, config): _LOGGER.debug("Loading Grohe Sense") - await initialize_shared_objects(hass, await get_token(hass, config.get(DOMAIN).get(CONF_USERNAME), config.get(DOMAIN).get(CONF_PASSWORD))) + await initialize_shared_objects(hass, config.get(DOMAIN).get(CONF_USERNAME), config.get(DOMAIN).get(CONF_PASSWORD)) await hass.helpers.discovery.async_load_platform('sensor', DOMAIN, {}, config) await hass.helpers.discovery.async_load_platform('switch', DOMAIN, {}, config) return True -async def initialize_shared_objects(hass, refresh_token): +async def initialize_shared_objects(hass, username, password): session = aiohttp_client.async_get_clientsession(hass) - auth_session = OauthSession(session, refresh_token) + auth_session = OauthSession(session, username, password) devices = [] hass.data[DOMAIN] = { 'session': auth_session, 'devices': devices } @@ -106,9 +105,11 @@ def __init__(self, error_code, reason): self.reason = reason class OauthSession: - def __init__(self, session, refresh_token): + def __init__(self, session, username, password): self._session = session - self._refresh_token = refresh_token + self._username = username + self._password = password + self._refresh_token = get_token(self._session, self._username, self._password) self._access_token = None self._fetching_new_token = None @@ -167,7 +168,8 @@ async def _http_request(self, url, method='get', auth_token=None, headers={}, ** token = await auth_token.token(token) else: _LOGGER.error('Grohe sense refresh token is invalid (or expired), please update your configuration with a new refresh token') - raise OauthException(response.status, await response.text()) + self._refresh_token = get_token(self._session, self._username, self._password) + token = await auth_token.token(token) else: _LOGGER.debug('Request to %s returned status %d, %s', url, response.status, await response.text()) except OauthException as oe: From cc6bf0628f8dffc25509191b06cc8160860918ab Mon Sep 17 00:00:00 2001 From: Cosmin Date: Sun, 16 Jul 2023 23:41:22 +0300 Subject: [PATCH 13/15] Fixes --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index d8998b6..76f08a3 100644 --- a/__init__.py +++ b/__init__.py @@ -169,7 +169,7 @@ async def _http_request(self, url, method='get', auth_token=None, headers={}, ** else: _LOGGER.error('Grohe sense refresh token is invalid (or expired), please update your configuration with a new refresh token') self._refresh_token = get_token(self._session, self._username, self._password) - token = await auth_token.token(token) + token = await self.token(token) else: _LOGGER.debug('Request to %s returned status %d, %s', url, response.status, await response.text()) except OauthException as oe: From 2f4ab5da282a6738ef741fb83fd5182bae99db83 Mon Sep 17 00:00:00 2001 From: Cosmin Date: Tue, 18 Jul 2023 11:43:11 +0300 Subject: [PATCH 14/15] Fix await refresh token --- __init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index 76f08a3..6d8a2d8 100644 --- a/__init__.py +++ b/__init__.py @@ -80,7 +80,7 @@ async def async_setup(hass, config): async def initialize_shared_objects(hass, username, password): session = aiohttp_client.async_get_clientsession(hass) - auth_session = OauthSession(session, username, password) + auth_session = OauthSession(session, username, password, await get_token(session, username, password)) devices = [] hass.data[DOMAIN] = { 'session': auth_session, 'devices': devices } @@ -105,11 +105,11 @@ def __init__(self, error_code, reason): self.reason = reason class OauthSession: - def __init__(self, session, username, password): + def __init__(self, session, username, password, refresh_token): self._session = session self._username = username self._password = password - self._refresh_token = get_token(self._session, self._username, self._password) + self._refresh_token = refresh_token self._access_token = None self._fetching_new_token = None From d19b1b09a1d58cd88b25ea9149d55394821454a6 Mon Sep 17 00:00:00 2001 From: Matthias Weiss Date: Sat, 23 Sep 2023 11:51:55 +0200 Subject: [PATCH 15/15] Update sensor.py Fixed API update to aggregate as suggested in https://github.com/FlorianSW/grohe-ondus-api-java/compare/2.0.0...3.0.0 --- sensor.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sensor.py b/sensor.py index 00ac095..ef30a90 100644 --- a/sensor.py +++ b/sensor.py @@ -119,7 +119,8 @@ def parse_time(s): return datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%f%z') poll_from=self._poll_from.strftime('%Y-%m-%d') - measurements_response = await self._auth_session.get(BASE_URL + f'locations/{self._locationId}/rooms/{self._roomId}/appliances/{self._applianceId}/data?from={poll_from}') + measurements_response = await self._auth_session.get(BASE_URL + f'locations/{self._locationId}/rooms/{self._roomId}/appliances/{self._applianceId}/data/aggregated?from={poll_from}') + _LOGGER.debug('Data read: %s', measurements_response['data']) if 'withdrawals' in measurements_response['data']: withdrawals = measurements_response['data']['withdrawals'] _LOGGER.debug('Received %d withdrawals in response', len(withdrawals)) @@ -137,12 +138,13 @@ def parse_time(s): if 'measurement' in measurements_response['data']: measurements = measurements_response['data']['measurement'] - measurements.sort(key = lambda x: x['timestamp']) + measurements.sort(key = lambda x: x['date']) if len(measurements): for key in SENSOR_TYPES_PER_UNIT[self._type]: + _LOGGER.debug('key: %s', key) if key in measurements[-1]: self._measurements[key] = measurements[-1][key] - self._poll_from = max(self._poll_from, parse_time(measurements[-1]['timestamp'])) + self._poll_from = datetime.strptime(measurements[-1]['date'], '%Y-%m-%d') else: _LOGGER.info('Data response for appliance %s did not contain any measurements data', self._applianceId)