Technical Document: Niagara Developer Driver Framework Guide
Technical Document: Niagara Developer Driver Framework Guide
January 7, 2008
NiagaraAX Developer Driver Framework Guide
Copyright © 2008 Tridium, Inc.
All rights reserved.
3951 Westerre Pkwy., Suite 350
Richmond
Virginia
23233
U.S.A.
Copyright Notice
The software described herein is furnished under a license agreement and may be used only in accordance with the terms of the
agreement.
This document may not, in whole or in part, be copied, photocopied, reproduced, translated, or reduced to any electronic medium
or machine-readable form without prior written consent from Tridium, Inc.
The confidential information contained in this document is provided solely for use by Tridium employees, licensees, and system
owners; and is not to be released to, or reproduced for, anyone else; neither is it to be used for reproduction of this Control System
or any of its components.
All rights to revise designs described herein are reserved. While every effort has been made to assure the accuracy of this document,
Tridium shall not be held responsible for damages, including consequential damages, arising from the application of the information
contained herein. Information and specifications published here are current as of the date of this publication and are subject to
change without notice.
The release and technology contained herein may be protected by one or more U.S. patents, foreign patents, or pending applications.
Trademark Notices
BACnet and ASHRAE are registered trademarks of American Society of Heating, Refrigerating and Air-Conditioning Engineers.
Microsoft and Windows are registered trademarks, and Windows NT, Windows 2000, Windows XP Professional, and Internet
Explorer are trademarks of Microsoft Corporation. Java and other Java-based names are trademarks of Sun Microsystems Inc. and
refer to Sun's family of Java-branded technologies. Mozilla and Firefox are trademarks of the Mozilla Foundation. Echelon, LON,
LonMark, LonTalk, and LonWorks are registered trademarks of Echelon Corporation. Tridium, JACE, Niagara Framework,
NiagaraAX and Vykon are registered trademarks, and Workbench, WorkPlaceAX, and AXSupervisor, are trademarks of Tridium Inc.
All other product names and services mentioned in this publication that is known to be trademarks, registered trademarks, or
service marks are the property of their respective owners.The software described herein is furnished under a license agreement and
may be used only in accordance with the terms of the agreement.
NiagaraAX Developer Driver Framework Guide
● Tutorial
❍ Revision History
❍ Overview
■ Platforms
■ Stations
■ Workbench
■ Logic
■ Services
■ Drivers
■ Chapter 2 - Device Id
■ Chapter 3 - Device
■ Chapter 4 - Receiver
■ Chapter 6 - Communicator
■ Chapter 7 - Network
■ Review
■ Chapter 14 - Point Id
■ Conclusion
■ Conclusion
■ Conclusion
■ Chapter 30
■ Chapter 31
■ Conclusion
● Revision History
● Overview
❍ Platforms
❍ Stations
❍ Workbench
❍ Logic
❍ Services
❍ Drivers
❍ Chapter 2 - Device Id
❍ Chapter 3 - Device
❍ Chapter 4 - Receiver
❍ Chapter 6 - Communicator
❍ Chapter 7 - Network
❍ Review
❍ Chapter 14 - Point Id
❍ Conclusion
❍ Conclusion
❍ Conclusion
❍ Conclusion
Revision History
June 04, 2007
● Early release.
● Official release.
● Polished tutorial.
● Updated Day 3 - Chapter 21 to instruct the developer to extend BDdfIdParams and implement
interface BIDdfWriteParams instead of extending from BDdfWriteParams (which does not exist).
● There were a few references to easy driver throughout the documentation, especially in Chapter 17.
Easy driver was an early code-name for Dev Driver when the product was in the earliest stages of
creation. All references to Easy Driver have been changed to either Dev Driver or Ddf.
● Updated the sample source in chapters 17 and 21 to cast the result of calling getWriteParameters to
BYourDriverWriteParams instead of BYourDriverReadParams.
● Added instructions to Day 4 - Chapter 23 to override BDdfDiscoverParams.getDiscoveryLeafType.
This step was accidentally left out of the tutorial but without it, device discovery does not occur.
● Replaced the links to Conclusion 1 and Conclusion 2 on the opening lesson for day 3 with a link to the
single Conclusion page for day 3.
● Fixed some of the formatting for chapter 23.
● Added more description to all line items in the table of contents.
● Added Appendix 7 - Accessing the Point Id From the Read Request
● Replaced bad references to ddfInet.udp, ddfInet.tcp, ddfUdp and ddfTcp with com.tridium.ddfIp.udp
or com.tridium.ddfIp.tcp
Jace
A Jace is a type of server (perhaps with an embedded operating system) that is created specifically for
the purposes of running Niagara AX stations.
Web Supervisor
Web Supervisors are enterprise servers that support the Niagara AX framework and run a Niagara
Station whose purpose (among other things) is typically to gather data from other Niagara stations.
Workstation
Workstations are personal computers that typically run the Niagara AX Workbench and allow
Niagara installation and (or) maintenance professionals to configure other Niagara platforms and
stations. Workstations are also personal computers used by professional developers to create software
applications for the Niagara AX framework (such as Niagara AX drivers).
This is the Tools menu in the Niagara AX Workbench. The New Station item is highlighted.
This is a screen-shot of the New Station Wizard from the Niagara AX Workbench.
The station, as generated by the wizard, is not a running station. Instead, it is an offline station. The
installation professional will then define the behavior of the station (by using the Niagara AX Workbench)
before copying it to the platform computer (using Niagara AX Workbench for this too) that will forever be
dedicated to running the station. To test his or her station's behavior, the installation professional may decide
to run the station in his or her own Workstation PC, before ultimately deploying the station in a platform
computer that is dedicated to running the station.
An online station is one that is currently running on a Niagara AX platform computer. Once again, a Niagara
AX platform computer can be a Jace, Web Supervisor, or Workstation. Installation professionals can use the
Niagara AX Workbench to configure stations that are online or offline.
Workbench - Palette
The Niagara AX installation professional can add logic (read further please for details about logic) to the station by
dragging components (read further please for details about components) out of one or more Palettes and dropping
these components into the station. The palettes are typically displayed at the bottom of the left-most third of the main
Workbench application window, below the navigation tree. If the palette is not there then you can reveal it by
clicking on the Window item of the main menu, hovering over to the Side Bars menu item, and then clicking the
Palette item from the Side Bars menu.
This is a screen-shot of the Niagara AX Workbench. The navigation tree (Nav) is located towards the left-center.
The palette is located at the lower-left. The main display area is located towards the center and right.
Logic - Components
The installation or maintenance professional drags components from the palettes within the Niagara AX
Workbench into the station as needed. Each component performs a simple or complicated computation upon its
properties. Properties are data values on a component.
Driver Components: Niagara AX drivers feature special software components whose properties display
data values from some external equipment that is connected to the platform computer on which the
Niagara AX station is running. When the property values are changed from within Niagara, these
special components update the corresponding data values in the external equipment.
The special components that feature properties and synchronize data values between a Niagara AX
station and some external equipment are called driver control points.
Logic - Actions
Components sometimes have actions on them. An action is a way of telling the component to perform a special
computation or to do something special. Actions are identified by names that usually describe the special
computation that the component will take if the action is invoked. Actions can be invoked directly from within the
Niagara AX Workbench or from a web browser (please read further about "Px" Pages for more information). To
view or invoke an action from within Workbench, right-click a component either in the Nav tree or in the Wire
Sheet, a pop-up menu will appear, hover over the Actions side-menu. All available actions on the component will
appear in the Actions side-menu. If the Actions side-menu is grayed-out, then that means that there are no actions
available for the selected component.
Niagara AX drivers feature special software components. The actions on these special component's
can be programmed so that when invoked, they cause some behavior to occur within the equipment
that is connected to the platform computer on which the station is running. Typical actions might be
called Reboot, Set Time, or Set Date. When invoked on a special component in your driver, it might
reboot any equipment that the component represents, set the time in the equipment, or set the date.
The special driver components that would likely feature these actions are called driver devices and
driver control points.
Logic - Topics
Components sometimes have topics. A topic is an event that the component fires when the component detects
that a certain condition has occurred, whatever the certain condition might be depends on the component itself.
Niagara AX drivers feature special software components. The topics on these special component's can
be programmed to fire whenever the driver detects that some behavior has just occurred inside the
external equipment. This is one way of allowing a Niagara AX station to know when something occurs
inside the external equipment.
In general, topics are not used very often in drivers. Here are a few typical examples though of when a
driver might make use of topics:
● Depending on your driver's abilities, you could add a topic called Rebooted to a driver device
component. Your driver could fire the rebooted topic whenever it detects that the
corresponding unit of equipment has been power-cycled or otherwise rebooted.
● Depending on your driver's abilities, you could add a topic called Time Changed to a driver
device component. Your driver could fire the Time Changed topic whenever it detects that the
corresponding unit of equipment has experienced a change to its internal clock.
● Depending on your driver's abilities, you could add a topic called Date Changed to a driver
device component. Your driver could fire the Date Changed topic whenever it detects that the
corresponding unit of equipment has experienced a change to its internal calendar.
This assumes that your external device supports such a notion.
The special driver components that would likely feature these topics are called driver devices and driver
control points.
Driver control point components are virtually tied to a data value that is in a unit of equipment that the
driver communicates with. The value for the property named out on the driver control point reflects the
value that is really in the unit of equipment that the driver communicates to. One or more links can be
drawn from the out property to feed any other logic in the station. Likewise, driver control points that
are writable, feature several properties whose names start with in. The value flowing through the logic
that is linked to the in property with the lowest index will be transferred into the proper unit of
equipment for the driver, thereby changing a setpoint or other setting in the corresponding equipment.
The Niagara installation professional can also draw a link from any property on a component to any action on
another component. Whenever the property changes on the first component, the action is invoked on the
corresponding component, thereby causing the corresponding component to perform the special computation or
behavior described by the action's name.
The Niagara installation professional can also draw a link from any topic on a component to any action on another
component. Whenever the topic is fired on the first component, the corresponding action on the other component
is invoked, thereby also causing the corresponding component to perform the special computation described by the
action's name.
Logic - Px Pages
Niagara AX installation professionals create Px-Pages to provide or gain feedback from other web users. The web
users are those who will ultimately be interacting with the equipment that the driver provides access to. Px-Pages
are assigned a URL (Internet address) that can be viewed from a web browser or the Workbench software. Niagara
AX installation professionals can add one or more Px-Pages to any component. To do this, they right-click a
component from the Nav Tree in Workbench and select the New View item from the pop-up menu.
After adding the new Px-Page to the component, the main viewing area of the Workbench will become like a
graphic editor. The Niagara installation professional will drag one or more of the other components from the
station (under the Nav tree in Workbench) and drop them into the Px-Page editor. When this happens, the editor
prompts the installation professional and asks him or her to choose one or more properties or actions from the
corresponding components that were dropped onto the graphic. Then text boxes, images, buttons, or other
graphical items appear on the graphic. These graphical items are called Px-Widgets.
The Niagara AX installation professional then drags these widgets around the screen and possibly edits them to
change their color, font, size, etc. As previously mentioned, the Px-Page is assigned a URL (special Internet address)
that can be visited from a web browser. These text boxes, images, buttons, etc. can be viewed or manipulated
directly from a web browser to gain real-time access to the equipment over the Internet.
Please notice that the image shows the station New Station in the Nav tree. It has an icon of a fox next to it ( ).
Under the station there is a component named Config. All stations feature this Config component. The author
has double-clicked the Config component to reveal (in the main Workbench view area) the properties and sub-
components of the Config component. The author has expanded the Drivers component by clicking the that
is next to it in the main view area.
Below the Drivers component you will see all of the driver networks that are in your station. The NewStation as
shown contains only one driver network, the Niagara Network, which will be further described later.
network represents a field-bus and a protocol that the station will communicate through in order to access
other equipment that is located across a field-bus, network, or communications port to which the station's
platform computer is connected.
The following image illustrates the Driver Manager. If you click the New button on the Driver Manager then a
window will pop up and ask you to choose which type of new driver network to create. There will be a drop-
down list-box that contains all possible new driver networks. The list of all possible networks is determined
based on the Niagara jar files that are present on your Workstation.
Each of the driver's control points represents one data value that is inside the corresponding, external field-
device. Control points are special logic components (as previously defined in this document). This is how
Niagara monitors and controls external, smart devices -- such as those found in commercial building control
systems (for heating, ventilation, air conditioning, security, lighting, automation, etc), industrial control systems
(for automation, etc.), and/or residential building control systems (for heating, ventilation, air conditioning,
security, lighting, automation, etc.).
Niagara AX has support for smart devices that communicate over common protocols such as Lon, Bacnet,
Obix, OPC, Snmp, and Modbus. Neither Niagara nor Tridium own these protocols. Furthermore, the names of
these protocols are likely registered trademarked of their respective owners. Niagara AX also features a rich
collection of drivers that allow stations to communicate to equipment that use proprietary protocols, such as
Siemens' Staefa Smart II ® commercial series of heating, ventilation, and air conditioning controllers. Staefa
Smart II ® is a registered trademark of Siemens.
Pre Requisite
● A general understanding of the Niagara-AX framework.
Having taken the Tridium Technical Certification Program is strongly recommended. At the very least,
please study the Niagara AX Framework Overview section at the very beginning of this tutorial.
Although the Niagara AX framework is programmed in Java, you do not need to be an experienced
Java programmer to successfully develop a driver using the developer driver framework.
● Protocol documentation for the equipment that your driver will communicate with.
If you are working directly for the manufacturer of the equipment, then they should be able to provide
you with one or more documents that describes the way in which the equipment communicates, plus
the structure of the data that the equipment expects to see on the field-bus. If you have purchased this
equipment then you will need to negotiate with the equipment's manufacturer in order to gain access to
the equipment's protocol.
Definitions
field-bus
A network that connects equipment together. A platform computer must also connect to this
network, in order to access the equipment from within the Niagara framework. The platform
computer will either need to masquerade as a similar unit of equipment or as a master controller, if
the equipment's communication protocol is designed to accommodate a master.
Chapters - Day 1
● Preparation
● Chapter 1
● Chapter 2
● Chapter 3
● Chapter 4
● Chapter 5
● Chapter 6
● Chapter 7
● Chapter 8
● Review
<module
name = "jarFileName"
bajaVersion="3.2"
preferredSymbol = "yd"
description = "A driver built on the development driver framework."
vendor = "yourCompany"
>
<!-- Dependencies -->
<dependency name="alarm" vendor="Tridium" vendorVersion="3.0" />
<dependency name="baja" vendor="Tridium" vendorVersion="3.0" />
<dependency name="driver" vendor="Tridium" vendorVersion="3.0" />
<dependency name="devDriver" vendor="Tridium" vendorVersion="3.2" />
<dependency name="platform" vendor="Tridium" vendorVersion="3.0" />
<dependency name="control" vendor="Tridium" vendorVersion="3.0" />
1. Review your equipment's protocol documentation. If you are working directly for the manufacturer of
the equipment, then they should be able to provide you with one or more documents that describes
the way in which the equipment communicates, plus the structure of the data that the equipment
expects to see on the field-bus. If you have purchased this equipment then you will need to negotiate
with the equipment's manufacturer in order to gain access to the equipment's protocol.
2. Pick a 'ping' message from your protocol. After reviewing the equipment's protocol documentation,
choose a message from the protocol that looks like the simplest message to which a unit of the
equipment (field-device) will respond.
3. Make a Java class that extends BDdfPingRequest. Name it BYourDriverPingRequest. Create this in
the package named com.yourCompany.yourDriver.comm.req. Also add an empty slotomatic
comment immediately after the opening brace for the class declaration.
To do this, create a text file named BYourDriverPingRequest.java in the jarFileName/src/com/
yourCompany/yourDriver/comm/req folder. Inside the text file, start with the following text:
Please replace yourCompany and yourDriver in both the file name and the following text, as
previously described.
package com.yourCompany.yourDriver.comm.req;
import javax.baja.sys.*;
import com.tridium.ddf.comm.req.*;
import com.tridium.ddf.comm.rsp.*;
import com.tridium.ddf.comm.*;
import com.yourCompany.yourDriver.identify.*;
4. Override the toByteArray method. Build the byte array following your protocol's ping message.
5. Inside the body of the toByteArray method, you will need to construct a Java byte array and return it.
The next step will further describe how to do this.
To add the toByteArray method, add the following lines of text on the line that is immediately above the
last, closing brace. A closing brace looks like this: }
6. Assume that any data that you need in order to construct your byte array to return from the
toByteArray method/function is the value in a frozen property on another, device Id class that extends
BDdfIdParams (we'll further discuss the device Id class in the next chapter). Please do not be
overwhelmed at the mention of these. Suffice it to say, you will soon create another class that we will
call a device id. On the device id, you will define one or more frozen properties that uniquely identify a
particular unit of equipment (field-device) on the field-bus.
Frozen properties are a special kind of Niagara AX property on a Niagara AX component that you can
easily access from Java source code. You will make the device id class in subsequent sections of this
document. For now, please assume that you have already created it.
However, when you create this device id class (please do not worry about creating it now!!!), you will
define some frozen properties on the class. More specifically, you will define the frozen properties
whose values you will need in order to construct the byte array that this toByteArray method/function
will return. For example, if you need something that your protocol document might call the unit
number in order to build a request message, then you will later add a property called unitNumber to
your device id.
To do that, you will add a special comment just after the class statement in the Java file. After doing
that, you will run a Niagara AX development utility called slotomatic that will parse the comment and
add some Java code to your file -- the Java code necessary to add the property to the Niagara AX
structure that the Java file defines. More specifically, the slotomatic utility will generate a method
(function) called getUnitNumber that will return a Java int, or another type as you would have defined
in the special comment. By the way, let's call that special comment a slotomatic statement.
In light of all this discussion, please finish updating the toByteArray method to return a byte array that
matches the description that your protocol document defines for the message that you choose to be
the ping request. Please follow this example as a guide:
BYourDriverDeviceId deviceId =
(BYourDriverDeviceId)getDeviceId();
(byte)'p',
(byte)'i',
(byte)'n',
(byte)'g',
EOT};
}
7. Override the processReceive method but simply return null for now. We will revisit this later. The
developer driver framework calls the toByteArray method (function), transmits the resulting byte
array onto the field-bus, looks for incoming data frames, and passes them to this method (until this
method returns a response (not null), throws an exception, or times out. We'll discuss this in further
detail later. For now, please do as follows:
1. Make a class named BYourDriverDeviceId that extends BDdfDeviceId. Create this in a package named
com.yourCompany.yourDriver.identify. Add a slotomatic properties declaration and declare any
properties that you needed for coding the toByteArray method in the previous chapter. In the property
declaration for each property, add the MGR_INCLUDE slot facet as shown in the following examples.
NOTE: The Dev in BDdfDeviceId, and in many other locations throughout the developer
driver, stands for Developer.
package com.yourCompany.yourDriver.identify;
import javax.baja.sys.*;
import com.tridium.ddf.identify.*;
import com.yourCompany.yourDriver.comm.req.*;
d:\Niagara-3.2.x> cd d:/directoryAboveYourCompanyYourDriverFolder
d:\directoryAboveYourCompnayYourDriverFolder> slot -mi yourCompanyYourDriver
The slotomatic utility parses the slotomatic comment that you added to the top of the
BYourDriverPingRequest and BYourDriverDeviceId classes and generates the boiler-plate code
necessary to add frozen properties to those classes. The -mi option works in Niagara AX versions 3.2
and later. It causes the slotomatic utility to also add an entry to your driver's module-include.xml file
that is necessary for each Niagara AX type in your driver.
4. Do a full build on your driver and resolve any compiler errors.
To do this, open a Niagara Console from a Niagara 3.2.x release (or later) and do the following, replacing
directoryAboveYourCompanyYourDriverFolder with the path to the folder that contains the outer-
most yourCompanyYourDriver folder, as created in the Preparation section.
d:\Niagara-3.2.x> cd d:/directoryAboveYourCompanyYourDriverFolder
d:\directoryAboveYourCompnayYourDriverFolder> build yourCompanyYourDriver full
5. Resolve any errors that might appear in the console window. Proceed after you achieve a completely
successful build that is free of any Java and Niagara AX build errors.
6. Return to the toByteArray method that you created in the previous chapter. Verify that you pull any
data you need to make your byte array from the variable named yourDriverDeviceId and that you call
the getters that were generated automatically by the Niagara AX slot utility in this chapter.
package com.yourCompany.yourDriver;
import javax.baja.sys.*;
import com.tridium.ddf.identify.*;
import com.yourCompany.yourDriver.identify.*;
2. If your driver will communicate over the serial port of the station platform computer:
❍ Extend BDdfSerialDevice.
❍ Import com.tridium.ddfSerial.* in BYourDriverDevice.java.
package com.yourCompany.yourDriver;
import javax.baja.sys.*;
import com.tridium.ddf.identify.*;
import com.tridium.ddfSerial.*;
import com.yourCompany.yourDriver.identify.*;
❍ Add the following lines to the <-- Dependencies --> section of the build.xml file that your
created in the Preparation section.
3. If your driver will communicate over Tcp/Ip through the station platform computer's LAN or network
adapter then:
❍ Extend BDdfTcpDevice.
❍ Import com.tridium.ddfIp.tcp.* in BYourDriverDevice.java.
package com.yourCompany.yourDriver;
import javax.baja.sys.*;
import com.tridium.ddf.identify.*;
import com.tridium.ddfIp.tcp.*;
import com.yourCompany.yourDriver.identify.*;
❍ Add the following line to the <-- Dependencies --> section of the build.xml file that your
created in the Preparation chapter.
4. If your driver will communicate over Udp/Ip through the station platform computer's LAN or network
adapter then:
❍ Extend BDdfUdpDevice.
❍ Import com.tridium.ddfIp.udp.* in BYourDriverDevice.java.
package com.yourCompany.yourDriver;
import javax.baja.sys.*;
import com.tridium.ddf.identify.*;
import com.tridium.ddfIp.udp.*;
import com.yourCompany.yourDriver.identify.*;
❍ Add the following line to the <-- Dependencies --> section of the build.xml file that your
created in the Preparation chapter.
5. In any case (serial, Tcp/Ip, or Udp/Ip device), add a slotomatic properties declaration and re-define the
'deviceId' property as follows (make sure you give it the MGR_INCLUDE slotfacet).
Place the following text on the line immediately following the opening brace for the Java class
declaration. This is the Niagara slotomatic declaration. The properties section redefines the deviceId
property. The deviceId type should be BDdfIdParams. The deviceId's default value should be an instance
of BYourDriverDeviceId.
/*-
class BYourDriverDevice
{
properties
{
deviceId : BDdfIdParams
-- This plugs in an instance of yourDriver's
-- device id as this device's deviceId
default {[new BYourDriverDeviceId()]}
slotfacets{[MGR_INCLUDE]}
}
}
-*/
6. Place the following text on the line immediately following the slotomatic declaration that you just added.
We will revisit this method (function) later in the tutorial.
Summary
In Chapter 1 you created a ping request class and started defining the outgoing frame. In Chapter 2 you created a
deviceId class that feeds data to your ping request. In Chapter 3 you created a device and put a deviceId property
on your device. You have just used a deviceId to glue your Niagara AX device to your ping request class.
At this point, you should have gained some familiarity with your protocol's messaging format. By defining the
toByteArray method in Chapters 1 and 2 above, you have already let Niagara know how to frame an outgoing
request. Next, you need to let Niagara know how to 'frame' up incoming messages that Niagara receives back from
the field bus.
1. Make a class named BYourDriverReceiver that extends BDdfTcpReceiver (if the protocol of your driver is
Tcp/Ip based) or BDdfSerialReceiver (if the protocol of your driver is serial or serial-wireless based). Create
this in the package named com.yourCompany.yourDriver.comm.
Please consider whether you need to make your class extend BDdfSerialReceiver or
BDdfTcpReceiver, declare the corresponding package (com.tridium.ddfSerial.comm.* or com.
tridium.ddfIp.tcp.comm.*) among your import statements, and change the extends clause
accordingly in your file (to extend either BDdfSerialReceiver or BDdfTcpReceiver).
package com.yourCompany.yourDriver.comm;
import javax.baja.sys.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddfSerial.comm.*; // This would import com.tridium.ddfIp.tcp.comm.*
// if the protocol was Tcp/Ip based.
NOTE: The multi-line comment that immediately follows the Java class declaration is an empty
Niagara slotomatic declaration.
ADVANCED TOPIC: The rest of this example works best for protocols whose data frames have a
definite, identifiable beginning and ending sequence of characters (bytes). If your protocol
document does not have such a notion of data frames, then you will need to override the
protected IDdfDataFrame doReceiveFrame() throws Exception method, read raw data directly
from your driver's field bus, analyze the data, and return an IDdfDataFrame. The doReceiveFrame
method is called in a tight loop. In fact, by default, the doReceiveFrame calls the following
method that should make it convenient in the event that the driver protocol supports definite,
identifiable data frames.
NOTE: This is an advanced topic that will be discussed in an appendix in a future version of this
documentation.
2. Override the isStartOfFrame method. This is called every time a byte is received from the field bus. Return
YES if the data in the frame so far looks like the start of a receive message according to your driver's protocol.
Return MAYBE if you need more bytes to determine this. Return NO if the data in the frame does not make
sense and cannot possibly be the start sequence for a receive message in your protocol.
Here is an example that would frame up receive messages in our hypothetical protocol where receive messages start
with a hex 02 byte.
package com.yourCompany.yourDriver.comm;
import javax.baja.sys.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddfSerial.comm.*; // This would import com.tridium.ddfIp.tcp.comm.*
// if the protocol was Tcp/Ip based.
We understand that chances are, your protocol is much more complicated than this. However, the general
principle still applies no matter how complicated the protocol is (provided that it still features the notion of
data frames). Analyze the given frame and return YES if the bytes that are in the frame so far are the beginning
sequence of frames in your protocol, NO if not, or MAYBE if you need more bytes before being able to decide
either way.
3. Override the isCompleteFrame method.
After your isStartOfFrame method returns YES, the isCompleteFrame method is then called for every byte
that is received. Return true if the data in the fame looks like a completed, receive frame in your protocol.
Return false if you need more data. If all of a sudden, you decide that the data in the frame is invalid, return
true for now (the checkFrame method (that you may also override) will take care of that scenario.
package com.yourCompany.yourDriver.comm;
import javax.baja.sys.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddfSerial.comm.*; // This would import com.tridium.ddfIp.tcp.comm.*
// if the protocol was Tcp/Ip based.
4. Override the checkFrame method. This is called after your isCompleteFrame method returns true. Run the
bytes in the given frame through any validation or checksum logic that your protocol requires. Return true if
you can verify the frame is valid. Return false if not. Note that if your java code returned true in step 3 because
it all of sudden decided invalid data in the frame, then your checkFrame method should also recognize this
and return false.
package com.yourCompany.yourDriver.comm;
import javax.baja.sys.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddfSerial.comm.*; // This would import com.tridium.ddfIp.tcp.comm.*
// if the protocol was Tcp/Ip based.
Note that if your checkFrame method returns True, the developer driver framework will pass the received
frame to the corresponding request, perhaps the one that you created in chapter 1. Also, your checkFrame
method will likely be much more complicated than in this example. Most serial protocols will place a
checksum, circular redundancy check (CRC), or longitudinal redundancy check (LRC) as one of the last bytes
in the message. Your checkFrame method, in that case, would implement the same algorithm, recompute the
checksum, CRC, or LRC, and verify that the result equals the checksum, CRC, or LRC that was received in the
data frame. This is done to guarantee data integrity -- that no bytes have been corrupted during transmission
(due to static, etc.)
5. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
package com.yourCompany.yourDriver.comm.rsp;
import javax.baja.sys.*;
import com.tridium.ddf.comm.rsp.*;
NOTE: The multi-line comment that immediately follows the Java class declaration is an
empty Niagara slotomatic declaration.
2. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
For now, you have been intentionally instructed to create empty response class. Later you will learn
about more that you can do with the response class. Fortunately, an empty response class often works
sufficiently for pinging in a simple request-response transactions. For now, however, let us not worry
about parsing the data that might be in the ping response. It is sufficient to simply know that we
received a response because at the very least, we can conclude that the external equipment is online,
otherwise, it would not have responded at all.
3. Next, please open BYourDriverPingRequest.Java in your text editor.
4. Add the following import statement to the imports of BYourDriverPingRequest.java:
import com.yourCompany.yourDriver.comm.rsp.*;
5. Update the processReceive method in your ping request class (BYourDriverPingRequest). Return an
instance of BYourDriverPingResponse.
6.
package com.yourCompany.yourDriver.comm.req;
import javax.baja.sys.*;
import com.tridium.ddf.comm.req.*;
import com.tridium.ddf.comm.rsp.*;
import com.tridium.ddf.comm.*;
import com.yourCompany.yourDriver.identify.*;
import com.yourCompany.yourDriver.comm.rsp.*;
...
/**
* For this example, we will assume that the mere fact that a data
* frame was received after transmitting this response means that
* the equipment must have responded to the request. Since in
* Niagara AX, the primary purpose of a ping request-response
* transaction is to determine whether or not the corresponding
* field-device is online, then this will suffice.
*/
public BIDdfResponse processReceive(IDdfDataFrame recieveFrame)
throws DdfResponseException
{
return new BYourDriverPingResponse();
}
ADVANCED: After transmitting the bytes for your request (the bytes returned by your
request's toByteArray method), the developer driver framework will pass all frames that
it receives to your request's processReceive method. This is done until your request
returns a BIDdfResponse from this method or throws a DevResponseException (or a
subclass of DevResponseException).
The processReceive method needs to take one of the following steps:
1. (ADVANCED) Ignore the frame and return null.
This could happen in more advanced protocols that are not "master-slave" by
nature. In this scenario, by returning null, the developer driver framework will
continue to pass subsequent data frames that it receives to this method.
2. (ADVANCED) Collect the frame and return a BIDdfMultiFrameResponse. In
which case, you need to implement your own collection mechanism. For
example, this could be as simple as putting them all in a Vector in the
BIDdfMultiFrameResponse (until the request is considered to have timed-out).
This could happen in more advanced protocols where the equipment might
respond by sending multiple frames of data. By returning an instance of
BIDdfMultiFrameResponse, the developer driver framework will know that
your request is making progress. The developer driver framework will also
continue to pass subsequent data frames that it receives to this method (until
the request is considered to have timed-out).
3. (TYPICAL) Return a BIDdfResponse for the data frame (ADVANCED -- and
any previously collected frames -- ADVANCED) that you determine together
make up a completed response.
By returning a response from your ping request's processReceive method, the
developer driver framework will know that the corresponding field-device is
online. The particular request-response transaction will be considered to be
complete. No subsequent data frames will be passed to this particular request's
processReceive method.
4. (SOMEWHAT TYPICAL) Throw a DevResponseException or subclass of
DevResponseException to indicate that the frame forms a complete message
but indicates an invalid condition within the frame (perhaps a frame that
indicates a negative acknowledgement or NACK).
By throwing a DevResponseException or a subclass of DevResponseException,
the developer driver framework will know that the corresponding field-device is
not completely online. The particular request-response transaction will be
considered to be complete. No subsequent data frames will be passed to this
particular request's processReceive method.
WARNING: In the middle two scenarios, if you need to keep the bytes that are received
then it is important that you retain only a copy the frame's bytes. The frame's actual byte
array could be a direct reference to an internal buffer in the receiver.
7. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
At this point, not only have you gained familiarly with your protocol and defined an outgoing request, you
have now told Niagara how to parse incoming data frames. You have also defined the incoming response class
and associated it with the outgoing request. Next, you will further associate the receiver that you created in
Chapter 4 to the device you created in Chapter 3 or to your driver's network class, which you will create in
chapter 7.
Choosing the proper class to extend could prove tricky. For Tcp/Ip protocols you should
always extend BDdfTcpCommunicator.
However for serial protocols (or wireless protocols where a wireless router or radio
connects to a serial port on the platform computer) then you will need to choose either
BDdfSerialSitCommunicator or BDdfSerialMutCommunicator. If your driver's protocol
is strictly master-slave in nature (that is, if your protocol indicates that the platform
computer transmits serial data onto the serial port at will but the field-devices will only
transmit in response to such a message) then you should extend
BDdfSerialSitCommunicator. If your driver's serial protocol is more complicated than
this then you should probably extend BDdfSerialMutCommunicator. You can certainly
modify this later if you change your mind.
Likewise, for Udp/Ip protocols you need to choose between a
BDdfUdpMutCommunicator or a BDdfUdpSitCommunicator. The choice depends on
the nature of the protocol. The same rules for choosing the Mut version as opposed to
the Sit version apply as for serial drivers, as explained in the previous paragraph.
package com.yourCompany.yourDriver.comm;
import javax.baja.sys.*;
import com.tridium.ddf.comm.defaultComm.*;
import com.tridium.ddfSerial.comm.singleTransaction.*;
5. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
package com.yourCompany.yourDriver;
import com.tridium.ddf.*;
import javax.baja.sys.*;
package com.yourCompany.yourDriver;
import com.tridium.ddfSerial.*;
import com.yourCompany.yourDriver.comm.*;
import javax.baja.sys.*;
3. If yourDriver is a Tcp/Ip driver whose driver protocol communicates directly to Tcp/Ip field-devices extend
BDdfTcpNetwork and start with the text that follows.
package com.yourCompany.yourDriver;
import com.tridium.ddfIp.tcp.*;
import com.yourCompany.yourDriver.comm.*;
import javax.baja.sys.*;
4. If yourDriver is a Tcp/Ip driver whose driver protocol communicates directly to a field-gateway that has field-
devices attached to it then extend BDdfTcpGatewayNetwork and name the class
BYourDriverGatewayNetwork instead of just BYourDriverNetwork. Name the text file
BYourDriverGatewayNetwork.java too! Also, (only if using a field-gateway) update your device from chapter
3 and make it extend BDdfTcpDeviceBehindGateway instead of BDdfTcpDevice.
Start with the following text in BYourDriverGatewayNetwork.java:
package com.yourCompany.yourDriver;
import com.tridium.ddfIp.tcp.*;
import com.yourCompany.yourDriver.comm.*;
import javax.baja.sys.*;
And then in this case, also change your driver's device (BYourDriverDevice.java like this:
package com.yourCompany.yourDriver;
import com.tridium.ddf.identify.*;
import com.tridium.ddfSerial.*;
import javax.baja.sys.*;
5. If yourDriver is a Udp/Ip driver whose driver protocol communicates directly to Udp/Ip field-devices extend
BDdfUdpNetwork and start with the text that follows.
package com.yourCompany.yourDriver;
import com.tridium.ddfIp.udp.*;
import com.yourCompany.yourDriver.comm.*;
import javax.baja.sys.*;
6. If yourDriver is a Udp/Ip driver whose driver protocol communicates directly to a field-gateway that has
field-devices attached to it then extend BDdfUdpGatewayNetwork and name the class
BYourDriverGatewayNetwork instead of just BYourDriverNetwork. Name the text file
BYourDriverGatewayNetwork.java too! Also, (only if using a field-gateway) update your device from chapter
3 and make it extend BDdfUdpDeviceBehindGateway instead of BDdfUdpDevice.
Start with the following text in BYourDriverGatewayNetwork.java:
package com.yourCompany.yourDriver;
import com.tridium.ddfIp.udp.*;
import com.yourCompany.yourDriver.comm.*;
import javax.baja.sys.*;
And then in this case, also change your driver's device (BYourDriverDevice.java like this:
package com.yourCompany.yourDriver;
import com.tridium.ddf.identify.*;
import com.tridium.ddfSerial.*;
import javax.baja.sys.*;
7. Regardless of which sample code example you choose to start with in BYourDriverNetwork.java, add the
following methods to your network on the line immediately above the closing brace for the class:
8. Open BYourDriverDevice.java and modify the getNetworkType method to have it return BYourDriverNetwork.
TYPE or BYourDriverGatewayNetwork.TYPE (if you choose to use a Tcp/Ip gateway network for your
driver).
9. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
1. Redefine the communicator property as follows on your driver's Tcp Device, Tcp Gateway Network,
Udp Device, Udp Gateway Network or Serial Network (which ever one applies to your driver, if you
followed chapters 3 and 7 then only one of these three scenarios should apply):
❍ For a TCP or UDP device, your device component's slotomatic header should look
something like this:
/*-
class BYourDriverDevice
{
properties
{
communicator : BValue
-- This plugs in an instance of yourDriver's
-- communicator onto the device to which
-- the Niagara station's platform will directly
-- communicate.
default{[ new BYourDriverCommunicator() ]}
}
}
-*/
❍ For a TCP or UDP gateway network, your gateway network component's slotomatic header
should look something like this:
/*-
class BYourDriverGatewayNetwork
{
properties
{
communicator : BValue
-- This plugs in an instance of yourDriver's
-- communicator onto the gateway network component.
-- The Niagara station's platform will communicate
-- directly to the corresponding gateway unit on the
-- field-bus.
default{[ new BYourDriverCommunicator() ]}
}
}
-*/
❍ For a serial network, your serial network component's slotomatic header should look
something like this:
/*-
class BYourDriverNetwork
{
properties
{
communicator : BValue
-- This plugs in an instance of yourDriver's
-- communicator onto the serial network component.
-- The Niagara station's platform will communicate
-- over a serial port that is configured on this
-- serial network. You can look at the property
-- sheet of this communicator to review the exact
-- settings.
default{[ new BYourDriverCommunicator() ]}
}
}
-*/
2. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
4. Enter a name (such as TestStation) into the Station Name text box.
5. Click the Next button. On the next screen of the wizard, you may enter an administrator password if you wish. You may also
leave this blank if you'd rather.
6. Click the Finish button. The New Station Wizard window will then disappear.
7. Notice that the Workbench Nav tree has automatically navigated to a file named config.bog that is directly under a folder with
the same name as the one you supplied for the station name. This is your station database. Your new station is in offline mode.
It is not actively running. It can still be configured though.
3. Click the New button that is located towards the bottom of the Driver Manager. A window titled New should appear. It has a
drop down list box named Type to Add and a text box named Number to Add.
4. Click the drop down list box and verify that a listing for your driver's network appears in the list. Hint: If the Java file for your
driver's network is named BYourDriverNetwork.java then you should see a listing of Your Driver Network in the list.
5. Select your driver's network from the drop down list box.
6. Click Ok. The New window disappears but another window titled New should appear.
7. Click Ok. An instance of your driver's network has been added to the station. If you expand the Driver component below the
config.bog file in the Nav tree you should see your network underneath. You should also see your network in the Driver
Manager's list.
Add Your Driver's Device to Your Driver's Network That You Added to
the New Station
1. Double click the new instance of your driver's network that you added to the station. You may double-click it in the Nav tree or
in the Driver Manager. The Device Manager for your driver should replace the Driver Manager in the main Workbench view
area.
You should see columns in the Device Manager that correspond to the names of the properties that you added to
BYourDriverDeviceId. This happens as a result of adding the slot facet MGR_INCLUDE to the deviceId property in
BYourDriverDevice.java and then to the properties that you declared in BYourDriverDeviceId.java.
The following image shows the Device Manager for the Test Driver Network that the previous illustration added to the
TestStation.
2. Click the New button that is located towards the bottom of the Device Manager.
❍ A window titled New should appear.
❍ This window should look nearly identical to the New window that appeared when you added your network to the new
station.
❍ This New window has a drop down list box named Type to Add and a text box named Number to Add.
3. Click the drop down list box and verify that a single item is in the listing. The single item should be your driver's device. Hint: If
the Java file for your driver's device is named BYourDriverDevice.java then you should see a listing of Your Driver Device in the
list.
4. Select your driver's device from the drop down list box.
5. Click Ok. The New window disappears but another window titled New should appear. This window should have a text box or
drop down list box for each property that you declared in BYourDriverDeviceId with the MGR_INCLUDE slot facet.
6. Enter what you believe are reasonable values into the fields for your device id. Your goal should be to identify a field-device that
you would like your driver to access.
7. Click Ok. An instance of your driver's device has been added to the station. If you expand your driver's network component
(below the Drivers component of the config.bog file) in the Nav tree you should see your device underneath. You should also see
your device in the Device Manager's list.
3. Your station program should begin executing. Your station is now online.
3. Leave the Host empty. That will cause it to default to the local host, which is your Workstation (personal computer).
4. For User enter admin (all lower case)
5. For Password either leave empty or enter the administrator password that you entered back when you created the station.
6. Click Ok.
7. The Open Station window should disappear. After collapsing the My File System item that is in the Workbench Nav tree, the
Nav tree should look something like the following image.
8. Expand the online station component that appears under your host in the Nav tree.
9. Expand the Config component of your station.
10. Expand your driver's network that is under the Config component in your station.
5. The main Workbench view are should now display the properties for the component that your right-clicked.
6. Notice the Communicator component appears in the Property Sheet. Expand it in the Property Sheet by clicking the plus sign
that is next to it.
7. Expand the Serial Port Parameters or the Tcp Ip Parameters property (which ever one is present) and enter the correct serial
settings (such as comm. port, baud rate, etc.) or the correct Tcp/Ip settings (i.p. address and port of a field-device or field-
gateway).
WARNING: To stop your station, type quit in its console window. Failure to do so will likely discard any modifications that you made to
the station while it was online.
The station will automatically, periodically ping your device. You can also right-click your device and invoke the ping action.
4. Notice that there is an entry for your network or device. Activate trace for it by clicking the [O] that is in the Trace column.
6. Then you will see in the station's standard output a summary of all bytes being sent and received.
It might take some testing to verify that your driver is indeed transmitting the correct bytes to your field device and that your field device
is indeed responding as expected.
Congratulations! You now have a driver with a network and a device that communicates!
Definitions:
field-bus
A network that connects equipment together.
field-device
A single unit of external equipment that is connected to a field-bus.
driver device
A special component in the Niagara AX framework that virtually represents a field-device.
point value
Individual data measurement or setpoint that a field-device makes externally available to other field-
devices or equipment for read and/or write access using field-bus communication.
driver control point
A special component in the Niagara AX framework that virtually represents a point value.
frozen property
A special kind of Niagara AX property on a Niagara AX component that (among other benefits) you
can easily access from Java source code.
Chapters - Day 2
● Chapter 9
● Chapter 10
● Chapter 11
● Chapter 12
● Chapter 13
● Chapter 14
● Chapter 15
● Chapter 16
● Conclusion
1. Review your equipment's protocol documentation again. If you are working directly for the
manufacturer of the equipment, then they should be able to provide you with one or more documents
that describes the way in which the equipment communicates, plus the structure of the data that the
equipment expects to see on the field-bus. If you have purchased this equipment then you will need to
negotiate with the equipment's manufacturer in order to gain access to the equipment's protocol.
2. Pick a message from your protocol that retrieves one or more data point values that you are interested
in. After reviewing the equipment's protocol documentation, choose a message from the protocol that
looks like the simplest message that accomplishes this task.
Some protocols provide several messages that read point values. Some messages read point values as
well as details about the point. For now, please only concern yourself with retrieving the actual value
of the point, such as a single temperature value or other measurement. Please defer your concerns
about retrieving extra information about the data point, such as engineering units (for example -
whether it is Fahrenheit or Celsius) until later.
3. Make a Java class that extends BDdfReadRequest. Name it BYourDriverReadRequest. Create this in
the package named com.yourCompany.yourDriver.comm.req. Also add an empty slotomatic
comment immediately after the opening brace for the class declaration.
To do this, create a text file named BYourDriverReadRequest.java in the jarFileName/src/com/
yourCompany/yourDriver/comm/req folder. Inside the text file, start with the following text:
package com.yourCompany.yourDriver.comm.req;
import javax.baja.sys.*;
import javax.baja.io.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddf.comm.req.*;
import com.tridium.ddf.comm.rsp.*;
import com.yourCompany.yourDriver.identify.*;
import com.yourCompany.yourDriver.comm.rsp.*;
4. Override the toByteArray method. Build the byte array following your protocol's read message.
Inside the body of the toByteArray method, you will need to construct a Java byte array and return it.
The next step will further describe how to do this.
To add the toByteArray method, add the following lines of text after the slotomatic comment of the class:
BYourDriverDeviceId deviceId =
(BYourDriverDeviceId)getDeviceId();
BYourDriverReadParams readParams =
(BYourDriverReadParams)getReadParameters();
// Let's use it to help us build the byte array that we will return
ByteBuffer bbuf = new ByteBuffer();
// Writes the hex 01 start character to bos, our byte buffer.
bbuf.write(SOH);
// Writes the unit number onto bbuf, our byte buffer.
bbuf.write(deviceId.getUnitNumber());
// Writes the ASCII bytes for the word "read" followed by a space to
// bbuf, our byte buffer.
bbuf.writeBytes("read ");
// Writes the ASCII bytes for the value of the "type string" frozen
// property that will be on our read parameters structure. Writes
// this to bbuf, our byte buffer. For this sample driver, we
// will create on our driver's read parameters structure a frozen
// property called typeString. The value in this property is a
// string. It will either be the string "analog" or "digital".
bbuf.writeBytes(readParams.getTypeString());
// Writes the ASCII byte for a space character. Note, we are
// Following our hypothetical driver's protocol structure.
bbuf.write(' ');
// Writes the ASCII bytes for the value of the "direction" frozen
// Property that will be on our read parameters structure. Writes
// This to bbuf, our buffer of bytes. For this sample driver,
// We will also create on our driver's read parameters structure
// A frozen property called direction. The value in this property
// Is a string. It will either be the string "outputs" or "inputs".
bbuf.writeBytes(readParams.getDirection());
// Writes the byte that according to our hypothetical protocol,
// Indicates the end of the message on the field-bus.
bbuf.write(EOT);
// Converts the byte buffer into an actual Java byte
// Array and returns it.
return bbuf.toByteArray();
}
6. Override the processReceive method and return a new instance of your read response class (to be
created in a subsequent chapter).
To recap part of day 1's lesson, the developer driver framework calls the toByteArray method
(function), transmits the resulting byte array onto the field-bus, looks for incoming data frames, and
passes them to this method (until this method returns a response (not null), throws an exception, or
times out.
Please implement the processReceive method as follows:
NOTE:When adding methods (functions) please add them to the bottom of your class
file (just above the closing squiggly brace). This is to ensure that you do not add your
function into the section towards the top of the class that is reserved for the source code
that the slotomatic utility automatically generates. Accidentally placing code up there
could result in the code being lost when you run the slot.exe program.
package com.yourCompany.yourDriver.identify;
import javax.baja.sys.*;
import com.tridium.ddf.identify.*;
import com.yourCompany.yourDriver.comm.req.*;
3. Add a properties section to the slotomatic header and declare properties for any values that you
needed in order to make the toByteArray method (function) in your read request.
In our hypothetical example, we required two string properties. In the example toByteArray method, we
accessed these by calling the getTypeString and getDirection methods. We designed the code for the
toByteArray method (function) like this because we planned that when creating the read parameters
structure, we would add these properties to it. We understood that by naming these two properties
typeString and direction that the Niagara AX slotomatic utility would automatically generate a
getTypeString method and a getDirection method.
Please make the slotomatic statement on your read parameters structure look something like this:
/*-
class BYourDriverReadParams
{
properties
{
typeString : String
-- This property has nothing to with the dev
-- driver framework itself. Instead, we need
-- to construct the toByteArray method of the
-- driver's read request in following the
-- driver's protocol to read data values.
default{["analog"]}
slotfacets{[MGR_INCLUDE]}
direction : String
-- This property has nothing to with the dev
-- driver framework itself. Instead, we need
-- to construct the toByteArray method of the
-- driver's read request in following the
-- driver's protocol to read data values.
default{["outputs"]}
slotfacets{[MGR_INCLUDE]}
}
}
-*/
NOTE: Providing the slotfacet of MGR_INCLUDE on each of these properties will cause them to
automatically appear in your point manager for your driver.
4. Override the getReadRequestType method and return the TYPE of your driver's read request.
Please note that we have not yet asked you to run the slotomatic utility today, therefore, the java file for
your read request class file might not yet have a TYPE constant (unless you ran the slotomatic utility
on your own). The Niagara AX slotomatic utility will automatically add the TYPE constant to that file,
and the java files for the other classes that you will be creating today. Do not worry, we will ask you to
run the slotomatic utility and the build utility at the end of the next chapter.
Add the following code to BYourDriverReadParams.java:
1. Review your equipment's protocol documentation (again), especially where it describes the structure of the field-bus
message that will be sent in reply to the read request that you constructed in a previous chapter of today's lesson.
If you are working directly for the manufacturer of the equipment, then they should be able to provide you with one or
more documents that describes the way in which the equipment communicates, plus the structure of the data that the
equipment expects to see on the field-bus. If you have purchased this equipment then you will need to negotiate with the
equipment's manufacturer in order to gain access to the equipment's protocol.
2. Determine exactly which data values are returned in the response. If there are more than one then determine exactly how
to extract the value for any particular data value in the response.
3. Some protocols provide several messages that read point values. Some messages read point values as well as details about
the point. For now, please only concern yourself with retrieving the actual value of the point, such as a single temperature
value or other measurement. Please defer your concerns about retrieving extra information about the data point, such as
engineering units (for example - whether it is Fahrenheit or Celsius) until later.
4. Make a Java class that extends BDdfResponse and implements BIDdfReadResponse. Name it
BYourDriverReadResponse. Create this in a package named com.yourCompany.yourDriver.comm.rsp. Also add an
empty slotomatic comment immediately after the opening brace for the class declaration.
To do this, create a text file named BYourDriverReadResponse.java in the jarFileName/src/com/yourCompany/yourDriver/
comm/rsp folder. Inside the text file, start with the following text:
package com.yourCompany.yourDriver.comm.rsp;
import javax.baja.sys.*;
import javax.baja.status.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddf.comm.rsp.*;
import com.tridium.ddf.comm.req.*;
import com.yourCompany.yourDriver.identify.*;
import com.yourCompany.yourDriver.point.*;
A constructor is a special method (function) that is called when a particular instance of the class
is allocated. To put this in perspective, the processReceive method that you coded in the
previous chapter, will call the constructor on the read response class that you are creating in this
chapter. Moreover, the processReceive method will pass in the IDdfDataFrame that it received
from the dev driver framework. An IDdfDataFrame is essentially a byte array wrapper. In effect,
your read request from the previous chapter will pass the bytes of the response into your read
response's constructor.
To add this constructor , add the following lines of text after the slotomatic comment of the class: >
You will need to make a copy of the bytes in the given data frame, since it could be a transient part of an
internal buffer used in the dev communicator. Declare a byte array on the line that is immediately above the
constructor. The byte array will hold a copy of the bytes that are passed to the constructor inside the
IDdfDataFrame.
Use the following code as the constructor that you already started making:
6. Also add an empty constructor. An empty constructor might be required with future implementations of the developer
driver framework in order to possibly allow client-side proxy copies of the request (if you do not understand what "client-
side proxy" means -- you are not expected to know this -- then do not despair, please just keep this in the back of your mind
and simply add the empty constructor.)
/**
* This empty constructor allows Niagara AX to instantiate
* a client-side proxy of this request. It is not presently
* used but could be required in future versions of the
* developer driver framework.
*/
public BYourDriverReadResponse()
{
}
7. Please add a public method (function) named parseReadValue that takes one parameter, an IDdfReadable, and returns a
BStatusValue. For now, please make this method return null. We will revisit this in a subsequent chapter of today's
8. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
1. Make a Java class that extends BDdfProxyExt. Name it BYourDriverProxyExt. Create this in a
package named com.yourCompany.yourDriver.point. Also add an empty slotomatic comment, with
an empty properties section immediately after the opening brace for the class declaration.
To do this, create a text file named BYourDriverProxyExt.java in the jarFileName/src/com/yourCompany/
yourDriver/point folder. Inside the text file, start with the following text:
package com.yourCompany.yourDriver.point;
import javax.baja.sys.*;
import com.tridium.ddf.point.*;
import com.tridium.ddf.identify.*;
import com.yourCompany.yourDriver.identify.*;
2. Redefine the readParameters property to use your driver's read parameters structure.
Declare an instance of your driver's read parameters as the default value for the readParameters property:
/*-
class BYourDriverProxyExt
{
properties
{
readParameters : BDdfIdParams
-- This hooks your driver's read parameters structure into the
-- proxy extension that is placed on control points that are
-- under devices in your driver. The read parameter's structure
-- tells the dev driver framework which read request to use to
-- read the control point. It also tells your read request's
-- toByteArray method how to construct the bytes for the request.
default{[new BYourDriverReadParams()]}
slotfacets{[MGR_INCLUDE]}
}
}
-*/
3. Redefine the pointId property to use the point id class from your driver (to be created soon).
Even though you have not yet created it, we will show you how to create the point id in a subsequent
chapter of today's lesson.
/*-
class BYourDriverProxyExt
{
properties
{
readParameters : BDdfIdParams
-- This hooks your driver's read parameters structure into the
-- proxy extension that is placed on control points that are
-- under devices in your driver. The read parameter's structure
-- tells the dev driver framework which read request to use to
-- read the control point. It also tells your read request's
-- toByteArray method how to construct the bytes for the request.
default{[new BYourDriverReadParams()]}
slotfacets{[MGR_INCLUDE]}
pointId : BDdfIdParams
-- This tells your read response's parseReadValue method how to
-- extract the data value for a particular control point.
default{[new BYourDriverPointId()]}
slotfacets{[MGR_INCLUDE]}
}
}
-*/
NOTE: Providing the slotfacet of MGR_INCLUDE on each of these structured properties will cause
any of their properties that are flagged with MGR_INCLUDE to automatically appear in your point
manager for your driver.
4. Run slotomatic with the -mi switch and resolve any slotomatic compiler errors. Do not perform an
actual build on your driver yet (this class is not yet ready to be fully compiled).
5. A couple more classes are required before you will be able to successfully perform a full build on your
driver. One of these is the point id, which we mentioned briefly in this chapter. The other is the point
device extension, which is very simple to create. Next, we will show you how to create these both.
After that, we will have you finish creating your proxy extension.
Just as you created your driver's read parameters structure for the purpose of helping you to
create the toByteArray method in your read request, you will likewise create your driver's
point id for the purpose of helping you to retrieve a particular control point's value from the
corresponding read response.
5. Perform whatever computation and lookup is necessary, from the byte array copy that you made in the
constructor of this class, in order to retrieve the correct, current value for the control point that the given
proxy extension belongs to. Use the information that you will encode in your point id to determine this.
Please use this hypothetical example as a guide:
import javax.baja.control.*;
/**
* This method is called by the parseReadValue method. It is not a
* requirement for the dev driver framework. Rather, we decided
* that we needed this method in order to support our own logic
* in the parseReadValue method.
*/
private void parseRawValues()
{
// The response frame contains a hex 02 character, followed by
// The device's unit number as one byte, followed by an ASCII
// Comma delimited string, followed by a hex 04 character.
// Makes a string from the bytes that were in the received frame
// Starting at location 2 (the third byte since Java arrays
// Start at index 0). The new string will be parsed starting at
// Location 2 of the receiveBytes and including as many bytes as
// The length of the receiveBytes array minus 3 bytes -- those
// Three bytes being skipped are the hex 02 char, the unit nbr,
// And the hex 04 terminating char.
String commaDelimitedString
= new String(receiveBytes, 2, receiveBytes.length-3);
}
// Allocates an array to hold each individual raw value
rawValues = new String[numValues];
// Uses the following int to index into rawValues in the next loop
int offset = 0;
// Loops again through all of the substrings that are between commas
// In the comma delimited string
t = new StringTokenizer(commaDelimitedString,",");
while (t.hasMoreTokens())
{
rawValues[offset]=t.nextToken();
offset = offset+1;
}
// NOTE: Now we can retrieve the string for any particular value by
// Directly indexing into the rawValues array.
}
/**
* We call this method ourselves from the parseReadValue method.
* It is not required by the developer driver framework, instead, we
* decided to create this method in order to shorten the
* parseReadValue method.
*
* This method looks up the string value for the given offset and
* returns it as a BStatusValue that is appropriate for the given
* proxy extension's driver control point.
*
* @param a reference to the BYourDriverProxyExt to parse the read
* value for.
*
* @param a reference to the BYourDriverPointId to use to tell us
* how to parse the read value from the response bytes.
*
* @return a BStatusNumeric, BStatusBoolean, BStatusEnum, or
* BStatusString that appropriately matches the proxy's control
* point type. If the proxy's control point is a BNumericWritable
* or BNumericPoint then this will return a BStatusNumeric that
* represents the present value of the point. If the proxy's
* control point is a BBooleanWritable or a BBooleanPoint then
* this returns a BStatusBoolean that represents the current value
* of the point. If the proxy's control point is a BEnumWritable
* or BEnumPoint then this returns BStatusEnum that represents the
* current value of the point. If this proxy's control point is a
* BStringPoint or BStringWritable then this returns a BStatusString
* that represents the current value of the point. Sorry for the
* verbose description.
*/
private BStatusValue getReadValue(BYourDriverProxyExt proxy,
BYourDriverPointId pointId)
{
// Gets the raw value at the index for the given proxy
// NOTE: When we create our pointId, we will give it an int property named offset
String sRawValue = rawValues[pointId.getOffset()];
// Normalizes the string raw value into an int
int iRawValue = 0;
if (sRawValue.equalsIgnoreCase("on"))
iRawValue = 1;
else if (sRawValue.equalsIgnoreCase("off"))
iRawValue = 0;
else
iRawValue = Integer.parseInt(sRawValue);
6. Proceed to the next chapter without performing a build on the response. The response will not successfully
compile until you define your driver's point id (BYourDriverPointId). The next chapter explains how to do
this.
1. Make a Java class that extends BDdfIdParams. Name it BYourDriverPointId. Create this in a
package named com.yourCompany.yourDriver.identify. Also add an empty slotomatic comment,
with an empty properties section immediately after the opening brace for the class declaration.
To do this, create a text file named BYourDriverPointId.java in the jarFileName/src/com/yourCompany/
yourDriver/identify folder. Inside the text file, start with the following text:
package com.yourCompany.yourDriver.identify;
import javax.baja.sys.*;
import com.tridium.ddf.identify.*;
2. In the properties section of the slotomatic header, declare properties for any information that you
needed in order to complete the parseReadValue method (function) in your read response (the
previous chapter covers this lesson).
In our hypothetical example, we required an integer offset between 0 and 29. In the example
parseReadValue method, we accessed this by calling the getOffset method. We coded the
parseReadValue method like this because we knew that in this chapter, we would add a property named
offset to our hypothetical point id structure. We knew that by naming this property offset that the
Niagara AX slotomatic utility would automatically generate a getOffset method (function).
Please make the slotomatic statement on your point id structure look something like this (instead of
adding a property named offset add one or more properties named appropriately to accommodate
your version of the parseReadValue method in the previous chapter):
/*-
class BYourDriverPointId
{
properties
{
offset : int
-- This property has nothing to do with the dev
-- driver framework itself. Instead, we need to
-- know the location of a point's value when in
-- the parseReadValue method of yourDriver's
-- read response
default{[0]}
slotfacets{[MGR_INCLUDE]}
}
}
-*/
NOTE: Providing the slotfacet of MGR_INCLUDE on each of these properties will cause them to
automatically appear in your point manager for your driver. }
3. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
Now that your driver's point id has been defined, the rest of the classes that you created in today's lesson
should now be ready to compile.
package com.yourCompany.yourDriver.point;
import com.tridium.ddf.*;
import javax.baja.sys.*;
2. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
NOTE: Like the device folder, the point folder is practically a formality in the Niagara AX
framework.
package com.yourCompany.yourDriver;
import javax.baja.sys.*;
import javax.baja.util.*;
import com.tridium.ddf.*;
import com.yourCompany.yourDriver.point.*;
/**
* Associates BYourDriverPointDeviceExt to BYourDriverDevice.
*/
public Type getDeviceType()
{
return BYourDriverDevice.TYPE;
}
/**
* Associates BYourDriverPointDeviceExt to BYourDriverPointFolder.
*/
public Type getPointFolderType()
{
return BYourDriverPointFolder.TYPE;
}
/**
* Associates BYourDriverPointDeviceExt to BYourDriverProxyExt
*/
public Type getProxyExtType()
{
return BYourDriverProxyExt.TYPE;
}
/**
* This can be left null, depending on how you decide to define
* the discovery behavior in your driver. We will visit the
* discovery process in further detail during another day's
* lesson.
*/
public BFolder getDiscoveryFolder()
{
return null;
}
3. Open the BYourDriverProxyExt.java file that you created in the previous chapters of today's lesson.
4. Implement the getDeviceExtType method as follows:
/**
* This associates BYourDriverDeviceExt with
* BYourDriverProxyExt within the Niagara AX
* framework.
*/
public Type getDeviceExtType()
{
return BYourDriverPointDeviceExt.TYPE;
}
5. Add the following import statement to the top of BYourDriverProxyExt.java (along with the other
import statements)
import com.yourCompany.yourDriver.*;
6. Open the BYourDriverDevice.java file that you created in the previous day's lesson.
7. Add a property named points whose type is BYourDriverPointDeviceExt
/*-
class BYourDriverDevice
{
properties
{
deviceId : BDdfIdParams
-- This plugs in an instance of yourDriver's
-- device id as this device's deviceId
default {[new BYourDriverDeviceId()]}
points : BYourDriverPointDeviceExt
-- Adds the special point device extension
-- component to the property sheet and the
-- Niagara AX navigation tree. This special
-- component will contain and process all
-- control points for YourDriver
default{[new BYourDriverPointDeviceExt()]}
}
}
-*/
8. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
3. Your station program should begin executing. Your station is now online (again)!
4. Run Niagara AX Workbench.
5. Use Niagara AX Workbench to connect to the Niagara AX station that is running in your console window.
NOTE: For illustrations of this procedure, please review the conclusion for Day 1's
lesson.
your workstation.
3. Click the plus sign next to the Config component that is under the station. Watch it expand.
4. Click the plus sign next to the Drivers component that is under the Config component. Watch it expand.
5. Click the plus-sign that is next to the network component that you created in day 1's lesson. Watch the
network expand.
6. Click the plus-sign that is next to the device component that you created in day 1's lesson. Watch the device
expand.
7. Double-click the special points folder under your device. It's title will be Your Driver Point Manager where
Your Driver is the name of your driver module.
8. The Dev Point Manager is displayed in right-most two-thirds or so of the Workbench.
9. Notice that there are columns in the Dev Point Manager for each property on your read parameters structure
and point id on which you added the MGR_INCLUDE facet.
Verify That Your Driver is Trying to Perform Communication to Read Your Control Point's Value
1. Right click the station icon in the Navigation tree.
2. Click Spy. The main Workbench area changes to become the Spy page.
3. Click logSetup in the Spy page located in the main Workbench area. The main Workbench area displays the
Logs table.
4. Locate a row for your network or device's communicator and click the circle with a line under it [O] that is in
the Trace column of the table.
5. Click the Save to File link that is located near the top of the Log table.
6. Restore your station's Niagara AX console (DOS window) (this is a separate windows application that you
have running on your workstation).
7. Verify that the station is printing the bytes to the Niagara AX console (DOS) window that it is trying to send
and also those that it is receiving.
8. Repeat any of the steps from today's lesson, if you desire, to test other data points in your driver.
At this point, if you have not already connected your equipment to your Workstation, we encourage you to
do so. If your equipment is serial then you should follow your equipment's wiring and set-up instructions
to hook your Workstation's serial port onto your equipment's field-bus.
If your equipment communicates wirelessly, such as over a mesh network, you will likely need to connect a
serial-to-wireless radio to the serial port. Please consult the documentation for your equipment (or consult
the equipment manufacturer directly) if you do not know how to connect your PC to the wireless network.
In summary though, you will usually plug a serial-to-wireless radio into your PC's serial port.
If your equipment communicates over Tcp/Ip or Udp/Ip then connect it to the same local area network
(LAN) as your PC. Please also make sure that you follow the manufacturer's instructions for configuring
the Udp/Ip or Tcp/Ip settings of your equipment. This is necessary so that your equipment can truly
communicate over your LAN. As a first step towards troubleshooting the connection you can open a DOS
console and attempt to ping your equipment's i.p. address. However, please keep in mind that not all
equipment supports the standard ping mechanism used by the DOS console's ping utility. So, pinging your
equipment from DOS is just one suggestion that may or may not help.
Another way to troubleshoot a Tcp/Ip or Udp/Ip connection is to place your personal computer and your
Tcp/Ip or Udp/Ip equipment onto an Ethernet hub and possibly assigning one or both a static i.p. address.
This can help eliminate many of the variables involved in troubleshooting Tcp/Ip connectivity issues. You
can also ask your equipment manufacturer if they provide any software that can connect to the equipment.
If so, you can install the manufacturer's software on your PC. If your manufacturer's software truly uses
Tcp/Ip or Udp/Ip and if you can connect to the equipment using their software on your PC then your Dev
Tcp or Dev Udp driver (running in a station on your PC) should be able to connect to the same equipment
as well.
Nevertheless, troubleshooting Tcp/Ip or Udp/Ip connections can be rather challenging due to the many
possible configurations of a local area network (LAN). For that reason, any further discussion about
troubleshooting Tcp/Ip or Udp/Ip connections is beyond the scope of this tutorial.
WARNING:Depending on how well documented and straight-forward your equipment's protocol is, it
could take days or weeks for you to test all possible data points. We hope that the developer driver
framework alleviates your concerns about the inner workings of Niagara and allows you, instead, to
stay focused on your equipment and on your equipment's communication protocol.
Chapters - Day 3
● Chapter 17
● Chapter 18
● Chapter 19
● Chapter 20
● Chapter 21
● Conclusion
1. Further review your equipment's protocol documentation. If you are working directly for the manufacturer
of the equipment, then they should be able to provide you with one or more documents that describes the
way in which the equipment communicates, plus the structure of the data that the equipment expects to
see on the field-bus. If you have purchased this equipment then you will need to negotiate with the
equipment's manufacturer in order to gain access to the equipment's protocol.
2. Pick a message from your protocol that updates one or more of the data point values that you are interested
in, preferably one or more of the data point values that you retrieved in day 2's lesson. After reviewing the
equipment's protocol documentation, choose a message from the protocol that looks like the simplest
message that accomplishes this task.
Some protocols are designed so that one write message can update more than one data point in the
equipment. Other protocols are designed with a one-to-one relationship between write messages and point
values. The developer driver framework accommodates both of these scenarios.
3. Make a Java class that extends BDdfWriteRequest. Name it BYourDriverWriteRequest. Create this in a
package named com.yourCompany.yourDriver.comm.req. Also add an empty slotomatic comment
immediately after the opening brace for the class declaration.
To do this, create a text file named BYourDriverWriteRequest.java in the jarFileName/src/com/yourCompany/
yourDriver/comm/req folder. Inside the text file, start with the following text:
package com.yourCompany.yourDriver.comm.req;
import com.tridium.ddf.comm.req.*;
import javax.baja.sys.*;
4. Override the toByteArray method. Build the byte array following your protocol's write message.
Inside the body of the toByteArray method, you will need to construct a Java byte array and return it. The
next step will further describe how to do this.
To add the toByteArray method, add the following lines of text after the slotomatic comment of the class:
5. Assume that any data you need in order to construct the write message itself, ignoring any details about
which particular data value to write, is a frozen property on a given write parameters structure. Just as in
previous lessons, you will create this structure later in the day's lesson. This will help you determine what
information needs to be included in the write parameters structure. Suffice it to say, you will soon create
another component that we will call the write parameters. On this component, you will define one or
more frozen properties that uniquely identifies a particular write request message on your equipment's
field-bus.
As mentioned during the lesson for day 1, frozen properties are a special kind of Niagara AX property on a
Niagara AX component that you can easily access from Java source code. In subsequent chapters, you will
make the class for the write parameters structure. For now, please assume that you have already created it.
Please recall that to create frozen properties on a component, you add a special comment just after the
class statement in the Java file. After doing that, you will run a Niagara AX development utility called
slotomatic that will parse the special comment and add some Java code to your file -- the Java code
necessary to add the property to the Niagara AX component that the Java file defines. This automatically
generated Java code includes a method (function) called getMyProperty (where myProperty is the name of
the frozen property, as you would have declared in the special comment).
In light of all this discussion, please finish updating the toByteArray method to return a byte array that
matches the description that your protocol document defined for the message that you chose to be the
write request. Please follow this example as a guide:
BYourDriverDeviceId deviceId =
(BYourDriverDeviceId)getDeviceId();
BYourDriverWriteParams writeParams =
(BYourDriverWriteParams)getWriteParameters();
// Loops through all control points that are to be updated by this request.
// The dev driver framework will try to coalesce all control points
// Under a device that have equivalent write parameters into a single write
// Request. You can get the array of these items by calling the method
// named getWritableSource. It returns an array of IDdfWritable -- these
// will be the proxy extensions of your driver's control points.
IDdfWritable pointsToUpdate[] = getWritableSource();
for (int i=0; i<pointsToUpdate.length;i++)
{ // This is a good thing to check in case dev driver adds support for
// Writing components that are not proxy extensions
if (pointsToUpdate[i] instanceof BYourDriverProxyExt)
{ // Casts the IDdfWritable to BYourDriverProxyExt
BYourDriverProxyExt updateProxy = (BYourDriverProxyExt)pointsToUpdate[i];
// Gets the read parameters structure, it helped BYourDriverReadRequest
// know whether to read analog or digital. We can make optimal use of this
// property by re-using it here also.
BYourDriverReadParameters proxyReadParams =
(BYourDriverReadParams)updateProxy.getReadParameters();
// Gets the point id structure for the particular point. We previously
// defined it to contain an offset. We can make optimal use of this offset
// by re-using it here also.
BYourDriverPointId proxyPointId =
(BYourDriverPointId)updateProxy.getPointId();
// This boolean helps our logic remember whether the point is digital
boolean digitalProxy=false;
// The getRawValue method is available for your convenience to convert any
// IDdfWritable among the writable source into a double precision value.
double proxyValue = getRawValue(updateProxy);
if (proxyReadParams.getTypeString().equalsIgnoreCase("analog"))
{ // If updating an analog output on the field-device
bos.write('a'); bos.write('o');
digitalProxy=false;
}
else if (proxyReadParams.getTypeString().equalsIgnoreCase("digital"))
{ // Else, if updating a digital output on the field-device
bos.write('d'); bos.write('o');
digitalProxy=true;
}
else // Sanity check
throw new RuntimeException("Oops! Writing type string '"+
proxyReadParams.getTypeString()+"' is not supported.");
// Writes the point index as a string
bos.write(Integer.toString(proxyId.getOffset()).getBytes());
bos.write('=');
// Writes the new value
if (digitalProxy) // If the point is digital
{
if (proxyValue>0) // If the rawValue is greater than 0
{
bos.write('O'); bos.write('n'); // Writes "On"
}
else // Else, rawValue must be 0
{ // Writes "Off"
6. Override the processReceive method and return a new instance of your write response class (to be created
in a subsequent chapter).
To recap part of day 1's lesson, the developer driver framework calls the toByteArray method (function),
transmits the resulting byte array onto the field-bus, looks for incoming data frames, and passes them to
this method (until this method returns a response (not null), throws an exception, or times out.
Please implement the processReceive using the following Java code as a guide:
package com.yourCompany.yourDriver.identify;
import com.tridium.ddf.identify.*;
import javax.baja.sys.*;
3. Add a properties section to the slotomatic header and declare properties for any values that you
needed in order to make the toByteArray method (function) in your write request.
In our hypothetical example, we required one boolean property named "forceWrite". In the example
toByteArray method, we accessed this by calling the getForceWrite method. We coded the toByteArray
like this because we knew that in this chapter, we would add this property to our hypothetical write
parameters structure. We knew that by naming the properties forceWrite that the Niagara AX
slotomatic utility would automatically generate a getForceWrite method.
Please make the slotomatic statement on your read parameters structure look something like this:
/*-
class BYourDriverWriteParams
{
properties
{
forceWrite : boolean
-- This property has nothing to with the dev
-- driver framework itself. Instead, we need
-- to construct the toByteArray method of the
-- driver's write request in following the
-- driver's protocol to write data values.
-- In this hypothetical protocol, if we do not
-- forceWrite then the equipment's internal
-- program could overwrite any change that
-- Niagara might make to a data value.
default{[true]}
}
}
-*/
4. Override the getWriteRequestType method and return the TYPE of your driver's write request. The
Niagara AX slotomatic utility automatically adds the TYPE constant to your Java file when you run
the utility.
Add the following code to BYourDriverWriteParams.java:
5. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
1. Open BYourDriverProxyExt.java.
2. Redefine the 'writeParameters' property by modifying the slotomatic header of BYourDriverProxyExt.
java as follows:
class BYourDriverProxyExt
{
properties
{
readParameters : BDdfIdParams
-- This hooks your driver's read parameters structure into the
-- proxy extension that is placed on control points that are
-- under devices in your driver. The read parameter's structure
-- tells the dev driver framework which read request to use to
-- read the control point. It also tells your read request's
-- toByteArray method how to construct the bytes for the request.
default{[new BYourDriverReadParams()]}
slotfacets{[MGR_INCLUDE]}
pointId : BDdfIdParams
-- This tells your read response's parseReadValue method how to
-- extract the data value for a particular control point.
default{[new BYourDriverPointId()]}
slotfacets{[MGR_INCLUDE]}
writeParameters : BDdfIdParams
-- This hooks your driver's write parameters structure into the
-- proxy extension that is placed on control points that are
-- under devices in your driver. The write parameter's structure
-- tells the dev driver framework which write request to use to
-- write the control point. It also tells your write request's
-- toByteArray method how to construct the bytes for the request.
default{[new BYourDriverWriteParams()]}
slotfacets{[MGR_INCLUDE]}
}
}
-*/
NOTE: Providing the slotfacet of MGR_INCLUDE on each of these structured properties will cause
any of their properties that are flagged with MGR_INCLUDE to automatically appear in your point
manager for your driver.
3. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
1. Make a Java class that extends BDdfResponse. Name it BYourDriverWriteResponse. Create this in
a package named com.yourCompany.yourDriver.comm.rsp. Also add an empty slotomatic
comment immediately after the opening brace for the class declaration.
To do this, create a text file named BYourDriverWriteResponse.java in the jarFileName/src/com/
yourCompany/yourDriver/comm/rsp folder. Inside the text file, start with the following text:
package com.yourCompany.yourDriver.comm.rsp;
import com.tridium.ddf.comm.rsp.*;
import javax.baja.sys.*;
NOTE: If you do not define any constructors at all then Java automatically creates an
empty constructor for you. By not defining any constructors for
BYourDriverWriteResponse in this example, we implicitly create an empty constructor
with full, public access.
2. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
1. Make another Java class that extends BDdfWriteRequest. Name it BYourDriverPointAutoRequest. Create
this in the package named com.yourCompany.yourDriver.comm.req. Also add an empty slotomatic comment
immediately after the opening brace for the class declaration.
To do this, create a text file named BYourDriverPointAutoRequest.java in the jarFileName/src/com/yourCompany/
yourDriver/comm/rsp folder. Inside the text file, start with the following text:
package com.yourCompany.yourDriver.comm.req;
import com.tridium.ddf.comm.req.*;
import javax.baja.sys.*;
2. Override the toByteArray method. Build the byte array following your protocol's message that un-forces the data
point. Please review your driver's protocol once again, if necessary, to find such a message. If you cannot find
such a message then you can probably skip this chapter!
Inside the body of the toByteArray method, you will need to construct a Java byte array and return it. The next
step will further describe how to do this.
Assume that any data you need in order to construct the auto (unforce) message itself, ignoring any details about
which particular data value to auto (unforce), is a frozen property on the write parameters structure (the same
structure that your driver's write request uses to construct the byte array that it returns from its toByteArray
method). If you need more information than your write parameters presently provides, then you will need to
update your write parameters structure and add frozen properties for the required information.
In light of this discussion, please code the toByteArray method to return a byte array that matches the
description that your protocol document defines for the message that you identify as the un-force or relinquish-
auto request. Please follow this example as a guide:
BYourDriverDeviceId deviceId =
(BYourDriverDeviceId)getDeviceId();
BYourDriverWriteParams writeParams =
(BYourDriverWriteParams)getWriteParameters();
// Loops through all control points that are to be auto 'ed (unforced) by this request.
// The dev driver framework will try to coalesce all control points that need updated
// Under a device that have equivalent write parameters into a single write
// Request. You can get the array of these items by calling the method
// named getWritableSource. It returns an array of IDdfWritable -- these
// will be the proxy extensions of your driver's control points.
IDdfWritable pointsToUpdate[] = getWritableSource();
for (int i=0; i<pointsToUpdate.length;i++)
{ // This is a good thing to check in case dev driver adds support for
// Auto'ing components that are not proxy extensions
if (pointsToUpdate[i] instanceof BYourDriverProxyExt)
{ // Casts the IDdfWritable to BYourDriverProxyExt
BYourDriverProxyExt updateProxy = (BYourDriverProxyExt)pointsToUpdate[i];
// Gets the read parameters structure, it helped BYourDriverReadRequest
// know whether to read analog or digital. We can make optimal use of this
// property by re-using it here also.
BYourDriverReadParameters proxyReadParams =
(BYourDriverReadParams)updateProxy.getReadParameters();
// Gets the point id structure for the particular point. We previously
// defined it to contain an offset. We can make optimal use of this offset
// by re-using it here also.
BYourDriverPointId proxyPointId =
(BYourDriverPointId)updateProxy.getPointId();
if (proxyReadParams.getTypeString().equalsIgnoreCase("analog"))
{ // If updating an analog output on the field-device
bos.write('a'); bos.write('o');
}
else if (proxyReadParams.getTypeString().equalsIgnoreCase("digital"))
{ // Else, if updating a digital output on the field-device
bos.write('d'); bos.write('o');
}
else // Sanity check
throw new RuntimeException("Oops! Auto'ing type string '"+
proxyReadParams.getTypeString()+"' is not supported.");
// Writes the point index as a string
bos.write(Integer.toString(proxyId.getOffset()).getBytes());
3. Override the processReceive method and return a new instance of your auto response class (to be created in a
subsequent step). You can re-use your write response here, if you determine that it provides the same exact
functionality that you would need for your point-auto response. This will most likely be the case if you left your
write response essentially as an empty class that extends BDdfWriteRequest.
To recap part of day 1's lesson, the dev driver framework calls the toByteArray method (function), transmits the
resulting byte array onto the field-bus, looks for incoming data frames, and passes them to this method (until this
method returns a response (not null), throws an exception, or times out.
Please implement the processReceive using the following Java code as a guide. You may notice that this
hypothetical example is nearly identical to the processReceive method that was in the example for the read
request!
4. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
5. Create your point-auto response, if you were not able to re-use your write response.
As a guide, please follow the instructions in the earlier chapter of today's lesson where we showed you how to create
your driver's write response.
6. Associate your auto request to your write parameters class.
Do this by changing the class declaration on BYourDriverWriteParams to specify that it also implements the
BIDdfAutoParams interface. Then define a method called getDevAutoRequestType on
BYourDriverWriteRequest
Follow this example as a guide:
package com.yourCompany.yourDriver.identify;
import com.tridium.ddf.identify.*;
import javax.baja.sys.*;
7. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
Emergency Override
Sets the control value for the most critical priority -- priority level 1.
Emergency Auto
Clears the control value for the most critical priority -- priority level 1.
Override
Sets the intermediate control value -- priority level 8.
Auto
Clears the intermediate control value -- priority level 8.
Set
Sets the fallback value. This value takes effect if no value is being supplied to any of inputs 1-16.
Driver control points automatically use your driver to set the data value in the field-device equal to the input
value that is provided at the priority of the index closest to input one. If no values are provided to any of the
sixteen input properties then the driver control point will set the data value equal to the fallback value (if one is
specified) on the property sheet of the control point.
If no fallback value is provided and if no input values are provided, then the driver control point will not modify
the particular data value in your field-device. Whenever a transition occurs within the station that causes this
scenario to occur, the driver control point will ask your driver to auto the data value in the field-device. Your
driver may or may not need to take any special action during this scenario. If you specified a point auto request
then the dev driver framework will automatically transmit it through your field-bus in this scenario.
We hope that this discussion will allow you to test the write and auto functionality that you created during
today's lesson.
NOTE: You will need to follow the procedure outline in the conclusion for Day 2. However, make sure that you
add a writable control point from your driver's point manager. Writable control points are Numeric Writable,
Boolean Writable, Enum Writable, and String Writable. We recommend that you start testing with a Numeric
Writable and/or a Boolean Writable.
After adding a writable, driver control point to your station, right-click the writable control point, hover over
the actions side-menu, choose the Emergency Override action, and specify a value on the dialog that appears.
The developer driver framework and the Niagara AX framework will work together to transmit the byte array
from an instance of your driver's write request, as described during today's lesson.
After running tests to write a value to one your driver's control points, please consider running tests to auto
your driver's control point. To do this, right-click the writable control point, hover over the actions side-menu,
and choose the Emergency Auto action. The developer driver framework and the Niagara AX framework will
work together to transmit the byte array from an instance of your driver's auto request, as described during
today's lesson.
Please refer to the conclusion of Day 2 for illustrations about how to use your driver's point manager to create
driver control points.
Chapters - Day 4
● Chapter 23
● Chapter 24
● Chapter 25
● Chapter 26
● Conclusion
A. If your driver's protocol features a message that can retrieve information about multiple field-devices:
package com.yourCompany.yourDriver.comm.identify;
import com.tridium.ddf.identify.*;
import javax.baja.sys.*;
Based on your driver's protocol document, please be prepared to add properties to the
slotomatic statement to help you construct the byte array in the device discovery request
(you will create the device discovery request in the next chapter)
3. Define the getFirst, getLast, getNext, and isAfter methods. These are essential and allow the
developer driver auto discovery process to automatically loop through all possible combinations of
your discovery parameters.
getFirst
The getFirst method should return an instance of
BYourDriverDeviceDiscoverParams that represents the data that would be
placed in the protocol request to request information about the first device or
series of devices. If your protocol has no way of identifying a definite first device
then you will need to be creative here and return an instance of
BYourDriverDeviceDiscoverParams with all properties set to special values, that
you will determine, such as Integer.MIN_VALUE for int properties.
getLast
The getLast method should return an instance of
BYourDriverDeviceDiscoverParams that represents the data in the protocol
request that would request information about the last device or series of devices.
This might not be possible to accurately define for some protocols. If your
protocol does not have a way of identifying the last series of devices, then you
will need to be creative here and return an instance of
BYourDriverDeviceDiscoverParams with all properties set to special values, that
you will create, such as Integer.MAX_VALUE for int properties.
getNext
The getNext method should return an instance of
BYourDriverDeviceDiscoverParams that represents the data in the protocol
request that would request the next device or series of devices, with respect to
the particular instance of BYourDriverDeviceDiscoverParams. This method
should review the values of some or all of the properties that you add to
BYourDriverDeviceDiscoverParams. This method should return a new instance
of BYourDriverDeviceDiscoverParams whose property values are incremented
in such a way that the new instance would represent the data needed in the
toByteArray method of the device discovery request in order to ask the field-
gateway for information about the next device or set of devices.
isAfter
The isAfter method will be passed an instance of
BYourDriverDeviceDiscoverParams. You should review the property values of
the given instance of BYourDriverDeviceDiscoverParams and return true if the
current instance would request information about a device or series of devices
that would be after the device or series of devices that the given instance of
BYourDriverDeviceDiscoverParams would identify. If the current instance
represents a device or series of devices that is before or equal to the given
instance, then this method should return false.
We understand that this can be a bit tricky, since the definitions of first, last, next, and
after can be somewhat abstract in some protocols. Please feel free to skip ahead and
revisit this later if you wish.
4. Your device discover params class also needs to define the getDiscoverRequestType method and
return the Niagara-AX TYPE for your device discovery request class (you will create this in the next
chapter).
/**
* The implementing class needs to return a Type that represents
* the discover request from the driver whose discoverId can be an
* instance of this object's class. If the class supports more than
* one discover request type, then this should return the type that is
* the most appropriate for the particular instance of the implementing
* class.
*
* This is fundamental to dev driver's auto-discovery features.
*
* @return review method description.
*/
public Type getDiscoverRequestType()
{
return null; // For now! Soon we will revisit this and return
// BYourDriverDeviceDiscoveryRequest.TYPE after
// Creating it.
}
5. Update your driver's device id structure, make it implement BIDdfDiscoveryLeaf. Implement the
following methods that BIDdfDiscoveryLeaf requires.
NOTE: BIDdfDiscoveryLeaf objects appear in the "discovered list" (top half) of the device manager.
Your driver's device id also needs to import javax.baja.registry.*;, com.tridium.ddf.discover.*;, and
import com.yourCompany.yourDriver.*;
package com.yourCompany.yourDriver;
import javax.baja.sys.*;
import javax.baja.registry.*;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.comm.req.*;
import com.tridium.ddf.discover.*;
import com.yourCompany.yourDriver.*;
...
...
/**
* When a control point is added to the station from the Dev
* Point Manager, it is given this name by default (possibly
* with a suffix to make it unique).
* @return
*/
public String getDiscoveryName()
{
return "NewDevice";
}
/**
* Descendants need to return an array of TypeInfo objects corresponding
* to all valid Niagara Ax types for this discovery object. This is
* important when the end-user clicks 'Add' from the user interface for
* the manager.
*
/**
* The implementing class needs to return a Type that will be the
* discovery leaves in the ddf manager. For the device discovery
* process, the discovery leaf TYPE is the device id TYPE.
*
* @return BYourDriverDeviceId.TYPE
*/
public Type getDiscoveryLeafType()
{
return BYourDriverDeviceId.TYPE
}
B. If your driver is a serial driver, uses a master-slave protocol, and provides no message that can retrieve
information about multiple field-devices:
You should modify your device id, ping params, ping request, and ping response to also serve in the auto-
discovery procedure.
1. Open the BYourDriverDeviceId.java file from Day 1 of this tutorial.
2. Modify the class declaration statement and declare that BYourDriverDeviceId implements
BIDdfDiscoverParams and BIDdfDiscoveryLeaf.
3. Your driver's device id structure, acting also as the device discover params structure, needs to satisfy
the BIDdfDiscoverParams interface and define the getFirst, getLast, getNext, and isAfter methods in
order to allow the developer driver auto discovery process to automatically loop through all possible
combinations of your discovery parameters.
For example, in the hypothetical protocol that we used in the days past, our device had a
unitNbr. Let's assume that the unitNbr could range from 0 to 50. The getFirst method
would return an instance of BYourDriverDeviceId with a unitNbr of 0. The getLast
method would return an instance of BYourDriverDeviceId with a unitNbr of 50. The
getNext method would return an instance of BYourDriverDeviceId with a unitNbr that
is one more than the current instances's unitNbr: this.getUnitNumber()+1. Etc...
Your driver's device id also needs to satisfy the BIDdfDiscoveryLeaf interface and
implement the getDiscoveryName and getDatabaseTypes methods.
Your driver's device id also needs to import javax.baja.registry.*;, com.tridium.ddf.
discover.*;, import com.yourCompany.yourDriver.*;, and com.yourCompany.yourDriver.
comm.req.*;.
package com.yourCompany.yourDriver;
import javax.baja.sys.*;
import javax.baja.registry.*;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.comm.req.*;
import com.tridium.ddf.discover.*;
import com.yourCompany.yourDriver.*;
import com.yourCompany.yourDriver.comm.req.*;
/**
* Niagara AX requires a public, empty constructor, so that it can perform
* Its own introspection operations.
*/
public BYourDriverDeviceId(){}
/**
* This constructor is for our own convenience in the methods getFirst,
* getNext, etc.
*/
public BYourDriverDeviceId(int unit)
{
setUnitNumber(unit);
}
public BIDdfDiscoverParams getFirst()
{
/**
* When a control point is added to the station from the Dev
* Point Manager, it is given this name by default (possibly
* with a suffix to make it unique).
* @return
*/
public String getDiscoveryName()
{
return "NewDevice";
}
/**
* Descendants need to return an array of TypeInfo objects corresponding
* to all valid Niagara Ax types for this discovery object. This is
* important when the end-user clicks 'Add' from the user interface for
* the manager.
*
* For this discovery object, please return a list of the types
* which may be used to model it as a BComponent in the station
* database. If the discovery object represents a device in your
* driver then method should return an array with size of
* at least one, filled with TypeInfo's that wrap the Niagara AX
* TYPE's for your driver's device components.
*
* The type at index 0 in the array should be the type which
* provides the best mapping. Please return an empty array if the
* discovery cannot be mapped.
*/
public TypeInfo[] getValidDatabaseTypes()
{
return new TypeInfo[]{BYourDriverDevice.TYPE.getTypeInfo()};
}
4. Your driver's device id structure, acting also as the device discover params structure, also needs to
define the getDiscoverRequestType method (inherited from the interface BIDdfDiscoverParams) and
return the BYourDriverPingRequest that you created in one of the previous day's lessons. This tutorial
will soon show you how to update BYourDriverPingRequest to serve as the device discovery request
too.
/**
* The implementing class needs to return a Type that represents
* the discover request from the driver whose discoverId can be an
* instance of this object's class. If the class supports more than
* one discover request type, then this should return the type that is
* the most appropriate for the particular instance of the implementing
* class.
*
* This is fundamental to dev driver's auto-discovery features.
*
* @return review method description.
*/
public Type getDiscoverRequestType()
{
return BYourDriverPingRequest.TYPE;
}
5. Your driver's device id structure, acting also as the device discover params structure, also needs to
define the getDiscoverRequestTypes method (inherited from the interface BIDdfDiscoverParams)
and return an array of size 1, including whatever is returned by the getDiscoverRequestType method:
/**
* Some drivers might require multiple, completely different requests
* to be used to discover devices. This should not be the case here though.
*
* @return review method description.
*/
public Type[] getDiscoverRequestTypes()
{
return new Type[]{getDiscoverRequestType()};
}
6. Your driver's device id structure, acting also as the device discover params structure, also needs to
define the getDiscoveryLeafType method (inherited from the interface BIDdfDiscoverParams) and
return its own type (since it is acting as both the discover params and as the discovery leaf):
/**
* Some drivers could hypothetically group discovery objects. This
* method returns the Type that will ultimately form the bottom-
* most, non-grouped discovery objects. The columns of the device
* manager will be determined from this type.
*
* In this case, the discovery leaf type is also BYourDriverDeviceId.
*
* @return review method description.
*/
public Type getDiscoveryLeafType()
{
return getType();
}
NOTE:If your driver communicates directly over Tcp/Ip or Udp/Ip and not through a Tcp/Ip or Udp/Ip gateway
then a discovery is probably not possible. You may be able to do a Udp/Ip multi-cast discovery, depending on
your equipment. However, that is beyond the scope of this tutorial. You will probably have to skip the chapters
that pertain to device discovery (today's lesson).
package com.yourCompany.yourDriver.comm.req;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.comm.req.*;
import javax.baja.sys.*;
B. If you modified the BYourDriverDeviceId class in the previous chapter to serve in the device discovery:
...
import com.tridium.ddf.discover.*;
import com.tridium.ddf.identify.*;
...
/*-
class BYourDriverPingRequest
{
properties
{
...
discoverParameters : BDdfIdParams
-- This provides the necessary data that the toByteArray method
-- Needs in order to construct the byte array.
-- NOTE: During auto-discovery, the auto discovery job loops
-- through all possible combinations of discoverParameters. Each
-- pass through the loop, the next discoverParameters value for
-- your driver is passed to this property. When you implement
-- the toByteArray method, you may cast this to your own
-- discoveryParameters class (that is what it will ultimately be).
default{[new BYourDriverDeviceId()]}
...
}
}
-*/
3. Add the following methods to BYourDriverDevicePingRequest. Please add them exactly as-is. You
shouldn't need to modify these methods, except maybe to change the comments.
/**
* The setDiscoverer method will be passed an instance of
* IDdfDiscoverer. You need to retain the reference on
* the instance and return it (whenever requested) from
* the getDiscoverer method.
*/
IDdfDiscoverer discoverer = null;
/**
* The BDdfAutoDiscoveryJob will pass an inner instance
* of itself to the setDiscoverer method. In there, you
* need to save away the reference. In here, please return
* the most recent reference that was passed to the
* setDiscoverer method.
*/
public IDdfDiscoverer getDiscoverer(){return discoverer;}
/**
* The BDdfAutoDiscoveryJob will pass an inner instance
* of itself here. Please save away the reference. Other
* than that, you should not need to concern yourself
* with this.
*/
public void setDiscoverer(IDdfDiscoverer discoverer)
{
this.discoverer=discoverer;
}
NOTES:
● You already defined the toByteArray method during one of the previous day's lessons. You should not
need to change it any further.
● In this scenario, since you are using the ping request as the discover request too, the values of the
deviceId and discoverParameters properties will be equivalent. Since your toByteArray method has
already been implemented to use the deviceId then that will suffice.
package com.yourCompany.yourDriver.comm.rsp;
import com.tridium.ddf.comm.rsp.*;
import javax.baja.sys.*;
B. If you decided to have your driver's ping request and device id also serve as the discover request/
discover parameters:
Update the class declaration for BYourDriverPingResponse and declare it so that it implements
BIDdfDiscoverResponse. Also add a statement to import com.tridium.ddf.discover.*.
package com.yourCompany.yourDriver.comm.rsp;
import com.tridium.ddf.comm.*;
import com.tridium.ddf.discover.*;
import javax.baja.sys.*;
/**
* This method parses the response byte array and returns an
* array of BYourDriverDeviceId objects describing the devices
* that this response is able to identify. This is called during
* the auto discovery process.
*/
public BIDdfDiscoveryObject[] parseDiscoveryObjects(Context c)
{
return null;
}
/**
* In our hypothetical protocol, we know that by receiving any
* response to the ping request than the deviceId of the transaction
* represents a device that is online.
*/
public BIDdfDiscoveryObject[] parseDiscoveryObjects(Context c)
{ // Returns an array of size one, containing just the deviceId
// Of the response. Please note that for drivers that are not
// Re-using the ping request as a discovery request, then this
// Method should parse through the response bytes and make an
// Array of BYourDriverDeviceId whose length corresponds to
// The number of device entries that you determine are present
// In the response bytes. Then assign each BYourDriverDeviceId
// Entry in the array based on what you are able to parse from
// The response bytes.
return new BIDdfDiscoveryObject[]{
(BIDdfDiscoveryObject)getDeviceId().newCopy()
};
}
package com.yourCompany.yourDriver.discover;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.discover.auto.*;
import com.yourCompany.yourDriver.identify.*;
import javax.baja.sys.*;
default{[(BDdfIdParams)new BYourDriverDeviceId().getLast()]}
}
}
-*/
}
9. Redefine the discoveryPreferences property on BYourDriverNetwork and specify the default value to be an
instance of BYourDriverDeviceDiscoveryPreferences.
❍ Add the following import statements to the top of BYourDriverNetwork or
BYourDriverGatewayNetwork:
import com.tridium.ddf.discover.*;
import com.yourCompany.yourDriver.discover.*;
❍ For a serial network, your serial network component's slotomatic header should look something like
this:
/*-
class BYourDriverSerialNetwork
{
properties
{
communicator : BValue
-- This plugs in an instance of yourDriver's
-- communicator onto the serial network component.
-- The Niagara station's platform will communicate
-- over a serial port that is configured on this
-- serial network. You can look at the property
-- sheet of this communicator to review the exact
-- settings.
default{[ new BYourDriverCommunicator() ]} }
discoveryPreferences : BDdfDiscoveryPreferences
-- This saves the last set of discovery preferences
-- that the user provides on the device manager. It
-- is also used as the default for the first Time
-- that the user is prompted for a discovery.
default{[ new BYourDriverDeviceDiscoveryPreferences()]}
}
}
-*/
❍ For a TCP gateway network, your gateway network component's slotomatic header should look
something like this:
/*-
class BYourDriverGatewayNetwork
{
properties
{
communicator : BValue
-- This plugs in an instance of yourDriver's
-- communicator onto the gateway network component.
-- The Niagara station's platform will communicate
-- directly to the corresponding gateway unit on the
-- field-bus.
default{[ new BYourDriverCommunicator() ]} }
discoveryPreferences : BDdfDiscoveryPreferences
-- This saves the last set of discovery parameters
-- that the user provides on the device manager. It
-- is also used as the default for the first Time
-- that the user is prompted for a discovery.
default{[ new BYourDriverDeviceDiscoveryPreferences()]} }
}
-*/
❍ For a Tcp/Ip network that is not a Tcp/Ip gateway network, device discovery is probably not
possible.
SIDENOTE: If the network supports UDP Multi Cast capabilities then an auto-learn
might still be possible. However, UDP Multi Cast is a subject that is beyond the scope of
this tutorial.
10. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
Day 4 Conclusion
Congratulations! Your driver is now almost finished. In today's lesson you created a device discovery
parameters structure, a device discovery request, a device discovery response, and a device discovery
preferences structure. In Java code, you made some associations between these and your driver's network. By
doing this, your driver should now support device discovery.
To discover field-devices on your field-bus:
❍ Open a Workbench.
❍ Double-click your driver's network that you added in the previous day's lessons.
❍ You should see a Discover button at the bottom of the Dev Device Manager.
❍ Provided that you did not set the default value of the doNotAskAgain property on
❍ The job progress bar at the top of the Dev Device Manager should provide visual feedback
In your station, the driver will loop through all possible combinations of
BYourDriverDeviceParams from the min to max that was specified on the
window that popped up after you clicked the Discover button.
If all went well then you should see one or more rows in the top half of the Dev
Device Manager. This area of the Dev Device Manager is called the discover
pane. The columns in the discover pane should correspond to the properties in
BYourDriverDeviceId that are defined the MGR_INCLUDE facet.
❍ A window should appear that allows you to optionally edit some information.
❍ Click Ok.
station.
Tomorrow we will show you how to update your driver to support discovery of data points in your device. The
procedure for point-discovery is very similar to the procedure that you followed today for adding device-
discovery support to your driver.
If you are creating a driver that follows a serial, master-slave protocol then you likely modified the device id that
you previously created and made it serve also as the device discover parameters. You likewise modified the ping
request and ping response that you previously created and made them serve also as the device discover request and
device discover response.
Alternatively, you may have decided to create a separate device discovery parameters, device discovery request,
and device discovery response classes.
In both cases, you created a new class for the device discovery preferences structure.
By doing this, the developer driver framework uses these classes and associations in your driver to perform the
discovery feature on your network's Ddf Device Manager.
If you follow the steps in today's lesson and update your driver accordingly, then the developer driver
framework on which your driver is built, will automatically perform all details for the point discovery process.
After following today's procedure, your driver will support discovery of data points within your device. Today's
procedure is very similar to yesterday's procedure, although slightly more complicated.
Chapters - Day 5
● Chapter 27
● Chapter 28
● Chapter 29
● Chapter 30
● Chapter 31
● Conclusion
Please follow one of the next two paths (A or B, but not both).
A. If your driver's protocol features a message that can be sent to the field-device to ask it for a list data points:
package com.yourCompany.yourDriver.identify;
import com.tridium.ddf.identify.*;
import javax.baja.sys.*;
import com.yourCompany.yourDriver.comm.req.*;
import com.testCompany.yourDriver.discover.*;
Based on your driver's protocol document, please be prepared to add properties to the
slotomatic statement to help you construct the byte array in the point discovery request
(you will create the point discovery request in the next chapter). You may return to this class
and add properties to the slotomatic statement for any data values you require to
construct the byte array for your point discovery request (to be created in the next chapter).
3. Define the getFirst, getLast, getNext, and isAfter methods. These are essential and allow the
developer driver auto discovery process to automatically loop through all possible combinations of
your discovery parameters.
NOTE: Yesterday's lesson explained how to do this for the device discovery process.
getFirst
The getFirst method should return an instance of
BYourDriverPointDiscoverParams that represents the data that would be placed
in the protocol request to request information about the first data point or series
of data points. If your protocol has no way of identifying a definite first data
point then you will need to be creative here and return an instance of
BYourDriverPointDiscoverParams with all properties set to special values, that
you will determine, such as Integer.MIN_VALUE for int properties.
getLast
The getLast method should return an instance of
BYourDriverPointDiscoverParams that represents the data in the protocol
request that would request information about the last device or series of devices.
This might not be possible to accurately define for some protocols. If your
protocol does not have a way of identifying the last series of data points, then
you will need to be creative here and return an instance of
BYourDriverPointDiscoverParams with all properties set to special values, that
you will create, such as Integer.MAX_VALUE for int properties.
getNext
The getNext method should return an instance of
BYourDriverPointDiscoverParams that represents the data in the protocol
request that would request the next data point or series of data points, with
respect to the particular instance of BYourDriverPointDiscoverParams. This
method should review the values of some or all of the properties that you add to
BYourDriverPointDiscoverParams. This method should return a new instance
of BYourDriverPointDiscoverParams whose property values are incremented in
such a way that the new instance would represent the data needed in the
toByteArray method of the point discovery request in order to ask the field-
device for information about the next data point or set of data points.
isAfter
The isAfter method will be passed an instance of
BYourDriverPointDiscoverParams. You should review the property values of
the given instance of BYourDriverPointDiscoverParams and return true if the
current instance would request information about a data point or series of data
points that would be after the data point or series of data points that the given
instance of BYourDriverPointDiscoverParams would identify. If the current
instance represents a data point or series of data points that are before or equal
to the given instance, then this method should return false.
We understand that this can be a bit tricky, since the definitions of first, last, next, and
after can be somewhat vague in some protocols. Please feel free to skip ahead and revisit
this later if you wish.
As a reference, please see the sample getFirst, getLast, getNext, and isAfter methods
that are illustrated below, for part B.
4. Define the getDiscoverRequestType method and make it return an instance of your driver's point
discovery request. You will create the point discovery request later during today's lesson.
5. Define the getDiscoveryLeafType method and make it return a reference to your driver's point
discovery leafTYPE. The point discovery leaf will be created in a subsequent chapter of today's lesson.
/**
* This tells the developer driver framework that
* instances of BYourDriverDiscoveryLeaf will be
* placed into the discovery list of the point
* manager to represent each data point that the
* driver discovers.
*/
public Type getDiscoveryLeafType()
{
return BYourDriverPointDiscoveryLeaf.TYPE;
}
B. If your driver's protocol does not feature a message that can be sent to the field-device to ask it for a list of
data points then you will modify your driver's read parameters to also serve as the point discover parameters.
3. Your driver's read parameters structure, acting also as the point discover params structure, needs to
satisfy the BIDdfDiscoverParams interface and define the getFirst, getLast, getNext, and isAfter
methods in order to allow the developer driver auto discovery process to automatically loop through
all possible combinations of your discovery parameters.
Please review the discussion concerning these methods as described in part A (in this chapter - above)
EXAMPLE: In the hypothetical protocol that we used back during day 2 of this tutorial,
the read request retrieves data point values by asking the hypothetical field device for the
values of all points of a certain type and direction (for example, analog inputs, analog
outputs, digital inputs, or digital outputs).
In that scenario, the getFirst method would return an instance of
BYourDriverReadParameters with a type string of analog and a direction string of inputs.
The getNext method would return an instance of BYourDriverReadParameters with a
type string of analog but with a direction string of outputs. That instance's getNext
method would return an instance of BYourDriverReadParameters with a type string of
digital and a direction string of inputs. That instance's getNext method would return an
instance of BYourDriverReadParameters with a type string of digital but with a direction
string of outputs. Finally, the getLast method will return an instance of
BYourDriverReadParameters with a type string of digital and a direction string of outputs.
NOTE: In this example, we have imposed our own ordering for all of the possible
combinations of the sample read parameters structure.
Please define your own version of these methods using the following example as a guide.
NOTE: Your driver's read parameters structure also needs to import packages:
❍ javax.baja.registry.*
❍ com.tridium.ddf.discover.*
❍ com.yourCompany.yourDriver.discover.*
package com.yourCompany.yourDriver.identify;
import javax.baja.sys.*;
import javax.baja.registry.*;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.discover.*;
import com.yourCompany.yourDriver.comm.req.*;
import com.yourCompany.yourDriver.discover.*;
/**
* Niagara AX requires a public, empty constructor, so that it can perform
* Its own introspection operations.
*/
public BYourDriverReadParams(){}
/**
* This constructor is for our own convenience in the methods getFirst,
* getNext, etc.
*/
public BYourDriverReadParams(String typeString, String direction)
{
setTypeString(typeString);
setDirection(direction);
}
/**
* We created up this method to help with the isAfter and getNext methods.
*
* This method allows us to effectively enumerate all possible, reasonable
* instances of this class.
*/
private int getConvenientNumber()
{ // This creates an "ordering" on all reasonable instances of this class.
if (getTypeString().equals("analog") &&
getDirection().equals("inputs"))
{
return 0;
}
else if (getTypeString().equals("analog") &&
getDirection().equals("outputs"))
{
return 1;
}
else if (getTypeString().equals("digital") &&
getDirection().equals("inputs"))
{
return 2;
}
else // "digital outputs" or anything else (invalid as it
// might be)
{
return 3;
}
}
...
...
...
}
4. Define the getDiscoverRequestType method and make it return a reference to your driver's read
request TYPE.
REMINDER: In this scenario, your driver's read request will also serve during the point discovery process.
/**
* The read request will also serve during the
* point discovery process.
*/
public Type getDiscoverRequestType()
{
return BYourDriverReadRequest.TYPE;
}
5. Define the getDiscoverRequestTypes method and make it return an array of size one, with the one
element being a reference to your driver's read request TYPE.
REMINDER: In this scenario, your driver's read request will also serve during the point discovery process.
6. Define the getDiscoveryLeafType method and make it return a reference to your driver's point
discovery leafTYPE. The point discovery leaf will be created in a subsequent chapter of today's lesson.
/**
* This tells the developer driver framework that
* instances of BYourDriverDiscoveryLeaf will be
* placed into the discovery list of the point
* manager to represent each data point that the
* driver discovers.
*/
public Type getDiscoveryLeafType()
{
return BYourDriverPointDiscoveryLeaf.TYPE;
}
package com.yourCompany.yourDriver.discover;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.discover.*;
import com.yourCompany.yourDriver.identify.*;
import javax.baja.sys.*;
package com.yourCompany.yourDriver.discover;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.discover.*;
import com.yourCompany.yourDriver.identify.*;
import javax.baja.sys.*;
3. Implement the getDiscoveryName method and return a Java string that will serve as the recommended name.
Please note that Niagara AX installation professionals will see an instance of BYourDriverPointDiscoveryLeaf
in the Discovered spreadsheet-like list for each data point that your driver finds. If the installation professional
chooses to work further with a particular data point, then he or she will click the Add button to add that data
point to the database and thereby create a fully-qualified driver control point for the item. The string that you
return from this method will be assigned to the newly created driver control point, by default. The framework
will automatically add suffixes, as necessary, to ensure unique names in the database. You may return a simple,
static, string or you may compute the return string based on other information about the discovered point, as the
following example shows:
/**
* When a control point is added to the station from the
* Point Manager, it is given this name by default (possibly
* with a suffix to make it unique).
*/
public String getDiscoveryName()
{ // For the test driver, let's return a rather descriptive name
String typeString = ((BYourDriverReadParams)getReadParameters()).getTypeString();
String directionString = ((BYourDriverReadParams)getReadParameters()).getDirection();
int offset = ((BYourDriverPointId)getPointId()).getOffset();
// Capitalize the first letter of the typeString and the directionString
typeString = Character.toUpperCase(typeString.charAt(0)) +
typeString.substring(1);
directionString = Character.toUpperCase(directionString.charAt(0)) +
directionString.substring(1);
// Concatenates everything together and returns the result, for example,
// AnalogOutputs_1
return typeString + directionString + '_'+offset;
}
1. If your driver's protocol features a message that can be sent to the field-device to ask it
for a list data points:
❍ Create a class named BYourDriverPointDiscoverRequest that extends BDdfDiscoveryRequest
in the package com.yourCompany.yourDriver.comm.req
To do this, create a text file named BYourDriverPointDiscoverRequest.java in the jarFileName/src/
com/yourCompany/yourDriver/comm/req folder. Inside the text file, start with the following text:
package com.yourCompany.yourDriver.comm.req;
import javax.baja.sys.*;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddf.comm.req.*;
import com.tridium.ddf.comm.rsp.*;
import com.yourCompany.yourDriver.identify.*;
package com.yourCompany.yourDriver.comm.req;
import javax.baja.sys.*;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddf.comm.req.*;
import com.tridium.ddf.comm.rsp.*;
import com.yourCompany.yourDriver.identify.*;
import com.yourCompany.yourDriver.comm.rsp.*;
HINT: If you require information about the field-device in order to construct the
byte array that the toByteArray method returns then simply call the getDeviceId
method and cast the result to BYourDriverDeviceId!
In fact, all requests have access to the device id in this fashion.
2. If your driver's protocol does not feature a message that can be sent to the field-device to ask it for a list of
data points then you should modify your driver's read request to also serve as the point discover request.
❍ Modify the declaration of BYourDriverReadRequest and make it implement
BIDdfDiscoverRequest. Also make it import the following additional packages:
...
import com.tridium.ddf.discover.*;
import com.tridium.ddf.identify.*;
...
/*-
class BYourDriverReadRequest
{
properties
{
...
discoverParameters : BDdfIdParams
-- This provides the necessary data that the toByteArray method
-- Needs in order to construct the byte array. Since this class
-- Is the read request and also serves as the discovery request,
-- Then this property's value will be a copy of the value of the
-- Read Parameters property.
default{[new BYourDriverReadParams()]}
...
}
}
-*/
❍ Add the following methods to BYourDriverReadRequest. Please add them exactly as-is. You
shouldn't need to modify these methods, except maybe to change the comments.
/**
* The setDiscoverer method will be passed an instance of
* IDdfDiscoverer. You need to retain the reference on
* the instance and return it (whenever requested) from
* the getDiscoverer method.
*/
IDdfDiscoverer discoverer = null;
/**
* The BDdfAutoDiscoveryJob will pass an inner instance
* of itself to the setDiscoverer method. In there, you
* need to save away the reference. In here, please return
* the most recent reference that was passed to the
* setDiscoverer method.
*/
public IDdfDiscoverer getDiscoverer(){return discoverer;}
/**
* The BDdfAutoDiscoveryJob will pass an inner instance
* of itself here. Please save away the reference. Other
* than that, you should not need to concern yourself
* with this.
*/
public void setDiscoverer(IDdfDiscoverer discoverer)
{
this.discoverer=discoverer;
}
NOTES:
❍ You already defined the toByteArray method during one of the previous day's lessons. You
should not need to change it any further. It pulls the data necessary to construct the outgoing
frame from the read parameters structure.
❍ The discover parameters structure will be assigned a copy of the read parameters structure. This is
a special case since you are using the read request also as a discovery request.
A. If you created a class named BYourDriverPointDiscoverRequest previously during the day's lesson:
package com.yourCompany.yourDriver.comm.rsp;
import java.util.*;
import javax.baja.sys.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddf.comm.rsp.*;
import com.tridium.ddf.discover.*;
import com.yourCompany.yourDriver.discover.*;
import com.yourCompany.yourDriver.identify.*;
/**
* This method parses the response byte array and returns an
* array of BYourDriverPointDiscoveryLeaf objects describing
* the data points that this response is able to identify.
* This is called during the auto discovery process.
*/
public BIDdfDiscoveryObject[] parseDiscoveryObjects(Context c)
{
return null;
}
public BYourDriverPointDiscoverResponse()
{
}
4. Declare a constructor that takes as parameters any data that you will need to construct the return array
for the parseDiscoveryObjects method.
/**
* This constructor does not necessarily need to take an IDdfDataFrame
* as a parameter. It could take any parameters that you wish to pass
* to the response from the request's processReceive method. The data
* that is passed to this constructor will be saved on instance variables
* and used in the parseDiscoveryObjects method to construct the return
* array.
*/
public BYourDriverPointDiscoverResponse(IDdfDataFrame receiveFrame)
{
// TODO: Make a copy of any bytes that you need from the receiveFrame
// since the receive frame could be part of an internal buffer
// of the receiver.
}
5. Revisit your driver's point discovery request and implement the processReceive method.
The processReceive method should return an instance of BYourDriverPointDiscoverResponse. When
invoking the constructor of BYourDriverPointDiscoverResponse, pass in the received data frame
and/or any data that you will need to construct the return array for the parseDiscoveryObjects
method of BYourDriverDiscoverResponse.
package com.yourCompany.yourDriver.comm.req;
import java.util.*;
import javax.baja.sys.*;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddf.comm.req.*;
import com.tridium.ddf.comm.rsp.*;
import com.yourCompany.yourDriver.identify.*;
/**
* In our hypothetical protocol, the request returns a comma-
* separated list of analog inputs, analog outputs, digital
* inputs, or digital outputs.
*/
public BIDdfDiscoveryObject[] parseDiscoveryObjects(Context c)
{
// In our hypothetical protocol, the read request asks to read
// either analog or digital outputs or inputs. The field device
// Has up to 30 analog or digital outputs or inputs (the exact number
// can vary). The read response returns all of the corresponding
// analog input, analog output, digital input, or digital output values
// that were requested -- inside a comma delimited string. The substrings in
// between the commas are signed integers for analog values or
// The text "on" or "off" for digital values. The actual values are not
// important though. The position is the most important as it provides
// us with vital information as to how to read and/or write the individual
// data point.
if (rawValues==null) // Parses each of the values from the
parseRawValues(); // receive frame into a string array.
BYourDriverPointDiscoveryLeaf[] discoveredPoints =
new BYourDriverPointDiscoveryLeaf[rawValues.length];
// Loops once for each data point in the response data.
for (int i=0; i<discoveredPoints.length; i++)
{
discoveredPoints[i] = new BYourDriverPointDiscoveryLeaf();
// Sets the typeString property of the discovery leaf
((BYourDriverReadParams)discoveredPoints[i].getReadParameters()).
setTypeString(readParams.getTypeString());
// Sets the direction property of the discovery leaf
((BYourDriverReadParams)discoveredPoints[i].getReadParameters()).
setDirection(readParams.getDirection());
// We know that by returning a value for this index that the data point
// Exists in the hypothetical device
((BYourDriverPointId)discoveredPoints[i].getPointId()).setOffset(i);
// TODO: We could update the writeParameters on the discovery leaf too
// If we had anything there to update
}
return discoveredPoints;
}
B. If you decided to have your driver's read request and read parameters structure also serve as the discover
request/discover parameters:
package com.yourCompany.yourDriver.comm.rsp;
import javax.baja.sys.*;
import javax.baja.status.*;
import com.tridium.ddf.comm.*;
import com.tridium.ddf.comm.rsp.*;
import com.tridium.ddf.comm.req.*;
import com.tridium.ddf.discover.*;
import com.yourCompany.yourDriver.identify.*;
import com.yourCompany.yourDriver.point.*;
import com.yourCompany.yourDriver.discover.*;
/**
* This method parses the response byte array and returns an
* array of BYourDriverPointDiscoveryLeaf objects describing
* the data points that this response is able to identify.
* This is called during the auto discovery process.
*/
public BIDdfDiscoveryObject[] parseDiscoveryObjects(Context c)
{
return null;
}
/**
* In our hypothetical protocol, the request returns a comma-
* separated list of analog inputs, analog outputs, digital
* inputs, or digital outputs.
*/
public BIDdfDiscoveryObject[] parseDiscoveryObjects(Context c)
{
// In our hypothetical protocol, the read request asks to read
// either analog or digital outputs or inputs. The field device
// Has up to 30 analog or digital outputs or inputs (the exact number
// can vary). The read response returns all of the corresponding
// analog input, analog output, digital input, or digital output values
// that were requested -- inside a comma delimited string. The substrings in
// between the commas are signed integers for analog values or
// The text "on" or "off" for digital values. The actual values are not
// important though. The position is the most important as it provides
// us with vital information as to how to read and/or write the individual
// data point.
if (rawValues==null) // Parses each of the values from the
parseRawValues(); // receive frame into a string array.
BYourDriverPointDiscoveryLeaf[] discoveredPoints =
new BYourDriverPointDiscoveryLeaf[rawValues.length];
// Loops once for each data point in the response data.
for (int i=0; i<discoveredPoints.length; i++)
{
discoveredPoints[i] = new BYourDriverPointDiscoveryLeaf();
// Sets the typeString property of the discovery leaf
((BYourDriverReadParams)discoveredPoints[i].getReadParameters()).
setTypeString(readParams.getTypeString());
// Sets the direction property of the discovery leaf
((BYourDriverReadParams)discoveredPoints[i].getReadParameters()).
setDirection(readParams.getDirection());
// We know that by returning a value for this index that the data point
// Exists in the hypothetical device
((BYourDriverPointId)discoveredPoints[i].getPointId()).setOffset(i);
// TODO: We could update the writeParameters on the discovery leaf too
// If we had anything there to update
}
return discoveredPoints;
}
C. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
package com.yourCompany.yourDriver.discover;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.discover.auto.*;
import com.yourCompany.yourDriver.identify.*;
import javax.baja.sys.*;
Specify the default amount of time that your driver should wait after transmitting your point
discovery request before timing out.
retryCount
Specify the default number of retries that your driver should attempt after a request times-out
(before giving up on that particular request).
NOTE: Sometimes during a discovery process it could be helpful to specify shorter time-outs
and less retries so that the entire point discovery process can complete sooner.
min
Specify the default to be a copy of whatever your point discovery parameters class returns from
its getFirst method (or whatever your read request parameters class returns from its getFirst
method, if you chose to have it serve in the discovery process).
max
Specify the default to be a copy of whatever your point discovery params class returns from its
getLast method (or whatever your read request parameters class returns from its getLast
method, if you chose to have it serve in the discovery process).
NOTE: Your min and max properties will be instances of the read request id if you re-use your
read request as the discovery request. If not, your min and max will be instances of the
BYourDriverPointDiscoverParams object that you created during today's lesson.
doNotAskAgain (optional)
Declare the default value as True if you do not want the integrator to receive a special prompt
when he or she clicks the Discover button on the device manager. If you set this to true then the
discovery process will automatically loop from the min to the max that you also specify here.
Once you finish today's lesson, we encourage you to try this both ways and decide which way
makes the most sense for your driver.
package com.yourCompany.yourDriver.discover;
import com.tridium.ddf.identify.*;
import com.tridium.ddf.discover.auto.*;
import com.yourCompany.yourDriver.identify.*;
import javax.baja.sys.*;
3. Redefine the discoveryPreferences property on the BYourDriverPointDeviceExt class that you created
back during the lesson for day 2. Specify the default value to be an instance of
BYourDriverPointDiscoveryPreferences.
NOTE: Please also add statements to import com.yourCompany.yourDriver.discover.*; and com.tridium.
ddf.discover.*;
package com.yourCompany.yourDriver;
import javax.baja.sys.*;
import javax.baja.util.*;
import com.tridium.ddf.*;
import com.tridium.ddf.discover.*;
import com.yourCompany.yourDriver.point.*;
import com.yourCompany.yourDriver.discover.*;
4. Run slotomatic and perform a full build on your driver (as described in Chapter 2).
Day 5 Conclusion
Congratulations! You have followed all of the steps of the Developer Driver Tutorial! In today's lesson you
created a point discover parameters structure, a point discovery leaf, a point discover request, a point
discover response, and a point discovery preferences structure. In Java code, you made some associations
between these and your driver's point-device-extension. By doing this, your driver now supports point
discovery.
In your station, the driver will loop through all possible combinations of
BYourDriverPointDiscoverParams from the min to max that was specified on
the window that popped up after you clicked the Discover button (or if you
configured the default for the doNotAskAgain property to be True then the
driver will loop automatically from the min to the max.
If all went well then you should see one or more rows in the top half of the Point
Manager. This area of the Point Manager is called the discover pane. The
columns in the discover pane should correspond to the properties that are
declared with the MGR_INCLUDE facet on the read parameters, write
parameters, and point id structures that you defined on
BYourDriverPointDiscoveryLeaf.
The driver should automatically start polling the control point and updating the
control point's out value.
3. Have your do... method (for the new action that you added to your device) place an instance of the
request onto the device's communicator.
❍ Call getDdfCommunicator().communicate( new BYourDriverDoSomethingRequest() ); from the
do... method that defines the action's behavior.
❍ Note that the call to communicate is a non-blocking call. Please read further for an
explanation of how to perform special processing on the response.
4. To update your device after receiving the response to the Do Something request:
❍ Give the request a constructor that takes the device as a parameter.
❍ In your device's do... method, pass an instance of the device (using the Java this keyword) to
the request.
❍ Make the request class implement the BIDdfCustomResponse interface from the com.
tridium.ddf.comm.rsp package in the devDriver jar.
❍ Declare a processResponse method on the request. The developer driver framework will
automatically call this method when the do something response is received for the do
something request. The do something response is passed in as a parameter.
❍ In the processResponse method, you may use the reference to your device (the reference that
you passed to the request's constructor) and update your device accordingly.
❍ Declare a processTimeout method on the response. This method is automatically called if the
do something request times out. You may add any Java code here that you wish (or you may
leave the method empty).
❍ Declare a processErrorResponse method on the response. This method is automatically called
if the do something request's processRecieve method throws a DdfResponseException. You may
add any Java code here that you wish (or you may leave the method empty).
❍ Declare a processLateResponse method on the response. This method is automatically called if
the do something response is received after the request times out. Please note that this can
only happen if your driver uses a multiple-transaction-communicator. You may add any Java
code here that you wish (or you may leave the method empty).
/**
* This name can be either just a name or a lexicon key that defines the button text
and the
* optional button label.
*/
public String getUiName();
/**
* This method is called when the user clicks the
* corresponding button on the device manager for this
* agent.The developer may define any functionality
* here.
*
* NOTE: This will execute on the client-side proxy's
* virtual machine. Any access to the server-side
* host will therefore have to be through properties,
* actions, etc.
*
* @param a reference to the device manager
* @param a reference to the network that the device manager is
* operating upon
*
* @return an undo/redo command artifact or null
*/
public CommandArtifact doInvoke(BDdfDeviceManager deviceManager, BDdfNetwork network);
/**
* This method is called when the ddf device manager
* is created. It allows the developer to specify the MGR_CONTROLLER flags
* that govern whether a button, menu item, toolbar item, etc. is created
* for this agent.
*
* @return
*/
public int getFlags();
/**
* The developer should review the given BDdfDeviceManager and consider
* updating (eg. enable/disable) the given agentCommand and/or any other
* commands on the manager's controller. The method is called anytime
* there is a change of state on the device manager (eg. discovery list
* selection change, database list selection change, database component
* event, learn mode changed, etc.)
*
* For example (to enable the agent's UI widget(s) if one database item is selected):
* agentCommand.setEnabled(deviceManager.getController().getSelectedRows().length ==
1);
*
* For example (to enable the agent's UI widget(s) if one or more database items are
selected):
* agentCommand.setEnabled(deviceManager.getController().getSelectedRows().length > 0);
*
* For example (to enable the agent's UI widget(s) if zero database items are selected):
* agentCommand.setEnabled(deviceManager.getController().getSelectedRows().length ==
0);
*
* @param deviceManager
*
* @param agentCommand this is a special instance of IMgrCommand. It is
* a reference to the corresponding GUI command (button, menu item, and/or
* toolbar button) on the device manager.
*/
public void update(BDdfDeviceManager deviceManager, DdfDeviceMgrAgentCommand
agentCommand);
NOTE: In order to make complete use of a Device Manager Agent, some familiarity with aspects of
the core bajaui and baja driver framework will be required. For example, the value returned by
the getFlags method needs to be a Java int with various flags bit wise or'd from the javax.baja.
workbench.mgr.MgrController class. These flags can be:
Example
Here is an example that places an item onto the menu bar, action bar, and toolbar.
BTestDeviceMgrButton.java
package com.testCompany.testDriver.ui;
import javax.baja.sys.Sys;
import javax.baja.sys.Type;
import javax.baja.ui.CommandArtifact;
import com.tridium.ddf.BDdfNetwork;
import com.tridium.ddf.ui.DdfMgrControllerUtil.DdfDeviceMgrAgentCommand;
import com.tridium.ddf.ui.device.BDdfDeviceManager;
import com.tridium.ddf.ui.device.BDdfDeviceMgrAgent;
////////////////////////////////////////////////////////////////
// Type
////////////////////////////////////////////////////////////////
/**
* This name can be either just a name or a lexicon key that defines the button text
and the
* optional button label.
*/
public String getUiName()
{
return "DeviceMgr.TestButton";
}
/**
* This method is called when the user clicks the
* corresponding button on the device manager for this
* agent.The developer may define any functionality
* here.
*
* NOTE: This will execute on the client-side proxy's
* virtual machine. Any access to the server-side
* host will therefore have to be through properties,
* actions, etc.
*
* @param a reference to the device manager
* @param a reference to the network that the device manager is
* operating upon
*
* @return an undo/redo command artifact or null
*/
public CommandArtifact doInvoke(BDdfDeviceManager deviceManager, BDdfNetwork network)
{
System.out.println("'Test' button clicked on the device manager!");
return null;
}
/**
* The developer should review the given BDdfDeviceManager and consider
* updating (eg. enable/disable) the given agentCommand and/or any other
* commands on the manager's controller. The method is called anytime
* there is a change of state on the device manager (eg. discovery list
* selection change, database list selection change, database component
* event, learn mode changed, etc.)
*
* For example (to enable the agent's UI widget(s) if one database item is selected):
* agentCommand.setEnabled(deviceManager.getController().getSelectedRows().length ==
1);
*
* For example (to enable the agent's UI widget(s) if one or more database items are
selected):
* agentCommand.setEnabled(deviceManager.getController().getSelectedRows().length > 0);
*
* For example (to enable the agent's UI widget(s) if zero database items are selected):
* agentCommand.setEnabled(deviceManager.getController().getSelectedRows().length ==
0);
*
* @param deviceManager
*
* @param agentCommand this is a special instance of IMgrCommand. It is
* a reference to the corresponding GUI command (button, menu item, and/or
* toolbar button) on the device manager.
*/
public void update(BDdfDeviceManager deviceManager, DdfDeviceMgrAgentCommand
agentCommand)
{
// To disable the agent's button, toolbar button, and menu-item, I would need to
// Do this:
// agentCommand.setEnabled(false);
}
}
module-include.xml
The testDriver's module-include.xml file defines the BTestDeviceMgrButton as follows:
<types>
<!-- Type Example:
<type name="YourClass" class="com.yourDriver.BYourClass"/>
-->
<type name="TestDeviceMgrButton" class="com.testCompany.testDriver.ui.
BTestDeviceMgrButton">
<agent>
<on type="myTest:TestDriverNetwork"/>
</agent>
</type>
...
<type name="TestDriverNetwork" class="com.testCompany.testDriver.BTestDriverNetwork"/>
...
</types>
This defines BTestDeviceMgrButton as an agent on the BTestDriverNetwork component. When initializing itself,
the ddf device manager that BTestNetwork automatically receives (since it extends com.tridium.ddf.
BDdfNetwork) reviews the network on which it is operating (BTestNetwork in this case) and adds action buttons,
toolbar buttons, and menu items for each type in the driver whose class implements BIDdfDeviceMgrAgent
provided that the type is registered as an agent on the corresponding network type for the driver.
module.lexicon
DeviceMgr.TestButton.label=Test Device
DeviceMgr.TestButton.icon=module://myTest/images/myTest.png
DeviceMgr.TestButton.accelerator=CTRL+SHIFT+ALT+T
DeviceMgr.TestButton.description=This is a test button on the device manager
Please notice that the getUiName method returns "DeviceMgr.TestButton". The module.lexicon (which is a text,
properties file) defines entries for:
● DeviceMgr.TestButton.label
● DeviceMgr.TestButton.icon
● DeviceMgr.TestButton.accelerator
● DeviceMgr.TestButton.description
DeviceMgr.TestButton.label=Test Device
Defines the text that will be displayed inside the corresponding button on the device manager and for
the corresponding menu item on the device manager.
DeviceMgr.TestButton.icon=module://myTest/images/myTest.png
OPTIONAL. Defines the icon that will be displayed inside the corresponding button, menu item, and
toolbar button on the device manager. This identifies a sixteen-by-sixteen image of png format.
DeviceMgr.TestButton.accelerator=CTRL+SHIFT+ALT+T
OPTIONAL. Defines the hot key that will cause the corresponding button, menu item, and toolbar
button to be invoked from the device manager.
DeviceMgr.TestButton.description=This is a test button on the device manager
OPTIONAL. Niagara will use the description as it deems necessary to provide a hint to the end-user
explaining the function of the corresponding button, menu item, and toolbar button. For example,
Niagara might place this description into the Workbench status line.
The getUiName method, however, does not have to return a base into the lexicon. Alternatively, the getUiName
method can return a single key into the lexicon. If that is the case, then the device manager will use the
corresponding text directly as the label for the button and the menu text. This manner does not define an icon,
however, so no toolbar button will be generated (even if the getFlags method dictates otherwise).
Finally, if the String returned by the getUiName method is not found in your driver's lexicon at all then the
String itself will be used as the button and menu item's label (no toolbar button will be generated).
● The procedure for this is very similar to the procedure for adding a button to the device manager as
described in Appendix 2.
● Make a class that extends com.tridium.ddf.ui.point.BDdfPointMgrAgent
● In your driver's module-include file, declare your point manager agent class as an agent on your
driver's device or point-device-extension.
● The BDdfPointMgrAgent class implements the interface BIDdfPointMgrAgent. Here are the
method declarations for BIDdfDeviceMgrAgent. Please review these.
/**
* This name can be either just a name or a lexicon key that defines the button text
and the
* optional button label.
*/
public String getUiName();
/**
* This method is called when the user clicks the
* corresponding button on the point manager for this
* agent. The developer may define any functionality
* here.
*
* NOTE: This will execute on the client-side proxy's
* virtual machine. Any access to the server-side
* host will therefore have to be through properties,
* actions, etc.
*
* @param a reference to the point manager
* @param a reference to the network that is above the point device extension
* that the point manager is operating upon.
* @param a reference to the device that is above the point device ext
* that the point manager is operating upon
* @param a reference to the point-device-ext that the point manager
* is operating upon.
*
* @return an undo/redo command artifact or null
*/
public CommandArtifact doInvoke(BDdfPointManager deviceManager, BDdfNetwork network,
BDdfDevice device, BDdfPointDeviceExt ptDevExt);
/**
* This method is called when the ddf point manager
* is created. It allows the developer to specify the MGR_CONTROLLER flags
* that govern whether a button, menu item, toolbar item, etc. is created
* for this agent.
*
* @return
*/
public int getFlags();
/**
* The developer should review the given BDdfPointManager and consider
* updating (eg. enable/disable) the given agentCommand and/or any other
* commands on the manager's controller. The method is called anytime
* there is a change of state on the point manager (eg. discovery list
* selection change, database list selection change, database component
* event, learn mode changed, etc.)
*
* For example (to enable the agent's UI widget(s) if one database item is selected):
* agentCommand.setEnabled(pointManager.getController().getSelectedRows().length == 1);
*
* For example (to enable the agent's UI widget(s) if one or more database items are
selected):
* agentCommand.setEnabled(pointManager.getController().getSelectedRows().length > 0);
*
* For example (to enable the agent's UI widget(s) if zero database items are selected):
* agentCommand.setEnabled(pointManager.getController().getSelectedRows().length == 0);
*
* @param pointManager
*
* @param agentCommand this is a special instance of IMgrCommand. It is
* a reference to the corresponding GUI command (button, menu item, and/or
* toolbar button) on the device manager.
*/
public void update(BDdfPointManager pointManager, DdfPointMgrAgentCommand agentCommand);
● NOTE: BDdfPointMgrAgent provides default implementations of the update and getFlags methods.
Therefore, you really only need to define the getUiName and doInvoke methods.
● BDdfPointMgrAgent's default implementation of update does nothing. You should override this if
you need to enable or disabled the agent's button or otherwise change the appearance of the device
manager (to enable or disable other buttons, etc). Remember, the update method is called often
(basically whenever there is any detectable change on anything related to the point manager).
● BDdfPointMgrAgent's default implementation of getFlags always returns MgrController.BARS.
This places your agent onto the device manager as a button, menu item, and toolbar item (if you
specify a lexicon key that defines a toolbar icon from your getUiName method).
● Following this procedure will place a button, menu item, and (or) toolbar button onto the point
manager (depending on the value that the getFlags method returns). When either of these are clicked
on the Point Manager, your agent's doInvoke method will be called (on the client-side) Java virtual
machine. This is where you may define any behavior that should happen as a result.
NOTE: In order to make complete use of a Point Manager Agent, some familiarity with aspects of
the core bajaui and baja driver framework will be required. For example, the value returned by
the getFlags method needs to be a Java int with various flags bit wise or'd from the javax.baja.
workbench.mgr.MgrController class. These flags can be:
Example
Please refer to the example provided in appendix 2. Although that example illustrates how to implement an
agent on the device manager, everything required of the point manager is analogous to everything that is
required for the device manager.
Example
The following example allows only certain types of requests to be processed (transmitted onto the field-bus)
when certain conditions are in effect. If those certain conditions are not in effect, then this example allows all
requests to be processed (transmitted onto the field-bus).
/**
* This method is called for each request that is dequeued.
*
* Overridden in Sample driver to provide exclusive access to the
* communicator when doing terminal mode sessions and backups and/or
* restores of the controllers.
*
* If returning true then the request will be processed
* immediately (transmitted and scheduled for response checking). If
* the developer returns false then the request will be placed back
* in the back of the communicator queue.
*
* @param ddfRequest this is a request that was just pulled off of the
* communicator queue. However, this request has not yet been processed.
* This request will be processed immediately if this method returns
* true. This request will be processed later if this method returns
* false.
*
* @return true, unless the backup, restore, or terminal mode is active, and
* the request is a Reload, ReloadLine, or Keystroke command.
*/
protected boolean grantAccess(BIDdfRequest ddfRequest)
{
BSampleNetwork network = (BSampleNetwork)getParent();
// In terminal mode, the end-user has visited a Vt100 terminal display from
// within Workbench AX. He or she may type at the keyboard and the corresponding
// keys are passed through to the field-device. While in this mode, routine
// driver pinging, polling, etc. needs to temporarily stop.
if(network.isTerminalModeActive())
return ddfRequest instanceof BSampleKeystrokeRequest;
// If the driver is reloading the field-device then only those request types that
// are used during the reload process will be allowed to proceed. Any others will
// be denied access to the field-bus.
if(network.isReloadModeActive())
{
return (ddfRequest instanceof BSampleReloadNetRequest) ||
(ddfRequest instanceof BSampleReloadLineRequest) ||
(ddfRequest instanceof BSampleKeystrokeRequest);
}
// If none of the above scenarios are in effect, then any request type may
// be granted access to the field-bus at this exact moment in time.
return true;
}
1. In the case where request and receive frame tags are in use, any incoming data frame whose tag does
not match the recently transmitted request (or requests, if the driver uses a multiple transaction
communicator) will be passed to the communicator's unsolicited handler. Also any incoming data
frame whose tag does indeed match the outstanding request(s) tag but the outstanding request(s)'s
processReceive method neither returns a completed response nor throws a DdfResponseException (in
other words, it returns null or throws a RuntimeException). then the incoming data frame will be
passed to the unsolicited manager.
2. In the case where the driver does not define tags for its requests or incoming data frames, then any
incoming data frame whereby when passed to the outstanding request (or all outstanding requests in
the event of a multiple-transaction-communicator) and no outstanding request returns a response or
throws a DdfResponseException when passed the incoming data frame (in other words, if the
outstanding request(s) return null from the processReceive method or throw a RuntimeException
when passed the data frame), then the data frame will be passed to the communicator's unsolicited
handler
That being said, if a driver needs to process unsolicited data frames then the developer needs to create a class
that extends BDdfUnsolicitedMgr. The developer should override the processUnsolicitedFrame method.
The processUnsolicitedFrame method is passed the unsolicited data frame as a parameter. From there the
developer may perform any custom processing that he or she deems necessary.
The BDdfUnsolicitedManager implements its own queue and thread. It automatically queues up unsolicited
data frames for the communicator and processes each unsolicited frame on its own thread. Therefore, the
driver's unsolicited manager's processUnsolicitedFrame runs on the driver's own, dedicated, unsolicited-receive
handler thread.
After creating the unsolicited manager class, the developer needs to associate the unsolicited receive handler
with his or her driver's communicator. This is accomplished by redefining the unsolicitedMgr property on the
driver's communicator component. In fact, the BDdfCommunicator itself defines the unsolicitedMgr in its
slotomatic header as follows:
unsolicitedMgr : BDdfUnsolicitedMgr
-- The simplest of drivers will not need unsolicited support
default{[new BDdfNullUnsolicitedMgr()]}
Notice that the type of the unsolicitedMgr is BDdfUnsolicitedMgr. Also notice that the default value for the
unsolicitedMgr is an instance of a new BDdfNullUnsolicitedMgr.
As a side-note, the BDdfNullUnsolicitedMgr is a class that extends BDdfUnsolicitedMgr and provides an
empty processUnsolicitedFrame method. Furthermore the BDdfNullUnsolicitedMgr removes the queue
and the thread from the unsolicited manager, thereby serving a null version of an unsolicited manger. Since the
developer will extend BDdfUnsolicitedMgr and not BDdfNullUnsolicitedMgr, his or her unsolicited
manager will inherit its own queue and thread for processing unsolicited data frames.
The developer should redefine the unsolicitedMgr as follows, in his or her communicator (replacing
YourDriver with the name of the developer's driver):
unsolicitedMgr : BDdfUnsolicitedMgr
-- Plugs in an instance of BYourDriverUnsolicitedMgr as the
-- unsolicited handler for BYourDriverCommunicator
default{[new BYourDriverUnsolicitedMgr()]}
Depending on the situation, there are endless possibilities that the developer may need to support in processing
the unsolicited data frame. For example, if the developer needs the transmit a request onto the field-bus as a
result of the unsolicited data frame, then his or her code in the unsolicited manager class may call the
getDdfCommunicator method to gain a reference to the driver's communicator component.
(BDdfUnsolicitedMgr provides the getDdfCommunicator method as a convenience.) With the communicator
component, the developer may call the communicate method and pass it a new instance of a custom
BDdfRequest for the driver.
This is just one small example out of countless scenarios. We wish you pleasant development!
1. Move the particular property that you need to access from the pointId into the readParameters. This
step is generally the suggested course of action because it takes maximum advantage of devDriver's
ability to combine as many driver control points as possible into a single read request.
2. From your toByteArray method call getReadableSource(). This returns an array of IDdfReadable. These
are the proxy extensions for all of the driver control points that share the same read parameters
structure and therefore share the same read request. Then loop through the array of IDdfReadables,
verify that each is an instance of your driver's proxy ext class (future support of virtual points
anticipated), cast each to your driver's proxy ext class, call getPointId() upon each, cast the result to
your driver's point id class, and directly access the point id. Please beware that this step might not
allow devDriver to combine as many driver control points as possible into a single read request.
Note: Niagara will allow the user of the driver to add two identical control points to the database. This would
be accomplished by choosing a row in the Discovered list of the point manager and simply adding it two or more
times to the database. In this scenario, the devDriver framework will likewise group each of the identical
database points into the same read request. This optimizes throughput on the field-bus by using one single read
request to update each of the identical data points. Please keep this in mind while choosing option one or
option two from above. If you choose option two but then always index into location zero of the readable
source array (in a hard-coded manner) then please consider switching to use option one instead.