Skip to content
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

Breaking changes #157

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 7 additions & 1 deletion .devcontainer/configuration.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
default_config:

logger:
default: info
default: warning
logs:
custom_components.nordpool: debug
custom_components.nordpool.config_flow: debug

# If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
# debugpy:


sensor:
- platform: nordpool
region: "Kr.sand"
24 changes: 13 additions & 11 deletions custom_components/nordpool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
from datetime import datetime, timedelta
from functools import partial
from random import randint
from types import MappingProxyType

import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import Config, HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
Expand All @@ -22,13 +24,8 @@
RANDOM_SECOND = randint(0, 59)
EVENT_NEW_DATA = "nordpool_update"
_CURRENCY_LIST = ["DKK", "EUR", "NOK", "SEK"]


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


NAME = DOMAIN
VERSION = "0.0.7"
VERSION = "1.0.0"
ISSUEURL = "https://github.com/custom-components/nordpool/issues"

STARTUP = f"""
Expand Down Expand Up @@ -113,7 +110,7 @@ async def tomorrow(self, area: str, currency: str):


async def _dry_setup(hass: HomeAssistant, config: Config) -> bool:
"""Set up using yaml config file."""
"""Helper"""

if DOMAIN not in hass.data:
api = NordpoolData(hass)
Expand Down Expand Up @@ -170,18 +167,23 @@ async def new_data_cb(n):


async def async_setup(hass: HomeAssistant, config: Config) -> bool:
"""Set up using yaml config file."""
return await _dry_setup(hass, config)
"""Setup using yaml isnt supported."""
return True


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up nordpool as config entry."""
# If any options is passed they should override default config.
d = dict(entry.data)
d.update(dict(entry.options))
# So many broken rules :P
entry.data = MappingProxyType(d)

