Niagara AX - Developer Guide
Niagara AX - Developer Guide
April 6, 2010
NiagaraAX Developer Guide
Confidentiality Notice
The information contained in this document is confidential information of Tridium, Inc., a Delaware corporation (“Tridium”). Such
information, and the software described herein, is furnished under a license agreement and may be used only in accordance with
that agreement.
The information contained in this document is provided solely for use by Tridium employees, licensees, and system owners; and,
except as permitted under the below copyright notice, is not to be released to, or reproduced for, anyone else.
While every effort has been made to assure the accuracy of this document, Tridium is not responsible for damages of any kind,
including without limitation 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 latest
product specifications can be found by contacting our corporate headquarters, Richmond, Virginia.
Trademark Notice
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, Niaga-
raAX Framework, and Sedona Framework are registered trademarks, and Workbench, WorkPlaceAX, and AXSupervisor, are trade-
marks of Tridium Inc. All other product names and services mentioned in this publication that is known to be trademarks, regis-
tered trademarks, or service marks are the property of their respective owners.
User Interface
Gx Provides an overview of gx graphic toolkit.
Bajaui Provides an overview of the widget component toolkit.
Workbench Overview of the workbench shell.
Web Overview of web APIs.
Hx Overview of Hx APIs.
Px Overview of Px technology.
Hx
Overview Overview of Hx Architecture
HxView Details of HxView
HxOp Details of HxOp
HxProfile Details of HxProfile
Events Detail of Events
Dialogs Details of Dialogs
Theming Details of Theming
Horizontal Applications
Control Overview of the control and automation module.
History Overview to the historical database module.
April 6, 2010 1
NiagaraAX-3.5 Developer Guide
BQL
BQL Overview of the Baja Query Language.
BQL Expressions Details on BQL expressions.
BQL Examples Provides BQL examples.
Drivers
Driver Framework Overview of the driver framework.
PointDevicelet For reading and writing proxy points.
HistoryDevicelet For importing and exporting histories.
AlarmDevicelet For routing incoming and outgoing alarms.
ScheduleDevicelet Used to perform master/slave scheduling.
Basic Driver APIs for the basic driver framework
BACnet APIs for the BACnet driver
Lonworks APIs for the Lonworks driver
Lon Markup Language Specification for the lonml XML format
Development Tools
Build Documentation on using the build tool to compile and package software modules.
Deploying Help How to build and package help documentation with Niagara modules.
Slot-o-matic 2000 Documentation for the slot-o-matic tool used to aid in the generation of boiler plate code for slot
definitions.
Architecture Diagrams
Software Stack Provides an illustration of the major software subsystems in Niagara AX.
Class Diagram Illustration of the class hierarchy.
Communication Illustrates Niagara's software processes and their protocols.
Remote Programming Provides an overview of programming with remote components over fox.
Driver Hierarchy Illustration of driver hierarchy.
ProxyExt Illustration of proxy point design.
Driver Learn Illustration of AbstractManager learn with discovery job.
April 6, 2010 2
NiagaraAX-3.5 Developer Guide
Niagara Overview
Mile High View
Niagara: a Java software framework and infrastructure with a focus on three major problems:
Problem Space
Java
The framework uses the Java VM as a common runtime environment across various operating systems and hardware platforms. The
core framework scales from small embedded controllers to high end servers. The framework runtime is targeted for J2ME compliant
VMs. The user interface toolkit and graphical programming tools are targetted for J2SE 1.4 VMs.
Embedded Systems
Niagara is targeted for embedded systems capable of running a Java VM. This excludes low devices without 32-bit processors or
several megs of RAM. But even embedded systems with the horsepower of low end workstations have special needs. They are always
headless and require remote administration. Embedded systems also tend to use solid state storage with limited write cycles and
much smaller volume capacities than hard drives.
Distributed Systems
The framework is designed to provide scalability to highly distributed systems composed of 10,000s of nodes running the Niagara
Framework software. Systems of this size span a wide range of network topologies and usually communicate over unreliable Internet
connections. Niagara is designed to provide an infrastructure for managing systems of this scale.
Component Software
Niagara tackles these challenges by using an architecture centered around the concept of "Component Oriented Development".
Components are pieces of self-describing software that can be assembled like building blocks to create new applications. A
component centric architecture solves many problems in Niagara:
Components provide a model used to normalize the data and features of heterogeneous protocols and networks so that they
can be integrated seamlessly.
Applications can be assembled with components using graphical tools. This allows new applications to be built without
requiring a Java developer.
Components provide unsurpassed visibility into applications. Since components are self-describing, it is very easy for tools to
introspect how an application is assembled, configured, and what is occurring at any point in time. This provides immense
value in debugging and maintaining Niagara applications.
Components enable software reuse.
April 6, 2010 3
NiagaraAX-3.5 Developer Guide
Architecture
Overview
This chapter introduces key concepts and terminology used in the Niagara architecture.
Programs
There are typically four different programs (or processes) associated with a Niagara system. These programs and their network
communication are illustrated via the Communications Diagram:
Station: is the Niagara runtime - a Java VM which runs a Niagara component application.
Workbench: is the Niagara tool - a Java VM which hosts Niagara plugin components.
Daemon: is a native daemon process. The daemon is used to boot stations and to manage platform configuration such as IP
settings.
Web Browser: is standard web browser such as IE or FireFox that hosts one of Niagara's web user interfaces.
Protocols
There are typically three network protocols that are used to integrate the four programs described above:
Fox: is the proprietary TCP/IP protocol used for station-to-station and workbench-to-station communication.
HTTP: is the standard protcol used by web browsers to access web pages from a station.
Niagarad: is the proprietary protocol used for workbench-to-daemon communication.
Platforms
Niagara is hosted on a wide range of platforms from small embedded controllers to high end servers:
Jace: the term Jace (Java Application Control Engine) is used to describe a variety of headless, embedded platforms. Typically
a Jace runs on a Flash file system and provides battery backup. Jaces usually host a station and a daemon process, but not
workbench. Jaces typically run QNX or embedded Windows XP as their operating system.
Supervisor: the term Supervisor is applied to a station running on a workstation or server class machine. Supervisors are
typically stations that provide support services to other stations within a system such as history or alarm concentration.
Supervisors by definition run a station, and may potentially run the daemon or workbench.
Client: most often clients running a desktop OS such as Windows or Linux access Niagara using the workbench or a web
browser.
Stations
The Niagara architecture is designed around the concept of component oriented programming. Components are self contained units
of code written in Java and packaged up for deployment as modules. Components are then wired together to define an application
and executed using the station runtime.
A Niagara application designed to be run as a station is stored in an XML file called config.bog. The bog file contains a tree of
components, their property configuration, and how they are wired together using links. Station databases can be created using a
variety of mechanisms:
Created on the fly and in the field using workbench graphical programming tools.
Created offline using workbench graphical programming tools.
Predefined and installed at manufacturing time.
Programmatically generated in the field, potentially from a learn operation.
Stations which restrict their programmability to accomplish a dedicated task are often called appliances.
Often the term Supevisor or Jace will be used interchangeably with station. Technically the term station describes the component
runtime environment common all to all platforms, and Supervisor and Jace describe the hosting platform.
Daemon
April 6, 2010 4
NiagaraAX-3.5 Developer Guide
The Niagara daemon is the one piece of Niagara written in native code, not Java. The daemon provides functionality used to
commission and bootstrap a Niagara platform:
On Windows platforms, the daemon is run in the background as a Window's service. On QNX it is run as a daemon process on
startup.
The most common way to access daemon functionality is through the workbench. A connection to the daemon is established via the
"Open Platform" command which opens a PlatformSession to the remote machine. A suite of views on the PlatformSession provides
tools for accomplishing the tasks listed above.
Another mechanism used to access daemon functionality is via the plat.exe command line utility. This utility provides much of the
functionality of the workbench tools, but via a command line program suitable for scripting. Run plat.exe in a console for more
information.
Workbench
Niagara includes a powerful tool framework called the workbench. The workbench is built using the bajaui widget framework which
is itself built using the standard Niagara component model.
The workbench architecture is designed to provide a common shell used to host plugins written by multiple vendors. The most
common type of plugin is a view which is a viewer or editor for working with a specific type of object such as a component or file.
Other plugins include sidebars and tools.
Workbench itself may be morphed into new applications using the BWbProfile API. Profiles allow developers to reuse the
workbench infrastructure to create custom applications by adding or removing menu items, toolbar buttons, sidebars, and views.
Web UI
An important feature of Niagara is the ability to provide a user interface via a standard web browser such as IE or FireFox. Niagara
provides both server side and client side technologies to build web UIs.
On the server side, the WebService component provides HTTP and HTTPS support in a station runtime. The WebService provides a
standard servlet engine. Servlets are deployed as components subclassed from BWebServlet. Additional classes and APIs are built
upon this foundation to provide higher level abstractions such as BServletView.
There are two client side technologies provided by Niagara. The first is web workbench which allows the standard workbench
software to be run inside a web browser using the Java Plugin. The web workbench uses a small applet called wbapplet to download
modules as needed to the client machine and to host the workbench shell. These modules are cached locally on the browser's hard
drive.
In addition to the web workbench, a suite of technology called hx is available. The hx framework is a set of server side servlets and a
client side JavaScript library. Hx allows a real-time user interface to be built without use of the Java Plugin. It requires only web
standards: HTML, CSS, and JavaScript.
Fox
The Niagara Framework includes a proprietary protocol called Fox which is used for all network communication between stations as
well as between Workbench and stations. Fox is a multiplexed peer to peer protocol which sits on top of a TCP connection. The
default port for Fox connections is 1911. Fox features include:
April 6, 2010 5
NiagaraAX-3.5 Developer Guide
Peer to peer
Request / response
Asynchronous eventing
Streaming
Ability to support multiple applications over a single socket via channel multiplexing
Text based framing and messaging for easy debugging
Unified message payload syntax
High performance
Java implementation of the protocol stack
API Stack
Niagara provides a broad suite of Java APIs used to customize and extend the station and workbench. The software stack diagram
illustrates the various software layers of the architecture:
Baja: The foundation of the architecture is defined via the baja module APIs. These APIs define the basics such as modules,
component model, naming, navigation, and security.
Horizontal: Niagara includes an extensive library of prebuilt components applicable to various M2M domains. The modules
provide standard components and APIs, including: control, alarming, historical data collection, scheduling, and BQL.
Drivers: Niagara is designed from the ground up to support multiple heterogeneous protocols. Modules designed to model
and synchronize data with external devices or systems are called drivers and are typically built with the driver framework.
Drivers integrate both fieldbus protocols like BACnet and Lonworks as well as enterprise systems like relational databases
and web services.
Human Interfaces: An extensive software stack is provided for user interfaces. The gx framework provides a standard model
and APIs for low level graphics. Built upon gx is the bajaui module which provides a professional toolkit of standard widgets.
Built upon bajaui is the workbench framework which provides the standard APIs for writing plugin tools. The px framework
and tools are used to enable non-programmers and developers alike to create new user interfaces via XML.
April 6, 2010 6
NiagaraAX-3.5 Developer Guide
API Information
Overview
There are a huge number of APIs available which are documented to varying degrees. In working with a specific API there are a
couple key points to understand:
Stability: a designation for the maturity of the API and its likelihood for incompatible changes;
Baja vs Tridium: public APIs are published under java.baja packages, and implementation specific code is published under
com.tridium ;
Stability
Public APIs are classified into three categories:
Stable: this designation is for mature APIs which have been thoroughly evaluated and locked down. Every attempt is made
to keep stable APIs source compatible between releases (a recompile may be necessary). Only critical bug fixes or design flaws
are just cause to break compatibility, and even then only between major revisions (such 3.0 to 3.1). This does not mean that
stable APIs are frozen, they will continue to be enhanced with new classes and new methods. But no existing classes or
methods will be removed.
Evaluation: this designation is for a functionally complete API published for public use. Evaluation APIs are mature enough
to use for production development. However, they have not received enough utilization and feedback to justify locking them
down. Evaluation APIs will likely undergo minor modification between major revisions (such 3.0 to 3.1). These changes will
likely break both binary and source compatibility. However, any changes should be easily incorporated into production code
with reasonable refactoring of the source code (such as a method being renamed).
Development: this designation is for code actively under development. It is published for customers who need the latest
development build of the framework. Non-compatible changes should be expected, with the potential for large scale redesign.
What is Baja?
Baja is a term coined from Building Automation Java Architecture. The core framework built by Tridium is designed to be
published as an open standard. This standard is being developed through Sun's Java Community Process as JSR 60. This JSR is still
an ongoing effort, but it is important to understand the distinction between Baja and Niagara.
Over time many more specifications for features will be added to Baja. But what is important to remember is that Baja is only a
specification. Niagara is an implementation of that specification. Furthermore you will find a vast number of features in Niagara, that
are not included under the Baja umbrella. In this respect Niagara provides a superset of the Baja features.
April 6, 2010 7
NiagaraAX-3.5 Developer Guide
starting with java or javax . The APIs developed for Baja are all grouped under javax.baja . These are APIs that will be part of the
open Baja specification and maybe implemented by vendors other than Tridium. Using these APIs guarantees a measure of vendor
neutrality and backward compatibility.
Software developed by Tridium which is proprietary and outside of the Baja specification is grouped under the com.tridium
packages. The com.tridium packages contain code specific to how Niagara implements the Baja APIs. The com.tridium code may
or may not be documented. Most often these packages have their components and slots documented (doc=bajaonly), but not their
low level fields and methods. In general com.tridium APIs should never be used by developers, and no compatibility is guaranteed.
Note: Tridium has developed some APIs under javax.baja even though they are not currently part of the Baja specification. These
are APIs that Tridium feels may eventually be published through Baja, but are currently in a development stage.
April 6, 2010 8
NiagaraAX-3.5 Developer Guide
Modules
Overview
The first step in understanding the Niagara architecture is to grasp the concept of modules. Modules are the smallest unit of
deployment and versioning in the Niagara architecture. A module is:
Versions
Versions are specified as a series of whole numbers separated by a period, for example "1.0.3042". Two versions can be compared
resulting in equality, less than, or greater than. This comparision is made by comparing the version numbers from left to right. If two
versions are equal, except one contains more numbers then it is considered greater than the shorter version. For example:
Every module has two versions. The first is the "bajaVersion" which maps the module to a Baja specification version. If the module is
not published under the Baja process then this value is "0". Secondly every module declares a "vendor" name and "vendorVersion".
The vendor name is a case insensitive identifier for the company who developed the module and the vendorVersion identifies the
vendor's specific version of that module.
Tridium's vendorVersions are formatted as "major.minor.build[.patch]:
So the vendorVersion "3.0.22" represents a module of build 22 in Niagara release 3.0. The vendorVersion "3.0.45.2" is the second
patch of build 45 in release 3.0.
Manifest
All module JAR files must include a manifest file in "meta-inf/module.xml". The best way to examine the contents of this file is to
take an example:
<dependencies>
<dependency name="baja" vendor="Tridium" vendorVersion="3.0"/>
April 6, 2010 9
NiagaraAX-3.5 Developer Guide
<dependency name="bajaui" vendor="Tridium" vendorVersion="3.0"/>
<dependency name="bql" vendor="Tridium" vendorVersion="3.0"/>
<dependency name="gx" vendor="Tridium" vendorVersion="3.0"/>
<dependency name="workbench" vendor="Tridium" vendorVersion="3.0"/>
</dependencies>
<dirs>
<dir name="javax/baja/control" install="runtime"/>
<dir name="javax/baja/control/enum" install="runtime"/>
<dir name="javax/baja/control/ext" install="runtime"/>
<dir name="javax/baja/control/trigger" install="runtime"/>
<dir name="javax/baja/control/util" install="runtime"/>
<dir name="com/tridium/control/ui" install="ui"/>
<dir name="com/tridium/control/ui/trigger" install="ui"/>
<dir name="doc" install="doc"/>
</dirs>
<defs>
<def name="control.foo" value="something"/>
</defs>
<types>
<type name="FooBar" class="javax.baja.control.BFooBar"/>
</types>
</module>
Looking at the root module element the following attributes are required:
name: The globally unique name of the module. Developers should use a unique prefix for their modules to avoid name
collisions. Module names must be one to 25 ASCII characters in length.
bajaVersion: Baja specification version as discussed above.
vendor: The company name of the module's owner.
vendorVersion: Vendor specific version as discussed above.
description: A short summary of the module's purpose.
preferredSymbol: This is used during XML serialization.
All modules must include a dirs element, which contains a dir subelement for each of the module's content directories. Each dir
has a name attribute which contains a system-home relative file path for a directory in the module, and an install attribute which
has one of the following values:
All modules include zero or one dependencies element. This element contains zero or more dependency elements which
enumerate the module's dependencies. Dependencies must be resolved by the framework before the module can be successfully used.
Each dependency has one required attribute. The name attribute specifies the globally unique name of the dependent module. A
dependency may also specify a bajaVersion and/or a vendor version. If the bajaVersion attribute is declared then it specifies the
lowest bajaVersion of the dependent module required. It is assumed that higher versions of a module are backward compatible, thus
any version greater than the one specified in a dependency is considered usable. Likewise the vendor and vendorVersion may be
specified to declare a dependency on a specific implementation of a module. The vendor attribute may be specified without the
vendorVersion attribute, but not vise versa. The required embed attribute specifies whether the dependency should be enforced on
embedded Niagara devices.
April 6, 2010 10
NiagaraAX-3.5 Developer Guide
Modules can declare zero or more def elements which store String name/value pairs. The defs from all modules are collapsed into a
global def database by the registry.
Modules which contain concrete Niagara BObjects also include a types element. This element includes zero or more type
elements. Each type element defines a mapping between a Baja type name and a Java class name. This definition is specified in the
two required attributes type and class.
April 6, 2010 11
NiagaraAX-3.5 Developer Guide
Object Model
Niagara Types
The heart of Niagara is its type system layered above Java type system. Niagara Types are monikers to a Java class in a specific
module. The interface javax.baja.sys.Type is used to represent Types in the Niagara Framework. Every Type is globally
identified by its module name and its type name. As previously discussed, a module name globally identifies a Niagara software
module. The type name is a simple String name which is mapped to a Java class name by the "module.xml" manifest file. Type's are
commonly identified using a format of:
Examples:
baja:AbsTime
bajaui:TextField
Note: to avoid confusion with the various uses of the word type, we will use capitalization when talking about a Niagara Type.
BObject
All Java classes which implement a Niagara Type are subclassed from BObject . It is useful to compare Type and BObject to their
low level Java counter parts:
Java Niagara
java.lang.Object javax.baja.sys.BObject
java.lang.Class javax.baja.sys.Type
java.lang.reflect.Member javax.baja.sys.Slot (discussed later)
Type and Slot capture the concepts of meta-data, while BObject provides the base class of Niagara object instances themselves.
BInterface
Java interfaces may be mapped into the Niagara type system by extending BInterface . You can query whether a Type maps to a
class or an interface using the method isInterface().
Classes which implement BInterface must also extent BObject . All BInterfaces class names should be prefixed with "BI".
BObject Semantics
Subclassing from BObject provides some common semantics that all instances of Niagara Types share:
Building BObject
By subclassing BObject you make an ordinary Java class into a Nigara Type. You must obey the following rules when creating a
Type:
Types must declare a mapping between their type name and their qualified Java class name in "module.xml". The Java class
name must always be prefixed with 'B', but the type name doesn't include this leading 'B'. For example:
All Types must override the getType() method to return a statically cached Type instance created by the Sys.loadType()
April 6, 2010 12
NiagaraAX-3.5 Developer Guide
method:
April 6, 2010 13
NiagaraAX-3.5 Developer Guide
Component Model
Introduction
Built upon Niagara's object model is the component model. Components are a special class of BObjects used to assemble
applications using graphical programming tools.
Slots
Niagara components are defined as a collection of Slots. There are three types of slots:
The Java interfaces used to model slots in the Niagara framework are:
javax.baja.sys.Slot
|
+- javax.baja.sys.Property
|
+- javax.baja.sys.Action
|
+- javax.baja.sys.Topic
Every slot is identified by a String name which is unique within its Type. Slot names must contain ASCII letters or numbers. Other
characters may be escaped using "$xx" or "$uxxxx". Refer to SlotPath for the formal grammar of slot names and utilities for escaping
and unescaping.
Slots also contain a 32-bit mask of flags which provide additional meta-data about the slot. These flag constants are defined in the
javax.baja.sys.Flags class. Additional meta-data which is not predefined by a flag constant may be specified using BFacets
which support arbitrary name/value pairs
Slots are either frozen or dynamic. A frozen slot is defined at compile time within a Type's Java class. Frozen slots are consistent
across all instances of a specified Type. Dynamic slots may be added, removed, renamed, and reordered during runtime. The power
of the Niagara Framework is in providing a consistent model for both compile time slots and runtime slots. Frozen and dynamic slots
are discussed in detail in Building Complexes.
BValue
All values of Property slots are instances of javax.baja.sys.BValue . The BValue class hierarchy is:
javax.baja.sys.BObject
|
+- javax.baja.sys.BValue
|
+- javax.baja.sys.BSimple
|
+- javax.baja.sys.BComplex
|
+- javax.baja.sys.BStruct
|
+- javax.baja.sys.BComponent
BSimples are atomic Types in the Niagara Framework, they never contain any slots themselves. The BComplex class is used to built
April 6, 2010 14
NiagaraAX-3.5 Developer Guide
Types which are composed of slots. Every BComplex can be recursively broken down into its primitive BSimples.
Building BValues
To define new BValues types refer to the following for rules and design patterns:
April 6, 2010 15
NiagaraAX-3.5 Developer Guide
Building Simples
Overview
BSimple is the base class for all atomic data types in Niagara. As an atomic data type, BSimples store a simple piece of data which
cannot be decomposed. All simples are immutable, that is once an instance is created it may never change its state. Concrete
subclasses of BSimples must meet the following requirements:
Convention is to make constructors private and provide one or more factory methods called make .
Example
The following source provides a example:
/*
* Copyright 2000 Tridium, Inc. All Rights Reserved.
*/
package javax.baja.sys;
import java.io.*;
/**
* The BInteger is the wrapper class for Java primitive
* int objects.
*/
public final class BInteger
extends BNumber
{
April 6, 2010 16
NiagaraAX-3.5 Developer Guide
}
April 6, 2010 17
NiagaraAX-3.5 Developer Guide
}
catch(Exception e)
{
throw new IOException("Invalid integer: " + s);
}
}
April 6, 2010 18
NiagaraAX-3.5 Developer Guide
Building Enums
Overview
The BEnum base class is used to define enumerated types. An enum is composed of a fixed set of int/String pairs called its range. The
int identifiers are called ordinals and the String identifiers are called tags. Enum ranges are managed by the BEnumRange class.
There are three subclasses of BEnum . BBoolean is a special case which models a boolean primitive. The BDynamicEnum class is used
to manage weakly typed enums which may store any ordinal and range. Strongly typed enums may be defined at compile time by
subclassing BFrozenEnum . The Niagara Framework builds a BFrozenEnum's range using the following set of introspection rules:
Example
The following source provides a complete example of the implementation for BOrientation :
/*
* Copyright 2000 Tridium, Inc. All Rights Reserved.
*/
package javax.baja.ui.enum;
import javax.baja.sys.*;
/**
* BOrientation defines a widget's orientation as
* either horizontal or vertical.
*/
public final class BOrientation
extends BFrozenEnum
{
April 6, 2010 19
NiagaraAX-3.5 Developer Guide
}
April 6, 2010 20
NiagaraAX-3.5 Developer Guide
Building Complexes
BStructs vs BComponents
BComplex is the base class for both BStruct and BComponent . Classes never subclass BComplex directly (it doesn't support any
public or protected constructors). Rather developers subclass from BStruct or BComponent depending on their needs. In general
structs are used as complex data types. BStructs can be built only using frozen properties. BComponents support much more
flexibility and are built using frozen and dynamic slots of all types:
BStruct BComponent
Frozen Property X X
Frozen Action X
Frozen Topic X
Dynamic Property X
Dynamic Action X
Dynamic Topic X
As you will learn, BComponents are also the basis for many other features such as BOrds , links, and the event model. You may
wonder why you would use a BStruct ? There are two main reasons. The first is that because of its limited feature set, it is more
memory efficient. The other reason is that properties containing BComponents cannot be linked, but BStructs can be (see Links).
Building BComplexes
All concrete subclasses of BComplex must meet the following requirements:
Introspection Patterns
We have discussed how frozen slots are defined at compile time. Let's take a look at the frameworks knows when frozen slots have
been declared. Every slot is composed of two or three Java members. A member is the technical term for a Java field, method, or
constructor. At runtime the framework uses Java reflection to examine the members of each class, looking for patterns to self-
discover slots. These patterns are based on the patterns used by JavaBeans, with significant extensions. Remember introspection is
used only to define frozen slots, dynamic slots are not specified in the classfile itself. There is a different pattern for each slot type.
These introspection patterns require a fair amount of boiler plate code. Although it is not too painful to write this code by hand, you
may use Slot-o-matic to generate the boiler plate code for you.
Frozen Properties
Rules
Every frozen property must follow these rules:
Declare a public static final Property field where the field name is the property name.
The property field must be allocated a Property instance using the BComplex.newProperty() method. This method takes a
set of flags for the property, and a default value.
Declare a public getter method with JavaBean conventions: type getCapitalizedName() .
Declare a public setter method with JavaBean conventions: void setCapitalizedName(type v) .
The getter must call BObject.get(Property) . The method must not perform any addition behavior.
The setter must call BObject.set(Property, BObject). The method must not perform any additional behavior.
The only types which may be used in a property are: subclasses of BValue, boolean, int, long, float, double, and
String . The six non- BValue types have special accessors which should be used in the getter and setter implementations.
April 6, 2010 21
NiagaraAX-3.5 Developer Guide
Semantics
The introspection rules map Property meta-data as follows:
Example
The following illustrates an example for different property types:
Frozen Actions
Rules
Every frozen action must follow these rules:
Declare a public static final Action field where the field name is the action name.
The action must be allocated an Action instance using the BComponent.newAction() method. This method takes a set of
flags for the action and an optional default argument.
Declare a public invocation method with the action name. This method must return void or a BObject type. This method
must take zero or one parameters. If it takes a parameter, it should be a BObject type.
Declare a public implementation method, which is named doCapitalizedName. This method must have the same return type
as the invocation method. This method must have the same parameter list as the invocation method.
The implementation of the invocation method must call BComponent.invoke() . No other behavior is permitted in the
method.
Semantics
The introspection rules map Action meta-data as follows:
April 6, 2010 22
NiagaraAX-3.5 Developer Guide
Example
The following illustrates two examples. The first action contains neither a return value nor an argument value. The second declares
both a return and argument value:
// action: makeMyDay
public static final Action makeMyDay = newAction(0);
public void makeMyDay() { invoke(makeMyDay, null, null); }
public void doMakeMyDay() { System.out.println("Make my day!"); }
// action: increment
public static final Action increment = newAction(0, new BInteger(1));
public BInteger increment(BInteger v)
{ return (BInteger)invoke(increment, v, null); }
public BInteger doIncrement(BInteger i)
{ return new BInteger(i.getInt()+1); }
Frozen Topics
Rules
Every frozen topic must follow these rules:
Declare a public static final Topic field where the field name is the topic name.
Declare a fire method of the signature: void fireCapitalizedName(EventType) .
The implementation of the fire method is to call BComponent.fire() . No other behavior is permitted in the method.
Semantics
The introspection rules map Topic meta-data as follows:
Example
The following code example illustrates declaring a frozen topic:
// topic: exploded
public static final Topic exploded = newTopic(0);
public void fireExploded(BString event) { fire(exploded, event, null); }
Dynamic Slots
Dynamic slots are not declared as members in the classfile, but rather are managed at runtime using a set of methods on
BComponent . These methods allow you to add, remove, rename, and reorder dynamic slots. A small sample of these methods follows:
Note: You will notice that methods dealing with dynamic slots take a Property, not a Slot. This is because all dynamic slots including
April 6, 2010 23
NiagaraAX-3.5 Developer Guide
dynamic Actions and Topics are also Properties. Dynamic Actions and Topics are implemented by subclassing BAction and BTopic
respectively.
April 6, 2010 24
NiagaraAX-3.5 Developer Guide
Registry
Overview
The registry is a term for a small database built by the Niagara runtime whenever it detects that a module has been added, changed,
or removed. During the registry build process all the types in all the modules are scanned. Their classfiles are parsed to build an
index for the class hierarchy of all the Niagara types available in the installation.
Some of the functions the registry provides:
API
The Registry database may be accessed via Sys.getRegistry() . Since the primary use of the registry is to interrogate the system
about modules and types without loading them into memory, the registry API uses light weight wrappers:
Agents
An agent is a special BObject type that provides services for other BObject types. Agents are registered on their target types via the
module manifest and queried via the Registry interface. Agents are used extensively in the framework for late binding - such as
defining views, popup menus, or exporters for specified target types. Typically agent queries are combined with a type filter. For
example, to find all the BExporters registered on a given file:
A couple of examples of how an agent type is registered on a target type in the module manifest (module-include.xml):
Agents can be registered on a target only for a specific application using the app attribute within the agent tag. The application
name can be queried at runtime via the AgentInfo.getAppName() method. Agent application names are used in conjunction with
the getAppName() method of BWbProfile and BHxProfile . An example application specific agent:
April 6, 2010 25
NiagaraAX-3.5 Developer Guide
</agent>
</type>
Defs
Module's can declare zero or more defs in their module manifest. Defs are simple String name/value pairs that are collapsed into a
single global map by the registry. A good use of defs is to map a device id to a typespec, bog file, or some other metadata file. Then
the registry may be used to map devices to Niagara information at learn time.
Since the defs of all modules are collapsed into a single map, it is important to avoid name collisions. Convention is to prefix your
defs using module name plus a dot, for example "lonworks."
When using Niagara's standard build tools, defs are defined in your "module-include.xml":
<defs>
<def name="test.a" value="alpha"/>
<def name="test.b" value="beta"/>
</defs>
Spy
A good way to learn about the registry is to navigate its spy pages.
April 6, 2010 26
NiagaraAX-3.5 Developer Guide
Naming
Overview
Niagara provides a uniform naming system to identify any resource which may be represented using an instance of BObject . These
names are called ords for Object Resolution Descriptor. You can think of a ords as URIs on steriods.
An ord is a list of one or more queries separated by the "|" pipe symbol. Each query is an ASCII string formatted as
"<scheme>:<body>". The scheme name is a globally unique identifier which instructs Niagara how to find a piece of code to lookup an
object from the body string. The body string is opaque and is formatted differently depending on the scheme. The only rule is that it
can't contain a pipe symbol.
Queries can be piped together to let each scheme focus on how to lookup a specific type of object. In general absolute ords are of the
format: host | session | space . Some examples:
ip:somehost|fox:|file:/dir/somefile.txt
ip:somehost|fox:1912|station:|slot:/Graphics/Home
local:|module://icons/x16/cut.png
In the examples above note that the "ip" scheme is used to identify a host machine using an IP address. The "fox" scheme specifies a
session to that machine usually on a specific IP port number. In the first example we identify an instance of a file within somehost's
file system. In the second example we identify a specific component in the station database.
The third example illustrates a special case. The scheme "local" which always resolves to BLocalHost.INSTANCE is both a host
scheme and a session scheme. It represents objects found within the local VM.
APIs
The core naming APIs are defined in the javax.baja.naming package. Ords are represented using the BOrd class.
Ords may be resolved using the BOrd.resolve() or BOrd.get() methods. The resolve method returns an intermediate
OrdTarget that provides contextual information about how the ord was resolved. The get method is a convenience for
resolve().get() .
Ords may be absolute or relative. When resolving a relative ord you must pass in a base object. If no base object is specified then
BLocalHost.INSTANCE is assumed. Some simple examples of resolving an ord:
BIFile f1 = (BIFile)BOrd.make("module://icons/x16/cut.png").get();
BIFile f2 = (BIFile)BOrd.make("file:somefile.txt").get(baseDir);
Parsing
Ords may be parsed into their constituent queries using the method BOrd.parse() which returns OrdQuery[] . In many cases you
migth cast a OrdQuery into a concrete class. For example:
Common Schemes
The following is an informal introduction some common ord schemes used in Niagara.
ip:
The "ip" scheme is used to identify a BIpHost instance. Ords starting with "ip" are always absolute and ignore any base which may be
April 6, 2010 27
NiagaraAX-3.5 Developer Guide
specified. The body of a "ip" query is a DNS hostname or an IP address of the format "dd.dd.dd.dd".
fox:
The "fox" scheme is used to establish a Fox session. Fox is the primary protocol used by Niagara for IP communication. A "fox" query
is formatted as "fox:" or "fox:<port>". If port is unspecified then the default 1911 port is assumed.
file:
The "file" scheme is used to identify files on the file system. All file ords resolve to instances of javax.baja.file.BIFile . File
queries always parse into a FilePath File ords come in the following flavors:
Sys absolute paths indicate files rooted under the Niagara installation directory identified via Sys.getBajaHome() . User absolute
paths are rooted under the user home directory identified via Sys.getUserHome() . In the case of station VMs, user home is the
directory of the station database.
module:
The "module" scheme is used to access BIFiles inside the module jar files. The module scheme uses the "file:" scheme's formating
where the authority name is the module name. Module queries can be relative also. If the query is local absolute then it is assumed to
be relative to the current module. Module queries always parse into a FilePath
module://icons/x16/file.png
module://baja/javax/baja/sys/BObject.bajadoc
module:/doc/index.html
station:
The "station" scheme is used to resolve the BComponentSpace of a station database.
slot:
The "slot" scheme is used to resolve a BValue within a BComplex by walking down a path of slot names. Slot queries always parse
into a SlotPath.
h:
The "h" scheme is used to resolve a BComponent by its handle. Handles are unique String identifiers for BComponents within a
BComponentSpace . Handles provide a way to persistently identify a component independent of any renames which modify a
component's slot path.
service:
The "service" scheme is used to resolve a BComponent by its service type. The body of the query should be a type spec.
spy:
The "spy" scheme is used to navigate spy pages. The javax.baja.spy APIs provide a framework for making diagnostics information
easily available.
bql:
The "bql" scheme is used to encapsulate a BQL query.
April 6, 2010 28
NiagaraAX-3.5 Developer Guide
Links
Overview
Links are the basic mechanism of execution flow in the Niagara Framework. Links allow components to be wired together graphically
by propogating an event on a one slot to another slot. An event occurs:
Links
A link is used to establish an event relationship between two slots. There are two sides to the relationship:
Source: The source of the link is the BComponent generating the event either because one its properties is modified or one
its topics is fired. The source of a link is always passive in that is has no effect on the component itself.
Target: The target is the active side of the link. The target BComponent responds to an event from the source.
A link is established using a property slot on the target BComponent which is an instance of BLink . The BLink struct stores:
Note: The target ord is not stored explictly in a BLink because it is implicitly derived by being a direct child of the target component.
The following table diagrams how slots may be linked together:
Link Check
Every component has a set of predefined rules which allow links to be established. These rules are embodied in the LinkCheck class.
Subclasses may override the BComponent.doLinkCheck() method to provide additional link checking.
...or...
April 6, 2010 29
NiagaraAX-3.5 Developer Guide
link.activate();
An indirect link is created through indirect names. A BOrd specifies the source component and Strings are used for the source and
target slot names. Since an indirect link requires resolution of a BOrd to get its source component, the source is required to be
mounted when the link is activated. Indirect links are automatically removed if their source component is unmounted while the link
is activated. Examples of creating an indirect link:
Note: Links are rarely created programmatically, but rather are configured using the graphical programming tools. The major
exception to this rule is building GUIs in code. In this case it is best to establish direct links in your constructor.
Activation
Links exist in either an activated or deactivated state. When a link is activated it is actively propagating events from the source slot to
the target slot. Activated links also maintain a Knob on the source component. Knobs are basically a mirror image of a link stored on
the source component to indicate the source is actively propagating events over one or more links. When a link is deactivated event
propagation ceases and the Knob is removed from the source component.
Activation:
1. Links are activated when the BLink.activate() method is called. If the link is indirect, then the source ord must be
resolvable otherwise an UnresolvedException is thrown.
2. If creating a direct link using the BComponent.linkTo() method the link is automatically activated.
3. Enabled links are activated during BComponent start. This is how most indirect links are activated (at station boot time).
4. Anytime a BLink value is added as a dynamic property on a running BComponent it is activated.
Deactivation:
April 6, 2010 30
NiagaraAX-3.5 Developer Guide
Execution
Overview
It is important to understand how BComponents are executed so that your components play nicely in the Niagara Framework. The
Niagara execution model is based upon:
Running State
Every BComponent maintains a running state which may be checked via the BComponent.isRunning() method. A component may
be put into the running state via the BComponent.start() method and taken out of the running state via the BComponent.stop()
method.
By default whenever a BComponent is started, all of its descendent components are also started recursively. This behavior may be
suppressed using the Flags.NO_RUN flag on a property. During startup, any properties encountered with the noRun flag set will not
be recursed.
Every BComponent may add its component specific startup and shutdown behavior by overriding the started() and stopped()
methods. These methods should be kept short; any lengthy tasks should be spawned off on another thread.
Note: Developers will rarely call start() and stop() themselves. Rather these methods are automatically called during station
bootup and shutdown. See Station Bootstrap.
Links
The primary mechanism for execution flow is via the link mechanism. Links provide a powerful tool for configuring execution flow
at deployment time using Niagara's graphical programming tools. Developers should design their components so that hooks are
exposed via property, action, and topic slots.
One of the requirements for link propagation is normalized types. Therefore Niagara establishes some standard types which should
be used to provide normalized data. Any control point data should use one of the standard types found in the javax.baja.status
package.
Timers
Niagara provides a standard timer framework which should be used by components to setup periodic and one-shot timers. Timers
are created using the schedule() and schedulePeriodically() methods on Clock . Timer callbacks are an action slot. The
BComponent must be mounted and running in order to create a timer.
There are four types of timers created with four different methods on Clock. Two are one-shot timers and two are periodic timers.
The difference between the two one-shots and periodic timers is based on how the timers drift. Refer to the Clock bajadoc for more
information.
Async Actions
The Niagara execution model is event based. What this means is that events are chained through link propagation. This model allows
the possibility of feedback loops when a event will loop forever in a cyclical link chain. To prevent feedback loops, component which
might be configured with cyclical links should use async actions. An async action is an action slot with the Flags.ASYNC flag set.
Normal actions are invoked immediately either through a direct invocation or a link propagation. This invocation occurs on the
callers thread synchronously. On the other hand, async actions are designed to run asynchronously on another thread and
immediately return control to the callers thread. Typically async actions will coalesce multiple pending invocations.
By default async actions are scheduled by the built in engine manager. The engine manager automatically coalesces action
invocations, and schedules them to be run in the near future (100s of ms). Thus between actual execution times if the action is
April 6, 2010 31
NiagaraAX-3.5 Developer Guide
invoked one or one hundred times, it is only executed once every execution cycle. This makes it a very efficient way to handle event
blasts such as dozens of property changes at one time. However all timer callbacks and async actions in the VM share the same
engine manager thread, so developers should be cautious not to consume this thread except for short periods.
Niagara also provides a hook so that async actions may be scheduled by subclasses by overriding the post() method. Using this
method subclasses may schedule the action using their own queues and threads. A standard library for managing invocations, queues,
and threads is provided by the following utility classes:
Invocation
;
Queue
;
CoalesceQueue
;
Worker
;
ThreadPoolWorker
;
BWorker
;
BThreadPoolWorker
;
April 6, 2010 32
NiagaraAX-3.5 Developer Guide
Station
Overview
A station is the main unit of server processing in the Niagara architecture:
Bootstrap
The following defines the station boot process:
1. Load: The first phase of bootstrap is to deserialize the config.bog database into memory as a BStation and mount it into
the ord namespace as "local:|station:" .
2. Service Registration: Once the bog file has been loaded into memory and mounted, the framework registers all services.
Services are defined by implementing the BIService . After this step is complete each service from the bog file may be
resolved using the Sys.getService() and Sys.getServices() methods.
3. Service Initialization: Once all services are registered by the framework, each service is initialized via the
Service.serviceStarted() callback. This gives services a chance to initialize themselves after other services have been
registered, but before general components get started.
4. Component Start: After service initialization the entire component tree under "local:|station:" is started using
BComponent.start(). This call in turn results in the started() and descendentsStarted() callbacks. Once this phase is
complete the entire station database is in the running state and all active links continue propagation until the station is
shutdown.
5. Station Started: After all the components under the BStation have been started, each component receives the
stationStarted() callback. As a general rule, external communications should wait until this stage so that all components
get a chance to initialize themselves.
6. Steady State: Some control algorithms take a few seconds before the station should start sending control commands to
external devices. To handle this case there is a built-in timer during station bootstrap that waits a few seconds, then invokes
the BComponent.atSteadyState() callback. The steady state timer may be configured using the "nre.steadystate" system
property. Use Sys.atSteadyState() to check if a station VM has completed its steady state wait period.
April 6, 2010 33
NiagaraAX-3.5 Developer Guide
Remote Programming
Overview
Remote programming is one of the most powerful features of Niagara. It is also the number one cause of confusion and performance
problems. The term remote programming broadly applies to using the component model across a network connection. Some topics
like subscription are critical concepts for many subsystems. But most often remote programming applies to programming with
components in the workbench across a fox connection to a station (illustration).
The component model provides a number features for network programming:
Fundamentals
The component model has the ability to make remote programming virtually transparent. In this diagram, the component "/a/b" is
accessed in the workbench VM, but actually lives and is executing in the station VM. The instance of the component in the
workbench is called the proxy and the instance in the station is called the master.
The first thing to note in Niagara is that both the proxy and master are instances of the same class. This is unlike technologies such
as RMI where the proxy is accessed using a special interface. Also unlike RMI and its brethren, nothing special is required to make a
component remote accessible. All Niagara components are automatically remotable by virtue of subclassing BComponent.
From an API perspective there is no difference between programming against a proxy or a master component. Both are instances of
the same class with the same methods. However, sometimes it is important to make a distinction. The most common way to achieve
this is via the BComponent.isRunning() method. A master component will return true and a proxy false. Although isRunning() is
usually suitable for most circumstances, technically it covers other semantics such as working offline. The specific call for checking
proxy status is via BComponent.getComponentSpace().isProxyComponentSpace() .
Note that proxy components receive all the standard change callbacks like changed() or added() . Typically developers should short
circuit these callbacks if the component is not running since executing callback code within a proxy can produce unintended side
effects.
April 6, 2010 34
NiagaraAX-3.5 Developer Guide
Proxy Features
The framework provides a host of features which lets you program against a proxy component transparently:
The proxy can maintain the state of the master by synchronizing all properties in real-time;
Actions on the proxy act like RPCs;
Any changes to the proxy are automatically propagated to the master;
The framework provides the ability to keep a proxy's properties completely synchronized in real-time to the master using
subscription. While subscribed all property changes are immediately reflected in the proxy. This enables easy development of user
interfaces that reflect the current state of a component. Note that only properties support this feature - other fields of your class will
not be synchronized, and likely will be invalid if they are populated via station execution. Subscription is covered in more detail later.
Another feature of Niagara is that all actions automatically act like RPCs (Remote Procedure Calls). When you invoke an action on a
proxy, it automatically marshals the argument across the network, invokes the action on the master, and then marshals the result
back to the proxy VM. Note that all other methods are invoked locally.
Perhaps the most powerful feature of proxies is the ability to transparently and automatically propagate proxy side changes to the
master. For example when you set a property on a proxy, it actually marshals the change over the network and makes the set on the
master (which in turn synchronizes to the proxy once complete). This functionality works for all component changes: sets, adds,
removes, renames, reorders, flag sets, and facet sets. Note that if making many changes it is more economical to batch the changes
using a Transaction; this is discussed later.
Proxy States
A proxy component exists in three distinct states:
Unloaded: in this state the proxy has not even been loaded across the network.
Loaded: in this state the proxy is loaded across the network and is known to the proxy VM; it may or may not be out-of-
date with the master.
Subscribed: in this state the proxy is actively synchronized with the master.
When a session is first opened to a station, none of the components in the station are known in the workbench. Rather components
are lazily loaded into the workbench only when needed. Components which haven't been loaded yet are called unloaded.
Components become loaded via the BComplex.loadSlots() method. Components must always be loaded according to their tree
structure, thus once loaded it is guaranteed that all a component's ancestors are also loaded. Rarely does a developer use the
loadSlots() method. Rather components are loaded as the user expands the navigation tree or a component is resolved by ord.
A loaded component means that a proxy instance representing the master component has been created in the workbench. The
proxy instance is of the same class as the master, and occupies a slot in the tree structure identical to the master (remember all
ancestors must also be loaded). The proxy has the same identity as the master. That means calling methods such as getName() ,
getHandle() , and getSlotPath() return the same result. However, note that the absolute ords of a proxy and master will be
different since the proxy's ord includes how it was accessed over the network (see diagram).
Once a proxy component has been loaded, it remains cached in the loaded state until the session is closed. Loaded proxies maintain
their structure and identity automatically through the use of NavEvents. NavEvents are always routed across the network to maintain
the proxy tree structure independent of the more fine grained component eventing. For example if a loaded component is renamed,
it always reflects the new name independent of subscription state. Or if removed it is automatically removed from the cache.
Loaded components provide a cache of structure and identity, but they do not guarantee access to the current state of the master via
its properties. The subscribed state is used to synchronize a proxy with it's master. Subscription is achieved using a variety of
mechanisms discussed next. Once subscribed a component is guaranteed to have all its property values synchronized and kept up-
to-date with the master. Subscription is an expensive state compared to just being loaded, therefore it is imporant to unsubscribe
when finished working with a proxy.
Subscription
Subscription is a concept used throughout the framework. Components commonly model entities external to the VM. For example,
proxy components model a master component in the station VM. Likewise, components in a station often model an external system
April 6, 2010 35
NiagaraAX-3.5 Developer Guide
or device. Keeping components synchronized with their external representations is usually computationally expensive. Therefore all
components are built with a mechanism to be notified when they really need to be synchronized. This mechanism is called
subscription.
Subscription is a boolean state. A component can check it's current state via the BComponent.isSubscribed() method. The
subscribed() callback is invoked when entering the subscribed state, and unsubscribed() when exiting the subscribed state. The
subscribed state means that something is currently interested in the component. Subscribed usually means the component should
attempt to keep itself synchronized through polling or eventing. The unsubscribed state may be used to disable synchronization to
save CPU, memory, or bandwidth resources.
Subscriptions often chain across multiple tiers. For example when you subscribe to a component in the workbench, that subscribes to
the master in a station. Suppose the station component is a proxy point for a piece of data running in a Jace. That causes a
subscription over the station-to-station connection resulting in the Jace's component to be subscribed. If the Jace component models
an external device, that might initiate a polling operation. Keep in mind that n-tier subscribes might introduce delays. The stale status
bit is often used with subscription to indicate that a value hasn't yet been updated from an external device.
A component is moved into the subscribed state if any of the following are true:
If the component is running and any slot in the component is used as the source of an active link: isRunning() &&
getKnobs().length > 0 .
There are one or more active Subscribers .
The component is permanently subscribed via the setPermanentlySubscribed() method. A typical example is a control
point with an extension that returns true for requiresPointSubscription() .
Collectively these three cases are used by the framework to indicate interest in a component. The framework does not make a
distinction between how a component is subscribed, rather all three cases boil down to a simple boolean condition: subscribed or
unsubscribed.
The Subscriber API is the standard mechanism to register for component events. You can think of Subscriber as the BComponent
listener API. Subscriber maintains a list of all the components it is subscribed to, which makes cleanup easy via the
unsubscribeAll() method. Subscribers receive the event() callback for any component event in their subscription list. Note that
workbench developers typically use BWbComponentView which wraps the Subscriber API and provides automatic cleanup.
Leasing
A common need is to ensure that a component is synchronized, but only as a snapshot for immediate use. The framework provides a
feature called leasing to handle this problem. A lease is a temporary subscription, typically for one minute. After one minute, the
component automatically falls back to the unsubscribed state. However, if the component is leased again before the minute expires,
then the lease time is reset.
Leasing is accomplished via the BComponent.lease() method.
Batch Calls
Although the framework provides a nice abstraction for remote programming, you must be cognizant that network calls are occuring
under the covers and that network calls are extremely expensive operations. The number one cause of performance problems is too
many round robin network calls. The golden rule for remote programming is that one large batch network call is almost always
better performing than multiple small network calls. Niagara provides APIs to batch many common operations.
Batch Resolve
The first opportunity to batch network calls is when resolving more than one ord to a component. Resolving a component deep
down in the tree for the first time requires loading the component and all it's ancestors across the network. And if the ord is a handle
ord, a network call is needed to translate the handle into a slot path. The most efficient way to batch resolve is the via the
BatchResolve API.
Batch Subscribe
Subscription is another key area to perform batch network calls. There are three mechanisms for batch subscribe:
1. The first mechanism is to subscribe using a depth. The common case for subscription is when working with a subsection of
April 6, 2010 36
NiagaraAX-3.5 Developer Guide
the component tree. Depth based subscribe allows a component and a number of descendent levels to be subscribed via one
operation. For example if working with the children and grandchildren of a component, then subscribe with a depth of 2.
2. On rare occasions you may need to subscribe to a set of components scattered across the database. For this case there is a
subscribe method that accepts an array of BComponents. Both the Subscriber and BWbComponentView classes provide
methods that accept a depth or an array.
3. The third mechanism for batch subscribe is do a batch lease. Batch leasing is accomplished via the static
BComponent.lease() method.
Transactions
By default, when making changes to a proxy component, each change is immediately marshaled over the network to the master.
However, if making many changes, then it is more efficient to batch these changes using Transaction . Note most Transactions are
used to batch a network call, but do not provide atomic commit capability like a RDBMS transaction.
Transactions are passed as the Context to the various change methods like set() or add() . Instead of committing the change, the
change is buffered up in the Transaction. Note that Transaction implements Context and is a SyncBuffer . Refer to Transaction's
class header documentation for code examples.
Debugging
The following provides some tips for debugging remote components:
The spy pages provide a wealth of information about both proxy and master components including their subscribe state. A
component spy's page also contains information about why a component is subscribed including the knobs and registered
Subscribers. Note that right clicking a proxy component in the workbench causes a local lease, so it does introduce a Heisenberg
effect; one work around is to bookmark the spy page to avoid right clicks.
The outstanding leases of a VM can be accessed via the LeaseManager spy page.
The most common performance problem is not batching up network calls. The mechanism for diagnosis is to turn on fox tracing.
Specially the "fox.broker" log will illustrate network calls for loads, subscribes (sub), unsubscribes (unsub), and proxy side changes
(syncToMaster). The simplest way to turn on this tracing is Log Setup spy page.
April 6, 2010 37
NiagaraAX-3.5 Developer Guide
Files
Overview
The Niagara Framework is built upon the fundamental principle that everything of interest is modeled as a BObject . Files are one of
the most basic entities which are mapped into the object model.
The Niagara file model is a comprehensive architecture for mapping all files into a consistent set of APIs:
API
The javax.baja.file package provides the core APIs used for file acess. There are three core concepts in the file model:
1. BIFile : represents a file. In general file extensions are mapped to specific Types of BIFile using the registry. Effectively the
Niagara Type wraps the MIME type. For example common file types include file:TextFile, file:XmlFile ,
file:ImageFile , file:WordFile. The "file" module contains mappings for common file extensions.
2. BIFileStore : models a BIFile backing store. For example a file:TextFile might exist on the file system, in a zip file, or
over a network. Each of these file storage mechanism reads and writes the file differently. There a BIFileStore for every
BIFile which may be accessed via the BIFile.getStore() method. Common store types include baja:LocalFileStore ,
baja:MemoryFileStore , and baja:ZipFileEntry .
3. BFileSpace : represents a set of files with a common storage model. BFileSpaces are responsible for resolving FilePaths
into BIFiles . The prototypical file space is the singleton for local file system BFileSystem . The ord "local:|file:" always maps
to BFileSystem.INSTANCE .
Create an implementation of BIFile . Utilize one of the existing base classes such as baja:DataFile. If you wish to utilize
agents such as file text editors then you must extent file:TextFile or at least implement file:ITextFile .
Make sure you override getMimeType() to return the MIME type for the file's contents:
April 6, 2010 38
NiagaraAX-3.5 Developer Guide
Security
Overview
Security in the Niagara framework covers a couple of broad topics:
Users
The BUser component models security principles in a Niagara system. Typically BUsers map to a human user, but can also be used
to represent machine accounts for machine to machine logins.
The BUserService is used to store and lookup BUsers during login. The default implementation of BUserService simply stores the
system users as dynamic slots. You can alternatively use BLdapUserService to lookup users via the LDAP protocol.
BUser is used to store the authentication credentials, permissions, as well as any other required meta-data for each user. As a
developer if you wish to add additional meta-data to users, then you might consider declaring your own BIMixIn.
1. Fox Workbench to Station: When a connection is made from workbench to a station, the user is prompted for a username
and password which is used to authenticate the Fox connection.
2. Fox Station to Station: When a connection is made from a station to another station, a preconfigured username/password is
used to authenticate the Fox connection. These credentials are stored in the NiagaraStation.clientConnection
component.
3. HTTP Browser to Station: When a browser hits a station URL, an HTTP authentication mechanism is used to validate the
user.
Fox Authentication
The Fox authentication mechanism is configurable via the FoxService (usually under the NiagaraNetwork ). Authentication can be
either basic or digest. Digest is the preferred mechanism since the password is never passed in cleartext. However if using LDAP,
then basic authentication must be used.
HTTP Authentication
The BWebService is used to manage HTTP requests to a Niagara station. The HTTP realm is always the value of
April 6, 2010 39
NiagaraAX-3.5 Developer Guide
BStation.stationName . The default authentication mechanism used by the WebService is cookie based authentication. Cookie
authentication is required if using the workbench with the Java plugin. If you are deploying an application without web workbench
support, you can change the authentication mechaism via the WebService.authenticationScheme property.
You can use the guest account to provide access to some or all of your station without requiring a web login. If the guest account is
enabled, then any object the guest has operator read access to may be accessed via the web UI without a logon. As soon as an object
is accessed which guest does not have operator read on, then the logon challenge is issued. Set the guest user to disabled to turn this
feature off.
Categories
All objects designed to be protected by the security model implement the BIProtected interface. The BIProtected interface extends
from the BICategorizable interface. An ICategorizable object has the ability to be assigned to one or more categories. In essense a
category is just a number: Category 1, Category 2, Category 3, etc. You can give meaningful names categories by mapping category
numbers to a BCategory component within the BCategoryService. Most objects of interest implement the BIProtected interface
including BComponent , BIFile , and BIHistory .
Categories are just arbitrary groups - you can use categories to model whatever your imagination dreams up. Typically for security
they will map to some type of role, for example any device associated with lighting may be assigned to a "lighting" category. But that
same device may also be assigned to a "floor3" category.
Categories are implemented as variable length bit strings with each bit representing a category number: bit 1 for Category 1, bit 2 for
Category 2, etc. This bit mask is encapsulated via the BCategoryMask class. CategoryMasks are stored and displayed as hex strings,
for example the mask for membership in category 2 and 4 would be "a". There are two special CategoryMasks, the "" empty string
represents the NULL mask (membership in no categories) and "*" represents the WILDCARD mask (membership in all categories).
The BICategorizable interface provides a getCategoryMask() method to get the configured category mask for the object.
However most objects support the notation of category inheritence, where the configured mask is null and the applicable category
mask is inherited from an ancestor. This is called the applied category mask and is accessed via the getAppliedCategoryMask()
method.
Permissions
Once a user has been authenticated, the user is granted or denied permissions for each protected object in the system using the
user's configured BPermissionsMap. This map grants the user permissions for each category, thereby granting the user permissions
for objects assigned to that category. Users may be configured as super users by setting their permissions map to
BPermissionsMap.SUPER_USER . Super users are automatically granted every permission in every category for every object.
Permission Levels
Niagara defines two permission levels called operator and admin. Each slot in a BComponent is assigned to be operator or admin
based on whether the Flags.OPERATOR bit is set.
Permissions
Each slot is defined as admin or operator level. Six permissions are derived to control access to slots:
Computing Permissions
To check the permissions available for a specific object use the BIProtected.getPermissions(Context) method. If working with
an OrdTarget, then it is preferable to use OrdTarget.getPermissionsForTarget() , which computes the permissions once and
then caches the result.
The standard mechanism to compute permissions by an IProtected object is:
There are a couple special cases to note. First is that BComponent access requires access to the entire ancestor tree. For example to
access "c" in "/a/b/c", requires at least operatorRead access to "a" and "b". The system will automatically grant operatorRead to all
ancestors of a component which a user has at least one permission on. Note that this calculation is only done periodically, but can be
forced using the CategoryService.update action.
Another special case is BIFile which applies these special rules for file system protection:
1. Files in a BModule are automatically granted operatorRead (this does not include .class files which are never mapped into the
ord name space).
2. If the user is not a super user, automatically deny any permissions outside of the station home directory
3. Any remaining cases map to user's configured permissions via the file's categories
Checking Permissions
Permission checks are built-in at several layers of the framework:
April 6, 2010 41
NiagaraAX-3.5 Developer Guide
BComponent Modification
The following methods will check user permissions if a non-null Context is passed with a non-null BUser. If the permission is not
available then a PermissionException is thrown.
set(): If the property is operator, then must have operator write, otherwise admin write of the containing BComponent.
setFlags(): Must have admin write of containing BComponent.
add(): Must have admin write.
remove(): Must have admin write.
rename(): Must have admin write.
reorder(): Must have admin write.
invoke(): If the action is operator, then must have operator invoke, otherwise admin invoke.
Developers should take care to use the proper version of the method with a user context when applicable.
Fox Traffic
Fox is the primary protocol used for workbench-to-station and station-to-station communication. Fox automatically performs all
permission checks on the server side before sensitive data can be accessed or modified by a client. By the time a BComponent
reaches the client Fox ensures the following:
Dynamic slots which the user lacks permission to read are never sent across the network and will never appear in the client.
Frozen slots which is the user lacks permission to read/invoke will automatically have the hidden flag set.
Frozen properties which the user lacks permission to write will automatically have the readonly flag set.
Furthermore all attempts to modify components are checked by the server being committed.
Workbench Access
Each view declares the permissions a user is required to have on a given BComponent in order to access the view. These permissions
are usually declared in the module manifest (module-include). By default views require adminWrite. To override the default:
Note that required permissions for a dynamic PxViews are configured via the BPxView.requiredPermissions property.
Auditing
One of the important aspects of security is the ability to analyze what has happened after the fact. The Niagara component model is
designed to audit all property modifications and action invocations. Auditable actions include:
Property changed
Property added
Property removed
Property renamed
Properties reordered
Action invoked
Component modifications are only audited when the modification method is passed a non-null Context with a non-null BUser. The
history module includes a standard implementation of an audit trail stored to a history database file.
Code Samples
In order to check if a BUser has a operator read permission on specified component:
This snippet of code will throw a PermissionException if the user lacks the admin invoke permission:
user.check(target, BPermissions.adminInvoke)
April 6, 2010 42
NiagaraAX-3.5 Developer Guide
Use an AccessCursor to automatically skip slots that a user lacks permission to read/invoke:
April 6, 2010 43
NiagaraAX-3.5 Developer Guide
Localization
Overview
All aspects of the Niagara framework are designed for localization. The basic philosophy for localization is that one language may
supported in-place or multiple languages may be supported via indirection. The foundation of localization is based on the Context
and Lexicon APIs.
Context
Any framework API which is designed to return a string for human display, takes a Context parameter. Context provides information
to an API about the context of the call including the desired locale. Many APIs implement Context directly including OrdTarget,
ExportOp, and WebOp. For example if you are processing a web HTTP request, you can pass the WebOp instance as your Context
and the framework will automatically localize display strings based on the user who is logged in for that HTTP session.
Note that Workbench code always uses the default locale of the VM, so it is typical to just use null for Context. However code
designed to run in a station VM should always pass through Context.
Lexicon
Lexicons are Java properties files which store localized key/value pairs. A directory called "file:!lexicon/lang" is used to store the
lexicon files for a specific language, where lang is the locale code. Within the directory there is a file per module named
"moduleName.lexicon". Every module with a lexicon should also provide a fallback lexicon bundled in the root directory of module's
jar file: "module://moduleName/moduleName.lexicon" (note in the source tree it is just "module.lexicon").
Access to lexicons is provided via the Lexicon API.
BFormat
Many Niagara APIs make use of the BFormat class to store a formatted display string. BFormat provides the ability to insert special
function calls into the display string using the percent sign. One of these calls maps a string defined in a lexicon via the syntax
"%lexicon(module:key)%. Whenever a display string is stored as a BFormat, you may store one locale in-place or you may use the
%lexicon()% call to indirectly reference a lexicon string.
Slots
One of the first steps in localization, is to provide locale specific slot names. Every slot has a programmatic name and a context
sensitive display name. The process for deriving the display name for a slot:
1. BComplex.getDisplayName(Slot, Context): The first step is to call this API. You may override this method to provide your
own implementation for localization.
2. NameMap: The framework looks for a slot called "displayNames" that stores a BNameMap. If a NameMap is found and it
contains an entry for the slot, that is used for the display name. Note the NameMap value is evaluated as a BFormat, so it
may contain a lexicon call. NameMaps are useful ways to localize specific slots, localize instances, or to localize dynamic slots.
3. Lexicon: Next the framework attempts to find the display name for a slot using the lexicon. The lexicon module is based on
the slot's declaring type and the key is the slot name itself.
4. Slot Default: If we still haven't found a display name, then we use a fallback mechanism. If the slot is frozen, the display name
is the result of TextUtil.toFriendly(name) . If the slot is dynamic the display name is the result of
SlotPath.unescape(name).
Facets
Sometimes facets are used to store display string. In these cases, the string is interpreted as a BFormat so that a %lexicon()% call may
be configured. This design pattern is used for:
Boolean trueText
Boolean falseText
April 6, 2010 44
NiagaraAX-3.5 Developer Guide
FrozenEnums
Compile time enums subclass from BFrozenEnum. Similar to slot names and display names, enums have a programmatic tag and a
display tag. Localization of display tags uses the following process:
1. Lexicon: The framework first attempts to map the display tag to a lexicon. The module is the declaring type of the
FrozenEnum and the key is the programmatic tag.
2. Default: If a display tag isn't found in the lexicon, then the fallback is the result of TextUtil.toFriendly(tag) .
DynamicEnums
Localization of BDynamicEnums is done via the BEnumRange API. An EnumRange may be associated with a DynamicEnum directly
via DynamicEnum.make() or indirectly via Context facets. An EnumRange may be composed of a FrozenEnum's range and/or
dynamic ordinal/tag pairs. Any portion of the frozen range uses the same localization process as FrozenEnun. The dynamic portion
of the range uses the following process:
1. Lexicon: If BEnumRange.getOptions() contains a "lexicon" value, then we attempt to map the display tag to a lexicon where
the module is the value of the "lexicon" option and the key is the programmatic tag.
2. Default: If a display tag is not found using the lexicon, and the ordinal does map to a programmatic tag, then the result of
SlotPath.unescape(tag) is returned.
3. Ordinal: The display tag for an ordinal that isn't included in the range is the ordinal itself as a decimal integer.
User Interface
When building a user interface via the bajaui APIs, all display text should be localizable via lexicons. In the case of simple BLabels,
just using the Lexicon API is the best strategy.
The Command and ToggleCommand APIs also provide built-in support for fetching their label, icon, accelerator, and description
from a lexicon. Take the following code example:
In the example above DoIt would automatically have it's display configured from the declaring module's lexicon:
do.it.label=Do It
do.it.icon=module://icons/x16/build.png
do.it.accelerator=Ctrl+D
do.it.description=Do it, whatever it is.
Locale Selection
Every time a Niagara VM is started it attempts to select a default locale using the host operating system. The OS default may be
overridden via the command line flag "-locale:lang", where lang is the locale code. The locale code can be any string that maps to a
lexicon directory, but typically it is a ISO 639 locale code such as "fr". The default locale of the VM may be accessed via the
Sys.getLanguage() API.
When the workbench is launched as a desktop application it follows the rules above to select it's
locale. Once selected the entire workbench uses that locale independent of user accounts used to log
into stations.
The locale for web browser access to a station follows the rules:
1. User.language: If the language property of user is a non-empty string, then it defines the locale
to use.
2. Accept Language: Next the framework tries to select a locale based on the "Accept-Language" passed
in the browser's HTTP request. Typically this is configured in the browser's options.
April 6, 2010 45
NiagaraAX-3.5 Developer Guide
3. Default: If all else fails, then the default locale of the station's VM is used
Time Formatting
The default time format is defined by the lexicon key baja:timeFormat. But it may be selectively
overridden by users. To change the time format in the Workbench use General Options under Tools |
Options. Use the User.facets property to change it for browser users.
Niagara' time format uses a simple pattern language:
Pattern Description
YY Two digit year
YYYY Four digit year
M One digit month
MM Two digit month
MMM Abbreviated month name
D One digit day of month
DD Two digit day of month
h One digit 12 hour
hh Two digit 12 hour
H One digit 24 hour
HH Two digit 24 hour
mm Two digit minutes
ss Seconds (and milliseconds if applicable)
a AM/PM marker
z Timezone
anything else Character literal
In addition to the time format configured by the user, developers may customize the resolution via the
following facets:
BFacets.SHOW_TIME
BFacets.SHOW_DATE
BFacets.SHOW_SECONDS
BFacets.SHOW_MILLISECONDS
BFacets.SHOW_TIME_ZONE
To programmatically format a time using this infrastructure use the BAbsTime or BTime APIs.
Unit Conversion
By default the framework displays all numeric values using their configured units (via Context facets).
Users may override this behavior to have all values converted to the US/English system or SI/Metric
systems. To enable this feature in Workbench use General Options under Tools | Options. Use the
User.facets property to enable it for browser users.
The list of units known to the system and how to convert is configured via the file:!lib/units.xml XML
file. The mapping of those units between English and Metric is done in the file:!lib/unitConversion.xml
XML file.
To programmatically format and auto-convert numerics use the BFloat or BDouble APIs.
Note this unit conversion is independent of the conversion which may be performed by ProxyExts when
mapping a point into a driver.
April 6, 2010 46
NiagaraAX-3.5 Developer Guide
Spy
Overview
The Niagara Framework is built upon a principle of high visibility. By modeling everything as a BObjects most data and
functionality is automatically made visible using the tools built into the workbench. However it is infeasible to model all data using
the component model. The spy framework provides a diagnostics window into the system internals for debugging which goes
beyond the component model.
Spy pages are accessed via the spy:/ ord.
See javax.baja.spy package for more details.
April 6, 2010 47
NiagaraAX-3.5 Developer Guide
Licensing
Overview
The Niagara licensing model is based upon the following elements:
HostId A short String id which uniquely identifies a physical box which runs Niagara. This could a Windows workstation,
Jace-NP, or any Jace-XXX embedded platform. You can always check your hostId using the command "nre -version".
Certificate: A file ending in "certificate" which matches a vendor id to a public key. Certificates are granted by Tridium, and
digitally signed to prevent tampering. Certificates are stored in the "{home}\certificates" directory.
License File: A file ending in "license" which enables a set of vendor specific features. A licenses file is only valid for a
machine which matches its hostId. Licenses are digitally signed by a specific vendor to prevent tampering. License files are
stored in the "{home}\licenses" directory.
Feature: A feature is a unique item in the license database keyed by a vendor id and feature name. For example
"Tridium:jade" is required to run the Jade tool.
API: The javax.baja.license package provides a simple API to perform checks against the license database.
License File
A license file is an XML file with a ".license" extension. License files are placed in "{home}\licenses". The filename itself can be
whatever you like, but convention is to name the file based on the file's vendor id. The following is an example license file:
<license
version="1.0"
vendor="Acme"
generated="2002-06-01"
expiration="never"
hostId="Win-0000-1111-2222-3333">
<feature name="alpha"/>
<feature name="beta" expiration="2003-01-15"/>
<feature name="gamma" count="10"/>
<signature>MC0CFACwUvUwA+mNXMfogNb6PVURneerAhUAgZnTYb6kBCsvsmC2by1tUe/5k/4=</signature>
</license>
Validation
During bootstrap, the Niagara Framework loads its license database based on the files found in the "{home}\licenses" directory. Each
license file is validated using the following steps:
1. The hostId attribute matches the license file to a specific machine. If this license file is placed onto a machine with a
different hostId, then the license is automatically invalidated.
2. The expiration attribute in the root element specifies the master expiration. Expiration must be a format of "YYYY-MM-
DD". If the current time is past the expiration, the license file is invalidated. The string "never" may be used to indicate no
expiration.
3. The generated attribute in the root element specifies the license file generation date as "YYYY-MM-DD". If the current time
is before the generated date, the license file is invalidated.
4. The vendor attribute is used to inform the framework who has digitally signed this license file. In order to use a license file,
there must be a corresponding certificate file for that vendor in the "{home}\certificates" directory.
5. The signature element contains the digital signature of the license file. The digital signature is created by the vendor using
the vendor's private key. The signature is verified against the vendor's public key as found in the vendor's certificate. If the
digital signature indicates tampering, the license file is invalid.
Features
A license database is a list of features merged from the machine's license files that are validated using the procedure discussed above.
April 6, 2010 48
NiagaraAX-3.5 Developer Guide
Each feature is defined using a single XML element called feature . Features are identified by the vendor id which is signed into the
license file and a feature name defined by the name attribute.
The expiration attribute may be specified in the feature element to declare a feature level expiration. Expiration is a string in the
format of "never" or "YYYY-MM-DD". If expiration is not specified then never is assumed.
Each feature may declare zero or more name/value properties as additional XML attributes. In the example license above the
"gamma" feature has one property called "count" with a value of "10".
Predefined Features
The following is a list of predefined features used by the Niagara Framework. All of these features require a vendor id of "Tridium":
API Usage
The following are some snippets of Java code used to access the license database:
Checking Licenses
You may use the following mechanisms to check your license database:
April 6, 2010 49
NiagaraAX-3.5 Developer Guide
XML
Overview
The javax.baja.xml package defines the core XML API used in the Niagara architecture. The two cornerstones of this APIs are:
1. XElem: Provides a standard representation of an XML element tree to be used in memory. It is similar to the W3's DOM, but
much lighter weight.
2. XParser: XParser is a light weight XML parser. It may be used in two modes: to read an entire XML document into memory
or as a pull-parser.
The Baja XML APIs are designed to be small, fast, and easy to use. To achieve this simplicity many advanced features of XML are
not supported by the javax.baja.xml APIs:
Only UTF-8 and UTF-16 encodings are supported. Unicode characters in attributes and text sections are escaped using the
standard entity syntax '&#dd;' or '&#xhh;'.
All element, attribute, and character data productions are supported.
CDATA sections are supported.
Namespaces are supported at both the element and attribute level.
Doctype declarations, DTDs, entity declarations are all ignored by the XML parser. XML used in Niagara is always validated
at the application level for completeness and efficiency.
Processing instructions are ignored by the XML parser.
No access to comments is provided by the XML parser.
Character data consisting only of whitespace is always ignored.
Example XML
For the code examples provided we will use this file "test.xml":
Namespace: Elements which are in a namespace will return a non-null value for ns() . You may also use the prefix() and
uri() methods to access the namespace prefix and URI. The "xmlns" attribute defines the default namespace which will
apply to all child elements without an explicit prefix. The "xmlns:{prefix}" attribute defines an namespace used by child
elements with the specified prefix.
Name: The name() method returns the local name of the element without the prefix. You may also use qname() to get the
qualified name with the prefix.
Attributes: Every element has zero or more attributes declared within the element start tag. There are an abundance of
convenience methods used to access these attributes. Attributes without an explicit prefix are assumed to be in no
namespace, not the default namespace.
Content: Every element has zero of more content children. Each content child is either an XText or XElem instance.
April 6, 2010 50
NiagaraAX-3.5 Developer Guide
The following code illustrates many of the commonly used methods on XElem:
// get elements
System.out.println("elems() = " + root.elems().length);
System.out.println("elems(user) = " + root.elems("user").length);
// biff
XElem biff = root.elem(0);
System.out.println("biff.name = " + biff.name());
System.out.println("biff.ns = " + biff.ns());
System.out.println("biff.age = " + biff.get("age"));
// elvin
XElem elvis = root.elem(1);
XElem skills = elvis.elem("skills");
System.out.println("elvis.name = " + elvis.name());
System.out.println("elvis.ns = " + elvis.ns());
System.out.println("skills.sing = " + skills.getb("sing"));
root.name = root
root.ns = ns-stuff
elems() = 3
elems(user) = 2
biff.name = user
biff.ns = ns-user
biff.age = 29
elvis.name = user
elvis.ns = null
skills.sing = true
The above code follows the W3 DOM model of parsing a document entirely into memory. In most cases this is usually acceptable.
However it can create efficiency problems when parsing large documents, especially when mapping the XElems into other data
April 6, 2010 51
NiagaraAX-3.5 Developer Guide
structures. To support more efficient parsing of XML streams, XParser may also be used to read elements off the input stream one at
a time. This is similar to the SAX API, except you pull events instead of having them pushed to you. A pull model is much easier to
work with.
To work with the pull XParser APIs you will use the next() method to iterate through the content instances. This effectively
tokenizes the stream into XElem and XText chunks. Each call to next() advances to the next token and returns an int constant:
ELEM_START , ELEM_END, TEXT , or EOF. You may also check the type of the current token using type() . You may access the current
token using elem() or text() .
XParser maintains a stack of XElems for you from the root element down to the current element. You may check the depth of the
stack using the depth() method. You can also get the current element at any position in the stack using elem(int depth) .
It is very important to understand the XElem at given depth is only valid until the parser returns ELEM_END for that depth. After
that the element will be reused. The XText instance is only valid until the next call to next() . You can make a safe copy of the
current token using copy() .
The following code illustrates using XParser in pull mode:
root.start: root 1
biff.start: user 2
desc.start: description 3
desc.text: Biff rocks 3
desc.end: description 3
biff.end: user 2
elvis.start: user 2
April 6, 2010 52
NiagaraAX-3.5 Developer Guide
Bog Files
Overview
Niagara provides a standard XML format to store a tree of BValues. This XML format is called "bog" for Baja Object Graph. The bog
format is designed for the following criteria:
Bog files are typically given a ".bog" extention. Although the ".palette" extension can be used to distinguish a bog designed for use as
palette; other than extension bog and palette files are identical.
Bog files can be flat XML files or stored inside zip files. If zipped, then the zip file contains a single entry called "file.xml" with the
XML document. You use workbench to copy any BComponent to a directory on your file system to easily generate a bog.
API
In general the best way to read and write bog files is via the standard APIs. The BogEncoder class is used to write BValues to an
output stream using bog format. Note that BogEncoder subclasses XWriter for generating an XML document. You can use the
XWriter.setZipped() method to compress the bog file to to a zip file with one entry called "file.xml". In general you should use the
encodeDocument() method to generate a complete bog document. However you can also use BogEncoder to stream multiple
BValues to an XML document using encode().
The BogDecoder class is used to decode a bog document back into BValue instances. Note that BogDecoder subclasses XParser for
parsing XML. When decoding a bog file, XParser will automatically detect if the file is zipped or not. General usage is to use
decodeDocument() in conjunction with BogEncoder.encodeDocument() for decoding the entire XML document as a BValue.
However BogDecoder can also be used to decode BValues mixed with other XML data using BogDecoder.decode() and the
standard XParser APIs.
BogEncoder.marshal() and BogDecoder.unmarshal() are convenience methods to encode and decode a BValue to and from a
String.
Syntax
The bog format conforms to a very simple syntax. The root of a bog document must always be "bajaObjectGraph". Under the root
there are only three element types, which map to the three slot types:
Element Description
p Contains information about a property slot
a Contains information about a frozen action slot
t Contains information about a frozen topic slot
All other information is encoded into XML attributes:
Attribute Description
n This required attribute stores the slot name.
Defines a module symbol using the format "symbol=name". Once defined, the symbol is used in
m
subsequent t attributes.
Specifies the type of a property using the format "symbol:typename", where symbol must map to a
t module declaration earlier in the document. If unspecified, then the type of the property's default
value is used.
April 6, 2010 53
NiagaraAX-3.5 Developer Guide
Example
A short example of a kitControl:SineWave linked to a kitControl:Add component. The Add component has a dynamic slot called
description where value is "hello", operator flag is set, and facets are defined with multiLine=true.
April 6, 2010 54
NiagaraAX-3.5 Developer Guide
Distributions
Overview
A distribution is a platform-specific archive of deployable software. The distribution file:
Manifest
The distribution manifest is found in the meta-inf/dist.xml JAR entry. It
An example distribution file manifest is provided as a reference for the remaining specification:
<dist name="qnx-jace-york"
version="2.1.6"
description=""
buildDate="Thu Jan 18 10:58:39 Eastern Standard Time 2007"
buildHost="BRUTUS"
reboot="true"
noRunningApp="true"
absoluteElementPaths="true"
osInstall="true"
>
<dependencies>
<part name="york" desc="York System Board" />
</dependencies>
<exclusions>
<os name="qnx-jace-york" version="2.2" />
</exclusionss>
<provides>
<os name="qnx-jace-york" version="2.1.6" />
</provides>
<fileHandling>
<file name="dev/shmem/york.image" replace="oscrc"/>
April 6, 2010 55
NiagaraAX-3.5 Developer Guide
</fileHandling>
</dist>
os element [optional]
Describes an operating system dependency. Attributes:
April 6, 2010 56
NiagaraAX-3.5 Developer Guide
os element [optional]
Describes an operating system element. Attributes:
vm element [optional]
Describes an installable java virtual machine. Attributes:
remove element
Specifies a file or directory to be removed prior to installation. Its required name attribute specifies the path (according to
April 6, 2010 57
NiagaraAX-3.5 Developer Guide
absoluteElementPaths element) to the file/directory. If name specifies a directory, exceptions may be specified using the keep sub-
element.
April 6, 2010 58
NiagaraAX-3.5 Developer Guide
Test
Overview
Niagara includes a unit testing framework very similiar to JUnit, in the package javax.baja.test . There are two key pieces of the
test framework: BTest is the base class for defining new test cases and TestRunner is used to run a suite of tests.
BTests
The BTest class is the base class used to define new test cases. Create subclasses of BTest to create new tests. Any public methods
on your test class which begin with "test" will be your test methods. Within your test methods, you write code to assert a series of
conditions by calling the BTest.verifyXXX() methods.
A fresh instance of your test class is created for each test method invocation. Your class will receive the setup() callback before
each test method and the cleanup() callback after. The lifecycle of a test run:
BTest Example
Here is a simple example to test java.lang.String :
// acmeStuff:StringTest
public class BStringTest
extends BTest
{
April 6, 2010 59
NiagaraAX-3.5 Developer Guide
}
}
The example above has three test methods: testCharAt() , testTrim() , and testSubstring() . Each test asserts a series of
conditions - in this case using either verifyEq or verifySame .
TestRunner
The TestRunner class manages running BTests . You can use the TestRunner API directly or subclass it to plug the Niagara test
framework into other tools. But the easiest way to run tests is from the command line using test.exe which wraps
TestRunner.main(String[]) . The following illustrate some different ways to run the test suite:
April 6, 2010 60
NiagaraAX-3.5 Developer Guide
Virtual Components
Overview
Refer to the Virtual API (available only in Niagara 3.2 and beyond).
The virtual components feature was originally driven by a common use case of most drivers in Niagara AX. However, since the
original brainstorming for "phantom" components (later termed "virtual" components), it has since grown to cover a broader range of
possible applications. This document (intended for developers) will focus its examples on driver applications, but the idea of
transient, on-demand components can obviously reach to many other applications.
As mentioned, the term virtual components refers to transient, on-demand components in a station database that only exist when
needed. Virtual components are created dynamically only when they are first required by the station (ie. enter a subscribed state), and
then when they are no longer needed (ie. enter an unsubscribed state), they are automatically cleaned up from the station database
(subject to virtual cache life constraints). This lifecycle for virtual components provides for efficiency. The key concepts that drive
virtual components are their virtual Ords (Object Resolution Descriptors) and their existence within a virtual component space. The
Ords for virtual components follow the SlotPath design (refer to VirtualPath) and must uniquely define virtual components (and
provide enough information to create the virtual component at runtime). These unique, on-demand virtual components live within a
Virtual Component Space, which is different from the normal component space which manages components that are persisted in the
station. The link between the normal component space and the virtual component space is through the Virtual Gateway. There is a
one-to-one relationship between a virtual gateway and its corresponding virtual component space, so it is possible to have multiple
virtual gateways and virtual component spaces in the same running station. These concepts will be described in more detail in the
class descriptions that follow.
From a drivers perspective, virtual components means that driver data can be addressed without premapping. Prior to this new
feature, the old Niagara AX model used by drivers boiled down to a collection of BComponents used to normalize driver data. For
example, most drivers contain a device network, devices, and proxy points (control points with proxy extensions). Proxy points are
useful for modeling the smallest pieces of driver data ("point" information) and normalizing them for use in the Niagara AX
environment. This model works well for linking proxy points to control logic for monitor and control. The problem with this model
is that every piece of driver data that a user may want to visualize/configure in Niagara AX requires the overhead of a persistent
component (i.e. proxy point) existing somewhere in the station's component space. The overhead of having persistent, premapped
components limits the capacity of points that a station can monitor. This limitation especially becomes a problem on small embedded
platforms (such as a JACE) where memory is limited.
There are two common driver use cases we identified where a user might want to have access to driver data, while not wanting the
extra overhead of using persistent components. The first is that a user wants to build a Px view to look at device point data (simply
for monitoring purposes). In this case, simply a polled value is sufficient to present the data to the user in the view only when it is
needed (the view is open). The second use case is for configuration/commissioning a device in which the user wants to see a
snapshot (i.e. property sheet) of the values within the device, and allow the user to monitor/modify these device values for one time
configuration purposes. In both of these cases, building persistent components to model the driver data is not necessary and simply
costs the user extra overhead. Instead, a transient display of the driver data is useful only when the user enters the view, but at all
other times, the values are not needed and do not need to be consuming memory (i.e. not needed for linking to any other logic).
Thus virtual components is a solution to both of these use cases.
In general, linking in the virtual component space is not supported, as virtual components are not persisted (thus any user
created links would be lost).
BVirtualComponent
A BVirtualComponent is a BComponent, however it extends the functionality to support living in a virtual component space by
keeping track of its last active ticks. The last active ticks are the clock ticks when the virtual component was last needed (ie. the
moment the virtual component switches from a subscribed state back to an unsubscribed state, the last active ticks are updated to
April 6, 2010 61
NiagaraAX-3.5 Developer Guide
indicate the ticks when the virtual component was last in use*). The last active ticks for each virtual component in the virtual
component space are consistently monitored by the space's VirtualCacheCallbacks instance, which uses this information to
determine when the virtual component is subject to auto-removal (clearing from the cache). Virtual Components can also be spared
from auto removal if the instance is the root component of the virtual component space, or if the auto-removal behavior is
specifically disabled for the virtual component (by subclassing and overridding the performAutoRemoval() callback). By default,
virtual components also override the normal BComponent behavior to specify their virtual nav Ord, enforce a few parent/child
restrictions**, and provide a convenient way to retrieve the parent BVirtualGateway instance, which is important because the
gateway is the link between the normal component space and the virtual component space.
The BVirtualComponent class is the key structure to use for modeling objects in your virtual space. You can use
BVirtualComponent (or a subclass of it) to model your data (or data groupings), and since BVirtualComponent is itself a
BComponent , it supports all of the normal component life-cycle features. Just remember BComponent instances (those that aren't
BVirtualComponents or BVectors) should not be used in the virtual component space, so keep this in mind when determining what
types of frozen/dynamic slots your BVirtualComponents need to model the data.
* NOTE - The last active ticks for a virtual component are also modified by a "touch" feature of the navigation tree in Workbench.
Basically, for any virtual component's nav tree node currently in view in Workbench, there is a periodic message sent that "touches"
the virtual component, in order to keep it active and prevent it from being auto cleaned. This is useful because a virtual component
simply viewed in the nav tree is not guaranteed to be in a subscribed state.
** NOTE - The general rule that should be followed is that the virtual component space should not contain BComponent instances
that are not BVirtualComponent s. So there are a few child/parent checks in place that attempt to enforce this rule. Of course,
BVirtualComponent instances living within a virtual component space can contain other non- BComponent children, such as
BSimple s, BStruct s, and there is even an exception made for BVector s. The reason you should keep non-virtual BComponent
children out of the virtual component space is because it can break the virtual cache cleanup mechanism (discussed below for the
VirtualCacheCallbacks class).
BVirtualComponentSpace
The BVirtualComponentSpace is an extension of BComponentSpace which contains a mapping of BVirtualComponent s
(organized as a tree). The virtual component space is created at runtime when a BVirtualGateway instance is started in the station's
component space. There is a one-to-one relationship between the virtual gateway and its virtual component space. The virtual
component space has a few supporting Callbacks classes. In addition to those provided by BComponentSpace ( LoadCallbacks,
SubscribeCallbacks, and TrapCallbacks), BVirtualComponentSpace kicks off an instance of VirtualCacheCallbacks
(described below). It is important to remember that the scope of the virtual component space is limited to its tree of virtual
components, but it also has a reference to its BVirtualGateway instance which provides the link to the normal component space.
BVirtualGateway
BVirtualGateway is an abstract subclass of BComponent designed to reside in the station component space and act as a "gateway" to
its corresponding virtual component space. As mentioned previously, there is a one-to-one relationship between the virtual gateway
and its virtual component space. For the virtual gateway, this means that the nav children displayed under the gateway in the nav tree
will be the nav children for the root component of the virtual space. Just to clarify the point, the virtual gateway functions as the link
between the normal (station) component space and its virtual space. Thus it overrides all of the nav methods to route to the virtual
space's tree (of virtual components). In practice, you should always avoid adding frozen/dynamic slots as children of the virtual
gateway directly, as the nav overrides will route users to the virtual space by default, thus making it difficult and confusing to
view/change slots that are direct children on the virtual gateway itself.
The other important function of the virtual gateway is to provide the hooks for subclasses to load/create virtual components at
runtime. This includes a few callback methods that the framework makes to the virtual gateway to tell it to load an individual virtual
slot or load all of the virtual slots for a given virtual component. Two important factors to consider when subclassing
BVirtualGateway and its methods are:
By contract, whenever slots are added to virtual components, they should always be assigned a slot name that is the escaped
virtual path name (ie. use SlotPath.escape(virtualPathName) ). This is very important as virtual path names can be
unescaped, but the contract is that their corresponding slot path name is simply the escaped version of the virtual path name.
In order for virtual lookup to work correctly, this rule must be followed.
Due to the possibility of a partial loaded state supported by virtuals, when you subclass BVirtualGateway (and even
April 6, 2010 62
NiagaraAX-3.5 Developer Guide
BVirtualComponent ) and implement its methods, you should always be keenly aware of the present subscription state of the
virtual components. For example, the BVirtualGateway load methods could be called and cause a new slot to be created for
a parent virtual component while that parent is already in a subscribed state. So this could affect how the new virtual slot
should be handled (ie. it may need to be added to a poll scheduler for updates). Subclasses should always be aware of this
potential state and perform the proper checks to handle this case.
BVirtualScheme
BVirtualScheme extends BSlotScheme and defines the "virtual" ord scheme ID. It works in close conjunction with VirtualPath
for resolving virtual Ords (see below for further details).
VirtualCacheCallbacks
This class is instantiated by BVirtualComponentSpace with a purpose to manage the virtual cache (ie. to determine when its
appropriate to auto cleanup virtuals that are no longer in use). The default implementation of VirtualCacheCallbacks has a shared
thread pool (used by multiple virtual component space instances) designed to monitor the virtual components in each registered
virtual space, and check the min/max virtual cache life for any unused virtual components. The idea is that virtual components, when
no longer needed, will remain in the cache for a certain cache life before they get automatically removed. The following static
variables allow for tuning the performance of the virtual cache management (all default values can be tweaked by making the
appropriate settings in the system.properties file):
public static final BRelTime MAX_CACHE_LIFE - Specifies the default virtual cache life maximum (default 45 seconds). When
a virtual component expires, it will remain in memory for a maximum of this amount of time before it will be automatically cleaned
up from the cache (assuming the virtual component is not re-activated in the meantime).
public static final BRelTime MIN_CACHE_LIFE - Specifies the default virtual cache life minimum (default 25 seconds). When
a virtual component expires, it will remain in memory for a minimum of this amount of time before it will be subject to automatic
clean up from the cache (assuming the virtual component is not re-activated in the meantime). This minimum cache life is only a
factor when the virtual threshold limit has been exceeded (meaning that virtuals need to be cleaned up faster than normal). If the
virtual threshold limit has not been exceeded, the maximum virtual cache life will be used (normal operation).
public static final int VIRTUAL_THRESHOLD - Specifies a global virtual threshold limit (default 1000), above which virtuals
will start being auto cleaned from the cache quicker as space is needed (ie. the MIN_CACHE_LIFE will be used in the cache life
determination when the number of virtuals in the station exceeds this threshold limit, otherwise the MAX_CACHE_LIFE will be used
when the number of virtuals doesn't exceed this limit).
public static final long VIRTUAL_THRESHOLD_SCAN_RATE - Specifies the default time (in milliseconds) in which to perform a
full scan of the station for virtuals, used for threshold level checking. The default is 1000, which means that every second, a full scan
will occur. A value of zero disables the virtual threshold checking feature entirely.
public static final int THREAD_POOL_SIZE - Specifies the maximum number of worker threads in the thread pool shared by
VirtualCacheCallbacks instances. There is a VirtualCacheCallbacks instance per virtual component space, however, the
default implementation shares a common worker thread pool. Therefore, this setting determines the maximum number of virtual
cleanup worker threads (10 default).
public static final int SPACES_PER_THREAD - Specifies the ideal number of virtual component spaces managed per worker
thread in thread pool (this limit can be exceeded if all threads in the pool are already at capacity). The default is 5 virtual spaces
(optimum) per thread.
VirtualPath
VirtualPath extends SlotPath and allows for resolving BVirtualComponent s (and their child slots) using unescaped slot names in
the path (note that this is different from SlotPath which enforces the rule that only escaped slot names can be contained in the
path). The '/', '|', '$', and ':' characters are reserved and not allowed in a virtual path entry. Also, the "../" is reserved for backups.
The most common use case of VirtualPath follows the following format:
April 6, 2010 63
NiagaraAX-3.5 Developer Guide
local:|fox:|station:|slot:/Config/Drivers/YourNetwork/YourDevice/YourVirtualGateway
|virtual:/Virtual Component A/Virtual Component B/Output Value
This example shows how the virtual gateway is always the link point between the normal component space and the virtual space. The
"|virtual:" in the example above indicates the jump to the virtual component space. When resolving such an Ord, once it starts
parsing the virtual path, it will start from the left and work to the right (the "/" acts as the separator between virtual slots). So this
means it will first check for the existence of a slot named "Virtual$20Component$20A" under the root component of the virtual
space and return it if it exists (remember that by contract, virtual path names should be escaped to form the slot name). If it doesn't
already exist, the virtual gateway will be given the opportunity to create a virtual object to represent it given the virtual path name and
parent (subclasses will normally put enough information in the virtual path to know how to create the object). This process continues
from left to right until the virtual path has resolved the last in the list. The example above would be represented in the nav tree like
this:
Config
|
|
|____Drivers
|
|
|____YourNetwork
|
|
|____YourDevice
|
|
|____YourVirtualGateway
| (entrance to virtual space)
| virtual space root component (hidden)
|____Virtual Component A
|
|
|____Virtual Component B
|
|
|____Output Value
It is also worth noting that due to the virtual/slot path name contract, the following Ord is functionally equivalent to the example
above (disregard the line wrap):
local:|fox:|station:|slot:/Config/Drivers/YourNetwork/YourDevice/YourVirtualGateway
|virtual:|slot:/Virtual$20Component$20A/Virtual$20Component$20B/Output$20Value
April 6, 2010 64
NiagaraAX-3.5 Developer Guide
April 6, 2010 65
NiagaraAX-3.5 Developer Guide
Gx Graphics Toolkit
Overview
The gx module defines the graphics primitives used for rendering to a display device. For example there implements for "painting" to
computer screens and another for "painting" a PDF file. Many of the simple types used in the rest of the stack are defined in gx
including BColor, BFont, BPen, BBrush, BGeom, and BTransform.
The gx APIs use a vector coordinate system based on x and y represented as doubles. The origin 0,0 is the top left hand corner with
x incrementing to the right and y incrementing down. This coordinate system is called the logical coordinate space (sometimes called
the user space). How the logical coordinate space maps to the device coordinate space is environment specific. Usually a logical
coordinate maps directly into pixels, although transforms may alter this mapping.
Color
BColor stores an RGBA color. It's string syntax supports a wide range of formats including most specified by CSS3:
SVG Keywords: the full list of X11/SVG keywords is available by name and also defined as constants on BColor. Examples:
red, springgreen, navajowhite.
RGB Function: the rgb() function may be used with the red, green, and blue components specified as an integer between 0-
255 or as a percent 0%-100%. Examples: rgb(0,255,0), rgb(0%,100%,0%).
RGBA Function: the rgba() function works just like rgb(), but adds a fourth alpha component expressed as a float between
0.0 and 1.0. Example: rgba(0,100,255,0.5).
Hash: the following hash formats are supported #rgb, #rrggbb, and #aarrggbb. The first two follow CSS rules, and the last
defines the alpha component using the highest 8 bits. Examples: #0b7, #00bb77, #ff00bb77 (all are equivalent).
Font
BFont is composed of three components:
The format of fonts is "[italic || bold || underline] {size}pt {name}". Examples: "12pt Times New Roman", "bold 11pt sans-serif", "italic
underline 10pt Arial". The BFont class also provides access to a font's metrics such as baseline, height, ascent, descent, and for
calculating character widths.
Brush
The BBrush class encapsulates how a shape is filled. The gx brush model is based on the SVG paint model. There are four types of
brushes:
Solid Color: the string format is just a standard color string such as "red"
Inverse: uses an XOR painting mode
Gradients: linear and radial gradients
Image: based on a bitmap image file which may tiled or untiled
Pen
The BPen class models how a geometric shape is outlined. A pen is composed of:
April 6, 2010 66
NiagaraAX-3.5 Developer Guide
Coordinates
The following set of classes is designed to work with the gx coordinate system. Each concept is modeled by three classes: an
interface, a mutable class, and an immutable BSimple version that manages the string encoding.
Point
The IPoint, Point, and BPoint classes store an x and y location using two doubles. The string format is "x, y". Example "40,20",
"0.4,17.33".
Size
The ISize, Size, and BSize classes store a width and height using two doubles. The string format is "width,height". Examples include
"100,20", "10.5, 0.5".
Insets
The IInsets, Insets, and BInsets classes store side distances using four doubles (top, right, bottom, and right). The string format for
insets follows CSS margin style: "top,right,bottom,left". If only one value is provided it applies to all four sides. If two values are
provided the first is top/bottom and the second right/left. If three values are provided the first is top, second is right/left, and third is
bottom. Four values apply to top, right, bottom, left respectively. Examples "4" expands to "4,4,4,4"; "2,3" expands to "2,3,2,3"; "1,2,3"
expands to "1,2,3,2".
Geom
The geometry classes are used to model rendering primitives. They all following the pattern used with Point, Size, and Insets with an
interface, mutable class, and immutable BSimple. Geometries can be used to stroke outlines, fill shapes, or set clip bounds.
Geom
The IGeom, Geom, and BGeom classes are all abstract base classes for the geometry APIs.
LineGeom
The ILineGeom, LineGeom, and BLineGeom classes model a line between two points in the logical coordinate system. The string
format of line is "x1,y1,x2,y2".
RectGeom
The IRectGeom, RectGeom, and BRectGeom classes model a rectangle in the logical coordinate system. The string format of
rectangle is "x,y,width,height".
EllipseGeom
The IEllipseGeom, EllipseGeom, and BEllipseGeom classes model a ellipse bounded by a rectangle in the logical coordinate space.
The string format is "x,y,width,height".
PolygonGeom
The IPolygonGeom, PolygonGeom, and BPolygonGeom classes model a closed area defined by a series of line segments. The string
format of polygon is "x1,y1 x2,y2,...".
PathGeom
The IPathGeom, PathGeom, and BPathGeom classes define a general path to draw or fill. The model and string format of a path is
based on the SVG path element. The format is a list of operations. Each operation is denoted by a single letter. A capital letter
implies absolute coordinates and a lowercase letter implies relative coordinates. Multiple operations of the same type may omit the
letter after the first declaration.
April 6, 2010 67
NiagaraAX-3.5 Developer Guide
Smooth Curveto: draws a Bezier curve from current point to x,y. The first control point is assumed to be the reflection of the
second control point on the previous command relative to the current point. "S x2,y2 x,y" or "s x2,y2 x,y".
Quadratic Curveto: draws a quadratic Bezier curve from current point to x,y using x1,y1 as control point: "Q x1,y1 x,y" or "q
x1,y1 x,y".
Smooth Quadratic Curveto: draws a quadratic Bezier curve from current point to x,y with control point a reflection of
previous control point: "T x,y" or "t x,y".
Arc: draws an elliptical arc from the current point to x,y: "A rx,ry x-axis-rotation large-arc-flag sweep-flag x y" or "a rx,ry x-
axis-rotation large-arc-flag sweep-flag x y".
Refer to the W3 SVG spec (http://www.w3.org/TR/SVG/) for the formal specification and examples.
Transform
Transforms allow a new logical coordinate system to be derived from an existing coordinate system. The gx transform model is based
on SVG and uses the exact string formatting. BTransform stores a list of transform operations:
Image
The BImage class is used to manage bitmap images. Image's are typically loaded from a list of ords which identify a list of files (GIF,
PNG, and JPEG supported). When more than file is specified, the image is composited using alpha transparency from bottom to top
(useful for "badging" icons). Images may also be created in memory and "painted" using the Graphics API.
The framework will often load images asynchronsouly in a background thread to maintain performance. Developers using BImages
directly can poll to see if the image is loaded via the isLoaded() method. Use the sync() method to block until the image is fully
loaded. Animated GIFs require that the developer call animate() at a frame rate of 10frames/second. Typically developers should
display images using BLabel which automatically handles async loading and animation.
The framework caches images based on their size and how recently they are used. You may use the Image Manager spy page to
review the current cache.
Graphics
Painting to a device is encapsulated by the Graphics class. The primitive paint operations are:
fill(IGeom): filling a geometry involves painting a geometry's interior area with the current brush. Remember a brush can be
a solid color, a gradient, or texture.
stroke(IGeom): stroking a geometry is to draw its outline or line segments. Stroking uses the current pen to derive the
"stroke geometry" based on the pen's width and style. Then the interior of the stroke is filled using the current brush.
drawString(): this draws a set of characters to the device. The shape of the characters is derived from the current font. The
interior of the font is filled with the current brush. Note: we don't currently support stroking fonts like SVG.
drawImage(): this draws an bitmap image to the device; the image may be scaled depending on the parameters and/or
current transform.
All paint operations perform compositing and clipping. Compositing means that colors are combined as painting occurs bottom to
top. For example drawing a GIF or PNG file with transparent pixels allows the pixels drawn underneath to show through. Alpha
transparency performs color blending with the pixels underneath. Clipping is the processing of constraining paint operations to a
specified geometry. Any pixels from a paint operation which would be drawn outside the clip geometry are ignored. Use the clip()
method to clip to a specific region.
Often it is necessary to save the current state of the graphics to perform a temporary paint operation, and then to restore the
graphics. An example is to set a clip region, paint something, then restore the original clip. The push() and pop() are used to
accomplish this functionality by maintaining a stack of graphics state. You should always call pop() in a try finally block to
ensure that your code cleans up properly.
April 6, 2010 68
NiagaraAX-3.5 Developer Guide
Layout: defines the layout model - how widget trees are positioned on the graphics device
Painting: defines how widgets paint themselves using graphical composition and clipping
Input: defines how user widgets process user input in the form of mouse, keyboard, and focus events
Data Binding: defines are how widgets are bound to data sources
Widgets are organized in a tree structure using the standard component slot model. Typically the root of the tree is a widget
modeling a top level window such as BFrame or BDialog.
Layout
All widgets occupy a rectangular geometry called the bounds. Bounds includes a position and a size. Position is a x,y point relative to
the widget parent's coordinate system. Size is the width and height of the widget. The widget itself defines its own logical coordinate
system with its origin in the top left corner, which is then used to position its children widgets. Every widget may define a preferred
size using computePreferredSize() . Layout is the process of assigning bounds to all the widgets in a widget tree. Every widget is
given a chance to set the bounds of all its children in the doLayout() callback. When a layout refresh is needed, you may call
relayout() . The relayout call is asynchronous - it merely enqueues the widget (and all its ancestors) for layout at some point in the
near future.
Panes
Widget's which are designed to be containers for child widgets derive from the BPane class. A summary of commonly used panes:
BCanvasPane: used for absolute positioning (discussed below);
BBorderPane: is used to wrap one widget and provide margin, border, and padding similar to the CSS box model.
BEdgePane: supports five potential children top, bottom, left, right, and center. The top and bottom widgets fill the pane
horizontally and use their preferred height. The left and right widgets use their preferred width, and occupy the vertical space
between the top and bottom. The center widget gets all the remainder space.
BGridPane: lays out it children as a series of columns and rows. Extra space in the rows and columns is configurable a
number of ways.
BSplitPane: supports two children with a movable splitter between them.
BTabbedPane: supports any number of children - only one is currently selected using a set of tabs.
BScrollPane: supports a single child that may have a preferred size larger than the available bounds using a set of scroll bars.
Absolute Layout
Every widget also has a frozen property called layout of type BLayout. The BLayout class is used to store absolute layout. Widgets
which wish to use absolute layout should be placed in a BCanvasPane . BLayout is a simple with the following string format
"x,y,width,height". Each value may be a logical coordinate within the parent's coordinate space or it may be a percent of the parent's
size. Additionally width and height may use the keyword "pref" to indicate use of preferred width or height. Examples include
"10,5,100,20" "0,0,30%,100%", and "10%,10%,pref,pref".
Lastly the keyword "fill" may be used as a shortcut for "0,0,100%,100%" which means fill the parent pane. Fill is the default for the
layout property which makes it easy to define layers and shapes.
Painting
All widgets are given a chance to paint themselves using the paint(Graphics) callback. The graphics is always translated so that the
origin 0,0 is positioned at the top, left corner of the widget. The graphic's clip is set to the widget's size. Widget's with children,
should route to paintChild() or paintChildren() . Painting follows the gx compositing rules. Alpha and transparent pixels blend
with the pixels already drawn. Widgets are drawn in property order. So the first widget is drawn first (at the bottom), and the last
widget drawn last (on the top). Note that hit testing occurs in reverse order (last is checked first). Effective z-order is reverse of
April 6, 2010 69
NiagaraAX-3.5 Developer Guide
Input
User events are grouped into keyboard input, mouse input, and focus events. The following events are defined for each group:
BKeyEvent
keyPressed
keyReleased
keyTyped
BMouseEvent
mouseEntered
mouseExited
mousePressed
mouseReleased
mouseMoved
mouseDragged
mouseDragStarted
mouseHover
mousePulse
mouseWheel
BFocusEvent
focusGained
focusLost
Design Patterns
Some complicated widgets have mini-frameworks all to their own. These include BTable, BTree, BTreeTable, and BTextEditor. All
of these widgets use a consistent design pattern based on a set of support APIs:
Commands
The bajaui module provides a standard API for managing user commands using the Command and ToggleCommand classes.
Commands are associated with one or more widgets which invoke the command. Typically this association happens by using a
special constructor of the widget such as BButton(Command cmd) or using a setCommand() method. Commands are commonly used
with BButton and BActionMenuItem. ToggleCommands are commonly used with BCheckBox, BRadioButton, BCheckBoxMenuItem,
and BRadioButtonMenuItem.
Commands provide several functions. First they provide a centralized location to enable and disable the command. It is common for
a command to be available via a menu item, a toolbar button, and a popup menu. By enabling and disabling the command all the
widgets are automatically enabled and disabled.
Commands also provide a standard mechanism used for localization via the lexicon APIs. If one of the module or lexicon
constructors is used the command automatically loads its visualization from a lexicon using a naming pattern: keyBase+".label",
keyBase+".icon", keyBase+".accelerator", and keyBase+".description". The icon value should be an ord to a 16x16 png file. Widgets
created with the Command will automatically set their properties accordingly.
The Command API also defines the basic framework for undo and redo. Whenever a command is invoked via the invoke()
method, the Command can return an instance of CommandArtifact to add to the undo stack. Commands which don't support undo
can just return null .
April 6, 2010 70
NiagaraAX-3.5 Developer Guide
Data Binding
All widgets may be bound to zero or more data sources using the BBinding class. Bindings are added to the widget as dynamic child
slots. You may use the BWidget.getBindings() to access the current bindings on a given widget. Bindings always reference a data
source via an ord. The BBinding API defines the basic hooks given to bindings to animate their parent widget.
The most common type of binding is the BValueBinding class. Value binding provides typical functionality associated with building
real-time graphics. It supports mouse over status and right click actions. Additionally it provides a mechanism to animate any
property of its parent widget using BConverters to convert the target object into property values. Converters are added as dynamic
properties using the name of the widget property to animate. For example to animate the "text" property of a BLabel you might add a
ObjectToString converter to the binding using the property name "text".
Performance Tuning
The gx and bajaui toolkits are built using the AWT and Java2D. A key characteristic of performance is based on how widgets are
double buffered and drawn to the screen. The following system properties may be used to tune widget renderering:
niagara.ui.volatileBackBuffer: Defines whether the back buffer used for widget rendering uses createVolatile() or
createImage(). Volatile back buffers can take advantage of Video RAM and hardware acceleration, non-volatile back
buffers are located in system memory. Note: this feature is currently disabled.
sun.java2d.noddraw: Used to disable Win32 DirectDraw. Often disabling DirectDraw can correct problems with flickering.
April 6, 2010 71
NiagaraAX-3.5 Developer Guide
Workbench
Overview
The workbench module define's the framework for building standardized user interfaces. The workbench provides enhancements to
the bajaui widget toolkit:
Note: The term workbench applies both to the actual application itself as well as the underlying technology used to build customized
applications. As you will see, all apps are really just different veneers of the same workbench customized using WbProfiles.
Layout
The illustration aboves shows the key components of the Workbench layout:
The BWbShell class is used to model the entire workbench window (or the applet in a browser environment). The getActiveOrd()
method provides access to the current location, and hyperlink() is used to hyperlink to a new ord.
April 6, 2010 72
NiagaraAX-3.5 Developer Guide
WbPlugins
The workbench is fundamentally a command and navigation shell, with all of it's functionality provided by plugins. The plugins
available to the workbench are discovered by searching the registry for the appriopate type (WbProfiles allow further customization).
This means that installing a new workbench plugin is as simple as dropping in a module.
All plugins subclass BWbPlugin , which is itself a BWidget . The following common plugins are discussed in the following sections:
WbViews
WbFieldEditor
WbSideBars
WbTools
WbView
Views are the workhorses of the workbench. Views provide the content viewers and editors for working with the active objects.
Views also have the unique ability to do menu and toolbar merging. To implementing a new view plugin follow these rules:
Writing a view for a BIFile typically involved reading the file's content for display on doLoadValue(), and writing back the
contents on doSaveValue().
Writing a BWbComponentView for a BComponent typically involves subscribing to the necessary component or components on
doLoadValue(), and saving back changes on doSaveValue(). The WbComponentView class provides a series of registerX()
methods for managing the view's subscriptions. Remember that if working with remote components, batching resolves, subscribes,
and transactions can make significant performance improvements. Refer to Remote Programming for more information.
WbFieldEditor
BSimple BStruct
April 6, 2010 73
NiagaraAX-3.5 Developer Guide
Field editors are similar to views, except they typically are smaller editors used to edit a or . Unlike views, a field
editor never fills the view content area, but rather is used inside views like the PropertySheet.
The rules for building a field editor are very similar to views:
BWbFieldEditor also provides some convenience methods for displaying a dialog to input a specific BObject type. For example to
prompt the user input a street address:
WbSideBar
Sidebars are auxiliary tools designed to be used in conjunction with the active view. Sidebars are displayed along the left edge of the
view. Multiple sidebars can be open at one time. Unlike views, sidebars are independent of the active ord.
The rules for building a sidebar:
BookmarkSideBar.displayName=Bookmarks
BookmarkSideBar.icon=module://icons/x16/bookmark.png
WbTool
Tools are plugins to the workbench Tools menu. Tools provide functionality independent of the active ord. Typically tools are dialogs
or wizards used to accompish a task. There are three types of tools:
BWbTool : is the base class of all tools. It provides a single invoke(BWbShell shell) callback when the tool is selected from
the Tools menu. Often invoke is used to launch a dialog or wizard.
BWbNavNodeTool : is a tool which gets mounted into the ord namespace as "tool:{typespec}|slot:/". Selecting the tool from the
Tools menu hyperlinks as the tool's ord and then standard WbViews are used to interact with the tool. Typically in this
scenerio the tool itself is just a dummy component used to register one or more views.
BWbService : is the most sophisticated type of tool. Services are WbNavNodeTools , so selecting them hyperlinks to the tool's
ord. Services also provide the ability to run continuously in the background independent of the active ord. This is useful for
monitoring tools or to run drivers locally inside the workbench VM. Services can be configured to start, stop, and auto start
via the WbServiceManager.
NewModuleTool.displayName=New Module
NewModuleTool.icon=module://icons/x16/newModule.png
WbProfiles
The BWbProfile class provides the ability to create new customized versions of the workbench. WbProfile provides hooks to replace
April 6, 2010 74
NiagaraAX-3.5 Developer Guide
all of the standard layout components such as the MenuBar, ToolBar, LocatorBar, and StatusBar. Plus it provides the ability to
customize which views, sidebars, and tools are available. Using WbProfiles you can quickly create custom applications that provide
just the functionality needed for your domain.
You can launch workbench with a specific profile via a command parameter: wb -profile:{typespec}.
Note that if you wish to create an application that runs in the web browser you will need to subclass BWbWebProfile.
April 6, 2010 75
NiagaraAX-3.5 Developer Guide
Web
Overview
The web module is used to provide HTTP connectivity to a station via the BWebService . The web module provides a layered set of
abstractions for serving HTTP requests and building a web interface:
Servlet: a standard javax.servlet API provides the lowest level of web integration.
ServletView: is used to provide object views in a web interface.
Web Workbench: is technology which enables the standard workbench to be run in a browser.
Hx: is technology used to build web interfaces using only standards: HTML, JavaScript, and CSS.
Servlet
Niagara provides a standard javax.servlet API to service HTTP requests. The WebOp class is used to wrap HttpServletRequest and
HttpServletResponse . WebOp implements Context to provide additional Niagara specific information. These APIs form the basis
for the rest of the web framework.
The BWebServlet component is used to install a basic servlet which may be installed into a station database. The servletName is
used to define how the servlet is registered into the URI namespace. A servletName of "foo" would receive all requests to the host
that started with "/foo". Servlets are automatically registered into the URI namespace on their component started() method and
unregistered on stopped() . The service() or doGet()/doPost() methods are used to process HTTP requests.
Note: The current javax.servlet implementation is based on version 2.4. The following interfaces and methods are not supported:
ServletView
The web framework follows an object oriented model similar to the workbench. The user navigates to objects within the station
using ords. One or more web enabled views are used to view and edit each object.
When navigating objects using ords, Niagara must map ords into the URI namespace. This is done with the URI format of
"/ord?ord".
The BServletView class is used to build servlets that plug into the ord space using the "view:" scheme. For example if you wish to
display an HTML table for every instance of component type "Foo", you could create a ServletView called "FooTable". Given an
April 6, 2010 76
NiagaraAX-3.5 Developer Guide
instance of "Foo", the URI to access that view might be "/ord?slot:/foo3|view:acme:FooTable". The WebOp passed to
BServletView.service() contains the target object being viewed (note WebOp subclasses from OrdTarget ).
Web Workbench
A nice feature of Niagara's web framework is the ability to run the entire workbench right in a browser. This web workbench
technology allows almost any view (or plugin) to run transparently in both a desktop and browser environment. The following
process illustrates how web workbench works:
1. User requests a workbench view for a specific object via its ord.
2. An HTML page is returned that fills the entire page with a small signed applet called wbapplet.
3. The wbapplet is hosted by the Java Plugin which must be preinstalled on the client's machine.
4. The wbapplet loads modules from the station as needed, and caches them on the browser's local drive.
5. The workbench opens a fox connection under the covers for workbench to station communication.
6. The workbench displays itself inside the wbapplet using the respective WbProfile and WbView.
Web workbench technology allows a sophisticated UI to be downloaded to a user's browser straight out of a Jace. It is downloaded
the first time and cached - subsequent access requires only the download of wbapplet (13kb). Development for web versus desktop
workbench is completely transparent. The only difference is that the BWbProfile used for a web interface must subclass from
BWbWebProfile. Some functionality is limited only to the desktop like the ability to access the console and Jikes compiler. Also note
that web workbench session is limited to a specific station. So it doesn't make sense to navigate to ords outside that station such a
"local:|file:".
Note: in order for web workbench to be used, the client browser machine must have access to the station's fox port. This may require
the fox port to be opened up in the firewall.
Hx
There are cases where using the workbench is overkill or we don't wish to require the Java Plugin. For these use cases, Niagara
provides the hx technology. Hx is a mini-framework used to build real-time web interfaces only with standard HTML, JavaScript,
and CSS. See the hx chapter for details.
WebProfileConfig
The web experience of a given user is controlled via the BWebProfileConfig class. WebProfileConfig is a MixIn added to every
User component. The web profile determines whether web workbench or hx is used by specifying an WbProfile or HxProfile for the
user.
April 6, 2010 77
NiagaraAX-3.5 Developer Guide
Px
Overview
Px is a technology used to package a UI presentation as an XML file. A px file defines a tree of bajaui widgets and their data bindings.
Any BWidget and BBinding may be used, including those custom developed by you. Typically px files are created using a
WYSIWYG tool called the PxEditor, although they can also be handcoded or auto-generated.
Px files are always given a ".px" extension, and modeled with the file:PxFile type.
Px Views
A px file may be used in a UI via a variety of mechanisms:
The WbPxView is the standard presentation engine for px files. WbPxView is the default view of file:PxFile , so you can use px files
just like an HTML file - by navigating to one.
The BPxView class may be used to define dynamic views on components. Dynamic views are like dynamic slots, in that they are
registered on an instance versus a type. A dynamic view is automatically available for every BPxView found in a component. Each
BPxView provides an ord to the px file to use for that view. PxViews may be added through the workbench or programmatically.
Since the bindings within a px file are always resolved relative to the current ord, you can reuse the same px file across multiple
components by specifying bindings with relative ords.
If all the widgets used in a px file have a translation to hx, then the entire px file can be automatically translated into HTML for hx
users. See hx for more details.
PxMedia
As a general rule any BWidget is automatically supported when viewing a px file. However, viewing a px file in hx only supports a
subset of widgets (those that have a hx agent). This means that you must target the lowest common denominator when creating px
presentations. The target media for a px presentation is captured via the BPxMedia class. Both the px file and the PxView can store a
PxMedia type. Currently there are only two available media types: workbench:WbPxMedia and hx:HxPxMedia . The PxEditor will
warn you if you attempt to use widgets and bindings not supported by the target media.
API
The bajaui module provides a standard API for serializing and deserializing a widget tree to and from it's XML representation. The
PxEncoder class writes a widget tree to an XML document. PxDecoder is used to read an XML document into memory as a widget
tree.
Syntax
The bog XML format is optimized to be very concise with equal weight given to both read and write speed. The px XML format is
designed to be more human usable. All px files have a root px element with a required version and optional media attribute.
Within the root px element is an import element and a content element.
The import section contains a list of module elements. Each module specifies a Niagara module name using the name attribute. This
module list is used to resolve type names declared in the content section.
The content section contains a single element which is the root of the px file's widget tree. Each component in the tree uses a type
name as the element name. These type names are resolved to fully specified type specs via the import section.
Frozen simple properties of each component are declared as attributes in the component's start tag. Complex and dynamic slots are
specified as children elements. The name of the slot inside the parent component may be specified using the name attribute. Dynamic
simple properties specify their string encoding using the value attribute.
April 6, 2010 78
NiagaraAX-3.5 Developer Guide
Example
The following example shows a BoundLabel placed at 20,20 on a CanvasPane, which is itself nested in a ScrollPane. Note since the
CanvasPane is the value of ScrollPane's frozen property content , it uses the name attribute. Note how frozen simple properties like
viewSize, layout , and ord are defined as attributes.
April 6, 2010 79
NiagaraAX-3.5 Developer Guide
Hx
Overview
The hx module defines the framework for building HTML-based user interfaces using HTML, CSS, JavaScript, and XmlHttp.
Hx is designed to approximate the same paradigms that exist for developing user interfaces in the Workbench enviornment, such as
Views, FieldEditors, and Profiles. It's main goal is try and transparently produce lightweight HTML-only interfaces automatically
based on the workbench views. Limited support exists for standard views like the Property Sheet, but Px is the main reuse target.
If you are not familiar with how interfaces are designed in workbench you should read the Workbench documentation before
continuing.
April 6, 2010 80
NiagaraAX-3.5 Developer Guide
Hx - HxView
HxView Overview
HxView provides the content viewers and editors for working with the active objects. As you have probably guessed, HxView is the
Hx equivalent of WbView . HxView is designed to produce and interact with a snippet of HTML. BHxProfile takes one or more
HxViews , adds some supporting markup plus some chrome, and produces a complete HTML page.
Must have logic to render a HTML snippet from an object (write). This is synonymous to BWbView.doLoadValue() .
May have logic to save changes back to the object (save). This is synonymous to BWbView.doSaveValue() .
May have logic to periodically update the HTML snippet (update).
May have logic to respond to client background requests (process).
The name in parenthesis at the end of each bullet is the corresponding method in HxView responsible for that behavior. Details on
each method can be found below. The HxProfile is responsible for building the containing HTML around each HxView.
Example
The details of each method can be found at the end of this document, but lets take a simple example to walk through the API:
protected BFooView() {}
April 6, 2010 81
NiagaraAX-3.5 Developer Guide
{
BFoo foo = (BFoo)op.get();
HtmlWriter out = op.getHtmlWriter();
out.w("Current name: ").w(foo.getName()).w("<br/>");
out.w("<input type='text' name='").w(op.scope("name")).w("'");
out.w(" value='").w(foo.getName()).w("' /><br/>");
out.w("<input type='submit' value='Submit' />");
}
Assuming the current name in our BFoo object is "Peter Gibbons", the above code will produce the following HTML (ignoring the
profile):
If you are familiar with Niagara AX and HTML, this should be pretty straightforward. Let's walk through it though. Here's the class
heirarchy of the main hx components:
The first thing to note is that BHxView extends BServletView , which extends BSingleton , which requires a public static
final INSTANCE variable for all concrete implementations. If you have ever programmed Servlets before, you'll know that a Servlet
must be re-entrant, and HxViews follow the same model. The INSTANCE object is what will be used to handle requests. (The
protected constructor is a convention used to enforce the singleton design pattern). Since HxView can't maintain state while its
doing its thing, we need to stash stuff somewhere - thats where HxOp comes in. We won't get into the details of HxOp yet, just be
aware that anything I need to know about my current request or response is very likely accessible via the op.
April 6, 2010 82
NiagaraAX-3.5 Developer Guide
Let's move on to the lifecycle of an HxView . The first thing a view will do is render its HTML to the client. This occurs in the
write() method. By default Hx documents use the XHTML 1.0 Strict DOCTYPE. So you are encouraged to use valid XHTML in
your write method. Since HxViews are designed to be chromable, and compositable, you'll also only write the markup that directly
pertains to this view in your write method. Here are the things you should take note of about write :
Think BWbView.doLoadValue()
Only write the HTML that directly pertains to this view.
You should always use op.scope() when writing a form element name. We'll get to why you should do that in 'Writing
Reusable HxViews' down below.
This is just a plain old HTML form, so all the normal form elements are applicable. And of course any other HTML.
Just like a plain HTML file, <input type='submit' /> is used to submit changes back to the server. The Hx framework
will take care of building the form tag so this request gets routed to your save() method.
Saving Changes
Ok, my name is not "Peter Gibbons", so we need to be able to save something else back to the station. This is just as easy as writing
my HTML, you simply implement a save method on your view. The request will automatically be routed, and all form data will be
available from the HxOp.getFormValue() method.
So now if I view our example view in my browser, enter "Michael Bolton" and hit "Submit", the page will refresh to display:
Technically, what happens, is the POST request gets routed to save , then Hx responds with a redirect back the same location. This
forces the page contents to be requested on a GET request, avoiding double-posting problems.
HxOp.getFormValue() will automatically handle the "unscoping" for you. This allows any HxView to be nested anywhere without
knowing its context. However, this only works if you follow a few rules:
Always give sub-views a sub-op using HxOp.make() - there should always be a 1:1 ratio between HxOps and HxViews . See
"Managing Subviews" below.
HxOp.scope() name
April 6, 2010 83
NiagaraAX-3.5 Developer Guide
Managing Subviews
Hx does not contain any notion of containment, so composite views are responsible for routing all write/save/update/process
requests to its children:
Don't forget to always create a sub-op to your child views so the Hx framework can strut its stuff.
April 6, 2010 84
NiagaraAX-3.5 Developer Guide
<agent><on type="foo:WbFooView"/></agent>
</type>
station:|slot:/bar|view:foo:WbFooView
Also note that if your view is the default view on that object, the default ord will choose the correct view as well:
station:|slot:/bar
April 6, 2010 85
NiagaraAX-3.5 Developer Guide
op.setMultiPartForm();
out.w("<input type='file' name='someFile' />");
}
This code will upload a file to a temporary file, accessible as "someFile", and move it to another location so that it will not be deleted
at the end of the request.
There is only one form tag in an hx page, and is written by the profile. HxViews should never write their own form blocks. So by
design, the entire page content is encoded for save and Events . Naming collisions are handled automatically using the HxOp scoping
rules (see 'Writing Reusable HxViews' above for more info on scoping).
The write method is always called on an HTTP GET request. However, if its written correctly (which typically means escaping
quotes properly), it may be reused by update or process if it makes sense.
save
Save is used to save changes made from the view back to the target object. This is usually just a standard response to a form post,
where the form values are accessed using HxOp.getFormValue() . Views on BSimples should return a new instance based on their
new value. Views on BComponents should modify the existing instance and return that instance.
After a save is handled, a redirect is sent back to the browser to the current location. This is used to refresh the current page values,
but more importantly to avoid double-posting problems. Content is always be requested on a GET request (and handled by write ).
You may choose to redirect back to a different URL using the HxOp.setRedirect() method.
The save method is always called on a standard HTTP POST form submit request. Both standard url-encoded and multi-part forms
are supported. See 'Uploading Files with Multi-part Forms' above for info on multi-part forms.
update
Update is automatically called periodically on all views if at least one view was marked as dynamic (via HxOp ). This is a background
request made using JavaScript and XmlHttp. The content returned to the browser must be executable JavaScript. For example:
April 6, 2010 86
NiagaraAX-3.5 Developer Guide
}
Here, after the page is initially written, the browser will poll the station every five seconds running update on all the views. So this
code will simply update the current time each time the station is polled.
process
Process is used to handle non-update background requests. A process request is targeted and serviced by a single HxView . The
default implementation for process handles routing events to the correct view. See Events.
Note: If you override process, you must call super or event routing will fail.
April 6, 2010 87
NiagaraAX-3.5 Developer Guide
Hx - HxOp
HxOp
HxOp maintains all the state for the current request, and provides the interface for creating and consuming a document. The original
HxOp wraps the WebOp for the current request. Sub-views should be given a new HxOp from the current op via the HxOp.make()
method. See 'Writing Reusable HxViews' in HxView.
Note: There should always be a one-to-one mapping of HxOps to HxViews .
WebOp API
HxOp API
Servlet API
April 6, 2010 88
NiagaraAX-3.5 Developer Guide
Hx - HxProfile
HxProfiles
The BHxProfile is used to customize the HTML page around the current HxView . The profile is responsible for writing out the
outer HTML tags ( html , head , and body ), any custom markup, and the current view. It is important that your profile respect the
order HxOps are created in these methods: writeDocument, updateDocument, processDocument, and saveDocument . If any
HxView uses the auto name constructor of HxOp to create a unique path name, it must be called in the exact same order in order to
resolve correctly.
HxProfile exposes customization hooks through convenience methods, so there is no need to handle the boilerplate code:
protected BMyProfile() {}
April 6, 2010 89
NiagaraAX-3.5 Developer Guide
Hx - Events
Events
Hx uses Events to provide background interaction between the server and the browser. Events always originate from the client
browser, and must return executable javascript as the response (you are not required to return content). The html form is encoded
and sent for every event fire, so op.getFormValue() can be used to query the browser page state.
Events are implemented on top of the HxView.process method, and therefore use the XmlHttp support implemented in the major
browsers.
Command extends Event to add some convenience methods and a display name property. By convention Commands are triggered by
the user (maybe by clicking on a button), while Events are triggered programmatically. Though in reality they are interchangeable.
Note: javax.baja.hx.Command is not the same class as the javax.baja.ui.Command .
April 6, 2010 90
NiagaraAX-3.5 Developer Guide
Hx - Dialogs
Dialogs
Support for modal dialog boxes is provided with Dialog and is typically used from an Command :
refresh(op);
}
}
out.w("<table>");
out.w("<tr>");
out.w(" <td>Name</td>");
out.w(" <td><input type='text' name='").w(op.scope("name"));
out.w("' value='").w(dude.getName()).w("'/></td>");
out.w("</tr>");
out.w("<tr>");
out.w(" <td>Age</td>");
out.w(" <td><input type='text' name='").w(op.scope("age"));
out.w("' value='").w(dude.getAge()).w("'/></td>");
out.w("</tr>");
April 6, 2010 91
NiagaraAX-3.5 Developer Guide
out.w("</table>");
}
}
April 6, 2010 92
NiagaraAX-3.5 Developer Guide
Hx - Theming
Theming
All styling in hx is handled with CSS. The core colors and fonts are defined in module://hx/javax/baja/hx/default.css . In
order for your view to use the default theme, you should write your markup like this:
This order is important. The default class should always come first in the selector list, and before any style tag (though you should
avoid using style directly in your view) - so that styles are overridden correctly.
Note: HxProfiles that override the theme should always place their custom stylesheet last to make sure it overrides any stylesheets
loaded during the write() phase.
April 6, 2010 93
NiagaraAX-3.5 Developer Guide
Control
Overview
The control module provides normalized components for representing control points. All control points subclass from the
BControlPoint base class. Control points are typically used with the driver framework to read and write points in external devices.
There are four normalized categories of data matching the four BStatusValue types. Within each of the four categories is a readonly
component and a writable component. These eight components are:
Design Patterns
All control points use BStatusValues to represent their inputs and output. All points have one output called "out". The readonly
points contain no inputs. Typically they model a value being read from a device via the driver framework.
The writable points all contain 16 inputs and a fallback value. These 16 inputs are prioritized with 1 being the highest and 16 being
the lowest. The value to write is calculated by finding the highest valid input (1, 2, 3, down to 16). An input is considered valid if none
of the following status bits are set: disabled, fault, down, stale, or null. If all 16 levels are invalid, then the fallback value is used. Note
that the fallback value itself can have the null bit set in which case the point outputs null. The active level is indicated in the output
as a status facet.
Each of the writable points reserves level 1 and level 8 for user invoked overrides. Level 1 is an emergency override which when
invoked remains in effect permanently until the emergencyAuto action is invoked. Level 8 overrides are for normal manual overrides.
Manual overrides may be timed to expire after a period of time, or may be explicitly canceled via the auto action. Whenever level 1
or 8 is the active level then the overridden status bit is set in the output. If a timed override is in effect then the overrideExpiration
property indicates when the override will expire.
Extensions
Extensions provide building blocks to extend and change the behavior of control points. Every extension must derive from
BPointExtension. They are added as dynamic properties on a control point. Extensions can process and modify the value of a control
point whenever it executes. For example, an alarm extension can monitor the value and set the alarm bit of the output's status if an
alarm condition was detected. A list of extensions include:
BDiscreteTotalizerExt
BNumericTotalizerExt
BProxyExt
BAlarmSourceExt
BIntervalHistoryExt
BCovHistoryExt
Extensions are always invoked in the order they are declared in the slot list. They may be reordered using the standard reorder API
and workbench commands.
April 6, 2010 94
NiagaraAX-3.5 Developer Guide
When the execute method is invoked on a BControlPoint, the pointChanged(ControlPoint pt) method is in turn invoked on
each extension.
Note that when using extensions with driver proxy points, only the value being read is processed by extensions.
April 6, 2010 95
NiagaraAX-3.5 Developer Guide
History
Overview
Refer to the javax.baja.history API.
The History module manages the storage, collection, and archiving of data logs (historical data). A data log in Niagara is often
referred to as a Baja history (or history for short) and is an implementation of BIHistory. Within Niagara, histories can be accessed
locally or remotely (via Niagara's Fox communication). The History API provides the basis for creating, configuring, modifying,
accessing, and deleting histories. The Driver History API provides the means for archiving histories (pulling/pushing histories from
one station to another).
In order to provide support for a database of histories in a Niagara station, the History Service must be added (BHistoryService). It is
responsible for creating the database and enables collection and storage of histories in the database. Once the History Service is in
place, the basis for managing access to histories in the database is through the History Space (BHistorySpace). Whenever you wish to
gain access to a history, it is handled by resolving through the BHistorySpace. BHistoryDatabase is a local implementation of
BHistorySpace. It handles opening and closing history files as they are needed and also provides efficient access to these files.
Access
As mentioned, in order to access histories in the database, you must first gain access to the database itself. This is done by resolving
the history ord scheme (as defined by BHistoryScheme). The unique history scheme name is "history". Refer to the Naming
documentation for details on Niagara's naming system. For example, if you want to access a history named "TestLog" in a station's
database (the station being named "demo"), your ord would contain the query, "history:/demo/TestLog". You will notice that histories
are organized by their source station (device), or BHistoryDevice.
When a history is retrieved from the database, it is always an implementation of BIHistory. BIHistory provides access to the
following:
The history's identification. Histories are uniquely identified by a String identification composed of two parts, the source
device name and the history name. This identification information is encapsulated in BHistoryId. For example, if you have a
history named "TestLog" and it is located uder the local station named "demo", the history id would be the combination of
device (station) name and history name: "demo/TestLog".
Note: For convenience when importing/exporting histories between Niagara stations (refer to Driver History), you can use
the shorthand character '^' to refer to the parent device name. For example, if you are exporting a local history generated by
the local station, the shorthand representation for the previous example would be: "^TestLog".
Summary information about the history. This information is encapsulated in BHistorySummary. It provides such things as
the history ID, number of records in the history, the timestamp of the first record in the history, and the timestamp of the last
record in the history.
The type of records in the history. This is normally a concrete type of BHistoryRecord which will be described in more detail
later.
The configuration of the history. This is defined in BHistoryConfig which will be described in more detail later.
The data in the history itself. It provides support for scanning the records in the history, performing a time based query for
records, and appending or updating records within the history.
A history contains records which are keyed by timestamp. A record is an instance of BHistoryRecord which supplies the timestamp
key (records can always be identified by timestamp) and implements the BIHistoryRecordSet interface (always a set of 1 for a single
history record). A BTrendRecord is a special extension of a BHistoryRecord which adds two more tidbits of information to a
history record: trend flags (BTrendFlags) and status (BStatus). Trend flags are used to provide extra context information about the
record data, such as the starting record, out of order records, hidden records, modified records, or interpolated records. The status
("ok", "alarm", "fault", etc.) is associated with the collected data value. The standard Niagara data value types are supported via
extensions of BTrendRecord : BBooleanTrendRecord, BEnumTrendRecord, BNumericTrendRecord, and BStringTrendRecord.
Note: When a BIHistory is scanned or queried for its data records, it most often returns a Cursor (HistoryCursor) or a
BICollection. When iterating through this Cursor or BICollection , it is important to note that it returns the same instance of
BHistoryRecord for each iteration. This is done for performance reasons. So, if you need to store the records for later use as you
April 6, 2010 96
NiagaraAX-3.5 Developer Guide
iterate through them, be sure to make a copy of the instance (you can use the newCopy() method).
You can also query the database via a history ordQuery as defined in HistoryQuery. This allows you to find histories and filter the
data returned.
The configuration of the history to create. This information is contained in a BHistoryConfig instance. It contains the
following:
The unique identifier for the history within the entire system ( BHistoryId ).
The original source of the history.
The timezone where the history was originally collected.
The type of records contained in the history (i.e. BBooleanTrendRecord s, BNumericTrendRecord s, etc.).
The schema (BHistorySchema) for the records which allows the history to be read even if the original record type
class has changed or is not available.
The amount of data that can be stored in the history (BCapacity).
The behavior when an attempt is made to write records to the (limited capacity) history that is already full
(BFullPolicy).
The mechanism for storage of the history records (BStorageType). This defaults to a file.
The amount of time between records in the history (BCollectionInterval).
The time period when the history extension should be collecting history records (BActivePeriod). This is normally a
BBasicActivePeriod which allows the user to specify the days of the week and time of day that history records should be
recorded.
A definition of the pattern for deriving the name of the history created by the history extension. This property is of type
BFormat and it can be static text or a simple pattern that allows the actual history name to be derived from the context.
There are two main types of BHistoryExt s supported in the History module. These are the typed instances of BCovHistoryExt and
BIntervalHistoryExt. BCovHistoryExt provides support for collecting history records triggered on changes to the value of the parent
control point while BIntervalHistoryExt provides support for collecting history records based on a user defined fixed interval.
Compatibility
It is important to remember that there are two types of changes that an end user can make to a history extension (or
BIHistorySource ) to cause its history to be split (recreated with a new name). If the record type changes (i.e. a switch from numeric
records to String records), this is an incompatible change. Another incompatible change is if the interval of collection changes. In
both of these cases, the generated history will be split; the old history will keep its name, and the new history will have the same root
name, but with a postfix ("_cfg#") appended to the end of it. For example, if the history "TestLog" encounters an incompatible change,
the old history will keep its records and the name "TestLog", while any new records will be placed in a new history named
"TestLog_cfg0". If yet another incompatible change occurs after the first, the next split will have the new history named
"TestLog_cfg1", and so on.
Archiving
Refer to the Driver History documentation.
History Exceptions
The History API defines a few standard history exceptions. These all extend from HistoryException which is a
BajaRuntimeException.
A ConfigurationMismatchException is thrown when the properties of a BIHistory do not match the properties for that
April 6, 2010 97
NiagaraAX-3.5 Developer Guide
April 6, 2010 98
NiagaraAX-3.5 Developer Guide
Alarm
Introduction
The Alarm module provides core functionality for lifecycle managment of alarms within the Baja framework. Alarms are used to
indicate that some value is not within an appropriate or expected range. Alarms may be routed from the system to a variety of
external sources, be it email, a printer or a console application.
Object Model
All alarms in the Baja Framework are generated by objects implementing the BIAlarmSource interface. Those alarms
(BAlarmRecord) are then routed to the BAlarmService. The service for storing and routing of alarms. Alarms are then routed to one
or more recipients (BAlarmRecipient) via their BAlarmClass.
Alarm Sources
While BIAlarmSource is an interface, most alarm sources are instances of javax.baja.control.alarm.BAlarmSourceExt , the
alarm point extension. The alarm extension determines when it's parent point is in an alarmable condition, and uses the
AlarmSupport class to take care of routing and issuing alarms to the alarm service. The alarm source updates the alarm when the
parent point goes back to its normal condition as well as notifies the point that an acknowledgement has been received.
Objects implementing BIAlarmSource that have a status (BStatus) should use the following rules when setting the status bits.
Note that BStatus.UNACKED_ALARM should only be set if the BAlarmClass.ackRequired bit is set for that transition in the
AlarmSource's AlarmClass. This can easily be ontained if using the AlarmSupport class by calling
BAlarmSupport.ackRequired(BSourceState state).
Alarm Service
The BAlarmService coordinates routing of alarms within the framework. It routes alarms from their source to the appropriate
recipients, and alarm acknowledgements from the recipients back to the source. The alarm service routes individual alarms via their
alarm class. All alarm classes available to the system are maintained as slots on BAlarmService. The BAlarmService also maintains
the Alarm Database. It is acessed though the getAlarmDb() method.
Alarm Class
The alarm classes, as stated above, are maintained as slots on the alarm service and serve to route alarms with similar sets ot
properties along common routes - they serve as channels for like data. BAlarmClass manages the persistence of the alarms as needed
via the alarm database. The AlarmClass manages the priority of an alarm and also which alarm require acknowledgement. Each alarm
class can be linked to one or more alarm recipients.
Alarm Recipients
Alarm recipients are linked to an alarm class (from the alarm topic on the alarm class to the routeAlarm action on
BAlarmRecipient .) Recipients may be configured to receive alarms only at certain times of day, certain days of the week, and
receiving alarms of only certain transitions (eg. toOffnormal, toFault, toNormal, toAlert).
Three subclasses of BAlarmRecipient are worth noting: BConsoleRecipient , BStationRecipient and BEmailRecipient .
BConsoleRecipient
This recipient manages the transfer of alarms between the alarm history and the alarm console, i.e. it gets open alarms from the alarm
history for the console and updates the history when they are acknowledged.
BStationRecipient
This recipient manages the transfer of alarms between the alarm service and a remote Niagara station.
BEmailRecipient
April 6, 2010 99
NiagaraAX-3.5 Developer Guide
The email recipient is part of the email package. It allows alarms to be sent to users via email.
Lifecycle
Each alarm is a single BAlarmRecord that changes throughout its lifecycle. An alarm has four general states that it may be in:
1. New Alarm
2. Acknowledged Alarm
3. Normal Alarm
4. Acknowledged Normal Alarm
All alarms start as New Alarms and end as Acknowledged Normal Alarms. They may be acknowledged then go back to normal or go
back to normal then be acknowledged.
An Alert is an alarm that does not have a normal state and thus its lifecycle consists of New Alarm and Acknowledged Alarm.
New Alarms
1. BIAlarmSource generates an offnormalAlarm (or faultAlarm).
2. It is sent to the BAlarmService
3. BAlarmService routes it to its BAlarmClass.
4. The BAlarmClass sets the alarm's priority, ackRequired bit, and optional data.
5. It is then routed to any number of BAlarmRecipients.
Alarm Acks
1. When a BAlarmRecipient acknowledges an alarm, the acknowledgement is sent to the BAlarmService
2. The BAlarmService routes back to the BIAlarmSource (if an ack is required).
3. The Alarm Acknowledgement is then routed to AlarmRecipients along the same path as a New Alarm.
Usage
Setup
The most basic piece needed is a control point. Then add an alarm extension from the alarm module palette. There are several types
of extensions depending upon the type of point selected. The AlarmExtension are disabled by default. You must enabled
toOffnormal or toFault alamrs and configure and enable the alarm algorithms.
An Alarm Service is also required. Depending on your needs, it may require some of the following slots:
Link any of the slots as needed. The alarm recipients must be linked to an alarm class in order to receive alarms from that alarm
class.
To generate an alarm, go to a point with an alarm extension and put it an alarm condition.
Station Recipient
A BStationRecipient allows sending alarms to remote Niagara stations. A remote station is selected from the stations you have
configured in your Niagara Network. This recipient require that the remote station be properly configured in the Niagara Network.
Schedule
Overview
A schedule is effective or it is not. When it becomes effective, it will do something like fire an event or change an output.
When a schedule is not effective, it will have some default configurable behavior.
Most schedules will be a hierarchy of many schedules. Container schedules combine the effective state of their descendants
to determine effectiveness. Atomic schedules use some internal criteria to determine effectiveness. An example of an atomic
schedule is the month schedule. It can be configured to be effective in some months and not in others.
Subclassing. To create a new schedule type, one simply needs to implement methods isEffective(BAbsTime) and
nextEvent(BAbsTime) . See the API documentation for details.
New Properties. Properties on new schedule types should have the user_defined_1 flag set. This is important for
properties who when changed, should cause supervisor (master) schedules to update their subordinates (slaves).
Output. If the new schedule is going to be used in a control schedule, it will be necessary to assign an effective value
to it. A control schedule finds output by searching child schedules, in order, for the first effective schedule with a
dynamic property named "effectiveValue". The effectiveValue may be 10 levels deep, it will be found. Just remember
the order of schedules in a composite is important.
BCompositeSchedule
Composite schedules shouldn't need to be subclassed. However, they will be used (frequently) in building new schedule
hierarchies.
These schedules perform a simple function, they determine their effective state by combining the effective state of their
children. A composite can either perform a union or an intersection of it's children. A union means only one child has to be
effective for the parent composite to be effective. An intersection means all children have to be effective.
Report
Introduction
The Report module provides facilities for running periodic background reports on a station.
ReportService
The ReportService provides a container for the components responsible for generating and routing reports. The process of
generating a report is broken down into two components: BReportSource and BReportRecipient.
BReport Lifecycle
(ExportSource and EmailRecipient are concrete implementations for ReportSource and ReportRecipient, respectively.)
1. The generate action gets invoked on BReportSource. The action can be invoked manually or automatically via the built-in
schedule property.
2. ReportSource creates a new BReport object which gets propagated to the ReportRecipient.
3. BReportRecipient handles routing the report to some destination.
BQL
Introduction
The Baja Query Language (BQL) is an SQL-like query language that provides a mechanism for identifying various sets of data. It
provides an ad hoc way to search for data based on some criteria. By including BQL in an ord, the results can be easily book marked
or embedded in graphics views. This makes BQL an excellent tool for building reports.
Select
The select query is the most common type of BQL query. It is very similar to the select statement in SQL. The syntax is as follows:
The select statement always returns a table even if the result is actually a single object.
Extent
The first concept to understand about the above query is the extent. The extent is specified in the "from" clause of the query. The
extent works together with the ord base to determine the general set of objects in the result. The rest of the query only narrows the
general result. This is best explained with a few examples.
In the above query, the base of the "bql" query is the "slot" query. The slot scheme is used to access data in a Baja component tree. In
this case, "slot:/a/b/c" identifies the root of the tree where the BQL query processing will start. From that point, the query will
recursively search the tree for components of type "control:ControlPoint". So, when the base of the "bql" query is a slot path, the path
identifies the subtree that will be searched by the query, and the extent identifies the type of component to search for. This query
would get the name and toString for all control points under the /a/b/c in the component tree.
In this query, the base of the "bql" query is a "history" query. The history scheme is used to access data in the Baja history database.
In this case, "history:" identifies the entire set of histories in the database. The query extent "/myStation/myHistory" identifies a
specific history in the database. This query would get the timestamp, value, and status of all records in the history with the id
"/myStation/myHistory".
Projection
The projection is a comma separated list of the columns that will be returned in the result. Each element in the list must have a
column specification and may have a display name specified with the 'as' clause. Beginning in Niagara 3.5, columns may be arbitrary
expressions. The most frequent type of expression is a path expression, but you can also call scalar or aggregate functions.
In the second query, we know that all numeric points have an "out" property that is a StatusValue. A StatusValue is a structure that
contains both a status and a value. In this query, we use a path to dive into the structure and extract the value and status individually.
In the third query, we use two aggregate functions, MAX and MIN, to find the largest and smallest value of all the
control:ControlPoints in our query. The result will only have one row. See the section on BQL functions for more details
In the fourth query, we perform a calculation on the out.value to make it a percent, and then append the '%' character to the result so
that the column values display with a percent sign. The column name is aliased as 'Percent'.
Predicate
The predicate must be a boolean expression. Its purpose is to apply criteria for filtering objects out of the extent. Look at this query:
April 6, 2010 105
NiagaraAX-3.5 Developer Guide
This query would retrieve the timestamp and value of all records in the specified history. That's often not a useful query and
depending on how long the history has been collected, it may return a lot more data than we care to see. Instead, let's find all records
where the value exceeds 80 degrees.
By adding the "where" clause with "value > 80", all records with a value less than 80 are filtered out of the result. To learn more about
BQL expressions, see BQL Expressions.
Having
The "having" clause must be a boolean expression. The having clause has the same semantics as in SQL. You can use the having
clause to filter the results of your query based on aggregate functions. Consider this query:
First, note that this query could return multiple rows since its projection contains both scalar columns ("displayName") and aggregate
columns ("SUM(out.value)"). Each row will contain a distinct displayName, and the SUM of all the "out.value" values for the objects
with that displayName. The HAVING clause will further restrict the result to only contain rows where the SUM of all the out.value
values is greater than 100.
Note that if the above query had only asked for "SUM(out.value)" and did not ask for the displayName, there would only be one row
in the result. It would contain the SUM of all the "out.value" values regardless of the object's displayName. It would not be very
useful to include a HAVING clause in such a query.
Order By
The "order by" clause can be used to sort the results of the bql query. It also has similar semantics to SQL. You can order by a path
expression, a column alias, or column position (using a 1-based index). Further, you can specify whether you want the ordering to be
done in ascending (ASC) or descending (DESC) order. ASC is assumed if not specified. For example,
Group By
BQL does not have a GROUP BY clause. If you mention ANY path expression in a query that contains aggregate functions, BQL
implicitly defines a distinct grouping based on all the unique path expressions in your query. Consider:
This query will cause the bql engine to define an implicit grouping based on the "displayName" and "isWritablePoint" values.
Simple Expressions
In some cases, it may be desirable to fetch a single value instead of a table of objects. You can can accomplish that with BQL by using
a simple BQL expression.
slot:/a/b/c|bql:handle
Putting a simple path expression in the BQL ord, causes the expression to be evaluated relative to the base. Resolving this ord just
returns the value of the expression. In this case the result is the handle of the component identified by "/a/b/c". Note: If you run this
query in Workbench, you will get a "No views are accessible" error since the there are no views registered on the simple type
"java.lang.String", which is the type of the "handle" path expression.
Beginning in Niagara 3.5, you can evaluate multiple expressions against the base and have the results returned in a table with a single
row.
Each of the expressions in the list is evaluated against the component at "slot:/a/b/c". The result is a table with a single row with the
result of evaluating each expression in its corresponding column.
BQL Paths
BQL paths are an important element of any BQL query. A path can be used to specify column content or to filter the rows in a query
result. In all cases, a path is relative to the set of objects defined by the extent.
A path is a dot-separated list of fields. Consider the following example:
This retrieves the name and configured capacity of all history extensions under "/a/b". The extent tells me that I am only looking for
history extensions. The second column specifier tells me to look inside the historyConfig and extract the value of the "capacity"
property. The same concept can be applied in the "where" clause.
In this case, the extent tells me that I am only looking for numeric points. The where clause looks at the "value" property of the "out"
property of each numeric point in "/a/b" and only includes the ones that are greater than 50.
Presenting a list of all available fields in a path is not feasible. The fields that can be accessed in a path include all frozen and dynamic
properties of any component or struct (given sufficient security permissions) plus many of the methods on the target type. The
Bajadoc reference is the best place to find this information for a particular type.
A method is accessible via a path if it is public and returns a non-void value and takes either no parameters or only a Context as a
parameter. Methods that match the "getX" pattern are handled specially. To access a getter from BQL, the "get" is dropped and the
next letter is changed to lowercase resulting in the name of the desired value rather than the method name for getting it.
getX -> x
getCurrentTemperature -> currentTemperature
A few methods are used particularly often. "name" gets the slot name of a value on its parent. "parent" get the parent component.
"parent" is useful because it allows you to look up the component tree.
This query finds the name and path of all containers that contain a BooleanSchedule.
For more examples, see BQL Examples.
For more information about expressions, see BQL Expressions.
The type spec is only required when the function is not part of the built-in BQL library. This is described in more detail in the
sections below.
Scalar Functions
BQL provides the following built-in scalar functions
BBoolean slotExists(BString slotName): return true if an object has a slot with the given name.
BBoolean propertyExists(BString propName): return true if an object has a property with the given name.
BString substr(BString str, BNumber start, BNumber end): similar to Java substr() function.
The first query returns the first letter of all BFolders. The second query returns the slot path of every BComponent that has an 'out'
slot.
That's it! Pretty straight-forward. Assuming this function was in a module called "MyBql", here is how you could use it to get the
displayName and its length for every BFolder (note the use of the BTypeSpec to call the function):
Aggregate Functions
BQL provides the following built-in aggregate functions:
1. COUNT(<expresion>): count the number of items in the result set. Supports special syntax COUNT(*).
2. MAX(<expression>): evaluates the expression for every item in the result set and returns the maximum value. The expression
must evaluate to a BNumber or BStatusNumeric.
3. MIN(<expression>): evaluates the expression for every item in the result set and returns the minimum value. The expression
must evaluate to a BNumber or BStatusNumeric.
4. SUM(<expression>): evaluates the expression for every item in the result set and returns the sum of all the values. The
expression must evaluate to a BNumber or BStatusNumeric.
5. AVG(<expression>): evaluates the expression for every item in the result set and returns the average of all the values. The
expression must evaluate to a BNumber or BStatusNumeric.
The first query returns the max, min, average, and sum of all the out properties of all control:NumericWritables. The resulting table
will have a single row with four columns. The second query gets the first letter of every folder and then counts how many folders
start with that letter.
Here is an implementation of AVG that supports averaging BNumbers and BStatusNumerics. This code example shows how to
implement step 1 above.
In the scalar example above, we created a class "BLib" in the "MyBql" module to create the "strlen()" function. Here is how we can
modify that class to define the AVG function we just created. This shows how to implement step 2 from the outline above.
Note that the name of the aggregate function is determined by its declaration in step 2, it is NOT the name of the class that
implements the aggregation logic. Also, aggregate names are case-insensitive. Here is how you would call your implementation of the
average aggregate function (note the use of the BTypeSpec)
If the query has a projection, the result is a BITable and must be accessed that way to get the column data.
Beginning in Niagara 3.5 you can perform BQL queries against unmounted components. This is useful when you are
programmatically constructing component trees, and want to query the tree structure, but the components are not mounted in a
station or bog. The example below illustrates how to do this.
// NOTE: using setOut() for numeric writables because set() doesn't work when not mounted.
BQL Expressions
Back to BQL Overview
BQL Expressions are used in the where clause of a BQL query to further qualify a result by narrowing the set of objects in the extent.
Operator Precedence
BQL supports the following set of operators ordered by precedence:
*, / multiplication, division
+, - addition, subtraction
=, !=, >, >=, <, <=
like, in comparisons
Typed Literals
All primitive types and BSimple types can be expressed as literals in BQL. The syntax for primitives types is:
String - single quoted string
Example: 'This is a string literal'
number - a numeric value, unquoted
Example: 10
boolean - true or false, unquoted
Example: true
enum - The enum type spec followed by the tag separated by a dot.
Example: alarm:SourceState.normal
Expressing other BSimple types in BQL is more verbose because a type specifier is required. The syntax for a BSimple value is the
type spec (i.e. moduleName:typeName) followed by a string literal with the string encoding of the value (the result of
encodeToString() for the type). Example: baja:RelTime '10000'
Baja types are expressed in BQL using the type spec. Any type spec that is not followed by a quoted string refers to the type itself.
Example: where out.type = baja:StatusNumeric
BQL Examples
Back to BQL Overview
This document is a collection of example queries that illustrate how to identify some common sets of data with BQL. While each
example in this document only presents a single solution, keep in mind that in most cases there are several different ways get the
same result.
All points
The result is the slot path and output value of all control points. Since we specified "out" the result is the combination of value and
status. If we wanted just the value, we would have used out.value. Or if we wanted value and status in separate columns we would
have specified out.value and out.status.
The result is the slot path and output value of all control points currently in the alarm state. In the where clause, the path
"status.alarm" evaluates to true if the alarm status bit is set and false otherwise. This mechanism can be used to check the state of any
of the status bits. See BStatus for more information on status flags.
The result is the slot path and output value of all points whose name includes the substring "Meter". BQL supports simple pattern
matching. A '%' or '*' matches zero or more characters. A '_' matches exactly one character. The normal character matching is case
sensitive.
The result is the slot path of every point that has a totalizer extension and the total for each totalizer. Note that the extent is the set
of all totalizers. To get the point path, we look at the parent of each object in the extent.
The result is the slot path and output value of all schedules. Note the keyword "stop". The schedule component model makes the
"stop" keyword necessary. All of the common schedule (BooleanSchedule, NumericSchedule, etc.) are actually composed of many
more precise schedules. Without the "stop", the result would include all of the inner schedules in addition to the top level schedules
that this query is actually looking for. The "stop" tells the query processor to stop the recursion when it reaches a component whose
type matches the extent type.
The result is the slot path and output value of all writable points that are currently overridden at priority level 8. I know that every
writable point is an instance of BIWritablePoint. All writable points provide access to their active level with a method called
getActiveLevel(). Following the pattern for translating method names to BQL fields, I can access the active level on writable points
using "activeLevel". In this case I know that active level is represented by a PriorityLevel enum. The level 8 value of the enum is
specified by "control:PriorityLevel.level_8".
The key to this query is understanding how units are associated with a point. All control points have facets. For numeric points, the
units are defined as a facet. So facets.units gets the units for the point. BUnit has a method called getUnitName() so "unitName" gets
the result of that method.
This one is tricky. Because links are dynamic, they do not have a fixed name that we can search for. There is also no way to access
just the links to a schedule output from BQL. Instead we have to look at all of the links and check the endpoints. So the extent is all
links. Then we check for a source slot of "out". Finally we check the source slot path.
The result is the slot path of all control points that generate alarms for the "hvac" alarm class. The extent is all alarm source
extensions. We find the extensions that specify "hvac" for the alarm class and get the parent slot path from those. The parent of an
alarm source extension is always a control point.
This one is simple. We find all of the history extensions by using history:HistoryExt as the extent. Then we just get the slot path of
the parent. The parent of a history extension is always a control point.
All points that collect a history with a capacity greater than 1000 records.
For this query you have to understand how history extensions are configured. The capacity is a property of HistoryConfig. However,
Capacity is not a simple numeric value. To exceed 1000 records of capacity, the configured capacity may either be unlimited or
limited to a value greater than 1000. So first we check for unlimited and then we check for a limit of more than 1000 records.
This query just looks at all of the alarm classes and for each one returns the name and the unackedAlarmCount. In this case, it will
be much more efficient to narrow the search by making the alarm service be the query base. All alarm classes must be children of the
AlarmService. So it is much better to only search the AlarmService container.
Driver Framework
Overview
The driver framework provides a common model for abstracting how information is imported and exported from the station VM.
The model is built upon the following concepts
Driver Hierarchy
Drivers are always structured according to a fixed slot hierarchy as illustrated the Driver Hierarchy Diagram:
DriverContainer: Typically all drivers are located in this folder directly under the station root.
DeviceNetwork: Models the specific driver's protocol stack.
DeviceFolder: Zero or more levels of DeviceFolder can be used to organize the driver's Devices.
Device: Devices model the physical or logical device of the driver. Devices are descendents of the DeviceNetwork either as
direct children or inside DeviceFolders.
DeviceExt: DeviceExts are always direct children of Devices, typically declared as frozen slots.
Within each DeviceExt, there is usually a well defined hierarchy. For example the PointDeviceExt follows a similar model with
PointDeviceExt, PointFolders, ControlPoints, and ProxyExt.
Status
A key function of the driver framework is providing normalized management of status. The follows semantics are defined for status
flags:
The driver framework provides a standard mechanism to manage each of these status flags. A component is disabled when a user
manually sets the enabled property to false. Disable automatically propagates down the tree. For example setting the network level
disabled automatically sets all devices and points under it disabled.
The fault status is typically a merge of multiple fault situations. The driver framework does its own fault detection to detect fatal
faults. Fatal faults typically occur because a device or component has been placed inside the wrong container (such as putting a
ModbusDevice under a LonworksNetwork). Licensing failures can also trigger fatal faults. Driver developers can set their own fault
conditions in networks and devices using the configFail() and configOk() methods. A faultCause method provides a short
description of why a component is in fault. Fault conditions automaticlly propagate down the tree.
The down status indicates a communication failure at the network or device level. Down status is managed by the ping APIs using
pingFail() and pingOk(). Ping status is maintained in the health property. The driver framework includes a PingMonitor which
automatically pings devices on a periodic basis to check their health. The PingMonitor can generate alarms if it detects a device has
gone down.
DeviceExts
The following standard device extensions provide a framework for working specific types of data:
User Interfaces
The driver framework provides a comprehensive set of APIs for building tools for managing configuration and learns based on the
AbstractManager API. Also see the Driver Learn illustration.
Point Modes
There are three modes which a proxy point may operate in:
Readonly: These points are read from the device, but never written.
ReadWrite: These are points which the driver can both read from and write to.
Writeonly: These are points which the driver can write to, but cannot read.
A ProxyExt must indicate which mode it is operating by overriding the getMode() method
.
Proxy Ext
The ProxyExt component contains two properties used for managing read and write values.
The readValue property indicates the last value read from the device. For writeonly points this is the last value successfully written.
This value is used to feed the parent point's extensions and out property. If numeric, it is in device units.
The writeValue property stores the value currently desired to be written to the device. If numeric, it is in device units.
ProxyExt.readSubscribed() : This callback is made when the point enters the subscribed state. This is an indication to the
driver that something is now interested in this point. Drivers should begin polling or register for changes.
ProxyExt.readUnsubscribed() : This callback is made when the point enters the unsubscribed state. This is an indication
to the driver that no one is interested in the point's current value anymore. Drivers should cease polling or unregister for
changes.
ProxyExt.write() : This callback is made when the framework determines that a point should be written. The tuning policy
is used to manage write scheduling.
Note: All three callbacks should be handled quickly and should never perform IO on the callers thread. Instead drivers should use
queues and asynchronous threads to perform the actual IO.
If a write operation completes successfully then the writeOk() method should be called with the value written. If the write fails for
any reason then call writeFail() .
Tuning
All ProxyExts contain a Tuning property that manages how read and writes are tuned. All drivers which implement proxy points
should create a "tuningPolicies" property of type TuningPolicyMap on their DeviceNetwork. The Tuning structure on each ProxyExt
identifies its TuningPolicy within the network by slot name. TuningPolicies allow users to configure which state transitions result in a
April 6, 2010 117
NiagaraAX-3.5 Developer Guide
write() callback. TuningPolicies may also be used to setup a minWriteTime to throttle writes and a maxWriteTime to do rewrites.
Utilities
The driver framework provides a suite of APIs to aid developers in building their drivers:
BPollScheduler : This is a prebuild component that manages polling the points using a set of configurable buckets. To use
this feature have your ProxyExt implement the BIPollable interface.
ByteBuffer : This class provides a wealth of methods when working with byte buffers such as reading and writing integers
using big or little endian.
Receiving Alarms
BAlarmDeviceExt is used for receiving alarms from a remote device. The BAlarmDeviceExt should be used as the source for all
incoming alarms. If more detail is needed about the actual source, the BAlarmRecord.SOURCE_NAME or additional fields in the
BAlarmRecord's alarmData can be used. Alarm Ack Request will be routed back to the BAlarmDeviceExt when it is set as the source.
In Niagara Offnormal and Normal alarms are not two separate alarms as is found in some systems. In Niagara Offnormal and
Normal are two states of the same alarm. This is important to keep in mind is not using the AlarmSupport class as each offnormal
alarm generated will need it's source state set to Normal when it's source goes back to the normal state.
Sending Alarms
Sending alarms from the Niagara system to a remote device is accomplished by implmenting a BAlarmRecipient. The
BAlarmRecipient's handleAlarm method should route alarms from the Niagara system to the remote device and the originating
source. The actual sending of alarms to the device network should be done on a separate thread so as to not block the control engine
thread. The DeviceExt should not attempt to send alarms to Devices which are down or disabled.
BScheduleDeviceExt
Container of supervisor schedule export descriptors and subordinate schedules.
Subscription
At a random time after station startup and within the subscribeWindow property value, all subordinate schedules who have
not communicated with their supervisor will have their execute action invoked. For drivers where remote supervisors do not
persist information about local subordinates, the subscribe window should be some small value rather than the default of a
day.
Retries
Periodically the execute action of all BScheduleExports and BScheduleImportExts who are in fault is invoked. The retry
interval is controled by the retryTrigger property.
Subclasses
Implement makeExport(String supervisorId) to create BScheduleExport objects for incoming subscription requests from
remote subordinates.
Implement makeImportExt() to create the schedule extension for new subordinate schedules.
Can call processImport() to handle requests from remote subordinates.
Can call processExport() to handle updates from remote supervisors.
BScheduleExport
Maps a local supervisor to a remote subordinate. Will be a child of a BScheduleDeviceExt.
Execution
The execute action is where the the local supervisor schedule configuration is sent to the remote subordinate. It is only
invoked if the local supervisor schedule has been modified since the last time it was sent to the remote subordinate. The
executionTime property controls when the local supervisor version is compared to the remote subordinate.
Subclasses
Implement doExecute() to upload the supervisor schedule configuration.
Implement postExecute() to enqueue the execute action on an async thread.
Always call getExportableSchedule() before encoding a schedule for transmission. This inlines schedule references.
BScheduleImportExt
Maps a local subordinate to a remote supervisor. Will be a child of the subordinate schedule.
Execution
The execute action is where the local subordinate makes a request to the remote supervisor for a configuration update. The
executionTime property controls when execute is invoked but it is turned off by default. Since BScheduleImportExt.execute
will always result in a message to the remote supervisor, it is more efficient to have the supervisor push changes only when
necessary.
When the schedule device extension performs subscription, it is simply invoking the execute action on BScheduleImportExt.
Subclasses
Implement doExecute() to download the supervisor schedule configuration.
Implement postExecute() to enqueue the execute action on an async thread.
Can call processExport() to handle configuration updates from the remote supervisor.
BScheduleExportManager
This is the manager view for local supervisor schedules. This is a convenience and can be ignored.
Subclasses
Subclass ScheduleExportModel to add MgrColumns for properties added to your BScheduleExport.
Override makeModel() to return your new model.
Make the manager an agent on your schedule device extenstion.
BScheduleImportManager
This is the manager view for local subordinate schedules. This is a convenience and can be ignored.
Subclasses
Subclass ScheduleImportModel to add MgrColumns for properties added to your BScheduleImportExt.
Override makeModel() to return your new model.
Make the manager an agent on your schedule device extenstion.
Basic Driver
Overview
Refer to the Basic Driver API.
This package provides some basic classes that may be useful to developers building a new driver (i.e. field bus driver). These classes
can be used (or subclassed) to provide some basic driver functionality, such as worker (queue) management, basic poll schedule
handling, basic messages and management of these basic messages through request/response transactions (as well as unsolicited
message handling), etc. It also provides a serial implementation (com.tridium.basicdriver.serial) which can be subclassed by drivers
that use a serial port for communication. Here is an overview of basicDriver's structure:
BBasicNetwork
/ / | \
/ / | \
BBasicDevices / | Worker Queues (BBasicWorkers)
/ | - dispatcher (used for
/ | synchronizing access to Comm)
BBasicPollScheduler | - worker (for posting async
| operations, such as learns)
| - write worker (for posting
| async coalescing operations,
| such as writes)
|
Comm
/ | \
/ | \
CommReceiver | CommTransactionManager
CommTransmitter | - CommTransactions
|
|
UnsolicitedMessageListeners
(registered if needed by network)
The abstract class BBasicNetwork is the root component of basicDriver. It is the base container for BBasicDevice objects, and it
provides a basic poll scheduler where objects implementing the BIBasicPollable interface can register to be polled (i.e. points,
devices). It also provides three worker threads (queues) for handling asynchonous operations and synchronization of request
messages to the Comm for transmission to the output stream (the following outlines the INTENDED use of these worker queues):
Asynchronous operations should be posted onto either the worker queue or write worker queue (coalescing). Write operations
should always go to the write worker queue so they will be coalesced. Most other asynchronous operations, such as learns, should be
posted to the worker queue to keep the write worker queue free for write operations. As these async operations are processed
(dequeued), they should post any necessary message requests to the dispatcher queue, which synchronizes access to the Comm ( Comm
is ultimately responsible for sending the request message to the output stream via the CommTransmitter and receiving the response
message from the input stream via the CommReceiver ). Other threads may also post directly to the dispatcher queue (for example,
the poll thread can post poll message requests directly to the dispatcher queue).
Supporting Classes
BBasicNetwork also handles initialization, starting, and stopping the Comm , or communication handler. Comm is used to manage
request/response message transactions for the network, handles the interaction between the low-level transmitter and receiver, and
routes any unsolicited received messages to the appropriate listener. Comm uses the following supporting classes to accomplish its
tasks:
CommTransactionManager: provides a pool of CommTransaction objects that are used for request/response message
matching. Matching a request message to a response message is determined through an Object tag on the Message
(discussed below).
CommReceiver: an abstract class implementing Runnable which handles receiving and forming ReceivedMessage s from the
input stream. Subclasses must override the receive() abstract method to read and return a complete ReceivedMessage .
CommReceiver will loop and continuously call receive() in order to receive messages. Once a complete ReceivedMessage
is received, this class routes the ReceivedMessage back up to the Comm for further processing. The returned
ReceivedMessage may also need to contain data for request/response message matching (tag data) and unsolicited message
listener processing (unsolicited listener code).
CommTransmitter: provides access and synchronization for writing Message s (and/or bytes) to the output stream.
UnsolicitedMessageListener: Comm can store a list of objects implementing this interface in order to process unsolicited
received messages. UnsolicitedMessageListener objects can be registered to the Comm with an unsolicited listener code
key. Then when a ReceivedMessage is received and determined to be unsolicited, it can match the unsolicited listener code
to determine which UnsolicitedMessageListener instance should handle the ReceivedMessage .
MessageListener: This is a helper interface that should be implemented by objects that wish to receive a response Message .
When using the sendAsync() or sendAsyncWrite() convenience methods of BBasicNetwork, they require a parameter of
type MessageListener in order to determine where to route the response Message .
Messages
The com.tridium.basicdriver.message package contains classes useful for building driver messages (using the Message abstract class),
allowing these Message s to be written to the output stream, and formatting a response received ( ReceivedMessage ) into a proper
Message .
Message: an abstract class for wrapping a driver message and providing some methods necessary for handling a response to
this message. At a minimum, subclasses will need to provide the implementation for writing the message to the output stream
and determine how a response ( ReceivedMessage ) should be interpreted and formed into a Message .
ReceivedMessage: an abstract class for wrapping a received driver message and providing some methods for determining if it
is unsolicited and/or the unsolicited listener code to use for finding the correct UnsolicitedMessageListener if the
message is determined to be unsolicited. Subclasses should provide a means to serve the appropriate data to form a complete
Message .
Utility Classes
The com.tridium.basicdriver.util package contains utility classes useful to most drivers.
BasicException: an extension of BajaRuntimeException, a BasicException can be thrown when an error occurs in the
driver.
BBasicWorker: an extension of BWorker, it manages a basic worker thread for a queue. Used by the BBasicNetwork for the
asynchronous worker.
BBasicCoalescingWorker: an extension of BBasicWorker , it manages a basic worker thread for a coalescing queue. Used by
the BBasicNetwork for the asynchronous write worker.
BBasicPollScheduler: an extension of BPollScheduler, it handles subscribing, unsubscribing, and polling of BIBasicPollable
objects.
BIBasicPollable: an extension of BIPollable, this interface should be implemented by any objects that wish to register to
receive poll requests from the BBasicPollScheduler . Subclasses of basicDriver can use this to poll any devices, points, etc.
as needed.
Serial Driver
The com.tridium.basicdriver.serial package contains classes useful to most serial drivers (with the communication handler, Comm , at
the network level).
BSerialNetwork: an extension of BBasicNetwork that supports serial communication on a single configurable serial port. This
abstract class can be subclassed to provide a frozen property of type BSerialHelper. This property, called 'Serial Port Config',
provides an end user the ability to configure a serial port and its settings (i.e. baud rate, data bits, etc.) to use for
communication with devices on the serial network.
SerialComm: an extension of Comm that handles opening the user selected serial port as well as the input and output streams
to that port. It is used by the BSerialNetwork to handle synchronization of the serial communication.
BACnet Driver
Overview
The Niagara AX BACnet driver provides both client and server side BACnet functionality. On the server side, the Niagara station is
represented as a BACnet device on the network. Certain objects in Niagara can be exposed as BACnet Objects. Niagara will respond
to BACnet service requests for these objects, according to the BACnet specification. On the client side, Niagara can represent other
BACnet devices in the Framework. Properties of BACnet objects can be brought into Niagara as BACnet Proxy Points. In addition,
the BACnet driver provides client side schedule and trend log access. The BACnet objects can also be viewed as a whole, using the
Config views. Both client-side and server-side alarm support is provided, using the intrinsic alarming mechanism. The basic
components in the BACnet driver are
Server
The server side functionality of the driver is accomplished by using export descriptors to map Niagara objects as BACnet Objects.
The Local BACnet Device contains an export table from where all of the export descriptors are managed. The
javax.baja.bacnet.export package contains the standard export descriptors. The base interface for an export descriptor, which
must be implemented by all export descriptors, is BIBacnetServerObject . This contains the methods that are used by the comm
stack and export mechanisms to access the BACnet Object properties of whatever Niagara object is being exported. The primary
classes implementing this interface are
Wherever a BACnet property is available directly from the exported Niagara object, this property is used. In some cases, a BACnet-
required property is not available on the Niagara object being exported. In those cases, the property is defined within the export
descriptor itself.
To export an object, the Bacnet Export Manager is used. A BQL query is made against the station to find components of a particular
type, and the results are displayed. When a decision is made to add an export descriptor for a particular component, the registry is
searched for export descriptors that are registered as agents on the component's Type. If any are found, these are presented to the
user in the Add dialog.
For accepting writes, the BACnet driver requires that a BACnet user be defined in the User Service. The password for this user is
not important, except for Device Management functions such as DeviceCommunicationControl and ReinitializeDevice. The
permissions assigned for this user define what level of access is allowed for BACnet devices. Reads are always allowed; writes and
modifications (such as AddListElement) are governed by the permissions of the BACnet user. If no BACnet user is defined, writes
are not allowed.
The main area where the server side of the BACnet driver is extensible is through the creation of new export descriptor types. To
create export descriptors for object types that are not currently exportable (such as a String Point), you simply need to create a class
that implements BIBacnetServerObject . You may find that you want to subclass one of the base export descriptor classes
mentioned above, or you may find it easier to create your own, using these classes as a guide.
Client
The client side functionality of the driver is accomplished with the BBacnetDevice and its device extensions. There are extensions
for each of the normalized models
BBacnetPointDeviceExt - for modeling properties of BACnet objects into Niagara control points.
BBacnetScheduleDeviceExt - for representing BACnet Schedules as Niagara schedules for monitor or control.
BBacnetHistoryDeviceExt - for representing BACnet Trend Logs as Niagara histories for configuration and archiving.
BBacnetAlarmDeviceExt - for managing BACnet alarms from the device.
BBacnetConfigDeviceExt - for viewing and modifying BACnet Objects in their native model - as an entire object, rather
than by individual properties.
BACnet Proxy Points are configured by using a BBacnetProxyExt . There are four subclasses of this, one for each type of Niagara
control point. The extensions are polymorphic, in that they know how to convert data from any of the primitive data types to the
data type of their parent point. Any proxy point can be written to if it is of the proper type. The BACnet proxy extensions manage
writes for both priority-array and non-prioritized points.
BACnet client-side Scheduling can be accomplished in two ways
BBacnetScheduleExport - This descriptor is used when Niagara is the supervisor, driving the schedule in the device. It
contains the object identifier of the remote schedule, and the ord to the Niagara schedule that is to be the source of
scheduling data. At configurable times this data is written down to the remote schedule.
BBacnetScheduleImportExt - This extension is used when the remote schedule is the source of data, and Niagara is simply
reading scheduling information from the device. The schedule is queried at configurable times to update the Niagara
schedule.
BACnet client-side Trending is accomplished by using the BBacnetHistoryImport . This descriptor periodically archives data from
the Trend Log object in the remote device for storage by the Niagara station.
The operation of BACnet objects is sometimes easier to understand when the object is viewed as a whole, with all of its properties
viewed toegether. For this reason, the Config device extension is provided. This allows you to view, for example, all of the properties
of an Analog Input object together, without having to create proxy points for all of them. The expected use case is initial
configuration or commissioning. The base object for representing BACnet Objects is BBacnetObject. Specific subclasses for
BACnet standard object types exist in javax.baja.bacnet.config .
The main areas where the client side of the BACnet driver is extensible are
1. BBacnetDevice. For specialized device behavior, the BBacnetDevice can be subclassed. This is not for adding additional
BACnet properties; the device object properties are contained in the BBacnetDeviceObject . Each BBacnetDevice has an
enumeration list which contains all of the extensions known to that device. Specific device classes might have preconfigured
entries for these enumerations, that allow it to better interpret and represent proprietary enumeration values received from
this device.
2. BBacnetObject. For specialized object types, such as a representation of a proprietary object type, the BBacnetObject class
should be subclassed. This includes any specific device object properties, which would be contained in a subclass of
BBacnetDeviceObject .
3. proprietary data types. If any proprietary data types are created, they can be modelled corresponding to the data types in
javax.baja.bacnet.datatypes . Primitive data types are generally modelled as simples. Constructed data types are
generally modelled as a subclass of BComplex. The data type must implement BIBacnetDataType .
4. proprietary enumerations. Proprietary enumerations can also be created. If a property in an object is of an extensible
enumeration, it should be modelled as a dynamic enum whose range is defined by the specified frozen enum. Examples of
both extensible and non-extensible enumerations exist in javax.baja.bacnet.enum .
Lonworks Driver
Overview
The Lonworks API provides the means to model lonwork networks and devices for configuration and run time control. A network is
a collection of connected lonworks devices and routers.
Basic components
BLonNetwork : Is the top level container for BLonDevices . It provides manager views for commissioning, binding and trouble
shooting.
BLonDevice : Provides a database model for lonDevices to facilitate configuration and access to run time data.
BLonProxyExt : Customizes proxy points for data elements on lonDevices. Proxy points provide the mechanism to interface
point data in devices with Niagara control logic and graphics
Misc components
BLonRouter : Contains the database model needed for network management of lonworks router.
LonDevice
A BLonDevice contains BDeviceData and the means to manage a collection of BLonComponents and BMessageTags .
BLonComponents are database representation of specific components in a device. They contain one or more data elements (see
LonDataModel below) and specific config information. There are three types: BNetworkVariable , BNetworkConfig ,
BConfigParameter .
BMessageTags are only for linking. There is no behavior implemented in massage tags in the station.
BLonDevice is an abstract class and is the root class for all lonworks devices. There are two flavors of BLonDevice implemented in
the lonworks drivers:
BLocalLonDevice is a final class which provides the means to manage the local neuron. It is a frozen slot on BLonNetwork .
BDynamicDevice provides support for dynamically building the devices data and BLonComponents . There are two actions to
accomplish this: learnNv uses the self documentation in the device, importXLon uses an xml file containing a representation
of the device.
BLonByteArray is used in special cases to model data when can not be meaningfully modeled as primitive elements
BLonSimple is used in special cases to model data which has been representated by a simple. The simple must implement
BILonNetworkSimple .
Proxy points
Proxy points are standard Niagara control points used to access data elements in foreign devices. Proxy points have a driver specific
ProxyExt that handles addressing data elements in a specific device and data conversion needed to present the data in a normalized
format. The inputs and outputs of proxies can be linked to other control logic or graphical points.
A BLonProxyExt in a Proxy point makes it a lonworks proxy point. There are different BLonProxyExts for each primitive data type.
These can be seen in javax.baja.lonworks.proxy.
Lon Proxy Points are managed by LonPointManager which is a view on the points container in each BLonDevice .
Network Management
Implements a set of standard lonworks network management functions. The user has access to these functions through the following
manager view.
DeviceManager - provides support for discovering and adding lonwork devices to the database, for managing device
addresses, and downloading standard applications to devices.
RouterManager - provides support for discovering and adding lonwork routers to the database, and for managing device
addresses
LinkManagar - provides means to manage link types and bind links.
LonUtiliesManager - provides a set of utilities useful for managing a lon network
LonComm
The lonworks communication stack can be accessed through a call to BLonNetwork.lonComm(). LonComm is provides APIs which
allow the user to send LonMessages with one of the LonTalk service types (unackowledged, acknowledged, unackowledged repeat,
request response).
LonComm also provides a means to receive unsolicited messages by registering a LonListener for a specifed message type from an
optional subnetNode address.
LonMessage
LonMessage is the base class for all messages passed to/from LonComm APIs.
Users should subclass LonMessage if they wish to create a new explicit message type.
A set of LonTalk defined messages is provide in com/tridium/lonworks/netmessages. The definition of these message is found in
Neuron Chip Data Book Appendix B, Lonworks Router User's Guide, and EIA/CEA-709.1-B.
name of the file containing the device representation. The xml file formate is described in Lon Markup Language.
Examples of def entries in lon device module-include.xml file.
<defs>
<def name="lonworks.80 00 0c 50 3c 03 04 17" value="cl=lonHoneywell:Q7300" />
<def name="lonworks.80 00 16 50 0a 04 04 0a" value="xml=lonSiebe/Mnlrv3.lnml"/>
<def name="lonworks.80 00 8e 10 0a 04 0* **" value="xml=lonCompany/dev.lnml"/>
</defs>
<name type="XLonXmlType">
<name v="value">
Example:
<T7300h type="XLonDevice">
<!-- Defined element deviceData with no type specified -- >
<deviceData
<!-- Defined element with value -- >
<programID v="80 0 c 50 3c 3 4 17"/>
. . .
</deviceData>
The set of valid LonXmlTypes are: XLonXMLInterfaceFile, XLonDevice, XEnumDef, XTypeDef, XNetworkVariable,
XNetworkConfig, XConfigProperty, XMessageTag
LonXMLInterfaceFile
The root type is LonXMLInterfaceFile. It may contain EnumDefs, TypeDefs, and LonDevices. It may also reference other
LonXMLInterfaceFiles to allow for EnumDefs, and TypeDefs to be shared. The file attribute indicates the element is an included file
<!-- Example with enumDefs and typeDefs included in single file. -->
<T7300h type="XLonXMLInterfaceFile">
<HwThermAlarmEnum type="XenumDef"> . . . </HwThermAlarmEnum>
<HwThermAlarm type="XTypeDef"> . . . </HwThermAlarm>
<T7300h type="XLonDevice"> . . . </T7300h>
</T7300h>
TypeDefs
EnumDefs and TypeDefs elements are needed to define the data portion of nvs, ncis, and config properties. An EnumDef contains a
set of tag/id pairs where the name of the element is the tag and the value is the id.
<HwThermAlarmEnum type="XEnumDef">
<NoAlarm v="0"/>
<T7300CommFailed v="2"/>
<AlarmNotifyDisabled v="255"/>
</HwThermAlarmEnum>
A TypeDef contains a set of data elements. Each data element contains a name and set of qualifiers. The "qual" attribute contains a
type field(u8, s8, b8, e8 ..), type restrictions (min,max) and encoding (resolution, byteOffset, bitOffset, len) information. For a
description of valid element values see Appendix B. If an element is an enumeration then the enumDef attribute must be included to
specify the name of the EnumDef used.
<HwThermAlarm type="XTypeDef">
< elem n="subnet" qual="u8 res=1.0 off=0.0"/>
< elem n="type" qual="e8" enumDef="HwThermAlarmEnum"/>
</HwThermAlarm>
<HwThermConfig type="XTypeDef">
<TODOffset qual="u8 byt=0 bit=0 len=4 min=0.0 max=15.0 "
default="0" engUnit="F"/>
<DeadBand qual="ub byt=0 bit=4 len=4 min=2.0 max=10.0 "
default="2" engUnit="F"/>
</HwThermConfig>
A TypeDef may have nonstandard features which require a software implementation. This is the case for typedefs with unions.
Unions are not currently supported. A typeSpec attribute can be used to specify a class file in a baja module as the implementation
of the TypeDef. The class must be a subclass of BLonData and provide overrides to byte[] toNetBytes() and
fromNetBytes(byte[] netBytes) .
<FileStatus type="XTypeDef">
< typeSpec v="lonworks:LonFileStatus"/>
</FileStatus>
LonDevice
A LonDevice consists of a defined element deviceData and sets of 0 or more of each XNetworkVariable, XNetworkConfig,
XConfigProperty, and XMessageTag type elements.
<T7300h type="XLonDevice">
<deviceData> . . . </deviceData>
<nviRequest type="XNetworkVariable"> . . . </nviRequest>
<nvoAlarmLog type="XNetworkVariable"> . . . </nvoAlarmLog>
<nciApplVer type="XNetworkConfig"> . . . </nciApplVe>
<ScheduleFile type="XConfigProperty"> . . . </ScheduleFile>
<fx_explicit_tag type="XMessageTag"> . . . </fx_explicit_tag>
</T7300h>
DeviceData
DeviceData is a defined set of values need to describe or qualify a lonworks device. A complete list of elements and their default
values provided later.
<deviceData>
<majorVersion v="4"/>
<nvoAlarmLog type="XNetworkVariable>
<index v="38"/>
<direction v="output"/>
<typeDef="HwThermAlarmLog"/>
</nvoAlarmLog>
<nviRequest type="XNetworkVariable">
<index v="0"/>
<snvtType v="objRequest"/>
. . .
</nviRequest>
<nciSetpoints type="XNetworkConfig">
<index v="17"/>
<snvtType v="tempSetpt"/>
. . .
</nciSetpoints>
<bypassTime type="XConfigProperty">
<scptType v="CpBypassTime"/>
<scope v="object"/>
<select v="0"/>
. . .
</bypassTime>
File Attribute
There will be cases where it is desirable to nest interface files. This will provide a means to share type definitions between multiple
device interface files. It may also ease the process of auto generating the files when the data is contained in multiple forms (i.e. xif
files, resource files, ...).
To include a file an element with the "file" attribute is included in the root. The path in the file attribute entry is specified relative to
the containing file.
The following is an example of nested files. File #1 contains enum definitions, File #2 contains type definitions which use the
enumDefs and file #3 contains the device definition which may use both.
File #3 ..\honeywell\
<?xml version="1.0" encoding="UTF-8"?>
<T7300h type="XLonXMLInterfaceFile">
<HwTherm file="..\datatypes\HwTherm.xml"/>
<T7300h type="XLonDevice">
<nvoAlarm type="XNetworkVariable" >
<typeDef="HwThermAlarm"/>
<index v="36"/>
<direction v="output"/>
...
</nvoAlarm>
</T7300h>
</T7300h>
XDeviceData Definition
XDeviceData definition: see LonMark External Interface File Reference Guide 4.0B
int networkOutputBuffers 0 -
int priorityNetworkOutputBuffers 0 -
int priorityApplicationOutputBuffers 0 -
int applicationOutputBuffers 0 -
int applicationInputBuffers 0 -
int sizeNetworkInputBuffer 0 -
int sizeNetworkOutputBuffer 0 -
int sizeAppOutputBuffer 0 -
int sizeAppInputBuffer 0 -
String applicationType unknown unknown,mip,neuron,hostSelect,hostNISelect
int numNetworkVariablesNISelect 0 -
int rcvTransactionBuffers 0 -
int aliasCount 0 -
boolean bindingII false -
boolean allowStatRelativeAddressing false -
int maxSizeWrite 11 -
int maxNumNvSupported 0 -
int neuronChipType 0 -
int clockRate 0 -
int firmwareRevision 0 -
int rcvTransactionBlockSize 0 -
int transControlBlockSize 0 -
int neuronFreeRam 0 -
int domainTableEntrySize 0 -
int addressTableEntrySize 0 -
int nvConfigTableEntrySize 0 -
int domainToUserSize 0 -
int nvAliasTableEntrySize 0 -
boolean standardTransceiverTypeUsed true -
int standardTransceiverTypeId 0 -
int transceiverType 0 -
int transceiverInterfaceRate 0 -
int numPrioritySlots 0 -
int minimumClockRate 0 -
int averagePacketSize 0 -
int oscillatorAccuracy 0 -
int oscillatorWakeupTime 0 -
int channelBitRate 0 -
boolean specialBitRate false -
boolean specialPreambleControl false -
String specialWakeupDirection input input,output
boolean overridesGenPurposeData false -
int generalPurposeData1 0 -
int generalPurposeData2 0 -
int generalPurposeData3 0 -
int generalPurposeData4 0 -
int generalPurposeData5 0 -
int generalPurposeData6 0 -
int generalPurposeData7 0 -
int rcvStartDelay 0 -
int rcvEndDelay 0 -
int indeterminateTime 0 -
int minInterpacketTime 0 -
int preambleLength 0 -
int turnaroundTime 0 -
int missedPreambleTime 0 -
int packetQualificationTime 0 -
boolean rawDataOverrides false -
int rawDataClockRate 0 -
int rawData1 0 -
int rawData2 0 -
int rawData3 0 -
int rawData4 0 -
int rawData5 0 -
String nodeSelfID "" -
NetworkVariable and Network Config common elements
int maximumRate 0 -
int arraySize 1 -
boolean offline false -
boolean bindable true -
String direction "input" input,output
String serviceType "unacked" acked, repeat, unacked, unackedRpt
boolean serviceTypeConfigurable true -
boolean authenticated false -
boolean authenticatedConfigurable true -
boolean priority false -
boolean priorityConfigurable true -
NetworkVariable only elements
Element Qualifier
The format for an element attribute is:
qual="Type [qualifier=xx]"
example: qual="u8 res=0.1 min=5 max=12"
Build
Contents
This document is structured into the following sections:
Overview
Directory Structure
build.xml
module-include.xml
module.palette
module.lexicon
Grouping Modules
devkit.properties
Using Build
Overview
The Niagara developers kit includes the build tool used by Tridium to build the framework itself. You may utilize this build tool to
manage your own Niagara modules. The build tool includes the following:
Directory Structure
Every module is managed in its own directory structure. Below is an example of a directory for the alarm module:
[alarm]
+- build.xml
+- module-include.xml
+- module.palette
+- module.lexicon
+- [src]
| +- [javax]
| | +- [baja]
| | +- [alarm]
| | +- JavaClass1.java
| | +- JavaClass2.java
| +- [com]
| | +- [tridium]
| | +- [alarm]
| | + JavaClass3.java
| | +- [ui]
| | + BAlarmConsole.java
| +- [doc]
| +- AlarmConsole-guide.html
+- [libJar]
All of the source code which is used to build the module's jar file is located under the "src" directory. During the build process the
"libJar" directory is used to build up the image which will be zipped up for the module's jar file.
build.xml
April 6, 2010 139
NiagaraAX-3.5 Developer Guide
At the root of every module's directory must be a "build.xml" file. This file instructs the build tool how to build the module. An
example of alarm's "build.xml" file:
<module
name = "alarm"
bajaVersion = "0"
preferredSymbol = "a"
description = "Niagara Alarm Module"
vendor = "Tridium"
>
name [required]: Name of the module which should match the directory name.
bajaVersion [required]: Baja specification version number, or "0" if not a public specification.
preferredSymbol [required]: Pick a short symbol to use as a abbreviation for the module name. This symbol is used during
XML serialization.
description [required]: A short description of the module's purpose in life.
vendor [required]: Vendor for the module.
install [optional]: Sets the default value of install for packages and resources. This attribute is used during provisioning to
strip optional directories for headless devices. Valid values are "runtime", "ui", and "doc". The default is "runtime".
edition [optional]: Sets the default value of edition for packages. This attribute determines which Java library edition to
compile against. Valid values are "j2me", "j2se", and "j2se-5.0". The default is "j2me".
Package Element
The package element is used to declare a Java package. These elements are used for both compiling java source files as well as
generating reference documentation. The following attributes are supported:
name [required]: Name of package as it would be declared in a package or import statement. There must be a
Resources Element
The resources element is used to copy files from the "src" directories into the "libJar" directories. The following are the attributes
supported:
name [required]: Name of files to copy from the "src" to "libJar" directory. The path is relative to the "src" directory and is
copied to a matching directory under "libJar". You may specify an explicit filename or use wildcards such as "*.*" or "*.png".
install [optional]: This attribute is used during provisioning to strip optional directories for headless devices. Valid values are
"runtime", "ui", and "doc". The default is defined by module element.
module-include.xml
The "module-include.xml" file is an optional file that is placed directly under the module's root directory with the "build.xml". If it is
declared, then the build tool automatically includes it in the module's manifest file "meta-inf/module.xml" file. It's primary purpose is
to allow developers to declare def and type elements. An example is provided:
<types>
<type name="AlarmRecord" class="javax.baja.alarm.BAlarmRecord" />
<type name="AlarmRecipient" class="javax.baja.alarm.BAlarmRecipient" />
<type name="AlarmClass" class="javax.baja.alarm.BAlarmClass"/>
</types>
module.palette
The "module.palette" file is an optional file that is placed directly under the module's root directory. If included it is automatically put
into the root of the "libJar" directory, and accessible in the module as "/module.palette". The "module.palette" file should contain the
standard palette of public components provided by the module. The format of the file is the same as a standard .bog file.
module.lexicon
The "module.lexicon" file is an optional file that is placed directly under the module's root directory. If included it is automatically
put into the root of the "libJar" directory. The lexicon file defines the name/value pairs accessed via the Lexicon API.
Grouping Modules
The Niagara build tool allows you to create hierarchies of modules which may be managed together. This is done through the use of
grouping "build.xml" files. Let's look at an example:
<module
name = "drivers"
specVersion = "1"
April 6, 2010 141
NiagaraAX-3.5 Developer Guide
preferredSymbol = "drivers"
description = "Niagara Framework Build Driver"
vendor = "Tridium"
>
</module>
You will note that it appears very similar to a standard "build.xml" file, except that it only contains submodule elements. The
submodule elements contain a single attribute name which maps to the sub-module's root directory. If the modules have inter-
dependencies, then they should be listed in order of the dependencies. You may use grouping "build.xml" files to create groupings of
groupings and built up projects of arbitary size and depth.
devkit.properties
The build tool uses the "home/lib/devkit.properties" file to store a couple key pieces of information:
# This property defines the master build number used for vendorVersion.
build=3.0.59
Using Build
Once you have your directories and "build.xml" files all squared away you use "build.exe" to actually build your modules. The first
argument to most build commands is the path of the module or group of modules to build. The second argument is typically the
build command to execute, the default is jar.
The following command compiles and jars the module "foo" located under the project root (declared in devkit.properties):
build /foo
The following command cleans the "libJar" directory of the module contained in a directory "goop" relative to the current working
directory:
The following command cleans all modules in the project, then rebuilds them:
build / cleanjar
Execute the following command to dump a full listing of commands and options supported by the build tool:
build -help
Deploying Help
Overview
Help documentation is deployed as a set of files zipped up in module jar files. Help content can be any MIME typed file. The primary
content types are:
HTML: Niagara provides support for HTML 3.2 with cascading style sheets. See HTML Support in the User Guide for
specific tags and attributes supported. This is the main format used to distribute help content.
Bajadoc: The Bajadoc format is a special XML file used to distribute reference documentation. Niagara supports a special
plugin, the BajadocViewer which allows users to view the reference documentation in a variety of ways. Bajadoc files are
generated from Javadoc comments using the build tool.
In addition to help content, the Niagara Framework also supports delivering navigation data using JavaHelp files. Niagara help system
is loosely compliant with version 1.0 of JavaHelp specification from Sun Microsystems. By the term "loosely"; we mean that it is
compliant with some of its requirements as of version 1.0.
There are three steps in help content creation:
1. Developer supplies help content files and help structure files. Most of help content will be in form of HTML files, maybe with
some graphics to enhance the presentation. As a general rule, you as developer should not concern yourself with anything but
the content itself, providing HTML files with defined title and body that contains only content-related information.
Developers should also include guide help for all their BPlugins designed for use by BComponents . This documentation is
standard HTML files located in the "doc" directory using a naming convention of "module-TypeName.html ".
You should provide a TOC file, to specify the logical order of the help files.
2. (Optional ) Developer supplies lexicon key to point to module containing help. Guide help (Guide on Target) will look for the
HTML file defined above in the doc directory of its module if the help.guide.base is not defined in its lexicon. You can
supply this key to point to another module. As an example, most core modules point to docUser:
help.guide.base=module://docUser/doc
3. Build the module. The module containing the help content is built using the standard build tool. See Using Build. In addition
to the standard build, you should use the htmldoc tool to enhance HTML files, and then use the index tool to build search
index files. During this step, the help content is indexed for the full text search purposes. For example, to build docDeveloper,
we ran this sequence of commands:
Help Views
The same help content can be presented in many different ways. Each way of presenting help content is called view in JavaHelp
lingo. Three most typical views are: Table of Contents, API and Search.
Table of Contents, a.k.a. TOC, is used for presenting help content in a structured way, in some logical order.
API is used for presenting bajadoc organized by module.
Search allows full text search of the help content based on some search criteria.
We have added our own "standard" view - BajaDoc view. This is a way of presenting reference documentation for the module classes.
TOC
As a general rule, you should provide a rough TOC with your help content. This should be an XML file, named toc.xml , located in
the doc/ directory. This file is required for a module to appear in the help table of contents. Here's the DTD for this file:
It should have <toc> as its root element, and a list of files that you want to include in the final TOC, in the logical order. Although
TOC structure can be many levels deep, the most likely case will be a flat list of files.
Each file is included via the <tocitem> element, that has two attributes: text and target . The text attribute specified the text of
the TOC node as it appears in the TOC tree, the target attribute can be either relative URL of the help content file associated with
this TOC item (relative to the doc/ directory). It is important that the help file URL is relative to the doc/ directory. We require that
at least one of these attributes is defined.
You may use tocitem elements with only the text attribute defined as grouping TOC nodes. If you want to define a TOC node
associated with some help content, you must provide the target . If you provide the target only, the text will be generated as the
name of the target file, without path and extension.
Here's a sample TOC file:
<toc version="1.0">
<tocitem text="Overview" target="overview.html" />
<tocitem text="User Guide" target="userGuide.html" />
<tocitem text="Developer Guide" target="devGuide.html" />
</toc>
API
This is a list of modules with bajadoc.
Search
This is a search view to search text.
bajadoc Command
Every module should include reference documentation. Reference documentation is built using the Niagara build tool:
The module must already have been built using the build [moduleDir] command. The .bajadoc files are compiled from the
Javadoc found in the source code and placed in the "libJar" directory. Then the module is re-jarred using the new .bajadoc files. For
more information on building BajaDoc, see build.xml in build.html.
htmldoc Command
htmldoc tool is invoked using the Niagara build tool:
The module must already have been built using the build [moduleDir] command.
This tool enhances HTML files. Every HTML file will be enhanced with the following:
style sheet link - This is hard-coded and not configurable. If your HTML file already has a link element in document's head
section, the auto-generated link will not be inserted.
copyright notice - The copyright notice generated is controlled by a property in devkit.properties:
If your document has <p class="copyright"> tag, empty or not, the auto-generated copyright notice will not be inserted
in it.
navigation links - Based on the information in the TOC, navigation links are inserted in every HTML file that doesn't have <p
class="navbar"> or <div class="navbar"> tag. The navigation links include three links:
The entire module is then re-jarred, and the enhanced help content is included in the JAR produced.
index Command
Index tool is invoked using the Niagara build tool:
The module must already have been built using the build [moduleDir] command.
Building search indices out of help content.
If you want the .bajadoc documentation to also be indexed, you should run the bajadoc command before running the index
command.
During this step, all visible text inside the help content files will be broken into word tokens and stored in the binary files
documents.dat, postings.dat, worddocs.dat and words.dat. The entire module is then re-jarred, and the enhanced help content is
included in the JAR produced.
Slot-o-matic 2000
Overview
The Slot-o-matic 2000 is a java source code preprocessor which generates java source code for Baja slots based on a predefined
comment header block. The generated code is placed in the same source file, and all other code in the original source is not modified
in any way.
Usage
Invocation
Slot-o-matic is invoked by the executable slot.exe . To get help invoke:
D:\>slot -?
Slot-o-matic will compile any file that meets the following conditions:
1. The file name is of format B[A-Z]*.java, e.g. BObject.java or BSystem.java, but not Ball.java.
2. The source code has a comment block delimited by /*- -*/ .
When Slot-o-matic compiles a file, it reads the comment block and generates new java source code based on the contents of that
block. The new source is placed in the file being compiled immediately after the Baja comment block. If any errors are found, the
contents of the file are not altered in any way. The source file may (and indeed probably must) have any other source code required
to implement the class in the source file, as with normal java source. The only difference between a normal java source file and one
usable by Slot-o-matic is the /*- -*/ comment block.
Compiling a file is simple:
D:\>slot D:\niagara\r3dev\fw\history\javax\baja\history\BHistoryService.java
Compile BHistoryService.java
Compiled 1 files
D:\>
As is a directory:
D:\>slot D:\niagara\r3dev\fw\history
Compile BHistoryService.java
Compiled 1 files
D:\>
Slot-o-matic works like make in that it will only compile files whose /*- -*/ comment block's content has changed since the last
compile. To force recompile, use the -f flag on a file or directory:
D:\>slot -f D:\niagara\r3dev\fw\history\src\javax\baja\history\
Compile BBooleanHistory.java
Compile BFloatHistory.java
Compile BHistory.java
Compile BHistoryDevicelet.java
Compile BHistoryJoin.java
Compile BHistoryPeriod.java
Compile BHistoryService.java
Compile BHistorySync.java
Compile BStorageType.java
Compiled 9 files
D:\>
Examples
Class Example
This example class would resolve in a file named BImaginaryObject.java.
/*-
class BImaginaryObject
{
properties
{
imaginaryName: String
-- The imaginary name for the imaginary object.
default {[ "imaginaryName" ]}
size: int
-- The size of the imaginary object.
flags { readonly, transient }
default {[ 0 ]}
}
actions
{
imagine(arg: BComponent)
-- Imagine something
default {[ new BComponent() ]}
create(): BSystem
-- Create a new imaginary system.
}
topics
{
imaginationLost: BImaginaryEvent
-- Fire an event when the object loses its imagination.
There are blocks for each of the major slot types: properties, actions, and topics. None of the blocks needs to be present.
Properties Block
Each property has a name and a data type. Comments are specified via the "--" tag per line of comment. All comments are
transferred to the javadoc headers of the generated source code but are of course optional. A default value for all properties must be
specified. The default block is delineated by {[ ]} and may have any sequence of java code inside it. Flags on the property may also
optionally be specified. For more information on the available flags, see the Flags bajadoc. Slot-o-matic will generate all get and set
methods for the property.
Actions Block
Each action may have 0 or 1 input (formal) arguments, and may optionally return a value. Actions are commented like properties.
The input argument, if present, must have a default value as with a property. Slot-o-matic will generate the action invocation code;
the implementor of the class must provide a do<actionName> method that provides the action implementation.
Topics Block
Each topic specifies a name and an event type that it sends when fired. Slot-o-matic generates code to fire the event.
Enum Example
This example class would resolve in a file named BImaginaryEnum.java.
/*-
enum BImaginaryEnum
{
range
{
good,
bad,
ugly
}
}
-*/
BNF
The formal BNF of the format is as follows:
Architecture - Communication
Architecture - ProxyExt