Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Controller IO HeatingElement: add cumulated active time #1760

Merged
merged 1 commit into from
Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions io.openems.edge.controller.io.heatingelement/bnd.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.edge.controller.api,\
io.openems.edge.ess.api,\
io.openems.edge.io.api,\
io.openems.edge.timedata.api,\

-testpath: \
${testpath}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

import io.openems.edge.controller.io.heatingelement.enums.Level;
import io.openems.edge.controller.io.heatingelement.enums.Mode;
import io.openems.edge.controller.io.heatingelement.enums.WorkMode;

@ObjectClassDefinition(//
name = "Controller IO Heating Element", //
description = "Controls a three-phase heating element via Relays, according to grid active power")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,53 @@
package io.openems.edge.controller.io.heatingelement;

import io.openems.common.channel.Unit;
import io.openems.common.types.OpenemsType;
import static io.openems.common.channel.PersistencePriority.HIGH;
import static io.openems.common.channel.Unit.CUMULATED_SECONDS;
import static io.openems.common.channel.Unit.SECONDS;
import static io.openems.common.types.OpenemsType.INTEGER;
import static io.openems.common.types.OpenemsType.LONG;

import io.openems.edge.common.channel.Doc;
import io.openems.edge.controller.io.heatingelement.enums.Level;

public interface ControllerHeatingElement {

public enum ChannelId implements io.openems.edge.common.channel.ChannelId {
LEVEL(Doc.of(Level.values()) //
.text("Current Level")),
AWAITING_HYSTERESIS(Doc.of(OpenemsType.INTEGER)), //
PHASE1_TIME(Doc.of(OpenemsType.INTEGER)//
.unit(Unit.SECONDS)), //
PHASE2_TIME(Doc.of(OpenemsType.INTEGER)//
.unit(Unit.SECONDS)), //
PHASE3_TIME(Doc.of(OpenemsType.INTEGER)//
.unit(Unit.SECONDS)), //
LEVEL1_TIME(Doc.of(OpenemsType.INTEGER)//
.unit(Unit.SECONDS)), //
LEVEL2_TIME(Doc.of(OpenemsType.INTEGER)//
.unit(Unit.SECONDS)), //
LEVEL3_TIME(Doc.of(OpenemsType.INTEGER)//
.unit(Unit.SECONDS)), //
TOTAL_PHASE_TIME(Doc.of(OpenemsType.INTEGER)//
.unit(Unit.SECONDS)), //
FORCE_START_AT_SECONDS_OF_DAY(Doc.of(OpenemsType.INTEGER)//
.unit(Unit.SECONDS)); //
AWAITING_HYSTERESIS(Doc.of(INTEGER)), //
PHASE1_TIME(Doc.of(INTEGER)//
.unit(SECONDS)), //
PHASE2_TIME(Doc.of(INTEGER)//
.unit(SECONDS)), //
PHASE3_TIME(Doc.of(INTEGER)//
.unit(SECONDS)), //
/*
* LEVELx_TIME was used for old history view. It is left for the analysis of the
* forced duration on a day.
*/
LEVEL1_TIME(Doc.of(INTEGER)//
.unit(SECONDS)), //
LEVEL2_TIME(Doc.of(INTEGER)//
.unit(SECONDS)), //
LEVEL3_TIME(Doc.of(INTEGER)//
.unit(SECONDS)), //

/*
* Total active Time of each Level.
*/
LEVEL1_CUMULATED_TIME(Doc.of(LONG)//
.unit(CUMULATED_SECONDS) //
.persistencePriority(HIGH)), //
LEVEL2_CUMULATED_TIME(Doc.of(LONG)//
.unit(CUMULATED_SECONDS) //
.persistencePriority(HIGH)), //
LEVEL3_CUMULATED_TIME(Doc.of(LONG)//
.unit(CUMULATED_SECONDS) //
.persistencePriority(HIGH)), //
TOTAL_PHASE_TIME(Doc.of(INTEGER)//
.unit(SECONDS)), //
FORCE_START_AT_SECONDS_OF_DAY(Doc.of(INTEGER)//
.unit(SECONDS)); //

private final Doc doc;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
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;
Expand All @@ -24,21 +27,46 @@
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.common.sum.Sum;
import io.openems.edge.controller.api.Controller;
import io.openems.edge.controller.io.heatingelement.enums.Level;
import io.openems.edge.controller.io.heatingelement.enums.Phase;
import io.openems.edge.controller.io.heatingelement.enums.WorkMode;
import io.openems.edge.timedata.api.Timedata;
import io.openems.edge.timedata.api.TimedataProvider;
import io.openems.edge.timedata.api.utils.CalculateActiveTime;

@Designate(ocd = Config.class, factory = true)
@Component(name = "Controller.IO.HeatingElement", //
immediate = true, //
configurationPolicy = ConfigurationPolicy.REQUIRE //
)
public class ControllerHeatingElementImpl extends AbstractOpenemsComponent
implements ControllerHeatingElement, Controller, OpenemsComponent {
implements ControllerHeatingElement, Controller, OpenemsComponent, TimedataProvider {

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

/**
* Definitions for each phase.
*/
private final PhaseDef phase1;
private final PhaseDef phase2;
private final PhaseDef phase3;

/**
* Cumulated active time for each level.
*/
private final CalculateActiveTime totalTimeLevel1 = new CalculateActiveTime(this,
ControllerHeatingElement.ChannelId.LEVEL1_CUMULATED_TIME);
private final CalculateActiveTime totalTimeLevel2 = new CalculateActiveTime(this,
ControllerHeatingElement.ChannelId.LEVEL2_CUMULATED_TIME);
private final CalculateActiveTime totalTimeLevel3 = new CalculateActiveTime(this,
ControllerHeatingElement.ChannelId.LEVEL3_CUMULATED_TIME);

// Current Level
private Level currentLevel = Level.UNDEFINED;

// Last Level change time, used for the hysteresis
private LocalDateTime lastLevelChange = LocalDateTime.MIN;

private Config config;

/**
Expand All @@ -52,6 +80,9 @@ public class ControllerHeatingElementImpl extends AbstractOpenemsComponent
@Reference
protected Sum sum;

@Reference(policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY, cardinality = ReferenceCardinality.OPTIONAL)
private volatile Timedata timedata = null;

public ControllerHeatingElementImpl() throws OpenemsNamedException {
super(//
OpenemsComponent.ChannelId.values(), //
Expand Down Expand Up @@ -118,6 +149,8 @@ public void run() throws OpenemsNamedException {
this.channel(ControllerHeatingElement.ChannelId.LEVEL1_TIME).setNextValue(phase1Time - phase2Time);
this.channel(ControllerHeatingElement.ChannelId.LEVEL2_TIME).setNextValue(phase2Time - phase3Time);
this.channel(ControllerHeatingElement.ChannelId.LEVEL3_TIME).setNextValue(phase3Time);

this.updateCumulatedActiveTime();
}

/**
Expand Down Expand Up @@ -195,7 +228,6 @@ protected void modeAutomatic() throws IllegalArgumentException, OpenemsNamedExce

// Apply Level
this.applyLevel(targetLevel);

}

/**
Expand All @@ -212,6 +244,7 @@ protected void modeAutomatic() throws IllegalArgumentException, OpenemsNamedExce
* <li>in {@link WorkMode#NONE}: always return 0
* </ul>
*
* @param config the component {@link Config}
* @return the minimum total phase time [s]
*/
private static long calculateMinimumTotalPhaseTime(Config config) {
Expand Down Expand Up @@ -276,6 +309,7 @@ private LocalTime calculateLatestForceHeatingStartTime() {
public void applyLevel(Level level) throws IllegalArgumentException, OpenemsNamedException {
// Update Channel
this.channel(ControllerHeatingElement.ChannelId.LEVEL).setNextValue(level);
this.currentLevel = level;

// Set phases accordingly
switch (level) {
Expand Down Expand Up @@ -330,9 +364,6 @@ private Level applyHysteresis(Level targetLevel) {
return this.currentLevel;
}

private Level currentLevel = Level.UNDEFINED;
private LocalDateTime lastLevelChange = LocalDateTime.MIN;

/**
* Gets the configured Power-per-Phase in [W].
*
Expand All @@ -345,9 +376,8 @@ protected int getPowerPerPhase() {
/**
* Helper function to switch an output if it was not switched before.
*
* @param value The boolean value which must set on the output
* channel address.
* @param outputChannelAddress The address of the channel.
* @param phase {@link Phase}
* @param value The boolean value which must set on the output channel address.
* @throws OpenemsNamedException on error.
* @throws IllegalArgumentException on error.
*/
Expand Down Expand Up @@ -380,4 +410,37 @@ private ChannelAddress getChannelAddressForPhase(Phase phase) throws OpenemsName
assert true; // can never happen
return null;
}

/**
* Update the total time of the level depending on the current level.
*/
private void updateCumulatedActiveTime() {
var level1Active = false;
var level2Active = false;
var level3Active = false;

switch (this.currentLevel) {
case LEVEL_0:
case UNDEFINED:
break;
case LEVEL_1:
level1Active = true;
break;
case LEVEL_2:
level2Active = true;
break;
case LEVEL_3:
level3Active = true;
break;
}

this.totalTimeLevel1.update(level1Active);
this.totalTimeLevel2.update(level2Active);
this.totalTimeLevel3.update(level3Active);
}

@Override
public Timedata getTimedata() {
return this.timedata;
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.time.LocalTime;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.edge.controller.io.heatingelement.enums.Phase;

/**
* PhaseDef represents one Phase of the Heating Element.
Expand All @@ -23,7 +24,7 @@ public class PhaseDef {
* keeps the total summed up Duration of the current day; it is updated on
* switchOff() and reset after midnight by getTotalDuration().
*/
private Duration duration = Duration.ZERO;
private Duration dailyDuration = Duration.ZERO;

/**
* Keeps the current day to detect changes in day.
Expand All @@ -43,8 +44,6 @@ public PhaseDef(ControllerHeatingElementImpl parent, Phase phase) {
/**
* Switch the output ON.
*
* @param outputChannelAddress address of the channel which must set to ON
*
* @throws OpenemsNamedException on error.
* @throws IllegalArgumentException on error.
*/
Expand All @@ -59,14 +58,12 @@ protected void switchOn() throws IllegalArgumentException, OpenemsNamedException
/**
* Switch the output OFF.
*
* @param outputChannelAddress address of the channel which must set to OFF.
*
* @throws OpenemsNamedException on error.
* @throws IllegalArgumentException on error.
*/
protected void switchOff() throws IllegalArgumentException, OpenemsNamedException {
if (this.lastSwitchOn != null) {
this.duration = this.getTotalDuration();
this.dailyDuration = this.getTotalDuration();
this.lastSwitchOn = null;
}

Expand All @@ -85,7 +82,7 @@ public Duration getTotalDuration() {
if (!this.currentDay.equals(today)) {
// Always reset Duration
this.currentDay = today;
this.duration = Duration.ZERO;
this.dailyDuration = Duration.ZERO;
if (this.lastSwitchOn != null) {
this.lastSwitchOn = LocalTime.MIN;
}
Expand All @@ -94,8 +91,8 @@ public Duration getTotalDuration() {
// Calculate and return the Duration
if (this.lastSwitchOn != null) {
var now = LocalTime.now(this.parent.componentManager.getClock());
return this.duration.plus(Duration.between(this.lastSwitchOn, now));
return this.dailyDuration.plus(Duration.between(this.lastSwitchOn, now));
}
return this.duration;
return this.dailyDuration;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.openems.edge.controller.io.heatingelement;
package io.openems.edge.controller.io.heatingelement.enums;

import io.openems.common.types.OptionsEnum;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.openems.edge.controller.io.heatingelement.enums;

public enum Mode {
MANUAL_ON, MANUAL_OFF, AUTOMATIC;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.openems.edge.controller.io.heatingelement.enums;

public enum Phase {
L1, L2, L3
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

package io.openems.edge.controller.io.heatingelement;
package io.openems.edge.controller.io.heatingelement.enums;

public enum WorkMode {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,26 @@
import io.openems.edge.common.test.AbstractComponentTest.TestCase;
import io.openems.edge.common.test.DummyComponentManager;
import io.openems.edge.common.test.TimeLeapClock;
import io.openems.edge.controller.io.heatingelement.enums.Level;
import io.openems.edge.controller.io.heatingelement.enums.Mode;
import io.openems.edge.controller.io.heatingelement.enums.WorkMode;
import io.openems.edge.controller.test.ControllerTest;
import io.openems.edge.io.test.DummyInputOutput;

public class HeatingElementTest {

private final static String CTRL_ID = "ctrl0";
private final static String IO_ID = "io0";
private static final String CTRL_ID = "ctrl0";
private static final String IO_ID = "io0";

private final static ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress("_sum", "GridActivePower");
private static final ChannelAddress SUM_GRID_ACTIVE_POWER = new ChannelAddress("_sum", "GridActivePower");

private final static ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput1");
private final static ChannelAddress IO_OUTPUT2 = new ChannelAddress(IO_ID, "InputOutput2");
private final static ChannelAddress IO_OUTPUT3 = new ChannelAddress(IO_ID, "InputOutput3");
private static final ChannelAddress IO_OUTPUT1 = new ChannelAddress(IO_ID, "InputOutput1");
private static final ChannelAddress IO_OUTPUT2 = new ChannelAddress(IO_ID, "InputOutput2");
private static final ChannelAddress IO_OUTPUT3 = new ChannelAddress(IO_ID, "InputOutput3");

private final static ChannelAddress CTRL_PHASE1TIME = new ChannelAddress(CTRL_ID, "Phase1Time");
private final static ChannelAddress CTRL_PHASE2TIME = new ChannelAddress(CTRL_ID, "Phase2Time");
private final static ChannelAddress CTRL_PHASE3TIME = new ChannelAddress(CTRL_ID, "Phase3Time");
private static final ChannelAddress CTRL_PHASE1TIME = new ChannelAddress(CTRL_ID, "Phase1Time");
private static final ChannelAddress CTRL_PHASE2TIME = new ChannelAddress(CTRL_ID, "Phase2Time");
private static final ChannelAddress CTRL_PHASE3TIME = new ChannelAddress(CTRL_ID, "Phase3Time");

@Test
public void test() throws Exception {
Expand All @@ -51,7 +54,7 @@ public void test() throws Exception {
.setMinimumSwitchingTime(60) //
.build()) //
.next(new TestCase() //
// Grid active power : 0, Excess power : 0,
// Grid active power : 0, Excess power : 0,
// from -> UNDEFINED --to--> LEVEL_0, no of relais = 0
.input(SUM_GRID_ACTIVE_POWER, 0) //
.output(IO_OUTPUT1, false) //
Expand Down
Loading