-
-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Add pvpc electricity prices integration #32092
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
bdraco
merged 34 commits into
home-assistant:dev
from
azogue:feature/electric-prices-spain
Mar 22, 2020
Merged
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
d245990
Add new integration: pvpc_hourly_pricing
azogue f8c15f1
Update requirements and add to codeowners
azogue 2a62889
Avoid passing in hass as a parameter to the entity
azogue a76ae86
Fix lint issues
azogue d51d700
Add tests for config & options flow
azogue 9bb3cbe
Add tests for manual yaml config
azogue f9bcff8
Fix placement of PLATFORM_SCHEMA and update generated config_flows
azogue 6e05637
Store prices internally linked to UTC timestamps
azogue 5de754c
Add availability to sensor
azogue a830b02
Add more tests
azogue 33fa936
fix linter
azogue 3d4d98c
Better handling of sensor availability and minor enhancements
azogue db82db4
Mock aiosession to not access real API, store fixture data
azogue 977077c
Change API endpoint to retrieve JSON data
azogue f29a165
Adapt tests to new API endpoint
azogue 5d866f6
Translate tariff labels to plain English
azogue 0ac1ea7
Relax logging levels to meet silver requirements
azogue 0e684f9
Fix requirements
azogue 727daa2
Mod tests to work with timezone Atlantic/Canary
azogue d960135
Try to fix CI tests
azogue 883c0d9
Externalize pvpc data and simplify sensor.py
azogue e36ab00
Simplify tests for pvpc_hourly_pricing
azogue a2fb561
Fix updater for options flow
azogue 1e5324b
Fix lint
azogue 463b107
Bump aiopvpc
azogue ff206c5
Remove options flow and platform setup
azogue 30304ce
Fix docstring on test
azogue aa500b1
Remove timeout manual config, fix entry.options usage, simplify uniqu…
azogue da195fa
Simplify tests
azogue a64cc99
Fix possible duplicated update
azogue 6ffc837
Do not access State last_changed for log messages
azogue da4e6ec
Do not update until entity is added to hass
azogue 97f100a
minor changes
azogue 2b53ee0
Rename method to select current price and make it a callback
azogue File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
homeassistant/components/pvpc_hourly_pricing/.translations/en.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "config": { | ||
| "abort": { | ||
| "already_configured": "Integration is already configured with an existing sensor with that tariff" | ||
| }, | ||
| "step": { | ||
| "user": { | ||
| "data": { | ||
| "name": "Sensor Name", | ||
| "tariff": "Contracted tariff (1, 2, or 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/).\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" | ||
| } | ||
| }, | ||
| "title": "Hourly price of electricity in Spain (PVPC)" | ||
| } | ||
| } |
18 changes: 18 additions & 0 deletions
18
homeassistant/components/pvpc_hourly_pricing/.translations/es.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "config": { | ||
| "abort": { | ||
| "already_configured": "La integraci\u00f3n ya est\u00e1 configurada con un sensor existente con esa tarifa" | ||
| }, | ||
| "step": { | ||
| "user": { | ||
| "data": { | ||
| "name": "Nombre del sensor", | ||
| "tariff": "Tarifa contratada (1, 2, o 3 periodos)" | ||
| }, | ||
| "description": "Este sensor utiliza la API oficial de REE para obtener el [precio horario de la electricidad (PVPC)](https://www.esios.ree.es/es/pvpc) en Espa\\u00f1a.\nPara instrucciones detalladas consulte la [documentación de la integración](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/).\n\nSeleccione la tarifa contratada en base al número de periodos de facturación al día:\n- 1 periodo: normal\n- 2 periodos: discriminación (tarifa nocturna)\n- 3 periodos: coche eléctrico (tarifa nocturna de 3 periodos)", | ||
| "title": "Selecci\u00f3n de tarifa" | ||
| } | ||
| }, | ||
| "title": "Precio horario de la electricidad en Espa\u00f1a (PVPC)" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| """The pvpc_hourly_pricing integration to collect Spain official electric prices.""" | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant import config_entries | ||
| from homeassistant.const import CONF_NAME | ||
| from homeassistant.core import HomeAssistant | ||
| import homeassistant.helpers.config_validation as cv | ||
|
|
||
| from .const import ATTR_TARIFF, DEFAULT_NAME, DEFAULT_TARIFF, DOMAIN, PLATFORM, TARIFFS | ||
|
|
||
| UI_CONFIG_SCHEMA = vol.Schema( | ||
| { | ||
| vol.Required(CONF_NAME, default=DEFAULT_NAME): str, | ||
| vol.Required(ATTR_TARIFF, default=DEFAULT_TARIFF): vol.In(TARIFFS), | ||
| } | ||
| ) | ||
| CONFIG_SCHEMA = vol.Schema( | ||
| {DOMAIN: cv.ensure_list(UI_CONFIG_SCHEMA)}, extra=vol.ALLOW_EXTRA | ||
| ) | ||
|
|
||
|
|
||
| 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 | ||
| ``` | ||
| """ | ||
| for conf in config.get(DOMAIN, []): | ||
| hass.async_create_task( | ||
| hass.config_entries.flow.async_init( | ||
| DOMAIN, data=conf, context={"source": config_entries.SOURCE_IMPORT} | ||
| ) | ||
| ) | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry): | ||
| """Set up pvpc hourly pricing from a config entry.""" | ||
| hass.async_create_task( | ||
| hass.config_entries.async_forward_entry_setup(entry, PLATFORM) | ||
| ) | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| async def async_unload_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry): | ||
| """Unload a config entry.""" | ||
| return await hass.config_entries.async_forward_entry_unload(entry, PLATFORM) |
27 changes: 27 additions & 0 deletions
27
homeassistant/components/pvpc_hourly_pricing/config_flow.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| """Config flow for pvpc_hourly_pricing.""" | ||
| from homeassistant import config_entries | ||
|
|
||
| from . import CONF_NAME, UI_CONFIG_SCHEMA | ||
| from .const import ATTR_TARIFF, DOMAIN | ||
|
|
||
| _DOMAIN_NAME = DOMAIN | ||
|
|
||
|
|
||
| class TariffSelectorConfigFlow(config_entries.ConfigFlow, domain=_DOMAIN_NAME): | ||
| """Handle a config flow for `pvpc_hourly_pricing` to select the tariff.""" | ||
|
|
||
| VERSION = 1 | ||
| CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL | ||
|
|
||
| async def async_step_user(self, user_input=None): | ||
| """Handle the initial step.""" | ||
| if user_input is not None: | ||
| await self.async_set_unique_id(user_input[ATTR_TARIFF]) | ||
| self._abort_if_unique_id_configured() | ||
| return self.async_create_entry(title=user_input[CONF_NAME], data=user_input) | ||
|
|
||
| return self.async_show_form(step_id="user", data_schema=UI_CONFIG_SCHEMA) | ||
|
|
||
| async def async_step_import(self, import_info): | ||
| """Handle import from config file.""" | ||
| return await self.async_step_user(import_info) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| """Constant values for pvpc_hourly_pricing.""" | ||
| from aiopvpc import TARIFFS | ||
|
|
||
| DOMAIN = "pvpc_hourly_pricing" | ||
| PLATFORM = "sensor" | ||
| ATTR_TARIFF = "tariff" | ||
| DEFAULT_NAME = "PVPC" | ||
| DEFAULT_TARIFF = TARIFFS[1] |
10 changes: 10 additions & 0 deletions
10
homeassistant/components/pvpc_hourly_pricing/manifest.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "domain": "pvpc_hourly_pricing", | ||
| "name": "Spain electricity hourly pricing (PVPC)", | ||
| "config_flow": true, | ||
| "documentation": "https://www.home-assistant.io/integrations/pvpc_hourly_pricing", | ||
| "requirements": ["aiopvpc==1.0.2"], | ||
| "dependencies": [], | ||
| "codeowners": ["@azogue"], | ||
| "quality_scale": "platinum" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| """ | ||
| Sensor to collect the reference daily prices of electricity ('PVPC') in Spain. | ||
|
|
||
| For more details about this platform, please refer to the documentation at | ||
| https://www.home-assistant.io/integrations/pvpc_hourly_pricing/ | ||
|
azogue marked this conversation as resolved.
|
||
| """ | ||
| from datetime import timedelta | ||
| import logging | ||
| from random import randint | ||
| from typing import Optional | ||
|
|
||
| from aiopvpc import PVPCData | ||
|
|
||
| from homeassistant import config_entries | ||
| from homeassistant.const import CONF_NAME | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
| from homeassistant.helpers.event import ( | ||
| async_track_point_in_time, | ||
| async_track_time_change, | ||
| ) | ||
| from homeassistant.helpers.restore_state import RestoreEntity | ||
| import homeassistant.util.dt as dt_util | ||
|
|
||
| from .const import ATTR_TARIFF | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| ATTR_PRICE = "price" | ||
| ICON = "mdi:currency-eur" | ||
| UNIT = "€/kWh" | ||
|
|
||
| _DEFAULT_TIMEOUT = 10 | ||
|
|
||
|
|
||
| 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)], True | ||
| ) | ||
|
|
||
|
|
||
| class ElecPriceSensor(RestoreEntity): | ||
| """Class to hold the prices of electricity as a sensor.""" | ||
|
|
||
| unit_of_measurement = UNIT | ||
| icon = ICON | ||
| should_poll = False | ||
|
|
||
| def __init__(self, name, unique_id, pvpc_data_handler): | ||
| """Initialize the sensor object.""" | ||
| self._name = name | ||
| self._unique_id = unique_id | ||
| self._pvpc_data = pvpc_data_handler | ||
| self._num_retries = 0 | ||
|
|
||
| self._init_done = False | ||
| 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): | ||
| """Handle entity which will be added.""" | ||
| await super().async_added_to_hass() | ||
| state = await self.async_get_last_state() | ||
| if state: | ||
| self._pvpc_data.state = state.state | ||
|
|
||
| # Update 'state' value in hour changes | ||
| self._hourly_tracker = async_track_time_change( | ||
|
bdraco marked this conversation as resolved.
|
||
| self.hass, self.async_update, 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, | ||
| ) | ||
| _LOGGER.debug( | ||
| "Setup of price sensor %s (%s) with tariff '%s', " | ||
| "updating prices each hour at %s min", | ||
| self.name, | ||
| self.entity_id, | ||
| self._pvpc_data.tariff, | ||
| mins_update, | ||
| ) | ||
| await self.async_update_prices(dt_util.utcnow()) | ||
|
bdraco marked this conversation as resolved.
|
||
| self._init_done = True | ||
| await self.async_update_ha_state(True) | ||
|
bdraco marked this conversation as resolved.
Outdated
|
||
|
|
||
| @property | ||
| def unique_id(self) -> Optional[str]: | ||
| """Return a unique ID.""" | ||
| return self._unique_id | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the name of the sensor.""" | ||
| return self._name | ||
|
|
||
| @property | ||
| def state(self): | ||
| """Return the state of the sensor.""" | ||
| return self._pvpc_data.state | ||
|
|
||
| @property | ||
| def available(self) -> bool: | ||
| """Return True if entity is available.""" | ||
| return self._pvpc_data.state_available | ||
|
|
||
| @property | ||
| def device_state_attributes(self): | ||
| """Return the state attributes.""" | ||
| return self._pvpc_data.attributes | ||
|
|
||
| async def async_update(self, *args): | ||
| """Update the sensor state.""" | ||
| if not self._init_done: | ||
| # abort until added_to_hass is finished | ||
| return | ||
|
|
||
| now = dt_util.utcnow() if not args else args[0] | ||
| self._pvpc_data.process_state_and_attributes(now) | ||
| self.async_write_ha_state() | ||
|
bdraco marked this conversation as resolved.
|
||
|
|
||
| async def async_update_prices(self, now): | ||
| """Update electricity prices from the ESIOS API.""" | ||
| prices = await self._pvpc_data.async_update_prices(now) | ||
| if not prices and self._pvpc_data.source_available: | ||
| self._num_retries += 1 | ||
| if self._num_retries > 2: | ||
| _LOGGER.warning( | ||
| "Repeated bad data update, mark component as unavailable source" | ||
| ) | ||
| self._pvpc_data.source_available = False | ||
| return | ||
|
|
||
| retry_delay = 2 * self._pvpc_data.timeout | ||
| _LOGGER.debug( | ||
| "Bad update[retry:%d], will try again in %d s", | ||
| self._num_retries, | ||
| retry_delay, | ||
| ) | ||
| async_track_point_in_time( | ||
|
azogue marked this conversation as resolved.
|
||
| self.hass, | ||
| self.async_update_prices, | ||
| dt_util.now() + timedelta(seconds=retry_delay), | ||
| ) | ||
| return | ||
|
|
||
| if not prices: | ||
| _LOGGER.debug("Data source is not yet available") | ||
| return | ||
|
|
||
| self._num_retries = 0 | ||
| if not self._pvpc_data.source_available: | ||
| self._pvpc_data.source_available = True | ||
| _LOGGER.warning("Component has recovered data access") | ||
| self.async_schedule_update_ha_state(True) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "config": { | ||
| "title": "Hourly price of electricity in Spain (PVPC)", | ||
| "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)", | ||
| "data": { | ||
| "name": "Sensor Name", | ||
| "tariff": "Contracted tariff (1, 2, or 3 periods)" | ||
| } | ||
| } | ||
| }, | ||
| "abort": { | ||
| "already_configured": "Integration is already configured with an existing sensor with that tariff" | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,6 +80,7 @@ | |
| "plex", | ||
| "point", | ||
| "ps4", | ||
| "pvpc_hourly_pricing", | ||
| "rachio", | ||
| "rainmachine", | ||
| "ring", | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Tests for the pvpc_hourly_pricing integration.""" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.