mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
Add CO2 emission channels (#16330)
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
55c84385ab
commit
a26a84c958
@ -47,22 +47,24 @@ It will not impact channels, see [Electricity Tax](#electricity-tax) for further
|
|||||||
|
|
||||||
### Channel Group `electricity`
|
### Channel Group `electricity`
|
||||||
|
|
||||||
| Channel | Type | Description | Advanced |
|
| Channel | Type | Description |
|
||||||
|--------------------------|--------------------|--------------------------------------------------------------------------------|----------|
|
|--------------------------|--------------------------|----------------------------------------------------------------------------------------|
|
||||||
| spot-price | Number:EnergyPrice | Spot price in DKK or EUR per kWh | no |
|
| spot-price | Number:EnergyPrice | Spot price in DKK or EUR per kWh |
|
||||||
| grid-tariff | Number:EnergyPrice | Grid tariff in DKK per kWh. Only available when `gridCompanyGLN` is configured | no |
|
| grid-tariff | Number:EnergyPrice | Grid tariff in DKK per kWh. Only available when `gridCompanyGLN` is configured |
|
||||||
| system-tariff | Number:EnergyPrice | System tariff in DKK per kWh | no |
|
| system-tariff | Number:EnergyPrice | System tariff in DKK per kWh |
|
||||||
| transmission-grid-tariff | Number:EnergyPrice | Transmission grid tariff in DKK per kWh | no |
|
| transmission-grid-tariff | Number:EnergyPrice | Transmission grid tariff in DKK per kWh |
|
||||||
| electricity-tax | Number:EnergyPrice | Electricity tax in DKK per kWh | no |
|
| electricity-tax | Number:EnergyPrice | Electricity tax in DKK per kWh |
|
||||||
| reduced-electricity-tax | Number:EnergyPrice | Reduced electricity tax in DKK per kWh. For electric heating customers only | no |
|
| reduced-electricity-tax | Number:EnergyPrice | Reduced electricity tax in DKK per kWh. For electric heating customers only |
|
||||||
|
| co2-emission-prognosis | Number:EmissionIntensity | Estimated prognosis for CO₂ emission following the day-ahead market in g/kWh |
|
||||||
|
| co2-emission-realtime | Number:EmissionIntensity | Near up-to-date history for CO₂ emission from electricity consumed in Denmark in g/kWh |
|
||||||
|
|
||||||
_Please note:_ There is no channel providing the 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.
|
Instead, create a group item with `SUM` as aggregate function and add the individual price items as children.
|
||||||
This has the following advantages:
|
This has the following advantages:
|
||||||
|
|
||||||
- Full customization possible: Freely choose the channels which should be included in the total.
|
- Full customization possible: Freely choose the channels which should be included in the total (even between different bindings).
|
||||||
- An additional item containing the kWh fee from your electricity supplier can be added also.
|
- Spot price can be configured in EUR while tariffs are in DKK (and currency conversions are performed outside the binding).
|
||||||
- Spot price can be configured in EUR while tariffs are in DKK.
|
- An additional item containing the kWh fee from your electricity supplier can be added also (and it can be dynamic).
|
||||||
|
|
||||||
If you want electricity tax included in your total price, please add either `electricity-tax` or `reduced-electricity-tax` to the group - depending on which one applies.
|
If you want electricity tax included in your total price, please add either `electricity-tax` or `reduced-electricity-tax` to the group - depending on which one applies.
|
||||||
See [Electricity Tax](#electricity-tax) for further information.
|
See [Electricity Tax](#electricity-tax) for further information.
|
||||||
@ -141,6 +143,17 @@ This reduced rate is made available through channel `reduced-electricity-tax`.
|
|||||||
The binding cannot determine or manage rate variations as they depend on metering data.
|
The binding cannot determine or manage rate variations as they depend on metering data.
|
||||||
Usually `reduced-electricity-tax` is preferred when using electricity for heating.
|
Usually `reduced-electricity-tax` is preferred when using electricity for heating.
|
||||||
|
|
||||||
|
#### CO₂ Emissions
|
||||||
|
|
||||||
|
Data for the CO₂ emission channels is published as time series with a resolution of 5 minutes.
|
||||||
|
|
||||||
|
Channel `co2-emission-realtime` provides near up-to-date historic emission and is refreshed every 5 minutes.
|
||||||
|
When the binding is started, or a new item is linked, or a linked item receives an update command, historic data for the last 24 hours is provided in addition to the current value.
|
||||||
|
|
||||||
|
Channel `co2-emission-prognosis` provides estimated prognosis for future emissions and is refreshed every 15 minutes.
|
||||||
|
Depending on the time of the day, an update of the prognosis may include estimates for more than 9 hours, but every update will have at least 9 hours into the future.
|
||||||
|
A persistence configuration is required for this channel.
|
||||||
|
|
||||||
## Thing Actions
|
## Thing Actions
|
||||||
|
|
||||||
Thing actions can be used to perform calculations as well as import prices directly into rules without relying on persistence.
|
Thing actions can be used to perform calculations as well as import prices directly into rules without relying on persistence.
|
||||||
|
@ -38,8 +38,11 @@ import org.eclipse.jetty.http.HttpMethod;
|
|||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.openhab.binding.energidataservice.internal.api.ChargeType;
|
import org.openhab.binding.energidataservice.internal.api.ChargeType;
|
||||||
import org.openhab.binding.energidataservice.internal.api.DatahubTariffFilter;
|
import org.openhab.binding.energidataservice.internal.api.DatahubTariffFilter;
|
||||||
|
import org.openhab.binding.energidataservice.internal.api.Dataset;
|
||||||
import org.openhab.binding.energidataservice.internal.api.DateQueryParameter;
|
import org.openhab.binding.energidataservice.internal.api.DateQueryParameter;
|
||||||
import org.openhab.binding.energidataservice.internal.api.GlobalLocationNumber;
|
import org.openhab.binding.energidataservice.internal.api.GlobalLocationNumber;
|
||||||
|
import org.openhab.binding.energidataservice.internal.api.dto.CO2EmissionRecord;
|
||||||
|
import org.openhab.binding.energidataservice.internal.api.dto.CO2EmissionRecords;
|
||||||
import org.openhab.binding.energidataservice.internal.api.dto.DatahubPricelistRecord;
|
import org.openhab.binding.energidataservice.internal.api.dto.DatahubPricelistRecord;
|
||||||
import org.openhab.binding.energidataservice.internal.api.dto.DatahubPricelistRecords;
|
import org.openhab.binding.energidataservice.internal.api.dto.DatahubPricelistRecords;
|
||||||
import org.openhab.binding.energidataservice.internal.api.dto.ElspotpriceRecord;
|
import org.openhab.binding.energidataservice.internal.api.dto.ElspotpriceRecord;
|
||||||
@ -66,9 +69,6 @@ public class ApiController {
|
|||||||
private static final String ENDPOINT = "https://api.energidataservice.dk/";
|
private static final String ENDPOINT = "https://api.energidataservice.dk/";
|
||||||
private static final String DATASET_PATH = "dataset/";
|
private static final String DATASET_PATH = "dataset/";
|
||||||
|
|
||||||
private static final String DATASET_NAME_SPOT_PRICES = "Elspotprices";
|
|
||||||
private static final String DATASET_NAME_DATAHUB_PRICELIST = "DatahubPricelist";
|
|
||||||
|
|
||||||
private static final String FILTER_KEY_PRICE_AREA = "PriceArea";
|
private static final String FILTER_KEY_PRICE_AREA = "PriceArea";
|
||||||
private static final String FILTER_KEY_CHARGE_TYPE = "ChargeType";
|
private static final String FILTER_KEY_CHARGE_TYPE = "ChargeType";
|
||||||
private static final String FILTER_KEY_CHARGE_TYPE_CODE = "ChargeTypeCode";
|
private static final String FILTER_KEY_CHARGE_TYPE_CODE = "ChargeTypeCode";
|
||||||
@ -111,7 +111,7 @@ public class ApiController {
|
|||||||
throw new IllegalArgumentException("Invalid currency " + currency.getCurrencyCode());
|
throw new IllegalArgumentException("Invalid currency " + currency.getCurrencyCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
Request request = httpClient.newRequest(ENDPOINT + DATASET_PATH + DATASET_NAME_SPOT_PRICES)
|
Request request = httpClient.newRequest(ENDPOINT + DATASET_PATH + Dataset.SpotPrices)
|
||||||
.timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) //
|
.timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) //
|
||||||
.param("start", start.toString()) //
|
.param("start", start.toString()) //
|
||||||
.param("filter", "{\"" + FILTER_KEY_PRICE_AREA + "\":\"" + priceArea + "\"}") //
|
.param("filter", "{\"" + FILTER_KEY_PRICE_AREA + "\":\"" + priceArea + "\"}") //
|
||||||
@ -119,23 +119,8 @@ public class ApiController {
|
|||||||
.agent(userAgent) //
|
.agent(userAgent) //
|
||||||
.method(HttpMethod.GET);
|
.method(HttpMethod.GET);
|
||||||
|
|
||||||
logger.trace("GET request for {}", request.getURI());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ContentResponse response = request.send();
|
String responseContent = sendRequest(request, properties);
|
||||||
|
|
||||||
updatePropertiesFromResponse(response, properties);
|
|
||||||
|
|
||||||
int status = response.getStatus();
|
|
||||||
if (!HttpStatus.isSuccess(status)) {
|
|
||||||
throw new DataServiceException("The request failed with HTTP error " + status, status);
|
|
||||||
}
|
|
||||||
String responseContent = response.getContentAsString();
|
|
||||||
if (responseContent.isEmpty()) {
|
|
||||||
throw new DataServiceException("Empty response");
|
|
||||||
}
|
|
||||||
logger.trace("Response content: '{}'", responseContent);
|
|
||||||
|
|
||||||
ElspotpriceRecords records = gson.fromJson(responseContent, ElspotpriceRecords.class);
|
ElspotpriceRecords records = gson.fromJson(responseContent, ElspotpriceRecords.class);
|
||||||
if (records == null) {
|
if (records == null) {
|
||||||
throw new DataServiceException("Error parsing response");
|
throw new DataServiceException("Error parsing response");
|
||||||
@ -153,6 +138,27 @@ public class ApiController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String sendRequest(Request request, Map<String, String> properties)
|
||||||
|
throws TimeoutException, ExecutionException, InterruptedException, DataServiceException {
|
||||||
|
logger.trace("GET request for {}", request.getURI());
|
||||||
|
|
||||||
|
ContentResponse response = request.send();
|
||||||
|
|
||||||
|
updatePropertiesFromResponse(response, properties);
|
||||||
|
|
||||||
|
int status = response.getStatus();
|
||||||
|
if (!HttpStatus.isSuccess(status)) {
|
||||||
|
throw new DataServiceException("The request failed with HTTP error " + status, status);
|
||||||
|
}
|
||||||
|
String responseContent = response.getContentAsString();
|
||||||
|
if (responseContent.isEmpty()) {
|
||||||
|
throw new DataServiceException("Empty response");
|
||||||
|
}
|
||||||
|
logger.trace("Response content: '{}'", responseContent);
|
||||||
|
|
||||||
|
return responseContent;
|
||||||
|
}
|
||||||
|
|
||||||
private void updatePropertiesFromResponse(ContentResponse response, Map<String, String> properties) {
|
private void updatePropertiesFromResponse(ContentResponse response, Map<String, String> properties) {
|
||||||
HttpFields headers = response.getHeaders();
|
HttpFields headers = response.getHeaders();
|
||||||
String remainingCalls = headers.get(HEADER_REMAINING_CALLS);
|
String remainingCalls = headers.get(HEADER_REMAINING_CALLS);
|
||||||
@ -200,7 +206,7 @@ public class ApiController {
|
|||||||
filterMap.put(FILTER_KEY_NOTE, notes);
|
filterMap.put(FILTER_KEY_NOTE, notes);
|
||||||
}
|
}
|
||||||
|
|
||||||
Request request = httpClient.newRequest(ENDPOINT + DATASET_PATH + DATASET_NAME_DATAHUB_PRICELIST)
|
Request request = httpClient.newRequest(ENDPOINT + DATASET_PATH + Dataset.DatahubPricelist)
|
||||||
.timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) //
|
.timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) //
|
||||||
.param("filter", mapToFilter(filterMap)) //
|
.param("filter", mapToFilter(filterMap)) //
|
||||||
.param("columns", columns) //
|
.param("columns", columns) //
|
||||||
@ -212,23 +218,8 @@ public class ApiController {
|
|||||||
request = request.param("start", dateQueryParameter.toString());
|
request = request.param("start", dateQueryParameter.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace("GET request for {}", request.getURI());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ContentResponse response = request.send();
|
String responseContent = sendRequest(request, properties);
|
||||||
|
|
||||||
updatePropertiesFromResponse(response, properties);
|
|
||||||
|
|
||||||
int status = response.getStatus();
|
|
||||||
if (!HttpStatus.isSuccess(status)) {
|
|
||||||
throw new DataServiceException("The request failed with HTTP error " + status, status);
|
|
||||||
}
|
|
||||||
String responseContent = response.getContentAsString();
|
|
||||||
if (responseContent.isEmpty()) {
|
|
||||||
throw new DataServiceException("Empty response");
|
|
||||||
}
|
|
||||||
logger.trace("Response content: '{}'", responseContent);
|
|
||||||
|
|
||||||
DatahubPricelistRecords records = gson.fromJson(responseContent, DatahubPricelistRecords.class);
|
DatahubPricelistRecords records = gson.fromJson(responseContent, DatahubPricelistRecords.class);
|
||||||
if (records == null) {
|
if (records == null) {
|
||||||
throw new DataServiceException("Error parsing response");
|
throw new DataServiceException("Error parsing response");
|
||||||
@ -255,4 +246,48 @@ public class ApiController {
|
|||||||
e -> "\"" + e.getKey() + "\":[\"" + e.getValue().stream().collect(Collectors.joining("\",\"")) + "\"]")
|
e -> "\"" + e.getKey() + "\":[\"" + e.getValue().stream().collect(Collectors.joining("\",\"")) + "\"]")
|
||||||
.collect(Collectors.joining(",")) + "}";
|
.collect(Collectors.joining(",")) + "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve CO2 emissions for requested area.
|
||||||
|
*
|
||||||
|
* @param dataset Dataset to obtain
|
||||||
|
* @param priceArea Usually DK1 or DK2
|
||||||
|
* @param start Specifies the start point of the period for the data request
|
||||||
|
* @param properties Map of properties which will be updated with metadata from headers
|
||||||
|
* @return Records with 5 minute periods and emissions in g/kWh.
|
||||||
|
* @throws InterruptedException
|
||||||
|
* @throws DataServiceException
|
||||||
|
*/
|
||||||
|
public CO2EmissionRecord[] getCo2Emissions(Dataset dataset, String priceArea, DateQueryParameter start,
|
||||||
|
Map<String, String> properties) throws InterruptedException, DataServiceException {
|
||||||
|
if (dataset != Dataset.CO2Emission && dataset != Dataset.CO2EmissionPrognosis) {
|
||||||
|
throw new IllegalArgumentException("Invalid dataset " + dataset + " for getting CO2 emissions");
|
||||||
|
}
|
||||||
|
Request request = httpClient.newRequest(ENDPOINT + DATASET_PATH + dataset)
|
||||||
|
.timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS) //
|
||||||
|
.param("start", start.toString()) //
|
||||||
|
.param("filter", "{\"" + FILTER_KEY_PRICE_AREA + "\":\"" + priceArea + "\"}") //
|
||||||
|
.param("columns", "Minutes5UTC,CO2Emission") //
|
||||||
|
.param("sort", "Minutes5UTC DESC") //
|
||||||
|
.agent(userAgent) //
|
||||||
|
.method(HttpMethod.GET);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String responseContent = sendRequest(request, properties);
|
||||||
|
CO2EmissionRecords records = gson.fromJson(responseContent, CO2EmissionRecords.class);
|
||||||
|
if (records == null) {
|
||||||
|
throw new DataServiceException("Error parsing response");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (records.total() == 0 || Objects.isNull(records.records()) || records.records().length == 0) {
|
||||||
|
throw new DataServiceException("No records");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.stream(records.records()).filter(Objects::nonNull).toArray(CO2EmissionRecord[]::new);
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
throw new DataServiceException("Error parsing response", e);
|
||||||
|
} catch (TimeoutException | ExecutionException e) {
|
||||||
|
throw new DataServiceException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,10 @@ public class EnergiDataServiceBindingConstants {
|
|||||||
+ ChannelUID.CHANNEL_GROUP_SEPARATOR + "reduced-electricity-tax";
|
+ ChannelUID.CHANNEL_GROUP_SEPARATOR + "reduced-electricity-tax";
|
||||||
public static final String CHANNEL_TRANSMISSION_GRID_TARIFF = CHANNEL_GROUP_ELECTRICITY
|
public static final String CHANNEL_TRANSMISSION_GRID_TARIFF = CHANNEL_GROUP_ELECTRICITY
|
||||||
+ ChannelUID.CHANNEL_GROUP_SEPARATOR + "transmission-grid-tariff";
|
+ ChannelUID.CHANNEL_GROUP_SEPARATOR + "transmission-grid-tariff";
|
||||||
|
public static final String CHANNEL_CO2_EMISSION_PROGNOSIS = CHANNEL_GROUP_ELECTRICITY
|
||||||
|
+ ChannelUID.CHANNEL_GROUP_SEPARATOR + "co2-emission-prognosis";
|
||||||
|
public static final String CHANNEL_CO2_EMISSION_REALTIME = CHANNEL_GROUP_ELECTRICITY
|
||||||
|
+ ChannelUID.CHANNEL_GROUP_SEPARATOR + "co2-emission-realtime";
|
||||||
|
|
||||||
public static final Set<String> ELECTRICITY_CHANNELS = Set.of(CHANNEL_SPOT_PRICE, CHANNEL_GRID_TARIFF,
|
public static final Set<String> ELECTRICITY_CHANNELS = Set.of(CHANNEL_SPOT_PRICE, CHANNEL_GRID_TARIFF,
|
||||||
CHANNEL_SYSTEM_TARIFF, CHANNEL_TRANSMISSION_GRID_TARIFF, CHANNEL_ELECTRICITY_TAX,
|
CHANNEL_SYSTEM_TARIFF, CHANNEL_TRANSMISSION_GRID_TARIFF, CHANNEL_ELECTRICITY_TAX,
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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.energidataservice.internal.api;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Energi Data Service dataset.
|
||||||
|
*
|
||||||
|
* @author Jacob Laursen - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public enum Dataset {
|
||||||
|
SpotPrices("Elspotprices"),
|
||||||
|
DatahubPricelist("DatahubPricelist"),
|
||||||
|
CO2Emission("CO2Emis"),
|
||||||
|
CO2EmissionPrognosis("CO2EmisProg");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
Dataset(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 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.energidataservice.internal.api.dto;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record as part of {@link CO2EmissionRecords} from Energi Data Service.
|
||||||
|
*
|
||||||
|
* @author Jacob Laursen - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public record CO2EmissionRecord(@SerializedName("Minutes5UTC") Instant start,
|
||||||
|
@SerializedName("CO2Emission") BigDecimal emission) {
|
||||||
|
|
||||||
|
public Instant end() {
|
||||||
|
return start.plus(5, ChronoUnit.MINUTES);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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.energidataservice.internal.api.dto;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Received {@link CO2EmissionRecords} from Energi Data Service.
|
||||||
|
*
|
||||||
|
* @author Jacob Laursen - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public record CO2EmissionRecords(int total, String filters, int limit, String dataset, CO2EmissionRecord[] records) {
|
||||||
|
}
|
@ -47,9 +47,11 @@ import org.openhab.binding.energidataservice.internal.api.ChargeType;
|
|||||||
import org.openhab.binding.energidataservice.internal.api.ChargeTypeCode;
|
import org.openhab.binding.energidataservice.internal.api.ChargeTypeCode;
|
||||||
import org.openhab.binding.energidataservice.internal.api.DatahubTariffFilter;
|
import org.openhab.binding.energidataservice.internal.api.DatahubTariffFilter;
|
||||||
import org.openhab.binding.energidataservice.internal.api.DatahubTariffFilterFactory;
|
import org.openhab.binding.energidataservice.internal.api.DatahubTariffFilterFactory;
|
||||||
|
import org.openhab.binding.energidataservice.internal.api.Dataset;
|
||||||
import org.openhab.binding.energidataservice.internal.api.DateQueryParameter;
|
import org.openhab.binding.energidataservice.internal.api.DateQueryParameter;
|
||||||
import org.openhab.binding.energidataservice.internal.api.DateQueryParameterType;
|
import org.openhab.binding.energidataservice.internal.api.DateQueryParameterType;
|
||||||
import org.openhab.binding.energidataservice.internal.api.GlobalLocationNumber;
|
import org.openhab.binding.energidataservice.internal.api.GlobalLocationNumber;
|
||||||
|
import org.openhab.binding.energidataservice.internal.api.dto.CO2EmissionRecord;
|
||||||
import org.openhab.binding.energidataservice.internal.api.dto.DatahubPricelistRecord;
|
import org.openhab.binding.energidataservice.internal.api.dto.DatahubPricelistRecord;
|
||||||
import org.openhab.binding.energidataservice.internal.api.dto.ElspotpriceRecord;
|
import org.openhab.binding.energidataservice.internal.api.dto.ElspotpriceRecord;
|
||||||
import org.openhab.binding.energidataservice.internal.config.DatahubPriceConfiguration;
|
import org.openhab.binding.energidataservice.internal.config.DatahubPriceConfiguration;
|
||||||
@ -61,6 +63,7 @@ import org.openhab.core.i18n.TimeZoneProvider;
|
|||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
import org.openhab.core.library.unit.CurrencyUnits;
|
import org.openhab.core.library.unit.CurrencyUnits;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
import org.openhab.core.thing.Channel;
|
import org.openhab.core.thing.Channel;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
@ -85,6 +88,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class EnergiDataServiceHandler extends BaseThingHandler {
|
public class EnergiDataServiceHandler extends BaseThingHandler {
|
||||||
|
|
||||||
|
private static final Duration emissionPrognosisJobInterval = Duration.ofMinutes(15);
|
||||||
|
private static final Duration emissionRealtimeJobInterval = Duration.ofMinutes(5);
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(EnergiDataServiceHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(EnergiDataServiceHandler.class);
|
||||||
private final TimeZoneProvider timeZoneProvider;
|
private final TimeZoneProvider timeZoneProvider;
|
||||||
private final ApiController apiController;
|
private final ApiController apiController;
|
||||||
@ -92,7 +98,10 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
|||||||
|
|
||||||
private EnergiDataServiceConfiguration config;
|
private EnergiDataServiceConfiguration config;
|
||||||
private RetryStrategy retryPolicy = RetryPolicyFactory.initial();
|
private RetryStrategy retryPolicy = RetryPolicyFactory.initial();
|
||||||
private @Nullable ScheduledFuture<?> refreshFuture;
|
private boolean realtimeEmissionsFetchedFirstTime = false;
|
||||||
|
private @Nullable ScheduledFuture<?> refreshPriceFuture;
|
||||||
|
private @Nullable ScheduledFuture<?> refreshEmissionPrognosisFuture;
|
||||||
|
private @Nullable ScheduledFuture<?> refreshEmissionRealtimeFuture;
|
||||||
private @Nullable ScheduledFuture<?> priceUpdateFuture;
|
private @Nullable ScheduledFuture<?> priceUpdateFuture;
|
||||||
|
|
||||||
public EnergiDataServiceHandler(Thing thing, HttpClient httpClient, TimeZoneProvider timeZoneProvider) {
|
public EnergiDataServiceHandler(Thing thing, HttpClient httpClient, TimeZoneProvider timeZoneProvider) {
|
||||||
@ -111,8 +120,14 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ELECTRICITY_CHANNELS.contains(channelUID.getId())) {
|
String channelId = channelUID.getId();
|
||||||
|
if (ELECTRICITY_CHANNELS.contains(channelId)) {
|
||||||
refreshElectricityPrices();
|
refreshElectricityPrices();
|
||||||
|
} else if (CHANNEL_CO2_EMISSION_PROGNOSIS.equals(channelId)) {
|
||||||
|
rescheduleEmissionPrognosisJob();
|
||||||
|
} else if (CHANNEL_CO2_EMISSION_REALTIME.equals(channelId)) {
|
||||||
|
realtimeEmissionsFetchedFirstTime = false;
|
||||||
|
rescheduleEmissionRealtimeJob();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,15 +155,32 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
|||||||
|
|
||||||
updateStatus(ThingStatus.UNKNOWN);
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
|
||||||
refreshFuture = scheduler.schedule(this::refreshElectricityPrices, 0, TimeUnit.SECONDS);
|
refreshPriceFuture = scheduler.schedule(this::refreshElectricityPrices, 0, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
if (isLinked(CHANNEL_CO2_EMISSION_PROGNOSIS)) {
|
||||||
|
rescheduleEmissionPrognosisJob();
|
||||||
|
}
|
||||||
|
if (isLinked(CHANNEL_CO2_EMISSION_REALTIME)) {
|
||||||
|
rescheduleEmissionRealtimeJob();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
ScheduledFuture<?> refreshFuture = this.refreshFuture;
|
ScheduledFuture<?> refreshPriceFuture = this.refreshPriceFuture;
|
||||||
if (refreshFuture != null) {
|
if (refreshPriceFuture != null) {
|
||||||
refreshFuture.cancel(true);
|
refreshPriceFuture.cancel(true);
|
||||||
this.refreshFuture = null;
|
this.refreshPriceFuture = null;
|
||||||
|
}
|
||||||
|
ScheduledFuture<?> refreshEmissionPrognosisFuture = this.refreshEmissionPrognosisFuture;
|
||||||
|
if (refreshEmissionPrognosisFuture != null) {
|
||||||
|
refreshEmissionPrognosisFuture.cancel(true);
|
||||||
|
this.refreshEmissionPrognosisFuture = null;
|
||||||
|
}
|
||||||
|
ScheduledFuture<?> refreshEmissionRealtimeFuture = this.refreshEmissionRealtimeFuture;
|
||||||
|
if (refreshEmissionRealtimeFuture != null) {
|
||||||
|
refreshEmissionRealtimeFuture.cancel(true);
|
||||||
|
this.refreshEmissionRealtimeFuture = null;
|
||||||
}
|
}
|
||||||
ScheduledFuture<?> priceUpdateFuture = this.priceUpdateFuture;
|
ScheduledFuture<?> priceUpdateFuture = this.priceUpdateFuture;
|
||||||
if (priceUpdateFuture != null) {
|
if (priceUpdateFuture != null) {
|
||||||
@ -164,6 +196,30 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
|||||||
return Set.of(EnergiDataServiceActions.class);
|
return Set.of(EnergiDataServiceActions.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelUnlinked(ChannelUID channelUID) {
|
||||||
|
super.channelUnlinked(channelUID);
|
||||||
|
|
||||||
|
if (CHANNEL_CO2_EMISSION_PROGNOSIS.equals(channelUID.getId()) && !isLinked(CHANNEL_CO2_EMISSION_PROGNOSIS)) {
|
||||||
|
logger.debug("No more items linked to channel '{}', stopping emission prognosis refresh job",
|
||||||
|
channelUID.getId());
|
||||||
|
ScheduledFuture<?> refreshEmissionPrognosisFuture = this.refreshEmissionPrognosisFuture;
|
||||||
|
if (refreshEmissionPrognosisFuture != null) {
|
||||||
|
refreshEmissionPrognosisFuture.cancel(true);
|
||||||
|
this.refreshEmissionPrognosisFuture = null;
|
||||||
|
}
|
||||||
|
} else if (CHANNEL_CO2_EMISSION_REALTIME.contains(channelUID.getId())
|
||||||
|
&& !isLinked(CHANNEL_CO2_EMISSION_REALTIME)) {
|
||||||
|
logger.debug("No more items linked to channel '{}', stopping realtime emission refresh job",
|
||||||
|
channelUID.getId());
|
||||||
|
ScheduledFuture<?> refreshEmissionRealtimeFuture = this.refreshEmissionRealtimeFuture;
|
||||||
|
if (refreshEmissionRealtimeFuture != null) {
|
||||||
|
refreshEmissionRealtimeFuture.cancel(true);
|
||||||
|
this.refreshEmissionRealtimeFuture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void refreshElectricityPrices() {
|
private void refreshElectricityPrices() {
|
||||||
RetryStrategy retryPolicy;
|
RetryStrategy retryPolicy;
|
||||||
try {
|
try {
|
||||||
@ -208,7 +264,7 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
rescheduleRefreshJob(retryPolicy);
|
reschedulePriceRefreshJob(retryPolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadSpotPrices() throws InterruptedException, DataServiceException {
|
private void downloadSpotPrices() throws InterruptedException, DataServiceException {
|
||||||
@ -299,6 +355,79 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
|||||||
Duration.ofHours(-CacheManager.NUMBER_OF_HISTORIC_HOURS)));
|
Duration.ofHours(-CacheManager.NUMBER_OF_HISTORIC_HOURS)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshCo2EmissionPrognosis() {
|
||||||
|
try {
|
||||||
|
updateCo2Emissions(Dataset.CO2EmissionPrognosis, CHANNEL_CO2_EMISSION_PROGNOSIS,
|
||||||
|
DateQueryParameter.of(DateQueryParameterType.UTC_NOW, Duration.ofMinutes(-5)));
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
} catch (DataServiceException e) {
|
||||||
|
if (e.getHttpStatus() != 0) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||||
|
HttpStatus.getCode(e.getHttpStatus()).getMessage());
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
if (e.getCause() != null) {
|
||||||
|
logger.debug("Error retrieving CO2 emission prognosis", e);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.debug("Emission prognosis refresh job interrupted");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshCo2EmissionRealtime() {
|
||||||
|
try {
|
||||||
|
updateCo2Emissions(Dataset.CO2Emission, CHANNEL_CO2_EMISSION_REALTIME,
|
||||||
|
DateQueryParameter.of(DateQueryParameterType.UTC_NOW,
|
||||||
|
realtimeEmissionsFetchedFirstTime ? Duration.ofMinutes(-5) : Duration.ofHours(-24)));
|
||||||
|
realtimeEmissionsFetchedFirstTime = true;
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
} catch (DataServiceException e) {
|
||||||
|
if (e.getHttpStatus() != 0) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||||
|
HttpStatus.getCode(e.getHttpStatus()).getMessage());
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
if (e.getCause() != null) {
|
||||||
|
logger.debug("Error retrieving CO2 realtime emissions", e);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.debug("Emission realtime refresh job interrupted");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCo2Emissions(Dataset dataset, String channelId, DateQueryParameter dateQueryParameter)
|
||||||
|
throws InterruptedException, DataServiceException {
|
||||||
|
Map<String, String> properties = editProperties();
|
||||||
|
CO2EmissionRecord[] emissionRecords = apiController.getCo2Emissions(dataset, config.priceArea,
|
||||||
|
dateQueryParameter, properties);
|
||||||
|
updateProperties(properties);
|
||||||
|
|
||||||
|
TimeSeries timeSeries = new TimeSeries(REPLACE);
|
||||||
|
Instant now = Instant.now();
|
||||||
|
|
||||||
|
if (dataset == Dataset.CO2Emission && emissionRecords.length > 0) {
|
||||||
|
// Records are sorted descending, first record is current.
|
||||||
|
updateState(channelId, new QuantityType<>(emissionRecords[0].emission(), Units.GRAM_PER_KILOWATT_HOUR));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CO2EmissionRecord emissionRecord : emissionRecords) {
|
||||||
|
State state = new QuantityType<>(emissionRecord.emission(), Units.GRAM_PER_KILOWATT_HOUR);
|
||||||
|
timeSeries.add(emissionRecord.start(), state);
|
||||||
|
|
||||||
|
if (dataset == Dataset.CO2EmissionPrognosis && now.compareTo(emissionRecord.start()) >= 0
|
||||||
|
&& now.compareTo(emissionRecord.end()) < 0) {
|
||||||
|
updateState(channelId, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendTimeSeries(channelId, timeSeries);
|
||||||
|
}
|
||||||
|
|
||||||
private void updatePrices() {
|
private void updatePrices() {
|
||||||
cacheManager.cleanup();
|
cacheManager.cleanup();
|
||||||
|
|
||||||
@ -472,19 +601,19 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
|||||||
logger.debug("Price update job rescheduled in {} milliseconds", millisUntilNextClockHour);
|
logger.debug("Price update job rescheduled in {} milliseconds", millisUntilNextClockHour);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rescheduleRefreshJob(RetryStrategy retryPolicy) {
|
private void reschedulePriceRefreshJob(RetryStrategy retryPolicy) {
|
||||||
// Preserve state of previous retry policy when configuration is the same.
|
// Preserve state of previous retry policy when configuration is the same.
|
||||||
if (!retryPolicy.equals(this.retryPolicy)) {
|
if (!retryPolicy.equals(this.retryPolicy)) {
|
||||||
this.retryPolicy = retryPolicy;
|
this.retryPolicy = retryPolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScheduledFuture<?> refreshJob = this.refreshFuture;
|
ScheduledFuture<?> refreshJob = this.refreshPriceFuture;
|
||||||
|
|
||||||
long secondsUntilNextRefresh = this.retryPolicy.getDuration().getSeconds();
|
long secondsUntilNextRefresh = this.retryPolicy.getDuration().getSeconds();
|
||||||
Instant timeOfNextRefresh = Instant.now().plusSeconds(secondsUntilNextRefresh);
|
Instant timeOfNextRefresh = Instant.now().plusSeconds(secondsUntilNextRefresh);
|
||||||
this.refreshFuture = scheduler.schedule(this::refreshElectricityPrices, secondsUntilNextRefresh,
|
this.refreshPriceFuture = scheduler.schedule(this::refreshElectricityPrices, secondsUntilNextRefresh,
|
||||||
TimeUnit.SECONDS);
|
TimeUnit.SECONDS);
|
||||||
logger.debug("Refresh job rescheduled in {} seconds: {}", secondsUntilNextRefresh, timeOfNextRefresh);
|
logger.debug("Price refresh job rescheduled in {} seconds: {}", secondsUntilNextRefresh, timeOfNextRefresh);
|
||||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PROPERTY_DATETIME_FORMAT);
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PROPERTY_DATETIME_FORMAT);
|
||||||
updateProperty(PROPERTY_NEXT_CALL, LocalDateTime.ofInstant(timeOfNextRefresh, timeZoneProvider.getTimeZone())
|
updateProperty(PROPERTY_NEXT_CALL, LocalDateTime.ofInstant(timeOfNextRefresh, timeZoneProvider.getTimeZone())
|
||||||
.truncatedTo(ChronoUnit.SECONDS).format(formatter));
|
.truncatedTo(ChronoUnit.SECONDS).format(formatter));
|
||||||
@ -493,4 +622,28 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
|||||||
refreshJob.cancel(true);
|
refreshJob.cancel(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void rescheduleEmissionPrognosisJob() {
|
||||||
|
logger.debug("Scheduling emission prognosis refresh job now and every {}", emissionPrognosisJobInterval);
|
||||||
|
|
||||||
|
ScheduledFuture<?> refreshEmissionPrognosisFuture = this.refreshEmissionPrognosisFuture;
|
||||||
|
if (refreshEmissionPrognosisFuture != null) {
|
||||||
|
refreshEmissionPrognosisFuture.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshEmissionPrognosisFuture = scheduler.scheduleWithFixedDelay(this::refreshCo2EmissionPrognosis, 0,
|
||||||
|
emissionPrognosisJobInterval.toSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rescheduleEmissionRealtimeJob() {
|
||||||
|
logger.debug("Scheduling emission realtime refresh job now and every {}", emissionRealtimeJobInterval);
|
||||||
|
|
||||||
|
ScheduledFuture<?> refreshEmissionFuture = this.refreshEmissionRealtimeFuture;
|
||||||
|
if (refreshEmissionFuture != null) {
|
||||||
|
refreshEmissionFuture.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshEmissionRealtimeFuture = scheduler.scheduleWithFixedDelay(this::refreshCo2EmissionRealtime, 0,
|
||||||
|
emissionRealtimeJobInterval.toSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,10 @@ thing-type.config.energidataservice.service.reducedElectricityTax.description =
|
|||||||
|
|
||||||
channel-group-type.energidataservice.electricity.label = Electricity
|
channel-group-type.energidataservice.electricity.label = Electricity
|
||||||
channel-group-type.energidataservice.electricity.description = Channels related to electricity
|
channel-group-type.energidataservice.electricity.description = Channels related to electricity
|
||||||
|
channel-group-type.energidataservice.electricity.channel.co2-emission-prognosis.label = CO₂ Emission Prognosis
|
||||||
|
channel-group-type.energidataservice.electricity.channel.co2-emission-prognosis.description = Estimated prognosis for CO₂ emission following the day-ahead market in g/kWh.
|
||||||
|
channel-group-type.energidataservice.electricity.channel.co2-emission-realtime.label = CO₂ Emission Realtime
|
||||||
|
channel-group-type.energidataservice.electricity.channel.co2-emission-realtime.description = Near up-to-date history for CO₂ emission from electricity consumed in Denmark in g/kWh.
|
||||||
channel-group-type.energidataservice.electricity.channel.electricity-tax.label = Electricity Tax
|
channel-group-type.energidataservice.electricity.channel.electricity-tax.label = Electricity Tax
|
||||||
channel-group-type.energidataservice.electricity.channel.electricity-tax.description = Electricity tax in DKK per kWh.
|
channel-group-type.energidataservice.electricity.channel.electricity-tax.description = Electricity tax in DKK per kWh.
|
||||||
channel-group-type.energidataservice.electricity.channel.grid-tariff.label = Grid Tariff
|
channel-group-type.energidataservice.electricity.channel.grid-tariff.label = Grid Tariff
|
||||||
@ -73,6 +77,8 @@ channel-group-type.energidataservice.electricity.channel.transmission-grid-tarif
|
|||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
|
|
||||||
|
channel-type.energidataservice.co2-emission.label = CO₂ Emission
|
||||||
|
channel-type.energidataservice.co2-emission.description = CO₂ emission in g/kWh.
|
||||||
channel-type.energidataservice.datahub-price.label = Datahub Price
|
channel-type.energidataservice.datahub-price.label = Datahub Price
|
||||||
channel-type.energidataservice.datahub-price.description = Datahub price.
|
channel-type.energidataservice.datahub-price.description = Datahub price.
|
||||||
channel-type.energidataservice.spot-price.label = Spot Price
|
channel-type.energidataservice.spot-price.label = Spot Price
|
||||||
|
@ -32,6 +32,14 @@
|
|||||||
<label>Reduced Electricity Tax</label>
|
<label>Reduced Electricity Tax</label>
|
||||||
<description>Reduced electricity tax in DKK per kWh. For electric heating customers only.</description>
|
<description>Reduced electricity tax in DKK per kWh. For electric heating customers only.</description>
|
||||||
</channel>
|
</channel>
|
||||||
|
<channel id="co2-emission-prognosis" typeId="co2-emission">
|
||||||
|
<label>CO₂ Emission Prognosis</label>
|
||||||
|
<description>Estimated prognosis for CO₂ emission following the day-ahead market in g/kWh.</description>
|
||||||
|
</channel>
|
||||||
|
<channel id="co2-emission-realtime" typeId="co2-emission">
|
||||||
|
<label>CO₂ Emission Realtime</label>
|
||||||
|
<description>Near up-to-date history for CO₂ emission from electricity consumed in Denmark in g/kWh.</description>
|
||||||
|
</channel>
|
||||||
</channels>
|
</channels>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|
||||||
|
@ -21,4 +21,12 @@
|
|||||||
<config-description-ref uri="channel-type:energidataservice:datahub-price"/>
|
<config-description-ref uri="channel-type:energidataservice:datahub-price"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="co2-emission">
|
||||||
|
<item-type>Number:EmissionIntensity</item-type>
|
||||||
|
<label>CO₂ Emission</label>
|
||||||
|
<description>CO₂ emission in g/kWh.</description>
|
||||||
|
<category>Smoke</category>
|
||||||
|
<state readOnly="true" pattern="%.1f %unit%"></state>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</channel-groups>
|
</channel-groups>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<property name="thingTypeVersion">4</property>
|
<property name="thingTypeVersion">5</property>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<config-description-ref uri="thing-type:energidataservice:service"/>
|
<config-description-ref uri="thing-type:energidataservice:service"/>
|
||||||
|
@ -65,6 +65,19 @@
|
|||||||
<remove-channel id="hourly-prices" groupIds="electricity"/>
|
<remove-channel id="hourly-prices" groupIds="electricity"/>
|
||||||
</instruction-set>
|
</instruction-set>
|
||||||
|
|
||||||
|
<instruction-set targetVersion="5">
|
||||||
|
<add-channel id="co2-emission-prognosis" groupIds="electricity">
|
||||||
|
<type>energidataservice:co2-emission</type>
|
||||||
|
<label>CO₂ Emission Prognosis</label>
|
||||||
|
<description>Estimated prognosis for CO₂ emission following the day-ahead market in g/kWh.</description>
|
||||||
|
</add-channel>
|
||||||
|
<add-channel id="co2-emission-realtime" groupIds="electricity">
|
||||||
|
<type>energidataservice:co2-emission</type>
|
||||||
|
<label>CO₂ Emission Realtime</label>
|
||||||
|
<description>Near up-to-date history for CO₂ emission from electricity consumed in Denmark in g/kWh.</description>
|
||||||
|
</add-channel>
|
||||||
|
</instruction-set>
|
||||||
|
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
</update:update-descriptions>
|
</update:update-descriptions>
|
||||||
|
Loading…
Reference in New Issue
Block a user