In this tutorial you will be introduced to the EIA-709 extensions for JControl. The first section will provide a brief overview of the EIA-709 standard and the functionality of network variables. Section 2 will introduce the JControl EIA-709 API by means of a sample program. The tutorial will conclude with more programming examples illustrating more advanced techniques.
The communication protocol EIA-709.1 is a flexible field bus protocol. It was developed and commercialized by Echelon under the name LonTalk and later turned into an EIA standard. After some reorganization within the Electronic Industries Association, the standard has been renamed to ANSI/CEA-709.1. However, the term EIA-709.1 is still widely used as of today and often even shortened to EIA-709 when referring to the protocol. As you will notice, this is what we are going to do as well throughout this tutorial.
The EIA-709 protocol offers a lot of benefits compared to similar protocols, one of the key aspects is the sophisticated configuration options of EIA-709 networks. However, for this tutorial it is not required to know extensive details on these protocol. Instead we will focus on a more palpable innovation of the protocol: the network variables.
The network variables follow a very simple concept. They add what variables are in normal programming to field bus communication. It is possible to read and write network variables not only locally, but also remotely over the network. The logic behind this is that each node in the network defines a set of variables it would like to share with other devices. A network variable in EIA-709 can either be incoming or outgoing. In the first case, the node expects this variable to be changed by remote nodes. An example for this would be a lamp switch, that allows for turning the lamp on/off remotely. The second class of variables define outgoing variables. Outgoing variables are used to report something to other nodes. For instance, a weather station would use outgoing variables to provide the current temperature, air pressure or humidity for other devices.
EIA-709 allows two different methods of transferring network variables. An outgoing variable that uses the sync method will submit changes to the network immediately, giving it an event-like character, which is useful to react to button presses or similar situations. The polled method will not propagate new values automatically. Instead, such network variables must be polled by the external nodes. The fictive weather station would possibly use this mode for its network variables, so that remote stations can poll the current values whenever needed.
In the following section, we will look at the example of a simple network lamp. It will provide an incoming and an outgoing network variable. The incoming variable will be used to turn the lamp on or off, the outgoing variable serves as a status report. Accompanying we will examine the implementation of such a lamp for the JControl EIA-709 framework.
In this section you will learn how to implement EIA-709 enabled applications using the JControl API. In Section 2.2 you will get an insight into the API structure, Section 2.1 will present you an example application and the following sections will discuss the different sections of the example program and introduce the different API functions in more detail.
The JControl API for EIA-709 features a comfortable network variable layer that abstracts away the protocol details. The components an application programmer is being exposed to are:
Nodes,
Controllets,
Network Variables, and
Addresses.
In order for an application to take part in network communication,
it must first create one or more nodes. A node
represents an autonomous communication partner in a network, for other
communication partners in the network each node will appear as an
individual device with its own address configuration as shown in Figure 1. Virtual EIA-709 nodes are represented by
class Eia709Node
(see Section 2.3).
The demo version of the JControl EIA-709 implementation supports only one node.
Each virtual node can run one controllet.
Controllets are mini applications that act in a network by communicating
with other nodes and by controlling local functions. The controllets are
represented by interface
Eia709Controllet
. A very basic controllet
could implement the behaviour of a lamp. It would wait for incoming
messages and turn on/off a light bulb accordingly. Additionally it could
report its status to other nodes. As illustrated previously, such a
communication is established through network
variables, which are represented by the class
Eia709NetworkVariable
. Each controllet has to
create all the network variables it wants to use. The lamp controllet
would define an incoming network variable the allows to turn on/off the
light remotely and an outgoing network variable to report status
information.
Figure 2 shows the basic setup of
the class LampDemo. The two discussed network variables are
nvi_Switch
and nvo_State
.
By creating network variables, we have defined the interface for
communication with other nodes in the network. What is still missing is
the binding to such other nodes. Therefore we have to assign an
address (class
Eia709Address
) to each outgoing network variable.
Whenever the network variable should be propagated, it will send its
value to the specified address.
To summarize, in order to create a JControl application to work in an EIA-709 network, we have to
Create and configure an Eia709Node
instance,
supply a controllet to the node,
create network variables,
and assign addresses and selectors to network variables.
Steps 3 and 4 have to be implemented inside the controllet.
In the previous section, we introduced the LampDemo, but until now we haven't written a single line of program code. We are going to make up for this now by having a look at the entire program. In the following sections we will go into the details of the various parts of the program.
1 | package jcontrol.demos.eia709.lampdemo; |
2 | |
3 | import java.io.IOException; |
4 | import jcontrol.comm.eia709.Eia709Address; |
5 | import jcontrol.comm.eia709.Eia709Controllet; |
6 | import jcontrol.comm.eia709.Eia709NetworkVariable; |
7 | import jcontrol.comm.eia709.Eia709Node; |
8 | import jcontrol.comm.nv.NetworkVariableEvent; |
9 | import jcontrol.comm.nv.NetworkVariableListener; |
10 | import jcontrol.demos.eia709.common.Eia709NetworkVariableSwitch; |
11 | import jcontrol.io.Console; |
12 | |
13 | /** |
14 | * The TestControllet can be used to toggle the Mini-Gizmo's LEDs. |
15 | * |
16 | * @author Lorenz Witte |
17 | */ |
18 | public class LampDemo implements Eia709Controllet, NetworkVariableListener { |
19 | |
20 | /** |
21 | * node this program runs on |
22 | */ |
23 | private Eia709Node m_node; |
24 | |
25 | /** |
26 | * Toggling this variable causes the lamp to be turned |
27 | * on/off. |
28 | */ |
29 | private Eia709NetworkVariableSwitch m_nviSwitch; |
30 | |
31 | /** |
32 | * This network variable reports the lamp state. |
33 | */ |
34 | private Eia709NetworkVariableSwitch m_nvoState; |
35 | |
36 | /** Supplies static binding information. */ |
37 | public void bind() { |
38 | // set nviSwitch selector |
39 | m_nviSwitch.setSelector( 1000); |
40 | |
41 | // set nvoState selector |
42 | m_nvoState.setSelector( 1001); |
43 | |
44 | // set address for nvo_State (domain broadcast, subnet: 20) |
45 | Eia709Address addr = Eia709Address.createAddressType0( m_node, 20); |
46 | m_nvoState.setAddress(addr); |
47 | } |
48 | |
49 | /** Creates network variables */ |
50 | public void configure(Eia709Node node) { |
51 | m_node = node; |
52 | |
53 | // create nviSwitch and attach listener |
54 | m_nviSwitch = new Eia709NetworkVariableSwitch( m_node, "nviSwitch", |
55 | Eia709NetworkVariable.MOD_DIR_INCOMING); |
56 | m_nviSwitch.setListener( this); |
57 | |
58 | // create nvoState |
59 | m_nvoState = new Eia709NetworkVariableSwitch( m_node, "nvoState", |
60 | Eia709NetworkVariable.MOD_DIR_OUTGOING | Eia709NetworkVariable.MOD_SYNC); |
61 | } |
62 | |
63 | /** Returns program name (8 bytes). */ |
64 | public byte[] getProgramName() { |
65 | return new byte[] { 'L', 'A', 'M', 'P', 'D', 'E', 'M', 'O'}; |
66 | } |
67 | |
68 | /** Starts the program. */ |
69 | public void start() {} |
70 | |
71 | /** Stops the program. */ |
72 | public void stop() {} |
73 | |
74 | /** Is called when the switch is toggled. */ |
75 | public void networkVariableChanged(NetworkVariableEvent event) { |
76 | try { |
77 | Console.out.println("setState to: "+m_nviSwitch.getState()); |
78 | m_nvoState.setState( m_nviSwitch.getState(), m_nviSwitch.getIntensity()); |
79 | } catch (IOException e) { |
80 | Console.out.println( "something odd happened!"); |
81 | } |
82 | } |
83 | |
84 | public static void main(String[] args) { |
85 | // create node |
86 | Eia709Node node = new Eia709Node( new byte[] { 'N', 'O', 'D', 'E', 'I', 'D'}); |
87 | |
88 | // set node address (domain: "JCNTRL", subnet: 20, node: 1) |
89 | node.setNodeAddress( new byte[] { 'J', 'C', 'N', 'T', 'R', 'L'}, 20, 1); |
90 | |
91 | // install controllet |
92 | node.setControllet( new LampDemo()); |
93 | |
94 | // start node |
95 | node.start(); |
96 | } |
97 | } |
The LampDemo simply waits for external accesses to the
nviSwitch
variable. Whenever it is accessed, the lamp
updates its nvo_State
variable accordingly. Since the
latter uses the sync propagation mode, changes
will be propagated to the network immediately.
The class Eia709Node
appears in the
LampDemo at several parts, most notably in the
main()
method. Let's have a closer look:
83 | public static void main(String[] args) { |
84 | // create node |
85 | Eia709Node node = new Eia709Node( new byte[] { 'N', 'O', 'D', 'E', 'I', 'D'}); |
86 | |
87 | // set node address (domain: "JCNTRL", subnet: 20, node: 1) |
88 | node.setNodeAddress( new byte[] { 'J', 'C', 'N', 'T', 'R', 'L'}, 20, 1); |
89 | |
90 | // install controllet |
91 | node.setControllet( new LampDemo()); |
92 | |
93 | // start node |
94 | node.start(); |
95 | } |
The first thing that happens in the program is that an Eia709Node instance is created. The constructor expects the called to supply a unique node ID or neuron ID. The unique node ID must be - as the name suggests - globally unique, that means that it may not be assigned to two different nodes in the entire network. You can see that the unique node ID of this example is not very well thought-out. IDs like this should be avoided in real-world applications.
The next step is that we assign an address to the node. In this
case we choose "JCNTRL"
as domain name, 20
as
subnet and 1
as node address. Following, the controllet is
instantiated and installed. The last step is to start the node. At this
point the node takes control of the application. The node instance is
passed over to the Controllet via the configure(Eia709Node
node)
method and is used subsequently for the creation of
network variables and addresses.
Table 2 shows
the methods provided by Eia709Node
. For in-depth
information, please refer to the JControl
API documentation.
Table 1. Class Eia709Node
Method | Description |
---|---|
Eia709Node ( byte[]
neuronId ); | Creates a new node with the given neuron ID. |
void
setNodeAddress ( byte[]
domain , int
subnet , int
node ) | Configures the node address consisting of a domain name, a subnet and a node address. |
void setControllet (
Eia709Controllet
program ) | Installs the controllet. |
void
start () | Starts node and controllet operation. |
void stop () | Stops node and controllet operation. |
void
addGroupMembership ( int
groupId , int
groupSize , int
memberId ) | Adds a group membership to this node. |
As already discussed in the previous sections, the network variables provide the main application interface. It is possible to change network variables as well as to react to network variable changes made by other parts of the program or by remote nodes.
Of course, we first have to create network variables. Network
variables logically belong to the controllet, so this is where we have
to create them. The interface
Eia709Controllet
therefore defines the
method configure
( Eia709Node
node
). Let's have a look at the
implementation in the LampDemo
:
50 | public void configure(Eia709Node node) { |
51 | m_node = node; |
52 | |
53 | // create nviSwitch and attach listener |
54 | m_nviSwitch = new Eia709NetworkVariableSwitch( m_node, "nviSwitch", |
55 | Eia709NetworkVariable.MOD_DIR_INCOMING); |
56 | m_nviSwitch.setListener( this); |
57 | |
58 | // create nvoState |
59 | m_nvoState = new Eia709NetworkVariableSwitch( m_node, "nvoState", |
60 | Eia709NetworkVariable.MOD_DIR_OUTGOING | Eia709NetworkVariable.MOD_SYNC); |
61 | } |
We can see that two network variables are created:
nviSwitch
and nvoState
, each
having different properties or "modifiers". The modifiers define the
behaviour of a network variable. In this case,
nviSwitch
is declared as incoming
(MOD_DIR_INCOMING
), nvo_State
is
declared as outgoing (MOD_DIR_OUTGOING
), using the
sync propagation mode
(MOD_SYNC
). Several other modifiers exists, but for
simplicity avoided in the LampDemo. Most notably, it is possible to
choose the transport service used for the
propagation of network variable values. For a full list of possible
modifiers, please refer to the JControl
API docs.
You probably have noticed, that the
Eia709Node
object is required. How does that
relate to the proposition that network variables are logically parts of
a controllet? Well, this is no contradiction. While a network variable
represents a part of the function interface of a controllet, it can only
operate on a node in order to transmit or receive variable updates.
Without a node, a network variable cannot provide any meaningful
functionality.
Likewise, a network variable must have a selector and - in case of
an outgoing network variable - an address. These attributes are part of
the binding information - they describe the linkage with other nodes -,
therefore they are supplied in the bind
()
method:
37 | public void bind() { |
38 | // set nviSwitch selector |
39 | m_nviSwitch.setSelector( 1000); |
40 | |
41 | // set nvoState selector |
42 | m_nvoState.setSelector( 1001); |
43 | |
44 | // set address for nvo_State (domain broadcast, subnet: 20) |
45 | Eia709Address addr = Eia709Address.createAddressType0( m_node, 20); |
46 | m_nvoState.setAddress(addr); |
47 | } |
As we can see, the LampDemo
assigns the
selectors 1000
and 1001
to its
two network variables. The address for the outgoing network variable
nvoState is a type 0
(broadcast) address targeting at subnet
20
.
Network variables require a selector and a node assignment to operate.
In order to stay updated on changes of
nviSwitch
, the LampDemo
installs itself as listener to this variable. Following, whenever the
nviSwitch
variable receives a write access, the
listener will be informed via the method networkVariableChanged(
NetworkVariableEvent event). In the implementation of this method, the
LampDemo simply copies the value from nviSwitch
to
nvoState
:
75 | public void networkVariableChanged(NetworkVariableEvent event) { |
76 | try { |
77 | Console.out.println("setState to: "+m_nviSwitch.getState()); |
78 | m_nvoState.setState( m_nviSwitch.getState(), m_nviSwitch.getIntensity()); |
79 | } catch (IOException e) { |
80 | Console.out.println( "something odd happened!"); |
81 | } |
82 | } |
As we remember, the network variable nvoState
was configured using the sync modifier. This
means that the value will automatically propagated on the network. You
will have noticed the usage of methods getState(), getIntensity() and
setState(). These are not part of the standard Eia709NetworkVariable,
but are additional methods provided by the
Eia709NetworkVariableSwitch
included in the
LampDemo project. This demonstrates how specific network variable types
like SNVTs or user defined types should be implemented. Please refer to
Section 3.1
for a detailed look at the implementation of the
Eia709NetworkVariableSwitch
.
The recommended way to specialize the network variable type is
to extend the class Eia709NetworkVariable by methods for setting and
reading special data types. The LampDemo
does
this by implementing the class
Eia709NetworkVariableSwitch
, which provides the
additional methods getState
(),
getIntensity
() and
setState
().
Table 2
contains an incomplete list of methods provided by the
Eia709NetworkVariable
. For details - as always -
refer to the JControl
API docs.
Table 2. Class Eia709NetworkVariable
Method | Description |
---|---|
Eia709NetworkVariable( Eia709Node node, String identifier, int type, int size, int modifier) | Creates a new Eia709NetworkVariable for the given node. |
void setValue( byte[] value) | Sets the raw value of the network variable. In case of an outgoing network variable defined with the sync modifier, the value is instantly propagated to the network. |
byte[] value getValue() | Returns the raw value of the network variable. |
void setListener( NetworkVariableListener listener) | Attaches a listener. |
void setSelector( int selector) | Sets the selector. This function may only be called from
within the bind () method of a
controllet. |
void setAddress( Eia709Address address) | Sets the remote address. This function may only be called
from within the bind () method of a
controllet. |
DataOutputStream getDataOutputStream() | Returns a DataOutputStream to
write JAVA basic types directly to the network variable value.
After modifying the value, the network variable must be updated
via the propagate () method. |
DataInputStream getDataInputStream() | Returns a DataInputStream which
allows to read JAVA basic types from the network variable
value. |
void poll() | Polls an incoming network variable. The JControl board will issue a request to the remote station and wait for a response containing the current variable state. |
void propagate() | Propagates outgoing network variables. This method has to
be called after modifying the variable value with the
DataOutputStream method. |
The class Eia709Address
represents an
address of a remote node. Addresses must be assigned to outgoing network
variables in order to propagate its value to the network. Three
different type of addresses are supported in the network variable API,
as show in Table 3.
Table 3. EIA-709 Address Types
Type | Description |
---|---|
0 (Broadcast) | Messages are propagated to all nodes of a specified subnet or even the entire domain (subnet 0). |
1 (Multicast) | Messages are propagated to all members of a group. |
2a (Singlecast) | Messages are propagated to a single node represented by a subnet and a node address. |
The API provides three different static methods for the creation of the different address types, listed in Table 2.
The rule that a network variable cannot exist without a node applies here as well. An address only makes sense in the context of a node.
Table 4. Class Eia709Address
Method | Description |
---|---|
static Eia709Address createAddressType0( Eia709Node node, int dstSubnet) | Creates a type 0 address. |
static Eia709Address createAddressType1( Eia709Node node, int dstGroup) | Creates a type 1 address. |
static Eia709Address createAddressType2a( Eia709Node node, int dstSubnet, int dstNode) | Creates a type 2a address. |
As we can see, the LampDemo creates a type 0
target address for the variable nvoState
with a
subnet of 20:
45 | Eia709Address addr = Eia709Address.createAddressType0( m_node, 20); |
The interface Eia709Controllet defines the methods and EIA-709
application must support. We have already been exposed to the methods
bind
() and configure
()
in the previous sections. We remember that
configure
() was used to create the network
variables the program requires and bind
() to
supply the so-called binding information: target
addresses and selectors.
To understand this separation better, we have to realize the different nature of these two actions. By creating network variables, we are defining a remote interface for the application and thus are a crucial part of it. We know what types of network variables we need, and we assign prominent names to them. Once the network variables are created, they provide an abstraction layer for the main program, which remains free from specific addresses or selectors.
However, as we've learnt before, network variables cannot be used without addresses and selectors. The method bind() is included to fill this gap. By assigning the missing selectors and addresses it exposes the otherwise lifeless network variables to the network.
While it is technically possible to declare and/or configure network variables in other parts of the program, this would break the programming concept and is highly discouraged.
Apart from these two methods, the interface defines a few other methods to control the behaviour.
Table 5. Interface Eia709Controllet
Method | Description |
---|---|
byte[] getProgramName() | Must return the program name consisting of 8 bytes. |
void configure( Eia709Node node) | Must create the required network variables. |
void bind() | Must provide the binding information consisting of network variable selectors and addresses. |
void start() | Is called when starting a node. A controllet may start addition threads for control functions here. |
void stop() | Is called when stopping a node. A controllet should stop or pause all of its threads. |
In this section we will have a closer look on the implementation of controllets, specific network variable types, and on the usage of DataInputStream and DataOutputStream for accessing variable values.
The LampDemo
from Section 2 used a special network variable class.
Eia709NetworkVariableSwitch
provides an
implementation of the network variable type SNVT_switch.
The following listing shows a simplified version of this class:
package jcontrol.demos.eia709.common; | |
import java.io.IOException; | |
import jcontrol.comm.eia709.Eia709NetworkVariable; | |
import jcontrol.comm.eia709.Eia709Node; | |
public class Eia709NetworkVariableSwitch extends Eia709NetworkVariable { | |
/** Minimum intensity: 0% */ | |
public static final float MIN_INTENSITY = 0f; | |
/** Maximum intensity: 100% */ | |
public static final float MAX_INTENSITY = 100f; | |
/** Switch state "off" */ | |
public static final int STATE_OFF = 0; | |
/** Switch state "on" */ | |
public static final int STATE_ON = 1; | |
/** Switch state "invalid" */ | |
public static final int STATE_INVALID = -1; | |
/** Network variable value size in bytes.*/ | |
public static final int SIZE = 2; | |
/** | |
* Creates a new SNVT_switch network variable. | |
* | |
* @param node node the network variable should be registered to | |
* @param identifier identifier | |
* @param modifier modifier | |
*/ | |
public Eia709NetworkVariableSwitch(Eia709Node node, String identifier, int modifier) { | |
super(node, identifier, Eia709NetworkVariable.TYPE_SNVT_SWITCH, SIZE, modifier); | |
} | |
/** | |
* Creates a new SNVT_switch network variable. | |
* | |
* @param node node the network variable should be registered to | |
* @param identifier identifier | |
* @param modifier modifier | |
* @param selfDescription self description | |
*/ | |
public Eia709NetworkVariableSwitch(Eia709Node node, String identifier, int modifier, String selfDescription) { | |
super(node, identifier, Eia709NetworkVariable.TYPE_SNVT_SWITCH, SIZE, modifier, selfDescription); | |
} | |
/** | |
* Returns the current intensity. | |
* | |
* @return intensity in percent | |
*/ | |
public float getIntensity() { | |
byte[] value = getValue(); | |
return (float)((int)value[INTENSITY_OFFSET] & 0xff) / 2; | |
} | |
/** | |
* Returns the current state. | |
* | |
* @return STATE_ON, STATE_OFF, STATE_INVALID | |
*/ | |
public int getState() { | |
byte[] value = getValue(); | |
switch (value[1]) { | |
case STATE_OFF: | |
return STATE_OFF; | |
case STATE_ON: | |
return STATE_ON; | |
default: | |
return STATE_INVALID; | |
} | |
} | |
/** | |
* Sets the state without changing the intensity. | |
* | |
* @param state new state (STATE_ON or STATE_OFF) | |
* | |
* @throws IOException in case of transfer errors | |
*/ | |
public void setState( int state) throws IOException { | |
byte[] value = getValue(); | |
value[1] = (byte)state; | |
setValue( value); | |
} | |
/** | |
* Sets state and intensity. | |
* | |
* @param state new state (STATE_ON or STATE_OFF) | |
* @param intensity new intensity in percent | |
* | |
* @throws IOException in case of transfer errors | |
*/ | |
public void setState( int state, float intensity) throws IOException { | |
byte[] value = new byte[2]; | |
value[1] = (byte)state; | |
value[0] = (byte)(int)(intensity*2); | |
setValue( value); | |
} | |
} |
A look on the constructor reveals that the
Eia709NetworkVariableSwitch
is nothing but a
plain network variable with a 2-byte value. To understand the
implementation of the setters and getters, we have to know the structure
behind this two byte value. The SNVT specification defines the value as
follows:
Table 6. SNVT_switch: Structure
Offset | Type | Description |
---|---|---|
0 | byte | Contains an intensity value between
0 and 200 . This is
meant to be used for light dimming and similar applications. The
value reflects a percentage in 0.5% steps. |
1 | byte | Contains the state. A value of 0
means "off", a value of 1 means "on". Other
values are undefined. |
With this knowledge we can implement the specific getters and setters. The getters are trivial, while the getters require more thought, since the setting of a network variable consists of two steps: Firstly, the value has to be changed in the local copy of the network variable, secondly, the new value has to be propagated. The means if we would use two separate setters for state and intensity, two subsequent calls to these functions would propagate the network variable twice. To avoid this, we simply provide an additional function allowing simultaneous changes of state and intensity.
For more transparency, we choose that the intensity is provided as float containing a percentage by the application instead of an integer range from 0 to 200.
So far we have seen in the Eia709NetworkVariableSwitch that we can
modify the value of a network variable by calling the
setValue
() method. We can perform a
read-modify-write cycle by combining setValue
()
with getValue
(). However, this can turn quite
tedious when it comes to more complex structures than the ones we've
seen so far.
Let's assume we create our own network variable type. It should contain a float value, an int, and a string of 8 bytes, all of which can be modified individually. Of course we can still use the get/set approach, but this would mean we have to convert the values manually and split them up into separate bytes and then deserialize them again, when we want to read the values. Using the data streams makes this a very simple job.
Let's have a look at the setValues() of our imaginary network variable:
public class MyEia709NetworkVariable extends Eia709NetworkVariable { | |
// --- snip --- | |
public void setValues( float f, int i, String s) throws IOException { | |
byte[] bytes = s.getBytes(); | |
if ( bytes.length < 8) throw new IllegalArgumentException( "string must consist of 8 bytes"); | |
DataOutputStream out = this.getDataOutputStream(); | |
out.writeFloat( f); | |
out.writeInt( i); | |
out.write( bytes, 0, 8); | |
this.propagate(); | |
} | |
// --- snip --- | |
} |
First the code turns the string into a byte array and checks its
length, then it creates a DataOutputStream for its own value by calling
the method getDataOutputStream
(). After writing
out the values, the network variable has not generated a message on the
network. This only happens after calling the
propagate
() method.
Reading a network variable is equally simple. Exemplary we'll have
a look at method getFloat
():
public float getFloat() { | |
float f = 0.0; | |
try { | |
DataInputStream in = this.getDataInputStream(); | |
f = in.readFloat(); | |
} catch (IOException e) { | |
} | |
return f; | |
} |