res = await _dry_setup(hass, entry.data)
hass.async_create_task(
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 Down
22 changes: 11 additions & 11 deletions custom_components/nordpool/aio_price.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ def join_result_for_correct_time(results, dt):
"""
# utc = datetime.utcnow()
fin = defaultdict(dict)
_LOGGER.debug("join_result_for_correct_time %s", dt)
# _LOGGER.debug("join_result_for_correct_time %s", dt)
utc = dt

for day_ in results:
for key, value in day_.get("areas", {}).items():
zone = tzs.get(key)
if zone is None:
_LOGGER.debug("Skipping %s", key)
# _LOGGER.debug("Skipping %s", key)
continue
else:
zone = tz.gettz(zone)
Expand Down Expand Up @@ -134,7 +134,7 @@ def join_result_for_correct_time(results, dt):
if start_of_day <= local and local <= end_of_day:
fin["areas"][key]["values"].append(val)

_LOGGER.debug("Combines result: %s", fin)
# _LOGGER.debug("Combines result: %s", fin)

return fin

Expand All @@ -149,12 +149,12 @@ def __init__(self, currency, client, tz=None):
async def _io(self, url, **kwargs):

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

return await resp.json()

async def _fetch_json(self, data_type, end_date=None, areas=None):
""" Fetch JSON from API """
"""Fetch JSON from API"""
# If end_date isn't set, default to tomorrow
if end_date is None:
end_date = date.today() + timedelta(days=1)
Expand Down Expand Up @@ -251,27 +251,27 @@ async def fetch(self, data_type, end_date=None, areas=[]):
return join_result_for_correct_time(raw, end_date)

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

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

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

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

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

def _conv_to_float(self, s):
""" Convert numbers to float. Return infinity, if conversion fails. """
"""Convert numbers to float. Return infinity, if conversion fails."""
try:
return float(s.replace(",", ".").replace(" ", ""))
except ValueError:
Expand Down
168 changes: 124 additions & 44 deletions custom_components/nordpool/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,90 @@
import logging
import re

from typing import Optional

import voluptuous as vol
from homeassistant import config_entries
from homeassistant.helpers.template import is_template_string, Template
from homeassistant.core import callback
from homeassistant.helpers.template import Template

from . import DOMAIN
from .sensor import _PRICE_IN, _REGIONS, DEFAULT_TEMPLATE

regions = sorted(list(_REGIONS.keys()))
currencys = sorted(list(set(v[0] for k, v in _REGIONS.items())))
price_types = sorted(list(_PRICE_IN.keys()))

placeholders = {
"region": regions,
"currency": currencys,
"price_type": price_types,
"additional_costs": "{{0.0|float}}",
}

_LOGGER = logging.getLogger(__name__)


class NordpoolFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
def get_schema(existing_config: Optional[dict] = None) -> dict:
"""Helper to get schema with editable default"""

ec = existing_config

if ec is None:
ec = {}

data_schema = {
vol.Required("region", default=ec.get("region", None)): vol.In(regions),
vol.Optional("currency", default=ec.get("currency", "")): vol.In(currencys),
vol.Optional("VAT", default=ec.get("VAT", True)): bool,
vol.Optional("precision", default=ec.get("precision", 3)): vol.Coerce(int),
vol.Optional(
"low_price_cutoff", default=ec.get("low_price_cutoff", 1.0)
): vol.Coerce(float),
vol.Optional("price_in_cents", default=ec.get("price_in_cents", False)): bool,
vol.Optional("price_type", default=ec.get("price_type", "kWh")): vol.In(
price_types
),
vol.Optional("additional_costs", default=ec.get("additional_costs", "")): str,
}

return data_schema


class Base:
"""Simple helper"""

async def _valid_template(self, user_template):
try:
_LOGGER.debug(user_template)
ut = Template(user_template, self.hass).async_render()
if isinstance(ut, float):
return True
else:
return False
except Exception as e:
_LOGGER.error(e)

return False

async def check_settings(self, user_input):
template_ok = False
if user_input is not None:
if user_input["additional_costs"] in (None, ""):
user_input["additional_costs"] = DEFAULT_TEMPLATE
else:
# Lets try to remove the most common mistakes, this will still fail if the template
# was writte in notepad or something like that..
user_input["additional_costs"] = re.sub(
r"\s{2,}", "", user_input["additional_costs"]
)

template_ok = await self._valid_template(user_input["additional_costs"])

return template_ok, user_input


class NordpoolFlowHandler(Base, config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for Nordpool."""

VERSION = 1
Expand All @@ -32,39 +102,13 @@ async def async_step_user(
self._errors = {}

if user_input is not None:
template_ok = False
if user_input["additional_costs"] in (None, ""):
user_input["additional_costs"] = DEFAULT_TEMPLATE
else:
# Lets try to remove the most common mistakes, this will still fail if the template
# was writte in notepad or something like that..
user_input["additional_costs"] = re.sub(
r"\s{2,}", "", user_input["additional_costs"]
)

template_ok = await self._valid_template(user_input["additional_costs"])
template_ok, user_input = await self.check_settings(user_input)
if template_ok:
return self.async_create_entry(title="Nordpool", data=user_input)
return self.async_create_entry(title=DOMAIN, data=user_input)
else:
self._errors["base"] = "invalid_template"

data_schema = {
vol.Required("region", default=None): vol.In(regions),
vol.Optional("currency", default=""): vol.In(currencys),
vol.Optional("VAT", default=True): bool,
vol.Optional("precision", default=3): vol.Coerce(int),
vol.Optional("low_price_cutoff", default=1.0): vol.Coerce(float),
vol.Optional("price_in_cents", default=False): bool,
vol.Optional("price_type", default="kWh"): vol.In(price_types),
vol.Optional("additional_costs", default=""): str,
}

placeholders = {
"region": regions,
"currency": currencys,
"price_type": price_types,
"additional_costs": "{{0.0|float}}",
}
data_schema = get_schema(user_input)

return self.async_show_form(
step_id="user",
Expand All @@ -73,22 +117,58 @@ async def async_step_user(
errors=self._errors,
)

async def _valid_template(self, user_template):
try:
_LOGGER.debug(user_template)
ut = Template(user_template, self.hass).async_render()
if isinstance(ut, float):
return True
else:
return False
except Exception as e:
_LOGGER.error(e)
pass
return False

async def async_step_import(self, user_input): # pylint: disable=unused-argument
"""Import a config entry.
Special type of import, we're not actually going to store any data.
Instead, we're going to rely on the values that are in config file.
"""
return self.async_create_entry(title="configuration.yaml", data={})

@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the Options handler"""
return NordpoolOptionsFlowHandler(config_entry)


class NordpoolOptionsFlowHandler(Base, config_entries.OptionsFlow):
"""Handles the options for the component"""

def __init__(self, config_entry) -> None:
self.config_entry = config_entry
# We dont really care about the options, this component allows all
# settings to be edit after the sensor is created.
# For this to work we need to have a stable entity id.
self.options = dict(config_entry.data)
# self.data = config_entries.data
self._errors = {}

async def async_step_init(self, user_input=None): # pylint: disable=unused-argument
"""Manage the options."""
return await self.async_step_user(user_input=user_input)

async def async_step_edit(self, user_input=None): # pylint: disable=unused-argument
"""Manage the options."""
return await self.async_step_user(user_input=user_input)

async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if user_input is not None:
template_ok, user_input = await self.check_settings(user_input)
if template_ok:
return self.async_create_entry(title=DOMAIN, data=user_input)
else:
self._errors["base"] = "invalid_template"

self.options.update(user_input)
return self.async_create_entry(title=DOMAIN, data=self.options)

# Get the current settings and use them as default.
ds = get_schema(self.options)

return self.async_show_form(
step_id="edit",
data_schema=vol.Schema(ds),
description_placeholders=placeholders,
errors={},
)
Loading