mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
Introduce console command for history persistence (#16656)
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
be7c6bbcdb
commit
321fde5486
@ -90,6 +90,19 @@ The recommended persistence strategy is `forecast`, as it ensures a clean histor
|
||||
Prices from the past 24 hours and all forthcoming prices will be stored.
|
||||
Any changes that impact published prices (e.g. selecting or deselecting VAT Profile) will result in the replacement of persisted prices within this period.
|
||||
|
||||
##### Manually Persisting History
|
||||
|
||||
During extended service interruptions, data unavailability, or openHAB downtime, historic prices may be absent from persistence.
|
||||
A console command is provided to fill gaps: `energidataservice update [SpotPrice|GridTariff|SystemTariff|TransmissionGridTariff|ElectricityTax|ReducedElectricitytax] <StartDate> [<EndDate>]`.
|
||||
|
||||
Example:
|
||||
|
||||
```shell
|
||||
energidataservice update spotprice 2024-04-12 2024-04-14
|
||||
```
|
||||
|
||||
This can also be useful for retrospectively changing the [VAT profile](https://www.openhab.org/addons/transformations/vat/).
|
||||
|
||||
#### Grid Tariff
|
||||
|
||||
Discounts are automatically taken into account for channel `grid-tariff` so that it represents the actual price.
|
||||
|
@ -106,7 +106,7 @@ public class ApiController {
|
||||
* @throws DataServiceException
|
||||
*/
|
||||
public ElspotpriceRecord[] getSpotPrices(String priceArea, Currency currency, DateQueryParameter start,
|
||||
Map<String, String> properties) throws InterruptedException, DataServiceException {
|
||||
DateQueryParameter end, Map<String, String> properties) throws InterruptedException, DataServiceException {
|
||||
if (!SUPPORTED_CURRENCIES.contains(currency)) {
|
||||
throw new IllegalArgumentException("Invalid currency " + currency.getCurrencyCode());
|
||||
}
|
||||
@ -119,6 +119,10 @@ public class ApiController {
|
||||
.agent(userAgent) //
|
||||
.method(HttpMethod.GET);
|
||||
|
||||
if (!end.isEmpty()) {
|
||||
request = request.param("end", end.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
String responseContent = sendRequest(request, properties);
|
||||
ElspotpriceRecords records = gson.fromJson(responseContent, ElspotpriceRecords.class);
|
||||
@ -209,9 +213,14 @@ public class ApiController {
|
||||
.agent(userAgent) //
|
||||
.method(HttpMethod.GET);
|
||||
|
||||
DateQueryParameter dateQueryParameter = tariffFilter.getDateQueryParameter();
|
||||
if (!dateQueryParameter.isEmpty()) {
|
||||
request = request.param("start", dateQueryParameter.toString());
|
||||
DateQueryParameter start = tariffFilter.getStart();
|
||||
if (!start.isEmpty()) {
|
||||
request = request.param("start", start.toString());
|
||||
}
|
||||
|
||||
DateQueryParameter end = tariffFilter.getEnd();
|
||||
if (!end.isEmpty()) {
|
||||
request = request.param("end", end.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -31,7 +31,7 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||
@NonNullByDefault
|
||||
public class EnergiDataServiceBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "energidataservice";
|
||||
public static final String BINDING_ID = "energidataservice";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_SERVICE = new ThingTypeUID(BINDING_ID, "service");
|
||||
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* {@link PriceComponent} represents the different components making up the total electricity price.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum PriceComponent {
|
||||
SPOT_PRICE("SpotPrice", null),
|
||||
GRID_TARIFF("GridTariff", DatahubTariff.GRID_TARIFF),
|
||||
SYSTEM_TARIFF("SystemTariff", DatahubTariff.SYSTEM_TARIFF),
|
||||
TRANSMISSION_GRID_TARIFF("TransmissionGridTariff", DatahubTariff.TRANSMISSION_GRID_TARIFF),
|
||||
ELECTRICITY_TAX("ElectricityTax", DatahubTariff.ELECTRICITY_TAX),
|
||||
REDUCED_ELECTRICITY_TAX("ReducedElectricityTax", DatahubTariff.REDUCED_ELECTRICITY_TAX);
|
||||
|
||||
private static final Map<String, PriceComponent> NAME_MAP = Stream.of(values())
|
||||
.collect(Collectors.toMap(PriceComponent::toLowerCaseString, Function.identity()));
|
||||
|
||||
private String name;
|
||||
private @Nullable DatahubTariff datahubTariff;
|
||||
|
||||
private PriceComponent(String name, @Nullable DatahubTariff datahubTariff) {
|
||||
this.name = name;
|
||||
this.datahubTariff = datahubTariff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
private String toLowerCaseString() {
|
||||
return name.toLowerCase();
|
||||
}
|
||||
|
||||
public static PriceComponent fromString(final String name) {
|
||||
PriceComponent myEnum = NAME_MAP.get(name.toLowerCase());
|
||||
if (null == myEnum) {
|
||||
throw new IllegalArgumentException(String.format("'%s' has no corresponding value. Accepted values: %s",
|
||||
name, Arrays.asList(values())));
|
||||
}
|
||||
return myEnum;
|
||||
}
|
||||
|
||||
public @Nullable DatahubTariff getDatahubTariff() {
|
||||
return datahubTariff;
|
||||
}
|
||||
}
|
@ -47,9 +47,19 @@ public class PriceListParser {
|
||||
}
|
||||
|
||||
public Map<Instant, BigDecimal> toHourly(Collection<DatahubPricelistRecord> records) {
|
||||
Instant firstHourStart = Instant.now(clock).minus(CacheManager.NUMBER_OF_HISTORIC_HOURS, ChronoUnit.HOURS)
|
||||
.truncatedTo(ChronoUnit.HOURS);
|
||||
Instant lastHourStart = Instant.now(clock).truncatedTo(ChronoUnit.HOURS).plus(2, ChronoUnit.DAYS)
|
||||
.truncatedTo(ChronoUnit.DAYS);
|
||||
|
||||
return toHourly(records, firstHourStart, lastHourStart);
|
||||
}
|
||||
|
||||
public Map<Instant, BigDecimal> toHourly(Collection<DatahubPricelistRecord> records, Instant firstHourStart,
|
||||
Instant lastHourStart) {
|
||||
Map<Instant, BigDecimal> totalMap = new ConcurrentHashMap<>(CacheManager.TARIFF_MAX_CACHE_SIZE);
|
||||
records.stream().map(record -> record.chargeTypeCode()).distinct().forEach(chargeTypeCode -> {
|
||||
Map<Instant, BigDecimal> currentMap = toHourly(records, chargeTypeCode);
|
||||
Map<Instant, BigDecimal> currentMap = toHourly(records, chargeTypeCode, firstHourStart, lastHourStart);
|
||||
for (Entry<Instant, BigDecimal> current : currentMap.entrySet()) {
|
||||
BigDecimal total = totalMap.get(current.getKey());
|
||||
if (total == null) {
|
||||
@ -62,14 +72,10 @@ public class PriceListParser {
|
||||
return totalMap;
|
||||
}
|
||||
|
||||
public Map<Instant, BigDecimal> toHourly(Collection<DatahubPricelistRecord> records, String chargeTypeCode) {
|
||||
private Map<Instant, BigDecimal> toHourly(Collection<DatahubPricelistRecord> records, String chargeTypeCode,
|
||||
Instant firstHourStart, Instant lastHourStart) {
|
||||
Map<Instant, BigDecimal> tariffMap = new ConcurrentHashMap<>(CacheManager.TARIFF_MAX_CACHE_SIZE);
|
||||
|
||||
Instant firstHourStart = Instant.now(clock).minus(CacheManager.NUMBER_OF_HISTORIC_HOURS, ChronoUnit.HOURS)
|
||||
.truncatedTo(ChronoUnit.HOURS);
|
||||
Instant lastHourStart = Instant.now(clock).truncatedTo(ChronoUnit.HOURS).plus(2, ChronoUnit.DAYS)
|
||||
.truncatedTo(ChronoUnit.DAYS);
|
||||
|
||||
LocalDateTime previousValidFrom = LocalDateTime.MAX;
|
||||
LocalDateTime previousValidTo = LocalDateTime.MIN;
|
||||
Map<LocalTime, BigDecimal> tariffs = Map.of();
|
||||
|
@ -24,9 +24,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.measure.quantity.Energy;
|
||||
import javax.measure.quantity.Power;
|
||||
@ -35,6 +33,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.energidataservice.internal.DatahubTariff;
|
||||
import org.openhab.binding.energidataservice.internal.PriceCalculator;
|
||||
import org.openhab.binding.energidataservice.internal.PriceComponent;
|
||||
import org.openhab.binding.energidataservice.internal.exception.MissingPriceException;
|
||||
import org.openhab.binding.energidataservice.internal.handler.EnergiDataServiceHandler;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
@ -64,44 +63,6 @@ public class EnergiDataServiceActions implements ThingActions {
|
||||
|
||||
private @Nullable EnergiDataServiceHandler handler;
|
||||
|
||||
private enum PriceComponent {
|
||||
SPOT_PRICE("spotprice", null),
|
||||
GRID_TARIFF("gridtariff", DatahubTariff.GRID_TARIFF),
|
||||
SYSTEM_TARIFF("systemtariff", DatahubTariff.SYSTEM_TARIFF),
|
||||
TRANSMISSION_GRID_TARIFF("transmissiongridtariff", DatahubTariff.TRANSMISSION_GRID_TARIFF),
|
||||
ELECTRICITY_TAX("electricitytax", DatahubTariff.ELECTRICITY_TAX),
|
||||
REDUCED_ELECTRICITY_TAX("reducedelectricitytax", DatahubTariff.REDUCED_ELECTRICITY_TAX);
|
||||
|
||||
private static final Map<String, PriceComponent> NAME_MAP = Stream.of(values())
|
||||
.collect(Collectors.toMap(PriceComponent::toString, Function.identity()));
|
||||
|
||||
private String name;
|
||||
private @Nullable DatahubTariff datahubTariff;
|
||||
|
||||
private PriceComponent(String name, @Nullable DatahubTariff datahubTariff) {
|
||||
this.name = name;
|
||||
this.datahubTariff = datahubTariff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static PriceComponent fromString(final String name) {
|
||||
PriceComponent myEnum = NAME_MAP.get(name.toLowerCase());
|
||||
if (null == myEnum) {
|
||||
throw new IllegalArgumentException(String.format("'%s' has no corresponding value. Accepted values: %s",
|
||||
name, Arrays.asList(values())));
|
||||
}
|
||||
return myEnum;
|
||||
}
|
||||
|
||||
public @Nullable DatahubTariff getDatahubTariff() {
|
||||
return datahubTariff;
|
||||
}
|
||||
}
|
||||
|
||||
@RuleAction(label = "@text/action.get-prices.label", description = "@text/action.get-prices.description")
|
||||
public @ActionOutput(name = "prices", type = "java.util.Map<java.time.Instant, java.math.BigDecimal>") Map<Instant, BigDecimal> getPrices() {
|
||||
EnergiDataServiceHandler handler = this.handler;
|
||||
|
@ -27,21 +27,31 @@ public class DatahubTariffFilter {
|
||||
|
||||
private final Set<ChargeTypeCode> chargeTypeCodes;
|
||||
private final Set<String> notes;
|
||||
private final DateQueryParameter dateQueryParameter;
|
||||
private final DateQueryParameter start;
|
||||
private final DateQueryParameter end;
|
||||
|
||||
public DatahubTariffFilter(DatahubTariffFilter filter, DateQueryParameter dateQueryParameter) {
|
||||
this(filter.chargeTypeCodes, filter.notes, dateQueryParameter);
|
||||
public DatahubTariffFilter(DatahubTariffFilter filter, DateQueryParameter start) {
|
||||
this(filter, start, DateQueryParameter.EMPTY);
|
||||
}
|
||||
|
||||
public DatahubTariffFilter(DatahubTariffFilter filter, DateQueryParameter start, DateQueryParameter end) {
|
||||
this(filter.chargeTypeCodes, filter.notes, start, end);
|
||||
}
|
||||
|
||||
public DatahubTariffFilter(Set<ChargeTypeCode> chargeTypeCodes, Set<String> notes) {
|
||||
this(chargeTypeCodes, notes, DateQueryParameter.EMPTY);
|
||||
}
|
||||
|
||||
public DatahubTariffFilter(Set<ChargeTypeCode> chargeTypeCodes, Set<String> notes,
|
||||
DateQueryParameter dateQueryParameter) {
|
||||
public DatahubTariffFilter(Set<ChargeTypeCode> chargeTypeCodes, Set<String> notes, DateQueryParameter start) {
|
||||
this(chargeTypeCodes, notes, start, DateQueryParameter.EMPTY);
|
||||
}
|
||||
|
||||
public DatahubTariffFilter(Set<ChargeTypeCode> chargeTypeCodes, Set<String> notes, DateQueryParameter start,
|
||||
DateQueryParameter end) {
|
||||
this.chargeTypeCodes = chargeTypeCodes;
|
||||
this.notes = notes;
|
||||
this.dateQueryParameter = dateQueryParameter;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public Collection<String> getChargeTypeCodesAsStrings() {
|
||||
@ -52,7 +62,11 @@ public class DatahubTariffFilter {
|
||||
return notes;
|
||||
}
|
||||
|
||||
public DateQueryParameter getDateQueryParameter() {
|
||||
return dateQueryParameter;
|
||||
public DateQueryParameter getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public DateQueryParameter getEnd() {
|
||||
return end;
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,14 @@ public class DateQueryParameter {
|
||||
return this == EMPTY;
|
||||
}
|
||||
|
||||
public @Nullable DateQueryParameterType getDateType() {
|
||||
return dateType;
|
||||
}
|
||||
|
||||
public @Nullable LocalDate getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public static DateQueryParameter of(LocalDate localDate) {
|
||||
return new DateQueryParameter(localDate);
|
||||
}
|
||||
|
@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 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.console;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.energidataservice.internal.DatahubTariff;
|
||||
import org.openhab.binding.energidataservice.internal.EnergiDataServiceBindingConstants;
|
||||
import org.openhab.binding.energidataservice.internal.PriceComponent;
|
||||
import org.openhab.binding.energidataservice.internal.exception.DataServiceException;
|
||||
import org.openhab.binding.energidataservice.internal.handler.EnergiDataServiceHandler;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.StringsCompleter;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link EnergiDataServiceCommandExtension} is responsible for handling console commands.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ConsoleCommandExtension.class)
|
||||
public class EnergiDataServiceCommandExtension extends AbstractConsoleCommandExtension {
|
||||
|
||||
private static final String SUBCMD_UPDATE = "update";
|
||||
|
||||
private static final StringsCompleter SUBCMD_COMPLETER = new StringsCompleter(List.of(SUBCMD_UPDATE), false);
|
||||
|
||||
private final ThingRegistry thingRegistry;
|
||||
|
||||
private class EnergiDataServiceConsoleCommandCompleter implements ConsoleCommandCompleter {
|
||||
@Override
|
||||
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
|
||||
if (cursorArgumentIndex <= 0) {
|
||||
return SUBCMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
} else if (cursorArgumentIndex == 1) {
|
||||
return new StringsCompleter(Stream.of(PriceComponent.values()).map(PriceComponent::toString).toList(),
|
||||
false).complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Activate
|
||||
public EnergiDataServiceCommandExtension(final @Reference ThingRegistry thingRegistry) {
|
||||
super(EnergiDataServiceBindingConstants.BINDING_ID, "Interact with the Energi Data Service binding.");
|
||||
this.thingRegistry = thingRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String[] args, Console console) {
|
||||
if (args.length < 1) {
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[0].toLowerCase()) {
|
||||
case SUBCMD_UPDATE -> update(args, console);
|
||||
default -> printUsage(console);
|
||||
}
|
||||
}
|
||||
|
||||
private void update(String[] args, Console console) {
|
||||
ParsedUpdateParameters updateParameters;
|
||||
try {
|
||||
updateParameters = new ParsedUpdateParameters(args);
|
||||
|
||||
for (EnergiDataServiceHandler handler : thingRegistry.getAll().stream().map(thing -> thing.getHandler())
|
||||
.filter(EnergiDataServiceHandler.class::isInstance).map(EnergiDataServiceHandler.class::cast)
|
||||
.toList()) {
|
||||
Instant measureStart = Instant.now();
|
||||
int items = switch (updateParameters.priceComponent) {
|
||||
case SPOT_PRICE ->
|
||||
handler.updateSpotPriceTimeSeries(updateParameters.startDate, updateParameters.endDate);
|
||||
default -> {
|
||||
DatahubTariff datahubTariff = updateParameters.priceComponent.getDatahubTariff();
|
||||
yield datahubTariff == null ? 0
|
||||
: handler.updateTariffTimeSeries(datahubTariff, updateParameters.startDate,
|
||||
updateParameters.endDate);
|
||||
}
|
||||
};
|
||||
Instant measureEnd = Instant.now();
|
||||
console.println(items + " prices updated as time series in "
|
||||
+ Duration.between(measureStart, measureEnd).toMillis() + " milliseconds.");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
console.println("Interrupted.");
|
||||
} catch (DataServiceException e) {
|
||||
console.println("Failed to fetch prices: " + e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
String message = e.getMessage();
|
||||
if (message != null) {
|
||||
console.println(message);
|
||||
}
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private class ParsedUpdateParameters {
|
||||
PriceComponent priceComponent;
|
||||
LocalDate startDate;
|
||||
LocalDate endDate;
|
||||
|
||||
private int ARGUMENT_POSITION_PRICE_COMPONENT = 1;
|
||||
private int ARGUMENT_POSITION_START_DATE = 2;
|
||||
private int ARGUMENT_POSITION_END_DATE = 3;
|
||||
|
||||
ParsedUpdateParameters(String[] args) {
|
||||
if (args.length < 3 || args.length > 4) {
|
||||
throw new IllegalArgumentException("Incorrect number of parameters");
|
||||
}
|
||||
|
||||
priceComponent = PriceComponent.fromString(args[ARGUMENT_POSITION_PRICE_COMPONENT].toLowerCase());
|
||||
|
||||
try {
|
||||
startDate = LocalDate.parse(args[ARGUMENT_POSITION_START_DATE]);
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new IllegalArgumentException("Invalid start date: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
endDate = args.length == 3 ? startDate : LocalDate.parse(args[ARGUMENT_POSITION_END_DATE]);
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new IllegalArgumentException("Invalid end date: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (endDate.isBefore(startDate)) {
|
||||
throw new IllegalArgumentException("End date must be equal to or higher than start date");
|
||||
}
|
||||
|
||||
if (endDate.isAfter(LocalDate.now())) {
|
||||
throw new IllegalArgumentException("Future end date is not allowed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUsages() {
|
||||
return Arrays.asList(buildCommandUsage(SUBCMD_UPDATE + " ["
|
||||
+ String.join("|", Stream.of(PriceComponent.values()).map(PriceComponent::toString).toList())
|
||||
+ "] <StartDate> [<EndDate>]", "Update time series in requested period"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConsoleCommandCompleter getCompleter() {
|
||||
return new EnergiDataServiceConsoleCommandCompleter();
|
||||
}
|
||||
}
|
@ -18,12 +18,15 @@ import static org.openhab.core.types.TimeSeries.Policy.REPLACE;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Currency;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -41,6 +44,8 @@ import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.energidataservice.internal.ApiController;
|
||||
import org.openhab.binding.energidataservice.internal.CacheManager;
|
||||
import org.openhab.binding.energidataservice.internal.DatahubTariff;
|
||||
import org.openhab.binding.energidataservice.internal.EnergiDataServiceBindingConstants;
|
||||
import org.openhab.binding.energidataservice.internal.PriceListParser;
|
||||
import org.openhab.binding.energidataservice.internal.action.EnergiDataServiceActions;
|
||||
import org.openhab.binding.energidataservice.internal.api.ChargeType;
|
||||
import org.openhab.binding.energidataservice.internal.api.ChargeTypeCode;
|
||||
@ -246,7 +251,7 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
updatePrices();
|
||||
updateTimeSeries();
|
||||
updateElectricityTimeSeriesFromCache();
|
||||
|
||||
if (isLinked(CHANNEL_SPOT_PRICE)) {
|
||||
long numberOfFutureSpotPrices = cacheManager.getNumberOfFutureSpotPrices();
|
||||
@ -297,7 +302,7 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
||||
Map<String, String> properties = editProperties();
|
||||
try {
|
||||
ElspotpriceRecord[] spotPriceRecords = apiController.getSpotPrices(config.priceArea, config.getCurrency(),
|
||||
start, properties);
|
||||
start, DateQueryParameter.EMPTY, properties);
|
||||
cacheManager.putSpotPrices(spotPriceRecords, config.getCurrency());
|
||||
} finally {
|
||||
updateProperties(properties);
|
||||
@ -305,10 +310,7 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
||||
}
|
||||
|
||||
private void downloadTariffs(DatahubTariff datahubTariff) throws InterruptedException, DataServiceException {
|
||||
GlobalLocationNumber globalLocationNumber = switch (datahubTariff) {
|
||||
case GRID_TARIFF -> config.getGridCompanyGLN();
|
||||
default -> config.getEnerginetGLN();
|
||||
};
|
||||
GlobalLocationNumber globalLocationNumber = getGlobalLocationNumber(datahubTariff);
|
||||
if (globalLocationNumber.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@ -316,17 +318,28 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
||||
logger.debug("Cached tariffs of type {} still valid, skipping download.", datahubTariff);
|
||||
cacheManager.updateTariffs(datahubTariff);
|
||||
} else {
|
||||
DatahubTariffFilter filter = switch (datahubTariff) {
|
||||
case GRID_TARIFF -> getGridTariffFilter();
|
||||
case SYSTEM_TARIFF -> DatahubTariffFilterFactory.getSystemTariff();
|
||||
case TRANSMISSION_GRID_TARIFF -> DatahubTariffFilterFactory.getTransmissionGridTariff();
|
||||
case ELECTRICITY_TAX -> DatahubTariffFilterFactory.getElectricityTax();
|
||||
case REDUCED_ELECTRICITY_TAX -> DatahubTariffFilterFactory.getReducedElectricityTax();
|
||||
};
|
||||
DatahubTariffFilter filter = getDatahubTariffFilter(datahubTariff);
|
||||
cacheManager.putTariffs(datahubTariff, downloadPriceLists(globalLocationNumber, filter));
|
||||
}
|
||||
}
|
||||
|
||||
private DatahubTariffFilter getDatahubTariffFilter(DatahubTariff datahubTariff) {
|
||||
return switch (datahubTariff) {
|
||||
case GRID_TARIFF -> getGridTariffFilter();
|
||||
case SYSTEM_TARIFF -> DatahubTariffFilterFactory.getSystemTariff();
|
||||
case TRANSMISSION_GRID_TARIFF -> DatahubTariffFilterFactory.getTransmissionGridTariff();
|
||||
case ELECTRICITY_TAX -> DatahubTariffFilterFactory.getElectricityTax();
|
||||
case REDUCED_ELECTRICITY_TAX -> DatahubTariffFilterFactory.getReducedElectricityTax();
|
||||
};
|
||||
}
|
||||
|
||||
private GlobalLocationNumber getGlobalLocationNumber(DatahubTariff datahubTariff) {
|
||||
return switch (datahubTariff) {
|
||||
case GRID_TARIFF -> config.getGridCompanyGLN();
|
||||
default -> config.getEnerginetGLN();
|
||||
};
|
||||
}
|
||||
|
||||
private Collection<DatahubPricelistRecord> downloadPriceLists(GlobalLocationNumber globalLocationNumber,
|
||||
DatahubTariffFilter filter) throws InterruptedException, DataServiceException {
|
||||
Map<String, String> properties = editProperties();
|
||||
@ -369,8 +382,8 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
||||
start);
|
||||
}
|
||||
|
||||
return new DatahubTariffFilter(filter, DateQueryParameter.of(filter.getDateQueryParameter(),
|
||||
Duration.ofHours(-CacheManager.NUMBER_OF_HISTORIC_HOURS)));
|
||||
return new DatahubTariffFilter(filter,
|
||||
DateQueryParameter.of(filter.getStart(), Duration.ofHours(-CacheManager.NUMBER_OF_HISTORIC_HOURS)));
|
||||
}
|
||||
|
||||
private void refreshCo2EmissionPrognosis() {
|
||||
@ -494,7 +507,79 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTimeSeries() {
|
||||
/**
|
||||
* Download spot prices in requested period and update corresponding channel with time series.
|
||||
*
|
||||
* @param startDate Start date of period
|
||||
* @param endDate End date of period
|
||||
* @return number of published states
|
||||
*/
|
||||
public int updateSpotPriceTimeSeries(LocalDate startDate, LocalDate endDate)
|
||||
throws InterruptedException, DataServiceException {
|
||||
if (!isLinked(CHANNEL_SPOT_PRICE)) {
|
||||
return 0;
|
||||
}
|
||||
Map<String, String> properties = editProperties();
|
||||
try {
|
||||
Currency currency = config.getCurrency();
|
||||
ElspotpriceRecord[] spotPriceRecords = apiController.getSpotPrices(config.priceArea, currency,
|
||||
DateQueryParameter.of(startDate), DateQueryParameter.of(endDate.plusDays(1)), properties);
|
||||
boolean isDKK = EnergiDataServiceBindingConstants.CURRENCY_DKK.equals(currency);
|
||||
TimeSeries spotPriceTimeSeries = new TimeSeries(REPLACE);
|
||||
if (spotPriceRecords.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
for (ElspotpriceRecord record : Arrays.stream(spotPriceRecords)
|
||||
.sorted(Comparator.comparing(ElspotpriceRecord::hour)).toList()) {
|
||||
spotPriceTimeSeries.add(record.hour(), getEnergyPrice(
|
||||
(isDKK ? record.spotPriceDKK() : record.spotPriceEUR()).divide(BigDecimal.valueOf(1000)),
|
||||
currency));
|
||||
}
|
||||
sendTimeSeries(CHANNEL_SPOT_PRICE, spotPriceTimeSeries);
|
||||
return spotPriceRecords.length;
|
||||
} finally {
|
||||
updateProperties(properties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download tariffs in requested period and update corresponding channel with time series.
|
||||
*
|
||||
* @param datahubTariff Tariff to update
|
||||
* @param startDate Start date of period
|
||||
* @param endDate End date of period
|
||||
* @return number of published states
|
||||
*/
|
||||
public int updateTariffTimeSeries(DatahubTariff datahubTariff, LocalDate startDate, LocalDate endDate)
|
||||
throws InterruptedException, DataServiceException {
|
||||
if (!isLinked(datahubTariff.getChannelId())) {
|
||||
return 0;
|
||||
}
|
||||
GlobalLocationNumber globalLocationNumber = getGlobalLocationNumber(datahubTariff);
|
||||
if (globalLocationNumber.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
DatahubTariffFilter filter = getDatahubTariffFilter(datahubTariff);
|
||||
DateQueryParameter start = filter.getStart();
|
||||
DateQueryParameterType filterStartDateType = start.getDateType();
|
||||
LocalDate filterStartDate = start.getDate();
|
||||
if (filterStartDateType != null) {
|
||||
// For filters with date relative to current date, override with provided parameters.
|
||||
filter = new DatahubTariffFilter(filter, DateQueryParameter.of(startDate), DateQueryParameter.of(endDate));
|
||||
} else if (filterStartDate != null && startDate.isBefore(filterStartDate)) {
|
||||
throw new IllegalArgumentException("Start date before " + start.getDate() + " is not supported");
|
||||
}
|
||||
Collection<DatahubPricelistRecord> datahubRecords = downloadPriceLists(globalLocationNumber, filter);
|
||||
ZoneId zoneId = timeZoneProvider.getTimeZone();
|
||||
Instant firstHourStart = startDate.atStartOfDay(zoneId).toInstant();
|
||||
Instant lastHourStart = endDate.plusDays(1).atStartOfDay(zoneId).toInstant();
|
||||
Map<Instant, BigDecimal> tariffMap = new PriceListParser().toHourly(datahubRecords, firstHourStart,
|
||||
lastHourStart);
|
||||
|
||||
return updatePriceTimeSeries(datahubTariff.getChannelId(), tariffMap, CURRENCY_DKK, true);
|
||||
}
|
||||
|
||||
private void updateElectricityTimeSeriesFromCache() {
|
||||
updatePriceTimeSeries(CHANNEL_SPOT_PRICE, cacheManager.getSpotPrices(), config.getCurrency(), false);
|
||||
|
||||
for (DatahubTariff datahubTariff : DatahubTariff.values()) {
|
||||
@ -503,10 +588,10 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePriceTimeSeries(String channelId, Map<Instant, BigDecimal> priceMap, Currency currency,
|
||||
private int updatePriceTimeSeries(String channelId, Map<Instant, BigDecimal> priceMap, Currency currency,
|
||||
boolean deduplicate) {
|
||||
if (!isLinked(channelId)) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
List<Entry<Instant, BigDecimal>> prices = priceMap.entrySet().stream().sorted(Map.Entry.comparingByKey())
|
||||
.toList();
|
||||
@ -525,6 +610,7 @@ public class EnergiDataServiceHandler extends BaseThingHandler {
|
||||
if (timeSeries.size() > 0) {
|
||||
sendTimeSeries(channelId, timeSeries);
|
||||
}
|
||||
return timeSeries.size();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,7 +97,8 @@ public class PriceListParserTest {
|
||||
PriceListParser priceListParser = new PriceListParser(
|
||||
Clock.fixed(Instant.parse("2022-12-31T12:00:00Z"), EnergiDataServiceBindingConstants.DATAHUB_TIMEZONE));
|
||||
DatahubPricelistRecords records = getObjectFromJson("DatahubPricelistN1.json", DatahubPricelistRecords.class);
|
||||
Map<Instant, BigDecimal> tariffMap = priceListParser.toHourly(Arrays.stream(records.records()).toList(), "CD");
|
||||
Map<Instant, BigDecimal> tariffMap = priceListParser
|
||||
.toHourly(Arrays.stream(records.records()).filter(r -> r.chargeTypeCode().equals("CD")).toList());
|
||||
|
||||
assertThat(tariffMap.size(), is(60));
|
||||
assertThat(tariffMap.get(Instant.parse("2022-12-31T22:00:00Z")), is(equalTo(new BigDecimal("0.407717"))));
|
||||
@ -110,8 +111,8 @@ public class PriceListParserTest {
|
||||
PriceListParser priceListParser = new PriceListParser(
|
||||
Clock.fixed(Instant.parse("2022-12-31T12:00:00Z"), EnergiDataServiceBindingConstants.DATAHUB_TIMEZONE));
|
||||
DatahubPricelistRecords records = getObjectFromJson("DatahubPricelistN1.json", DatahubPricelistRecords.class);
|
||||
Map<Instant, BigDecimal> tariffMap = priceListParser.toHourly(Arrays.stream(records.records()).toList(),
|
||||
"CD R");
|
||||
Map<Instant, BigDecimal> tariffMap = priceListParser
|
||||
.toHourly(Arrays.stream(records.records()).filter(r -> r.chargeTypeCode().equals("CD R")).toList());
|
||||
|
||||
assertThat(tariffMap.size(), is(60));
|
||||
assertThat(tariffMap.get(Instant.parse("2022-12-31T22:00:00Z")), is(equalTo(new BigDecimal("-0.407717"))));
|
||||
|
Loading…
Reference in New Issue
Block a user