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

Add Get price service to Nord Pool #130185

Open
wants to merge 4 commits into
base: dev
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
12 changes: 12 additions & 0 deletions homeassistant/components/nordpool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,25 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util

from .const import DOMAIN, PLATFORMS
from .coordinator import NordPoolDataUpdateCoordinator
from .services import async_setup_services

type NordPoolConfigEntry = ConfigEntry[NordPoolDataUpdateCoordinator]

CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Habitica service."""

async_setup_services(hass)
return True


async def async_setup_entry(hass: HomeAssistant, entry: NordPoolConfigEntry) -> bool:
"""Set up Nord Pool from a config entry."""
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/nordpool/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,10 @@
"default": "mdi:cash-multiple"
}
}
},
"services": {
"get_prices_for_date": {
"service": "mdi:cash-multiple"
}
}
}
127 changes: 127 additions & 0 deletions homeassistant/components/nordpool/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"""Services for Nord Pool integration."""

from __future__ import annotations

from datetime import date, datetime
import logging
from typing import TYPE_CHECKING

from pynordpool import (
Currency,
NordPoolAuthenticationError,
NordPoolEmptyResponseError,
NordPoolError,
)
import voluptuous as vol

from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_DATE
from homeassistant.core import (
HomeAssistant,
ServiceCall,
ServiceResponse,
SupportsResponse,
)
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.selector import ConfigEntrySelector
from homeassistant.util import dt as dt_util
from homeassistant.util.json import JsonValueType

if TYPE_CHECKING:
from . import NordPoolConfigEntry
from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)
ATTR_CONFIG_ENTRY = "config_entry"
ATTR_AREAS = "areas"
ATTR_CURRENCY = "currency"

SERVICE_GET_PRICES_FOR_DATE = "get_prices_for_date"
SERVICE_GET_PRICES_SCHEMA = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector(),
vol.Required(ATTR_DATE): cv.date,
vol.Optional(ATTR_AREAS, default=[]): cv.ensure_list,
vol.Optional(ATTR_CURRENCY): cv.string,
}
)


def get_config_entry(hass: HomeAssistant, entry_id: str) -> NordPoolConfigEntry:
"""Return config entry."""
if not (entry := hass.config_entries.async_get_entry(entry_id)):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="entry_not_found",
)
if entry.state is not ConfigEntryState.LOADED:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="entry_not_loaded",
)
return entry


def async_setup_services(hass: HomeAssistant) -> None:
"""Set up services for Nord Pool integration."""

async def get_prices_for_date(call: ServiceCall) -> ServiceResponse:
"""Get price service."""
entry = get_config_entry(hass, call.data[ATTR_CONFIG_ENTRY])
asked_date: date = call.data[ATTR_DATE]
client = entry.runtime_data.client
areas: list[str] | None = call.data.get(ATTR_AREAS)
if not areas:
areas = entry.data[ATTR_AREAS]
currency: str | None = call.data.get(ATTR_CURRENCY)
if not currency:
currency = entry.data[ATTR_CURRENCY]

if TYPE_CHECKING:
assert isinstance(areas, list)
assert isinstance(currency, str)
areas = [area.upper() for area in areas]
currency = currency.upper()

try:
price_data = await client.async_get_delivery_period(
datetime.combine(asked_date, dt_util.utcnow().time()),
Currency(currency),
areas,
)
except NordPoolAuthenticationError as error:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="authentication_error",
) from error
except NordPoolEmptyResponseError as error:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="empty_response",
) from error
except NordPoolError as error:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="connection_error",
) from error

result: dict[str, JsonValueType] = {}
for area in areas:
result[area] = [
{
"start": price_entry.start.isoformat(),
"end": price_entry.end.isoformat(),
"price": price_entry.entry[area],
}
for price_entry in price_data.entries
]
return result

hass.services.async_register(
DOMAIN,
SERVICE_GET_PRICES_FOR_DATE,
get_prices_for_date,
schema=SERVICE_GET_PRICES_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
26 changes: 26 additions & 0 deletions homeassistant/components/nordpool/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
get_prices_for_date:
fields:
config_entry:
required: true
selector:
config_entry:
integration: nordpool
date:
required: true
selector:
date:
areas:
selector:
text:
multiple: true
currency:
selector:
select:
options:
- "DKK"
- "EUR"
- "NOK"
- "PLN"
- "SEK"
mode: dropdown
translation_key: "currency"
50 changes: 50 additions & 0 deletions homeassistant/components/nordpool/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,59 @@
}
}
},
"services": {
"get_prices_for_date": {
"name": "Get prices for date",
"description": "Retrieve the prices for a specific date.",
"fields": {
"config_entry": {
"name": "Select Nord Pool configuration entry",
"description": "Choose the configuration entry."
},
"date": {
"name": "Date",
"description": "Only dates two months in the past and one day in the future is allowed."
},
"areas": {
"name": "Areas",
"description": "One or multiple areas to get prices for. If left empty it will use the areas already configured."
},
"currency": {
"name": "Currency",
"description": "Currency to get prices in. If left empty it will use the currency already configured."
}
}
}
},
"selector": {
"currency": {
"options": {
"dkk": "DKK",
"eur": "EUR",
"nok": "NOK",
"pln": "PLN",
"sek": "SEK"
}
}
},
"exceptions": {
"initial_update_failed": {
"message": "Initial update failed on startup with error {error}"
},
"entry_not_found": {
"message": "The Nord Pool integration is not configured in Home Assistant."
},
"entry_not_loaded": {
"message": "The Nord Pool integration is currently not loaded or disabled in Home Assistant."
},
"authentication_error": {
"message": "There was an authentication error as you tried to retrieve data too far in the past."
},
"empty_response": {
"message": "Nord Pool has not posted market prices for the provided date."
},
"connection_error": {
"message": "There was a connection error connecting to the API. Try again later."
}
}
}
Loading