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!