Skip to content

Commit ece901a

Browse files
committed
Smart history update
Past data from entities is uploaded in the background in fragments. Data is no longer uploaded in the config_flow stage.
1 parent 2d95d46 commit ece901a

File tree

6 files changed

+302
-93
lines changed

6 files changed

+302
-93
lines changed

custom_components/optispark/api.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import gzip
1313
import base64
1414
from .const import LOGGER
15+
import traceback
1516

1617

1718
class OptisparkApiClientError(Exception):
@@ -91,31 +92,57 @@ def __init__(
9192
"""Sample API Client."""
9293
self._session = session
9394

95+
def extra_to_datetime(self, extra):
96+
"""Corvert unix_time to datetime object."""
97+
if 'oldest_dates' in extra and 'newest_dates' in extra:
98+
# Convert from unix time to datetime object
99+
for key in ['oldest_dates', 'newest_dates']:
100+
for column in extra[key]:
101+
if extra[key][column] is not None:
102+
extra[key][column] = datetime.fromtimestamp(extra[key][column])
103+
return extra
104+
94105
async def upload_history(self, dynamo_data):
95106
"""Upload historical data to dynamoDB without calculating heat pump profile."""
96107
lambda_url = 'https://lhyj2mknjfmatuwzkxn4uuczrq0fbsbd.lambda-url.eu-west-2.on.aws/'
97108
payload = {'dynamo_data': dynamo_data}
98109
payload['upload_only'] = True
99-
await self._api_wrapper(
110+
extra = await self._api_wrapper(
111+
method="post",
112+
url=lambda_url,
113+
data=payload,
114+
)
115+
extra = self.extra_to_datetime(extra)
116+
return extra['oldest_dates'], extra['newest_dates']
117+
118+
async def get_data_dates(self, dynamo_data: dict):
119+
"""Call lambda and only get the newest and oldest dates in dynamo.
120+
121+
dynamo_data will only contain the user_hash.
122+
"""
123+
lambda_url = 'https://lhyj2mknjfmatuwzkxn4uuczrq0fbsbd.lambda-url.eu-west-2.on.aws/'
124+
payload = {'dynamo_data': dynamo_data}
125+
payload['get_newest_oldest_data_date_only'] = True
126+
extra = await self._api_wrapper(
100127
method="post",
101128
url=lambda_url,
102129
data=payload,
103-
headers={"Content-type": "application/json; charset=UTF-8"},
104130
)
131+
extra = self.extra_to_datetime(extra)
132+
return extra['oldest_dates'], extra['newest_dates']
105133

106-
async def async_get_data(self, lambda_args: dict, dynamo_data: dict) -> any:
107-
"""Upload historical data to dynamoDB and calculate heat pump profile."""
134+
async def async_get_profile(self, lambda_args: dict):
135+
"""Get heat pump profile only."""
108136
lambda_url = 'https://lhyj2mknjfmatuwzkxn4uuczrq0fbsbd.lambda-url.eu-west-2.on.aws/'
109137

110138
payload = lambda_args
111-
payload['dynamo_data'] = dynamo_data
112-
results, errors, extra = await self._api_wrapper(
139+
payload['get_profile_only'] = True
140+
LOGGER.debug('----------Lambda get profile----------')
141+
results, errors = await self._api_wrapper(
113142
method="post",
114143
url=lambda_url,
115144
data=payload,
116-
headers={"Content-type": "application/json; charset=UTF-8"},
117145
)
118-
LOGGER.debug('----------Lambda request----------')
119146
if errors['success'] is False:
120147
LOGGER.debug(f'OptisparkApiClientLambdaError: {errors["error_message"]}')
121148
raise OptisparkApiClientLambdaError(errors['error_message'])
@@ -124,18 +151,18 @@ async def async_get_data(self, lambda_args: dict, dynamo_data: dict) -> any:
124151
results['projected_percent_savings'] = 100
125152
else:
126153
results['projected_percent_savings'] = results['base_cost']/results['optimised_cost']*100 - 100
127-
return results, extra['oldest_dates']
154+
return results
128155

129156
async def _api_wrapper(
130157
self,
131158
method: str,
132159
url: str,
133160
data: dict | None = None,
134-
headers: dict | None = None,
135161
):
136162
"""Call the Lambda function."""
137163
try:
138-
data['dynamo_data'] = floats_to_decimal(data['dynamo_data'])
164+
if 'dynamo_data' in data:
165+
data['dynamo_data'] = floats_to_decimal(data['dynamo_data'])
139166
uncompressed_data = pickle.dumps(data)
140167
compressed_data = gzip.compress(uncompressed_data)
141168
LOGGER.debug(f'len(uncompressed_data): {len(uncompressed_data)}')
@@ -146,7 +173,6 @@ async def _api_wrapper(
146173
response = await self._session.request(
147174
method=method,
148175
url=url,
149-
#headers=headers,
150176
json=base64_string,
151177
)
152178
if response.status in (401, 403):
@@ -162,16 +188,19 @@ async def _api_wrapper(
162188
return await response.json()
163189

164190
except asyncio.TimeoutError as exception:
191+
LOGGER.error(traceback.format_exc())
165192
LOGGER.error('OptisparkApiClientTimeoutError:\n Timeout error fetching information')
166193
raise OptisparkApiClientTimeoutError(
167194
"Timeout error fetching information",
168195
) from exception
169196
except (aiohttp.ClientError, socket.gaierror) as exception:
197+
LOGGER.error(traceback.format_exc())
170198
LOGGER.error('OptisparkApiClientCommunicationError:\n Error fetching information')
171199
raise OptisparkApiClientCommunicationError(
172200
"Error fetching information",
173201
) from exception
174202
except Exception as exception: # pylint: disable=broad-except
203+
LOGGER.error(traceback.format_exc())
175204
LOGGER.error('OptisparkApiClientError:\n Something really wrong happened!')
176205
raise OptisparkApiClientError(
177206
"Something really wrong happened!"

custom_components/optispark/config_flow.py

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import voluptuous as vol
55
from homeassistant import config_entries
6-
from homeassistant.helpers.aiohttp_client import async_get_clientsession
76
from homeassistant.helpers.selector import selector
87
from geopy.adapters import AioHTTPAdapter
98
from geopy.geocoders import Nominatim
@@ -13,15 +12,10 @@
1312
from .api import (
1413
OptisparkApiClientPostcodeError,
1514
OptisparkApiClientUnitError,
16-
OptisparkApiClient,
17-
OptisparkApiClientTimeoutError,
18-
OptisparkApiClientCommunicationError,
19-
OptisparkApiClientError
2015
)
2116
from . import OptisparkGetEntityError
2217
from .const import DOMAIN, LOGGER
23-
from . import const, get_entity, get_username
24-
from .history import get_history, OptisparkGetHistoryError
18+
from . import get_entity, get_username
2519

2620

2721
class OptisparkFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
@@ -155,37 +149,9 @@ async def async_step_accept(self, user_input: dict | None = None, reject=False)
155149
user_hash = hashlib.sha256(user_hash.encode('utf-8')).hexdigest()
156150
user_input['user_hash'] = user_hash
157151

158-
try:
159-
dynamo_data = await get_history(
160-
hass=self.hass,
161-
history_days=const.HISTORY_DAYS,
162-
climate_entity_id=user_input['climate_entity_id'],
163-
heat_pump_power_entity_id=user_input['heat_pump_power_entity_id'],
164-
external_temp_entity_id=user_input['external_temp_entity_id'],
165-
user_hash=user_hash,
166-
postcode=user_input['postcode'],
167-
tariff=user_input['tariff'],
168-
include_user_info=False)
169-
LOGGER.debug('************ Uploading history ***********')
170-
tmp_client = OptisparkApiClient(
171-
session=async_get_clientsession(self.hass))
172-
173-
await tmp_client.upload_history(dynamo_data)
174-
LOGGER.debug('************ Upload complete ***********')
175-
176-
except OptisparkApiClientTimeoutError:
177-
errors['base'] = 'optispark_timeout_error'
178-
except OptisparkApiClientCommunicationError:
179-
errors['base'] = 'optispark_communication_error'
180-
except OptisparkApiClientError:
181-
errors['base'] = 'optispark_communication_error'
182-
except OptisparkGetHistoryError:
183-
errors['base'] = 'optispark_history_error'
184-
185-
if errors == {}:
186-
return self.async_create_entry(
187-
title='OptiSpark Entry',
188-
data=user_input)
152+
return self.async_create_entry(
153+
title='OptiSpark Entry',
154+
data=user_input)
189155
else:
190156
errors['base'] = 'accept_agreement'
191157
data_schema = {}

custom_components/optispark/const.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
NAME = "Optispark"
77
DOMAIN = "optispark"
8-
VERSION = "0.1.5"
8+
VERSION = "0.1.6"
99
ATTRIBUTION = "Data provided by http://jsonplaceholder.typicode.com/"
1010

1111
LAMBDA_TEMP = 'temps'
@@ -21,7 +21,9 @@
2121
LAMBDA_POSTCODE = 'postcode'
2222
LAMBDA_HOUSE_CONFIG = 'house_config'
2323

24-
HISTORY_DAYS = 28
24+
HISTORY_DAYS = 3 # the number of days initially required by our algorithm
25+
DYNAMO_HISTORY_DAYS = 365*5
26+
MAX_UPLOAD_HISTORY_READINGS = 5000
2527
DATABASE_COLUMN_SENSOR_HEAT_PUMP_POWER = 'heat_pump_power'
2628
DATABASE_COLUMN_SENSOR_EXTERNAL_TEMPERATURE = 'external_temperature'
2729
DATABASE_COLUMN_SENSOR_CLIMATE_ENTITY = 'climate_entity'

0 commit comments

Comments
 (0)