Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ESIOS API Token support #47

Merged
merged 26 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a103074
:recycle: Minor fix to avoid toggling data-source on errors
azogue Dec 23, 2021
5f12624
:sparkles: Add ESIOS token support for PVPC as new data-source
azogue Dec 23, 2021
097b3bd
:truck: Add text fixture for ESIOS PVPC data
azogue Dec 23, 2021
c86b569
:white_check_mark: Adapt tests to cover 'esios' data source
azogue Dec 23, 2021
4adf645
🎨 Add types-attrs to mypy config
azogue Oct 1, 2022
3f66a70
🎨 lint: Minor cleaning for typing annots
azogue Jan 6, 2023
b50ddca
💥 Remove 'apidatos' support and redefine esios indicators
azogue Jan 6, 2023
55b22fe
💥 Rework `PVPCData` to not store the downloaded prices
azogue Jan 6, 2023
f973507
🍱 tests: Remove 'apidatos' fixtures and add new ones for esios sensors
azogue Jan 6, 2023
21ffef0
🔥 tests: Remove 'apidatos' tests
azogue Jan 6, 2023
20d660f
♻️ tests: Adapt tests to new interface
azogue Jan 6, 2023
a5872f3
♻️ Renaming for 'sensor keys' and minor cleaning
azogue Jan 6, 2023
b5a631a
♻️ tests: Adapt skipped test to make real API calls
azogue Jan 6, 2023
c9f56c3
♻️ tests: Add more fixtures for esios sensors
azogue Jan 6, 2023
c3d982c
✅ tests: Adapt tests to work with multiple sensors
azogue Jan 6, 2023
7c719a5
📦️ Bump mayor version to v4
azogue Jan 6, 2023
6da1152
✨ Signal bad auth for esios token with custom exception
azogue Jan 6, 2023
b126a22
♻️ tests: Minor refactor
azogue Jan 6, 2023
f536075
✨ Add helper methods for HA integration
azogue Jan 6, 2023
619de98
🧹 Minor cleaning
azogue Jan 6, 2023
3ca26db
✨ Add HA helper to check API token
azogue Jan 9, 2023
bc90b50
♻️ Make `EsiosApiData` a dataclass
azogue Jan 9, 2023
ec7f417
🎨 Minor refactor for `EsiosResponse` as dataclass
azogue Jan 9, 2023
c73950f
🔊 Minor cleaning and uniform logs
azogue Jan 9, 2023
9989ab8
🐛 Fix 'better_price' concept for 'INYECTION' price sensor
azogue Jan 9, 2023
8b425f4
📝 Update CHANGELOG.md
azogue Jan 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ repos:
- id: "mypy"
name: "Check type hints (mypy)"
verbose: true
additional_dependencies:
- types-attrs
ci:
autofix_commit_msg: |
[pre-commit.ci] auto fixes from pre-commit.com hooks
Expand Down
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
# Changelog

## [v4.0.0](https://github.com/azogue/aiopvpc/tree/v4.0.0) - Implement ESIOS API Token (2023-01-09)

