### Channel Group `electricity`
-| Channel | Type | Description | Advanced |
-|--------------------------|--------------------|--------------------------------------------------------------------------------|----------|
-| spot-price | Number:EnergyPrice | Spot price in DKK or EUR per kWh | no |
-| grid-tariff | Number:EnergyPrice | Grid tariff in DKK per kWh. Only available when `gridCompanyGLN` is configured | no |
-| system-tariff | Number:EnergyPrice | System tariff in DKK per kWh | no |
-| transmission-grid-tariff | Number:EnergyPrice | Transmission grid tariff in DKK per kWh | no |
-| electricity-tax | Number:EnergyPrice | Electricity tax in DKK per kWh | no |
-| reduced-electricity-tax | Number:EnergyPrice | Reduced electricity tax in DKK per kWh. For electric heating customers only | no |
+| Channel | Type | Description |
+|--------------------------|--------------------------|----------------------------------------------------------------------------------------|
+| 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 |
+| system-tariff | Number:EnergyPrice | System tariff in DKK per kWh |
+| transmission-grid-tariff | Number:EnergyPrice | Transmission grid tariff in DKK per kWh |
+| 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 |
+| 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.
Instead, create a group item with `SUM` as aggregate function and add the individual price items as children.
This has the following advantages:
-- Full customization possible: Freely choose the channels which should be included in the total.
-- 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.
+- Full customization possible: Freely choose the channels which should be included in the total (even between different bindings).
+- Spot price can be configured in EUR while tariffs are in DKK (and currency conversions are performed outside the binding).
+- 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.
See [Electricity Tax](#electricity-tax) for further information.
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.
+#### 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 can be used to perform calculations as well as import prices directly into rules without relying on persistence.
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.energidataservice.internal.api.ChargeType;
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.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.DatahubPricelistRecords;
import org.openhab.binding.energidataservice.internal.api.dto.ElspotpriceRecord;
private static final String ENDPOINT = "https://api.energidataservice.dk/";
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_CHARGE_TYPE = "ChargeType";
private static final String FILTER_KEY_CHARGE_TYPE_CODE = "ChargeTypeCode";
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) //
.param("start", start.toString()) //
.param("filter", "{\"" + FILTER_KEY_PRICE_AREA + "\":\"" + priceArea + "\"}") //
.agent(userAgent) //
.method(HttpMethod.GET);
- logger.trace("GET request for {}", request.getURI());
-
try {
- 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);
-
+ String responseContent = sendRequest(request, properties);
ElspotpriceRecords records = gson.fromJson(responseContent, ElspotpriceRecords.class);
if (records == null) {
throw new DataServiceException("Error parsing response");
}
}
+ 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) {
HttpFields headers = response.getHeaders();
String remainingCalls = headers.get(HEADER_REMAINING_CALLS);
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) //
.param("filter", mapToFilter(filterMap)) //
.param("columns", columns) //
request = request.param("start", dateQueryParameter.toString());
}
- logger.trace("GET request for {}", request.getURI());
-
try {
- 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);
-
+ String responseContent = sendRequest(request, properties);
DatahubPricelistRecords records = gson.fromJson(responseContent, DatahubPricelistRecords.class);
if (records == null) {
throw new DataServiceException("Error parsing response");
e -> "\"" + e.getKey() + "\":[\"" + e.getValue().stream().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);
+ }
+ }
}
+ ChannelUID.CHANNEL_GROUP_SEPARATOR + "reduced-electricity-tax";
public static final String CHANNEL_TRANSMISSION_GRID_TARIFF = CHANNEL_GROUP_ELECTRICITY
+ 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,
CHANNEL_SYSTEM_TARIFF, CHANNEL_TRANSMISSION_GRID_TARIFF, CHANNEL_ELECTRICITY_TAX,
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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) {
+}
import org.openhab.binding.energidataservice.internal.api.ChargeTypeCode;
import org.openhab.binding.energidataservice.internal.api.DatahubTariffFilter;
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.DateQueryParameterType;
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.ElspotpriceRecord;
import org.openhab.binding.energidataservice.internal.config.DatahubPriceConfiguration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
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.ChannelUID;
import org.openhab.core.thing.Thing;
@NonNullByDefault
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 TimeZoneProvider timeZoneProvider;
private final ApiController apiController;
private EnergiDataServiceConfiguration config;
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;
public EnergiDataServiceHandler(Thing thing, HttpClient httpClient, TimeZoneProvider timeZoneProvider) {
return;
}
- if (ELECTRICITY_CHANNELS.contains(channelUID.getId())) {
+ String channelId = channelUID.getId();
+ if (ELECTRICITY_CHANNELS.contains(channelId)) {
refreshElectricityPrices();
+ } else if (CHANNEL_CO2_EMISSION_PROGNOSIS.equals(channelId)) {
+ rescheduleEmissionPrognosisJob();
+ } else if (CHANNEL_CO2_EMISSION_REALTIME.equals(channelId)) {
+ realtimeEmissionsFetchedFirstTime = false;
+ rescheduleEmissionRealtimeJob();
}
}
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
public void dispose() {
- ScheduledFuture<?> refreshFuture = this.refreshFuture;
- if (refreshFuture != null) {
- refreshFuture.cancel(true);
- this.refreshFuture = null;
+ ScheduledFuture<?> refreshPriceFuture = this.refreshPriceFuture;
+ if (refreshPriceFuture != null) {
+ refreshPriceFuture.cancel(true);
+ 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;
if (priceUpdateFuture != null) {
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() {
RetryStrategy retryPolicy;
try {
return;
}
- rescheduleRefreshJob(retryPolicy);
+ reschedulePriceRefreshJob(retryPolicy);
}
private void downloadSpotPrices() throws InterruptedException, DataServiceException {
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() {
cacheManager.cleanup();
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.
if (!retryPolicy.equals(this.retryPolicy)) {
this.retryPolicy = retryPolicy;
}
- ScheduledFuture<?> refreshJob = this.refreshFuture;
+ ScheduledFuture<?> refreshJob = this.refreshPriceFuture;
long secondsUntilNextRefresh = this.retryPolicy.getDuration().getSeconds();
Instant timeOfNextRefresh = Instant.now().plusSeconds(secondsUntilNextRefresh);
- this.refreshFuture = scheduler.schedule(this::refreshElectricityPrices, secondsUntilNextRefresh,
+ this.refreshPriceFuture = scheduler.schedule(this::refreshElectricityPrices, secondsUntilNextRefresh,
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);
updateProperty(PROPERTY_NEXT_CALL, LocalDateTime.ofInstant(timeOfNextRefresh, timeZoneProvider.getTimeZone())
.truncatedTo(ChronoUnit.SECONDS).format(formatter));
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);
+ }
}
channel-group-type.energidataservice.electricity.label = 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.description = Electricity tax in DKK per kWh.
channel-group-type.energidataservice.electricity.channel.grid-tariff.label = Grid Tariff
# 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.description = Datahub price.
channel-type.energidataservice.spot-price.label = Spot Price
<label>Reduced Electricity Tax</label>
<description>Reduced electricity tax in DKK per kWh. For electric heating customers only.</description>
</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>
</channel-group-type>
<config-description-ref uri="channel-type:energidataservice:datahub-price"/>
</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>
</channel-groups>
<properties>
- <property name="thingTypeVersion">4</property>
+ <property name="thingTypeVersion">5</property>
</properties>
<config-description-ref uri="thing-type:energidataservice:service"/>
<remove-channel id="hourly-prices" groupIds="electricity"/>
</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>
</update:update-descriptions>