Skip to content

Commit

Permalink
ESS Standby-Controller (OpenEMS#1158)
Browse files Browse the repository at this point in the history
Regularly checks the functionality of an ESS once per week while being in Standby mode.

Use-Case::
  In projects with Hochlastzeitfenster (HLZF) the storage system might not be used for some weeks or months. In these periods it is required to make a regular check of the system and to calibrate the battery. This controller automates this function.

Scheduling::
  The controller is supposed to be configured to run with less priority than "LimitTotalDischarge", "BatteryHandling",... and higher priority than "HLZF", "Balancing", "Peak-Shaving",...
  • Loading branch information
sfeilmeier authored Sep 9, 2021
1 parent 59db904 commit 5c9b781
Show file tree
Hide file tree
Showing 22 changed files with 1,088 additions and 0 deletions.
2 changes: 2 additions & 0 deletions io.openems.edge.application/EdgeApp.bndrun
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
bnd.identity;id='io.openems.edge.controller.ess.predictivedelaycharge',\
bnd.identity;id='io.openems.edge.controller.ess.reactivepowervoltagecharacteristic',\
bnd.identity;id='io.openems.edge.controller.ess.selltogridlimit',\
bnd.identity;id='io.openems.edge.controller.ess.standby',\
bnd.identity;id='io.openems.edge.controller.evcs',\
bnd.identity;id='io.openems.edge.controller.evcs.fixactivepower',\
bnd.identity;id='io.openems.edge.controller.generic.jsonlogic',\
Expand Down Expand Up @@ -200,6 +201,7 @@
io.openems.edge.controller.ess.predictivedelaycharge;version=snapshot,\
io.openems.edge.controller.ess.reactivepowervoltagecharacteristic;version=snapshot,\
io.openems.edge.controller.ess.selltogridlimit;version=snapshot,\
io.openems.edge.controller.ess.standby;version=snapshot,\
io.openems.edge.controller.evcs;version=snapshot,\
io.openems.edge.controller.evcs.fixactivepower;version=snapshot,\
io.openems.edge.controller.generic.jsonlogic;version=snapshot,\
Expand Down
12 changes: 12 additions & 0 deletions io.openems.edge.controller.ess.standby/.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.controller.ess.standby/.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.controller.ess.standby/.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.controller.ess.standby</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.controller.ess.standby/bnd.bnd
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Bundle-Name: OpenEMS Edge Controller ESS Standby
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.controller.api,\
io.openems.edge.ess.api,\

-testpath: \
${testpath}
11 changes: 11 additions & 0 deletions io.openems.edge.controller.ess.standby/readme.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
= ESS Standby

Regularly checks the functionality of an ESS once per week while being in Standby mode.

Use-Case::
In projects with Hochlastzeitfenster (HLZF) the storage system might not be used for some weeks or months. In these periods it is required to make a regular check of the system and to calibrate the battery. This controller automates this function.

Scheduling::
The controller is supposed to be configured to run with less priority than "LimitTotalDischarge", "BatteryHandling",... and higher priority than "HLZF", "Balancing", "Peak-Shaving",...

https://github.com/OpenEMS/openems/tree/develop/io.openems.edge.controller.ess.standby[Source Code icon:github[]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.openems.edge.controller.ess.standby;

import java.time.DayOfWeek;

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

@ObjectClassDefinition(//
name = "Controller ESS Standby", //
description = "Puts a energy storage system in standby mode while regularly checking the functionality once per week.")
public @interface Config {

@AttributeDefinition(name = "Component-ID", description = "Unique ID of this Component")
String id() default "ctrlEssStandby0";

@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 = "Ess-ID", description = "ID of Ess device.")
String ess_id();

@AttributeDefinition(name = "Startdate", description = "for example: 30.12.1998")
String startDate();

@AttributeDefinition(name = "Enddate", description = "for example: 31.12.1998")
String endDate();

@AttributeDefinition(name = "Day of week", description = "On which weekday should the regular check run?")
DayOfWeek dayOfWeek();

String webconsole_configurationFactory_nameHint() default "Controller ESS Standby [{id}]";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.openems.edge.controller.ess.standby;

import io.openems.edge.common.channel.Doc;
import io.openems.edge.controller.api.Controller;
import io.openems.edge.controller.ess.standby.statemachine.StateMachine.State;

public interface StandbyController extends Controller {

public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
STATE_MACHINE(Doc.of(State.values()) //
.text("Current State of State-Machine")), //
;

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,119 @@
package io.openems.edge.controller.ess.standby;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
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.common.sum.GridMode;
import io.openems.edge.common.sum.Sum;
import io.openems.edge.controller.api.Controller;
import io.openems.edge.controller.ess.standby.statemachine.Context;
import io.openems.edge.controller.ess.standby.statemachine.StateMachine;
import io.openems.edge.controller.ess.standby.statemachine.StateMachine.State;
import io.openems.edge.ess.api.ManagedSymmetricEss;

@Designate(ocd = Config.class, factory = true)
@Component(//
name = "Controller.Ess.Standby", //
immediate = true, //
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
public class StandbyControllerImpl extends AbstractOpenemsComponent
implements StandbyController, Controller, OpenemsComponent {

private static final String DATE_FORMAT = "dd.MM.yyyy";

private final Logger log = LoggerFactory.getLogger(StandbyControllerImpl.class);

public StandbyControllerImpl() {
super(//
OpenemsComponent.ChannelId.values(), //
Controller.ChannelId.values(), //
StandbyController.ChannelId.values() //
);
}

@Reference
protected ComponentManager componentManager;

@Reference
protected Sum sum;

/**
* Manages the {@link State}s of the StateMachine.
*/
private final StateMachine stateMachine = new StateMachine(State.UNDEFINED);

private Config config;
private LocalDate configuredStartDate;
private LocalDate configuredEndDate;

@Activate
void activate(ComponentContext context, Config config) {
super.activate(context, config.id(), config.alias(), config.enabled());

this.config = config;
this.configuredStartDate = convertDate(config.startDate());
this.configuredEndDate = convertDate(config.endDate());
}

@Deactivate
protected void deactivate() {
super.deactivate();
}

@Override
public void run() throws OpenemsNamedException {
ManagedSymmetricEss ess = this.componentManager.getComponent(this.config.ess_id());

/*
* Check that we are On-Grid (and warn on undefined Grid-Mode)
*/
GridMode gridMode = ess.getGridMode();
if (gridMode.isUndefined()) {
this.logWarn(this.log, "Grid-Mode is [UNDEFINED]");
}
switch (gridMode) {
case ON_GRID:
case UNDEFINED:
break;
case OFF_GRID:
return;
}

// Store the current State
this.channel(StandbyController.ChannelId.STATE_MACHINE).setNextValue(this.stateMachine.getCurrentState());

// Prepare Context
Context context = new Context(this, ess, this.sum, this.configuredStartDate, this.configuredEndDate,
this.config.dayOfWeek(), this.componentManager.getClock());

// Call the StateMachine
this.stateMachine.run(context);
}

/**
* Converts a string to a LocalDate.
*
* @param date the date as string in the format "dd.MM.yyyy" (DATE_FORMAT).
* @return the date as {@link LocalDate}
*/
private static LocalDate convertDate(String date) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
LocalDate localDate = LocalDate.parse(date, dateTimeFormatter);
return localDate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.openems.edge.controller.ess.standby.statemachine;

import java.time.Clock;
import java.time.DayOfWeek;
import java.time.LocalDate;

import io.openems.edge.common.statemachine.AbstractContext;
import io.openems.edge.common.sum.Sum;
import io.openems.edge.controller.ess.standby.StandbyController;
import io.openems.edge.ess.api.ManagedSymmetricEss;

public class Context extends AbstractContext<StandbyController> {

protected final ManagedSymmetricEss ess;
protected final Sum sum;
protected final LocalDate configuredStartDate;
protected final LocalDate configuredEndDate;
protected final DayOfWeek configuredDayOfWeek;

/**
* The clock. Used to provide a mocked clock for unit tests.
*/
protected final Clock clock;

public Context(StandbyController parent, ManagedSymmetricEss ess, Sum sum, LocalDate configuredStartDate,
LocalDate configuredEndDate, DayOfWeek configuredDayOfWeek, Clock clock) {
super(parent);
this.ess = ess;
this.sum = sum;
this.configuredStartDate = configuredStartDate;
this.configuredEndDate = configuredEndDate;
this.configuredDayOfWeek = configuredDayOfWeek;
this.clock = clock;
}

}
Loading

0 comments on commit 5c9b781

Please sign in to comment.