Issue 2 move folders and use deep path include file names to prevent collisions (#4)
* moving folders and files and adjust server demo build * Fix Makefile for apps/server on Linux * fix unit test source file folders * fix datetime convert UTC functions. Add Code::Blocks project for datetime testing * added some ignore extensions * disable parallel make option * fix build for abort, dcc, and epics apps * fix build for dcc, epics, error, and getevent apps. * Fixed building of all apps * fix the ipv4 to ipv6 router app build * Change indent style from Google to Webkit * make pretty to re-format style * removed common Makefile since we already had one and two was too many * remove scripts from root folder that are no longer maintained or used * remove mercurial EOL and ignore files for git repo * remove .vscodeconfig files from repo * tweak clang-format style * clang-format src and apps with tweaked style * added clang-tidy to fix readability if braces in src * result of make tidy for src and apps * fix clang-tidy mangling * Added code::blocks project for BACnet server simulation * added code::blocks linux project for WhoIs app * update text files for EOL * fix EOL in some files * fixed make win32 apps for older gcc * Removed Borland C++ Makefile in apps. Unable to maintain support for Borland C++ compiler. * created codeblocks project for apps/epics for Windows * fixing ports/xplained to work with new data structure. * fix ports/xplained example for Atmel Studio compile * fix ports/stm32f10x example for gcc Makefile compile * fix ports/stm32f10x example for IAR EWARM compile * fix ports/xplained timer callback * fix ports/bdk_atxx_mspt build with subdirs * fix ports/bdk_atxx_mspt build with subdirs * updated git ignore for IAR build artifacts * updated gitignore for non-tracked files and folders * fixed bdk-atxx4-mstp port for Rowley Crossworks project file * fixed bdk-atxx4-mstp port for GCC AVR Makefile * fixed atmega168 port for IAR AVR and GCC AVR Makefile * fixed at91sam7s port for IAR ARM and GCC ARM Makefile * removed unmaintainable DOS, RTOS32, and atmega8 ports. Updated rx62n (untested). * changed arm7 to uip port
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>API Documentation</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<link rev="made" href="mailto:rurban@x-ray.at" />
|
||||
</head>
|
||||
|
||||
<body style="background-color: white">
|
||||
|
||||
|
||||
<!-- INDEX BEGIN -->
|
||||
<div name="index">
|
||||
<p><a name="__index__"></a></p>
|
||||
|
||||
<ul>
|
||||
|
||||
<li><a href="#name">NAME</a></li>
|
||||
<li><a href="#description">DESCRIPTION</a></li>
|
||||
<li><a href="#options">OPTIONS</a></li>
|
||||
<li><a href="#this_tool_s_api">This tool's API</a></li>
|
||||
<ul>
|
||||
|
||||
<li><a href="#readproperty">ReadProperty</a></li>
|
||||
<ul>
|
||||
|
||||
<li><a href="#inputs_to_readproperty">Inputs to ReadProperty</a></li>
|
||||
<li><a href="#outputs_from_readproperty">Outputs from ReadProperty</a></li>
|
||||
<li><a href="#example_of_readproperty">Example of ReadProperty</a></li>
|
||||
</ul>
|
||||
|
||||
<li><a href="#readpropertymultiple">ReadPropertyMultiple</a></li>
|
||||
<ul>
|
||||
|
||||
<li><a href="#inputs_to_readpropertymultiple">Inputs to ReadPropertyMultiple</a></li>
|
||||
<li><a href="#outputs_from_readpropertymultiple">Outputs from ReadPropertyMultiple</a></li>
|
||||
<li><a href="#example_of_readpropertymultiple">Example of ReadPropertyMultiple</a></li>
|
||||
</ul>
|
||||
|
||||
<li><a href="#writeproperty">WriteProperty</a></li>
|
||||
<ul>
|
||||
|
||||
<li><a href="#inputs_to_writeproperty">Inputs to WriteProperty</a></li>
|
||||
<li><a href="#outputs_from_writeproperty">Outputs from WriteProperty</a></li>
|
||||
<li><a href="#example_of_writeproperty">Example of WriteProperty</a></li>
|
||||
</ul>
|
||||
|
||||
<li><a href="#timesync">TimeSync</a></li>
|
||||
<ul>
|
||||
|
||||
<li><a href="#inputs_to_timesync">Inputs to TimeSync</a></li>
|
||||
<li><a href="#outputs_from_timesync">Outputs from TimeSync</a></li>
|
||||
<li><a href="#example_of_timesync">Example of TimeSync</a></li>
|
||||
</ul>
|
||||
|
||||
<li><a href="#log">Log</a></li>
|
||||
<ul>
|
||||
|
||||
<li><a href="#inputs_to_log">Inputs to Log</a></li>
|
||||
<li><a href="#example_of_log">Example of Log</a></li>
|
||||
</ul>
|
||||
|
||||
<li><a href="#silencelog">SilenceLog</a></li>
|
||||
<ul>
|
||||
|
||||
<li><a href="#inputs_to_silencelog">Inputs to SilenceLog</a></li>
|
||||
<li><a href="#outputs_from_silencelog">Outputs from SilenceLog</a></li>
|
||||
<li><a href="#example_of_silencelog">Example of SilenceLog</a></li>
|
||||
</ul>
|
||||
|
||||
<li><a href="#retry">Retry</a></li>
|
||||
<ul>
|
||||
|
||||
<li><a href="#inputs_to_retry">Inputs to Retry</a></li>
|
||||
<li><a href="#outputs_from_retry">Outputs from Retry</a></li>
|
||||
<li><a href="#example_of_retry">Example of Retry</a></li>
|
||||
</ul>
|
||||
|
||||
</ul>
|
||||
|
||||
</ul>
|
||||
|
||||
<hr name="index" />
|
||||
</div>
|
||||
<!-- INDEX END -->
|
||||
|
||||
<p>
|
||||
</p>
|
||||
<h1><a name="name">NAME</a></h1>
|
||||
<p>bacnet.pl - Scriptable BACnet communications</p>
|
||||
<p>
|
||||
</p>
|
||||
<hr />
|
||||
<h1><a name="description">DESCRIPTION</a></h1>
|
||||
<p>This is a tool for scriptable BACnet communication. Users can write their own
|
||||
scripts using standard Perl syntax and API defined in this tool to perform desired
|
||||
execution sequences. For details on this tool's API, see Documentation.html. For other
|
||||
Perl documentation, see <a href="http://perldoc.perl.org">http://perldoc.perl.org</a></p>
|
||||
<link href="syntax.css" rel="stylesheet" type="text/css">
|
||||
<script src="jquery.js"></script>
|
||||
<script src="syntax.js"></script><p>
|
||||
</p>
|
||||
<hr />
|
||||
<h1><a name="options">OPTIONS</a></h1>
|
||||
<p>Usage: bacnet.pl [program_options] [-- script_args]</p>
|
||||
<p>This program executes a script in perl syntax to perform BACnet/IP operations.</p>
|
||||
<pre>
|
||||
|
||||
Possible program options:
|
||||
--script=s The script to execute.
|
||||
--log=s The file to log all output.
|
||||
--help This help message.</pre>
|
||||
<pre>
|
||||
Possible environment variables are:
|
||||
BACNET_IFACE - set this value to dotted IP address of the interface (see
|
||||
ipconfig) for which you want to bind. Default is the interface which
|
||||
Windows considers to be the default (how???). Hence, if there is only a
|
||||
single network interface on Windows, the applications will choose it, and
|
||||
this setting will not be needed.
|
||||
BACNET_IP_PORT - UDP/IP port number (0..65534) used for BACnet/IP
|
||||
communications. Default is 47808 (0xBAC0).
|
||||
BACNET_APDU_TIMEOUT - set this value in milliseconds to change the APDU
|
||||
timeout. APDU Timeout is how much time a client waits for a response from
|
||||
a BACnet device.
|
||||
BACNET_BBMD_PORT - UDP/IP port number (0..65534) used for Foreign Device
|
||||
Registration. Defaults to 47808 (0xBAC0).
|
||||
BACNET_BBMD_TIMETOLIVE - number of seconds used in Foreign Device
|
||||
Registration (0..65535). Defaults to 60000 seconds.
|
||||
BACNET_BBMD_ADDRESS - dotted IPv4 address of the BBMD or Foreign Device
|
||||
Registrar.</pre>
|
||||
<p>
|
||||
</p>
|
||||
<hr />
|
||||
<h1><a name="this_tool_s_api">This tool's API</a></h1>
|
||||
<p>In addition to having all standard Perl flow control, functions, and modules,
|
||||
the this tool provides an API for performing BACnet communication functions.</p>
|
||||
<p>
|
||||
</p>
|
||||
<h2><a name="readproperty">ReadProperty</a></h2>
|
||||
<p>This function implements the ReadProperty service. There are no built in retry
|
||||
mechanisms. NOTE: all enumerations are defined in <em class="file">bacenum.h</em></p>
|
||||
<p>
|
||||
</p>
|
||||
<h3><a name="inputs_to_readproperty">Inputs to ReadProperty</a></h3>
|
||||
<ul>
|
||||
<li><b>devideInstance</b> - the instance number of the device we are reading</li>
|
||||
<li><b>objectName</b> - the enumeration for the object name we are reading</li>
|
||||
<li><b>objectInstance</b> - the instance number of the object we are reading</li>
|
||||
<li><b>propertyName</b> - the enumeration for the property name we are reading</li>
|
||||
<li><b>index</b> - Optional (default -1): the index number we are reading from. -1 if not applicable</li>
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="outputs_from_readproperty">Outputs from ReadProperty</a></h3>
|
||||
<ul>
|
||||
<li><b>result</b> - the sting result (value or error) for ReadProperty</li>
|
||||
<li><b>isFailure</b> - zero means no failure, non-zero means failure</li>
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="example_of_readproperty">Example of ReadProperty</a></h3>
|
||||
<p>The following example will read AV0.PresentValue from device 1234</p>
|
||||
<pre>
|
||||
my ($res, $failed) = ReadProperty(1234, 'OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE');</pre>
|
||||
<p>
|
||||
</p>
|
||||
<h2><a name="readpropertymultiple">ReadPropertyMultiple</a></h2>
|
||||
<p>This function implements the ReadPropertyMultiple service. There are no built in retry
|
||||
mechanisms. NOTE: all enumerations are defined in <em class="file">bacenum.h</em></p>
|
||||
<p>
|
||||
</p>
|
||||
<h3><a name="inputs_to_readpropertymultiple">Inputs to ReadPropertyMultiple</a></h3>
|
||||
<ul>
|
||||
<li><b>devideInstance</b> - the instance number of the device we are reading</li>
|
||||
<li><b>r_answerList</b> - reference to a list where to store the answers</li>
|
||||
<li><b>list</b> - a list of ReadAccessSpecifications</li>
|
||||
<ul>
|
||||
<li><b>objectType</b> - the enumeration for the object name to read from</li>
|
||||
<li><b>objectInstance</b> - the instance number of the object we are reading</li>
|
||||
<li><b>propertyName</b> - the enumeration for the property name we are reading</li>
|
||||
<li><b>index</b> - the index number we are reading from. Use -1 if not applicable</li>
|
||||
</ul>
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="outputs_from_readpropertymultiple">Outputs from ReadPropertyMultiple</a></h3>
|
||||
<ul>
|
||||
<li><b>result</b> - the 'QQQ' delimited concatenated sting result (value or error) for ReadPropertyMultiple. The parsed out result is returned in r_answerList</li>
|
||||
<li><b>isFailure</b> - zero means no failure, non-zero means failure</li>
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="example_of_readpropertymultiple">Example of ReadPropertyMultiple</a></h3>
|
||||
<p>The following example will read AV0.PresentValue and AV1.PresentValue from device 1234</p>
|
||||
<pre>
|
||||
my @RPM_request = ();
|
||||
my @RPM_answer = ();
|
||||
my $failed;
|
||||
push @RPM_request, ['OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE', -1];
|
||||
push @RPM_request, ['OBJECT_ANALOG_VALUE', 1, 'PROP_PRESENT_VALUE', -1];
|
||||
(undef, $failed) = ReadPropertyMultiple(1234, \@RPM_answer, @RPM_request);</pre>
|
||||
<p>
|
||||
</p>
|
||||
<h2><a name="writeproperty">WriteProperty</a></h2>
|
||||
<p>This function implements the WriteProperty service. There are no built in retry
|
||||
mechanisms. NOTE: all enumerations are defined in <em class="file">bacenum.h</em></p>
|
||||
<p>
|
||||
</p>
|
||||
<h3><a name="inputs_to_writeproperty">Inputs to WriteProperty</a></h3>
|
||||
<ul>
|
||||
<li><b>devideInstance</b> - the instance number of the device we are writing</li>
|
||||
<li><b>objectName</b> - the enumeration for the object name we are writing</li>
|
||||
<li><b>objectInstance</b> - the instance number of the object we are writing</li>
|
||||
<li><b>propertyName</b> - the enumeration for the property name we are writing</li>
|
||||
<li><b>tagName</b> - the enumeration for the type of value we are writing. To specify context tags, prepend the tag name with "Cn:" where 'n' is the context number.</li>
|
||||
<li><b>value</b> - the value we are writing</li>
|
||||
<li><b>priority</b> - Optional (default 0): the priority within Priority Array to write at. Use 1-16 when specify priority, 0 to not specify priority.</li>
|
||||
<li><b>index</b> - Optional (default -1): the index within an array we are writing to. Use positive number to indicate index, -1 to not specify index.</li>
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="outputs_from_writeproperty">Outputs from WriteProperty</a></h3>
|
||||
<ul>
|
||||
<li><b>result</b> - the sting result (value or error) for WriteProperty</li>
|
||||
<li><b>isFailure</b> - zero means no failure, non-zero means failure</li>
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="example_of_writeproperty">Example of WriteProperty</a></h3>
|
||||
<p>The following example will write 1.0 to AV0.PresentValue in device 1234</p>
|
||||
<pre>
|
||||
my ($res, $failed) = WriteProperty(1234, 'OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE', 'BACNET_APPLICATION_TAG_REAL', 1.0);</pre>
|
||||
<p>
|
||||
</p>
|
||||
<h2><a name="timesync">TimeSync</a></h2>
|
||||
<p>This function implements the TimeSync and UTCTimeSync services</p>
|
||||
<p>
|
||||
</p>
|
||||
<h3><a name="inputs_to_timesync">Inputs to TimeSync</a></h3>
|
||||
<ul>
|
||||
<li><b>deviceInstanceNumber</b> - the instance number of the device we are reading</li>
|
||||
<li><b>year</b> - Year (i.e. 2011)</li>
|
||||
<li><b>month</b> - Month (i.e. 11 for November)</li>
|
||||
<li><b>day</b> - Day (i.e. 1 for first of month)</li>
|
||||
<li><b>hour</b> - Hour (i.e. 23 for 11pm)</li>
|
||||
<li><b>minute</b> - Minute (i.e. 0-59)</li>
|
||||
<li><b>second</b> - Second (i,e. 0-59)</li>
|
||||
<li><b>utcOffset</b> - Optional: if specified defines the UTC offset and forces UTCTimeSync</li>
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="outputs_from_timesync">Outputs from TimeSync</a></h3>
|
||||
<ul>
|
||||
<li><b>isFailure</b> - zero means no failure, non-zero means failure</li>
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="example_of_timesync">Example of TimeSync</a></h3>
|
||||
<pre>
|
||||
$isFailure = TimeSync($deviceInstance, $1, $2, $3, $4, $5, $6) unless $isFailure;</pre>
|
||||
<p>
|
||||
</p>
|
||||
<h2><a name="log">Log</a></h2>
|
||||
<p>This function prints out to the desired method of logging (STDOUT or file).
|
||||
NewLine characters are not required when making calls to this function. If any
|
||||
NewLine characters are specified, they will be stripped out. To print an empty
|
||||
line, pass in a space as the message. NOTE: This function will honor previous
|
||||
requests to silence the log (see SilcenseLog for details)</p>
|
||||
<p>
|
||||
</p>
|
||||
<h3><a name="inputs_to_log">Inputs to Log</a></h3>
|
||||
<ul>
|
||||
<li><b>msg</b> - the message to output
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="example_of_log">Example of Log</a></h3>
|
||||
<p>The following example will print out "hello world"</p>
|
||||
<pre>
|
||||
Log("Hello World");</pre>
|
||||
<p>
|
||||
</p>
|
||||
<h2><a name="silencelog">SilenceLog</a></h2>
|
||||
<p>This function requests that all future log messages be either suppressed or
|
||||
enabled.</p>
|
||||
<p>
|
||||
</p>
|
||||
<h3><a name="inputs_to_silencelog">Inputs to SilenceLog</a></h3>
|
||||
<ul>
|
||||
<li><b>logIsQuiet</b> - zero means print to log, non-zero means supress log
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="outputs_from_silencelog">Outputs from SilenceLog</a></h3>
|
||||
<p>The previous value of whether or not the log was silenced before caling this
|
||||
function.</p>
|
||||
<p>
|
||||
</p>
|
||||
<h3><a name="example_of_silencelog">Example of SilenceLog</a></h3>
|
||||
<p>The following example will print out "hello", but not "world"</p>
|
||||
<pre>
|
||||
Log("Hello");
|
||||
SilenceLog(1);
|
||||
Log("World");</pre>
|
||||
<p>
|
||||
</p>
|
||||
<h2><a name="retry">Retry</a></h2>
|
||||
<p>This function will try to execute the requested command up to specified number
|
||||
of times, awaiting the requested answer, with a specified pause between
|
||||
retries. NOTE: the only functions which can be executed by this function are
|
||||
ones which return two parameres in the form of ($response, $isFailure)</p>
|
||||
<p>
|
||||
</p>
|
||||
<h3><a name="inputs_to_retry">Inputs to Retry</a></h3>
|
||||
<ul>
|
||||
<li><b>r_func</b> - The reference to the function which is to be retried</li>
|
||||
<li><b>r_funcArgs</b> - A reference to an array of arguments for the function to be executed</li>
|
||||
<li><b>desiredOutput</b> - The condition which will terminate the retrying. Can be either a number or a regexp to patch against the $response return of the function</li>
|
||||
<li><b>maxTries</b> - The maximum number of retry attempts before calling it quits</li>
|
||||
<li><b>sleepSeconds</b> - The number of seconds (could be fractional) to wait between retries</li>
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="outputs_from_retry">Outputs from Retry</a></h3>
|
||||
<ul>
|
||||
<li><b>$resp</b> - The response from the last execution of requested function</li>
|
||||
<li><b>isFailure</b> - zero means no failure, non-zero means failure</li>
|
||||
</ul><p>
|
||||
</p>
|
||||
<h3><a name="example_of_retry">Example of Retry</a></h3>
|
||||
<p>The following example will execute the ReadProperty function to read a property
|
||||
from an object (see ReadProperty for details on those arguments) with up to
|
||||
$maxRetries retries (with $retryDelay delay between retries) or unitl the
|
||||
desired answer of 42 is received.</p>
|
||||
<pre>
|
||||
my ($resp, $isFailure) = Retry(
|
||||
\&ReadProperty, [$deviceInstance, 'OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE'],
|
||||
42, $maxRetries, $retryDelay
|
||||
);
|
||||
if ($isFailure)
|
||||
{
|
||||
die "Value was not 42. Last response was '$resp'";
|
||||
}</pre>
|
||||
<p>The following example will try to execute a WriteProperty (see that function for
|
||||
details on its arguments) until the write succeeds.</p>
|
||||
<pre>
|
||||
my ($resp, $isFailure) = Retry(
|
||||
\&WriteProperty, [$deviceInstance, 'OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE', 'BACNET_APPLICATION_TAG_REAL', 42.0],
|
||||
"Acknowledged", $maxRetries, $retryDelay
|
||||
);
|
||||
if ($isFailure)
|
||||
{
|
||||
die "Could not write 42. Last response was '$resp'";
|
||||
}</pre>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Vendored
+4
File diff suppressed because one or more lines are too long
@@ -0,0 +1,75 @@
|
||||
pre{
|
||||
font-family: "Courier New", Courier, monospace, sans-serif;
|
||||
text-align: left;
|
||||
line-height: 1.6em;
|
||||
font-size: 11px;
|
||||
padding: 0.1em 0.5em 0.3em 0.7em;
|
||||
border: 2px solid #888;
|
||||
margin: 1.7em 0 1.7em 0.3em;
|
||||
overflow: auto;
|
||||
width: 93%;
|
||||
background: #EEEEEE;
|
||||
}
|
||||
h1 {
|
||||
font-size: 20pt;
|
||||
counter-increment: counter-h1;
|
||||
counter-reset: counter-h2;
|
||||
}
|
||||
h2 {
|
||||
font-size: 17pt;
|
||||
counter-increment: counter-h2;
|
||||
counter-reset: counter-h3;
|
||||
}
|
||||
h3 {
|
||||
font-size: 14pt;
|
||||
counter-increment: counter-h3;
|
||||
counter-reset: counter-h4;
|
||||
}
|
||||
h1:before {
|
||||
content: counter(counter-h1) ". ";
|
||||
}
|
||||
h2:before {
|
||||
content: counter(counter-h1) "." counter(counter-h2) ". ";
|
||||
}
|
||||
h3:before {
|
||||
content: counter(counter-h1) "." counter(counter-h2) "." counter(counter-h3) ". ";
|
||||
}
|
||||
ul {
|
||||
list-style-type: circle;
|
||||
}
|
||||
.quotedString
|
||||
{
|
||||
color: #0000FF;
|
||||
}
|
||||
.comment
|
||||
{
|
||||
color: #999999;
|
||||
}
|
||||
.operator
|
||||
{
|
||||
color: #00CCCC;
|
||||
}
|
||||
.builtinVariable
|
||||
{
|
||||
color: #CCCC00;
|
||||
}
|
||||
.variableSpecifier
|
||||
{
|
||||
color: #FF0000;
|
||||
}
|
||||
.keyword
|
||||
{
|
||||
color: #AA0033;
|
||||
}
|
||||
.builtinFunction
|
||||
{
|
||||
color: #AA00AA;
|
||||
}
|
||||
.identifier
|
||||
{
|
||||
color: #009900;
|
||||
}
|
||||
.number
|
||||
{
|
||||
color: #9999FF;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
var ie = document.all != null;
|
||||
var moz = !ie && document.getElementById != null && document.layers == null;
|
||||
function emulateHTMLModel()
|
||||
{
|
||||
// copied from http://www.webfx.nu/dhtml/ieemu/htmlmodel.html
|
||||
|
||||
// This function is used to generate a html string for the text properties/methods
|
||||
// It replaces '\n' with "<BR"> as well as fixes consecutive white spaces
|
||||
// It also repalaces some special characters
|
||||
function convertTextToHTML(s) {
|
||||
s = s.replace(/\&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\n/g, "<BR>").replace(/\t/g, " "); //tachyon
|
||||
while (/\s\s/.test(s))
|
||||
s = s.replace(/\s\s/, " ");
|
||||
return s.replace(/\s/g, " ");
|
||||
}
|
||||
|
||||
|
||||
HTMLElement.prototype.__defineSetter__("innerText", function (sText) {
|
||||
this.innerHTML = convertTextToHTML(sText);
|
||||
return sText;
|
||||
});
|
||||
|
||||
var tmpGet;
|
||||
HTMLElement.prototype.__defineGetter__("innerText", tmpGet = function () {
|
||||
var r = this.ownerDocument.createRange();
|
||||
r.selectNodeContents(this);
|
||||
return r.toString();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (moz)
|
||||
emulateHTMLModel();
|
||||
|
||||
|
||||
// Regular Expressions largely copied from Cory Johns (darkness@yossman.net) excellent Syntax::Highlight::Perl module (see http://search.cpan.org/~johnsca/)
|
||||
|
||||
var re;
|
||||
var RE = new Array;
|
||||
|
||||
// quoted string
|
||||
re = /('|"|`).*?\1/;
|
||||
RE[0] = new RegExp(re);
|
||||
|
||||
// comment
|
||||
re = /\#.*?([\r\n]+|$)/; //tachyon
|
||||
RE[1] = new RegExp(re);
|
||||
|
||||
// operator
|
||||
re = /xor|\.\.\.|and|not|\|\|\=|cmp|\>\>\=|\<\<\=|\<\=\>|\&\&\=|or|\=\>|\!\~|\^\=|\&\=|\|\=|\.\=|x\=|\%\=|\/\=|\*\=|\-\=|\+\=|\=\~|\*\*|\-\-|\.\.|\|\||\&\&|\+\+|\-\>|ne|eq|\!\=|\=\=|ge|le|gt|lt|\>\=|\<\=|\>\>|\<\<|\,|\=|\:|\?|\^|\||x|\%|\/|\*|\<|\&|\\|\~|\!|\>|\.|\-|\+ /;
|
||||
RE[2] = new RegExp(re);
|
||||
|
||||
// builtin variables
|
||||
re = /\$\#?_|\$(?:\^[LAECDFHIMOPRSTWX]|[0-9&`'+*.\/|,\\";#%=\-~^:?!@\$<>()\[\]])|\$\#?ARGV(?:\s*\[)?|\$\#?INC\s*\[|\$(?:ENV|SIG|INC)\s*\{|\@(?:_|ARGV|INC)|\%(?:INC|ENV|SIG)/;
|
||||
RE[3] = new RegExp(re);
|
||||
|
||||
// variable class specifiers
|
||||
re = /(?:(?:[\@\%\*]|\$\#?)\$*)/;
|
||||
RE[4] = new RegExp(re);
|
||||
|
||||
// keyword
|
||||
re = /(continue|foreach|require|package|scalar|format|unless|local|until|while|elsif|next|last|goto|else|redo|sub|for|use|no|if|my)\b/;
|
||||
RE[5] = new RegExp(re);
|
||||
|
||||
// builtin function
|
||||
re = /(getprotobynumber|getprotobyname|getservbyname|gethostbyaddr|gethostbyname|getservbyport|getnetbyaddr|getnetbyname|getsockname|getpeername|setpriority|getprotoent|setprotoent|getpriority|endprotoent|getservent|setservent|endservent|sethostent|socketpair|getsockopt|gethostent|endhostent|setsockopt|setnetent|quotemeta|localtime|prototype|getnetent|endnetent|rewinddir|wantarray|getpwuid|closedir|getlogin|readlink|endgrent|getgrgid|getgrnam|shmwrite|shutdown|readline|endpwent|setgrent|readpipe|formline|truncate|dbmclose|syswrite|setpwent|getpwnam|getgrent|getpwent|ucfirst|sysread|setpgrp|shmread|sysseek|sysopen|telldir|defined|opendir|connect|lcfirst|getppid|binmode|syscall|sprintf|getpgrp|readdir|seekdir|waitpid|reverse|unshift|symlink|dbmopen|semget|msgrcv|rename|listen|chroot|msgsnd|shmctl|accept|unpack|exists|fileno|shmget|system|unlink|printf|gmtime|msgctl|semctl|values|rindex|substr|splice|length|msgget|select|socket|return|caller|delete|alarm|ioctl|index|undef|lstat|times|srand|chown|fcntl|close|write|umask|rmdir|study|sleep|chomp|untie|print|utime|mkdir|atan2|split|crypt|flock|chmod|BEGIN|bless|chdir|semop|shift|reset|link|stat|chop|grep|fork|dump|join|open|tell|pipe|exit|glob|warn|each|bind|sort|pack|eval|push|keys|getc|kill|seek|sqrt|send|wait|rand|tied|read|time|exec|recv|eof|chr|int|ord|exp|pos|pop|sin|log|abs|oct|hex|tie|cos|vec|END|ref|map|die|\-C|\-b|\-S|\-u|\-t|\-p|\-l|\-d|\-f|\-g|\-s|\-z|uc|\-k|\-e|\-O|\-T|\-B|\-M|do|\-A|\-X|\-W|\-c|\-R|\-o|\-x|lc|\-w|\-r)\b/;
|
||||
RE[6] = new RegExp(re);
|
||||
|
||||
// identifier (variable, subroutine, packages)
|
||||
re = /(?:(?:[A-Za-z_]|::)(?:\w|::)*)/;
|
||||
RE[7] = new RegExp(re);
|
||||
|
||||
// number
|
||||
re = /0x[\da-fA-F]+|[_.\d]+([eE][-+]?\d+)?/;
|
||||
RE[8] = new RegExp(re);
|
||||
|
||||
|
||||
var classes = new Array("quotedString", "comment", "operator", "builtinVariable", "variableSpecifier", "keyword", "builtinFunction", "identifier", "number");
|
||||
|
||||
|
||||
/* This is the actual highlighting function.
|
||||
* Takes an html object as argument
|
||||
* returns nothing
|
||||
* replaces the text inside the html object with colored text using <span>'s
|
||||
* css is defined separately. See the array classes to find out the css class names.
|
||||
*/
|
||||
function HighlightCode(object)
|
||||
{
|
||||
codeText = object.innerText; //HTML.replace(/<.*?>/g, "");
|
||||
object.innerHTML = '';
|
||||
var left;
|
||||
var match;
|
||||
var right;
|
||||
while (codeText.length > 0)
|
||||
{
|
||||
var mode = -1 ;
|
||||
var index = 999999999;
|
||||
for (var i = 0; i < RE.length; i++)
|
||||
{
|
||||
if ((codeText.match(RE[i])) && (RegExp.leftContext.length < index))
|
||||
{
|
||||
left = RegExp.leftContext;
|
||||
match = RegExp.lastMatch;
|
||||
right = RegExp.rightContext;
|
||||
index = RegExp.leftContext.length;
|
||||
mode = i;
|
||||
}
|
||||
}
|
||||
if (mode == -1)
|
||||
{
|
||||
object.appendChild(document.createTextNode(codeText)); //.replace(/\r\n/g, "\r")));
|
||||
codeText = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
// append the plain text to the <code> block
|
||||
object.appendChild(document.createTextNode(left)); //.replace(/\r\n/g, "\r")));
|
||||
|
||||
// create a new <span> with the current code
|
||||
var span = document.createElement("span");
|
||||
span.setAttribute("className", classes[mode]); // ie
|
||||
span.setAttribute("class", classes[mode]); //mozilla
|
||||
span.appendChild(document.createTextNode(match));
|
||||
object.appendChild(span);
|
||||
|
||||
codeText = right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// little bit of JQuery to highlight code in all pre elements
|
||||
$(document).ready(function(){
|
||||
$("pre").each(function(i){
|
||||
HighlightCode(this);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,869 @@
|
||||
use warnings;
|
||||
use strict;
|
||||
use Getopt::Long;
|
||||
use Convert::Binary::C;
|
||||
use Hash::Util qw/lock_hash/;
|
||||
use English;
|
||||
use Scalar::Util qw/looks_like_number/;
|
||||
use File::Basename;
|
||||
use File::Spec;
|
||||
use Pod::Usage;
|
||||
use Carp;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
bacnet.pl - Scriptable BACnet communications
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is a tool for scriptable BACnet communication. Users can write their own
|
||||
scripts using standard Perl syntax and API defined in this tool to perform desired
|
||||
execution sequences. For details on this tool's API, see Documentation.html. For other
|
||||
Perl documentation, see http://perldoc.perl.org
|
||||
|
||||
=begin html
|
||||
<link href="syntax.css" rel="stylesheet" type="text/css">
|
||||
<script src="jquery.js"></script>
|
||||
<script src="syntax.js"></script>
|
||||
|
||||
=end html
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
Usage: bacnet.pl [program_options] [-- script_args]
|
||||
|
||||
This program executes a script in perl syntax to perform BACnet/IP operations.
|
||||
|
||||
Possible program options:
|
||||
--script=s The script to execute.
|
||||
--log=s The file to log all output.
|
||||
--help This help message.
|
||||
|
||||
Possible environment variables are:
|
||||
BACNET_IFACE - set this value to dotted IP address of the interface (see
|
||||
ipconfig) for which you want to bind. Default is the interface which
|
||||
Windows considers to be the default (how???). Hence, if there is only a
|
||||
single network interface on Windows, the applications will choose it, and
|
||||
this setting will not be needed.
|
||||
BACNET_IP_PORT - UDP/IP port number (0..65534) used for BACnet/IP
|
||||
communications. Default is 47808 (0xBAC0).
|
||||
BACNET_APDU_TIMEOUT - set this value in milliseconds to change the APDU
|
||||
timeout. APDU Timeout is how much time a client waits for a response from
|
||||
a BACnet device.
|
||||
BACNET_BBMD_PORT - UDP/IP port number (0..65534) used for Foreign Device
|
||||
Registration. Defaults to 47808 (0xBAC0).
|
||||
BACNET_BBMD_TIMETOLIVE - number of seconds used in Foreign Device
|
||||
Registration (0..65535). Defaults to 60000 seconds.
|
||||
BACNET_BBMD_ADDRESS - dotted IPv4 address of the BBMD or Foreign Device
|
||||
Registrar.
|
||||
|
||||
=cut
|
||||
|
||||
############################################
|
||||
# Steps to prepare for execution
|
||||
############################################
|
||||
|
||||
# This is the relative path to get to the base directory cotaining the BACnet
|
||||
# Stack sources from the directory containing this file and the directory
|
||||
# within which InlineC code is built. The reason for delaring it here and
|
||||
# setting the value in a BEGIN block is so that the variable gets its value at
|
||||
# compile time before Inline::C tries to use that variable.
|
||||
my $relSourcePath;
|
||||
my $inlineCFile;
|
||||
my $inlineBuildDir;
|
||||
my $libDir;
|
||||
my $incDir1;
|
||||
my $incDir2;
|
||||
my $incDir3;
|
||||
BEGIN {
|
||||
# the Perl source file is in the same directory as in the InlineC file
|
||||
# this path should not contain any spaces
|
||||
$relSourcePath = File::Spec->rel2abs(dirname($0));
|
||||
die "Install path must not have spaces.\n" if $relSourcePath =~ /\s/;
|
||||
my @dirs = ();
|
||||
push @dirs, $relSourcePath;
|
||||
$inlineCFile = File::Spec->catfile(@dirs, "perl_bindings.c");
|
||||
|
||||
# all Inline C sources shall be contained in ./.Inline
|
||||
push @dirs, ".Inline";
|
||||
$inlineBuildDir = File::Spec->catdir(@dirs);
|
||||
pop @dirs;
|
||||
|
||||
# to properly link, need to reference ./../../lib
|
||||
push @dirs, "..";
|
||||
push @dirs, "..";
|
||||
push @dirs, "lib";
|
||||
$libDir = File::Spec->catdir(@dirs);
|
||||
pop @dirs;
|
||||
|
||||
# to properly build, need to reference ./../../include
|
||||
push @dirs, "include";
|
||||
$incDir1 = File::Spec->catdir(@dirs);
|
||||
pop @dirs;
|
||||
|
||||
# we will use the demo handlers, need to reference ./../../demo/object
|
||||
push @dirs, "demo";
|
||||
push @dirs, "object";
|
||||
$incDir2 = File::Spec->catdir(@dirs);
|
||||
pop @dirs;
|
||||
pop @dirs;
|
||||
|
||||
# TODO: This should be done in a more universal way
|
||||
# to properly build Win32 ports, need to refrence ./../../ports/win32
|
||||
push @dirs, "ports";
|
||||
push @dirs, "win32";
|
||||
$incDir3 = File::Spec->catdir(@dirs);
|
||||
}
|
||||
|
||||
use Inline (
|
||||
C => Config =>
|
||||
LIBS => "-L$libDir -lbacnet -liphlpapi",
|
||||
INC => ["-I$incDir1", "-I$incDir2", "-I$incDir3"],
|
||||
DIRECTORY => $inlineBuildDir,
|
||||
);
|
||||
|
||||
# this is the C source file for interfacing to the library. Yes, this could be
|
||||
# done natively in Perl, but this is just as easy (and probably faster to
|
||||
# execute).
|
||||
use Inline C => "$inlineCFile";
|
||||
|
||||
|
||||
my $ask_help = 0;
|
||||
my $script;
|
||||
my $log;
|
||||
my $logTo = \*STDOUT;
|
||||
my $logIndent = 0;
|
||||
my $logIsQuiet = 0;
|
||||
my $errorMsg;
|
||||
my $answer = '';
|
||||
|
||||
($ask_help = 1) unless GetOptions(
|
||||
'help|?' => \$ask_help,
|
||||
'script=s' => \$script,
|
||||
'log=s' => \$log,
|
||||
);
|
||||
|
||||
if (!defined($script) || !(-f $script))
|
||||
{
|
||||
print "Bad or no script file scpecified.\n";
|
||||
$ask_help = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
# Add the script's location to @INC so that they can include other scripts
|
||||
# using relative paths
|
||||
my $scriptdir = File::Spec->rel2abs(dirname($script));
|
||||
push @INC,$scriptdir;
|
||||
}
|
||||
|
||||
if ($ask_help) {
|
||||
print "============================\n\n";
|
||||
pod2usage(
|
||||
-exitval => 0,
|
||||
-verbose => 99,
|
||||
-sections => "NAME|DESCRIPTION|OPTIONS"
|
||||
);
|
||||
}
|
||||
|
||||
if (defined($log))
|
||||
{
|
||||
open(LOG, ">$log") || croak "Cannot open $log for writing: $!\n";
|
||||
$logTo = \*LOG;
|
||||
}
|
||||
|
||||
# Pull in the BACnet enumerations from the C header file
|
||||
my %C_ENUMS;
|
||||
eval {
|
||||
my $pwd = File::Spec->rel2abs(File::Spec->curdir());
|
||||
|
||||
# let's get into the directory so that we can pull in the bacnet enumerations
|
||||
my @dirs = ();
|
||||
push @dirs, dirname($0);
|
||||
push @dirs, "../../include";
|
||||
chdir(File::Spec->catdir(@dirs));
|
||||
my $c = Convert::Binary::C->new->parse_file('bacenum.h');
|
||||
foreach my $typedef ($c->typedef)
|
||||
{
|
||||
if (ref($$typedef{type}) eq "HASH")
|
||||
{
|
||||
my $enumeration = \%{$C_ENUMS{$$typedef{declarator}}};
|
||||
foreach my $enum_name (keys %{$$typedef{type}{enumerators}})
|
||||
{
|
||||
${$C_ENUMS{$$typedef{declarator}}}{$enum_name} = ${$$typedef{type}{enumerators}}{$enum_name};
|
||||
}
|
||||
}
|
||||
}
|
||||
lock_hash(%C_ENUMS);
|
||||
chdir($pwd);
|
||||
};
|
||||
if ($EVAL_ERROR)
|
||||
{
|
||||
croak "Error pulling in the enumerations. $@\n";
|
||||
}
|
||||
|
||||
# Prepare things for communication
|
||||
BacnetPrepareComm();
|
||||
|
||||
# Execute the user specified script
|
||||
Log("Executing $script - start time " . scalar(localtime(time())) );
|
||||
unless (my $return = do $script)
|
||||
{
|
||||
croak "could not parse $script: $@" if $@;
|
||||
croak "could not pull in $script: $!" unless defined $return;
|
||||
croak "could not execute $script" unless $return;
|
||||
}
|
||||
Log("Finished executing $script - end time " . scalar(localtime(time())) );
|
||||
|
||||
=head1 This tool's API
|
||||
|
||||
In addition to having all standard Perl flow control, functions, and modules,
|
||||
the this tool provides an API for performing BACnet communication functions.
|
||||
|
||||
=cut
|
||||
|
||||
##########################################
|
||||
# This block is the external API
|
||||
##########################################
|
||||
|
||||
=head2 ReadProperty
|
||||
|
||||
This function implements the ReadProperty service. There are no built in retry
|
||||
mechanisms. NOTE: all enumerations are defined in F<bacenum.h>
|
||||
|
||||
=head3 Inputs to ReadProperty
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>devideInstance</b> - the instance number of the device we are reading</li>
|
||||
<li><b>objectName</b> - the enumeration for the object name we are reading</li>
|
||||
<li><b>objectInstance</b> - the instance number of the object we are reading</li>
|
||||
<li><b>propertyName</b> - the enumeration for the property name we are reading</li>
|
||||
<li><b>index</b> - Optional (default -1): the index number we are reading from. -1 if not applicable</li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Outputs from ReadProperty
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>result</b> - the sting result (value or error) for ReadProperty</li>
|
||||
<li><b>isFailure</b> - zero means no failure, non-zero means failure</li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Example of ReadProperty
|
||||
|
||||
The following example will read AV0.PresentValue from device 1234
|
||||
|
||||
my ($res, $failed) = ReadProperty(1234, 'OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE');
|
||||
|
||||
=cut
|
||||
|
||||
sub ReadProperty {
|
||||
my $deviceInstance = shift;
|
||||
my $objectName = shift;
|
||||
my $objectInstance = shift;
|
||||
my $propertyName = shift;
|
||||
my $index = shift;
|
||||
my $isFailure = BindToDevice($deviceInstance);
|
||||
|
||||
# Loop for early exit
|
||||
while(1)
|
||||
{
|
||||
last if $isFailure;
|
||||
|
||||
my ($objectPrintName, $objectValue) = LookupEnumValue('BACNET_OBJECT_TYPE', $objectName);
|
||||
my ($propertyPrintName, $propertyValue) = LookupEnumValue('BACNET_PROPERTY_ID', $propertyName);
|
||||
|
||||
my $msg = "ReadProperty $objectPrintName" . '[' . $objectInstance . "].$propertyPrintName";
|
||||
if (defined($index))
|
||||
{
|
||||
$msg .= ".$index";
|
||||
} else {
|
||||
$index = -1;
|
||||
}
|
||||
$msg .= " from Device" . '[' . $deviceInstance . "] ==> ";
|
||||
|
||||
LogAnswer('', 0);
|
||||
if ( BacnetReadProperty($deviceInstance, $objectValue, $objectInstance, $propertyValue, $index) )
|
||||
{
|
||||
BacnetGetError($errorMsg);
|
||||
$msg .= "Problem: $errorMsg";
|
||||
$isFailure = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$msg .= $answer;
|
||||
$isFailure = 0;
|
||||
}
|
||||
Log($msg);
|
||||
last;
|
||||
}
|
||||
|
||||
return ($answer, $isFailure);
|
||||
}
|
||||
|
||||
=head2 ReadPropertyMultiple
|
||||
|
||||
This function implements the ReadPropertyMultiple service. There are no built in retry
|
||||
mechanisms. NOTE: all enumerations are defined in F<bacenum.h>
|
||||
|
||||
=head3 Inputs to ReadPropertyMultiple
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>devideInstance</b> - the instance number of the device we are reading</li>
|
||||
<li><b>r_answerList</b> - reference to a list where to store the answers</li>
|
||||
<li><b>list</b> - a list of ReadAccessSpecifications</li>
|
||||
<ul>
|
||||
<li><b>objectType</b> - the enumeration for the object name to read from</li>
|
||||
<li><b>objectInstance</b> - the instance number of the object we are reading</li>
|
||||
<li><b>propertyName</b> - the enumeration for the property name we are reading</li>
|
||||
<li><b>index</b> - the index number we are reading from. Use -1 if not applicable</li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Outputs from ReadPropertyMultiple
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>result</b> - the 'QQQ' delimited concatenated sting result (value or error) for ReadPropertyMultiple. The parsed out result is returned in r_answerList</li>
|
||||
<li><b>isFailure</b> - zero means no failure, non-zero means failure</li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Example of ReadPropertyMultiple
|
||||
|
||||
The following example will read AV0.PresentValue and AV1.PresentValue from device 1234
|
||||
|
||||
my @RPM_request = ();
|
||||
my @RPM_answer = ();
|
||||
my $failed;
|
||||
push @RPM_request, ['OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE', -1];
|
||||
push @RPM_request, ['OBJECT_ANALOG_VALUE', 1, 'PROP_PRESENT_VALUE', -1];
|
||||
(undef, $failed) = ReadPropertyMultiple(1234, \@RPM_answer, @RPM_request);
|
||||
|
||||
=cut
|
||||
|
||||
sub ReadPropertyMultiple
|
||||
{
|
||||
my $deviceInstanceNumber = shift;
|
||||
my $r_answerList = shift;
|
||||
my @list = @ARG;
|
||||
my @modifiedList = ();
|
||||
my $msg = '';
|
||||
my $isFailure = BindToDevice($deviceInstanceNumber);
|
||||
|
||||
# loop for early exit
|
||||
while(1)
|
||||
{
|
||||
last if $isFailure;
|
||||
|
||||
Log("ReadPropertyMultiple:");
|
||||
$logIndent += 4;
|
||||
|
||||
foreach my $r_prop (@list)
|
||||
{
|
||||
my @tmpList = ();
|
||||
push @tmpList, $$r_prop[$_] for (0 .. 3);
|
||||
(undef, $tmpList[0]) = LookupEnumValue('BACNET_OBJECT_TYPE', $$r_prop[0]);
|
||||
(undef, $tmpList[2]) = LookupEnumValue('BACNET_PROPERTY_ID', $$r_prop[2]);
|
||||
push @modifiedList, \@tmpList;
|
||||
}
|
||||
|
||||
LogAnswer('', 0);
|
||||
@{$r_answerList} = ();
|
||||
if (BacnetReadPropertyMultiple($deviceInstanceNumber, @modifiedList))
|
||||
{
|
||||
BacnetGetError($errorMsg);
|
||||
Log("Problem: $errorMsg");
|
||||
$isFailure = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
my $i = 0;
|
||||
foreach (split('QQQ', $answer))
|
||||
{
|
||||
my ($objectPrintName, undef) = LookupEnumValue('BACNET_OBJECT_TYPE', $list[$i][0]);
|
||||
my ($propertyPrintName, undef) = LookupEnumValue('BACNET_PROPERTY_ID', $list[$i][2]);
|
||||
my $msg = $objectPrintName . '.[' . $list[$i][1] . '].' . $propertyPrintName;
|
||||
if ($list[$i][3] != -1)
|
||||
{
|
||||
$msg .= '.[' . $list[$i][3] . ']';
|
||||
}
|
||||
$msg .= " ==> $_";
|
||||
Log($msg);
|
||||
push @{$r_answerList}, $_;
|
||||
$i++;
|
||||
}
|
||||
$isFailure = 0;
|
||||
}
|
||||
|
||||
$logIndent -= 4;
|
||||
last;
|
||||
}
|
||||
|
||||
return ($answer, $isFailure);
|
||||
}
|
||||
|
||||
=head2 WriteProperty
|
||||
|
||||
This function implements the WriteProperty service. There are no built in retry
|
||||
mechanisms. NOTE: all enumerations are defined in F<bacenum.h>
|
||||
|
||||
=head3 Inputs to WriteProperty
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>devideInstance</b> - the instance number of the device we are writing</li>
|
||||
<li><b>objectName</b> - the enumeration for the object name we are writing</li>
|
||||
<li><b>objectInstance</b> - the instance number of the object we are writing</li>
|
||||
<li><b>propertyName</b> - the enumeration for the property name we are writing</li>
|
||||
<li><b>tagName</b> - the enumeration for the type of value we are writing. To specify context tags, prepend the tag name with "Cn:" where 'n' is the context number.</li>
|
||||
<li><b>value</b> - the value we are writing</li>
|
||||
<li><b>priority</b> - Optional (default 0): the priority within Priority Array to write at. Use 1-16 when specify priority, 0 to not specify priority.</li>
|
||||
<li><b>index</b> - Optional (default -1): the index within an array we are writing to. Use positive number to indicate index, -1 to not specify index.</li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Outputs from WriteProperty
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>result</b> - the sting result (value or error) for WriteProperty</li>
|
||||
<li><b>isFailure</b> - zero means no failure, non-zero means failure</li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Example of WriteProperty
|
||||
|
||||
The following example will write 1.0 to AV0.PresentValue in device 1234
|
||||
|
||||
my ($res, $failed) = WriteProperty(1234, 'OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE', 'BACNET_APPLICATION_TAG_REAL', 1.0);
|
||||
|
||||
=cut
|
||||
|
||||
sub WriteProperty {
|
||||
my $deviceInstance = shift;
|
||||
my $objectName = shift;
|
||||
my $objectInstance = shift;
|
||||
my $propertyName = shift;
|
||||
my $tagName = shift;
|
||||
my $value = shift;
|
||||
my $priority = shift;
|
||||
my $index = shift;
|
||||
my $isFailure = BindToDevice($deviceInstance);
|
||||
|
||||
# loop for early exit
|
||||
while(1)
|
||||
{
|
||||
last if $isFailure;
|
||||
|
||||
my ($objectPrintName, $objectValue) = LookupEnumValue('BACNET_OBJECT_TYPE', $objectName);
|
||||
my ($propertyPrintName, $propertyValue) = LookupEnumValue('BACNET_PROPERTY_ID', $propertyName);
|
||||
|
||||
my $tagValue = '';
|
||||
if ($tagName =~ /^(C\d+):(.*)$/)
|
||||
{
|
||||
$tagName = $2;
|
||||
$tagValue = "$1 ";
|
||||
}
|
||||
my ($tagPrintName, $tagNewValue) = LookupEnumValue('BACNET_APPLICATION_TAG', $tagName);
|
||||
$tagValue .= $tagNewValue;
|
||||
|
||||
my $msg = "WriteProperty $tagPrintName:$value to $objectPrintName" . '[' . $objectInstance . "].$propertyPrintName";
|
||||
if (defined($index))
|
||||
{
|
||||
$msg .= '[' . $index . ']';
|
||||
}
|
||||
else
|
||||
{
|
||||
# an index of -1 means that we are not writing to an array
|
||||
$index = -1;
|
||||
}
|
||||
if (defined($priority))
|
||||
{
|
||||
$msg .= '@' . $priority
|
||||
}
|
||||
else
|
||||
{
|
||||
# a priority of 0 means we are not writing to a priority array
|
||||
$priority = 0;
|
||||
}
|
||||
$msg .= " in Device" . '[' . $deviceInstance . "] ==> ";
|
||||
|
||||
LogAnswer('', 0);
|
||||
if ( BacnetWriteProperty($deviceInstance, $objectValue, $objectInstance, $propertyValue, $priority, $index, $tagValue, $value) )
|
||||
{
|
||||
BacnetGetError($errorMsg);
|
||||
$msg .= "Problem: $errorMsg\n";
|
||||
$isFailure = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$msg .= $answer;
|
||||
$isFailure = 0;
|
||||
}
|
||||
Log($msg);
|
||||
last;
|
||||
}
|
||||
|
||||
return ($answer, $isFailure);
|
||||
}
|
||||
|
||||
=head2 TimeSync
|
||||
|
||||
This function implements the TimeSync and UTCTimeSync services
|
||||
|
||||
=head3 Inputs to TimeSync
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>deviceInstanceNumber</b> - the instance number of the device we are reading</li>
|
||||
<li><b>year</b> - Year (i.e. 2011)</li>
|
||||
<li><b>month</b> - Month (i.e. 11 for November)</li>
|
||||
<li><b>day</b> - Day (i.e. 1 for first of month)</li>
|
||||
<li><b>hour</b> - Hour (i.e. 23 for 11pm)</li>
|
||||
<li><b>minute</b> - Minute (i.e. 0-59)</li>
|
||||
<li><b>second</b> - Second (i,e. 0-59)</li>
|
||||
<li><b>utcOffset</b> - Optional: if specified defines the UTC offset and forces UTCTimeSync</li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Outputs from TimeSync
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>isFailure</b> - zero means no failure, non-zero means failure</li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Example of TimeSync
|
||||
|
||||
$isFailure = TimeSync($deviceInstance, $1, $2, $3, $4, $5, $6) unless $isFailure;
|
||||
|
||||
=cut
|
||||
|
||||
sub TimeSync
|
||||
{
|
||||
my $deviceInstanceNumber = shift;
|
||||
my $year = shift;
|
||||
my $month = shift;
|
||||
my $day = shift;
|
||||
my $hour = shift;
|
||||
my $minute = shift;
|
||||
my $second = shift;
|
||||
my $utcOffset = shift;
|
||||
my $isUTC;
|
||||
|
||||
my $isFailure = BindToDevice($deviceInstanceNumber);
|
||||
|
||||
# loop for early exit
|
||||
while(1)
|
||||
{
|
||||
last if $isFailure;
|
||||
|
||||
# be a pessimist. Assume things will fail
|
||||
$isFailure = 1;
|
||||
|
||||
if (defined($utcOffset))
|
||||
{
|
||||
$isUTC = 1;
|
||||
Log("UTC Time Sync not yet supported.");
|
||||
last;
|
||||
}
|
||||
else
|
||||
{
|
||||
$utcOffset = 0;
|
||||
$isUTC = 0;
|
||||
}
|
||||
|
||||
if ($year < 1900 || $year > 2099)
|
||||
{
|
||||
Log("Year '$year' is invalid.");
|
||||
last;
|
||||
}
|
||||
|
||||
if ($month <= 0 || $month > 12)
|
||||
{
|
||||
Log("Month '$month' is invalid.");
|
||||
last;
|
||||
}
|
||||
|
||||
if ($day <= 0 || $day > 31)
|
||||
{
|
||||
Log("Day '$day' is invalid.");
|
||||
last;
|
||||
}
|
||||
|
||||
if ($hour < 0 || $hour > 23)
|
||||
{
|
||||
Log("Hour '$hour' is invalid.");
|
||||
last;
|
||||
}
|
||||
|
||||
if ($minute < 0 || $minute > 59)
|
||||
{
|
||||
Log("Minute '$minute' is invalid.");
|
||||
last;
|
||||
}
|
||||
|
||||
if ($second < 0 || $second > 59)
|
||||
{
|
||||
Log("Second '$second' is invalid.");
|
||||
last;
|
||||
}
|
||||
|
||||
Log("TimeSync: Device[$deviceInstanceNumber] $year/$month/$day $hour:$minute:$second");
|
||||
|
||||
$isFailure = BacnetTimeSync($deviceInstanceNumber, $year, $month, $day, $hour, $minute, $second, $isUTC, $utcOffset);
|
||||
last;
|
||||
}
|
||||
|
||||
return $isFailure;
|
||||
}
|
||||
|
||||
=head2 Log
|
||||
|
||||
This function prints out to the desired method of logging (STDOUT or file).
|
||||
NewLine characters are not required when making calls to this function. If any
|
||||
NewLine characters are specified, they will be stripped out. To print an empty
|
||||
line, pass in a space as the message. NOTE: This function will honor previous
|
||||
requests to silence the log (see SilcenseLog for details)
|
||||
|
||||
=head3 Inputs to Log
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>msg</b> - the message to output
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Example of Log
|
||||
|
||||
The following example will print out "hello world"
|
||||
|
||||
Log("Hello World");
|
||||
|
||||
=cut
|
||||
|
||||
###############################################################################
|
||||
# Global Variables affecting this function
|
||||
# logIsQuiet do not print anytihng if the log was qieted
|
||||
# logIndent how many spaces to put in front of every logged line
|
||||
###############################################################################
|
||||
sub Log {
|
||||
my $msg = shift;
|
||||
|
||||
if (defined($msg) && !$logIsQuiet)
|
||||
{
|
||||
my @last = split('', substr($msg, -2));
|
||||
|
||||
# if there is nothing to print, then don't do it
|
||||
return if (scalar(@last) == 0);
|
||||
|
||||
# if there are newline-like characters, get rid of them.
|
||||
while ($msg =~/^(.*)[\r\n]+(.*)$/)
|
||||
{
|
||||
$msg = $1 . $2;
|
||||
}
|
||||
|
||||
local $OUTPUT_RECORD_SEPARATOR = "\n";
|
||||
print $logTo ' ' x $logIndent . $msg;
|
||||
}
|
||||
}
|
||||
|
||||
=head2 SilenceLog
|
||||
|
||||
This function requests that all future log messages be either suppressed or
|
||||
enabled.
|
||||
|
||||
=head3 Inputs to SilenceLog
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>logIsQuiet</b> - zero means print to log, non-zero means supress log
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Outputs from SilenceLog
|
||||
|
||||
The previous value of whether or not the log was silenced before caling this
|
||||
function.
|
||||
|
||||
=head3 Example of SilenceLog
|
||||
|
||||
The following example will print out "hello", but not "world"
|
||||
|
||||
Log("Hello");
|
||||
SilenceLog(1);
|
||||
Log("World");
|
||||
|
||||
=cut
|
||||
|
||||
sub SilenceLog {
|
||||
my $prevValue = $logIsQuiet;
|
||||
$logIsQuiet = shift;
|
||||
return $prevValue;
|
||||
}
|
||||
|
||||
=head2 Retry
|
||||
|
||||
This function will try to execute the requested command up to specified number
|
||||
of times, awaiting the requested answer, with a specified pause between
|
||||
retries. NOTE: the only functions which can be executed by this function are
|
||||
ones which return two parameres in the form of ($response, $isFailure)
|
||||
|
||||
=head3 Inputs to Retry
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>r_func</b> - The reference to the function which is to be retried</li>
|
||||
<li><b>r_funcArgs</b> - A reference to an array of arguments for the function to be executed</li>
|
||||
<li><b>desiredOutput</b> - The condition which will terminate the retrying. Can be either a number or a regexp to patch against the $response return of the function</li>
|
||||
<li><b>maxTries</b> - The maximum number of retry attempts before calling it quits</li>
|
||||
<li><b>sleepSeconds</b> - The number of seconds (could be fractional) to wait between retries</li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Outputs from Retry
|
||||
|
||||
=begin html
|
||||
<ul>
|
||||
<li><b>$resp</b> - The response from the last execution of requested function</li>
|
||||
<li><b>isFailure</b> - zero means no failure, non-zero means failure</li>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
|
||||
=head3 Example of Retry
|
||||
|
||||
The following example will execute the ReadProperty function to read a property
|
||||
from an object (see ReadProperty for details on those arguments) with up to
|
||||
$maxRetries retries (with $retryDelay delay between retries) or unitl the
|
||||
desired answer of 42 is received.
|
||||
|
||||
my ($resp, $isFailure) = Retry(
|
||||
\&ReadProperty, [$deviceInstance, 'OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE'],
|
||||
42, $maxRetries, $retryDelay
|
||||
);
|
||||
if ($isFailure)
|
||||
{
|
||||
die "Value was not 42. Last response was '$resp'";
|
||||
}
|
||||
|
||||
The following example will try to execute a WriteProperty (see that function for
|
||||
details on its arguments) until the write succeeds.
|
||||
|
||||
my ($resp, $isFailure) = Retry(
|
||||
\&WriteProperty, [$deviceInstance, 'OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE', 'BACNET_APPLICATION_TAG_REAL', 42.0],
|
||||
"Acknowledged", $maxRetries, $retryDelay
|
||||
);
|
||||
if ($isFailure)
|
||||
{
|
||||
die "Could not write 42. Last response was '$resp'";
|
||||
}
|
||||
|
||||
=cut
|
||||
sub Retry {
|
||||
my $r_func = shift;
|
||||
my $r_funcArgs = shift;
|
||||
my $desiredOutput = shift;
|
||||
my $maxTries = shift;
|
||||
my $sleepSeconds = shift;
|
||||
|
||||
my ($resp, $failed);
|
||||
|
||||
my $i;
|
||||
for ($i=0; $i<$maxTries; $i++)
|
||||
{
|
||||
($resp, $failed) = &{$r_func}(@{$r_funcArgs});
|
||||
unless ($failed)
|
||||
{
|
||||
if (looks_like_number($desiredOutput))
|
||||
{
|
||||
last if (looks_like_number($resp) && ($resp == $desiredOutput));
|
||||
}
|
||||
else
|
||||
{
|
||||
last if ($resp =~ /$desiredOutput/);
|
||||
}
|
||||
}
|
||||
select(undef, undef, undef, $sleepSeconds);
|
||||
}
|
||||
|
||||
return ($resp, ($i == $maxTries));
|
||||
}
|
||||
|
||||
|
||||
##########################################
|
||||
# These are the supporting functions
|
||||
##########################################
|
||||
|
||||
sub LookupEnumValue {
|
||||
my $enumType = shift;
|
||||
my $enumName = shift;
|
||||
my $printName;
|
||||
|
||||
if (!exists($C_ENUMS{$enumType}{$enumName}))
|
||||
{
|
||||
print "Requested enumeration '$enumName' does not exist within '$enumType'.\n";
|
||||
exit -1;
|
||||
}
|
||||
|
||||
# lookup the value
|
||||
my $value = $C_ENUMS{$enumType}{$enumName};
|
||||
|
||||
# reformat the OBJECT name style
|
||||
my %reformat = (
|
||||
'BACNET_PROPERTY_ID' => 'PROP',
|
||||
'BACNET_OBJECT_TYPE' => 'OBJECT',
|
||||
'BACNET_APPLICATION_TAG' => 'BACNET_APPLICATION_TAG',
|
||||
);
|
||||
|
||||
if (exists($reformat{$enumType}))
|
||||
{
|
||||
if ($enumName =~ /$reformat{$enumType}_(.*)/)
|
||||
{
|
||||
$printName = '';
|
||||
$printName .= ucfirst lc $_ foreach (split('_', $1));
|
||||
}
|
||||
}
|
||||
|
||||
return ($printName, $value);
|
||||
}
|
||||
|
||||
sub BindToDevice {
|
||||
my $deviceInstance = shift;
|
||||
my $isFailure = 0;
|
||||
|
||||
if ( BacnetBindToDevice($deviceInstance) )
|
||||
{
|
||||
BacnetGetError($errorMsg);
|
||||
Log("Problem binding to deivce $deviceInstance: $errorMsg\n");
|
||||
$isFailure = 1;
|
||||
}
|
||||
|
||||
return $isFailure;
|
||||
}
|
||||
|
||||
sub LogAnswer {
|
||||
my $newAnswer = shift;
|
||||
my $append = shift;
|
||||
|
||||
$answer = '' unless $append;
|
||||
$answer .= $newAnswer;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
my (
|
||||
$device, # device instance number
|
||||
$objectName, # object type name
|
||||
$objectInst, # object instance number
|
||||
$propName, # property name
|
||||
$index, # property index
|
||||
);
|
||||
|
||||
GetOptions(
|
||||
'device=i' => \$device,
|
||||
'objName=s' => \$objectName,
|
||||
'objInst=i' => \$objectInst,
|
||||
'property=s' => \$propName,
|
||||
'index=i' => \$index,
|
||||
);
|
||||
|
||||
Help() unless ( defined($device) &&
|
||||
defined($objectName) &&
|
||||
defined($objectInst) &&
|
||||
defined($propName)
|
||||
);
|
||||
|
||||
my ($resp, $failed) = ReadProperty($device, $objectName, $objectInst, $propName, $index);
|
||||
print "status was '$failed' and the response was '$resp'\n";
|
||||
|
||||
sub Help {
|
||||
print <<END;
|
||||
|
||||
This script demonstrates the ReadProperty service functionality using Perl
|
||||
bindings. To run this script, you must specify the following arguments to it:
|
||||
* device This is the device instance number (i.e. 1234)
|
||||
* objName This is the object type name (i.e. OBJECT_ANALOG_VALUE). See
|
||||
include/bacenum.h for complete list
|
||||
* objInst This is the object instance number you want to read (i.e. 1)
|
||||
* property This is the name of the property you want to read (i.e.
|
||||
PROP_PRESENT_VALUE). See include/bacenum.h for complete list
|
||||
* index This is an optional parameter. If you want to read from a specific
|
||||
index, then specify here (i.e. 1). Otherwise, don't specify this
|
||||
option.
|
||||
|
||||
As a complete example, to run this script using the main bacnet tool to read
|
||||
AnalogValue1.PresentValue from device instance 1234, use
|
||||
|
||||
perl bacnet.pl --script example_readprop.pl -- --device=1234 --objName=OBJECT_ANALOG_VALUE --objInst=1 --property=PROP_PRESENT_VALUE
|
||||
|
||||
END
|
||||
exit 1;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,933 @@
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/basic/services.h"
|
||||
#include "bacnet/bacenum.h"
|
||||
#include "bacnet/datalink/datalink.h"
|
||||
#include "bacnet/basic/object/device.h"
|
||||
#include <time.h>
|
||||
#include "bacnet/arf.h"
|
||||
|
||||
/* Free is redefined as a macro, but Perl does not like that. */
|
||||
#undef free
|
||||
|
||||
/* global variables used in this file */
|
||||
static uint32_t Target_Device_Object_Instance = 4194303;
|
||||
static unsigned Target_Max_APDU = 0;
|
||||
static bool Error_Detected = false;
|
||||
static BACNET_ADDRESS Target_Address;
|
||||
static uint8_t Request_Invoke_ID = 0;
|
||||
static bool isReadPropertyHandlerRegistered = false;
|
||||
static bool isReadPropertyMultipleHandlerRegistered = false;
|
||||
static bool isWritePropertyHandlerRegistered = false;
|
||||
static bool isAtomicWriteFileHandlerRegistered = false;
|
||||
static bool isAtomicReadFileHandlerRegistered = false;
|
||||
|
||||
/****************************************/
|
||||
/* Logging Support */
|
||||
/****************************************/
|
||||
#define MAX_ERROR_STRING 128
|
||||
#define NO_ERROR "No Error"
|
||||
static char Last_Error[MAX_ERROR_STRING] = NO_ERROR;
|
||||
static void LogError(const char *msg)
|
||||
{
|
||||
strcpy(Last_Error, msg);
|
||||
Error_Detected = true;
|
||||
}
|
||||
|
||||
void BacnetGetError(SV *errorMsg)
|
||||
{
|
||||
sv_setpv(errorMsg, Last_Error);
|
||||
strcpy(Last_Error, NO_ERROR);
|
||||
Error_Detected = false;
|
||||
}
|
||||
|
||||
static void __LogAnswer(const char *msg, unsigned append)
|
||||
{
|
||||
dSP;
|
||||
ENTER;
|
||||
SAVETMPS;
|
||||
PUSHMARK(SP);
|
||||
XPUSHs(sv_2mortal(newSVpv(msg, 0)));
|
||||
XPUSHs(sv_2mortal(newSViv(append)));
|
||||
PUTBACK;
|
||||
call_pv("LogAnswer", G_DISCARD);
|
||||
FREETMPS;
|
||||
LEAVE;
|
||||
}
|
||||
|
||||
/**************************************/
|
||||
/* error handlers */
|
||||
/*************************************/
|
||||
static void MyAbortHandler(
|
||||
BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t abort_reason, bool server)
|
||||
{
|
||||
(void)server;
|
||||
if (address_match(&Target_Address, src) &&
|
||||
(invoke_id == Request_Invoke_ID)) {
|
||||
char msg[MAX_ERROR_STRING];
|
||||
sprintf(msg, "BACnet Abort: %s",
|
||||
bactext_abort_reason_name((int)abort_reason));
|
||||
LogError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void MyRejectHandler(
|
||||
BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t reject_reason)
|
||||
{
|
||||
if (address_match(&Target_Address, src) &&
|
||||
(invoke_id == Request_Invoke_ID)) {
|
||||
char msg[MAX_ERROR_STRING];
|
||||
sprintf(msg, "BACnet Reject: %s",
|
||||
bactext_reject_reason_name((int)reject_reason));
|
||||
LogError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void My_Error_Handler(BACNET_ADDRESS *src,
|
||||
uint8_t invoke_id,
|
||||
BACNET_ERROR_CLASS error_class,
|
||||
BACNET_ERROR_CODE error_code)
|
||||
{
|
||||
if (address_match(&Target_Address, src) &&
|
||||
(invoke_id == Request_Invoke_ID)) {
|
||||
char msg[MAX_ERROR_STRING];
|
||||
sprintf(msg, "BACnet Error: %s: %s",
|
||||
bactext_error_class_name((int)error_class),
|
||||
bactext_error_code_name((int)error_code));
|
||||
LogError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************/
|
||||
/* ACK handlers */
|
||||
/**********************************/
|
||||
|
||||
/*****************************************/
|
||||
/* Decode the ReadProperty Ack and pass to perl */
|
||||
/****************************************/
|
||||
#define MAX_ACK_STRING 512
|
||||
void rp_ack_extract_data(BACNET_READ_PROPERTY_DATA *data)
|
||||
{
|
||||
char ackString[MAX_ACK_STRING] = "";
|
||||
char *pAckString = &ackString[0];
|
||||
BACNET_OBJECT_PROPERTY_VALUE object_value; /* for bacapp printing */
|
||||
BACNET_APPLICATION_DATA_VALUE value; /* for decode value data */
|
||||
int len = 0;
|
||||
uint8_t *application_data;
|
||||
int application_data_len;
|
||||
bool first_value = true;
|
||||
bool print_brace = false;
|
||||
|
||||
if (data) {
|
||||
application_data = data->application_data;
|
||||
application_data_len = data->application_data_len;
|
||||
/* FIXME: what if application_data_len is bigger than 255? */
|
||||
/* value? need to loop until all of the len is gone... */
|
||||
for (;;) {
|
||||
len = bacapp_decode_application_data(
|
||||
application_data, (uint8_t)application_data_len, &value);
|
||||
if (first_value && (len < application_data_len)) {
|
||||
first_value = false;
|
||||
strncat(pAckString, "{", 1);
|
||||
pAckString += 1;
|
||||
print_brace = true;
|
||||
}
|
||||
object_value.object_type = data->object_type;
|
||||
object_value.object_instance = data->object_instance;
|
||||
object_value.object_property = data->object_property;
|
||||
object_value.array_index = data->array_index;
|
||||
object_value.value = &value;
|
||||
bacapp_snprintf_value(pAckString,
|
||||
MAX_ACK_STRING - (pAckString - ackString), &object_value);
|
||||
if (len > 0) {
|
||||
if (len < application_data_len) {
|
||||
application_data += len;
|
||||
application_data_len -= len;
|
||||
/* there's more! */
|
||||
strncat(pAckString, ",", 1);
|
||||
pAckString += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (print_brace) {
|
||||
strncat(pAckString, "}", 1);
|
||||
pAckString += 1;
|
||||
}
|
||||
/* Now let's call a Perl function to display the data */
|
||||
__LogAnswer(ackString, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************/
|
||||
/* Decode the ReadPropertyMultiple Ack and pass to perl */
|
||||
/****************************************/
|
||||
void rpm_ack_extract_data(BACNET_READ_ACCESS_DATA *rpm_data)
|
||||
{
|
||||
BACNET_OBJECT_PROPERTY_VALUE object_value; /* for bacapp printing */
|
||||
BACNET_PROPERTY_REFERENCE *listOfProperties;
|
||||
BACNET_APPLICATION_DATA_VALUE *value;
|
||||
bool array_value = false;
|
||||
char ackString[MAX_ACK_STRING] = "";
|
||||
char *pAckString = &ackString[0];
|
||||
|
||||
if (rpm_data) {
|
||||
listOfProperties = rpm_data->listOfProperties;
|
||||
while (listOfProperties) {
|
||||
value = listOfProperties->value;
|
||||
if (value) {
|
||||
if (value->next) {
|
||||
strncat(pAckString, "{", 1);
|
||||
pAckString++;
|
||||
array_value = true;
|
||||
} else {
|
||||
array_value = false;
|
||||
}
|
||||
object_value.object_type = rpm_data->object_type;
|
||||
object_value.object_instance = rpm_data->object_instance;
|
||||
while (value) {
|
||||
object_value.object_property =
|
||||
listOfProperties->propertyIdentifier;
|
||||
object_value.array_index =
|
||||
listOfProperties->propertyArrayIndex;
|
||||
object_value.value = value;
|
||||
bacapp_snprintf_value(pAckString,
|
||||
MAX_ACK_STRING - (pAckString - ackString),
|
||||
&object_value);
|
||||
if (value->next) {
|
||||
strncat(pAckString, ",", 1);
|
||||
pAckString++;
|
||||
} else {
|
||||
if (array_value) {
|
||||
strncat(pAckString, "}", 1);
|
||||
pAckString++;
|
||||
}
|
||||
}
|
||||
value = value->next;
|
||||
}
|
||||
} else {
|
||||
/* AccessError */
|
||||
sprintf(ackString, "BACnet Error: %s: %s",
|
||||
bactext_error_class_name(
|
||||
(int)listOfProperties->error.error_class),
|
||||
bactext_error_code_name(
|
||||
(int)listOfProperties->error.error_code));
|
||||
LogError(ackString);
|
||||
}
|
||||
listOfProperties = listOfProperties->next;
|
||||
|
||||
/* Add a separator between consecutive entries so that Perl can */
|
||||
/* parse this out */
|
||||
strncat(pAckString, "QQQ", 3);
|
||||
pAckString += 3;
|
||||
}
|
||||
|
||||
/* Now let's call a Perl function to display the data */
|
||||
__LogAnswer(ackString, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void AtomicReadFileAckHandler(uint8_t *service_request,
|
||||
uint16_t service_len,
|
||||
BACNET_ADDRESS *src,
|
||||
BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
|
||||
{
|
||||
int len = 0;
|
||||
BACNET_ATOMIC_READ_FILE_DATA data;
|
||||
|
||||
if (address_match(&Target_Address, src) &&
|
||||
(service_data->invoke_id == Request_Invoke_ID)) {
|
||||
len =
|
||||
arf_ack_decode_service_request(service_request, service_len, &data);
|
||||
if (len > 0) {
|
||||
/* validate the parameters before storing data */
|
||||
if ((data.access == FILE_STREAM_ACCESS) &&
|
||||
(service_data->invoke_id == Request_Invoke_ID)) {
|
||||
char msg[32];
|
||||
uint8_t *pFileData;
|
||||
int i;
|
||||
|
||||
sprintf(msg, "EOF=%d,start=%d,", data.endOfFile,
|
||||
data.type.stream.fileStartPosition);
|
||||
__LogAnswer(msg, 0);
|
||||
|
||||
pFileData = octetstring_value(&data.fileData);
|
||||
for (i = 0; i < octetstring_length(&data.fileData); i++) {
|
||||
sprintf(msg, "%02x ", *pFileData);
|
||||
__LogAnswer(msg, 1);
|
||||
pFileData++;
|
||||
}
|
||||
} else {
|
||||
LogError("Bad stream access reported");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Handler for a ReadProperty ACK.
|
||||
* @ingroup DSRP
|
||||
* Doesn't actually do anything, except, for debugging, to
|
||||
* print out the ACK data of a matching request.
|
||||
*
|
||||
* @param service_request [in] The contents of the service request.
|
||||
* @param service_len [in] The length of the service_request.
|
||||
* @param src [in] BACNET_ADDRESS of the source of the message
|
||||
* @param service_data [in] The BACNET_CONFIRMED_SERVICE_DATA information
|
||||
* decoded from the APDU header of this message.
|
||||
*/
|
||||
static void My_Read_Property_Ack_Handler(uint8_t *service_request,
|
||||
uint16_t service_len,
|
||||
BACNET_ADDRESS *src,
|
||||
BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
|
||||
{
|
||||
int len = 0;
|
||||
BACNET_READ_PROPERTY_DATA data;
|
||||
|
||||
if (address_match(&Target_Address, src) &&
|
||||
(service_data->invoke_id == Request_Invoke_ID)) {
|
||||
len =
|
||||
rp_ack_decode_service_request(service_request, service_len, &data);
|
||||
if (len > 0) {
|
||||
rp_ack_extract_data(&data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Handler for a ReadPropertyMultiple ACK.
|
||||
* @ingroup DSRPM
|
||||
* For each read property, print out the ACK'd data,
|
||||
* and free the request data items from linked property list.
|
||||
*
|
||||
* @param service_request [in] The contents of the service request.
|
||||
* @param service_len [in] The length of the service_request.
|
||||
* @param src [in] BACNET_ADDRESS of the source of the message
|
||||
* @param service_data [in] The BACNET_CONFIRMED_SERVICE_DATA information
|
||||
* decoded from the APDU header of this message.
|
||||
*/
|
||||
static void My_Read_Property_Multiple_Ack_Handler(uint8_t *service_request,
|
||||
uint16_t service_len,
|
||||
BACNET_ADDRESS *src,
|
||||
BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
|
||||
{
|
||||
int len = 0;
|
||||
BACNET_READ_ACCESS_DATA *rpm_data;
|
||||
BACNET_READ_ACCESS_DATA *old_rpm_data;
|
||||
BACNET_PROPERTY_REFERENCE *rpm_property;
|
||||
BACNET_PROPERTY_REFERENCE *old_rpm_property;
|
||||
BACNET_APPLICATION_DATA_VALUE *value;
|
||||
BACNET_APPLICATION_DATA_VALUE *old_value;
|
||||
|
||||
if (address_match(&Target_Address, src) &&
|
||||
(service_data->invoke_id == Request_Invoke_ID)) {
|
||||
rpm_data = calloc(1, sizeof(BACNET_READ_ACCESS_DATA));
|
||||
if (rpm_data) {
|
||||
len = rpm_ack_decode_service_request(
|
||||
service_request, service_len, rpm_data);
|
||||
}
|
||||
if (len > 0) {
|
||||
while (rpm_data) {
|
||||
rpm_ack_extract_data(rpm_data);
|
||||
rpm_property = rpm_data->listOfProperties;
|
||||
while (rpm_property) {
|
||||
value = rpm_property->value;
|
||||
while (value) {
|
||||
old_value = value;
|
||||
value = value->next;
|
||||
free(old_value);
|
||||
}
|
||||
old_rpm_property = rpm_property;
|
||||
rpm_property = rpm_property->next;
|
||||
free(old_rpm_property);
|
||||
}
|
||||
old_rpm_data = rpm_data;
|
||||
rpm_data = rpm_data->next;
|
||||
free(old_rpm_data);
|
||||
}
|
||||
} else {
|
||||
LogError("RPM Ack Malformed! Freeing memory...");
|
||||
while (rpm_data) {
|
||||
rpm_property = rpm_data->listOfProperties;
|
||||
while (rpm_property) {
|
||||
value = rpm_property->value;
|
||||
while (value) {
|
||||
old_value = value;
|
||||
value = value->next;
|
||||
free(old_value);
|
||||
}
|
||||
old_rpm_property = rpm_property;
|
||||
rpm_property = rpm_property->next;
|
||||
free(old_rpm_property);
|
||||
}
|
||||
old_rpm_data = rpm_data;
|
||||
rpm_data = rpm_data->next;
|
||||
free(old_rpm_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void My_Write_Property_SimpleAck_Handler(BACNET_ADDRESS *src, uint8_t invoke_id)
|
||||
{
|
||||
if (address_match(&Target_Address, src) &&
|
||||
(invoke_id == Request_Invoke_ID)) {
|
||||
__LogAnswer("WriteProperty Acknowledged!", 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void Init_Service_Handlers()
|
||||
{
|
||||
Device_Init(NULL);
|
||||
|
||||
/* we need to handle who-is to support dynamic device binding to us */
|
||||
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is);
|
||||
|
||||
/* handle i-am to support binding to other devices */
|
||||
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_bind);
|
||||
|
||||
/* set the handler for all the services we don't implement
|
||||
It is required to send the proper reject message... */
|
||||
apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
|
||||
|
||||
/* we must implement read property - it's required! */
|
||||
apdu_set_confirmed_handler(
|
||||
SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property);
|
||||
|
||||
/* handle generic errors coming back */
|
||||
apdu_set_abort_handler(MyAbortHandler);
|
||||
apdu_set_reject_handler(MyRejectHandler);
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
waitAnswer,
|
||||
waitBind,
|
||||
} waitAction;
|
||||
|
||||
static void Wait_For_Answer_Or_Timeout(unsigned timeout_ms, waitAction action)
|
||||
{
|
||||
/* Wait for timeout, failure, or success */
|
||||
time_t last_seconds = time(NULL);
|
||||
time_t timeout_seconds = (apdu_timeout() / 1000) * apdu_retries();
|
||||
time_t elapsed_seconds = 0;
|
||||
uint16_t pdu_len = 0;
|
||||
BACNET_ADDRESS src = { 0 }; /* address where message came from */
|
||||
uint8_t Rx_Buf[MAX_MPDU] = { 0 };
|
||||
|
||||
while (true) {
|
||||
time_t current_seconds = time(NULL);
|
||||
|
||||
/* If error was detected then bail out */
|
||||
if (Error_Detected) {
|
||||
LogError("Some other error occurred");
|
||||
break;
|
||||
}
|
||||
|
||||
if (elapsed_seconds > timeout_seconds) {
|
||||
LogError("APDU Timeout");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Process PDU if one comes in */
|
||||
pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout_ms);
|
||||
if (pdu_len) {
|
||||
npdu_handler(&src, &Rx_Buf[0], pdu_len);
|
||||
}
|
||||
|
||||
/* at least one second has passed */
|
||||
if (current_seconds != last_seconds) {
|
||||
tsm_timer_milliseconds(((current_seconds - last_seconds) * 1000));
|
||||
}
|
||||
|
||||
if (action == waitAnswer) {
|
||||
/* Response was received. Exit. */
|
||||
if (tsm_invoke_id_free(Request_Invoke_ID)) {
|
||||
break;
|
||||
} else if (tsm_invoke_id_failed(Request_Invoke_ID)) {
|
||||
LogError("TSM Timeout!");
|
||||
tsm_free_invoke_id(Request_Invoke_ID);
|
||||
break;
|
||||
}
|
||||
} else if (action == waitBind) {
|
||||
if (address_bind_request(Target_Device_Object_Instance,
|
||||
&Target_Max_APDU, &Target_Address)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogError("Invalid waitAction requested");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Keep track of time */
|
||||
elapsed_seconds += (current_seconds - last_seconds);
|
||||
last_seconds = current_seconds;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************/
|
||||
/* Interface API */
|
||||
/****************************************************/
|
||||
|
||||
/****************************************************/
|
||||
/* This is the most fundamental setup needed to start communication */
|
||||
/****************************************************/
|
||||
void BacnetPrepareComm()
|
||||
{
|
||||
/* setup my info */
|
||||
Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE);
|
||||
address_init();
|
||||
Init_Service_Handlers();
|
||||
dlenv_init();
|
||||
}
|
||||
|
||||
/****************************************************/
|
||||
/* Try to bind to a device. If successful, return zero. If failure, return */
|
||||
/* non-zero and log the error details */
|
||||
/****************************************************/
|
||||
int BacnetBindToDevice(int deviceInstanceNumber)
|
||||
{
|
||||
int isFailure = 0;
|
||||
|
||||
/* Store the requested device instance number in the global variable for */
|
||||
/* reference in other communication routines */
|
||||
Target_Device_Object_Instance = deviceInstanceNumber;
|
||||
|
||||
/* try to bind with the device */
|
||||
if (!address_bind_request(
|
||||
deviceInstanceNumber, &Target_Max_APDU, &Target_Address)) {
|
||||
Send_WhoIs(
|
||||
Target_Device_Object_Instance, Target_Device_Object_Instance);
|
||||
|
||||
/* Wait for timeout, failure, or success */
|
||||
Wait_For_Answer_Or_Timeout(100, waitBind);
|
||||
}
|
||||
/* Clean up after ourselves */
|
||||
isFailure = Error_Detected;
|
||||
Error_Detected = false;
|
||||
return isFailure;
|
||||
}
|
||||
|
||||
/****************************************************/
|
||||
/* This is the interface to ReadProperty */
|
||||
/****************************************************/
|
||||
int BacnetReadProperty(int deviceInstanceNumber,
|
||||
int objectType,
|
||||
int objectInstanceNumber,
|
||||
int objectProperty,
|
||||
int objectIndex)
|
||||
{
|
||||
if (!isReadPropertyHandlerRegistered) {
|
||||
/* handle the data coming back from confirmed requests */
|
||||
apdu_set_confirmed_ack_handler(
|
||||
SERVICE_CONFIRMED_READ_PROPERTY, My_Read_Property_Ack_Handler);
|
||||
|
||||
/* handle any errors coming back */
|
||||
apdu_set_error_handler(
|
||||
SERVICE_CONFIRMED_READ_PROPERTY, My_Error_Handler);
|
||||
|
||||
/* indicate that handlers are now registered */
|
||||
isReadPropertyHandlerRegistered = true;
|
||||
}
|
||||
/* Send the message out */
|
||||
Request_Invoke_ID = Send_Read_Property_Request(deviceInstanceNumber,
|
||||
objectType, objectInstanceNumber, objectProperty, objectIndex);
|
||||
Wait_For_Answer_Or_Timeout(100, waitAnswer);
|
||||
|
||||
int isFailure = Error_Detected;
|
||||
Error_Detected = 0;
|
||||
return isFailure;
|
||||
}
|
||||
|
||||
/************************************************/
|
||||
/* This is the interface to ReadPropertyMultiple */
|
||||
/************************************************/
|
||||
int BacnetReadPropertyMultiple(int deviceInstanceNumber, ...)
|
||||
{
|
||||
/* Get the variable argument list from the stack */
|
||||
Inline_Stack_Vars;
|
||||
int rpmIndex = 1;
|
||||
BACNET_READ_ACCESS_DATA *rpm_object =
|
||||
calloc(1, sizeof(BACNET_READ_ACCESS_DATA));
|
||||
BACNET_READ_ACCESS_DATA *Read_Access_Data = rpm_object;
|
||||
BACNET_PROPERTY_REFERENCE *rpm_property;
|
||||
uint8_t buffer[MAX_PDU] = { 0 };
|
||||
|
||||
while (rpmIndex < Inline_Stack_Items) {
|
||||
SV *pSV = Inline_Stack_Item(rpmIndex++);
|
||||
|
||||
/* Make sure the argument is an Array Reference */
|
||||
if (SvTYPE(SvRV(pSV)) != SVt_PVAV) {
|
||||
LogError("Argument is not an Array reference");
|
||||
break;
|
||||
}
|
||||
/* Make sure we can access the memory */
|
||||
if (rpm_object) {
|
||||
rpm_object->listOfProperties = NULL;
|
||||
} else {
|
||||
LogError("Memory Allocation Issue");
|
||||
break;
|
||||
}
|
||||
|
||||
AV *pAV = (AV *)SvRV(pSV);
|
||||
SV **ppSV;
|
||||
|
||||
/* The 0th argument is the object type */
|
||||
ppSV = av_fetch(pAV, 0, 0);
|
||||
if (ppSV) {
|
||||
rpm_object->object_type = SvIV(*ppSV);
|
||||
} else {
|
||||
LogError("Problem parsing the Array of arguments");
|
||||
break;
|
||||
}
|
||||
|
||||
/* The 1st argument is the object instance */
|
||||
ppSV = av_fetch(pAV, 1, 0);
|
||||
if (ppSV) {
|
||||
rpm_object->object_instance = SvIV(*ppSV);
|
||||
} else {
|
||||
LogError("Problem parsing the Array of arguments");
|
||||
break;
|
||||
}
|
||||
|
||||
/* The 2nd argument is the property type */
|
||||
ppSV = av_fetch(pAV, 2, 0);
|
||||
if (ppSV) {
|
||||
rpm_property = calloc(1, sizeof(BACNET_PROPERTY_REFERENCE));
|
||||
rpm_object->listOfProperties = rpm_property;
|
||||
if (rpm_property) {
|
||||
rpm_property->propertyIdentifier = SvIV(*ppSV);
|
||||
} else {
|
||||
LogError("Memory allocation error");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LogError("Problem parsing the Array of arguments");
|
||||
break;
|
||||
}
|
||||
|
||||
/* The 3rd argument is the property index */
|
||||
ppSV = av_fetch(pAV, 3, 0);
|
||||
if (ppSV) {
|
||||
rpm_property->propertyArrayIndex = SvIV(*ppSV);
|
||||
} else {
|
||||
LogError("Problem parsing the Array of arguments");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Advance to the next RPM index */
|
||||
if (rpmIndex < Inline_Stack_Items) {
|
||||
rpm_object->next = calloc(1, sizeof(BACNET_READ_ACCESS_DATA));
|
||||
rpm_object = rpm_object->next;
|
||||
} else {
|
||||
rpm_object->next = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isReadPropertyMultipleHandlerRegistered) {
|
||||
/* handle the data coming back from confirmed requests */
|
||||
apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE,
|
||||
My_Read_Property_Multiple_Ack_Handler);
|
||||
|
||||
/* handle any errors coming back */
|
||||
apdu_set_error_handler(
|
||||
SERVICE_CONFIRMED_READ_PROP_MULTIPLE, My_Error_Handler);
|
||||
|
||||
/* indicate that handlers are now registered */
|
||||
isReadPropertyMultipleHandlerRegistered = true;
|
||||
}
|
||||
/* Send the message out */
|
||||
if (!Error_Detected) {
|
||||
Request_Invoke_ID = Send_Read_Property_Multiple_Request(
|
||||
&buffer[0], sizeof(buffer), deviceInstanceNumber, Read_Access_Data);
|
||||
Wait_For_Answer_Or_Timeout(100, waitAnswer);
|
||||
}
|
||||
/* Clean up allocated memory */
|
||||
BACNET_READ_ACCESS_DATA *old_rpm_object;
|
||||
BACNET_PROPERTY_REFERENCE *old_rpm_property;
|
||||
|
||||
rpm_object = Read_Access_Data;
|
||||
old_rpm_object = rpm_object;
|
||||
while (rpm_object) {
|
||||
rpm_property = rpm_object->listOfProperties;
|
||||
while (rpm_property) {
|
||||
old_rpm_property = rpm_property;
|
||||
rpm_property = rpm_property->next;
|
||||
free(old_rpm_property);
|
||||
}
|
||||
old_rpm_object = rpm_object;
|
||||
rpm_object = rpm_object->next;
|
||||
free(old_rpm_object);
|
||||
}
|
||||
|
||||
/* Process the return value */
|
||||
int isFailure = Error_Detected;
|
||||
Error_Detected = 0;
|
||||
return isFailure;
|
||||
}
|
||||
|
||||
/****************************************************/
|
||||
/* This is the interface to WriteProperty */
|
||||
/****************************************************/
|
||||
int BacnetWriteProperty(int deviceInstanceNumber,
|
||||
int objectType,
|
||||
int objectInstanceNumber,
|
||||
int objectProperty,
|
||||
int objectPriority,
|
||||
int objectIndex,
|
||||
const char *tag,
|
||||
const char *value)
|
||||
{
|
||||
char msg[MAX_ERROR_STRING];
|
||||
int isFailure = 1;
|
||||
|
||||
if (!isWritePropertyHandlerRegistered) {
|
||||
/* handle the ack coming back */
|
||||
apdu_set_confirmed_simple_ack_handler(SERVICE_CONFIRMED_WRITE_PROPERTY,
|
||||
My_Write_Property_SimpleAck_Handler);
|
||||
|
||||
/* handle any errors coming back */
|
||||
apdu_set_error_handler(
|
||||
SERVICE_CONFIRMED_WRITE_PROPERTY, My_Error_Handler);
|
||||
|
||||
/* indicate that handlers are now registered */
|
||||
isWritePropertyHandlerRegistered = true;
|
||||
}
|
||||
|
||||
if (objectIndex == -1) {
|
||||
objectIndex = BACNET_ARRAY_ALL;
|
||||
}
|
||||
/* Loop for eary exit; */
|
||||
do {
|
||||
/* Handle the tag/value pair */
|
||||
uint8_t context_tag = 0;
|
||||
BACNET_APPLICATION_TAG property_tag;
|
||||
BACNET_APPLICATION_DATA_VALUE propertyValue;
|
||||
|
||||
if (toupper(tag[0]) == 'C') {
|
||||
context_tag = strtol(&tag[1], NULL, 0);
|
||||
propertyValue.context_tag = context_tag;
|
||||
propertyValue.context_specific = true;
|
||||
} else {
|
||||
propertyValue.context_specific = false;
|
||||
}
|
||||
property_tag = strtol(tag, NULL, 0);
|
||||
|
||||
if (property_tag >= MAX_BACNET_APPLICATION_TAG) {
|
||||
sprintf(msg, "Error: tag=%u - it must be less than %u",
|
||||
property_tag, MAX_BACNET_APPLICATION_TAG);
|
||||
LogError(msg);
|
||||
break;
|
||||
}
|
||||
if (!bacapp_parse_application_data(
|
||||
property_tag, value, &propertyValue)) {
|
||||
sprintf(msg, "Error: unable to parse the tag value");
|
||||
LogError(msg);
|
||||
break;
|
||||
}
|
||||
propertyValue.next = NULL;
|
||||
|
||||
/* Send out the message */
|
||||
Request_Invoke_ID = Send_Write_Property_Request(deviceInstanceNumber,
|
||||
objectType, objectInstanceNumber, objectProperty, &propertyValue,
|
||||
objectPriority, objectIndex);
|
||||
Wait_For_Answer_Or_Timeout(100, waitAnswer);
|
||||
|
||||
/* If we get here, then there were no explicit failures. However, there
|
||||
*/
|
||||
/* could have been implicit failures. Let's look at those also. */
|
||||
isFailure = Error_Detected;
|
||||
} while (false);
|
||||
|
||||
/* Clean up after ourselves. */
|
||||
Error_Detected = false;
|
||||
return isFailure;
|
||||
}
|
||||
|
||||
int BacnetAtomicWriteFile(int deviceInstanceNumber,
|
||||
int fileInstanceNumber,
|
||||
int blockStartAddr,
|
||||
int blockNumBytes,
|
||||
char *nibbleBuffer)
|
||||
{
|
||||
BACNET_OCTET_STRING fileData;
|
||||
int i, nibble;
|
||||
uint8_t byteValue;
|
||||
unsigned char nibbleValue;
|
||||
|
||||
if (!isAtomicWriteFileHandlerRegistered) {
|
||||
/* handle any errors coming back */
|
||||
apdu_set_error_handler(
|
||||
SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, My_Error_Handler);
|
||||
|
||||
/* indicate that handlers are now registered */
|
||||
isAtomicWriteFileHandlerRegistered = true;
|
||||
}
|
||||
|
||||
for (i = 0; i < blockNumBytes; i++) {
|
||||
byteValue = 0;
|
||||
for (nibble = 0; nibble < 2; nibble++) {
|
||||
nibbleValue = toupper(nibbleBuffer[i * 2 + nibble]);
|
||||
if ((nibbleValue >= '0') && (nibbleValue <= '9')) {
|
||||
byteValue += (nibbleValue - '0') << (4 * (1 - nibble));
|
||||
} else if ((nibbleValue >= 'A') && (nibbleValue <= 'F')) {
|
||||
byteValue += (nibbleValue - 'A' + 10) << (4 * (1 - nibble));
|
||||
} else {
|
||||
LogError("Bad data in buffer.");
|
||||
}
|
||||
}
|
||||
fileData.value[i] = byteValue;
|
||||
}
|
||||
octetstring_truncate(&fileData, blockNumBytes);
|
||||
|
||||
/* Send out the message and wait for answer */
|
||||
if (!Error_Detected) {
|
||||
Request_Invoke_ID = Send_Atomic_Write_File_Stream(deviceInstanceNumber,
|
||||
fileInstanceNumber, blockStartAddr, &fileData);
|
||||
Wait_For_Answer_Or_Timeout(100, waitAnswer);
|
||||
}
|
||||
|
||||
int isFailure = Error_Detected;
|
||||
Error_Detected = 0;
|
||||
return isFailure;
|
||||
}
|
||||
|
||||
int BacnetGetMaxApdu()
|
||||
{
|
||||
unsigned requestedOctetCount = 0;
|
||||
uint16_t my_max_apdu = 0;
|
||||
|
||||
/* calculate the smaller of our APDU size or theirs
|
||||
and remove the overhead of the APDU (varies depending on size).
|
||||
note: we could fail if there is a bottle neck (router)
|
||||
and smaller MPDU in betweeen. */
|
||||
if (Target_Max_APDU < MAX_APDU) {
|
||||
my_max_apdu = Target_Max_APDU;
|
||||
} else {
|
||||
my_max_apdu = MAX_APDU;
|
||||
}
|
||||
/* Typical sizes are 50, 128, 206, 480, 1024, and 1476 octets */
|
||||
if (my_max_apdu <= 50) {
|
||||
requestedOctetCount = my_max_apdu - 19;
|
||||
} else if (my_max_apdu <= 480) {
|
||||
requestedOctetCount = my_max_apdu - 32;
|
||||
} else if (my_max_apdu <= 1476) {
|
||||
requestedOctetCount = my_max_apdu - 64;
|
||||
} else {
|
||||
requestedOctetCount = my_max_apdu / 2;
|
||||
}
|
||||
|
||||
return requestedOctetCount;
|
||||
}
|
||||
|
||||
int BacnetTimeSync(int deviceInstanceNumber,
|
||||
int year,
|
||||
int month,
|
||||
int day,
|
||||
int hour,
|
||||
int minute,
|
||||
int second,
|
||||
int isUTC,
|
||||
int UTCOffset)
|
||||
{
|
||||
BACNET_DATE bdate;
|
||||
BACNET_TIME btime;
|
||||
struct tm my_time;
|
||||
time_t aTime;
|
||||
struct tm *newTime;
|
||||
|
||||
my_time.tm_sec = second;
|
||||
my_time.tm_min = minute;
|
||||
my_time.tm_hour = hour;
|
||||
my_time.tm_mday = day;
|
||||
my_time.tm_mon = month - 1;
|
||||
my_time.tm_year = year - 1900;
|
||||
my_time.tm_wday = 0; /* does not matter */
|
||||
my_time.tm_yday = 0; /* does not matter */
|
||||
my_time.tm_isdst = 0; /* does not matter */
|
||||
|
||||
aTime = mktime(&my_time);
|
||||
newTime = localtime(&aTime);
|
||||
|
||||
bdate.year = newTime->tm_year;
|
||||
bdate.month = newTime->tm_mon + 1;
|
||||
bdate.day = newTime->tm_mday;
|
||||
bdate.wday = newTime->tm_wday ? newTime->tm_wday : 7;
|
||||
btime.hour = newTime->tm_hour;
|
||||
btime.min = newTime->tm_min;
|
||||
btime.sec = newTime->tm_sec;
|
||||
btime.hundredths = 0;
|
||||
|
||||
int len = 0;
|
||||
int pdu_len = 0;
|
||||
int bytes_sent = 0;
|
||||
BACNET_NPDU_DATA npdu_data;
|
||||
BACNET_ADDRESS my_address;
|
||||
uint8_t Handler_Transmit_Buffer[MAX_PDU] = { 0 };
|
||||
|
||||
/* Loop for eary exit */
|
||||
do {
|
||||
if (!dcc_communication_enabled()) {
|
||||
LogError("DCC communicaiton is not enabled");
|
||||
break;
|
||||
}
|
||||
|
||||
/* encode the NPDU portion of the packet */
|
||||
npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL);
|
||||
datalink_get_my_address(&my_address);
|
||||
pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], &Target_Address,
|
||||
&my_address, &npdu_data);
|
||||
|
||||
/* encode the APDU portion of the packet */
|
||||
len = timesync_encode_apdu(
|
||||
&Handler_Transmit_Buffer[pdu_len], &bdate, &btime);
|
||||
pdu_len += len;
|
||||
|
||||
/* send it out the datalink */
|
||||
bytes_sent = datalink_send_pdu(
|
||||
&Target_Address, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len);
|
||||
if (bytes_sent <= 0) {
|
||||
char errorMsg[64];
|
||||
sprintf(errorMsg,
|
||||
"Failed to Send Time-Synchronization Request (%s)!",
|
||||
strerror(errno));
|
||||
LogError(errorMsg);
|
||||
break;
|
||||
}
|
||||
|
||||
Wait_For_Answer_Or_Timeout(100, waitAnswer);
|
||||
} while (false);
|
||||
|
||||
int isFailure = Error_Detected;
|
||||
Error_Detected = 0;
|
||||
return isFailure;
|
||||
}
|
||||
|
||||
/****************************************************/
|
||||
/* This is the interface to AtomicReadFile */
|
||||
/****************************************************/
|
||||
int BacnetAtomicReadFile(int deviceInstanceNumber,
|
||||
int fileInstanceNumber,
|
||||
int startOffset,
|
||||
int numBytes)
|
||||
{
|
||||
if (!isAtomicReadFileHandlerRegistered) {
|
||||
/* handle the data coming back from confirmed requests */
|
||||
apdu_set_confirmed_ack_handler(
|
||||
SERVICE_CONFIRMED_ATOMIC_READ_FILE, AtomicReadFileAckHandler);
|
||||
|
||||
/* handle any errors coming back */
|
||||
apdu_set_error_handler(
|
||||
SERVICE_CONFIRMED_ATOMIC_READ_FILE, My_Error_Handler);
|
||||
|
||||
/* indicate that handlers are now registered */
|
||||
isAtomicReadFileHandlerRegistered = true;
|
||||
}
|
||||
/* Send the message out */
|
||||
Request_Invoke_ID = Send_Atomic_Read_File_Stream(
|
||||
deviceInstanceNumber, fileInstanceNumber, startOffset, numBytes);
|
||||
Wait_For_Answer_Or_Timeout(100, waitAnswer);
|
||||
|
||||
int isFailure = Error_Detected;
|
||||
Error_Detected = 0;
|
||||
return isFailure;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
The BACnet Scriptable (using Perl) Tool.
|
||||
|
||||
* Running this tool assumes that the library has been already built. The
|
||||
library should be built with a command similar to
|
||||
|
||||
CC=/mingw/bin/gcc BACNET_DEFINES="-DPRINT_ENABLED -DBACAPP_ALL -DBACFILE
|
||||
-DINTRINSIC_REPORTING" BBMD_DEFINE=-DBBMD_ENABLED\=1 BACNET_PORT=win32 make
|
||||
clean library
|
||||
|
||||
* Currently, the tool assumes only win32 port, but should be easily modifiable
|
||||
for any port build.
|
||||
* This tool has to be run from a path without any spaces. The presence of the
|
||||
.Inline directory is required.
|
||||
* Run the tool without any arguments to see usage instructions
|
||||
* To run the example ReapProperty script (which reads Analog Value 0 Present
|
||||
Value) for Device at instance 1234 run the following command
|
||||
|
||||
perl bacnet.pl --script example_readprop.pl -- 1234
|
||||
|
||||
|
||||
Reference in New Issue
Block a user