diff --git a/net.solarnetwork.node.openadr.mockven/.classpath b/net.solarnetwork.node.openadr.mockven/.classpath
new file mode 100644
index 000000000..aeeaff84d
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/net.solarnetwork.node.openadr.mockven/.gitignore b/net.solarnetwork.node.openadr.mockven/.gitignore
new file mode 100644
index 000000000..c3dca1b96
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/.gitignore
@@ -0,0 +1,2 @@
+/build
+/target
diff --git a/net.solarnetwork.node.openadr.mockven/.project b/net.solarnetwork.node.openadr.mockven/.project
new file mode 100644
index 000000000..382984931
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/.project
@@ -0,0 +1,28 @@
+
+
+ net.solarnetwork.node.openadr.mockven
+
+
+
+
+
+ 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.openadr.mockven/.settings/org.eclipse.jdt.core.prefs b/net.solarnetwork.node.openadr.mockven/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..c537b6306
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/.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.openadr.mockven/.settings/org.eclipse.pde.core.prefs b/net.solarnetwork.node.openadr.mockven/.settings/org.eclipse.pde.core.prefs
new file mode 100644
index 000000000..e8ff8be0b
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/.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.openadr.mockven/META-INF/MANIFEST.MF b/net.solarnetwork.node.openadr.mockven/META-INF/MANIFEST.MF
new file mode 100644
index 000000000..b16c26b90
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/META-INF/MANIFEST.MF
@@ -0,0 +1,28 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: OpenADR VEN Mock
+Bundle-SymbolicName: net.solarnetwork.node.openadr.mockven
+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.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",
+ openadr.model.v20b;version="2.1.0",
+ org.quartz;version="[2.2.3,3.0.0)",
+ org.quartz.impl.triggers;version="2.2.3",
+ 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.support;version="[4.2.9.RELEASE,5.0.0)",
+ org.springframework.core;version="[4.2.9.RELEASE,5.0.0)",
+ org.springframework.http;version="4.2.9.RELEASE",
+ org.springframework.scheduling.quartz;version="[4.2.9.RELEASE,5.0.0)",
+ org.springframework.web.client;version="4.2.9.RELEASE"
+Require-Bundle: net.solarnetwork.external.openadr.model
diff --git a/net.solarnetwork.node.openadr.mockven/OSGI-INF/blueprint/module.xml b/net.solarnetwork.node.openadr.mockven/OSGI-INF/blueprint/module.xml
new file mode 100644
index 000000000..9598662f4
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/OSGI-INF/blueprint/module.xml
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ net.solarnetwork.node.job.ManagedTriggerAndJobDetail
+ net.solarnetwork.node.job.ServiceProvider
+ net.solarnetwork.node.settings.SettingSpecifierProvider
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/net.solarnetwork.node.openadr.mockven/build.properties b/net.solarnetwork.node.openadr.mockven/build.properties
new file mode 100644
index 000000000..668f1032f
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = build/eclipse/
+bin.includes = META-INF/,\
+ .
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/MockVen.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/MockVen.java
new file mode 100644
index 000000000..df9213a9b
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/MockVen.java
@@ -0,0 +1,171 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+import openadr.model.v20b.OadrCreatedPartyRegistration;
+import openadr.model.v20b.OadrDistributeEvent;
+import openadr.model.v20b.OadrPayload;
+import openadr.model.v20b.OadrSignedObject;
+
+/**
+ *
+ * Class to simulate a Virtual End Node (VEN). This class is designed to talk to
+ * a Virtual Top Node (VTN) via OpenADR 2.0b
+ *
+ *
+ * @author robert
+ * @version 1.0
+ */
+public class MockVen extends OadrParams {
+
+ private boolean registered = false;
+ private String url;
+ private TopNodeConnection connection;
+
+ public MockVen() {
+ connection = new TopNodeConnection();
+ }
+
+ @Override
+ public void setVenName(String venName) {
+ //if this parameter changes we assume we are no longer registered
+ if ( !venName.equals(getVenName()) ) {
+ registered = false;
+ }
+ super.setVenName(venName);
+ }
+
+ public void setVtnURL(String url) {
+ //ensure the URL ends with a / as we will be needing to call subdomains from OadrSubDomains
+ if ( !url.endsWith("/") ) {
+ url = url + "/";
+ }
+ //if this parameter changes we assume we are no longer registered
+ if ( !url.equals(this.url) ) {
+ registered = false;
+ }
+
+ this.url = url;
+ }
+
+ /**
+ * sends a OadrPoll message to the VTN and acts according to the message
+ * sent back
+ */
+ public void pollAndRespond() {
+
+ if ( registered == false ) {
+ queryAndRegister();
+ }
+
+ OadrSignedObject response = pollVTN().getOadrSignedObject();
+
+ /**
+ * currently the only supported response is for a OadrDistrubuteEvent.
+ * There are other possible message a VTN can send to a VEN such as
+ * asking for a report.
+ *
+ */
+ if ( response.getOadrDistributeEvent() != null ) {
+ respondToDistributeEvent(response.getOadrDistributeEvent());
+ }
+ }
+
+ /**
+ * This method takes a OadrDistrubuteEvent and sends the VTN a response on
+ * whether the VEN is opting in or not. As well as applying a demand
+ * response strategy if the VTN has chosen to opt into said event.
+ *
+ * Modify this method for custom behaviours
+ *
+ * @param event
+ */
+ public void respondToDistributeEvent(OadrDistributeEvent event) {
+
+ //I don't think it makes sense for the decision to opt in to be in the generator
+ OadrCreatedEventGenerator generator = new OadrCreatedEventGenerator();
+
+ /**
+ * You can potentially use this location for interacting with a demand
+ * response engine or some other mechanism for doing demand response on
+ * the solarnetwork all of the parameters for the event are inside the
+ * OadrDistributeEvent
+ *
+ * https://pastebin.com/zrNwcQAX
+ *
+ * e.g. Link above is an XML generated from the EPRI VTN asking for
+ * 250kW of demand response (specific LOAD_CONTROL with
+ * X_LOAD_CONTROL_CAPACITY)
+ *
+ * if this was to be implemented here you can write some code to extract
+ * the "payloadFloat" value and pass it to a demand response engine
+ *
+ *
+ */
+ OadrPayload payload = generator.createPayload(this, event);
+
+ OadrPayload response = connection.postPayload(url + OadrSubDomains.EiEvent, payload);
+
+ }
+
+ /**
+ * Polls the VTN with an OadrPoll payload and returns the OadrPayload
+ * response. If the VEN is not registered to the VTN it tries to register
+ * first before polling.
+ *
+ * @return
+ */
+ private OadrPayload pollVTN() {
+ if ( registered == false ) {
+ queryAndRegister();
+ }
+
+ OadrPollGenerator generator = new OadrPollGenerator();
+
+ OadrPayload payload = generator.createPayload(this);
+
+ OadrPayload response = connection.postPayload(url + OadrSubDomains.OadrPoll, payload);
+ return response;
+
+ }
+
+ /**
+ * sends a OadrQueryRegistration payload to the VTN and then proceeds to
+ * register with the VTN. Currently there is no error correction this method
+ * can fail if the VTN refuses registration, or if the VEN fails to make a
+ * connection to the VTN.
+ */
+ public void queryAndRegister() {
+ OadrQueryRegistrationGenerator payloadGen = new OadrQueryRegistrationGenerator();
+ OadrPayload payload = payloadGen.createPayload(this);
+
+ //Currently there is no procedure in place to handle if the VTN is unreachable eg invalid url
+ OadrPayload response = connection.postPayload(url + OadrSubDomains.EiRegisterParty, payload);
+ OadrCreatedPartyRegistration partyReg = response.getOadrSignedObject()
+ .getOadrCreatedPartyRegistration();
+
+ //Check to see if that the VTN is okay before proceeding
+ if ( partyReg.getEiResponse().getResponseDescription().equalsIgnoreCase("OK") ) {
+ setVtnID(partyReg.getVtnID());
+ OadrCreatePartyRegistrationGenerator payloadGen2 = new OadrCreatePartyRegistrationGenerator();
+ payload = payloadGen2.createPayload(this);
+ response = connection.postPayload(url + OadrSubDomains.EiRegisterParty, payload);
+ partyReg = response.getOadrSignedObject().getOadrCreatedPartyRegistration();
+ setRegistrationID(partyReg.getRegistrationID());
+ setVenID(partyReg.getVenID());
+ registered = true;
+ //skip register report for now
+ /**
+ * TODO normally at this stage the VTN returns a register report and
+ * the VEN does a response to that, I have noticed that with the
+ * EPRI mocks that this step is not needed and one can just go and
+ * start polling. I don't know if this is allowed in the standard so
+ * it is best for in the future to follow proper steps.
+ */
+
+ } else {
+ //Perhaps in future this could be a checked exception
+ throw new RuntimeException("Failed at registering with VTN");
+ }
+ }
+
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/MockVenJobService.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/MockVenJobService.java
new file mode 100644
index 000000000..d38e573fa
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/MockVenJobService.java
@@ -0,0 +1,93 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+import java.util.List;
+import net.solarnetwork.node.job.JobService;
+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;
+
+/**
+ *
+ * Setting specifier for the MockVEN it also implements JobService to allow
+ * periodic polling of the VTN
+ *
+ * @author robert
+ * @version 1.0
+ */
+public class MockVenJobService extends DatumDataSourceSupport
+ implements JobService, SettingSpecifierProvider {
+
+ private String venName;
+ private String vtnAddress;
+ private String vtnName;
+ private MockVen mockVen = null;
+
+ @Override
+ public String getSettingUID() {
+ return "net.solarnetwork.node.openadr.mockven";
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "OpenADR Ven Mock";
+ }
+
+ @Override
+ public List getSettingSpecifiers() {
+ List results = getIdentifiableSettingSpecifiers();
+ //default names used by EPRI's VEN and VTN
+ //see results.add(new BasicTextFieldSettingSpecifier("vtnName", "EPRI"));
+ //results.add(new BasicTextFieldSettingSpecifier("vtnName", "EPRI"));
+ results.add(new BasicTextFieldSettingSpecifier("venName", "Test_VEN_Name"));
+ results.add(new BasicTextFieldSettingSpecifier("vtnName", "EPRI"));
+
+ results.add(new BasicTextFieldSettingSpecifier("vtnAddress", null));
+ return results;
+ }
+
+ public String getVenName() {
+ return venName;
+ }
+
+ public void setVenName(String venName) {
+ this.venName = venName;
+ }
+
+ public String getVtnAddress() {
+ return vtnAddress;
+ }
+
+ public void setVtnAddress(String vtnAddress) {
+ this.vtnAddress = vtnAddress;
+ }
+
+ public String getVtnName() {
+ return vtnName;
+ }
+
+ public void setVtnName(String vtnName) {
+ this.vtnName = vtnName;
+ }
+
+ private void runMockVen() {
+ //create a MockVen instance when we don't have one
+ if ( mockVen == null ) {
+ mockVen = new MockVen();
+ }
+ //put the settings into the mock ven
+ mockVen.setVtnURL(vtnAddress);
+ mockVen.setVenName(venName);
+
+ //the poll and respond method will register to the VTN if it is not already registered
+ //otherwise it will send poll request to the VTN and respond to any events
+ mockVen.pollAndRespond();
+ }
+
+ @Override
+ public void executeJobService() throws Exception {
+ runMockVen();
+ }
+
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/MockVenJobService.properties b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/MockVenJobService.properties
new file mode 100644
index 000000000..dbf613baa
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/MockVenJobService.properties
@@ -0,0 +1,17 @@
+title = OpenADR Mock Virtual End Node
+triggerCronExpression.key = Schedule
+triggerCronExpression.desc = A \
+ \
+ cron expression representing the schedule to sample data at.
+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.
+venName.key = VEN Name
+venName.desc = The human readable name of the VEN to be given to the VTN
+venID.key = VEN ID
+venID.desc = The ID number this VEN has associated with VTN
+vtnAddress.key = VTN URL
+vtnAddress.desc = The URL of the VTN to where OpenADR 2.0b messages get sent to. These typically end with /OpenADR2/Simple/2.0b/
+vtnName.key = VTN Name
+vtnName.desc = The human readable name of the VTN
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrCreatePartyRegistrationGenerator.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrCreatePartyRegistrationGenerator.java
new file mode 100644
index 000000000..d80e222d0
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrCreatePartyRegistrationGenerator.java
@@ -0,0 +1,35 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+import openadr.model.v20b.OadrCreatePartyRegistration;
+import openadr.model.v20b.OadrPayload;
+import openadr.model.v20b.OadrSignedObject;
+
+/**
+ * Class to generate OadrPayloads with OadrCreatePartyRegistration used for
+ * registering with the VTN
+ *
+ * @author robert
+ * @version 1.0
+ */
+public class OadrCreatePartyRegistrationGenerator extends OadrPayloadGenerator {
+
+ public OadrPayload createPayload(OadrParams params) {
+ OadrPayload payload = new OadrPayload();
+ OadrSignedObject signedObject = new OadrSignedObject();
+ OadrCreatePartyRegistration partyReg = new OadrCreatePartyRegistration();
+ payload.withOadrSignedObject(signedObject.withOadrCreatePartyRegistration(partyReg));
+
+ //most of these values are defaults, I don't know what some of them mean I just say the EPRI VEN use them so im using them
+ partyReg.setSchemaVersion(params.getProfileName());
+ partyReg.setRequestID(genRandomRequestID());
+ partyReg.setOadrHttpPullModel(params.isHttpPullModel());
+ partyReg.setOadrVenName(params.getVenName());
+ partyReg.setOadrXmlSignature(params.isXmlSignature());
+ partyReg.setOadrReportOnly(params.isReportOnly());
+ partyReg.setOadrTransportName(params.getTransportName());
+
+ return payload;
+
+ }
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrCreatedEventGenerator.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrCreatedEventGenerator.java
new file mode 100644
index 000000000..df912f069
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrCreatedEventGenerator.java
@@ -0,0 +1,230 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+import java.time.Duration;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import openadr.model.v20b.OadrCreatedEvent;
+import openadr.model.v20b.OadrDistributeEvent;
+import openadr.model.v20b.OadrDistributeEvent.OadrEvent;
+import openadr.model.v20b.OadrPayload;
+import openadr.model.v20b.OadrSignedObject;
+import openadr.model.v20b.ei.EiEventSignal;
+import openadr.model.v20b.ei.EiResponse;
+import openadr.model.v20b.ei.EventDescriptor;
+import openadr.model.v20b.ei.EventResponses;
+import openadr.model.v20b.ei.EventResponses.EventResponse;
+import openadr.model.v20b.ei.OptTypeType;
+import openadr.model.v20b.ei.QualifiedEventID;
+import openadr.model.v20b.ei.ResponseCode;
+import openadr.model.v20b.ei.SignalTypeEnumeratedType;
+import openadr.model.v20b.pyld.EiCreatedEvent;
+
+/**
+ *
+ * Class to generate OadrPayloads containing OadrCreatedEvent. This class is
+ * different to my other generators as it requires a OadrDistrubuteEvent object.
+ * This class also has some logic in deciding whether to opt into events or not.
+ *
+ * @author robert
+ * @version 1.0
+ */
+public class OadrCreatedEventGenerator {
+
+ /**
+ *
+ * @param params
+ * @param event
+ * @return Payload with a OadrCreatedEvent
+ */
+ public OadrPayload createPayload(OadrParams params, OadrDistributeEvent event) {
+ //NOTE HARDCODED FOR ONE EVENT
+ EventDescriptor eventParams = event.getOadrEvents().get(0).getEiEvent().getEventDescriptor();
+ String eventID = eventParams.getEventID();
+ Long modNum = eventParams.getModificationNumber();
+ String requestId = event.getRequestID();
+
+ //we have received and event
+ OadrPayload payload = new OadrPayload();
+ OadrSignedObject signedObject = new OadrSignedObject();
+ OadrCreatedEvent createdEvent = new OadrCreatedEvent();
+ payload.setOadrSignedObject(signedObject.withOadrCreatedEvent(createdEvent));
+
+ createdEvent.setSchemaVersion(params.getProfileName());
+ EiCreatedEvent eiCreatedEvent = new EiCreatedEvent();
+ eiCreatedEvent.setVenID(params.getVenID());
+
+ EiResponse eiResponse = new EiResponse();
+ eiResponse.setRequestID(requestId);//check that is not from event
+ eiResponse.setResponseCode(new ResponseCode().withValue("200"));
+
+ eiCreatedEvent.setEiResponse(eiResponse);
+
+ EventResponse eventResponse = new EventResponse();
+
+ eventResponse.setOptType(OptInLogic(params, event.getOadrEvents().get(0)));
+ eventResponse.setQualifiedEventID(
+ new QualifiedEventID().withEventID(eventID).withModificationNumber(modNum));
+
+ EventResponses eventResponses = new EventResponses();
+ eventResponses.getEventResponses().add(eventResponse);
+ eiCreatedEvent.setEventResponses(eventResponses);
+
+ createdEvent.setEiCreatedEvent(eiCreatedEvent);
+
+ return payload;
+ }
+
+ //Feel free to overwrite this method to change opt in behavior.
+ /**
+ * This method is used to decide whether to opt into the event or not it
+ * should not be used for applying a demand response instead put that logic
+ * in MocVen.respondToDistributeEvent()
+ *
+ * @param params
+ * @param event
+ * @return
+ */
+ public OptTypeType OptInLogic(OadrParams params, OadrEvent event) {
+
+ //ensure that the signals are valid if not opt out. This is the behavior of the EPRI VEN
+ for ( EiEventSignal eventSig : event.getEiEvent().getEiEventSignals().getEiEventSignals() ) {
+ if ( !validSignalNameAndType(eventSig) ) {
+ return OptTypeType.OPT_OUT;
+ }
+ }
+ if ( eventOver(event) ) {
+ return OptTypeType.OPT_OUT;
+ }
+
+ //proof of concept showing how to select a specific signalName
+ if ( event.getEiEvent().getEiEventSignals().getEiEventSignals().get(0).getSignalName()
+ .equals(SignalNameEnumeratedType.LOAD_CONTROL.toString()) ) {
+ return OptTypeType.OPT_IN;
+ }
+ return OptTypeType.OPT_IN;
+ }
+
+ /**
+ * Returns true if the EiEventSignal has a singal name compatible with the
+ * signal type. This is a translation of the C# implementation in the EPRI
+ * mock VEN.
+ *
+ * @param event
+ * @return
+ */
+ public boolean validSignalNameAndType(EiEventSignal event) {
+ String singalName = event.getSignalName();
+ SignalTypeEnumeratedType signalType = event.getSignalType();
+
+ SignalNameEnumeratedType sigName = SignalNameEnumeratedType.valueOf(singalName);
+
+ switch (sigName) {
+ case simple:
+ if ( signalType.equals(SignalTypeEnumeratedType.LEVEL) ) {
+ break;
+ }
+ return false;
+ case SIMPLE:
+ if ( signalType.equals(SignalTypeEnumeratedType.LEVEL) ) {
+ break;
+ }
+ return false;
+ case ELECTRICITY_PRICE:
+ if ( signalType.equals(SignalTypeEnumeratedType.PRICE)
+ || signalType.equals(SignalTypeEnumeratedType.PRICE_RELATIVE)
+ || signalType.equals(SignalTypeEnumeratedType.PRICE_MULTIPLIER) ) {
+ break;
+ }
+ return false;
+ case ENERGY_PRICE:
+ if ( signalType.equals(SignalTypeEnumeratedType.PRICE)
+ || signalType.equals(SignalTypeEnumeratedType.PRICE_RELATIVE)
+ || signalType.equals(SignalTypeEnumeratedType.PRICE_MULTIPLIER) ) {
+ break;
+ }
+ return false;
+ case DEMAND_CHARGE:
+ if ( signalType.equals(SignalTypeEnumeratedType.PRICE)
+ || signalType.equals(SignalTypeEnumeratedType.PRICE_RELATIVE)
+ || signalType.equals(SignalTypeEnumeratedType.PRICE_MULTIPLIER) ) {
+ break;
+ }
+ return false;
+ case BID_PRICE:
+ if ( signalType.equals(SignalTypeEnumeratedType.PRICE) ) {
+ break;
+ }
+ return false;
+ case BID_LOAD:
+ if ( signalType.equals(SignalTypeEnumeratedType.SETPOINT) ) {
+ break;
+ }
+ return false;
+ case BID_ENERGY:
+ if ( signalType.equals(SignalTypeEnumeratedType.SETPOINT) ) {
+ break;
+ }
+ return false;
+ case CHARGE_STATE:
+ if ( signalType.equals(SignalTypeEnumeratedType.SETPOINT)
+ || signalType.equals(SignalTypeEnumeratedType.DELTA)
+ || signalType.equals(SignalTypeEnumeratedType.MULTIPLIER) ) {
+ break;
+ }
+ return false;
+ case LOAD_DISPATCH:
+ if ( signalType.equals(SignalTypeEnumeratedType.SETPOINT)
+ || signalType.equals(SignalTypeEnumeratedType.DELTA)
+ || signalType.equals(SignalTypeEnumeratedType.MULTIPLIER)
+ || signalType.equals(SignalTypeEnumeratedType.LEVEL) ) {
+ break;
+ }
+ return false;
+ case LOAD_CONTROL:
+ if ( signalType.equals(SignalTypeEnumeratedType.X_LOAD_CONTROL_CAPACITY)
+ || signalType.equals(SignalTypeEnumeratedType.X_LOAD_CONTROL_LEVEL_OFFSET)
+ || signalType.equals(SignalTypeEnumeratedType.X_LOAD_CONTROL_PERCENT_OFFSET)
+ || signalType.equals(SignalTypeEnumeratedType.X_LOAD_CONTROL_SETPOINT) ) {
+ break;
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * returns true if the OadrEvent has already ended. A event is ended when
+ * the starting time plus the duration is less than the current time. This
+ * method is included to match the behaviour
+ *
+ * @param event
+ * @return
+ */
+ public boolean eventOver(OadrEvent event) {
+
+ GregorianCalendar now = (GregorianCalendar) GregorianCalendar.getInstance();
+
+ String durationString = event.getEiEvent().getEiActivePeriod().getProperties().getDuration()
+ .getDuration().getValue();
+
+ GregorianCalendar eventdate = event.getEiEvent().getEiActivePeriod().getProperties().getDtstart()
+ .getDateTime().getValue().toGregorianCalendar();
+
+ Duration d = Duration.parse(durationString);
+
+ eventdate.add(Calendar.SECOND, (int) d.getSeconds());
+
+ int dateCompare = eventdate.compareTo(now);
+ if ( dateCompare > 0 ) {
+ //event still has some time to go
+ return true;
+ } else {
+ //event has already been completed
+ return false;
+ }
+
+ }
+
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrParams.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrParams.java
new file mode 100644
index 000000000..d15ad0e8e
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrParams.java
@@ -0,0 +1,128 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+import openadr.model.v20b.OadrTransportType;
+
+/**
+ * This abstract class just has a bunch of getters and setters of parameters
+ * that often go into OadrPayloads. These are used in the generators of the
+ * various payloads.
+ *
+ * @author robert
+ * @version 1.0
+ */
+public class OadrParams {
+
+ private String venName;
+
+ private String venID;
+ private String registrationID;
+ private String requestID;
+
+ private String vtnID;
+ private int modificationNumber;
+ private String eventID;
+
+ //default values I copied from the EPRI VEN
+ private OadrTransportType transportType = OadrTransportType.SIMPLE_HTTP;
+ private String profileName = "2.0b";
+ private Boolean reportOnly = false;
+ private Boolean xmlSignature = false;
+ private Boolean httpPullModel = true;
+
+ public String getVenName() {
+ return venName;
+ }
+
+ public void setVenName(String venName) {
+ this.venName = venName;
+ }
+
+ public String getVenID() {
+ return venID;
+ }
+
+ public void setVenID(String venID) {
+ this.venID = venID;
+ }
+
+ public String getRegistrationID() {
+ return registrationID;
+ }
+
+ public void setRegistrationID(String registrationID) {
+ this.registrationID = registrationID;
+ }
+
+ public String getRequestID() {
+ return requestID;
+ }
+
+ public void setRequestID(String requestID) {
+ this.requestID = requestID;
+ }
+
+ public OadrTransportType getTransportName() {
+ return transportType;
+ }
+
+ public void setTransportName(OadrTransportType transportName) {
+ this.transportType = transportName;
+ }
+
+ public String getProfileName() {
+ return profileName;
+ }
+
+ public void setProfileName(String profileName) {
+ this.profileName = profileName;
+ }
+
+ public Boolean isReportOnly() {
+ return reportOnly;
+ }
+
+ public void setReportOnly(boolean reportOnly) {
+ this.reportOnly = reportOnly;
+ }
+
+ public Boolean isXmlSignature() {
+ return xmlSignature;
+ }
+
+ public void setXmlSignature(boolean xmlSignature) {
+ this.xmlSignature = xmlSignature;
+ }
+
+ public Boolean isHttpPullModel() {
+ return httpPullModel;
+ }
+
+ public void setHttpPullModel(boolean httpPullModel) {
+ this.httpPullModel = httpPullModel;
+ }
+
+ public String getVtnID() {
+ return vtnID;
+ }
+
+ public void setVtnID(String vtnID) {
+ this.vtnID = vtnID;
+ }
+
+ public int getModificationNumber() {
+ return modificationNumber;
+ }
+
+ public void setModificationNumber(int modificationNumber) {
+ this.modificationNumber = modificationNumber;
+ }
+
+ public String getEventID() {
+ return eventID;
+ }
+
+ public void setEventID(String eventID) {
+ this.eventID = eventID;
+ }
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrPayloadGenerator.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrPayloadGenerator.java
new file mode 100644
index 000000000..eda5cc877
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrPayloadGenerator.java
@@ -0,0 +1,35 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+import java.util.Random;
+
+/**
+ * Abstract class containing useful functions often used in OadrPayload
+ * generators
+ *
+ * @author robert
+ * @version 1.0
+ */
+public abstract class OadrPayloadGenerator {
+
+ Random rand = new Random();
+
+ //returns a hex string 10 characters in length used for generating request IDs
+ public String genRandomRequestID() {
+ StringBuffer sb = new StringBuffer();
+ for ( int i = 0; i < 10; i++ ) {
+ //this produces lowercase letters
+ sb.append(Integer.toHexString(rand.nextInt(16)));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Dependency Injection for increased controllability to allow testing.
+ *
+ * @param newrandom
+ */
+ public void setRandom(Random newrandom) {
+ rand = newrandom;
+ }
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrPollGenerator.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrPollGenerator.java
new file mode 100644
index 000000000..c3381f1a9
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrPollGenerator.java
@@ -0,0 +1,32 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+import openadr.model.v20b.OadrPayload;
+import openadr.model.v20b.OadrPoll;
+import openadr.model.v20b.OadrSignedObject;
+
+/**
+ *
+ * Creates a OadrPayload containing a OadrPoll. An OadrPoll is used to ask the
+ * VTN has anything happened and to ensure there is still communication with the
+ * VTN.
+ *
+ * @author robert
+ * @version 1.0
+ */
+public class OadrPollGenerator extends OadrPayloadGenerator {
+
+ public OadrPayload createPayload(OadrParams params) {
+ OadrPayload payload = new OadrPayload();
+ OadrSignedObject signedObject = new OadrSignedObject();
+ OadrPoll poll = new OadrPoll();
+
+ payload.withOadrSignedObject(signedObject.withOadrPoll(poll));
+
+ //These values should be set from the registration
+ poll.setVenID(params.getVenID());
+ poll.setSchemaVersion(params.getProfileName());
+
+ return payload;
+ }
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrQueryRegistrationGenerator.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrQueryRegistrationGenerator.java
new file mode 100644
index 000000000..9c4ae7bb1
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrQueryRegistrationGenerator.java
@@ -0,0 +1,32 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+import openadr.model.v20b.OadrPayload;
+import openadr.model.v20b.OadrQueryRegistration;
+import openadr.model.v20b.OadrSignedObject;
+
+/**
+ *
+ * Creates a OadrPayload with a OadrQueryRegistration used in registering with
+ * the VTN
+ *
+ * @author robert
+ * @version 1.0
+ */
+public class OadrQueryRegistrationGenerator extends OadrPayloadGenerator {
+
+ public OadrPayload createPayload(OadrParams params) {
+ OadrPayload payload = new OadrPayload();
+ OadrSignedObject signedObject = new OadrSignedObject();
+ OadrQueryRegistration queryReg = new OadrQueryRegistration();
+ payload.withOadrSignedObject(signedObject.withOadrQueryRegistration(queryReg));
+
+ queryReg.setRequestID(genRandomRequestID());
+
+ //TODO I not sure if profilename and schema version are the same thing from what I saw it is always "2.0b"
+ queryReg.setSchemaVersion(params.getProfileName());
+
+ return payload;
+
+ }
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrSubDomains.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrSubDomains.java
new file mode 100644
index 000000000..7b218ebc1
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/OadrSubDomains.java
@@ -0,0 +1,17 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+/**
+ *
+ * Class containing static constants of the URL subdomains in OpenADR requests
+ *
+ * @author robert
+ * @version 1.0
+ */
+public class OadrSubDomains {
+
+ public static final String EiRegisterParty = "EiRegisterParty";
+ public static final String EiReport = "EiReport";
+ public static final String EiEvent = "EiEvent";
+ public static final String OadrPoll = "OadrPoll";
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/PollTrigger.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/PollTrigger.java
new file mode 100644
index 000000000..0a240d983
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/PollTrigger.java
@@ -0,0 +1,32 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+import java.util.Date;
+import org.quartz.impl.triggers.SimpleTriggerImpl;
+
+/**
+ *
+ * FIXME
+ *
+ * This class currently does not do what I want it to do. I want to be able to
+ * poll the VTN every 10 seconds. Currently instead I am using a CronTrigger
+ * which limits my polling to every minute or second.
+ *
+ * @author robert
+ * @version 1.0
+ */
+public class PollTrigger extends SimpleTriggerImpl {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 5752379851134828607L;
+
+ public PollTrigger() {
+ setStartTime(new Date());
+ setEndTime(null);
+ setRepeatInterval(10000L);
+ setRepeatCount(REPEAT_INDEFINITELY);
+
+ }
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/SignalNameEnumeratedType.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/SignalNameEnumeratedType.java
new file mode 100644
index 000000000..279ba620d
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/SignalNameEnumeratedType.java
@@ -0,0 +1,44 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+/**
+ *
+ * enum of the OpenADR signal names. perhaps this should be moved into the
+ * OpenADR package. Followed the design pattern of SignalTypeEnumeratedType
+ *
+ * @author robert
+ * @version 1.0
+ */
+public enum SignalNameEnumeratedType {
+ SIMPLE("SIMPLE"),
+ simple("simple"),
+ ELECTRICITY_PRICE("ELECTRICITY_PRICE"),
+ ENERGY_PRICE("ENERGY_PRICE"),
+ DEMAND_CHARGE("DEMAND_CHARGE"),
+ BID_PRICE("BID_PRICE"),
+ BID_LOAD("BID_LOAD"),
+ BID_ENERGY("BID_ENERGY"),
+ CHARGE_STATE("CHARGE_STATE"),
+ LOAD_DISPATCH("LOAD_DISPATCH"),
+ LOAD_CONTROL("LOAD_CONTROL");
+
+ private final String value;
+
+ SignalNameEnumeratedType(String v) {
+ value = v;
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ public static SignalNameEnumeratedType fromValue(String v) {
+ for ( SignalNameEnumeratedType c : SignalNameEnumeratedType.values() ) {
+ if ( c.value.equals(v) ) {
+ return c;
+ }
+ }
+ throw new IllegalArgumentException(v);
+ }
+}
diff --git a/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/TopNodeConnection.java b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/TopNodeConnection.java
new file mode 100644
index 000000000..7ff8c2d3e
--- /dev/null
+++ b/net.solarnetwork.node.openadr.mockven/src/net/solarnetwork/node/openadr/mockven/TopNodeConnection.java
@@ -0,0 +1,84 @@
+
+package net.solarnetwork.node.openadr.mockven;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+import org.xml.sax.InputSource;
+import openadr.model.v20b.OadrPayload;
+import openadr.model.v20b.ObjectFactory;
+
+/**
+ *
+ * Class that handles the connection to the VTN. Supports posting OadrPayloads
+ * getting the response.
+ *
+ * @author robert
+ * @version 1.0
+ */
+public class TopNodeConnection {
+
+ private HttpHeaders headers;
+ private RestTemplate rest;
+ private JAXBContext jaxbContext;
+ private Marshaller marshaller;
+
+ public TopNodeConnection() {
+ headers = new HttpHeaders();
+ rest = new RestTemplate();
+ headers.setContentType(MediaType.TEXT_XML);
+
+ //Require a class loader when using OSGI otherwise you get JAXBException
+ //https://stackoverflow.com/a/1043807
+ ClassLoader cl = ObjectFactory.class.getClassLoader();
+ try {
+ jaxbContext = JAXBContext.newInstance("openadr.model.v20b:" + "openadr.model.v20b.atom:"
+ + "openadr.model.v20b.currency:" + "openadr.model.v20b.ei:"
+ + "openadr.model.v20b.emix:" + "openadr.model.v20b.gml:"
+ + "openadr.model.v20b.greenbutton:" + "openadr.model.v20b.power:"
+ + "openadr.model.v20b.pyld:" + "openadr.model.v20b.siscale:"
+ + "openadr.model.v20b.strm:" + "openadr.model.v20b.xcal:"
+ + "openadr.model.v20b.xmldsig:" + "openadr.model.v20b.xmldsig11", cl);
+
+ marshaller = jaxbContext.createMarshaller();
+ } catch ( JAXBException e ) {
+ //have no way of recovering from exception. So I turn it into a RuntimeException so I don't have to declare that this class throws and exception
+ throw new RuntimeException(e);
+ }
+ }
+
+ //I assume I have to synchronize this method to prevent trying to connect at the same time. Never tested multipal connections.
+ public synchronized OadrPayload postPayload(String url, OadrPayload payload) {
+ StringWriter out = new StringWriter();
+ try {
+ //turns the payload into a XML string and writes it to the StringWriter
+ marshaller.marshal(payload, out);
+
+ //send the xml to the VTN and get the response
+ ResponseEntity response = rest.postForEntity(url, out.toString(), String.class);
+
+ Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
+
+ //convert the response into a OadrPayload object
+ OadrPayload returnPayload = (OadrPayload) jaxbUnmarshaller
+ .unmarshal(new InputSource(new StringReader(response.getBody())));
+
+ //TODO remove print statment
+ System.out.println(response.getBody());
+ return returnPayload;
+
+ } catch ( JAXBException e ) {
+ //have no way of recovering from exception. So I turn it into a RuntimeException so I don't have to declare that this class throws and exception
+ throw new RuntimeException(e);
+ }
+
+ }
+
+}