From b7c1df7864468ddc4b9a2bec51894c7e25fb0104 Mon Sep 17 00:00:00 2001 From: Eugenio Panadero Date: Thu, 17 Jun 2021 09:03:28 +0200 Subject: [PATCH] Adopt new electricity tariffs in pvpc hourly pricing (#51789) --- .../pvpc_hourly_pricing/__init__.py | 101 ++- .../pvpc_hourly_pricing/config_flow.py | 51 +- .../components/pvpc_hourly_pricing/const.py | 5 +- .../pvpc_hourly_pricing/manifest.json | 2 +- .../components/pvpc_hourly_pricing/sensor.py | 48 +- .../pvpc_hourly_pricing/strings.json | 21 +- .../pvpc_hourly_pricing/translations/en.json | 21 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../pvpc_hourly_pricing/conftest.py | 7 + .../pvpc_hourly_pricing/test_config_flow.py | 56 +- .../pvpc_hourly_pricing/test_sensor.py | 89 ++- .../PVPC_CURV_DD_2021_06_01.json | 604 ++++++++++++++++++ 13 files changed, 938 insertions(+), 71 deletions(-) create mode 100644 tests/fixtures/pvpc_hourly_pricing/PVPC_CURV_DD_2021_06_01.json diff --git a/homeassistant/components/pvpc_hourly_pricing/__init__.py b/homeassistant/components/pvpc_hourly_pricing/__init__.py index 22ad590659e..3e98274c696 100644 --- a/homeassistant/components/pvpc_hourly_pricing/__init__.py +++ b/homeassistant/components/pvpc_hourly_pricing/__init__.py @@ -1,17 +1,38 @@ """The pvpc_hourly_pricing integration to collect Spain official electric prices.""" +import logging + +from aiopvpc import DEFAULT_POWER_KW, TARIFFS import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_NAME -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity_registry import ( + EntityRegistry, + async_get, + async_migrate_entries, +) -from .const import ATTR_TARIFF, DEFAULT_NAME, DEFAULT_TARIFF, DOMAIN, PLATFORMS, TARIFFS +from .const import ( + ATTR_POWER, + ATTR_POWER_P3, + ATTR_TARIFF, + DEFAULT_NAME, + DOMAIN, + PLATFORMS, +) +_LOGGER = logging.getLogger(__name__) +_DEFAULT_TARIFF = TARIFFS[0] +VALID_POWER = vol.All(vol.Coerce(float), vol.Range(min=1.0, max=15.0)) +VALID_TARIFF = vol.In(TARIFFS) UI_CONFIG_SCHEMA = vol.Schema( { vol.Required(CONF_NAME, default=DEFAULT_NAME): str, - vol.Required(ATTR_TARIFF, default=DEFAULT_TARIFF): vol.In(TARIFFS), + vol.Required(ATTR_TARIFF, default=_DEFAULT_TARIFF): VALID_TARIFF, + vol.Required(ATTR_POWER, default=DEFAULT_POWER_KW): VALID_POWER, + vol.Required(ATTR_POWER_P3, default=DEFAULT_POWER_KW): VALID_POWER, } ) CONFIG_SCHEMA = vol.Schema( @@ -20,19 +41,8 @@ CONFIG_SCHEMA = vol.Schema( ) -async def async_setup(hass: HomeAssistant, config: dict): - """ - Set up the electricity price sensor from configuration.yaml. - - ```yaml - pvpc_hourly_pricing: - - name: PVPC manual ve - tariff: electric_car - - name: PVPC manual nocturna - tariff: discrimination - timeout: 3 - ``` - """ +async def async_setup(hass: HomeAssistant, config: dict) -> bool: + """Set up the electricity price sensor from configuration.yaml.""" for conf in config.get(DOMAIN, []): hass.async_create_task( hass.config_entries.flow.async_init( @@ -45,10 +55,67 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up pvpc hourly pricing from a config entry.""" + if len(entry.data) == 2: + defaults = { + ATTR_TARIFF: _DEFAULT_TARIFF, + ATTR_POWER: DEFAULT_POWER_KW, + ATTR_POWER_P3: DEFAULT_POWER_KW, + } + data = {**entry.data, **defaults} + hass.config_entries.async_update_entry( + entry, unique_id=_DEFAULT_TARIFF, data=data, options=defaults + ) + + @callback + def update_unique_id(reg_entry): + """Change unique id for sensor entity, pointing to new tariff.""" + return {"new_unique_id": _DEFAULT_TARIFF} + + try: + await async_migrate_entries(hass, entry.entry_id, update_unique_id) + _LOGGER.warning( + "Migrating PVPC sensor from old tariff '%s' to new '%s'. " + "Configure the integration to set your contracted power, " + "and select prices for Ceuta/Melilla, " + "if that is your case", + entry.data[ATTR_TARIFF], + _DEFAULT_TARIFF, + ) + except ValueError: + # there were multiple sensors (with different old tariffs, up to 3), + # so we leave just one and remove the others + ent_reg: EntityRegistry = async_get(hass) + for entity_id, reg_entry in ent_reg.entities.items(): + if reg_entry.config_entry_id == entry.entry_id: + ent_reg.async_remove(entity_id) + _LOGGER.warning( + "Old PVPC Sensor %s is removed " + "(another one already exists, using the same tariff)", + entity_id, + ) + break + + await hass.config_entries.async_remove(entry.entry_id) + return False + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + entry.async_on_unload(entry.add_update_listener(async_update_options)) return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Handle options update.""" + if any( + entry.data.get(attrib) != entry.options.get(attrib) + for attrib in (ATTR_TARIFF, ATTR_POWER, ATTR_POWER_P3) + ): + # update entry replacing data with new options + hass.config_entries.async_update_entry( + entry, data={**entry.data, **entry.options} + ) + await hass.config_entries.async_reload(entry.entry_id) + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/pvpc_hourly_pricing/config_flow.py b/homeassistant/components/pvpc_hourly_pricing/config_flow.py index 971a13acc2f..76694d570b5 100644 --- a/homeassistant/components/pvpc_hourly_pricing/config_flow.py +++ b/homeassistant/components/pvpc_hourly_pricing/config_flow.py @@ -1,17 +1,24 @@ """Config flow for pvpc_hourly_pricing.""" +import voluptuous as vol + from homeassistant import config_entries +from homeassistant.core import callback -from . import CONF_NAME, UI_CONFIG_SCHEMA -from .const import ATTR_TARIFF, DOMAIN - -_DOMAIN_NAME = DOMAIN +from . import CONF_NAME, UI_CONFIG_SCHEMA, VALID_POWER, VALID_TARIFF +from .const import ATTR_POWER, ATTR_POWER_P3, ATTR_TARIFF, DOMAIN -class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=_DOMAIN_NAME): - """Handle a config flow for `pvpc_hourly_pricing` to select the tariff.""" +class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle config flow for `pvpc_hourly_pricing`.""" VERSION = 1 + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return PVPCOptionsFlowHandler(config_entry) + async def async_step_user(self, user_input=None): """Handle the initial step.""" if user_input is not None: @@ -24,3 +31,35 @@ class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=_DOMAIN_NAME): async def async_step_import(self, import_info): """Handle import from config file.""" return await self.async_step_user(import_info) + + +class PVPCOptionsFlowHandler(config_entries.OptionsFlow): + """Handle PVPC options.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + # Fill options with entry data + tariff = self.config_entry.options.get( + ATTR_TARIFF, self.config_entry.data[ATTR_TARIFF] + ) + power = self.config_entry.options.get( + ATTR_POWER, self.config_entry.data[ATTR_POWER] + ) + power_valley = self.config_entry.options.get( + ATTR_POWER_P3, self.config_entry.data[ATTR_POWER_P3] + ) + schema = vol.Schema( + { + vol.Required(ATTR_TARIFF, default=tariff): VALID_TARIFF, + vol.Required(ATTR_POWER, default=power): VALID_POWER, + vol.Required(ATTR_POWER_P3, default=power_valley): VALID_POWER, + } + ) + return self.async_show_form(step_id="init", data_schema=schema) diff --git a/homeassistant/components/pvpc_hourly_pricing/const.py b/homeassistant/components/pvpc_hourly_pricing/const.py index 9e11bc57d6d..ad97124c330 100644 --- a/homeassistant/components/pvpc_hourly_pricing/const.py +++ b/homeassistant/components/pvpc_hourly_pricing/const.py @@ -1,8 +1,7 @@ """Constant values for pvpc_hourly_pricing.""" -from aiopvpc import TARIFFS - DOMAIN = "pvpc_hourly_pricing" PLATFORMS = ["sensor"] +ATTR_POWER = "power" +ATTR_POWER_P3 = "power_p3" ATTR_TARIFF = "tariff" DEFAULT_NAME = "PVPC" -DEFAULT_TARIFF = TARIFFS[1] diff --git a/homeassistant/components/pvpc_hourly_pricing/manifest.json b/homeassistant/components/pvpc_hourly_pricing/manifest.json index bbbe18350c8..612376a7931 100644 --- a/homeassistant/components/pvpc_hourly_pricing/manifest.json +++ b/homeassistant/components/pvpc_hourly_pricing/manifest.json @@ -3,7 +3,7 @@ "name": "Spain electricity hourly pricing (PVPC)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/pvpc_hourly_pricing", - "requirements": ["aiopvpc==2.1.2"], + "requirements": ["aiopvpc==2.2.0"], "codeowners": ["@azogue"], "quality_scale": "platinum", "iot_class": "cloud_polling" diff --git a/homeassistant/components/pvpc_hourly_pricing/sensor.py b/homeassistant/components/pvpc_hourly_pricing/sensor.py index 5fe65e3dc65..75881f93f0a 100644 --- a/homeassistant/components/pvpc_hourly_pricing/sensor.py +++ b/homeassistant/components/pvpc_hourly_pricing/sensor.py @@ -3,19 +3,21 @@ from __future__ import annotations import logging from random import randint +from typing import Any from aiopvpc import PVPCData -from homeassistant import config_entries from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, CURRENCY_EURO, ENERGY_KILO_WATT_HOUR from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_call_later, async_track_time_change from homeassistant.helpers.restore_state import RestoreEntity import homeassistant.util.dt as dt_util -from .const import ATTR_TARIFF +from .const import ATTR_POWER, ATTR_POWER_P3, ATTR_TARIFF _LOGGER = logging.getLogger(__name__) @@ -27,15 +29,18 @@ _DEFAULT_TIMEOUT = 10 async def async_setup_entry( - hass: HomeAssistant, config_entry: config_entries.ConfigEntry, async_add_entities -): + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the electricity price sensor from config_entry.""" name = config_entry.data[CONF_NAME] pvpc_data_handler = PVPCData( tariff=config_entry.data[ATTR_TARIFF], + power=config_entry.data[ATTR_POWER], + power_valley=config_entry.data[ATTR_POWER_P3], local_timezone=hass.config.time_zone, websession=async_get_clientsession(hass), - logger=_LOGGER, timeout=_DEFAULT_TIMEOUT, ) async_add_entities( @@ -57,15 +62,7 @@ class ElecPriceSensor(RestoreEntity, SensorEntity): self._pvpc_data = pvpc_data_handler self._num_retries = 0 - self._hourly_tracker = None - self._price_tracker = None - - async def async_will_remove_from_hass(self) -> None: - """Cancel listeners for sensor updates.""" - self._hourly_tracker() - self._price_tracker() - - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" await super().async_added_to_hass() state = await self.async_get_last_state() @@ -73,14 +70,18 @@ class ElecPriceSensor(RestoreEntity, SensorEntity): self._pvpc_data.state = state.state # Update 'state' value in hour changes - self._hourly_tracker = async_track_time_change( - self.hass, self.update_current_price, second=[0], minute=[0] + self.async_on_remove( + async_track_time_change( + self.hass, self.update_current_price, second=[0], minute=[0] + ) ) # Update prices at random time, 2 times/hour (don't want to upset API) random_minute = randint(1, 29) mins_update = [random_minute, random_minute + 30] - self._price_tracker = async_track_time_change( - self.hass, self.async_update_prices, second=[0], minute=mins_update + self.async_on_remove( + async_track_time_change( + self.hass, self.async_update_prices, second=[0], minute=mins_update + ) ) _LOGGER.debug( "Setup of price sensor %s (%s) with tariff '%s', " @@ -90,8 +91,9 @@ class ElecPriceSensor(RestoreEntity, SensorEntity): self._pvpc_data.tariff, mins_update, ) - await self.async_update_prices(dt_util.utcnow()) - self.update_current_price(dt_util.utcnow()) + now = dt_util.utcnow() + await self.async_update_prices(now) + self.update_current_price(now) @property def unique_id(self) -> str | None: @@ -99,12 +101,12 @@ class ElecPriceSensor(RestoreEntity, SensorEntity): return self._unique_id @property - def name(self): + def name(self) -> str: """Return the name of the sensor.""" return self._name @property - def state(self): + def state(self) -> float: """Return the state of the sensor.""" return self._pvpc_data.state @@ -114,7 +116,7 @@ class ElecPriceSensor(RestoreEntity, SensorEntity): return self._pvpc_data.state_available @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes.""" return self._pvpc_data.attributes diff --git a/homeassistant/components/pvpc_hourly_pricing/strings.json b/homeassistant/components/pvpc_hourly_pricing/strings.json index a1536d2186f..89da917c8ea 100644 --- a/homeassistant/components/pvpc_hourly_pricing/strings.json +++ b/homeassistant/components/pvpc_hourly_pricing/strings.json @@ -2,16 +2,31 @@ "config": { "step": { "user": { - "title": "Tariff selection", - "description": "This sensor uses official API to get [hourly pricing of electricity (PVPC)](https://www.esios.ree.es/es/pvpc) in Spain.\nFor more precise explanation visit the [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).\n\nSelect the contracted rate based on the number of billing periods per day:\n- 1 period: normal\n- 2 periods: discrimination (nightly rate)\n- 3 periods: electric car (nightly rate of 3 periods)", + "title": "Sensor setup", + "description": "This sensor uses official API to get [hourly pricing of electricity (PVPC)](https://www.esios.ree.es/es/pvpc) in Spain.\nFor more precise explanation visit the [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", "data": { "name": "Sensor Name", - "tariff": "Contracted tariff (1, 2, or 3 periods)" + "tariff": "Applicable tariff by geographic zone", + "power": "Contracted power (kW)", + "power_p3": "Contracted power for valley period P3 (kW)" } } }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } + }, + "options": { + "step": { + "init": { + "title": "Sensor setup", + "description": "This sensor uses official API to get [hourly pricing of electricity (PVPC)](https://www.esios.ree.es/es/pvpc) in Spain.\nFor more precise explanation visit the [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", + "data": { + "tariff": "Applicable tariff by geographic zone", + "power": "Contracted power (kW)", + "power_p3": "Contracted power for valley period P3 (kW)" + } + } + } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/en.json b/homeassistant/components/pvpc_hourly_pricing/translations/en.json index 02acb46eeb6..38f45d36ab6 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/en.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/en.json @@ -7,10 +7,25 @@ "user": { "data": { "name": "Sensor Name", - "tariff": "Contracted tariff (1, 2, or 3 periods)" + "power": "Contracted power (kW)", + "power_p3": "Contracted power for valley period P3 (kW)", + "tariff": "Applicable tariff by geographic zone" }, - "description": "This sensor uses official API to get [hourly pricing of electricity (PVPC)](https://www.esios.ree.es/es/pvpc) in Spain.\nFor more precise explanation visit the [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).\n\nSelect the contracted rate based on the number of billing periods per day:\n- 1 period: normal\n- 2 periods: discrimination (nightly rate)\n- 3 periods: electric car (nightly rate of 3 periods)", - "title": "Tariff selection" + "description": "This sensor uses official API to get [hourly pricing of electricity (PVPC)](https://www.esios.ree.es/es/pvpc) in Spain.\nFor more precise explanation visit the [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", + "title": "Sensor setup" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "power": "Contracted power (kW)", + "power_p3": "Contracted power for valley period P3 (kW)", + "tariff": "Applicable tariff by geographic zone" + }, + "description": "This sensor uses official API to get [hourly pricing of electricity (PVPC)](https://www.esios.ree.es/es/pvpc) in Spain.\nFor more precise explanation visit the [integration docs](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).", + "title": "Sensor setup" } } } diff --git a/requirements_all.txt b/requirements_all.txt index 866ecd62814..3ba957f69c3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -224,7 +224,7 @@ aiopulse==0.4.2 aiopvapi==1.6.14 # homeassistant.components.pvpc_hourly_pricing -aiopvpc==2.1.2 +aiopvpc==2.2.0 # homeassistant.components.webostv aiopylgtv==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 326e4bbc667..e82fa369708 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -146,7 +146,7 @@ aiopulse==0.4.2 aiopvapi==1.6.14 # homeassistant.components.pvpc_hourly_pricing -aiopvpc==2.1.2 +aiopvpc==2.2.0 # homeassistant.components.webostv aiopylgtv==0.4.0 diff --git a/tests/components/pvpc_hourly_pricing/conftest.py b/tests/components/pvpc_hourly_pricing/conftest.py index a16923d0a73..2421c753518 100644 --- a/tests/components/pvpc_hourly_pricing/conftest.py +++ b/tests/components/pvpc_hourly_pricing/conftest.py @@ -14,6 +14,7 @@ from tests.test_util.aiohttp import AiohttpClientMocker FIXTURE_JSON_DATA_2019_10_26 = "PVPC_CURV_DD_2019_10_26.json" FIXTURE_JSON_DATA_2019_10_27 = "PVPC_CURV_DD_2019_10_27.json" FIXTURE_JSON_DATA_2019_10_29 = "PVPC_CURV_DD_2019_10_29.json" +FIXTURE_JSON_DATA_2021_06_01 = "PVPC_CURV_DD_2021_06_01.json" def check_valid_state(state, tariff: str, value=None, key_attr=None): @@ -60,4 +61,10 @@ def pvpc_aioclient_mock(aioclient_mock: AiohttpClientMocker): text=load_fixture(f"{DOMAIN}/{FIXTURE_JSON_DATA_2019_10_29}"), ) + # new format for prices >= 2021-06-01 + aioclient_mock.get( + "https://api.esios.ree.es/archives/70/download_json?locale=es&date=2021-06-01", + text=load_fixture(f"{DOMAIN}/{FIXTURE_JSON_DATA_2021_06_01}"), + ) + return aioclient_mock diff --git a/tests/components/pvpc_hourly_pricing/test_config_flow.py b/tests/components/pvpc_hourly_pricing/test_config_flow.py index 2a64d81ef98..1c9cb7e133d 100644 --- a/tests/components/pvpc_hourly_pricing/test_config_flow.py +++ b/tests/components/pvpc_hourly_pricing/test_config_flow.py @@ -3,7 +3,13 @@ from datetime import datetime from unittest.mock import patch from homeassistant import config_entries, data_entry_flow -from homeassistant.components.pvpc_hourly_pricing import ATTR_TARIFF, DOMAIN +from homeassistant.components.pvpc_hourly_pricing import ( + ATTR_POWER, + ATTR_POWER_P3, + ATTR_TARIFF, + DOMAIN, + TARIFFS, +) from homeassistant.const import CONF_NAME from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util @@ -20,13 +26,20 @@ async def test_config_flow( """ Test config flow for pvpc_hourly_pricing. - - Create a new entry with tariff "normal" + - Create a new entry with tariff "2.0TD (Ceuta/Melilla)" - Check state and attributes - Check abort when trying to config another with same tariff - Check removal and add again to check state restoration + - Configure options to change power and tariff to "2.0TD" """ hass.config.time_zone = dt_util.get_time_zone("Europe/Madrid") - mock_data = {"return_time": datetime(2019, 10, 26, 14, 0, tzinfo=date_util.UTC)} + tst_config = { + CONF_NAME: "test", + ATTR_TARIFF: TARIFFS[1], + ATTR_POWER: 4.6, + ATTR_POWER_P3: 5.75, + } + mock_data = {"return_time": datetime(2021, 6, 1, 12, 0, tzinfo=date_util.UTC)} def mock_now(): return mock_data["return_time"] @@ -38,13 +51,13 @@ async def test_config_flow( assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_NAME: "test", ATTR_TARIFF: "normal"} + result["flow_id"], tst_config ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY await hass.async_block_till_done() state = hass.states.get("sensor.test") - check_valid_state(state, tariff="normal") + check_valid_state(state, tariff=TARIFFS[1]) assert pvpc_aioclient_mock.call_count == 1 # Check abort when configuring another with same tariff @@ -53,7 +66,7 @@ async def test_config_flow( ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_NAME: "test", ATTR_TARIFF: "normal"} + result["flow_id"], tst_config ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert pvpc_aioclient_mock.call_count == 1 @@ -70,11 +83,38 @@ async def test_config_flow( assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result = await hass.config_entries.flow.async_configure( - result["flow_id"], {CONF_NAME: "test", ATTR_TARIFF: "normal"} + result["flow_id"], tst_config ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY await hass.async_block_till_done() state = hass.states.get("sensor.test") - check_valid_state(state, tariff="normal") + check_valid_state(state, tariff=TARIFFS[1]) + price_pbc = state.state assert pvpc_aioclient_mock.call_count == 2 + assert state.attributes["period"] == "P2" + assert state.attributes["next_period"] == "P1" + assert state.attributes["available_power"] == 4600 + + # check options flow + current_entries = hass.config_entries.async_entries(DOMAIN) + assert len(current_entries) == 1 + config_entry = current_entries[0] + + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ATTR_TARIFF: TARIFFS[0], ATTR_POWER: 3.0, ATTR_POWER_P3: 4.6}, + ) + await hass.async_block_till_done() + state = hass.states.get("sensor.test") + price_cym = state.state + check_valid_state(state, tariff=TARIFFS[0]) + assert pvpc_aioclient_mock.call_count == 3 + assert state.attributes["period"] == "P2" + assert state.attributes["next_period"] == "P1" + assert state.attributes["available_power"] == 3000 + assert price_cym < price_pbc diff --git a/tests/components/pvpc_hourly_pricing/test_sensor.py b/tests/components/pvpc_hourly_pricing/test_sensor.py index 19f3a7aa31c..cee2374f192 100644 --- a/tests/components/pvpc_hourly_pricing/test_sensor.py +++ b/tests/components/pvpc_hourly_pricing/test_sensor.py @@ -3,15 +3,20 @@ from datetime import datetime, timedelta import logging from unittest.mock import patch -from homeassistant.components.pvpc_hourly_pricing import ATTR_TARIFF, DOMAIN +from homeassistant.components.pvpc_hourly_pricing import ( + ATTR_POWER, + ATTR_POWER_P3, + ATTR_TARIFF, + DOMAIN, + TARIFFS, +) from homeassistant.const import CONF_NAME from homeassistant.core import ATTR_NOW, EVENT_TIME_CHANGED -from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util from .conftest import check_valid_state -from tests.common import date_util +from tests.common import MockConfigEntry, date_util, mock_registry from tests.test_util.aiohttp import AiohttpClientMocker @@ -32,14 +37,27 @@ async def test_sensor_availability( ): """Test sensor availability and handling of cloud access.""" hass.config.time_zone = dt_util.get_time_zone("Europe/Madrid") - config = {DOMAIN: [{CONF_NAME: "test_dst", ATTR_TARIFF: "discrimination"}]} + config_entry = MockConfigEntry( + domain=DOMAIN, data={CONF_NAME: "test_dst", ATTR_TARIFF: "discrimination"} + ) + config_entry.add_to_hass(hass) + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 mock_data = {"return_time": datetime(2019, 10, 27, 20, 0, 0, tzinfo=date_util.UTC)} def mock_now(): return mock_data["return_time"] with patch("homeassistant.util.dt.utcnow", new=mock_now): - assert await async_setup_component(hass, DOMAIN, config) + assert await hass.config_entries.async_setup(config_entry.entry_id) + + # check migration + current_entries = hass.config_entries.async_entries(DOMAIN) + assert len(current_entries) == 1 + migrated_entry = current_entries[0] + assert migrated_entry.version == 1 + assert migrated_entry.data[ATTR_POWER] == migrated_entry.data[ATTR_POWER_P3] + assert migrated_entry.data[ATTR_TARIFF] == TARIFFS[0] + await hass.async_block_till_done() caplog.clear() assert pvpc_aioclient_mock.call_count == 2 @@ -85,3 +103,64 @@ async def test_sensor_availability( assert pvpc_aioclient_mock.call_count == 33 assert len(caplog.messages) == 1 assert caplog.records[0].levelno == logging.WARNING + + +async def test_multi_sensor_migration( + hass, caplog, legacy_patchable_time, pvpc_aioclient_mock: AiohttpClientMocker +): + """Test tariff migration when there are >1 old sensors.""" + entity_reg = mock_registry(hass) + hass.config.time_zone = dt_util.get_time_zone("Europe/Madrid") + uid_1 = "discrimination" + uid_2 = "normal" + old_conf_1 = {CONF_NAME: "test_pvpc_1", ATTR_TARIFF: uid_1} + old_conf_2 = {CONF_NAME: "test_pvpc_2", ATTR_TARIFF: uid_2} + + config_entry_1 = MockConfigEntry(domain=DOMAIN, data=old_conf_1, unique_id=uid_1) + config_entry_1.add_to_hass(hass) + entity1 = entity_reg.async_get_or_create( + domain="sensor", + platform=DOMAIN, + unique_id=uid_1, + config_entry=config_entry_1, + suggested_object_id="test_pvpc_1", + ) + + config_entry_2 = MockConfigEntry(domain=DOMAIN, data=old_conf_2, unique_id=uid_2) + config_entry_2.add_to_hass(hass) + entity2 = entity_reg.async_get_or_create( + domain="sensor", + platform=DOMAIN, + unique_id=uid_2, + config_entry=config_entry_2, + suggested_object_id="test_pvpc_2", + ) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 2 + assert len(entity_reg.entities) == 2 + + mock_data = {"return_time": datetime(2019, 10, 27, 20, tzinfo=date_util.UTC)} + + def mock_now(): + return mock_data["return_time"] + + caplog.clear() + with caplog.at_level(logging.WARNING): + with patch("homeassistant.util.dt.utcnow", new=mock_now): + assert await hass.config_entries.async_setup(config_entry_1.entry_id) + assert len(caplog.messages) == 2 + + # check migration with removal of extra sensors + assert len(entity_reg.entities) == 1 + assert entity1.entity_id in entity_reg.entities + assert entity2.entity_id not in entity_reg.entities + + current_entries = hass.config_entries.async_entries(DOMAIN) + assert len(current_entries) == 1 + migrated_entry = current_entries[0] + assert migrated_entry.version == 1 + assert migrated_entry.data[ATTR_POWER] == migrated_entry.data[ATTR_POWER_P3] + assert migrated_entry.data[ATTR_TARIFF] == TARIFFS[0] + + await hass.async_block_till_done() + assert pvpc_aioclient_mock.call_count == 2 diff --git a/tests/fixtures/pvpc_hourly_pricing/PVPC_CURV_DD_2021_06_01.json b/tests/fixtures/pvpc_hourly_pricing/PVPC_CURV_DD_2021_06_01.json new file mode 100644 index 00000000000..59559d3c3f7 --- /dev/null +++ b/tests/fixtures/pvpc_hourly_pricing/PVPC_CURV_DD_2021_06_01.json @@ -0,0 +1,604 @@ +{ + "PVPC": [ + { + "Dia": "01/06/2021", + "Hora": "00-01", + "PCB": "116,33", + "CYM": "116,33", + "COF2TD": "0,000088075182000000", + "PMHPCB": "104,00", + "PMHCYM": "104,00", + "SAHPCB": "3,56", + "SAHCYM": "3,56", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,00", + "PCAPCYM": "0,00", + "TEUPCB": "6,00", + "TEUCYM": "6,00", + "CCVPCB": "2,57", + "CCVCYM": "2,57", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "01-02", + "PCB": "115,95", + "CYM": "115,95", + "COF2TD": "0,000073094842000000", + "PMHPCB": "103,18", + "PMHCYM": "103,18", + "SAHPCB": "3,99", + "SAHCYM": "3,99", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,00", + "PCAPCYM": "0,00", + "TEUPCB": "6,00", + "TEUCYM": "6,00", + "CCVPCB": "2,58", + "CCVCYM": "2,58", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "02-03", + "PCB": "114,89", + "CYM": "114,89", + "COF2TD": "0,000065114032000000", + "PMHPCB": "101,87", + "PMHCYM": "101,87", + "SAHPCB": "4,25", + "SAHCYM": "4,25", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,00", + "PCAPCYM": "0,00", + "TEUPCB": "6,00", + "TEUCYM": "6,00", + "CCVPCB": "2,56", + "CCVCYM": "2,56", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "03-04", + "PCB": "114,96", + "CYM": "114,96", + "COF2TD": "0,000061272596000000", + "PMHPCB": "102,01", + "PMHCYM": "102,01", + "SAHPCB": "4,19", + "SAHCYM": "4,19", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,00", + "PCAPCYM": "0,00", + "TEUPCB": "6,00", + "TEUCYM": "6,00", + "CCVPCB": "2,57", + "CCVCYM": "2,57", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "04-05", + "PCB": "114,84", + "CYM": "114,84", + "COF2TD": "0,000059563056000000", + "PMHPCB": "101,87", + "PMHCYM": "101,87", + "SAHPCB": "4,21", + "SAHCYM": "4,21", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,00", + "PCAPCYM": "0,00", + "TEUPCB": "6,00", + "TEUCYM": "6,00", + "CCVPCB": "2,56", + "CCVCYM": "2,56", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "05-06", + "PCB": "116,03", + "CYM": "116,03", + "COF2TD": "0,000059907686000000", + "PMHPCB": "103,14", + "PMHCYM": "103,14", + "SAHPCB": "4,11", + "SAHCYM": "4,11", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,00", + "PCAPCYM": "0,00", + "TEUPCB": "6,00", + "TEUCYM": "6,00", + "CCVPCB": "2,58", + "CCVCYM": "2,58", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "06-07", + "PCB": "116,29", + "CYM": "116,29", + "COF2TD": "0,000062818713000000", + "PMHPCB": "103,64", + "PMHCYM": "103,64", + "SAHPCB": "3,88", + "SAHCYM": "3,88", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,00", + "PCAPCYM": "0,00", + "TEUPCB": "6,00", + "TEUCYM": "6,00", + "CCVPCB": "2,57", + "CCVCYM": "2,57", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "07-08", + "PCB": "115,70", + "CYM": "115,70", + "COF2TD": "0,000072575564000000", + "PMHPCB": "103,85", + "PMHCYM": "103,85", + "SAHPCB": "3,10", + "SAHCYM": "3,10", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,00", + "PCAPCYM": "0,00", + "TEUPCB": "6,00", + "TEUCYM": "6,00", + "CCVPCB": "2,55", + "CCVCYM": "2,55", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "08-09", + "PCB": "152,89", + "CYM": "152,89", + "COF2TD": "0,000086825264000000", + "PMHPCB": "105,65", + "PMHCYM": "105,65", + "SAHPCB": "2,36", + "SAHCYM": "2,36", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,34", + "PCAPCYM": "0,34", + "TEUPCB": "41,77", + "TEUCYM": "41,77", + "CCVPCB": "2,57", + "CCVCYM": "2,57", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "09-10", + "PCB": "150,83", + "CYM": "150,83", + "COF2TD": "0,000095768317000000", + "PMHPCB": "103,77", + "PMHCYM": "103,77", + "SAHPCB": "2,24", + "SAHCYM": "2,24", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,34", + "PCAPCYM": "0,34", + "TEUPCB": "41,77", + "TEUCYM": "41,77", + "CCVPCB": "2,53", + "CCVCYM": "2,53", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "10-11", + "PCB": "242,62", + "CYM": "149,28", + "COF2TD": "0,000102672431000000", + "PMHPCB": "102,38", + "PMHCYM": "102,11", + "SAHPCB": "2,38", + "SAHCYM": "2,37", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "2,01", + "PCAPCYM": "0,34", + "TEUPCB": "133,12", + "TEUCYM": "41,77", + "CCVPCB": "2,54", + "CCVCYM": "2,51", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "11-12", + "PCB": "240,50", + "CYM": "240,50", + "COF2TD": "0,000105691470000000", + "PMHPCB": "100,14", + "PMHCYM": "100,14", + "SAHPCB": "2,52", + "SAHCYM": "2,52", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "2,02", + "PCAPCYM": "2,02", + "TEUPCB": "133,12", + "TEUCYM": "133,12", + "CCVPCB": "2,51", + "CCVCYM": "2,51", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "12-13", + "PCB": "238,09", + "CYM": "238,09", + "COF2TD": "0,000110462952000000", + "PMHPCB": "97,58", + "PMHCYM": "97,58", + "SAHPCB": "2,71", + "SAHCYM": "2,71", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "2,02", + "PCAPCYM": "2,02", + "TEUPCB": "133,12", + "TEUCYM": "133,12", + "CCVPCB": "2,47", + "CCVCYM": "2,47", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "13-14", + "PCB": "235,30", + "CYM": "235,30", + "COF2TD": "0,000119052052000000", + "PMHPCB": "94,65", + "PMHCYM": "94,65", + "SAHPCB": "2,89", + "SAHCYM": "2,89", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "2,02", + "PCAPCYM": "2,02", + "TEUPCB": "133,12", + "TEUCYM": "133,12", + "CCVPCB": "2,43", + "CCVCYM": "2,43", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "14-15", + "PCB": "137,96", + "CYM": "231,28", + "COF2TD": "0,000117990009000000", + "PMHPCB": "89,95", + "PMHCYM": "90,19", + "SAHPCB": "3,37", + "SAHCYM": "3,38", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,34", + "PCAPCYM": "2,03", + "TEUPCB": "41,77", + "TEUCYM": "133,12", + "CCVPCB": "2,34", + "CCVCYM": "2,37", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "15-16", + "PCB": "132,88", + "CYM": "132,88", + "COF2TD": "0,000108598330000000", + "PMHPCB": "84,43", + "PMHCYM": "84,43", + "SAHPCB": "3,89", + "SAHCYM": "3,89", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,34", + "PCAPCYM": "0,34", + "TEUPCB": "41,77", + "TEUCYM": "41,77", + "CCVPCB": "2,26", + "CCVCYM": "2,26", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "16-17", + "PCB": "131,93", + "CYM": "131,93", + "COF2TD": "0,000104114191000000", + "PMHPCB": "83,66", + "PMHCYM": "83,66", + "SAHPCB": "3,73", + "SAHCYM": "3,73", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,34", + "PCAPCYM": "0,34", + "TEUPCB": "41,77", + "TEUCYM": "41,77", + "CCVPCB": "2,25", + "CCVCYM": "2,25", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "17-18", + "PCB": "135,99", + "CYM": "135,99", + "COF2TD": "0,000105171071000000", + "PMHPCB": "88,07", + "PMHCYM": "88,07", + "SAHPCB": "3,31", + "SAHCYM": "3,31", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,34", + "PCAPCYM": "0,34", + "TEUPCB": "41,77", + "TEUCYM": "41,77", + "CCVPCB": "2,31", + "CCVCYM": "2,31", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "18-19", + "PCB": "231,44", + "CYM": "138,13", + "COF2TD": "0,000106417649000000", + "PMHPCB": "90,57", + "PMHCYM": "90,33", + "SAHPCB": "3,16", + "SAHCYM": "3,15", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,16", + "FOSCYM": "0,16", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "2,02", + "PCAPCYM": "0,34", + "TEUPCB": "133,12", + "TEUCYM": "41,77", + "CCVPCB": "2,37", + "CCVCYM": "2,34", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "19-20", + "PCB": "240,40", + "CYM": "240,40", + "COF2TD": "0,000108017615000000", + "PMHPCB": "99,53", + "PMHCYM": "99,53", + "SAHPCB": "3,00", + "SAHCYM": "3,00", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "2,04", + "PCAPCYM": "2,04", + "TEUPCB": "133,12", + "TEUCYM": "133,12", + "CCVPCB": "2,52", + "CCVCYM": "2,52", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "20-21", + "PCB": "246,20", + "CYM": "246,20", + "COF2TD": "0,000114631042000000", + "PMHPCB": "104,32", + "PMHCYM": "104,32", + "SAHPCB": "3,90", + "SAHCYM": "3,90", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "2,05", + "PCAPCYM": "2,05", + "TEUPCB": "133,12", + "TEUCYM": "133,12", + "CCVPCB": "2,61", + "CCVCYM": "2,61", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "21-22", + "PCB": "248,08", + "CYM": "248,08", + "COF2TD": "0,000127585671000000", + "PMHPCB": "107,28", + "PMHCYM": "107,28", + "SAHPCB": "2,78", + "SAHCYM": "2,78", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "2,06", + "PCAPCYM": "2,06", + "TEUPCB": "133,12", + "TEUCYM": "133,12", + "CCVPCB": "2,64", + "CCVCYM": "2,64", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "22-23", + "PCB": "155,91", + "CYM": "249,41", + "COF2TD": "0,000130129026000000", + "PMHPCB": "108,02", + "PMHCYM": "108,39", + "SAHPCB": "2,93", + "SAHCYM": "2,94", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,35", + "PCAPCYM": "2,09", + "TEUPCB": "41,77", + "TEUCYM": "133,12", + "CCVPCB": "2,64", + "CCVCYM": "2,67", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + }, + { + "Dia": "01/06/2021", + "Hora": "23-24", + "PCB": "156,50", + "CYM": "156,50", + "COF2TD": "0,000110367990000000", + "PMHPCB": "108,02", + "PMHCYM": "108,02", + "SAHPCB": "3,50", + "SAHCYM": "3,50", + "FOMPCB": "0,03", + "FOMCYM": "0,03", + "FOSPCB": "0,17", + "FOSCYM": "0,17", + "INTPCB": "0,00", + "INTCYM": "0,00", + "PCAPPCB": "0,35", + "PCAPCYM": "0,35", + "TEUPCB": "41,77", + "TEUCYM": "41,77", + "CCVPCB": "2,66", + "CCVCYM": "2,66", + "EDSRPCB": "0,00", + "EDSRCYM": "0,00" + } + ] +} \ No newline at end of file