[Full Changelog](https://github.com/azogue/aiopvpc/compare/v4.0.0...v3.0.0)

- ✨ Implement **support to access the extended ESIOS API** with a personal token
(you must request yours by mailing to [[email protected]](mailto:[email protected]?subject=Personal%20token%20request)),
with initial support for the existent PVPC price sensor (ESIOS indicator code: **1001**), and **3 new ones** 🤩:
* **Inyection price** sensor (ESIOS indicator code: **1739**),
name: "Precio de la energía excedentaria del autoconsumo para el mecanismo de compensación simplificada"
* **MAG price** sensor (ESIOS indicator code: **1900**),
name: "Desglose peaje por defecto 2.0TD excedente o déficit de la liquidación del mecanismo de ajuste de costes de producción"
* **OMIE price** sensor (ESIOS indicator code: **10211**),
name: "Precio medio horario final suma de componentes"

- 💥 Remove 'apidatos' support as alternative _data-source_, leaving only public and private paths for https://api.esios.ree.es

- ✨ Signal bad auth for esios token calls with a custom exception, to handle 'reauth' flow in Home-Assistant

- ✨ Add helper methods for HA integration to manage unique ids for each sensor, to update the enabled sensors to download, and to check the API token

- ♻️ Use dataclasses for `EsiosApiData` and `EsiosResponse` data containers, instead of typed dicts

- ✅ tests: Update fixtures for esios sensors and adapt tests to the new interface and the multiple-sensors behaviour

- 📦️ Bump mayor version to **v4** and lighten dev-env, removing pre-commit related modules and adding python-dotenv

- (from #46, with v3.1.0) Remove `holidays` dependency to evaluate special days under 'P3' period and fix tests

## [v3.0.0](https://github.com/azogue/aiopvpc/tree/v3.0.0) - Change Data Source to apidatos.ree.es (2021-12-05)

[Full Changelog](https://github.com/azogue/aiopvpc/compare/v3.0.0...v2.3.0)
Expand Down
14 changes: 11 additions & 3 deletions aiopvpc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
"""Simple aio library to download Spanish electricity hourly prices."""
from .const import DEFAULT_POWER_KW, TARIFFS
from .pvpc_data import PVPCData
from .const import DEFAULT_POWER_KW, EsiosApiData, TARIFFS
from .ha_helpers import get_enabled_sensor_keys
from .pvpc_data import BadApiTokenAuthError, PVPCData

__all__ = ("DEFAULT_POWER_KW", "PVPCData", "TARIFFS")
__all__ = (
"BadApiTokenAuthError",
"EsiosApiData",
"DEFAULT_POWER_KW",
"PVPCData",
"TARIFFS",
"get_enabled_sensor_keys",
)
83 changes: 68 additions & 15 deletions aiopvpc/const.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
"""
Simple aio library to download Spanish electricity hourly prices. Constants.
"""
"""Simple aio library to download Spanish electricity hourly prices. Constants."""
import zoneinfo
from datetime import date
from typing import Dict, Literal
from dataclasses import dataclass
from datetime import date, datetime
from typing import Literal

DATE_CHANGE_TO_20TD = date(2021, 6, 1)

Expand All @@ -22,21 +21,75 @@
REFERENCE_TZ = zoneinfo.ZoneInfo("Europe/Madrid")
UTC_TZ = zoneinfo.ZoneInfo("UTC")

DEFAULT_TIMEOUT = 5
DEFAULT_TIMEOUT = 10
PRICE_PRECISION = 5

DataSource = Literal["esios_public", "apidatos"] # , "esios"
URL_PVPC_RESOURCE = (
DataSource = Literal["esios_public", "esios"]
GEOZONES = ["Península", "Canarias", "Baleares", "Ceuta", "Melilla", "España"]
GEOZONE_ID2NAME: dict[int, str] = {
3: "España",
8741: "Península",
8742: "Canarias",
8743: "Baleares",
8744: "Ceuta",
8745: "Melilla",
}
URL_PUBLIC_PVPC_RESOURCE = (
"https://api.esios.ree.es/archives/70/download_json"
"?locale=es&date={day:%Y-%m-%d}"
)
URL_APIDATOS_PRICES_RESOURCE = (
"https://apidatos.ree.es/es/datos/mercados/precios-mercados-tiempo-real"
"?time_trunc=hour"
"&geo_ids={geo_id}"
"&start_date={start:%Y-%m-%dT%H:%M}&end_date={end:%Y-%m-%dT%H:%M}"
URL_ESIOS_TOKEN_RESOURCE = (
"https://api.esios.ree.es/indicators/{ind}"
+ "?start_date={day:%Y-%m-%d}T00:00&end_date={day:%Y-%m-%d}T23:59"
)
ATTRIBUTIONS: Dict[DataSource, str] = {
ATTRIBUTIONS: dict[DataSource, str] = {
"esios_public": "Data retrieved from api.esios.ree.es by REE",
"apidatos": "Data retrieved from apidatos.ree.es by REE",
"esios": "Data retrieved with API token from api.esios.ree.es by REE",
}

# api.esios.ree.es/indicators
ESIOS_PVPC = "1001"
ESIOS_INYECTION = "1739"
ESIOS_MAG = "1900" # regargo GAS
ESIOS_OMIE = "10211" # precio mayorista

# unique ids for each series
KEY_PVPC = "PVPC"
KEY_INYECTION = "INYECTION"
KEY_MAG = "MAG" # regargo GAS
KEY_OMIE = "OMIE" # precio mayorista

ALL_SENSORS = (KEY_PVPC, KEY_INYECTION, KEY_MAG, KEY_OMIE)
SENSOR_KEY_TO_DATAID = {
KEY_PVPC: ESIOS_PVPC,
KEY_INYECTION: ESIOS_INYECTION,
KEY_MAG: ESIOS_MAG,
KEY_OMIE: ESIOS_OMIE,
}
SENSOR_KEY_TO_NAME = {
KEY_PVPC: "PVPC T. 2.0TD",
KEY_INYECTION: "Precio de la energía excedentaria",
KEY_MAG: "2.0TD Excedente o déficit ajuste liquidación",
KEY_OMIE: "Precio medio horario final suma",
}


@dataclass
class EsiosResponse:
"""Data schema for parsed data coming from ESIOS API."""

name: str
data_id: str
last_update: datetime
unit: str
series: dict[str, dict[datetime, float]]


@dataclass
class EsiosApiData:
"""Data schema to store multiple series from ESIOS API."""

last_update: datetime
data_source: str
sensors: dict[str, dict[datetime, float]]
availability: dict[str, bool]
42 changes: 42 additions & 0 deletions aiopvpc/ha_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Home Assistant helper methods."""
from aiopvpc.const import (
ALL_SENSORS,
KEY_INYECTION,
KEY_MAG,
KEY_OMIE,
KEY_PVPC,
TARIFFS,
)

_ha_uniqueid_to_sensor_key = {
TARIFFS[0]: KEY_PVPC,
TARIFFS[1]: KEY_PVPC,
f"{TARIFFS[0]}_{KEY_INYECTION}": KEY_INYECTION,
f"{TARIFFS[1]}_{KEY_INYECTION}": KEY_INYECTION,
f"{TARIFFS[0]}_{KEY_MAG}": KEY_MAG,
f"{TARIFFS[1]}_{KEY_MAG}": KEY_MAG,
f"{TARIFFS[0]}_{KEY_OMIE}": KEY_OMIE,
f"{TARIFFS[1]}_{KEY_OMIE}": KEY_OMIE,
}


def get_enabled_sensor_keys(
using_private_api: bool, disabled_sensor_ids: list[str]
) -> set[str]:
"""(HA) Get enabled API indicators."""
sensor_keys = set(ALL_SENSORS) if using_private_api else {KEY_PVPC}
for unique_id in disabled_sensor_ids:
disabled_ind = _ha_uniqueid_to_sensor_key[unique_id]
if disabled_ind in sensor_keys:
sensor_keys.remove(disabled_ind)

return sensor_keys


def make_sensor_unique_id(config_entry_id: str, sensor_key: str) -> str:
"""(HA) Generate unique_id for each sensor kind and config entry."""
assert sensor_key in ALL_SENSORS
if sensor_key == KEY_PVPC:
# for old compatibility
return config_entry_id
return f"{config_entry_id}_{sensor_key}"
Loading