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."""
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)

View File

@ -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)

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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

View File

@ -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

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_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

View File

@ -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

View File

@ -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

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