Netlinx Programming Language
Netlinx Programming Language
Netlinx Programming Language
NetLinx
Programming Language
INTELLECTUAL PROPERTY.
The AMX Software is owned by AMX and is protected by United States copyright laws, patent laws, international treaty provisions, and/or state
of Texas trade secret laws. Licensee may make copies of the AMX Software solely for backup or archival purposes. Licensee may not copy
the written materials accompanying the AMX Software.
TERMINATION.
AMX RESERVES THE RIGHT, IN ITS SOLE DISCRETION, TO TERMINATE THIS LICENSE FOR ANY REASON AND UPON WRITTEN
NOTICE TO LICENSEE. In the event that AMX terminates this License, the Licensee shall return or destroy all originals and copies of the
AMX Software to AMX and certify in writing that all originals and copies have been returned or destroyed.
PRE-RELEASE CODE.
Portions of the AMX Software may, from time to time, as identified in the AMX Software, include PRE-RELEASE CODE and such
code may not be at the level of performance, compatibility and functionality of the final code. The PRE-RELEASE CODE may not
operate correctly and may be substantially modified prior to final release or certain features may not be generally released. AMX is
not obligated to make or support any PRE-RELEASE CODE. ALL PRE-RELEASE CODE IS PROVIDED "AS IS" WITH NO
WARRANTIES.
LIMITED WARRANTY.
AMX warrants that the AMX Software will perform substantially in accordance with the accompanying written materials for a period of ninety
(90) days from the date of receipt. AMX DISCLAIMS ALL OTHER WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, WITH REGARD TO THE
AMX SOFTWARE. THIS LIMITED WARRANTY GIVES LICENSEE SPECIFIC LEGAL RIGHTS. Any supplements or updates to the AMX
SOFTWARE, including without limitation, any (if any) service packs or hot fixes provided to Licensee after the expiration of the ninety (90) day
Limited Warranty period are not covered by any warranty or condition, express, implied or statutory.
LICENSEE REMEDIES.
AMX's entire liability and Licensee's exclusive remedy shall be repair or replacement of the AMX Software that does not meet AMX's Limited
Warranty and which is returned to AMX. This Limited Warranty is void if failure of the AMX Software has resulted from accident, abuse, or
misapplication. Any replacement AMX Software will be warranted for the remainder of the original warranty period or thirty (30) days,
whichever is longer. Outside the United States, these remedies may not available.
NO LIABILITY FOR CONSEQUENTIAL DAMAGES. IN NO EVENT SHALL AMX BE LIABLE FOR ANY DAMAGES WHATSOEVER
(INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS
INFORMATION, OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS AMX SOFTWARE,
EVEN IF AMX HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME STATES/COUNTRIES DO NOT
ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION
MAY NOT APPLY TO LICENSEE.
SOFTWARE AND OTHER MATERIALS FROM AMX.COM MAY BE SUBJECT TO EXPORT CONTROL.
The United States Export Control laws prohibit the export of certain technical data and software to certain territories. No software from this Site
may be downloaded or exported (i) into (or to a national or resident of) Cuba, Iraq, Libya, North Korea, Iran, Syria, or any other country to
which the United States has embargoed goods; or (ii) anyone on the United States Treasury Department's list of Specially Designated Nation-
als or the U.S. Commerce Department's Table of Deny Orders. AMX does not authorize the downloading or exporting of any software or
technical data from this site to any jurisdiction prohibited by the United States Export Laws.
This Agreement replaces and supersedes all previous AMX Software License Agreements and is governed by the laws of the State of Texas,
and all disputes will be resolved in the courts in Collin County, Texas, USA. For any questions concerning this Agreement, or to contact AMX
for any reason, please write: AMX, 3000 Research Drive, Richardson, TX 75082.
Table of Contents
Introduction ........................................................................................................1
Conventions Used in this Document ......................................................................... 1
Related Instruction Manuals...................................................................................... 1
NetLinx Programming Overview ........................................................................3
Defining the Superset ............................................................................................... 3
NetLinx vs. Axcess - Comparison by Structure.......................................................... 4
DEFINE_DEVICE.............................................................................................................. 4
DEFINE_CONSTANT ...................................................................................................... 4
DEFINE_VARIABLES........................................................................................................ 5
DEFINE_CALL (Subroutines) ........................................................................................... 6
DEFINE_START ............................................................................................................... 7
DEFINE_EVENT .............................................................................................................. 7
DEFINE_PROGRAM ........................................................................................................ 8
Operators ................................................................................................................. 8
Axcess/NetLinx Incompatibility................................................................................. 9
Data Types.............................................................................................................. 10
Constants................................................................................................................ 10
Variables ................................................................................................................. 11
Persistent Variables ................................................................................................ 12
Arrays ..................................................................................................................... 12
Structures ............................................................................................................... 14
Data sets ....................................................................................................................... 15
Conditionals & Loops.............................................................................................. 17
SWITCH...CASE statements........................................................................................... 17
FOR loops ..................................................................................................................... 18
Functions ................................................................................................................ 19
DEFINE_CALL................................................................................................................ 19
DEFINE_FUNCTION ...................................................................................................... 20
Events ..................................................................................................................... 21
Button Events................................................................................................................ 21
Channel Events.............................................................................................................. 22
Data Events ................................................................................................................... 24
Level Events .................................................................................................................. 27
Combining Devices, Channels and Levels ............................................................... 28
Virtual devices, levels and device/channel sets ............................................................. 28
Combining and uncombining devices............................................................................ 28
Combining and uncombining levels............................................................................... 28
Variables ................................................................................................................. 45
Scope ............................................................................................................................ 45
Local variables............................................................................................................... 45
Global variables............................................................................................................. 47
Constancy...................................................................................................................... 48
Persistence .................................................................................................................... 48
Constants................................................................................................................ 49
Data Types.............................................................................................................. 50
Intrinsic types ................................................................................................................ 50
Type conversion ............................................................................................................ 50
Type conversion rules.................................................................................................... 50
Strings .................................................................................................................... 51
String expressions ......................................................................................................... 51
Wide strings .................................................................................................................. 51
Arrays .................................................................................................................... 52
Multi-dimensional arrays ............................................................................................... 53
Structures ............................................................................................................... 55
Subroutines............................................................................................................. 56
DEFINE_CALL subroutines ............................................................................................ 56
SYSTEM_CALL subroutines ........................................................................................... 56
Function Subroutines .................................................................................................... 57
Calling parameters ........................................................................................................ 59
Event Handlers .................................................................................................61
Button events................................................................................................................ 62
Channel events.............................................................................................................. 63
Data events ................................................................................................................... 64
Level events .................................................................................................................. 65
Custom events .............................................................................................................. 67
Event Parameters .......................................................................................................... 68
Timeline Functions .................................................................................................. 71
Creating a timeline........................................................................................................ 71
TIMELINE example ........................................................................................................ 74
TIMELINE IDs ................................................................................................................ 78
Combining Devices, Levels, and Channels ........................................................79
Combining and Un-Combining Devices................................................................... 79
Combining devices ........................................................................................................ 79
Un-combining devices ................................................................................................... 81
Combining and Un-Combining Levels ..................................................................... 82
Combining levels........................................................................................................... 83
Introduction
NetLinx® is the second generation of the Axcess® programming language and is a superset of the
original Axcess language with extensions for additional data types, new event handlers, structure
support, multi-dimensional arrays, and other features. This document assumes that you are familiar with
Axcess; the focus is on the new language elements and how they extend the functionality of the existing
language.
For background information on Axcess, refer to the Axcess Programming Language instruction manual.
For a side-by-side comparison of programming in Axcess and NetLinx, refer to the NetLinx
Programming Overview section on page 3.
Keywords are case insensitive. For example, the PUSH command is the same as push. Keywords are
reserved, meaning that identifiers (device names, constants, or variables) must have unique names. These
keywords are listed and defined in the Reserved Identifiers section on page 99. All references to NetLinx
language keywords in this document appear in THE FONT SHOWN HERE, in all capital letters.
Programming examples appear in the same fixed font. For example:
DEFINE_VARIABLE
CHAR MyString[32]
INTEGER StrLen
Square brackets indicate an optional element in a command. Angle brackets indicate substitution. In the
example below, the notation <return type> indicates that a valid data type (such as CHAR, INTEGER,
or FLOAT) must be substituted for <return type>. The square brackets surrounding it indicate that
the return type is optional.
DEFINE_FUNCTION [<return type>] <name> [(Param1, Param2, …)]
{
(* body of subroutine *)
}
Use the NetLinx Studio software program to create, compile, and transfer Axcess/
NetLinx code.
The exceptions are the Axcent, the Axcent2, the AXB-EM232, and the AXB-MPE+
Master Port Expander. None of these integrated controllers allow you to disable the
Central Controller. Both Axcess Card Frame Systems and Axcent3 systems allow you
to either remove or disable the Axcess Central Controller. If you are using an
Axcent3 / Axcent3 Pro, you can disable the Master with the OpenAxcess program.
You can connect the Axcent3 / Axcent3 Pro to a NetLinx Master Module via AXlink.
Then you can compile and download the existing Axcess code.
DEFINE_CONSTANT
Axcess Language NetLinx Language
Axcess defines constants as either a fixed integer NetLinx processes constants just like Axcess. NetLinx
value between 0 and 65,535 or an array with a max- also allows you to define an expression in the
imum length of 255 bytes in which each element can DEFINE_CONSTANT section. The expression cannot
hold a value from 0 to 255. These values can be contain any variables.
expressed in ASCII, Decimal, or Hexadecimal. DEFINE_CONSTANT
DEFINE_CONSTANT VALUE_MIN = 40
VALUE_MAX = 140 DEFAULT_NAME = 'Axcess'
DEFAULT_NAME = 'Axcess' ETX [] = {$FE,$FF}
ETX = "$FE,$FF" VALUE_MAX = VALUE_MIN + 100
VALUE_MAX = VALUE_MIN + 100
DEFINE_VARIABLES
Axcess Language NetLinx Language
Axcess supports 5 types of variables: NetLinx substantially increased the number of supported
• Integer Variables (default) can contain a value variable types. In addition to more data types, NetLinx
from 0 to 65,535. also supports Sets, Structures, and Multi-dimensional
arrays.
• Character Arrays are single element arrays, in
which each element has a value from 0 to 255 with Arrays default to Character Arrays. Variables default to
a maximum of 255 elements Integer Variables. Variables default to Non-Volatile, but
can be set as Non-Volatile or Volatile (Volatile variables
• 2-Dimensional Arrays equate to a maximum of are initialized when code is loaded or when the system
255 single element character arrays. Each
is reset).
element can have a value from 0 to 255.
DEFINE_VARIABLE
• Integer Arrays are single element arrays, in which
CHAR VALUE1
each element can contain a value from 0 to 65,535
WIDECHAR BIGCHAR
with a maximum of 255 elements
INTEGER VALUE2
• 2-Dimensional Integer Arrays may have a
SINTEGER SIGNED1
maximum value of 65,535.
LONG BIGVALUE
Variables are Non-Volatile (the variable loses its
SLONG SIGNED2
value when the program is loaded, but retains its
FLOAT DECIMAL
value if the controller is reset).
DOUBLE VERYBIGVALUE
DEFINE_VARIABLE
INTEGER ARRAY[3][3][3]
VALUE
VOLATILE INTEGER RESET_VAR
ARRAY[3]
ARRAY_2DIM[4][6]
INTEGER INT_ARRAY[6]
DEFINE_CALL (Subroutines)
Axcess Language NetLinx Language
Axcess provides two methods for incorporating sub- Like Axcess, NetLinx supports DEFINE_CALL and
routines into your program. SYSTEM_CALL. NetLinx also supports functions, which
• DEFINE_CALL subroutines are defined in the are similar to a DEFINE_CALL(s). They can be used
program and support parameter passing into the standalone or in-line as an expression.
call. Changing the parameter value inside the call Functions are defined in the DEFINE_CALL section of
changes the value of the variable passed to the the code as a global function.
parameter. The DEFINE_CALL can use global Defining a function differs slightly from a DEFINE_CALL:
variables or defined local variables.
• The data type of the function's return value must be
DEFINE_CALL is for standalone statements and specified.
cannot be used in-line as an expression.
• The function name is not enclosed with quotes or case
• SYSTEM_CALL is an externally defined subroutine sensitive.
with a '.LIB' extension. SYSTEM_CALL programs
DEFINE_CALL 'SWITCH' (CARD,IN,OUT)
are produced by AMX and are available on
CD-ROM and on the Tech Support Web site at {
www.amx.com. SEND_STRING CARD,
"ITOA(IN),'*',ITOA(OUT),'!'"
DEFINE_CALL 'SWITCH' (CARD,IN,OUT)
}
{
SEND_STRING CARD, DEFINE_FUNCTION INTEGER MULTIPLY
"ITOA(IN),'*',ITOA(OUT),'!'" (INTEGER X, INTEGER Y)
} {
DEFINE_CALL 'MULTIPLY' (X,Y,RESULT) RETURN (X * Y)
{ }
RESULT = X * Y DEFINE_PROGRAM
} PUSH[TP,11]
DEFINE_PROGRAM {
PUSH[TP,11] CALL 'SWITCH' (SWITCHER,4,1)
{ }
CALL 'SWITCH' (SWITCHER,4,1) PUSH[TP,12]
} {
PUSH[TP,12] VALUE = MULTIPLY(3, 4)
{ }
CALL 'MULTIPLY' (3,4,VALUE) SYSTEM_CALL [1] 'VCR1'
} (VCR,TP,21,22,23,24,25,26,27,28,0)
SYSTEM_CALL [1] 'VCR1'
(VCR,TP,21,22,23,24,25,26,27,28,0)
DEFINE_START
Axcess Language NetLinx Language
DEFINE_START sets the initialization parameters for There is no difference between the way Axcess and
the Axcess program. This section defines buffers, NetLinx handle the DEFINE_START section of the pro-
levels, sets communication settings, and initializes gram; however, the role of the DEFINE_START section
variables. is greatly reduced. Variable initializations are handled in
DEFINE_START is run once when the program is the DEFINE_VARIABLE section. Device initializations
loaded or the system is reset. are handled with a DATA_EVENT in the DEFINE_EVENT
section.
DEFINE_START
DEFINE_START
CREATE_BUFFER TP, TP_BUFFER
CREATE_LEVEL VOL, 1, VOL_LEVEL1 ON[CLEAR_TO_SEND]
SEND_COMMAND SWT,
'SET BAUD 9600,N,8,1,DISABLE'
ON[CLEAR_TO_SEND]
DEFINE_EVENT
Axcess Language NetLinx Language
Axcess does not support events. Events are a new process in NetLinx. The events thread
runs parallel to the mainline thread. Events describe cer-
tain types of conditions within the control system. If the
conditions are defined as a DEFINE_EVENT, the event
code is run and mainline is bypassed.
There are five different types of events: Button Events,
Channel Events, Data Events, Level Events, and Time-
line Events.
DEFINE_EVENT
BUTTON_EVENT[TP,21]
(* KC REPEAT 'A' *)
{
PUSH:
{SEND_STRING KC, 'A'
}
RELEASE:
{
}
HOLD[5,REPEAT]:
{
SEND_STRING KC, 'A'
}
}
DEFINE_PROGRAM
Axcess Language NetLinx Language
The DEFINE_PROGRAM or mainline section of the The DEFINE_PROGRAM or mainline section of the
Axcess program is where most of the programming NetLinx program and the DEFINE_EVENTS section of
process takes place. Axcess supports 99 reserved code are responsible for processing events in a NetLinx
identifiers or keywords. 83 of these keywords can be system. NetLinx has expanded the list of keywords to
used in the mainline. 194 reserved identifiers. NetLinx also supports loops,
Axcess runs through a loop where: data conversions, string processing, and file handling.
• The AXlink bus is queried for any changes. NetLinx handles mainline in a similar fashion to Axcess,
with a couple of differences. Because NetLinx supports
• Mainline code is run.
multiple bus formats (AXlink, ICSNet, and Ethernet),
• Axcess checks the wait stack and the pulse stacks events and changes in bus status are handled through a
for any expired waits and pulses. connection manager and message queue. NetLinx
• The process is repeated. checks the message queue to see if an event is defined
for the message. If not, NetLinx makes a pass through
mainline. When NetLinx finishes the event handler or
mainline, NetLinx processes the Wait list and Pulse list,
and returns to the message queue to start the process
again.
Operators
NetLinx added several operators to the language consistent with C++ programming. In conditional
statements (True or False statements), the double equal signs (==) can be used to evaluate whether two
statements are equal. The double equal signs perform the same function as a single equal sign.
There are two Bitwise operators:
Shift Left shifts the bits of a value to the left n binary positions or effectively multiplies the
value by 2n, where n is the number of places to shift. Shift Left is designated by a double less-
than sign (<<) or the LSHIFT keyword.
Shift Right shifts the bits of a value to the right n binary positions or effectively divides the
value by 2n, where n is the number of places to shift. Shift Right is designated by a double
greater-than sign (>>)or the RSHIFT keyword.
An example of both is shown below.
X = 1
Y = 8
X = X << 2 (* X is now equal to 4 *)
Z = Y >> 3 (* Z is now equal to 1 *)
NetLinx also includes value increment and decrement operators. These operators with variables as
statements work just like an Assignment operator or the equal sign does. The Increment-by-One operator
or double plus sign (++) increments the value of its variable by one. The
Decrement-by-One operator or double minus sign (--) decrements the value of its variable by one.
An example of value increment and decrement operators is shown below.
X = 1
Y = 5
X++ (* X is now equal to 2 *)
Y-- (* Y is now equal to 4 *)X = Y++(* This is not a legal statement *)
Axcess/NetLinx Incompatibility
According to previous versions of each of their language reference manuals, Axcess and NetLinx each
give the operator NOT highest precedence while giving AND and OR lowest. As demonstrated in the
following code, however, the two systems behave differently. In reality, Axcess gives the operator NOT
lowest precedence.
DEFINE_VARIABLE
C D E
DEFINE_CALL 'GO' (A,B)
{
C = !A && B
D = B && !A
E = !B && !A
}
DEFINE_PROGRAM
PUSH[1,1]
CALL 'GO' (0,0)
PUSH[1,2]
CALL 'GO' (1,0)
PUSH[1,3]
CALL 'GO' (0,1)
PUSH[1,4]
CALL 'GO' (1,1)
Axcess RESULTS
A B !A && B B && !A !B && !A
0 0 1 0 1
1 0 1 0 1
0 1 1 1 0
1 1 0 0 1
NETLINX RESULTS
A B !A && B B && !A !B && !A
0 0 0 0 1
1 0 0 0 0
0 1 1 1 0
1 1 0 0 0
The problem applies whether A and B are channels, variables, or expressions, and for OR as well as AND.
To solve the problem, AMX always recommends the use of (!A) && B instead of !A && B; however,
and this is critical, some programs out there are taking advantage of the logic flaw. Where the Axcess
programmer intended the truth table of !(A && B) he/she may have coded !A && B and gotten the
desired result. If these systems are converted to NetLinx Masters, the logic will not work as desired.
Please be aware of this difference as you support programs being converted from Axcess to NetLinx.
When it occurs, Axcess-like operation can generally be achieved by including all the conditions to the
right of the NOT in a single set of parentheses. For example:
IF (SYSTEM_POWER && ![VCR,PLAY] || [VCR,RECORD])
becomes:
IF (SYSTEM_POWER && !([VCR,PLAY] || [VCR,RECORD]))
Data Types
NetLinx expanded the types of data handled beyond the 8-bit and 16-bit integers handled by Axcess.
NetLinx supports integers up to 32-bits and signed values to allow positive and negative values. The
following table lists the data types available to NetLinx.
Data Types Supported by NetLinx
Type Used to Store Data Ranges Sample of Stored Values
Names
CHAR Single byte values and character 0 to 255 (8-bit) 'a', 145, $FE, 'The quick gray fox'
strings
WIDECHAR Wide character strings dealing with 0 to 65,535 (16-bit) "'OFF',500"
Unicode fonts that use 16-bit character
codes (and most Far-eastern fonts)
INTEGER Default variable value to store values 0 to 65,535 (16-bit) 512, 32468, 12
up to 65,535
SINTEGER Signed integer values both greater 32,767 to 32,767 24, -24, 568, -568
than and less than zero (16-bit)
FLOAT Small real numbers with 5 digits of 10e-38 to 10e38 1.2345
precision 123.451.2345e5
-16.323.1415
DOUBLE Large real numbers with 15 digits of 10e-308 to 10e308 1.23456789012345
precision 12,345,678.9012545
3.14159265358979
-0.048512934
LONG Stores large integer values esp. 0 to 4,294,967,295 1,000,000
greater than 65,535 (32-bit) 2,000,046
SLONG Signed large integer values less than - -2,147,483,647 to -1,000,000
32,767 and greater than 32,767 2,147,483,647 1,000,000-2,000,000
(32-bit)
2,000,000
Constants
The DEFINE_CONSTANT section in NetLinx is similar to the DEFINE_CONSTANTS section in Axcess.
The scope of the constant extends throughout the module in which it is defined. If the
DEFINE_CONSTANT section appears in the main program or in an include file, the constant's scope
extends globally throughout the program. DEFINE_CONSTANT accepts data in these formats:
DEFINE_CONSTANT Data Formats
Types Formats Examples
Decimal Integer 0000 1500
Hexadecimal Integer $000 $DE60
Binary Integer 000b 01110011b
Floating Point 000.0 924.5
Exponential Notation 0.0e0 .5e-12
Character 'c' or <char code> 'R' or 255
String Literal 'ssss’ 'Reverse'
NetLinx allows variables to be defined as constants in the DEFINE_VARIABLE section of the program
or module, and in the LOCAL_VAR section of a DEFINE_CALL or a DEFINE_FUNCTION. Assigning
constants is consistent with C++ programming conventions.
Variables
The role of the DEFINE_VARIABLE section is enhanced for NetLinx. The structure of a variable
definition is:
[NON_VOLATILE|VOLATILE][CONSTANT][<type>]<variable name> [= <value>]
NetLinx handles variables just like Axcess. NetLinx defaults non-array variables to the integer data types
and defaults array variables to character data type array. The variable must be explicitly declared if using
any other data type.
The NON_VOLATILE and VOLATILE keywords specify what happens to a variable when the program is
downloaded or after a system reset.
NON_VOLATILE variables (the default) lose their values when the program is downloaded, but
retain their values when the system resets.
VOLATILE variables lose their values when the system is loaded and after the system resets.
If you initialize a VOLATILE variable in the DEFINE_VARIABLE section, the variable initializes every
time the code is loaded or after a system reset. The variable initializes like it would in the
DEFINE_START section. If you initialize a NON_VOLATILE variable within the DEFINE_VARIABLE
section, the variable only initializes when the system is loaded, and it retains any changed values after
system resets.
Variables can now be defined as constant variables. Since the DEFINE_CONSTANT section does not
allow you to explicitly declare a constant's data type, using the CONSTANT keyword allows you to
explicitly declare the data type of a constant, and to define constant values for structures and arrays of
structures.
CONSTANT STR_TV CHAN_5 = {'KXAS', 5}
CONSTANT SINTEGER ABS_ZERO = -273
With Axcess, the DEFINE_CALL section allowed you to define local variables with the LOCAL_VAR
keyword. NetLinx expands the scope of LOCAL_VAR beyond the DEFINE_CALL section of code. Local
variables now come in two flavors:
LOCAL_VAR now defines a static (fixed) local variable (the next time a DEFINE_CALL is
called, the last value of the LOCAL_VAR will be in memory unless the variable is initialized).
This is how Axcess handles variables defined with LOCAL_VAR. NetLinx does not limit
LOCAL_VAR definitions strictly to the DEFINE_CALL section. LOCAL_VAR definitions can
appear within any statement block. This includes (but is not limited to) DEFINE_FUNCTION,
DEFINE_EVENT, WHILE statements, WAIT statements, etc.
STACK_VAR defines a non-static local variable. STACK_VAR defines local variables the same
way as LOCAL_VAR, and like LOCAL_VAR, STACK_VAR can appear in any statement block.
The difference is that the value stored in the variable is initialized to zero whenever the
statement block is called, and the value is destroyed when the statement block is finished. The
structure for LOCAL_VAR and STACK_VAR variables include:
LOCAL_VAR [NON_VOLATILE | VOLATILE] [CONSTANT] [<type>] name [= <value>]STACK_VAR
[<type>] name [= <value>]
Persistent Variables
Persistent variables have been implemented in the second release of NetLinx. Persistent variables are
NetLinx program variables that maintain their value between updates to the NetLinx program. The user
can define a variable to be persistent using the PERSISTENT storage modifier as show below:
PERSISTENT CHAR cMyString[100]
All persistent variables are automatically non-volatile. It is not legal to define a variable as VOLATILE
and PERSISTENT.
When a NetLinx program has a persistent variable declared, subsequent downloads of new NetLinx
programs containing the same persistent variable will retain the variable settings. By default, non-
persistent variables are set to zero after a NetLinx program download. Persistence overrides this behavior
by setting the variable in the newly downloaded program to be the same as it was before the download.
Typically, persistent variables are used for saving preset information. Suppose you have a system that
contains several PosiTrack camera positioning systems, and that the user interface to the system allows
the user to set the position of any of the cameras and record that position for recalling later. The position
presets are stored in a non-volatile array variable so they are maintained during a power cycle. Without
persistent variables, an update to the NetLinx program would zero out all of the presets the user had
stored. With persistent variables, the new NetLinx program can be downloaded and all of the presets
remain intact.
When a new NetLinx program is downloaded to the Master, the Master iterates through all non-volatile
variables from the new program looking for persistent ones. When it finds a persistent variable in the
new program, it searches the old programs persistent variable space for the same variable. When it finds
the same variable, the value of the new variable is set to the same value as the old variable. The Master
identifies the same variable by verifying the following:
Variable name
Variable source location
Variable type
Therefore, in order for persistence to function properly the name, type, and file location declared must be
the same as the previously downloaded NetLinx program. If you changed any of the three, the new
persistent variable will not be set with the old variable's value.
Arrays
Arrays are the most common way of combining a number of data items into a single unit. Axcess uses
three methods to store data in arrays:
8-bit single dimensional arrays
16-bit single dimensional arrays
8-bit two-dimensional arrays
Axcess arrays are limited to storing 255 elements per dimension. Axcess does not allow you to store
two-dimensional arrays as constants; instead, you set and initialize a two-dimensional array in the
DEFINE_START section. You are responsible for maintaining the integrity of the initialized value.
NetLinx enhances the handling of arrays. You can define arrays of any data type in single and multi-
dimensional arrays. You can define arrays of structures, initialize arrays within the DEFINE_VARIABLE
section, and define arrays as constants.
NetLinx handles arrays similar to C++, except that the first index value of the array is 1 rather than an
index of 0 used by C++. With array initialization you don't need to count how many items are initialized.
These definitions are functionally the same:
Multi-dimensional arrays allow multiple collections of data. NetLinx allows up to five array dimensions;
array size is limited only by available memory. A two-dimensional array is a collection of single
dimensional arrays. Three-dimensional arrays are collections of two-dimensional arrays. Here are
examples of multi-dimensional arrays:
INTEGER NUM1D[10] (* [COLUMN] *)
INTEGER NUM2D[5][10] (* [ROW][COLUMN] *)
INTEGER NUM3D[2][5][10] (* [TABLE][ROW][COLUMN] *)
CHAR USER_PRESET[10][10][16] allows you to define tables that can store ten 16-character preset
names for ten users. With Axcess, you would either store ten two-dimensional arrays or index one two-
dimensional array (USER_PRESET[100][16]). For example, the fifth user would occupy
USER_PRESET[41] through USER_PRESET[50].
It is sometimes difficult for people to envision multi-dimensional arrays beyond three-dimensions. We
sometimes try to define the arrays spatially, as in a three-dimensional array. If we take the approach of
cascading arrays, it is easier to understand. Using the previous example of defining user presets, you can
expand the array to five dimensions by classifying the preset name by location and department. For
example: AMX has three domestic locations; each location has a sales team, a professional services team
and a tech support team; each team has a maximum of ten employees; each employee has the ability to
store 10 preset names; each preset name can have up to 16 characters. The array would look like this:
CHAR USER_PRESET[3][3][10][10][16]
(*[LOCATION][DEPT][USER][PRESET][NAME]*)
NetLinx has a new set of functions to better deal with arrays. LENGTH_ARRAY and
MAX_LENGTH_ARRAY determine the effective length and defined length of an array. When used with
multi-dimensional arrays, LENGTH_ARRAY and MAX_LENGTH_ARRAY return the lengths associated
with the level of the array supplied as the argument. For example:
INTEGER NUM_LIST [10] = {1, 2, 3, 4, 5}
LEN = MAX_LENGTH_ARRAY (NUM_LIST) (* LEN = 10 *)
LEN = LENGTH_ARRAY (NUM_LIST) (* LEN = 5 *)
INTEGER NEW_LIST[] = {10, 20, 30, 40}
LEN = MAX_LENGTH_ARRAY (NEW_LIST) (* LEN = 4 *)
LEN = LENGTH_ARRAY (NEW_LIST) (* LEN = 4 *)
INTEGER MULTI_LIST[4][10] = { {1, 2, 3}, {4, 5, 6, 7}, {8, 9} }
LEN = MAX_LENGTH_ARRAY (MULTI_LIST[2]) (* LEN = 10 *)
LEN = LENGTH_ARRAY (MULTI_LIST[2]) (* LEN = 4 *)
LEN = MAX_LENGTH_ARRAY (MULTI_LIST) (* LEN = 4 *)
LEN = LENGTH_ARRAY (MULTI_LIST) (* LEN = 3 *)
NetLinx expands the capabilities of the assignment operator '=' to support arrays. Similar array levels are
assigned to another array using the '=' operator, if the arrays match the number of dimensions and the
data type of the array. You cannot assign a two-dimensional long array to a one-dimensional character
array. The MAX_LENGTH_ARRAY of the array to the left of the '=' operator must be greater than or equal
to the LENGTH_ARRAY of the array to the right of the '=' operator.
INTEGER ARRAY1[10] = {1, 2, 3, 4}
INTEGER ARRAY2[10] = {5, 6, 7}
INTEGER ARRAY3[10]
INTEGER DIM2ARRAY1[3][4] = { {1, 2, 3}, {4, 5, 6} }
INTEGER DIM2ARRAY2[3][4] = { {7, 8, 9} }
INTEGER DIM2ARRAY3[3][4]
ARRAY3 = ARRAY1 (* ARRAY3 = {1, 2, 3, 4} *)
DIM2ARRAY2[2] = ARRAY1 (* DIM2ARRAY2 = { {7, 8, 9}, {1, 2, 3, 4} } *)
DIM2ARRAY3 = DIM2ARRAY1 (* DIM2ARRAY3 = { {1, 2, 3}, {4, 5, 6} } *)
Structures
Arrays are limited by their inability to have multiple data-types within one array. NetLinx supports
Structures to remove this limitation. Structures group different data types together as one data unit.
Structures also group arrays of structures together so that each element of the array contains all of the
elements of the structure. This may sound complex, but it is actually very familiar.
A database table is an array of structures. The database table is an array of records. Each record is a
structure. Each record contains data of different types. Let's first consider the elements of a database
table. We then show how to define the structure and create a variable that uses the data structure in an
array. We show how to access the individual elements of the structure.
Employee Number (* INDEX - Integer Value *)
Employee National Insurance Number (* National Insurance Number - Long *)
Employee First Name (* First Name - Character Array *)
Employee Last Name (* Last Name - Character Array *)
Contribution to Pension (* Contribution in % - Float *)
The DEFINE_TYPE section is added to the basic structure of a NetLinx Program. Structures are defined
within the DEFINE_TYPE section. The DEFINE_TYPE section appears between the
DEFINE_CONSTANT section and the DEFINE_VARIABLE section. Since structures cannot be used
within the DEFINE_CONSTANT section but must be declared before they are used within the
DEFINE_VARIABLE section, placing DEFINE_TYPE between DEFINE_CONSTANT and
DEFINE_VARIABLE is the logical location.
The attributes NON_VOLATILE, VOLATILE, and CONSTANT do not apply to the individual data
elements of the structure, but can be attributed to the instances of the structure as defined in the
DEFINE_VARIABLE section.
The standard format for structures is:
STRUCTURE <name>
{
[<type>] <data1>
[<type>] <data2>
.
.
}
Using this format, we define our 'employee' structure in the DEFINE_TYPE section:
DEFINE_TYPE
STRUCTURE EMP
{
INTEGER EMP_NUM
CHAR NI_NUM[9]
CHAR F_NAME[16]
CHAR L_NAME[16]
FLOAT CONT_PENSION
}
Then, within the DEFINE_VARIABLE section, you create an instance of the structure and an array of
the structure as follows:
DEFINE_VARIABLE
EMP JOHN_DOE
EMP AMX_EMP[1000]
Within the program, we use the information stored within the structure and assign information to the
structure in the following manner:
JOHN_DOE.EMP_NUM = 101
JOHN_DOE.NI_NUM = ’155426367’
JOHN_DOE.F_NAME = ’JOHN’
JOHN_DOE.L_NAME = ’DOE’
JOHN_DOE.CONT_PENSION = 0.01
Other uses for arrays of structures include channel listings, speed-dial lists, and user password lists.
Data sets
NetLinx predefines several structures designed to work with NetLinx device numbers, channels, and
levels. Data sets allow you to group and combine certain elements of NetLinx devices. There are three
data set structures supported by NetLinx:
DEV (Device Sets)
DEVCHAN (Device-Channel Sets)
DEVLEV (Device-Level Sets)
You have already seen the structure DEV structure in the DEFINE_DEVICE section. If we were to define
the structure DEV in the DEFINE_TYPE section, it would look like this:
STRUCTURE DEV
{
INTEGER DEVICE
INTEGER PORT
INTEGER SYSTEM
}
The actual instancing of the structure is unique to the DEV structure because you separate the individual
structure's elements with colons (:) instead of enclosing the structure with braces {} and separating the
elements with commas (,). For example:
DEV PANEL_A = 128:1:0 (* correct *)
DEV PANEL_B = {128, 1, 0} (* wrong *)
Using the DEV structure, you create the structures DEVCHAN and DEVLEV like this:
STRUCTURE DEVCHAN
{
DEV DEVICE
INTEGER CHANNEL
}
STRUCTURE DEVLEV
{
DEV DEVICE
INTEGER LEVEL
}
DEVCHAN and DEVLEV instance and initialize similarly to other NetLinx structures:
DEV PANEL_A = 192:1:0
DEV PANEL_B = 129:1:0
DEVCHAN BUTTON_A = { PANEL_A, 1 }
DEVCHAN BUTTON_B = { 128:1:0, 2 }
DEVLEV LEVEL_1 = { PANEL_A, 1 }
DEVLEV LEVEL_2 = { 128:1:0, 2 }
DEV, DEVCHAN, and DEVLEV are structures built into the NetLinx language. You can do more with DEV,
DEVCHAN, and DEVLEV than you could with structures you create within the code.
DEV PANEL_GROUP1[] = { 128:1:0, 129:1:0, 130:1:0 }
DEV MSP_GROUP[5] = { MSP1, MSP2, MSP3 }
DEVCHAN PRESET1_BUTTONS[5] = { {TP1, 21}, {MSP1, 1}, {134:1:0, 1} }
DEVLEV VOL1_LEVEL[] = { {TP1, 1}, {MSP1, 1}, {192:1:0, 1} }
You can use the structures and arrays of the structures within many commands and situations where you
would use a device number, a device and channel combination, or a device and level combination. These
data sets allow you to combine devices, devices and channels, and devices and levels without using the
DEFINE_COMBINE or DEFINE_CONNECT_LEVEL sections. This gives you the ability to combine
certain pages of panels or to combine panels under certain conditions. In Axcess, once the panels were
combined you were locked into that system configuration.
Instead of writing the following statements:
PUSH[MSP1, 1]
PUSH[MSP2, 1]
PUSH[MSP3, 1]
[RELAY, 1] = ![RELAY, 1]
[MSP1, 1] = [RELAY, 1]
[MSP2, 1] = [RELAY, 1]
[MSP3, 1] = [RELAY, 1]
You can use device sets or channel sets to accomplish the same functionality:
- or -
SELECT...ACTIVE statements
Loops:
WHILE statements
MEDIUM_WHILE statements
LONG_WHILE statements
NetLinx supports:
Conditional statements:
IF...ELSE statements
SELECT...ACTIVE statements
SWITCH...CASE statements
Loops:
FOR statements
WHILE statements
LONG_WHILE statements
MEDIUM_WHILE statements are obsolete in NetLinx due to eliminating the timeout of WHILE loops.
LONG_WHILE loops now differ from WHILE loops in the way input change notifications are processed
during the programming loop. WHILE, MEDIUM_WHILE and LONG_WHILE statements are all still
accepted syntax to provide compatibility with existing Axcess programs.
SWITCH...CASE statements
NetLinx adds the SWITCH...CASE conditional statements. The SWITCH...CASE statements provide
selective execution of code blocks evaluated by a single condition. The value of the SWITCH expression
is tested against each CASE value (which must be a numeric constant or a string literal). If a match is
found, the statements associated with the CASE are executed. All other CASE statements are ignored. If
no match is found, the DEFAULT case statements (if any) are executed. The SWITCH expression is
evaluated only once.
The following rules apply to SWITCH...CASE statements:
Only the statements associated with the first case that matches the value of the expression are
executed. Multiple CASE statements can be stacked within the SWITCH...CASE statement. If
the value matches one of the CASE statements, the statements associated with the stack will be
executed.
If no CASE matches the SWITCH expression, then the statements under the default case (if
available) are executed. The default statement must be the last case within the
SWITCH...CASE, otherwise the remaining case statements will not execute.
All cases must be unique.
Braces should be used to bracket the statements in a case. They are required only if variables
are declared within the case.
The BREAK statement applies to the SWITCH and takes execution to the end of the SWITCH.
Unlike C and C++, cases do not fall through to the next case if a break is not used. Because of
this, BREAK statements are not required between cases.
The following is the structure for the SWITCH...CASE statement:
SWITCH (<expression>)
{
CASE <numeric constant or string literal>:
{
(* statements for CASE 1 *)
}
CASE <numeric constant or string literal>:
{
(* statements for CASE 2 *)
}
CASE <numeric constant or string literal>:
{
(* statements for CASE n; there can be as many cases as necessary *)
}
DEFAULT <numeric constant or string literal>:
{
(* statements for DEFAULT case *)
}
}
FOR loops
FOR loops are an alternative to traditional loops. Functionally they do the same thing, but FOR loops are
more readable. FOR loops, like WHILE loops, do not process input changes from the message buffer. The
structure for a FOR loop is shown below:
FOR (<INITIAL>;<condition>;<after pass>)
{
(* loop statements *)
}
Parameters:
<INITIAL> Contains one or more statements that are executed one time before any
FOR loop statements are executed; each statement must be separated by a
comma (,).
<condition> The condition for which the loop is evaluated before each pass. If the condi-
tion evaluates TRUE, the FOR loop statements execute. If the condition eval-
uates FALSE, the loop terminates.
<after pass> Contains one or more statements that are executed after each pass through
the loop statements; each statement is separated by a comma (,). This is
typically a statement that increments the FOR-loop index.
The number of loop executions is usually stated at the beginning of the loop, unlike WHILE and
LONG_WHILE loops.
In Axcess, a typical loop may look something like this:
COUNT = 0
WHILE (COUNT<10)
{
COUNT = COUNT + 1
(* loop statements *)
}
In NetLinx you can write the same loop with a FOR statement and clarify how the loop operates:
FOR (COUNT=0 ; COUNT<10 ; COUNT++)
{
(* loop statements *)
}
By defining the loop like this, you clearly see how it is initialized and incremented. No errors appear if
you forget to initialize the WHILE loop or counter. The FOR loop helps to insure proper structure.
Functions
Axcess only supports one method to create subroutines: DEFINE_CALL. The DEFINE_CALL does not
return values very eloquently. If you pass a variable to a parameter of the DEFINE_CALL and then
change the parameter value within the subroutine, the program updates the value of the global variable in
the mainline code.
NetLinx has two methods for creating subroutines: DEFINE_CALL and DEFINE_FUNCTION.
DEFINE_CALL
DEFINE_CALL is intended to run segments of code that are repeated throughout the program, but don't
require a return value. For example, this DEFINE_CALL creates a macro to lower a screen, turn on the
projector, and set the lights to Preset 1. The subroutine executes three commands and no values are
returned to the program.
DEFINE_CALL 'PRESENTATION MACRO'
{
SYSTEM_CALL [1] 'SCREEN1' (0, 0, 1, 0, SCREEN, 1, 2, 3, 0)
SEND_STRING VPROJ, "'PON',$0D,$0A"
SEND_STRING RADIA, "'1B',$0D"
}
The NetLinx compiler passes all variables by reference. This means that the variable the subroutine
operates on is the same variable the caller passed. Any change made to the variable, passed as a calling
parameter, updates the variable's value from the caller's perspective. You can take advantage of this pass
by reference feature by returning an updated value through a calling parameter rather than as the return
value.
Constants, on the other hand, are passed by value. When this happens, a copy of the parameter is
delivered to the subroutine. Any change made to the variable representing the constant is lost once the
function or subroutine is lost.
To specify an array as a function or subroutine parameter, one set of brackets for each array dimension
must follow the variable name, as shown in the following example:
DEFINE_CALL 'READ INPUT' (CHAR BUFFER[][])
{
(* body of the subroutine *)
}
The parameter BUFFER is declared to be a two-dimensional array by including two sets of brackets after
the name. For compatibility with existing programs, the array dimensions may be specified inside the
brackets. These dimensions, however, are not required and are ignored by the compiler. The NetLinx
Interpreter will do bounds checking on the array and generate a run-time error if the array bounds are
exceeded.
DEFINE_FUNCTION
DEFINE_FUNCTION provides a way to return a value to a statement. It has the same functionality as a
DEFINE_CALL. The DEFINE_FUNCTION is used inline in a statement, where a DEFINE_CALL must be
used as a standalone statement. The basic structure is:
DEFINE_FUNCTION [<return type>]<name>[(<param1>,<param2>, … <parameN>)]
{
(* statements *)
}
The following DEFINE_FUNCTION creates a subroutine to cube a number and returns a LONG integer
value:
DEFINE_FUNCTION LONG CUBEIT (LONG VALUE)
{
STACK_VAR RESULT
RESULT = VALUE * VALUE * VALUE
RETURN RESULT
}
DEFINE_PROGRAM
PUSH[TP1, 1]
{
CUBED_VAL = CUBEIT ( 3 )
(* CUBED_VAL = 27 *)
}
Events
Axcess is a linear environment. All interactions between external devices and the master processor are
handled within mainline code. The processor runs mainline code, services the wait and pulse queues, and
checks the bus for any changes in device status. We view these interactions or changes in status as
Events, which fall into one of four categories: Button Events, Channel Events, Data Events, and Level
Events.
NetLinx has a special program section called DEFINE_EVENT to handle incoming events. The event
processing that previously could only occur within mainline code can now be handled in the
DEFINE_EVENT section.
NetLinx maintains a table of defined event handlers. When a new event comes into the NetLinx
processing queue, the event is compared against the table of events. If the event is found, only the code in
the event definition is evaluated and executed; mainline is bypassed. If an event handler is not defined,
mainline is run and the event is evaluated against the mainline code. This provides a more efficient
mechanism for processing events, since mainline is not required to process a single
I/O request. If no events are pending, mainline is run. Mainline becomes an idle time process.
With the addition of the DEFINE_EVENT section for processing events, the mainline's role in NetLinx
becomes greatly diminished, if not totally eliminated. Programs can still be written using the traditional
technique of processing events and providing feedback in mainline code; however, programs written
using the event table structure will run faster and be much easier to maintain.
Button Events
Events associated with a button on a touch panel or an AXD-MSP32 will fall into one of three
categories:
What happens when the button is pushed.
What happens when the button is released.
What happens if the button is held.
The structure for Button Events is as follows:
BUTTON_EVENT [<device>,<channel>]
{
PUSH:
{
(* push event handler code *)
}
RELEASE:
{
(* release event handler code *)
}
HOLD [<time>,[REPEAT]]
{
(* hold event handler code *)
}
}
The [<device>, <channel>] declaration can contain a DEV device set, or a DEVCHAN device-
channel set in addition to individual device and channel declarations. The HOLD event specifies the
actions to be performed when a button is pressed and held for a minimum length of time indicated by the
<time> parameter, which is specified in tenth seconds.
The following is an example of how a block of existing Axcess code can be rewritten using the NetLinx
BUTTON_EVENT handler. The code below will send an 'A' to an RS-232 port defined as KC1 upon a
button push and will repeat the 'A' string every 0.5 seconds until the button is released.
Axcess Language NetLinx Language
DEFINE_PROGRAM DEFINE_EVENT
. .
. .
PUSH[TP1,10] BUTTON_EVENT[TP1,10]
{ {
SEND_STRING KC1, 'A' PUSH:
ON[REPEAT_KC] {
} TO[TP1,10]
RELEASE[TP1,10] SEND_STRING KC1, 'A'
{ }
CANCEL_WAIT 'REPEAT KC' RELEASE:
OFF[REPEAT_KC] {
} }
IF (REPEAT_KC) HOLD[5,REPEAT]:
{ {
WAIT 5 'REPEAT KC' SEND_STRING KC1, 'A'
SEND_STRING KC1, 'A' }
} }
[TP1,10] = REPEAT_KC .
. .
. DEFINE_PROGRAM
.
.
In addition to evaluating the push within the event handler structure, you can see the simplified logic for
creating the repeating 'A' string using the HOLD event handler.
Channel Events
Channel Events are similar to Button Events. Channel Events are generated by ON, OFF, PULSE, TO, or
MIN_TO. The format for a Channel Event is shown below:
CHANNEL_EVENT[<device>,<channel>]
{
ON:
{
(* on event handler code *)
}
OFF:
{
(* off event handler code *)
}
}
Like Button Events, the [<device>, <channel>] declaration can contain a DEV device set, or a
DEVCHAN device-channel set in addition to individual device and channel declarations.
In the following example, a Channel Event is defined to turn off a video projector every time the
projector lift is raised. In Axcess, you need to include the code to turn off the projector whenever the
projector lift is raised. In NetLinx, you define a Channel Event for the 'Projector Lift Up' relay and tell
the system to turn off the projector every time this relay is turned on. Since turning on or pulsing the
relay does not produce a push, a Button Event is not generated.
Data Events
Data Events provide some interesting capabilities in a NetLinx system. At first glance, it seems to be
concerned with receiving strings of data either from a serial data device such as an NXC-COM2 card or
an interface device such as a touch panel or WebLinx. While this is a valid function, DATA_EVENT has
many more capabilities and works with many devices. The structure for a DATA_EVENT is:
DATA_EVENT [<device>]
{
COMMAND:
{
(* command data event handler *)
}
STRING:
{
(* string data event handler *)
}
ONLINE:
{
(* online data event handler *)
}
OFFLINE:
{
(* offline data event handler *)
}
ONERROR:
{
(* error data event handler *)
}
}
In Axcess, strings are handled in mainline code. Between each pass through mainline, the data received
by a device is placed within a created buffer. The next pass through mainline allows the Axcess program
to evaluate the string. This has two limitations:
First, Axcess must evaluate the contents of the buffer with each pass through mainline,
whether there is data in the buffer or not. This adds to the length of mainline and slows
mainline.
Second, data is only received into the buffer between passes through mainline. In large
systems, data processing is delayed, and some buffers may be overrun and some data may be
lost.
Because the role of mainline is diminished in NetLinx and events can be processed quickly, NetLinx is
able to process data received by a DATA_EVENT in real time. When data is received, it enters the
message queue and triggers a data event. If a buffer has been created for the device, the data is placed
within the buffer and can be used by either the DATA_EVENT or mainline.
The data can be evaluated in two ways. The actual string that is received by the message queue can be
evaluated using the DATA.TEXT object within the event. The string in DATA.TEXT is also added to the
end of the device's buffer. This becomes a factor when receiving large strings, or when receiving strings
with an embedded string length or start and end characters. DATA_EVENT then evaluates the buffer to see
if the entire string has been received before processing it; however, the evaluation is done immediately
upon receipt of another chunk of data, and is only done when data is received. For example, DATA.TEXT
may equal {'over the lazy brown dog',ETX} and the DATA_BUFFER[500] might equal
{STX,'The quick gray fox jumps over the lazy brown dog',ETX}. By evaluating the
buffer value, you can evaluate the entire string at once.
Two other important aspects of the DATA_EVENT are the ONLINE and OFFLINE event handlers.
ONLINE and OFFLINE events are triggered when the master recognizes a device has come on the bus or
has dropped off the bus.
In Axcess, device initialization is primarily handled with the DEFINE_START section of code. The other
alternative was to evaluate the DEVICE_ID on each pass through mainline. If the
DEVICE_ID(<device>) equaled the DEVICE_ID of the device, the device was online. If
DEVICE_ID(<device>) equaled 0, the device was offline. Within the conditional statements, the
device could be initialized or a warning could be sent. The downfall of these approaches is that
DEFINE_START initializations are only run when the master is reset, and evaluations of the DEVICE_ID
must run with each pass of mainline and are dependent on the speed of mainline.
NetLinx handles all device initializations and offline warning through the DATA_EVENT. Since every
device triggers an ONLINE event when the master is reset, this not only ensures that the device will be
initialized on startup, but also insures that the device will be initialized any time the device comes online.
The DATA_EVENT is also evaluated on a need to know basis, rather than on each pass through mainline.
The following example shows basic code for tracking a touch panel page in Axcess. Assume that the
variables have been properly defined in the DEFINE_VARIABLE section. The DEFINE_START section
contains the creation of the buffer and the DEFINE_PROGRAM section contains the string evaluation.
Existing Axcess code:
DEFINE_START
.
.
CREATE_BUFFER TP1, TP1_BUFFER
SEND_COMMAND TP1, 'TPAGEON'
.
.
DEFINE_PROGRAM
.
.
IF (LENGTH_STRING (TP1_BUFFER))
{
SELECT
{
ACTIVE (FIND_STRING (TP1_BUFFER,'PAGE-',1)):
{
JUNK = REMOVE_STRING (TP1_BUFFER,'PAGE-',1)
CUR_PAGE = TP1_BUFFER
}
ACTIVE (FIND_STRING (TP1_BUFFER,'KEYP-',1)):
{
(* keypad code *)
}
ACTIVE (FIND_STRING (TP1_BUFFER,'KEYB-',1)):
{
(* keyboard code *)
Continued
}
ACTIVE (1):
{
(* keypad code *)
}
}
}
.
.
Continued
.
.
Each event handler contains several imbedded data objects that pass data values into the event handler
code.
Level Events
Level Events are triggered by a level change on a particular device. This eliminates constantly evaluating
a level against a previous value. In Axcess, a level would need to be created in the DEFINE_START
section and a conditional statement would appear in mainline to evaluate and update the level. The
format for the LEVEL_EVENT is:
LEVEL_EVENT[<device>,<level>]
{
(* level event handler *)
}
OFF[RELAY,FAN]
}
Devices combined with COMBINE_DEVICES respond like devices combined using the
DEFINE_COMBINE section. The central controller recognizes any input from the devices in the combine
list as the first device in the list.
Combining and uncombining levels
The NetLinx functions COMBINE_LEVELS and UNCOMBINE_LEVELS work similar to the
DEFINE_CONNECT_LEVEL section in Axcess. For compatibility with Axcess code, the
DEFINE_CONNECT_LEVEL section is still valid. Like COMBINE_DEVICES, COMBINE_LEVELS and
UNCOMBINE_LEVELS can be used within events and mainline code to dynamically change what levels
are connected to each other. It is also recommended that a Virtual DEVLEV set be used as the first
DEVLEV set in the COMBINE_LEVELS function. The format for COMBINE_LEVELS and
UNCOMBINE_LEVELS is:
DEVLEV structures defined within the COMBINE_LEVELS are either individual DEVLEV structures or
one dimension of a DEVLEV array. Any reference to the levels is handled through the first device in the
list.
Combining and uncombining channels
Combining DEVCHANs is unique to NetLinx. The NetLinx function COMBINE_CHANNELS combines an
individual channel on a virtual device to one or more channels on another device (or devices). The
format for COMBINE_CHANNELS and UNCOMBINE_CHANNELS is:
COMBINE_CHANNELS (<virtual DEVCHAN>, <DEVCHAN1[]>, <DEVCHAN2[]>…)
UNCOMBINE_CHANNELS (<virtual DEVCHAN>)
String Comparisons
While in Axcess it is possible to perform a string comparison using the '?' wildcard, Netlinx requires the
COMPARE_STRING function to be used instead.
Modules
There are two ways to reuse code in different Axcess programs: Include Files and System Calls.
Include files redirect the compiler to files with an .AXI extension. The .AXI files can contain
the same type of information present within an Axcess program. All data is accessible both
within the Include file and within the primary Axcess program. Include files are limited
because they are static. Mainline statements within the Include file cannot be adapted from
program to program without altering the Include file. To update the Include files in a program,
the entire program must be compiled and loaded.
System calls are external subroutines that can be instanced and referenced in the main
program. Like DEFINE_CALL subroutines, System Calls can pass parameters to adapt the
System Call to the needs of different programs. System Calls have been one of the primary
tools for creating standardized reusable blocks of code. To update the System Calls within a
program, the entire program must be compiled and loaded.
Modules are unique to NetLinx. Like Include files, the code within the Module is not limited to the
DEFINE_CALL section. Modules can contain variable definitions, functions, subroutines, startup code,
events, and mainline. Modules are passed parameters that are used to adapt the information and variables
used within the Module (similar to System calls).
Modules are similar to programs loaded into AXB-232++ boxes. They operate as stand-alone programs
inside the NetLinx program. Interaction between the Module and the NetLinx Program is done through
User Interface (UI) pushes and releases, turning virtual device channels on and off, and passing variables
and arrays to the Module. The code in the Module is local, or is restricted to use only within the Module.
This means that functions and subroutines defined with Module cannot be directly used with the main
NetLinx code.
Modules will eventually replace System calls. Where several system calls are currently needed to
provide device initialization, buffer processing, and device functionality, one module will handle all
three functions.
The first line of a Module contains the MODULE_NAME keyword, the Module name, and the parameter
list. The format is shown below:
MODULE_NAME = '<module name>' [(<param1>, <param2>, … , <paramN>)]
The <module name> must match the file name, but has the .AXS extension. The module name can be
64 characters long and contain valid file name characters. The parameter name is optional and follows
the same restrictions as subroutine parameters, with the exception that constants and expressions cannot
be used as arguments.
Within the NetLinx program, the Module is referenced using the following format:
DEFINE_MODULE '<module name>' <instance name> [(<pass1>, <pass2>, … , <passN>)]
The <module name> must match the module name specified in the Module file, as shown above. The
<instance name> is a unique name given to each occurrence of the module within the program. If
the module is used twice within the program, each occurrence gets a unique instance name. The
parameter list passed to the module must match number and types of parameters listed in the module file
above. The DEFINE_MODULE statements are listed in the code after the DEFINE_CALL and
DEFINE_FUNCTION sections, but before the DEFINE_START section.
The DEFINE_MODULE statements cannot appear within the DEFINE_PROGRAM or DEFINE_EVENTS
section.
In order to use a module, the module must be compiled with the Source Code, and
the Master must be rebooted to run the new module.
Language Elements
Statements and Expressions
A statement refers to a complete programming instructions such as:
Each of these statements compile, providing the referenced variables are defined.
Expressions are sub-components of statements. The following expressions are used in the above
example:
X + 1 (* Arithmetic Expression *)
Y < 10 (* Logical Expression *)
Y + 1 (* Arithmetic Expression *)
[TP, 5] (* I/O Device Expression *)
[VCR, 1] (* I/O Device Expression *)
Assignments
Assignment statements include:
Variables
Output Channels
Variables
The simplest type of assignment statement is a variable, which assigns the value of an expression to a
variable. The expression may be a constant, a variable / mathematical / logical expression, or a return
from function. The data type associated with the expression should match the data type of the variable
receiving the assignment. If not, the value of the expression is typecast to match the destination variable.
An example is:
VariableName = <expression>
Output channels
This type of statement is typically used for feedback. It sends an output change to the specified channel
on the given device. An example is:
[Device, Channel] = <expression>
Conditionals
IF…ELSE
The IF...ELSE statement provides a structure for conditional branching of program execution. If a
condition evaluates to true, the statement(s) associated with it are executed; otherwise, statements are not
executed. An example is:
IF (<conditional expression 1>)
{
(* statements for condition 1 *)
}
ELSE IF (<conditional expression 2>)
{
(* statements for condition 2 *)
}
ELSE
{
(* statements for all other conditions *)
}
Regarding IF statements:
ELSE IF is optional.
Braces are generally recommended in all cases but are only required if multiple statements are
assigned to a given condition.
IF statements may be nested to any number of levels.
SELECT…ACTIVE
The SELECT…ACTIVE statement provides a programming structure for selective execution of code
blocks based on the evaluation of a series of conditions. The first block whose ACTIVE condition
evaluates to true is executed; the remaining blocks are ignored. If no ACTIVE condition evaluates to true,
no statements are executed. An example is:
SELECT
{
ACTIVE (<condition 1>) :
{
(* statements for condition 1*)
}
ACTIVE (<condition 2>) :
{
(* statements for condition 2*)
}
ACTIVE (<condition n>) :
{
ACTIVE (1)
(* statements for condition n*)
}
}
SWITCH (var)
{
CASE 1:
{
(*statements go here*)
BREAK
}
CASE 3:
{
(*statements go here*)
BREAK
}
CASE 5:
{
(*statements go here*)
BREAK
}
DEFAULT:
{
(*statements go here*)
BREAK
}
}
Loops
WHILE statements
A WHILE statement executes its statement block as long as its associated condition evaluates to true. The
condition is evaluated before the first pass through the statements. Therefore, if the conditional
expression is never true, the conditional statements are never executed. An example is:
WHILE (<conditional expression>)
{
(* conditional statements *)
}
LONG_WHILE statements
A LONG_WHILE differs from a WHILE statement in the way input change notifications are processed
during the programming loop. The system checks the input queue for a change notification message
before execution of each loop, beginning with the second loop. The message is retrieved if one exists.
This message must be processed before another one is retrieved, either at the start of the next loop or the
beginning of the next mainline iteration. Otherwise, the message is lost. For example:
LONG_WHILE (<conditional expression>)
{
(* conditional statements *)
}
Parameters:
<INITIAL> One or more statements that are executed one time before any FOR loop state-
ments are executed. Each statement must be separated with a comma; this is
typically a FOR loop index initialization statement.
<condition> A condition whose value is computed before each pass. If the condition evalu-
ates to TRUE, the FOR loop statements are executed. If the condition evaluates
to FALSE, the loop is terminated.
<after pass> One or more statements that are executed after each pass through the state-
ments. Each statement must be separated with a comma. This is typically a
statement that increments the FOR loop index.
Waits
Wait instructions allow delayed execution of one or more program statements. When a wait statement is
executed, it is added to a list of currently active wait requests and the program continues running.
Naming Waits
Supplying a unique name in the wait statement allows the wait to be identified for purposes of canceling,
pausing, or restarting the wait request. The name must not conflict with previously defined constants,
variables, buffers, subroutines, or functions. Unlike other NetLinx identifiers, wait names may contain
spaces.
If a wait instruction that uses a name currently in the wait list is encountered, the new wait instruction is
thrown away so as not to conflict with the one currently in progress. If this feature is not desired, the
current wait must be canceled before processing the new request. For information, refer to the Canceling
Waits section on page 38.
Types of Waits
Types of Wait statements include:
Timed Waits have an associated parameter that indicates the amount of time that must elapse
before the associated wait instruction(s) are to be executed.
Conditional Waits require that a specified condition be met before the instructions are
executed.
Timed Conditional Waits have a timeout parameter; if the condition is not met before the
specified time elapses, the wait request is cancelled.
Types of Waits
Timed Waits Syntax:
WAIT time ['<name>']
{
(* wait statements *)
}
Parameters:
• time: A constant or variable indicating the wait time. Time is expressed in 1/
10th second units. The statement below specifies a wait time of 5 seconds
for the wait named FIRST WAIT.
• <name>: The name to assign to the wait. This name must be a literal string.
The wait name is optional, although unless a wait is named it cannot be
individually cancelled, paused, or restarted.
If greater precision is required, the time parameter can be expressed as a dec-
imal fraction, for example 0.1 to specify a wait time of 1/100th of a second. The
range is from 0.1 to 0.9.
WAIT 50 'FIRST WAIT'
{
(* wait statements *)
}
Continued
Nesting Waits
The wait time for a nested wait is the sum of it's own wait time, plus that of the enclosing waits. In the
example below, SECOND WAIT occurs 0.5 seconds after FIRST WAIT is executed, or 1.5 seconds after
FIRST WAIT is added to the wait list.
WAIT 10 'FIRST WAIT'
{
(* FIRST WAIT statements *)
WAIT 5 'SECOND WAIT'
{
(* SECOND WAIT statements *)
}
}
To execute the inner wait of a nested conditional wait, the conditions must be met in the order specified
(condition 1, then condition 2) but not necessarily at the same time.
Canceling Waits
Canceling Waits
CANCEL_WAIT / CANCEL_WAIT and CANCEL_WAIT_UNTIL removes the wait specified by
CANCEL_WAIT_UNTIL name from the appropriate wait list. The syntax:
CANCEL_WAIT '<name>
CANCEL_WAIT_UNTIL '<name>'
CANCEL_ALL_WAIT / CANCEL_ALL_WAIT and CANCEL_ALL_WAIT_UNTIL cancels all waits
CANCEL_ALL_WAIT_UNTIL (named or unnamed) from the appropriate wait list. The syntax:
CANCEL_ALL_WAIT
CANCEL_ALL_WAIT_UNTIL
Comments
Comments are designated with a parentheses-asterisk to begin the comment and asterisk-parentheses to
end the comment; for example, (*COMMENT*). These comments can span lines and are not limited in
length. NetLinx supports a second type of comment with a double forward-slash (//).
All text following the double forward-slash is treated as a comment. This type of comment closely
follows the conventions of C++.
Comments are not part of the actual program code; they are not compiled. Comments can appear
anywhere except within literal strings, either on the same line as a programming statement or on a
separate line. Comments can span multiple lines with a single set of comment delimiters and can be
nested. The compiler recognizes nested comments by pairing up sets of comment delimiters. For
example:
(* The section to follow contains all variable declarations. *)
Single line comments can be specified using the double forward slash (//) notation. When a pair of
forward slash characters is encountered, all text on the same line following the slash pair, except the *)
end comment sequence, is considered a comment and ignored by the compiler. For example:
(*INTEGER Vol1 // volume for room 1 *)
The "*)" in the line above terminates the open "(*" command even though it appears after a double
slash comment command.
Operators
An operator is a character (or group of characters) that performs a specific mathematical or relational
function. Each operator type is described below.
Arithmetic operators
Arithmetic operators create a numeric value from one or more operations such as addition,
multiplication, and division.
Arithmetic Operators
Operator Function
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulo (remainder after division)
Relational operators
A relational operator is a conditional statement; it tells NetLinx whether to execute a particular
function(s) in the program.
Relational Operators
Operator Function
< Less Than
> Greater Than
= Equal To
== Equal To
<= Less Than or Equal To
>= Greater Than or Equal To
<> Not Equal To
Logical operators
Logical operators compare two conditions or, in the case of NOT, invert one condition. A true or false
result is produced.
Logical Operators
Operator Function Keyword
&& Logical And AND
|| Logical Or OR
^^ Logical Xor XOR
! Logical Not NOT
Bitwise operators
Bitwise operators are keywords or symbols that perform a bit-by-bit operation between two items.
Bitwise Operators
Operator Function Keyword
& Bitwise And BAND
| Bitwise Or BOR
^ Bitwise Xor BXOR
~ Bitwise Not BNOT
<< Shift Left LSHIFT
>> Shift Right RSHIFT
Assignment operators
The assignment operators may appear only once in a single NetLinx statement.
Assignment Operators
Operator Function
= Assignment
++ Increment by 1
-- Decrement by 1
Operator precedence
The table below shows the inherent precedence assigned to the operators. As noted in the chart, the NOT
(!) operator has the highest precedence in NetLinx systems but the lowest precedence in Axcess
systems. Axcess programs that are converted to NetLinx may exhibit logic problems if they use
statements that combine NOT (!) and other operators. Contact AMX Technical Support for help
resolving these issues.
Operator Precedence
Level Operators Associability
1 ! ~ Left To Right
2 * / % Left To Right
3 << >> Left To Right
4 + - Left To Right
5 < <= > >= = == <> Left To Right
6 & | ^ Left To Right
7 && || ^^ Left To Right
Identifiers
An Identifier is a combination of letters, numbers, or underscores that represents a device, constant, or
variable. Identifier types include:
Devices
A device is any hardware component that can be connected to the NetLinx bus. Each device must be
assigned a unique number to identify it on the bus. While the Axcess language allows physical device
numbers in the range 0-255, the NetLinx language allows numbers in the range 0-32767. Device 0 refers
to the Master; numbers above 32767 are reserved for internal use.
NetLinx requires a Device:Port:System (D:P:S) specification where Axcess expected only a device
number. This D:P:S triplet can be expressed as a series of constants, variables separated by colons, or a
DEV structure. For example:
STRUCTURE DEV
{
INTEGER Number // Device number
INTEGER Port // Port on device
INTEGER System // System device belongs to
}
Parameters:
Device arrays
In order to specify a group of devices for a command or event handler, NetLinx provides the capability to
define an array of DEVs and treat it as a device array. A device array may be used anywhere a device
specification is required. The result provides a range of targets for the command or instruction where it is
used.
Device arrays are declared in the DEFINE_VARIABLE section of the program in one of two ways:
DEV DSName[ ] = {Dev1, Dev2, ..., Devn}
DEV DSName[MaxLen] = {Dev1, Dev2, ..., Devn}
Each device name appearing on the right-hand side of the declaration should be defined as a device in the
DEFINE_DEVICE section; however, it can also be defined in the DEFINE_VARIABLE or
DEFINE_CONSTANT section.
The first statement above declares a device array whose maximum length is determined by the number of
elements in the initialization array on the right-hand side.
The second form uses MaxLen to specify the maximum length of the device array. In either case, the
number of elements in the initialization array determines the effective length of the device array. That
value can be determined at run-time by calling LENGTH_ARRAY. The maximum length available for a
device array can be determined by calling MAX_LENGTH_ARRAY.
The following program fragment illustrates device array initialization:
DEFINE_DEVICE
panel3 = 130
DEFINE_CONSTANT
DEV panel1 = 128:1:0
integer panel2 = 129
DEFINE_VARIABLE
// dvs is an array of three devices:
// 128:1:0
// 129:1:0
// 130:1:0
DEV dvs[ ] = {panel1, panel2, panel3}
The individual elements of a device array can be referenced by their defined names (Dev1, Dev2, etc.)
or by using array notation with the device array name. For example, the 3rd device in the device array,
MyDeviceSet, would be referenced by MyDeviceSet[3].
The index of the last member of the array for which an event notification was received can be determined
by calling GET_LAST(MydeviceSet). This is useful for determining which device in an array is
referenced in a particular notification message.
Device array examples
The command below sends 'CHARD10' to all devices in the array, DeviceSetA.
DEV DeviceSetA[ ] = {Device1, Device2, Device3}
SEND_COMMAND DeviceSetA, 'CHARD10'
The command below sends 'CHARD10' to the third device in the array, DeviceSetA,
SEND_COMMAND DeviceSetA[3], 'CHARD10'
The intent of the feedback statement is to set channel 1 in every device in DeviceSetA to either on or
off, depending on the value of the right-hand expression; it is unclear what the right-hand expression
evaluates to. The compiler will issue a warning indicating the syntax is unclear and that
DeviceSetB[1] is assumed. To avoid this warning, specify a particular device in the array. Here's an
example:
[DeviceSetA, 1] = [DeviceSetB[1], 2] (* Correct *)
The first component of a device-channel pair represents the device number, port, and system. It can be
specified as either a single device number, a constant DEV structure or as a D:P:S specification. Each
device specified in a device-channel pair should be defined in the DEFINE_DEVICE section.
Channels are expressed as integer constants. A DEVCHAN is declared in either the DEFINE_VARIABLE
or DEFINE_CONSTANT section. For example, "[128, 1]", "[CONSTANTDPS, 9]" and "[128:1:0,
5]" are all valid representations of device-channel pairs.
A DEVCHAN enclosed within square brackets implies an evaluation, whereas a DEVCHAN enclosed within
curly braces does not, as illustrated below:
DEFINE_VARIABLE
DEVCHAN dc1 = {128:1:0, 1}
DEVCHAN dcset[ ] = { {128:1:0, 1}, {128:1:0, 2}, {128:1:0, 3} }
DEFINE_PROGRAM
Use MAXLEN to specify the maximum length of the array, as shown below:
DEVCHAN[ ] DCSName[MAXLEN] = {{Dev1,Chan1}, {Dev2,Chan2}, ...}
In either case, the number of elements in the initialization array determines the effective length of the
array. That value can be determined at run-time by calling LENGTH_ARRAY. The maximum length
available for a DEVCHAN[ ] array can be determined by calling MAX_LENGTH_ARRAY.
The individual elements of a DEVCHAN array can be referenced by their defined names (Dev1, Chan1,
Dev2, Chan2, etc.) or by using array notation with the device-channel array name. For example, the
third element in the device-channel array, MyDCSet, would be referenced by MyDCSet[3].
Furthermore, since a DEVCHAN array is an array of DEVCHAN structures, DEVCHAN members can be
referenced using the dot operator notation such as MyDCSet[3].Device or MyDCSet[1].Channel.
A DEVCHAN array can be used anywhere a [Device, Channel] specification is required with the
result of providing a range of targets for the command or instruction where it is used. This implies an
alternate form for the following commands:
Button[(DEVCHAN)] PULSE[(DEVCHAN)]
DO_PUSH[(DEVCHAN)] PUSH[(DEVCHAN)]
DO_RELEASE[(DEVCHAN)] RELEASE[(DEVCHAN)]
OFF[(DEVCHAN)] TO[(DEVCHAN)]
ON[(DEVCHAN)]
The index of the last member of the array for which an event notification was received can be determined
by calling GET_LAST(MyDCSet). This is useful for determining which device and channel in an array
is referenced to in a particular notification message.
Device-level arrays
A device-level array (DEVLEV array) is an array of device-level pairs. Each element is represented
internally as a DEVLEV structure. This structure combines the fields of a DEV structure representing the
device with a field representing the level number.
STRUCTURE DEVLEV
{
DEV // Device
INTEGER // Level
}
The first component of a device-level pair (Device) represents the device number, port, and system. It
can be specified as either a single device number, a constant DEV structure or as a D:P:S specification.
Each device specified in a device-level pair should be defined in the DEFINE_DEVICE section. The
second component is the level number on the device. The level number is expressed as an integer
constant.
A DEVLEV array is declared in the DEFINE_VARIABLE or DEFINE_CONSTANT section in one of two
ways:
Declare a DEVLEV array whose maximum length is determined by the number of elements in
the initialization array on the right-hand side.
DEVLEV DLName[ ] = {{Dev1,Level1}, {Dev2,Level2}, ...}
In either case, the number of elements in the initialization array determines the effective length of the
array. That value can be determined at run-time by calling LENGTH_ARRAY. The maximum length
available for a DEVLEV array can be determined by calling MAX_LENGTH_ARRAY.
The individual elements of a level array can be referenced by their defined names (Dev1, Level1,
Dev2, Level2, etc.) or alternatively, by using array notation with the device-level array name. For
example, the 3rd element in the device-level array, MyDLSet, would be referenced by MyDLSet[3].
Furthermore, since a DEVLEV array is an array of DEVLEV structures, DEVLEV members can be
referenced using the dot operator notation such as MyDLSet[3].Device or MyDLSet[1].Level.
The index of the last member of the array for which an event notification was received can be determined
by calling GET_LAST(MyDLSet). This is useful for determining which device and level in an array is
referenced to in a particular notification message.
Variables
NetLinx provides support for several different types of variables distinguished by attributes, such as:
Scope
Constancy
Persistence
Scope
Scope is a term used in reference to program variables that describe where in the program they can be
accessed. There are two types:
Local scope: a variable can only be accessed in the subroutine or method that it is declared.
Global scope: a variable can be accessed anywhere in the program.
Scope differentiates the two basic classes of NetLinx variables:
Local variable: a variable declared within a subroutine or function whose scope is limited to
that subroutine or function.
Global variable: a variable declared in the DEFINE_VARIABLE section; its scope extends
throughout the module in which it is declared.
Local variables
Local variables are restricted in scope to the statement block in which they are declared. A statement
block is one or more NetLinx statements enclosed in a pair of braces, like the blocks following
subroutines, functions, conditionals, loops, waits, and so on. Local variables must be declared
immediately after the opening brace of a block but before the first executable statement. To provide
compatibility with the Axcess language, local variables may be declared right before the opening brace
for DEFINE_CALL declarations only. For example, both formats shown below are legal in the NetLinx
language:
The scope of a local variable is restricted to the statement block in which it is declared. A local variable
is either static or non-static, depending on whether it is declared as LOCAL_VAR or STACK_VAR:
A static variable maintains its value throughout the execution of the program,
regardless of whether it is within scope of the current program instruction.
The keyword LOCAL_VAR specifies a static variable. A static variable's value is initialized the
first time the statement block in which it is declared is executed and retained after execution of
the statement block has finished.
The STACK_VAR keyword specifies a non-static variable. A non-static variable's value is re-
initialized every time the statement block in which it is declared is executed.
If neither the LOCAL_VAR nor the STACK_VAR keyword is specified, STACK_VAR is assumed
(default).
IF (X > 10)
{
LOCAL_VAR INTEGER INT2 // static (permanent)
STACK_VAR CHAR ARRAY1[10] // non-static (temporary)
(* statements *)
}
LOCAL_VAR and STACK_VAR can be used interchangeably in any statement block except for waits.
Only LOCAL_VAR variables may be declared inside a wait block.
WAIT 10, 'My Wait Name'
{
LOCAL_VAR CHAR TempBuf[80]
(* statements *)
}
A name assigned to a local variable must be unique within the statement block in which it is declared and
any statement block enclosing that block. Therefore, non-nested statement blocks can define the same
local variable name without conflict. For example:
Since non-static local variables are allocated on the program stack (a block of memory reserved for
allocation of temporary variables), the keywords VOLATILE, PERSISTENT, and CONSTANT do not
apply.
Global variables
Global variables are defined in the DEFINE_VARIABLE section of any program module. For example:
DEFINE_VARIABLE
CONSTANT INTEGER MAXLEN = 64
CHAR STR[MAXLEN] = 'No errors were found.'
INTEGER ARRAY[ ] = {100, 200, 300}
A global variable is accessible throughout the module or program in which it is defined. Global variables
retain their value as long as the program runs. They may retain their value after powering down or
reloading the system, depending on the variable's persistence attributes (VOLATILE and PERSISTENT).
Modules are reusable NetLinx sub-programs that can be inserted into the main
program. The main program is also a module. Refer to the NetLinx Modules section
on page 143 for information on program modules.
If a local variable shares the same name as a global variable, the local variable always takes precedence.
The general form of a global variable definition is:
[NON_VOLATILE | VOLATILE | PERSISTENT] [CONSTANT] [<type>] name [= <value>]
Constancy
Any variable may also be assigned the attribute CONSTANT. This declares a variable to be immutable
(cannot change at run-time). The variable must be initialized as part of its declaration if this keyword is
used.
Persistence
The persistence of a variable is controlled through the NON_VOLATILE, VOLATILE and PERSISTENT
keywords.
Non-volatile variables: A variable declared with the NON_VOLATILE keyword is stored in
non-volatile memory. It will retain its value in the event of a system power-down, but is reset
to zero if the program is reloaded. Unless specified otherwise, all variables are stored in non-
volatile memory.
Volatile variables: A variable declared with the VOLATILE keyword is stored in volatile
memory and resets to zero after either a power-down or reload. Volatile memory is generally
faster and more plentiful than non-volatile memory. For this reason, you should use the
VOLATILE keyword when declaring large data arrays where persistence of the data is not a
requirement.
Persistent variables: If a variable is declared with the PERSISTENT keyword, it is initialized
to zero the first time the program is loaded but will retain its value after either power-down or
reload.
If the data type is omitted from the variable definition, the following defaults are assumed:
Single variables are INTEGER type.
Arrays are CHAR type.
You can define a variable to be persistent using the PERSISTENT storage modifier as show below:
DEFINE_VARIABLE
PERSISTENT CHAR cMyString[100]
All persistent variables are automatically non-volatile, and it’s not legal to define a variable as
VOLATILE and PERSISTENT.
Any time after a NetLinx program that has a persistent variable declared subsequent downloads of new
NetLinx programs that contain the same persistent variable will automatically be set to contain the same
value as it previously did. The default behavior for non-persistent variables is they are set to zero after a
NetLinx program downloads. Persistence overrides this behavior by setting the variable in the newly
downloaded program to be the same as it was before the download.
Typically, persistent variables are used for saving preset information. Suppose you have a system that
contains several Positrack camera positioning systems and that the user interface to the system allows the
user to set the position of any of the cameras and record that position for recalling later. The position
presets are stored in a non-volatile array variable so they are maintained during a power cycle. Without
persistent variables, an update to the NetLinx program would zero out all of the presets that the user had
stored. With persistent variables, the new NetLinx program can be downloaded and all of the presets
remain intact.
When a new NetLinx program is downloaded to the Master, the Master iterates through all non-volatile
variables from the new program looking for persistent ones. When it finds a persistent variable in the
new program, it searches the old programs persistent variable space for the "same variable". When it
finds the same variable, the value of the new variable is set to the same value as the old programs
variable. It is important to note what is considered to be the "same variable". The master identifies the
"same variable" by verifying for duplicity the following:
Variable name
Variable source location
Variable type
Therefore, in order for persistence to function properly, the name, type, and file declared in must be the
same as the previously downloaded NetLinx program. If you changed any of the three, the new persistent
variable will not be set with the old variable’s value.
Constants
Constants are defined in the DEFINE_CONSTANT section. The scope of a constant extends throughout
the module or program in which it is defined. The name assigned to a constant must be unique among all
other identifiers defined in the module or program. The syntax is:
DEFINE_CONSTANT
<constant name> = <constant expression>
Constants may be assigned expressions that consist only of other constants and operators. Variables are
not allowed in constant expressions. An example is:
VALUE_OFFSET = 500
VALUE1 = VALUE_OFFSET + 1
STR1 = 'This is a string constant.'
Constants can be used anywhere that a numeric or string constant is allowed. The value assigned to a
constant can be specified in one of the formats listed in the following table.
Valid Formats for Constants
Type Format Example
Decimal Integer 0000 1500
Hexadecimal Integer $000 $DE60
Floating Point 000.0 924.5
Exponential Notation 0.0e0 1.5e-12
Character 'c' or <char code> 'R' or 255
String Literal 'ssss' 'Reverse'
Data Types
Intrinsic types
The following table lists the data types inherently supported by the NetLinx language.
NetLinx Intrinsic Data Types
Keyword Data Type Sign Size Range
CHAR Byte Unsigned 8-bit 0 - 255
WIDECHAR Integer Unsigned 16-bit 0 - 65535
INTEGER Integer Unsigned 16-bit 0 - 65536
SINTEGER Integer Signed 16-bit -32768 to +32768
LONG Long Integer Unsigned 32-bit 4,294,967,295
SLONG Long Integer Signed 32-bit + 2,147,483,647
FLOAT Floating Point Signed 32-bit 1.79769313 E+308 to 2.22507385 E-308
DOUBLE Double Precision Signed 32-bit 3.40282347 E+38 to 1.17549435 E-38
Floating Point
Type conversion
Although explicit type casting is not supported in the NetLinx language, the compiler is forced to do type
conversion in situations where an arithmetic assignment or other operation is defined with constants and/
or variables having mixed data types. Type conversions will occur under the following circumstances:
A value of one type is assigned to a variable of another type.
A value passed as a parameter to a subroutine does not match the declared parameter type.
The value returned by a subroutine does not match the declared return type.
Type conversion rules
If the expression contains a 32 or 64-bit floating-point variable or constant, all variables and
constants in the expression are converted to 64-bit floating point before resolving.
If the expression contains only whole number value variables and constants, all variables and
constants in the expression are converted to 32-bit integers before resolving.
If type conversion is required for an assignment or as a result of a parameter or return type
mismatch, the value is converted to fit the type of the target variable. This may involve
truncating the high order bytes(s) when converting to a smaller size variable, or sign
conversion when converting signed values to unsigned or vice versa.
Strings
A string is an array of characters of known length. This length may be less than the dimensioned length.
For example:
DEFINE_VARIABLE
CHAR MyString[32]
INTEGER StrLen
DEFINE_START
MyString = 'STOP'
StrLen = LENGTH_STRING(MyString)
In the example above, StrLen holds the value 4, the length of MyString. The length of MyString can
range from 0 to 32. If an attempt is made to assign a string longer than the capacity of the destination
string, the copied string is truncated to fit. The string length is implicitly set when a string literal, string
expression, or variable is assigned to the string. The function SET_LENGTH_STRING can be used to
explicitly set the length of a string to any arbitrary length between 0 and the dimension of the character
array. Here's an example:
SET_LENGTH_STRING(MyString, 3)
This causes the contents of MyString to read 'STO', even though the character 'P' still resides in
MYSTRING[4].
String expressions
A string expression is a string enclosed in double quotes containing a series of constants and/or variables
evaluated at run-time to form a string result. String expressions can contain up to 16000 characters
consisting of string literals, variables, arrays, and ASCII values between 0 and 255. Here's an example:
CHAR StrExp[6]
StrExp = "STOP, 25, 'OFF', X"
In the example above, the string expression contains the constant STOP, the value 25, the string literal
'OFF', and the variable X. Assuming STOP is 2 and X = 5, the string expression will evaluate to "2,
25, 'OFF', 5".
Wide strings
A wide character string data type is provided for dealing with Unicode fonts, which use 16-bit character
codes (used for many Far-Eastern fonts) instead of the standard 8-bit codes (used with most Western
fonts). Here's a syntax sample for a wide character string:
WIDECHAR WChar[40]
The statement above declares a wide character string containing 40 elements, for a total of 80 bytes. A
wide character string can be used in the same manner as other character strings. It maintains a length
field that can be retrieved using LENGTH_STRING and set using SET_LENGTH_STRING. Here's an
example:
WIDECHAR StrExp[6]
INTEGER StrLen
In the example above, if STOP is 2 and X is a wide character whose value is 1000, the string expression
will evaluate to "2, 500, 79, 70, 70, 1000" and StrLen is 6. Each array element can now
assume a value of up to 65,535, rather than the limit of 255 imposed by the standard character string.
A CHAR string may be assigned or compared to a wide character string. For example:
WChar = 'FFWD'
- or -
IF (WChar = 'REV')
{
(* statements *)
}
Each 8-bit character in the CHAR string is converted to 16-bit before the assignment or comparison
operation is performed.
Arrays
In the Axcess language, arrays can be declared with 8-bit (string) or 16-bit (integer) fields.
The syntax for an 8-bit (string) field is:
Name[20] // 8-bit character array
The NetLinx language allows arrays of any data type supported by the language, as well as, arrays of
user-defined structures and classes. If an initialization statement is included in the variable declaration,
the array dimension is not required. If the array dimension is omitted, both the maximum and effective
length is set to the length needed to hold the data contained in the initialization string.
CHAR STRING[ ] = 'character string'
WIDECHAR WideString[ ] = 'wide character string'
INTEGER IntegerNum[ ] = {1, 2, 3, 4, 5}
SINTEGER SINTEGERNum[ ] = {-1, 5, -6}
LONG LONGNum[ ] = {$EFFF, 0, 89000}
SLONG LONGNum[ ] = {-99000, 50, 100, 100000}
FLOAT FloatingNum[ ] = {1.0, 20000.0, 17.5, 80.0}
DOUBLE DoubleNum[ ] = {1.0e28, 5.12e-6, 128000.0}
The initialization statement for a single dimension character string is enclosed in single quotes; data for
other types is enclosed in braces. In the case of a multidimensional character string, the strings in the
initialization statement are separated by commas and enclosed in braces. In order to populate the array,
for example:
DEFINE_VARIABLE
CHAR StringTable_3[3][5]
DEFINE_START
CHAR StringTable_3[1] = 'Str 1'
CHAR StringTable_3[2] = 'Str 2'
CHAR StringTable_3[3] = 'Str 3'
For multidimensional array types, the data pertaining to each dimension is delimited using braces, as
shown below:
INTEGER Num2D[ ][ ] = {{1, 3}, {2, 4}, {7, 8}}
(* This sets the dimensions to Num2D[3][2] *)
Arrays can be manipulated using the operator "=". The "=" operator is used to assign one array to
another. In the example below, the contents of Array1 are replaced by the contents of Array2.
Array1 = Array2
The arrays must match in number of dimensions and type. The size of each dimension of the destination
array must be greater than or equal to the corresponding dimension of the array being assigned;
otherwise, the contents of the array being assigned are truncated to fit into the destination array. If a type
mismatch is detected, the compiler will issue an appropriate warning.
The length of an array is determined by calling LENGTH_ARRAY and MAX_LENGTH_ARRAY.
LENGTH_ARRAY returns the effective length of a dimension of an array; the length set implicitly through
array initialization or array manipulation operations (+ and -) or explicitly through a call to
SET_LENGTH_ARRAY. MAX_LENGTH_ARRAY is used to determine the maximum length of a dimension
of an array. For example:
INTEGER Len
INTEGER Array[1] = {3, 4, 5, 6, 7}
INTEGER Array2[10] = {1, 2}
Len = MAX_LENGTH_ARRAY(Array1) // Len = 5
Len = MAX_LENGTH_ARRAY(Array2) // Len = 10
LENGTH_ARRAY is called to determine the effective length of Array1 and Array2. This value is set
automatically when the arrays are initialized.
Len = LENGTH_ARRAY(Array1) // Len = 5
Len = LENGTH_ARRAY(Array2) // Len = 2
Multi-dimensional arrays
Any of the single dimension array types listed above can be used to define an array of n-dimensions. A
2-dimensional array is simply a collection of 1-dimensional arrays; a 3-dimensional array is a collection
of 2-dimensional arrays, and so forth. Here's an example:
INTEGER Num1D[10] // [Column]
INTEGER Num2D[5][10] // [Row][Column]
INTEGER Num3D[2][5][10] // [Table][Row][Column]
One way to view these arrays is to think of Num2D as being a collection of five Num1D's and Num3D as
being a collection of two Num2D's.
When referencing elements of the above arrays:
LENGTH_ARRAY and MAX_LENGTH_ARRAY are used to determine the effective and maximum lengths
of multidimensional arrays as shown in the following examples:
INTEGER Len
INTEGER My3DArray[5][3][4]
Structures
A structure provides the ability to create a new data type composed of other data types arranged in a
specified order.
Here's an example:
DEFINE_TYPE
STRUCTURE NEWSTRUCT
{
INTEGER Number
CHAR Text[20]
}
In the example above, a structure named NEWSTRUCT is declared to contain two data types, a 16-bit
number and a 20-character array. Once declared, a structure may be used in the same way as any other
data type. Here is a syntax sample:
DEFINE_VARIABLE
NEWSTRUCT MyNewStruct
NEWSTRUCT MyNewStructArray[3]
Structures can be initialized using set notation as in the two examples below. Notice that the members of
each structure, as well as, the entire array are enclosed in braces.
MyNewStruct.Number = 0
MyNewStruct.Text= 'Copyright by Company X'
MyNewStructArray[1].Number = 1
MyNewStructArray[1].Text = 'Line 1'
MyNewStructArray[2].Number = 2
MyNewStructArray[2].Text = 'Line 2'
MyNewStructArray[3].Number = 3
MyNewStructArray[3].Text = 'Line 3'
The attributes VOLATILE, PERSISTENT, and CONSTANT do not apply to the individual members of a
structure.
Subroutines
A subroutine is a section of code that stands alone, and can be called from anywhere else in the program.
DEFINE_CALL subroutines
The DEFINE_CALL is the standard method provided by NetLinx for defining subroutines.
DEFINE_CALL '<subroutine name>' [(Param1,Param2,...)]
{
(* statements *)
}
To invoke a user-defined subroutine, use the CALL keyword plus the name of subroutine and any
required calling parameters.
CALL 'Read Input' (Buf1)
In NetLinx, DEFINE_CALL supports the RETURN statement (as shown in the following example),
although return values are not supported.
DEFINE_CALL 'Read Input' (CHAR Buffer)
{
if (nChars = 0)
{
RETURN // exit subroutine
}
(* read input *)
}
SYSTEM_CALL subroutines
A SYSTEM_CALL subroutine is a special type of DEFINE_CALL subroutine defined in a separate
program file called a LIB file with a PROGRAM_NAME entry matching the subroutine name.
PROGRAM_NAME = 'COSX'
To invoke a system call, use the SYSTEM_CALL keyword followed by the name in single quotes and any
calling parameters, as shown below:
SYSTEM_CALL 'COSX' (45)
System calls are resolved automatically at compile time, without requiring an INCLUDE instruction to
include the system call source file.
For special cases where multiple copies of a system call are needed, an instance number can be specified
in the call. The compiler will compile a separate copy of the subroutine for each system call instance
number. For example, the following commands force the compiler to include two separate copies of
COSX:
SYSTEM_CALL[1] 'COSX' (45)
SYSTEM_CALL[2] 'COSX' (60)
This technique could be useful in cases where a system call contains a wait instruction that conflicts
when multiple calls to the same subroutine were made during a single wait period.
Function Subroutines
A function is similar to a DEFINE_CALL, but is intended for use either standalone or in-line as an
expression. Instead of requiring a string literal for its name, it requires a name that follows the rules for
naming constants and variables. This eliminates the need for using the CALL keyword to invoke the
subroutine. DEFINE_FUNCTION subroutines also differ from DEFINE_CALL by allowing values to be
returned using the RETURN statement (see below).
The return type may only be one of the 8 intrinsic types. Strings, arrays, structures,
classes and other user-defined types may not be returned.
Syntax:
DEFINE_FUNCTION [<return type>] FnName[(Param1,Param2,...)]
{
(* statements *)
}
Example:
DEFINE_FUNCTION INTEGER myFunction (INTEGER Var0)
{
INTEGER nBytes
STACK_VAR RESULT
nBytes = 0
RETURN = Var0 + nBytes
RETURN RESULT
}
When it is a NetLinx function, a syntax where there appears a ([ ]), the ( ) are NOT
OPTIONAL but the [ ] are optional.
The DEFINE_FUNCTION subroutine can be called as a single programming statement. For example,
the following syntax:
ReadBuffer(Buffer,BufSize)
Can be used in an assignment statement such as:
Count = ReadBuffer(Buffer,BufSize)
or as part of an expression such as:
IF (ReadBuffer(Buffer,BufSize) > 0)
{
(* statements *)
}
The rules pertaining to calling parameters are the same for DEFINE_FUNCTION as they are for
DEFINE_CALL subroutines. The parameter list must appear in parentheses to the right of the function
name. If the function has no calling parameters a set of parentheses must still be included. For example,
MyFunc() // calling a function with no parameters
The return type may be omitted, as an alternate way of defining a subroutine. In this case the function
cannot be used as part of an expression or in an assignment statement.
DEFINE_FUNCTION also allows the use of the RETURN keyword that serves two purposes:
To return prematurely from a function.
To return a value from a function.
The format of the return statement is:
RETURN [<return value>]
If a return statement is encountered anywhere in the function, execution of the function is terminated
immediately and the value (if any) specified as the <return value> is returned to the caller.
A function that returns a value through the RETURN keyword must be declared with a return type.
Conversely, a function that is declared without a return type cannot return a value.
In the example below, GetBufferSize returns an unsigned 16-bit integer, BufSize.
The return type is indicated before the DEFINE_FUNCTION keyword.
DEFINE_FUNCTION INTEGER GetBufferSize()
LOCAL_VAR INTEGER BufSize = 0;
{
.
.
.
RETURN BufSize;
}
To call this function and to retrieve the RETURN value, use the following syntax:
BufSize = GetBufferSize()
The return type may only be one of the 8 intrinsic types (see Data Types). Strings,
arrays, structures, classes and other user-defined types may not be returned.
Calling parameters
Parameters may be passed to any NetLinx function or subroutine. Calling parameters are simply
variables or constants that originate from the caller and are received by the function or subroutine being
invoked.
The NetLinx compiler passes all variables by reference. This means that the variable the subroutine
operates on is the same variable the caller passed. Any change made to a variable passed as a calling
parameter updates the value of the variable from the perspective of the caller. You can take advantage of
this pass by reference feature to return an updated value through a calling parameter rather than as the
return value.
Constants, on the other hand, are passed by value. When this happens, a copy of the parameter is
delivered to the subroutine. Any change made to the variable representing the constant is lost once the
function or subroutine finishes.
Function and subroutine declarations must include the type and name of each parameter expected. If the
type is omitted, the default type is assumed; arrays are CHAR type and non-array parameters are
INTEGER.
To specify an array as a function or subroutine parameter, one set of brackets for each array dimension
must follow the variable name, as shown in the following example:
DEFINE_CALL 'Process Array' (CHAR Array[ ][ ])
{
(* body of subroutine *)
}
The parameter Array is declared to be a 2-dimensional array, by including two sets of brackets after the
name. For compatibility with existing programs, the array dimensions may be specified inside the
brackets. These dimensions are not required and are ignored by the compiler. The NetLinx interpreter
will do bounds checking on the array and generate a run-time error if the array bounds are exceeded.
When calling a subroutine that takes an array as one of its parameters, pass only the name of the array as
the calling parameter, as shown below:
CHAR Buffer[10][20]
CALL 'Process Array' (Array)
If dimensions are specified in the call statement, the compiler will interpret that as specifying a subset of
the array. For example, suppose Array were defined as a 3-dimensional array. The third table of that
dimensional array could be passed to 'Process Array' as follows:
CHAR Buffer[5][5][10]
CALL 'Process Array' (Array [3])
Event Handlers
The NetLinx language provides a special program section called DEFINE_EVENT to define handlers for
incoming events/notifications. These handlers are stored in an event table providing quick access to code
that must be executed when an event is received. There are handlers to support five types of events:
Button events include pushes, releases, and holds, which are associated with a push or release
on a particular device-channel.
Channel events occur when an output change (On/Off) is detected on a device-channel.
Data events include commands, strings, status, and error messages.
Level events are received as a result of a level change on a particular device.
Timeline events trigger events based on a sequence of times.
All incoming events are stored in a queue pending processing. Messages are processed in the order they
are received. The steps to processing an event are:
1. Check all events for a handler matching the specified event. If a handler is found, run it.
2. If there is no event handler, run MAINLINE.
Event
handler NO
Start Run Mainline
available?
YES
Run event
handler Stop
More than one handler can be defined for the same event. In this case, the handlers
are executed in the order in which they are defined in the program.
Button events
Button events include pushes, releases, and holds. These events are associated with a push or release on
a particular device-channel. A sample button event is shown below:
BUTTON_EVENT[DEVICE,CHANNEL] or BUTTON_EVENT[(DEVCHAN[ ])]
{
PUSH:
{
// PUSH event handler
}
RELEASE:
{
// RELEASE event handler
}
HOLD[TIME]: or HOLD[TIME, REPEAT]:
{
// HOLD event handler
}
}
A HOLD event handler specifies the actions that should be performed when a button is pressed and held
for a minimum length of time indicated by the TIME parameter (TIME is specified in .10 second
increments). The REPEAT keyword specifies that the event notification should be repeated in TIME
increments as long as the button is held.
The BUTTON object is available to the button event handler as a local variable. The following table lists
the information contained in Button Objects.
Button Objects
Property Name Type Description
Button.Input DEVCHAN Device + Channel
Button.Input.Channel INTEGER Channel
Button.Input.Device DEV Device
Button.Input.Device.Number INTEGER Device number
Button.Input.Device.Port INTEGER Device port
Button.Input.Device.System INTEGER System number
Button.Holdtime LONG Current hold time in .10 second increments
Button.SourceDev DEV Source device of button event
Button.SourceDev.Number INTEGER Source device number
Button.SourceDev.Port INTEGER Source device port
Button.SourceDev.System INTEGER Source device system.
If the event handler is specified using an array for DEV,CHANNEL, or a DEVCHAN array, GET_LAST
can determine which index in the array caused the event to run.
Channel events
A channel event is generated when PULSE, TO, MIN_TO, ON or OFF is called. An example channel event
is shown below:
Channel_Event[DEVICE,CHANNEL] or Channel_Event[(DEVCHAN[ ])]
{
ON:
{
// Channel ON event handler
}
OFF:
{
// Channel OFF event handler
}
}
The Channel object is available to the channel event handler as a local variable.
Channel Objects
Property Name Type Description
Channel.Device DEV Device
Channel.Device.Number INTEGER Device number
Channel.Device.Port INTEGER Device port
Channel.Device.System INTEGER System number
Channel.Channel INTEGER Device channel
Channel.SourceDev DEV Source Device of Channel Event
Channel.SourceDev.Number INTEGER Source Device Number
Channel.SourceDev.Port INTEGER Source Device Port
Channel.SourceDev.System INTEGER Source Device System.
If the event handler is specified using an array for DEV, CHANNEL, or a DEVCHAN array, GET_LAST
can be used to determine which index in the array caused the event to run.
Data events
The data object is available to the data event handler as a local variable. An example data event is:
DATA_EVENT[DEVICE] or DATA_EVENT[DEV[ ]]
{
COMMAND:
{
// COMMAND event handler
}
STRING:
{
// STRING event handler
}
ONLINE:
{
// ONLINE event handler
}
OFFLINE:
{
// OFFLINE event handler
}
ONERROR:
{
// ONERROR event handler
}
}
The event number is a number associated with a command, error condition or the device ID
associated with an online/offline event. The numeric value is stored as either a floating-point
number or integer, as appropriate; the value can be assigned to a variable of any numeric type.
This field could be a value associated with a command event or error condition.
Text associated with the event is associated with a command, string, or error notification. It
can also be the device ID string in the case of an online/offline event.
The following table shows the fields that contain relevant information for data or notifications received
via Internet protocol (IP):
Data Objects Received Via the Internet Protocol (IP)
Property Name Type Description
Data.SourceIP CHAR Array IP address of the client/source application
Data.SourcePort LONG Server/source port number
Not all fields in the DATA object apply to all types of events. The following table lists the fields and the
corresponding events. An 'X' indicates that the field applies (or could apply) to the given event.
Data Object Fields
Property Name Command String OnLine OffLine OnError
Data.Device X X X X X
Data.Number X X X
Data.Text X X X X X
Data.SourceIP X X X X X
Data.ServerIP X X X X X
Data.SourcePort X X X X X
Level events
The level object is available to the level event handler as a local variable. Level events are triggered by a
level change on a particular device. This eliminates having to constantly evaluate a level against a
previous value. In Axcess, a level needs to be created in the DEVICE_START section and then a
conditional statement appears in the mainline to evaluate and update the level. The format of the
LEVEL_EVENT is:
The numeric value is stored either as a floating-point number or integer, as appropriate; but the value can
be assigned to a variable of any numeric type.
Existing Axcess code:
DEFINE_START
.
.
CREATE_LEVEL TEMP, 1, TEMP_LEVEL
.
.
DEFINE_PROGRAM
.
.
IF (TEMP_LEVEL >= COOL_POINT)
{
ON[RELAY,FAN]
}
ELSE IF (TEMP_LEVEL <= HEAT_POINT)
{
OFF[RELAY,FAN]
}
.
.
LEVEL_VALUE is an embedded object value in the LEVEL_EVENT statement. If the event handler is
specified using an array for DEV, CHANNEL, or a DEVCHAN array, GET_LAST can be used to determine
which index in the array caused the event to run.
Custom events
A custom event is generated by certain devices in response to query commands or unique device events.
For instance, G4 touch panels generate custom events in response to button query commands or mouse
clicks. An example channel event is shown below:
CUSTOM_EVENT[DEVICE,ADDRESS,EVENTID] or CUSTOM_EVENT[DEVCHAN,EVENTID]
{
}
The EVENTID is specific to each device. For instance, the EVENTID sent in response to a button text
query command for G4 touch panels is 1001. For more information on EVENTID values and the values
of the custom event for each EVENTID, see the programming section of the device manual with which
you are working.
The following table lists the information contained in Custom events:
Channel Objects
Property Name Type Description
Custom.Device DEV Device
Custom.Device.Number INTEGER Device number
Custom.Device.Port INTEGER Device port
Custom.Device.System INTEGER System number
Custom.ID INTEGER The address that generated the event
Custom.Type INTEGER The EVENTID of the event
Custom.Flag INTEGER A flag associated with the event
Custom.Value1 SLONG The first value associated with the event
Custom.Value2 SLONG The second value associated with the event
Custom.Value3 SLONG The third value associated with the event
Custom.Text CHAR[] Text associated with the event
Custom.Encode CHAR[] A string encoded with VARIABLE_TO_STRING encoding for
complex data types.
Custom.SourceDev DEV Source device of custom event
Custom.SourceDev.Number INTEGER Source device number
Custom.SourceDev.Port INTEGER Source device port
Custom.SourceDev.System INTEGER Source device system.
If the event handler is specified using an array for DEV, INTEGER, or a DEVCHAN array, GET_LAST
can determine which index in the array caused the event to run.
Event Parameters
It has already been stated that DEFINE_EVENT handlers are stored in an event table providing quick
access to code that must be executed when an event is received. The event table keeps a list of all events
in a sorted order to more quickly determine which code needs to be accessed for a giving incoming
event. The event table is built before DEFINE_START runs and it not changed anytime after that. As a
result, there are certain rules that must be applied to the parameters used in DEFINE_EVENTs.
Since the event table is built before DEFINE_START, all event parameters must contain the correct
information prior to DEFINE_START. This requires that all EVENT parameters must be defined at
compile time. In addition, many parameter "shortcuts" to help fulfill this requirement.
Using BUTTON_EVENT as an example, the simplest version of event parameters is a device and channel
reference. In the following example:
Example 1:
DEFINE_DEVICE
dvTp = 128:1:0
DEFINE_EVENT
BUTTON_EVENT[dvTp,1]
{
PUSH:
Send_String 0,'Button 1 of dvTp was pushed'
}
The device, dvTp, was defined in the DEFINE_DEVICE section, which has the effect of making it an
initialized variable of type DEV, and the channel number was a hard-coded value of 1. Since both of
these value were defined at compile time, the event is entered into the event table correctly. Let's take
another example:
Example 2:
DEFINE_DEVICE
dvTp = 128:1:0
DEFINE_VARIABLE
Integer nMyChannel
DEFINE_START
nMyChannel = 1
DEFINE_EVENT
BUTTON_EVENT[dvTp,nMyChannel]
{
PUSH:
Send_String 0,"'Button ',ITOA(nMyChannel),' of dvTp was pushed'"
}
In this example, the event will not perform as the previous one did. When the code is compiled, the
event parameters are dvTp, which is already assigned, and nMyChannel, which has a value of 0.
nMyChannel does not get assigned a value of 1 until DEFINE_START, at which time the event has
already been added to the event table. If you were to run this code, you would discover that it did in fact
run when button 1 was pushed, leading us to one of the "shortcuts":
<bold>
So, the reason the above example runs when button 1 was pushed is that the above example runs when
any button on dvTp is pushed. This "shortcut" was added so you could define an event handler for all
buttons, channel or levels of a device without having to define a DEVCHAN of DEVLEV containing every
value you may want to handle.
To make the example 2 behave like the example 1, we simply need to make sure the value of
nMyChannel contains a value of 1 at compile time. This is simply done by initializing nMyChannel a
value of 1 in the DEFINE_VARIABLE section. The new example reads:
Example 3:
DEFINE_DEVICE
dvTp = 128:1:0
DEFINE_VARIABLE
Integer nMyChannel = 1
DEFINE_EVENT
BUTTON_EVENT[dvTp,nMyChannel]
{
PUSH:
Send_String 0,"'Button ',ITOA(nMyChannel),' of dvTp was pushed'"
}
You may be tempted to use a more traditional variable as the channel number, mainly PUSH_CHANNEL
or RELEASE_CHANNEL. It is important to realize that the identifiers are nothing more than global
(system) variable. At compile time, the values are defined and contain a value of 0. So the following
code:
Example 4:
DEFINE_EVENT
BUTTON_EVENT[dvTp,PUSH_CHANNEL]
{
PUSH:
Send_String 0,"'Button ',ITOA(BUTTON.INPUT.CHANNEL),' of dvTp was pushed'"
RELEASE:
Send_String 0,"'Button ',ITOA(BUTTON.INPUT.CHANNEL),' of dvTp was released'"
}
Will have the effect you expect button probably for a different reason than you expect. Although the
event will run for both the push and release of all buttons for dvTp, you may also be tempted to think that
you need to make sure the event runs for RELEASE_CHANNEL by adding the following:
Example 5:
DEFINE_EVENT
BUTTON_EVENT[dvTp,PUSH_CHANNEL]
BUTTON_EVENT[dvTp,RELEASE_CHANNEL]
{
PUSH:
Send_String 0,"'Button ',ITOA(BUTTON.INPUT.CHANNEL),' of dvTp was pushed'"
RELEASE:
Send_String 0,"'Button ',ITOA(BUTTON.INPUT.CHANNEL),' of dvTp was released'"
}
However, since both PUSH_CHANNEL and RELEASE_CHANNEL have a value of 0 at compile time, you
are in effect stacking two events that are interpreted as running for any button pushed on the panel and as
a result, the event is run twice every time a button is pushed or released. This may not seem like a big
problem until you try to toggle a variable in the event: since the event runs twice for every button push,
the variable toggles on then toggles off again.
There are some additional parameter "shortcuts" available. In all cases, the following rules apply:
When a DEV can be used, a DEV array can also be used.
When a DEVCHAN can be used, a DEVCHAN array can be used.
When a DEVLEV can be used, a DEVLEV array can be used.
When a Char, Integer or Long can be used, a Char, Integer or Long array can also be
used.
You can apply more then 1 of the above rules at a time in a given event handler.
GET_LAST() can be used to determine which index of an array (any type) caused the event to
fire.
The above rules can let you write some interesting event handler. Let's say you wanted to handle 4
buttons from 6 panels all with one button event. You could write:
Example 6:
DEFINE_DEVICE
dvPanel1 = 128:1:0
dvPanel2 = 129:1:0
dvPanel3 = 130:1:0
dvPanel4 = 131:1:0
dvPanel5 = 132:1:0
dvPanel6 = 133:1:0
DEFINE_VARIABLE
DEV dvMyPanels[] = { dvPanel1, dvPanel2, dvPanel3, dvPanel4, dvPanel5, dvPanel6
}
INTEGER nMyButtons[] = { 4, 3, 2, 1 }
INTEGER nPanelIndex
INTEGER nButtonIndex
DEFINE_EVENT
Continued
BUTTON_EVENT[dvMyPanels,nMyButtons]
{
PUSH:
{
nPanelIndex = GET_LAST(dvMyPanels)
nButtonIndex = GET_LAST(nMyButtons)
Send_String 0,"'Button Index=',ITOA(nButtonIndex),' was pushed on Panel
Index=',ITOA(nPanelIndex)"
}
}
This event will be run for all combinations of dvMyPanel and nMyButtons, 24 buttons in all. The
GET_LAST() function is very useful when running event using array as parameters. GET_LAST()
returns an index value, starting at 1, for the element that triggered the event. In the case of
nButtonIndex, it will contain a value of 1 when button 4 was pressed, a value of 2 when button 3 was
pressed, ... This can be very useful in the case of transmitters and wired panels where the channel
number may not reflect a numerical sequence you would like, such as with Numeric Keypads.
Timeline Functions
The NetLinx timeline functions provide a mechanism for triggering events based upon a sequence of
times. The sequence of times is passed into the timeline functions as an array of LONG values, with each
value representing a time period (in milliseconds) that is either relative to the start time of the timeline or
to the previously triggered event.
The old way of programming timed sequences was to cascade or nest WAITs. Using nested WAITs hard-
coded the timed sequence; so, the only way to modify the timing was to modify the NetLinx program,
recompile, and download.
Timelines make adding, deleting and editing the sequence much simpler for the programmer. Timeline
functions and debugging allow the timings to be modified without the modify/ compile/ download cycle
because the array of times may be modified via NetLinx debugging. Once the timings have been
tweaked, the changes can be incorporated in the NetLinx program.
Creating a timeline
Timelines are represented by the illustration in (FIG. 2). When the TIMELINE_CREATE function is
executed, the timeline starts at zero and begins counting. When the timer value equals a value in the
TIMES array, a TIMELINE_EVENT is triggered. Within the timeline event, a TIMELINE structure is
available to get information about the specific time from the TIMES array that generated the event. When
a relative timeline is created, the NetLinx Master converts the provided relative times into absolute times
that are stored internally.
The TIMELINE structure contains the following members:
STRUCTURE TIMELINE
{
INTEGER ID //user supplied ID
INTEGER SEQUENCE //index in Times array
LONG TIME //time since start of timeline
INTEGER RELATIVE //0=absolute 1=relative
LONG REPETITION //# of loops for repeating timeline
Continued
TIMELINE_EVENT[TL1]
Triggered
TIMELINE_CREATE
Time 0
1000 2000 3000 4000 5000
Time (1mS resolution)
Timeline.Sequence = 1 2 3 4 5
}
Each TIMELINE data member is defined as follows:
ID The ID that the user assigned to the timeline in the TIMELINE_CREATE func-
tion.
SEQUENCE The index of the time in the Times array that was passed to the
TIMELINE_CREATE function. The SEQUENCE data member is used to deter-
mine what action to take for the event and is normally decoded with a SWITCH/
CASE structure (as shown in the example).
TIME The amount of time that has elapsed since the timeline started. For repeating
timelines, the TIME and REPETITION data members can be used to calculate
the total amount of time it has been running.
RELATIVE If the timeline is operating in relative mode, this data member is equal to
TIMELINE_RELATIVE. If the timeline is absolute, it is equal to
TIMELINE_ABSOLUTE.
REPETITION If the timeline was created with TIMELINE_REPEAT, this data member holds
the number of times the timeline has been executed. REPETITION contains
zero for the first pass through the timeline. Thus, the calculation to determine
the total amount of time the timeline has been running is simply:
TIMELINE.TIME * TIMELINE.REPETITION.
Return Valuess:
0 Successful
1 Timeline ID already in use
2 Specified array is not an array of LONGs
3 Specified length is greater than the length of the passed array
4 Out of memory
Example:
DEFINE_VARIABLE
LONG TimeArray[100]
DEFINE_CONSTANT
TL1 = 1
TL2 = 2
DEFINE_EVENT
Continued
DEFINE_PROGRAM
PUSH[dvPanel,1]
{
TimeArray[1] = 1000
TimeArray[2] = 2000
TimeArray[3] = 3000
TimeArray[4] = 4000
TimeArray[5] = 5000
TIMELINE_CREATE(TL1, TimeArray, 5, TIMELINE_ABSOLUTE,
TIMELINE_REPEAT)
}
PUSH[dvPanel,2]
{
TimeArray[1] = 1000
TimeArray[2] = 1000
TimeArray[3] = 1000
TimeArray[4] = 1000
TimeArray[5] = 1000
TIMELINE_CREATE(TL2, TimeArray, 5, TIMELINE_RELATIVE, TIMELINE_ONCE)
}
The example above creates two timelines (TL1 and TL2) that trigger events at the same rate (once per
second).
TL1 uses TIMELINE_ABSOLUTE to specify that the times in TimeArray are absolute with
respect to the start of the timeline. Since TL1 specifies the TIMELINE_REPEAT, it is also
repeating and will generate a TIMELINE_EVENT every second iterating through all five times
in a round-robin fashion: 1,2,3,4,5,1,2,3,4,5,1,2,3, and so on.
TL2 uses TIMELINE_RELATIVE to specify that the times in TimeArray are relative to each
other (i.e. each events occurs 1000 milliseconds after the previous). Since TL2 specifies the
TIMELINE_ONCE parameter, it will execute the entire timeline once, then stop: 1,2,3,4,5.
TIMELINE example
The following code is an example of how to use TIMELINE functions.
PROGRAM_NAME='TimelineExample'
(*{{PS_SOURCE_INFO(PROGRAM STATS) *)
(***********************************************************)
(* FILE CREATED ON: 05/22/2001 AT: 12:05:56 *)
(***********************************************************)
(* FILE_LAST_MODIFIED_ON: 05/22/2001 AT: 12:15:56 *)
(***********************************************************)
(* ORPHAN_FILE_PLATFORM: 1 *)
(***********************************************************)
(*!!FILE REVISION: *)
(* REVISION DATE: 05/22/2001 *)
(* *)
(* COMMENTS: *)
(* *)
(***********************************************************)
(*}}PS_SOURCE_INFO *)
(***********************************************************)
(***********************************************************)
(* DEVICE NUMBER DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_DEVICE
dvPanel = 128:1:0
dvDebug = 0:0:0
(***********************************************************)
(* CONSTANT DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_CONSTANT
MY_LINE_1 = 1
MY_LINE_2 = 2
(***********************************************************)
(* VARIABLE DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_VARIABLE
Continued
LONG TimeArray[100]
INTEGER iLoop
(***********************************************************)
(* STARTUP CODE GOES BELOW *)
(***********************************************************)
DEFINE_START
(***********************************************************)
(* THE EVENTS GOES BELOW *)
(***********************************************************)
DEFINE_EVENT
TIMELINE_EVENT[MY_LINE_1]
{
switch(Timeline.Sequence)
{
case 1: { SEND_COMMAND dvPanel,"'TEXT1-1 1'" }
case 2: { SEND_COMMAND dvPanel,"'TEXT1-1 2'" }
case 3: { SEND_COMMAND dvPanel,"'TEXT1-1 3'" }
case 4: { SEND_COMMAND dvPanel,"'TEXT1-1 4'" }
case 5: { SEND_COMMAND dvPanel,"'TEXT1-1 5'" }
}
SEND_STRING dvDebug,"'Timer ',ITOA(Timeline.ID),' Event
',ITOA(Timeline.Sequence), ' Time= ',ITOA(Timeline.Time),
'Repetition = ',ITOA(Timeline.Repetition),' Relative =
',ITOA(Timeline.Relative)"
}
TIMELINE_EVENT[MY_LINE_2]
{
switch(Timeline.Sequence)
{
case 1: { SEND_COMMAND dvPanel,"'TEXT2-2 1'" }
case 2: { SEND_COMMAND dvPanel,"'TEXT2-2 2'" }
case 3: { SEND_COMMAND dvPanel,"'TEXT2-2 3'" }
case 4: { SEND_COMMAND dvPanel,"'TEXT2-2 4'" }
case 5: { SEND_COMMAND dvPanel,"'TEXT2-2 5'" }
}
Continued
DEFINE_PROGRAM
(***********************************************************)
(* create will sort the order of the times but index stays *)
(* with the time. This example will execute 1 2 4 3 5 *)
(* sequence numbers *)
(***********************************************************)
PUSH[dvPanel,1]
{
TimeArray[1] = 1000
TimeArray[2] = 2000
TimeArray[4] = 3000
TimeArray[3] = 4000
TimeArray[5] = 5000
TIMELINE_CREATE(MY_LINE_1,TimeArray,5,TIMELINE_ABSOLUTE,TIMELINE_ONCE)
}
PUSH[dvPanel,2]
{
TimeArray[1] = 1000
TimeArray[2] = 2000
TimeArray[3] = 3000
TimeArray[4] = 4000
TimeArray[5] = 5000
TIMELINE_CREATE(MY_LINE_2,TimeArray,5,TIMELINE_ABSOLUTE,TIMELINE_REPEAT)
}
(***********************************************************)
(* Modify the timeline my kill, pause and restarting *)
(***********************************************************)
PUSH[dvPanel,3]
{
IF(TIMELINE_ACTIVE(MY_LINE_1))TIMELINE_KILL(MY_LINE_1)
IF(TIMELINE_ACTIVE(MY_LINE_2))TIMELINE_KILL(MY_LINE_2)
}
PUSH[dvPanel,4]
{
IF(TIMELINE_ACTIVE(MY_LINE_1))TIMELINE_PAUSE(MY_LINE_1)
IF(TIMELINE_ACTIVE(MY_LINE_2))TIMELINE_PAUSE(MY_LINE_2)
}
PUSH[dvPanel,5]
{
IF(TIMELINE_ACTIVE(MY_LINE_1))TIMELINE_RESTART(MY_LINE_1)
IF(TIMELINE_ACTIVE(MY_LINE_2))TIMELINE_RESTART(MY_LINE_2)
Continued
(***********************************************************)
(* Force time to a different value *)
(***********************************************************)
PUSH[dvPanel,6]
{
IF (TIMELINE_ACTIVE(MY_LINE_1))
TIMELINE_SET(MY_LINE_1,2000)
}
(***********************************************************)
(* Get the current time from create *)
(***********************************************************)
PUSH[dvPanel,7]
{
SEND_COMMAND dvPanel,"'TEXT3-','Timer 1 Time is
',ITOA(TIMELINE_GET(MY_LINE_1))"
SEND_COMMAND dvPanel,"'TEXT4-','Timer 2 Time is
',ITOA(TIMELINE_GET(MY_LINE_2))"
}
(***********************************************************)
(* Pause and restart the timeline at new locations *)
(***********************************************************)
PUSH[dvPanel,8]
{
TIMELINE_PAUSE(MY_LINE_1)
TIMELINE_PAUSE(MY_LINE_2)
TIMELINE_SET(MY_LINE_1,0)
TIMELINE_SET(MY_LINE_2,0)
TIMELINE_RESTART(MY_LINE_1)
TIMELINE_RESTART(MY_LINE_2)
}
(***********************************************************)
(* END OF PROGRAM *)
(* DO NOT PUT ANY CODE BELOW THIS COMMENT *)
(***********************************************************)
TIMELINE IDs
When creating a TIMELINE_EVENT, the timeline ID must be a user defined long constant. The NetLinx
compiler will not semantic check the type of the timeline ID, and the NetLinx runtime system will
attempt to cast the contents of the timeline ID constant, to a long constant. A runtime error will occur if
the cast is unsuccessful.
Here's an example of TIMELINE code:
DEFINE_VARIABLE
CONSTANT LONG TimelineID_1 = 1
CONSTANT LONG TimelineID_2 = 2
CONSTANT LONG TimelineID_3 = 3
CONSTANT LONG TimelineID_4 = 4
LONG TimeArray[4] =
{
1000, // 1 second
2000, // 2 seconds
3000, // 3 seconds
4000 // 4 seconds
}
DEFINE_START
TIMELINE_CREATE
(TimelineID_1,TimeArray,LENGTH_ARRAY(TimeArray),TIMELINE_RELATIVE,TIMELINE_REP
EAT)
TIMELINE_CREATE
(TimelineID_2,TimeArray,LENGTH_ARRAY(TimeArray),TIMELINE_RELATIVE,TIMELINE_REP
EAT)
TIMELINE_CREATE
(TimelineID_3,TimeArray,LENGTH_ARRAY(TimeArray),TIMELINE_RELATIVE,TIMELINE_REP
EAT)
TIMELINE_CREATE
(TimelineID_4,TimeArray,LENGTH_ARRAY(TimeArray),TIMELINE_RELATIVE,TIMELINE_REP
EAT)
DEFINE_EVENT
// typical TIMELINE_EVENT statement
TIMELINE_EVENT[TimelineID_1] // capture all events for Timeline 1
{
SEND_STRING 0,"'TL ID = ', itoa(timeline.id),', sequence =
',itoa(timeline.sequence)"
}
// example of "stacked" TIMELINE_EVENT statements
TIMELINE_EVENT[TimelineID_2] // capture all events for Timeline 2
TIMELINE_EVENT[TimelineID_3] // capture all events for Timeline 3
TIMELINE_EVENT[TimelineID_4] // capture all events for Timeline 4
{
SEND_STRING 0,"'TL ID = ', itoa(timeline.id),', sequence =
',itoa(timeline.sequence)"
}
// end
If you have combined Devices, Levels and/or Channels, they must be un-combined
before they can be added as part of a new COMBINE function.
DEFINE_COMBINE
(TP1, TP2, TP3)
DEFINE_PROGRAM
RELEASE[TP1,1]
{
(*Do Something*)
}
The code shown in the Axcess example will not work in NetLinx, due to
incompatibilities between the languages (i.e. Axcess does not allow virtual devices,
which are required for Combine/Uncombine operations in NetLinx).
This combines a common level to each of three devices TP1 , TP2 , and TP3 . If an input change occurs
on any of the three devices, Axcess sees the input as coming only from the first device in the list (TP1).
If button [TP2,12] is pressed, Axcess will see the input coming from [TP1,12] due to the
combination. Likewise, any output change sent to any device in the list will automatically be sent to all
devices in the list. This includes level changes. For example, the statement ON [TP1,5Ø] will turn on
channel 50 for all three devices in the list.
Now let's see how the code example shown above would translate into NetLinx.
DEFINE_COMBINE
DEFINE_DEVICE
VIRTUAL1 = 33000
TP1 = 128
TP2 = 129
TP3 = 130
DEFINE_COMBINE
(VIRTUAL1, TP1, TP2, TP3)
DEFINE_PROGRAM
RELEASE[VIRTUAL1,1]
{
(*Do Something*)
}
Note the use of the virtual device (VIRTUAL1) in the above example. Combine operations in NetLinx
require that the first device in the list (the primary device) must be a virtual device. By specifying a
virtual device as the primary device in a DEFINE_COMBINE statement, NetLinx code can be written
targeting the virtual device, but effectively operating on each physical device. Furthermore, since a
virtual device is not an actual physical device, the primary device cannot be taken off-line or removed
from the system (which avoids the potential problems that occurred in Axcess).
The virtual device's address number must be in the range of 32768 to 36863.
The example above combines the three touch panel devices: TP1, TP2 and TP3. Whenever an input
change occurs on any of the three devices, NetLinx detects the input as coming only from VIRTUAL1.
For example, if button [TP3, 5] is pressed, NetLinx sees input coming from [VIRTUAL1, 5] as a
result of the combination.
Output changes (including level changes) sent to any device in the list will automatically be sent to all
devices in the list. For instance, the statement: ON [VIRTUAL1, 50] turns on channel 50 on all three
panels and OFF [VIRTUAL1, 10] turns off channel 10 on all three panels.
The example below illustrates the use of a device array (Dev[ ]), instead of specifying the individual
devices (TP1, TP2, and TP3). Device arrays can further simplify your code and allow you to
dynamically combine/un-combine devices. Any input events for any device in the array will appear to
the program as coming from the virtual device. Output changes, directed to the virtual device or any
device in the array, are sent to all devices in the array. Here's a syntax example:
COMBINE_DEVICES (VIRTUAL1, TP1, TP2, TP3)
In addition to virtual devices and device arrays, the NetLinx language contains several new keywords for
combine and un-combine operations:
COMBINE_DEVICES, UNCOMBINE_DEVICES
COMBINE_LEVELS, UNCOMBINE_LEVELS
COMBINE_CHANNELS, UNCOMBINE_CHANNELS
Refer to the Combining and Un-Combining Levels section on page 82 for more
information.
Un-combining devices
UNCOMBINE_DEVICES reverses the effect of COMBINE_DEVICES. All combines related to the
specified virtual device are disabled. A syntax example is:
UNCOMBINE_DEVICES (VDC)
Parameters:
The following NetLinx code example illustrates combining and un-combining the panels from the
previous example:
Input and output changes occurring on non-combined panels will not affect combined
panels, and vice versa.
DEFINE_DEVICE
VIRTUAL1 = 33000
TP1 = 128
TP2 = 129
TP3 = 130
TP4 = 131
DEFINE_PROGRAM
(* Activate dynamic device combine*)
RELEASE[TP4,1]
{
COMBINE_DEVICES(VIRTUAL1, TP1, TP2, TP3)
}
(*Remove dynamic device combine*)
RELEASE[TP4,1]
{
UNCOMBINE_DEVICES(VIRTUAL1)
}
(*Pushes come here when a combine is active*)
Continued
RELEASE[VIRTUAL1,1]
{
(*Do Something*)
}
(*This will only see pushes when combine is NOT active*)
RELEASE[TP1,1]
{
(*Do Something*)
}
The code shown in the Axcess example will not work in NetLinx, due to
incompatibilities between the languages (i.e. Axcess does not allow virtual devices,
which are required for Combine/Uncombine operations in NetLinx).
DEFINE_DEVICE
TP1 = 128
TP2 = 129
TP3 = 130
DEFINE_CONNECT_LEVEL
(TP1,1, TP2,1, TP3,1)
TP1, TP2, and TP3 are devices; this example combines Level 1 on each device. If a level change occurs
on any of the three devices, Axcess sees the level coming only from the first device in the list (TP1).
Likewise, any level change sent to any device in the list will automatically be sent to all devices in the
list.
Now let's see how the code example shown above would translate into NetLinx. This is code that would
function correctly within a NetLinx system, but still uses the Axcess-based.
DEFINE_CONNECT_LEVEL
DEFINE_DEVICE
VIRTUAL1 = 33000
TP1 = 128
TP2 = 129
TP3 = 130
DEFINE_CONNECT_LEVEL
(VIRTUAL1, 1, TP1,1, TP2,1, TP3,1)
The example above combines the levels for the three touch panels: TP1, TP2 and TP3. Whenever a level
change occurs on any of the three devices, NetLinx detects the level as coming only from VIRTUAL1.
The example below illustrates the use of a device array (Dev[ ]), instead of specifying the individual
devices (TP1, TP2, and TP3). Device arrays further simplify code and allow you to dynamically
combine/un-combine levels. Any input events for any device in the array will appear to the program as
coming from the virtual device. Output changes, directed to the virtual device or any device in the array,
are sent to all devices in the array. The syntax must follow one of these two forms:
DEFINE_CONNECT_LEVEL
(Vdevice1, 1, DEVLEV [ ])
- or -
DEFINE_CONNECT_LEVEL
(VDEVLEV, DEVLEV [ ])
Combining levels
COMBINE_LEVELS connects a single device-level array (DEVLEV[ ]) to a DEVLEV array.
Any element in a DEVLEV array appears to come from the virtual device-level representing the group,
and output to any element in a DEVLEV array is directed to all elements in the group. Here's a syntax
example:
COMBINE_LEVELS (DEVLEV VDLSET, DEVLEV[ ] DLSETS)
Parameters:
VDLSET Virtual device-level. Each element will represent one device-level combine group.
DLSETS Device-level sets containing the device-level pairs to combine. Corresponding ele-
ments in each set are combined with the corresponding element in the virtual device-
level array.
Un-combining levels
UNCOMBINE_LEVELS undoes the effect of COMBINE_LEVELS. All combines related to the specified
virtual device-level are disabled.
UNCOMBINE_LEVELS (DEVLEV)
Parameters:
COMBINE_LEVELS(VDL, DLSet)
.
.
UNCOMBINE_LEVELS(VDL)
The NetLinx code example below illustrates how to dynamically combine and un-combine levels.
Input and output changes occurring on non-combined panels will not affect combined
panels, and vice versa.
DEFINE_DEVICE
VIRTUAL1 = 33000
TP1 = 128
TP2 = 129
TP3 = 130
Continued
TP4 = 131
DEFINE_PROGRAM
(*Activate dynamic level combine*)
RELEASE[TP4,1]
{
COMBINE_LEVELS(VIRTUAL1,1,TP1,1,TP2,1,TP3,1)
}
Un-combining channels
UNCOMBINE_CHANNELS reverses the effect of COMBINE_CHANNELS. All combines related to the
specified virtual device-channel are disabled.
UNCOMBINE_CHANNELS (DEVCHAN VDC)
Parameters:
.
UNCOMBINE_CHANNELS (VDC)
The 6 examples in the program below demonstrate the use of COMBINE_CHANNELS and
UNCOMBINE_CHANNELS:
PROGRAM_NAME='CombineChannelsExample'
DEFINE_DEVICE // common devices for all examples below
dvTP = 128:1:0
dvREL10 = 301:1:0
dvIO10 = 310:1:0
vdvControl = 33000:1:0
// example of combining a DEVCHAN set to a virtual [DEV,CHAN] pair
DEFINE_VARIABLE
DEVCHAN dc1[] = {{dvIO10,1},{dvREL10,1},{dvTP,1}}
DEFINE_EVENT
BUTTON_EVENT[dvTP,11] // combine_channels 1
{
RELEASE:
{
COMBINE_CHANNELS (vdvControl,1,dc1)
}
}
BUTTON_EVENT[dvTP,12] // uncombine_channels 1
{
RELEASE:
{
UNCOMBINE_CHANNELS (vdvControl,1)
}
}
BUTTON_EVENT[vdvControl,1] // this will work when the combine_channels above is
invoked
{
PUSH:
{
TO[BUTTON.INPUT]
}
}
// example of combining individual DEVCHANs to a virtual [DEV,CHAN] pair
DEFINE_VARIABLE
DEVCHAN dc2[] = {{dvIO10,2},{dvREL10,2},{dvTP,2}}
DEFINE_EVENT
BUTTON_EVENT[dvTP,13] // combine_channels 2
{
RELEASE:
{
COMBINE_CHANNELS (vdvControl,2,dc2[1],dc2[2],dc2[3])
}
}
Continued
BUTTON_EVENT[dvTP,14] // uncombine_channels 2
{
RELEASE:
{
UNCOMBINE_CHANNELS (vdvControl,2)
}
}
BUTTON_EVENT[vdvControl,2] // this will work when the combine_channels above is
invoked
{
PUSH:
{
TO[BUTTON.INPUT]
}
}
{
PUSH:
{
TO[BUTTON.INPUT]
}
}
// example of combining a DEVCHAN set to a virtual DEVCHAN
DEFINE_VARIABLE
DEVCHAN vdc4 = {vdvControl,4}
DEVCHAN dc4[] = {{dvIO10,4},{dvREL10,4},{dvTP,4}}
DEFINE_EVENT
BUTTON_EVENT[dvTP,17] // combine_channels 4
{
RELEASE:
{
COMBINE_CHANNELS (vdc4,dc4)
}
}
BUTTON_EVENT[dvTP,18] // uncombine_channels 4
{
RELEASE:
{
UNCOMBINE_CHANNELS (vdc4)
}
}
BUTTON_EVENT[vdc4] // this will work when the combine_channels above is invoked
{
PUSH:
{
TO[BUTTON.INPUT]
}
}
{
RELEASE:
{
COMBINE_CHANNELS (vdc5,dc5[1],dc5[2],dc5[3])
}
}
BUTTON_EVENT[dvTP,20] // uncombine_channels 5
{
RELEASE:
{
UNCOMBINE_CHANNELS (vdc5)
}
}
BUTTON_EVENT[vdc5] // this will work when the combine_channels above is invoked
{
PUSH:
{
TO[BUTTON.INPUT]
}
}
// example of combining individual [DEV,CHAN] pairs to a virtual DEVCHAN
DEFINE_VARIABLE
DEVCHAN vdc6 = {vdvControl,6}
DEVCHAN dc6[] = {{dvIO10,6},{dvREL10,6},{dvTP,6}}
DEFINE_EVENT
BUTTON_EVENT[dvTP,21] // combine_channels 6
{
RELEASE:
{
COMBINE_CHANNELS (vdc6,
dc6[1].DEVICE,
dc6[1].CHANNEL,
dc6[2].DEVICE,
dc6[2].CHANNEL,
dc6[3].DEVICE,
dc6[3].CHANNEL)
}
}
BUTTON_EVENT[dvTP,16] // uncombine_channels 6
{
RELEASE:
{
UNCOMBINE_CHANNELS (vdc6)
}
}
Continued
Master-To-Master (M2M)
The functionality of Master-to-Master (M2M) includes several new features including Master routing
and intersystem control. Master routing supports the ability to route messages to any other Master or
device and is the foundation of all M2M functionality. Intersystem control allows a Master, or its
NetLinx program, to control and get status of any other device (or master) connected to another Master.
The illustration below depicts a typical system of two interconnected NetLinx control systems with
several devices connected to each one.
The top portion of the illustration shows the physical connections and the devices represented. The
bottom portion shows the logical connections that have been assigned.
Master Routing
Master routing primarily involves the communication of routing tables between masters. Routing tables
are exchanged between masters upon their initial connection, and updates to the routing tables are
exchanged as the connections change.
NetLinx masters do not automatically connect to other NetLinx masters by virtue of being on the same
network. The URL List of the NetLinx master is used to force the master to initiate a TCP connection to
the specified URL/IP address. Therefore, the first step in assembling an M2M system is to setup the
URL in at least one of the masters to point to the other master. For example, in FIG. 1 NetLinx Master
System #1 could have its URL set with a single entry that contains the IP address of the NetLinx Master
System #7.
Note that any TCP/IP device, including NetLinx masters, that utilizes DHCP to obtain its TCP/IP
configuration are subject to having their IP address change at any time. Therefore, a NetLinx master's IP
address must be static, unless the network supports Dynamic DNS and a DHCP server capable of
updating the DNS tables on behalf of the DHCP client. If Dynamic DNS/DHCP servers are available, the
NetLinx master's host name may be used in the URL list. As of this writing, only Windows 2000's DNS
server/DHCP servers support the required dynamic capabilities.
Once the systems are connected, they exchange routing information so each master will learn about all
the masters connected to the other. As a diagnostic aid, the "show route" command can be issued from a
Telnet session to show how masters are connected to each other. Consider the following system of
interconnected NetLinx masters:
NetLinx Master
System #1
192.168.12.105
NetLinx Master
System #4
192.168.12.80
In FIG. 2, arrows depict the direction of the initiated connection. I.e. System #1 initiated the connection
to System #2 by having the IP address of System #2 in its URL List.
The following sample output is from a Telnet session connected to System #5. The connection of the
NetLinx system is depicted in FIG. .
>show route
System Route Metric PhyAddress
-----------------------------------------
1 2 2 TCP Socket=18 IP=192.168.12.76 Index=3
2 2 1 TCP Socket=18 IP=192.168.12.76 Index=3
3 2 2 TCP Socket=18 IP=192.168.12.76 Index=3
4 4 1 TCP Socket=16 IP=192.168.12.80 Index=1
->5 5 0 AXlink
106 106 1 TCP Socket=19 IP=192.168.12.106 Index=2
111 106 2 TCP Socket=19 IP=192.168.12.106 Index=2
-> The "->" to the left of system # 5 indicates that system # 5 is the local system
(i.e. the system that the telnet session is connected to).
System column Lists all of the systems in the master's routing table.
Route column Indicates which system number packets are to be routed to in order to get to their
destination. For example, to send a message from System #5 to System #1, the
message must be sent to/through System #2. You can see this by examining the
Route entry for System #1 in the "show route" table.
Metric column Indicates the number of system masters the message must transverse in order to
get to its destination. For the example above, the metric is 2 because the message
must enter System #2, then System #1. Note that a metric of 16 indicates a "dead"
route (i.e. a "dead" route is a route that used to exist but is no longer valid). Fur-
thermore, since the maximum usable metric is 15, there is a limit of 16 masters in
the width plus height of the master topology (see the Design considerations and
constraints section on page 93).
PhyAddress column Indicates the internal connection parameters used by the master to maintain the
connection information.
The end result of the routing and connection data is that any device or master can communicate with any
other device or Master, regardless of the physical connection of the device. Note that Masters may only
be "connected" to each other via Ethernet/TCP/IP. As an example (FIG. 1 on page 91), NetLinx Studio is
running on a PC connected to System #7 as device number 32002. The routing capabilities of the
NetLinx Master allow NetLinx Studio to download IR codes to the NXC-IRS4 (S=7 D=24), a master
firmware upgrade to NetLinx Master #1, and new touch panel pages to the touch panel on Master #1. All
of this is possible simply by having NetLinx Studio connected to a NetLinx Master with M2M firmware.
Design considerations and constraints
The routing metric limit of 15 usable hops imposes some constraints on the topology of the
interconnected NetLinx masters. While the limit of 15 hops may seem very limiting, this is not really the
case if you carefully architect the topology. FIG. 3 illustrates a single dimensional view of the 15-hop
limit.
FIG. 3 Single dimensional view of the maximum number of interconnected NetLinx masters
This shows a maximum of 15 masters connected to each other, such that any master is routeable to any
other master. FIG. 4 expands FIG. 3 into two dimensions and takes advantage of the fact that each
NetLinx master supports multiple connections to masters.
FIG. 5 shows that a maximum of 512 systems can be interconnected using a three-dimensional
interconnection topology. Note that for the diagram in FIG. 4 that a single NetLinx master may have up
to eight connections to remote masters (all 6 sides of the "box" plus two diagonal connections).
The maximum number of TCP/IP connections supported by a single master is 200 simultaneous TCP/IP
connections.
Another possible connection topology is to establish communication hubs that optimize the traffic with
adjacent masters but still allows connections to other masters, as shown in FIG. 6.
When determining the interconnection topology of many NetLinx Masters, special consideration should
be made to the Masters that communicate large amounts of information with each other. Thus, if you
have 2 systems that share devices, control, or information, they should be side-by-side in the topology,
not at opposite ends of the connection Matrix where each message is forced to pass through several
NetLinx Masters.
Mainline
Mainline is the program section executed continuously by the NetLinx Central Controller as long as the
Controller has power. DEFINE_PROGRAM contains the code known as mainline.
A typical NetLinx program is composed of a number of different sections. Each section defines some
aspect of a program such as device definitions, variable declarations, channel characteristics, or event
processing. The sections that can comprise a NetLinx program are listed in the following table.
Program Sections
DEFINE_DEVICE DEFINE_MUTUALLY_EXCLUSIVE
DEFINE_COMBINE DEFINE_TOGGLING
DEFINE_CONSTANT DEFINE_CALL
DEFINE_TYPE DEFINE_FUNCTION
DEFINE_VARIABLE DEFINE_START
DEFINE_CONNECT_LEVEL DEFINE_EVENT
DEFINE_LATCHING DEFINE_PROGRAM
Not all of the sections listed above are required to create a complete program. In an Axcess system, only
DEFINE_PROGRAM is required. In a NetLinx system, either DEFINE_PROGRAM or DEFINE_EVENT is
required. Other sections are required only to support code in one of these two sections, although the
Compiler might require more.
Axcess communication updates occur only between passes through mainline (or after each iteration
through LONG_WHILE loops). This places timing constraints on mainline processing in order for the
system to operate properly. NetLinx avoids these constraints by processing network activity through a
separate thread of execution. Bus activity is serviced concurrently with event processing and mainline
execution.
The event processing that previously could occur only through mainline code can now be handled
through code in the DEFINE_EVENT section. This provides a more efficient mechanism for processing
events; mainline does not have to be traversed to process a single I/O request. A handler can be defined
for processing device-specific events, as well as, providing feedback for the device initiating the event
notification. If a handler is present, mainline will not be called to process the event; the handler is called
instead. Once the handler completes its execution, the system is ready to process the next input message.
When no more messages are pending, mainline runs. In effect, mainline becomes an idle time process.
With the addition of the DEFINE_EVENT section for processing events, the role of mainline in a NetLinx
program becomes greatly diminished if not totally eliminated. Programs can still be written using the
traditional technique of processing events and providing feedback through mainline code. However,
programs written using the event table structure, provided in the NetLinx system, will likely run faster
and be much easier to maintain.
FIG. 1 illustrates message and mainline processing as it appears in the NetLinx system. Note that bus
servicing is taken care of by a separate process thread (Connection Manager & Message Dispatcher)
and, therefore, is not a task that must follow mainline.
Reserved Identifiers
Compiler Directives
The compiler directives supported by NetLinx are described in the table below.
Compiler Directives
#DEFINE This directive defines a symbol to be used only by #IF_DEFINED and
#IF_NOT_DEFINED directives.
#DEFINE <symbol> [<constant expression>]
The name of the symbol must be unique among all other identifiers in the pro-
gram. The symbol can be defined anywhere in the program file but cannot be
used in any statement that appears before it is defined.
Example:
#DEFINE STRING_1 `Hello World`
#DEFINE STRING_2 "`Hello Letter `,65"
#DEFINE STRING_3 "65,66,67,68,69,70"
DEFINE_PROGRAM
PUSH[TP,1]
{
send_string 0,STRING_1 // This will send out `Hello
World`
DEFINE_VARIABLE
SLONG Var1, Var2
DEFINE_START
Var1 = -1
DEFINE_PROGRAM
Var2 = ABS_VALUE(Var1) // Var2 = 1
ACTIVE See SELECT...ACTIVE on page 148.
tstStr = 'ALEXERICRYAN'
ulError = COMPARE_STRING ( tstStr, 'ALEXERICRYAN' )
if ( ulError == 0 )
SEND_STRING dvDebug, 'ALEXERICRYAN !=
ALEXERICRYAN...BAD!'
else
SEND_STRING dvDebug, 'ALEXERICRYAN == ALEXERICRYAN'
tstStr = 'ALEXERICRYAN'
ulError = COMPARE_STRING ( tstStr, 'ALEX????RYAN' )
if ( ulError == 0 )
SEND_STRING dvDebug, 'ALEXERICRYAN !=
ALEX????RYAN...BAD!'
else
SEND_STRING dvDebug, 'ALEXERICRYAN == ALEX????RYAN
Another example of a use for this feature is if you want an event to occur every
hour. You would enter a time string that would contain a '??;00 ;00' (hours/
minute/sec) for the recurring event that in this case would occur every hour.
Result: The returned result can only be True (1) or False (0).
• 0 = the strings don't match
• 1 = the strings are the same
CONSTANT This keyword is used as part of a variable declaration to specify that the variable
cannot be changed at run-time. If a variable is declared with this keyword, it
must be initialized in its declaration.
CREATE_BUFFER This keyword creates a buffer and can only appear in the DEFINE_START
section of the program.
CREATE_BUFFER DEV, Buffer
CREATE_BUFFER directs NetLinx to place any strings received from the speci-
fied device into the specified buffer (character array). When strings are added
to the buffer, the length of the buffer is automatically adjusted. If the buffer is
full, all bytes in the buffer are shifted to make room for the new string. A buffer
can be manipulated in the same way as a character array.
DEFINE_TOGGLING
[RELAY,SCREEN_UP][RELAY,SCREEN_DOWN]
The last entry specifies a set of mutually exclusive variables - VCR_SELECT,
CD_SELECT, and CASS_SELECT. If any one of the three variables is turned on
(e.g., "ON [VCR_SELECT]") the other two are turned off.
If a channel is defined to be both mutually exclusive and latching, it has the
same behavior described above except that the channel stays on even after the
button that activated it is released. Theoretically, a channel in a mutually exclu-
sive latching set cannot be turned off without activating another channel in the
same set. In NetLinx, you can bypass this rule by using TOTAL_OFF. The
TOTAL_OFF function turns a channel or variable off. Unlike OFF, TOTAL_OFF
turns off the status of a channel or variable that is in a mutually exclusive set.
DEFINE_PROGRAM This keyword defines the mainline code, which is executed continuously to pro-
cess input and to provide device feedback.
See the Mainline section on page 97 for more information.
DEFINE_START This keyword contains instructions that are executed once at program startup;
in other words, at power-up or after a system reset.
DEFINE_TOGGLING When a channel is defined as mutually exclusive and latching, there is no way
to turn off the channel without activating another. Mutually exclusive toggling
allows a channel to be turned on or off by successive presses of the same but-
ton, like a normal latching channel. The channel is still affected by its mutually
exclusive characteristics; if the channel is on, it can be turned off by activating
another channel in the set. The status of a mutually exclusive toggling button
operates the same way as a mutually exclusive latching button.
In order to make a channel toggling, it must be defined as both mutually exclu-
sive and toggling, as shown below:
DEFINE_MUTUALLY_EXCLUSIVE([RELAY, SCREEN_UP], [RELAY,
SCREEN_DOWN])DEFINE_TOGGLING[RELAY, SCREEN_UP][RELAY,
SCREEN_DOWN]
DEFINE_TYPE This keyword section defines custom data types such as structures and arrays.
An example DEFINE_TYPE section is shown below.
DEFINE_TYPE
STRUCTURE MyStruct
{
LONG Num
CHAR Name[30]
}
See the Data Types section on page 10 for a discussion of structures.
CHAR Buffer[1024]
SLONG NumFiles = 1
LONG Entry = 1
BUTTON_EVENT[dcMyDCSet]
{
PUSH:
{
Index = GET_LAST(dcMyDCSet)
Switch (Index)
{
Case 1: {} (* Button 5 was pressed *)
Case 2: {} (* Button 4 was pressed *)
Case 3: {} (* Button 3 was pressed *)
Case 4: {} (* Button 2 was pressed *)
Case 5: {} (* Button 1 was pressed *)
}
}
}
Result:
• 0: No Event was triggered using this array.
• >0: The index that causes an event to be triggered.
Since the PUSH and RELEASE keywords can be written using DEVCHAN arrays,
this function can also be used to determine which element causes a push or
release to be triggered. The function can be called anywhere in code but is usu-
ally called from within an event handler. A classic application of this function is
to determine the keypad number pressed when the channel codes for the key-
pad are out of order, which they typically are for a wireless transmitter.
Note: GET_LAST works with BUTTON_EVENTS and CHANNEL_EVENTS, but not
with LEVEL_EVENTS.
The Flags member is a bit field that is used for several different purposes.
Each bit is defined in the table below:
Len = LENGTH_ARRAY(My3Darray)
(* Len = 2, the number of tables *)
Len = LENGTH_ARRAY(My3Darray[2])
(* Len = 1, the number of rows in table 2 *)
Len = LENGTH_ARRAY(My3Darray[1][3])
(* Len = 3, the number of columns in table 1, row 3 *)
STRUCTURE _AlbumStruct
{
LONG lTitleID
CHAR sArtist[100]
CHAR sTitle[100]
}
DEFINE_VARIABLE
_AlbumStruct MyAlbumStruct[3]
LONG lPos
SLONG slReturn
SLONG slFile
SLONG slResult
CHAR sBinaryString[10000]
CHAR sXMLString[50000]
DEFINE_START
MyAlbumStruct[1].lTtleID = 11101000
MyAlbumStruct[1].sArtist = ‘Buffet, Jimmy’
MyAlbumStruct[1].sTitle = ‘Living & Dying in ¾ Time’
MyAlbumStruct[2].lTtleID = 11101012
MyAlbumStruct[2].sArtist = ‘Sinatra, Frank’
MyAlbumStruct[2].sTitle = ‘Come Fly With Me’
MyAlbumStruct[3].lTtleID = 33101000
MyAlbumStruct[3].sArtist = ‘Holiday, Billie’
MyAlbumStruct[3].sTitle = ‘Lady in satin’
// Convert To XML
lPos = 1
slReturn = VARIABLE_TO_XML(MyAlbumStruct, sXMLString, lPos,
0)
SEND_STRING 0,"’POSITION=’,ITOA(lPos),’ – Result =
‘,ITOA(slReturn)"
}
// Convert To Binary
lPos = 1
slReturn = STRING_TO_VARIABLE(MyAlbumStruct, sBinaryString,
slPos)
// OR Convert To XML
slPos = 1
slReturn = XML_TO_VARIABLE (MyAlbumStruct, sXMLString, slPos,
0)
VOLATILE This keyword is used as part of a variable declaration to specify that storage
space for the variable be allocated in volatile memory. Variables stored in vola-
tile memory are not retained when the system is powered-down, as are vari-
ables stored in non-volatile memory. The trade-off is that volatile memory is
generally more plentiful and therefore a good choice for storing large data
arrays.
WAIT This keyword delays execution of one or more statements for a specified
period of time. The syntax is:
WAIT time ['<name>']
{
(* wait statements *)
}
For more information, refer to the Waits section on page 36.
WAIT_UNTIL This keyword is used to delay execution of one or more statements until a
specified condition is met. The syntax is:
WAIT_UNTIL <condition> ['<name>']
{
(* wait statements *)
}
For more information, refer to the Waits section on page 36.
WHILE This keyword executes its statement block as long as its associated condition
evaluates to true. The condition is evaluated before the first pass through the
statements. Therefore, if the conditional expression is never true the condi-
tional statements will never be executed.
WHILE (<conditional expression>)
{
(* conditional statements *)
}
Refer to the discussion on programming loop constructs under the Language
Elements section on page 31 for more information.
WIDECHAR This keyword is an intrinsic data type representing a 16-bit unsigned integer.
This data type is intended for use with Unicode character strings.
STRUCTURE _AlbumStruct
{
LONG lTitleID
CHAR sArtist[100]
CHAR sTitle[100]
}
DEFINE_VARIABLE
_AlbumStruct MyAlbumStruct[3]
LONG lPos
SLONG slReturn
SLONG slFile
SLONG slResult
CHAR sBinaryString[10000]
CHAR sXMLString[50000]
DEFINE_START
MyAlbumStruct[1].lTtleID = 11101000
MyAlbumStruct[1].sArtist = ‘Buffet, Jimmy’
MyAlbumStruct[1].sTitle = ‘Living & Dying in ¾ Time’
MyAlbumStruct[2].lTtleID = 11101012
MyAlbumStruct[2].sArtist = ‘Sinatra, Frank’
MyAlbumStruct[2].sTitle = ‘Come Fly With Me’
MyAlbumStruct[3].lTtleID = 33101000
MyAlbumStruct[3].sArtist = ‘Holiday, Billie’
MyAlbumStruct[3].sTitle = ‘Lady in satin’
DEFINE_EVENT
BUTTON_EVENT[TP,1] / /Convert And Save
{
// Convert To XML
lPos = 1
slReturn = VARIABLE_TO_XML(MyAlbumStruct, sXMLString, lPos,
0)
SEND_STRING 0,"’POSITION=’,ITOA(lPos),’ – Result =
‘,ITOA(slReturn)"
}
RELEASE:
{
}
}
BUTTON_EVENT[TP,2] // Read and Decode
{
PUSH:
{
// Read Binary File
slFile = FILE_OPEN(‘BinaryEncode.xml’,1)
slResult = FILE_READ(slFile, sBinaryString,
MAX_LENGTH_STRING(sBinaryString)
slResult = FILE_CLOSE (slFile)
// Convert To Binary
lPos = 1
slReturn = STRING_TO_VARIABLE(MyAlbumStruct, sBinaryString,
slPos)
// OR Convert To XML
slPos = 1
slReturn = XML_TO_VARIABLE (MyAlbumStruct, sXMLString,
slPos, 0)
}
RELEASE:
{
}
Send_Commands
Two new Send_Commands have been added to the master. The new commands enable or disable a feature
that helps synchronize level values. By default, this feature is disabled for compatibility reasons.
The synchronization algorithm works by setting the level value of a level five seconds after receiving a
level value from a level. While it may not be apparent, this makes sure that level values remain in sync
with each other if they ever get out of sync. The only way levels could ever get out of sync is when the
situation of "dueling levels" arises. A typical example of "dueling levels" is when two touch panels with
active sliders are combined with a volume control. If one slider attempts to raise the volume level while
the other is attempting to lower the volume level the level value bounces back and forth somewhere
between the desired levels. If both sliders are released at the exact same time, it is possible that one of
level values displayed on the touch panel's slider is inaccurate. The level synchronization algorithm
corrects the incorrect level five seconds after activity ceases.
The commands are ~LEVSYNCON and ~LEVSYNCOFF are sent to the level that should have the
synchronization algorithm enabled or disabled. The command itself is never sent to the device because
the master intercepts the command and processes it internally. Both commands accept a single parameter
that specifies the level number.
Using the "dueling levels" example above, the following send commands will turn on the
synchronization algorithm for level #1 of Touch Panel 1, level #4 of touch panel #2, and level #2 of the
volume control.
SEND_COMMAND dvTouchPanel1,'~LEVSYNCON 1'
SEND_COMMAND dvTouchPanel2,'~LEVSYNCON 4'
SEND_COMMAND dvVolume,'~LEVSYNCON 2'
Note that for some devices, turning the level synchronization algorithm on can cause undesired results.
The undesired results will vary from device to device so it is difficult to indicate any specific failure
mode. Keep in mind that the algorithm should only be turned on when necessary.
Also note that the LEVSYNCON and LEVSYNCOFF Send_Commands may not be sent to remote devices
(devices that belong to other systems) and only the device's master may issue these commands.
DEFINE_MUTUALLY_EXCLUSIVE and Variables
Symptom: If you have a set of variables that are mutually exclusive and you set one of the variables to a
non-zero value by assignment, e.g. Var1 = 1 or the Studio Debug window, then the other variables in the
set stay "on" i.e. non-zero.
DEFINE_VARIABLE
INTEGER var[4]
INTEGER x
DEFINE_MUTUALLY_EXCLUSIVE
(var[1],var[2],var[3],var[4])
DEFINE_PROGRAM
WAIT 20
{
x++; IF (x > 4) x = 1;
var[x] = x // This will not invoke the mutually exclusive magic
}
In the NetLinx code example above, all elements of var will eventually be non-zero.
Axcess does not support making elements of an INTEGER array mutually exclusive.
Compiler Messages
Compiler Warnings
Sometimes the compiler generates a warning message instead of an error message; these warning
messages always start with w. A warning about a particular statement means that the statement is not
technically an error, but you should be careful doing it. Warnings, unlike errors, do not stop the program
from compiling.
(w) Cannot assign unlike types
This warning occurs when a variable or value of one type is assigned to a variable of a different type.
Here are some examples:
Assigning a string literal, string expression, or array to a non-array variable
Assigning a non-array variable to an entire array
Assigning an integer array to a non-integer array
Assigning a two-dimensional array to a one-dimensional array, or vice versa
Assigning the result of a function that returns an array type to a non-array variable or to a two-
dimensional array variable (for example, X = ITOA(12), where X is a non-array variable or
two-dimensional array variable)
Assigning the result of a function that returns a non-array type to a one- or two-dimensional
array variable (for example, X = ATOI('AMX'), where X is a one- or two-dimensional array
variable)
This message is a warning and not an error, because X = ITOA(12) works correctly when X is a simple
variable, since the result is a single value between Ø and 65,535.
(w) Define_Call is not used
This warning occurs at the end of program compilation for each DEFINE_CALL subroutine that was
declared but never used.
(w) Integer applies to arrays only
This warning appears when the keyword INTEGER is applied to a non-array type of variable.
Doing this is not an error, because non-array variables are already integers, but it is redundant.
(w) Long_While within While
This warning occurs if the compiler finds a LONG_WHILE or MEDIUM_WHILE inside a block of
code following a WHILE keyword.
This warning exists because the WHILE command has a 1/2 second timeout period, and the
LONG_WHILE and MEDIUM_WHILE keywords do not. This could create a hard-to-find logic error.
The solution is to change the WHILE to a LONG_WHILE.
(w) Possibly too many nested levels
This warning appears if there is a large amount of nesting in the program. This can happen with a long
chain of IF...ELSE IF statements.
The solution is to use the SELECT...ACTIVE set of statements.
Compiler Errors
The compiler informs you when it finds an error during the compilation process. Most of the time these
errors occur due to a typographical error or incorrect syntax of a particular command. Unlike warnings,
errors must be corrected before your NetLinx program can be executed. The table below lists all of the
messages that can occur when there is an error during the compilation of your program.
A "<symbol>" was expected
The compiler is expecting a certain symbol at this particular place.
ACTIVE keyword expected
An ACTIVE keyword is not present after a SELECT keyword.
Allowed only in DEFINE_START
A keyword that is only allowed to appear in the DEFINE_START section of the program was
encountered elsewhere.
Attempted CALL to undefined subroutine
A CALL statement refers to a subroutine that has not been defined with a DEFINE_CALL statement.
Comment never ends, EOF encountered
A comment begins but never ends. Place a close comment, * ) at the end of the unfinished comment.
Conditional compile nesting too deep
There are too many nested #IF_DEFINED or #IF_NOT_DEFINED conditional compilation statements.
The limit is 20 nested conditional compilation statements.
Constant type not allowed
A constant value was declared as latching, toggling, or mutually exclusive, as shown below:
DEFINE_CONSTANT
PLAY = 1
DEFINE_LATCHING
PLAY (* Error: PLAY is a constant *)
Run-Time Errors
In many cases, a program is compiled and sent to the Central Controller error-free, but the system does
not act in the way it should. If the program code is correct, you should check for run-time errors. These
errors occur in the Central Controller, usually when it could not perform a particular operation in the
program.
Bad assign 2dim...
These errors occur if an attempt is made to assign a two-dimensional array to a different type (such as a
variable or one-dimensional array), and vice versa.
All Unicode string literals must be wrapped in the _WC macro. Failing to wrap a Unicode string in the
_WC macro will result in a compiler error.
Storing a Unicode String
Unicode strings are stored in WIDECHAR arrays, similar to the way ASCII strings are stored in CHAR
arrays. To define a WIDECHAR constant or variable and initialize it using a Unicode string literal, use
the following syntax:
WIDECHAR wcMyString[] = _WC('My String')
The "wc" prefix is Hungarian notation for widechar. This is simply a programming
convention and is completely optional. Hungarian notation helps you better identify
your variables while you are programming and is a general recommended standard.
For more information, see Wikipedia's Hungarian Notation page.
The string functions defined for CHAR arrays have been defined for WIDECHAR array for use in
Unicode programming. These functions allow you to operate on strings similar to the way you would
with CHAR array. For instance, to remove the first 3 characters from a WIDECHAR array and return
those characters as a WIDECHAR array, use WC_GET_BUFFER_STRING:
wcRemoved = WC_GET_BUFFER_STRING(wcMyString,3)
You will find that most other function work exactly as their CHAR counterpart do except they work on
and return WIDECHAR arrays. The list of Unicode compatible functions is:
WC_COMPARE_STRING
WC_GET_BUFFER_CHAR
WC_GET_BUFFER_STRING
WC_LEFT_STRING
WC_FIND_STRING
WC_LENGTH_STRING
WC_LOWER_STRING
WC_MAX_LENGTH_STRING
WC_MID_STRING
WC_REMOVE_STRING
WC_RIGHT_STRING
WC_SET_LENGTH_STRING
WC_UPPER_STRING
If you attempt to concatenate Unicode strings or WIDECHAR arrays using NetLinx string expressions,
expect data loss.
Converting between WIDECHAR and CHAR
On occasion, you may need to convert a CHAR array to a WIDECHAR array or a WIDECHAR array to
a CHAR array. The CH_TO_WC and WC_TO_CH functions can be used to accomplish these
conversions. For example:
wcMyString = CH_TO_WC('Any ASCII string')
wcMyString = CH_TO_WC(cMyString)
When converting from WIDECHAR to CHAR, Unicode characters are converted to '?'. Any ASCII or
extended ASCII characters, i.e. 8-bit characters, contained in the WIDECHAR array will appear in the
CHAR array. Converting from CHAR to WIDECHAR never results in loss of data.
Using FORMAT
The NetLinx Unicode library does not include a Unicode compatible FORMAT function. In NetLinx, the
format function is used to convert numbers to text. To use FORMAT with Unicode string, use
FORMAT to convert the number to a CHAR array and then use CH_TO_WC and
WC_CONCAT_STRING to combine the result with an existing WIDECHAR array. The following two
syntaxes are functionality equivalent:
fTemperature = 98.652
cMyString = FORMAT('The current temperature is %3.2f',fTemperature)
fTemperature = 98.652
cTempString = FORMAT('%3.2f',fTemperature)
wcMyString = _WC('The current temperature is ')
wcMyString = WC_CONCAT_STRING(wcMyString,CH_TO_WC(cTempString))
Right-to-left languages are not stored differently than left-to-right languages, they are simply rendered
differently than right to left languages.
However, note that the functions WC_LEFT_STRING and WC_RIGHT_STRING remove a number of
characters from the start and end of a string respectively. Using WC_LEFT_STRING on a right-to-left
language will return the number of right-most, i.e. first, characters you requested, not the left-most, i.e.
end, characters.
WC_LEFT_STRING returns the number of characters request from the front of the string and
WC_RIGHT_STRING return the number of characters requested from the end of the string, regardless
of the language's orientation.
Compiler Errors
The most common type of compiler errors you will encounter while programming for Unicode are
caused by not wrapping Unicode string literals in _WC, passing a WIDECHAR to a function that take a
CHAR array or passing a CHAR array to a function that takes a WIDECHAR array.
If you forget to wrap a Unicode string in _WC, expect to see the following compiler error:
On the line where the string is defined:
C10571: Converting type [string] to [WIDECHAR]
If you try to pass a CHAR array to a function that expects a WIDECHAR array, expect to see the
following compiler error:
On the line where the function call is made
C10585: Dimension mismatch: [1] vs. [0] and Type mismatch in call for parameter
[WCDATA]
If you try to pass a WIDECHAR array to a function that expects a CHAR array, expect to see the
following compiler error:
On the line where the function call is made
C10585: Dimension mismatch: [1] vs. [0] and Type mismatch in call for parameter [A]
IP Communication
Clients and servers communicate via Internet Protocol (IP) using either a connection-oriented or
connection-less protocol.
Connection-oriented input/output (I/O) channels require a connection or virtual circuit to be established
between the client and server before data can be transmitted or received. Transmission Control Protocol
(TCP) is the transport protocol typically used for connection-oriented I/O. With TCP, delivery of the data
is guaranteed.
With connection-less I/O, a connection is not established between the client and server before data is
exchanged. Instead, the identity of the client and server is established each time data is sent or received.
This type of communication is usually recommended for applications that transfer only small amounts of
data. User Datagram Protocol (UDP) is the transport protocol used for connection-less I/O. With UDP,
delivery of the data is not guaranteed.
Both the client and server must be able to identify incoming and outgoing data for a particular
conversation. To achieve this, each application assigns a unique number to the conversation. This number
is the local port number. A local port is not a physical port but rather a virtual port that identifies the
source or destination for data exchanged during the conversation. Local ports are specific to either the
client or the server; they need not match across applications.
The application assigns the number for the local port - as opposed to letting the system assign it (for
instance, as the return value for IP_CLIENT_OPEN or IP_SERVER_OPEN) - to satisfy the static nature
of DEFINE_EVENT handlers. All event handlers must specify a device, port, and system to identify the
events' source. This device information must be constant; that is, it cannot change at run-time. A constant
IP device specification can be defined using a local port number. For example:
Device Number = 0 The master
Port = LocalPort The local port number
System = 0 This system (where the application is running)
A range of numbers is reserved for local port numbers to make sure that this IP device-naming
convention does not interfere with future naming schemes. The program can only assign local port
numbers at or above the value of the keyword, FIRST_LOCAL_PORT. All port numbers below
FIRST_LOCAL_PORT are reserved for future use. For example:
DEFINE_CONSTANT
PORT_REMOTE_MASTER1 = FIRST_LOCAL_PORT
PORT_REMOTE_MASTER2 = FIRST_LOCAL_PORT + 1
PORT_REMOTE_MASTER3 = FIRST_LOCAL_PORT + 2
Client Programming
Initiating a conversation
To initiate a conversation with a server, the client must use the IP_CLIENT_OPEN command and supply
either the IP address or domain name of the server and a port number for the requested service. The
client must also specify a local port number to use for sending and receiving data. This number
represents a virtual port on the client machine; it is not the actual port number used to create the client-
end socket. A local port number may not be used in another call to IP_CLIENT_OPEN until
IP_CLIENT_CLOSE is called for that port number. The syntax is shown below:
IP_Client_Open(LocalPort, ServerAddress, ServerPort, Protocol)
Parameters:
LocalPort: A user-defined, non-zero integer value representing the virtual port on the client
machine that will be used for this conversation. This port number must be passed to
IP_CLIENT_CLOSE to close the conversation.
ServerAddress: A string containing either the IP address (in dotted-quad-notation) or the
domain name of the server to connect to.
ServerPort: The port number on the server that identifies the program or service the client is
requesting.
Protocol: The transport protocol to use (1 = TCP, 2 = UDP). If this parameter is not specified,
TCP (1) is assumed. The constants IP_TCP and IP_UDP can be used to specify this
parameter.
Terminating a conversation
To terminate a conversation, you must use the IP_CLIENT_CLOSE command and pass the number of
the local port used for the conversation. The syntax:
IP_Client_Close(LocalPort)
Parameters:
LocalPort: A user-defined, non-zero integer value representing the virtual port on the client
machine that will be used for this conversation.
Sending data
To send data to the server, use the SEND_STRING command.
SEND_STRING 0:LocalPort:0, '<string>'
Parameters:
Device is (or contains as part of an array) the device representing the conversation
(0:LocalPort:0)
When using IP sockets in NetLinx, it is not uncommon to create a buffer using a CREATE_BUFFER
keyword and processing the buffer in the DATA_EVENT...OFFLINE event.
Netlinx has an important behavior than can affect the performance of IP socket code. This is not a bug
but a feature. If you are aware of it, you can write your code to take maximum advantage of the speed
NetLinx offers.
When processing string data from a device, whether it is a regular device or an IP socket, the master will
attempt to copy this data to a buffer, if one has been created using the CREATE_BUFFER keyword, and
then try to run a DATA_EVENT…STRING handler for this device. If a DATA_EVENT…STRING handler
does not exists, Netlinx will run mainline to allow for any buffer processing that might occur in mainline.
At the end of a conversation with an IP device, there will usually be an incoming string event followed
by an offline event. The NetLinx master will copy the string to a buffer, if it exists, check for a string
event handler, run mainline if one does not exist, then process the offline event.
If you are processing that data in an offline event for an IP device, you will see a time delay between the
IP device or server closing the connection and the processing of the offline event. This delay will vary
with the size and complexity of mainline.
To eliminate this delay, simply include and empty string event handler in the DATA_EVENT section. This
will keep NetLinx from running mainline between the last incoming string and the offline event. See this
example:
DATA_EVENT[dvIP]
{
OFFLINE:
{
(* PROCESS THE DATA HERE*)
}
STRING:
{
(* DO NOT REMOVE ME! *)
}
}
Server Programming
Listening for client requests
A client gains access to a service by sending a request to the server specifying the port assigned to the
service. For the request to be acknowledged, the server must be listening on that port. To do this, the
server calls IP_SERVER_OPEN. This opens the port and allows the server to listen for requests from
client applications.
IP_SERVER_OPEN requires the caller to supply a local port number. This local port number is a virtual
port, as opposed to an actual physical port on the server. When TCP is the transport protocol, the local
port represents a single client connection on the server's physical port. When UDP is the transport
protocol, it represents a single point where all client requests on the associated port are routed.
The local port number is the key to identifying data sent to or received from a client application. A local
port number may not be used in another call to IP_SERVER_OPEN, until IP_SERVER_CLOSE is called
for that port number. The syntax:
IP_SERVER_OPEN(LocalPort, ServerPort, Protocol)
Parameters:
LocalPort: The local port number to open. This port number must be passed to
IP_CLIENT_CLOSE to close the conversation.
ServerPort: The port number on the server identifies the program or service the client is
requesting.
Protocol: The transport protocol to use (1 = TCP, 2 = UDP). If this parameter is not specified,
TCP (1) is assumed. The constants IP_TCP and IP_UDP can be used to specify this
parameter.
Multiple client connections
With connection-oriented I/O (TCP), more than one client could request a connection with the server at
the same time. Support for multiple client connections applies only to connection-oriented I/O, that is,
TCP protocol. Opening multiple ports using UDP as the protocol serves no purpose. In that case, any
additional open commands will fail.
To support concurrent requests, the server must call IP_SERVER_OPEN once for each simultaneous
connection allowed. For example:
IP_SERVER_OPEN (First_Local_Port, 10510, IP_TCP)
IP_SERVER_OPEN (First_Local_Port, 10510, IP_TCP)
IP_SERVER_OPEN (First_Local_Port, 10510, IP_TCP)
This allows three simultaneous connections on port 10510. Note that each call to IP_SERVER_OPEN
uses a different local port number.
Closing a local port
To close a local port, the server application must call IP_SERVER_CLOSE. Once that is called, no
I/O can be handled using the specified local port. The syntax:
IP_SERVER_CLOSE(LocalPort)
Parameters:
LocalPort: The local port number to close.
Connection-oriented notifications
The server receives the following notifications when a client connects or disconnects. The protocol in
this case must be TCP.
DATA[0:LocalPort:0]
{
ONLINE:
{
// client has connected
}
OFFLINE:
{
// client has disconnected
}
}
Parameters:
Device is (or contains as part of an array) the device representing the conversation
(0:LocalPort:0).
Receiving data
To receive data from a client, use a DATA event handler or a buffer created with CREATE_BUFFER or
CREATE_MULTI_BUFFER. If an event handler is used, the data is located in the Text field of the DATA
object. The syntax:
Data_Event[Device]
{
STRING:
{
// process incoming string (Data.Text)
}
}
Parameters:
Device is (or contains as part of an array) the device representing the conversation
(0:LocalPort:0).
Sending data
To send data to the client, use the SEND_STRING command.
SEND_STRING 0:LocalPort:0, '<string>'
Now, if you were also writing the code for 192.168.0.1, you would need to have opened a UDP server
using the following:
IP_SERVER_OPEN(dvUDPServer,6000,IP_UDP)
When the message is received at 192.168.0.1, the message will be delivered to the DATA_EVENT for
dvUDPServer and the IP address UDP/IP port of the sender of the message will be available in the
DATA.SOURCEIP and DATA.SOURCEPORT variables. A UDP (2) socket would be used in this case to
send a response to the client. Since we will no longer need to listen after sending the response, since
there would be no response to the response, we would open the socket using the following:
IP_CLIENT_OPEN(dvUDPClient,DATA.SOURCEIP,DATA.SOURCEPORT,IP_UDP)
Note that UDP with Receive (3) is only available when calling IP_CLIENT_OPEN.
Multicast
NetLinx can send and receive multi-cast UDP messages. To send a multi-cast UDP message, all you
need to do is specify a multi-cast address and port in the IP_CLIENT_OPEN function such as the
following:
IP_CLIENT_OPEN (dvIPClient.Port,'239.255.255.250',1900,IP_UDP)
To receive multi-cast UDP messages, you must call the IP_MC_SERVER_OPEN function:
IP_MC_SERVER_OPEN (dvIPServer,'239.255.255.250',1900)
The NetLinx master will join the multi-cast session and allow you to receive and transmit UDP multi-
cast messages.
Example IP Code
PROGRAM_NAME='IPExample'
(***********************************************************)
(* DEVICE NUMBER DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_DEVICE
dvIPServer = 0:2:0
dvIPClient = 0:3:0
(***********************************************************)
(* CONSTANT DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_CONSTANT
nIPPort = 8000
(***********************************************************)
(* VARIABLE DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_VARIABLE
DEFINE_START
(* Get My IP Address *)
GET_IP_ADDRESS(0:0:0,MyIPAddress)
(* Open The Server *)
IP_SERVER_OPEN(dvIPServer.Port,nIPPort,IP_TCP)
(* Open The Client *)
IP_CLIENT_OPEN(dvIPClient.Port,MyIPAddress.IPAddress,nIPPort,IP_TCP)
(***********************************************************)
(* THE EVENTS GO BELOW *)
(***********************************************************)
DEFINE_EVENT
{
SEND_STRING 0,"'offline: client'"
}
STRING:
{
SEND_STRING 0,"'string: client=',Data.Text"
}
}
(***********************************************************)
(* THE ACTUAL PROGRAM GOES BELOW *)
(***********************************************************)
DEFINE_PROGRAM
(***********************************************************)
(* END OF PROGRAM *)
(* DO NOT PUT ANY CODE BELOW THIS COMMENT *)
(***********************************************************)
NetLinx Modules
The ability to reuse code is a desirable goal in software development; however, code reuse takes careful
planning and organization. As discussed earlier, NetLinx provides tools such as functions and modules
to promote reusability. Modules are NetLinx sub-programs designed to be "plugged into" a main
program.
Defining a module
The MODULE_NAME entry on the first line of the file defines the module. The syntax is:
MODULE_NAME = '<module name>' [(<parameter list>)]
The MODULE_NAME entry identifies the file as containing a NetLinx module, as opposed to a standard
NetLinx source code file. The module name is any valid string literal not to exceed 64 characters. A file
can contain only one module and the file name must be the same as the module name with the addition of
the ".AXS" extension.
Module parameters behave exactly like subroutine parameters; the parameter list is optional. The value
for each parameter is set either by the main program or another module. If the value of a parameter is
changed, both the main program and module see the change.
The example below defines a module named ModuleExample. Aside from the MODULE_NAME entry,
the code looks like any standard NetLinx source code file.
All parameters to a module must be one of the instrinsic types: CHAR, INTEGER, SINTEGER, LONG,
SLONG, FLOAT, DOUBLE, DEV, DEVCHAN or DEVLEV. Also, any of the above array types can be used.
MODULE_NAME='ModuleExample'(DEV dvDECK, DEVCHAN dcTRANPORTS[], INTEGER nFIRST)
(*{{PS_SOURCE_INFO(PROGRAM STATS) *)
(***********************************************************)
(* ORPHAN_FILE_PLATFORM: 1 *)
(***********************************************************)
(*}}PS_SOURCE_INFO *)
(***********************************************************)
(* DEVICE NUMBER DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_DEVICE
(***********************************************************)
(* CONSTANT DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_CONSTANT
NO_BUTTON = 0
NO_FUNCTION = 256
PLAY = 1
STOP = 2
PAUSE = 3
FFWD = 4
REW = 5
SFWD = 6
SREV = 7
REC = 8
PLAY_FB = 241
STOP_FB = 242
PAUSE_FB = 243
FFWD_FB = 244
REW_FB = 245
SFWD_FB = 246
SREV_FB = 247
REC_FB = 248
(* vcr will go into stop after search rewinding for a certain time *)
VCR1_SREV_TO_STOP = 12000 (* 20 min *)
(* vcr will go into stop after being paused for a certain time *)
VCR1_PAUSE_TO_STOP = 6000 (* 10 min *)
(***********************************************************)
(* TYPE DEFINITIONS GO BELOW *)
(***********************************************************
DEFINE_TYPE*)
(***********************************************************)
(* VARIABLE DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_VARIABLE
(***********************************************************)
(* SUBROUTINE DEFINITIONS GO BELOW *)
(***********************************************************)
OFF [dvDECK,nOFFSET_FN+PLAY]
OFF [dvDECK,nOFFSET_FN+STOP]
OFF [dvDECK,nOFFSET_FN+PAUSE]
OFF [dvDECK,nOFFSET_FN+FFWD]
OFF [dvDECK,nOFFSET_FN+REW]
OFF [dvDECK,nOFFSET_FN+SFWD]
OFF [dvDECK,nOFFSET_FN+SREV]
OFF [dvDECK,nOFFSET_FN+REC]
}
(***********************************************************)
(* EVENT PROCESSING ROUTINES BELOW *)
(***********************************************************)
DEFINE_EVENT
(***********************************************************)
(* dcTRANPORTS - TRANSPORT CONTROLS *)
(***********************************************************)
BUTTON_EVENT[dcTRANPORTS]
{
PUSH:
{
#IF_DEFINED SYSCALL_NOTIFY
SEND_STRING 0,"'IN MODULE ',39,'ModuleExample',39"
#END_IF
(* RUN A FUNCTION *)
nFUNC = GET_LAST(dcTRANPORTS)
SWITCH (nFUNC)
{
CASE PLAY:
{
IF (![dvDECK,nOFFSET_FB+REC_FB])
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+PLAY]
CALL 'FEEDBACK' (PLAY)
}
}
CASE STOP:
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+STOP]
CALL 'FEEDBACK' (STOP)
}
CASE PAUSE:
{
SELECT
{
ACTIVE ([dvDECK,nOFFSET_FB+PAUSE_FB]
AND [dvDECK,nOFFSET_FB+REC_FB]
AND dcTRANPORTS[8].CHANNEL<NO_FUNCTION):
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+REC]
CALL 'FEEDBACK' (REC)
}
ACTIVE ([dvDECK,nOFFSET_FB+PAUSE_FB]
AND dcTRANPORTS[1].CHANNEL<NO_FUNCTION):
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+PLAY]
CALL 'FEEDBACK' (PLAY)
}
ACTIVE ([dvDECK,nOFFSET_FB+PLAY_FB]):
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
WAIT VCR1_PAUSE_TO_STOP 'VCR1 PAUSE TO STOP'
SYSTEM_CALL 'FUNCTION' (dvDECK,STOP,nFIRST)
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+PAUSE]
CALL 'FEEDBACK' (PAUSE)
}
ACTIVE ([dvDECK,nOFFSET_FB+REC_FB]):
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
WAIT VCR1_PAUSE_TO_STOP 'VCR1 PAUSE TO STOP'
SYSTEM_CALL 'FUNCTION' (dvDECK,STOP,nFIRST)
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+PAUSE]
CALL 'FEEDBACK' (PAUSE)
ON [dvDECK,nOFFSET_FB+REC_FB]
}
}
}
CASE FFWD:
{
SELECT
{
ACTIVE ([dvDECK,nOFFSET_FB+STOP_FB]
OR [dvDECK,nOFFSET_FB+FFWD_FB]
OR [dvDECK,nOFFSET_FB+REW_FB]
OR (dcTRANPORTS[6].CHANNEL
AND ([dvDECK,nOFFSET_FB+PLAY_FB]
OR [dvDECK,nOFFSET_FB+SREV_FB]
OR [dvDECK,nOFFSET_FB+SFWD_FB]))):
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CASE SFWD:
{
IF ([dvDECK,nOFFSET_FB+PLAY_FB]
OR [dvDECK,nOFFSET_FB+STOP_FB]
OR [dvDECK,nOFFSET_FB+REW_FB]
OR [dvDECK,nOFFSET_FB+FFWD_FB]
OR [dvDECK,nOFFSET_FB+SREV_FB]
OR [dvDECK,nOFFSET_FB+SFWD_FB])
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+SFWD]
CALL 'FEEDBACK' (SFWD)
}
}
CASE REW:
{
SELECT
{
ACTIVE ([dvDECK,nOFFSET_FB+STOP_FB]
OR [dvDECK,nOFFSET_FB+FFWD_FB]
OR [dvDECK,nOFFSET_FB+REW_FB]
OR (dcTRANPORTS[7].CHANNEL
AND ([dvDECK,nOFFSET_FB+PLAY_FB]
OR [dvDECK,nOFFSET_FB+SREV_FB]
OR [dvDECK,nOFFSET_FB+SFWD_FB]))):
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
WAIT VCR1_REW_TO_STOP 'VCR1 REW TO STOP'
SYSTEM_CALL 'FUNCTION' (dvDECK,STOP,nFIRST)
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+REW]
CALL 'FEEDBACK' (REW)
}
ACTIVE (dcTRANPORTS[7].CHANNEL=NO_BUTTON
AND ([dvDECK,nOFFSET_FB+PLAY_FB]
OR [dvDECK,nOFFSET_FB+SREV_FB]
OR [dvDECK,nOFFSET_FB+SFWD_FB])):
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
WAIT VCR1_SREV_TO_STOP 'VCR1 SREV TO STOP'
SYSTEM_CALL 'FUNCTION' (dvDECK,STOP,nFIRST)
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+SREV]
CALL 'FEEDBACK' (SREV)
}
}
}
CASE SREV:
{
IF ([dvDECK,nOFFSET_FB+PLAY_FB]
OR [dvDECK,nOFFSET_FB+STOP_FB]
OR [dvDECK,nOFFSET_FB+REW_FB]
OR [dvDECK,nOFFSET_FB+FFWD_FB]
OR [dvDECK,nOFFSET_FB+SREV_FB]
OR [dvDECK,nOFFSET_FB+SFWD_FB])
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
WAIT VCR1_SREV_TO_STOP 'VCR1 SREV TO STOP'
SYSTEM_CALL 'FUNCTION' (dvDECK,STOP,nFIRST)
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+SREV]
CALL 'FEEDBACK' (SREV)
}
}
Continued
CASE REC:
{
IF ([dvDECK,nOFFSET_FB+STOP_FB]
OR [dvDECK,nOFFSET_FB+REC_FB])
{
CANCEL_WAIT 'VCR1 REW TO STOP'
CANCEL_WAIT 'VCR1 PAUSE TO STOP'
CANCEL_WAIT 'VCR1 SREV TO STOP'
CALL 'ALL OFF'
MIN_TO [dvDECK,nOFFSET_FN+REC]
CALL 'FEEDBACK' (REC)
}
}
}
}
}
(***********************************************************)
(* THE ACTUAL PROGRAM GOES BELOW *)
(***********************************************************)
DEFINE_PROGRAM
[dcTRANPORTS[1]] = [dvDECK,nOFFSET_FB+PLAY_FB]
[dcTRANPORTS[2]] = [dvDECK,nOFFSET_FB+STOP_FB]
[dcTRANPORTS[3]] = [dvDECK,nOFFSET_FB+PAUSE_FB]
[dcTRANPORTS[4]] = ([dvDECK,nOFFSET_FB+FFWD_FB] OR
(dcTRANPORTS[6].CHANNEL=NO_BUTTON AND [dvDECK,nOFFSET_FB+SFWD_FB]))
[dcTRANPORTS[5]] = ([dvDECK,nOFFSET_FB+REW_FB] OR
(dcTRANPORTS[7].CHANNEL=NO_BUTTON AND [dvDECK,nOFFSET_FB+SREV_FB]))
[dcTRANPORTS[6]] = [dvDECK,nOFFSET_FB+SFWD_FB]
[dcTRANPORTS[7]] = [dvDECK,nOFFSET_FB+SREV_FB]
[dcTRANPORTS[8]] = ([dvDECK,nOFFSET_FB+REC_FB] AND
(![dvDECK,nOFFSET_FB+PAUSE_FB]))
(***********************************************************)
(* END OF PROGRAM *)
(* DO NOT PUT ANY CODE BELOW THIS COMMENT *)
(***********************************************************)
Technically, modules can contain declarations to other modules, provided that no circular references are
involved. However, because different instances of the same module must not be separated by instances of
a different module, it is highly recommended that you do not declare modules from within other modules
- if you have multiple declarations of the parent module they will then be separated by the declarations of
the child module.
FIG. 1 demonstrates how a NetLinx module is incorporated into a main program. In this example, the
main program has no event table or mainline code.
PROGRAM_NAME='ModuleExampleTest'
(*{{PS_SOURCE_INFO(PROGRAM STATS) *)
(***********************************************************)
(* ORPHAN_FILE_PLATFORM: 1 *)
(***********************************************************)
(*}}PS_SOURCE_INFO *)
(***********************************************************)
(* DEVICE NUMBER DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_DEVICE
dvVCR = 1:7:0
dvTP = 128:1:0
(***********************************************************)
(* VARIABLE DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_VARIABLE
VOLATILE
DEVCHAN dcTRANPORTS[] = {
{ dvTP,1 }, { dvTP,2 }, { dvTP,3 }, { dvTP,4 },
{ dvTP,5 }, { dvTP,6 }, { dvTP,7 }, { dvTP,8 }
}
VOLATILE
INTEGER nVCR_FIRST = 0
(***********************************************************)
(* MODULE CODE GOES BELOW *)
(***********************************************************)
(***********************************************************)
(* END OF PROGRAM *)
(* DO NOT PUT ANY CODE BELOW THIS COMMENT *)
(***********************************************************)
Internet Inside
The Internet Inside™ feature of the NetLinx master allows a web browser to retrieve web pages directly
from the master. The web pages provide a user interface that mimics the look and feel of an AMX touch
panel. In fact, TPDesign generates the web pages.
The components of Internet Inside are as follows:
Java TPClasses – The Java client code that runs on the browser essentially emulating a touch
panel.
WDM – Web Device Manager. A software module that runs on the NetLinx masters that
proxies control information to the master on behalf of the Java client.
PNG files – Portable Network Graphics. Bitmap graphics that get displayed in the browser.
Both touch panel icons and touch panel bitmaps get converted to PNG files for the Java client
to display.
XML files - Extensible Markup Language. Page definition files that describe that layout of
each page. The Java client loads one of the XML files for every page flip that occurs (load on
demand) and, therefore, are not loaded into the browser when the client makes its initial
connection.
HTML file – There is a single HTML file called INDEX.HTM for each user interface. The
HTML file contains just enough information to get the Java client up and running.
WDM Configuration file – The WDM references a configuration file (WDM.CONF) to
determine its operational parameters.
When TPDesign generates the web pages for NetLinx it creates all of the files necessary and places them
into a local directory. The created files must be downloaded to the NetLinx master into a sub-directory of
the /USER directory. For example, a board room system that contains a NetLinx master might be placed
in a directory named /BoardRoom as a subdirectory of /USER.
If you decide to download the files to the NetLinx master, you’ll have to use an FTP client. The FTP user
name must be "NetLinx" (case sensitive) and the password is "password" (case sensitive). The default
directory returned by FTP is doc:/user. The user must create a subdirectory of /user and download all of
the files into that newly created directory.
Java TPClasses
The Java TPClasses client code runs in the users web browser and is loaded when the INDEX.HTM file
loaded. The Java TPClasses code performs several different functions, including providing the look and
feel of the user interface and providing the communication mechanism to the WDM. The
communication connection between TPClasses and WDM is a persistent TCP/IP connection using TCP
port 10510 by default. The TCP port number may be changed, if necessary, by editing the INDEX.HTM
and WDM.CONF files. The line in the INDEX.HTM file that needs to change is:
<param name="connectport" value="10500">
The INDEX.HTM file also contains the NetLinx device number that the web user interface will connect to
the NetLinx master. This device number must have NetLinx code written to support it. Typically, a
simple DEFINE_COMBINE between the "real" touch panel and the web user interface device number is
utilized that provides identical functionality to the web user interface that the "real" touch panel has.
TPDesign provides a facility for setting the device number and range of devices for the web user
interface. However, the device number and range may be changed by editing the following line in the
INDEX.HTM file:
<param name="devrange1" value="225,228,4">
The first parameter is the starting device number, the second parameter is the ending device number
inclusive, and the last parameter is the number of NetLinx devices the web user interface uses per
instance. The purpose of this format is to allow multiple instances of the same web user interface on
multiple browsers. For example, assume a conference room where the user desires the ability to connect
two simultaneous web user interfaces. Since duplicate device numbers are not allowed, the two user
interfaces must have distinctly different device numbers. The following might exist in the INDEX.HTM
file:
<param name="devrange1" value="225,232,4">
When the Java client code connects to the WDM it negotiates an unused device number or range with the
WDM based upon the devrange1 parameters. The example above would allow a web user interface to
have either the range of devices 225,226,227 and 228, or the range of 229,230,231, and 232. Note that
the NetLinx program would have to contain a DEFINE_COMBINE that included combining device 225
with 229, 226 with 230, etc.
WDM Configuration
The configuration of the WDM is optional because the WDMs default configuration options work for
most applications. The only WDM configuration option that might need to be modified is the TCP port
that the TPClasses Java code attempts to connect. By default, the WDM listens for the Java code to
connect on TCP port 10500.
The WDM reads the WDM.CONF file from the NetLinx master’s disk-on-chip /USER directory. Normally,
there is no WDM.CONF file in the /USER directory so the WDM uses its default values. To override the
defaults, a properly modified WDM.CONF file must be placed in the /USER directory. Conveniently,
TPDesign creates a WDM.CONF file that can be used as a template. Shown below is the WDM.CONF file
that TPDesign creates automatically:
DEBUG_PORT 10000
PERSIST_SRV_PORT 10500
MASTER 1 127.0.0.1 1319
LISTEN_BACKLOG 5
The only configuration setting that should be modified is the PERSIST_SRV_PORT parameter, and it
must be set to the same port number as the TPClasses Java code’s "connectport" parameter. The
"connectport" parameter exists in the INDEX.HTM file that is also created by TPDesign (see the Java
TPClasses section on page 207 for more information).
The WDM listens on a single TCP port; every web user interface and WDM MUST
have the same TCP port setting. If you manually change the TCP port configuration
of one, you must change the TCP port configuration on all of them (WDM and all
INDEX.HTM files).
(*!!FILE REVISION: *)
(* REVISION DATE: 05/22/2001 *)
(* *)
(* COMMENTS: *)
(* *)
(***********************************************************)
(*}}PS_SOURCE_INFO *)
(***********************************************************)
(***********************************************************)
(* System Type : NetLinx *)
(***********************************************************)
(* REV HISTORY: *)
(***********************************************************)
(***********************************************************)
(* DEVICE NUMBER DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_DEVICE
dvTP = 128:1:0
(***********************************************************)
(* CONSTANT DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_CONSTANT
nFileRead = 1
nFileWrite = 2
(***********************************************************)
(* DATA TYPE DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_TYPE
STRUCTURE _AlbumStruct
{
LONG lTitleID
CHAR sArtist[100]
CHAR sTitle[100]
CHAR sCopyright[100]
CHAR sLabel[100]
CHAR sReleaseDate[100]
INTEGER nNumTracks
CHAR sCode[100]
INTEGER nDiscNumber
}
STRUCTURE _AlbumStruct2
{
CHAR sArtist[100]
CHAR sTitle[100]
INTEGER nNumTracks}
(***********************************************************)
(* VARIABLE DEFINITIONS GO BELOW *)
(***********************************************************)
DEFINE_VARIABLE
VOLATILE _AlbumStruct AlbumStruct[3]
VOLATILE _AlbumStruct2
AlbumStruct2[3]
VOLATILE CHAR sBinaryString[10000]
VOLATILE CHAR sXMLString[50000]
VOLATILE LONG lPos
VOLATILE SLONG slFile
VOLATILE SLONG slReturn
(***********************************************************)
(* STARTUP CODE GOES BELOW *)
(***********************************************************)
DEFINE_START
(***********************************************************)
(* THE EVENTS GO BELOW *)
(***********************************************************)
DEFINE_EVENT
(* CONVERT TO XML *)
lPos = 1
slReturn = VARIABLE_TO_XML (AlbumStruct,sXMLString,lPos,0)
SEND_STRING 0,"'POSITION=',ITOA(lPos),'; RETURN=',ITOA(slReturn)"
(* Clear string *)
sBinaryString = ""
sXMLString = ""
}
}
{
PUSH:
{
(* NOW WE CAN SAVE THESE BOTH TO DISCS *)
slFile = FILE_OPEN('BinaryEncode.xml',nFileRead)
IF (slFile > 0)
{
slReturn =
FILE_READ(slFile,sBinaryString,MAX_LENGTH_STRING(sBinaryString))
IF (slReturn < 0) SEND_STRING 0,"'FILE WRITE FAIL RETURN=',ITOA(slReturn)"
slReturn = FILE_CLOSE(slFile)
IF (slReturn < 0) SEND_STRING 0,"'FILE CLOSE FAIL RETURN=',ITOA(slReturn)"
}
slFile = FILE_OPEN('XMLEncode.xml',nFileRead)
IF (slFile > 0)
{
slReturn = FILE_READ(slFile,sXMLString,MAX_LENGTH_STRING(sXMLString))
IF (slReturn < 0) SEND_STRING 0,"'FILE WRITE FAIL RETURN=',ITOA(slReturn)"
slReturn = FILE_CLOSE(slFile)
IF (slReturn < 0) SEND_STRING 0,"'FILE CLOSE FAIL RETURN=',ITOA(slReturn)"
}
(* CONVERT TO BINARY *)
lPos = 1
slReturn = STRING_TO_VARIABLE (AlbumStruct,sBinaryString,lPos)
SEND_STRING 0,"'POSITION=',ITOA(lPos),'; RETURN=',ITOA(slReturn)"
(* CONVERT TO XML *)
lPos = 1
slReturn = XML_TO_VARIABLE (AlbumStruct,sXMLString,lPos,0)
SEND_STRING 0,"'POSITION=',ITOA(lPos),'; RETURN=',ITOA(slReturn)"
}
}
(* READ AND DECODE *)
(* THE BINARY WILL FAIL SINCE THE DECODE TYPE DOES NOT MATCH THE ENCODE TYPE *)
(* THE XML WILL NOT FAIL SINCE IT DOES NOT REQUIRE DATA TO BE THE SEQUENTIAL *)
BUTTON_EVENT[dvTP,3]
{
PUSH:
{
(* NOW WE CAN SAVE THESE BOTH TO DISCS *)
slFile = FILE_OPEN('BinaryEncode.xml',nFileRead)
IF (slFile > 0)
{
slReturn =
FILE_READ(slFile,sBinaryString,MAX_LENGTH_STRING(sBinaryString))
IF (slReturn < 0) SEND_STRING 0,"'FILE WRITE FAIL RETURN=',ITOA(slReturn)"
slReturn = FILE_CLOSE(slFile)
IF (slReturn < 0) SEND_STRING 0,"'FILE CLOSE FAIL RETURN=',ITOA(slReturn)"
}
slFile = FILE_OPEN('XMLEncode.xml',nFileRead)
IF (slFile > 0)
{
slReturn = FILE_READ(slFile,sXMLString,MAX_LENGTH_STRING(sXMLString))
IF (slReturn < 0) SEND_STRING 0,"'FILE WRITE FAIL RETURN=',ITOA(slReturn)"
slReturn = FILE_CLOSE(slFile)
IF (slReturn < 0) SEND_STRING 0,"'FILE CLOSE FAIL RETURN=',ITOA(slReturn)"
}
(* CONVERT TO BINARY *)
lPos = 1
slReturn = STRING_TO_VARIABLE (AlbumStruct2,sBinaryString,lPos)
SEND_STRING 0,"'POSITION=',ITOA(lPos),'; RETURN=',ITOA(slReturn)"
(* CONVERT TO XML *)
lPos = 1
slReturn = XML_TO_VARIABLE (AlbumStruct2,sXMLString,lPos,0)
SEND_STRING 0,"'POSITION=',ITOA(lPos),'; RETURN=',ITOA(slReturn)"
}
}
(***********************************************************)
(* THE ACTUAL PROGRAM GOES BELOW *)
(***********************************************************)
DEFINE_PROGRAM
(***********************************************************)
(* END OF PROGRAM *)
(* DO NOT PUT ANY CODE BELOW THIS COMMENT *)
(***********************************************************)
END- Byte indicator for end of structure - not really a data type pre- 0xEB
STRUCT fix.
ARRAY Array of any one of the types in this table whose element- 0xEC
count can be > 64K. Each element in an array is selfdescrip- Length MSB
tive.The type of the first element (byte after LengthLSB) is
.
the type of the entire array.
.
Length LSB
<Array Element 1>
.
.
SKIP Byte indicator for space to be skipped in the input and 0xED
NULL'ed in the marshalled output. This can be viewed as a
NULL data type prefix.
Encoding notes:
The encoding XML will not contain any white space. This includes CR,LF pairs.
The decoding XML may contain white spaces. They will be ignored according to standard
XML rules (i.e. Spaces as between tags are read.)
Array may be encoded or decoded as binary encoded data
XML comments, <!-- -->, will be ignored in decode.
String encoding
NetLinx has no native string type, but since it is a common type the encoding/decoding of the string data
will be logically handled so the XML remains concise. CHAR arrays will be encoded/decoded as a string
type, printable ASCII characters appear as ASCII, and non-printable characters appear as escaped
decimal or hex code, &#<decimal code>; or &#x<hex code>;. An example string would be:
<data>My Name is Jimmy Buffet
</data>
- or -
<data>My Name is Jimmy Buffet </data>
Additionally, some characters have a more readable syntax. These characters are invalid in XML; so, the
following characters can be encoded in the above format or the following format:
<encoded>
<style>BE</ style >
<size>2</size>
<data>010203040B0C0D0E</data>
</encoded>
This is the default type of encoding for non-CHAR arrays but can be used to encode/decode char arrays
as well. The data section must contain BytesSize*Elements nibbles.
<name>NDISCNUMBER</name>
<data>91</data>
</var>
</struct>
<struct>
<index>2</index>
<var>
<name>LTITLEID</name>
<data>17248229</data>
</var>
....
<var>
<name>NDISCNUMBER</name>
<data>105</data>
</var>
</struct>
<struct>
<index>3</index>
<var>
<name>LTITLEID</name>
<data>12328612</data>
</var>
...
<var>
<name>NDISCNUMBER</name>
<data>189</data>
</var>
</struct>
</array>
Appendix B: Glossary
Array: A single variable that has more than one storage location.
Buffer: An array variable that is associated with a particular device for the purpose of storing
information sent by the device.
Button Event: Include pushes, releases, and holds associated with a push or release on a particular
device-channel.
Calling Parameters: Variables or constants that originate from the caller and are received by the
function or subroutine being invoked
Central Controller: The NetLinx controller (processor) that controls the activities of the NetLinx
system.
Channel: The basic I/O unit in a NetLinx system. It corresponds to single unit of control such as a relay
or an IR signal. The Axcess system supports up to 255 channels per device; the new NetLinx system
will support up to 65,535.
Channel Event: Generated when PULSE, TO, MIN_TO, ON or OFF is called. The CHANNEL object is
available to the channel event handler as a local variable.
Constant: An identifier whose value cannot be changed throughout the entire program.
Data Event: Events associated with a device only; includes commands, strings, status, and error
messages.
Device: A component that has an address and can communicate on the NetLinx bus.
Device array: Specifies a group of DEVs (devices) for a command or event handler.
Device-channel: A reference to a specific device-channel (DEVCHAN) in the NetLinx system.
Device-channel array: Specifies a group of DEVCHANs.
Device-level: A reference to a specific level in the NetLinx system (DEVLEV).
Device-level array: Specifies a group of DEVLEVs.
Device number: A unique number from 1 to 32767 designating each device connected to the NetLinx
bus. The compiler replaces the device number with an internally generated DEV structure. This DEV
structure contains the specified device Number, a value of one (1) for Port indicating the first port, and a
value of zero (0) for System indicating this system (the system that is executing the code).
Device:Port:System (D:P:S): Notation used to explicitly represent a device number, port, and system.
For example, 128:1:0 represents the first port of the device number 128 on this system. The syntax is
NUMBER:PORT:SYSTEM, where the parameters are: NUMBER is a 16-bit integer representing the Device
number, PORT is a 16-bit integer representing the Port number on the device, and SYSTEM is a 16-bit
integer representing the System number.
Event: An activity such as a button push, relay closure, or device status change. Events are received by
NetLinx in the form of messages that are generally acted upon by blocks of code called event handlers.
An event is always associated with a particular device on the bus.
Event Handlers: Blocks of code defined in DEFINE_EVENT for incoming events/notifications. There
are handlers to support five types of events: Button Events, Channel events, Data Events, Level Events,
and Timeline Events.
Expression: Sub-component of a programming statement, such as the conditional portion of an IF
statement or the right-hand side of an arithmetic assignment statement.
Wildcard character: In NetLinx, the question mark (?) can only be used in a COMPARE_STRING
operation (unlike Axcess, which uses the question mark to compare dates and times).
3000 RESEARCH DRIVE, RICHARDSON, TX 75082 USA • 800.222.0193 • 469.624.8000 • 469-624-7153 fax • 800.932.6993 technical support • www.amx.com