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:
Steve Karg
2019-12-13 15:19:10 -06:00
committed by GitHub
parent 8a38dbe2cf
commit d50c190957
912 changed files with 36206 additions and 52502 deletions
+348
View File
@@ -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 &quot;hello world&quot;</p>
<pre>
Log(&quot;Hello World&quot;);</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 &quot;hello&quot;, but not &quot;world&quot;</p>
<pre>
Log(&quot;Hello&quot;);
SilenceLog(1);
Log(&quot;World&quot;);</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(
\&amp;ReadProperty, [$deviceInstance, 'OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE'],
42, $maxRetries, $retryDelay
);
if ($isFailure)
{
die &quot;Value was not 42. Last response was '$resp'&quot;;
}</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(
\&amp;WriteProperty, [$deviceInstance, 'OBJECT_ANALOG_VALUE', 0, 'PROP_PRESENT_VALUE', 'BACNET_APPLICATION_TAG_REAL', 42.0],
&quot;Acknowledged&quot;, $maxRetries, $retryDelay
);
if ($isFailure)
{
die &quot;Could not write 42. Last response was '$resp'&quot;;
}</pre>
</body>
</html>
File diff suppressed because one or more lines are too long
+75
View File
@@ -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;
}
+137
View File
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<BR>").replace(/\t/g, " "); //tachyon
while (/\s\s/.test(s))
s = s.replace(/\s\s/, "&nbsp; ");
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);
});
});
+869
View File
@@ -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;
}
+53
View File
@@ -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;
+933
View File
@@ -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;
}
+20
View File
@@ -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