modified: .gitignore
modified: src/CHANGELOG.md modified: src/Dockerfile modified: src/INSTALL.md modified: src/README.md modified: src/api.py modified: src/config.yaml deleted: src/cron.py modified: src/img/grafana_02.png modified: src/img/grafana_03.png modified: src/img/grafana_06.png deleted: src/img/grafana_07.png renamed: srcdev/img/grafana_08.png -> src/img/grafana_08.png renamed: srcdev/img/grafana_09.png -> src/img/grafana_09.png renamed: srcdev/img/grafana_10.png -> src/img/grafana_10.png renamed: srcdev/log_config.py -> src/log_config.py modified: src/main.py modified: src/moj_licznik.py renamed: srcdev/run.py -> src/run.py modified: src/run.sh deleted: srcdev/CHANGELOG.md deleted: srcdev/Dockerfile deleted: srcdev/INSTALL.md deleted: srcdev/README.md deleted: srcdev/api.py deleted: srcdev/config.yaml deleted: srcdev/cron.py deleted: srcdev/icon.png deleted: srcdev/img/addon.png deleted: srcdev/img/grafana_01.png deleted: srcdev/img/grafana_02.png deleted: srcdev/img/grafana_03.png deleted: srcdev/img/grafana_04.png deleted: srcdev/img/grafana_05.png deleted: srcdev/img/grafana_06.png deleted: srcdev/logo.png deleted: srcdev/main.py deleted: srcdev/moj_licznik.py deleted: srcdev/requirements.txt deleted: srcdev/run.sh
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
src/*.sqlite
|
src/*.sqlite
|
||||||
src/config.ini
|
src/config.ini
|
||||||
src/rund.sh
|
src/rund.sh
|
||||||
|
src.old/*
|
||||||
import.py
|
import.py
|
||||||
check.py
|
check.py
|
||||||
*.sqlite
|
*.sqlite
|
||||||
|
|||||||
@@ -1,4 +1,18 @@
|
|||||||
## v0.1.0 [2023-10-21]
|
# EnergaMeter
|
||||||
- Wersja beta
|
## 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]
|
## v0.1.1 [2023-10-22]
|
||||||
- Dodano obsługę błędnego logowania
|
- Dodano obsługę błędnego logowania
|
||||||
|
## v0.1.0 [2023-10-21]
|
||||||
|
- Wersja beta
|
||||||
@@ -6,12 +6,12 @@ RUN apk add --update py3-pip
|
|||||||
|
|
||||||
# Copy data for add-on
|
# Copy data for add-on
|
||||||
COPY run.sh /
|
COPY run.sh /
|
||||||
|
COPY run.py /
|
||||||
COPY requirements.txt /
|
COPY requirements.txt /
|
||||||
COPY main.py /
|
COPY main.py /
|
||||||
COPY api.py /
|
COPY api.py /
|
||||||
COPY cron.py /
|
|
||||||
# COPY database.sqlite /
|
|
||||||
COPY moj_licznik.py /
|
COPY moj_licznik.py /
|
||||||
|
COPY log_config.py /
|
||||||
RUN chmod a+x /run.sh
|
RUN chmod a+x /run.sh
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[](https://www.home-assistant.io/)
|
[](https://www.home-assistant.io/)
|
||||||
# [Energa meter](https://github.com/tcich/ha-addon-energa-meter) Home Assistant add-on
|
# [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
|
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
|
||||||
[amd64-shield]: https://img.shields.io/badge/amd64-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
|
[armv6-shield]: https://img.shields.io/badge/armv6-yes-green.svg
|
||||||
@@ -12,13 +12,19 @@
|
|||||||
![armv7-shield]
|
![armv7-shield]
|
||||||
![i386-shield]
|
![i386-shield]
|
||||||
|
|
||||||
<a href="https://buycoffee.to/tcich"><img src="../img/logo-buycoffee-wide.jpg" width=200 alt="Postaw kawę"></a>
|
|
||||||
|
|
||||||
|
[kawa-logo]: https://github.com/tcich/ha-addon-energa-meter/blob/main/img/buycoffeeto-btn-primary-outline.png
|
||||||
|
[kawa]: https://buycoffee.to/tcich
|
||||||
|
|
||||||
|
<a href="https://buycoffee.to/tcich"><img src="../img/logo-buycoffee-wide.jpg" width=200 alt="Postaw kawę"></a>
|
||||||
|
|
||||||
## O dodatku
|
## 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.
|
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
|
## Instalacja
|
||||||
1) Dodaj repozytorium do repozytoriów dodatków swojego HA za pomocą poniższego przycisku
|
1) Dodaj repozytorium do repozytoriów dodatków swojego HA za pomocą poniższego przycisku
|
||||||
|
|
||||||
@@ -47,59 +53,53 @@ Wymagane parametry:
|
|||||||
|
|
||||||
|
|
||||||
## Konfiguracja sensorów
|
## 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:
|
sensor:
|
||||||
- platform: rest
|
- platform: rest
|
||||||
resource: http://localhost:8000/meters/12335379
|
resource: http://localhost:8000/123456789/A%2B/1
|
||||||
name: "Energia aktualna T1"
|
name: "A+ Taryfa 1"
|
||||||
unique_id: 12335379_sumz1
|
unique_id: 123456789_apt1
|
||||||
unit_of_measurement: "kWh"
|
unit_of_measurement: "kWh"
|
||||||
value_template: "{{ value_json.meter.zone1.meter | round(2) }}"
|
value_template: "{{ value_json.countner.meter_value | round(2) }}"
|
||||||
- platform: rest
|
- platform: rest
|
||||||
resource: http://localhost:8000/meters/12335379
|
resource: http://localhost:8000/123456789/A%2B/2
|
||||||
name: "Dzienny odczyt licznika"
|
name: "A+ Taryfa 2"
|
||||||
unique_id: 12335379_meterz1
|
unique_id: 123456789_apt2
|
||||||
unit_of_measurement: "kWh"
|
unit_of_measurement: "kWh"
|
||||||
value_template: "{{ value_json.meter.zone1.sum | round(2) }}"
|
value_template: "{{ value_json.countner.meter_value | 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) }}"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Opis konfiguracji
|
### Opis konfiguracji
|
||||||
| element konfiguracji | Opis |
|
| 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|
|
| 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: "Energia aktualna" | Nazwa sensora, wpisz dowolną|
|
| name: "A+ Taryfa 1" | Nazwa sensora, wpisz dowolną|
|
||||||
| unique_id | Unikalny ID sensora, nie mogą być w systemie dwa sensory z tym samym ID|
|
| 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|
|
| 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|
|
| value_template: "{{ value_json.meter.countner.meter_value \| 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)|
|
|
||||||
|
|
||||||
|
|
||||||
## API dla wykresów, np. Grafana
|
## 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:
|
### Opis konfiguracji
|
||||||
* 12729 - jest to ID licznika
|
| element konfiguracji | Opis |
|
||||||
* start_date - początek okresu w milisekundach wg. standardu EPOCH (timestamp)
|
|-------------------|-------------------|
|
||||||
* end_date - koniec okresu w milisekundach wg. standardu EPOCH (timestamp)
|
| 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
|
## Jak dodać wykres do Grafana
|
||||||
### Źródło danych
|
### Źródło danych
|
||||||
@@ -118,12 +118,13 @@ gdzie:
|
|||||||
1) Przechodzimy do Dashboards
|
1) Przechodzimy do Dashboards
|
||||||
2) Klikamy New -> New dashboard -> Add visualization
|
2) Klikamy New -> New dashboard -> Add visualization
|
||||||
3) Wskazujemy Data source: ENERGA
|
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 **Fields** wpisujemy \$.charts\[\*\].czas typu Time oraz $.charts[*].value typu number z aliasem kWh
|
||||||
5) W **Params** wpisujemy Key: start_date Value: $__from
|
5) W **Params** wpisujemy Key: start_date Value: $__from
|
||||||
6) W **Params** wpisujemy Key: end_date Value: $__to
|
6) W **Params** wpisujemy Key: end_date Value: $__to
|
||||||
|
|
||||||
|
|
||||||
<img src="img/grafana_06.png" style="width: 80%;" alt="Grafana">
|
<img src="img/grafana_06.png" style="width: 80%;" alt="Grafana">
|
||||||
|
|
||||||
<img src="img/grafana_02.png" style="width: 80%;" alt="Grafana">
|
<img src="img/grafana_02.png" style="width: 80%;" alt="Grafana">
|
||||||
@@ -134,10 +135,23 @@ gdzie:
|
|||||||
|
|
||||||
<img src="img/grafana_05.png" style="width: 80%;" alt="Grafana">
|
<img src="img/grafana_05.png" style="width: 80%;" alt="Grafana">
|
||||||
|
|
||||||
|
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-
|
||||||
|
|
||||||
|
<img src="img/grafana_07.png" style="width: 80%;" alt="Grafana">
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
<img src="img/grafana_08.png" style="width: 80%;" alt="Grafana">
|
||||||
|
|
||||||
|
9) W **Params** aby uzyskać bilans energii należy dodać **Transform**, przy czym wartość energii oddawanej powinna być jako energia ujemna (parametr negative)
|
||||||
|
|
||||||
|
<img src="img/grafana_09.png" style="width: 80%;" alt="Grafana">
|
||||||
|
|
||||||
|
<img src="img/grafana_10.png" style="width: 80%;" alt="Grafana">
|
||||||
|
|
||||||
## Znane problemy
|
## Znane problemy
|
||||||
Czasami w aplikacji Mój Licznik włącza się captha (jeżeli masz dużo danych historycznych lub wielokrotnie instalujesz dodatek)
|
* 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
|
||||||
Dane wytwórcy (energia oddana oraz bilans) nie są dostępne, prace w tym zakresie trwają.
|
|
||||||
|
|
||||||
## Uwagi
|
## Uwagi
|
||||||
Dostęp do aktualnej wersji API nie jest zabezpieczony tokenem
|
Dostęp do aktualnej wersji API nie jest zabezpieczony tokenem
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[](https://www.home-assistant.io/)
|
[](https://www.home-assistant.io/)
|
||||||
# [Energa meter](https://github.com/tcich/ha-addon-energa-meter) Home Assistant add-on
|
# [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
|
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
|
||||||
[amd64-shield]: https://img.shields.io/badge/amd64-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
|
[armv6-shield]: https://img.shields.io/badge/armv6-yes-green.svg
|
||||||
@@ -13,10 +13,17 @@
|
|||||||
![i386-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
|
## 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.
|
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
|
## Instalacja
|
||||||
1) Dodaj repozytorium do repozytoriów dodatków swojego HA za pomocą poniższego przycisku
|
1) Dodaj repozytorium do repozytoriów dodatków swojego HA za pomocą poniższego przycisku
|
||||||
|
|
||||||
@@ -45,64 +52,72 @@ Wymagane parametry:
|
|||||||
|
|
||||||
|
|
||||||
## Konfiguracja sensorów
|
## 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:
|
sensor:
|
||||||
- platform: rest
|
- platform: rest
|
||||||
resource: http://localhost:8000/meters/12335379
|
resource: http://localhost:8000/123456789/A%2B/1
|
||||||
name: "Energia aktualna T1"
|
name: "A+ Taryfa 1"
|
||||||
unique_id: 12335379_sumz1
|
unique_id: 123456789_apt1
|
||||||
unit_of_measurement: "kWh"
|
unit_of_measurement: "kWh"
|
||||||
value_template: "{{ value_json.meter.zone1.meter | round(2) }}"
|
value_template: "{{ value_json.countner.meter_value | round(2) }}"
|
||||||
- platform: rest
|
- platform: rest
|
||||||
resource: http://localhost:8000/meters/12335379
|
resource: http://localhost:8000/123456789/A%2B/2
|
||||||
name: "Dzienny odczyt licznika"
|
name: "A+ Taryfa 2"
|
||||||
unique_id: 12335379_meterz1
|
unique_id: 123456789_apt2
|
||||||
unit_of_measurement: "kWh"
|
unit_of_measurement: "kWh"
|
||||||
value_template: "{{ value_json.meter.zone1.sum | round(2) }}"
|
value_template: "{{ value_json.countner.meter_value | round(2) }}"
|
||||||
- platform: rest
|
```
|
||||||
resource: http://localhost:8000/meters/12335379
|
## Suma liczników, bilans
|
||||||
name: "Energia aktualna T2"
|
W celu uzyskania sumy liczników, bilansu, itp należy użyć templates:
|
||||||
unique_id: 12335379_sumz2
|
```
|
||||||
unit_of_measurement: "kWh"
|
template:
|
||||||
value_template: "{{ value_json.meter.zone2.meter | round(2) }}"
|
- sensor:
|
||||||
- platform: rest
|
- name: "Suma liczników"
|
||||||
resource: http://localhost:8000/meters/12335379
|
unit_of_measurement: "kWh"
|
||||||
name: "Dzienny odczyt licznika"
|
state: "{{ states('sensor.123456789_apt1') | float + states('sensor.123456789_apt2') | float | round(2) }}"
|
||||||
unique_id: 12335379_meterz2
|
- sensor:
|
||||||
unit_of_measurement: "kWh"
|
- name: "Bilans/różnica liczników"
|
||||||
value_template: "{{ value_json.meter.zone2.sum | round(2) }}"
|
unit_of_measurement: "kWh"
|
||||||
|
state: "{{ states('sensor.123456789_apt1') | float - states('sensor.123456789_apt2') | float | round(2) }}"
|
||||||
```
|
```
|
||||||
|
|
||||||
# Opis konfiguracji
|
|
||||||
|
### Opis konfiguracji
|
||||||
| element konfiguracji | Opis |
|
| 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|
|
| 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: "Energia aktualna" | Nazwa sensora, wpisz dowolną|
|
| name: "A+ Taryfa 1" | Nazwa sensora, wpisz dowolną|
|
||||||
| unique_id | Unikalny ID sensora, nie mogą być w systemie dwa sensory z tym samym ID|
|
| 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|
|
| 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|
|
| value_template: "{{ value_json.meter.countner.meter_value \| 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)|
|
|
||||||
|
|
||||||
|
|
||||||
## API dla wykresów, np. Grafana
|
## 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
|
## Grafana
|
||||||
|
|
||||||
Instrukcja konfiguracji dla Grafana znajduje się tutaj [link](https://github.com/tcich/ha-addon-energa-meter/tree/e702ed49436fb9e0b675dcac3001bd9de5aab3c0/src/INSTALL.md)
|
Instrukcja konfiguracji dla Grafana znajduje się tutaj [link](https://github.com/tcich/ha-addon-energa-meter/tree/e702ed49436fb9e0b675dcac3001bd9de5aab3c0/src/INSTALL.md)
|
||||||
|
|
||||||
|
|
||||||
## Znane problemy
|
## Znane problemy
|
||||||
Czasami w aplikacji Mój Licznik włącza się captha (jeżeli masz dużo danych historycznych lub wielokrotnie instalujesz dodatek)
|
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ą.
|
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
|
## Uwagi
|
||||||
Dostęp do aktualnej wersji API nie jest zabezpieczony tokenem
|
Dostęp do aktualnej wersji API nie jest zabezpieczony tokenem
|
||||||
Każde przeinstalowanie dodatku pobiera ponownie dane z aplikacji Mój Licznik
|
Każde przeinstalowanie dodatku pobiera ponownie dane z aplikacji Mój Licznik
|
||||||
|
|
||||||
|
|||||||
295
src/api.py
@@ -1,37 +1,67 @@
|
|||||||
from peewee import SqliteDatabase
|
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
|
from waitress import serve
|
||||||
import time
|
#from datetime
|
||||||
from datetime import datetime
|
import datetime
|
||||||
from moj_licznik import PPETable, MainChartTable
|
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__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
db = SqliteDatabase('database.sqlite')
|
|
||||||
|
|
||||||
@app.route('/', methods=['GET'])
|
@app.route('/', methods=['GET'])
|
||||||
def root_redirect():
|
def root():
|
||||||
query = PPETable.select().where(PPETable.is_active == True)
|
query = PPETable.select() #.where(PPETable.is_active == True)
|
||||||
result_ppes = list(query)
|
result_ppes = list(query)
|
||||||
meters = []
|
ppes = []
|
||||||
|
|
||||||
for p in result_ppes:
|
for p in result_ppes:
|
||||||
meter = {
|
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,
|
'name': p.name,
|
||||||
'id': p.id
|
'id': p.id,
|
||||||
|
'type': p.type,
|
||||||
|
'isActive': p.is_active,
|
||||||
|
'meters': meters
|
||||||
}
|
}
|
||||||
|
ppes.append(ppe)
|
||||||
|
logger.debug("API: GET /")
|
||||||
|
|
||||||
meters.append(meter)
|
return jsonify({'ppes': ppes})
|
||||||
if DEBUG:
|
|
||||||
print("API: GET /")
|
|
||||||
|
|
||||||
return jsonify({'meters': meters})
|
|
||||||
|
|
||||||
@app.route('/meters', methods=['GET'])
|
@app.route('/meters', methods=['GET'])
|
||||||
|
@app.route('/meters/', methods=['GET'])
|
||||||
def meters():
|
def meters():
|
||||||
query = PPETable.select().where(PPETable.is_active == True)
|
query = PPETable.select() #.where(PPETable.is_active == True)
|
||||||
result_ppes = list(query)
|
result_ppes = list(query)
|
||||||
meters = []
|
meters = []
|
||||||
|
|
||||||
@@ -40,11 +70,11 @@ def meters():
|
|||||||
'name': p.name,
|
'name': p.name,
|
||||||
'id': p.id,
|
'id': p.id,
|
||||||
'ppe': p.ppe,
|
'ppe': p.ppe,
|
||||||
'number_of_zones': p.number_of_zones,
|
'number_of_zones': '',#p.number_of_zones,
|
||||||
'tariffCode': p.tariffCode,
|
'tariffCode': p.tariffCode,
|
||||||
'first_date': p.first_date,
|
# 'first_date': p.first_date,
|
||||||
'last_update_date': p.last_update_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):
|
for i in range(1, p.number_of_zones + 1):
|
||||||
@@ -65,139 +95,148 @@ def meters():
|
|||||||
}
|
}
|
||||||
|
|
||||||
meters.append(meter)
|
meters.append(meter)
|
||||||
if DEBUG:
|
logger.debug("GET /meters")
|
||||||
print("API: GET /")
|
|
||||||
|
|
||||||
return jsonify({'meters': meters})
|
return jsonify({'meters': meters})
|
||||||
|
|
||||||
@app.route('/meters/<int:meter_id>', 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:
|
@app.route('/<int:ppe_id>', methods=['GET'])
|
||||||
p = result_ppes[0] # There should be only one matching record
|
@app.route('/<int:ppe_id>/', 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('/<int:ppe_id>/<meter_type_url>', methods=['GET'])
|
||||||
|
@app.route('/<int:ppe_id>/<meter_type_url>/', 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 = {
|
||||||
'name': p.name,
|
'meter_type': meter.meter_type,
|
||||||
'id': p.id,
|
'meter_type_url': urllib.parse.quote_plus(meter.meter_type),
|
||||||
'ppe': p.ppe,
|
'last_update_date': meter.last_update_date,
|
||||||
'number_of_zones': p.number_of_zones,
|
'first_date': meter.first_date,
|
||||||
'tariffCode': p.tariffCode,
|
'countners': countners
|
||||||
'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):
|
logger.debug(f"API: GET /{ppe_id}/{meter_type_url}")
|
||||||
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}")
|
|
||||||
return jsonify({'meter': meter})
|
return jsonify({'meter': meter})
|
||||||
else:
|
|
||||||
return jsonify({'error': 'Meter not found'}, 404)
|
@app.route('/<int:ppe_id>/<meter_type_url>/<tariff>', methods=['GET'])
|
||||||
|
@app.route('/<int:ppe_id>/<meter_type_url>/<tariff>/', 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/<int:meter_id>', 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:
|
@app.route('/charts', methods=['GET'])
|
||||||
# p = result_ppes[0] # There should be only one matching record
|
@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
|
|
||||||
# }
|
|
||||||
|
|
||||||
# for i in range(1, p.number_of_zones + 1):
|
current_time = datetime.datetime.now()
|
||||||
# zone_key = f'zone{i}'
|
current_time_unix = time.mktime(current_time.timetuple())
|
||||||
# daily_chart_key = f'zone{i}_daily_chart_sum'
|
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)
|
||||||
|
|
||||||
# meter[zone_key] = getattr(p, zone_key)
|
if meter_type_url:
|
||||||
# meter[daily_chart_key] = getattr(p, daily_chart_key)
|
meter_type = urllib.parse.unquote(meter_type_url)
|
||||||
|
query = query.where(MainChartTable.meter_type == meter_type)
|
||||||
|
|
||||||
# print(f"API: GET /meters/{meter_id}")
|
if zone:
|
||||||
# return jsonify({'meter': meter})
|
query = query.where(MainChartTable.zone == zone)
|
||||||
# else:
|
|
||||||
# return jsonify({'error': 'Meter not found'}, 404)
|
|
||||||
|
|
||||||
@app.route('/charts/<mp>', 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)
|
result_ppes = list(query)
|
||||||
charts = []
|
charts = []
|
||||||
|
|
||||||
for p in result_ppes:
|
for p in result_ppes:
|
||||||
czas = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(p.tm/1000))
|
czas = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(p.tm/1000))
|
||||||
|
|
||||||
chart = {
|
chart = {
|
||||||
'mp': p.mp,
|
'mp': p.mp,
|
||||||
|
'meter_type': p.meter_type,
|
||||||
|
'meter_type_url': urllib.parse.quote_plus(p.meter_type),
|
||||||
'zone': p.zone,
|
'zone': p.zone,
|
||||||
'tm': p.tm,
|
'time_tm': p.tm,
|
||||||
'czas': czas,
|
'time': czas,
|
||||||
'value': p.value
|
'value': p.value * factor
|
||||||
}
|
}
|
||||||
charts.append(chart)
|
charts.append(chart)
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
print(f"API: GET / - {start_date} - {end_date}")
|
|
||||||
|
|
||||||
return jsonify({'charts': charts})
|
return jsonify({'charts': charts})
|
||||||
|
|
||||||
@app.route('/<mp>/<zone>', 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)
|
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
name: "Energa meter"
|
name: "Energa meter"
|
||||||
description: "Energa meter addon"
|
description: "Energa meter addon"
|
||||||
version: "0.1.1"
|
version: "1.0.0"
|
||||||
slug: "energa_meter"
|
slug: "energa_meter"
|
||||||
init: false
|
init: false
|
||||||
options:
|
options:
|
||||||
energa_username: ""
|
energa_username: ""
|
||||||
energa_password: ""
|
energa_password: ""
|
||||||
|
log_level: "INFO"
|
||||||
schema:
|
schema:
|
||||||
energa_username: str
|
energa_username: str
|
||||||
energa_password: password
|
energa_password: password
|
||||||
|
log_level: str
|
||||||
arch:
|
arch:
|
||||||
- aarch64
|
- aarch64
|
||||||
- amd64
|
- amd64
|
||||||
|
|||||||
33
src/cron.py
@@ -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()
|
|
||||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
31
src/main.py
@@ -3,33 +3,24 @@ from moj_licznik import MojLicznik
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
plik = Path('config.ini')
|
|
||||||
username = None
|
username = None
|
||||||
password = None
|
password = None
|
||||||
if plik.is_file():
|
username = os.getenv("USERNAME")
|
||||||
print(f"Pobieram parametry z config.ini.")
|
password = os.getenv("PASSWORD")
|
||||||
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")
|
|
||||||
|
|
||||||
print(f"Inicjacja...")
|
print(f"Inicjacja...")
|
||||||
mojLicznik = MojLicznik()
|
mojLicznik = MojLicznik()
|
||||||
print(f"Logowanie...", username)
|
print(f"Logowanie...", username)
|
||||||
mojLicznik.login(username, password)
|
mojLicznik.login(username, password)
|
||||||
if mojLicznik.loginStatus:
|
print(f"Aktualizacja liczników...")
|
||||||
print(f"Aktualizacja liczników...")
|
mojLicznik.uppdate_measurments()
|
||||||
mojLicznik.uppdate_measurments()
|
print(f"Wyszukiwanie najstarszych danych...")
|
||||||
print(f"Wyszukiwanie najstarszych danych...")
|
mojLicznik.update_first_date()
|
||||||
mojLicznik.update_first_date()
|
print(f"Pobieranie danych...")
|
||||||
print(f"Pobieranie danych...")
|
mojLicznik.download_charts(True)
|
||||||
mojLicznik.download_charts(True)
|
mojLicznik.update_last_days()
|
||||||
mojLicznik.update_last_days()
|
mojLicznik.set_daily_zones()
|
||||||
mojLicznik.set_daily_zones()
|
mojLicznik.logout()
|
||||||
mojLicznik.logout()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
from peewee import SqliteDatabase
|
from peewee import SqliteDatabase
|
||||||
from datetime import datetime, timedelta, date
|
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
|
import http.cookiejar as cookiejar
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from enum import Enum
|
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):
|
class ChartType(Enum):
|
||||||
DAY = "DAY"
|
DAY = "DAY"
|
||||||
@@ -15,26 +20,54 @@ class ChartType(Enum):
|
|||||||
YEAR = "YEAR"
|
YEAR = "YEAR"
|
||||||
|
|
||||||
class PPETable(Model):
|
class PPETable(Model):
|
||||||
ppe = CharField()
|
id = CharField(primary_key=True)
|
||||||
|
ppe = CharField(unique=True)
|
||||||
tariffCode = CharField()
|
tariffCode = CharField()
|
||||||
|
type = CharField()
|
||||||
name = 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)
|
|
||||||
is_active = BooleanField(default=True)
|
|
||||||
measurement_date = DateField(null=True)
|
|
||||||
first_date = DateField(null=True)
|
|
||||||
last_update_date = DateField(null=True)
|
last_update_date = DateField(null=True)
|
||||||
|
is_active = BooleanField(default=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
database = db
|
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):
|
class ChartTable(Model):
|
||||||
id = IntegerField()
|
id = IntegerField()
|
||||||
|
meter_type = CharField()
|
||||||
year = IntegerField()
|
year = IntegerField()
|
||||||
month = IntegerField(null=True)
|
month = IntegerField(null=True)
|
||||||
day = IntegerField(null=True)
|
day = IntegerField(null=True)
|
||||||
@@ -42,10 +75,12 @@ class ChartTable(Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
database = db
|
database = db
|
||||||
|
table_name = 'CHART_CACHE'
|
||||||
primary_key = CompositeKey('id', 'year', 'month', 'day')
|
primary_key = CompositeKey('id', 'year', 'month', 'day')
|
||||||
|
|
||||||
class MainChartTable(Model):
|
class MainChartTable(Model):
|
||||||
mp = CharField()
|
mp = CharField()
|
||||||
|
meter_type = CharField()
|
||||||
zone = IntegerField()
|
zone = IntegerField()
|
||||||
tm = IntegerField()
|
tm = IntegerField()
|
||||||
value = DecimalField(max_digits=20, decimal_places=16, null=True)
|
value = DecimalField(max_digits=20, decimal_places=16, null=True)
|
||||||
@@ -55,9 +90,74 @@ class MainChartTable(Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
database = db
|
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:
|
class MojLicznik:
|
||||||
|
|
||||||
@@ -66,9 +166,13 @@ class MojLicznik:
|
|||||||
|
|
||||||
meter_url = "https://mojlicznik.energa-operator.pl"
|
meter_url = "https://mojlicznik.energa-operator.pl"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def databaseInit(self):
|
def databaseInit(self):
|
||||||
db.create_tables([ChartTable], safe=True)
|
|
||||||
db.create_tables([PPETable], 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)
|
db.create_tables([MainChartTable], safe=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -87,13 +191,18 @@ class MojLicznik:
|
|||||||
|
|
||||||
login_url = f"{self.meter_url}/dp/UserLogin.do"
|
login_url = f"{self.meter_url}/dp/UserLogin.do"
|
||||||
|
|
||||||
|
self.loginStatus = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logger.debug("Pobieram formularz logowania.")
|
||||||
response = self.session.get(login_url)
|
response = self.session.get(login_url)
|
||||||
response.raise_for_status()
|
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:
|
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')
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
csrf_token = soup.find('input', {'name': '_antixsrf'})['value']
|
csrf_token = soup.find('input', {'name': '_antixsrf'})['value']
|
||||||
@@ -113,7 +222,7 @@ class MojLicznik:
|
|||||||
|
|
||||||
|
|
||||||
except HTTPError as e:
|
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')
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
|
|
||||||
@@ -121,12 +230,16 @@ class MojLicznik:
|
|||||||
login_error = soup.find('div', text=login_error_text)
|
login_error = soup.find('div', text=login_error_text)
|
||||||
|
|
||||||
if login_error:
|
if login_error:
|
||||||
self.loginStatus = False
|
logger.critical(login_error_text)
|
||||||
print(login_error_text)
|
return
|
||||||
else:
|
else:
|
||||||
self.loginStatus = True
|
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')
|
select_elements = soup.find_all('script', type='text/javascript')
|
||||||
meter_isd = []
|
meter_isd = []
|
||||||
for el in select_elements:
|
for el in select_elements:
|
||||||
@@ -140,16 +253,17 @@ class MojLicznik:
|
|||||||
meter_isd.append(id_value)
|
meter_isd.append(id_value)
|
||||||
retrieved_record = PPETable.get_or_none(id=id_value)
|
retrieved_record = PPETable.get_or_none(id=id_value)
|
||||||
if retrieved_record:
|
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:
|
if not retrieved_record.is_active:
|
||||||
retrieved_record.is_active = True
|
retrieved_record.is_active = True
|
||||||
retrieved_record.save()
|
retrieved_record.save()
|
||||||
else:
|
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(
|
data = PPETable.create(
|
||||||
id=id_value,
|
id=id_value,
|
||||||
ppe=ppe_value,
|
ppe=ppe_value,
|
||||||
tariffCode=tariffCode_value,
|
tariffCode=tariffCode_value,
|
||||||
|
type=type_value,
|
||||||
name=name_value
|
name=name_value
|
||||||
)
|
)
|
||||||
update_query = PPETable.update(is_active=0).where(PPETable.id.not_in(meter_isd))
|
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 = self.session.get(logout_url)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
self.loginStatus = False
|
self.loginStatus = False
|
||||||
print(f"Wylogowano.")
|
logger.info(f"Wylogowano.")
|
||||||
except HTTPError as e:
|
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)
|
query = PPETable.select().where(PPETable.is_active == True)
|
||||||
result_ppes = query.execute()
|
result_ppes = query.execute()
|
||||||
@@ -175,87 +289,86 @@ class MojLicznik:
|
|||||||
response = self.session.get(meter_url)
|
response = self.session.get(meter_url)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
soup = BeautifulSoup(response.text, 'html.parser')
|
soup = BeautifulSoup(response.text, 'html.parser')
|
||||||
td_elements = soup.find_all('td', class_='last')
|
countners_dict = findCountners(soup)
|
||||||
date_divs = soup.find_all("div", style="font-size: 10px")
|
|
||||||
|
|
||||||
for div in date_divs:
|
for c in countners_dict:
|
||||||
p.measurement_date = datetime.strptime(div.text.strip(), "%Y-%m-%d %H:%M").date()
|
mn, mu = MeterTable.get_or_create(ppe_id=p.id, meter_type=c['meter_type'])
|
||||||
i = 0
|
mn.last_update_date = datetime.now()
|
||||||
|
mn.save()
|
||||||
|
cn, cu = CounterTable.get_or_create(
|
||||||
|
meter_id = mn.id,
|
||||||
|
tariff=c['tariff']
|
||||||
|
)
|
||||||
|
|
||||||
for td in td_elements:
|
cn.meter_value = c['meter_value']
|
||||||
text = td.get_text()
|
cn.measurement_date = c['measurement_date']
|
||||||
cleaned_text = re.sub(r'[^\d,]', '', text)
|
cn.save()
|
||||||
cleaned_number_str = cleaned_text.lstrip('0').replace(',', '.')
|
|
||||||
i = i + 1
|
logger.info(f"Zapisano stan licznika {p.id} {c['meter_type']} taryfa {c['tariff']} z dnia: {c['measurement_date']} : {c['meter_value']}")
|
||||||
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}")
|
|
||||||
except HTTPError as e:
|
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):
|
def update_first_date(self):
|
||||||
query = PPETable.select().where(PPETable.first_date.is_null(True) & (PPETable.is_active == True))
|
ppes_query = PPETable.select().where(PPETable.is_active == True)
|
||||||
result_ppes = query.execute()
|
result_ppes = ppes_query.execute()
|
||||||
for p in result_ppes:
|
for p in result_ppes:
|
||||||
print(f"Szukam najstarsze dane historyczne licznika {p.name}")
|
meters_query = MeterTable.select().where((MeterTable.ppe_id == p.id) & (MeterTable.first_date.is_null(True)))
|
||||||
meter_point = p.id
|
meters_result = meters_query.execute()
|
||||||
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()
|
|
||||||
|
|
||||||
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:
|
for val in vals:
|
||||||
#try:
|
try:
|
||||||
|
logger.debug(f"save_main_charts: mp: {mp}, val: {val}, meter_type: {m_type}")
|
||||||
z = val["zones"]
|
z = val["zones"]
|
||||||
# {"tm": "1690412400000", "tarAvg": 0.3899153269199055, "zones": [null, 0.232, null], "est": false, "cplt": true},
|
|
||||||
if z[0]:
|
if z[0]:
|
||||||
# MainChartTable.get_or_create(tm = val["tm"], zone = 1, value = z[0], tarAvg=val["tarAvg"], est=val["est"], cplt=val["cplt"])
|
# MainChartTable.get_or_create(tm = val["tm"], zone = 1, value = z[0], tarAvg=val["tarAvg"], est=val["est"], cplt=val["cplt"])
|
||||||
try:
|
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:
|
except MainChartTable.DoesNotExist:
|
||||||
# Jeśli rekord nie istnieje, utwórz nowy
|
# Jeśli rekord nie istnieje, utwórz nowy
|
||||||
MainChartTable.create(
|
MainChartTable.create(
|
||||||
mp=mp,
|
mp=mp,
|
||||||
|
meter_type=m_type,
|
||||||
tm=val["tm"],
|
tm=val["tm"],
|
||||||
zone=1,
|
zone=1,
|
||||||
value=z[0],
|
value=z[0],
|
||||||
@@ -266,11 +379,12 @@ class MojLicznik:
|
|||||||
|
|
||||||
if z[1]:
|
if z[1]:
|
||||||
try:
|
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:
|
except MainChartTable.DoesNotExist:
|
||||||
# Jeśli rekord nie istnieje, utwórz nowy
|
# Jeśli rekord nie istnieje, utwórz nowy
|
||||||
MainChartTable.create(
|
MainChartTable.create(
|
||||||
mp=mp,
|
mp=mp,
|
||||||
|
meter_type=m_type,
|
||||||
tm=val["tm"],
|
tm=val["tm"],
|
||||||
zone=2,
|
zone=2,
|
||||||
value=z[1],
|
value=z[1],
|
||||||
@@ -281,11 +395,12 @@ class MojLicznik:
|
|||||||
|
|
||||||
if z[2]:
|
if z[2]:
|
||||||
try:
|
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:
|
except MainChartTable.DoesNotExist:
|
||||||
# Jeśli rekord nie istnieje, utwórz nowy
|
# Jeśli rekord nie istnieje, utwórz nowy
|
||||||
MainChartTable.create(
|
MainChartTable.create(
|
||||||
mp=mp,
|
mp=mp,
|
||||||
|
meter_type=m_type,
|
||||||
tm=val["tm"],
|
tm=val["tm"],
|
||||||
zone=3,
|
zone=3,
|
||||||
value=z[2],
|
value=z[2],
|
||||||
@@ -294,12 +409,12 @@ class MojLicznik:
|
|||||||
cplt=val["cplt"]
|
cplt=val["cplt"]
|
||||||
)
|
)
|
||||||
|
|
||||||
#except:
|
except Exception as e:
|
||||||
# pass
|
logging.error(f"Wystąpił błąd: {str(e)}")
|
||||||
|
|
||||||
return None
|
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:
|
if type == ChartType.DAY:
|
||||||
chart_type = "DAY"
|
chart_type = "DAY"
|
||||||
@@ -316,7 +431,9 @@ class MojLicznik:
|
|||||||
first_day = datetime(date.year, 1, 1)
|
first_day = datetime(date.year, 1, 1)
|
||||||
tsm_date = int(time.mktime(first_day.timetuple()) * 1000)
|
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:
|
try:
|
||||||
response = self.session.get(chart_url)
|
response = self.session.get(chart_url)
|
||||||
data = json.loads(response.text)
|
data = json.loads(response.text)
|
||||||
@@ -326,7 +443,7 @@ class MojLicznik:
|
|||||||
mainChartDate = data["response"]["mainChartDate"]
|
mainChartDate = data["response"]["mainChartDate"]
|
||||||
mainChart = data["response"]["mainChart"]
|
mainChart = data["response"]["mainChart"]
|
||||||
if type == ChartType.DAY:
|
if type == ChartType.DAY:
|
||||||
self.save_main_charts(meter_point, mainChart)
|
self.save_main_charts(meter_point, mainChart, meter_type)
|
||||||
|
|
||||||
date = int(mainChartDate) / 1000
|
date = int(mainChartDate) / 1000
|
||||||
month = None
|
month = None
|
||||||
@@ -345,35 +462,42 @@ class MojLicznik:
|
|||||||
chart_record.value = json_dump
|
chart_record.value = json_dump
|
||||||
chart_record.save()
|
chart_record.save()
|
||||||
except ChartTable.DoesNotExist:
|
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:
|
else:
|
||||||
try:
|
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:
|
except:
|
||||||
pass
|
pass
|
||||||
return json_dump
|
return json_dump
|
||||||
return None
|
return None
|
||||||
except HTTPError as e:
|
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):
|
def download_charts(self, full_mode=False):
|
||||||
query = PPETable.select().where(PPETable.is_active == True)
|
query = PPETable.select().where(PPETable.is_active == True)
|
||||||
result_ppes = query.execute()
|
result_ppes = query.execute()
|
||||||
for p in result_ppes:
|
for p in result_ppes:
|
||||||
current_date = p.first_date
|
meters_query = MeterTable.select().where((MeterTable.ppe_id == p.id)) # // & (MeterTable.first_date.is_null(True)))
|
||||||
if not full_mode:
|
meters_result = meters_query.execute()
|
||||||
current_date = p.measurement_date - timedelta(days=1)
|
|
||||||
|
|
||||||
while current_date <= date.today():
|
for meter in meters_result:
|
||||||
try:
|
meter_type = meter.meter_type
|
||||||
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.
|
logger.info(f"Pobieram dane historyczne dla {p.name} ({p.id}) typ: {meter_type}")
|
||||||
print(f"Posiadam dane historyczne dla {p.name} na dzień: {current_date}")
|
current_date = meter.first_date
|
||||||
except ChartTable.DoesNotExist:
|
if not full_mode:
|
||||||
self.download_chart(ChartType.DAY, current_date, p.id)
|
current_date = meter.last_update_date - timedelta(days=1)
|
||||||
print(f"Pobieram dane historyczne dla {p.name} na dzień: {current_date}")
|
|
||||||
current_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):
|
def update_last_days(self):
|
||||||
today = datetime.today().date()
|
today = datetime.today().date()
|
||||||
@@ -381,16 +505,23 @@ class MojLicznik:
|
|||||||
result_ppes = query.execute()
|
result_ppes = query.execute()
|
||||||
|
|
||||||
for p in result_ppes:
|
for p in result_ppes:
|
||||||
if not p.last_update_date:
|
meters_query = MeterTable.select().where((MeterTable.ppe_id == p.id) & (MeterTable.first_date.is_null(True)))
|
||||||
p.last_update_date = today - timedelta(days=5)
|
meters_result = meters_query.execute()
|
||||||
p.save()
|
|
||||||
last_update_date = p.last_update_date - timedelta(days=1)
|
for meter in meters_result:
|
||||||
while last_update_date <= today:
|
meter_type = meter.meter_type
|
||||||
print(f"Aktualizacja danych dla {p.name} na dzień: {last_update_date}")
|
|
||||||
self.download_chart(ChartType.DAY, last_update_date, p.id, True)
|
logger.info(f"Aktualizacja danych bieżących dla {p.name} ({p.id}) typ: {meter_type}")
|
||||||
p.last_update_date = last_update_date
|
if not p.last_update_date:
|
||||||
p.save()
|
p.last_update_date = today - timedelta(days=5)
|
||||||
last_update_date += timedelta(days=1)
|
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):
|
def get_current_meters(self, add_daily_char_data=False):
|
||||||
|
|
||||||
@@ -408,81 +539,38 @@ class MojLicznik:
|
|||||||
if zones:
|
if zones:
|
||||||
zone1_data = zones[0]
|
zone1_data = zones[0]
|
||||||
zone1_main_chart = zone1_data.get("mainChart", [])
|
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):
|
# def set_daily_zones(self):
|
||||||
# query = PPETable.select().where(PPETable.is_active == True)
|
# query = PPETable.select().where(PPETable.is_active == True)
|
||||||
# result_ppes = query.execute()
|
# result_ppes = query.execute()
|
||||||
|
|
||||||
# for p in result_ppes:
|
# 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 = ChartTable.select().where(
|
||||||
# query_count = query.count()
|
# (ChartTable.id == p.id) &
|
||||||
# if (query_count > 0):
|
# ((ChartTable.year > p.measurement_date.year) |
|
||||||
# query_first = query.first()
|
# ((ChartTable.year == p.measurement_date.year) &
|
||||||
# value_json = json.loads(query_first.value)
|
# (ChartTable.month > p.measurement_date.month)) |
|
||||||
# main_chart = value_json.get("mainChart", [])
|
# ((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}_daily_chart_sum": 0.0 for i in range(3)}
|
||||||
# zones_sums = {f"zone{i+1}": 0.0 for i in range(len(main_chart[0].get("zones", [])))}
|
|
||||||
|
# for chart_entry in query:
|
||||||
|
# value_json = json.loads(chart_entry.value)
|
||||||
|
# main_chart = value_json.get("mainChart", [])
|
||||||
|
|
||||||
# for entry in main_chart:
|
# for entry in main_chart:
|
||||||
# zones = entry.get("zones", [])
|
# zones = entry.get("zones", [])
|
||||||
|
|
||||||
# for i, value in enumerate(zones):
|
# for i, value in enumerate(zones):
|
||||||
# if value is not None:
|
# if value is not None:
|
||||||
# zones_sums[f"zone{i+1}"] += value
|
# zones_sums[f"zone{i+1}_daily_chart_sum"] += value
|
||||||
# if (zones_sums["zone1"] > 0):
|
|
||||||
# p.zone1_daily_chart_sum = zones_sums["zone1"]
|
# for key, value in zones_sums.items():
|
||||||
# else:
|
# setattr(p, key, value)
|
||||||
# p.zone1_daily_chart_sum = None
|
# p.save()
|
||||||
# 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()
|
|
||||||
|
|
||||||
|
|
||||||
def print_summary_zones(self):
|
def print_summary_zones(self):
|
||||||
|
|||||||
12
src/run.sh
@@ -2,16 +2,8 @@
|
|||||||
|
|
||||||
export USERNAME=$(bashio::config 'energa_username')
|
export USERNAME=$(bashio::config 'energa_username')
|
||||||
export PASSWORD=$(bashio::config 'energa_password')
|
export PASSWORD=$(bashio::config 'energa_password')
|
||||||
|
export LOG_LEVEL=$(bashio::config 'log_level')
|
||||||
|
|
||||||
bashio::log.info "Uruchamiam API"
|
bashio::log.info "Uruchamiam API"
|
||||||
python api.py &
|
python run.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
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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" ]
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
<a href="https://buycoffee.to/tcich"><img src="../img/logo-buycoffee-wide.jpg" width=200 alt="Postaw kawę"></a>
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
<img src="https://github.com/tcich/ha-addon-energa-meter/blob/e702ed49436fb9e0b675dcac3001bd9de5aab3c0/srcdev/img/addon.png" style="width: 80%;" alt="Nazwa hosta">
|
|
||||||
|
|
||||||
<img src="img/grafana_01.png" style="width: 80%;" alt="Grafana źródło danych">
|
|
||||||
|
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
<img src="img/grafana_06.png" style="width: 80%;" alt="Grafana">
|
|
||||||
|
|
||||||
<img src="img/grafana_02.png" style="width: 80%;" alt="Grafana">
|
|
||||||
|
|
||||||
<img src="img/grafana_03.png" style="width: 80%;" alt="Grafana">
|
|
||||||
|
|
||||||
<img src="img/grafana_04.png" style="width: 80%;" alt="Grafana">
|
|
||||||
|
|
||||||
<img src="img/grafana_05.png" style="width: 80%;" alt="Grafana">
|
|
||||||
|
|
||||||
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-
|
|
||||||
|
|
||||||
<img src="img/grafana_07.png" style="width: 80%;" alt="Grafana">
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
<img src="img/grafana_08.png" style="width: 80%;" alt="Grafana">
|
|
||||||
|
|
||||||
9) W **Params** aby uzyskać bilans energii należy dodać **Transform**, przy czym wartość energii oddawanej powinna być jako energia ujemna (parametr negative)
|
|
||||||
|
|
||||||
<img src="img/grafana_09.png" style="width: 80%;" alt="Grafana">
|
|
||||||
|
|
||||||
<img src="img/grafana_10.png" style="width: 80%;" alt="Grafana">
|
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|
||||||
<a href="https://buycoffee.to/tcich"><img src="../img/logo-buycoffee-wide.jpg" width=200 alt="Postaw kawę"></a>
|
|
||||||
|
|
||||||
128
srcdev/README.md
@@ -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
|
|
||||||
|
|
||||||
242
srcdev/api.py
@@ -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('/<int:ppe_id>', methods=['GET'])
|
|
||||||
@app.route('/<int:ppe_id>/', 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('/<int:ppe_id>/<meter_type_url>', methods=['GET'])
|
|
||||||
@app.route('/<int:ppe_id>/<meter_type_url>/', 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('/<int:ppe_id>/<meter_type_url>/<tariff>', methods=['GET'])
|
|
||||||
@app.route('/<int:ppe_id>/<meter_type_url>/<tariff>/', 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})
|
|
||||||
@@ -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
|
|
||||||
BIN
srcdev/icon.png
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 84 KiB |
BIN
srcdev/logo.png
|
Before Width: | Height: | Size: 17 KiB |
@@ -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()
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||