Skip to content

Commit

Permalink
Merge pull request #462 from custom-components/service_call
Browse files Browse the repository at this point in the history
Service call
  • Loading branch information
Hellowlol authored Dec 30, 2024
2 parents 61f121f + 04e3194 commit 226e9e5
Show file tree
Hide file tree
Showing 14 changed files with 609 additions and 713 deletions.
66 changes: 38 additions & 28 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"image": "ghcr.io/ludeeus/devcontainer/integration:stable",
"name": "Nordpool integration development",
"context": "..",
"appPort": [
"9123:8123"
"name": "ludeeus/integration_blueprint",
"image": "mcr.microsoft.com/devcontainers/python:3.12",
"postCreateCommand": "scripts/setup",
"forwardPorts": [
8123
],
"postCreateCommand": "container install",
"extensions": [
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance"
],
"settings": {
"files.eol": "\n",
"editor.tabSize": 4,
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/bin/python3",
"python.analysis.autoSearchPaths": false,
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
}

"portsAttributes": {
"8123": {
"label": "Home Assistant",
"onAutoForward": "notify"
}
},
"customizations": {
"vscode": {
"extensions": [
"charliermarsh.ruff",
"github.vscode-pull-request-github",
"ms-python.python",
"ms-python.vscode-pylance",
"ryanluker.vscode-coverage-gutters"
],
"settings": {
"files.eol": "\n",
"editor.tabSize": 4,
"editor.formatOnPaste": true,
"editor.formatOnSave": true,
"editor.formatOnType": false,
"files.trimTrailingWhitespace": true,
"python.analysis.typeCheckingMode": "basic",
"python.analysis.autoImportCompletions": true,
"python.defaultInterpreterPath": "/usr/local/bin/python",
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff"
}
}
}
},
"remoteUser": "vscode",
"features": {}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ __pycache__/
# C extensions
*.so

# Dont commit the config folder.
config/

# Distribution / packaging
.Python
build/
Expand Down
64 changes: 41 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MAXZPYVPD8XS6)
[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/2ys3cdCZk)

Nord Pool is a service provider that operates an electricity market and power system services, including the exchange of electricity on a spot market Nordics and Baltic countries.
Nord Pool is a service provider that operates an electricity market and power system services, including the exchange of electricity on a spot market Nordics and Baltic countries.

This integration provides the spot market (hourly) electricity prices for the Nordic, Baltic and part of Western Europe.
This integration provides the spot market (hourly) electricity prices for the Nordic, Baltic and part of Western Europe.

The Nordpool sensor provides the current price with today's and tomorrow's prices as attributes. Prices become available around 13:00.

Expand All @@ -31,8 +31,8 @@ The Nordpool sensor provides the current price with today's and tomorrow's price
- Restart Home Assistant

*or*
- Go to `HACS` -> `Integrations`,
- Select `+`,
- Go to `HACS` -> `Integrations`,
- Select `+`,
- Search for `nordpool` and install it,
- Restart Home Assistant

