myelectricaldatapy 2.3.1__tar.gz → 2.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.pre-commit-config.yaml +1 -0
- myelectricaldatapy-2.4.1/PKG-INFO +194 -0
- myelectricaldatapy-2.4.1/README.md +159 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/__init__.py +33 -4
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/analytics.py +49 -3
- myelectricaldatapy-2.4.1/myelectricaldatapy/api.py +312 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/auth.py +19 -10
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/const.py +1 -1
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/exceptions.py +9 -1
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/mypdl.py +111 -115
- myelectricaldatapy-2.4.1/myelectricaldatapy/types.py +282 -0
- myelectricaldatapy-2.4.1/myelectricaldatapy.egg-info/PKG-INFO +194 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy.egg-info/SOURCES.txt +1 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy.egg-info/requires.txt +1 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy.egg-info/scm_file_list.json +1 -0
- myelectricaldatapy-2.4.1/myelectricaldatapy.egg-info/scm_version.json +8 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/pyproject.toml +1 -0
- myelectricaldatapy-2.4.1/tests/conftest.py +115 -0
- myelectricaldatapy-2.4.1/tests/fixtures/identity.json +10 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/test_analytics.py +38 -56
- myelectricaldatapy-2.4.1/tests/test_load_data.py +239 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/uv.lock +113 -0
- myelectricaldatapy-2.3.1/PKG-INFO +0 -154
- myelectricaldatapy-2.3.1/README.md +0 -120
- myelectricaldatapy-2.3.1/myelectricaldatapy/api.py +0 -213
- myelectricaldatapy-2.3.1/myelectricaldatapy/types.py +0 -71
- myelectricaldatapy-2.3.1/myelectricaldatapy.egg-info/PKG-INFO +0 -154
- myelectricaldatapy-2.3.1/myelectricaldatapy.egg-info/scm_version.json +0 -8
- myelectricaldatapy-2.3.1/tests/conftest.py +0 -106
- myelectricaldatapy-2.3.1/tests/test_load_data.py +0 -229
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.devcontainer/devcontainer-lock.json +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.devcontainer/devcontainer.json +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.github/dependabot.yml +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.github/workflows/auto-approve.yml +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.github/workflows/python-lint.yml +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.github/workflows/python-publish.yml +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.github/workflows/release.yml +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.gitignore +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.vscode/settings.json +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/LICENSE +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/_secrets.yaml +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/example.py +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/py.typed +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/tz.py +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy.egg-info/dependency_links.txt +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy.egg-info/top_level.txt +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/setup.cfg +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/__init__.py +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/consts.py +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/access.json +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/address.json +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/contract.json +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/daily.json +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/detail.json +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/ecowatt.json +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/invalid_access.json +0 -0
- {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/tempo.json +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: myelectricaldatapy
|
|
3
|
+
Version: 2.4.1
|
|
4
|
+
Summary: Fetch Linky data from myelectricaldata.fr
|
|
5
|
+
Author-email: Cyr-ius <cyr-ius@ipocus.net>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/cyr-ius/myelectricaldatapy
|
|
8
|
+
Keywords: enedis,linky,async,myelectricaldata
|
|
9
|
+
Platform: any
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Framework :: AsyncIO
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Topic :: Home Automation
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: aiohttp>=3.8.1
|
|
23
|
+
Requires-Dist: pandas>=2.0.2
|
|
24
|
+
Requires-Dist: pydantic>=2.5
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: prek; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff; extra == "dev"
|
|
28
|
+
Requires-Dist: yamllint; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
31
|
+
Requires-Dist: python-typing-update; extra == "dev"
|
|
32
|
+
Requires-Dist: typing_extensions; extra == "dev"
|
|
33
|
+
Requires-Dist: freezegun; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# myelectricaldatapy
|
|
37
|
+
|
|
38
|
+
Fetch Enedis Linky data from myelectricaldata.fr (enedisgateway.tech)
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
Use the PIP package manager
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
$ pip install myelectricaldatapy
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or manually download and install the last version from github
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
$ git clone https://github.com/cyr-ius/myelectricaldatapy.git
|
|
52
|
+
$ pip install .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Public API
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from myelectricaldatapy import Enedis, EnedisByPDL, EnedisException
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `Enedis` : thin async client, one method per myelectricaldata.fr endpoint.
|
|
62
|
+
- `EnedisByPDL` : higher level helper that aggregates every piece of
|
|
63
|
+
information for a single delivery point (PDL) and computes analytics.
|
|
64
|
+
- `EnedisException` : base exception. Every library error
|
|
65
|
+
(`HttpRequestError`, `LimitReached`, `PayloadError`,
|
|
66
|
+
`TimeoutExceededError`, `AnalyticsError`) derives from it.
|
|
67
|
+
|
|
68
|
+
All payloads are validated and returned as [pydantic](https://docs.pydantic.dev)
|
|
69
|
+
models (`Contract`, `UsagePoint`, `DataCollect`, `TempoDays`, `Prices`, ...).
|
|
70
|
+
|
|
71
|
+
## `Enedis`
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
Enedis(token: str, session: ClientSession | None = None, timeout: int = 30)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| Method | Description |
|
|
78
|
+
| ------------------------------------------------------------ | ----------------------------------- |
|
|
79
|
+
| `async_fetch_datas(service, pdl, start=None, end=None)` | Raw call to any service |
|
|
80
|
+
| `async_valid_access(pdl)` / `async_has_access(pdl)` | Access / quota status |
|
|
81
|
+
| `async_get_contract(pdl)` / `async_get_contracts(pdl)` | Contract(s) |
|
|
82
|
+
| `async_get_address(pdl)` / `async_get_addresses(pdl)` | Address(es) |
|
|
83
|
+
| `async_get_identity(pdl)` | Identity |
|
|
84
|
+
| `async_get_daily_consumption(pdl, start, end)` | Daily consumption (max 1095 days) |
|
|
85
|
+
| `async_get_daily_production(pdl, start, end)` | Daily production (max 1095 days) |
|
|
86
|
+
| `async_get_details_consumption(pdl, start, end)` | Load curve consumption (max 7 days) |
|
|
87
|
+
| `async_get_details_production(pdl, start, end)` | Load curve production (max 7 days) |
|
|
88
|
+
| `async_get_max_power(pdl, start, end)` | Max power |
|
|
89
|
+
| `async_get_ecowatt(start=None, end=None)` | Ecowatt forecast |
|
|
90
|
+
| `async_get_tempo(start=None, end=None)` | Tempo day colors on a range |
|
|
91
|
+
| `async_get_tempo_days()` | Tempo calendar |
|
|
92
|
+
| `async_get_tempo_prices()` | Tempo prices |
|
|
93
|
+
| `async_has_offpeak(pdl)` / `async_check_offpeak(pdl, start)` | Off-peak helpers |
|
|
94
|
+
| `async_close()` | Close the aiohttp session |
|
|
95
|
+
|
|
96
|
+
## `EnedisByPDL`
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
EnedisByPDL(
|
|
100
|
+
pdl: str,
|
|
101
|
+
token: str,
|
|
102
|
+
subscription: str = "standard", # "standard" | "hphc" | "tempo"
|
|
103
|
+
session: ClientSession | None = None,
|
|
104
|
+
timeout: int = 30,
|
|
105
|
+
timezone: tzinfo | None = None,
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Workflow:
|
|
110
|
+
|
|
111
|
+
1. Optionally declare what you want to collect with
|
|
112
|
+
`set_data_fetch(...)`, `set_ecowatt_subscription(True)` and
|
|
113
|
+
`set_maxpower_subscription(True)`.
|
|
114
|
+
2. Call `await async_update()` (or `async_update(force_refresh=True)`).
|
|
115
|
+
3. Read the aggregated results.
|
|
116
|
+
|
|
117
|
+
| Member | Description |
|
|
118
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
119
|
+
| `set_data_fetch(service, start=None, end=None, intervals=None, prices=None, cum_value=None, cum_price=None)` | Register a collect (`service` is one of `DAILY_CONSUM`, `DAILY_PROD`, `DETAIL_CONSUM`, `DETAIL_PROD`) |
|
|
120
|
+
| `set_ecowatt_subscription(activate=False)` | Enable Ecowatt retrieval |
|
|
121
|
+
| `set_maxpower_subscription(activate=False)` | Enable max power retrieval |
|
|
122
|
+
| `async_update(force_refresh=False)` | Refresh access, contract, address, tempo, ecowatt, collects |
|
|
123
|
+
| `async_update_collects()` | Refresh only the registered collects |
|
|
124
|
+
| `stats` | `dict` keyed by mode (`"consumption"` / `"production"`) with the computed analytics |
|
|
125
|
+
| `access`, `contract`, `address`, `ecowatt`, `max_power`, `tempo`, `tempo_days`, `tempo_prices` | Last fetched models |
|
|
126
|
+
| `is_connected`, `has_intervals`, `has_tempo_subscription`, `has_offpeak_hours_subscription`, `has_standard_subscription`, `has_ecowatt_subscription`, `has_maxpower_subscription` | Boolean state |
|
|
127
|
+
| `ecowatt_day`, `tempo_day`, `consum_prices`, `prod_prices` | Convenience accessors for today |
|
|
128
|
+
| `async_close()` | Close the aiohttp session |
|
|
129
|
+
|
|
130
|
+
## Timezone
|
|
131
|
+
|
|
132
|
+
Enedis/RTE APIs return naive timestamps that represent local wall-clock time. This
|
|
133
|
+
library needs to know which timezone that is in order to compute things like
|
|
134
|
+
off-peak hours or hourly statistics correctly.
|
|
135
|
+
|
|
136
|
+
By default it falls back to the timezone of the host machine running the code
|
|
137
|
+
(`/etc/localtime`), but that has no reason to match the timezone your
|
|
138
|
+
application is actually configured for (e.g. Home Assistant's
|
|
139
|
+
`hass.config.time_zone`). Pass it explicitly to avoid a fixed-offset shift in
|
|
140
|
+
the reported times:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
api = EnedisByPDL(pdl=PDL, token=TOKEN, timezone=ZoneInfo("Europe/Paris"))
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Alternatively, set it once globally for every instance:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from myelectricaldatapy import set_local_timezone
|
|
150
|
+
|
|
151
|
+
set_local_timezone(ZoneInfo("Europe/Paris"))
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Get started
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import asyncio
|
|
158
|
+
from datetime import timedelta
|
|
159
|
+
|
|
160
|
+
from myelectricaldatapy import DETAIL_CONSUM, EnedisByPDL, EnedisException
|
|
161
|
+
from myelectricaldatapy.tz import local_now
|
|
162
|
+
|
|
163
|
+
TOKEN = "012345"
|
|
164
|
+
PDL = "012345012345"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
async def main() -> None:
|
|
168
|
+
api = EnedisByPDL(pdl=PDL, token=TOKEN)
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
# Off-peak intervals and per-interval prices are optional.
|
|
172
|
+
api.set_data_fetch(
|
|
173
|
+
DETAIL_CONSUM,
|
|
174
|
+
start=local_now() - timedelta(days=7),
|
|
175
|
+
end=local_now(),
|
|
176
|
+
intervals=[("08:00", "12:00")],
|
|
177
|
+
prices={"standard": {"price": 0.18}, "offpeak": {"price": 0.1641}},
|
|
178
|
+
)
|
|
179
|
+
await api.async_update()
|
|
180
|
+
|
|
181
|
+
print(api.contract)
|
|
182
|
+
print(api.address)
|
|
183
|
+
print(api.stats["consumption"])
|
|
184
|
+
except EnedisException as error:
|
|
185
|
+
print(error)
|
|
186
|
+
finally:
|
|
187
|
+
await api.async_close()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
asyncio.run(main())
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Have a look at [example.py](https://github.com/cyr-ius/myelectricaldatapy/blob/master/example.py)
|
|
194
|
+
for a more complete overview.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# myelectricaldatapy
|
|
2
|
+
|
|
3
|
+
Fetch Enedis Linky data from myelectricaldata.fr (enedisgateway.tech)
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Use the PIP package manager
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
$ pip install myelectricaldatapy
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or manually download and install the last version from github
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
$ git clone https://github.com/cyr-ius/myelectricaldatapy.git
|
|
17
|
+
$ pip install .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Public API
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from myelectricaldatapy import Enedis, EnedisByPDL, EnedisException
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `Enedis` : thin async client, one method per myelectricaldata.fr endpoint.
|
|
27
|
+
- `EnedisByPDL` : higher level helper that aggregates every piece of
|
|
28
|
+
information for a single delivery point (PDL) and computes analytics.
|
|
29
|
+
- `EnedisException` : base exception. Every library error
|
|
30
|
+
(`HttpRequestError`, `LimitReached`, `PayloadError`,
|
|
31
|
+
`TimeoutExceededError`, `AnalyticsError`) derives from it.
|
|
32
|
+
|
|
33
|
+
All payloads are validated and returned as [pydantic](https://docs.pydantic.dev)
|
|
34
|
+
models (`Contract`, `UsagePoint`, `DataCollect`, `TempoDays`, `Prices`, ...).
|
|
35
|
+
|
|
36
|
+
## `Enedis`
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
Enedis(token: str, session: ClientSession | None = None, timeout: int = 30)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
| Method | Description |
|
|
43
|
+
| ------------------------------------------------------------ | ----------------------------------- |
|
|
44
|
+
| `async_fetch_datas(service, pdl, start=None, end=None)` | Raw call to any service |
|
|
45
|
+
| `async_valid_access(pdl)` / `async_has_access(pdl)` | Access / quota status |
|
|
46
|
+
| `async_get_contract(pdl)` / `async_get_contracts(pdl)` | Contract(s) |
|
|
47
|
+
| `async_get_address(pdl)` / `async_get_addresses(pdl)` | Address(es) |
|
|
48
|
+
| `async_get_identity(pdl)` | Identity |
|
|
49
|
+
| `async_get_daily_consumption(pdl, start, end)` | Daily consumption (max 1095 days) |
|
|
50
|
+
| `async_get_daily_production(pdl, start, end)` | Daily production (max 1095 days) |
|
|
51
|
+
| `async_get_details_consumption(pdl, start, end)` | Load curve consumption (max 7 days) |
|
|
52
|
+
| `async_get_details_production(pdl, start, end)` | Load curve production (max 7 days) |
|
|
53
|
+
| `async_get_max_power(pdl, start, end)` | Max power |
|
|
54
|
+
| `async_get_ecowatt(start=None, end=None)` | Ecowatt forecast |
|
|
55
|
+
| `async_get_tempo(start=None, end=None)` | Tempo day colors on a range |
|
|
56
|
+
| `async_get_tempo_days()` | Tempo calendar |
|
|
57
|
+
| `async_get_tempo_prices()` | Tempo prices |
|
|
58
|
+
| `async_has_offpeak(pdl)` / `async_check_offpeak(pdl, start)` | Off-peak helpers |
|
|
59
|
+
| `async_close()` | Close the aiohttp session |
|
|
60
|
+
|
|
61
|
+
## `EnedisByPDL`
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
EnedisByPDL(
|
|
65
|
+
pdl: str,
|
|
66
|
+
token: str,
|
|
67
|
+
subscription: str = "standard", # "standard" | "hphc" | "tempo"
|
|
68
|
+
session: ClientSession | None = None,
|
|
69
|
+
timeout: int = 30,
|
|
70
|
+
timezone: tzinfo | None = None,
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Workflow:
|
|
75
|
+
|
|
76
|
+
1. Optionally declare what you want to collect with
|
|
77
|
+
`set_data_fetch(...)`, `set_ecowatt_subscription(True)` and
|
|
78
|
+
`set_maxpower_subscription(True)`.
|
|
79
|
+
2. Call `await async_update()` (or `async_update(force_refresh=True)`).
|
|
80
|
+
3. Read the aggregated results.
|
|
81
|
+
|
|
82
|
+
| Member | Description |
|
|
83
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `set_data_fetch(service, start=None, end=None, intervals=None, prices=None, cum_value=None, cum_price=None)` | Register a collect (`service` is one of `DAILY_CONSUM`, `DAILY_PROD`, `DETAIL_CONSUM`, `DETAIL_PROD`) |
|
|
85
|
+
| `set_ecowatt_subscription(activate=False)` | Enable Ecowatt retrieval |
|
|
86
|
+
| `set_maxpower_subscription(activate=False)` | Enable max power retrieval |
|
|
87
|
+
| `async_update(force_refresh=False)` | Refresh access, contract, address, tempo, ecowatt, collects |
|
|
88
|
+
| `async_update_collects()` | Refresh only the registered collects |
|
|
89
|
+
| `stats` | `dict` keyed by mode (`"consumption"` / `"production"`) with the computed analytics |
|
|
90
|
+
| `access`, `contract`, `address`, `ecowatt`, `max_power`, `tempo`, `tempo_days`, `tempo_prices` | Last fetched models |
|
|
91
|
+
| `is_connected`, `has_intervals`, `has_tempo_subscription`, `has_offpeak_hours_subscription`, `has_standard_subscription`, `has_ecowatt_subscription`, `has_maxpower_subscription` | Boolean state |
|
|
92
|
+
| `ecowatt_day`, `tempo_day`, `consum_prices`, `prod_prices` | Convenience accessors for today |
|
|
93
|
+
| `async_close()` | Close the aiohttp session |
|
|
94
|
+
|
|
95
|
+
## Timezone
|
|
96
|
+
|
|
97
|
+
Enedis/RTE APIs return naive timestamps that represent local wall-clock time. This
|
|
98
|
+
library needs to know which timezone that is in order to compute things like
|
|
99
|
+
off-peak hours or hourly statistics correctly.
|
|
100
|
+
|
|
101
|
+
By default it falls back to the timezone of the host machine running the code
|
|
102
|
+
(`/etc/localtime`), but that has no reason to match the timezone your
|
|
103
|
+
application is actually configured for (e.g. Home Assistant's
|
|
104
|
+
`hass.config.time_zone`). Pass it explicitly to avoid a fixed-offset shift in
|
|
105
|
+
the reported times:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
api = EnedisByPDL(pdl=PDL, token=TOKEN, timezone=ZoneInfo("Europe/Paris"))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Alternatively, set it once globally for every instance:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from myelectricaldatapy import set_local_timezone
|
|
115
|
+
|
|
116
|
+
set_local_timezone(ZoneInfo("Europe/Paris"))
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Get started
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
import asyncio
|
|
123
|
+
from datetime import timedelta
|
|
124
|
+
|
|
125
|
+
from myelectricaldatapy import DETAIL_CONSUM, EnedisByPDL, EnedisException
|
|
126
|
+
from myelectricaldatapy.tz import local_now
|
|
127
|
+
|
|
128
|
+
TOKEN = "012345"
|
|
129
|
+
PDL = "012345012345"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def main() -> None:
|
|
133
|
+
api = EnedisByPDL(pdl=PDL, token=TOKEN)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
# Off-peak intervals and per-interval prices are optional.
|
|
137
|
+
api.set_data_fetch(
|
|
138
|
+
DETAIL_CONSUM,
|
|
139
|
+
start=local_now() - timedelta(days=7),
|
|
140
|
+
end=local_now(),
|
|
141
|
+
intervals=[("08:00", "12:00")],
|
|
142
|
+
prices={"standard": {"price": 0.18}, "offpeak": {"price": 0.1641}},
|
|
143
|
+
)
|
|
144
|
+
await api.async_update()
|
|
145
|
+
|
|
146
|
+
print(api.contract)
|
|
147
|
+
print(api.address)
|
|
148
|
+
print(api.stats["consumption"])
|
|
149
|
+
except EnedisException as error:
|
|
150
|
+
print(error)
|
|
151
|
+
finally:
|
|
152
|
+
await api.async_close()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
asyncio.run(main())
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Have a look at [example.py](https://github.com/cyr-ius/myelectricaldatapy/blob/master/example.py)
|
|
159
|
+
for a more complete overview.
|
|
@@ -3,46 +3,75 @@
|
|
|
3
3
|
from .api import Enedis
|
|
4
4
|
from .const import *
|
|
5
5
|
from .exceptions import (
|
|
6
|
+
AnalyticsError,
|
|
6
7
|
EnedisException,
|
|
7
8
|
HttpRequestError,
|
|
8
9
|
LimitReached,
|
|
10
|
+
PayloadError,
|
|
9
11
|
TimeoutExceededError,
|
|
10
12
|
)
|
|
11
|
-
from .mypdl import EnedisByPDL
|
|
13
|
+
from .mypdl import EnedisByPDL
|
|
12
14
|
from .types import (
|
|
15
|
+
AccessResponse,
|
|
13
16
|
ConsumptionCollect,
|
|
17
|
+
Contract,
|
|
14
18
|
Cum,
|
|
19
|
+
Customer,
|
|
20
|
+
CustomerResponse,
|
|
21
|
+
DataCollect,
|
|
22
|
+
EcowattDay,
|
|
15
23
|
EnergyCollect,
|
|
24
|
+
IntervalReading,
|
|
25
|
+
MeterReading,
|
|
16
26
|
Mode,
|
|
17
27
|
Prices,
|
|
18
28
|
ProductionCollect,
|
|
29
|
+
ReadingType,
|
|
19
30
|
Service,
|
|
20
31
|
StandardPrice,
|
|
21
32
|
Subscription,
|
|
33
|
+
TempoDays,
|
|
34
|
+
TempoLabels,
|
|
22
35
|
TempoPrice,
|
|
36
|
+
UsagePoint,
|
|
37
|
+
UsagePointAddress,
|
|
38
|
+
UsagePointWrapper,
|
|
23
39
|
)
|
|
24
40
|
from .tz import get_local_timezone, set_local_timezone
|
|
25
41
|
|
|
26
42
|
__all__ = [
|
|
43
|
+
"AccessResponse",
|
|
44
|
+
"AnalyticsError",
|
|
27
45
|
"ConsumptionCollect",
|
|
46
|
+
"Contract",
|
|
28
47
|
"Cum",
|
|
48
|
+
"Customer",
|
|
49
|
+
"CustomerResponse",
|
|
50
|
+
"DataCollect",
|
|
51
|
+
"EcowattDay",
|
|
29
52
|
"Enedis",
|
|
30
53
|
"EnedisByPDL",
|
|
31
54
|
"EnedisException",
|
|
32
55
|
"EnergyCollect",
|
|
33
|
-
"FormatError",
|
|
34
56
|
"HttpRequestError",
|
|
57
|
+
"IntervalReading",
|
|
35
58
|
"LimitReached",
|
|
59
|
+
"MeterReading",
|
|
36
60
|
"Mode",
|
|
61
|
+
"PayloadError",
|
|
37
62
|
"Prices",
|
|
38
63
|
"ProductionCollect",
|
|
64
|
+
"ReadingType",
|
|
39
65
|
"Service",
|
|
40
66
|
"StandardPrice",
|
|
41
67
|
"Subscription",
|
|
68
|
+
"TempoDays",
|
|
69
|
+
"TempoLabels",
|
|
42
70
|
"TempoPrice",
|
|
43
71
|
"TimeoutExceededError",
|
|
72
|
+
"UsagePoint",
|
|
73
|
+
"UsagePointAddress",
|
|
74
|
+
"UsagePointWrapper",
|
|
44
75
|
"get_local_timezone",
|
|
45
76
|
"set_local_timezone",
|
|
46
|
-
"validate_cumsum",
|
|
47
|
-
"validate_prices",
|
|
48
77
|
]
|
|
@@ -7,6 +7,8 @@ from typing import Any
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
|
|
9
9
|
from .const import ATTR_OFFPEAK, ATTR_STANDARD, TEMPO_DAYS
|
|
10
|
+
from .exceptions import AnalyticsError, EnedisException
|
|
11
|
+
from .types import TempoLabels
|
|
10
12
|
from .tz import get_local_timezone
|
|
11
13
|
|
|
12
14
|
|
|
@@ -15,7 +17,12 @@ class EnedisAnalytics:
|
|
|
15
17
|
|
|
16
18
|
def __init__(self, data: Any, timezone: _tzinfo | None = None) -> None:
|
|
17
19
|
"""Initialize Dataframe."""
|
|
18
|
-
|
|
20
|
+
try:
|
|
21
|
+
self.df = pd.DataFrame(data)
|
|
22
|
+
except Exception as error: # normalized below
|
|
23
|
+
raise AnalyticsError(
|
|
24
|
+
f"Could not build a dataframe from the provided data: {error}"
|
|
25
|
+
) from error
|
|
19
26
|
# Resolved per instance (not at import/class-definition time) so a
|
|
20
27
|
# timezone set later via tz.set_local_timezone(), or passed here
|
|
21
28
|
# explicitly, is honored.
|
|
@@ -32,7 +39,46 @@ class EnedisAnalytics:
|
|
|
32
39
|
cum_value: dict[str, Any] | None = None,
|
|
33
40
|
cum_price: dict[str, Any] | None = None,
|
|
34
41
|
prices: dict[str, Any] | None = None,
|
|
35
|
-
tempo: dict[str,
|
|
42
|
+
tempo: dict[str, TempoLabels] | None = None,
|
|
43
|
+
) -> Any:
|
|
44
|
+
"""Convert data to analyze.
|
|
45
|
+
|
|
46
|
+
Any failure while crunching the dataframe is re-raised as
|
|
47
|
+
:class:`AnalyticsError` so callers only have to catch
|
|
48
|
+
:class:`EnedisException`.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
return self._get_data_analytics(
|
|
52
|
+
convertKwh=convertKwh,
|
|
53
|
+
convertUTC=convertUTC,
|
|
54
|
+
start_date=start_date,
|
|
55
|
+
intervals=intervals,
|
|
56
|
+
groupby=groupby,
|
|
57
|
+
summary=summary,
|
|
58
|
+
cum_value=cum_value,
|
|
59
|
+
cum_price=cum_price,
|
|
60
|
+
prices=prices,
|
|
61
|
+
tempo=tempo,
|
|
62
|
+
)
|
|
63
|
+
except EnedisException:
|
|
64
|
+
raise
|
|
65
|
+
except Exception as error: # normalized below
|
|
66
|
+
raise AnalyticsError(
|
|
67
|
+
f"Failed to compute analytics from the provided dataset: {error}"
|
|
68
|
+
) from error
|
|
69
|
+
|
|
70
|
+
def _get_data_analytics(
|
|
71
|
+
self,
|
|
72
|
+
convertKwh: bool = False,
|
|
73
|
+
convertUTC: bool = False,
|
|
74
|
+
start_date: dt | None = None,
|
|
75
|
+
intervals: list[tuple[str, str]] | None = None,
|
|
76
|
+
groupby: bool = False,
|
|
77
|
+
summary: bool = False,
|
|
78
|
+
cum_value: dict[str, Any] | None = None,
|
|
79
|
+
cum_price: dict[str, Any] | None = None,
|
|
80
|
+
prices: dict[str, Any] | None = None,
|
|
81
|
+
tempo: dict[str, TempoLabels] | None = None,
|
|
36
82
|
) -> Any:
|
|
37
83
|
"""Convert data to analyze."""
|
|
38
84
|
|
|
@@ -162,7 +208,7 @@ class EnedisAnalytics:
|
|
|
162
208
|
|
|
163
209
|
return self.df
|
|
164
210
|
|
|
165
|
-
def _set_tempo_days(self, tempo: dict[str,
|
|
211
|
+
def _set_tempo_days(self, tempo: dict[str, TempoLabels]) -> None:
|
|
166
212
|
"""Add columns with tempo day."""
|
|
167
213
|
for str_date, value in tempo.items():
|
|
168
214
|
dt_date = pd.to_datetime(str_date, format="%Y-%m-%d")
|