Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"image": "ludeeus/container:integration-debian",
//"image": "ludeeus/container:integration-debian",
"image": "ghcr.io/ludeeus/devcontainer/integration:stable",
"name": "Nordpool integration development",
"context": "..",
"appPort": [
Expand All @@ -16,7 +17,7 @@
"settings": {
"files.eol": "\n",
"editor.tabSize": 4,
"terminal.integrated.shell.linux": "/bin/bash",
//"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/bin/python3",
"python.analysis.autoSearchPaths": false,
"python.linting.pylintEnabled": true,
Expand Down
103 changes: 78 additions & 25 deletions custom_components/nordpool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from homeassistant.core import Config, HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import (async_call_later,
async_track_time_change)
from homeassistant.helpers.event import async_call_later, async_track_time_change
from homeassistant.util import dt as dt_utils
from pytz import timezone

from .aio_price import AioPrices
from .events import async_track_time_change_in_tz
from .misc import test_valid_nordpooldata, test_valid_nordpolldata2, stock

DOMAIN = "nordpool"
_LOGGER = logging.getLogger(__name__)
Expand All @@ -24,6 +24,34 @@
EVENT_NEW_DATA = "nordpool_update"
_CURRENCY_LIST = ["DKK", "EUR", "NOK", "SEK"]

_REGIONS = {
"DK1": ["DKK", "Denmark", 0.25],
"DK2": ["DKK", "Denmark", 0.25],
"FI": ["EUR", "Finland", 0.24],
"EE": ["EUR", "Estonia", 0.20],
"LT": ["EUR", "Lithuania", 0.21],
"LV": ["EUR", "Latvia", 0.21],
"Oslo": ["NOK", "Norway", 0.25],
"Kr.sand": ["NOK", "Norway", 0.25],
"Bergen": ["NOK", "Norway", 0.25],
"Molde": ["NOK", "Norway", 0.25],
"Tr.heim": ["NOK", "Norway", 0.25],
"Tromsø": ["NOK", "Norway", 0.25],
"SE1": ["SEK", "Sweden", 0.25],
"SE2": ["SEK", "Sweden", 0.25],
"SE3": ["SEK", "Sweden", 0.25],
"SE4": ["SEK", "Sweden", 0.25],
# What zone is this?
"SYS": ["EUR", "System zone", 0.25],
"FR": ["EUR", "France", 0.055],
"NL": ["EUR", "Netherlands", 0.21],
"BE": ["EUR", "Belgium", 0.21],
"AT": ["EUR", "Austria", 0.20],
# Tax is disabled for now, i need to split the areas
# to handle the tax.
"DE-LU": ["EUR", "Germany and Luxembourg", 0],
}


CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)

Expand Down Expand Up @@ -52,7 +80,7 @@ def __init__(self, hass: HomeAssistant):
self.currency = []
self.listeners = []

async def _update(self, type_="today", dt=None):
async def _update(self, *args, type_="today", dt=None):
_LOGGER.debug("calling _update %s %s", type_, dt)
hass = self._hass
client = async_get_clientsession(hass)
Expand All @@ -64,23 +92,54 @@ async def _update(self, type_="today", dt=None):
# when the region is in another timezone
# as we request data for 3 days anyway.
# Keeping this for now, but this should be changed.
attemps = []
for currency in self.currency:
spot = AioPrices(currency, client)
data = await spot.hourly(end_date=dt)
if data:
# We only verify the the areas that has the correct currency, example AT is always inf for all other currency then EUR
# Now this will fail for any users that has a non local currency for the region they selected.
# Thats a problem for another day..
regions_to_verify = [k for k, v in _REGIONS.items() if v[0] == currency]
data_ok = test_valid_nordpooldata(data, region=regions_to_verify)
attemps.append(data_ok)
if data_ok is False:
np_should_have_released_new_data = stock(dt).replace(
hour=13, minute=RANDOM_MINUTE, second=RANDOM_SECOND
)

if type_ == "tomorrow":
if stock(dt) >= np_should_have_released_new_data:
_LOGGER.info(
"The time is %s, but nordpool havnt released any new data retrying in 5 minutes",
dt,
)
p = partial(self._update, type_, dt)
async_call_later(hass, 60 * 5, p)
else:
_LOGGER.debug("No new data is availble yet")

else:
_LOGGER.debug("Retrying request for %s", type_)
p = partial(self._update, type_, dt)
async_call_later(hass, 60 * 5, p)
return False

if data_ok:
self._data[currency][type_] = data["areas"]
else:
_LOGGER.info("Some crap happend, retrying request later.")
async_call_later(hass, 20, partial(self._update, type_=type_, dt=dt))

async def update_today(self, n: datetime):
_LOGGER.debug("Updating tomorrows prices.")
await self._update("today")
_LOGGER.debug("ATTEMPTS %s", attemps)
return all(attemps)

async def update_tomorrow(self, n: datetime):
async def update_today(self, n: datetime, currency=None, area=None):
_LOGGER.debug("Updating todays prices.")
return await self._update("today")

async def update_tomorrow(self, n: datetime, currency=None, area=None):
_LOGGER.debug("Updating tomorrows prices.")
await self._update(type_="tomorrow", dt=dt_utils.now() + timedelta(hours=24))
self._tomorrow_valid = True
result = await self._update(
type_="tomorrow", dt=dt_utils.now() + timedelta(hours=24)
)
return result

async def _someday(self, area: str, currency: str, day: str):
"""Returns todays or tomorrows prices in a area in the currency"""
Expand All @@ -99,9 +158,6 @@ async def _someday(self, area: str, currency: str, day: str):

return self._data.get(currency, {}).get(day, {}).get(area)

def tomorrow_valid(self) -> bool:
return self._tomorrow_valid

async def today(self, area: str, currency: str) -> dict:
"""Returns todays prices in a area in the requested currency"""
res = await self._someday(area, currency, "today")
Expand All @@ -123,13 +179,9 @@ async def _dry_setup(hass: HomeAssistant, config: Config) -> bool:
async def new_day_cb(n):
"""Cb to handle some house keeping when it a new day."""
_LOGGER.debug("Called new_day_cb callback")
api._tomorrow_valid = False

for curr in api.currency:
if not len(api._data[curr]["tomorrow"]):
api._data[curr]["today"] = await api.update_today(None)
else:
api._data[curr]["today"] = api._data[curr]["tomorrow"]
await api.update_today(None)
api._data[curr]["tomorrow"] = {}

async_dispatcher_send(hass, EVENT_NEW_DATA)
Expand Down Expand Up @@ -182,7 +234,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.config_entries.async_forward_entry_setup(entry, "sensor")
)

