Skip to content

Commit

Permalink
Add Time-Of-Use Tariff Tibber (#173) (#1663)
Browse files Browse the repository at this point in the history
Implemented Tibber API to retrieve the prices for Time-Of-Use Tariff API service.
Coorent readme doc for corrently.

Co-authored-by: Stefan Feilmeier <[email protected]>
Reviewed-on: https://git.intranet.fenecon.de/FENECON/fems/pulls/173
Reviewed-by: Stefan Feilmeier <[email protected]>
Co-authored-by: Sagar Venu <[email protected]>
Co-committed-by: Sagar Venu <[email protected]>
(cherry picked from commit b798b26)

Co-authored-by: Sagar Venu <[email protected]>
  • Loading branch information
sfeilmeier and venu-sagar authored Nov 15, 2021
1 parent b724fef commit 40f58e2
Show file tree
Hide file tree
Showing 13 changed files with 539 additions and 1 deletion.
2 changes: 2 additions & 0 deletions io.openems.edge.application/EdgeApp.bndrun
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
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',\

-runbundles: \
Java-WebSocket;version='[1.5.2,1.5.3)',\
Expand Down Expand Up @@ -296,6 +297,7 @@
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.shared.influxdb;version=snapshot,\
io.openems.wrapper.eu.chargetime.ocpp;version=snapshot,\
io.openems.wrapper.fastexcel;version=snapshot,\
Expand Down
2 changes: 1 addition & 1 deletion io.openems.edge.timeofusetariff.corrently/readme.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
= Time-Of-Use Tariff Corrently by STROMDAO

Retrieves the hourly prices from the Corrently API and converts them into quarterly prices. Prices are updated every day at 14:00 and stored locally.
Retrieves the quarterly prices from the Corrently API. Prices are updated every day at 14:00 and stored locally.

https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.corrently[Source Code icon:github[]]
12 changes: 12 additions & 0 deletions io.openems.edge.timeofusetariff.tibber/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="aQute.bnd.classpath.container"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="src" output="bin" path="src"/>
<classpathentry kind="src" output="bin_test" path="test">
<attributes>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="bin"/>
</classpath>
2 changes: 2 additions & 0 deletions io.openems.edge.timeofusetariff.tibber/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/bin_test/
/generated/
23 changes: 23 additions & 0 deletions io.openems.edge.timeofusetariff.tibber/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>io.openems.edge.timeofusetariff.tibber</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>bndtools.core.bndbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>bndtools.core.bndnature</nature>
</natures>
</projectDescription>
14 changes: 14 additions & 0 deletions io.openems.edge.timeofusetariff.tibber/bnd.bnd
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Bundle-Name: OpenEMS Edge Time-Of-Use Tariff Tibber
Bundle-Vendor: FENECON GmbH
Bundle-License: https://opensource.org/licenses/EPL-2.0
Bundle-Version: 1.0.0.${tstamp}

-buildpath: \
${buildpath},\
io.openems.common,\
io.openems.edge.common,\
io.openems.edge.timeofusetariff.api,\
io.openems.wrapper.okhttp,\

-testpath: \
${testpath}
5 changes: 5 additions & 0 deletions io.openems.edge.timeofusetariff.tibber/readme.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
= Time-Of-Use Tariff Tibber

Retrieves the hourly prices from the Tibber API and converts them into quarterly prices. Prices are updated every day at 14:00 and stored locally.

https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.timeofusetariff.corrently[Source Code icon:github[]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.openems.edge.timeofusetariff.tibber;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

@ObjectClassDefinition(//
name = "Time-Of-Use Tariff Tibber", //
description = "Time-Of-Use Tariff implementation for Tibber.")
@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 = "Access Token", description = "Access token for the Tibber API", type = AttributeType.PASSWORD)
String accessToken() default "";

String webconsole_configurationFactory_nameHint() default "Time-Of-Use Tariff Tibber [{id}]";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.openems.edge.timeofusetariff.tibber;

import io.openems.common.types.OpenemsType;
import io.openems.edge.common.channel.Doc;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.timeofusetariff.api.TimeOfUseTariff;

public interface Tibber extends TimeOfUseTariff, OpenemsComponent {

public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
HTTP_STATUS_CODE(Doc.of(OpenemsType.INTEGER)//
.text("Displays the HTTP status code"))//
;

private final Doc doc;

private ChannelId(Doc doc) {
this.doc = doc;
}

@Override
public Doc doc() {
return this.doc;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package io.openems.edge.timeofusetariff.tibber;

import java.io.IOException;
import java.time.Clock;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.TreeMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

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.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;

import com.google.common.collect.ImmutableSortedMap;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.utils.JsonUtils;
import io.openems.common.utils.ThreadPoolUtils;
import io.openems.edge.common.component.AbstractOpenemsComponent;
import io.openems.edge.common.component.ComponentManager;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.timeofusetariff.api.TimeOfUsePrices;
import io.openems.edge.timeofusetariff.api.TimeOfUseTariff;
import io.openems.edge.timeofusetariff.api.utils.TimeOfUseTariffUtils;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

@Designate(ocd = Config.class, factory = true)
@Component(//
name = "TimeOfUseTariff.Tibber", //
immediate = true, //
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
public class TibberImpl extends AbstractOpenemsComponent implements TimeOfUseTariff, OpenemsComponent, Tibber {

private static final String TIBBER_API_URL = "https://api.tibber.com/v1-beta/gql";

private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

private Config config = null;

private final AtomicReference<ImmutableSortedMap<ZonedDateTime, Float>> prices = new AtomicReference<ImmutableSortedMap<ZonedDateTime, Float>>(
ImmutableSortedMap.of());

private ZonedDateTime updateTimeStamp = null;

private final Runnable task = () -> {

/*
* Update Map of prices
*/
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, JsonUtils.buildJsonObject() //
.addProperty("query", //
"{\n"
+ " viewer {\n"
+ " homes {\n"
+ " currentSubscription{\n"
+ " priceInfo{\n"
+ " today {\n"
+ " total\n"
+ " startsAt\n"
+ " }\n"
+ " tomorrow {\n"
+ " total\n"
+ " startsAt\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}" + "") //
.build().toString());
Request request = new Request.Builder() //
.url(TIBBER_API_URL) //
.header("Authorization", this.config.accessToken()).post(body) //
.build();
int httpStatusCode;
try (Response response = client.newCall(request).execute()) {
httpStatusCode = response.code();

if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}

// Parse the response for the prices
this.prices.set(TibberImpl.parsePrices(response.body().string()));

// store the time stamp
this.updateTimeStamp = ZonedDateTime.now();

} catch (IOException | OpenemsNamedException e) {
e.printStackTrace();
httpStatusCode = 0;
}

this.channel(Tibber.ChannelId.HTTP_STATUS_CODE).setNextValue(httpStatusCode);

/*
* Schedule next price update for 2 pm
*/
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime nextRun = now.withHour(14).truncatedTo(ChronoUnit.HOURS);
if (now.isAfter(nextRun)) {
nextRun = nextRun.plusDays(1);
}

Duration duration = Duration.between(now, nextRun);
long delay = duration.getSeconds();

this.executor.schedule(this.task, delay, TimeUnit.SECONDS);
};

@Reference
private ComponentManager componentManager;

public TibberImpl() {
super(//
OpenemsComponent.ChannelId.values(), //
Tibber.ChannelId.values() //
);
}

@Activate
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 TimeOfUsePrices getPrices() {
// return null if data is not yet available.
if (this.updateTimeStamp == null) {
return null;
}

return TimeOfUseTariffUtils.getNext24HourPrices(Clock.systemDefaultZone() /* can be mocked for testing */,
this.prices.get(), this.updateTimeStamp);
}

/**
* Parse the Tibber JSON to the Price Map.
*
* @param jsonData the Tibber JSON
* @return the Price Map
* @throws OpenemsNamedException on error
*/
public static ImmutableSortedMap<ZonedDateTime, Float> parsePrices(String jsonData) throws OpenemsNamedException {
TreeMap<ZonedDateTime, Float> result = new TreeMap<>();

if (!jsonData.isEmpty()) {

JsonObject line = JsonUtils.parseToJsonObject(jsonData);
JsonArray homes = JsonUtils.getAsJsonObject(line, "data") //
.getAsJsonObject("viewer") //
.getAsJsonArray("homes");

for (JsonElement home : homes) {

JsonObject priceInfo = JsonUtils.getAsJsonObject(home, "currentSubscription") //
.getAsJsonObject("priceInfo");

// Price info for today and tomorrow.
JsonArray today = JsonUtils.getAsJsonArray(priceInfo, "today");
JsonArray tomorrow = JsonUtils.getAsJsonArray(priceInfo, "tomorrow");

// Adding to an array to avoid individual variables for individual for loops.
JsonArray[] days = { today, tomorrow };

// parse the arrays for price and time stamps.
for (JsonArray day : days) {
for (JsonElement element : day) {
float marketPrice = JsonUtils.getAsFloat(element, "total");
ZonedDateTime startTime = ZonedDateTime
.parse(JsonUtils.getAsString(element, "startsAt"), DateTimeFormatter.ISO_DATE_TIME)
.withZoneSameInstant(ZoneId.systemDefault());
// Adding the values in the Map.
result.put(startTime, marketPrice);
}
}
}
}
return ImmutableSortedMap.copyOf(result);
}
}
Empty file.
Loading

0 comments on commit 40f58e2

Please sign in to comment.