class ElecPricesDataUpdateCoordinator(DataUpdateCoordinator[Mapping[datetime, float]]): """Class to manage fetching Electricity prices data from API.""" def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize.""" self.api = PVPCData( session=async_get_clientsession(hass), tariff=entry.data[ATTR_TARIFF], local_timezone=hass.config.time_zone, power=entry.data[ATTR_POWER], power_valley=entry.data[ATTR_POWER_P3], ) super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=timedelta(minutes=30)) self._entry = entry @property def entry_id(self) -> str: """Return entry ID.""" return self._entry.entry_id async def _async_update_data(self) -> Mapping[datetime, float]: """Update electricity prices from the ESIOS API.""" prices = await self.api.async_update_prices(dt_util.utcnow()) self.api.process_state_and_attributes(dt_util.utcnow()) if not prices: raise UpdateFailed return prices
async def test_bad_downloads( available, day_str, num_log_msgs, status, exception, caplog, ): """Test data parsing of official API files.""" day = datetime.fromisoformat(day_str) mock_session = MockAsyncSession(status=status, exc=exception) with caplog.at_level(logging.DEBUG): pvpc_data = PVPCData( local_timezone=REFERENCE_TZ, tariff="normal", websession=mock_session, ) pvpc_data.source_available = available assert not pvpc_data.process_state_and_attributes(day) prices = await pvpc_data.async_update_prices(day) assert not prices assert not pvpc_data.process_state_and_attributes(day) assert len(caplog.messages) == num_log_msgs assert mock_session.call_count == 1 assert len(prices) == 0
async def test_real_download_today_async(): async with ClientSession() as session: pvpc_handler = PVPCData("discrimination", websession=session) prices = await pvpc_handler.async_update_prices(datetime.utcnow()) assert 22 < len(prices) < 49 # Check error without session pvpc_handler_bad = PVPCData("discriminacion") with pytest.raises(AssertionError): await pvpc_handler_bad.async_update_prices(datetime.utcnow())
def test_real_download_range(): # No async pvpc_handler = PVPCData("normal") start = datetime(2019, 10, 26, 15) end = datetime(2019, 10, 28, 13) prices = pvpc_handler.download_prices_for_range(start, end) assert len(prices) == 48 no_prices = pvpc_handler.download_prices_for_range( datetime(2010, 8, 26, 23), datetime(2010, 8, 27, 22)) assert len(no_prices) == 0
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize.""" self.api = PVPCData( session=async_get_clientsession(hass), tariff=entry.data[ATTR_TARIFF], local_timezone=hass.config.time_zone, power=entry.data[ATTR_POWER], power_valley=entry.data[ATTR_POWER_P3], ) super().__init__( hass, _LOGGER, name=DOMAIN, update_interval=timedelta(minutes=30) ) self._entry = entry
async def test_real_download_range_async(): start = datetime(2019, 10, 26, 15) end = datetime(2019, 10, 28, 13) async with ClientSession() as session: pvpc_handler = PVPCData("normal", websession=session) prices = await pvpc_handler.async_download_prices_for_range(start, end) assert len(prices) == 48 # without session also works, creating one for each download range call pvpc_handler_no_s = PVPCData("normal") prices2 = await pvpc_handler_no_s.async_download_prices_for_range( start, end) assert len(prices2) == 48 assert prices == prices2
async def test_download_range(caplog): """Test retrieval of full PVPC data in a day range.""" start = datetime(2019, 10, 26, 15) end = datetime(2019, 10, 28, 13) mock_session = MockAsyncSession() with caplog.at_level(logging.WARNING): pvpc_data = PVPCData(tariff="electric_car", local_timezone=_TZ_TEST, websession=mock_session) prices = await pvpc_data.async_download_prices_for_range(start, end) assert mock_session.call_count == 3 assert len(prices) == 33 assert len(caplog.messages) == 2 no_prices = await pvpc_data.async_download_prices_for_range( datetime(2010, 8, 26, 23), datetime(2010, 8, 27, 22)) assert len(no_prices) == 0 assert len(caplog.messages) == 4 first_price = min(prices) assert first_price.hour == 14 and first_price.tzname() == "UTC" # Check only tariff values are retrieved assert isinstance(prices[first_price], float) assert prices[first_price] < 1
async def test_price_extract(day_str, num_prices, num_calls, num_prices_8h, available_8h): """Test data parsing of official API files.""" day = datetime.fromisoformat(day_str) mock_session = MockAsyncSession() pvpc_data = PVPCData( local_timezone=_TZ_TEST, tariff="discrimination", websession=mock_session, ) pvpc_data.source_available = True assert not pvpc_data.process_state_and_attributes(day) await pvpc_data.async_update_prices(day) has_prices = pvpc_data.process_state_and_attributes(day) assert len(pvpc_data._current_prices) == num_prices assert mock_session.call_count == num_calls assert has_prices has_prices = pvpc_data.process_state_and_attributes(day + timedelta(hours=10)) assert len(pvpc_data._current_prices) == num_prices_8h assert has_prices == available_8h
def test_full_data_download_range(): """Test retrieval of full PVPC data in a day range.""" start = _TZ_TEST.localize(datetime(2019, 10, 26, 15)) end = _TZ_TEST.localize(datetime(2019, 10, 27, 13)) with patch("aiohttp.ClientSession", MockAsyncSession): pvpc_data = PVPCData() prices = pvpc_data.download_prices_for_range(start, end) assert len(prices) == 24 first_price = min(prices) last_price = max(prices) assert first_price.hour == 14 and first_price.tzname() == "UTC" assert last_price.hour == 13 and last_price.tzname() == "UTC" data_first_hour = prices[first_price] # Check full PVPC data is retrieved assert len(data_first_hour) == 30 assert all(tag in data_first_hour for tag in ESIOS_TARIFFS) # Check units have not changed in full data retrieval (they are in €/MWh) assert all(data_first_hour[tag] > 1 for tag in ESIOS_TARIFFS)
def test_full_data_download_range(timezone, start, end): """Test retrieval of full PVPC data in a day range.""" with patch("aiohttp.ClientSession", MockAsyncSession): pvpc_data = PVPCData(local_timezone=timezone) prices = pvpc_data.download_prices_for_range(start, end) assert len(prices) == 24 first_price = min(prices) last_price = max(prices) data_first_hour = prices[first_price] # Check full PVPC data is retrieved assert len(data_first_hour) == 30 assert all(tag in data_first_hour for tag in ESIOS_TARIFFS) # Check units have not changed in full data retrieval (they are in €/MWh) assert all(data_first_hour[tag] > 1 for tag in ESIOS_TARIFFS) # check tz-alignment (price at 15h is tz-independent) assert prices[first_price]["NOC"] == 119.16 assert first_price.astimezone(timezone).hour == 15 assert last_price.astimezone(timezone).hour == 13
async def async_setup_entry(hass: HomeAssistant, config_entry: config_entries.ConfigEntry, async_add_entities): """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], local_timezone=hass.config.time_zone, websession=async_get_clientsession(hass), logger=_LOGGER, timeout=_DEFAULT_TIMEOUT, ) async_add_entities( [ElecPriceSensor(name, config_entry.unique_id, pvpc_data_handler)], False)
async def async_setup_entry( 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), timeout=_DEFAULT_TIMEOUT, ) async_add_entities( [ElecPriceSensor(name, config_entry.unique_id, pvpc_data_handler)], False)
async def get_pvpc_data( consumo: pd.Series, path_csv_pvpc_store: Optional[Union[Path, str]] = None) -> pd.DataFrame: """ Download PVPC data for the given consumption series using `aiopvpc`. If a path for a PVPC local csv store is given, it'll try to use pre-loaded data, and it'll update the local file with new data. """ df_store = pd.DataFrame() path_pvpc_csv = None if path_csv_pvpc_store is not None: # Use PVPC local storage path_pvpc_csv = Path(path_csv_pvpc_store) # check if already have it if path_csv_pvpc_store is not None and path_pvpc_csv.exists(): df_store = _load_stored_csv(path_pvpc_csv) df = df_store.loc[consumo.index[0]:consumo.index[-1]] if not df.empty and df.shape[0] == consumo.shape[0]: print("USING cached data ;-)") return df # proceed to download PVPC range pvpc_handler = PVPCData() data = await pvpc_handler.async_download_prices_for_range( consumo.index[0], consumo.index[-1]) df = pd.DataFrame(data).T.reindex(consumo.index) assert df.index.equals(consumo.index) if path_csv_pvpc_store is None: return df if df_store.empty: df.round(12).to_csv(path_pvpc_csv) return df # TODO check drop duplicates! df_store = df_store.append(df).drop_duplicates().sort_index() df_store.round(12).to_csv(path_pvpc_csv) return df