Skip to content

Commit 3c58741

Browse files
Merge pull request #176 from PiotrMachowski/dev
v2.6.0
2 parents 1a65394 + 24ab04e commit 3c58741

File tree

11 files changed

+241
-98
lines changed

11 files changed

+241
-98
lines changed

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[![HACS Default][hacs_shield]][hacs]
22
[![GitHub Latest Release][releases_shield]][latest_release]
3-
[![GitHub All Releases][downloads_total_shield]][releases]<!-- piotrmachowski_support_badges_start -->
3+
[![GitHub All Releases][downloads_total_shield]][releases]
4+
[![Installations][installations_shield]][releases]<!-- piotrmachowski_support_badges_start -->
45
[![Ko-Fi][ko_fi_shield]][ko_fi]
56
[![buycoffee.to][buycoffee_to_shield]][buycoffee_to]
67
[![PayPal.Me][paypal_me_shield]][paypal_me]
@@ -18,6 +19,8 @@
1819
[releases]: https://github.com/PiotrMachowski/Home-Assistant-custom-components-Tauron-AMIplus/releases
1920
[downloads_total_shield]: https://img.shields.io/github/downloads/PiotrMachowski/Home-Assistant-custom-components-Tauron-AMIplus/total
2021

22+
[installations_shield]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fanalytics.home-assistant.io%2Fcustom_integrations.json&query=%24.tauron_amiplus.total&style=popout&color=41bdf5&label=analytics
23+
2124

2225
# Tauron AMIplus sensor
2326

