Skip to content

Commit

Permalink
Remove API + config
Browse files Browse the repository at this point in the history
  • Loading branch information
JRascagneres committed Jun 2, 2024
1 parent 8358647 commit 3a18843
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 99 deletions.
18 changes: 3 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.\
Expand Down
3 changes: 1 addition & 2 deletions custom_components/national_grid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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)

Expand Down
55 changes: 8 additions & 47 deletions custom_components/national_grid/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,30 @@

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

_LOGGER = logging.getLogger(__name__)


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)
4 changes: 0 additions & 4 deletions custom_components/national_grid/const.py
Original file line number Diff line number Diff line change
@@ -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"
46 changes: 20 additions & 26 deletions custom_components/national_grid/coordinators/national_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand All @@ -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(
Expand All @@ -123,7 +120,6 @@ def get_data(
current_data,
"grid_generation",
get_generation_combined,
api_key,
now_utc_full,
today_utc,
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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="
Expand All @@ -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="
Expand Down Expand Up @@ -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,
)
Expand Down
7 changes: 2 additions & 5 deletions custom_components/national_grid/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 3a18843

Please sign in to comment.