[entsoe] Initial contribution (#17416)

Signed-off-by: Jørgen Melhus <jmelhus@outlook.com>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Jørgen Melhus 2024-10-21 21:32:23 +02:00 committed by Ciprian Pascu
parent 89b4e2267d
commit ff4d3f8313
20 changed files with 1311 additions and 0 deletions

View File

@ -104,6 +104,7 @@
/bundles/org.openhab.binding.enigma2/ @gdolfen /bundles/org.openhab.binding.enigma2/ @gdolfen
/bundles/org.openhab.binding.enocean/ @fruggy83 /bundles/org.openhab.binding.enocean/ @fruggy83
/bundles/org.openhab.binding.enphase/ @Hilbrand /bundles/org.openhab.binding.enphase/ @Hilbrand
/bundles/org.openhab.binding.entsoe/ @jmelhus
/bundles/org.openhab.binding.enturno/ @klocsson /bundles/org.openhab.binding.enturno/ @klocsson
/bundles/org.openhab.binding.ephemeris/ @clinique /bundles/org.openhab.binding.ephemeris/ @clinique
/bundles/org.openhab.binding.epsonprojector/ @mlobstein /bundles/org.openhab.binding.epsonprojector/ @mlobstein

View File

@ -511,6 +511,11 @@
<artifactId>org.openhab.binding.enphase</artifactId> <artifactId>org.openhab.binding.enphase</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.entsoe</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.openhab.addons.bundles</groupId> <groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.enturno</artifactId> <artifactId>org.openhab.binding.enturno</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,85 @@
# ENTSO-E Binding
This binding fetches day-ahead energy spot prices from ENTSO-E, the European Network of Transmission System Operators for Electricity.
Users can select a specific area to retrieve the relevant energy prices.
This binding helps users monitor and manage their energy consumption based on real-time pricing data.
It is recommended to use this binding together with a currency provider (e.g. [Freecurrency binding](https://www.openhab.org/addons/bindings/freecurrency/)) for exchanging euro spot prices to local currency.
## Supported Things
- `day-ahead`: This is the main and single Thing of the binding.
## Thing Configuration
To access the ENTSO-E Transparency Platform API, users need a **security token** for authentication and authorization.
This token ensures secure access to the platform's data and services.
For detailed instructions on obtaining this token, you can refer to the [ENTSO-E API Guide 2. Authentication and Authorisation](https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_authentication_and_authorisation).
Mandatory parameters of the Thing are security token and area.
Optional parameters are historic days, resolution, availability hour for day ahead spot prices and request timeout.
### `entsoe` Thing Configuration
| Name | Type | Description | Default | Required | Advanced |
|-------------------------------|-------------------|---------------------------------------------------------------------------|-----------|----------|----------|
| securityToken | text | Security token to fetch from ENTSO-E | N/A | yes | no |
| area | text | Area | N/A | yes | no |
| historicDays | integer | Historic days to get prices from (will use exchange rate as of today) | 0 | no | no |
| resolution | text | Data resolution | PT60M | no | no |
| spotPricesAvailableCetHour | integer | Which CET hour binding assumes new spot prices for next day is available | 13 | no | yes |
| requestTimeout | integer | Request timeout in seconds | 30 | no | yes |
## Channels
Binding has one channel.
spot-price which are the values fetched from ENTSO-E and persisted in openHAB as time series.
The price is per kWh at your selected base currency.
| Channel | Type | Read/Write | Description |
|--------------------------|-----------------------|------------|-------------------------------------------|
| spot-price | Number:EnergyPrice | R | Spot prices |
### Thing Configuration
```java
Thing entsoe:day-ahead:eda "Entsoe Day Ahead" [ securityToken="your-security-token", area="10YNO-3--------J", historicDays=14 ]
```
### Item Configuration
```java
Number:EnergyPrice energySpotPrice "Current Spot Price" <price> { channel="entsoe:day-ahead:eda:spot-price" }
```
#### Value-Added Tax
VAT is not included in any of the prices.
To include VAT for items linked to the `Number:EnergyPrice` channel, the [VAT profile](https://www.openhab.org/addons/transformations/vat/) can be used.
This must be installed separately.
Once installed, simply select "Value-Added Tax" as Profile when linking an item.
#### Total Price
_Please note:_ There is no channel providing the total price.
Instead, create a group item with `SUM` as aggregate function and add the individual price items as children.
Read more about how to in this similar binding [Energi Data Service](https://www.openhab.org/addons/bindings/energidataservice/#total-price)
### Trigger Channels
Channel `prices-received` is triggered when new prices are available.
### Examples
examples.rules
```java
rule "Spot prices received"
when
Channel "entsoe:day-ahead:eda:prices-received" triggered
then
// Do something within rule
logInfo("ENTSO-E Rule", "ENTSO-E channel triggered, new spot prices available")
end
```

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>4.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.entsoe</artifactId>
<name>openHAB Add-ons :: Bundles :: ENTSO-E Binding</name>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.entsoe-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-entsoe" description="ENTSO-E Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.entsoe/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.entsoe.internal;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.TimeSeries.Policy;
/**
* The {@link EntsoeBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Jørgen Melhus - Initial contribution
* @author Miika Jukka - Initial contribution
*/
@NonNullByDefault
public class EntsoeBindingConstants {
private static final String BINDING_ID = "entsoe";
public static final Policy TIMESERIES_POLICY = Policy.REPLACE;
public static final String CHANNEL_SPOT_PRICE = "spot-price";
public static final String CHANNEL_TRIGGER_PRICES_RECEIVED = "prices-received";
// Thing Type UIDs
public static final ThingTypeUID THING_TYPE_DAY_AHEAD = new ThingTypeUID(BINDING_ID, "day-ahead");
// List of all Thing Type UIDs
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPE_UIDS = Set.of(THING_TYPE_DAY_AHEAD);
}

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.entsoe.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link EntsoeConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Jørgen Melhus - Initial contribution
*/
@NonNullByDefault
public class EntsoeConfiguration {
public String securityToken = "";
public String area = "";
public int spotPricesAvailableCetHour = 13;
public int historicDays = 1;
public int requestTimeout = 30;
public String resolution = "PT60M";
}

View File

@ -0,0 +1,273 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.entsoe.internal;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.entsoe.internal.client.Client;
import org.openhab.binding.entsoe.internal.client.Request;
import org.openhab.binding.entsoe.internal.client.SpotPrice;
import org.openhab.binding.entsoe.internal.exception.EntsoeConfigurationException;
import org.openhab.binding.entsoe.internal.exception.EntsoeResponseException;
import org.openhab.binding.entsoe.internal.exception.EntsoeResponseMapException;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.TimeSeries;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link EntsoeHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Jørgen Melhus - Initial contribution
*/
@NonNullByDefault
public class EntsoeHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(EntsoeHandler.class);
private EntsoeConfiguration config;
private @Nullable ScheduledFuture<?> refreshJob;
private Map<Instant, SpotPrice> entsoeTimeSeries = new LinkedHashMap<>();
private final ZoneId cetZoneId = ZoneId.of("CET");
private ZonedDateTime lastDayAheadReceived = ZonedDateTime.of(LocalDateTime.MIN, cetZoneId);
private int historicDaysInitially = 0;
public EntsoeHandler(Thing thing) {
super(thing);
config = new EntsoeConfiguration();
}
@Override
public void channelLinked(ChannelUID channelUID) {
logger.trace("channelLinked(channelUID:{})", channelUID.getAsString());
String channelID = channelUID.getId();
if (EntsoeBindingConstants.CHANNEL_SPOT_PRICE.equals(channelID)) {
if (entsoeTimeSeries.isEmpty()) {
refreshPrices();
}
updateCurrentState(EntsoeBindingConstants.CHANNEL_SPOT_PRICE);
}
}
@Override
public void dispose() {
entsoeTimeSeries.clear();
ScheduledFuture<?> refreshJob = this.refreshJob;
if (refreshJob != null) {
refreshJob.cancel(true);
this.refreshJob = null;
}
super.dispose();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("handleCommand(channelUID:{}, command:{})", channelUID.getAsString(), command.toFullString());
if (command instanceof RefreshType) {
fetchNewPrices();
}
}
@Override
public void initialize() {
config = getConfigAs(EntsoeConfiguration.class);
lastDayAheadReceived = ZonedDateTime.of(LocalDateTime.MIN, cetZoneId);
if (historicDaysInitially == 0) {
historicDaysInitially = config.historicDays;
}
if (isLinked(EntsoeBindingConstants.CHANNEL_SPOT_PRICE)) {
updateStatus(ThingStatus.UNKNOWN);
} else {
updateStatus(ThingStatus.ONLINE);
}
refreshJob = scheduler.schedule(this::refreshPrices, 0, TimeUnit.SECONDS);
}
private ZonedDateTime currentCetTime() {
return ZonedDateTime.now(cetZoneId);
}
private ZonedDateTime currentCetTimeWholeHours() {
return currentCetTime().truncatedTo(ChronoUnit.HOURS);
}
private long getMillisToNextStateUpdate() {
Instant now = Instant.now();
Instant nextHour = now.truncatedTo(ChronoUnit.HOURS).plus(1, ChronoUnit.HOURS);
long millis = Duration.between(now, nextHour).toMillis();
try {
Instant nextInstant = getNextSpotPrice().getInstant();
millis = Duration.between(now, nextInstant).toMillis();
} catch (EntsoeResponseMapException e) {
logger.warn("Using millis to next whole hour");
}
return millis + 1;
}
private SpotPrice getCurrentSpotPrice() throws EntsoeResponseMapException {
Duration duration = Duration.parse(config.resolution);
Instant now = Instant.now();
for (Map.Entry<Instant, SpotPrice> entry : entsoeTimeSeries.entrySet()) {
Instant entryStart = entry.getKey();
Instant entryEnd = entryStart.plus(duration);
if (!now.isBefore(entryStart) && now.isBefore(entryEnd)) {
return entry.getValue();
}
}
throw new EntsoeResponseMapException("Could not get current SpotPrice");
}
private SpotPrice getNextSpotPrice() throws EntsoeResponseMapException {
Instant now = Instant.now();
for (Map.Entry<Instant, SpotPrice> entry : entsoeTimeSeries.entrySet()) {
Instant entryStart = entry.getKey();
if (entryStart.isAfter(now)) {
return entry.getValue();
}
}
throw new EntsoeResponseMapException("Could not get next SpotPrice");
}
private boolean needToFetchHistoricDays() {
return needToFetchHistoricDays(false);
}
private boolean needToFetchHistoricDays(boolean updateHistoricDaysInitially) {
boolean needToFetch = false;
if (historicDaysInitially < config.historicDays) {
logger.debug("Need to fetch historic data. Historicdays was changed to a greater number: {}",
config.historicDays);
needToFetch = true;
}
if (updateHistoricDaysInitially && historicDaysInitially != config.historicDays) {
historicDaysInitially = config.historicDays;
}
return needToFetch;
}
private void refreshPrices() {
if (!isLinked(EntsoeBindingConstants.CHANNEL_SPOT_PRICE)) {
logger.debug("Channel {} is not linked, skipping channel update",
EntsoeBindingConstants.CHANNEL_SPOT_PRICE);
return;
}
if (entsoeTimeSeries.isEmpty()) {
fetchNewPrices();
return;
}
boolean needsInitialUpdate = lastDayAheadReceived.equals(ZonedDateTime.of(LocalDateTime.MIN, cetZoneId))
|| needToFetchHistoricDays(true);
Instant nextDayInstant = ZonedDateTime.now(cetZoneId).plusDays(1).with(LocalTime.MIDNIGHT).toInstant();
boolean hasNextDayValue = entsoeTimeSeries.containsKey(nextDayInstant);
boolean readyForNextDayValue = currentCetTime()
.isAfter(currentCetTimeWholeHours().withHour(config.spotPricesAvailableCetHour));
if (needsInitialUpdate || (!hasNextDayValue && readyForNextDayValue)) {
fetchNewPrices();
} else {
updateCurrentState(EntsoeBindingConstants.CHANNEL_SPOT_PRICE);
schedule(true);
}
}
private void fetchNewPrices() {
logger.trace("Fetching new prices");
Instant startUtc = ZonedDateTime.now(cetZoneId)
.minusDays(needToFetchHistoricDays() ? config.historicDays - 1 : 0).with(LocalTime.MIDNIGHT)
.toInstant();
Instant endUtc = ZonedDateTime.now(cetZoneId).plusDays(2).with(LocalTime.MIDNIGHT).toInstant();
Request request = new Request(config.securityToken, config.area, startUtc, endUtc);
Client client = new Client();
boolean success = false;
try {
entsoeTimeSeries = client.doGetRequest(request, config.requestTimeout * 1000, config.resolution);
TimeSeries baseTimeSeries = new TimeSeries(EntsoeBindingConstants.TIMESERIES_POLICY);
for (Map.Entry<Instant, SpotPrice> entry : entsoeTimeSeries.entrySet()) {
baseTimeSeries.add(entry.getValue().getInstant(), entry.getValue().getState(Units.KILOWATT_HOUR));
}
updateStatus(ThingStatus.ONLINE);
lastDayAheadReceived = currentCetTime();
sendTimeSeries(EntsoeBindingConstants.CHANNEL_SPOT_PRICE, baseTimeSeries);
updateCurrentState(EntsoeBindingConstants.CHANNEL_SPOT_PRICE);
triggerChannel(EntsoeBindingConstants.CHANNEL_TRIGGER_PRICES_RECEIVED);
success = true;
} catch (EntsoeResponseException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (EntsoeConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
} finally {
schedule(success);
}
}
private void schedule(boolean success) {
if (!success) {
logger.trace("schedule(success:{})", success);
refreshJob = scheduler.schedule(this::refreshPrices, 5, TimeUnit.MINUTES);
} else {
refreshJob = scheduler.schedule(this::refreshPrices, getMillisToNextStateUpdate(), TimeUnit.MILLISECONDS);
}
}
private void updateCurrentState(String channelID) {
logger.trace("Updating current state");
try {
updateState(channelID, getCurrentSpotPrice().getState(Units.KILOWATT_HOUR));
} catch (EntsoeConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
}
}

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.entsoe.internal;
import static org.openhab.binding.entsoe.internal.EntsoeBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
/**
* The {@link EntsoeHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Jørgen Melhus - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.entsoe", service = ThingHandlerFactory.class)
public class EntsoeHandlerFactory extends BaseThingHandlerFactory {
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPE_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_DAY_AHEAD.equals(thingTypeUID)) {
return new EntsoeHandler(thing);
}
return null;
}
}

View File

@ -0,0 +1,175 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.entsoe.internal.client;
import java.io.IOException;
import java.io.StringReader;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.entsoe.internal.exception.EntsoeConfigurationException;
import org.openhab.binding.entsoe.internal.exception.EntsoeResponseException;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* @author Miika Jukka - Initial contribution
* @author Jørgen Melhus - Contribution
*/
@NonNullByDefault
public class Client {
private final Logger logger = LoggerFactory.getLogger(Client.class);
private DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
public Map<Instant, SpotPrice> doGetRequest(Request request, int timeout, String configResolution)
throws EntsoeResponseException, EntsoeConfigurationException {
try {
logger.debug("Sending GET request with parameters: {}", request);
String url = request.toUrl();
String responseText = HttpUtil.executeUrl("GET", url, timeout);
if (responseText == null) {
throw new EntsoeResponseException("Request failed");
}
logger.trace("Response: {}", responseText);
return parseXmlResponse(responseText, configResolution);
} catch (IOException e) {
String message = e.getMessage();
if (message != null && message.contains("Authentication challenge without WWW-Authenticate header")) {
throw new EntsoeConfigurationException("Authentication failed. Please check your security token");
}
throw new EntsoeResponseException(e);
} catch (ParserConfigurationException | SAXException e) {
throw new EntsoeResponseException(e);
}
}
private Map<Instant, SpotPrice> parseXmlResponse(String responseText, String configResolution)
throws ParserConfigurationException, SAXException, IOException, EntsoeResponseException,
EntsoeConfigurationException {
Map<Instant, SpotPrice> responseMap = new LinkedHashMap<>();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(new InputSource(new StringReader(responseText)));
document.getDocumentElement().normalize();
// Check for rejection
if (document.getDocumentElement().getNodeName().equals("Acknowledgement_MarketDocument")) {
NodeList reasonOfRejection = document.getElementsByTagName("Reason");
Node reasonNode = reasonOfRejection.item(0);
Element reasonElement = (Element) reasonNode;
String reasonCode = reasonElement.getElementsByTagName("code").item(0).getTextContent();
String reasonText = reasonElement.getElementsByTagName("text").item(0).getTextContent();
throw new EntsoeResponseException(
String.format("Request failed with API response: Code %s, Text %s", reasonCode, reasonText));
}
// Get all "timeSeries" nodes from document
NodeList listOfTimeSeries = document.getElementsByTagName("TimeSeries");
boolean resolutionFound = false;
for (int i = 0; i < listOfTimeSeries.getLength(); i++) {
Node timeSeriesNode = listOfTimeSeries.item(i);
if (timeSeriesNode.getNodeType() == Node.ELEMENT_NODE) {
Element timeSeriesElement = (Element) timeSeriesNode;
String currency = timeSeriesElement.getElementsByTagName("currency_Unit.name").item(0).getTextContent();
String measureUnit = timeSeriesElement.getElementsByTagName("price_Measure_Unit.name").item(0)
.getTextContent();
NodeList listOfPeriod = timeSeriesElement.getElementsByTagName("Period");
Node periodNode = listOfPeriod.item(0);
Element periodElement = (Element) periodNode;
NodeList listOfTimeInterval = periodElement.getElementsByTagName("timeInterval");
Node startTimeNode = listOfTimeInterval.item(0);
Element startTimeElement = (Element) startTimeNode;
String startTime = startTimeElement.getElementsByTagName("start").item(0).getTextContent();
Instant startTimeInstant = ZonedDateTime.parse(startTime, DateTimeFormatter.ISO_ZONED_DATE_TIME)
.toInstant();
Node endTimeNode = listOfTimeInterval.item(0);
Element endTimeElement = (Element) endTimeNode;
String endTime = endTimeElement.getElementsByTagName("end").item(0).getTextContent();
Instant endTimeInstant = ZonedDateTime.parse(endTime, DateTimeFormatter.ISO_ZONED_DATE_TIME)
.toInstant();
String resolution = periodElement.getElementsByTagName("resolution").item(0).getTextContent();
Duration durationResolution = Duration.parse(resolution);
Duration durationTotal = Duration.between(startTimeInstant, endTimeInstant);
int numberOfDurations = Math.round(durationTotal.toMinutes() / durationResolution.toMinutes());
logger.debug("\"timeSeries\" node: {}/{} with start time: {} and resolution {}", (i + 1),
listOfTimeSeries.getLength(), startTimeInstant.atZone(ZoneId.of("UTC")), resolution);
NodeList listOfPoints = periodElement.getElementsByTagName("Point");
/*
* EntsoE changed their API on October 1 2024 so that they use the A03 curve type instead of A01. The
* difference between these curve types is that in A03 they dont repeat an hour if it has the same
* price
* as the previous hour.
*/
int pointNr = 0;
for (int p = 0; p < numberOfDurations && resolution.equalsIgnoreCase(configResolution); p++) {
resolutionFound = true;
Node pointNode = listOfPoints.item(pointNr);
int multiplier = p;
if (pointNode != null) {
Element pointElement = (Element) pointNode;
String price = pointElement.getElementsByTagName("price.amount").item(0).getTextContent();
Double priceAsDouble = Double.parseDouble(price);
SpotPrice t = new SpotPrice(currency, measureUnit, priceAsDouble, startTimeInstant, multiplier,
resolution);
responseMap.put(t.getInstant(), t);
logger.trace("\"Point\" node: {}/{} with values: {} - {} {}/{}", (p + 1), numberOfDurations,
t.getInstant(), priceAsDouble, currency, measureUnit);
}
Node nextPointNode = listOfPoints.item(pointNr + 1);
if (nextPointNode != null) {
Element nextPointElement = (Element) nextPointNode;
Node nextPositionNode = nextPointElement.getElementsByTagName("position").item(0);
if (nextPositionNode != null) {
int nextPosition = Integer.parseInt(nextPositionNode.getTextContent());
if (nextPosition == p + 2) {
pointNr++;
}
}
}
}
}
}
if (!resolutionFound) {
throw new EntsoeConfigurationException("Resolution " + configResolution + " not found in ENTSOE response");
}
return responseMap;
}
}

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.entsoe.internal.client;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Miika Jukka - Initial contribution
*
*/
@NonNullByDefault
public class Request {
private static DateTimeFormatter requestFormat = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
private static final String BASE_URL = "https://web-api.tp.entsoe.eu/api?";
private static final String DOCUMENT_TYPE_PRICE = "A44";
private final String securityToken;
private final String area;
private final Instant periodStart;
private final Instant periodEnd;
public Request(String securityToken, String area, Instant periodStart, Instant periodEnd) {
this.securityToken = securityToken;
this.area = area;
this.periodStart = periodStart;
this.periodEnd = periodEnd;
}
public String toUrl() {
return urlBuilder(this.securityToken);
}
@Override
public String toString() {
return urlBuilder("xxxxx-xxxxx-xxxxx");
}
private String urlBuilder(String securityToken) {
StringBuilder urlBuilder = new StringBuilder(BASE_URL).append("securityToken=").append(securityToken)
.append("&documentType=").append(DOCUMENT_TYPE_PRICE).append("&in_domain=").append(area)
.append("&out_domain=").append(area).append("&periodStart=")
.append(periodStart.atZone(ZoneOffset.UTC).format(requestFormat)).append("&periodEnd=")
.append(periodEnd.atZone(ZoneOffset.UTC).format(requestFormat));
return urlBuilder.toString();
}
}

View File

@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.entsoe.internal.client;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.time.Period;
import java.time.format.DateTimeParseException;
import javax.measure.Unit;
import javax.measure.quantity.Energy;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.entsoe.internal.exception.EntsoeResponseException;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.State;
/**
* @author Jørgen Melhus - Initial contribution
*/
@NonNullByDefault
public class SpotPrice {
private String currency;
private Unit<Energy> unit;
private Double price;
private Instant time;
public SpotPrice(String currency, String unit, Double price, Instant start, int iteration, String resolution)
throws EntsoeResponseException {
this.currency = currency;
this.unit = convertEntsoeUnit(unit);
this.price = price;
this.time = calculateDateTime(start, iteration, resolution);
}
private Instant calculateDateTime(Instant start, int iteration, String resolution) throws EntsoeResponseException {
try {
if (resolution.toUpperCase().startsWith("PT")) {
Duration d = Duration.parse(resolution).multipliedBy(iteration);
return start.plus(d);
} else if (resolution.toUpperCase().startsWith("P1")) {
return start.plus(Period.parse(resolution).multipliedBy(iteration));
}
throw new EntsoeResponseException("Unknown resolution: " + resolution);
} catch (DateTimeParseException e) {
throw new EntsoeResponseException(
"DateTimeParseException (ENTSOE resolution: " + resolution + "): " + e.getMessage(), e);
}
}
private Unit<Energy> convertEntsoeUnit(String unit) throws EntsoeResponseException {
if ("MWh".equalsIgnoreCase(unit)) {
return Units.MEGAWATT_HOUR;
}
if ("kWh".equalsIgnoreCase(unit)) {
return Units.KILOWATT_HOUR;
}
if ("Wh".equalsIgnoreCase(unit)) {
return Units.WATT_HOUR;
}
throw new EntsoeResponseException("Unit from ENTSO-E is unknown: " + unit);
}
private BigDecimal getPrice(Unit<Energy> toUnit) {
return new DecimalType(toUnit.getConverterTo(this.unit).convert(this.price)).toBigDecimal();
}
public State getState(Unit<Energy> toUnit) {
try {
return new QuantityType<>(getPrice(toUnit) + " " + this.currency + "/" + toUnit.toString());
} catch (IllegalArgumentException e) {
return new DecimalType(getPrice(toUnit));
}
}
public Instant getInstant() {
return this.time;
}
}

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.entsoe.internal.exception;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Miika Jukka - Initial contribution
*
*/
@NonNullByDefault
public class EntsoeConfigurationException extends Exception {
private static final long serialVersionUID = 1L;
public EntsoeConfigurationException(String message) {
super(message);
}
public EntsoeConfigurationException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.entsoe.internal.exception;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Miika Jukka - Initial contribution
*
*/
@NonNullByDefault
public class EntsoeResponseException extends Exception {
private static final long serialVersionUID = 1L;
public EntsoeResponseException(String message) {
super(message);
}
public EntsoeResponseException(String message, Throwable cause) {
super(message, cause);
}
public EntsoeResponseException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.entsoe.internal.exception;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Jørgen Melhus - Initial contribution
*
*/
@NonNullByDefault
public class EntsoeResponseMapException extends EntsoeConfigurationException {
private static final long serialVersionUID = 1L;
public EntsoeResponseMapException(String message) {
super(message);
}
public EntsoeResponseMapException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="entsoe" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>ENTSO-E Binding</name>
<description>This is the binding for European Network of Transmission System Operators (ENTSO-E) to receive electricity
market spot prices.
</description>
<connection>cloud</connection>
</addon:addon>

View File

@ -0,0 +1,142 @@
# add-on
addon.entsoe.name = ENTSO-E Binding
addon.entsoe.description = This is the binding for European Network of Transmission System Operators (ENTSO-E) to receive electricity market spot prices.
# thing types
thing-type.entsoe.day-ahead.label = Day-ahead prices
thing-type.entsoe.day-ahead.description = Hourly spot prices of the electricity market from European Network of Transmission System Operators (ENTSO-E)
# thing types config
thing-type.config.entsoe.day-ahead.area.label = Area
thing-type.config.entsoe.day-ahead.area.description = Choose from list or enter custom EIC area. See https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_areas and https://www.entsoe.eu/data/energy-identification-codes-eic/eic-approved-codes/ (Area Y, Bidding Zone)
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A39I = EE Estonia
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A44P = SE1 Swedish Elspot Area 1
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A45N = SE2 Swedish Elspot Area 2
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A46L = SE3 Swedish Elspot Area 3
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A47J = SE4 Swedish Elspot Area 4
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A48H = NO5 Norwegian Area Elspot Area 5
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A50U = KALININGRAD_AREA Kaliningrad area
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A51S = BELARUS_AREA Belarus area
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A55K = LT_BEL_IMP_AREA Lithuania-Belarus Import Area
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A56I = LT_BEL_EXP_AREA Lithuania-Belarus Export Area
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A57G = GB_N2EX_PRICZONE Bidding Zone - Great Britain (N2EX)
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A58E = GB_APX_PRICEZONE Bidding Zone - Great Britain (APX)
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A59C = SEM Ireland and Northern Ireland
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A63L = DE_AT_LU Germany_Austria_Luxemburg
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A66F = IT-GR Italy Greece
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A67D = IT-NORTH_SI Italy North_Slovenia
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A68B = IT-NORTH_CH Italy North_Switzerland
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A70O = IT-CENTRE_NORTH Italy Centre-North
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A71M = IT-CENTRE_SOUTH Italy Centre-South
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A73I = IT-NORTH Italy North
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A74G = IT-SARDINIA Italy Sardinia
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A75E = IT-SICILY Italy Sicily
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A788 = IT-SOUTH Italy South
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A80L = IT-NORTH_AT Italy North_Austria
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A81J = IT-NORTH_FR Italy North_France
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A82H = DE_LU Germany_Luxemburg
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A877 = IT-MT Italy_Malta
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A885 = IT-SACO_AC Italy Saco_AC
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A893 = IT-SACODC Italy Saco_DC
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A90I = IT-MONFALCONE Italy Monfalcone
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A958 = ICELAND Member State Iceland
thing-type.config.entsoe.day-ahead.area.option.10Y1001A1001A990 = MOLDOVA Moldova
thing-type.config.entsoe.day-ahead.area.option.10Y1001C--000182 = UA-IPS Ukraine IPS
thing-type.config.entsoe.day-ahead.area.option.10Y1001C--000611 = IT-MONTENEGRO BIDDING ZONE ITALY-MONTENEGRO
thing-type.config.entsoe.day-ahead.area.option.10Y1001C--00096J = IT-CALABRIA Italy Calabria
thing-type.config.entsoe.day-ahead.area.option.10Y1001C--00098F = FR_UK_IFA_BZN IFA Bidding Zone
thing-type.config.entsoe.day-ahead.area.option.10Y1001C--00100H = CAKOSTT Control Area Kosovo*
thing-type.config.entsoe.day-ahead.area.option.10Y1001C--001219 = NO_FICT_AREA_2A Norwegian Fictitious Area 2A
thing-type.config.entsoe.day-ahead.area.option.10Y1001C--00146U = NORDIC_SYNC_AREA Nordic Synchronous Area
thing-type.config.entsoe.day-ahead.area.option.10Y1001C--00148Q = SE3A Swedish Fictitious Area SE3A
thing-type.config.entsoe.day-ahead.area.option.10YAL-KESH-----5 = AL Albania
thing-type.config.entsoe.day-ahead.area.option.10YAT-APG------L = AT Austria
thing-type.config.entsoe.day-ahead.area.option.10YBA-JPCC-----D = BA Bosnia and Herzegovina
thing-type.config.entsoe.day-ahead.area.option.10YBE----------2 = BE Belgium
thing-type.config.entsoe.day-ahead.area.option.10YCA-BULGARIA-R = BG Bulgaria
thing-type.config.entsoe.day-ahead.area.option.10YCH-SWISSGRIDZ = CH Switzerland
thing-type.config.entsoe.day-ahead.area.option.10YCS-CG-TSO---S = ME Montenegro
thing-type.config.entsoe.day-ahead.area.option.10YCS-SERBIATSOV = RS Serbia
thing-type.config.entsoe.day-ahead.area.option.10YCY-1001A0003J = CY Cyprus
thing-type.config.entsoe.day-ahead.area.option.10YCZ-CEPS-----N = CZ Czech Republic
thing-type.config.entsoe.day-ahead.area.option.10YDK-1--------W = DK1 Denmark DK1
thing-type.config.entsoe.day-ahead.area.option.10YDK-2--------M = DK2 Denmark DK2
thing-type.config.entsoe.day-ahead.area.option.10YES-REE------0 = ES Spain
thing-type.config.entsoe.day-ahead.area.option.10YFI-1--------U = FI Finland
thing-type.config.entsoe.day-ahead.area.option.10YFR-RTE------C = FR France
thing-type.config.entsoe.day-ahead.area.option.10YGB----------A = GB Great Britain
thing-type.config.entsoe.day-ahead.area.option.10YGR-HTSO-----Y = GR Greece
thing-type.config.entsoe.day-ahead.area.option.10YHR-HEP------M = HR Croatia
thing-type.config.entsoe.day-ahead.area.option.10YHU-MAVIR----U = HU Hungary
thing-type.config.entsoe.day-ahead.area.option.10YLT-1001A0008Q = LT Lithuania
thing-type.config.entsoe.day-ahead.area.option.10YLV-1001A00074 = LV Latvia
thing-type.config.entsoe.day-ahead.area.option.10YMK-MEPSO----8 = MK FYROM
thing-type.config.entsoe.day-ahead.area.option.10YNL----------L = NL Netherlands
thing-type.config.entsoe.day-ahead.area.option.10YNO-1--------2 = NO1 Norwegian Area Elspot Area 1
thing-type.config.entsoe.day-ahead.area.option.10YNO-2--------T = NO2 Norwegian Area Elspot Area 2
thing-type.config.entsoe.day-ahead.area.option.10YNO-3--------J = NO3 Norwegian Area Elspot Area 3
thing-type.config.entsoe.day-ahead.area.option.10YNO-4--------9 = NO4 Norwegian Area Elspot Area 4
thing-type.config.entsoe.day-ahead.area.option.10YPL-AREA-----S = PL Poland
thing-type.config.entsoe.day-ahead.area.option.10YPT-REN------W = PT Portugal
thing-type.config.entsoe.day-ahead.area.option.10YRO-TEL------P = RO Romania
thing-type.config.entsoe.day-ahead.area.option.10YSI-ELES-----O = SI Slovenia
thing-type.config.entsoe.day-ahead.area.option.10YSK-SEPS-----K = SK Slovak Republic
thing-type.config.entsoe.day-ahead.area.option.10YTR-TEIAS----W = TEIAS_AREA TEIAS Area
thing-type.config.entsoe.day-ahead.area.option.10YUA-WEPS-----0 = UA-BEI Ukrainian Area of Burshtyn island
thing-type.config.entsoe.day-ahead.area.option.14Y----0000041-W = AT-EXAAMC Virtual Bidding Zone EXAA
thing-type.config.entsoe.day-ahead.area.option.14YCCPADATENMLDW = CCPACCP1 CCP Austria GmbH
thing-type.config.entsoe.day-ahead.area.option.14YEXAADATENMLDU = EXAACCP EXAA Abwicklungsstelle für Energieprodukte AG
thing-type.config.entsoe.day-ahead.area.option.17Y000000930808E = FR_UK_IFA2000_1 vhub_UK_IFA2000_link1
thing-type.config.entsoe.day-ahead.area.option.17Y000000930809C = FR_FR_IFA2000_1 vhub_FR_IFA2000_link1
thing-type.config.entsoe.day-ahead.area.option.17Y000000930810R = FR_UK_IFA2000_2 vhub_UK_IFA2000_link2
thing-type.config.entsoe.day-ahead.area.option.17Y000000930811P = FR_FR_IFA2000_2 vhub_FR_IFA2000_link2
thing-type.config.entsoe.day-ahead.area.option.17Y000000930814J = FR_UK_IFA2 vhub_UK_IFA2
thing-type.config.entsoe.day-ahead.area.option.17Y000000930815H = FR_FR_IFA2 vhub_FR_IFA2
thing-type.config.entsoe.day-ahead.area.option.17Y000000930816F = FR_IT_SAV_PIE vhub_IT_Savoie_Piemont
thing-type.config.entsoe.day-ahead.area.option.17Y000000930817D = FR_FR_SAV_PIE vhub_FR_Savoie_Piemont
thing-type.config.entsoe.day-ahead.area.option.17Y0000009369493 = FR_UK_IFA2_V_BZN IFA2_virtual_BZN
thing-type.config.entsoe.day-ahead.area.option.44Y-00000000160K = FI_FS Fingrid Oyj
thing-type.config.entsoe.day-ahead.area.option.44Y-00000000161I = FI_EL Fingrid Oyj
thing-type.config.entsoe.day-ahead.area.option.45Y000000000001C = DKW-NO2 DKW-NO2 virtual Bidding Zone Border
thing-type.config.entsoe.day-ahead.area.option.45Y000000000002A = DKW-SE3 DKW-SE3 virtual Bidding Zone Border
thing-type.config.entsoe.day-ahead.area.option.45Y0000000000038 = DKW-DKE DKW-DKE virtual Bidding Zone Border
thing-type.config.entsoe.day-ahead.area.option.46Y000000000001Y = SE3_FS SE3_FS (SE3, Fennoskan)
thing-type.config.entsoe.day-ahead.area.option.46Y000000000002W = SE3_KS SE3_KS (SE3, Kontiskan)
thing-type.config.entsoe.day-ahead.area.option.46Y000000000003U = SE4_SP SE4_SP (SE4, Swepol link)
thing-type.config.entsoe.day-ahead.area.option.46Y000000000004S = SE4_NB SE4_NB (SE4, Nordbalt)
thing-type.config.entsoe.day-ahead.area.option.46Y000000000005Q = SE4_BC SE4_BC (SE4, Baltic Cable)
thing-type.config.entsoe.day-ahead.area.option.46Y000000000007M = CUT_AREA_SE3LS Cut area SE3LS
thing-type.config.entsoe.day-ahead.area.option.46Y000000000008K = CUT_AREA_SE3 Cut area SE3
thing-type.config.entsoe.day-ahead.area.option.46Y000000000009I = CUTCOR_SE3LS-SE3 Cut corridor SE3LS-SE3
thing-type.config.entsoe.day-ahead.area.option.46Y000000000015N = VBZ_SE3-SE4_ACSW Virtual bidding zone border SE3-SE4 AC+SWL
thing-type.config.entsoe.day-ahead.area.option.46Y000000000016L = VBZ_SE4-SE3_ACSW Virtual bidding zone border SE4-SE3 AC+SWL
thing-type.config.entsoe.day-ahead.area.option.46Y000000000017J = SE3_SWL SE3_SWL (SE3, Sydvastlanken)
thing-type.config.entsoe.day-ahead.area.option.46Y000000000018H = SE4_SWL SE4_SWL (SE4, Sydvastlanken)
thing-type.config.entsoe.day-ahead.area.option.46Y000000000019F = CUTCOR_SE3A-SE3 Cut corridor SE3A-SE3
thing-type.config.entsoe.day-ahead.area.option.50Y0JVU59B4JWQCU = NO_NO2NSL Virtual Bidding Zone NO2NSL
thing-type.config.entsoe.day-ahead.area.option.50Y73EMZ34CQL9AJ = NO_NO2_NL Virtual Bidding Zone NO2-NL
thing-type.config.entsoe.day-ahead.area.option.50YCUY85S1HH29EK = NO_NO2_DK1 Virtual Bidding Zone NO2-DK1
thing-type.config.entsoe.day-ahead.area.option.50Y-HTS3792HUOAC = NO_NO2-GB Virtual bidding Zone NO2-GB
thing-type.config.entsoe.day-ahead.area.option.50YNBFFTWZRAHA3P = NO_NO2-DE Virtual bidding Zone NO2-DE
thing-type.config.entsoe.day-ahead.area.option.67Y-VISKOL-2003T = VISKOL2003 VISKOL DOO NOVI SAD SRBIJA
thing-type.config.entsoe.day-ahead.historicDays.label = Historic Data
thing-type.config.entsoe.day-ahead.historicDays.description = Specify number of days to download historic data of energy spot prices (historic data will get exchange rate of today)
thing-type.config.entsoe.day-ahead.requestTimeout.label = Request Timeout
thing-type.config.entsoe.day-ahead.requestTimeout.description = Request timeout value in seconds
thing-type.config.entsoe.day-ahead.resolution.label = Resolution
thing-type.config.entsoe.day-ahead.resolution.description = Data resolution
thing-type.config.entsoe.day-ahead.resolution.option.PT15M = 15 minutes
thing-type.config.entsoe.day-ahead.resolution.option.PT30M = 30 minutes
thing-type.config.entsoe.day-ahead.resolution.option.PT60M = 60 minutes
thing-type.config.entsoe.day-ahead.securityToken.label = Security Token
thing-type.config.entsoe.day-ahead.securityToken.description = Security token for ENTSO-E API
thing-type.config.entsoe.day-ahead.spotPricesAvailableCetHour.label = Publish Hour
thing-type.config.entsoe.day-ahead.spotPricesAvailableCetHour.description = CET hour spot prices will get published on ENTSO-E. Usually at 13 CET.
# channel types
channel-type.entsoe.spot-price.label = Spot Price
channel-type.entsoe.trigger-prices-received.label = Trigger Prices Received

View File

@ -0,0 +1,194 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="entsoe"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="day-ahead">
<label>Day-ahead prices</label>
<description>Hourly spot prices of the electricity market from European Network of Transmission System Operators
(ENTSO-E)</description>
<category>WebService</category>
<channels>
<channel id="spot-price" typeId="spot-price"/>
<channel id="prices-received" typeId="trigger-prices-received"/>
</channels>
<config-description>
<parameter name="securityToken" type="text" required="true">
<context>password</context>
<label>Security Token</label>
<description>Security token for ENTSO-E API</description>
</parameter>
<parameter name="area" type="text" required="true">
<label>Area</label>
<description>Choose from list or enter custom EIC area. See
https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_areas
and
https://www.entsoe.eu/data/energy-identification-codes-eic/eic-approved-codes/ (Area Y, Bidding Zone)</description>
<limitToOptions>false</limitToOptions>
<options>
<!--
url: https://www.entsoe.eu/data/energy-identification-codes-eic/eic-approved-codes/
filter:
EIC Type Code: Area Y
Function: Bidding Zone
-->
<option value="10Y1001A1001A39I">EE Estonia</option>
<option value="10Y1001A1001A44P">SE1 Swedish Elspot Area 1</option>
<option value="10Y1001A1001A45N">SE2 Swedish Elspot Area 2</option>
<option value="10Y1001A1001A46L">SE3 Swedish Elspot Area 3</option>
<option value="10Y1001A1001A47J">SE4 Swedish Elspot Area 4</option>
<option value="10Y1001A1001A48H">NO5 Norwegian Area Elspot Area 5</option>
<option value="10Y1001A1001A50U">KALININGRAD_AREA Kaliningrad area</option>
<option value="10Y1001A1001A51S">BELARUS_AREA Belarus area</option>
<option value="10Y1001A1001A55K">LT_BEL_IMP_AREA Lithuania-Belarus Import Area</option>
<option value="10Y1001A1001A56I">LT_BEL_EXP_AREA Lithuania-Belarus Export Area</option>
<option value="10Y1001A1001A57G">GB_N2EX_PRICZONE Bidding Zone - Great Britain (N2EX)</option>
<option value="10Y1001A1001A58E">GB_APX_PRICEZONE Bidding Zone - Great Britain (APX)</option>
<option value="10Y1001A1001A59C">SEM Ireland and Northern Ireland</option>
<option value="10Y1001A1001A63L">DE_AT_LU Germany_Austria_Luxemburg</option>
<option value="10Y1001A1001A66F">IT-GR Italy Greece</option>
<option value="10Y1001A1001A67D">IT-NORTH_SI Italy North_Slovenia</option>
<option value="10Y1001A1001A68B">IT-NORTH_CH Italy North_Switzerland</option>
<option value="10Y1001A1001A70O">IT-CENTRE_NORTH Italy Centre-North</option>
<option value="10Y1001A1001A71M">IT-CENTRE_SOUTH Italy Centre-South</option>
<option value="10Y1001A1001A73I">IT-NORTH Italy North</option>
<option value="10Y1001A1001A74G">IT-SARDINIA Italy Sardinia</option>
<option value="10Y1001A1001A75E">IT-SICILY Italy Sicily</option>
<option value="10Y1001A1001A788">IT-SOUTH Italy South</option>
<option value="10Y1001A1001A80L">IT-NORTH_AT Italy North_Austria</option>
<option value="10Y1001A1001A81J">IT-NORTH_FR Italy North_France</option>
<option value="10Y1001A1001A82H">DE_LU Germany_Luxemburg</option>
<option value="10Y1001A1001A877">IT-MT Italy_Malta</option>
<option value="10Y1001A1001A885">IT-SACO_AC Italy Saco_AC</option>
<option value="10Y1001A1001A893">IT-SACODC Italy Saco_DC</option>
<option value="10Y1001A1001A90I">IT-MONFALCONE Italy Monfalcone</option>
<option value="10Y1001A1001A958">ICELAND Member State Iceland</option>
<option value="10Y1001A1001A990">MOLDOVA Moldova</option>
<option value="10Y1001C--000182">UA-IPS Ukraine IPS</option>
<option value="10Y1001C--000611">IT-MONTENEGRO BIDDING ZONE ITALY-MONTENEGRO</option>
<option value="10Y1001C--00096J">IT-CALABRIA Italy Calabria</option>
<option value="10Y1001C--00098F">FR_UK_IFA_BZN IFA Bidding Zone</option>
<option value="10Y1001C--00100H">CAKOSTT Control Area Kosovo*</option>
<option value="10Y1001C--001219">NO_FICT_AREA_2A Norwegian Fictitious Area 2A</option>
<option value="10Y1001C--00146U">NORDIC_SYNC_AREA Nordic Synchronous Area</option>
<option value="10Y1001C--00148Q">SE3A Swedish Fictitious Area SE3A</option>
<option value="10YAL-KESH-----5">AL Albania</option>
<option value="10YAT-APG------L">AT Austria</option>
<option value="10YBA-JPCC-----D">BA Bosnia and Herzegovina</option>
<option value="10YBE----------2">BE Belgium</option>
<option value="10YCA-BULGARIA-R">BG Bulgaria</option>
<option value="10YCH-SWISSGRIDZ">CH Switzerland</option>
<option value="10YCS-CG-TSO---S">ME Montenegro</option>
<option value="10YCS-SERBIATSOV">RS Serbia</option>
<option value="10YCY-1001A0003J">CY Cyprus</option>
<option value="10YCZ-CEPS-----N">CZ Czech Republic</option>
<option value="10YDK-1--------W">DK1 Denmark DK1</option>
<option value="10YDK-2--------M">DK2 Denmark DK2</option>
<option value="10YES-REE------0">ES Spain</option>
<option value="10YFI-1--------U">FI Finland</option>
<option value="10YFR-RTE------C">FR France</option>
<option value="10YGB----------A">GB Great Britain</option>
<option value="10YGR-HTSO-----Y">GR Greece</option>
<option value="10YHR-HEP------M">HR Croatia</option>
<option value="10YHU-MAVIR----U">HU Hungary</option>
<option value="10YLT-1001A0008Q">LT Lithuania</option>
<option value="10YLV-1001A00074">LV Latvia</option>
<option value="10YMK-MEPSO----8">MK FYROM</option>
<option value="10YNL----------L">NL Netherlands</option>
<option value="10YNO-1--------2">NO1 Norwegian Area Elspot Area 1</option>
<option value="10YNO-2--------T">NO2 Norwegian Area Elspot Area 2</option>
<option value="10YNO-3--------J">NO3 Norwegian Area Elspot Area 3</option>
<option value="10YNO-4--------9">NO4 Norwegian Area Elspot Area 4</option>
<option value="10YPL-AREA-----S">PL Poland</option>
<option value="10YPT-REN------W">PT Portugal</option>
<option value="10YRO-TEL------P">RO Romania</option>
<option value="10YSI-ELES-----O">SI Slovenia</option>
<option value="10YSK-SEPS-----K">SK Slovak Republic</option>
<option value="10YTR-TEIAS----W">TEIAS_AREA TEIAS Area</option>
<option value="10YUA-WEPS-----0">UA-BEI Ukrainian Area of Burshtyn island</option>
<option value="14Y----0000041-W">AT-EXAAMC Virtual Bidding Zone EXAA</option>
<option value="14YCCPADATENMLDW">CCPACCP1 CCP Austria GmbH</option>
<option value="14YEXAADATENMLDU">EXAACCP EXAA Abwicklungsstelle für Energieprodukte AG</option>
<option value="17Y000000930808E">FR_UK_IFA2000_1 vhub_UK_IFA2000_link1</option>
<option value="17Y000000930809C">FR_FR_IFA2000_1 vhub_FR_IFA2000_link1</option>
<option value="17Y000000930810R">FR_UK_IFA2000_2 vhub_UK_IFA2000_link2</option>
<option value="17Y000000930811P">FR_FR_IFA2000_2 vhub_FR_IFA2000_link2</option>
<option value="17Y000000930814J">FR_UK_IFA2 vhub_UK_IFA2</option>
<option value="17Y000000930815H">FR_FR_IFA2 vhub_FR_IFA2</option>
<option value="17Y000000930816F">FR_IT_SAV_PIE vhub_IT_Savoie_Piemont</option>
<option value="17Y000000930817D">FR_FR_SAV_PIE vhub_FR_Savoie_Piemont</option>
<option value="17Y0000009369493">FR_UK_IFA2_V_BZN IFA2_virtual_BZN</option>
<option value="44Y-00000000160K">FI_FS Fingrid Oyj</option>
<option value="44Y-00000000161I">FI_EL Fingrid Oyj</option>
<option value="45Y000000000001C">DKW-NO2 DKW-NO2 virtual Bidding Zone Border</option>
<option value="45Y000000000002A">DKW-SE3 DKW-SE3 virtual Bidding Zone Border</option>
<option value="45Y0000000000038">DKW-DKE DKW-DKE virtual Bidding Zone Border</option>
<option value="46Y000000000001Y">SE3_FS SE3_FS (SE3, Fennoskan)</option>
<option value="46Y000000000002W">SE3_KS SE3_KS (SE3, Kontiskan)</option>
<option value="46Y000000000003U">SE4_SP SE4_SP (SE4, Swepol link)</option>
<option value="46Y000000000004S">SE4_NB SE4_NB (SE4, Nordbalt)</option>
<option value="46Y000000000005Q">SE4_BC SE4_BC (SE4, Baltic Cable)</option>
<option value="46Y000000000007M">CUT_AREA_SE3LS Cut area SE3LS</option>
<option value="46Y000000000008K">CUT_AREA_SE3 Cut area SE3</option>
<option value="46Y000000000009I">CUTCOR_SE3LS-SE3 Cut corridor SE3LS-SE3</option>
<option value="46Y000000000015N">VBZ_SE3-SE4_ACSW Virtual bidding zone border SE3-SE4 AC+SWL</option>
<option value="46Y000000000016L">VBZ_SE4-SE3_ACSW Virtual bidding zone border SE4-SE3 AC+SWL</option>
<option value="46Y000000000017J">SE3_SWL SE3_SWL (SE3, Sydvastlanken)</option>
<option value="46Y000000000018H">SE4_SWL SE4_SWL (SE4, Sydvastlanken)</option>
<option value="46Y000000000019F">CUTCOR_SE3A-SE3 Cut corridor SE3A-SE3</option>
<option value="50Y0JVU59B4JWQCU">NO_NO2NSL Virtual Bidding Zone NO2NSL</option>
<option value="50Y73EMZ34CQL9AJ">NO_NO2_NL Virtual Bidding Zone NO2-NL</option>
<option value="50YCUY85S1HH29EK">NO_NO2_DK1 Virtual Bidding Zone NO2-DK1</option>
<option value="50Y-HTS3792HUOAC">NO_NO2-GB Virtual bidding Zone NO2-GB</option>
<option value="50YNBFFTWZRAHA3P">NO_NO2-DE Virtual bidding Zone NO2-DE</option>
<option value="67Y-VISKOL-2003T">VISKOL2003 VISKOL DOO NOVI SAD SRBIJA</option>
</options>
</parameter>
<parameter name="historicDays" type="integer" min="1" max="365" required="false">
<label>Historic Data</label>
<default>1</default>
<description>Specify number of days to download historic data of energy spot prices (historic data will get exchange
rate of today)</description>
</parameter>
<parameter name="resolution" type="text" required="false">
<label>Resolution</label>
<default>PT60M</default>
<description>Data resolution</description>
<limitToOptions>true</limitToOptions>
<options>
<option value="PT15M">15 minutes</option>
<option value="PT30M">30 minutes</option>
<option value="PT60M">60 minutes</option>
</options>
</parameter>
<parameter name="spotPricesAvailableCetHour" type="integer" min="0" max="23" required="false">
<label>Publish Hour</label>
<default>13</default>
<description>CET hour spot prices will get published on ENTSO-E. Usually at 13 CET.</description>
<advanced>true</advanced>
</parameter>
<parameter name="requestTimeout" type="integer" min="10" max="300" required="false">
<label>Request Timeout</label>
<default>30</default>
<description>Request timeout value in seconds</description>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<channel-type id="spot-price">
<item-type>Number:EnergyPrice</item-type>
<label>Spot Price</label>
<category>Price</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="trigger-prices-received">
<kind>trigger</kind>
<label>Trigger Prices Received</label>
</channel-type>
</thing:thing-descriptions>

View File

@ -138,6 +138,7 @@
<module>org.openhab.binding.enigma2</module> <module>org.openhab.binding.enigma2</module>
<module>org.openhab.binding.enocean</module> <module>org.openhab.binding.enocean</module>
<module>org.openhab.binding.enphase</module> <module>org.openhab.binding.enphase</module>
<module>org.openhab.binding.entsoe</module>
<module>org.openhab.binding.enturno</module> <module>org.openhab.binding.enturno</module>
<module>org.openhab.binding.ephemeris</module> <module>org.openhab.binding.ephemeris</module>
<module>org.openhab.binding.epsonprojector</module> <module>org.openhab.binding.epsonprojector</module>