Skip to content

Commit

Permalink
Energy Scheduler - next generation of Time-of-Use optimization (#2789)
Browse files Browse the repository at this point in the history
- Introduce generically usable EnergyScheduleHandler (ESH) for executing energy simulations and applying schedules
  - Provide nice debugLog output and channel "SimulationsPerQuarter" to detect performance issues
  - Use Cache for cost of Genotypes
- **Add config property "Version" to be able to switch between old (only ESS, but fast and well tested) and new (generic ESH but slower) implementation.**
  - Attention: be sure to set EnergyScheduler (`Core.Energy`) and `Controller.Ess.Time-Of-Use-Tariff` to the same Version!
- Introduce new implementation of `EnergyFlow` that uses linear constraint validation and optimization
- Implement ESHs for 
  - `Controller.Ess.Time-Of-Use-Tariff`,
  - `Controller.Ess.EmergencyCapacityReserve`, 
  - `Controller.Ess.LimitTotalDischarge` and 
  - `Controller.Ess.GridOptimizedCharge` (MANUAL only)
- Old implementations are moved to `v1` packages and marked @deprecated and will be removed in one of the next versions of OpenEMS. Unfortunately right now this leads to some mixed code.
  • Loading branch information
sfeilmeier authored Oct 22, 2024
1 parent 58cf33d commit 34b5e3a
Show file tree
Hide file tree
Showing 100 changed files with 7,502 additions and 1,595 deletions.
1 change: 1 addition & 0 deletions cnf/build.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ buildpath: \
org.osgi.service.metatype.annotations;version='1.4.1',\
org.osgi.util.promise;version='1.2.0',\
com.google.guava;version='33.3.1.jre',\
com.google.guava.failureaccess;version='1.0.2',\
com.google.gson;version='2.11.0',\

testpath: \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,48 @@ public DummySum withGridActivePower(int value) {
return this.self();
}

/**
* Set {@link Sum.ChannelId#ESS_CAPACITY}.
*
* @param value the value
* @return myself
*/
public DummySum withEssCapacity(int value) {
withValue(this, Sum.ChannelId.ESS_CAPACITY, value);
return this.self();
}

/**
* Set {@link Sum.ChannelId#ESS_SOC}.
*
* @param value the value
* @return myself
*/
public DummySum withEssSoc(int value) {
withValue(this, Sum.ChannelId.ESS_SOC, value);
return this.self();
}

/**
* Set {@link Sum.ChannelId#ESS_MIN_DISCHARGE_POWER}.
*
* @param value the value
* @return myself
*/
public DummySum withEssMinDischargePower(int value) {
withValue(this, Sum.ChannelId.ESS_MIN_DISCHARGE_POWER, value);
return this.self();
}

/**
* Set {@link Sum.ChannelId#ESS_MAX_DISCHARGE_POWER}.
*
* @param value the value
* @return myself
*/
public DummySum withEssMaxDischargePower(int value) {
withValue(this, Sum.ChannelId.ESS_MAX_DISCHARGE_POWER, value);
return this.self();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import java.io.IOException;
import java.net.ServerSocket;
import java.time.Instant;
import java.util.function.BiFunction;
import java.util.function.Function;

import io.openems.common.test.TimeLeapClock;
import io.openems.edge.common.channel.Channel;
import io.openems.edge.common.channel.ChannelId;
import io.openems.edge.common.channel.value.Value;
import io.openems.edge.common.component.OpenemsComponent;

public class TestUtils {
Expand Down Expand Up @@ -80,4 +83,24 @@ public static void withValue(Channel<?> channel, Object value) {
channel.setNextValue(value);
channel.nextProcessImage();
}

/**
* Helper to test a {@link #withValue(Channel, Object)} method in a JUnit test.
*
* @param <T> the type of the {@link AbstractDummyOpenemsComponent}
* @param sut the actual system-under-test
* @param setter the getChannel getter method
* @param getter the withChannel setter method
*/
public static <T> void testWithValue(T sut, BiFunction<T, Integer, T> setter, Function<T, Value<Integer>> getter) {
var before = getter.apply(sut).get();
if (before != null) {
throw new IllegalArgumentException("TestUtils.testWithValue() expected [null] got [" + before + "]");
}
setter.apply(sut, 123);
var after = getter.apply(sut).get().intValue();
if (after != 123) {
throw new IllegalArgumentException("TestUtils.testWithValue() expected [123] got [" + after + "]");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -364,23 +364,15 @@ public static JsonElement getAsJson(OpenemsType type, Object originalValue) {
return JsonNull.INSTANCE;
}
var value = TypeUtils.getAsType(type, originalValue);
switch (type) {
case BOOLEAN:
return new JsonPrimitive((Boolean) value ? 1 : 0);
case SHORT:
return new JsonPrimitive((Short) value);
case INTEGER:
return new JsonPrimitive((Integer) value);
case LONG:
return new JsonPrimitive((Long) value);
case FLOAT:
return new JsonPrimitive((Float) value);
case DOUBLE:
return new JsonPrimitive((Double) value);
case STRING:
return new JsonPrimitive((String) value);
}
throw new IllegalArgumentException("Converter for value [" + value + "] to JSON is not implemented.");
return switch (type) {
case BOOLEAN -> new JsonPrimitive((Boolean) value ? 1 : 0);
case SHORT -> new JsonPrimitive((Short) value);
case INTEGER -> new JsonPrimitive((Integer) value);
case LONG -> new JsonPrimitive((Long) value);
case FLOAT -> new JsonPrimitive((Float) value);
case DOUBLE -> new JsonPrimitive((Double) value);
case STRING -> new JsonPrimitive((String) value);
};
}

/**
Expand Down Expand Up @@ -427,6 +419,28 @@ public static Integer sum(Integer... values) {
return result;
}

/**
* Safely add Floats. If one of them is null it is considered '0'. If all of
* them are null, 'null' is returned.
*
* @param values the {@link Float} values
* @return the sum
*/
public static Float sum(Float... values) {
Float result = null;
for (Float value : values) {
if (value == null) {
continue;
}
if (result == null) {
result = value;
} else {
result += value;
}
}
return result;
}

/**
* Safely add Longs. If one of them is null it is considered '0'. If all of them
* are null, 'null' is returned.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.openems.edge.common.sum;

import static io.openems.edge.common.test.TestUtils.testWithValue;

import org.junit.Test;

import io.openems.common.exceptions.OpenemsException;

public class DummySumTest {

@Test
public void test() throws OpenemsException {
final var sut = new DummySum();

testWithValue(sut, DummySum::withProductionAcActivePower, Sum::getProductionAcActivePower);
testWithValue(sut, DummySum::withGridActivePower, Sum::getGridActivePower);
testWithValue(sut, DummySum::withEssCapacity, Sum::getEssCapacity);
testWithValue(sut, DummySum::withEssSoc, Sum::getEssSoc);
testWithValue(sut, DummySum::withEssMinDischargePower, Sum::getEssMinDischargePower);
testWithValue(sut, DummySum::withEssMaxDischargePower, Sum::getEssMaxDischargePower);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
package io.openems.edge.common.type;

import static com.google.gson.JsonNull.INSTANCE;
import static io.openems.common.types.OpenemsType.BOOLEAN;
import static io.openems.common.types.OpenemsType.DOUBLE;
import static io.openems.common.types.OpenemsType.FLOAT;
import static io.openems.common.types.OpenemsType.INTEGER;
import static io.openems.common.types.OpenemsType.LONG;
import static io.openems.common.types.OpenemsType.SHORT;
import static io.openems.common.types.OpenemsType.STRING;
import static io.openems.edge.common.type.TypeUtils.getAsJson;
import static io.openems.edge.common.type.TypeUtils.sum;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import java.util.Optional;

import org.junit.Test;

import com.google.gson.JsonPrimitive;

import io.openems.common.function.ThrowingRunnable;
import io.openems.common.types.OpenemsType;
import io.openems.common.types.OptionsEnum;
import io.openems.edge.common.channel.value.Value;

Expand Down Expand Up @@ -81,8 +92,8 @@ public void testMin() {

@Test
public void testSumDouble() {
assertNull(TypeUtils.sum((Double) null, null));
assertEquals(4.0, TypeUtils.sum(1.5, 2.5), 0.1);
assertNull(sum((Double) null, null));
assertEquals(4.0, sum(1.5, 2.5), 0.1);
}

@Test
Expand Down Expand Up @@ -269,6 +280,47 @@ public void testGetAsType() {
}
}

@Test
public void testGetAsJson() {
assertEquals(INSTANCE, getAsJson(INTEGER, null));
assertEquals(new JsonPrimitive(0), getAsJson(BOOLEAN, false));
assertEquals(new JsonPrimitive(1), getAsJson(BOOLEAN, true));
assertEquals(new JsonPrimitive(123), getAsJson(SHORT, 123));
assertEquals(new JsonPrimitive(234), getAsJson(INTEGER, 234));
assertEquals(new JsonPrimitive(345), getAsJson(LONG, 345));
assertEquals(new JsonPrimitive(45.6F), getAsJson(FLOAT, 45.6F));
assertEquals(new JsonPrimitive(56.7), getAsJson(DOUBLE, 56.7));
assertEquals(new JsonPrimitive("678"), getAsJson(STRING, "678"));
}

@Test
public void sumInteger() {
assertEquals(6, sum(1, 2, 3).intValue());
assertNull(sum((Integer) null));
assertEquals(6, sum(1, null, 2, 3).intValue());
}

@Test
public void sumFloat() {
assertEquals(6F, sum(1F, 2F, 3F).floatValue(), 0.001F);
assertNull(sum((Float) null));
assertEquals(6F, sum(1F, null, 2F, 3F).floatValue(), 0.001F);
}

@Test
public void sumLong() {
assertEquals(6L, sum(1L, 2L, 3L).longValue());
assertNull(sum((Long) null));
assertEquals(6L, sum(1L, null, 2L, 3L).longValue());
}

@Test
public void sumDouble() {
assertEquals(6., sum(1., 2., 3.).doubleValue(), 0.001);
assertNull(sum((Double) null));
assertEquals(6., sum(1., null, 2., 3.).doubleValue(), 0.001);
}

private static void assertException(ThrowingRunnable<Exception> runnable) {
try {
runnable.run();
Expand All @@ -279,31 +331,31 @@ private static void assertException(ThrowingRunnable<Exception> runnable) {
}

private static Boolean getAsBoolean(Object value) {
return TypeUtils.getAsType(OpenemsType.BOOLEAN, value);
return TypeUtils.getAsType(BOOLEAN, value);
}

private static Short getAsShort(Object value) {
return TypeUtils.getAsType(OpenemsType.SHORT, value);
return TypeUtils.getAsType(SHORT, value);
}

private static Integer getAsInteger(Object value) {
return TypeUtils.getAsType(OpenemsType.INTEGER, value);
return TypeUtils.getAsType(INTEGER, value);
}

private static Long getAsLong(Object value) {
return TypeUtils.getAsType(OpenemsType.LONG, value);
return TypeUtils.getAsType(LONG, value);
}

private static Float getAsFloat(Object value) {
return TypeUtils.getAsType(OpenemsType.FLOAT, value);
return TypeUtils.getAsType(FLOAT, value);
}

private static Double getAsDouble(Object value) {
return TypeUtils.getAsType(OpenemsType.DOUBLE, value);
return TypeUtils.getAsType(DOUBLE, value);
}

private static String getAsString(Object value) {
return TypeUtils.getAsType(OpenemsType.STRING, value);
return TypeUtils.getAsType(STRING, value);
}

private static enum MyOptionsEnum implements OptionsEnum {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.common,\
io.openems.edge.common,\
io.openems.edge.controller.api,\
io.openems.edge.energy.api,\
io.openems.edge.ess.api,\
io.openems.edge.ess.generic,\

Expand Down
Loading

0 comments on commit 34b5e3a

Please sign in to comment.