From e03e31600f32d04ef4885c090807141185f3c721 Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Thu, 1 Jun 2023 15:41:21 +0200 Subject: [PATCH 01/16] Start implementation of ENTSO-e as Time-of-Use Tariff provider --- .../src/io/openems/common/utils/XmlUtils.java | 31 + .../.classpath | 16 + .../.gitignore | 1 + .../.project | 23 + .../org.eclipse.core.resources.prefs | 9 + .../bnd.bnd | 19 + .../generated/buildfiles | 1 + .../readme.adoc | 7 + .../edge/timeofusetariff/entsoe/Config.java | 24 + .../timeofusetariff/entsoe/EntsoeApi.java | 82 +++ .../timeofusetariff/entsoe/TouEntsoe.java | 35 + .../timeofusetariff/entsoe/TouEntsoeImpl.java | 64 ++ .../timeofusetariff/entsoe/EntsoeApiTest.java | 654 ++++++++++++++++++ .../edge/timeofusetariff/entsoe/MyConfig.java | 51 ++ .../timeofusetariff/entsoe/ParserTest.java | 5 + .../timeofusetariff/entsoe/TouEntsoeTest.java | 21 + 16 files changed, 1043 insertions(+) create mode 100644 io.openems.edge.timeofusetariff.entsoe/.classpath create mode 100644 io.openems.edge.timeofusetariff.entsoe/.gitignore create mode 100644 io.openems.edge.timeofusetariff.entsoe/.project create mode 100644 io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs create mode 100644 io.openems.edge.timeofusetariff.entsoe/bnd.bnd create mode 100644 io.openems.edge.timeofusetariff.entsoe/generated/buildfiles create mode 100644 io.openems.edge.timeofusetariff.entsoe/readme.adoc create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java diff --git a/io.openems.common/src/io/openems/common/utils/XmlUtils.java b/io.openems.common/src/io/openems/common/utils/XmlUtils.java index 6431897bda9..54273f07094 100644 --- a/io.openems.common/src/io/openems/common/utils/XmlUtils.java +++ b/io.openems.common/src/io/openems/common/utils/XmlUtils.java @@ -1,7 +1,11 @@ package io.openems.common.utils; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.IntStream; +import java.util.stream.Stream; import org.w3c.dom.DOMException; import org.w3c.dom.NamedNodeMap; @@ -207,4 +211,31 @@ public static String getContentAsString(Node node) { public static int getContentAsInt(Node node) { return Integer.parseInt(node.getTextContent()); } + + // Source: https://stackoverflow.com/a/48153597/4137113 + public static Iterable list(final Node node) { + return () -> new Iterator() { + + private int index = 0; + + @Override + public boolean hasNext() { + return index < node.getChildNodes().getLength(); + } + + @Override + public Node next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return node.getChildNodes().item(index++); + } + }; + } + + // Source: https://stackoverflow.com/a/62171621/4137113 + public static Stream stream(final Node node) { + var childNodes = node.getChildNodes(); + return IntStream.range(0, childNodes.getLength()).boxed().map(childNodes::item); + } } diff --git a/io.openems.edge.timeofusetariff.entsoe/.classpath b/io.openems.edge.timeofusetariff.entsoe/.classpath new file mode 100644 index 00000000000..1a2da58b855 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/.classpath @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/io.openems.edge.timeofusetariff.entsoe/.gitignore b/io.openems.edge.timeofusetariff.entsoe/.gitignore new file mode 100644 index 00000000000..1c316c001c9 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/.gitignore @@ -0,0 +1 @@ +/bin_test/ diff --git a/io.openems.edge.timeofusetariff.entsoe/.project b/io.openems.edge.timeofusetariff.entsoe/.project new file mode 100644 index 00000000000..2423c50c37c --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/.project @@ -0,0 +1,23 @@ + + + io.openems.edge.timeofusetariff.entsoe + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..02307e84079 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +encoding//src/io/openems/edge/timeofusetariff/entsoe/Config.java=UTF-8 +encoding//src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java=UTF-8 +encoding//src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java=UTF-8 +encoding//test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java=UTF-8 +encoding//test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java=UTF-8 +encoding/=UTF-8 +encoding/bnd.bnd=UTF-8 +encoding/readme.adoc=UTF-8 diff --git a/io.openems.edge.timeofusetariff.entsoe/bnd.bnd b/io.openems.edge.timeofusetariff.entsoe/bnd.bnd new file mode 100644 index 00000000000..fe633fabf09 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/bnd.bnd @@ -0,0 +1,19 @@ +Bundle-Name: OpenEMS Edge Time-Of-Use ENTSO-E +Bundle-Vendor: FENECON GmbH +Bundle-License: https://opensource.org/licenses/EPL-2.0 +Bundle-Version: 1.0.0.${tstamp} + +-buildpath: \ + ${buildpath},\ + com.squareup.okio,\ + io.openems.common,\ + io.openems.edge.common,\ + io.openems.edge.timeofusetariff.api,\ + io.openems.wrapper.okhttp,\ + +-testpath: \ + com.squareup.okio,\ + io.openems.wrapper.kotlinx-coroutines-core-jvm,\ + io.openems.wrapper.okhttp,\ + org.jetbrains.kotlin.osgi-bundle,\ + ${testpath} diff --git a/io.openems.edge.timeofusetariff.entsoe/generated/buildfiles b/io.openems.edge.timeofusetariff.entsoe/generated/buildfiles new file mode 100644 index 00000000000..2ade7672f71 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/generated/buildfiles @@ -0,0 +1 @@ +C:/Users/stefan.feilmeier/fems/develop3/io.openems.edge.timeofusetariff.entsoe/generated/io.openems.edge.timeofusetariff.entsoe.jar diff --git a/io.openems.edge.timeofusetariff.entsoe/readme.adoc b/io.openems.edge.timeofusetariff.entsoe/readme.adoc new file mode 100644 index 00000000000..02d969de42e --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/readme.adoc @@ -0,0 +1,7 @@ += Time-Of-Use Tariff ENTSO-E + +This implementation uses the ENTSO-E transparency platform to receive day-ahead prices in European power grids. + +To request a (free) authentication token, please see chapter "2. Authentication and Authorisation" in the official API documentation: https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_authentication_and_authorisation + +https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.entsoe[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java new file mode 100644 index 00000000000..702ec596473 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java @@ -0,0 +1,24 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@ObjectClassDefinition(// + name = "Time-Of-Use Tariff ENTSO-E", // + description = "Time-Of-Use Tariff implementation that uses the ENTSO-E transparency platform.") +@interface Config { + + @AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component") + String id() default "timeOfUseTariff0"; + + @AttributeDefinition(name = "Alias", description = "Human-readable name of this Component; defaults to Component-ID") + String alias() default ""; + + @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") + boolean enabled() default true; + + @AttributeDefinition(name = "Security Token", description = "Security token for the ENTSO-E Transparency Platform") + String securityToken(); + + String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff ENTSO-E [{id}]"; +} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java new file mode 100644 index 00000000000..a94d09c7730 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java @@ -0,0 +1,82 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; + +public class EntsoeApi { + + public final static DateTimeFormatter FORMATTER_MINUTES = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mmX"); + public final static DateTimeFormatter FORMATTER_SECONDS = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mm:ssX"); + public final static ZoneId UTC = ZoneId.of("UTC"); + public final static String URI = "https://web-api.tp.entsoe.eu/api"; + + /** + * Queries the ENTSO-E API for day-ahead prices + * + * @param token the Security Token + * @param areaCode Area EIC code; see + * https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_areas + * @param fromDate the From-Date + * @param toDate the To-Date + * @return + * @throws IOException on error + */ + protected static String query(String token, String areaCode, ZonedDateTime fromDate, ZonedDateTime toDate) + throws IOException { + var client = new OkHttpClient(); + var request = new Request.Builder() // + .url(URI) // + .header("SECURITY_TOKEN", token) // + .post(RequestBody + // ProcessType A01 -> Day ahead + // DocumentType A44 -> Price Document + .create(""" + + SampleCallToRestfulApi + A59 + 10X1001A1001A450 + A07 + 10X1001A1001A450 + A32 + %s + + DocumentType + A44 + + + In_Domain + %s + + + Out_Domain + %s + + + TimeInterval + %s/%s + + """ + .formatted(// + FORMATTER_SECONDS.format(ZonedDateTime.now(UTC)), // + areaCode, areaCode, // + FORMATTER_MINUTES.format(fromDate.withZoneSameInstant(UTC)), // + FORMATTER_MINUTES.format(toDate.withZoneSameInstant(UTC)) // + ), MediaType.parse("application/xml"))) // + .build(); + + try (var response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + response); + } + + return response.body().string(); + } + } +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java new file mode 100644 index 00000000000..e2e535f897e --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java @@ -0,0 +1,35 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventConstants; +import org.osgi.service.event.EventHandler; +import org.osgi.service.metatype.annotations.Designate; + +import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.event.EdgeEventConstants; + +public interface TouEntsoe extends OpenemsComponent, EventHandler { + + public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + ; + + private final Doc doc; + + private ChannelId(Doc doc) { + this.doc = doc; + } + + @Override + public Doc doc() { + return this.doc; + } + } + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java new file mode 100644 index 00000000000..edbf4756895 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -0,0 +1,64 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventConstants; +import org.osgi.service.event.EventHandler; +import org.osgi.service.metatype.annotations.Designate; + +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.event.EdgeEventConstants; + +@Designate(ocd = Config.class, factory = true) +@Component(// + name = "TimeOfUseTariff.ENTSO-E", // + immediate = true, // + configurationPolicy = ConfigurationPolicy.REQUIRE, // + property = { // + EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE // + } // +) +public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe, OpenemsComponent, EventHandler { + + private Config config = null; + + public TouEntsoeImpl() { + super(// + OpenemsComponent.ChannelId.values(), // + TouEntsoe.ChannelId.values() // + ); + } + + @Activate + private void activate(ComponentContext context, Config config) { + super.activate(context, config.id(), config.alias(), config.enabled()); + this.config = config; + } + + @Deactivate + protected void deactivate() { + super.deactivate(); + } + + @Override + public void handleEvent(Event event) { + if (!this.isEnabled()) { + return; + } + switch (event.getTopic()) { + case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE: + // TODO: fill channels + break; + } + } + + @Override + public String debugLog() { + return "Hello World"; + } +} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java new file mode 100644 index 00000000000..d4cec469cfc --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java @@ -0,0 +1,654 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import static io.openems.common.utils.XmlUtils.stream; +import static io.openems.edge.timeofusetariff.entsoe.EntsoeApi.FORMATTER_MINUTES; + +import java.io.IOException; +import java.io.StringReader; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.Test; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import io.openems.common.utils.XmlUtils; + +public class EntsoeApiTest { + + @Test + public void testQuery() throws IOException { + var token = ""; + var areaCode = "10Y1001A1001A82H"; + var fromDate = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("Europe/Berlin")); + var toDate = fromDate.plusDays(1); + + var result = EntsoeApi.query(token, areaCode, fromDate, toDate); + + System.out.println(result); + } + + private static String XML = """ + + + 946edcf0cf33426aa666e1069ececbe7 + 1 + A44 + 10X1001A1001A450 + A32 + 10X1001A1001A450 + A33 + 2023-06-01T12:40:44Z + + 2023-05-31T22:00Z + 2023-06-01T22:00Z + + + 1 + A62 + 10Y1001A1001A82H + 10Y1001A1001A82H + EUR + MWH + A01 + + + 2023-05-31T22:00Z + 2023-06-01T22:00Z + + PT15M + + 1 + 109.93 + + + 2 + 85.84 + + + 3 + 65.09 + + + 4 + 55.07 + + + 5 + 90.10 + + + 6 + 78.30 + + + 7 + 71.20 + + + 8 + 60.80 + + + 9 + 79.70 + + + 10 + 70.60 + + + 11 + 75.10 + + + 12 + 66.14 + + + 13 + 74.00 + + + 14 + 70.60 + + + 15 + 70.20 + + + 16 + 71.34 + + + 17 + 52.70 + + + 18 + 62.10 + + + 19 + 77.40 + + + 20 + 93.40 + + + 21 + 40.60 + + + 22 + 56.20 + + + 23 + 87.20 + + + 24 + 144.06 + + + 25 + 50.68 + + + 26 + 96.20 + + + 27 + 117.60 + + + 28 + 121.50 + + + 29 + 125.10 + + + 30 + 111.80 + + + 31 + 91.80 + + + 32 + 77.46 + + + 33 + 179.06 + + + 34 + 111.90 + + + 35 + 69.50 + + + 36 + 30.10 + + + 37 + 151.70 + + + 38 + 96.20 + + + 39 + 69.91 + + + 40 + 7.20 + + + 41 + 114.91 + + + 42 + 75.20 + + + 43 + 49.90 + + + 44 + 1.20 + + + 45 + 89.91 + + + 46 + 64.90 + + + 47 + 29.20 + + + 48 + -11.47 + + + 49 + 69.80 + + + 50 + 34.90 + + + 51 + 6.10 + + + 52 + -20.00 + + + 53 + 44.20 + + + 54 + 24.91 + + + 55 + -12.03 + + + 56 + -19.94 + + + 57 + -25.30 + + + 58 + -13.50 + + + 59 + 18.83 + + + 60 + 39.90 + + + 61 + -49.90 + + + 62 + -10.80 + + + 63 + 21.40 + + + 64 + 61.62 + + + 65 + -61.69 + + + 66 + 7.16 + + + 67 + 58.60 + + + 68 + 109.92 + + + 69 + -35.10 + + + 70 + 39.93 + + + 71 + 109.93 + + + 72 + 139.94 + + + 73 + 29.91 + + + 74 + 54.10 + + + 75 + 105.11 + + + 76 + 149.91 + + + 77 + 75.05 + + + 78 + 93.33 + + + 79 + 130.10 + + + 80 + 139.96 + + + 81 + 139.92 + + + 82 + 110.22 + + + 83 + 95.08 + + + 84 + 95.05 + + + 85 + 139.98 + + + 86 + 114.80 + + + 87 + 90.09 + + + 88 + 80.07 + + + 89 + 134.70 + + + 90 + 100.68 + + + 91 + 80.03 + + + 92 + 70.02 + + + 93 + 119.70 + + + 94 + 82.41 + + + 95 + 75.10 + + + 96 + 65.07 + + + + + 2 + A62 + 10Y1001A1001A82H + 10Y1001A1001A82H + EUR + MWH + A01 + + + 2023-05-31T22:00Z + 2023-06-01T22:00Z + + PT60M + + 1 + 84.15 + + + 2 + 74.30 + + + 3 + 70.10 + + + 4 + 66.72 + + + 5 + 67.70 + + + 6 + 80.45 + + + 7 + 97.04 + + + 8 + 108.23 + + + 9 + 99.07 + + + 10 + 87.30 + + + 11 + 68.19 + + + 12 + 59.92 + + + 13 + 38.86 + + + 14 + 9.35 + + + 15 + 3.01 + + + 16 + 13.35 + + + 17 + 56.14 + + + 18 + 72.00 + + + 19 + 87.86 + + + 20 + 100.46 + + + 21 + 120.04 + + + 22 + 103.43 + + + 23 + 95.41 + + + 24 + 86.53 + + + + + """; + + private static record QueryResult(ZonedDateTime start, List prices) { + protected static class Builder { + private ZonedDateTime start; + private List prices; + + public Builder start(ZonedDateTime start) { + this.start = start; + return this; + } + + public Builder prices(List prices) { + this.prices = prices; + return this; + } + + public QueryResult build() { + return new QueryResult(this.start, this.prices); + } + } + + public static Builder create() { + return new Builder(); + } + } + + /** + * + * @param xml + * @param resolution PT15M or PT60M + * @return + * @throws ParserConfigurationException + * @throws SAXException + * @throws IOException + */ + private QueryResult parse(String xml, String resolution) + throws ParserConfigurationException, SAXException, IOException { + var dbFactory = DocumentBuilderFactory.newInstance(); + var dBuilder = dbFactory.newDocumentBuilder(); + var is = new InputSource(new StringReader(XML)); + var doc = dBuilder.parse(is); + var root = doc.getDocumentElement(); + + var result = QueryResult.create(); + + stream(root) // + // + .filter(n -> n.getNodeName() == "TimeSeries") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "Period") // + // Find Period with correct resolution + .filter(p -> stream(p) // + .filter(n -> n.getNodeName() == "resolution") // + .map(XmlUtils::getContentAsString) // + .anyMatch(r -> r.equals(resolution))) // + .forEach(period -> { + result.start(ZonedDateTime.parse(// + stream(period) // + // + .filter(n -> n.getNodeName() == "timeInterval") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "start") // + .map(XmlUtils::getContentAsString) // + .findFirst().get(), + FORMATTER_MINUTES)); + + result.prices(stream(period) // + // + .filter(n -> n.getNodeName() == "Point") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "price.amount") // + .map(XmlUtils::getContentAsString) // + .map(Double::parseDouble) // + .toList()); + }); + ; + + return result.build(); + } + + @Test + public void testParse() throws IOException, ParserConfigurationException, SAXException { +// EntsoeApi.parse(XML); + + var result = parse(XML, "PT15M"); + System.out.println(result); + } +} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java new file mode 100644 index 00000000000..5599b33b98f --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java @@ -0,0 +1,51 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import io.openems.common.test.AbstractComponentConfig; + +@SuppressWarnings("all") +public class MyConfig extends AbstractComponentConfig implements Config { + + protected static class Builder { + private String id; + public String securityToken; + + private Builder() { + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setSecurityToken(String securityToken) { + this.securityToken = securityToken; + return this; + } + + public MyConfig build() { + return new MyConfig(this); + } + } + + /** + * Create a Config builder. + * + * @return a {@link Builder} + */ + public static Builder create() { + return new Builder(); + } + + private final Builder builder; + + private MyConfig(Builder builder) { + super(Config.class, builder.id); + this.builder = builder; + } + + @Override + public String securityToken() { + return this.builder.securityToken; + } + +} \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java new file mode 100644 index 00000000000..621fd080584 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java @@ -0,0 +1,5 @@ +package io.openems.edge.timeofusetariff.entsoe; + +public class ParserTest { + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java new file mode 100644 index 00000000000..feab26d6a5d --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java @@ -0,0 +1,21 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import org.junit.Test; + +import io.openems.edge.common.test.AbstractComponentTest.TestCase; +import io.openems.edge.common.test.ComponentTest; + +public class TouEntsoeTest { + + private static final String COMPONENT_ID = "tou0"; + + @Test + public void test() throws Exception { + new ComponentTest(new TouEntsoeImpl()) // + .activate(MyConfig.create() // + .setId(COMPONENT_ID) // + .build()) + .next(new TestCase()); + } + +} From e4122cf2975d0a2badac5c990ecac9b2a292a457 Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Tue, 25 Jul 2023 16:02:19 +0200 Subject: [PATCH 02/16] Modified existing Entso-E implementation. Implemented new Exchange rate API. --- io.openems.edge.application/EdgeApp.bndrun | 2 + .../edge/common/currency/Currency.java | 35 + .../edge/common/currency/package-info.java | 3 + .../bin_test/.gitignore | 0 .../.gitignore | 1 + .../bnd.bnd | 4 +- .../timeofusetariff/entsoe/BiddingZone.java | 44 ++ .../edge/timeofusetariff/entsoe/Config.java | 11 +- .../timeofusetariff/entsoe/EntsoeApi.java | 2 +- .../entsoe/ExchangeRateApi.java | 37 + .../timeofusetariff/entsoe/TouEntsoe.java | 18 +- .../timeofusetariff/entsoe/TouEntsoeImpl.java | 129 +++- .../edge/timeofusetariff/entsoe/Utils.java | 147 ++++ .../timeofusetariff/entsoe/EntsoeApiTest.java | 641 +----------------- .../entsoe/ExchangeRateApiTest.java | 213 ++++++ .../edge/timeofusetariff/entsoe/MyConfig.java | 25 +- .../timeofusetariff/entsoe/ParserTest.java | 544 +++++++++++++++ .../timeofusetariff/entsoe/TouEntsoeTest.java | 10 +- 18 files changed, 1192 insertions(+), 674 deletions(-) create mode 100644 io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java create mode 100644 io.openems.edge.common/src/io/openems/edge/common/currency/package-info.java create mode 100644 io.openems.edge.controller.ess.timeofusetariff/bin_test/.gitignore create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index 3858b2e831f..dc83559f5a5 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -178,6 +178,7 @@ bnd.identity;id='io.openems.edge.timeofusetariff.awattar',\ bnd.identity;id='io.openems.edge.timeofusetariff.corrently',\ bnd.identity;id='io.openems.edge.timeofusetariff.tibber',\ + bnd.identity;id='io.openems.edge.timeofusetariff.entsoe',\ -runbundles: \ Java-WebSocket;version='[1.5.3,1.5.4)',\ @@ -343,6 +344,7 @@ io.openems.edge.timeofusetariff.awattar;version=snapshot,\ io.openems.edge.timeofusetariff.corrently;version=snapshot,\ io.openems.edge.timeofusetariff.tibber;version=snapshot,\ + io.openems.edge.timeofusetariff.entsoe;version=snapshot,\ io.openems.shared.influxdb;version=snapshot,\ io.openems.wrapper.eu.chargetime.ocpp;version=snapshot,\ io.openems.wrapper.fastexcel;version=snapshot,\ diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java new file mode 100644 index 00000000000..0b4aa119e26 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java @@ -0,0 +1,35 @@ +package io.openems.edge.common.currency; + +import io.openems.common.types.OptionsEnum; + +public enum Currency implements OptionsEnum { + UNDEFINED(-1, "-"), // + EUR(0, "€"), // + SEK(1, "kr"), // + USD(2, "$"); + + private final String name; + private final int value; + public static final Currency DEFAULT = EUR; + + private Currency(int value, String name) { + this.value = value; + this.name = name; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public OptionsEnum getUndefined() { + return Currency.UNDEFINED; + } + +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/package-info.java b/io.openems.edge.common/src/io/openems/edge/common/currency/package-info.java new file mode 100644 index 00000000000..af89b5b5e53 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.edge.common.currency; diff --git a/io.openems.edge.controller.ess.timeofusetariff/bin_test/.gitignore b/io.openems.edge.controller.ess.timeofusetariff/bin_test/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/io.openems.edge.timeofusetariff.entsoe/.gitignore b/io.openems.edge.timeofusetariff.entsoe/.gitignore index 1c316c001c9..c2b941a96de 100644 --- a/io.openems.edge.timeofusetariff.entsoe/.gitignore +++ b/io.openems.edge.timeofusetariff.entsoe/.gitignore @@ -1 +1,2 @@ /bin_test/ +/generated/ diff --git a/io.openems.edge.timeofusetariff.entsoe/bnd.bnd b/io.openems.edge.timeofusetariff.entsoe/bnd.bnd index fe633fabf09..68b6fbaf51c 100644 --- a/io.openems.edge.timeofusetariff.entsoe/bnd.bnd +++ b/io.openems.edge.timeofusetariff.entsoe/bnd.bnd @@ -6,11 +6,11 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ com.squareup.okio,\ - io.openems.common,\ io.openems.edge.common,\ io.openems.edge.timeofusetariff.api,\ io.openems.wrapper.okhttp,\ - + io.openems.common,\ + -testpath: \ com.squareup.okio,\ io.openems.wrapper.kotlinx-coroutines-core-jvm,\ diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java new file mode 100644 index 00000000000..e29a63cfbb4 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java @@ -0,0 +1,44 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import io.openems.common.types.OptionsEnum; + +public enum BiddingZone implements OptionsEnum { + UNDEFINED(-1, "Undefined", "Undefined"), // + GERMANY(0, "10Y1001A1001A82H", "BZN|DE-LU"), // + AUSTRIA(1, "10YAT-APG------L", "BZN|AT"), // + SWEDEN_ZONE_1(2, "10Y1001A1001A44P", "BZN|SE1"), // + SWEDEN_ZONE_2(3, "10Y1001A1001A45N", "BZN|SE2"), // + SWEDEN_ZONE_3(4, "10Y1001A1001A46L", "BZN|SE3"), // + SWEDEN_ZONE_4(5, "10Y1001A1001A47J", "BZN|SE4"), // + ; + + private final int value; + private final String code; + private final String zone; + + private BiddingZone(int value, String code, String zone) { + this.value = value; + this.code = code; + this.zone = zone; + } + + @Override + public OptionsEnum getUndefined() { + return UNDEFINED; + } + + @Override + public int getValue() { + return this.value; + } + + @Override + public String getName() { + return this.code; + } + + public String getZone() { + return this.zone; + } + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java index 702ec596473..df49c8e38e9 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java @@ -1,8 +1,11 @@ package io.openems.edge.timeofusetariff.entsoe; import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.AttributeType; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.edge.common.currency.Currency; + @ObjectClassDefinition(// name = "Time-Of-Use Tariff ENTSO-E", // description = "Time-Of-Use Tariff implementation that uses the ENTSO-E transparency platform.") @@ -17,8 +20,14 @@ @AttributeDefinition(name = "Is enabled?", description = "Is this Component enabled?") boolean enabled() default true; - @AttributeDefinition(name = "Security Token", description = "Security token for the ENTSO-E Transparency Platform") + @AttributeDefinition(name = "Security Token", description = "Security token for the ENTSO-E Transparency Platform", type = AttributeType.PASSWORD) String securityToken(); + @AttributeDefinition(name = "Bidding Zone", description = "Zone corresponding to the customer's location") + BiddingZone biddingZone() default BiddingZone.GERMANY; + + @AttributeDefinition(name = "Currency", description = "Currency to be used for energy purchase; Energy price value is converted to appropraite currency based on current exchange rate") + Currency currency() default Currency.EUR; + String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff ENTSO-E [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java index a94d09c7730..45a2f87e725 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java @@ -25,7 +25,7 @@ public class EntsoeApi { * https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_areas * @param fromDate the From-Date * @param toDate the To-Date - * @return + * @return The response string. * @throws IOException on error */ protected static String query(String token, String areaCode, ZonedDateTime fromDate, ZonedDateTime toDate) diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java new file mode 100644 index 00000000000..e6a50e6bbcd --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java @@ -0,0 +1,37 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import java.io.IOException; + +import io.openems.edge.common.currency.Currency; +import okhttp3.OkHttpClient; +import okhttp3.Request; + +public class ExchangeRateApi { + + private static final String BASE_URL = "https://api.exchangerate.host/latest?base=%s"; + private static final OkHttpClient client = new OkHttpClient(); + + /** + * Fetches the exchange rate from base currency EUR to the currency requested by + * user. + * + * @param currency {@link Currency} requested by User. + * @return the Response string. + * @throws IOException on error. + */ + protected static String getExchangeRate() throws IOException { + var URL = String.format(BASE_URL, "EUR"); + var request = new Request.Builder() // + .url(URL) // + .build(); + + try (var response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Failed to fetch exchange rate. HTTP status code: " + response.code()); + } + + return response.body().string(); + } + } + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java index e2e535f897e..e45643cc8e9 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java @@ -1,23 +1,15 @@ package io.openems.edge.timeofusetariff.entsoe; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.ConfigurationPolicy; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.event.Event; -import org.osgi.service.event.EventConstants; -import org.osgi.service.event.EventHandler; -import org.osgi.service.metatype.annotations.Designate; - +import io.openems.common.channel.Level; import io.openems.edge.common.channel.Doc; -import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; -public interface TouEntsoe extends OpenemsComponent, EventHandler { +public interface TouEntsoe extends OpenemsComponent, TimeOfUseTariff { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { + UNABLE_TO_UPDATE_PRICES(Doc.of(Level.WARNING) // + .text("Unable to update prices from Entsoe API")), // ; private final Doc doc; diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index edbf4756895..53d376a8205 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -1,32 +1,63 @@ package io.openems.edge.timeofusetariff.entsoe; +import java.io.IOException; +import java.time.Clock; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import javax.xml.parsers.ParserConfigurationException; + import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.event.Event; -import org.osgi.service.event.EventConstants; -import org.osgi.service.event.EventHandler; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.ThreadPoolUtils; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.event.EdgeEventConstants; +import io.openems.edge.common.currency.Currency; +import io.openems.edge.common.meta.Meta; +import io.openems.edge.timeofusetariff.api.TimeOfUsePrices; +import io.openems.edge.timeofusetariff.api.TimeOfUseTariff; +import io.openems.edge.timeofusetariff.api.utils.TimeOfUseTariffUtils; @Designate(ocd = Config.class, factory = true) @Component(// name = "TimeOfUseTariff.ENTSO-E", // immediate = true, // - configurationPolicy = ConfigurationPolicy.REQUIRE, // - property = { // - EventConstants.EVENT_TOPIC + "=" + EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE // - } // + configurationPolicy = ConfigurationPolicy.REQUIRE // ) -public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe, OpenemsComponent, EventHandler { +public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe, OpenemsComponent, TimeOfUseTariff { + + private final Logger log = LoggerFactory.getLogger(TouEntsoeImpl.class); + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + private final AtomicReference> prices = new AtomicReference<>( + ImmutableSortedMap.of()); private Config config = null; + private ZonedDateTime updateTimeStamp = null; + + @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) + private Meta meta; + public TouEntsoeImpl() { super(// OpenemsComponent.ChannelId.values(), // @@ -37,28 +68,86 @@ public TouEntsoeImpl() { @Activate private void activate(ComponentContext context, Config config) { super.activate(context, config.id(), config.alias(), config.enabled()); + + if (!config.enabled()) { + return; + } + this.config = config; + this.executor.schedule(this.task, 0, TimeUnit.SECONDS); } @Deactivate protected void deactivate() { super.deactivate(); + ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 0); } - @Override - public void handleEvent(Event event) { - if (!this.isEnabled()) { - return; + private final Runnable task = () -> { + + var token = this.config.securityToken(); + var areaCode = this.config.biddingZone().getName(); + var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS); + var toDate = fromDate.plusDays(1); + var unableToUpdatePrices = false; + + try { + var result = EntsoeApi.query(token, areaCode, fromDate, toDate); + final double exchangeRate; + + if (this.config.currency() == Currency.EUR) { + // No need to fetch from API. + exchangeRate = 1.0; + } else { + exchangeRate = Utils.exchangeRateParser(ExchangeRateApi.getExchangeRate(), this.config.currency()); + } + + System.out.println("rate: " + exchangeRate); + + // Parse the response for the prices + this.prices.set(Utils.parse(result, "PT60M", exchangeRate)); + + // store the time stamp + this.updateTimeStamp = ZonedDateTime.now(); + } catch (IOException | ParserConfigurationException | SAXException | OpenemsNamedException e) { + e.printStackTrace(); + if (e instanceof OpenemsNamedException) { + this.logWarn(this.log, "Unable to get the currency exchange rate " + e.getMessage()); + } else { + this.logWarn(this.log, "Unable to Update Entsoe Time-Of-Use Price: " + e.getMessage()); + } + unableToUpdatePrices = true; } - switch (event.getTopic()) { - case EdgeEventConstants.TOPIC_CYCLE_BEFORE_PROCESS_IMAGE: - // TODO: fill channels - break; + + this.channel(TouEntsoe.ChannelId.UNABLE_TO_UPDATE_PRICES).setNextValue(unableToUpdatePrices); + + /* + * Schedule next price update at 2 o clock every day. + */ + var now = ZonedDateTime.now(); + var nextRun = now.withHour(14).truncatedTo(ChronoUnit.HOURS); + if (unableToUpdatePrices) { + // If the prices are not updated, try again in next minute. + nextRun = now.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES); + this.logWarn(this.log, "Unable to Update the prices, Trying again at: " + nextRun); + } else if (now.isAfter(nextRun)) { + nextRun = nextRun.plusDays(1); } - } + + var delay = Duration.between(now, nextRun).getSeconds(); + + this.executor.schedule(this.task, delay, TimeUnit.SECONDS); + }; @Override - public String debugLog() { - return "Hello World"; + public TimeOfUsePrices getPrices() { + // return empty TimeOfUsePrices if data is not yet available. + if (this.updateTimeStamp == null) { + return TimeOfUsePrices.empty(ZonedDateTime.now()); + } + + return TimeOfUseTariffUtils.getNext24HourPrices(Clock.systemDefaultZone() /* can be mocked for testing */, + this.prices.get(), this.updateTimeStamp); } + } diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java new file mode 100644 index 00000000000..b2bf6bc9548 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java @@ -0,0 +1,147 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import static io.openems.common.utils.XmlUtils.stream; + +import java.io.IOException; +import java.io.StringReader; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import com.google.common.collect.ImmutableSortedMap; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.utils.JsonUtils; +import io.openems.common.utils.XmlUtils; +import io.openems.edge.common.currency.Currency; + +public class Utils { + + public final static DateTimeFormatter FORMATTER_MINUTES = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mmX"); + + private static record QueryResult(ZonedDateTime start, List prices) { + protected static class Builder { + private ZonedDateTime start; + private List prices = new ArrayList<>();; + + public Builder start(ZonedDateTime start) { + this.start = start; + return this; + } + + public Builder prices(List prices) { + this.prices.addAll(prices); + return this; + } + + // public QueryResult build() { + // return new QueryResult(this.start, this.prices); + // } + + public ImmutableSortedMap toMap() { + + var result = new TreeMap(); + var timestamp = this.start.withZoneSameInstant(ZoneId.systemDefault()); + var quarterHourIncrements = this.prices.size() * 4; + + for (int i = 0; i < quarterHourIncrements; i++) { + result.put(timestamp, this.prices.get(i / 4)); + timestamp = timestamp.plusMinutes(15); + } + + return ImmutableSortedMap.copyOf(result); + }; + } + + public static Builder create() { + return new Builder(); + } + } + + /** + * + * @param xml + * @param resolution PT15M or PT60M + * @return + * @throws ParserConfigurationException + * @throws SAXException + * @throws IOException + */ + protected static ImmutableSortedMap parse(String xml, String resolution, + double currencyExchangeValue) throws ParserConfigurationException, SAXException, IOException { + var dbFactory = DocumentBuilderFactory.newInstance(); + var dBuilder = dbFactory.newDocumentBuilder(); + var is = new InputSource(new StringReader(xml)); + var doc = dBuilder.parse(is); + var root = doc.getDocumentElement(); + var result = QueryResult.create(); + + stream(root) // + // + .filter(n -> n.getNodeName() == "TimeSeries") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "Period") // + // Find Period with correct resolution + .filter(p -> stream(p) // + .filter(n -> n.getNodeName() == "resolution") // + .map(XmlUtils::getContentAsString) // + .anyMatch(r -> r.equals(resolution))) // + .forEach(period -> { + + var start = ZonedDateTime.parse(// + stream(period) // + // + .filter(n -> n.getNodeName() == "timeInterval") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "start") // + .map(XmlUtils::getContentAsString) // + .findFirst().get(), + FORMATTER_MINUTES).withZoneSameInstant(ZoneId.of("UTC")); + + if (result.start == null) { + // Avoiding overwriting of start due to multiple periods. + result.start(start); + } + + result.prices(stream(period) // + // + .filter(n -> n.getNodeName() == "Point") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "price.amount") // + .map(XmlUtils::getContentAsString) // + .map(s -> Float.parseFloat(s) * (float) currencyExchangeValue) // + .toList()); + }); + + return result.toMap(); + } + + /** + * Parses the response string from Exchange rate API. + * + * @param response The Response string from ExcahngeRate API. + * @param currency The {@link Curreny} selected by User. + * @return the exchange rate. + * @throws OpenemsNamedException on error. + */ + protected static Double exchangeRateParser(String response, Currency currency) throws OpenemsNamedException { + + var line = JsonUtils.parseToJsonObject(response); + var data = JsonUtils.getAsJsonObject(line, "rates"); + + return JsonUtils.getAsDouble(data, currency.toString()); + } + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java index d4cec469cfc..752875c77b3 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java @@ -1,654 +1,25 @@ package io.openems.edge.timeofusetariff.entsoe; -import static io.openems.common.utils.XmlUtils.stream; -import static io.openems.edge.timeofusetariff.entsoe.EntsoeApi.FORMATTER_MINUTES; - import java.io.IOException; -import java.io.StringReader; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.List; +import java.time.temporal.ChronoUnit; -import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.junit.Test; -import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import io.openems.common.utils.XmlUtils; - public class EntsoeApiTest { @Test - public void testQuery() throws IOException { + public void testQuery() throws IOException, ParserConfigurationException, SAXException { var token = ""; - var areaCode = "10Y1001A1001A82H"; - var fromDate = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("Europe/Berlin")); + var areaCode = BiddingZone.SWEDEN_ZONE_3.getName(); + var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS).withZoneSameLocal(ZoneId.systemDefault()); var toDate = fromDate.plusDays(1); + var response = EntsoeApi.query(token, areaCode, fromDate, toDate); - var result = EntsoeApi.query(token, areaCode, fromDate, toDate); - - System.out.println(result); - } - - private static String XML = """ - - - 946edcf0cf33426aa666e1069ececbe7 - 1 - A44 - 10X1001A1001A450 - A32 - 10X1001A1001A450 - A33 - 2023-06-01T12:40:44Z - - 2023-05-31T22:00Z - 2023-06-01T22:00Z - - - 1 - A62 - 10Y1001A1001A82H - 10Y1001A1001A82H - EUR - MWH - A01 - - - 2023-05-31T22:00Z - 2023-06-01T22:00Z - - PT15M - - 1 - 109.93 - - - 2 - 85.84 - - - 3 - 65.09 - - - 4 - 55.07 - - - 5 - 90.10 - - - 6 - 78.30 - - - 7 - 71.20 - - - 8 - 60.80 - - - 9 - 79.70 - - - 10 - 70.60 - - - 11 - 75.10 - - - 12 - 66.14 - - - 13 - 74.00 - - - 14 - 70.60 - - - 15 - 70.20 - - - 16 - 71.34 - - - 17 - 52.70 - - - 18 - 62.10 - - - 19 - 77.40 - - - 20 - 93.40 - - - 21 - 40.60 - - - 22 - 56.20 - - - 23 - 87.20 - - - 24 - 144.06 - - - 25 - 50.68 - - - 26 - 96.20 - - - 27 - 117.60 - - - 28 - 121.50 - - - 29 - 125.10 - - - 30 - 111.80 - - - 31 - 91.80 - - - 32 - 77.46 - - - 33 - 179.06 - - - 34 - 111.90 - - - 35 - 69.50 - - - 36 - 30.10 - - - 37 - 151.70 - - - 38 - 96.20 - - - 39 - 69.91 - - - 40 - 7.20 - - - 41 - 114.91 - - - 42 - 75.20 - - - 43 - 49.90 - - - 44 - 1.20 - - - 45 - 89.91 - - - 46 - 64.90 - - - 47 - 29.20 - - - 48 - -11.47 - - - 49 - 69.80 - - - 50 - 34.90 - - - 51 - 6.10 - - - 52 - -20.00 - - - 53 - 44.20 - - - 54 - 24.91 - - - 55 - -12.03 - - - 56 - -19.94 - - - 57 - -25.30 - - - 58 - -13.50 - - - 59 - 18.83 - - - 60 - 39.90 - - - 61 - -49.90 - - - 62 - -10.80 - - - 63 - 21.40 - - - 64 - 61.62 - - - 65 - -61.69 - - - 66 - 7.16 - - - 67 - 58.60 - - - 68 - 109.92 - - - 69 - -35.10 - - - 70 - 39.93 - - - 71 - 109.93 - - - 72 - 139.94 - - - 73 - 29.91 - - - 74 - 54.10 - - - 75 - 105.11 - - - 76 - 149.91 - - - 77 - 75.05 - - - 78 - 93.33 - - - 79 - 130.10 - - - 80 - 139.96 - - - 81 - 139.92 - - - 82 - 110.22 - - - 83 - 95.08 - - - 84 - 95.05 - - - 85 - 139.98 - - - 86 - 114.80 - - - 87 - 90.09 - - - 88 - 80.07 - - - 89 - 134.70 - - - 90 - 100.68 - - - 91 - 80.03 - - - 92 - 70.02 - - - 93 - 119.70 - - - 94 - 82.41 - - - 95 - 75.10 - - - 96 - 65.07 - - - - - 2 - A62 - 10Y1001A1001A82H - 10Y1001A1001A82H - EUR - MWH - A01 - - - 2023-05-31T22:00Z - 2023-06-01T22:00Z - - PT60M - - 1 - 84.15 - - - 2 - 74.30 - - - 3 - 70.10 - - - 4 - 66.72 - - - 5 - 67.70 - - - 6 - 80.45 - - - 7 - 97.04 - - - 8 - 108.23 - - - 9 - 99.07 - - - 10 - 87.30 - - - 11 - 68.19 - - - 12 - 59.92 - - - 13 - 38.86 - - - 14 - 9.35 - - - 15 - 3.01 - - - 16 - 13.35 - - - 17 - 56.14 - - - 18 - 72.00 - - - 19 - 87.86 - - - 20 - 100.46 - - - 21 - 120.04 - - - 22 - 103.43 - - - 23 - 95.41 - - - 24 - 86.53 - - - - - """; - - private static record QueryResult(ZonedDateTime start, List prices) { - protected static class Builder { - private ZonedDateTime start; - private List prices; - - public Builder start(ZonedDateTime start) { - this.start = start; - return this; - } - - public Builder prices(List prices) { - this.prices = prices; - return this; - } - - public QueryResult build() { - return new QueryResult(this.start, this.prices); - } - } - - public static Builder create() { - return new Builder(); - } - } - - /** - * - * @param xml - * @param resolution PT15M or PT60M - * @return - * @throws ParserConfigurationException - * @throws SAXException - * @throws IOException - */ - private QueryResult parse(String xml, String resolution) - throws ParserConfigurationException, SAXException, IOException { - var dbFactory = DocumentBuilderFactory.newInstance(); - var dBuilder = dbFactory.newDocumentBuilder(); - var is = new InputSource(new StringReader(XML)); - var doc = dBuilder.parse(is); - var root = doc.getDocumentElement(); - - var result = QueryResult.create(); - - stream(root) // - // - .filter(n -> n.getNodeName() == "TimeSeries") // - .flatMap(XmlUtils::stream) // - // - .filter(n -> n.getNodeName() == "Period") // - // Find Period with correct resolution - .filter(p -> stream(p) // - .filter(n -> n.getNodeName() == "resolution") // - .map(XmlUtils::getContentAsString) // - .anyMatch(r -> r.equals(resolution))) // - .forEach(period -> { - result.start(ZonedDateTime.parse(// - stream(period) // - // - .filter(n -> n.getNodeName() == "timeInterval") // - .flatMap(XmlUtils::stream) // - // - .filter(n -> n.getNodeName() == "start") // - .map(XmlUtils::getContentAsString) // - .findFirst().get(), - FORMATTER_MINUTES)); - - result.prices(stream(period) // - // - .filter(n -> n.getNodeName() == "Point") // - .flatMap(XmlUtils::stream) // - // - .filter(n -> n.getNodeName() == "price.amount") // - .map(XmlUtils::getContentAsString) // - .map(Double::parseDouble) // - .toList()); - }); - ; - - return result.build(); - } - - @Test - public void testParse() throws IOException, ParserConfigurationException, SAXException { -// EntsoeApi.parse(XML); - - var result = parse(XML, "PT15M"); - System.out.println(result); + System.out.println(response); } } diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java new file mode 100644 index 00000000000..8845829d46b --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java @@ -0,0 +1,213 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.junit.Ignore; +import org.junit.Test; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.edge.common.currency.Currency; + +public class ExchangeRateApiTest { + + private static final String EXCHANGE_DATA = "{\n" + + "\"success\": true,\n" + + "\"base\": \"EUR\",\n" + + "\"date\": \"2023-07-24\",\n" + + "\"rates\": {\n" + + "\"AED\": 4.083409,\n" + + "\"AFN\": 95.203885,\n" + + "\"ALL\": 100.955251,\n" + + "\"AMD\": 432.377222,\n" + + "\"ANG\": 2.00259,\n" + + "\"AOA\": 918.778961,\n" + + "\"ARS\": 298.697752,\n" + + "\"AUD\": 1.651218,\n" + + "\"AWG\": 2.003466,\n" + + "\"AZN\": 1.890136,\n" + + "\"BAM\": 1.953145,\n" + + "\"BBD\": 2.223345,\n" + + "\"BDT\": 120.586719,\n" + + "\"BGN\": 1.953979,\n" + + "\"BHD\": 0.418898,\n" + + "\"BIF\": 3145.089913,\n" + + "\"BMD\": 1.11291,\n" + + "\"BND\": 1.477507,\n" + + "\"BOB\": 7.677482,\n" + + "\"BRL\": 5.312471,\n" + + "\"BSD\": 1.112521,\n" + + "\"BTC\": 0.000037,\n" + + "\"BTN\": 91.091466,\n" + + "\"BWP\": 14.618309,\n" + + "\"BYN\": 2.804583,\n" + + "\"BZD\": 2.239344,\n" + + "\"CAD\": 1.469717,\n" + + "\"CDF\": 2756.233722,\n" + + "\"CHF\": 0.96343,\n" + + "\"CLF\": 0.033239,\n" + + "\"CLP\": 907.642573,\n" + + "\"CNH\": 7.999695,\n" + + "\"CNY\": 7.996629,\n" + + "\"COP\": 4424.597438,\n" + + "\"CRC\": 595.871673,\n" + + "\"CUC\": 1.112533,\n" + + "\"CUP\": 28.624803,\n" + + "\"CVE\": 110.101152,\n" + + "\"CZK\": 24.018341,\n" + + "\"DJF\": 197.805497,\n" + + "\"DKK\": 7.446058,\n" + + "\"DOP\": 62.354298,\n" + + "\"DZD\": 149.782102,\n" + + "\"EGP\": 34.250999,\n" + + "\"ERN\": 16.675045,\n" + + "\"ETB\": 61.186276,\n" + + "\"EUR\": 1,\n" + + "\"FJD\": 2.466294,\n" + + "\"FKP\": 0.863468,\n" + + "\"GBP\": 0.86372,\n" + + "\"GEL\": 2.868411,\n" + + "\"GGP\": 0.863794,\n" + + "\"GHS\": 12.888427,\n" + + "\"GIP\": 0.864212,\n" + + "\"GMD\": 66.356165,\n" + + "\"GNF\": 9556.367239,\n" + + "\"GTQ\": 8.722648,\n" + + "\"GYD\": 232.625421,\n" + + "\"HKD\": 8.690213,\n" + + "\"HNL\": 27.354468,\n" + + "\"HRK\": 7.530486,\n" + + "\"HTG\": 151.687614,\n" + + "\"HUF\": 379.234306,\n" + + "\"IDR\": 16699.073705,\n" + + "\"ILS\": 4.026696,\n" + + "\"IMP\": 0.864168,\n" + + "\"INR\": 91.124393,\n" + + "\"IQD\": 1456.187686,\n" + + "\"IRR\": 46699.377014,\n" + + "\"ISK\": 146.216182,\n" + + "\"JEP\": 0.863718,\n" + + "\"JMD\": 171.45343,\n" + + "\"JOD\": 0.789061,\n" + + "\"JPY\": 157.232974,\n" + + "\"KES\": 157.988099,\n" + + "\"KGS\": 97.680423,\n" + + "\"KHR\": 4588.755627,\n" + + "\"KMF\": 492.572188,\n" + + "\"KPW\": 1000.485181,\n" + + "\"KRW\": 1424.639558,\n" + + "\"KWD\": 0.342156,\n" + + "\"KYD\": 0.927141,\n" + + "\"KZT\": 495.498053,\n" + + "\"LAK\": 21217.844772,\n" + + "\"LBP\": 16675.943499,\n" + + "\"LKR\": 364.901196,\n" + + "\"LRD\": 205.933361,\n" + + "\"LSL\": 20.023877,\n" + + "\"LYD\": 5.262548,\n" + + "\"MAD\": 10.765587,\n" + + "\"MDL\": 19.231847,\n" + + "\"MGA\": 4923.895352,\n" + + "\"MKD\": 61.432255,\n" + + "\"MMK\": 2333.03677,\n" + + "\"MNT\": 3911.893647,\n" + + "\"MOP\": 8.951275,\n" + + "\"MRU\": 38.013659,\n" + + "\"MUR\": 50.758404,\n" + + "\"MVR\": 17.064246,\n" + + "\"MWK\": 1170.314043,\n" + + "\"MXN\": 18.886283,\n" + + "\"MYR\": 5.075542,\n" + + "\"MZN\": 70.868602,\n" + + "\"NAD\": 19.976991,\n" + + "\"NGN\": 880.960008,\n" + + "\"NIO\": 40.643549,\n" + + "\"NOK\": 11.195761,\n" + + "\"NPR\": 145.777404,\n" + + "\"NZD\": 1.801523,\n" + + "\"OMR\": 0.42859,\n" + + "\"PAB\": 1.112509,\n" + + "\"PEN\": 3.98693,\n" + + "\"PGK\": 4.02331,\n" + + "\"PHP\": 60.767256,\n" + + "\"PKR\": 318.56528,\n" + + "\"PLN\": 4.460479,\n" + + "\"PYG\": 8082.233385,\n" + + "\"QAR\": 4.051879,\n" + + "\"RON\": 4.93425,\n" + + "\"RSD\": 117.206982,\n" + + "\"RUB\": 101.116135,\n" + + "\"RWF\": 1300.127368,\n" + + "\"SAR\": 4.170544,\n" + + "\"SBD\": 9.288389,\n" + + "\"SCR\": 14.985735,\n" + + "\"SDG\": 668.657232,\n" + + "\"SEK\": 11.553347,\n" + + "\"SGD\": 1.479788,\n" + + "\"SHP\": 0.863472,\n" + + "\"SLL\": 19637.280787,\n" + + "\"SOS\": 632.688782,\n" + + "\"SRD\": 42.722066,\n" + + "\"SSP\": 144.804499,\n" + + "\"STD\": 25372.268235,\n" + + "\"STN\": 24.470982,\n" + + "\"SVC\": 9.727935,\n" + + "\"SYP\": 2793.052605,\n" + + "\"SZL\": 20.003394,\n" + + "\"THB\": 38.341309,\n" + + "\"TJS\": 12.170235,\n" + + "\"TMT\": 3.891181,\n" + + "\"TND\": 3.411579,\n" + + "\"TOP\": 2.604569,\n" + + "\"TRY\": 29.962976,\n" + + "\"TTD\": 7.544979,\n" + + "\"TWD\": 34.876985,\n" + + "\"TZS\": 2716.276713,\n" + + "\"UAH\": 40.828967,\n" + + "\"UGX\": 4045.157318,\n" + + "\"USD\": 1.112576,\n" + + "\"UYU\": 42.188114,\n" + + "\"UZS\": 12906.061037,\n" + + "\"VES\": 32.294958,\n" + + "\"VND\": 26298.838308,\n" + + "\"VUV\": 132.263292,\n" + + "\"WST\": 3.029704,\n" + + "\"XAF\": 655.421747,\n" + + "\"XAG\": 0.04519,\n" + + "\"XAU\": 0.001442,\n" + + "\"XCD\": 3.004786,\n" + + "\"XDR\": 0.820903,\n" + + "\"XOF\": 655.421697,\n" + + "\"XPD\": 0.001221,\n" + + "\"XPF\": 119.235182,\n" + + "\"XPT\": 0.001139,\n" + + "\"YER\": 278.302358,\n" + + "\"ZAR\": 19.993867,\n" + + "\"ZMW\": 21.636944,\n" + + "\"ZWL\": 357.951203\n" + + "}\n" + + "}" + ; + + @Test + @Ignore + public void testExchangeRateApi() throws IOException { + ExchangeRateApi.getExchangeRate(); + } + + @Test + public void testExchangeRateParser() throws OpenemsNamedException { + + var currency = Currency.EUR; + var response = Utils.exchangeRateParser(EXCHANGE_DATA, currency); + + assertTrue(response == 1.0); + + currency = Currency.SEK; + response = Utils.exchangeRateParser(EXCHANGE_DATA, currency); + + assertFalse(response == 1.0); + } +} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java index 5599b33b98f..7c5125802a8 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java @@ -1,13 +1,16 @@ package io.openems.edge.timeofusetariff.entsoe; import io.openems.common.test.AbstractComponentConfig; +import io.openems.edge.common.currency.Currency; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { protected static class Builder { private String id; - public String securityToken; + private String securityToken; + private BiddingZone biddingZone; + private Currency currency; private Builder() { } @@ -22,6 +25,16 @@ public Builder setSecurityToken(String securityToken) { return this; } + public Builder setBididngZone(BiddingZone biddingZone) { + this.biddingZone = biddingZone; + return this; + } + + public Builder setCurrency(Currency currency) { + this.currency = currency; + return this; + } + public MyConfig build() { return new MyConfig(this); } @@ -48,4 +61,14 @@ public String securityToken() { return this.builder.securityToken; } + @Override + public BiddingZone biddingZone() { + return this.builder.biddingZone; + } + + @Override + public Currency currency() { + return this.builder.currency; + } + } \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java index 621fd080584..3fb94e62bb7 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java @@ -1,5 +1,549 @@ package io.openems.edge.timeofusetariff.entsoe; +import java.io.IOException; + +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.Test; +import org.xml.sax.SAXException; + public class ParserTest { + private static String XML = """ + + + 946edcf0cf33426aa666e1069ececbe7 + 1 + A44 + 10X1001A1001A450 + A32 + 10X1001A1001A450 + A33 + 2023-06-01T12:40:44Z + + 2023-05-31T22:00Z + 2023-06-01T22:00Z + + + 1 + A62 + 10Y1001A1001A82H + 10Y1001A1001A82H + EUR + MWH + A01 + + + 2023-05-31T22:00Z + 2023-06-01T22:00Z + + PT15M + + 1 + 109.93 + + + 2 + 85.84 + + + 3 + 65.09 + + + 4 + 55.07 + + + 5 + 90.10 + + + 6 + 78.30 + + + 7 + 71.20 + + + 8 + 60.80 + + + 9 + 79.70 + + + 10 + 70.60 + + + 11 + 75.10 + + + 12 + 66.14 + + + 13 + 74.00 + + + 14 + 70.60 + + + 15 + 70.20 + + + 16 + 71.34 + + + 17 + 52.70 + + + 18 + 62.10 + + + 19 + 77.40 + + + 20 + 93.40 + + + 21 + 40.60 + + + 22 + 56.20 + + + 23 + 87.20 + + + 24 + 144.06 + + + 25 + 50.68 + + + 26 + 96.20 + + + 27 + 117.60 + + + 28 + 121.50 + + + 29 + 125.10 + + + 30 + 111.80 + + + 31 + 91.80 + + + 32 + 77.46 + + + 33 + 179.06 + + + 34 + 111.90 + + + 35 + 69.50 + + + 36 + 30.10 + + + 37 + 151.70 + + + 38 + 96.20 + + + 39 + 69.91 + + + 40 + 7.20 + + + 41 + 114.91 + + + 42 + 75.20 + + + 43 + 49.90 + + + 44 + 1.20 + + + 45 + 89.91 + + + 46 + 64.90 + + + 47 + 29.20 + + + 48 + -11.47 + + + 49 + 69.80 + + + 50 + 34.90 + + + 51 + 6.10 + + + 52 + -20.00 + + + 53 + 44.20 + + + 54 + 24.91 + + + 55 + -12.03 + + + 56 + -19.94 + + + 57 + -25.30 + + + 58 + -13.50 + + + 59 + 18.83 + + + 60 + 39.90 + + + 61 + -49.90 + + + 62 + -10.80 + + + 63 + 21.40 + + + 64 + 61.62 + + + 65 + -61.69 + + + 66 + 7.16 + + + 67 + 58.60 + + + 68 + 109.92 + + + 69 + -35.10 + + + 70 + 39.93 + + + 71 + 109.93 + + + 72 + 139.94 + + + 73 + 29.91 + + + 74 + 54.10 + + + 75 + 105.11 + + + 76 + 149.91 + + + 77 + 75.05 + + + 78 + 93.33 + + + 79 + 130.10 + + + 80 + 139.96 + + + 81 + 139.92 + + + 82 + 110.22 + + + 83 + 95.08 + + + 84 + 95.05 + + + 85 + 139.98 + + + 86 + 114.80 + + + 87 + 90.09 + + + 88 + 80.07 + + + 89 + 134.70 + + + 90 + 100.68 + + + 91 + 80.03 + + + 92 + 70.02 + + + 93 + 119.70 + + + 94 + 82.41 + + + 95 + 75.10 + + + 96 + 65.07 + + + + + 2 + A62 + 10Y1001A1001A82H + 10Y1001A1001A82H + EUR + MWH + A01 + + + 2023-05-31T22:00Z + 2023-06-01T22:00Z + + PT60M + + 1 + 84.15 + + + 2 + 74.30 + + + 3 + 70.10 + + + 4 + 66.72 + + + 5 + 67.70 + + + 6 + 80.45 + + + 7 + 97.04 + + + 8 + 108.23 + + + 9 + 99.07 + + + 10 + 87.30 + + + 11 + 68.19 + + + 12 + 59.92 + + + 13 + 38.86 + + + 14 + 9.35 + + + 15 + 3.01 + + + 16 + 13.35 + + + 17 + 56.14 + + + 18 + 72.00 + + + 19 + 87.86 + + + 20 + 100.46 + + + 21 + 120.04 + + + 22 + 103.43 + + + 23 + 95.41 + + + 24 + 86.53 + + + + + """; + + @Test + public void testParse() throws IOException, ParserConfigurationException, SAXException { + var currencyExchangeValue = 1.0; + var result = Utils.parse(XML, "PT15M", currencyExchangeValue); + System.out.println(result); + } + } diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java index feab26d6a5d..13ccada7cf3 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java @@ -2,6 +2,7 @@ import org.junit.Test; +import io.openems.edge.common.currency.Currency; import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; @@ -11,11 +12,18 @@ public class TouEntsoeTest { @Test public void test() throws Exception { - new ComponentTest(new TouEntsoeImpl()) // + var entsoe = new TouEntsoeImpl(); + new ComponentTest(entsoe) // .activate(MyConfig.create() // .setId(COMPONENT_ID) // + .setSecurityToken("29ea7484-f60c-421a-b312-9db19dfd930a") // + .setBididngZone(BiddingZone.GERMANY) // + .setCurrency(Currency.EUR) // .build()) .next(new TestCase()); + + // Thread.sleep(5000); + // System.out.println("prices" + entsoe.getPrices().getValues()); } } From 552925618cb63efbd6e5262e25ed9eba4935812c Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Wed, 26 Jul 2023 12:10:53 +0200 Subject: [PATCH 03/16] Checkstyle --- .../src/io/openems/common/utils/XmlUtils.java | 22 +++++++++++++++---- .../timeofusetariff/entsoe/TouEntsoeImpl.java | 3 ++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/io.openems.common/src/io/openems/common/utils/XmlUtils.java b/io.openems.common/src/io/openems/common/utils/XmlUtils.java index 54273f07094..c01db1e719a 100644 --- a/io.openems.common/src/io/openems/common/utils/XmlUtils.java +++ b/io.openems.common/src/io/openems/common/utils/XmlUtils.java @@ -212,7 +212,14 @@ public static int getContentAsInt(Node node) { return Integer.parseInt(node.getTextContent()); } - // Source: https://stackoverflow.com/a/48153597/4137113 + /** + * Iterates through a {@link Node}. + * + * Source: https://stackoverflow.com/a/48153597/4137113 + * + * @param node the {@link Node} + * @return the {@link Iterable} + */ public static Iterable list(final Node node) { return () -> new Iterator() { @@ -220,12 +227,12 @@ public static Iterable list(final Node node) { @Override public boolean hasNext() { - return index < node.getChildNodes().getLength(); + return this.index < node.getChildNodes().getLength(); } @Override public Node next() { - if (!hasNext()) { + if (!this.hasNext()) { throw new NoSuchElementException(); } return node.getChildNodes().item(index++); @@ -233,7 +240,14 @@ public Node next() { }; } - // Source: https://stackoverflow.com/a/62171621/4137113 + /** + * Iterates over {@link Node} through {@link Stream} + * + * Source: https://stackoverflow.com/a/62171621/4137113 + * + * @param node the {@link Node} + * @return the {@link Stream} + */ public static Stream stream(final Node node) { var childNodes = node.getChildNodes(); return IntStream.range(0, childNodes.getLength()).boxed().map(childNodes::item); diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index 53d376a8205..399ae127b49 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -50,6 +50,7 @@ public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private final AtomicReference> prices = new AtomicReference<>( ImmutableSortedMap.of()); + private static final int EUR_EXHANGE_RATE = 1; private Config config = null; @@ -97,7 +98,7 @@ protected void deactivate() { if (this.config.currency() == Currency.EUR) { // No need to fetch from API. - exchangeRate = 1.0; + exchangeRate = EUR_EXHANGE_RATE; } else { exchangeRate = Utils.exchangeRateParser(ExchangeRateApi.getExchangeRate(), this.config.currency()); } From a33d1668eda93766c7eb1c00122370efcf2a1944 Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Wed, 26 Jul 2023 12:34:04 +0200 Subject: [PATCH 04/16] Checkstyle errors --- io.openems.common/src/io/openems/common/utils/XmlUtils.java | 6 ++++-- io.openems.edge.timeofusetariff.entsoe/.classpath | 6 +----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/io.openems.common/src/io/openems/common/utils/XmlUtils.java b/io.openems.common/src/io/openems/common/utils/XmlUtils.java index c01db1e719a..b921670e906 100644 --- a/io.openems.common/src/io/openems/common/utils/XmlUtils.java +++ b/io.openems.common/src/io/openems/common/utils/XmlUtils.java @@ -215,6 +215,7 @@ public static int getContentAsInt(Node node) { /** * Iterates through a {@link Node}. * + *

* Source: https://stackoverflow.com/a/48153597/4137113 * * @param node the {@link Node} @@ -235,14 +236,15 @@ public Node next() { if (!this.hasNext()) { throw new NoSuchElementException(); } - return node.getChildNodes().item(index++); + return node.getChildNodes().item(this.index++); } }; } /** - * Iterates over {@link Node} through {@link Stream} + * Iterates over {@link Node} through {@link Stream}. * + *

* Source: https://stackoverflow.com/a/62171621/4137113 * * @param node the {@link Node} diff --git a/io.openems.edge.timeofusetariff.entsoe/.classpath b/io.openems.edge.timeofusetariff.entsoe/.classpath index 1a2da58b855..bbfbdbe40e7 100644 --- a/io.openems.edge.timeofusetariff.entsoe/.classpath +++ b/io.openems.edge.timeofusetariff.entsoe/.classpath @@ -1,11 +1,7 @@ - - - - - + From 3c4fa9f642ca94775e72e236d6a330072265ef45 Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Wed, 26 Jul 2023 16:54:13 +0200 Subject: [PATCH 05/16] Added Currency to Meta. Modifed the provider to activate when currency is updated. --- .../edge/common/currency/CurrencyUtils.java | 64 +++++++++++++++++++ .../src/io/openems/edge/common/meta/Meta.java | 22 ++++++- .../src/io/openems/edge/core/meta/Config.java | 6 ++ .../io/openems/edge/core/meta/MetaImpl.java | 8 ++- .../timeofusetariff/entsoe/EntsoeApi.java | 10 +-- .../entsoe/ExchangeRateApi.java | 4 +- .../timeofusetariff/entsoe/TouEntsoeImpl.java | 28 +++++--- .../edge/timeofusetariff/entsoe/Utils.java | 30 ++++----- 8 files changed, 135 insertions(+), 37 deletions(-) create mode 100644 io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java new file mode 100644 index 00000000000..5ea59c8d6d0 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java @@ -0,0 +1,64 @@ +package io.openems.edge.common.currency; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CurrencyUtils { + + private static final Logger LOG = LoggerFactory.getLogger(CurrencyUtils.class); + + /** + * Get {@link Currency} for given code of the country. If the country code does + * not exist, {@link Currency#EUR} is returned as default. The given code is + * removed from all leading and trailing white spaces and converts all + * characters to upper case. + * + *

+ * Applies to Tibber. + * + * @param countryCode The country code from the Json data retrieved from the + * API. + * @return Currency + */ + public static Currency getCurrencyFromCountryCode(String countryCode) { + + // Example: 'DE','SE'.. + var value = countryCode.trim().toUpperCase(); + + switch (value) { + case "DE": + return Currency.EUR; + case "SE": + return Currency.SEK; + case "US": + return Currency.USD; + default: + return Currency.DEFAULT; + } + } + + /** + * Get {@link Currency} for given unit of the currency. If the currency unit + * does not exist, {@link Currency#DEFAULT} is returned as default. The given + * unit is removed from all leading and trailing white spaces and converts all + * characters to upper case. + * + *

+ * Applies to aWATTar and Corrently. + * + * @param currencyUnit The element from the Json data retrieved from the API. + * @return Currency + */ + public static Currency getCurrencyFromCurrencyCode(String currencyUnit) { + // Split the unit to get only currency. + // example: 'EUR/MWh' -> 'EUR' + var value = currencyUnit.split("/")[0].trim().toUpperCase(); + + try { + return Currency.valueOf(value); + } catch (IllegalArgumentException e) { + LOG.warn("Currency [" + currencyUnit + "] is not supported"); + return Currency.DEFAULT; + } + } + +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java index 7d2c007b7a4..114f0967f3f 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java +++ b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java @@ -4,6 +4,8 @@ import io.openems.common.channel.AccessMode; import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; +import io.openems.edge.common.channel.EnumReadChannel; +import io.openems.edge.common.currency.Currency; import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; import io.openems.edge.common.modbusslave.ModbusSlaveTable; @@ -22,7 +24,17 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { *

  • Type: String * */ - VERSION(Doc.of(OpenemsType.STRING)); + VERSION(Doc.of(OpenemsType.STRING)), + + /** + * Edge currency. + * + *
      + *
    • Interface: Meta + *
    • Type: String + *
    + */ + CURRENCY(Doc.of(Currency.values())); private final Doc doc; @@ -52,4 +64,12 @@ public default ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { .build()); } + /** + * Gets the Channel for {@link ChannelId#Currency}. + * + * @return the Channel + */ + public default EnumReadChannel getCurrencyChannel() { + return this.channel(ChannelId.CURRENCY); + } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java index 3838315a22a..36ff8534315 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java @@ -1,7 +1,10 @@ package io.openems.edge.core.meta; +import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import io.openems.edge.common.currency.Currency; + @ObjectClassDefinition(// name = "Core Meta", // description = "The global manager for Metadata.") @@ -9,4 +12,7 @@ String webconsole_configurationFactory_nameHint() default "Core Meta"; + @AttributeDefinition(name = "Currency", description = "Currency to be used for energy purchase; Energy price value is converted to appropraite currency based on current exchange rate") + Currency currency() default Currency.EUR; + } \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java index 54af2dcffc3..1e1454358c9 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java @@ -36,18 +36,21 @@ public MetaImpl() { } @Activate - private void activate(ComponentContext context) { + private void activate(ComponentContext context, Config config) { super.activate(context, SINGLETON_COMPONENT_ID, Meta.SINGLETON_SERVICE_PID, true); + this.channel(Meta.ChannelId.CURRENCY).setNextValue(config.currency()); + if (OpenemsComponent.validateSingleton(this.cm, Meta.SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return; } } @Modified - private void modified(ComponentContext context) { + private void modified(ComponentContext context, Config config) { super.modified(context, SINGLETON_COMPONENT_ID, Meta.SINGLETON_SERVICE_PID, true); + this.channel(Meta.ChannelId.CURRENCY).setNextValue(config.currency()); if (OpenemsComponent.validateSingleton(this.cm, Meta.SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return; } @@ -58,5 +61,4 @@ private void modified(ComponentContext context) { protected void deactivate() { super.deactivate(); } - } diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java index 45a2f87e725..3830866994b 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java @@ -12,13 +12,13 @@ public class EntsoeApi { - public final static DateTimeFormatter FORMATTER_MINUTES = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mmX"); - public final static DateTimeFormatter FORMATTER_SECONDS = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mm:ssX"); - public final static ZoneId UTC = ZoneId.of("UTC"); - public final static String URI = "https://web-api.tp.entsoe.eu/api"; + public static final DateTimeFormatter FORMATTER_MINUTES = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mmX"); + public static final DateTimeFormatter FORMATTER_SECONDS = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mm:ssX"); + public static final ZoneId UTC = ZoneId.of("UTC"); + public static final String URI = "https://web-api.tp.entsoe.eu/api"; /** - * Queries the ENTSO-E API for day-ahead prices + * Queries the ENTSO-E API for day-ahead prices. * * @param token the Security Token * @param areaCode Area EIC code; see diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java index e6a50e6bbcd..8f751eaecc2 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java @@ -2,7 +2,6 @@ import java.io.IOException; -import io.openems.edge.common.currency.Currency; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -10,17 +9,16 @@ public class ExchangeRateApi { private static final String BASE_URL = "https://api.exchangerate.host/latest?base=%s"; private static final OkHttpClient client = new OkHttpClient(); + private static final String URL = String.format(BASE_URL, "EUR"); /** * Fetches the exchange rate from base currency EUR to the currency requested by * user. * - * @param currency {@link Currency} requested by User. * @return the Response string. * @throws IOException on error. */ protected static String getExchangeRate() throws IOException { - var URL = String.format(BASE_URL, "EUR"); var request = new Request.Builder() // .url(URL) // .build(); diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index 399ae127b49..5247297c505 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -9,6 +9,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import javax.xml.parsers.ParserConfigurationException; @@ -30,6 +31,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.utils.ThreadPoolUtils; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; import io.openems.edge.common.currency.Currency; @@ -50,11 +52,11 @@ public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private final AtomicReference> prices = new AtomicReference<>( ImmutableSortedMap.of()); - private static final int EUR_EXHANGE_RATE = 1; - + private Config config = null; - + private Currency currency; private ZonedDateTime updateTimeStamp = null; + private static final int EUR_EXHANGE_RATE = 1; @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) private Meta meta; @@ -66,6 +68,11 @@ public TouEntsoeImpl() { ); } + private final Consumer> callback = t -> { + this.currency = t.asEnum(); + this.executor.schedule(this.task, 0, TimeUnit.SECONDS); + }; + @Activate private void activate(ComponentContext context, Config config) { super.activate(context, config.id(), config.alias(), config.enabled()); @@ -73,14 +80,19 @@ private void activate(ComponentContext context, Config config) { if (!config.enabled()) { return; } - this.config = config; + this.currency = this.meta.getCurrencyChannel().value().asEnum(); this.executor.schedule(this.task, 0, TimeUnit.SECONDS); + + // Trigger to activate when the currency from Meta is updated. + this.meta.getCurrencyChannel().onSetNextValue(this.callback); } @Deactivate protected void deactivate() { super.deactivate(); + + this.meta.getCurrencyChannel().removeOnSetNextValueCallback(this.callback); ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 0); } @@ -94,17 +106,15 @@ protected void deactivate() { try { var result = EntsoeApi.query(token, areaCode, fromDate, toDate); + final double exchangeRate; - - if (this.config.currency() == Currency.EUR) { + if (this.currency == Currency.EUR) { // No need to fetch from API. exchangeRate = EUR_EXHANGE_RATE; } else { - exchangeRate = Utils.exchangeRateParser(ExchangeRateApi.getExchangeRate(), this.config.currency()); + exchangeRate = Utils.exchangeRateParser(ExchangeRateApi.getExchangeRate(), this.currency); } - System.out.println("rate: " + exchangeRate); - // Parse the response for the prices this.prices.set(Utils.parse(result, "PT60M", exchangeRate)); diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java index b2bf6bc9548..9d63f74a014 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java @@ -26,12 +26,12 @@ public class Utils { - public final static DateTimeFormatter FORMATTER_MINUTES = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mmX"); + private static final DateTimeFormatter FORMATTER_MINUTES = DateTimeFormatter.ofPattern("u-MM-dd'T'HH:mmX"); private static record QueryResult(ZonedDateTime start, List prices) { protected static class Builder { private ZonedDateTime start; - private List prices = new ArrayList<>();; + private List prices = new ArrayList<>(); public Builder start(ZonedDateTime start) { this.start = start; @@ -43,10 +43,6 @@ public Builder prices(List prices) { return this; } - // public QueryResult build() { - // return new QueryResult(this.start, this.prices); - // } - public ImmutableSortedMap toMap() { var result = new TreeMap(); @@ -59,7 +55,7 @@ public ImmutableSortedMap toMap() { } return ImmutableSortedMap.copyOf(result); - }; + } } public static Builder create() { @@ -68,16 +64,18 @@ public static Builder create() { } /** + * Parses the xml response from the Entso-E API. * - * @param xml - * @param resolution PT15M or PT60M - * @return - * @throws ParserConfigurationException - * @throws SAXException - * @throws IOException + * @param xml The xml string to be parsed. + * @param resolution PT15M or PT60M + * @param exchangeRate The exchange rate of user currency to EUR. + * @return The {@link ImmutableSortedMap} + * @throws ParserConfigurationException on error. + * @throws SAXException on error + * @throws IOException on error */ - protected static ImmutableSortedMap parse(String xml, String resolution, - double currencyExchangeValue) throws ParserConfigurationException, SAXException, IOException { + protected static ImmutableSortedMap parse(String xml, String resolution, double exchangeRate) + throws ParserConfigurationException, SAXException, IOException { var dbFactory = DocumentBuilderFactory.newInstance(); var dBuilder = dbFactory.newDocumentBuilder(); var is = new InputSource(new StringReader(xml)); @@ -121,7 +119,7 @@ protected static ImmutableSortedMap parse(String xml, Stri // .filter(n -> n.getNodeName() == "price.amount") // .map(XmlUtils::getContentAsString) // - .map(s -> Float.parseFloat(s) * (float) currencyExchangeValue) // + .map(s -> Float.parseFloat(s) * (float) exchangeRate) // .toList()); }); From 893a7a565a9ccc5aa551317ab0a72f811b9df260 Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Thu, 27 Jul 2023 09:12:08 +0200 Subject: [PATCH 06/16] checkstyle --- .../src/io/openems/edge/common/currency/CurrencyUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java index 5ea59c8d6d0..a207a6e0819 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java @@ -1,4 +1,5 @@ package io.openems.edge.common.currency; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 611f2baa1a2c44f39e9c61bac71032c577ffba97 Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Thu, 27 Jul 2023 15:47:28 +0200 Subject: [PATCH 07/16] Added Currency provider interface. Refactored code. Modified the test cases. --- .../edge/common/currency/CurrencyUtils.java | 65 ------------------- .../edge/timeofusetariff/entsoe/Config.java | 5 -- .../entsoe/CurrencyProvider.java | 31 +++++++++ .../timeofusetariff/entsoe/TouEntsoeImpl.java | 48 ++++++++++---- .../entsoe/CurrencyProviderTest.java | 25 +++++++ .../timeofusetariff/entsoe/EntsoeApiTest.java | 2 + .../edge/timeofusetariff/entsoe/MyConfig.java | 13 +--- .../timeofusetariff/entsoe/ParserTest.java | 13 +++- .../timeofusetariff/entsoe/TouEntsoeTest.java | 14 ++-- 9 files changed, 111 insertions(+), 105 deletions(-) delete mode 100644 io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java create mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java deleted file mode 100644 index a207a6e0819..00000000000 --- a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyUtils.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.openems.edge.common.currency; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CurrencyUtils { - - private static final Logger LOG = LoggerFactory.getLogger(CurrencyUtils.class); - - /** - * Get {@link Currency} for given code of the country. If the country code does - * not exist, {@link Currency#EUR} is returned as default. The given code is - * removed from all leading and trailing white spaces and converts all - * characters to upper case. - * - *

    - * Applies to Tibber. - * - * @param countryCode The country code from the Json data retrieved from the - * API. - * @return Currency - */ - public static Currency getCurrencyFromCountryCode(String countryCode) { - - // Example: 'DE','SE'.. - var value = countryCode.trim().toUpperCase(); - - switch (value) { - case "DE": - return Currency.EUR; - case "SE": - return Currency.SEK; - case "US": - return Currency.USD; - default: - return Currency.DEFAULT; - } - } - - /** - * Get {@link Currency} for given unit of the currency. If the currency unit - * does not exist, {@link Currency#DEFAULT} is returned as default. The given - * unit is removed from all leading and trailing white spaces and converts all - * characters to upper case. - * - *

    - * Applies to aWATTar and Corrently. - * - * @param currencyUnit The element from the Json data retrieved from the API. - * @return Currency - */ - public static Currency getCurrencyFromCurrencyCode(String currencyUnit) { - // Split the unit to get only currency. - // example: 'EUR/MWh' -> 'EUR' - var value = currencyUnit.split("/")[0].trim().toUpperCase(); - - try { - return Currency.valueOf(value); - } catch (IllegalArgumentException e) { - LOG.warn("Currency [" + currencyUnit + "] is not supported"); - return Currency.DEFAULT; - } - } - -} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java index df49c8e38e9..22e2e0a2f06 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java @@ -4,8 +4,6 @@ import org.osgi.service.metatype.annotations.AttributeType; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.common.currency.Currency; - @ObjectClassDefinition(// name = "Time-Of-Use Tariff ENTSO-E", // description = "Time-Of-Use Tariff implementation that uses the ENTSO-E transparency platform.") @@ -26,8 +24,5 @@ @AttributeDefinition(name = "Bidding Zone", description = "Zone corresponding to the customer's location") BiddingZone biddingZone() default BiddingZone.GERMANY; - @AttributeDefinition(name = "Currency", description = "Currency to be used for energy purchase; Energy price value is converted to appropraite currency based on current exchange rate") - Currency currency() default Currency.EUR; - String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff ENTSO-E [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java new file mode 100644 index 00000000000..527a852f951 --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java @@ -0,0 +1,31 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import java.util.function.Consumer; + +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.currency.Currency; + +public interface CurrencyProvider { + + /** + * Returns the current Currency. + * + * @return The {@link Currency} + */ + public Currency getCurrent(); + + /** + * Subscribes to the Currency channel to trigger on update. + * + * @param consumer The callback {@link Consumer}. + */ + public void subscribe(Consumer> consumer); + + /** + * Unsubscribe from the Currency channel. + * + * @param consumer The callback {@link Consumer}. + */ + public void unsubscribe(Consumer> consumer); + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index 5247297c505..98ffa78c43f 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -19,9 +19,6 @@ import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.osgi.service.component.annotations.ReferencePolicyOption; import org.osgi.service.metatype.annotations.Designate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,14 +49,14 @@ public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private final AtomicReference> prices = new AtomicReference<>( ImmutableSortedMap.of()); - + private Config config = null; private Currency currency; private ZonedDateTime updateTimeStamp = null; private static final int EUR_EXHANGE_RATE = 1; - @Reference(policy = ReferencePolicy.STATIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.MANDATORY) - private Meta meta; + @Reference + private CurrencyProvider currencyProvider; public TouEntsoeImpl() { super(// @@ -81,23 +78,50 @@ private void activate(ComponentContext context, Config config) { return; } this.config = config; - this.currency = this.meta.getCurrencyChannel().value().asEnum(); + this.currency = this.currencyProvider.getCurrent(); this.executor.schedule(this.task, 0, TimeUnit.SECONDS); // Trigger to activate when the currency from Meta is updated. - this.meta.getCurrencyChannel().onSetNextValue(this.callback); + this.currencyProvider.subscribe(this.callback); } @Deactivate protected void deactivate() { super.deactivate(); - - this.meta.getCurrencyChannel().removeOnSetNextValueCallback(this.callback); + this.currencyProvider.unsubscribe(this.callback); ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 0); } - private final Runnable task = () -> { + @Component + public static class CurrencyProviderOsgi implements CurrencyProvider { + + @Reference + private Meta meta; + public Currency getCurrent() { + return this.meta.getCurrencyChannel().getNextValue().asEnum(); + } + + /** + * Subscribes to the Currency channel to trigger on update. + * + * @param consumer The callback {@link Consumer}. + */ + public void subscribe(Consumer> consumer) { + this.meta.getCurrencyChannel().onSetNextValue(consumer); + } + + /** + * Unsubscribe from the Currency channel. + * + * @param consumer The callback {@link Consumer}. + */ + public void unsubscribe(Consumer> consumer) { + this.meta.getCurrencyChannel().removeOnSetNextValueCallback(consumer); + } + } + + private final Runnable task = () -> { var token = this.config.securityToken(); var areaCode = this.config.biddingZone().getName(); var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS); @@ -106,7 +130,7 @@ protected void deactivate() { try { var result = EntsoeApi.query(token, areaCode, fromDate, toDate); - + final double exchangeRate; if (this.currency == Currency.EUR) { // No need to fetch from API. diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java new file mode 100644 index 00000000000..aa975a159ea --- /dev/null +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java @@ -0,0 +1,25 @@ +package io.openems.edge.timeofusetariff.entsoe; + +import java.util.function.Consumer; + +import io.openems.edge.common.channel.value.Value; +import io.openems.edge.common.currency.Currency; + +public class CurrencyProviderTest implements CurrencyProvider { + + @Override + public Currency getCurrent() { + return Currency.DEFAULT; + } + + @Override + public void subscribe(Consumer> consumer) { + // Empty + } + + @Override + public void unsubscribe(Consumer> consumer) { + // Empty + } + +} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java index 752875c77b3..0395379c586 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java @@ -7,12 +7,14 @@ import javax.xml.parsers.ParserConfigurationException; +import org.junit.Ignore; import org.junit.Test; import org.xml.sax.SAXException; public class EntsoeApiTest { @Test + @Ignore public void testQuery() throws IOException, ParserConfigurationException, SAXException { var token = ""; var areaCode = BiddingZone.SWEDEN_ZONE_3.getName(); diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java index 7c5125802a8..2b624100cd7 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java @@ -10,7 +10,6 @@ protected static class Builder { private String id; private String securityToken; private BiddingZone biddingZone; - private Currency currency; private Builder() { } @@ -25,15 +24,10 @@ public Builder setSecurityToken(String securityToken) { return this; } - public Builder setBididngZone(BiddingZone biddingZone) { + public Builder setBiddingZone(BiddingZone biddingZone) { this.biddingZone = biddingZone; return this; } - - public Builder setCurrency(Currency currency) { - this.currency = currency; - return this; - } public MyConfig build() { return new MyConfig(this); @@ -66,9 +60,4 @@ public BiddingZone biddingZone() { return this.builder.biddingZone; } - @Override - public Currency currency() { - return this.builder.currency; - } - } \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java index 3fb94e62bb7..9d6ae61bce6 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java @@ -1,5 +1,8 @@ package io.openems.edge.timeofusetariff.entsoe; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import javax.xml.parsers.ParserConfigurationException; @@ -543,7 +546,13 @@ public class ParserTest { public void testParse() throws IOException, ParserConfigurationException, SAXException { var currencyExchangeValue = 1.0; var result = Utils.parse(XML, "PT15M", currencyExchangeValue); - System.out.println(result); - } + assertTrue(result.firstEntry().getValue() == 109.93f); + assertTrue(result.lastEntry().getValue() == 65.07f); + + result = Utils.parse(XML, "PT60M", currencyExchangeValue); + + assertFalse(result.firstEntry().getValue() == 109.93f); + assertTrue(result.lastEntry().getValue() == 86.53f); + } } diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java index 13ccada7cf3..d416a6b8f71 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java @@ -2,28 +2,24 @@ import org.junit.Test; -import io.openems.edge.common.currency.Currency; -import io.openems.edge.common.test.AbstractComponentTest.TestCase; import io.openems.edge.common.test.ComponentTest; public class TouEntsoeTest { private static final String COMPONENT_ID = "tou0"; + private static final String CURRENCY_PROVIDER_NAME = "currencyProvider"; @Test public void test() throws Exception { var entsoe = new TouEntsoeImpl(); new ComponentTest(entsoe) // + .addReference(CURRENCY_PROVIDER_NAME, new CurrencyProviderTest())// .activate(MyConfig.create() // .setId(COMPONENT_ID) // - .setSecurityToken("29ea7484-f60c-421a-b312-9db19dfd930a") // - .setBididngZone(BiddingZone.GERMANY) // - .setCurrency(Currency.EUR) // - .build()) - .next(new TestCase()); + .setSecurityToken("foo-bar") // + .setBiddingZone(BiddingZone.GERMANY) // + .build()); // Thread.sleep(5000); - // System.out.println("prices" + entsoe.getPrices().getValues()); } - } From a3e2c841bb977c7499086c68ffb70b584fab8012 Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Fri, 28 Jul 2023 16:35:48 +0200 Subject: [PATCH 08/16] Refactored the code based on review. --- .../src/io/openems/edge/common/meta/Meta.java | 9 ++++ .../io/openems/edge/core/meta/MetaImpl.java | 8 +++- .../timeofusetariff/entsoe/BiddingZone.java | 34 ++++---------- .../entsoe/CurrencyProvider.java | 9 ++-- .../timeofusetariff/entsoe/TouEntsoeImpl.java | 47 +++++++++++-------- .../entsoe/CurrencyProviderTest.java | 5 +- .../timeofusetariff/entsoe/EntsoeApiTest.java | 2 +- 7 files changed, 58 insertions(+), 56 deletions(-) diff --git a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java index 114f0967f3f..41799246b9e 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java +++ b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java @@ -72,4 +72,13 @@ public default ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { public default EnumReadChannel getCurrencyChannel() { return this.channel(ChannelId.CURRENCY); } + + /** + * Internal method to set the 'nextValue' on {@link ChannelId#Currency} Channel. + * + * @param value the next value + */ + public default void _setCurrency(Currency currency) { + this.getCurrencyChannel().setNextValue(currency); + } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java index 1e1454358c9..85c4dc3ec51 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java @@ -39,7 +39,7 @@ public MetaImpl() { private void activate(ComponentContext context, Config config) { super.activate(context, SINGLETON_COMPONENT_ID, Meta.SINGLETON_SERVICE_PID, true); - this.channel(Meta.ChannelId.CURRENCY).setNextValue(config.currency()); + this.applyConfig(config); if (OpenemsComponent.validateSingleton(this.cm, Meta.SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return; @@ -50,7 +50,7 @@ private void activate(ComponentContext context, Config config) { private void modified(ComponentContext context, Config config) { super.modified(context, SINGLETON_COMPONENT_ID, Meta.SINGLETON_SERVICE_PID, true); - this.channel(Meta.ChannelId.CURRENCY).setNextValue(config.currency()); + this.applyConfig(config); if (OpenemsComponent.validateSingleton(this.cm, Meta.SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return; } @@ -61,4 +61,8 @@ private void modified(ComponentContext context, Config config) { protected void deactivate() { super.deactivate(); } + + private void applyConfig(Config config) { + this._setCurrency(config.currency()); + } } diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java index e29a63cfbb4..a65b31cf16e 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java @@ -1,39 +1,23 @@ package io.openems.edge.timeofusetariff.entsoe; -import io.openems.common.types.OptionsEnum; - -public enum BiddingZone implements OptionsEnum { - UNDEFINED(-1, "Undefined", "Undefined"), // - GERMANY(0, "10Y1001A1001A82H", "BZN|DE-LU"), // - AUSTRIA(1, "10YAT-APG------L", "BZN|AT"), // - SWEDEN_ZONE_1(2, "10Y1001A1001A44P", "BZN|SE1"), // - SWEDEN_ZONE_2(3, "10Y1001A1001A45N", "BZN|SE2"), // - SWEDEN_ZONE_3(4, "10Y1001A1001A46L", "BZN|SE3"), // - SWEDEN_ZONE_4(5, "10Y1001A1001A47J", "BZN|SE4"), // +public enum BiddingZone { + GERMANY("10Y1001A1001A82H", "BZN|DE-LU"), // + AUSTRIA("10YAT-APG------L", "BZN|AT"), // + SWEDEN_ZONE_1("10Y1001A1001A44P", "BZN|SE1"), // + SWEDEN_ZONE_2("10Y1001A1001A45N", "BZN|SE2"), // + SWEDEN_ZONE_3("10Y1001A1001A46L", "BZN|SE3"), // + SWEDEN_ZONE_4("10Y1001A1001A47J", "BZN|SE4"), // ; - private final int value; private final String code; private final String zone; - private BiddingZone(int value, String code, String zone) { - this.value = value; + private BiddingZone(String code, String zone) { this.code = code; this.zone = zone; } - @Override - public OptionsEnum getUndefined() { - return UNDEFINED; - } - - @Override - public int getValue() { - return this.value; - } - - @Override - public String getName() { + public String getCode() { return this.code; } diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java index 527a852f951..1a9588c8863 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java @@ -2,7 +2,6 @@ import java.util.function.Consumer; -import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.currency.Currency; public interface CurrencyProvider { @@ -19,13 +18,11 @@ public interface CurrencyProvider { * * @param consumer The callback {@link Consumer}. */ - public void subscribe(Consumer> consumer); + public void subscribe(Consumer consumer); /** - * Unsubscribe from the Currency channel. - * - * @param consumer The callback {@link Consumer}. + * Unsubscribes from all the Subscriptions. */ - public void unsubscribe(Consumer> consumer); + public void unsubscribeAll(); } diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index 98ffa78c43f..c06742980f1 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -5,6 +5,8 @@ import java.time.Duration; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -19,6 +21,7 @@ import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ServiceScope; import org.osgi.service.metatype.annotations.Designate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,11 +68,6 @@ public TouEntsoeImpl() { ); } - private final Consumer> callback = t -> { - this.currency = t.asEnum(); - this.executor.schedule(this.task, 0, TimeUnit.SECONDS); - }; - @Activate private void activate(ComponentContext context, Config config) { super.activate(context, config.id(), config.alias(), config.enabled()); @@ -78,23 +76,29 @@ private void activate(ComponentContext context, Config config) { return; } this.config = config; - this.currency = this.currencyProvider.getCurrent(); - this.executor.schedule(this.task, 0, TimeUnit.SECONDS); + Consumer updateCurrency = t -> { + this.currency = t; + this.executor.schedule(this.task, 0, TimeUnit.SECONDS); + }; // Trigger to activate when the currency from Meta is updated. - this.currencyProvider.subscribe(this.callback); + this.currencyProvider.subscribe(updateCurrency); + + updateCurrency.accept(this.currencyProvider.getCurrent()); } @Deactivate protected void deactivate() { super.deactivate(); - this.currencyProvider.unsubscribe(this.callback); + this.currencyProvider.unsubscribeAll(); ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 0); } - @Component + @Component(scope = ServiceScope.PROTOTYPE) public static class CurrencyProviderOsgi implements CurrencyProvider { + private final List>> subscriptions = new ArrayList<>(); + @Reference private Meta meta; @@ -107,23 +111,28 @@ public Currency getCurrent() { * * @param consumer The callback {@link Consumer}. */ - public void subscribe(Consumer> consumer) { - this.meta.getCurrencyChannel().onSetNextValue(consumer); + public void subscribe(Consumer consumer) { + + Consumer> c = t -> { + consumer.accept(t.asEnum()); + }; + subscriptions.add(c); + this.meta.getCurrencyChannel().onSetNextValue(c); } /** - * Unsubscribe from the Currency channel. - * - * @param consumer The callback {@link Consumer}. + * Unsubscribes from all the Subscriptions. */ - public void unsubscribe(Consumer> consumer) { - this.meta.getCurrencyChannel().removeOnSetNextValueCallback(consumer); + public void unsubscribeAll() { + subscriptions.forEach(t -> { + this.meta.getCurrencyChannel().removeOnSetNextValueCallback(t); + }); } } private final Runnable task = () -> { var token = this.config.securityToken(); - var areaCode = this.config.biddingZone().getName(); + var areaCode = this.config.biddingZone().getCode(); var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS); var toDate = fromDate.plusDays(1); var unableToUpdatePrices = false; @@ -133,7 +142,7 @@ public void unsubscribe(Consumer> consumer) { final double exchangeRate; if (this.currency == Currency.EUR) { - // No need to fetch from API. + // No need to fetch exchange rate from API. exchangeRate = EUR_EXHANGE_RATE; } else { exchangeRate = Utils.exchangeRateParser(ExchangeRateApi.getExchangeRate(), this.currency); diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java index aa975a159ea..e27cf97b986 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java @@ -2,7 +2,6 @@ import java.util.function.Consumer; -import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.currency.Currency; public class CurrencyProviderTest implements CurrencyProvider { @@ -13,12 +12,12 @@ public Currency getCurrent() { } @Override - public void subscribe(Consumer> consumer) { + public void subscribe(Consumer consumer) { // Empty } @Override - public void unsubscribe(Consumer> consumer) { + public void unsubscribeAll() { // Empty } diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java index 0395379c586..5e205339a96 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java @@ -17,7 +17,7 @@ public class EntsoeApiTest { @Ignore public void testQuery() throws IOException, ParserConfigurationException, SAXException { var token = ""; - var areaCode = BiddingZone.SWEDEN_ZONE_3.getName(); + var areaCode = BiddingZone.SWEDEN_ZONE_3.getCode(); var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS).withZoneSameLocal(ZoneId.systemDefault()); var toDate = fromDate.plusDays(1); var response = EntsoeApi.query(token, areaCode, fromDate, toDate); From 69cc09451e30a2509eb950a9310af1a0c664ef2f Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Mon, 31 Jul 2023 10:19:38 +0200 Subject: [PATCH 09/16] Checkstyle changes --- .../src/io/openems/edge/common/meta/Meta.java | 6 +++--- .../openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java index 41799246b9e..531eb1f2b5a 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java +++ b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java @@ -31,7 +31,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * *

      *
    • Interface: Meta - *
    • Type: String + *
    • Type: Currency *
    */ CURRENCY(Doc.of(Currency.values())); @@ -78,7 +78,7 @@ public default EnumReadChannel getCurrencyChannel() { * * @param value the next value */ - public default void _setCurrency(Currency currency) { - this.getCurrencyChannel().setNextValue(currency); + public default void _setCurrency(Currency value) { + this.getCurrencyChannel().setNextValue(value); } } diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index c06742980f1..bc36066a26e 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -116,7 +116,7 @@ public void subscribe(Consumer consumer) { Consumer> c = t -> { consumer.accept(t.asEnum()); }; - subscriptions.add(c); + this.subscriptions.add(c); this.meta.getCurrencyChannel().onSetNextValue(c); } @@ -124,7 +124,7 @@ public void subscribe(Consumer consumer) { * Unsubscribes from all the Subscriptions. */ public void unsubscribeAll() { - subscriptions.forEach(t -> { + this.subscriptions.forEach(t -> { this.meta.getCurrencyChannel().removeOnSetNextValueCallback(t); }); } From 851a85df1e8b2859146a8c31a8b428ceaba84c72 Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Tue, 1 Aug 2023 16:39:30 +0200 Subject: [PATCH 10/16] Review changes --- .../io/openems/edge/core/meta/MetaImpl.java | 1 - .../bnd.bnd | 6 +--- .../timeofusetariff/entsoe/TouEntsoeImpl.java | 32 ++++++++++--------- .../test}/.gitignore | 0 4 files changed, 18 insertions(+), 21 deletions(-) rename {io.openems.edge.controller.ess.timeofusetariff/bin_test => io.openems.edge.timeofusetariff.entsoe/test}/.gitignore (100%) diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java index 85c4dc3ec51..1c62a29d894 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java @@ -40,7 +40,6 @@ private void activate(ComponentContext context, Config config) { super.activate(context, SINGLETON_COMPONENT_ID, Meta.SINGLETON_SERVICE_PID, true); this.applyConfig(config); - if (OpenemsComponent.validateSingleton(this.cm, Meta.SINGLETON_SERVICE_PID, SINGLETON_COMPONENT_ID)) { return; } diff --git a/io.openems.edge.timeofusetariff.entsoe/bnd.bnd b/io.openems.edge.timeofusetariff.entsoe/bnd.bnd index 68b6fbaf51c..5f93b54c0d0 100644 --- a/io.openems.edge.timeofusetariff.entsoe/bnd.bnd +++ b/io.openems.edge.timeofusetariff.entsoe/bnd.bnd @@ -6,14 +6,10 @@ Bundle-Version: 1.0.0.${tstamp} -buildpath: \ ${buildpath},\ com.squareup.okio,\ + io.openems.common,\ io.openems.edge.common,\ io.openems.edge.timeofusetariff.api,\ io.openems.wrapper.okhttp,\ - io.openems.common,\ -testpath: \ - com.squareup.okio,\ - io.openems.wrapper.kotlinx-coroutines-core-jvm,\ - io.openems.wrapper.okhttp,\ - org.jetbrains.kotlin.osgi-bundle,\ ${testpath} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index bc36066a26e..51fdf37a5c7 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -48,18 +48,20 @@ ) public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe, OpenemsComponent, TimeOfUseTariff { + private static final int EUR_EXHANGE_RATE = 1; + private static final int API_EXECUTE_HOUR = 14; + private final Logger log = LoggerFactory.getLogger(TouEntsoeImpl.class); private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private final AtomicReference> prices = new AtomicReference<>( ImmutableSortedMap.of()); + @Reference + private CurrencyProvider currencyProvider; + private Config config = null; private Currency currency; private ZonedDateTime updateTimeStamp = null; - private static final int EUR_EXHANGE_RATE = 1; - - @Reference - private CurrencyProvider currencyProvider; public TouEntsoeImpl() { super(// @@ -76,8 +78,8 @@ private void activate(ComponentContext context, Config config) { return; } this.config = config; - Consumer updateCurrency = t -> { - this.currency = t; + Consumer updateCurrency = currency -> { + this.currency = currency; this.executor.schedule(this.task, 0, TimeUnit.SECONDS); }; @@ -109,23 +111,23 @@ public Currency getCurrent() { /** * Subscribes to the Currency channel to trigger on update. * - * @param consumer The callback {@link Consumer}. + * @param updateCurrency The callback {@link Consumer}. */ - public void subscribe(Consumer consumer) { + public void subscribe(Consumer updateCurrency) { - Consumer> c = t -> { - consumer.accept(t.asEnum()); + Consumer> subscription = currency -> { + updateCurrency.accept(currency.asEnum()); }; - this.subscriptions.add(c); - this.meta.getCurrencyChannel().onSetNextValue(c); + this.subscriptions.add(subscription); + this.meta.getCurrencyChannel().onSetNextValue(subscription); } /** * Unsubscribes from all the Subscriptions. */ public void unsubscribeAll() { - this.subscriptions.forEach(t -> { - this.meta.getCurrencyChannel().removeOnSetNextValueCallback(t); + this.subscriptions.forEach(subscription -> { + this.meta.getCurrencyChannel().removeOnSetNextValueCallback(subscription); }); } } @@ -169,7 +171,7 @@ public void unsubscribeAll() { * Schedule next price update at 2 o clock every day. */ var now = ZonedDateTime.now(); - var nextRun = now.withHour(14).truncatedTo(ChronoUnit.HOURS); + var nextRun = now.withHour(API_EXECUTE_HOUR).truncatedTo(ChronoUnit.HOURS); if (unableToUpdatePrices) { // If the prices are not updated, try again in next minute. nextRun = now.plusMinutes(1).truncatedTo(ChronoUnit.MINUTES); diff --git a/io.openems.edge.controller.ess.timeofusetariff/bin_test/.gitignore b/io.openems.edge.timeofusetariff.entsoe/test/.gitignore similarity index 100% rename from io.openems.edge.controller.ess.timeofusetariff/bin_test/.gitignore rename to io.openems.edge.timeofusetariff.entsoe/test/.gitignore From 1ca80507f2ef49da3e81a82cc68d88e752c43ab4 Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Tue, 1 Aug 2023 16:41:22 +0200 Subject: [PATCH 11/16] sort alphabetically --- io.openems.edge.application/EdgeApp.bndrun | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io.openems.edge.application/EdgeApp.bndrun b/io.openems.edge.application/EdgeApp.bndrun index e532d934301..43947e79ab7 100644 --- a/io.openems.edge.application/EdgeApp.bndrun +++ b/io.openems.edge.application/EdgeApp.bndrun @@ -177,8 +177,8 @@ bnd.identity;id='io.openems.edge.timedata.rrd4j',\ bnd.identity;id='io.openems.edge.timeofusetariff.awattar',\ bnd.identity;id='io.openems.edge.timeofusetariff.corrently',\ - bnd.identity;id='io.openems.edge.timeofusetariff.tibber',\ bnd.identity;id='io.openems.edge.timeofusetariff.entsoe',\ + bnd.identity;id='io.openems.edge.timeofusetariff.tibber',\ -runbundles: \ Java-WebSocket;version='[1.5.3,1.5.4)',\ @@ -343,8 +343,8 @@ io.openems.edge.timeofusetariff.api;version=snapshot,\ io.openems.edge.timeofusetariff.awattar;version=snapshot,\ io.openems.edge.timeofusetariff.corrently;version=snapshot,\ - io.openems.edge.timeofusetariff.tibber;version=snapshot,\ io.openems.edge.timeofusetariff.entsoe;version=snapshot,\ + io.openems.edge.timeofusetariff.tibber;version=snapshot,\ io.openems.shared.influxdb;version=snapshot,\ io.openems.wrapper.eu.chargetime.ocpp;version=snapshot,\ io.openems.wrapper.fastexcel;version=snapshot,\ From c05195ed0f3e171b67e165a6d9725a394931797e Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Wed, 2 Aug 2023 10:11:03 +0200 Subject: [PATCH 12/16] Refactored Bidding zone --- .../timeofusetariff/entsoe/BiddingZone.java | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java index a65b31cf16e..2f8ce5d40e5 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java @@ -1,28 +1,42 @@ package io.openems.edge.timeofusetariff.entsoe; +/** + * https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_areas. + */ public enum BiddingZone { - GERMANY("10Y1001A1001A82H", "BZN|DE-LU"), // - AUSTRIA("10YAT-APG------L", "BZN|AT"), // - SWEDEN_ZONE_1("10Y1001A1001A44P", "BZN|SE1"), // - SWEDEN_ZONE_2("10Y1001A1001A45N", "BZN|SE2"), // - SWEDEN_ZONE_3("10Y1001A1001A46L", "BZN|SE3"), // - SWEDEN_ZONE_4("10Y1001A1001A47J", "BZN|SE4"), // + /** + * BZN|DE-LU. + */ + GERMANY("10Y1001A1001A82H"), // + /** + * BZN|AT. + */ + AUSTRIA("10YAT-APG------L"), // + /** + * BZN|SE1. + */ + SWEDEN_ZONE_1("10Y1001A1001A44P"), // + /** + * BZN|SE2. + */ + SWEDEN_ZONE_2("10Y1001A1001A45N"), // + /** + * BZN|SE3. + */ + SWEDEN_ZONE_3("10Y1001A1001A46L"), // + /** + * BZN|SE4. + */ + SWEDEN_ZONE_4("10Y1001A1001A47J"), // ; private final String code; - private final String zone; - private BiddingZone(String code, String zone) { + private BiddingZone(String code) { this.code = code; - this.zone = zone; } public String getCode() { return this.code; } - - public String getZone() { - return this.zone; - } - } From 28522d3b5ff9b479ea98fd7e9f5aaf41268e88ab Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Fri, 4 Aug 2023 14:24:08 +0200 Subject: [PATCH 13/16] Removed Currency Provider. Added Dummy Component. Added Currency config. --- .../edge/common/currency/Currency.java | 3 +- .../edge/common/currency/CurrencyConfig.java | 12 + .../openems/edge/common/test/DummyMeta.java | 22 ++ .../src/io/openems/edge/core/meta/Config.java | 4 +- .../io/openems/edge/core/meta/MetaImpl.java | 10 +- .../entsoe/CurrencyProvider.java | 28 -- .../entsoe/ExchangeRateApi.java | 5 +- .../timeofusetariff/entsoe/TouEntsoeImpl.java | 73 ++-- .../entsoe/CurrencyProviderTest.java | 24 -- .../entsoe/ExchangeRateApiTest.java | 367 +++++++++--------- .../edge/timeofusetariff/entsoe/MyConfig.java | 2 +- .../timeofusetariff/entsoe/TouEntsoeTest.java | 6 +- 12 files changed, 266 insertions(+), 290 deletions(-) create mode 100644 io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java create mode 100644 io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java delete mode 100644 io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java delete mode 100644 io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java index 0b4aa119e26..fca2ebca5b5 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/Currency.java @@ -6,11 +6,10 @@ public enum Currency implements OptionsEnum { UNDEFINED(-1, "-"), // EUR(0, "€"), // SEK(1, "kr"), // - USD(2, "$"); + ; private final String name; private final int value; - public static final Currency DEFAULT = EUR; private Currency(int value, String name) { this.value = value; diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java new file mode 100644 index 00000000000..5b54f7f8547 --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java @@ -0,0 +1,12 @@ +package io.openems.edge.common.currency; + +public enum CurrencyConfig { + /** + * Euro. + */ + EUR, + /** + * Swedish Kronas. + */ + SEK, +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java b/io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java new file mode 100644 index 00000000000..eed1aebcb0f --- /dev/null +++ b/io.openems.edge.common/src/io/openems/edge/common/test/DummyMeta.java @@ -0,0 +1,22 @@ +package io.openems.edge.common.test; + +import io.openems.edge.common.channel.Channel; +import io.openems.edge.common.component.AbstractOpenemsComponent; +import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.currency.Currency; +import io.openems.edge.common.meta.Meta; + +public class DummyMeta extends AbstractOpenemsComponent implements Meta { + + public DummyMeta(String id, Currency currency) { + super(// + OpenemsComponent.ChannelId.values(), // + Meta.ChannelId.values() // + ); + for (Channel channel : this.channels()) { + channel.nextProcessImage(); + } + this._setCurrency(currency); + super.activate(null, id, "", true); + } +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java index 36ff8534315..57376409f30 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java @@ -3,7 +3,7 @@ import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import io.openems.edge.common.currency.Currency; +import io.openems.edge.common.currency.CurrencyConfig; @ObjectClassDefinition(// name = "Core Meta", // @@ -13,6 +13,6 @@ String webconsole_configurationFactory_nameHint() default "Core Meta"; @AttributeDefinition(name = "Currency", description = "Currency to be used for energy purchase; Energy price value is converted to appropraite currency based on current exchange rate") - Currency currency() default Currency.EUR; + CurrencyConfig currency() default CurrencyConfig.EUR; } \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java index 1c62a29d894..cddc4414492 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java @@ -12,6 +12,7 @@ import io.openems.common.OpenemsConstants; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; +import io.openems.edge.common.currency.Currency; import io.openems.edge.common.meta.Meta; import io.openems.edge.common.modbusslave.ModbusSlave; @@ -62,6 +63,13 @@ protected void deactivate() { } private void applyConfig(Config config) { - this._setCurrency(config.currency()); + this._setCurrency(this.getCurrency(config)); + } + + private Currency getCurrency(Config config) { + return switch (config.currency()) { + case EUR -> Currency.EUR; + case SEK -> Currency.SEK; + }; } } diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java deleted file mode 100644 index 1a9588c8863..00000000000 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/CurrencyProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.openems.edge.timeofusetariff.entsoe; - -import java.util.function.Consumer; - -import io.openems.edge.common.currency.Currency; - -public interface CurrencyProvider { - - /** - * Returns the current Currency. - * - * @return The {@link Currency} - */ - public Currency getCurrent(); - - /** - * Subscribes to the Currency channel to trigger on update. - * - * @param consumer The callback {@link Consumer}. - */ - public void subscribe(Consumer consumer); - - /** - * Unsubscribes from all the Subscriptions. - */ - public void unsubscribeAll(); - -} diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java index 8f751eaecc2..ea5b38ca5ff 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java @@ -12,13 +12,12 @@ public class ExchangeRateApi { private static final String URL = String.format(BASE_URL, "EUR"); /** - * Fetches the exchange rate from base currency EUR to the currency requested by - * user. + * Fetches the exchange rates from base currency EUR. * * @return the Response string. * @throws IOException on error. */ - protected static String getExchangeRate() throws IOException { + protected static String getExchangeRates() throws IOException { var request = new Request.Builder() // .url(URL) // .build(); diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index 51fdf37a5c7..2bb880d88ff 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -21,7 +21,6 @@ import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ServiceScope; import org.osgi.service.metatype.annotations.Designate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,9 +54,10 @@ public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private final AtomicReference> prices = new AtomicReference<>( ImmutableSortedMap.of()); + private final List>> subscriptions = new ArrayList<>(); @Reference - private CurrencyProvider currencyProvider; + private Meta meta; private Config config = null; private Currency currency; @@ -78,58 +78,46 @@ private void activate(ComponentContext context, Config config) { return; } this.config = config; + Consumer updateCurrency = currency -> { this.currency = currency; this.executor.schedule(this.task, 0, TimeUnit.SECONDS); }; // Trigger to activate when the currency from Meta is updated. - this.currencyProvider.subscribe(updateCurrency); + this.subscribe(updateCurrency); - updateCurrency.accept(this.currencyProvider.getCurrent()); + updateCurrency.accept(this.meta.getCurrencyChannel().getNextValue().asEnum()); } @Deactivate protected void deactivate() { super.deactivate(); - this.currencyProvider.unsubscribeAll(); + this.unsubscribeAll(); ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 0); } - @Component(scope = ServiceScope.PROTOTYPE) - public static class CurrencyProviderOsgi implements CurrencyProvider { - - private final List>> subscriptions = new ArrayList<>(); - - @Reference - private Meta meta; - - public Currency getCurrent() { - return this.meta.getCurrencyChannel().getNextValue().asEnum(); - } - - /** - * Subscribes to the Currency channel to trigger on update. - * - * @param updateCurrency The callback {@link Consumer}. - */ - public void subscribe(Consumer updateCurrency) { + /** + * Subscribes to the Currency channel to trigger on update. + * + * @param updateCurrency The callback {@link Consumer}. + */ + private void subscribe(Consumer updateCurrency) { - Consumer> subscription = currency -> { - updateCurrency.accept(currency.asEnum()); - }; - this.subscriptions.add(subscription); - this.meta.getCurrencyChannel().onSetNextValue(subscription); - } + Consumer> subscription = currency -> { + updateCurrency.accept(currency.asEnum()); + }; + this.subscriptions.add(subscription); + this.meta.getCurrencyChannel().onSetNextValue(subscription); + } - /** - * Unsubscribes from all the Subscriptions. - */ - public void unsubscribeAll() { - this.subscriptions.forEach(subscription -> { - this.meta.getCurrencyChannel().removeOnSetNextValueCallback(subscription); - }); - } + /** + * Unsubscribes from all the Subscriptions. + */ + private void unsubscribeAll() { + this.subscriptions.forEach(subscription -> { + this.meta.getCurrencyChannel().removeOnSetNextValueCallback(subscription); + }); } private final Runnable task = () -> { @@ -142,13 +130,10 @@ public void unsubscribeAll() { try { var result = EntsoeApi.query(token, areaCode, fromDate, toDate); - final double exchangeRate; - if (this.currency == Currency.EUR) { - // No need to fetch exchange rate from API. - exchangeRate = EUR_EXHANGE_RATE; - } else { - exchangeRate = Utils.exchangeRateParser(ExchangeRateApi.getExchangeRate(), this.currency); - } + final double exchangeRate = this.currency == Currency.EUR + // No need to fetch exchange rate from API. + ? EUR_EXHANGE_RATE + : Utils.exchangeRateParser(ExchangeRateApi.getExchangeRates(), this.currency); // Parse the response for the prices this.prices.set(Utils.parse(result, "PT60M", exchangeRate)); diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java deleted file mode 100644 index e27cf97b986..00000000000 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/CurrencyProviderTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.openems.edge.timeofusetariff.entsoe; - -import java.util.function.Consumer; - -import io.openems.edge.common.currency.Currency; - -public class CurrencyProviderTest implements CurrencyProvider { - - @Override - public Currency getCurrent() { - return Currency.DEFAULT; - } - - @Override - public void subscribe(Consumer consumer) { - // Empty - } - - @Override - public void unsubscribeAll() { - // Empty - } - -} diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java index 8845829d46b..3ae473103d9 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java @@ -12,202 +12,203 @@ import io.openems.edge.common.currency.Currency; public class ExchangeRateApiTest { - - private static final String EXCHANGE_DATA = "{\n" - + "\"success\": true,\n" - + "\"base\": \"EUR\",\n" - + "\"date\": \"2023-07-24\",\n" - + "\"rates\": {\n" - + "\"AED\": 4.083409,\n" - + "\"AFN\": 95.203885,\n" - + "\"ALL\": 100.955251,\n" - + "\"AMD\": 432.377222,\n" - + "\"ANG\": 2.00259,\n" - + "\"AOA\": 918.778961,\n" - + "\"ARS\": 298.697752,\n" - + "\"AUD\": 1.651218,\n" - + "\"AWG\": 2.003466,\n" - + "\"AZN\": 1.890136,\n" - + "\"BAM\": 1.953145,\n" - + "\"BBD\": 2.223345,\n" - + "\"BDT\": 120.586719,\n" - + "\"BGN\": 1.953979,\n" - + "\"BHD\": 0.418898,\n" - + "\"BIF\": 3145.089913,\n" - + "\"BMD\": 1.11291,\n" - + "\"BND\": 1.477507,\n" - + "\"BOB\": 7.677482,\n" - + "\"BRL\": 5.312471,\n" - + "\"BSD\": 1.112521,\n" - + "\"BTC\": 0.000037,\n" - + "\"BTN\": 91.091466,\n" - + "\"BWP\": 14.618309,\n" - + "\"BYN\": 2.804583,\n" - + "\"BZD\": 2.239344,\n" - + "\"CAD\": 1.469717,\n" - + "\"CDF\": 2756.233722,\n" - + "\"CHF\": 0.96343,\n" - + "\"CLF\": 0.033239,\n" - + "\"CLP\": 907.642573,\n" - + "\"CNH\": 7.999695,\n" - + "\"CNY\": 7.996629,\n" - + "\"COP\": 4424.597438,\n" - + "\"CRC\": 595.871673,\n" - + "\"CUC\": 1.112533,\n" - + "\"CUP\": 28.624803,\n" - + "\"CVE\": 110.101152,\n" - + "\"CZK\": 24.018341,\n" - + "\"DJF\": 197.805497,\n" - + "\"DKK\": 7.446058,\n" - + "\"DOP\": 62.354298,\n" - + "\"DZD\": 149.782102,\n" - + "\"EGP\": 34.250999,\n" - + "\"ERN\": 16.675045,\n" - + "\"ETB\": 61.186276,\n" - + "\"EUR\": 1,\n" - + "\"FJD\": 2.466294,\n" - + "\"FKP\": 0.863468,\n" - + "\"GBP\": 0.86372,\n" - + "\"GEL\": 2.868411,\n" - + "\"GGP\": 0.863794,\n" - + "\"GHS\": 12.888427,\n" - + "\"GIP\": 0.864212,\n" - + "\"GMD\": 66.356165,\n" - + "\"GNF\": 9556.367239,\n" - + "\"GTQ\": 8.722648,\n" - + "\"GYD\": 232.625421,\n" - + "\"HKD\": 8.690213,\n" - + "\"HNL\": 27.354468,\n" - + "\"HRK\": 7.530486,\n" - + "\"HTG\": 151.687614,\n" - + "\"HUF\": 379.234306,\n" - + "\"IDR\": 16699.073705,\n" - + "\"ILS\": 4.026696,\n" - + "\"IMP\": 0.864168,\n" - + "\"INR\": 91.124393,\n" - + "\"IQD\": 1456.187686,\n" - + "\"IRR\": 46699.377014,\n" - + "\"ISK\": 146.216182,\n" - + "\"JEP\": 0.863718,\n" - + "\"JMD\": 171.45343,\n" - + "\"JOD\": 0.789061,\n" - + "\"JPY\": 157.232974,\n" - + "\"KES\": 157.988099,\n" - + "\"KGS\": 97.680423,\n" - + "\"KHR\": 4588.755627,\n" - + "\"KMF\": 492.572188,\n" - + "\"KPW\": 1000.485181,\n" - + "\"KRW\": 1424.639558,\n" - + "\"KWD\": 0.342156,\n" - + "\"KYD\": 0.927141,\n" - + "\"KZT\": 495.498053,\n" - + "\"LAK\": 21217.844772,\n" - + "\"LBP\": 16675.943499,\n" - + "\"LKR\": 364.901196,\n" - + "\"LRD\": 205.933361,\n" - + "\"LSL\": 20.023877,\n" - + "\"LYD\": 5.262548,\n" - + "\"MAD\": 10.765587,\n" - + "\"MDL\": 19.231847,\n" - + "\"MGA\": 4923.895352,\n" - + "\"MKD\": 61.432255,\n" - + "\"MMK\": 2333.03677,\n" - + "\"MNT\": 3911.893647,\n" - + "\"MOP\": 8.951275,\n" - + "\"MRU\": 38.013659,\n" - + "\"MUR\": 50.758404,\n" - + "\"MVR\": 17.064246,\n" - + "\"MWK\": 1170.314043,\n" - + "\"MXN\": 18.886283,\n" - + "\"MYR\": 5.075542,\n" - + "\"MZN\": 70.868602,\n" - + "\"NAD\": 19.976991,\n" - + "\"NGN\": 880.960008,\n" - + "\"NIO\": 40.643549,\n" - + "\"NOK\": 11.195761,\n" - + "\"NPR\": 145.777404,\n" - + "\"NZD\": 1.801523,\n" - + "\"OMR\": 0.42859,\n" - + "\"PAB\": 1.112509,\n" - + "\"PEN\": 3.98693,\n" - + "\"PGK\": 4.02331,\n" - + "\"PHP\": 60.767256,\n" - + "\"PKR\": 318.56528,\n" - + "\"PLN\": 4.460479,\n" - + "\"PYG\": 8082.233385,\n" - + "\"QAR\": 4.051879,\n" - + "\"RON\": 4.93425,\n" - + "\"RSD\": 117.206982,\n" - + "\"RUB\": 101.116135,\n" - + "\"RWF\": 1300.127368,\n" - + "\"SAR\": 4.170544,\n" - + "\"SBD\": 9.288389,\n" - + "\"SCR\": 14.985735,\n" - + "\"SDG\": 668.657232,\n" - + "\"SEK\": 11.553347,\n" - + "\"SGD\": 1.479788,\n" - + "\"SHP\": 0.863472,\n" - + "\"SLL\": 19637.280787,\n" - + "\"SOS\": 632.688782,\n" - + "\"SRD\": 42.722066,\n" - + "\"SSP\": 144.804499,\n" - + "\"STD\": 25372.268235,\n" - + "\"STN\": 24.470982,\n" - + "\"SVC\": 9.727935,\n" - + "\"SYP\": 2793.052605,\n" - + "\"SZL\": 20.003394,\n" - + "\"THB\": 38.341309,\n" - + "\"TJS\": 12.170235,\n" - + "\"TMT\": 3.891181,\n" - + "\"TND\": 3.411579,\n" - + "\"TOP\": 2.604569,\n" - + "\"TRY\": 29.962976,\n" - + "\"TTD\": 7.544979,\n" - + "\"TWD\": 34.876985,\n" - + "\"TZS\": 2716.276713,\n" - + "\"UAH\": 40.828967,\n" - + "\"UGX\": 4045.157318,\n" - + "\"USD\": 1.112576,\n" - + "\"UYU\": 42.188114,\n" - + "\"UZS\": 12906.061037,\n" - + "\"VES\": 32.294958,\n" - + "\"VND\": 26298.838308,\n" - + "\"VUV\": 132.263292,\n" - + "\"WST\": 3.029704,\n" - + "\"XAF\": 655.421747,\n" - + "\"XAG\": 0.04519,\n" - + "\"XAU\": 0.001442,\n" - + "\"XCD\": 3.004786,\n" - + "\"XDR\": 0.820903,\n" - + "\"XOF\": 655.421697,\n" - + "\"XPD\": 0.001221,\n" - + "\"XPF\": 119.235182,\n" - + "\"XPT\": 0.001139,\n" - + "\"YER\": 278.302358,\n" - + "\"ZAR\": 19.993867,\n" - + "\"ZMW\": 21.636944,\n" - + "\"ZWL\": 357.951203\n" - + "}\n" - + "}" - ; + + private static final String EXCHANGE_DATA = """ + { + "success": true, + "base": "EUR", + "date": "2023-07-27", + "rates": { + "AED": 4.074263, + "AFN": 96.546557, + "ALL": 102.049538, + "AMD": 431.402948, + "ANG": 2.00055, + "AOA": 916.112472, + "ARS": 302.239867, + "AUD": 1.629808, + "AWG": 1.998636, + "AZN": 1.886792, + "BAM": 1.958547, + "BBD": 2.219307, + "BDT": 120.446245, + "BGN": 1.955529, + "BHD": 0.418333, + "BIF": 3141.458211, + "BMD": 1.109917, + "BND": 1.473742, + "BOB": 7.669034, + "BRL": 5.256284, + "BSD": 1.109668, + "BTC": 0.000038, + "BTN": 91.002709, + "BWP": 14.488226, + "BYN": 2.802141, + "BZD": 2.237335, + "CAD": 1.46224, + "CDF": 2828.550254, + "CHF": 0.954345, + "CLF": 0.033979, + "CLP": 914.830807, + "CNH": 7.91491, + "CNY": 7.914035, + "COP": 4400.287374, + "CRC": 594.125915, + "CUC": 1.110171, + "CUP": 28.563636, + "CVE": 110.417189, + "CZK": 24.025043, + "DJF": 197.373766, + "DKK": 7.448692, + "DOP": 62.137242, + "DZD": 149.842018, + "EGP": 34.274712, + "ERN": 16.639118, + "ETB": 60.709379, + "EUR": 1, + "FJD": 2.461322, + "FKP": 0.857521, + "GBP": 0.857065, + "GEL": 2.879232, + "GGP": 0.856846, + "GHS": 12.563656, + "GIP": 0.857444, + "GMD": 66.111378, + "GNF": 9545.125955, + "GTQ": 8.711891, + "GYD": 232.184942, + "HKD": 8.646912, + "HNL": 27.325849, + "HRK": 7.53181, + "HTG": 152.038911, + "HUF": 381.18337, + "IDR": 16642.2715, + "ILS": 4.089653, + "IMP": 0.856722, + "INR": 90.963493, + "IQD": 1453.098308, + "IRR": 46879.045396, + "ISK": 145.620797, + "JEP": 0.857125, + "JMD": 171.374964, + "JOD": 0.787314, + "JPY": 155.307802, + "KES": 157.73419, + "KGS": 97.449403, + "KHR": 4577.516015, + "KMF": 493.194214, + "KPW": 998.312097, + "KRW": 1415.953271, + "KWD": 0.340683, + "KYD": 0.924915, + "KZT": 493.937153, + "LAK": 21383.452835, + "LBP": 16776.637925, + "LKR": 366.792902, + "LRD": 205.652382, + "LSL": 19.598023, + "LYD": 5.294188, + "MAD": 10.787573, + "MDL": 19.474446, + "MGA": 4984.515277, + "MKD": 61.641818, + "MMK": 2330.490351, + "MNT": 3903.398698, + "MOP": 8.922849, + "MRU": 37.929989, + "MUR": 50.325954, + "MVR": 17.027553, + "MWK": 1168.023721, + "MXN": 18.676827, + "MYR": 5.024866, + "MZN": 70.714101, + "NAD": 19.579235, + "NGN": 874.632255, + "NIO": 40.574721, + "NOK": 11.166941, + "NPR": 145.603541, + "NZD": 1.775313, + "OMR": 0.427697, + "PAB": 1.110007, + "PEN": 3.990703, + "PGK": 3.980789, + "PHP": 60.551875, + "PKR": 318.444017, + "PLN": 4.41964, + "PYG": 8080.356808, + "QAR": 4.039405, + "RON": 4.923793, + "RSD": 117.135798, + "RUB": 99.859233, + "RWF": 1302.494419, + "SAR": 4.160461, + "SBD": 9.298905, + "SCR": 14.821421, + "SDG": 667.205497, + "SEK": 11.504976, + "SGD": 1.469025, + "SHP": 0.857181, + "SLL": 19594.637867, + "SOS": 631.983688, + "SRD": 42.561687, + "SSP": 144.489173, + "STD": 25317.170823, + "STN": 24.847059, + "SVC": 9.710443, + "SYP": 2786.987107, + "SZL": 19.625727, + "THB": 37.81308, + "TJS": 12.130966, + "TMT": 3.882521, + "TND": 3.428976, + "TOP": 2.599614, + "TRY": 29.846988, + "TTD": 7.539697, + "TWD": 34.634021, + "TZS": 2717.626941, + "UAH": 40.986807, + "UGX": 4049.121605, + "USD": 1.109987, + "UYU": 42.02786, + "UZS": 12905.949812, + "VES": 32.209006, + "VND": 26260.304612, + "VUV": 131.976192, + "WST": 3.024415, + "XAF": 655.638919, + "XAG": 0.044704, + "XAU": 0.001581, + "XCD": 2.997771, + "XDR": 0.825364, + "XOF": 655.639063, + "XPD": 0.001494, + "XPF": 119.274762, + "XPT": 0.001957, + "YER": 277.641934, + "ZAR": 19.492845, + "ZMW": 21.436533, + "ZWL": 357.173757 + } + } + """; @Test @Ignore public void testExchangeRateApi() throws IOException { - ExchangeRateApi.getExchangeRate(); + ExchangeRateApi.getExchangeRates(); } - + @Test public void testExchangeRateParser() throws OpenemsNamedException { - + var currency = Currency.EUR; var response = Utils.exchangeRateParser(EXCHANGE_DATA, currency); assertTrue(response == 1.0); - + currency = Currency.SEK; response = Utils.exchangeRateParser(EXCHANGE_DATA, currency); - + assertFalse(response == 1.0); } } diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java index 2b624100cd7..6627c663921 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java @@ -1,7 +1,7 @@ package io.openems.edge.timeofusetariff.entsoe; import io.openems.common.test.AbstractComponentConfig; -import io.openems.edge.common.currency.Currency; +import io.openems.edge.common.currency.CurrencyConfig; @SuppressWarnings("all") public class MyConfig extends AbstractComponentConfig implements Config { diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java index d416a6b8f71..0e196ceef7a 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java @@ -2,18 +2,20 @@ import org.junit.Test; +import io.openems.edge.common.currency.Currency; import io.openems.edge.common.test.ComponentTest; +import io.openems.edge.common.test.DummyMeta; public class TouEntsoeTest { private static final String COMPONENT_ID = "tou0"; - private static final String CURRENCY_PROVIDER_NAME = "currencyProvider"; @Test public void test() throws Exception { var entsoe = new TouEntsoeImpl(); + var dummyMeta = new DummyMeta("foo0", Currency.EUR); new ComponentTest(entsoe) // - .addReference(CURRENCY_PROVIDER_NAME, new CurrencyProviderTest())// + .addReference("meta", dummyMeta)// .activate(MyConfig.create() // .setId(COMPONENT_ID) // .setSecurityToken("foo-bar") // From c2da9df6a0490141fceb0d0c7aadc1a1f43d4d20 Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Mon, 7 Aug 2023 12:25:46 +0200 Subject: [PATCH 14/16] Review --- .../openems/edge/common/currency/CurrencyConfig.java | 11 ++++++++++- .../src/io/openems/edge/common/meta/Meta.java | 4 ++-- .../.settings/org.eclipse.core.resources.prefs | 7 ------- .../generated/buildfiles | 1 - io.openems.edge.timeofusetariff.entsoe/readme.adoc | 4 ++++ .../edge/timeofusetariff/entsoe/BiddingZone.java | 8 ++++---- .../edge/timeofusetariff/entsoe/ExchangeRateApi.java | 10 ++++++++++ .../edge/timeofusetariff/entsoe/TouEntsoeImpl.java | 6 ++---- .../edge/timeofusetariff/entsoe/EntsoeApiTest.java | 2 +- 9 files changed, 33 insertions(+), 20 deletions(-) delete mode 100644 io.openems.edge.timeofusetariff.entsoe/generated/buildfiles diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java index 5b54f7f8547..4e638008932 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java @@ -1,12 +1,21 @@ package io.openems.edge.common.currency; +import io.openems.edge.common.meta.Meta; +import io.openems.edge.common.meta.Meta.ChannelId; + +/** + * The {@link ChannelId#CURRENCY} mandates the selection of the 'currency' + * configuration property of this specific type. Subsequently, this selected + * property is transformed into the corresponding {@link Currency} type before + * being written through {@link Meta#_setCurrency(Currency)}. + */ public enum CurrencyConfig { /** * Euro. */ EUR, /** - * Swedish Kronas. + * Swedish Krona. */ SEK, } diff --git a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java index 531eb1f2b5a..795bb202299 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java +++ b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java @@ -65,7 +65,7 @@ public default ModbusSlaveTable getModbusSlaveTable(AccessMode accessMode) { } /** - * Gets the Channel for {@link ChannelId#Currency}. + * Gets the Channel for {@link ChannelId#CURRENCY}. * * @return the Channel */ @@ -74,7 +74,7 @@ public default EnumReadChannel getCurrencyChannel() { } /** - * Internal method to set the 'nextValue' on {@link ChannelId#Currency} Channel. + * Internal method to set the 'nextValue' on {@link ChannelId#CURRENCY} Channel. * * @param value the next value */ diff --git a/io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs b/io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs index 02307e84079..99f26c0203a 100644 --- a/io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs +++ b/io.openems.edge.timeofusetariff.entsoe/.settings/org.eclipse.core.resources.prefs @@ -1,9 +1,2 @@ eclipse.preferences.version=1 -encoding//src/io/openems/edge/timeofusetariff/entsoe/Config.java=UTF-8 -encoding//src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java=UTF-8 -encoding//src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java=UTF-8 -encoding//test/io/openems/edge/timeofusetariff/entsoe/MyConfig.java=UTF-8 -encoding//test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java=UTF-8 encoding/=UTF-8 -encoding/bnd.bnd=UTF-8 -encoding/readme.adoc=UTF-8 diff --git a/io.openems.edge.timeofusetariff.entsoe/generated/buildfiles b/io.openems.edge.timeofusetariff.entsoe/generated/buildfiles deleted file mode 100644 index 2ade7672f71..00000000000 --- a/io.openems.edge.timeofusetariff.entsoe/generated/buildfiles +++ /dev/null @@ -1 +0,0 @@ -C:/Users/stefan.feilmeier/fems/develop3/io.openems.edge.timeofusetariff.entsoe/generated/io.openems.edge.timeofusetariff.entsoe.jar diff --git a/io.openems.edge.timeofusetariff.entsoe/readme.adoc b/io.openems.edge.timeofusetariff.entsoe/readme.adoc index 02d969de42e..a6e8c9ad0e1 100644 --- a/io.openems.edge.timeofusetariff.entsoe/readme.adoc +++ b/io.openems.edge.timeofusetariff.entsoe/readme.adoc @@ -4,4 +4,8 @@ This implementation uses the ENTSO-E transparency platform to receive day-ahead To request a (free) authentication token, please see chapter "2. Authentication and Authorisation" in the official API documentation: https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_authentication_and_authorisation +Euro-denominated prices retrieved from ENTSO-E are subsequently converted to the user's currency using the Exchange Rates API. + +For detailed information about the Exchange Rates API, please refer to: https://exchangerate.host/#/docs + https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.entsoe[Source Code icon:github[]] \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java index 2f8ce5d40e5..14ebb2999b7 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java @@ -15,19 +15,19 @@ public enum BiddingZone { /** * BZN|SE1. */ - SWEDEN_ZONE_1("10Y1001A1001A44P"), // + SWEDEN_SE1("10Y1001A1001A44P"), // /** * BZN|SE2. */ - SWEDEN_ZONE_2("10Y1001A1001A45N"), // + SWEDEN_SE2("10Y1001A1001A45N"), // /** * BZN|SE3. */ - SWEDEN_ZONE_3("10Y1001A1001A46L"), // + SWEDEN_SE3("10Y1001A1001A46L"), // /** * BZN|SE4. */ - SWEDEN_ZONE_4("10Y1001A1001A47J"), // + SWEDEN_SE4("10Y1001A1001A47J"), // ; private final String code; diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java index ea5b38ca5ff..579cc6d4ecd 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java @@ -5,6 +5,16 @@ import okhttp3.OkHttpClient; import okhttp3.Request; +/** + * A utility class for fetching exchange rates from a web API. + * + *

    + * Day ahead prices retrieved from ENTSO-E are always in Euros and required to + * be converted to the user's currency using the exchange rates provided by + * Exchange Rate API. For more information on the ExchangeRate API, visit: + * https://exchangerate.host/#/docs + */ public class ExchangeRateApi { private static final String BASE_URL = "https://api.exchangerate.host/latest?base=%s"; diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index 2bb880d88ff..6e219122170 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -47,7 +47,6 @@ ) public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe, OpenemsComponent, TimeOfUseTariff { - private static final int EUR_EXHANGE_RATE = 1; private static final int API_EXECUTE_HOUR = 14; private final Logger log = LoggerFactory.getLogger(TouEntsoeImpl.class); @@ -130,9 +129,8 @@ private void unsubscribeAll() { try { var result = EntsoeApi.query(token, areaCode, fromDate, toDate); - final double exchangeRate = this.currency == Currency.EUR - // No need to fetch exchange rate from API. - ? EUR_EXHANGE_RATE + final double exchangeRate = this.currency == Currency.EUR // + ? 1 // No need to fetch exchange rate from API. : Utils.exchangeRateParser(ExchangeRateApi.getExchangeRates(), this.currency); // Parse the response for the prices diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java index 5e205339a96..2c15914d240 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java @@ -17,7 +17,7 @@ public class EntsoeApiTest { @Ignore public void testQuery() throws IOException, ParserConfigurationException, SAXException { var token = ""; - var areaCode = BiddingZone.SWEDEN_ZONE_3.getCode(); + var areaCode = BiddingZone.SWEDEN_SE3.getCode(); var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS).withZoneSameLocal(ZoneId.systemDefault()); var toDate = fromDate.plusDays(1); var response = EntsoeApi.query(token, areaCode, fromDate, toDate); From 8e7224c6842e5591f31671c63323deb35538b13b Mon Sep 17 00:00:00 2001 From: Sagar Bandi Venu Date: Wed, 9 Aug 2023 11:32:06 +0200 Subject: [PATCH 15/16] Review --- .../edge/common/currency/CurrencyConfig.java | 14 +- .../src/io/openems/edge/core/meta/Config.java | 2 +- .../io/openems/edge/core/meta/MetaImpl.java | 10 +- .../bnd.bnd | 1 + .../readme.adoc | 4 +- .../timeofusetariff/entsoe/BiddingZone.java | 6 +- .../edge/timeofusetariff/entsoe/Config.java | 2 +- .../timeofusetariff/entsoe/EntsoeApi.java | 2 +- .../timeofusetariff/entsoe/TouEntsoe.java | 2 +- .../timeofusetariff/entsoe/TouEntsoeImpl.java | 62 ++-- .../edge/timeofusetariff/entsoe/Utils.java | 37 +- .../timeofusetariff/entsoe/EntsoeApiTest.java | 8 +- .../entsoe/ExchangeRateApiTest.java | 350 +++++++++--------- .../timeofusetariff/entsoe/ParserTest.java | 10 +- .../timeofusetariff/entsoe/TouEntsoeTest.java | 2 - 15 files changed, 267 insertions(+), 245 deletions(-) diff --git a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java index 4e638008932..aef402c2f16 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java +++ b/io.openems.edge.common/src/io/openems/edge/common/currency/CurrencyConfig.java @@ -17,5 +17,17 @@ public enum CurrencyConfig { /** * Swedish Krona. */ - SEK, + SEK; + + /** + * Converts the {@link CurrencyConfig} to the {@link Currency}. + * + * @return The {@link Currency}. + */ + public Currency toCurrency() { + return switch (this) { + case EUR -> Currency.EUR; + case SEK -> Currency.SEK; + }; + } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java index 57376409f30..41b9dd0f734 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/Config.java @@ -12,7 +12,7 @@ String webconsole_configurationFactory_nameHint() default "Core Meta"; - @AttributeDefinition(name = "Currency", description = "Currency to be used for energy purchase; Energy price value is converted to appropraite currency based on current exchange rate") + @AttributeDefinition(name = "Currency", description = "Every monetary value is inherently expressed in this Currency. Values obtained in a different currency (e.g. energy prices from a web service) are internally converted to this Currency using the current exchange rate.") CurrencyConfig currency() default CurrencyConfig.EUR; } \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java index cddc4414492..0f47201664f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/meta/MetaImpl.java @@ -12,7 +12,6 @@ import io.openems.common.OpenemsConstants; import io.openems.edge.common.component.AbstractOpenemsComponent; import io.openems.edge.common.component.OpenemsComponent; -import io.openems.edge.common.currency.Currency; import io.openems.edge.common.meta.Meta; import io.openems.edge.common.modbusslave.ModbusSlave; @@ -63,13 +62,6 @@ protected void deactivate() { } private void applyConfig(Config config) { - this._setCurrency(this.getCurrency(config)); - } - - private Currency getCurrency(Config config) { - return switch (config.currency()) { - case EUR -> Currency.EUR; - case SEK -> Currency.SEK; - }; + this._setCurrency(config.currency().toCurrency()); } } diff --git a/io.openems.edge.timeofusetariff.entsoe/bnd.bnd b/io.openems.edge.timeofusetariff.entsoe/bnd.bnd index 5f93b54c0d0..e1809acc25a 100644 --- a/io.openems.edge.timeofusetariff.entsoe/bnd.bnd +++ b/io.openems.edge.timeofusetariff.entsoe/bnd.bnd @@ -12,4 +12,5 @@ Bundle-Version: 1.0.0.${tstamp} io.openems.wrapper.okhttp,\ -testpath: \ + org.jetbrains.kotlin.osgi-bundle,\ ${testpath} diff --git a/io.openems.edge.timeofusetariff.entsoe/readme.adoc b/io.openems.edge.timeofusetariff.entsoe/readme.adoc index a6e8c9ad0e1..584d4fadb44 100644 --- a/io.openems.edge.timeofusetariff.entsoe/readme.adoc +++ b/io.openems.edge.timeofusetariff.entsoe/readme.adoc @@ -2,9 +2,9 @@ This implementation uses the ENTSO-E transparency platform to receive day-ahead prices in European power grids. -To request a (free) authentication token, please see chapter "2. Authentication and Authorisation" in the official API documentation: https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_authentication_and_authorisation +To request a (free) authentication token, please see chapter "2. Authentication and Authorization" in the official API documentation: https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_authentication_and_authorisation -Euro-denominated prices retrieved from ENTSO-E are subsequently converted to the user's currency using the Exchange Rates API. +Euro-denominated prices retrieved from ENTSO-E are subsequently converted to the user's currency (defined in Core.Meta Component) using the Exchange Rates API. For detailed information about the Exchange Rates API, please refer to: https://exchangerate.host/#/docs diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java index 14ebb2999b7..1ade97fa231 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/BiddingZone.java @@ -30,13 +30,9 @@ public enum BiddingZone { SWEDEN_SE4("10Y1001A1001A47J"), // ; - private final String code; + public final String code; private BiddingZone(String code) { this.code = code; } - - public String getCode() { - return this.code; - } } diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java index 22e2e0a2f06..dfc74112e38 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Config.java @@ -22,7 +22,7 @@ String securityToken(); @AttributeDefinition(name = "Bidding Zone", description = "Zone corresponding to the customer's location") - BiddingZone biddingZone() default BiddingZone.GERMANY; + BiddingZone biddingZone(); String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff ENTSO-E [{id}]"; } \ No newline at end of file diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java index 3830866994b..b09cb18c27f 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java @@ -73,7 +73,7 @@ protected static String query(String token, String areaCode, ZonedDateTime fromD try (var response = client.newCall(request).execute()) { if (!response.isSuccessful()) { - throw new IOException("Unexpected code " + response); + throw new IOException("Unable to get response from ENTSO-E API " + response); } return response.body().string(); diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java index e45643cc8e9..f3bab7a1533 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoe.java @@ -9,7 +9,7 @@ public interface TouEntsoe extends OpenemsComponent, TimeOfUseTariff { public enum ChannelId implements io.openems.edge.common.channel.ChannelId { UNABLE_TO_UPDATE_PRICES(Doc.of(Level.WARNING) // - .text("Unable to update prices from Entsoe API")), // + .text("Unable to update prices from ENTSO-E API")), // ; private final Doc doc; diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java index 6e219122170..8898a790860 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/TouEntsoeImpl.java @@ -5,8 +5,6 @@ import java.time.Duration; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -53,7 +51,6 @@ public class TouEntsoeImpl extends AbstractOpenemsComponent implements TouEntsoe private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private final AtomicReference> prices = new AtomicReference<>( ImmutableSortedMap.of()); - private final List>> subscriptions = new ArrayList<>(); @Reference private Meta meta; @@ -69,6 +66,11 @@ public TouEntsoeImpl() { ); } + private final Consumer> updateCurrency = currency -> { + this.currency = currency.asEnum(); + this.executor.schedule(this.task, 0, TimeUnit.SECONDS); + }; + @Activate private void activate(ComponentContext context, Config config) { super.activate(context, config.id(), config.alias(), config.enabled()); @@ -76,75 +78,63 @@ private void activate(ComponentContext context, Config config) { if (!config.enabled()) { return; } - this.config = config; - Consumer updateCurrency = currency -> { - this.currency = currency; - this.executor.schedule(this.task, 0, TimeUnit.SECONDS); - }; + if (config.securityToken() == null) { + this.logError(this.log, "Please enter Security token to access ENTSO-E"); + return; + } + this.config = config; // Trigger to activate when the currency from Meta is updated. - this.subscribe(updateCurrency); + this.setMeta(); - updateCurrency.accept(this.meta.getCurrencyChannel().getNextValue().asEnum()); + this.updateCurrency.accept(this.meta.getCurrencyChannel().getNextValue()); } @Deactivate protected void deactivate() { super.deactivate(); - this.unsubscribeAll(); + this.unsetMeta(); ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 0); } /** - * Subscribes to the Currency channel to trigger on update. - * - * @param updateCurrency The callback {@link Consumer}. + * Initializes the Currency channel listener. Adds the callback to the channel. */ - private void subscribe(Consumer updateCurrency) { - - Consumer> subscription = currency -> { - updateCurrency.accept(currency.asEnum()); - }; - this.subscriptions.add(subscription); - this.meta.getCurrencyChannel().onSetNextValue(subscription); + private void setMeta() { + this.meta.getCurrencyChannel().onSetNextValue(this.updateCurrency); } /** - * Unsubscribes from all the Subscriptions. + * Removes the Callback from Currency Channel. */ - private void unsubscribeAll() { - this.subscriptions.forEach(subscription -> { - this.meta.getCurrencyChannel().removeOnSetNextValueCallback(subscription); - }); + private void unsetMeta() { + this.meta.getCurrencyChannel().removeOnSetNextValueCallback(this.updateCurrency); } private final Runnable task = () -> { var token = this.config.securityToken(); - var areaCode = this.config.biddingZone().getCode(); + var areaCode = this.config.biddingZone().code; var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS); var toDate = fromDate.plusDays(1); var unableToUpdatePrices = false; try { - var result = EntsoeApi.query(token, areaCode, fromDate, toDate); + final var result = EntsoeApi.query(token, areaCode, fromDate, toDate); + final var entsoeCurrency = Utils.parseCurrency(result); - final double exchangeRate = this.currency == Currency.EUR // + final var exchangeRate = this.currency.toString() == entsoeCurrency // ? 1 // No need to fetch exchange rate from API. : Utils.exchangeRateParser(ExchangeRateApi.getExchangeRates(), this.currency); // Parse the response for the prices - this.prices.set(Utils.parse(result, "PT60M", exchangeRate)); + this.prices.set(Utils.parsePrices(result, "PT60M", exchangeRate)); // store the time stamp this.updateTimeStamp = ZonedDateTime.now(); } catch (IOException | ParserConfigurationException | SAXException | OpenemsNamedException e) { + this.logWarn(this.log, e.getMessage()); e.printStackTrace(); - if (e instanceof OpenemsNamedException) { - this.logWarn(this.log, "Unable to get the currency exchange rate " + e.getMessage()); - } else { - this.logWarn(this.log, "Unable to Update Entsoe Time-Of-Use Price: " + e.getMessage()); - } unableToUpdatePrices = true; } @@ -171,7 +161,7 @@ private void unsubscribeAll() { @Override public TimeOfUsePrices getPrices() { // return empty TimeOfUsePrices if data is not yet available. - if (this.updateTimeStamp == null) { + if (!this.config.enabled() || this.updateTimeStamp == null) { return TimeOfUsePrices.empty(ZonedDateTime.now()); } diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java index 9d63f74a014..e1938e71021 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/Utils.java @@ -64,9 +64,9 @@ public static Builder create() { } /** - * Parses the xml response from the Entso-E API. + * Parses the XML response from the Entso-E API to get the Day-Ahead prices. * - * @param xml The xml string to be parsed. + * @param xml The XML string to be parsed. * @param resolution PT15M or PT60M * @param exchangeRate The exchange rate of user currency to EUR. * @return The {@link ImmutableSortedMap} @@ -74,7 +74,7 @@ public static Builder create() { * @throws SAXException on error * @throws IOException on error */ - protected static ImmutableSortedMap parse(String xml, String resolution, double exchangeRate) + protected static ImmutableSortedMap parsePrices(String xml, String resolution, double exchangeRate) throws ParserConfigurationException, SAXException, IOException { var dbFactory = DocumentBuilderFactory.newInstance(); var dBuilder = dbFactory.newDocumentBuilder(); @@ -126,11 +126,40 @@ protected static ImmutableSortedMap parse(String xml, Stri return result.toMap(); } + /** + * Parses the XML response from the Entso-E API to extract the currency + * associated with the prices. + * + * @param xml The XML string to be parsed. + * @return The currency string. + * @throws ParserConfigurationException on error. + * @throws SAXException on error + * @throws IOException on error + */ + protected static String parseCurrency(String xml) throws ParserConfigurationException, SAXException, IOException { + var dbFactory = DocumentBuilderFactory.newInstance(); + var dBuilder = dbFactory.newDocumentBuilder(); + var is = new InputSource(new StringReader(xml)); + var doc = dBuilder.parse(is); + var root = doc.getDocumentElement(); + + var result = stream(root) // + // + .filter(n -> n.getNodeName() == "TimeSeries") // + .flatMap(XmlUtils::stream) // + // + .filter(n -> n.getNodeName() == "currency_Unit.name") // + .map(XmlUtils::getContentAsString) // + .findFirst().get(); + + return result; + } + /** * Parses the response string from Exchange rate API. * * @param response The Response string from ExcahngeRate API. - * @param currency The {@link Curreny} selected by User. + * @param currency The {@link Currency} selected by User. * @return the exchange rate. * @throws OpenemsNamedException on error. */ diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java index 2c15914d240..b8e950f4c11 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/EntsoeApiTest.java @@ -16,12 +16,10 @@ public class EntsoeApiTest { @Test @Ignore public void testQuery() throws IOException, ParserConfigurationException, SAXException { - var token = ""; - var areaCode = BiddingZone.SWEDEN_SE3.getCode(); + var token = ""; // Fill personal security token and remove 'Ignore' tag while testing. + var areaCode = BiddingZone.SWEDEN_SE3.code; var fromDate = ZonedDateTime.now().truncatedTo(ChronoUnit.HOURS).withZoneSameLocal(ZoneId.systemDefault()); var toDate = fromDate.plusDays(1); - var response = EntsoeApi.query(token, areaCode, fromDate, toDate); - - System.out.println(response); + EntsoeApi.query(token, areaCode, fromDate, toDate); } } diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java index 3ae473103d9..819d30d74f1 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApiTest.java @@ -14,181 +14,181 @@ public class ExchangeRateApiTest { private static final String EXCHANGE_DATA = """ - { - "success": true, - "base": "EUR", - "date": "2023-07-27", - "rates": { - "AED": 4.074263, - "AFN": 96.546557, - "ALL": 102.049538, - "AMD": 431.402948, - "ANG": 2.00055, - "AOA": 916.112472, - "ARS": 302.239867, - "AUD": 1.629808, - "AWG": 1.998636, - "AZN": 1.886792, - "BAM": 1.958547, - "BBD": 2.219307, - "BDT": 120.446245, - "BGN": 1.955529, - "BHD": 0.418333, - "BIF": 3141.458211, - "BMD": 1.109917, - "BND": 1.473742, - "BOB": 7.669034, - "BRL": 5.256284, - "BSD": 1.109668, - "BTC": 0.000038, - "BTN": 91.002709, - "BWP": 14.488226, - "BYN": 2.802141, - "BZD": 2.237335, - "CAD": 1.46224, - "CDF": 2828.550254, - "CHF": 0.954345, - "CLF": 0.033979, - "CLP": 914.830807, - "CNH": 7.91491, - "CNY": 7.914035, - "COP": 4400.287374, - "CRC": 594.125915, - "CUC": 1.110171, - "CUP": 28.563636, - "CVE": 110.417189, - "CZK": 24.025043, - "DJF": 197.373766, - "DKK": 7.448692, - "DOP": 62.137242, - "DZD": 149.842018, - "EGP": 34.274712, - "ERN": 16.639118, - "ETB": 60.709379, - "EUR": 1, - "FJD": 2.461322, - "FKP": 0.857521, - "GBP": 0.857065, - "GEL": 2.879232, - "GGP": 0.856846, - "GHS": 12.563656, - "GIP": 0.857444, - "GMD": 66.111378, - "GNF": 9545.125955, - "GTQ": 8.711891, - "GYD": 232.184942, - "HKD": 8.646912, - "HNL": 27.325849, - "HRK": 7.53181, - "HTG": 152.038911, - "HUF": 381.18337, - "IDR": 16642.2715, - "ILS": 4.089653, - "IMP": 0.856722, - "INR": 90.963493, - "IQD": 1453.098308, - "IRR": 46879.045396, - "ISK": 145.620797, - "JEP": 0.857125, - "JMD": 171.374964, - "JOD": 0.787314, - "JPY": 155.307802, - "KES": 157.73419, - "KGS": 97.449403, - "KHR": 4577.516015, - "KMF": 493.194214, - "KPW": 998.312097, - "KRW": 1415.953271, - "KWD": 0.340683, - "KYD": 0.924915, - "KZT": 493.937153, - "LAK": 21383.452835, - "LBP": 16776.637925, - "LKR": 366.792902, - "LRD": 205.652382, - "LSL": 19.598023, - "LYD": 5.294188, - "MAD": 10.787573, - "MDL": 19.474446, - "MGA": 4984.515277, - "MKD": 61.641818, - "MMK": 2330.490351, - "MNT": 3903.398698, - "MOP": 8.922849, - "MRU": 37.929989, - "MUR": 50.325954, - "MVR": 17.027553, - "MWK": 1168.023721, - "MXN": 18.676827, - "MYR": 5.024866, - "MZN": 70.714101, - "NAD": 19.579235, - "NGN": 874.632255, - "NIO": 40.574721, - "NOK": 11.166941, - "NPR": 145.603541, - "NZD": 1.775313, - "OMR": 0.427697, - "PAB": 1.110007, - "PEN": 3.990703, - "PGK": 3.980789, - "PHP": 60.551875, - "PKR": 318.444017, - "PLN": 4.41964, - "PYG": 8080.356808, - "QAR": 4.039405, - "RON": 4.923793, - "RSD": 117.135798, - "RUB": 99.859233, - "RWF": 1302.494419, - "SAR": 4.160461, - "SBD": 9.298905, - "SCR": 14.821421, - "SDG": 667.205497, - "SEK": 11.504976, - "SGD": 1.469025, - "SHP": 0.857181, - "SLL": 19594.637867, - "SOS": 631.983688, - "SRD": 42.561687, - "SSP": 144.489173, - "STD": 25317.170823, - "STN": 24.847059, - "SVC": 9.710443, - "SYP": 2786.987107, - "SZL": 19.625727, - "THB": 37.81308, - "TJS": 12.130966, - "TMT": 3.882521, - "TND": 3.428976, - "TOP": 2.599614, - "TRY": 29.846988, - "TTD": 7.539697, - "TWD": 34.634021, - "TZS": 2717.626941, - "UAH": 40.986807, - "UGX": 4049.121605, - "USD": 1.109987, - "UYU": 42.02786, - "UZS": 12905.949812, - "VES": 32.209006, - "VND": 26260.304612, - "VUV": 131.976192, - "WST": 3.024415, - "XAF": 655.638919, - "XAG": 0.044704, - "XAU": 0.001581, - "XCD": 2.997771, - "XDR": 0.825364, - "XOF": 655.639063, - "XPD": 0.001494, - "XPF": 119.274762, - "XPT": 0.001957, - "YER": 277.641934, - "ZAR": 19.492845, - "ZMW": 21.436533, - "ZWL": 357.173757 - } + { + "success": true, + "base": "EUR", + "date": "2023-07-27", + "rates": { + "AED": 4.074263, + "AFN": 96.546557, + "ALL": 102.049538, + "AMD": 431.402948, + "ANG": 2.00055, + "AOA": 916.112472, + "ARS": 302.239867, + "AUD": 1.629808, + "AWG": 1.998636, + "AZN": 1.886792, + "BAM": 1.958547, + "BBD": 2.219307, + "BDT": 120.446245, + "BGN": 1.955529, + "BHD": 0.418333, + "BIF": 3141.458211, + "BMD": 1.109917, + "BND": 1.473742, + "BOB": 7.669034, + "BRL": 5.256284, + "BSD": 1.109668, + "BTC": 0.000038, + "BTN": 91.002709, + "BWP": 14.488226, + "BYN": 2.802141, + "BZD": 2.237335, + "CAD": 1.46224, + "CDF": 2828.550254, + "CHF": 0.954345, + "CLF": 0.033979, + "CLP": 914.830807, + "CNH": 7.91491, + "CNY": 7.914035, + "COP": 4400.287374, + "CRC": 594.125915, + "CUC": 1.110171, + "CUP": 28.563636, + "CVE": 110.417189, + "CZK": 24.025043, + "DJF": 197.373766, + "DKK": 7.448692, + "DOP": 62.137242, + "DZD": 149.842018, + "EGP": 34.274712, + "ERN": 16.639118, + "ETB": 60.709379, + "EUR": 1, + "FJD": 2.461322, + "FKP": 0.857521, + "GBP": 0.857065, + "GEL": 2.879232, + "GGP": 0.856846, + "GHS": 12.563656, + "GIP": 0.857444, + "GMD": 66.111378, + "GNF": 9545.125955, + "GTQ": 8.711891, + "GYD": 232.184942, + "HKD": 8.646912, + "HNL": 27.325849, + "HRK": 7.53181, + "HTG": 152.038911, + "HUF": 381.18337, + "IDR": 16642.2715, + "ILS": 4.089653, + "IMP": 0.856722, + "INR": 90.963493, + "IQD": 1453.098308, + "IRR": 46879.045396, + "ISK": 145.620797, + "JEP": 0.857125, + "JMD": 171.374964, + "JOD": 0.787314, + "JPY": 155.307802, + "KES": 157.73419, + "KGS": 97.449403, + "KHR": 4577.516015, + "KMF": 493.194214, + "KPW": 998.312097, + "KRW": 1415.953271, + "KWD": 0.340683, + "KYD": 0.924915, + "KZT": 493.937153, + "LAK": 21383.452835, + "LBP": 16776.637925, + "LKR": 366.792902, + "LRD": 205.652382, + "LSL": 19.598023, + "LYD": 5.294188, + "MAD": 10.787573, + "MDL": 19.474446, + "MGA": 4984.515277, + "MKD": 61.641818, + "MMK": 2330.490351, + "MNT": 3903.398698, + "MOP": 8.922849, + "MRU": 37.929989, + "MUR": 50.325954, + "MVR": 17.027553, + "MWK": 1168.023721, + "MXN": 18.676827, + "MYR": 5.024866, + "MZN": 70.714101, + "NAD": 19.579235, + "NGN": 874.632255, + "NIO": 40.574721, + "NOK": 11.166941, + "NPR": 145.603541, + "NZD": 1.775313, + "OMR": 0.427697, + "PAB": 1.110007, + "PEN": 3.990703, + "PGK": 3.980789, + "PHP": 60.551875, + "PKR": 318.444017, + "PLN": 4.41964, + "PYG": 8080.356808, + "QAR": 4.039405, + "RON": 4.923793, + "RSD": 117.135798, + "RUB": 99.859233, + "RWF": 1302.494419, + "SAR": 4.160461, + "SBD": 9.298905, + "SCR": 14.821421, + "SDG": 667.205497, + "SEK": 11.504976, + "SGD": 1.469025, + "SHP": 0.857181, + "SLL": 19594.637867, + "SOS": 631.983688, + "SRD": 42.561687, + "SSP": 144.489173, + "STD": 25317.170823, + "STN": 24.847059, + "SVC": 9.710443, + "SYP": 2786.987107, + "SZL": 19.625727, + "THB": 37.81308, + "TJS": 12.130966, + "TMT": 3.882521, + "TND": 3.428976, + "TOP": 2.599614, + "TRY": 29.846988, + "TTD": 7.539697, + "TWD": 34.634021, + "TZS": 2717.626941, + "UAH": 40.986807, + "UGX": 4049.121605, + "USD": 1.109987, + "UYU": 42.02786, + "UZS": 12905.949812, + "VES": 32.209006, + "VND": 26260.304612, + "VUV": 131.976192, + "WST": 3.024415, + "XAF": 655.638919, + "XAG": 0.044704, + "XAU": 0.001581, + "XCD": 2.997771, + "XDR": 0.825364, + "XOF": 655.639063, + "XPD": 0.001494, + "XPF": 119.274762, + "XPT": 0.001957, + "YER": 277.641934, + "ZAR": 19.492845, + "ZMW": 21.436533, + "ZWL": 357.173757 + } } """; diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java index 9d6ae61bce6..b5f8b250900 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/ParserTest.java @@ -1,5 +1,6 @@ package io.openems.edge.timeofusetariff.entsoe; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -10,6 +11,8 @@ import org.junit.Test; import org.xml.sax.SAXException; +import io.openems.edge.common.currency.Currency; + public class ParserTest { private static String XML = """ @@ -545,14 +548,17 @@ public class ParserTest { @Test public void testParse() throws IOException, ParserConfigurationException, SAXException { var currencyExchangeValue = 1.0; - var result = Utils.parse(XML, "PT15M", currencyExchangeValue); + var result = Utils.parsePrices(XML, "PT15M", currencyExchangeValue); assertTrue(result.firstEntry().getValue() == 109.93f); assertTrue(result.lastEntry().getValue() == 65.07f); - result = Utils.parse(XML, "PT60M", currencyExchangeValue); + result = Utils.parsePrices(XML, "PT60M", currencyExchangeValue); assertFalse(result.firstEntry().getValue() == 109.93f); assertTrue(result.lastEntry().getValue() == 86.53f); + + var res = Utils.parseCurrency(XML); + assertEquals(res, Currency.EUR.toString()); } } diff --git a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java index 0e196ceef7a..719a35fc61d 100644 --- a/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java +++ b/io.openems.edge.timeofusetariff.entsoe/test/io/openems/edge/timeofusetariff/entsoe/TouEntsoeTest.java @@ -21,7 +21,5 @@ public void test() throws Exception { .setSecurityToken("foo-bar") // .setBiddingZone(BiddingZone.GERMANY) // .build()); - - // Thread.sleep(5000); } } From dd37ceb7970ad1b689511881b4f889302116ad1e Mon Sep 17 00:00:00 2001 From: Stefan Feilmeier Date: Thu, 24 Aug 2023 17:31:50 +0200 Subject: [PATCH 16/16] Improve handling of Currency from Meta & some small improvements --- .../src/io/openems/edge/common/meta/Meta.java | 10 ++++ .../readme.adoc | 4 +- .../timeofusetariff/entsoe/EntsoeApi.java | 2 +- .../entsoe/ExchangeRateApi.java | 2 +- .../timeofusetariff/entsoe/TouEntsoeImpl.java | 48 ++++++++----------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java index 795bb202299..dd7c72b89fb 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java +++ b/io.openems.edge.common/src/io/openems/edge/common/meta/Meta.java @@ -5,6 +5,7 @@ import io.openems.common.types.OpenemsType; import io.openems.edge.common.channel.Doc; import io.openems.edge.common.channel.EnumReadChannel; +import io.openems.edge.common.channel.value.Value; import io.openems.edge.common.currency.Currency; import io.openems.edge.common.modbusslave.ModbusSlave; import io.openems.edge.common.modbusslave.ModbusSlaveNatureTable; @@ -73,6 +74,15 @@ public default EnumReadChannel getCurrencyChannel() { return this.channel(ChannelId.CURRENCY); } + /** + * Gets the Capacity in [Wh]. See {@link ChannelId#CURRENCY}. + * + * @return the Channel {@link Value} + */ + public default Currency getCurrency() { + return this.getCurrencyChannel().value().asEnum(); + } + /** * Internal method to set the 'nextValue' on {@link ChannelId#CURRENCY} Channel. * diff --git a/io.openems.edge.timeofusetariff.entsoe/readme.adoc b/io.openems.edge.timeofusetariff.entsoe/readme.adoc index 584d4fadb44..aea3aee6223 100644 --- a/io.openems.edge.timeofusetariff.entsoe/readme.adoc +++ b/io.openems.edge.timeofusetariff.entsoe/readme.adoc @@ -2,9 +2,9 @@ This implementation uses the ENTSO-E transparency platform to receive day-ahead prices in European power grids. -To request a (free) authentication token, please see chapter "2. Authentication and Authorization" in the official API documentation: https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_authentication_and_authorisation +To request a (free) authentication token, please see chapter "2. Authentication and Authorisation" in the official API documentation: https://transparency.entsoe.eu/content/static_content/Static%20content/web%20api/Guide.html#_authentication_and_authorisation -Euro-denominated prices retrieved from ENTSO-E are subsequently converted to the user's currency (defined in Core.Meta Component) using the Exchange Rates API. +Prices retrieved from ENTSO-E are subsequently converted to the user's currency (defined in Core.Meta Component) using the Exchange Rates API. For detailed information about the Exchange Rates API, please refer to: https://exchangerate.host/#/docs diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java index b09cb18c27f..1ea088b09d1 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/EntsoeApi.java @@ -73,7 +73,7 @@ protected static String query(String token, String areaCode, ZonedDateTime fromD try (var response = client.newCall(request).execute()) { if (!response.isSuccessful()) { - throw new IOException("Unable to get response from ENTSO-E API " + response); + throw new IOException("Unable to get response from ENTSO-E API: " + response); } return response.body().string(); diff --git a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java index 579cc6d4ecd..569c851fcea 100644 --- a/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java +++ b/io.openems.edge.timeofusetariff.entsoe/src/io/openems/edge/timeofusetariff/entsoe/ExchangeRateApi.java @@ -9,7 +9,7 @@ * A utility class for fetching exchange rates from a web API. * *

    - * Day ahead prices retrieved from ENTSO-E are always in Euros and required to + * Day ahead prices retrieved from ENTSO-E are usually in EUR and might have to * be converted to the user's currency using the exchange rates provided by * Exchange Rate API. For more information on the ExchangeRate API, visit: * > updateCurrency = currency -> { - this.currency = currency.asEnum(); - this.executor.schedule(this.task, 0, TimeUnit.SECONDS); + private final BiConsumer, Value> onCurrencyChange = (a, b) -> { + this.scheduleTask(0); }; @Activate @@ -79,37 +76,30 @@ private void activate(ComponentContext context, Config config) { return; } - if (config.securityToken() == null) { - this.logError(this.log, "Please enter Security token to access ENTSO-E"); + if (config.securityToken() == null || config.securityToken().isBlank()) { + this.logError(this.log, "Please configure Security Token to access ENTSO-E"); return; } this.config = config; - // Trigger to activate when the currency from Meta is updated. - this.setMeta(); - - this.updateCurrency.accept(this.meta.getCurrencyChannel().getNextValue()); + // React on updates to Currency. + this.meta.getCurrencyChannel().onChange(this.onCurrencyChange); } @Deactivate protected void deactivate() { super.deactivate(); - this.unsetMeta(); + this.meta.getCurrencyChannel().removeOnChangeCallback(this.onCurrencyChange); ThreadPoolUtils.shutdownAndAwaitTermination(this.executor, 0); } /** - * Initializes the Currency channel listener. Adds the callback to the channel. - */ - private void setMeta() { - this.meta.getCurrencyChannel().onSetNextValue(this.updateCurrency); - } - - /** - * Removes the Callback from Currency Channel. + * Schedules execution the the update Task. + * + * @param seconds execute task in seconds */ - private void unsetMeta() { - this.meta.getCurrencyChannel().removeOnSetNextValueCallback(this.updateCurrency); + private void scheduleTask(long seconds) { + this.executor.schedule(this.task, seconds, TimeUnit.SECONDS); } private final Runnable task = () -> { @@ -122,18 +112,19 @@ private void unsetMeta() { try { final var result = EntsoeApi.query(token, areaCode, fromDate, toDate); final var entsoeCurrency = Utils.parseCurrency(result); - - final var exchangeRate = this.currency.toString() == entsoeCurrency // + final var globalCurrency = this.meta.getCurrency(); + final var exchangeRate = globalCurrency.name() == entsoeCurrency // ? 1 // No need to fetch exchange rate from API. - : Utils.exchangeRateParser(ExchangeRateApi.getExchangeRates(), this.currency); + : Utils.exchangeRateParser(ExchangeRateApi.getExchangeRates(), globalCurrency); // Parse the response for the prices this.prices.set(Utils.parsePrices(result, "PT60M", exchangeRate)); // store the time stamp this.updateTimeStamp = ZonedDateTime.now(); + } catch (IOException | ParserConfigurationException | SAXException | OpenemsNamedException e) { - this.logWarn(this.log, e.getMessage()); + this.logWarn(this.log, "Unable to Update Entsoe Time-Of-Use Price: " + e.getMessage()); e.printStackTrace(); unableToUpdatePrices = true; } @@ -154,8 +145,7 @@ private void unsetMeta() { } var delay = Duration.between(now, nextRun).getSeconds(); - - this.executor.schedule(this.task, delay, TimeUnit.SECONDS); + this.scheduleTask(delay); }; @Override