@@ -29,15 +32,15 @@ This sensor uses unofficial API to get energy usage and generation data from [*T
2932

3033
To configure this integration go to: _Configuration_ -> _Integrations_ -> _Add integration_ -> _Tauron AMIplus_.
3134

32-
You can also use following [My Home Assistant](http://my.home-assistant.io/) link
35+
You can also use following [My Home Assistant](http://my.home-assistant.io/) link:
3336

3437
[![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=tauron_amiplus)
3538

3639
### Manual - yaml
3740

3841

3942
<details>
40-
<summary>Warning: yaml configuration is not recommended</summary>
43+
<summary>Warning: yaml configuration is no longer recommended</summary>
4144

4245

4346
**Warning:** Not all features are available when using yaml configuration
@@ -108,6 +111,8 @@ sensor:
108111

109112
### Using [HACS](https://hacs.xyz/) (recommended)
110113

114+
[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=PiotrMachowski&repository=Home-Assistant-custom-components-Tauron-AMIplus&category=integration)
115+
111116
* In _Integrations_ section add repository "Tauron AMIplus"
112117
* Install added repository
113118

@@ -128,7 +133,7 @@ Then restart Home Assistant before applying configuration file changes.
128133

129134
* **How to display hourly data in Energy dashboard?**
130135

131-
To show hourly data in Energy dashboard you have to use `tauron_importer` positions.
136+
To show hourly data in Energy dashboard you have to use `tauron_importer` statistics instead of entities.
132137

133138
* **Why there are missing days in statistics/Energy dashboard?**
134139

custom_components/tauron_amiplus/config_flow.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,15 @@ async def async_step_select_meter(self, user_input=None):
9595

9696
if len(errors) == 0:
9797
try:
98-
tariff = None
99-
calculated = await self.hass.async_add_executor_job(
98+
tariff = await self.hass.async_add_executor_job(
10099
TauronAmiplusConnector.calculate_tariff, self._username, self._password,
101100
user_input[CONF_METER_ID])
102-
if calculated is not None:
103-
tariff = calculated
104101
if tariff is not None:
105102
self._meter_id = user_input[CONF_METER_ID]
106103
self._tariff = tariff
107104
return await self.async_step_config_options()
108105
errors = {CONF_METER_ID: "server_no_connection"}
109-
description_placeholders = {"error_info": str(calculated)}
106+
description_placeholders = {"error_info": str(tariff)}
110107
except Exception as e:
111108
errors = {CONF_PASSWORD: "server_no_connection"}
112109
description_placeholders = {"error_info": str(e)}
@@ -187,7 +184,8 @@ def get_schema_init(user_input=None):
187184
def get_schema_select_meter(self, user_input=None):
188185
if user_input is None:
189186
user_input = {}
190-
meter_options = list(map(lambda m: {"label": m["meter_name"], "value": m["meter_id"]}, self._meters))
187+
meter_options = list(
188+
map(lambda m: {"label": f"({m['meter_type']}) {m['meter_name']}", "value": m["meter_id"]}, self._meters))
191189
data_schema = vol.Schema({
192190
vol.Required(CONF_METER_ID,
193191
default=user_input.get(CONF_METER_ID, vol.UNDEFINED)): selector(
@@ -227,7 +225,7 @@ def async_get_options_flow(config_entry):
227225

228226

229227
class TauronAmiplusOptionsFlowHandler(config_entries.OptionsFlow):
230-
"""Blueprint config flow options handler."""
228+
"""Tauron Amiplus config flow options handler."""
231229

232230
def __init__(self, config_entry):
233231
"""Initialize HACS options flow."""

custom_components/tauron_amiplus/connector.py

Lines changed: 98 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
"""Update coordinator for TAURON sensors."""
22
import datetime
33
import logging
4-
import re
54
import ssl
5+
import re
66
from typing import Optional, Tuple
77

88
import requests
9-
from requests import adapters
9+
from requests import adapters, Response, Session
1010
from urllib3 import poolmanager
1111

12-
from .const import (CONST_DATE_FORMAT, CONST_MAX_LOOKUP_RANGE, CONST_REQUEST_HEADERS, CONST_URL_ENERGY, CONST_URL_LOGIN,
13-
CONST_URL_READINGS, CONST_URL_SELECT_METER, CONST_URL_SERVICE)
12+
from .const import (CONST_DATE_FORMAT, CONST_MAX_LOOKUP_RANGE, CONST_REQUEST_HEADERS, CONST_URL_ENERGY,
13+
CONST_URL_ENERGY_BUSINESS, CONST_URL_LOGIN, CONST_URL_LOGIN_MOJ_TAURON, CONST_URL_READINGS,
14+
CONST_URL_SELECT_METER, CONST_URL_SERVICE, CONST_URL_SERVICE_MOJ_TAURON)
1415

1516
_LOGGER = logging.getLogger(__name__)
1617

@@ -36,6 +37,8 @@ def __init__(self):
3637
self.tariff = None
3738
self.consumption: Optional[TauronAmiplusDataSet] = None
3839
self.generation: Optional[TauronAmiplusDataSet] = None
40+
self.amount_value: Optional[float] = None
41+
self.amount_status: Optional[str] = None
3942

4043
def data_unavailable(self):
4144
return self.consumption is None or self.generation is None
@@ -98,6 +101,7 @@ def __init__(self, username, password, meter_id, show_generation=False, show_12_
98101
self.username = username
99102
self.password = password
100103
self.meter_id = meter_id
104+
self.is_business = False
101105
self.meters = []
102106
self.show_generation = show_generation
103107
self.show_12_months = show_12_months
@@ -110,15 +114,16 @@ def __init__(self, username, password, meter_id, show_generation=False, show_12_
110114

111115
def get_raw_data(self) -> TauronAmiplusRawData:
112116
data = TauronAmiplusRawData()
113-
self.login()
117+
# amount_value, amount_status = self.get_moj_tauron()
118+
# data.amount_value = amount_value
119+
# data.amount_status = amount_status
120+
data.tariff = self.login()
114121
generation_max_cache = datetime.datetime.now()
115122
data.consumption, consumption_max_cache = self.get_data_set(generation=False)
116123
if self.show_generation or self.show_balanced:
117124
data.generation, generation_max_cache = self.get_data_set(generation=True)
118125
else:
119126
data.generation = TauronAmiplusDataSet()
120-
if data.consumption.json_yearly is not None:
121-
data.tariff = data.consumption.json_yearly["data"]["tariff"]
122127
self._cache.delete_older_than(min(consumption_max_cache, generation_max_cache))
123128
return data
124129

@@ -149,68 +154,77 @@ def get_data_set(self, generation) -> Tuple[TauronAmiplusDataSet, datetime.datet
149154
cache_max = potential_max
150155
return dataset, cache_max
151156

152-
def login(self):
157+
def login_service(self, login_url: str, service: str) -> tuple[Session, Response]:
153158
payload_login = {
154159
"username": self.username,
155160
"password": self.password,
156-
"service": CONST_URL_SERVICE,
161+
"service": service,
157162
}
158163
session = requests.session()
159164
session.mount("https://", TLSAdapter())
160-
self.log("Logging in...")
161-
session.request(
165+
self.log(f"Logging in... ({service})")
166+
r1 = session.request(
162167
"POST",
163-
CONST_URL_LOGIN,
168+
login_url,
164169
data=payload_login,
165170
headers=CONST_REQUEST_HEADERS,
166171
)
172+
if "Przekroczono maksymalną liczbę logowań." in r1.text:
173+
self.log("Too many login attempts")
174+
raise Exception("Too many login attempts")
167175
r2 = session.request(
168176
"POST",
169-
CONST_URL_LOGIN,
177+
login_url,
170178
data=payload_login,
171179
headers=CONST_REQUEST_HEADERS,
172180
)
181+
if "Przekroczono maksymalną liczbę logowań." in r2.text:
182+
self.log("Too many login attempts")
183+
raise Exception("Too many login attempts")
173184
if "Login lub hasło są nieprawidłowe." in r2.text:
174185
self.log("Invalid credentials")
175186
raise Exception("Invalid credentials")
176-
if self.username not in r2.text:
187+
if (self.username not in r2.text) and (self.username.upper() not in r2.text):
177188
self.log("Failed to login")
178189
raise Exception("Failed to login")
190+
return session, r2
191+
192+
def login(self):
193+
session, login_response = self.login_service(CONST_URL_LOGIN, CONST_URL_SERVICE)
194+
self.session = session
179195
self.log("Logged in.")
180-
self.meters = self._get_meters(r2.text)
196+
self.meters = self._get_meters(login_response.text)
181197
payload_select_meter = {"site[client]": self.meter_id}
198+
selected_meter_info = list(filter(lambda m: m["meter_id"] == self.meter_id, self.meters))
199+
if len(selected_meter_info) > 0:
200+
self.is_business = selected_meter_info[0]["meter_type"] == "WO"
201+
else:
202+
self.is_business = False
182203
self.log(f"Selecting meter: {self.meter_id}")
183-
session.request("POST", CONST_URL_SELECT_METER, data=payload_select_meter, headers=CONST_REQUEST_HEADERS)
184-
self.session = session
204+
select_response = self.session.request("POST", CONST_URL_SELECT_METER, data=payload_select_meter, headers=CONST_REQUEST_HEADERS)
205+
tariff_search = re.findall(r"'Tariff' : '(.*)',", select_response.text)
206+
if len(tariff_search) > 0:
207+
tariff = tariff_search[0]
208+
return tariff
209+
return "unknown"
185210

186211
@staticmethod
187-
def _get_meters(text):
212+
def _get_meters(text: str) -> list:
188213
regex = r".*data-data='{\"type\": \".*\"}'>.*"
189214
matches = list(re.finditer(regex, text))
190215
meters = []
191216
for match in matches:
192217
m1 = re.match(r".*value=\"([\d\_]+)\".*", match.group())
193218
m2 = re.match(r".*\"}'>(.*)</option>", match.group())
194-
if m1 is None or m2 is None:
219+
m3 = re.match(r".*data-data='{\"type\": \"(.*)\"}'>.*", match.group())
220+
if m1 is None or m2 is None or m3 is None:
195221
continue
196222
meter_id = m1.groups()[0]
197223
display_name = m2.groups()[0]
198-
meters.append({"meter_id": meter_id, "meter_name": display_name})
224+
meter_type = m3.groups()[0]
225+
meters.append({"meter_id": meter_id, "meter_name": display_name, "meter_type": meter_type})
199226
return meters
200227

201-
def calculate_configuration(self, days_before=2, throw_on_empty=True):
202-
self.log("Calculating configuration...")
203-
json_data, _ = self.get_raw_values_daily(days_before, generation=False)
204-
if json_data is None:
205-
self.log("Failed to calculate configuration")
206-
if throw_on_empty:
207-
raise Exception("Failed to login")
208-
else:
209-
return None
210-
tariff = json_data["data"]["tariff"]
211-
self.log(f"Calculated configuration: {tariff}")
212-
return tariff
213-
214228
def get_values_yearly(self, generation):
215229
now = datetime.datetime.now()
216230
first_day_of_year = now.replace(day=1, month=1)
@@ -220,6 +234,7 @@ def get_values_yearly(self, generation):
220234
"to": TauronAmiplusConnector.format_date(last_day_of_year),
221235
"profile": "year",
222236
"type": "oze" if generation else "consum",
237+
"energy": 2 if generation else 1,
223238
}
224239
self.log(f"Downloading yearly data for year: {now.year}, generation: {generation}")
225240
values = self.get_chart_values(payload)
@@ -240,6 +255,7 @@ def get_values_monthly(self, generation):
240255
"to": TauronAmiplusConnector.format_date(last_day_of_month),
241256
"profile": "month",
242257
"type": "oze" if generation else "consum",
258+
"energy": 2 if generation else 1,
243259
}
244260
values = self.get_chart_values(payload)
245261
if values is not None:
@@ -293,7 +309,8 @@ def get_raw_values_daily_for_range(self, day_from: datetime.date, day_to: dateti
293309
data["data"]["allData"].extend(day_data["data"]["allData"])
294310
data["data"]["sum"] += day_data["data"]["sum"]
295311
data["data"]["zonesName"] = day_data["data"]["zonesName"]
296-
data["data"]["tariff"] = day_data["data"]["tariff"]
312+
if "tariff" in day_data["data"]:
313+
data["data"]["tariff"] = day_data["data"]["tariff"]
297314
for z, v in day_data["data"]["zones"].items():
298315
if z in data["data"]["zones"]:
299316
data["data"]["zones"][z] += v
@@ -316,6 +333,7 @@ def get_raw_values_daily_for_day(self, day, generation):
316333
"to": day_str,
317334
"profile": "full time",
318335
"type": "oze" if generation else "consum",
336+
"energy": 2 if generation else 1,
319337
}
320338
self.log(f"Downloading daily data for day: {day_str}, generation: {generation}")
321339
values = self.get_chart_values(payload)
@@ -346,15 +364,20 @@ def get_reading(self, generation):
346364
return post
347365

348366
def get_chart_values(self, payload):
349-
return self.execute_post(CONST_URL_ENERGY, payload)
367+
return self.execute_post(CONST_URL_ENERGY_BUSINESS if self.is_business else CONST_URL_ENERGY, payload)
350368

351369
def execute_post(self, url, payload):
370+
self.log(f"EXECUTING: {url} with payload: {payload}")
352371
response = self.session.request(
353372
"POST",
354373
url,
355374
data=payload,
356375
headers=CONST_REQUEST_HEADERS,
357376
)
377+
self.log(f"RESPONSE: {response.text}")
378+
if "Przekroczono maksymalną liczbę logowań." in response.text:
379+
self.log("Too many login attempts")
380+
raise Exception("Too many login attempts")
358381
if response.status_code == 200 and response.text.startswith('{"success":true'):
359382
json_data = response.json()
360383
return json_data
@@ -363,6 +386,41 @@ def execute_post(self, url, payload):
363386
def log(self, msg):
364387
_LOGGER.debug(f"[{self.meter_id}]: {msg}")
365388

389+
def get_moj_tauron(self):
390+
session, response = self.login_service(CONST_URL_LOGIN_MOJ_TAURON, CONST_URL_SERVICE_MOJ_TAURON)
391+
392+
if response is None:
393+
return None, "unknown"
394+
find_1_1 = re.findall(r".*class=\"amount-value\".*", response.text)
395+
find_2_1 = re.findall(r".*class=\"amount-status\".*", response.text)
396+
if len(find_1_1) > 0 and len(find_2_1) > 0:
397+
amount_value = float(
398+
find_1_1[0].strip()
399+
.replace("<span class=\"amount-value\">", "")
400+
.replace("zł", "")
401+
.replace("</span>", "")
402+
.replace(",", ".").strip())
403+
amount_status = (find_2_1[0].strip()
404+
.replace("<span class=\"amount-status\">", "")
405+
.replace("</span>", "").strip())
406+
return amount_value, amount_status
407+
408+
find_1_2 = re.findall(r".*class=\"amount\".*\s*.*\s*</div>", response.text)
409+
find_2_2 = re.findall(r".*class=\"date\".*", response.text)
410+
if len(find_1_2) > 0 and len(find_2_2) > 0:
411+
amount_value = float(
412+
find_1_2[0].strip()
413+
.replace("<div class=\"amount\">", "")
414+
.replace("zł", "")
415+
.replace("</div>", "")
416+
.replace(",", ".").strip())
417+
amount_status = (find_2_2[0].strip()
418+
.replace("<div class=\"date\">", "")
419+
.replace("</div>", "").strip())
420+
return amount_value, amount_status
421+
422+
return None, "unknown"
423+
366424
@staticmethod
367425
def format_date(date):
368426
return date.strftime(CONST_DATE_FORMAT)
@@ -373,16 +431,15 @@ def get_available_meters(username, password):
373431
connector.login()
374432
if connector.meters is not None and len(connector.meters) > 0:
375433
return connector.meters
376-
raise Exception("Failed to login")
434+
raise Exception("Failed to retrieve energy meters")
377435

378436
@staticmethod
379437
def calculate_tariff(username, password, meter_id):
380438
connector = TauronAmiplusConnector(username, password, meter_id)
381-
connector.login()
382-
config = connector.calculate_configuration()
383-
if config is not None:
384-
return config
385-
raise Exception("Failed to login")
439+
tariff = connector.login()
440+
if tariff is not None:
441+
return tariff
442+
raise Exception("Failed to calculate configuration")
386443

387444
@staticmethod
388445
def add_all_data(data: dict, date):

0 commit comments

Comments
 (0)