Adopt new electricity tariffs in pvpc hourly pricing (#51789)

This commit is contained in:
Eugenio Panadero 2021-06-17 09:03:28 +02:00 committed by GitHub
parent d4ac5bf048
commit b7c1df7864
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 938 additions and 71 deletions

View File

@ -1,17 +1,38 @@
"""The pvpc_hourly_pricing integration to collect Spain official electric prices.""" """The pvpc_hourly_pricing integration to collect Spain official electric prices."""
import logging
from aiopvpc import DEFAULT_POWER_KW, TARIFFS
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv 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( UI_CONFIG_SCHEMA = vol.Schema(
{ {
vol.Required(CONF_NAME, default=DEFAULT_NAME): str, 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( CONFIG_SCHEMA = vol.Schema(
@ -20,19 +41,8 @@ CONFIG_SCHEMA = vol.Schema(
) )
async def async_setup(hass: HomeAssistant, config: dict): async def async_setup(hass: HomeAssistant, config: dict) -> bool:
""" """Set up the electricity price sensor from configuration.yaml."""
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
```
"""
for conf in config.get(DOMAIN, []): for conf in config.get(DOMAIN, []):
hass.async_create_task( hass.async_create_task(
hass.config_entries.flow.async_init( 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: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up pvpc hourly pricing from a config entry.""" """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) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_update_options))
return True 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.""" """Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -1,17 +1,24 @@
"""Config flow for pvpc_hourly_pricing.""" """Config flow for pvpc_hourly_pricing."""
import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.core import callback
from . import CONF_NAME, UI_CONFIG_SCHEMA from . import CONF_NAME, UI_CONFIG_SCHEMA, VALID_POWER, VALID_TARIFF
from .const import ATTR_TARIFF, DOMAIN from .const import ATTR_POWER, ATTR_POWER_P3, ATTR_TARIFF, DOMAIN
_DOMAIN_NAME = DOMAIN
class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=_DOMAIN_NAME): class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for `pvpc_hourly_pricing` to select the tariff.""" """Handle config flow for `pvpc_hourly_pricing`."""
VERSION = 1 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): async def async_step_user(self, user_input=None):
"""Handle the initial step.""" """Handle the initial step."""
if user_input is not None: 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): async def async_step_import(self, import_info):
"""Handle import from config file.""" """Handle import from config file."""
return await self.async_step_user(import_info) 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)

View File

@ -1,8 +1,7 @@
"""Constant values for pvpc_hourly_pricing.""" """Constant values for pvpc_hourly_pricing."""
from aiopvpc import TARIFFS
DOMAIN = "pvpc_hourly_pricing" DOMAIN = "pvpc_hourly_pricing"
PLATFORMS = ["sensor"] PLATFORMS = ["sensor"]
ATTR_POWER = "power"
ATTR_POWER_P3 = "power_p3"
ATTR_TARIFF = "tariff" ATTR_TARIFF = "tariff"
DEFAULT_NAME = "PVPC" DEFAULT_NAME = "PVPC"
DEFAULT_TARIFF = TARIFFS[1]

View File

@ -3,7 +3,7 @@
"name": "Spain electricity hourly pricing (PVPC)", "name": "Spain electricity hourly pricing (PVPC)",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/pvpc_hourly_pricing", "documentation": "https://www.home-assistant.io/integrations/pvpc_hourly_pricing",
"requirements": ["aiopvpc==2.1.2"], "requirements": ["aiopvpc==2.2.0"],
"codeowners": ["@azogue"], "codeowners": ["@azogue"],
"quality_scale": "platinum", "quality_scale": "platinum",
"iot_class": "cloud_polling" "iot_class": "cloud_polling"

View File

@ -3,19 +3,21 @@ from __future__ import annotations
import logging import logging
from random import randint from random import randint
from typing import Any
from aiopvpc import PVPCData from aiopvpc import PVPCData
from homeassistant import config_entries
from homeassistant.components.sensor import SensorEntity 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.const import CONF_NAME, CURRENCY_EURO, ENERGY_KILO_WATT_HOUR
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession 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.event import async_call_later, async_track_time_change
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
import homeassistant.util.dt as dt_util 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__) _LOGGER = logging.getLogger(__name__)
@ -27,15 +29,18 @@ _DEFAULT_TIMEOUT = 10
async def async_setup_entry( 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.""" """Set up the electricity price sensor from config_entry."""
name = config_entry.data[CONF_NAME] name = config_entry.data[CONF_NAME]
pvpc_data_handler = PVPCData( pvpc_data_handler = PVPCData(
tariff=config_entry.data[ATTR_TARIFF], 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, local_timezone=hass.config.time_zone,
websession=async_get_clientsession(hass), websession=async_get_clientsession(hass),
logger=_LOGGER,
timeout=_DEFAULT_TIMEOUT, timeout=_DEFAULT_TIMEOUT,
) )
async_add_entities( async_add_entities(
@ -57,15 +62,7 @@ class ElecPriceSensor(RestoreEntity, SensorEntity):
self._pvpc_data = pvpc_data_handler self._pvpc_data = pvpc_data_handler
self._num_retries = 0 self._num_retries = 0
self._hourly_tracker = None async def async_added_to_hass(self) -> 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):
"""Handle entity which will be added.""" """Handle entity which will be added."""
await super().async_added_to_hass() await super().async_added_to_hass()
state = await self.async_get_last_state() state = await self.async_get_last_state()
@ -73,14 +70,18 @@ class ElecPriceSensor(RestoreEntity, SensorEntity):
self._pvpc_data.state = state.state self._pvpc_data.state = state.state
# Update 'state' value in hour changes # Update 'state' value in hour changes
self._hourly_tracker = async_track_time_change( self.async_on_remove(
self.hass, self.update_current_price, second=[0], minute=[0] 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) # Update prices at random time, 2 times/hour (don't want to upset API)
random_minute = randint(1, 29) random_minute = randint(1, 29)
mins_update = [random_minute, random_minute + 30] mins_update = [random_minute, random_minute + 30]
self._price_tracker = async_track_time_change( self.async_on_remove(
self.hass, self.async_update_prices, second=[0], minute=mins_update async_track_time_change(
self.hass, self.async_update_prices, second=[0], minute=mins_update
)
) )
_LOGGER.debug( _LOGGER.debug(
"Setup of price sensor %s (%s) with tariff '%s', " "Setup of price sensor %s (%s) with tariff '%s', "
@ -90,8 +91,9 @@ class ElecPriceSensor(RestoreEntity, SensorEntity):
self._pvpc_data.tariff, self._pvpc_data.tariff,
mins_update, mins_update,
) )
await self.async_update_prices(dt_util.utcnow()) now = dt_util.utcnow()
self.update_current_price(dt_util.utcnow()) await self.async_update_prices(now)
self.update_current_price(now)
@property @property
def unique_id(self) -> str | None: def unique_id(self) -> str | None:
@ -99,12 +101,12 @@ class ElecPriceSensor(RestoreEntity, SensorEntity):
return self._unique_id return self._unique_id
@property @property
def name(self): def name(self) -> str:
"""Return the name of the sensor.""" """Return the name of the sensor."""
return self._name return self._name
@property @property
def state(self): def state(self) -> float:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._pvpc_data.state return self._pvpc_data.state
@ -114,7 +116,7 @@ class ElecPriceSensor(RestoreEntity, SensorEntity):
return self._pvpc_data.state_available return self._pvpc_data.state_available
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes.""" """Return the state attributes."""
return self._pvpc_data.attributes return self._pvpc_data.attributes

View File

@ -2,16 +2,31 @@
"config": { "config": {
"step": { "step": {
"user": { "user": {
"title": "Tariff selection", "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/).\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)", "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": { "data": {
"name": "Sensor Name", "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": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]" "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)"
}
}
}
} }
} }

View File

@ -7,10 +7,25 @@
"user": { "user": {
"data": { "data": {
"name": "Sensor Name", "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)", "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": "Tariff selection" "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"
} }
} }
} }

View File

@ -224,7 +224,7 @@ aiopulse==0.4.2
aiopvapi==1.6.14 aiopvapi==1.6.14
# homeassistant.components.pvpc_hourly_pricing # homeassistant.components.pvpc_hourly_pricing
aiopvpc==2.1.2 aiopvpc==2.2.0
# homeassistant.components.webostv # homeassistant.components.webostv
aiopylgtv==0.4.0 aiopylgtv==0.4.0

View File

@ -146,7 +146,7 @@ aiopulse==0.4.2
aiopvapi==1.6.14 aiopvapi==1.6.14
# homeassistant.components.pvpc_hourly_pricing # homeassistant.components.pvpc_hourly_pricing
aiopvpc==2.1.2 aiopvpc==2.2.0
# homeassistant.components.webostv # homeassistant.components.webostv
aiopylgtv==0.4.0 aiopylgtv==0.4.0

View File

@ -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_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_27 = "PVPC_CURV_DD_2019_10_27.json"
FIXTURE_JSON_DATA_2019_10_29 = "PVPC_CURV_DD_2019_10_29.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): 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}"), 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 return aioclient_mock

View File

@ -3,7 +3,13 @@ from datetime import datetime
from unittest.mock import patch from unittest.mock import patch
from homeassistant import config_entries, data_entry_flow 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.const import CONF_NAME
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
@ -20,13 +26,20 @@ async def test_config_flow(
""" """
Test config flow for pvpc_hourly_pricing. 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 state and attributes
- Check abort when trying to config another with same tariff - Check abort when trying to config another with same tariff
- Check removal and add again to check state restoration - 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") 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(): def mock_now():
return mock_data["return_time"] return mock_data["return_time"]
@ -38,13 +51,13 @@ async def test_config_flow(
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
result = await hass.config_entries.flow.async_configure( 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 assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get("sensor.test") 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 assert pvpc_aioclient_mock.call_count == 1
# Check abort when configuring another with same tariff # 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 assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
result = await hass.config_entries.flow.async_configure( 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 result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert pvpc_aioclient_mock.call_count == 1 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 assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
result = await hass.config_entries.flow.async_configure( 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 assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get("sensor.test") 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 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

View File

@ -3,15 +3,20 @@ from datetime import datetime, timedelta
import logging import logging
from unittest.mock import patch 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.const import CONF_NAME
from homeassistant.core import ATTR_NOW, EVENT_TIME_CHANGED 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 homeassistant.util import dt as dt_util
from .conftest import check_valid_state 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 from tests.test_util.aiohttp import AiohttpClientMocker
@ -32,14 +37,27 @@ async def test_sensor_availability(
): ):
"""Test sensor availability and handling of cloud access.""" """Test sensor availability and handling of cloud access."""
hass.config.time_zone = dt_util.get_time_zone("Europe/Madrid") 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)} mock_data = {"return_time": datetime(2019, 10, 27, 20, 0, 0, tzinfo=date_util.UTC)}
def mock_now(): def mock_now():
return mock_data["return_time"] return mock_data["return_time"]
with patch("homeassistant.util.dt.utcnow", new=mock_now): 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() await hass.async_block_till_done()
caplog.clear() caplog.clear()
assert pvpc_aioclient_mock.call_count == 2 assert pvpc_aioclient_mock.call_count == 2
@ -85,3 +103,64 @@ async def test_sensor_availability(
assert pvpc_aioclient_mock.call_count == 33 assert pvpc_aioclient_mock.call_count == 33
assert len(caplog.messages) == 1 assert len(caplog.messages) == 1
assert caplog.records[0].levelno == logging.WARNING 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

View File

@ -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"
}
]
}