Expand All @@ -44,15 +44,15 @@ cd YOUR_HASS_CONFIG_DIRECTORY # same place as configuration.yaml
mkdir -p custom_components/nordpool
cd custom_components/nordpool
unzip nordpool-X.Y.Z.zip
mv nordpool-X.Y.Z/custom_components/nordpool/* .
mv nordpool-X.Y.Z/custom_components/nordpool/* .
```

## Usage

### Configuration Variables
| Configuration | Required | Description |
| Configuration | Required | Description |
|----------------------| -------- | ----------------------------- |
| Region | **yes** | Country/region to get the energy prices for. See Country/region codes below for details.|
| Region | **yes** | Country/region to get the energy prices for. See Country/region codes below for details.|
| Currency | no | *Default: local currency* <br> Currency used to fetch the prices from the API.|
| Include VAT | no | *Default: true* <br> Add Value Added Taxes (VAT) or not.|
| Decimal precision | no | *Default: 3* <br> Energy price rounding precision. |
Expand All @@ -76,25 +76,25 @@ Set up the sensor using in `configuration.yaml`.
```yaml
sensor:
- platform: nordpool
region: "NO1"
region: "NO1"
```
#### Example configuration:
```yaml
sensor:
- platform: nordpool
# Country/region to get the energy prices for.
# Country/region to get the energy prices for.
region: "NO1"

# Override HA local currency used to fetch the prices from the API.
currency: "EUR"

# Add Value Added Taxes (VAT)?
VAT: True

# Energy price rounding precision.
precision: 3

# Percentage of average price to set the low price attribute
# low_price = hour_price < average * low_price_cutoff
low_price_cutoff: 0.95
Expand All @@ -107,13 +107,13 @@ sensor:

# Template to specify additional cost to be added to the tariff.
# The template price is in EUR, DKK, NOK or SEK (not in cents).
# For example: "{{ current_price * 0.19 + 0.023 | float}}"
# For example: "{{ current_price * 0.19 + 0.023 | float}}"
additional_costs: "{{0.0|float}}"
```
### Regions
See the [Nord Pool region map](https://www.nordpoolgroup.com/en/maps/) for details
| Country | Region code |
| Country | Region code |
| --------- | ----------- |
| Austria | AT |
| Belgium | BE |
Expand All @@ -132,30 +132,30 @@ See the [Nord Pool region map](https://www.nordpoolgroup.com/en/maps/) for detai
| Sweden | SE1, <br> SE2, <br> SE3, <br> SE4 |
### Additional costs
The idea behind `additional_costs` is to allow the users to add costs related to the official price from Nordpool:
The idea behind `additional_costs` is to allow the users to add costs related to the official price from Nordpool:
- Add simple or complex tariffs
- Calculate VAT

There are two special special arguments in that can be used in the template ([in addition to all default from Homeassistant](https://www.home-assistant.io/docs/configuration/templating/)):
- ```now()```: this always refer to the current hour of the price
- ```current_price```: price for the current hour. This can be used for example be used to calculate your own VAT or add overhead cost.
- ```current_price```: price for the current hour. This can be used for example be used to calculate your own VAT or add overhead cost.

Note: When configuring Nordpool using the UI, things like VAT and additional costs cannot be changed. If your energy supplier or region changes the additional costs or taxes on a semi-regular basis, the YAML configuration or a helper (example 4) work best.

#### Example 1: Overhead per kWh

Add 1,3 cents per kWh overhead cost to the current hour's price
Add 1,3 cents per kWh overhead cost to the current hour's price

```{{ 0.013 | float }}```

#### Example 2: Percentage (VAT)
Add 19 % VAT of the current hour's price
Add 19 % VAT of the current hour's price

```{{ (current_price * 0.19) | float }}```

#### Example 3: Overhead and VAT

Add 1,3 cents per kWh overhead cost, 0.002 flat tax and 19% VAT to the current hour's price
Add 1,3 cents per kWh overhead cost, 0.002 flat tax and 19% VAT to the current hour's price

```{{ (0.013 + 0.002 + (current_price * 0.19)) | float }}```

Expand Down Expand Up @@ -208,7 +208,7 @@ Add 21% tax and overhead cost stored in a helper
- ```country```: What Country data is fetched for
- ```region```: The specific region of prices
- ```low_price```: If price is below low_price_threshold
- ```price_percent_to_average```:
- ```price_percent_to_average```:
- ```today```: List of all values
- ```tomorrow```: list of all values
- ```tomorrow_valid```: If tomorow´s values is in yet
Expand All @@ -218,9 +218,27 @@ Add 21% tax and overhead cost stored in a helper
- ```additional_costs_current_hour```: If there is any additional costs this hour
- ```price_in_cents```: Boolean if prices is in cents

### One sensor per hour
## Actions
Actions has recently been added. The action will just forward the raw response from the Nordpool API so you can capture the value your are interested in.

By default, one sensor is created with the current energy price. The prices for other hours are stored in the attributes of this sensor. Most example code you will find uses the default one sensor option, but you can run the `create_template` script to create separate sensors for every hour. See the help options with ```python create_template --help```. You can run the script on any system where Python is installed (install the required packages `pyyaml` and `click` using `pip install pyyaml click`)
Example for an automation that get the last months averge price.
```yaml
alias: Example automation action call with storing with parsing and storing result
triggers: null
actions:
- action: nordpool.yearly
data:
currency: NOK
area: NO2
year: "2024"
response_variable: np_result
- action: input_text.set_value
target:
entity_id: input_text.test
data:
value: "{{np_result.prices[0].averagePerArea.NO2 | float}}"
mode: single
```

## Troubleshooting

Expand Down
59 changes: 33 additions & 26 deletions custom_components/nordpool/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import logging
from collections import defaultdict
from datetime import timedelta
from random import randint


import backoff
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.const import Platform
Expand All @@ -16,25 +15,21 @@

from .aio_price import AioPrices, InvalidValueException
from .events import async_track_time_change_in_tz
from .services import async_setup_services

from .const import (
NAME,
VERSION,
ISSUEURL,
DOMAIN,
EVENT_NEW_DAY,
EVENT_NEW_HOUR,
EVENT_NEW_PRICE,
_CURRENCY_LIST,
RANDOM_MINUTE,
RANDOM_SECOND,
)

DOMAIN = "nordpool"
_LOGGER = logging.getLogger(__name__)
RANDOM_MINUTE = randint(10, 30)
RANDOM_SECOND = randint(0, 59)
EVENT_NEW_HOUR = "nordpool_update_hour"
EVENT_NEW_DAY = "nordpool_update_day"
EVENT_NEW_PRICE = "nordpool_update_new_price"
SENTINEL = object()

_CURRENCY_LIST = ["DKK", "EUR", "NOK", "SEK"]


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


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

STARTUP = f"""
-------------------------------------------------------------------
Expand All @@ -46,6 +41,8 @@
-------------------------------------------------------------------
"""

_LOGGER = logging.getLogger(__name__)

PLATFORMS: list[Platform] = [Platform.SENSOR]


Expand Down Expand Up @@ -76,7 +73,9 @@ async def _update(self, type_="today", dt=None, areas=None):
# Keeping this for now, but this should be changed.
for currency in self.currency:
spot = AioPrices(currency, client)
data = await spot.hourly(end_date=dt, areas=self.areas if len(self.areas) > 0 else None)
data = await spot.hourly(
end_date=dt, areas=self.areas if len(self.areas) > 0 else None
)
if data:
self._data[currency][type_] = data["areas"]

Expand All @@ -92,7 +91,11 @@ async def update_tomorrow(self, areas=None):
_LOGGER.debug("Updating tomorrows prices.")
if areas is not None:
self.areas += [area for area in areas if area not in self.areas]
await self._update(type_="tomorrow", dt=dt_utils.now() + timedelta(hours=24), areas=self.areas if len(self.areas) > 0 else None)
await self._update(
type_="tomorrow",
dt=dt_utils.now() + timedelta(hours=24),
areas=self.areas if len(self.areas) > 0 else None,
)

async def _someday(self, area: str, currency: str, day: str):
"""Returns today's or tomorrow's prices in an area in the currency"""
Expand All @@ -103,7 +106,7 @@ async def _someday(self, area: str, currency: str, day: str):
)

if area not in self.areas:
self.areas.append(area);
self.areas.append(area)
# This is needed as the currency is
# set in the sensor.
if currency not in self.currency:
Expand All @@ -126,19 +129,19 @@ async def _someday(self, area: str, currency: str, day: str):
async def today(self, area: str, currency: str) -> dict:
"""Returns today's prices in an area in the requested currency"""
return await self._someday(area, currency, "today")


async def tomorrow(self, area: str, currency: str):
"""Returns tomorrow's prices in an area in the requested currency"""
return await self._someday(area, currency, "tomorrow")


async def _dry_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up using yaml config file."""
if DOMAIN not in hass.data:
api = NordpoolData(hass)
hass.data[DOMAIN] = api
_LOGGER.debug("Added %s to hass.data", DOMAIN)
await async_setup_services(hass)

async def new_day_cb(_):
"""Cb to handle some house keeping when it a new day."""
Expand All @@ -161,7 +164,11 @@ async def new_hr(_):
@backoff.on_exception(
backoff.constant,
(InvalidValueException),
logger=_LOGGER, interval=600, max_time=7200, jitter=None)
logger=_LOGGER,
interval=600,
max_time=7200,
jitter=None,
)
async def new_data_cb(_):
"""Callback to fetch new data for tomorrows prices at 1300ish CET
and notify any sensors, about the new data
Expand Down
Loading

0 comments on commit 226e9e5

Please sign in to comment.