NEESgrid logo

Writing your own NTCP control plugin


If you have an unusual/unsupported control system, you may need to write your own control plugin to handle it. There's a lot of information you'll need in the plugin API specification. This page is just elaborating on what's in the specification.

If you need to interface to a control system written in C, please see this page about the C gateway plugin.

Things to understand

First off, the nclient code is procedural, by design. It has a distinct start, middle, and end. The flow of control is consistent and one-directional. NTCP plugins, however, use a callback design, where you provide methods that are called by the NTCP server implementation as required. Akin to writing message callbacks for a windowing application, this requires a bit more understanding of NTCP and more thought. For example, what should your code do if setParameter is called before openSession?

It needs to be said that a control plugin should be your most carefully written code, since it will control devices potentially capable of large kinetic energy, dollars, life, etc.

A word about policy plugins

Policy plugins can be used for access control, and other things I don't yet understand. Since I don't grok 'em, I'm going to skip pontificating until I know more.

Another word about PluginFactories

These are used to instantiate control plugins, and as far as I understand you can just cut and paste from the examples.

Always start operations with an inert Dummy

We're going to start looking at the dummy plugin we've used for testing. Open
$HOME/code/NTCP/server/src/org/nees/ntcp/server/backend/test/DummyControlPlugin.java
in your favorite editor, preferably one with syntax highlighting.

Off we go!

Header, import and class declaration

package org.nees.ntcp.server.backend.test;

import java.util.Vector;
import org.nees.ntcp.ntcpServer.ParameterType;
import org.nees.ntcp.ntcpServer.GeomAxisType;
import org.nees.ntcp.ntcpServer.ControlPointType;
import org.nees.ntcp.ntcpServer.ControlPointParameterNameType;
import org.nees.ntcp.ntcpServer.ParameterType;
import org.nees.ntcp.ntcpServer.ControlPointGeomParameterType;
import org.nees.ntcp.server.backend.ControlPluginTransactionHandle;
import org.nees.ntcp.server.backend.ControlPluginTransactionListener;
import org.nees.ntcp.server.backend.LocalControlPlugin;
import org.nees.ntcp.server.backend.ServerStateAccessor;
import org.nees.ntcp.server.backend.ControlPluginException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class DummyControlPlugin implements LocalControlPlugin
{
The imports will be the same for any control plugin, plus whatever your system requires. Note the package name, and as before, the code path will need to match the package name.

The class declaration tells Java that we are implementing a LocalControlPlugin; this is key to giving the code the NTCP imprimatur.

All methods except getControlPoint

I'm going to include most of the code in one big block, as it's all the same pattern - the method does no work other than logging the request and returning. As we'll see in a second, getControlPoint is the only one with non-trivial code in it.
     static Log log = LogFactory.getLog(DummyControlPlugin.class.getName());

/**
* Constructor for DummyControlPlugin.
*/
public DummyControlPlugin() {
super();
}

public void openSession(Vector parameters)
throws ControlPluginException
{
log.debug("openSession");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {

}
}

public void closeSession()
throws ControlPluginException
{
log.debug("closeSession");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {

}
}

public boolean propose(ControlPluginTransactionHandle transaction)
throws ControlPluginException
{
log.debug("propose");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {

}
if (transaction.getName().startsWith("rejectMe")) {
log.debug("rejecting transaction " + transaction.getName());
return false;
} else {
log.debug("accepting transaction " + transaction.getName());
return true;
}
}

public void execute(ControlPluginTransactionHandle transaction)
throws ControlPluginException
{
log.debug("execute");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {

}
}

public void cancel(ControlPluginTransactionHandle transaction)
throws ControlPluginException
{
log.debug("cancel");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {

}
}

public Vector getParameter(Vector names)
throws ControlPluginException
{
log.debug("getParameter");
return null;
}

public void setParameter(Vector parameters)
throws ControlPluginException
{
log.debug("setParameter");
}
I assume the sleep calls are just there to simulate doing work.

getControlPoint

This method builds a vector of two fake parameters (X, displacement 1.0, Y rotation 2.0), puts them into an array, and returns it. Useful code to understand constructing these vectorized structures.

  public Vector getControlPoint(Vector names) 
throws ControlPluginException
{
log.debug("getControlPoint");
String myControlPointName = "dummy";
Vector out = new Vector();
for (int i = 0; i < names.size(); i++) {
if (((String)names.elementAt(i)).equals(myControlPointName)) {
ControlPointGeomParameterType param1 =
new ControlPointGeomParameterType();
param1.setName(ControlPointParameterNameType.displacement);
param1.setValue(new Float(1.0));
param1.setAxis(GeomAxisType.x);

ControlPointGeomParameterType param2 =
new ControlPointGeomParameterType();
param2.setName(ControlPointParameterNameType.rotation);
param2.setValue(new Float(2.0));
param2.setAxis(GeomAxisType.y);

ControlPointGeomParameterType[] geomParams =
new ControlPointGeomParameterType[] { param1, param2 };

ControlPointType controlPoint = new ControlPointType();
controlPoint.setControlPointName(myControlPointName);
controlPoint.setControlPointType(geomParams);
out.add(controlPoint);
}
}
return out;
}
}

