diff --git a/README.md b/README.md index 42d282e..c5468e5 100644 --- a/README.md +++ b/README.md @@ -25,23 +25,11 @@ This integration can be added through HACS, this is the easiest and recommended Note: A restart will be required for the integration to be registered. ## Setup -You can optionally provide an API key. The API key is only required for the "current_price" sensor. If you opt not to provide an API key everything else should work as expected, however, you'll be missing that sensor. - -If you wish to obtain an API key one must be obtained from the BMRS API following the below instructions. Alternatively simply select the no API key option and skip to the later steps. - -1. Register [here](https://www.elexonportal.co.uk/) -2. 'Register now' in the top right and follow the standard process -3. Once the account is created login and you'll see 'My Portal' banner open. -4. Inside the 'My Portal' banner click 'My Profile' - your API key is the 'Scripting Key' - -Note: Some users have seen that this key can take a significant period of time to become active. If the API key isn't accepted by the integration I'd recommend installing the integration without a key and then a day later, removing the integration and re-adding it with the key. The reason we recommend installing the integration without a key in the meantime is so that you can build up the data history for the sensors. - - -Then follow the same steps that are using to setup most integrations: +Follow the same steps that are using to setup most integrations: 1. Head to settings then Devices & Services 2. Click 'Add Integration' in the bottom right 3. Search for 'National Grid' -4. Enter API Key and hit Submit +4. Click 'National Grid' 5. Your device and entities will be created ## Sensors @@ -324,7 +312,7 @@ This section outlines some graphs / views I have personally created with the dat ### BMRS - Balancing Mechanism Reporting Service An Elexon developed API responsible for reporting power generation, interconnectors, pricing and wind forecasting.\ -Data is provided largely in an XML format but their newer API offers data in JSON too, this integration is moving slowly to the new API, however, the new API is in beta so some endpoint are not yet suitable. +Data is provided from their newer API which provides data in a JSON format (but optionally others too). ### Carbon Itensity API A National Grid ESO API developed in partner ship with the University of Oxford is responsible for reporting the carbon intensity of power generation in the UK in grams of carbon dioxide per kilowatt hour.\ diff --git a/custom_components/national_grid/__init__.py b/custom_components/national_grid/__init__.py index 685dd09..3e36e57 100644 --- a/custom_components/national_grid/__init__.py +++ b/custom_components/national_grid/__init__.py @@ -6,7 +6,7 @@ from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from .const import API_KEY_PROVIDED, DATA_CLIENT, DOMAIN +from .const import DATA_CLIENT, DOMAIN from .coordinators.national_grid import NationalGridCoordinator PLATFORMS = [Platform.SENSOR] @@ -35,7 +35,6 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry): if config_entry.version == 1: new = {**config_entry.data} - new[API_KEY_PROVIDED] = True config_entry.version = 2 hass.config_entries.async_update_entry(config_entry, data=new) diff --git a/custom_components/national_grid/config_flow.py b/custom_components/national_grid/config_flow.py index 9b2fe6a..f29f7f4 100644 --- a/custom_components/national_grid/config_flow.py +++ b/custom_components/national_grid/config_flow.py @@ -5,11 +5,7 @@ from homeassistant import config_entries -from .const import ( - API_KEY, - DOMAIN, - API_KEY_PROVIDED, -) +from .const import DOMAIN from .coordinators.national_grid import get_data from .errors import InvalidAuthError @@ -17,57 +13,22 @@ class NationalGridConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - VERSION = 2 - - def __init__(self) -> None: - self._api_option = False + VERSION = 3 # Initial step provides the API options async def async_step_user(self, user_input=None): - data_schema = vol.Schema({vol.Optional(API_KEY_PROVIDED, default=False): bool}) - - return self.async_show_form(step_id="include_api", data_schema=data_schema) - - # Following selection on whether to include API key this is hit - async def async_step_include_api(self, user_input=None): - if API_KEY_PROVIDED in user_input: - self._api_option = user_input[API_KEY_PROVIDED] - - # If API Key required option was not selected hit this. Skip through to the end. - # If this step fails it'll return to this. - if not self._api_option: - data = {API_KEY: "", API_KEY_PROVIDED: self._api_option} - data_schema = vol.Schema( - {vol.Optional(API_KEY_PROVIDED, default=False): bool} - ) - return await self.validate_and_create("include_api", data, data_schema) - - # Otherwise, API Key is required. Pass onto next step requiring API key - data_schema = vol.Schema({vol.Required(API_KEY): str}) - return self.async_show_form(step_id="add_api_key", data_schema=data_schema) - - async def async_step_add_api_key(self, user_input=None): - data = {API_KEY: user_input[API_KEY], API_KEY_PROVIDED: self._api_option} - data_schema = vol.Schema({vol.Required(API_KEY): str}) - return await self.validate_and_create("add_api_key", data, data_schema) + return await self.validate_and_create("user") - async def validate_and_create( - self, step_id: str, data: dict[str, Any], data_schema: vol.Schema - ): + async def validate_and_create(self, step_id: str): errors: dict[str, str] = {} try: - await self.hass.async_add_executor_job(get_data, self.hass, data, None) - except InvalidAuthError as e: - _LOGGER.error("Auth error ocurred") - errors["base"] = "Invalid API Key" + await self.hass.async_add_executor_job(get_data, self.hass, None, None) except Exception as e: # pylint: disable=broad-except _LOGGER.error(e) - errors["base"] = "error" + errors["base"] = "An error occurred" else: - return self.async_create_entry(title="National Grid", data=data) + return self.async_create_entry(title="National Grid", data={}) - return self.async_show_form( - step_id=step_id, data_schema=data_schema, errors=errors - ) + return self.async_show_form(step_id=step_id, data_schema={}, errors=errors) diff --git a/custom_components/national_grid/const.py b/custom_components/national_grid/const.py index 1c40e6a..85d6d79 100644 --- a/custom_components/national_grid/const.py +++ b/custom_components/national_grid/const.py @@ -1,8 +1,4 @@ # The domain of your component. Should be equal to the name of your component. -API_KEY = "api_key" - DOMAIN = "national_grid" DATA_CLIENT = "DATA_CLIENT" - -API_KEY_PROVIDED = "api_key_provided" diff --git a/custom_components/national_grid/coordinators/national_grid.py b/custom_components/national_grid/coordinators/national_grid.py index f13eda2..c86506f 100644 --- a/custom_components/national_grid/coordinators/national_grid.py +++ b/custom_components/national_grid/coordinators/national_grid.py @@ -16,7 +16,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.util import dt as dt_util -from ..const import API_KEY, API_KEY_PROVIDED, DOMAIN +from ..const import DOMAIN from ..errors import InvalidAuthError, UnexpectedDataError, UnexpectedStatusCode from ..models import ( DFSRequirementItem, @@ -68,7 +68,6 @@ def get_data( hass: HomeAssistant, config: Mapping[str, Any], current_data: NationalGridData ) -> NationalGridData: """Get data.""" - api_key = config[API_KEY] yesterday_utc = (dt_util.utcnow() - timedelta(days=1)).strftime("%Y-%m-%d") today_utc = dt_util.utcnow().strftime("%Y-%m-%d") @@ -80,25 +79,23 @@ def get_data( current_price = 0 solar_forecast = None - if config[API_KEY_PROVIDED]: - current_price = obtain_data_with_fallback( - current_data, - "sell_price", - get_current_price, - api_key, - today_utc, - yesterday_utc, - ) - solar_forecast = obtain_data_with_fallback( - current_data, - "solar_forecast", - get_half_hourly_solar_forecast, - api_key, - now_utc_full, - ) + + current_price = obtain_data_with_fallback( + current_data, + "sell_price", + get_current_price, + today_utc, + yesterday_utc, + ) + solar_forecast = obtain_data_with_fallback( + current_data, + "solar_forecast", + get_half_hourly_solar_forecast, + now_utc_full, + ) current_grid_frequency = obtain_data_with_fallback( - current_data, "grid_frequency", get_current_frequency, api_key, now_utc_full + current_data, "grid_frequency", get_current_frequency, now_utc_full ) wind_forecast = obtain_data_with_fallback( @@ -123,7 +120,6 @@ def get_data( current_data, "grid_generation", get_generation_combined, - api_key, now_utc_full, today_utc, ) @@ -357,9 +353,7 @@ def get_hourly_wind_forecast_earliest(now_utc: datetime) -> NationalGridWindFore ) -def get_half_hourly_solar_forecast( - api_key: str, now: datetime -) -> NationalGridSolarForecast: +def get_half_hourly_solar_forecast(now: datetime) -> NationalGridSolarForecast: """Get half hourly solar forecast.""" nearest_30_minutes = now + (now.min.replace(tzinfo=now.tzinfo) - now) % timedelta( minutes=30 @@ -422,7 +416,7 @@ def get_half_hourly_solar_forecast( ) -def get_current_price(api_key: str, today_utc: str, yesterday_utc: str) -> float: +def get_current_price(today_utc: str, yesterday_utc: str) -> float: """Get current grid price.""" url = ( "https://data.elexon.co.uk/bmrs/api/v1/balancing/pricing/market-index?from=" @@ -441,7 +435,7 @@ def get_current_price(api_key: str, today_utc: str, yesterday_utc: str) -> float return round(float(items[0]["price"]), 2) -def get_current_frequency(api_key: str, now_utc: datetime) -> float: +def get_current_frequency(now_utc: datetime) -> float: """Get current grid frequency.""" url = ( "https://data.elexon.co.uk/bmrs/api/v1/system/frequency?format=json&from=" @@ -1062,7 +1056,7 @@ def get_generation(utc_now: datetime) -> NationalGridGeneration: return national_grid_generation -def get_generation_combined(api_key: str, now_utc_full: datetime, today_utc: str): +def get_generation_combined(now_utc_full: datetime, today_utc: str): grid_generation = get_generation( now_utc_full, ) diff --git a/custom_components/national_grid/sensor.py b/custom_components/national_grid/sensor.py index 6d0b325..f729496 100644 --- a/custom_components/national_grid/sensor.py +++ b/custom_components/national_grid/sensor.py @@ -17,7 +17,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import NationalGridCoordinator -from .const import API_KEY_PROVIDED, DATA_CLIENT, DOMAIN +from .const import DATA_CLIENT, DOMAIN SCAN_INTERVAL = timedelta(minutes=5) _LOGGER = logging.getLogger(__name__) @@ -427,10 +427,7 @@ async def async_setup_entry( """Setup the National Grid sensor""" coordinator: NationalGridCoordinator = hass.data[DOMAIN][DATA_CLIENT] - sensors = SENSORS - - if entry.data[API_KEY_PROVIDED]: - sensors = sensors + API_SENSORS + sensors = SENSORS + API_SENSORS async_add_entities( NationalGridSensor(coordinator, description) for description in sensors