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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 extends GeneralNodeACEnergyDatum> 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;
+ }
+}