# entry.add_update_listener(async_reload_entry)
entry.add_update_listener(async_reload_entry)
return res


Expand All @@ -191,9 +243,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
unload_ok = await hass.config_entries.async_forward_entry_unload(entry, "sensor")

if unload_ok:
for unsub in hass.data[DOMAIN].listeners:
unsub()
hass.data.pop(DOMAIN)
if DOMAIN in hass.data:
for unsub in hass.data[DOMAIN].listeners:
unsub()
hass.data.pop(DOMAIN)

return True

Expand Down
41 changes: 32 additions & 9 deletions custom_components/nordpool/aio_price.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
from dateutil import tz
from dateutil.parser import parse as parse_dt
from nordpool.elspot import Prices
import backoff
import aiohttp

from .misc import add_junk
from .misc import add_junk, test_valid_nordpooldata

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -146,8 +148,8 @@ def __init__(self, currency, client, tz=None):
self.tz = tz
self.API_URL_CURRENCY = "https://www.nordpoolgroup.com/api/marketdata/page/%s"

@backoff.on_exception(backoff.expo, aiohttp.ClientError, logger=_LOGGER)
async def _io(self, url, **kwargs):

resp = await self.client.get(url, params=kwargs)
_LOGGER.debug("requested %s %s", resp.url, kwargs)

Expand All @@ -168,7 +170,8 @@ async def _fetch_json(self, data_type, end_date=None, areas=None):
endDate=end_date.strftime("%d-%m-%Y"),
)

async def fetch(self, data_type, end_date=None, areas=[]):
# https://github.com/custom-components/integration_blueprint/issues/71
async def fetch(self, data_type, end_date=None, areas=None):
"""
Fetch data from API.
Inputs:
Expand All @@ -189,6 +192,8 @@ async def fetch(self, data_type, end_date=None, areas=[]):
- list of values (dictionary with start and endtime and value)
- possible other values, such as min, max, average for hourly
"""
if areas is None:
areas = []

