diff --git a/.gitignore b/.gitignore
index 6e08080..a0bf7db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
src/*.sqlite
src/config.ini
src/rund.sh
+src.old/*
import.py
check.py
*.sqlite
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 9f4bb06..90a2681 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -1,4 +1,18 @@
-## v0.1.0 [2023-10-21]
-- Wersja beta
+# EnergaMeter
+## v1.0.0 [2023-11-06]
+- Pierwsza wersja stabilna
+- Zmieniono strukturę danych API (wymagana aktualizacja configuration.yaml oraz Grafana do nowego formatu danych)
+## v0.1.8 [2023-11-04]
+- Dodano możliwość odwrócenia znaku wartości pomiaru
+## v0.1.4 - v0.1.7 [2023-11-03]
+- Drobne poprawki
+## v0.1.3 [2023-10-31]
+- Dodano obsługę liczników wytwórcy (Uwaga: zmiana struktury JSON)
+## v0.1.2 [2023-10-23]
+- Dodano obsługę trybu serwisowego aplikacji Mój Licznik
+- Poprawiono logowanie błędów
+- W przypadku problemów z logowaniem liczniki w aplikacji pozostają aktywne
## v0.1.1 [2023-10-22]
-- Dodano obsługę błędnego logowania
\ No newline at end of file
+- Dodano obsługę błędnego logowania
+## v0.1.0 [2023-10-21]
+- Wersja beta
\ No newline at end of file
diff --git a/src/Dockerfile b/src/Dockerfile
index a4fd9db..83b8523 100644
--- a/src/Dockerfile
+++ b/src/Dockerfile
@@ -6,12 +6,12 @@ RUN apk add --update py3-pip
# Copy data for add-on
COPY run.sh /
+COPY run.py /
COPY requirements.txt /
COPY main.py /
COPY api.py /
-COPY cron.py /
-# COPY database.sqlite /
COPY moj_licznik.py /
+COPY log_config.py /
RUN chmod a+x /run.sh
RUN pip install -r requirements.txt
diff --git a/src/INSTALL.md b/src/INSTALL.md
index 71d4159..581d684 100644
--- a/src/INSTALL.md
+++ b/src/INSTALL.md
@@ -1,6 +1,6 @@
[](https://www.home-assistant.io/)
# [Energa meter](https://github.com/tcich/ha-addon-energa-meter) Home Assistant add-on
-# Wersja podstawowa
+# Wersja dev
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg
[armv6-shield]: https://img.shields.io/badge/armv6-yes-green.svg
@@ -12,13 +12,19 @@
![armv7-shield]
![i386-shield]
-
+[kawa-logo]: https://github.com/tcich/ha-addon-energa-meter/blob/main/img/buycoffeeto-btn-primary-outline.png
+[kawa]: https://buycoffee.to/tcich
+
+
## O dodatku
To jest dodatek dla [Home Assistant](https://www.home-assistant.io/). Instalacja dodatku [Energa meter](https://github.com/tcich/ha-addon-energa-meter) umożliwia cykliczne pobieranie danych z aplikacji [Mój Licznik - Energa](https://mojlicznik.energa-operator.pl) udostępnianej klientom Operatora energetycznego Energa.
+### Wersja dev
+Wersja dev jest wersją developeską, nie należy jej używać w produkcyjnej wersji HA, może powodować różne problemy, może nie działać.
+
## Instalacja
1) Dodaj repozytorium do repozytoriów dodatków swojego HA za pomocą poniższego przycisku
@@ -47,59 +53,53 @@ Wymagane parametry:
## Konfiguracja sensorów
+Do HA możesz dodać sensory, które zawierają informacje udostępniane przez API
+Poniższa instrukcja zawiera założenia:
+* dodatek jest dostępny pod adresem *localhost* na porcie *8000*
+* ID Twojego licznika to *123456789*
+
+1) Ustal ID Twoich liczników, w tym celu przejdź do adresu Twojego HA na porcie 8000 lub innym jeźeli zmieniłeś go w konfiguracji, np. http://192.168.1.10:8000 wyświetli się w formacie json lista dostępnych liczników.
+2) W pliku configuration.yaml w HA dodaj następującą konfigurację np.:
```
sensor:
- platform: rest
- resource: http://localhost:8000/meters/12335379
- name: "Energia aktualna T1"
- unique_id: 12335379_sumz1
+ resource: http://localhost:8000/123456789/A%2B/1
+ name: "A+ Taryfa 1"
+ unique_id: 123456789_apt1
unit_of_measurement: "kWh"
- value_template: "{{ value_json.meter.zone1.meter | round(2) }}"
+ value_template: "{{ value_json.countner.meter_value | round(2) }}"
- platform: rest
- resource: http://localhost:8000/meters/12335379
- name: "Dzienny odczyt licznika"
- unique_id: 12335379_meterz1
+ resource: http://localhost:8000/123456789/A%2B/2
+ name: "A+ Taryfa 2"
+ unique_id: 123456789_apt2
unit_of_measurement: "kWh"
- value_template: "{{ value_json.meter.zone1.sum | round(2) }}"
- - platform: rest
- resource: http://localhost:8000/meters/12335379
- name: "Energia aktualna T2"
- unique_id: 12335379_sumz2
- unit_of_measurement: "kWh"
- value_template: "{{ value_json.meter.zone2.meter | round(2) }}"
- - platform: rest
- resource: http://localhost:8000/meters/12335379
- name: "Dzienny odczyt licznika"
- unique_id: 12335379_meterz2
- unit_of_measurement: "kWh"
- value_template: "{{ value_json.meter.zone2.sum | round(2) }}"
+ value_template: "{{ value_json.countner.meter_value | round(2) }}"
```
-# Opis konfiguracji
+### Opis konfiguracji
| element konfiguracji | Opis |
|-------------------|-------------------|
-| resource: http://localhost:8000/meters/12335379 | Adres API z danymi konkretnego licznika, podajemy nazwę instancji dockera (**Nazwa hosta** z okna dodatku) lub localhost|
-| name: "Energia aktualna" | Nazwa sensora, wpisz dowolną|
+| resource: http://localhost:8000/123456789/A%2B/1 | Adres API z danymi konkretnego licznika, podajemy **localhost** lub nazwę instancji dockera (**Nazwa hosta** z okna dodatku), port, id licznika, rodzaj pomiaru, taryfa|
+| name: "A+ Taryfa 1" | Nazwa sensora, wpisz dowolną|
| unique_id | Unikalny ID sensora, nie mogą być w systemie dwa sensory z tym samym ID|
| unit_of_measurement: "kWh" | Jednostka miary, nie zmieniaj chyba, że wiesz co robisz|
-| value_template: "{{ value_json.meter.zone2.meter \| round(2) }}" | Zaokrąglony do dwóch miejsc po przecinku stan sensora|
-
-# Opis konfiguracji cd
-| value_template | Opis |
-|-------------------|-------------------|
-| value_json.meter.zone1.sum | Suma licznika oraz dziennego zużycia dla tartfy1 (dostępne są: zone1, zone2, zone3)|
-| value_json.meter.zone2.meter | Stan licznika dziennego dla taryfy1 (dostępne są: zone1, zone2, zone3)|
-
+| value_template: "{{ value_json.meter.countner.meter_value \| round(2) }}" | Zaokrąglony do dwóch miejsc po przecinku stan sensora|
## API dla wykresów, np. Grafana
-Aby pobrać dane z API w formacie JSON należy użyć adresu http://home_assistant:8000/charts/12729?start_date=1695332400129&end_date=1697924583285
+Aby pobrać dane z API w formacie JSON należy użyć adresu http://home_assistant:8000/charts/12729?start_date=1695332400129&end_date=1697924583285&mp=123456789&zone=1
-gdzie:
-* 12729 - jest to ID licznika
-* start_date - początek okresu w milisekundach wg. standardu EPOCH (timestamp)
-* end_date - koniec okresu w milisekundach wg. standardu EPOCH (timestamp)
+### Opis konfiguracji
+| element konfiguracji | Opis |
+|-------------------|-------------------|
+| resource: http://localhost:8000/charts | Adres API z danymi do wykresów, podajemy **localhost** lub nazwę instancji dockera (**Nazwa hosta** z okna dodatku), port, id licznika, rodzaj pomiaru, taryfa|
+| start_date | data początkowa danych w formacie epoch (ms), domyślnie czas bieżący |
+| end_date | data końcowa danych w formacie epoch (ms), domyślnie czas bieżący - 1 dzień |
+| mp | numer licznika |
+| meter_type_url | typ licznika (np. A+: A%2B, A-: A- ) |
+| zone | numer strefy (np. 1, 2) |
+| negative | dodanie parametru z dowolną wartością powoduje, że pomiar jest wartością ujemną|
## Jak dodać wykres do Grafana
### Źródło danych
@@ -118,12 +118,13 @@ gdzie:
1) Przechodzimy do Dashboards
2) Klikamy New -> New dashboard -> Add visualization
3) Wskazujemy Data source: ENERGA
-4) W **Path** wpisujemy: GET: /charts/12335379 (id Twojego licznika)
+4) W **Path** wpisujemy: GET: /charts
+
+ (id Twojego licznika)
5) W **Fields** wpisujemy \$.charts\[\*\].czas typu Time oraz $.charts[*].value typu number z aliasem kWh
5) W **Params** wpisujemy Key: start_date Value: $__from
6) W **Params** wpisujemy Key: end_date Value: $__to
-
@@ -134,10 +135,23 @@ gdzie:
+7) W **Params** wpisujemy Key: meter_type_url Value: A+ lub A- jeżeli mamy energię pobieraną i oddawaną, w takim przypadku tworzymy również oddzielne **Query** dla A+ i A-
+
+
+
+8) W **Params** wpisujemy Key: negative Value: OK (może być dowolna wartość) jeżeli chcemy aby wykres był z wartościami ujemnymi (poniżej osi X)
+
+
+
+9) W **Params** aby uzyskać bilans energii należy dodać **Transform**, przy czym wartość energii oddawanej powinna być jako energia ujemna (parametr negative)
+
+
+
+
## Znane problemy
-Czasami w aplikacji Mój Licznik włącza się captha (jeżeli masz dużo danych historycznych lub wielokrotnie instalujesz dodatek)
-Dane wytwórcy (energia oddana oraz bilans) nie są dostępne, prace w tym zakresie trwają.
+* Czasami w aplikacji Mój Licznik włącza się captha (jeżeli masz dużo danych historycznych lub wielokrotnie instalujesz dodatek) - należy poczekać kilka godzin, problem rozwiąże się sam
+
## Uwagi
Dostęp do aktualnej wersji API nie jest zabezpieczony tokenem
diff --git a/src/README.md b/src/README.md
index 337106a..4abf79b 100644
--- a/src/README.md
+++ b/src/README.md
@@ -1,6 +1,6 @@
[](https://www.home-assistant.io/)
# [Energa meter](https://github.com/tcich/ha-addon-energa-meter) Home Assistant add-on
-# Wersja podstawowa
+# Wersja dev
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg
[armv6-shield]: https://img.shields.io/badge/armv6-yes-green.svg
@@ -13,10 +13,17 @@
![i386-shield]
+
+[kawa-logo]: https://github.com/tcich/ha-addon-energa-meter/blob/main/img/buycoffeeto-btn-primary-outline.png
+[kawa]: https://buycoffee.to/tcich
+
## O dodatku
To jest dodatek dla [Home Assistant](https://www.home-assistant.io/). Instalacja dodatku [Energa meter](https://github.com/tcich/ha-addon-energa-meter) umożliwia cykliczne pobieranie danych z aplikacji [Mój Licznik - Energa](https://mojlicznik.energa-operator.pl) udostępnianej klientom Operatora energetycznego Energa.
+### Wersja dev
+Wersja dev jest wersją developeską, nie należy jej używać w produkcyjnej wersji HA, może powodować różne problemy, może nie działać.
+
## Instalacja
1) Dodaj repozytorium do repozytoriów dodatków swojego HA za pomocą poniższego przycisku
@@ -45,64 +52,72 @@ Wymagane parametry:
## Konfiguracja sensorów
+Do HA możesz dodać sensory, które zawierają informacje udostępniane przez API
+Poniższa instrukcja zawiera założenia:
+* dodatek jest dostępny pod adresem *localhost* na porcie *8000*
+* ID Twojego licznika to *123456789*
+
+1) Ustal ID Twoich liczników, w tym celu przejdź do adresu Twojego HA na porcie 8000 lub innym jeźeli zmieniłeś go w konfiguracji, np. http://192.168.1.10:8000 wyświetli się w formacie json lista dostępnych liczników.
+2) W pliku configuration.yaml w HA dodaj następującą konfigurację np.:
```
sensor:
- platform: rest
- resource: http://localhost:8000/meters/12335379
- name: "Energia aktualna T1"
- unique_id: 12335379_sumz1
+ resource: http://localhost:8000/123456789/A%2B/1
+ name: "A+ Taryfa 1"
+ unique_id: 123456789_apt1
unit_of_measurement: "kWh"
- value_template: "{{ value_json.meter.zone1.meter | round(2) }}"
+ value_template: "{{ value_json.countner.meter_value | round(2) }}"
- platform: rest
- resource: http://localhost:8000/meters/12335379
- name: "Dzienny odczyt licznika"
- unique_id: 12335379_meterz1
+ resource: http://localhost:8000/123456789/A%2B/2
+ name: "A+ Taryfa 2"
+ unique_id: 123456789_apt2
unit_of_measurement: "kWh"
- value_template: "{{ value_json.meter.zone1.sum | round(2) }}"
- - platform: rest
- resource: http://localhost:8000/meters/12335379
- name: "Energia aktualna T2"
- unique_id: 12335379_sumz2
- unit_of_measurement: "kWh"
- value_template: "{{ value_json.meter.zone2.meter | round(2) }}"
- - platform: rest
- resource: http://localhost:8000/meters/12335379
- name: "Dzienny odczyt licznika"
- unique_id: 12335379_meterz2
- unit_of_measurement: "kWh"
- value_template: "{{ value_json.meter.zone2.sum | round(2) }}"
+ value_template: "{{ value_json.countner.meter_value | round(2) }}"
```
+## Suma liczników, bilans
+W celu uzyskania sumy liczników, bilansu, itp należy użyć templates:
+```
+template:
+ - sensor:
+ - name: "Suma liczników"
+ unit_of_measurement: "kWh"
+ state: "{{ states('sensor.123456789_apt1') | float + states('sensor.123456789_apt2') | float | round(2) }}"
+ - sensor:
+ - name: "Bilans/różnica liczników"
+ unit_of_measurement: "kWh"
+ state: "{{ states('sensor.123456789_apt1') | float - states('sensor.123456789_apt2') | float | round(2) }}"
+```
-# Opis konfiguracji
+
+### Opis konfiguracji
| element konfiguracji | Opis |
|-------------------|-------------------|
-| resource: http://localhost:8000/meters/12335379 | Adres API z danymi konkretnego licznika, podajemy nazwę instancji dockera (**Nazwa hosta** z okna dodatku) lub localhost|
-| name: "Energia aktualna" | Nazwa sensora, wpisz dowolną|
+| resource: http://localhost:8000/123456789/A%2B/1 | Adres API z danymi konkretnego licznika, podajemy **localhost** lub nazwę instancji dockera (**Nazwa hosta** z okna dodatku), port, id licznika, rodzaj pomiaru, taryfa|
+| name: "A+ Taryfa 1" | Nazwa sensora, wpisz dowolną|
| unique_id | Unikalny ID sensora, nie mogą być w systemie dwa sensory z tym samym ID|
| unit_of_measurement: "kWh" | Jednostka miary, nie zmieniaj chyba, że wiesz co robisz|
-| value_template: "{{ value_json.meter.zone2.meter \| round(2) }}" | Zaokrąglony do dwóch miejsc po przecinku stan sensora|
-
-# Opis konfiguracji cd
-| value_template | Opis |
-|-------------------|-------------------|
-| value_json.meter.zone1.sum | Suma licznika oraz dziennego zużycia dla tartfy1 (dostępne są: zone1, zone2, zone3)|
-| value_json.meter.zone2.meter | Stan licznika dziennego dla taryfy1 (dostępne są: zone1, zone2, zone3)|
-
+| value_template: "{{ value_json.meter.countner.meter_value \| round(2) }}" | Zaokrąglony do dwóch miejsc po przecinku stan sensora|
## API dla wykresów, np. Grafana
-Aby pobrać dane z API w formacie JSON należy użyć adresu http://home_assistant:8000/charts/12729?start_date=1695332400129&end_date=1697924583285
+Aby pobrać dane z API w formacie JSON należy użyć adresu http://home_assistant:8000/charts/12729?start_date=1695332400129&end_date=1697924583285&mp=123456789&zone=1
+
+### Opis konfiguracji
+| element konfiguracji | Opis |
+|-------------------|-------------------|
+| resource: http://localhost:8000/charts | Adres API z danymi do wykresów, podajemy **localhost** lub nazwę instancji dockera (**Nazwa hosta** z okna dodatku), port, id licznika, rodzaj pomiaru, taryfa|
+| start_date | data początkowa danych w formacie epoch (ms), domyślnie czas bieżący |
+| end_date | data końcowa danych w formacie epoch (ms), domyślnie czas bieżący - 1 dzień |
+| mp | numer licznika |
+| meter_type_url | typ licznika (np. A+: A%2B, A-: A- ) |
+| zone | numer strefy (np. 1, 2) |
-gdzie:
-* 12729 - jest to ID licznika
-* start_date - początek okresu w milisekundach wg. standardu EPOCH (timestamp)
-* end_date - koniec okresu w milisekundach wg. standardu EPOCH (timestamp)
## Grafana
-
Instrukcja konfiguracji dla Grafana znajduje się tutaj [link](https://github.com/tcich/ha-addon-energa-meter/tree/e702ed49436fb9e0b675dcac3001bd9de5aab3c0/src/INSTALL.md)
+
## Znane problemy
Czasami w aplikacji Mój Licznik włącza się captha (jeżeli masz dużo danych historycznych lub wielokrotnie instalujesz dodatek)
Dane wytwórcy (energia oddana oraz bilans) nie są dostępne, prace w tym zakresie trwają.
@@ -110,3 +125,4 @@ Dane wytwórcy (energia oddana oraz bilans) nie są dostępne, prace w tym zakre
## Uwagi
Dostęp do aktualnej wersji API nie jest zabezpieczony tokenem
Każde przeinstalowanie dodatku pobiera ponownie dane z aplikacji Mój Licznik
+
diff --git a/src/api.py b/src/api.py
index 1074f89..522a136 100644
--- a/src/api.py
+++ b/src/api.py
@@ -1,37 +1,67 @@
from peewee import SqliteDatabase
-from flask import Flask, jsonify, request, redirect, url_for
+from flask import Flask, jsonify, request, redirect, url_for, abort
from waitress import serve
-import time
-from datetime import datetime
-from moj_licznik import PPETable, MainChartTable
+#from datetime
+import datetime
+import time, os, logging
+from moj_licznik import PPETable, MeterTable, CounterTable, MainChartTable
+import urllib.parse
-DEBUG = False
+logger = logging.getLogger("energaMeter.api")
+
+
+path = os.path.dirname(os.path.abspath(__file__))
+db_file = 'database.sqlite'
+db = SqliteDatabase(os.path.join(path, db_file))
app = Flask(__name__)
-db = SqliteDatabase('database.sqlite')
-
@app.route('/', methods=['GET'])
-def root_redirect():
- query = PPETable.select().where(PPETable.is_active == True)
+def root():
+ query = PPETable.select() #.where(PPETable.is_active == True)
result_ppes = list(query)
- meters = []
-
+ ppes = []
for p in result_ppes:
- meter = {
- 'name': p.name,
- 'id': p.id
- }
+ meters_query = MeterTable.select().where(MeterTable.ppe_id == p.id)
+ meter_result = meters_query.execute()
+ meters = []
+ for meter in meter_result:
+ countners_query = CounterTable.select().where(CounterTable.meter_id == meter.id)
+ countners_result = countners_query.execute()
+ countners = []
+ for countner in countners_result:
+ countner = {
+ 'tariff': countner.tariff,
+ 'measurement_date': countner.measurement_date,
+ 'meter_value': countner.meter_value
+ }
+ countners.append(countner)
- meters.append(meter)
- if DEBUG:
- print("API: GET /")
+ meter = {
+ 'meter_type': meter.meter_type,
+ 'meter_type_url': urllib.parse.quote_plus(meter.meter_type),
+ 'last_update_date': meter.last_update_date,
+ 'first_date': meter.first_date,
+ 'countners': countners
+ }
+ meters.append(meter)
+
+ ppe = {
+ 'name': p.name,
+ 'id': p.id,
+ 'type': p.type,
+ 'isActive': p.is_active,
+ 'meters': meters
+ }
+ ppes.append(ppe)
+ logger.debug("API: GET /")
- return jsonify({'meters': meters})
+ return jsonify({'ppes': ppes})
@app.route('/meters', methods=['GET'])
+@app.route('/meters/', methods=['GET'])
def meters():
- query = PPETable.select().where(PPETable.is_active == True)
+ query = PPETable.select() #.where(PPETable.is_active == True)
result_ppes = list(query)
meters = []
@@ -40,11 +70,11 @@ def meters():
'name': p.name,
'id': p.id,
'ppe': p.ppe,
- 'number_of_zones': p.number_of_zones,
+ 'number_of_zones': '',#p.number_of_zones,
'tariffCode': p.tariffCode,
- 'first_date': p.first_date,
+ # 'first_date': p.first_date,
'last_update_date': p.last_update_date,
- 'measurement_date': p.measurement_date,
+ # 'measurement_date': p.measurement_date,
}
for i in range(1, p.number_of_zones + 1):
@@ -65,139 +95,148 @@ def meters():
}
meters.append(meter)
- if DEBUG:
- print("API: GET /")
+ logger.debug("GET /meters")
return jsonify({'meters': meters})
-@app.route('/meters/', methods=['GET'])
-def get_meter(meter_id):
- query = PPETable.select().where((PPETable.is_active == True) & (PPETable.id == meter_id))
- result_ppes = list(query)
- if result_ppes:
- p = result_ppes[0] # There should be only one matching record
+@app.route('/', methods=['GET'])
+@app.route('//', methods=['GET'])
+def get_ppe(ppe_id):
+ meters_query = MeterTable.select().where(MeterTable.ppe_id == ppe_id)
+ meter_result = meters_query.execute()
+
+ if not meter_result:
+ abort(404)
+
+ meters = []
+ for meter in meter_result:
+ countners_query = CounterTable.select().where(CounterTable.meter_id == meter.id)
+ countners_result = countners_query.execute()
+ countners = []
+ for countner in countners_result:
+ countner = {
+ 'tariff': countner.tariff,
+ 'measurement_date': countner.measurement_date,
+ 'meter_value': countner.meter_value
+ }
+ countners.append(countner)
+
+ meter = {
+ 'meter_type': meter.meter_type,
+ 'meter_type_url': urllib.parse.quote_plus(meter.meter_type),
+ 'last_update_date': meter.last_update_date,
+ 'first_date': meter.first_date,
+ 'countners': countners
+ }
+ meters.append(meter)
+ logger.debug(f"API: GET /{ppe_id}")
+ return jsonify({'meters': meters})
+
+
+@app.route('//', methods=['GET'])
+@app.route('///', methods=['GET'])
+def get_meter_type(ppe_id, meter_type_url):
+ meter_type = urllib.parse.unquote(meter_type_url)
+ meters_query = MeterTable.select().where((MeterTable.ppe_id == str(ppe_id)) & (MeterTable.meter_type == meter_type))
+ meter_result = meters_query.execute()
+ if not meter_result:
+ abort(404)
+
+ meter = meter_result[0]
+ countners_query = CounterTable.select().where(CounterTable.meter_id == meter.id)
+ countners_result = countners_query.execute()
+ countners = []
+ for countner in countners_result:
+ countner = {
+ 'tariff': countner.tariff,
+ 'measurement_date': countner.measurement_date,
+ 'meter_value': countner.meter_value
+ }
+ countners.append(countner)
meter = {
- 'name': p.name,
- 'id': p.id,
- 'ppe': p.ppe,
- 'number_of_zones': p.number_of_zones,
- 'tariffCode': p.tariffCode,
- 'first_date': p.first_date,
- 'last_update_date': p.last_update_date,
- 'measurement_date': p.measurement_date
+ 'meter_type': meter.meter_type,
+ 'meter_type_url': urllib.parse.quote_plus(meter.meter_type),
+ 'last_update_date': meter.last_update_date,
+ 'first_date': meter.first_date,
+ 'countners': countners
}
- for i in range(1, p.number_of_zones + 1):
- zone_key = f'zone{i}'
- daily_chart_key = f'zone{i}_daily_chart_sum'
-
- zone_value = getattr(p, zone_key)
- daily_chart_value = getattr(p, daily_chart_key)
-
- # Zamień None na zero podczas obliczania sumy
- zone_value = float(zone_value) if zone_value is not None else 0
- daily_chart_value = float(daily_chart_value) if daily_chart_value is not None else 0
-
- meter[zone_key] = {
- 'meter': zone_value,
- 'daily_chart': daily_chart_value,
- 'sum': zone_value + daily_chart_value
- }
-
- print(f"API: GET /meters/{meter_id}")
+ logger.debug(f"API: GET /{ppe_id}/{meter_type_url}")
return jsonify({'meter': meter})
- else:
- return jsonify({'error': 'Meter not found'}, 404)
+
+@app.route('///', methods=['GET'])
+@app.route('////', methods=['GET'])
+def get_countners(ppe_id, meter_type_url, tariff):
+ meter_type = urllib.parse.unquote(meter_type_url)
+ meters_query = MeterTable.select().where((MeterTable.ppe_id == str(ppe_id)) & (MeterTable.meter_type == meter_type))
+ meter_result = meters_query.execute()
+
+ if not meter_result:
+ abort(404)
+
+ meter = meter_result[0]
+ countners_query = CounterTable.select().where((CounterTable.meter_id == meter.id) & (CounterTable.tariff == tariff))
+ countners_result = countners_query.execute()
+ countner = countners_result[0]
+ countner = {
+ 'tariff': countner.tariff,
+ 'measurement_date': countner.measurement_date,
+ 'meter_value': countner.meter_value
+ }
+
+ logger.debug(f"API: GET /{ppe_id}/{meter_type_url}/{countner}")
+ return jsonify({'countner': countner})
-# @app.route('/meters/', methods=['GET'])
-# def get_meter(meter_id):
-# query = PPETable.select().where((PPETable.is_active == True) & (PPETable.id == meter_id))
-# result_ppes = list(query)
-# if result_ppes:
-# p = result_ppes[0] # There should be only one matching record
+@app.route('/charts', methods=['GET'])
+@app.route('/charts/', methods=['GET'])
+def charts():
+
-# meter = {
-# 'name': p.name,
-# 'id': p.id,
-# 'ppe': p.ppe,
-# 'number_of_zones': p.number_of_zones,
-# 'tariffCode': p.tariffCode,
-# 'first_date': p.first_date,
-# 'last_update_date': p.last_update_date,
-# 'measurement_date': p.measurement_date
-# }
+ current_time = datetime.datetime.now()
+ current_time_unix = time.mktime(current_time.timetuple())
+ start_time = current_time - datetime.timedelta(days=1)
+ start_time_unix = time.mktime(start_time.timetuple())
+ start_date = request.args.get('start_date', start_time_unix*1000)
+ end_date = request.args.get('end_date', current_time_unix*1000)
+ mp = request.args.get('mp', None)
+ meter_type_url = request.args.get('meter_type_url', None)
+ zone = request.args.get('zone', None)
+ negative = request.args.get('negative', type=bool, default=False)
+ logger.debug(f"API: GET /charts - {start_date} - {end_date}")
+ query = MainChartTable.select().where((MainChartTable.tm >= int(start_date)) & (MainChartTable.tm <= int(end_date)))
+ logger.debug(f"{query}")
+ factor = 1
+ if negative:
+ factor = -1
+ if mp:
+ query = query.where(MainChartTable.mp == mp)
-# for i in range(1, p.number_of_zones + 1):
-# zone_key = f'zone{i}'
-# daily_chart_key = f'zone{i}_daily_chart_sum'
+ if meter_type_url:
+ meter_type = urllib.parse.unquote(meter_type_url)
+ query = query.where(MainChartTable.meter_type == meter_type)
-# meter[zone_key] = getattr(p, zone_key)
-# meter[daily_chart_key] = getattr(p, daily_chart_key)
+ if zone:
+ query = query.where(MainChartTable.zone == zone)
-# print(f"API: GET /meters/{meter_id}")
-# return jsonify({'meter': meter})
-# else:
-# return jsonify({'error': 'Meter not found'}, 404)
-
-@app.route('/charts/', methods=['GET'])
-def charts(mp):
- start_time = time.time()
- current_time = time.localtime()
- start_date = request.args.get('start_date', (time.mktime(current_time) - 864000))
- end_date = request.args.get('end_date', (time.mktime(current_time)))
- query = MainChartTable.select().where((MainChartTable.mp == mp) & (MainChartTable.tm >= start_date) & (MainChartTable.tm <= end_date))
result_ppes = list(query)
charts = []
for p in result_ppes:
czas = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(p.tm/1000))
-
chart = {
'mp': p.mp,
+ 'meter_type': p.meter_type,
+ 'meter_type_url': urllib.parse.quote_plus(p.meter_type),
'zone': p.zone,
- 'tm': p.tm,
- 'czas': czas,
- 'value': p.value
+ 'time_tm': p.tm,
+ 'time': czas,
+ 'value': p.value * factor
}
charts.append(chart)
end_time = time.time()
- if DEBUG:
- print(f"API: GET / - {start_date} - {end_date}")
-
return jsonify({'charts': charts})
-
-@app.route('//', methods=['GET'])
-def charts_zone(mp, zone):
- start_time = time.time()
- current_time = time.localtime()
- start_date = request.args.get('start_date', (time.mktime(current_time) - 864000))
- end_date = request.args.get('end_date', (time.mktime(current_time)))
- query = MainChartTable.select().where((MainChartTable.mp == mp) & (MainChartTable.tm >= start_date) & (MainChartTable.tm <= end_date) & (MainChartTable.zone == zone))
- result_ppes = list(query)
- charts = []
-
- for p in result_ppes:
- czas = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(p.tm/1000))
-
- chart = {
- 'mp': p.mp,
- 'zone': p.zone,
- 'tm': p.tm,
- 'czas': czas,
- 'value': p.value
- }
- charts.append(chart)
- end_time = time.time()
-
- if DEBUG:
- print(f"API: GET / - {start_date} - {end_date}")
-
- return jsonify({'charts': charts})
-
-if __name__ == "__main__":
- serve(app, host="0.0.0.0", port=8000, threads=8)
\ No newline at end of file
diff --git a/src/config.yaml b/src/config.yaml
index 99aa6a6..19a6062 100644
--- a/src/config.yaml
+++ b/src/config.yaml
@@ -1,14 +1,16 @@
name: "Energa meter"
description: "Energa meter addon"
-version: "0.1.1"
+version: "1.0.0"
slug: "energa_meter"
init: false
options:
energa_username: ""
energa_password: ""
+ log_level: "INFO"
schema:
energa_username: str
energa_password: password
+ log_level: str
arch:
- aarch64
- amd64
diff --git a/src/cron.py b/src/cron.py
deleted file mode 100644
index f2ea842..0000000
--- a/src/cron.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import configparser, time, datetime, os
-from moj_licznik import MojLicznik
-from pathlib import Path
-
-def main():
- plik = Path('config.ini')
- username = None
- password = None
- if plik.is_file():
- print(f"Pobieram parametry z config.ini.")
- config = configparser.ConfigParser()
- config.read("config.ini")
- username = config.get("Credentials", "username")
- password = config.get("Credentials", "password")
- else:
- username = os.getenv("USERNAME")
- password = os.getenv("PASSWORD")
- try:
- mojLicznik = MojLicznik()
- print(f"Update...{datetime.datetime.now()}")
- print(f"Logowanie...")
- mojLicznik.login(username, password)
- if mojLicznik.loginStatus:
- print(f"Aktualizacja danych bieżących...")
- mojLicznik.uppdate_measurments()
- mojLicznik.update_last_days()
- mojLicznik.set_daily_zones()
- mojLicznik.logout()
- except:
- print("Błąd aktualizacji danych...")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/src/img/grafana_02.png b/src/img/grafana_02.png
index f17d352..72e64e6 100644
Binary files a/src/img/grafana_02.png and b/src/img/grafana_02.png differ
diff --git a/src/img/grafana_03.png b/src/img/grafana_03.png
index 8fbd681..603c262 100644
Binary files a/src/img/grafana_03.png and b/src/img/grafana_03.png differ
diff --git a/src/img/grafana_06.png b/src/img/grafana_06.png
index a0bc00d..a4a9233 100644
Binary files a/src/img/grafana_06.png and b/src/img/grafana_06.png differ
diff --git a/src/img/grafana_07.png b/src/img/grafana_07.png
deleted file mode 100644
index f4ba9ba..0000000
Binary files a/src/img/grafana_07.png and /dev/null differ
diff --git a/srcdev/img/grafana_08.png b/src/img/grafana_08.png
similarity index 100%
rename from srcdev/img/grafana_08.png
rename to src/img/grafana_08.png
diff --git a/srcdev/img/grafana_09.png b/src/img/grafana_09.png
similarity index 100%
rename from srcdev/img/grafana_09.png
rename to src/img/grafana_09.png
diff --git a/srcdev/img/grafana_10.png b/src/img/grafana_10.png
similarity index 100%
rename from srcdev/img/grafana_10.png
rename to src/img/grafana_10.png
diff --git a/srcdev/log_config.py b/src/log_config.py
similarity index 100%
rename from srcdev/log_config.py
rename to src/log_config.py
diff --git a/src/main.py b/src/main.py
index 8cd9209..6703d12 100644
--- a/src/main.py
+++ b/src/main.py
@@ -3,33 +3,24 @@ from moj_licznik import MojLicznik
from pathlib import Path
def main():
- plik = Path('config.ini')
username = None
password = None
- if plik.is_file():
- print(f"Pobieram parametry z config.ini.")
- config = configparser.ConfigParser()
- config.read("config.ini")
- username = config.get("Credentials", "username")
- password = config.get("Credentials", "password")
- else:
- username = os.getenv("USERNAME")
- password = os.getenv("PASSWORD")
+ username = os.getenv("USERNAME")
+ password = os.getenv("PASSWORD")
print(f"Inicjacja...")
mojLicznik = MojLicznik()
print(f"Logowanie...", username)
mojLicznik.login(username, password)
- if mojLicznik.loginStatus:
- print(f"Aktualizacja liczników...")
- mojLicznik.uppdate_measurments()
- print(f"Wyszukiwanie najstarszych danych...")
- mojLicznik.update_first_date()
- print(f"Pobieranie danych...")
- mojLicznik.download_charts(True)
- mojLicznik.update_last_days()
- mojLicznik.set_daily_zones()
- mojLicznik.logout()
-
+ print(f"Aktualizacja liczników...")
+ mojLicznik.uppdate_measurments()
+ print(f"Wyszukiwanie najstarszych danych...")
+ mojLicznik.update_first_date()
+ print(f"Pobieranie danych...")
+ mojLicznik.download_charts(True)
+ mojLicznik.update_last_days()
+ mojLicznik.set_daily_zones()
+ mojLicznik.logout()
+
if __name__ == "__main__":
main()
\ No newline at end of file
diff --git a/src/moj_licznik.py b/src/moj_licznik.py
index 4450756..d10bf26 100644
--- a/src/moj_licznik.py
+++ b/src/moj_licznik.py
@@ -1,13 +1,18 @@
from peewee import SqliteDatabase
from datetime import datetime, timedelta, date
-import calendar, requests, re, time, json
+import calendar, requests, re, time, json, os, logging
import http.cookiejar as cookiejar
from requests.exceptions import HTTPError
from bs4 import BeautifulSoup
from enum import Enum
-from peewee import Model, CharField, IntegerField, DateField, BooleanField, CompositeKey, DecimalField
+from peewee import AutoField, Model, CharField, IntegerField, DateField, BooleanField, CompositeKey, DecimalField, ForeignKeyField, SQL
+import urllib.parse
-db = SqliteDatabase('database.sqlite')
+logger = logging.getLogger("energaMeter")
+
+path = os.path.dirname(os.path.abspath(__file__))
+db_file = 'database.sqlite'
+db = SqliteDatabase(os.path.join(path, db_file))
class ChartType(Enum):
DAY = "DAY"
@@ -15,26 +20,54 @@ class ChartType(Enum):
YEAR = "YEAR"
class PPETable(Model):
- ppe = CharField()
+ id = CharField(primary_key=True)
+ ppe = CharField(unique=True)
tariffCode = CharField()
+ type = CharField()
name = CharField()
- zone1 = DecimalField(max_digits=15, decimal_places=5, null=True)
- zone2 = DecimalField(max_digits=15, decimal_places=5, null=True)
- zone3 = DecimalField(max_digits=15, decimal_places=5, null=True)
- zone1_daily_chart_sum = DecimalField(max_digits=10, decimal_places=5, null=True)
- zone2_daily_chart_sum = DecimalField(max_digits=10, decimal_places=5, null=True)
- zone3_daily_chart_sum = DecimalField(max_digits=10, decimal_places=5, null=True)
- number_of_zones = IntegerField(default=0)
+ last_update_date = DateField(null=True)
is_active = BooleanField(default=True)
- measurement_date = DateField(null=True)
- first_date = DateField(null=True)
- last_update_date = DateField(null=True)
class Meta:
database = db
+ table_name = 'PPE'
+ constraints = [SQL('UNIQUE (ppe, tariffCode)')]
+
+class MeterTable(Model):
+ id = AutoField() # Meter point
+ ppe_id = ForeignKeyField(PPETable, backref='zones')
+ meter_type = CharField()
+ last_update_date = DateField(null=True)
+ first_date = DateField(null=True)
+
+ class Meta:
+ database = db
+ table_name = 'METER'
+ constraints = [SQL('UNIQUE (ppe_id, meter_type)')]
+
+class CounterTable(Model):
+ id = AutoField()
+ meter_id = ForeignKeyField(MeterTable, backref='meter')
+ tariff = CharField()
+ measurement_date = DateField(null=True)
+ meter_value = DecimalField(max_digits=15, decimal_places=5, null=True)
+
+ class Meta:
+ database = db
+ table_name = 'COUNTER'
+
+# class CounterTable(Model):
+# meter_id = ForeignKeyField(MeterTable, backref='zones')
+# measurement_date = DateField(null=True)
+# meter_value = DecimalField(max_digits=15, decimal_places=5, null=True)
+
+# class Meta:
+# database = db
+
class ChartTable(Model):
- id = IntegerField()
+ id = IntegerField()
+ meter_type = CharField()
year = IntegerField()
month = IntegerField(null=True)
day = IntegerField(null=True)
@@ -42,10 +75,12 @@ class ChartTable(Model):
class Meta:
database = db
+ table_name = 'CHART_CACHE'
primary_key = CompositeKey('id', 'year', 'month', 'day')
class MainChartTable(Model):
mp = CharField()
+ meter_type = CharField()
zone = IntegerField()
tm = IntegerField()
value = DecimalField(max_digits=20, decimal_places=16, null=True)
@@ -55,9 +90,74 @@ class MainChartTable(Model):
class Meta:
database = db
- primary_key = CompositeKey('mp', 'zone', 'tm')
+ table_name = 'CHART'
+ primary_key = CompositeKey('mp', 'meter_type', 'zone', 'tm')
+def znajdz_typ_odbiorcy(element):
+ typ_odbiorcy = ''
+ div_elements = element.find_all('div', recursive=False)
+ for div_element in div_elements:
+ typ_span = div_element.find('span', text='Typ')
+ if typ_span:
+ typ_odbiorcy = typ_span.next_sibling.strip()
+ return typ_odbiorcy
+ typ_odbiorcy = znajdz_typ_odbiorcy(div_element) # Rekurencyjne przeszukiwanie zagnieżdżonych div
+ return typ_odbiorcy
+def findCountners(page):
+ table = page.find('table')
+ countner_type_list = ["A-", "A+"]
+ data_list = []
+
+ # Jeśli znaleźliśmy tabelę, możemy przeszukać jej wiersze i komórki
+ if table:
+ for row in table.find_all('tr'):
+ cells = row.find_all('td')
+ if len(cells) > 1:
+ # Pobieramy opis z pierwszej komórki
+ description = cells[0].text.strip()
+
+ # Pomijamy, jeśli rodzaj_licznika jest pusty
+ if not description:
+ continue
+
+ # Usuwamy datę z opisu
+ description_parts = description.split('\n')
+ meter_type = description_parts[0][:2].strip()
+
+ if meter_type not in countner_type_list:
+ continue
+
+ tariff0 = description_parts[0][2:].strip()
+ tariff = ''.join(filter(str.isdigit, tariff0))
+ measurement_date = description_parts[1].strip()
+
+ # Pobieramy dane liczbowe z drugiej komórki
+ data = cells[1].text.strip()
+
+ # Usuwamy znaki nowej linii i spacje z danych
+ data = data.replace('\n', '').replace(' ', '')
+
+ data = data.replace(',', '.')
+
+ # Dzielimy dane na część całkowitą i część dziesiętną
+ parts = data.split('.')
+ if len(parts) == 2:
+ integer_part = parts[0]
+ decimal_part = parts[1]
+ else:
+ integer_part = parts[0]
+ decimal_part = '0'
+
+ data_dict = {
+ "meter_type": meter_type,
+ "tariff": tariff,
+ "measurement_date": measurement_date,
+ "meter_value": f"{integer_part}.{decimal_part}"
+ }
+
+ data_list.append(data_dict)
+ return data_list
class MojLicznik:
@@ -66,9 +166,13 @@ class MojLicznik:
meter_url = "https://mojlicznik.energa-operator.pl"
+
+
def databaseInit(self):
- db.create_tables([ChartTable], safe=True)
db.create_tables([PPETable], safe=True)
+ db.create_tables([MeterTable], safe=True)
+ db.create_tables([CounterTable], safe=True)
+ db.create_tables([ChartTable], safe=True)
db.create_tables([MainChartTable], safe=True)
@@ -87,13 +191,18 @@ class MojLicznik:
login_url = f"{self.meter_url}/dp/UserLogin.do"
+ self.loginStatus = False
+
try:
+ logger.debug("Pobieram formularz logowania.")
response = self.session.get(login_url)
response.raise_for_status()
- print(f"Logowanie rozpoczęte.")
+ if response.url == 'https://mojlicznik.energa-operator.pl/maintenance.html':
+ logger.critical("Trwają prace serwisowe w Mój Licznik. Logowanie nie jest możliwe, spróbuj później.")
+ return
except HTTPError as e:
- print(f"Wystąpił błąd HTTP: {e}")
+ logger.error(f"Wystąpił błąd HTTP: {e}")
soup = BeautifulSoup(response.text, 'html.parser')
csrf_token = soup.find('input', {'name': '_antixsrf'})['value']
@@ -113,7 +222,7 @@ class MojLicznik:
except HTTPError as e:
- print(f"Wystąpił błąd HTTP: {e}")
+ logger.error(f"Wystąpił błąd HTTP: {e}")
soup = BeautifulSoup(response.text, 'html.parser')
@@ -121,12 +230,16 @@ class MojLicznik:
login_error = soup.find('div', text=login_error_text)
if login_error:
- self.loginStatus = False
- print(login_error_text)
+ logger.critical(login_error_text)
+ return
else:
self.loginStatus = True
- print(f"Zalogowano")
+ logger.info(f"Zalogowano")
+ body = soup.find('body')
+ type_value = znajdz_typ_odbiorcy(body)
+
+ logger.debug(f"Typ umowy: {type_value}.")
select_elements = soup.find_all('script', type='text/javascript')
meter_isd = []
for el in select_elements:
@@ -140,16 +253,17 @@ class MojLicznik:
meter_isd.append(id_value)
retrieved_record = PPETable.get_or_none(id=id_value)
if retrieved_record:
- print(f"Licznik {id_value} istnieje w systemie.")
+ logger.info(f"Licznik {id_value} istnieje w systemie.")
if not retrieved_record.is_active:
retrieved_record.is_active = True
retrieved_record.save()
else:
- print(f"Licznik {id_value} nie istnieje w systemie.")
+ logger.info(f"Licznik {id_value} nie istnieje w systemie, zostanie dodany.")
data = PPETable.create(
id=id_value,
ppe=ppe_value,
tariffCode=tariffCode_value,
+ type=type_value,
name=name_value
)
update_query = PPETable.update(is_active=0).where(PPETable.id.not_in(meter_isd))
@@ -161,11 +275,11 @@ class MojLicznik:
response = self.session.get(logout_url)
response.raise_for_status()
self.loginStatus = False
- print(f"Wylogowano.")
+ logger.info(f"Wylogowano.")
except HTTPError as e:
- print(f"Wystąpił błąd HTTP: {e}")
+ logger.error(f"Wystąpił błąd HTTP: {e}")
- def uppdate_measurments(self):
+ def update_countners(self):
query = PPETable.select().where(PPETable.is_active == True)
result_ppes = query.execute()
@@ -175,87 +289,86 @@ class MojLicznik:
response = self.session.get(meter_url)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
- td_elements = soup.find_all('td', class_='last')
- date_divs = soup.find_all("div", style="font-size: 10px")
+ countners_dict = findCountners(soup)
- for div in date_divs:
- p.measurement_date = datetime.strptime(div.text.strip(), "%Y-%m-%d %H:%M").date()
- i = 0
-
- for td in td_elements:
- text = td.get_text()
- cleaned_text = re.sub(r'[^\d,]', '', text)
- cleaned_number_str = cleaned_text.lstrip('0').replace(',', '.')
- i = i + 1
- if i == 1:
- p.zone1 = float(cleaned_number_str)
- p.number_of_zones = 1
- elif i == 2:
- p.zone2 = float(cleaned_number_str)
- p.number_of_zones = 2
- elif i == 3:
- p.zone3 = float(cleaned_number_str)
- p.number_of_zones = 1
- p.last_update_date = datetime.now()
- p.save()
- print(f"Zapisano stan licznika {p.name} na dzień: {p.measurement_date}")
+ for c in countners_dict:
+ mn, mu = MeterTable.get_or_create(ppe_id=p.id, meter_type=c['meter_type'])
+ mn.last_update_date = datetime.now()
+ mn.save()
+ cn, cu = CounterTable.get_or_create(
+ meter_id = mn.id,
+ tariff=c['tariff']
+ )
+
+ cn.meter_value = c['meter_value']
+ cn.measurement_date = c['measurement_date']
+ cn.save()
+
+ logger.info(f"Zapisano stan licznika {p.id} {c['meter_type']} taryfa {c['tariff']} z dnia: {c['measurement_date']} : {c['meter_value']}")
except HTTPError as e:
- print(f"Wystąpił błąd HTTP: {e}")
+ logger.error(f"Wystąpił błąd HTTP: {e}")
def update_first_date(self):
- query = PPETable.select().where(PPETable.first_date.is_null(True) & (PPETable.is_active == True))
- result_ppes = query.execute()
+ ppes_query = PPETable.select().where(PPETable.is_active == True)
+ result_ppes = ppes_query.execute()
for p in result_ppes:
- print(f"Szukam najstarsze dane historyczne licznika {p.name}")
- meter_point = p.id
- max_years_back = 5
- start_date = datetime.now()
- last_chart_year = None
- for n in range(max_years_back + 1):
- first_day_of_year = datetime(start_date.year-n, 1, 1)
- data_json = self.download_chart(ChartType.YEAR, first_day_of_year, meter_point)
- if data_json:
- data = json.loads(data_json)
- if data and data.get("mainChart") and len(data["mainChart"]) > 0:
- last_chart_year = first_day_of_year.year
- last_chart_month = None
- max_month = 12
- for n in range(max_month, 0, -1):
- first_day_of_month = datetime(last_chart_year, n, 1)
- data_json = self.download_chart(ChartType.MONTH, first_day_of_month, meter_point)
- if data_json:
- data = json.loads(data_json)
- if data and data.get("mainChart") and len(data["mainChart"]) > 0:
- last_chart_month = n
- last_chart_day = None
- max_day = 31
- first_day_of_day = datetime(last_chart_year, last_chart_month, 1)
- _, max_day = calendar.monthrange(first_day_of_day.year, first_day_of_day.month)
- for n in range(max_day, 0, -1):
- first_day_of_day = datetime(last_chart_year, last_chart_month, n)
- data_json = self.download_chart(ChartType.DAY, first_day_of_day, meter_point)
- if data_json:
- data = json.loads(data_json)
- if data and data.get("mainChart") and len(data["mainChart"]) > 0:
- last_chart_day = n
- first_date = datetime(last_chart_year, last_chart_month, last_chart_day).date()
- print(f"Najstarsze dane historyczne dla licznika {p.name}: {first_date}")
- p.first_date = first_date
- p.save()
+ meters_query = MeterTable.select().where((MeterTable.ppe_id == p.id) & (MeterTable.first_date.is_null(True)))
+ meters_result = meters_query.execute()
- def save_main_charts(self, mp, vals):
+ for meter in meters_result:
+ meter_type = meter.meter_type
+
+ print(f"Szukam najstarsze dane historyczne licznika {p.name} (PPE: {p.ppe}, {p.id}) typ: {meter_type}")
+ meter_point = p.id
+ max_years_back = 5
+ start_date = datetime.now()
+ last_chart_year = None
+ for n in range(max_years_back + 1):
+ first_day_of_year = datetime(start_date.year-n, 1, 1)
+ data_json = self.download_chart(ChartType.YEAR, first_day_of_year, meter_point, meter_type)
+ if data_json:
+ data = json.loads(data_json)
+ if data and data.get("mainChart") and len(data["mainChart"]) > 0:
+ last_chart_year = first_day_of_year.year
+ last_chart_month = None
+ max_month = 12
+ for n in range(max_month, 0, -1):
+ first_day_of_month = datetime(last_chart_year, n, 1)
+ data_json = self.download_chart(ChartType.MONTH, first_day_of_month, meter_point, meter_type)
+ if data_json:
+ data = json.loads(data_json)
+ if data and data.get("mainChart") and len(data["mainChart"]) > 0:
+ last_chart_month = n
+ last_chart_day = None
+ max_day = 31
+ first_day_of_day = datetime(last_chart_year, last_chart_month, 1)
+ _, max_day = calendar.monthrange(first_day_of_day.year, first_day_of_day.month)
+ for n in range(max_day, 0, -1):
+ first_day_of_day = datetime(last_chart_year, last_chart_month, n)
+ data_json = self.download_chart(ChartType.DAY, first_day_of_day, meter_point, meter_type)
+ if data_json:
+ data = json.loads(data_json)
+ if data and data.get("mainChart") and len(data["mainChart"]) > 0:
+ last_chart_day = n
+ first_date = datetime(last_chart_year, last_chart_month, last_chart_day).date()
+ print(f"Najstarsze dane historyczne dla licznika {p.name} (PPE: {p.ppe}, {p.id}) typ: {meter_type}: {first_date}")
+ meter.first_date = first_date
+ meter.save()
+
+ def save_main_charts(self, mp, vals, m_type):
for val in vals:
- #try:
+ try:
+ logger.debug(f"save_main_charts: mp: {mp}, val: {val}, meter_type: {m_type}")
z = val["zones"]
- # {"tm": "1690412400000", "tarAvg": 0.3899153269199055, "zones": [null, 0.232, null], "est": false, "cplt": true},
if z[0]:
# MainChartTable.get_or_create(tm = val["tm"], zone = 1, value = z[0], tarAvg=val["tarAvg"], est=val["est"], cplt=val["cplt"])
try:
- existing_record = MainChartTable.get((MainChartTable.mp == mp) & (MainChartTable.tm == val["tm"]) & (MainChartTable.zone == 1))
+ existing_record = MainChartTable.get((MainChartTable.meter_type == m_type) & (MainChartTable.mp == mp) & (MainChartTable.tm == val["tm"]) & (MainChartTable.zone == 1))
except MainChartTable.DoesNotExist:
# Jeśli rekord nie istnieje, utwórz nowy
MainChartTable.create(
mp=mp,
+ meter_type=m_type,
tm=val["tm"],
zone=1,
value=z[0],
@@ -266,11 +379,12 @@ class MojLicznik:
if z[1]:
try:
- existing_record = MainChartTable.get((MainChartTable.mp == mp) & (MainChartTable.tm == val["tm"]) & (MainChartTable.zone == 2))
+ existing_record = MainChartTable.get((MainChartTable.meter_type == m_type) & (MainChartTable.mp == mp) & (MainChartTable.tm == val["tm"]) & (MainChartTable.zone == 2))
except MainChartTable.DoesNotExist:
# Jeśli rekord nie istnieje, utwórz nowy
MainChartTable.create(
mp=mp,
+ meter_type=m_type,
tm=val["tm"],
zone=2,
value=z[1],
@@ -281,11 +395,12 @@ class MojLicznik:
if z[2]:
try:
- existing_record = MainChartTable.get((MainChartTable.mp == mp) & (MainChartTable.tm == val["tm"]) & (MainChartTable.zone == 1))
+ existing_record = MainChartTable.get((MainChartTable.meter_type == m_type) & (MainChartTable.mp == mp) & (MainChartTable.tm == val["tm"]) & (MainChartTable.zone == 3))
except MainChartTable.DoesNotExist:
# Jeśli rekord nie istnieje, utwórz nowy
MainChartTable.create(
mp=mp,
+ meter_type=m_type,
tm=val["tm"],
zone=3,
value=z[2],
@@ -294,12 +409,12 @@ class MojLicznik:
cplt=val["cplt"]
)
- #except:
- # pass
+ except Exception as e:
+ logging.error(f"Wystąpił błąd: {str(e)}")
return None
- def download_chart(self, type, date, meter_point, update_mode=False):
+ def download_chart(self, type, date, meter_point, meter_type, update_mode=False):
if type == ChartType.DAY:
chart_type = "DAY"
@@ -316,7 +431,9 @@ class MojLicznik:
first_day = datetime(date.year, 1, 1)
tsm_date = int(time.mktime(first_day.timetuple()) * 1000)
- chart_url = f"{self.meter_url}/dp/resources/chart?mainChartDate={tsm_date}&type={chart_type}&meterPoint={meter_point}&mo=A%2B"
+ # meter_type = 'A+'
+ chart_url = f"{self.meter_url}/dp/resources/chart?mainChartDate={tsm_date}&type={chart_type}&meterPoint={meter_point}&mo={urllib.parse.quote_plus(meter_type)}"
+ logger.debug(f"chart_url: {chart_url}")
try:
response = self.session.get(chart_url)
data = json.loads(response.text)
@@ -326,7 +443,7 @@ class MojLicznik:
mainChartDate = data["response"]["mainChartDate"]
mainChart = data["response"]["mainChart"]
if type == ChartType.DAY:
- self.save_main_charts(meter_point, mainChart)
+ self.save_main_charts(meter_point, mainChart, meter_type)
date = int(mainChartDate) / 1000
month = None
@@ -345,35 +462,42 @@ class MojLicznik:
chart_record.value = json_dump
chart_record.save()
except ChartTable.DoesNotExist:
- chart_record = ChartTable.create(id=id, value=json_dump, year=year, month=month, day=day)
+ chart_record = ChartTable.create(id=id, meter_type=meter_type, value=json_dump, year=year, month=month, day=day)
else:
try:
- ChartTable.create(id=id, value=json_dump, year=year, month=month, day=day)
+ ChartTable.create(id=id, meter_type=meter_type, value=json_dump, year=year, month=month, day=day)
except:
pass
return json_dump
return None
except HTTPError as e:
- print(f"Wystąpił błąd HTTP: {e}")
+ logger.error(f"Wystąpił błąd HTTP: {e}")
def download_charts(self, full_mode=False):
query = PPETable.select().where(PPETable.is_active == True)
result_ppes = query.execute()
for p in result_ppes:
- current_date = p.first_date
- if not full_mode:
- current_date = p.measurement_date - timedelta(days=1)
-
- while current_date <= date.today():
- try:
- record = ChartTable.get(id=p.id, year=current_date.year, month=current_date.month, day=current_date.day)
- # Jeśli rekord o określonych wartościach klucza głównego istnieje, zostanie pobrany.
- print(f"Posiadam dane historyczne dla {p.name} na dzień: {current_date}")
- except ChartTable.DoesNotExist:
- self.download_chart(ChartType.DAY, current_date, p.id)
- print(f"Pobieram dane historyczne dla {p.name} na dzień: {current_date}")
- current_date += timedelta(days=1)
+ meters_query = MeterTable.select().where((MeterTable.ppe_id == p.id)) # // & (MeterTable.first_date.is_null(True)))
+ meters_result = meters_query.execute()
+
+ for meter in meters_result:
+ meter_type = meter.meter_type
+
+ logger.info(f"Pobieram dane historyczne dla {p.name} ({p.id}) typ: {meter_type}")
+ current_date = meter.first_date
+ if not full_mode:
+ current_date = meter.last_update_date - timedelta(days=1)
+
+ while current_date <= date.today():
+ try:
+ record = ChartTable.get(id=p.id, meter_type=meter_type, year=current_date.year, month=current_date.month, day=current_date.day)
+ # Jeśli rekord o określonych wartościach klucza głównego istnieje, zostanie pobrany.
+ logger.debug(f"Posiadam dane historyczne dla {p.name} ({p.id}) typ: {meter_type} na dzień: {current_date}")
+ except ChartTable.DoesNotExist:
+ self.download_chart(ChartType.DAY, current_date, p.id, meter_type)
+ logger.debug(f"Pobieram dane historyczne dla {p.name} ({p.id}) typ: {meter_type} na dzień: {current_date}")
+ current_date += timedelta(days=1)
def update_last_days(self):
today = datetime.today().date()
@@ -381,16 +505,23 @@ class MojLicznik:
result_ppes = query.execute()
for p in result_ppes:
- if not p.last_update_date:
- p.last_update_date = today - timedelta(days=5)
- p.save()
- last_update_date = p.last_update_date - timedelta(days=1)
- while last_update_date <= today:
- print(f"Aktualizacja danych dla {p.name} na dzień: {last_update_date}")
- self.download_chart(ChartType.DAY, last_update_date, p.id, True)
- p.last_update_date = last_update_date
- p.save()
- last_update_date += timedelta(days=1)
+ meters_query = MeterTable.select().where((MeterTable.ppe_id == p.id) & (MeterTable.first_date.is_null(True)))
+ meters_result = meters_query.execute()
+
+ for meter in meters_result:
+ meter_type = meter.meter_type
+
+ logger.info(f"Aktualizacja danych bieżących dla {p.name} ({p.id}) typ: {meter_type}")
+ if not p.last_update_date:
+ p.last_update_date = today - timedelta(days=5)
+ p.save()
+ last_update_date = p.last_update_date - timedelta(days=1)
+ while last_update_date <= today:
+ logger.debug(f"Aktualizacja danych dla {p.name} ({p.id}) typ: {meter_type} na dzień: {last_update_date}")
+ self.download_chart(ChartType.DAY, last_update_date, p.id, meter_type, True)
+ p.last_update_date = last_update_date
+ p.save()
+ last_update_date += timedelta(days=1)
def get_current_meters(self, add_daily_char_data=False):
@@ -408,81 +539,38 @@ class MojLicznik:
if zones:
zone1_data = zones[0]
zone1_main_chart = zone1_data.get("mainChart", [])
- #print(zone1_data)
- #else:
- #print(f"{p.name} ({p.measurement_date}) : {p.zone1}, {p.zone2}, {p.zone3}")
-
- #else:
- #print(f"{p.name} ({p.measurement_date}) : {p.zone1}, {p.zone2}, {p.zone3}")
-
- def set_daily_zones(self):
- query = PPETable.select().where(PPETable.is_active == True)
- result_ppes = query.execute()
-
- for p in result_ppes:
- query = ChartTable.select().where(
- (ChartTable.id == p.id) &
- ((ChartTable.year > p.measurement_date.year) |
- ((ChartTable.year == p.measurement_date.year) &
- (ChartTable.month > p.measurement_date.month)) |
- ((ChartTable.year == p.measurement_date.year) &
- (ChartTable.month == p.measurement_date.month) &
- (ChartTable.day >= p.measurement_date.day))
- ))
-
- zones_sums = {f"zone{i+1}_daily_chart_sum": 0.0 for i in range(3)}
-
- for chart_entry in query:
- value_json = json.loads(chart_entry.value)
- main_chart = value_json.get("mainChart", [])
-
- for entry in main_chart:
- zones = entry.get("zones", [])
-
- for i, value in enumerate(zones):
- if value is not None:
- zones_sums[f"zone{i+1}_daily_chart_sum"] += value
-
- for key, value in zones_sums.items():
- setattr(p, key, value)
-
- p.save()
-
# def set_daily_zones(self):
# query = PPETable.select().where(PPETable.is_active == True)
# result_ppes = query.execute()
# for p in result_ppes:
- # query = ChartTable.select().where((ChartTable.id == p.id) & (ChartTable.year >= p.measurement_date.year) & (ChartTable.month >= p.measurement_date.month) & (ChartTable.day >= p.measurement_date.day))
- # query_count = query.count()
- # if (query_count > 0):
- # query_first = query.first()
- # value_json = json.loads(query_first.value)
- # main_chart = value_json.get("mainChart", [])
+ # query = ChartTable.select().where(
+ # (ChartTable.id == p.id) &
+ # ((ChartTable.year > p.measurement_date.year) |
+ # ((ChartTable.year == p.measurement_date.year) &
+ # (ChartTable.month > p.measurement_date.month)) |
+ # ((ChartTable.year == p.measurement_date.year) &
+ # (ChartTable.month == p.measurement_date.month) &
+ # (ChartTable.day >= p.measurement_date.day))
+ # ))
- # # Inicjalizacja słownika do przechowywania sum dla każdej sekcji zones
- # zones_sums = {f"zone{i+1}": 0.0 for i in range(len(main_chart[0].get("zones", [])))}
+ # zones_sums = {f"zone{i+1}_daily_chart_sum": 0.0 for i in range(3)}
+
+ # for chart_entry in query:
+ # value_json = json.loads(chart_entry.value)
+ # main_chart = value_json.get("mainChart", [])
# for entry in main_chart:
# zones = entry.get("zones", [])
# for i, value in enumerate(zones):
# if value is not None:
- # zones_sums[f"zone{i+1}"] += value
- # if (zones_sums["zone1"] > 0):
- # p.zone1_daily_chart_sum = zones_sums["zone1"]
- # else:
- # p.zone1_daily_chart_sum = None
- # if (zones_sums["zone2"] > 0):
- # p.zone2_daily_chart_sum = zones_sums["zone2"]
- # else:
- # p.zone2_daily_chart_sum = None
- # if (zones_sums["zone3"]):
- # p.zone3_daily_chart_sum = zones_sums["zone3"]
- # else:
- # p.zone3_daily_chart_sum = None
- # p.save()
+ # zones_sums[f"zone{i+1}_daily_chart_sum"] += value
+
+ # for key, value in zones_sums.items():
+ # setattr(p, key, value)
+ # p.save()
def print_summary_zones(self):
diff --git a/srcdev/run.py b/src/run.py
similarity index 100%
rename from srcdev/run.py
rename to src/run.py
diff --git a/src/run.sh b/src/run.sh
index fcb60a6..a85830e 100644
--- a/src/run.sh
+++ b/src/run.sh
@@ -2,16 +2,8 @@
export USERNAME=$(bashio::config 'energa_username')
export PASSWORD=$(bashio::config 'energa_password')
+export LOG_LEVEL=$(bashio::config 'log_level')
bashio::log.info "Uruchamiam API"
-python api.py &
-bashio::log.info "Uruchamiam MAIN"
-python main.py
-bashio::log.info "Uruchamiam CRON"
-
-while true; do
- python cron.py
- bashio::log.info "Czekam..."
- sleep 1800
-done
+python run.py
diff --git a/srcdev/CHANGELOG.md b/srcdev/CHANGELOG.md
deleted file mode 100644
index 5c52fc8..0000000
--- a/srcdev/CHANGELOG.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# EnergaMeter (dev)
-## v0.1.8 [2023-11-04]
-- Dodano możliwość odwrócenia znaku wartości pomiaru
-## v0.1.4 - v0.1.7 [2023-11-03]
-- Drobne poprawki
-## v0.1.3 [2023-10-31]
-- Dodano obsługę liczników wytwórcy (Uwaga: zmiana struktury JSON)
-## v0.1.2 [2023-10-23]
-- Dodano obsługę trybu serwisowego aplikacji Mój Licznik
-- Poprawiono logowanie błędów
-- W przypadku problemów z logowaniem liczniki w aplikacji pozostają aktywne
-## v0.1.1 [2023-10-22]
-- Dodano obsługę błędnego logowania
-## v0.1.0 [2023-10-21]
-- Wersja beta
\ No newline at end of file
diff --git a/srcdev/Dockerfile b/srcdev/Dockerfile
deleted file mode 100644
index 328d165..0000000
--- a/srcdev/Dockerfile
+++ /dev/null
@@ -1,20 +0,0 @@
-ARG BUILD_FROM
-FROM $BUILD_FROM
-
-RUN apk add --no-cache python3
-RUN apk add --update py3-pip
-
-# Copy data for add-on
-COPY run.sh /
-COPY run.py /
-COPY requirements.txt /
-COPY main.py /
-COPY api.py /
-COPY cron.py /
-# COPY database.sqlite /
-COPY moj_licznik.py /
-COPY log_config.py /
-RUN chmod a+x /run.sh
-RUN pip install -r requirements.txt
-
-CMD [ "/run.sh" ]
\ No newline at end of file
diff --git a/srcdev/INSTALL.md b/srcdev/INSTALL.md
deleted file mode 100644
index 32abde7..0000000
--- a/srcdev/INSTALL.md
+++ /dev/null
@@ -1,161 +0,0 @@
-[](https://www.home-assistant.io/)
-# [Energa meter](https://github.com/tcich/ha-addon-energa-meter) Home Assistant add-on
-# Wersja dev
-[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
-[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg
-[armv6-shield]: https://img.shields.io/badge/armv6-yes-green.svg
-[armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg
-[i386-shield]: https://img.shields.io/badge/i386-yes-green.svg
-![aarch64-shield]
-![amd64-shield]
-![armv6-shield]
-![armv7-shield]
-![i386-shield]
-
-
-[kawa-logo]: https://github.com/tcich/ha-addon-energa-meter/blob/main/img/buycoffeeto-btn-primary-outline.png
-[kawa]: https://buycoffee.to/tcich
-
-
-
-## O dodatku
-
-To jest dodatek dla [Home Assistant](https://www.home-assistant.io/). Instalacja dodatku [Energa meter](https://github.com/tcich/ha-addon-energa-meter) umożliwia cykliczne pobieranie danych z aplikacji [Mój Licznik - Energa](https://mojlicznik.energa-operator.pl) udostępnianej klientom Operatora energetycznego Energa.
-
-### Wersja dev
-Wersja dev jest wersją developeską, nie należy jej używać w produkcyjnej wersji HA, może powodować różne problemy, może nie działać.
-
-## Instalacja
-1) Dodaj repozytorium do repozytoriów dodatków swojego HA za pomocą poniższego przycisku
-
-[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Ftcich%2Fha-addon-energa-meter)
-
-Lub zainstaluj manualnie z Ustawienia -> Dodatki -> Sklep z dodatkami -> ⁞ (Menu) -> Repozytoria -> Wpisz `https://github.com/tcich/hassio-mojlicznik` -> Dodaj. Następnie w ⁞ (Menu) -> Sprawdź aktualizacje (może być konieczne przeładowanie strony)
-
-2) Odszukaj dodatek na liście dodatków w sklepie z dodatkami i zainstaluj go.
-
-3) W zakładce konfiguracja uzupełnij nazwę użytkownika oraz hasło do aplikacji Mój Licznik, jeżeli potrzebujesz to zmień udostępniany port dla API
-
-4) Przejdź do zakładki informacje i uruchom dodatek (pierwsze uruchomienie może trwać kilkanaście minut), jeżeli w logu pojawi się informacja *INFO: Czekam...* oznacza to, że pierwsze inicjalne pobieranie danych zostało ukończone.
-
-
-## Wersja Docker
-Aby ruchomić wersję docker należy skorzystać z polecenia poniżej
-
-```
-docker run -p 8000:8000 -e ENERGA_USERNAME=LoginEnerga -e ENERGA_PASSWORD=HasloEnerga tomcic/energa-meter:v0.1.0
-```
-
-Wymagane parametry:
-
-* ENERGA_USERNAME - nazwa użytkownika w aplikacji Energa Mój licznik
-* ENERGA_PASSWORD - hasło użytkownika w aplikacji Energa Mój licznik
-
-
-## Konfiguracja sensorów
-Do HA możesz dodać sensory, które zawierają informacje udostępniane przez API
-
-Poniższa instrukcja zawiera założenia:
-* dodatek jest dostępny pod adresem *localhost* na porcie *8000*
-* ID Twojego licznika to *123456789*
-
-1) Ustal ID Twoich liczników, w tym celu przejdź do adresu Twojego HA na porcie 8000 lub innym jeźeli zmieniłeś go w konfiguracji, np. http://192.168.1.10:8000 wyświetli się w formacie json lista dostępnych liczników.
-2) W pliku configuration.yaml w HA dodaj następującą konfigurację np.:
-
-```
-sensor:
- - platform: rest
- resource: http://localhost:8000/123456789/A%2B/1
- name: "A+ Taryfa 1"
- unique_id: 123456789_apt1
- unit_of_measurement: "kWh"
- value_template: "{{ value_json.countner.meter_value | round(2) }}"
- - platform: rest
- resource: http://localhost:8000/123456789/A%2B/2
- name: "A+ Taryfa 2"
- unique_id: 123456789_apt2
- unit_of_measurement: "kWh"
- value_template: "{{ value_json.countner.meter_value | round(2) }}"
-```
-
-### Opis konfiguracji
-| element konfiguracji | Opis |
-|-------------------|-------------------|
-| resource: http://localhost:8000/123456789/A%2B/1 | Adres API z danymi konkretnego licznika, podajemy **localhost** lub nazwę instancji dockera (**Nazwa hosta** z okna dodatku), port, id licznika, rodzaj pomiaru, taryfa|
-| name: "A+ Taryfa 1" | Nazwa sensora, wpisz dowolną|
-| unique_id | Unikalny ID sensora, nie mogą być w systemie dwa sensory z tym samym ID|
-| unit_of_measurement: "kWh" | Jednostka miary, nie zmieniaj chyba, że wiesz co robisz|
-| value_template: "{{ value_json.meter.countner.meter_value \| round(2) }}" | Zaokrąglony do dwóch miejsc po przecinku stan sensora|
-
-## API dla wykresów, np. Grafana
-Aby pobrać dane z API w formacie JSON należy użyć adresu http://home_assistant:8000/charts/12729?start_date=1695332400129&end_date=1697924583285&mp=123456789&zone=1
-
-### Opis konfiguracji
-| element konfiguracji | Opis |
-|-------------------|-------------------|
-| resource: http://localhost:8000/charts | Adres API z danymi do wykresów, podajemy **localhost** lub nazwę instancji dockera (**Nazwa hosta** z okna dodatku), port, id licznika, rodzaj pomiaru, taryfa|
-| start_date | data początkowa danych w formacie epoch (ms), domyślnie czas bieżący |
-| end_date | data końcowa danych w formacie epoch (ms), domyślnie czas bieżący - 1 dzień |
-| mp | numer licznika |
-| meter_type_url | typ licznika (np. A+: A%2B, A-: A- ) |
-| zone | numer strefy (np. 1, 2) |
-| negative | dodanie parametru z dowolną wartością powoduje, że pomiar jest wartością ujemną|
-
-## Jak dodać wykres do Grafana
-### Źródło danych
-1) Dodajemy źródło danych Home -> Data sources - Add new datasources: Wyszukujemy JSON API (jeżeli nie ma to musimy dodać)
-2) NAME: ENERGA (1)
-3) URL: http://twoj_addon:8000 (2)
-4) Klikamy Save&test (3)
-5) Uwaga: Jeżeli Grafana jest addonem w HA użyj właściwej nazwy hosta dostępnej w docker.
-
-
-
-
-
-
-### Dashboard
-1) Przechodzimy do Dashboards
-2) Klikamy New -> New dashboard -> Add visualization
-3) Wskazujemy Data source: ENERGA
-4) W **Path** wpisujemy: GET: /charts
-
- (id Twojego licznika)
-5) W **Fields** wpisujemy \$.charts\[\*\].czas typu Time oraz $.charts[*].value typu number z aliasem kWh
-5) W **Params** wpisujemy Key: start_date Value: $__from
-6) W **Params** wpisujemy Key: end_date Value: $__to
-
-
-
-
-
-
-
-
-
-
-
-7) W **Params** wpisujemy Key: meter_type_url Value: A+ lub A- jeżeli mamy energię pobieraną i oddawaną, w takim przypadku tworzymy również oddzielne **Query** dla A+ i A-
-
-
-
-8) W **Params** wpisujemy Key: negative Value: OK (może być dowolna wartość) jeżeli chcemy aby wykres był z wartościami ujemnymi (poniżej osi X)
-
-
-
-9) W **Params** aby uzyskać bilans energii należy dodać **Transform**, przy czym wartość energii oddawanej powinna być jako energia ujemna (parametr negative)
-
-
-
-
-
-## Znane problemy
-* Czasami w aplikacji Mój Licznik włącza się captha (jeżeli masz dużo danych historycznych lub wielokrotnie instalujesz dodatek) - należy poczekać kilka godzin, problem rozwiąże się sam
-
-
-## Uwagi
-Dostęp do aktualnej wersji API nie jest zabezpieczony tokenem
-Każde przeinstalowanie dodatku pobiera ponownie dane z aplikacji Mój Licznik
-
-
-
diff --git a/srcdev/README.md b/srcdev/README.md
deleted file mode 100644
index 98aa77e..0000000
--- a/srcdev/README.md
+++ /dev/null
@@ -1,128 +0,0 @@
-[](https://www.home-assistant.io/)
-# [Energa meter](https://github.com/tcich/ha-addon-energa-meter) Home Assistant add-on
-# Wersja dev
-[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
-[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg
-[armv6-shield]: https://img.shields.io/badge/armv6-yes-green.svg
-[armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg
-[i386-shield]: https://img.shields.io/badge/i386-yes-green.svg
-![aarch64-shield]
-![amd64-shield]
-![armv6-shield]
-![armv7-shield]
-![i386-shield]
-
-
-
-[kawa-logo]: https://github.com/tcich/ha-addon-energa-meter/blob/main/img/buycoffeeto-btn-primary-outline.png
-[kawa]: https://buycoffee.to/tcich
-
-## O dodatku
-
-To jest dodatek dla [Home Assistant](https://www.home-assistant.io/). Instalacja dodatku [Energa meter](https://github.com/tcich/ha-addon-energa-meter) umożliwia cykliczne pobieranie danych z aplikacji [Mój Licznik - Energa](https://mojlicznik.energa-operator.pl) udostępnianej klientom Operatora energetycznego Energa.
-
-### Wersja dev
-Wersja dev jest wersją developeską, nie należy jej używać w produkcyjnej wersji HA, może powodować różne problemy, może nie działać.
-
-## Instalacja
-1) Dodaj repozytorium do repozytoriów dodatków swojego HA za pomocą poniższego przycisku
-
-[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Ftcich%2Fha-addon-energa-meter)
-
-Lub zainstaluj manualnie z Ustawienia -> Dodatki -> Sklep z dodatkami -> ⁞ (Menu) -> Repozytoria -> Wpisz `https://github.com/tcich/hassio-mojlicznik` -> Dodaj. Następnie w ⁞ (Menu) -> Sprawdź aktualizacje (może być konieczne przeładowanie strony)
-
-2) Odszukaj dodatek na liście dodatków w sklepie z dodatkami i zainstaluj go.
-
-3) W zakładce konfiguracja uzupełnij nazwę użytkownika oraz hasło do aplikacji Mój Licznik, jeżeli potrzebujesz to zmień udostępniany port dla API
-
-4) Przejdź do zakładki informacje i uruchom dodatek (pierwsze uruchomienie może trwać kilkanaście minut), jeżeli w logu pojawi się informacja *INFO: Czekam...* oznacza to, że pierwsze inicjalne pobieranie danych zostało ukończone.
-
-
-## Wersja Docker
-Aby ruchomić wersję docker należy skorzystać z polecenia poniżej
-
-```
-docker run -p 8000:8000 -e ENERGA_USERNAME=LoginEnerga -e ENERGA_PASSWORD=HasloEnerga tomcic/energa-meter:v0.1.0
-```
-
-Wymagane parametry:
-
-* ENERGA_USERNAME - nazwa użytkownika w aplikacji Energa Mój licznik
-* ENERGA_PASSWORD - hasło użytkownika w aplikacji Energa Mój licznik
-
-
-## Konfiguracja sensorów
-Do HA możesz dodać sensory, które zawierają informacje udostępniane przez API
-
-Poniższa instrukcja zawiera założenia:
-* dodatek jest dostępny pod adresem *localhost* na porcie *8000*
-* ID Twojego licznika to *123456789*
-
-1) Ustal ID Twoich liczników, w tym celu przejdź do adresu Twojego HA na porcie 8000 lub innym jeźeli zmieniłeś go w konfiguracji, np. http://192.168.1.10:8000 wyświetli się w formacie json lista dostępnych liczników.
-2) W pliku configuration.yaml w HA dodaj następującą konfigurację np.:
-
-```
-sensor:
- - platform: rest
- resource: http://localhost:8000/123456789/A%2B/1
- name: "A+ Taryfa 1"
- unique_id: 123456789_apt1
- unit_of_measurement: "kWh"
- value_template: "{{ value_json.countner.meter_value | round(2) }}"
- - platform: rest
- resource: http://localhost:8000/123456789/A%2B/2
- name: "A+ Taryfa 2"
- unique_id: 123456789_apt2
- unit_of_measurement: "kWh"
- value_template: "{{ value_json.countner.meter_value | round(2) }}"
-```
-## Suma liczników, bilans
-W celu uzyskania sumy liczników, bilansu, itp należy użyć templates:
-```
-template:
- - sensor:
- - name: "Suma liczników"
- unit_of_measurement: "kWh"
- state: "{{ states('sensor.123456789_apt1') | float + states('sensor.123456789_apt2') | float | round(2) }}"
- - sensor:
- - name: "Bilans/różnica liczników"
- unit_of_measurement: "kWh"
- state: "{{ states('sensor.123456789_apt1') | float - states('sensor.123456789_apt2') | float | round(2) }}"
-```
-
-
-### Opis konfiguracji
-| element konfiguracji | Opis |
-|-------------------|-------------------|
-| resource: http://localhost:8000/123456789/A%2B/1 | Adres API z danymi konkretnego licznika, podajemy **localhost** lub nazwę instancji dockera (**Nazwa hosta** z okna dodatku), port, id licznika, rodzaj pomiaru, taryfa|
-| name: "A+ Taryfa 1" | Nazwa sensora, wpisz dowolną|
-| unique_id | Unikalny ID sensora, nie mogą być w systemie dwa sensory z tym samym ID|
-| unit_of_measurement: "kWh" | Jednostka miary, nie zmieniaj chyba, że wiesz co robisz|
-| value_template: "{{ value_json.meter.countner.meter_value \| round(2) }}" | Zaokrąglony do dwóch miejsc po przecinku stan sensora|
-
-## API dla wykresów, np. Grafana
-Aby pobrać dane z API w formacie JSON należy użyć adresu http://home_assistant:8000/charts/12729?start_date=1695332400129&end_date=1697924583285&mp=123456789&zone=1
-
-### Opis konfiguracji
-| element konfiguracji | Opis |
-|-------------------|-------------------|
-| resource: http://localhost:8000/charts | Adres API z danymi do wykresów, podajemy **localhost** lub nazwę instancji dockera (**Nazwa hosta** z okna dodatku), port, id licznika, rodzaj pomiaru, taryfa|
-| start_date | data początkowa danych w formacie epoch (ms), domyślnie czas bieżący |
-| end_date | data końcowa danych w formacie epoch (ms), domyślnie czas bieżący - 1 dzień |
-| mp | numer licznika |
-| meter_type_url | typ licznika (np. A+: A%2B, A-: A- ) |
-| zone | numer strefy (np. 1, 2) |
-
-
-## Grafana
-Instrukcja konfiguracji dla Grafana znajduje się tutaj [link](https://github.com/tcich/ha-addon-energa-meter/tree/e702ed49436fb9e0b675dcac3001bd9de5aab3c0/srcdev/INSTALL.md)
-
-
-## Znane problemy
-Czasami w aplikacji Mój Licznik włącza się captha (jeżeli masz dużo danych historycznych lub wielokrotnie instalujesz dodatek)
-Dane wytwórcy (energia oddana oraz bilans) nie są dostępne, prace w tym zakresie trwają.
-
-## Uwagi
-Dostęp do aktualnej wersji API nie jest zabezpieczony tokenem
-Każde przeinstalowanie dodatku pobiera ponownie dane z aplikacji Mój Licznik
-
diff --git a/srcdev/api.py b/srcdev/api.py
deleted file mode 100644
index 522a136..0000000
--- a/srcdev/api.py
+++ /dev/null
@@ -1,242 +0,0 @@
-from peewee import SqliteDatabase
-from flask import Flask, jsonify, request, redirect, url_for, abort
-from waitress import serve
-#from datetime
-import datetime
-import time, os, logging
-from moj_licznik import PPETable, MeterTable, CounterTable, MainChartTable
-import urllib.parse
-
-logger = logging.getLogger("energaMeter.api")
-
-
-path = os.path.dirname(os.path.abspath(__file__))
-db_file = 'database.sqlite'
-db = SqliteDatabase(os.path.join(path, db_file))
-
-app = Flask(__name__)
-
-@app.route('/', methods=['GET'])
-def root():
- query = PPETable.select() #.where(PPETable.is_active == True)
- result_ppes = list(query)
- ppes = []
- for p in result_ppes:
- meters_query = MeterTable.select().where(MeterTable.ppe_id == p.id)
- meter_result = meters_query.execute()
- meters = []
- for meter in meter_result:
- countners_query = CounterTable.select().where(CounterTable.meter_id == meter.id)
- countners_result = countners_query.execute()
- countners = []
- for countner in countners_result:
- countner = {
- 'tariff': countner.tariff,
- 'measurement_date': countner.measurement_date,
- 'meter_value': countner.meter_value
- }
- countners.append(countner)
-
- meter = {
- 'meter_type': meter.meter_type,
- 'meter_type_url': urllib.parse.quote_plus(meter.meter_type),
- 'last_update_date': meter.last_update_date,
- 'first_date': meter.first_date,
- 'countners': countners
- }
- meters.append(meter)
-
- ppe = {
- 'name': p.name,
- 'id': p.id,
- 'type': p.type,
- 'isActive': p.is_active,
- 'meters': meters
- }
- ppes.append(ppe)
- logger.debug("API: GET /")
-
- return jsonify({'ppes': ppes})
-
-@app.route('/meters', methods=['GET'])
-@app.route('/meters/', methods=['GET'])
-def meters():
- query = PPETable.select() #.where(PPETable.is_active == True)
- result_ppes = list(query)
- meters = []
-
- for p in result_ppes:
- meter = {
- 'name': p.name,
- 'id': p.id,
- 'ppe': p.ppe,
- 'number_of_zones': '',#p.number_of_zones,
- 'tariffCode': p.tariffCode,
- # 'first_date': p.first_date,
- 'last_update_date': p.last_update_date,
- # 'measurement_date': p.measurement_date,
- }
-
- for i in range(1, p.number_of_zones + 1):
- zone_key = f'zone{i}'
- daily_chart_key = f'zone{i}_daily_chart_sum'
-
- zone_value = getattr(p, zone_key)
- daily_chart_value = getattr(p, daily_chart_key)
-
- # Zamień None na zero podczas obliczania sumy
- zone_value = float(zone_value) if zone_value is not None else 0
- daily_chart_value = float(daily_chart_value) if daily_chart_value is not None else 0
-
- meter[zone_key] = {
- 'meter': zone_value,
- 'daily_chart': daily_chart_value,
- 'sum': zone_value + daily_chart_value
- }
-
- meters.append(meter)
- logger.debug("GET /meters")
-
- return jsonify({'meters': meters})
-
-
-@app.route('/', methods=['GET'])
-@app.route('//', methods=['GET'])
-def get_ppe(ppe_id):
- meters_query = MeterTable.select().where(MeterTable.ppe_id == ppe_id)
- meter_result = meters_query.execute()
-
- if not meter_result:
- abort(404)
-
- meters = []
- for meter in meter_result:
- countners_query = CounterTable.select().where(CounterTable.meter_id == meter.id)
- countners_result = countners_query.execute()
- countners = []
- for countner in countners_result:
- countner = {
- 'tariff': countner.tariff,
- 'measurement_date': countner.measurement_date,
- 'meter_value': countner.meter_value
- }
- countners.append(countner)
-
- meter = {
- 'meter_type': meter.meter_type,
- 'meter_type_url': urllib.parse.quote_plus(meter.meter_type),
- 'last_update_date': meter.last_update_date,
- 'first_date': meter.first_date,
- 'countners': countners
- }
- meters.append(meter)
- logger.debug(f"API: GET /{ppe_id}")
- return jsonify({'meters': meters})
-
-
-@app.route('//', methods=['GET'])
-@app.route('///', methods=['GET'])
-def get_meter_type(ppe_id, meter_type_url):
- meter_type = urllib.parse.unquote(meter_type_url)
- meters_query = MeterTable.select().where((MeterTable.ppe_id == str(ppe_id)) & (MeterTable.meter_type == meter_type))
- meter_result = meters_query.execute()
- if not meter_result:
- abort(404)
-
- meter = meter_result[0]
- countners_query = CounterTable.select().where(CounterTable.meter_id == meter.id)
- countners_result = countners_query.execute()
- countners = []
- for countner in countners_result:
- countner = {
- 'tariff': countner.tariff,
- 'measurement_date': countner.measurement_date,
- 'meter_value': countner.meter_value
- }
- countners.append(countner)
-
- meter = {
- 'meter_type': meter.meter_type,
- 'meter_type_url': urllib.parse.quote_plus(meter.meter_type),
- 'last_update_date': meter.last_update_date,
- 'first_date': meter.first_date,
- 'countners': countners
- }
-
- logger.debug(f"API: GET /{ppe_id}/{meter_type_url}")
- return jsonify({'meter': meter})
-
-@app.route('///', methods=['GET'])
-@app.route('////', methods=['GET'])
-def get_countners(ppe_id, meter_type_url, tariff):
- meter_type = urllib.parse.unquote(meter_type_url)
- meters_query = MeterTable.select().where((MeterTable.ppe_id == str(ppe_id)) & (MeterTable.meter_type == meter_type))
- meter_result = meters_query.execute()
-
- if not meter_result:
- abort(404)
-
- meter = meter_result[0]
- countners_query = CounterTable.select().where((CounterTable.meter_id == meter.id) & (CounterTable.tariff == tariff))
- countners_result = countners_query.execute()
- countner = countners_result[0]
- countner = {
- 'tariff': countner.tariff,
- 'measurement_date': countner.measurement_date,
- 'meter_value': countner.meter_value
- }
-
- logger.debug(f"API: GET /{ppe_id}/{meter_type_url}/{countner}")
- return jsonify({'countner': countner})
-
-
-
-@app.route('/charts', methods=['GET'])
-@app.route('/charts/', methods=['GET'])
-def charts():
-
-
- current_time = datetime.datetime.now()
- current_time_unix = time.mktime(current_time.timetuple())
- start_time = current_time - datetime.timedelta(days=1)
- start_time_unix = time.mktime(start_time.timetuple())
- start_date = request.args.get('start_date', start_time_unix*1000)
- end_date = request.args.get('end_date', current_time_unix*1000)
- mp = request.args.get('mp', None)
- meter_type_url = request.args.get('meter_type_url', None)
- zone = request.args.get('zone', None)
- negative = request.args.get('negative', type=bool, default=False)
- logger.debug(f"API: GET /charts - {start_date} - {end_date}")
- query = MainChartTable.select().where((MainChartTable.tm >= int(start_date)) & (MainChartTable.tm <= int(end_date)))
- logger.debug(f"{query}")
- factor = 1
- if negative:
- factor = -1
- if mp:
- query = query.where(MainChartTable.mp == mp)
-
- if meter_type_url:
- meter_type = urllib.parse.unquote(meter_type_url)
- query = query.where(MainChartTable.meter_type == meter_type)
-
- if zone:
- query = query.where(MainChartTable.zone == zone)
-
- result_ppes = list(query)
- charts = []
-
- for p in result_ppes:
- czas = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(p.tm/1000))
- chart = {
- 'mp': p.mp,
- 'meter_type': p.meter_type,
- 'meter_type_url': urllib.parse.quote_plus(p.meter_type),
- 'zone': p.zone,
- 'time_tm': p.tm,
- 'time': czas,
- 'value': p.value * factor
- }
- charts.append(chart)
- end_time = time.time()
-
- return jsonify({'charts': charts})
diff --git a/srcdev/config.yaml b/srcdev/config.yaml
deleted file mode 100644
index b783a0b..0000000
--- a/srcdev/config.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: "Energa meter (dev)"
-description: "Energa meter addon"
-version: "0.1.8"
-slug: "energa_meter_dev"
-init: false
-options:
- energa_username: ""
- energa_password: ""
- log_level: "INFO"
-schema:
- energa_username: str
- energa_password: password
- log_level: str
-arch:
- - aarch64
- - amd64
- - armhf
- - armv7
- - i386
-startup: services
-ports:
- 8000/tcp: 8000
\ No newline at end of file
diff --git a/srcdev/cron.py b/srcdev/cron.py
deleted file mode 100644
index e69de29..0000000
diff --git a/srcdev/icon.png b/srcdev/icon.png
deleted file mode 100644
index edfc4f9..0000000
Binary files a/srcdev/icon.png and /dev/null differ
diff --git a/srcdev/img/addon.png b/srcdev/img/addon.png
deleted file mode 100644
index 7daebfe..0000000
Binary files a/srcdev/img/addon.png and /dev/null differ
diff --git a/srcdev/img/grafana_01.png b/srcdev/img/grafana_01.png
deleted file mode 100644
index 6fbbedb..0000000
Binary files a/srcdev/img/grafana_01.png and /dev/null differ
diff --git a/srcdev/img/grafana_02.png b/srcdev/img/grafana_02.png
deleted file mode 100644
index 72e64e6..0000000
Binary files a/srcdev/img/grafana_02.png and /dev/null differ
diff --git a/srcdev/img/grafana_03.png b/srcdev/img/grafana_03.png
deleted file mode 100644
index 603c262..0000000
Binary files a/srcdev/img/grafana_03.png and /dev/null differ
diff --git a/srcdev/img/grafana_04.png b/srcdev/img/grafana_04.png
deleted file mode 100644
index 1cf2b31..0000000
Binary files a/srcdev/img/grafana_04.png and /dev/null differ
diff --git a/srcdev/img/grafana_05.png b/srcdev/img/grafana_05.png
deleted file mode 100644
index c0819e1..0000000
Binary files a/srcdev/img/grafana_05.png and /dev/null differ
diff --git a/srcdev/img/grafana_06.png b/srcdev/img/grafana_06.png
deleted file mode 100644
index a4a9233..0000000
Binary files a/srcdev/img/grafana_06.png and /dev/null differ
diff --git a/srcdev/logo.png b/srcdev/logo.png
deleted file mode 100644
index edfc4f9..0000000
Binary files a/srcdev/logo.png and /dev/null differ
diff --git a/srcdev/main.py b/srcdev/main.py
deleted file mode 100644
index 6703d12..0000000
--- a/srcdev/main.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import configparser, time, datetime, os
-from moj_licznik import MojLicznik
-from pathlib import Path
-
-def main():
- username = None
- password = None
- username = os.getenv("USERNAME")
- password = os.getenv("PASSWORD")
-
- print(f"Inicjacja...")
- mojLicznik = MojLicznik()
- print(f"Logowanie...", username)
- mojLicznik.login(username, password)
- print(f"Aktualizacja liczników...")
- mojLicznik.uppdate_measurments()
- print(f"Wyszukiwanie najstarszych danych...")
- mojLicznik.update_first_date()
- print(f"Pobieranie danych...")
- mojLicznik.download_charts(True)
- mojLicznik.update_last_days()
- mojLicznik.set_daily_zones()
- mojLicznik.logout()
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/srcdev/moj_licznik.py b/srcdev/moj_licznik.py
deleted file mode 100644
index d10bf26..0000000
--- a/srcdev/moj_licznik.py
+++ /dev/null
@@ -1,601 +0,0 @@
-from peewee import SqliteDatabase
-from datetime import datetime, timedelta, date
-import calendar, requests, re, time, json, os, logging
-import http.cookiejar as cookiejar
-from requests.exceptions import HTTPError
-from bs4 import BeautifulSoup
-from enum import Enum
-from peewee import AutoField, Model, CharField, IntegerField, DateField, BooleanField, CompositeKey, DecimalField, ForeignKeyField, SQL
-import urllib.parse
-
-logger = logging.getLogger("energaMeter")
-
-path = os.path.dirname(os.path.abspath(__file__))
-db_file = 'database.sqlite'
-db = SqliteDatabase(os.path.join(path, db_file))
-
-class ChartType(Enum):
- DAY = "DAY"
- MONTH = "MONTH"
- YEAR = "YEAR"
-
-class PPETable(Model):
- id = CharField(primary_key=True)
- ppe = CharField(unique=True)
- tariffCode = CharField()
- type = CharField()
- name = CharField()
- last_update_date = DateField(null=True)
- is_active = BooleanField(default=True)
-
- class Meta:
- database = db
- table_name = 'PPE'
- constraints = [SQL('UNIQUE (ppe, tariffCode)')]
-
-class MeterTable(Model):
- id = AutoField() # Meter point
- ppe_id = ForeignKeyField(PPETable, backref='zones')
- meter_type = CharField()
- last_update_date = DateField(null=True)
- first_date = DateField(null=True)
-
- class Meta:
- database = db
- table_name = 'METER'
- constraints = [SQL('UNIQUE (ppe_id, meter_type)')]
-
-class CounterTable(Model):
- id = AutoField()
- meter_id = ForeignKeyField(MeterTable, backref='meter')
- tariff = CharField()
- measurement_date = DateField(null=True)
- meter_value = DecimalField(max_digits=15, decimal_places=5, null=True)
-
- class Meta:
- database = db
- table_name = 'COUNTER'
-
-# class CounterTable(Model):
-# meter_id = ForeignKeyField(MeterTable, backref='zones')
-# measurement_date = DateField(null=True)
-# meter_value = DecimalField(max_digits=15, decimal_places=5, null=True)
-
-# class Meta:
-# database = db
-
-
-class ChartTable(Model):
- id = IntegerField()
- meter_type = CharField()
- year = IntegerField()
- month = IntegerField(null=True)
- day = IntegerField(null=True)
- value =CharField()
-
- class Meta:
- database = db
- table_name = 'CHART_CACHE'
- primary_key = CompositeKey('id', 'year', 'month', 'day')
-
-class MainChartTable(Model):
- mp = CharField()
- meter_type = CharField()
- zone = IntegerField()
- tm = IntegerField()
- value = DecimalField(max_digits=20, decimal_places=16, null=True)
- tarAvg = DecimalField(max_digits=20, decimal_places=16, null=True)
- est = BooleanField(default=False)
- cplt = BooleanField(default=False)
-
- class Meta:
- database = db
- table_name = 'CHART'
- primary_key = CompositeKey('mp', 'meter_type', 'zone', 'tm')
-
-def znajdz_typ_odbiorcy(element):
- typ_odbiorcy = ''
- div_elements = element.find_all('div', recursive=False)
- for div_element in div_elements:
- typ_span = div_element.find('span', text='Typ')
- if typ_span:
- typ_odbiorcy = typ_span.next_sibling.strip()
- return typ_odbiorcy
- typ_odbiorcy = znajdz_typ_odbiorcy(div_element) # Rekurencyjne przeszukiwanie zagnieżdżonych div
- return typ_odbiorcy
-
-def findCountners(page):
- table = page.find('table')
- countner_type_list = ["A-", "A+"]
- data_list = []
-
- # Jeśli znaleźliśmy tabelę, możemy przeszukać jej wiersze i komórki
- if table:
- for row in table.find_all('tr'):
- cells = row.find_all('td')
- if len(cells) > 1:
- # Pobieramy opis z pierwszej komórki
- description = cells[0].text.strip()
-
- # Pomijamy, jeśli rodzaj_licznika jest pusty
- if not description:
- continue
-
- # Usuwamy datę z opisu
- description_parts = description.split('\n')
- meter_type = description_parts[0][:2].strip()
-
- if meter_type not in countner_type_list:
- continue
-
- tariff0 = description_parts[0][2:].strip()
- tariff = ''.join(filter(str.isdigit, tariff0))
- measurement_date = description_parts[1].strip()
-
- # Pobieramy dane liczbowe z drugiej komórki
- data = cells[1].text.strip()
-
- # Usuwamy znaki nowej linii i spacje z danych
- data = data.replace('\n', '').replace(' ', '')
-
- data = data.replace(',', '.')
-
- # Dzielimy dane na część całkowitą i część dziesiętną
- parts = data.split('.')
- if len(parts) == 2:
- integer_part = parts[0]
- decimal_part = parts[1]
- else:
- integer_part = parts[0]
- decimal_part = '0'
-
- data_dict = {
- "meter_type": meter_type,
- "tariff": tariff,
- "measurement_date": measurement_date,
- "meter_value": f"{integer_part}.{decimal_part}"
- }
-
- data_list.append(data_dict)
- return data_list
-
-class MojLicznik:
-
- session = requests.Session()
- session.cookies = cookiejar.LWPCookieJar(filename='cookies.txt')
-
- meter_url = "https://mojlicznik.energa-operator.pl"
-
-
-
- def databaseInit(self):
- db.create_tables([PPETable], safe=True)
- db.create_tables([MeterTable], safe=True)
- db.create_tables([CounterTable], safe=True)
- db.create_tables([ChartTable], safe=True)
- db.create_tables([MainChartTable], safe=True)
-
-
- def __init__(self):
- self.username = None
- self.password = None
- self.loginStatus = False
- self.data = [] # Change self.data to a list
- self.ppes = []
- self.databaseInit()
-
- def login(self, _username, _password):
-
- self.username = _username
- self.password = _password
-
- login_url = f"{self.meter_url}/dp/UserLogin.do"
-
- self.loginStatus = False
-
- try:
- logger.debug("Pobieram formularz logowania.")
- response = self.session.get(login_url)
- response.raise_for_status()
- if response.url == 'https://mojlicznik.energa-operator.pl/maintenance.html':
- logger.critical("Trwają prace serwisowe w Mój Licznik. Logowanie nie jest możliwe, spróbuj później.")
- return
-
- except HTTPError as e:
- logger.error(f"Wystąpił błąd HTTP: {e}")
-
- soup = BeautifulSoup(response.text, 'html.parser')
- csrf_token = soup.find('input', {'name': '_antixsrf'})['value']
-
- login_data = {
- 'j_username': self.username,
- 'j_password': self.password,
- 'selectedForm': '1',
- 'save': 'save',
- 'clientOS': 'web',
- '_antixsrf': csrf_token
- }
-
- try:
- response = self.session.post(login_url, data=login_data)
- response.raise_for_status()
-
-
- except HTTPError as e:
- logger.error(f"Wystąpił błąd HTTP: {e}")
-
- soup = BeautifulSoup(response.text, 'html.parser')
-
- login_error_text = 'Użytkownik lub hasło niepoprawne'
- login_error = soup.find('div', text=login_error_text)
-
- if login_error:
- logger.critical(login_error_text)
- return
- else:
- self.loginStatus = True
- logger.info(f"Zalogowano")
-
- body = soup.find('body')
- type_value = znajdz_typ_odbiorcy(body)
-
- logger.debug(f"Typ umowy: {type_value}.")
- select_elements = soup.find_all('script', type='text/javascript')
- meter_isd = []
- for el in select_elements:
- pattern = r"id:\s+(\d+),[\s\S]*?ppe:\s+'([\d\s]+)',[\s\S]*?tariffCode:\s+'([^']+)',[\s\S]*?name:\s+'([^']+)'"
- matches = re.search(pattern, el.text)
- if matches:
- id_value = matches.group(1)
- ppe_value = matches.group(2)
- tariffCode_value = matches.group(3)
- name_value = matches.group(4)
- meter_isd.append(id_value)
- retrieved_record = PPETable.get_or_none(id=id_value)
- if retrieved_record:
- logger.info(f"Licznik {id_value} istnieje w systemie.")
- if not retrieved_record.is_active:
- retrieved_record.is_active = True
- retrieved_record.save()
- else:
- logger.info(f"Licznik {id_value} nie istnieje w systemie, zostanie dodany.")
- data = PPETable.create(
- id=id_value,
- ppe=ppe_value,
- tariffCode=tariffCode_value,
- type=type_value,
- name=name_value
- )
- update_query = PPETable.update(is_active=0).where(PPETable.id.not_in(meter_isd))
- update_query.execute()
-
- def logout(self):
- logout_url = f"{self.meter_url}/dp/MainLogout.go"
- try:
- response = self.session.get(logout_url)
- response.raise_for_status()
- self.loginStatus = False
- logger.info(f"Wylogowano.")
- except HTTPError as e:
- logger.error(f"Wystąpił błąd HTTP: {e}")
-
- def update_countners(self):
-
- query = PPETable.select().where(PPETable.is_active == True)
- result_ppes = query.execute()
- for p in result_ppes:
- meter_url = f"{self.meter_url}/dp/UserData.do?mpc={p.id}&ppe={p.ppe}"
- try:
- response = self.session.get(meter_url)
- response.raise_for_status()
- soup = BeautifulSoup(response.text, 'html.parser')
- countners_dict = findCountners(soup)
-
- for c in countners_dict:
- mn, mu = MeterTable.get_or_create(ppe_id=p.id, meter_type=c['meter_type'])
- mn.last_update_date = datetime.now()
- mn.save()
- cn, cu = CounterTable.get_or_create(
- meter_id = mn.id,
- tariff=c['tariff']
- )
-
- cn.meter_value = c['meter_value']
- cn.measurement_date = c['measurement_date']
- cn.save()
-
- logger.info(f"Zapisano stan licznika {p.id} {c['meter_type']} taryfa {c['tariff']} z dnia: {c['measurement_date']} : {c['meter_value']}")
- except HTTPError as e:
- logger.error(f"Wystąpił błąd HTTP: {e}")
-
- def update_first_date(self):
- ppes_query = PPETable.select().where(PPETable.is_active == True)
- result_ppes = ppes_query.execute()
- for p in result_ppes:
- meters_query = MeterTable.select().where((MeterTable.ppe_id == p.id) & (MeterTable.first_date.is_null(True)))
- meters_result = meters_query.execute()
-
- for meter in meters_result:
- meter_type = meter.meter_type
-
- print(f"Szukam najstarsze dane historyczne licznika {p.name} (PPE: {p.ppe}, {p.id}) typ: {meter_type}")
- meter_point = p.id
- max_years_back = 5
- start_date = datetime.now()
- last_chart_year = None
- for n in range(max_years_back + 1):
- first_day_of_year = datetime(start_date.year-n, 1, 1)
- data_json = self.download_chart(ChartType.YEAR, first_day_of_year, meter_point, meter_type)
- if data_json:
- data = json.loads(data_json)
- if data and data.get("mainChart") and len(data["mainChart"]) > 0:
- last_chart_year = first_day_of_year.year
- last_chart_month = None
- max_month = 12
- for n in range(max_month, 0, -1):
- first_day_of_month = datetime(last_chart_year, n, 1)
- data_json = self.download_chart(ChartType.MONTH, first_day_of_month, meter_point, meter_type)
- if data_json:
- data = json.loads(data_json)
- if data and data.get("mainChart") and len(data["mainChart"]) > 0:
- last_chart_month = n
- last_chart_day = None
- max_day = 31
- first_day_of_day = datetime(last_chart_year, last_chart_month, 1)
- _, max_day = calendar.monthrange(first_day_of_day.year, first_day_of_day.month)
- for n in range(max_day, 0, -1):
- first_day_of_day = datetime(last_chart_year, last_chart_month, n)
- data_json = self.download_chart(ChartType.DAY, first_day_of_day, meter_point, meter_type)
- if data_json:
- data = json.loads(data_json)
- if data and data.get("mainChart") and len(data["mainChart"]) > 0:
- last_chart_day = n
- first_date = datetime(last_chart_year, last_chart_month, last_chart_day).date()
- print(f"Najstarsze dane historyczne dla licznika {p.name} (PPE: {p.ppe}, {p.id}) typ: {meter_type}: {first_date}")
- meter.first_date = first_date
- meter.save()
-
- def save_main_charts(self, mp, vals, m_type):
- for val in vals:
- try:
- logger.debug(f"save_main_charts: mp: {mp}, val: {val}, meter_type: {m_type}")
- z = val["zones"]
- if z[0]:
- # MainChartTable.get_or_create(tm = val["tm"], zone = 1, value = z[0], tarAvg=val["tarAvg"], est=val["est"], cplt=val["cplt"])
- try:
- existing_record = MainChartTable.get((MainChartTable.meter_type == m_type) & (MainChartTable.mp == mp) & (MainChartTable.tm == val["tm"]) & (MainChartTable.zone == 1))
- except MainChartTable.DoesNotExist:
- # Jeśli rekord nie istnieje, utwórz nowy
- MainChartTable.create(
- mp=mp,
- meter_type=m_type,
- tm=val["tm"],
- zone=1,
- value=z[0],
- tarAvg=val["tarAvg"],
- est=val["est"],
- cplt=val["cplt"]
- )
-
- if z[1]:
- try:
- existing_record = MainChartTable.get((MainChartTable.meter_type == m_type) & (MainChartTable.mp == mp) & (MainChartTable.tm == val["tm"]) & (MainChartTable.zone == 2))
- except MainChartTable.DoesNotExist:
- # Jeśli rekord nie istnieje, utwórz nowy
- MainChartTable.create(
- mp=mp,
- meter_type=m_type,
- tm=val["tm"],
- zone=2,
- value=z[1],
- tarAvg=val["tarAvg"],
- est=val["est"],
- cplt=val["cplt"]
- )
-
- if z[2]:
- try:
- existing_record = MainChartTable.get((MainChartTable.meter_type == m_type) & (MainChartTable.mp == mp) & (MainChartTable.tm == val["tm"]) & (MainChartTable.zone == 3))
- except MainChartTable.DoesNotExist:
- # Jeśli rekord nie istnieje, utwórz nowy
- MainChartTable.create(
- mp=mp,
- meter_type=m_type,
- tm=val["tm"],
- zone=3,
- value=z[2],
- tarAvg=val["tarAvg"],
- est=val["est"],
- cplt=val["cplt"]
- )
-
- except Exception as e:
- logging.error(f"Wystąpił błąd: {str(e)}")
-
- return None
-
- def download_chart(self, type, date, meter_point, meter_type, update_mode=False):
-
- if type == ChartType.DAY:
- chart_type = "DAY"
- first_day = datetime(date.year, date.month, date.day)
- tsm_date = int(time.mktime(first_day.timetuple()) * 1000)
-
- if type == ChartType.MONTH:
- chart_type = "MONTH"
- first_day = datetime(date.year, date.month, 1)
- tsm_date = int(time.mktime(first_day.timetuple()) * 1000)
-
- if type == ChartType.YEAR:
- chart_type = "YEAR"
- first_day = datetime(date.year, 1, 1)
- tsm_date = int(time.mktime(first_day.timetuple()) * 1000)
-
- # meter_type = 'A+'
- chart_url = f"{self.meter_url}/dp/resources/chart?mainChartDate={tsm_date}&type={chart_type}&meterPoint={meter_point}&mo={urllib.parse.quote_plus(meter_type)}"
- logger.debug(f"chart_url: {chart_url}")
- try:
- response = self.session.get(chart_url)
- data = json.loads(response.text)
- response.raise_for_status()
- if data["response"]:
- id = data["response"]["meterPoint"]
- mainChartDate = data["response"]["mainChartDate"]
- mainChart = data["response"]["mainChart"]
- if type == ChartType.DAY:
- self.save_main_charts(meter_point, mainChart, meter_type)
-
- date = int(mainChartDate) / 1000
- month = None
- day = None
- dt = datetime.fromtimestamp(date)
- year = dt.year
- if type == ChartType.MONTH:
- month = dt.month
- if type == ChartType.DAY:
- month = dt.month
- day = dt.day
- json_dump = json.dumps(data["response"], ensure_ascii=False)
- if update_mode:
- try:
- chart_record = ChartTable.get(id=id,year=year, month=month, day=day)
- chart_record.value = json_dump
- chart_record.save()
- except ChartTable.DoesNotExist:
- chart_record = ChartTable.create(id=id, meter_type=meter_type, value=json_dump, year=year, month=month, day=day)
-
- else:
- try:
- ChartTable.create(id=id, meter_type=meter_type, value=json_dump, year=year, month=month, day=day)
- except:
- pass
- return json_dump
- return None
- except HTTPError as e:
- logger.error(f"Wystąpił błąd HTTP: {e}")
-
- def download_charts(self, full_mode=False):
- query = PPETable.select().where(PPETable.is_active == True)
- result_ppes = query.execute()
- for p in result_ppes:
- meters_query = MeterTable.select().where((MeterTable.ppe_id == p.id)) # // & (MeterTable.first_date.is_null(True)))
- meters_result = meters_query.execute()
-
- for meter in meters_result:
- meter_type = meter.meter_type
-
- logger.info(f"Pobieram dane historyczne dla {p.name} ({p.id}) typ: {meter_type}")
- current_date = meter.first_date
- if not full_mode:
- current_date = meter.last_update_date - timedelta(days=1)
-
- while current_date <= date.today():
- try:
- record = ChartTable.get(id=p.id, meter_type=meter_type, year=current_date.year, month=current_date.month, day=current_date.day)
- # Jeśli rekord o określonych wartościach klucza głównego istnieje, zostanie pobrany.
- logger.debug(f"Posiadam dane historyczne dla {p.name} ({p.id}) typ: {meter_type} na dzień: {current_date}")
- except ChartTable.DoesNotExist:
- self.download_chart(ChartType.DAY, current_date, p.id, meter_type)
- logger.debug(f"Pobieram dane historyczne dla {p.name} ({p.id}) typ: {meter_type} na dzień: {current_date}")
- current_date += timedelta(days=1)
-
- def update_last_days(self):
- today = datetime.today().date()
- query = PPETable.select().where(PPETable.is_active == True)
- result_ppes = query.execute()
-
- for p in result_ppes:
- meters_query = MeterTable.select().where((MeterTable.ppe_id == p.id) & (MeterTable.first_date.is_null(True)))
- meters_result = meters_query.execute()
-
- for meter in meters_result:
- meter_type = meter.meter_type
-
- logger.info(f"Aktualizacja danych bieżących dla {p.name} ({p.id}) typ: {meter_type}")
- if not p.last_update_date:
- p.last_update_date = today - timedelta(days=5)
- p.save()
- last_update_date = p.last_update_date - timedelta(days=1)
- while last_update_date <= today:
- logger.debug(f"Aktualizacja danych dla {p.name} ({p.id}) typ: {meter_type} na dzień: {last_update_date}")
- self.download_chart(ChartType.DAY, last_update_date, p.id, meter_type, True)
- p.last_update_date = last_update_date
- p.save()
- last_update_date += timedelta(days=1)
-
- def get_current_meters(self, add_daily_char_data=False):
-
- query = PPETable.select().where(PPETable.is_active == True)
- result_ppes = query.execute()
- for p in result_ppes:
- if add_daily_char_data:
- query = ChartTable.select().where((ChartTable.id == p.id) & (ChartTable.year == p.measurement_date.year) & (ChartTable.month == p.measurement_date.month) & (ChartTable.day == p.measurement_date.day))
- query_count = query.count()
- if (query_count > 0):
- query_first = query.first()
- value_json = json.loads(query_first.value)
- print(query_first.value)
- zones = value_json.get("zones", [])
- if zones:
- zone1_data = zones[0]
- zone1_main_chart = zone1_data.get("mainChart", [])
-
- # def set_daily_zones(self):
- # query = PPETable.select().where(PPETable.is_active == True)
- # result_ppes = query.execute()
-
- # for p in result_ppes:
- # query = ChartTable.select().where(
- # (ChartTable.id == p.id) &
- # ((ChartTable.year > p.measurement_date.year) |
- # ((ChartTable.year == p.measurement_date.year) &
- # (ChartTable.month > p.measurement_date.month)) |
- # ((ChartTable.year == p.measurement_date.year) &
- # (ChartTable.month == p.measurement_date.month) &
- # (ChartTable.day >= p.measurement_date.day))
- # ))
-
- # zones_sums = {f"zone{i+1}_daily_chart_sum": 0.0 for i in range(3)}
-
- # for chart_entry in query:
- # value_json = json.loads(chart_entry.value)
- # main_chart = value_json.get("mainChart", [])
-
- # for entry in main_chart:
- # zones = entry.get("zones", [])
-
- # for i, value in enumerate(zones):
- # if value is not None:
- # zones_sums[f"zone{i+1}_daily_chart_sum"] += value
-
- # for key, value in zones_sums.items():
- # setattr(p, key, value)
- # p.save()
-
-
- def print_summary_zones(self):
- query = PPETable.select().where(PPETable.is_active == True)
- result_ppes = query.execute()
- for p in result_ppes:
- zon1 = (p.zone1 if p.zone1 is not None else 0 ) + (p.zone1_daily_chart_sum if p.zone1_daily_chart_sum is not None else 0)
- zon2 = (p.zone2 if p.zone2 is not None else 0 ) + (p.zone2_daily_chart_sum if p.zone2_daily_chart_sum is not None else 0)
- zon3 = (p.zone3 if p.zone3 is not None else 0 ) + (p.zone3_daily_chart_sum if p.zone3_daily_chart_sum is not None else 0)
- print(f"{p.name} : {round(zon1, 5)} "
- f"{round(zon2,5)} "
- f"{round(zon3,5)}")
-
- def get_current_meters_list(self):
- query = PPETable.select().where(PPETable.is_active == True)
- return query.execute()
-
- def get_current_meter_value(self, meter_id, zone):
- if zone == "zone1":
- pPETable = PPETable.get(PPETable.id == meter_id)
- return pPETable.zone1
- if zone == "zone2":
- pPETable = PPETable.get(PPETable.id == meter_id)
- return pPETable.zone2
- if zone == "zone3":
- pPETable = PPETable.get(PPETable.id == meter_id)
- return pPETable.zone3
- return None
\ No newline at end of file
diff --git a/srcdev/requirements.txt b/srcdev/requirements.txt
deleted file mode 100644
index d10d411..0000000
Binary files a/srcdev/requirements.txt and /dev/null differ
diff --git a/srcdev/run.sh b/srcdev/run.sh
deleted file mode 100644
index a85830e..0000000
--- a/srcdev/run.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/with-contenv bashio
-
-export USERNAME=$(bashio::config 'energa_username')
-export PASSWORD=$(bashio::config 'energa_password')
-export LOG_LEVEL=$(bashio::config 'log_level')
-
-bashio::log.info "Uruchamiam API"
-python run.py
-