Skip to content

Commit

Permalink
feat: Added support for national carbon intensity rates
Browse files Browse the repository at this point in the history
  • Loading branch information
BottlecapDave committed Apr 28, 2024
1 parent 4f749a3 commit f3877e1
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 1 deletion.
8 changes: 8 additions & 0 deletions custom_components/carbon_intensity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ async def async_setup_entry(hass, entry):
await async_setup_dependencies(hass, entry.data)

await hass.config_entries.async_forward_entry_setups(entry, ACCOUNT_PLATFORMS)

# If the main account has been reloaded, then reload all other entries to make sure they're referencing
# the correct references (e.g. rate coordinators)
child_entries = hass.config_entries.async_entries(DOMAIN)
for child_entry in child_entries:
if CONFIG_TARGET_NAME in child_entry.data:
await hass.config_entries.async_reload(child_entry.entry_id)

elif CONFIG_TARGET_NAME in entry.data:
await hass.config_entries.async_forward_entry_setups(entry, TARGET_RATE_PLATFORMS)

Expand Down
43 changes: 43 additions & 0 deletions custom_components/carbon_intensity/api_client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import timedelta
import logging
import json
import aiohttp
Expand All @@ -10,6 +11,48 @@ class CarbonIntensityApiClient:
def __init__(self):
self._base_url = 'https://api.carbonintensity.org.uk'

async def async_get_national_intensity_and_generation_rates(self, period_from):
"""Get the intensity and generation rates"""
results = []
async with aiohttp.ClientSession() as client:
url = f'{self._base_url}/intensity/{period_from.strftime("%Y-%m-%dT%H:%M:%SZ")}/fw48h'
async with client.get(url) as response:
try:
data = await self.__async_read_response(response, url, { "data": [] })
print(data)
if ("data" in data):
for item in data["data"]:
results.append({
"from": as_utc(parse_datetime(item["from"])),
"to": as_utc(parse_datetime(item["to"])),
"intensity_forecast": int(item["intensity"]["forecast"])
})
except:
_LOGGER.error(f'Failed to extract national intensity and generation rates: {url}')
raise

if len(results) > 0:
async with aiohttp.ClientSession() as client:
url = f'{self._base_url}/generation/{period_from.strftime("%Y-%m-%dT%H:%M:%SZ")}/{(period_from + timedelta(hours=48)).strftime("%Y-%m-%dT%H:%M:%SZ")}'
async with client.get(url) as response:
try:
data = await self.__async_read_response(response, url, { "data": [] })
if ("data" in data):
for item in data["data"]:
from_date = as_utc(parse_datetime(item["from"]))
to_date = as_utc(parse_datetime(item["to"]))

for rate in results:
if rate["from"] == from_date and rate["to"] == to_date:
rate["generation_mix"] = item["generationmix"]
break

except:
_LOGGER.error(f'Failed to extract national intensity and generation rates: {url}')
raise

return results

async def async_get_intensity_and_generation_rates(self, period_from, region):
"""Get the intensity and generation rates"""
results = []
Expand Down
2 changes: 1 addition & 1 deletion custom_components/carbon_intensity/coordinators/rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ async def async_refresh_rates_data(
current >= existing_rates_result.next_refresh or
existing_rates_result.rates[-1]["from"] < period_from):
try:
new_rates = await client.async_get_intensity_and_generation_rates(period_from, region)
new_rates = await client.async_get_national_intensity_and_generation_rates(period_from) if region == "0" else await client.async_get_intensity_and_generation_rates(period_from, region)
except:
_LOGGER.debug(f'Failed to retrieve rates for {region}')
if (existing_rates_result is not None):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def __init__(self, hass: HomeAssistant, coordinator, region: str):

self._state = None
self._region = region
self._attributes = {}

self.entity_id = generate_entity_id("sensor.{}", self.unique_id, hass=hass)

Expand Down
1 change: 1 addition & 0 deletions custom_components/carbon_intensity/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def apply_offset(date_time, offset, inverse = False):

def get_region_options():
return {
"0": "National",
"1": "North Scotland",
"2": "South Scotland",
"3": "North West England",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ async def test_when_get_intensity_and_generation_rate_called_then_calculation_re
assert "intensity_forecast" in item
assert "generation_mix" in item

for mix in item["generation_mix"]:
assert "fuel" in mix
assert "perc" in mix

expected_from = expected_to
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from datetime import datetime, timedelta
import os
import pytest

from custom_components.carbon_intensity.api_client import CarbonIntensityApiClient

@pytest.mark.asyncio
async def test_when_get_national_intensity_and_generation_rate_called_then_calculation_returned():
# Arrange
client = CarbonIntensityApiClient()
period_from = datetime.strptime("2021-12-01T00:00:00Z", "%Y-%m-%dT%H:%M:%S%z")

# Act
data = await client.async_get_national_intensity_and_generation_rates(period_from)

# Assert
assert len(data) == 97

# Make sure our data is returned in 30 minute increments
expected_from = period_from - timedelta(minutes=30)
for item in data:
expected_to = expected_from + timedelta(minutes=30)

assert "from" in item
assert item["from"] == expected_from
assert "to" in item
assert item["to"] == expected_to
assert "intensity_forecast" in item
assert "generation_mix" in item

for mix in item["generation_mix"]:
assert "fuel" in mix
assert "perc" in mix

expected_from = expected_to

0 comments on commit f3877e1

Please sign in to comment.