2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.energidataservice.internal.console;
15 import java.time.Duration;
16 import java.time.Instant;
17 import java.time.LocalDate;
18 import java.time.format.DateTimeParseException;
19 import java.util.Arrays;
20 import java.util.List;
21 import java.util.stream.Stream;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.energidataservice.internal.DatahubTariff;
26 import org.openhab.binding.energidataservice.internal.EnergiDataServiceBindingConstants;
27 import org.openhab.binding.energidataservice.internal.PriceComponent;
28 import org.openhab.binding.energidataservice.internal.exception.DataServiceException;
29 import org.openhab.binding.energidataservice.internal.handler.EnergiDataServiceHandler;
30 import org.openhab.core.io.console.Console;
31 import org.openhab.core.io.console.ConsoleCommandCompleter;
32 import org.openhab.core.io.console.StringsCompleter;
33 import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
34 import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
35 import org.openhab.core.thing.ThingRegistry;
36 import org.osgi.service.component.annotations.Activate;
37 import org.osgi.service.component.annotations.Component;
38 import org.osgi.service.component.annotations.Reference;
41 * The {@link EnergiDataServiceCommandExtension} is responsible for handling console commands.
43 * @author Jacob Laursen - Initial contribution
46 @Component(service = ConsoleCommandExtension.class)
47 public class EnergiDataServiceCommandExtension extends AbstractConsoleCommandExtension {
49 private static final String SUBCMD_UPDATE = "update";
51 private static final StringsCompleter SUBCMD_COMPLETER = new StringsCompleter(List.of(SUBCMD_UPDATE), false);
53 private final ThingRegistry thingRegistry;
55 private class EnergiDataServiceConsoleCommandCompleter implements ConsoleCommandCompleter {
57 public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
58 if (cursorArgumentIndex <= 0) {
59 return SUBCMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
60 } else if (cursorArgumentIndex == 1) {
61 return new StringsCompleter(Stream.of(PriceComponent.values()).map(PriceComponent::toString).toList(),
62 false).complete(args, cursorArgumentIndex, cursorPosition, candidates);
69 public EnergiDataServiceCommandExtension(final @Reference ThingRegistry thingRegistry) {
70 super(EnergiDataServiceBindingConstants.BINDING_ID, "Interact with the Energi Data Service binding.");
71 this.thingRegistry = thingRegistry;
75 public void execute(String[] args, Console console) {
76 if (args.length < 1) {
81 switch (args[0].toLowerCase()) {
82 case SUBCMD_UPDATE -> update(args, console);
83 default -> printUsage(console);
87 private void update(String[] args, Console console) {
88 ParsedUpdateParameters updateParameters;
90 updateParameters = new ParsedUpdateParameters(args);
92 for (EnergiDataServiceHandler handler : thingRegistry.getAll().stream().map(thing -> thing.getHandler())
93 .filter(EnergiDataServiceHandler.class::isInstance).map(EnergiDataServiceHandler.class::cast)
95 Instant measureStart = Instant.now();
96 int items = switch (updateParameters.priceComponent) {
98 handler.updateSpotPriceTimeSeries(updateParameters.startDate, updateParameters.endDate);
100 DatahubTariff datahubTariff = updateParameters.priceComponent.getDatahubTariff();
101 yield datahubTariff == null ? 0
102 : handler.updateTariffTimeSeries(datahubTariff, updateParameters.startDate,
103 updateParameters.endDate);
106 Instant measureEnd = Instant.now();
107 console.println(items + " prices updated as time series in "
108 + Duration.between(measureStart, measureEnd).toMillis() + " milliseconds.");
110 } catch (InterruptedException e) {
111 console.println("Interrupted.");
112 } catch (DataServiceException e) {
113 console.println("Failed to fetch prices: " + e.getMessage());
114 } catch (IllegalArgumentException e) {
115 String message = e.getMessage();
116 if (message != null) {
117 console.println(message);
124 private class ParsedUpdateParameters {
125 PriceComponent priceComponent;
129 private int ARGUMENT_POSITION_PRICE_COMPONENT = 1;
130 private int ARGUMENT_POSITION_START_DATE = 2;
131 private int ARGUMENT_POSITION_END_DATE = 3;
133 ParsedUpdateParameters(String[] args) {
134 if (args.length < 3 || args.length > 4) {
135 throw new IllegalArgumentException("Incorrect number of parameters");
138 priceComponent = PriceComponent.fromString(args[ARGUMENT_POSITION_PRICE_COMPONENT].toLowerCase());
141 startDate = LocalDate.parse(args[ARGUMENT_POSITION_START_DATE]);
142 } catch (DateTimeParseException e) {
143 throw new IllegalArgumentException("Invalid start date: " + e.getMessage(), e);
147 endDate = args.length == 3 ? startDate : LocalDate.parse(args[ARGUMENT_POSITION_END_DATE]);
148 } catch (DateTimeParseException e) {
149 throw new IllegalArgumentException("Invalid end date: " + e.getMessage(), e);
152 if (endDate.isBefore(startDate)) {
153 throw new IllegalArgumentException("End date must be equal to or higher than start date");
156 if (endDate.isAfter(LocalDate.now())) {
157 throw new IllegalArgumentException("Future end date is not allowed");
163 public List<String> getUsages() {
164 return Arrays.asList(buildCommandUsage(SUBCMD_UPDATE + " ["
165 + String.join("|", Stream.of(PriceComponent.values()).map(PriceComponent::toString).toList())
166 + "] <StartDate> [<EndDate>]", "Update time series in requested period"));
170 public @Nullable ConsoleCommandCompleter getCompleter() {
171 return new EnergiDataServiceConsoleCommandCompleter();