# Check how to handle all time zone in this,
# dunno how to do this yet.
Expand Down Expand Up @@ -248,26 +253,44 @@ async def fetch(self, data_type, end_date=None, areas=[]):
res = await asyncio.gather(*jobs)

raw = [self._parse_json(i, areas) for i in res]
return join_result_for_correct_time(raw, end_date)
result = join_result_for_correct_time(raw, end_date)
# test_result = test_valid_nordpooldata(result)
# _LOGGER.debug("DATA STATUS %s", test_result)
return result

async def hourly(self, end_date=None, areas=[]):
async def hourly(self, end_date=None, areas=None):
""" Helper to fetch hourly data, see Prices.fetch() """
if areas is None:
areas = []

return await self.fetch(self.HOURLY, end_date, areas)

async def daily(self, end_date=None, areas=[]):
async def daily(self, end_date=None, areas=None):
""" Helper to fetch daily data, see Prices.fetch() """
if areas is None:
areas = []

return await self.fetch(self.DAILY, end_date, areas)

async def weekly(self, end_date=None, areas=[]):
async def weekly(self, end_date=None, areas=None):
""" Helper to fetch weekly data, see Prices.fetch() """
if areas is None:
areas = []

return await self.fetch(self.WEEKLY, end_date, areas)

async def monthly(self, end_date=None, areas=[]):
async def monthly(self, end_date=None, areas=None):
""" Helper to fetch monthly data, see Prices.fetch() """
if areas is None:
areas = []

return await self.fetch(self.MONTHLY, end_date, areas)

async def yearly(self, end_date=None, areas=[]):
async def yearly(self, end_date=None, areas=None):
""" Helper to fetch yearly data, see Prices.fetch() """
if areas is None:
areas = []

return await self.fetch(self.YEARLY, end_date, areas)

def _conv_to_float(self, s):
Expand Down
2 changes: 1 addition & 1 deletion custom_components/nordpool/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


def stock(d):
"""convert datetime to stocholm time."""
"""convert datetime to stockholm time."""
return d.astimezone(timezone("Europe/Stockholm"))


Expand Down
16 changes: 11 additions & 5 deletions custom_components/nordpool/manifest.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@

{
"domain": "nordpool",
"name": "nordpool",
"documentation": "https://github.com/custom-components/nordpool/",
"issue_tracker": "https://github.com/custom-components/nordpool/issues",
"dependencies": [],
"after_dependencies": ["http"],
"after_dependencies": [
"http"
],
"config_flow": true,
"codeowners": ["@hellowlol"],
"codeowners": [
"@hellowlol"
],
"iot_class": "cloud_polling",
"requirements": ["nordpool>=0.2"],
"requirements": [
"nordpool>=0.2",
"backoff>=1.11.1"
],
"version": "0.0.4"
}
}
55 changes: 42 additions & 13 deletions custom_components/nordpool/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"end_of",
"stock",
"add_junk",
"test_valid_nordpooldata",
]

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -80,6 +81,47 @@ def is_inf(d):
return False


def test_valid_nordpolldata2(data_, region=None):
# not in use atm
for key, value in data.get("areas", {}).items():
if region is None or key in region:
# if region is not None and area in region:
if any([i["value"] == float("inf") for i in value.get("values", {})]):
_LOGGER.debug("Found infinty invalid data in %s", key)

return False

return True


def test_valid_nordpooldata(data_, region=None):
# from pprint import pformat

_LOGGER.debug("Checking for inf value in data for %s", region)

# _LOGGER.debug("DATA %s", pformat(data_))
if isinstance(data_, dict):
data_ = [data_]

for data in data_:
for currency, v in data.items():
for area, real_data in v.items():
# _LOGGER.debug("area %s", area)
if region is None or area in region:
# if region is not None and area in region:
if any(
[
i["value"] == float("inf")
for i in real_data.get("values", {})
]
):
_LOGGER.debug("Found infinty invalid data in area %s", area)

return False

return True


def has_junk(data) -> bool:
"""Check if data has some infinity values.

Expand Down Expand Up @@ -116,16 +158,3 @@ def extract_attrs(data) -> dict:
return d

return data


'''
def as_tz(dattim, tz=None):
"""Convert a UTC datetime object to local time zone."""

if dattim.tzinfo is None:
dattim = UTC.localize(dattim)

return dattim.astimezone(timezone(tz))


'''
Loading