Skip to content

OpenADR Mock VEN #4

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions net.solarnetwork.node.openadr.mockven/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="output" path="build/eclipse"/>
</classpath>
2 changes: 2 additions & 0 deletions net.solarnetwork.node.openadr.mockven/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/build
/target
28 changes: 28 additions & 0 deletions net.solarnetwork.node.openadr.mockven/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>net.solarnetwork.node.openadr.mockven</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
eclipse.preferences.version=1
pluginProject.equinox=false
pluginProject.extensions=false
resolve.requirebundle=false
28 changes: 28 additions & 0 deletions net.solarnetwork.node.openadr.mockven/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -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
120 changes: 120 additions & 0 deletions net.solarnetwork.node.openadr.mockven/OSGI-INF/blueprint/module.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ctx="http://www.springframework.org/schema/context"
xmlns:osgix="http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="
http://www.osgi.org/xmlns/blueprint/v1.0.0
http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium
http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium/gemini-blueprint-compendium.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">

<!-- provides access to local database storage for our datum -->
<!-- <bean id="generalNodeDatumDao" class="net.solarnetwork.util.DynamicServiceTracker">
<property name="bundleContext" ref="bundleContext"/>
<property name="serviceClassName" value="net.solarnetwork.node.dao.DatumDao"/>
<property name="serviceFilter" value="(datumClassName=net.solarnetwork.node.domain.GeneralNodeDatum)"/>
</bean> -->

<!-- support localized strings for the settings in the GUI -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames" value="net.solarnetwork.node.openadr.mockven.MockVenJobService"/>
</bean>

<!-- support localized strings for the periodic job settings in the GUI -->
<bean id="jobMessageSource" class="net.solarnetwork.node.util.PrefixedMessageSource">
<property name="prefix" value="jobService."/>
<property name="delegate" ref="messageSource"/>
</bean>

<!-- publish a "component factory" so our MockVen appears in the settings GUI -->
<service interface="net.solarnetwork.node.settings.SettingSpecifierProviderFactory">
<bean class="net.solarnetwork.node.settings.support.BasicSettingSpecifierProviderFactory">
<property name="displayName" value="OpenADR Mock Virtual End Node"/>
<property name="factoryUID" value="net.solarnetwork.node.openadr.mockven"/>
<property name="messageSource" ref="messageSource"/>
</bean>
</service>

<!-- publish a "service factory" that each MockVen instance can be
configured in the GUI, along with a periodic job to collect datum from it -->
<osgix:managed-service-factory factory-pid="net.solarnetwork.node.openadr.mockven" autowire-on-update="true">
<osgix:interfaces>
<beans:value>net.solarnetwork.node.job.ManagedTriggerAndJobDetail</beans:value>
<beans:value>net.solarnetwork.node.job.ServiceProvider</beans:value>
<beans:value>net.solarnetwork.node.settings.SettingSpecifierProvider</beans:value>
</osgix:interfaces>
<osgix:service-properties>
<beans:entry key="settingPid" value="net.solarnetwork.node.openadr.mockven"/>
</osgix:service-properties>
<bean class="net.solarnetwork.node.job.SimpleManagedTriggerAndJobDetail">

<!-- the trigger defines when the periodic job runs; in this case
we define a cron style trigger, that by default runs once/minute -->
<property name="trigger">
<bean class="net.solarnetwork.node.job.RandomizedCronTriggerFactoryBean">
<property name="name" value="mockvenPowerDatumLoggerTrigger"/>
<property name="cronExpression" value="5 * * * * ?"/>
<property name="misfireInstructionName" value="MISFIRE_INSTRUCTION_DO_NOTHING"/>
<property name="randomSecond" value="true"/>
</bean>

<!-- <bean class = "net.solarnetwork.node.openadr.mockven.PollTrigger">
<property name="name" value="mockvenPowerDatumLoggerTrigger"/>
</bean> -->


</property>

<!-- we can also publish the MockVenDatumDataSource instance itself as a service
by configuring serviceProviderConfigurations here... this is optional but
generally a good idea -->
<!-- <property name="serviceProviderConfigurations"> -->
<!-- <map> -->
<!-- <entry key="datumDataSource"> -->
<!-- <bean class="net.solarnetwork.node.job.SimpleServiceProviderConfiguration"> -->
<!-- <property name="interfaces"> -->
<!-- <list> -->
<!-- <value>net.solarnetwork.node.DatumDataSource</value> -->
<!-- </list> -->
<!-- </property> -->
<!-- <property name="properties"> -->
<!-- <map> -->
<!-- <entry key="datumClassName" value="net.solarnetwork.node.domain.EnergyDatum"/> -->
<!-- </map> -->
<!-- </property> -->
<!-- </bean> -->
<!-- </entry> -->
<!-- </map> -->
<!-- </property> -->


<!-- the jobDetail defines what job should be executed periodically by the
trigger; here we define a net.solarnetwork.node.job.DatumDataSourceManagedLoggerJob
job which will invoke the readCurrentDatum() method on a DatumDataSource, which
is what MockVenDatumDataSource is! -->
<property name="jobDetail">
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="mockvenPowerDatumLoggerJob"/>
<property name="jobClass" value="net.solarnetwork.node.job.SimpleServiceJob"/>
<property name="jobDataAsMap">
<map>
<!-- <entry key="datumDao" value-ref="generalNodeDatumDao"/> -->
<entry key="service">
<bean class="net.solarnetwork.node.openadr.mockven.MockVenJobService">
<property name="messageSource" ref="jobMessageSource"/>
</bean>
</entry>
</map>
</property>
</bean>
</property>
</bean>
</osgix:managed-service-factory>

</blueprint>
4 changes: 4 additions & 0 deletions net.solarnetwork.node.openadr.mockven/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source.. = src/
output.. = build/eclipse/
bin.includes = META-INF/,\
.
Original file line number Diff line number Diff line change
@@ -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");
}
}

}
Loading