So what have we learned from the Dummy?

... I hear you ask. Well, a bit of the required infrastructure, and that a basic plugin can be very, very simple. Now let's look at a more complicated example, the LabVIEW plugin.

The Misnamed LabVIEW Plugin

This plugin, which really needs a better name, is not really specific to LabVIEW at all. All it does is convert NTCP calls into ASCII, and back again. It was written for LabVIEW, but has been reused to do odd things such as control digital cameras. Given its simple nature as a serialization engine, it makes a decent example to visit.

The examples in this section are from the initial version of the code, circa v2.2 of the NEESGrid distribution. Note that this plugin has been completely rewritten since then, so the code is now totally different! I've left this section here for illustrative purposes, but please consult the source code to see the new code - it's much cleaner.

The entire file is too long to put here, so we'll just look at a few methods. Open
 $HOME/code/ntcp_plugins/labview/src/org/nees/ntcp/plugins/labview/LabviewNTCPPlugin.java
in your editor of choice.

Startup and TCP connections

In the constructor, we use the lvPort and lvHost defined in the server-config.wsdd to open a TCP connection to the control system. The 'stiffness vector' is code we added to handle the initial get/setParameter, as the LabVIEW system ignores both. This is vestigal and will need to be removed.
    public LabviewNTCPPlugin(String hostIp,int port)
throws ControlPluginException {
try {
//Initialize the plugin with ip and port on which labview shell is listening
this.hostIp = hostIp;
this.port = port;
logger.debug("Initiating socket connection to " + hostIp);
logger.debug(" on host: " + hostIp + " and port " + port);
connect();
controlPointStiffnessVector = new Vector();
}catch(Exception e) {
throw new ControlPluginException("Exception raised when initializing the socket");
}
}

public void connect()
throws ControlPluginException {
try {
this.lbSocket = new Socket(this.hostIp,this.port);
} catch (UnknownHostException uhe) {
logger.error("UnknownHostException while connecting to the host: "
+ hostIp,uhe);
} catch (IOException ioe) {
logger.error("IOException in constructor ", ioe);
throw new ControlPluginException("Exception raised when connecting to shell");

}
//aded
try{
lbPStream = new PrintStream(lbSocket.getOutputStream());
lbReader = new BufferedReader(new InputStreamReader (lbSocket.getInputStream()));
}catch(IOException ioe){
logger.error("IOException in constructor ", ioe);
throw new ControlPluginException("Exception raised when connecting to shell");
}
}

A simple method: closeSession

    public void executeCloseSession(byte[] command) {
if (lbSocket != null) {
try {
logger.debug("sending request: " + protoString(command));
lbSocket.getOutputStream().write(command);
lbSocket.getOutputStream().flush();
} catch(IOException ioe) {
ioe.printStackTrace();
}
}
}
This simply sends the argument string to the control system, and then returns.

The other methods

For rev 1.0 of this document, I'm going to have to handwave the rest of the methods in this plugin due to space and time constraints. Given that the code no longer looks like this, I think we're better served by having readers look at it themselves.

Where to go from here?

At this point, you should have the tools in place to work with NTCP: clients, control plugins, as well as policy plugins. Where to head from here depends on what you need to do with NTCP. A good starting project would be to take the dummy plugin, add more logging, change the package name and add it to the build process, then deploy it and run it.

Have fun, and good luck!