diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/.classpath b/net.solarnetwork.node.demandresponse.dresimplestrategy/.classpath new file mode 100644 index 000000000..2374a41aa --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/.gitignore b/net.solarnetwork.node.demandresponse.dresimplestrategy/.gitignore new file mode 100644 index 000000000..c3dca1b96 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/.gitignore @@ -0,0 +1,2 @@ +/build +/target diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/.project b/net.solarnetwork.node.demandresponse.dresimplestrategy/.project new file mode 100644 index 000000000..b68fcb300 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/.project @@ -0,0 +1,28 @@ + + + net.solarnetwork.node.demandresponse.dresimplestrategy + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/.settings/org.eclipse.jdt.core.prefs b/net.solarnetwork.node.demandresponse.dresimplestrategy/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..c537b6306 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/.settings/org.eclipse.pde.core.prefs b/net.solarnetwork.node.demandresponse.dresimplestrategy/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..e8ff8be0b --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +pluginProject.equinox=false +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/META-INF/MANIFEST.MF b/net.solarnetwork.node.demandresponse.dresimplestrategy/META-INF/MANIFEST.MF new file mode 100644 index 000000000..f88d5bcc1 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/META-INF/MANIFEST.MF @@ -0,0 +1,31 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Mimumum DR Engine +Bundle-SymbolicName: net.solarnetwork.node.demandresponse.dresimplestrategy +Bundle-Version: 1.0.0 +Bundle-Vendor: SolarNetwork +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Import-Package: build.eclipse.net.solarnetwork.node.demandresponse.dretargetcost, + net.solarnetwork.node;version="1.23.0", + net.solarnetwork.node.dao;version="1.8.0", + net.solarnetwork.node.demandresponse.dretargetcost, + net.solarnetwork.node.domain;version="1.11.0", + net.solarnetwork.node.job;version="1.13.4", + net.solarnetwork.node.reactor;version="1.3.0", + net.solarnetwork.node.reactor.support;version="1.3.0", + net.solarnetwork.node.settings;version="1.10.0", + net.solarnetwork.node.settings.support;version="1.8.0", + net.solarnetwork.node.support;version="1.14.0", + net.solarnetwork.node.test;version="1.3.0", + net.solarnetwork.node.util;version="1.7.2", + net.solarnetwork.util;version="1.28.0", + org.junit;version="[4.12.0,5.0.0)", + org.junit.runner;version="[4.12.0,5.0.0)", + org.quartz;version="[2.2.3,3.0.0)", + org.quartz.simpl;version="[2.2.3,3.0.0)", + org.slf4j;version="[1.7.24,2.0.0)", + org.springframework.beans;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.context;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.context.support;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.core;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.scheduling.quartz;version="[4.2.9.RELEASE,5.0.0)" diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/OSGI-INF/blueprint/module.xml b/net.solarnetwork.node.demandresponse.dresimplestrategy/OSGI-INF/blueprint/module.xml new file mode 100644 index 000000000..a4ec31e00 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/OSGI-INF/blueprint/module.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + net.solarnetwork.node.job.ManagedTriggerAndJobDetail + net.solarnetwork.node.job.ServiceProvider + net.solarnetwork.node.settings.SettingSpecifierProvider + + + + + + + + + + + + + + + + + + + + + + + + net.solarnetwork.node.DatumDataSource + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/build.properties b/net.solarnetwork.node.demandresponse.dresimplestrategy/build.properties new file mode 100644 index 000000000..668f1032f --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = build/eclipse/ +bin.includes = META-INF/,\ + . diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/src/net/solarnetwork/node/demandresponse/dresimplestrategy/DRESimpleStrategy.java b/net.solarnetwork.node.demandresponse.dresimplestrategy/src/net/solarnetwork/node/demandresponse/dresimplestrategy/DRESimpleStrategy.java new file mode 100644 index 000000000..36d62d3d7 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/src/net/solarnetwork/node/demandresponse/dresimplestrategy/DRESimpleStrategy.java @@ -0,0 +1,116 @@ +package net.solarnetwork.node.demandresponse.dresimplestrategy; + +import java.util.Collection; +import java.util.Date; +import java.util.Map; + +import net.solarnetwork.node.demandresponse.dretargetcost.DRSupportTools; +import net.solarnetwork.node.reactor.FeedbackInstructionHandler; +import net.solarnetwork.node.reactor.Instruction; +import net.solarnetwork.node.reactor.InstructionHandler; +import net.solarnetwork.node.reactor.support.BasicInstruction; + +/** + * Expirimental class looking into how a demand responce system my look like. + * Method names and API use will probably change in refactoring into a propper + * implementation. + * + * + * This class is very simple it turns off as many devices as it can and controls + * batterys via a mode textbox + * + * + * @author robert + * + */ +public class DRESimpleStrategy { + private DRESimpleStrategyDatumDataSource settings; + private Collection feedbackInstructionHandlers; + + // status variables for the datum source + private Integer numdrdevices = 0; + + public DRESimpleStrategyDatumDataSource getSettings() { + return settings; + } + + // configured in OSGI + public void setSettings(DRESimpleStrategyDatumDataSource settings) { + this.settings = settings; + + } + + protected void drupdate() { + numdrdevices = 0; + for (FeedbackInstructionHandler handler : feedbackInstructionHandlers) { + if (handler.handlesTopic(DRSupportTools.DRPARAMS_INSTRUCTION)) { + + BasicInstruction instr = new BasicInstruction(DRSupportTools.DRPARAMS_INSTRUCTION, new Date(), + Instruction.LOCAL_INSTRUCTION_ID, Instruction.LOCAL_INSTRUCTION_ID, null); + + // The devices want to know where the instruction came from for + // verification + instr.addParameter(settings.getUID(), ""); + + Map params = handler.processInstructionWithFeedback(instr).getResultParameters(); + if (DRSupportTools.isDRCapable(params)) { + numdrdevices++; + Integer watts = DRSupportTools.readWatts(params); + Integer minwatts = DRSupportTools.readMinWatts(params); + Integer maxwatts = DRSupportTools.readMaxWatts(params); + if (DRSupportTools.isChargeable(params)) { + String mode = settings.getBatteryMode(); + if (mode.equalsIgnoreCase("DISCHARGE")) { + // Tell the battery to discharge as much as it can + sendDRtoBattery(true, handler, maxwatts); + } else if (mode.equalsIgnoreCase("CHARGE")) { + // Tell the battery to charge as much as it can. For + // that we need the max charging watts param + sendDRtoBattery(false, handler, DRSupportTools.readMaxChargingWatts(params)); + } else { + // Assume idle tell battery to not discharge and set + // draw to 0 watts + // it should be the case for batterys to have + // minwatts at 0 but check anyways + if (minwatts.equals(0)) { + sendDRtoBattery(false, handler, 0); + } + } + } else if (watts > minwatts) { + Integer shedamount = watts - minwatts; + // in this strategy we just lower power of all devices + // as much as we can + instr = new BasicInstruction(InstructionHandler.TOPIC_SHED_LOAD, new Date(), + Instruction.LOCAL_INSTRUCTION_ID, Instruction.LOCAL_INSTRUCTION_ID, null); + + instr.addParameter(settings.getUID(), shedamount.toString()); + handler.processInstruction(instr); + } + } + } + } + + } + + private void sendDRtoBattery(Boolean discharge, FeedbackInstructionHandler handler, Integer wattValue) { + BasicInstruction instr = new BasicInstruction(InstructionHandler.TOPIC_SET_CONTROL_PARAMETER, new Date(), + Instruction.LOCAL_INSTRUCTION_ID, Instruction.LOCAL_INSTRUCTION_ID, null); + instr.addParameter(settings.getUID(), ""); + instr.addParameter(DRSupportTools.WATTS_PARAM, wattValue.toString()); + instr.addParameter(DRSupportTools.DISCHARGING_PARAM, discharge.toString()); + handler.processInstruction(instr); + } + + public Collection getFeedbackInstructionHandlers() { + return feedbackInstructionHandlers; + } + + // configured in OSGI + public void setFeedbackInstructionHandlers(Collection feedbackInstructionHandlers) { + this.feedbackInstructionHandlers = feedbackInstructionHandlers; + } + + public Integer getNumdrdevices() { + return numdrdevices; + } +} diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/src/net/solarnetwork/node/demandresponse/dresimplestrategy/DRESimpleStrategyDatumDataSource.java b/net.solarnetwork.node.demandresponse.dresimplestrategy/src/net/solarnetwork/node/demandresponse/dresimplestrategy/DRESimpleStrategyDatumDataSource.java new file mode 100644 index 000000000..223f705a6 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/src/net/solarnetwork/node/demandresponse/dresimplestrategy/DRESimpleStrategyDatumDataSource.java @@ -0,0 +1,86 @@ +package net.solarnetwork.node.demandresponse.dresimplestrategy; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import net.solarnetwork.node.DatumDataSource; +import net.solarnetwork.node.domain.GeneralNodeEnergyStorageDatum; +import net.solarnetwork.node.settings.SettingSpecifier; +import net.solarnetwork.node.settings.SettingSpecifierProvider; +import net.solarnetwork.node.settings.support.BasicRadioGroupSettingSpecifier; +import net.solarnetwork.node.support.DatumDataSourceSupport; + +public class DRESimpleStrategyDatumDataSource extends DatumDataSourceSupport + implements SettingSpecifierProvider, DatumDataSource { + + private DRESimpleStrategy linkedInstance; + private String batteryMode = "Idle"; + + @Override + public String getSettingUID() { + return "net.solarnetwork.node.demandresponse.dresimplestrategy"; + } + + @Override + public String getDisplayName() { + return "Minimum DR Engine"; + } + + @Override + public List getSettingSpecifiers() { + DRESimpleStrategyDatumDataSource defaults = new DRESimpleStrategyDatumDataSource(); + List results = getIdentifiableSettingSpecifiers(); + // results.add(new BasicTextFieldSettingSpecifier("batteryMode", + // defaults.batteryMode)); + BasicRadioGroupSettingSpecifier batteryModesSettings = new BasicRadioGroupSettingSpecifier("batteryMode", + batteryMode); + Map batteryModesMap = new LinkedHashMap(3); + batteryModesMap.put("Idle", "Idle"); + batteryModesMap.put("Charge", "Charge"); + batteryModesMap.put("Discharge", "Discharge"); + batteryModesSettings.setValueTitles(batteryModesMap); + results.add(batteryModesSettings); + return results; + } + + public DRESimpleStrategy getLinkedInstance() { + return linkedInstance; + } + + public void setLinkedInstance(DRESimpleStrategy linkedInstance) { + this.linkedInstance = linkedInstance; + linkedInstance.setSettings(this); + } + + @Override + public Class getDatumType() { + return GeneralNodeEnergyStorageDatum.class; + } + + @Override + public GeneralNodeEnergyStorageDatum readCurrentDatum() { + try { + getLinkedInstance().drupdate(); + } catch (RuntimeException e) { + // exceptions don't print inside this method. For debugging purposes + // I print the stacktrace if there is an exception + e.printStackTrace(); + } + + // the datum will contain num devices as well as watts cost? + GeneralNodeEnergyStorageDatum datum = new GeneralNodeEnergyStorageDatum(); + datum.putInstantaneousSampleValue("Num Devices", getLinkedInstance().getNumdrdevices()); + datum.setSourceId(getUID()); + return datum; + } + + public String getBatteryMode() { + return batteryMode; + } + + public void setBatteryMode(String batteryMode) { + this.batteryMode = batteryMode; + } + +} diff --git a/net.solarnetwork.node.demandresponse.dresimplestrategy/src/net/solarnetwork/node/demandresponse/dresimplestrategy/DRESimpleStrategyDatumDataSource.properties b/net.solarnetwork.node.demandresponse.dresimplestrategy/src/net/solarnetwork/node/demandresponse/dresimplestrategy/DRESimpleStrategyDatumDataSource.properties new file mode 100644 index 000000000..270b00e39 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dresimplestrategy/src/net/solarnetwork/node/demandresponse/dresimplestrategy/DRESimpleStrategyDatumDataSource.properties @@ -0,0 +1,13 @@ +title= Minimum DR Engine +uid.key = UID +groupUID.key = Service Group +groupUID.desc = An optional group to include this service in. +triggerCronExpression.key = Schedule +triggerCronExpression.desc = A \ + \ + cron expression representing the schedule to sample data at. +batteryMode.key = Battery Mode +batteryMode.desc = Possible values Charge, Discharge and Idle. +Idle.desc = Set batteries to Idle +Charge.desc = Tell batteries to start charging +Discharge.desc = Tell batteries to start discharging diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/.classpath b/net.solarnetwork.node.demandresponse.dretargetcost/.classpath new file mode 100644 index 000000000..2374a41aa --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/.gitignore b/net.solarnetwork.node.demandresponse.dretargetcost/.gitignore new file mode 100644 index 000000000..c3dca1b96 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/.gitignore @@ -0,0 +1,2 @@ +/build +/target diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/.project b/net.solarnetwork.node.demandresponse.dretargetcost/.project new file mode 100644 index 000000000..e3c32c3e8 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/.project @@ -0,0 +1,28 @@ + + + net.solarnetwork.node.demandresponse.dretargetcost + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/.settings/org.eclipse.jdt.core.prefs b/net.solarnetwork.node.demandresponse.dretargetcost/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..c537b6306 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/.settings/org.eclipse.pde.core.prefs b/net.solarnetwork.node.demandresponse.dretargetcost/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..e8ff8be0b --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +pluginProject.equinox=false +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/META-INF/MANIFEST.MF b/net.solarnetwork.node.demandresponse.dretargetcost/META-INF/MANIFEST.MF new file mode 100644 index 000000000..5c77d2ccd --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/META-INF/MANIFEST.MF @@ -0,0 +1,31 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Mock Anouncer +Bundle-SymbolicName: net.solarnetwork.node.demandresponse.dretargetcost +Bundle-Version: 1.0.0 +Bundle-Vendor: SolarNetwork +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Import-Package: net.solarnetwork.node;version="1.23.0", + net.solarnetwork.node.dao;version="1.8.0", + net.solarnetwork.node.domain;version="1.11.0", + net.solarnetwork.node.job;version="1.13.4", + net.solarnetwork.node.reactor;version="1.3.0", + net.solarnetwork.node.reactor.support;version="1.3.0", + net.solarnetwork.node.settings;version="1.10.0", + net.solarnetwork.node.settings.support;version="1.8.0", + net.solarnetwork.node.support;version="1.14.0", + net.solarnetwork.node.test;version="1.3.0", + net.solarnetwork.node.util;version="1.7.2", + net.solarnetwork.util;version="1.28.0", + org.junit;version="[4.12.0,5.0.0)", + org.junit.runner;version="[4.12.0,5.0.0)", + org.quartz;version="[2.2.3,3.0.0)", + org.quartz.simpl;version="[2.2.3,3.0.0)", + org.slf4j;version="[1.7.24,2.0.0)", + org.springframework.beans;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.context;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.context.support;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.core;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.scheduling.quartz;version="[4.2.9.RELEASE,5.0.0)" +Export-Package: build.eclipse.net.solarnetwork.node.demandresponse.dretargetcost, + net.solarnetwork.node.demandresponse.dretargetcost diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/OSGI-INF/blueprint/module.xml b/net.solarnetwork.node.demandresponse.dretargetcost/OSGI-INF/blueprint/module.xml new file mode 100644 index 000000000..8d227f735 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/OSGI-INF/blueprint/module.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + net.solarnetwork.node.job.ManagedTriggerAndJobDetail + net.solarnetwork.node.job.ServiceProvider + net.solarnetwork.node.settings.SettingSpecifierProvider + + + + + + + + + + + + + + + + + + + + + + + + net.solarnetwork.node.DatumDataSource + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/build.properties b/net.solarnetwork.node.demandresponse.dretargetcost/build.properties new file mode 100644 index 000000000..668f1032f --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = build/eclipse/ +bin.includes = META-INF/,\ + . diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCost$1.class b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCost$1.class new file mode 100644 index 000000000..9ae37b1a2 Binary files /dev/null and b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCost$1.class differ diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCost.class b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCost.class new file mode 100644 index 000000000..c79c18834 Binary files /dev/null and b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCost.class differ diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.class b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.class new file mode 100644 index 000000000..ea6332ca6 Binary files /dev/null and b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.class differ diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.properties b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.properties new file mode 100644 index 000000000..2a14192d5 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.properties @@ -0,0 +1,26 @@ +title= Target Cost DR Engine +sourceId.key = Source ID +uid.key = UID +groupUID.key = Service Group +groupUID.desc = An optional group to include this service in. +triggerCronExpression.key = Schedule +triggerCronExpression.desc = A \ + \ + cron expression representing the schedule to sample data at. + +poweredDevices.propertyFilters['UID'].key = Generation Component +poweredDevices.propertyFilters['UID'].desc = The Service Name of the generation data source \ + to use for collecting current generation levels from. + +poweredDevices.propertyFilters['groupUID'].key = Generation Group +poweredDevices.propertyFilters['groupUID'].desc = The Service Group of a collection of \ + generation data sources to use for collecting current generation levels from. If this is configured, \ + this value and the configured Generation Component will be used to determine which \ + data sources to use. To use all sources within a group, leave Generation Component empty. + +energyCost.key = Energy Cost +energyCost.desc = The current price of energy + +drtargetCost.key = Target Energy Cost +drtargetCost.desc = The target price to power the devices. The system tries to get as close as possible \ +without going over \ No newline at end of file diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRSupportTools$NotChargeableDeviceException.class b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRSupportTools$NotChargeableDeviceException.class new file mode 100644 index 000000000..869f36e68 Binary files /dev/null and b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRSupportTools$NotChargeableDeviceException.class differ diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRSupportTools.class b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRSupportTools.class new file mode 100644 index 000000000..ac4ae6024 Binary files /dev/null and b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/DRSupportTools.class differ diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/MinimumDRStrategy.class b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/MinimumDRStrategy.class new file mode 100644 index 000000000..1eec5ef11 Binary files /dev/null and b/net.solarnetwork.node.demandresponse.dretargetcost/build/eclipse/net/solarnetwork/node/demandresponse/dretargetcost/MinimumDRStrategy.class differ diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCost.java b/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCost.java new file mode 100644 index 000000000..60c3ab0bb --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCost.java @@ -0,0 +1,254 @@ +package net.solarnetwork.node.demandresponse.dretargetcost; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.solarnetwork.node.reactor.FeedbackInstructionHandler; +import net.solarnetwork.node.reactor.Instruction; +import net.solarnetwork.node.reactor.InstructionHandler; +import net.solarnetwork.node.reactor.support.BasicInstruction; + +/** + * Expirimental class looking into how a demand responce system my look like. + * Method names and API use will probably change in refactoring into a propper + * implementation. + * + * + * @author robert + * + */ +public class DRETargetCost { + private DRETargetCostDatumDataSource settings; + private Collection feedbackInstructionHandlers; + + // status variables for the datum source + private Integer numdrdevices = 0; + + public DRETargetCostDatumDataSource getSettings() { + return settings; + } + + // configured in OSGI + public void setSettings(DRETargetCostDatumDataSource settings) { + this.settings = settings; + + } + + protected void drupdate() { + + List drdevices = new ArrayList(); + + // the reason the mapping should be in String String is because perhapes + // in the future it could be JSON + Map> instructionMap = new HashMap>(); + for (FeedbackInstructionHandler handler : feedbackInstructionHandlers) { + + if (handler.handlesTopic(DRSupportTools.DRPARAMS_INSTRUCTION)) { + + BasicInstruction instr = new BasicInstruction(DRSupportTools.DRPARAMS_INSTRUCTION, new Date(), + Instruction.LOCAL_INSTRUCTION_ID, Instruction.LOCAL_INSTRUCTION_ID, null); + + // The devices want to know where the instruction came from for + // verification + instr.addParameter(settings.getUID(), ""); + + Map test = handler.processInstructionWithFeedback(instr).getResultParameters(); + if (test != null) { + + if (DRSupportTools.isDRCapable(test) && !DRSupportTools.isChargeable(test)) { + + drdevices.add(handler); + instructionMap.put(handler, test); + + } + } + + } + + } + + // This value is used by the datumdatasource + numdrdevices = drdevices.size(); + + // energyConsumption is energy used when not discharging + Integer energyConsumption = 0; + + // energyProduction is energy used when discharging + Integer energyProduction = 0; + for (FeedbackInstructionHandler d : drdevices) { + Map params = instructionMap.get(d); + + Integer wattValue = DRSupportTools.readWatts(params); + + energyConsumption += wattValue; + } + + Double newPrice = settings.getEnergyCost().doubleValue(); + + // need an object array cause I want to relate double with drdevice + // the reason im not using mapping is multiple drdevices could have the + // same double and I need to be able to sort the first column + Object[][] costArray = new Object[drdevices.size()][2]; + for (int i = 0; i < drdevices.size(); i++) { + FeedbackInstructionHandler d = drdevices.get(i); + Map params = instructionMap.get(d); + + Integer wattValue = DRSupportTools.readWatts(params); + Integer costValue = DRSupportTools.readEnergyDepreciationCost(params); + + costArray[i][0] = (costValue + newPrice) * wattValue; + costArray[i][1] = d; + } + + // simple sum to find the cost of running all these devices + Double totalCost = 0.0; + for (int i = 0; i < costArray.length; i++) { + totalCost += (Double) costArray[i][0]; + } + + // sorts the first columb which are doubles and keeps the relationship + // between cost and DRDevice + Arrays.sort(costArray, new Comparator() { + + @Override + public int compare(Object[] o1, Object[] o2) { + Double d1 = (Double) o1[0]; + Double d2 = (Double) o2[0]; + return d1.compareTo(d2); + } + + }); + // if this is true we need to reduce demand to get costs down + if (totalCost > settings.getDrtargetCost()) { + + // going from largest cost to smallest cost (this is just a design + // decision I made) + for (int i = drdevices.size() - 1; i >= 0; i--) { + FeedbackInstructionHandler d = (FeedbackInstructionHandler) costArray[i][1]; + Map params = instructionMap.get(d); + + Integer wattValue = DRSupportTools.readWatts(params); + Integer minValue = DRSupportTools.readMinWatts(params); + Integer energyCost = DRSupportTools.readEnergyDepreciationCost(params); + + // check if we can reduce consumption + if (wattValue > minValue) { + + // if we are here we need to reduce + Double reduceAmount = totalCost - settings.getDrtargetCost(); + Double energyReduction = reduceAmount / (energyCost + settings.getEnergyCost()); + Double appliedenergyReduction = (wattValue - energyReduction > minValue) ? energyReduction + : wattValue - minValue; + + // Im annoyed by this instruction because it is only reduce + // and not gain + sendShedInstruction(d, appliedenergyReduction); + + // we were able to increase to match demand no need for more + // devices to have DR + if (energyReduction.equals(appliedenergyReduction)) { + break; + } else { + // update the cost for the next devices to calcuate with + totalCost -= appliedenergyReduction * (energyCost + settings.getEnergyCost()); + } + + } + } + + // if true we need to increase demand + } else if (totalCost < settings.getDrtargetCost()) { + // this time we start with the cheapest devices (my reasoning is + // that these devices most likely have room to power on more) so we + // can acheive nessisary requirements with little number of + // instructions + for (int i = 0; i < drdevices.size(); i++) { + FeedbackInstructionHandler d = (FeedbackInstructionHandler) costArray[i][1]; + Map params = instructionMap.get(d); + + Integer wattValue = DRSupportTools.readWatts(params); + Integer maxValue = DRSupportTools.readMaxWatts(params); + Integer energyCost = DRSupportTools.readEnergyDepreciationCost(params); + + if (wattValue < maxValue) { + + // if we are here it is okay to increase usage + Double increaseAmount = settings.getDrtargetCost() - totalCost; + + // energy increase is the amount of energy we want to + // increase by + Double energyIncrease = increaseAmount / (energyCost + settings.getEnergyCost()); + energyIncrease += wattValue; + + // appliedenergyIncrease is the value we are going to send + // as the maxValue can limit us to how much we can increase + // by + Double appliedenergyIncrease = Math.min(energyIncrease, maxValue); + Double energydelta = appliedenergyIncrease - wattValue; + + setWattageInstruction(d, appliedenergyIncrease); + + // we were able to increase to match demand no need for more + // devices to have DR + if (energyIncrease.equals(appliedenergyIncrease)) { + break; + } else { + // update the cost for the next devices to calcuate with + totalCost += energydelta * (energyCost + settings.getEnergyCost()); + } + + } + + } + } + + } + + // Instruction to set the wattage parameter on the device it uses the + // TOPIC_SET_CONTROL_PARAMETER insrtuction + private void setWattageInstruction(InstructionHandler handler, Double energyLevel) { + BasicInstruction instr = new BasicInstruction(InstructionHandler.TOPIC_SET_CONTROL_PARAMETER, new Date(), + Instruction.LOCAL_INSTRUCTION_ID, Instruction.LOCAL_INSTRUCTION_ID, null); + instr.addParameter("watts", energyLevel.toString()); + + // add as a param the source ID so devices can verify + instr.addParameter(settings.getUID(), ""); + + handler.processInstruction(instr); + } + + // Instruction to reduce wattage parameter on the device. The only reason Im + // using this instead of setWattageInstruction is because this instruction + // already exists + private void sendShedInstruction(InstructionHandler handler, Double shedamount) { + BasicInstruction instr = new BasicInstruction(InstructionHandler.TOPIC_SHED_LOAD, new Date(), + Instruction.LOCAL_INSTRUCTION_ID, Instruction.LOCAL_INSTRUCTION_ID, null); + + // the convention I saw used from other classes is that the value of the + // shedload is from the UID for verification + instr.addParameter(settings.getUID(), shedamount.toString()); + handler.processInstruction(instr); + } + + public Collection getFeedbackInstructionHandlers() { + return feedbackInstructionHandlers; + } + + // configured in OSGI we automatically get a collection of + // FeedbackInstructionHandlers as they come threw + public void setFeedbackInstructionHandlers(Collection feedbackInstructionHandlers) { + this.feedbackInstructionHandlers = feedbackInstructionHandlers; + } + + // This is for the DatumDataSource + public Integer getNumdrdevices() { + return numdrdevices; + } + +} diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.java b/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.java new file mode 100644 index 000000000..156af39eb --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.java @@ -0,0 +1,96 @@ +package net.solarnetwork.node.demandresponse.dretargetcost; + +import java.util.List; + +import net.solarnetwork.node.DatumDataSource; +import net.solarnetwork.node.domain.GeneralNodeEnergyStorageDatum; +import net.solarnetwork.node.settings.SettingSpecifier; +import net.solarnetwork.node.settings.SettingSpecifierProvider; +import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier; +import net.solarnetwork.node.support.DatumDataSourceSupport; + +public class DRETargetCostDatumDataSource extends DatumDataSourceSupport + implements SettingSpecifierProvider, DatumDataSource { + private Integer energyCost = 10; + + // we will use this value as a means to calebrate the DR a lower value means + // more likly to turn things off. The goal is to get the cost of powering + // devices as close to this value without going over. + private Integer drtargetCost = 10; + + private DRETargetCost drengine; + + @Override + public String getSettingUID() { + return "net.solarnetwork.node.demandresponse.dretargetcost"; + } + + @Override + public String getDisplayName() { + return "DR Engine"; + } + + @Override + public List getSettingSpecifiers() { + List results = getIdentifiableSettingSpecifiers(); + DRETargetCostDatumDataSource defaults = new DRETargetCostDatumDataSource(); + + // cost per watt hour for grid energy + results.add(new BasicTextFieldSettingSpecifier("energyCost", defaults.energyCost.toString())); + + // the target cost per hour. This strategy tries to preform a demand + // response to get closest without going over + results.add(new BasicTextFieldSettingSpecifier("drtargetCost", defaults.drtargetCost.toString())); + + return results; + } + + public Integer getEnergyCost() { + return energyCost; + } + + public void setEnergyCost(Integer energyCost) { + this.energyCost = energyCost; + + } + + public Integer getDrtargetCost() { + return drtargetCost; + } + + public void setDrtargetCost(Integer drtargetCost) { + this.drtargetCost = drtargetCost; + + } + + public DRETargetCost getDRInstance() { + return drengine; + } + + public void setDRInstance(DRETargetCost linkedInstance) { + this.drengine = linkedInstance; + linkedInstance.setSettings(this); + } + + @Override + public Class getDatumType() { + return GeneralNodeEnergyStorageDatum.class; + } + + @Override + public GeneralNodeEnergyStorageDatum readCurrentDatum() { + try { + drengine.drupdate(); + } catch (RuntimeException e) { + // the try catch is only for debugging I have noticed that + e.printStackTrace(); + } + + // the datum will contain num devices as well as watts cost? + GeneralNodeEnergyStorageDatum datum = new GeneralNodeEnergyStorageDatum(); + datum.putInstantaneousSampleValue("Num Devices", drengine.getNumdrdevices()); + datum.setSourceId(getUID()); + return datum; + } + +} diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.properties b/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.properties new file mode 100644 index 000000000..2a14192d5 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRETargetCostDatumDataSource.properties @@ -0,0 +1,26 @@ +title= Target Cost DR Engine +sourceId.key = Source ID +uid.key = UID +groupUID.key = Service Group +groupUID.desc = An optional group to include this service in. +triggerCronExpression.key = Schedule +triggerCronExpression.desc = A \ + \ + cron expression representing the schedule to sample data at. + +poweredDevices.propertyFilters['UID'].key = Generation Component +poweredDevices.propertyFilters['UID'].desc = The Service Name of the generation data source \ + to use for collecting current generation levels from. + +poweredDevices.propertyFilters['groupUID'].key = Generation Group +poweredDevices.propertyFilters['groupUID'].desc = The Service Group of a collection of \ + generation data sources to use for collecting current generation levels from. If this is configured, \ + this value and the configured Generation Component will be used to determine which \ + data sources to use. To use all sources within a group, leave Generation Component empty. + +energyCost.key = Energy Cost +energyCost.desc = The current price of energy + +drtargetCost.key = Target Energy Cost +drtargetCost.desc = The target price to power the devices. The system tries to get as close as possible \ +without going over \ No newline at end of file diff --git a/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRSupportTools.java b/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRSupportTools.java new file mode 100644 index 000000000..0305c6999 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.dretargetcost/src/net/solarnetwork/node/demandresponse/dretargetcost/DRSupportTools.java @@ -0,0 +1,320 @@ +package net.solarnetwork.node.demandresponse.dretargetcost; + +import java.util.Map; + +/** + * Helper class to make use of demand response. It is expected that one uses the + * constants and methods provided in this class to help you with demand response + * implementation. + * + * Please have a good read of the documentation in this class before using + * demand response. For examples of classes using this form of demand response + * see the following packages + * {@link net.solarnetwork.node.demandresponse.mockbattery} + * {@link net.solarnetwork.node.demandresponse.dretargetcost} + * + * There are two main concepts for demand response the DR device and the DR + * engine. The device is meant to be physical hardware while the engine is an + * algorithm providing instructions to the device. + * + * DR Instructions use the pre-existing Instructions interface. Demand response + * requires two way communication meaning DR Devices must implement the + * {@link net.solarnetwork.node.reactor.FeedbackInstructionHandler} interface + * and must handle the {@value #DRPARAMS_INSTRUCTION} instruction. + * + * There is no set standard for how a DR Engine gets a hold of DR Devices the + * strategy I have been using is to configure OSGI a reference list of + * FeedbackInstructionHandlers. + * + * A DR Device should only accept demand response instructions from one DREngine + * this is done by checking that the source id of the DR Engine is present in + * every demand response instruction parameters. The value of the parameter does + * not have to be anything (except for InstructionHandler.TOPIC_SHED_LOAD) it + * just must be non null. By convention I have been using empty string. + * + * The procedure for demand response is as follows. The DR Engine sends to its + * devices a DRPARAMS_INSTRUCTION those devices respond by sending back a map of + * parameters. The map is of datatype due to implementation of + * FeedbackInstructionHandler. However the parameters should all come as + * . The following parameters are expected in the responding map + * in order for a device to claim it supports DRPARAMS_INSTRUCTION. + * + * WATTS_PARAM = The current power in watts the device is operating at. This + * value should always be positive. + * + * MAXWATTS_PARAM = The maximum amount of power the device can run at. For + * chargeable devices this parameter is used for the maximum discharge rate. + * + * MINWATTS_PARAM = The minimum amount of power the device can run at. This + * parameter does not represent a physical constraint of the device. Instead + * just tells the DR Engine that this device is too important to be powered + * below this specific level. + * + * ENERGY_DEPRECIATION = The cost of running this device per watt hour. This + * cost can reflect a resource consumption or simply deprecation from use. + * + * DRREADY_PARAM = setting this parameter to "true" tells the DREngine that it + * can expect all of these above parameters in this map. Set it to "false" when + * the device is not ready to handle demand response at that time. + * + * CHARGEABLE_PARAM = Set this to "true" if your device support demand response + * of charging and discharging other set to "false" + * + * If CHARGEABLE_PARAM was set to true the next parameter is required otherwise + * this one can be ignored + * + * DISCHARGING_PARAM = Set to "true" if the device is discharging otherwise + * "false". Not being powered is seen as not discharging. + * + * MAXCHARGEWATTS_PARAM = Should only be present when device is chargeable the + * value of this parameter is the maximum number of watt hours the chargeable + * device can charge at. This is different to MAXWATTS_PARAM which is the + * maximum discharge rate. + * + * CHARGE_PERCENTAGE_PARAM = Should only be present when device is chargeable + * the value of this parameter should be a number between 0-100 representing the + * percentage of remaining capacity. + * + * MAXCHARGE_PARAM = Should only be present when device is chargeable. The + * number of watt hours of charge the device will have when fully charged. + * + * NOTE do not assume that any of these parameters are constant eg + * MAXWATTS_PARAM could change depending on environmental factors. You should + * query for these parameters every time you want to calculate a demand + * response. + * + * + * To send a demand response you have the choice of sending + * InstructionHandler.TOPIC_SHED_LOAD or + * InstructionHandler.TOPIC_SET_CONTROL_PARAMETER. + * + * When sending a TOPIC_SHED_LOAD instruction you map the DR Engine's sourceID + * to the shedamount as per the convention already in place by this instruction. + * The DR Device should reduce its consumption by the shedamount so long as it + * does not conflict with its minimum wattage. It is up to your implemention on + * how to handle this instruction when the parameters are invalid. You may + * choose to make assumptions and error correct or decline the instruction in + * your instruction status. + * + * When sending a TOPIC_SET_CONTROL_PARAMETER instruction remember to include + * the source ID of the DR Engine in the parameters. The parameters that are + * allowed to be changed with this instruction are WATTS_PARAM and if + * CHARGEABLE_PARAM was true then DISCHARGING_PARAM can be configured in this + * instruction. You are allow to configure multiple parameters in a single + * instruction. map the parameter you want changed to the value you want set. + * + * @author robert + * + */ +public class DRSupportTools { + + // The name of the instruction a DREngine sends to a DRDevice. The DRDevice + // should respond with a map of parameters + public static final String DRPARAMS_INSTRUCTION = "getDRDeviceInstance"; + + // Parameters that must be in response to a DRPARAMS_INSTRUCTION + public static final String WATTS_PARAM = "watts"; + public static final String MAXWATTS_PARAM = "maxwatts"; + public static final String MINWATTS_PARAM = "minwatts"; + public static final String ENERGY_DEPRECIATION = "energycost"; + public static final String DRREADY_PARAM = "drready"; + public static final String CHARGEABLE_PARAM = "chargeable"; + public static final String SOURCEID_PARAM = "sourceID"; + + // These parameters are required if CHARGEABLE_PARAM is true + public static final String DISCHARGING_PARAM = "isDischarging"; + public static final String MAXCHARGEWATTS_PARAM = "chargePower"; + public static final String CHARGE_PERCENTAGE_PARAM = "chargePercent"; + public static final String MAXCHARGE_PARAM = "maxcharge"; + + public static String readSourceID(Map params) { + try { + return (String) params.get(SOURCEID_PARAM); + } catch (ClassCastException e) { + return null; + } + + } + + /** + * Exception for when trying to access a parameter for chargeable device + * when CHARGEABLE_PARAM is false + * + * @author robert + * + */ + public static class NotChargeableDeviceException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + } + + /** + * Reads the wattage reading from the parameters. If there is no wattage + * parameter the method returns null. + * + * @param params + * The parameters of the device + * @return The wattage reading of the device + */ + public static Integer readWatts(Map params) { + return readValue(WATTS_PARAM, params); + } + + /** + * Reads the max watts reading from the parameters. If no wattage reading is + * present the method returns null. + * + * A max wattage reading is the maximum amount of watts the device can + * operate on. + * + * @param params + * The parameters of the device + * @return The wattage reading of the device + */ + public static Integer readMaxWatts(Map params) { + return readValue(MAXWATTS_PARAM, params); + } + + /** + * Reads the maximum watts a chargeable device can charge at. + * + * @param params + * The parameters of the device + * @return + */ + public static Integer readMaxChargingWatts(Map params) { + if (!isChargeable(params)) { + throw new NotChargeableDeviceException(); + } + return readValue(MAXCHARGEWATTS_PARAM, params); + } + + /** + * Reads a number from 0-100 representing the charge percentage of the + * chargeable device. Returns null if parameter is missing. + * + * throws NotChargeableDeviceException if method is called when device is + * not a chargeable device + * + * throws NumberFormatException if the read value is not between (inclusive) + * 0 and 100 + * + * @param params + * The parameters of the device + * @return + */ + public static Integer readChargePercent(Map params) { + if (!isChargeable(params)) { + throw new NotChargeableDeviceException(); + } + Integer value = readValue(CHARGE_PERCENTAGE_PARAM, params); + if (value != null && value < 0 || value > 100) { + throw new NumberFormatException(); + } + return readValue(CHARGE_PERCENTAGE_PARAM, params); + } + + /** + * Reads the number of watt hours this chargeable device has at maximum + * charge. + * + * @param params + * The parameters of the device + * @return + */ + public static Integer readMaxCharge(Map params) { + if (!isChargeable(params)) { + throw new NotChargeableDeviceException(); + } + return readValue(MAXCHARGE_PARAM, params); + } + + /** + * Reads the min watts reading from the paramters. If no wattage reading is + * present the method returns null. + * + * A min wattage reading is the minimum amount of the watts the device can + * operate on. + * + * @param params + * The parameters of the device + * @return + */ + public static Integer readMinWatts(Map params) { + return readValue(MINWATTS_PARAM, params); + } + + /** + * Reads the energy cost from the parameters. If no energy cost reading is + * present the method returns null. + * + * The energy cost is the price per watt to operate the device. + * + * @param params + * The parameters of the device + * @return + */ + public static Integer readEnergyDepreciationCost(Map params) { + return readValue(ENERGY_DEPRECIATION, params); + } + + /** + * Checks whether the parameters specify whether the device can handle + * demand size response. + * + * @param params + * The parameters of the device + * @return + */ + public static boolean isDRCapable(Map params) { + return readBoolean(DRREADY_PARAM, params); + } + + /** + * Checks the parameters to see if the type of device supports charging for + * example a battery + * + * @param params + * The parameters of the device + * @return + */ + public static boolean isChargeable(Map params) { + return readBoolean(CHARGEABLE_PARAM, params); + } + + /** + * Checks the parameters to see if the device is discharging. Should only be + * called after verifying the device is chargeable + * + * + * @param params + * The parameters of the device + * @return + */ + public static boolean isDischarging(Map params) { + if (!isChargeable(params)) { + throw new NotChargeableDeviceException(); + } + return readBoolean(DISCHARGING_PARAM, params); + } + + // TODO fix behaviour + private static boolean readBoolean(String paramname, Map params) { + // cast to string then check for boolean + // just realised this does not check for nulls or non strings + return Boolean.parseBoolean((String) params.get(paramname)); + } + + private static Integer readValue(String paramname, Map params) { + // set variable to null to avoid uninitalised warnings + Integer result = null; + try { + result = (int) Double.parseDouble((String) params.get(paramname)); + } finally { + // Do nothing, in the case there was an exception the returned value + // will be null + } + return result; + } +} diff --git a/net.solarnetwork.node.demandresponse.mockbattery/.classpath b/net.solarnetwork.node.demandresponse.mockbattery/.classpath new file mode 100644 index 000000000..2374a41aa --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/net.solarnetwork.node.demandresponse.mockbattery/.gitignore b/net.solarnetwork.node.demandresponse.mockbattery/.gitignore new file mode 100644 index 000000000..c3dca1b96 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/.gitignore @@ -0,0 +1,2 @@ +/build +/target diff --git a/net.solarnetwork.node.demandresponse.mockbattery/.project b/net.solarnetwork.node.demandresponse.mockbattery/.project new file mode 100644 index 000000000..51d8740cc --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/.project @@ -0,0 +1,28 @@ + + + net.solarnetwork.node.demandresponse.mockbattery + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/net.solarnetwork.node.demandresponse.mockbattery/.settings/org.eclipse.jdt.core.prefs b/net.solarnetwork.node.demandresponse.mockbattery/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..c537b6306 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/net.solarnetwork.node.demandresponse.mockbattery/.settings/org.eclipse.pde.core.prefs b/net.solarnetwork.node.demandresponse.mockbattery/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..e8ff8be0b --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +pluginProject.equinox=false +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/net.solarnetwork.node.demandresponse.mockbattery/META-INF/MANIFEST.MF b/net.solarnetwork.node.demandresponse.mockbattery/META-INF/MANIFEST.MF new file mode 100644 index 000000000..f42f1dce0 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/META-INF/MANIFEST.MF @@ -0,0 +1,29 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: DRBattery +Bundle-SymbolicName: net.solarnetwork.node.demandresponse.mockbattery +Bundle-Version: 1.0.0 +Bundle-Vendor: SolarNetwork +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Import-Package: build.eclipse.net.solarnetwork.node.demandresponse.dretargetcost, + net.solarnetwork.node;version="1.23.0", + net.solarnetwork.node.dao;version="1.8.0", + net.solarnetwork.node.demandresponse.dretargetcost, + net.solarnetwork.node.domain;version="1.11.0", + net.solarnetwork.node.job;version="1.13.4", + net.solarnetwork.node.reactor;version="1.3.0", + net.solarnetwork.node.reactor.support;version="1.3.0", + net.solarnetwork.node.settings;version="1.10.0", + net.solarnetwork.node.settings.support;version="1.8.0", + net.solarnetwork.node.support;version="1.14.0", + net.solarnetwork.node.util;version="1.7.2", + net.solarnetwork.util;version="1.28.0", + org.quartz;version="[2.2.3,3.0.0)", + org.quartz.simpl;version="[2.2.3,3.0.0)", + org.slf4j;version="[1.7.24,2.0.0)", + org.springframework.beans;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.context;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.context.support;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.core;version="[4.2.9.RELEASE,5.0.0)", + org.springframework.scheduling.quartz;version="[4.2.9.RELEASE,5.0.0)" +Export-Package: net.solarnetwork.node.demandresponse.mockbattery diff --git a/net.solarnetwork.node.demandresponse.mockbattery/OSGI-INF/blueprint/module.xml b/net.solarnetwork.node.demandresponse.mockbattery/OSGI-INF/blueprint/module.xml new file mode 100644 index 000000000..b472e4a38 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/OSGI-INF/blueprint/module.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + net.solarnetwork.node.job.ManagedTriggerAndJobDetail + net.solarnetwork.node.job.ServiceProvider + net.solarnetwork.node.settings.SettingSpecifierProvider + net.solarnetwork.node.reactor.FeedbackInstructionHandler + + + + + + + + + + + + + + + + + + + + + + + + net.solarnetwork.node.DatumDataSource + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/net.solarnetwork.node.demandresponse.mockbattery/build.properties b/net.solarnetwork.node.demandresponse.mockbattery/build.properties new file mode 100644 index 000000000..668f1032f --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = build/eclipse/ +bin.includes = META-INF/,\ + . diff --git a/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/DRBattery.java b/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/DRBattery.java new file mode 100644 index 000000000..0d743754b --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/DRBattery.java @@ -0,0 +1,204 @@ +package net.solarnetwork.node.demandresponse.mockbattery; + +import java.util.Date; +import java.util.Hashtable; +import java.util.Map; + +import net.solarnetwork.node.demandresponse.dretargetcost.DRSupportTools; +import net.solarnetwork.node.job.SimpleManagedTriggerAndJobDetail; +import net.solarnetwork.node.reactor.FeedbackInstructionHandler; +import net.solarnetwork.node.reactor.Instruction; +import net.solarnetwork.node.reactor.InstructionHandler; +import net.solarnetwork.node.reactor.InstructionStatus; +import net.solarnetwork.node.reactor.InstructionStatus.InstructionState; +import net.solarnetwork.node.reactor.support.BasicInstructionStatus; + +/** + * Class to handle demand response instructions for a battery. This class + * follows the demand response rules described in the DRSupportTools class. This + * implementation is an example of a chargeable device, this means it can change + * from charging and discharging states. To be able to send demand response + * instructions to this device you need to include the name of the DR Engine as + * a parameter to each instruction. An in-depth explanation on how instruction + * parameters are formatted see DRSupportTools. + * + * @author robert + * + */ +public class DRBattery extends SimpleManagedTriggerAndJobDetail implements FeedbackInstructionHandler { + + @Override + public boolean handlesTopic(String topic) { + if (topic.equals(InstructionHandler.TOPIC_SHED_LOAD)) { + return true; + } + if (topic.equals(InstructionHandler.TOPIC_SET_CONTROL_PARAMETER)) { + return true; + } + if (topic.equals(DRSupportTools.DRPARAMS_INSTRUCTION)) { + return true; + } + return false; + } + + @Override + public InstructionState processInstruction(Instruction instruction) { + // use the with feedback method and remove the feedback + InstructionStatus status = processInstructionWithFeedback(instruction); + return status.getAcknowledgedInstructionState(); + + } + + @Override + public InstructionStatus processInstructionWithFeedback(Instruction instruction) { + DRBatteryDatumDataSource settings = getDRBatterySettings(); + InstructionState state; + MockBattery battery = settings.getMockBattery(); + if (instruction.getTopic().equals(DRSupportTools.DRPARAMS_INSTRUCTION)) { + + Map map = new Hashtable(); + + // this mock is always ready for demand response + map.put(DRSupportTools.DRREADY_PARAM, "true"); + + map.put(DRSupportTools.WATTS_PARAM, new Double(Math.abs(settings.getMockBattery().readDraw())).toString()); + + // This device is chargeable, + map.put(DRSupportTools.CHARGEABLE_PARAM, "true"); + map.put(DRSupportTools.DISCHARGING_PARAM, new Boolean(settings.getMockBattery().readDraw() > 0).toString()); + + // A Battery only has a limited number of charge and discharge + // cycles. The price of using energy is related to the cost of the + // battery and the number of cycles it can do and the maximum + // capacity of a charge. + map.put(DRSupportTools.ENERGY_DEPRECIATION, + new Integer((int) (settings.getBatteryCost().doubleValue() + / (settings.getBatteryCycles().doubleValue() * settings.getBatteryMaxCharge() * 2.0))) + .toString()); + + // This device is allowed to completly power down + map.put(DRSupportTools.MINWATTS_PARAM, "0"); + + map.put(DRSupportTools.MAXWATTS_PARAM, settings.getMaxDraw().toString()); + map.put(DRSupportTools.SOURCEID_PARAM, settings.getUID()); + map.put(DRSupportTools.CHARGE_PERCENTAGE_PARAM, + new Integer((int) settings.getMockBattery().capacityFraction() * 100).toString()); + map.put(DRSupportTools.MAXCHARGE_PARAM, settings.getBatteryMaxCharge().toString()); + map.put(DRSupportTools.MAXCHARGEWATTS_PARAM, settings.getMaxChargeDraw().toString()); + + // send the feedback of the instruction + state = InstructionState.Completed; + InstructionStatus status = new BasicInstructionStatus(instruction.getId(), state, new Date(), null, map); + + return status; + + } + + // This instruction is a little strange in that you have to map the DR + // Engine name with the shed amount see DRSupportTools for better + // explanation. + if (instruction.getTopic().equals(InstructionHandler.TOPIC_SHED_LOAD)) { + // the shed load value should be here + String param = instruction.getParameterValue(settings.getDREngineName()); + + if (param != null) { + try { + double value = Double.parseDouble(param); + + double draw = battery.readDraw(); + + // negative draw for a mockbattery means we are charging + if (draw < 0) { + // to shed out load we need to add + draw = draw + value; + } else { + draw = draw - value; + } + + battery.setDraw(draw); + state = InstructionState.Completed; + + } catch (NumberFormatException e) { + // Incorrectly formatted parameters should be declined + state = InstructionState.Declined; + } + + } else { + state = InstructionState.Declined; + } + } else { + state = InstructionState.Declined; + } + + if (instruction.getTopic().equals(InstructionHandler.TOPIC_SET_CONTROL_PARAMETER)) { + // verify the instruction came from the accepted DREngine + if (instruction.getParameterValue(settings.getDREngineName()) != null) { + + // for a battery we need two parameters one is what the watt + // level needs to be set to the other is whether we are charging + // or discharging. + String param = instruction.getParameterValue(DRSupportTools.WATTS_PARAM); + String param2 = instruction.getParameterValue(DRSupportTools.DISCHARGING_PARAM); + + // check we got both parameters + if (param != null && param2 != null) { + try { + + double value = Double.parseDouble(param); + + // the discharge parameter should be in form "true" or + // "false" + boolean discharge = Boolean.parseBoolean(param2); + + if (value < 0) { + // negative value does not make sense decline rather + // than assume positive + state = InstructionState.Declined; + + } else if (value > settings.getMaxDraw()) { + // the implementation of mockbattery has a negative + // draw + // as charging and positive draw as discharging + if (discharge) { + battery.setDraw(settings.getMaxDraw()); + } else { + battery.setDraw(-settings.getMaxDraw()); + } + state = InstructionState.Completed; + + } else { + // the implementation of mockbattery has a negative + // draw + // as charging and positive draw as discharging + if (discharge) { + battery.setDraw(value); + } else { + battery.setDraw(-value); + } + state = InstructionState.Completed; + + } + + } catch (NumberFormatException e) { + state = InstructionState.Declined; + } + + } else { + state = InstructionState.Declined; + } + } + + } + + InstructionStatus status = new BasicInstructionStatus(instruction.getId(), state, new Date()); + + return status; + } + + public DRBatteryDatumDataSource getDRBatterySettings() { + // Because of the way the OSGI is configured this is the best way I know + // to get access to the settings + return (DRBatteryDatumDataSource) getSettingSpecifierProvider(); + } + +} diff --git a/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/DRBatteryDatumDataSource.java b/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/DRBatteryDatumDataSource.java new file mode 100644 index 000000000..019fc6d90 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/DRBatteryDatumDataSource.java @@ -0,0 +1,183 @@ +package net.solarnetwork.node.demandresponse.mockbattery; + +import java.util.Date; +import java.util.List; + +import net.solarnetwork.node.DatumDataSource; +import net.solarnetwork.node.domain.GeneralNodeEnergyStorageDatum; +import net.solarnetwork.node.settings.SettingSpecifier; +import net.solarnetwork.node.settings.SettingSpecifierProvider; +import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier; +import net.solarnetwork.node.settings.support.BasicTitleSettingSpecifier; +import net.solarnetwork.node.support.DatumDataSourceSupport; + +/** + * DatumDataSource for the Demand response battery. When datums are read + * calculates the current battery charge. + * + * @author robert + * + */ +public class DRBatteryDatumDataSource extends DatumDataSourceSupport + implements SettingSpecifierProvider, DatumDataSource { + + // Default values + private Double maxCharge = 10.0; + private Double charge = 10.0; + private Integer cost = 1000; + private Integer cycles = 10000; + private String drEngineName = "DREngine"; + private Double maxDraw = 1.0; + private Double maxChargeDraw = 1.0; + + // The settings the user configures will be applied to this mockbattery + private MockBattery mockbattery; + + @Override + public Class getDatumType() { + return GeneralNodeEnergyStorageDatum.class; + } + + @Override + public GeneralNodeEnergyStorageDatum readCurrentDatum() { + + // create new datum + GeneralNodeEnergyStorageDatum datum = new GeneralNodeEnergyStorageDatum(); + datum.setCreated(new Date()); + datum.setSourceId(getUID()); + + // populate the datum with values from the battery + MockBattery mb = getMockBattery(); + datum.setAvailableEnergy((long) mb.readCharge()); + datum.setAvailableEnergyPercentage(mb.capacityFraction()); + + // Status indication on the datum + if (mb.readDraw() == 0) { + datum.putStatusSampleValue("Mode", "Idle"); + } else if (mb.readDraw() > 0) { + datum.putStatusSampleValue("Mode", "Discharging"); + } else { + datum.putStatusSampleValue("Mode", "Charging"); + } + + return datum; + + } + + public String getDREngineName() { + return drEngineName; + } + + public void setDrEngineName(String drEngineName) { + this.drEngineName = drEngineName; + } + + @Override + public String getSettingUID() { + return "net.solarnetwork.node.demandresponse.mockbattery"; + } + + @Override + public String getDisplayName() { + return "Mock DR Battery"; + } + + @Override + public List getSettingSpecifiers() { + List results = super.getIdentifiableSettingSpecifiers(); + DRBatteryDatumDataSource defaults = new DRBatteryDatumDataSource(); + results.add(new BasicTextFieldSettingSpecifier("batteryMaxCharge", defaults.maxCharge.toString())); + results.add(new BasicTextFieldSettingSpecifier("maxDraw", defaults.maxDraw.toString())); + results.add(new BasicTextFieldSettingSpecifier("maxChargeDraw", defaults.maxChargeDraw.toString())); + results.add(new BasicTextFieldSettingSpecifier("batteryCharge", defaults.charge.toString())); + results.add(new BasicTextFieldSettingSpecifier("batteryCost", defaults.cost.toString())); + results.add(new BasicTextFieldSettingSpecifier("batteryCycles", defaults.cycles.toString())); + + // Display the calculated energycost on the screen + // Limitation is you need to refresh the settings page for it to update + results.add(new BasicTitleSettingSpecifier("energyCost", calcCost(), true)); + + // SourceID of the DREngine to accept demand response from + results.add(new BasicTextFieldSettingSpecifier("drEngineName", defaults.drEngineName)); + return results; + } + + // configured in OSGI + public void setMockBattery(MockBattery mockbattery) { + this.mockbattery = mockbattery; + } + + public MockBattery getMockBattery() { + return this.mockbattery; + } + + public void setBatteryMaxCharge(Double charge) { + if (charge != null) { + mockbattery.setMax(charge); + } + this.maxCharge = charge; + } + + public Double getBatteryMaxCharge() { + return maxCharge; + } + + public Double getMaxDraw() { + return maxDraw; + } + + public void setMaxDraw(Double maxDraw) { + this.maxDraw = maxDraw; + // the mock battery does not have a concept of max draw so this method + // needs to handle the logic + if (mockbattery.readDraw() > maxDraw) { + mockbattery.setDraw(maxDraw); + } + } + + public Double getMaxChargeDraw() { + return maxChargeDraw; + } + + public void setMaxChargeDraw(Double maxChargeDraw) { + this.maxChargeDraw = maxChargeDraw; + } + + public void setBatteryCharge(Double charge) { + if (charge != null) { + mockbattery.setCharge(charge); + } + this.charge = charge; + } + + // note this returns the charge value the user set in settings page not the + // current charge + // of the battery + public Double getBatteryCharge() { + return this.charge; + } + + public Integer getBatteryCost() { + return cost; + } + + public void setBatteryCost(Integer cost) { + this.cost = cost; + } + + public Integer getBatteryCycles() { + return cycles; + } + + public void setBatteryCycles(Integer cycles) { + this.cycles = cycles; + } + + // Used for showing the depreciation cost on the settings page, + private String calcCost() { + return new Double( + (getBatteryCost().doubleValue() / (getBatteryCycles().doubleValue() * getBatteryMaxCharge() * 2.0))) + .toString(); + } + +} diff --git a/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/DRBatteryDatumDataSource.properties b/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/DRBatteryDatumDataSource.properties new file mode 100644 index 000000000..c3fb12157 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/DRBatteryDatumDataSource.properties @@ -0,0 +1,33 @@ +title= Mock DR Battery +uid.key = Source ID +groupUID.key = Service Group +groupUID.desc = An optional group to include this service in. +triggerCronExpression.key = Schedule +triggerCronExpression.desc = A \ + \ + cron expression representing the schedule to sample data at. + +batteryCharge.key = Battery Starting Charge +batteryCharge.desc = Setting this value will force the charge of the battery for that instant. The battery \ + is then free to drain or charge from that point. Units W/h + +batteryMaxCharge.key = Max Battery Charge +batteryMaxCharge.desc = The maximum charge the battery can have. Units W/h + +maxChargeDraw.key = Max Charging Rate +maxChargeDraw.desc = Maximum battery charging power in W/h + +batteryCost.key = Battery Cost +batteryCost.desc = The price of the mock battery + +batteryCycles.key = Battery Cycles +batteryCycles.desc = The number of charge discharge cycles the battery supports + +maxDraw.key = Max Discharge Rate +maxDraw.desc = Maximum battery discharge power + +energyCost.key = Hardware Depreciation +energyCost.desc = The depreciation cost per watt hour of using the battery value is based on the cost of battery and number of cycles + +drEngineName.key = DR Engine +drEngineName.desc = The name of the source to accept demand response queries \ No newline at end of file diff --git a/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/MockBattery.java b/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/MockBattery.java new file mode 100644 index 000000000..7431a8a0d --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockbattery/src/net/solarnetwork/node/demandresponse/mockbattery/MockBattery.java @@ -0,0 +1,135 @@ +package net.solarnetwork.node.demandresponse.mockbattery; + +public class MockBattery { + private Double maxcapacity = null; + private double charge; + private double draw; + private long lastsample; + + public MockBattery(double maxcapacity) { + if (maxcapacity < 0) { + throw new IllegalArgumentException(); + } + this.maxcapacity = maxcapacity; + setCharge(0); + setDraw(0); + } + + public MockBattery() { + this(10.0); + } + + /** + * Sets the maximal charge is kWh of the battery. If the battery's charge is + * greater than the new maxcharge there will be no error. However be sure to + * call the readCharge method to update the charge value to be within + * bounds. + * + * @param maxcapacity + */ + public void setMax(double maxcapacity) { + // you cannot have no or negative capacity keep current value if + // argument is invalid + if (maxcapacity > 0) { + this.maxcapacity = maxcapacity; + } + } + + /** + * Forces the battery to a specific charge at that instant. Try to set a + * negative charge will put the battery at 0. Trying to set the charge + * beyond max charge will have the battery set to max charge + * + * @param charge + */ + public void setCharge(double charge) { + this.lastsample = readTime(); + // can't have negative charge,if that happens we keep the current value + if (charge >= 0.0) { + this.charge = Math.min(charge, this.maxcapacity); + + } + + } + + // returns the time difference in hours between now and lastsample reading. + // This is used for calculating the battery's charge. + private double deltaTimeHours() { + long oldtime = this.lastsample; + long currenttime = readTime(); + this.lastsample = currenttime; + double delta = currenttime - oldtime; + delta = delta / 1000 / 60 / 60; + return delta; + } + + /** + * Returns how many kWh of charge the battery has + * + * @return Charge (kWh) + */ + public double readCharge() { + if (this.maxcapacity == null) { + throw new RuntimeException(); + } + double delta = deltaTimeHours(); + double newcharge = this.charge - this.draw * delta; + if (newcharge < 0.0) { + newcharge = 0; + } + if (newcharge > this.maxcapacity) { + newcharge = this.maxcapacity; + } + setCharge(newcharge); + return newcharge; + } + + /** + * + * @return the powerDraw of the battery (kWh) + */ + public double readDraw() { + if (this.maxcapacity == null) { + throw new RuntimeException(); + } + if (this.draw < 0 && this.charge == this.maxcapacity) { + // can't charge what is already fully charged + return 0; + } else if (this.draw > 0 && this.charge == 0) { + // can't draw from empty battery + return 0; + } else { + return this.draw; + } + } + + /** + * returns the fraction of remaining battery capacity as a value between 0 + * and 1. Multiply this value by 100 to get remaining capacity as a + * percentage. + * + * @return remaining battery life + */ + public float capacityFraction() { + if (this.maxcapacity == null) { + throw new RuntimeException(); + } + return (float) (readCharge() / this.maxcapacity); + } + + /** + * Sets the draw of the battery. The mock battery does not have a max or min + * draw. A negative draw value means you are charging battery and positive + * value means you are draining the battery + * + * @param draw + */ + public void setDraw(double draw) { + readCharge(); + this.draw = draw; + } + + public long readTime() { + return System.currentTimeMillis(); + } +} diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/.classpath b/net.solarnetwork.node.demandresponse.mockdrconsumer/.classpath new file mode 100644 index 000000000..2374a41aa --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/.gitignore b/net.solarnetwork.node.demandresponse.mockdrconsumer/.gitignore new file mode 100644 index 000000000..c3dca1b96 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/.gitignore @@ -0,0 +1,2 @@ +/build +/target diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/.project b/net.solarnetwork.node.demandresponse.mockdrconsumer/.project new file mode 100644 index 000000000..00eee3778 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/.project @@ -0,0 +1,28 @@ + + + net.solarnetwork.node.demandresponse.mockdrconsumer + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/.settings/org.eclipse.jdt.core.prefs b/net.solarnetwork.node.demandresponse.mockdrconsumer/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..c537b6306 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/.settings/org.eclipse.pde.core.prefs b/net.solarnetwork.node.demandresponse.mockdrconsumer/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..f29e940a0 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/META-INF/MANIFEST.MF b/net.solarnetwork.node.demandresponse.mockdrconsumer/META-INF/MANIFEST.MF new file mode 100644 index 000000000..dbc151872 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/META-INF/MANIFEST.MF @@ -0,0 +1,29 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Mock DRDevice +Bundle-SymbolicName: net.solarnetwork.node.demandresponse.mockdrconsumer +Bundle-Version: 1.0.0 +Bundle-Vendor: SolarNetwork +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Import-Package: + build.eclipse.net.solarnetwork.node.demandresponse.dretargetcost, + net.solarnetwork.node;version="1.22.0", + net.solarnetwork.node.dao;version="1.8.0", + net.solarnetwork.node.demandresponse.dretargetcost, + net.solarnetwork.node.domain;version="1.11.0", + net.solarnetwork.node.job;version="1.13.4", + net.solarnetwork.node.reactor;version="1.3.0", + net.solarnetwork.node.reactor.support;version="1.3.0", + net.solarnetwork.node.settings;version="1.10.0", + net.solarnetwork.node.settings.support;version="1.8.0", + net.solarnetwork.node.support;version="1.14.0", + net.solarnetwork.node.util;version="1.7.2", + net.solarnetwork.util;version="1.28.0", + org.quartz;version="[2.2.3,3.0)", + org.quartz.simpl;version="[2.2.3,3.0)", + org.slf4j;version="[1.7.24,2.0)", + org.springframework.beans;version="[4.2.9,5.0)", + org.springframework.context;version="[4.2.9,5.0)", + org.springframework.context.support;version="[4.2.9,5.0)", + org.springframework.core;version="[4.2.9,5.0)", + org.springframework.scheduling.quartz;version="[4.2.9,5.0)" diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/OSGI-INF/blueprint/module.xml b/net.solarnetwork.node.demandresponse.mockdrconsumer/OSGI-INF/blueprint/module.xml new file mode 100644 index 000000000..8685d263f --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/OSGI-INF/blueprint/module.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + net.solarnetwork.node.job.ManagedTriggerAndJobDetail + net.solarnetwork.node.job.ServiceProvider + net.solarnetwork.node.settings.SettingSpecifierProvider + net.solarnetwork.node.reactor.FeedbackInstructionHandler + + + + + + + + + + + + + + + + + + + + + + + + net.solarnetwork.node.DatumDataSource + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/bin/build/eclipse/net/solarnetwork/node/datum/energymeter/mock/MockEnergyMeterDatumSource.properties b/net.solarnetwork.node.demandresponse.mockdrconsumer/bin/build/eclipse/net/solarnetwork/node/datum/energymeter/mock/MockEnergyMeterDatumSource.properties new file mode 100644 index 000000000..d3bd6a7ab --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/bin/build/eclipse/net/solarnetwork/node/datum/energymeter/mock/MockEnergyMeterDatumSource.properties @@ -0,0 +1,32 @@ +title = Mock Energy Meter + +triggerCronExpression.key = Schedule +triggerCronExpression.desc = The cron expression controlling the sampling rate of the mock data. + +uid.key = Service Name +uid.desc = A unique name to identify this service with. +groupUID.key = Service Group +groupUID.desc = An optional group to include this service in. +sourceId.key = Source ID +sourceId.desc = A unique source identifier. + +voltage.key = RMS Voltage +voltage.desc = RMS of the mock power supply in volts. +frequency.key = Frequency +frequency.desc = Frequency of mock AC power supply, in Hz. +resistance.key = Resistance +resistance.desc = Resistance of the mock circuit, in Ohms. +inductance.key = Inductance +inductance.desc = Inductance of the mock circuit, in mirco Henry. +randomness.key = Toggle Randomness +randomness.desc = Set to true to have voltage and frequency deviate there values. +voltdev.key = Voltage Deviation +voltdev.desc = When randomness is on the source voltage will have random deviations +- this value. +freqdev.key = Frequency Deviation +freqdev.desc = When randomness is on the source frequency will have random deviation +- this value. + +resistanceDeviation.key = Resistance Deviation +resistanceDeviation.desc = When randomness is on the resistance will have random deviations +- this value. + +inductanceDeviation.key = Inductance Deviation +inductanceDeviation.desc = When randomness is on the inductance will have random deviations +- this value. diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/build.properties b/net.solarnetwork.node.demandresponse.mockdrconsumer/build.properties new file mode 100644 index 000000000..4fe8ae9bc --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = build/eclipse/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/ \ No newline at end of file diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRConsumer.java b/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRConsumer.java new file mode 100644 index 000000000..21ccb7607 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRConsumer.java @@ -0,0 +1,189 @@ +/* ================================================================== + * MockMeterDataSource.java - 10/06/2015 1:28:07 pm + * + * Copyright 2017 SolarNetwork.net Dev Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + * ================================================================== + */ + +package net.solarnetwork.node.demandresponse.mockdrconsumer; + +import java.util.Date; +import java.util.Hashtable; +import java.util.Map; + +import net.solarnetwork.node.DatumDataSource; +import net.solarnetwork.node.demandresponse.dretargetcost.DRSupportTools; +import net.solarnetwork.node.job.SimpleManagedTriggerAndJobDetail; +import net.solarnetwork.node.reactor.FeedbackInstructionHandler; +import net.solarnetwork.node.reactor.Instruction; +import net.solarnetwork.node.reactor.InstructionHandler; +import net.solarnetwork.node.reactor.InstructionStatus; +import net.solarnetwork.node.reactor.InstructionStatus.InstructionState; +import net.solarnetwork.node.reactor.support.BasicInstructionStatus; +import net.solarnetwork.node.settings.SettingSpecifierProvider; + +/** + * Mock plugin to be the source of values for a GeneralNodeACEnergyDatum, this + * mock tries to simulate a AC circuit containing a resister and inductor in + * series. + * + *

+ * This class implements {@link SettingSpecifierProvider} and + * {@link DatumDataSource} + *

+ * + * @author robert + * @version 1.0 + */ +public class MockDRConsumer extends SimpleManagedTriggerAndJobDetail implements FeedbackInstructionHandler { + + @Override + public boolean handlesTopic(String topic) { + if (topic.equals(InstructionHandler.TOPIC_SHED_LOAD)) { + return true; + } else if (topic.equals(InstructionHandler.TOPIC_SET_CONTROL_PARAMETER)) { + return true; + } else if (topic.equals(DRSupportTools.DRPARAMS_INSTRUCTION)) { + return true; + } + return false; + } + + @Override + // process an instruction just returning the status and not any feedback + public InstructionState processInstruction(Instruction instruction) { + InstructionStatus status = processInstructionWithFeedback(instruction); + return status.getAcknowledgedInstructionState(); + } + + @Override + /** + * List of supported Instructions getDRDeviceInstance, Shed load, set + * control parameter + */ + public InstructionStatus processInstructionWithFeedback(Instruction instruction) { + InstructionState state; + MockDRConsumerDatumDataSource settings = getSettings(); + + if (instruction.getTopic().equals(DRSupportTools.DRPARAMS_INSTRUCTION)) { + Map map = new Hashtable(); + + // check that this instruction came from the accepted source + if (instruction.getParameterValue(settings.getDrsource()) == null) { + // if not decline the instruction + state = InstructionState.Declined; + } else { + + state = InstructionState.Completed; + + // put values in the parameter map + map.put(DRSupportTools.DRREADY_PARAM, "true"); + map.put(DRSupportTools.WATTS_PARAM, settings.getWatts().toString()); + map.put(DRSupportTools.ENERGY_DEPRECIATION, settings.getEnergycost().toString()); + map.put(DRSupportTools.MINWATTS_PARAM, settings.getMinwatts().toString()); + map.put(DRSupportTools.MAXWATTS_PARAM, settings.getMaxwatts().toString()); + map.put(DRSupportTools.SOURCEID_PARAM, settings.getUID()); + } + + InstructionStatus status = new BasicInstructionStatus(instruction.getId(), state, new Date(), null, map); + return status; + + } + + // The shed load instruction reduces the wattage value by the set amount + if (instruction.getTopic().equals(InstructionHandler.TOPIC_SHED_LOAD)) { + // the value to shed should be mapped to the name of the drsource as + // was the convention used + // by this instruction in classes not written by me. + String param = instruction.getParameterValue(settings.getDrsource()); + if (param != null) { + try { + + // I did not see anywhere it previous uses of this + // instruction the requirment it had to be an interger + // while DRDevice is set to integer values I read a double + // and turn it to an int + double value = Double.parseDouble(param) + 0.5;// 0.5 for + // rounding + value = settings.getWatts() - value; + if (value < settings.getMinwatts()) { + settings.setWatts(settings.getMinwatts()); + } else if (value > settings.getMaxwatts()) { + settings.setWatts(settings.getMaxwatts()); + } else { + settings.setWatts((int) value); + } + + state = InstructionState.Completed; + } catch (NumberFormatException e) { + + // if we cannot parse any number decline the instruction + // because something went wrong + state = InstructionState.Declined; + } + + } else { + + // instruction came from an untrusted source decline the + // instruction + state = InstructionState.Declined; + } + + // this instruction sets the wattage to a specific value rather than + // subtracting it like shed load + // it is mainly used for increasing the wattage reading however it + // can be used to reduce + } else if (instruction.getTopic().equals(InstructionHandler.TOPIC_SET_CONTROL_PARAMETER)) { + + String param = instruction.getParameterValue("watts"); + // be sure the instruction came from the accepted DR Engine + if (instruction.getParameterValue(settings.getDrsource()) != null && param != null) { + try { + double value = Double.parseDouble(param); + if (value < 0) { + settings.setWatts(0); + + } else if (value > settings.getMaxwatts()) { + settings.setWatts(settings.getMaxwatts()); + + } else { + settings.setWatts((int) value); + } + state = InstructionState.Completed; + } catch (NumberFormatException e) { + state = InstructionState.Declined; + } + + } else { + state = InstructionState.Declined; + } + } else { + state = InstructionState.Declined; + } + + InstructionStatus status = new BasicInstructionStatus(instruction.getId(), state, new Date()); + + return status; + } + + public MockDRConsumerDatumDataSource getSettings() { + + return (MockDRConsumerDatumDataSource) getSettingSpecifierProvider(); + } + +} diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRConsumerDatumDataSource.java b/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRConsumerDatumDataSource.java new file mode 100644 index 000000000..246b8158c --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRConsumerDatumDataSource.java @@ -0,0 +1,108 @@ +package net.solarnetwork.node.demandresponse.mockdrconsumer; + +import java.util.Date; +import java.util.List; + +import net.solarnetwork.node.DatumDataSource; +import net.solarnetwork.node.domain.GeneralNodeACEnergyDatum; +import net.solarnetwork.node.settings.SettingSpecifier; +import net.solarnetwork.node.settings.SettingSpecifierProvider; +import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier; +import net.solarnetwork.node.support.DatumDataSourceSupport; + +public class MockDRConsumerDatumDataSource extends DatumDataSourceSupport + implements SettingSpecifierProvider, DatumDataSource { + + private Integer minwatts = 0; + private Integer maxwatts = 10; + private Integer energycost = 1; + private Integer watts = 0; + private String drsource = ""; + + @Override + public String getSettingUID() { + return "net.solarnetwork.node.demandresponse.mockdrconsumer"; + } + + @Override + public String getDisplayName() { + return "Mock DR Device"; + } + + @Override + public List getSettingSpecifiers() { + MockDRConsumerDatumDataSource defaults = new MockDRConsumerDatumDataSource(); + List results = super.getIdentifiableSettingSpecifiers(); + + // user enters text + results.add(new BasicTextFieldSettingSpecifier("minwatts", defaults.minwatts.toString())); + results.add(new BasicTextFieldSettingSpecifier("maxwatts", defaults.maxwatts.toString())); + results.add(new BasicTextFieldSettingSpecifier("energycost", defaults.energycost.toString())); + results.add(new BasicTextFieldSettingSpecifier("drsource", defaults.drsource)); + return results; + } + + public Integer getMinwatts() { + return minwatts; + } + + public void setMinwatts(Integer minwatts) { + if (watts < minwatts) { + watts = minwatts; + } + this.minwatts = minwatts; + } + + public Integer getMaxwatts() { + return maxwatts; + } + + public void setMaxwatts(Integer maxwatts) { + if (watts > maxwatts) { + watts = maxwatts; + } + this.maxwatts = maxwatts; + } + + public Integer getEnergycost() { + return energycost; + } + + public void setEnergycost(Integer energycost) { + this.energycost = energycost; + } + + // This is the sourceID of the DRAnouncer to which the device will follow + public String getDrsource() { + return drsource; + } + + public void setDrsource(String drsource) { + this.drsource = drsource; + } + + // protected as this is not set via the settings page but instead via demand + // response + protected void setWatts(Integer watts) { + this.watts = watts; + } + + public Integer getWatts() { + return watts; + } + + @Override + public Class getDatumType() { + return GeneralNodeACEnergyDatum.class; + } + + @Override + public GeneralNodeACEnergyDatum readCurrentDatum() { + GeneralNodeACEnergyDatum datum = new GeneralNodeACEnergyDatum(); + datum.setCreated(new Date()); + datum.setSourceId(getUID()); + datum.setWatts(getWatts()); + return datum; + } + +} diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRConsumerDatumDataSource.properties b/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRConsumerDatumDataSource.properties new file mode 100644 index 000000000..b3a739785 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRConsumerDatumDataSource.properties @@ -0,0 +1,15 @@ +title = Mock DR Consumer +triggerCronExpression.key = Schedule +triggerCronExpression.desc = The cron expression controlling the sampling rate of the mock data. +uid.key = Service Name +uid.desc = A unique name to identify this service with. +groupUID.key = Service Group +groupUID.desc = An optional group to include this service in. +minwatts.key = Min Watts +minwatts.desc = Minimum Watts +maxwatts.key = Max Watts +maxwatts.desc = Maximum Watts +energycost.key = Depreciation Cost +energycost.desc = This value represents how much it costs to operate this device per watt on energy. This is value can be used by demand response strageys to plan a demand response. +drsource.key = DR SourceID +drsource.desc = The sourceID of a DR Engine for which this DRDevice shall accept instructions from \ No newline at end of file diff --git a/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRDeviceSettings.java b/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRDeviceSettings.java new file mode 100644 index 000000000..2e01cb184 --- /dev/null +++ b/net.solarnetwork.node.demandresponse.mockdrconsumer/src/net/solarnetwork/node/demandresponse/mockdrconsumer/MockDRDeviceSettings.java @@ -0,0 +1,92 @@ +package net.solarnetwork.node.demandresponse.mockdrconsumer; + +import java.util.List; + +import net.solarnetwork.node.settings.SettingSpecifier; +import net.solarnetwork.node.settings.SettingSpecifierProvider; +import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier; +import net.solarnetwork.node.support.DatumDataSourceSupport; + +public class MockDRDeviceSettings extends DatumDataSourceSupport implements SettingSpecifierProvider { + + // default values + private String sourceId = "Mock DR Device"; + private Integer minwatts = 0; + private Integer maxwatts = 10; + private Integer energycost = 1; + private Integer watts = 0; + private String drsource; + + @Override + public String getSettingUID() { + return "net.solarnetwork.node.demandresponse.mockdevice"; + } + + @Override + public String getDisplayName() { + return "Mock DR Device"; + } + + @Override + public List getSettingSpecifiers() { + MockDRDeviceSettings defaults = new MockDRDeviceSettings(); + List results = super.getIdentifiableSettingSpecifiers(); + + // user enters text + results.add(new BasicTextFieldSettingSpecifier("sourceId", defaults.sourceId)); + results.add(new BasicTextFieldSettingSpecifier("minwatts", defaults.minwatts.toString())); + results.add(new BasicTextFieldSettingSpecifier("maxwatts", defaults.maxwatts.toString())); + results.add(new BasicTextFieldSettingSpecifier("energycost", defaults.energycost.toString())); + results.add(new BasicTextFieldSettingSpecifier("drsource", defaults.drsource)); + return results; + } + + public Integer getMinwatts() { + return minwatts; + } + + public void setMinwatts(Integer minwatts) { + if (watts < minwatts) { + watts = minwatts; + } + this.minwatts = minwatts; + } + + public Integer getMaxwatts() { + return maxwatts; + } + + public void setMaxwatts(Integer maxwatts) { + if (watts > maxwatts) { + watts = maxwatts; + } + this.maxwatts = maxwatts; + } + + public Integer getEnergycost() { + return energycost; + } + + public void setEnergycost(Integer energycost) { + this.energycost = energycost; + } + + // This is the sourceID of the DRAnouncer to which the device will follow + public String getDrsource() { + return drsource; + } + + public void setDrsource(String drsource) { + this.drsource = drsource; + } + + // protected as this is not set via the settings page but instead via demand + // response + protected void setWatts(Integer watts) { + this.watts = watts; + } + + public Integer getWatts() { + return watts; + } +}