1
0
mirror of https://github.com/JoelBender/bacpypes synced 2025-10-05 22:18:16 +08:00

Merge pull request #94 from kjlockhart/master

Documentation changes.
This commit is contained in:
Joel Bender 2016-09-19 21:58:47 -04:00 committed by GitHub
commit 305154d80b
21 changed files with 767 additions and 349 deletions

View File

@ -4,71 +4,76 @@ Getting Started
=============== ===============
Ah, so you are interested in getting started with BACnet and Python. Welcome Ah, so you are interested in getting started with BACnet and Python. Welcome
to BACpypes, I hope you enjoy your journey. This tutorial starts with some to BACpypes, I hope you enjoy your journey. This tutorial starts with
just enough of the basics of BACnet to get a workstation communicating with just enough of the basics of BACnet to get a workstation communicating with
another device, installing the library, and downloading and configuring the another device. We will cover installing the library, and downloading and
samples applications. configuring the samples applications.
Basic Assumptions Basic Assumptions
----------------- -----------------
Assume that you are a software developer and it is your job to communicate I will assume you are a software developer and it is your job to communicate
with a device from another company that uses BACnet. Your employer has with a device from another company that uses BACnet. Your employer has
given you a test device and purchased a copy of the standard. You have given you a test device and purchased a copy of the BACnet standard. I will
in your office... need...
- a development workstation running some flavor of Linux complete with - a development workstation running some flavor of Linux, complete with
the latest version of Python 2.7 and the latest version of Python (2.7 or 3.4) and
`setup tools <https://pypi.python.org/pypi/setuptools#unix-based-systems-including-mac-os-x>`_. `setup tools <https://pypi.python.org/pypi/setuptools#unix-based-systems-including-mac-os-x>`_.
- a small hub you can plug in your workstation and this misterious device - a small Ethernet hub into which you can plug both your workstation and your
and not get distracted by lots of other LAN traffic. mysterious BACnet device, so you won't be distracted by lots of other network traffic.
Before getting this test environment set up and you are still connected Before getting this test environment set up and while you are still connected
to the internet, install the BACpypes library:: to the internet, install the BACpypes library::
$ sudo easy_install bacpypes $ sudo easy_install bacpypes
or
$ sudo pip install bacpypes
And while you are at it, get a copy of the project from SourceForge that And while you are at it, get a copy of the BACpypes project from GitHub. It
has the library source code, sample code, and this documentation:: contains the library source code, sample code, and this documentation::
$ svn checkout svn://svn.code.sf.net/p/bacpypes/code/trunk bacpypes $ git clone https://github.com/JoelBender/bacpypes.git
No protocol analysis workbench would be complete without an installed No protocol analysis workbench would be complete without an installed
copy of `Wireshark <http://www.wireshark.org/>`_:: copy of `Wireshark <http://www.wireshark.org/>`_::
$ sudo apt-get install wireshark $ sudo apt-get install wireshark
Configuring the Workstation Configuring the Workstation
--------------------------- ---------------------------
The test device that you have is going to come with some configuration The mystery BACnet device you have is going to come with some configuration
information by default and sometimes it is easier to set up the test information by default and sometimes it is easier to set up the test
environment with same set of assumtions than come up with a fresh set environment with my set of assumptions than come up with a fresh set
from scratch. from scratch.
*IP Address* *IP Address*
The device will probably come with an IP address, assume that it The device will probably come with an IP address, let's assume that it
is 192.168.0.10, subnet mask 255.255.0.0, gateway address 192.168.0.1. is 192.168.0.10, subnet mask 255.255.0.0, gateway address 192.168.0.1.
You are going to be joining the same network, so pick 192.168.0.11 You are going to be joining the same network, so pick 192.168.0.11
for the workstation address with the same subnet mask. for your workstation address and use the same subnet mask 255.255.0.0.
*Device Identifier*
Every BACnet device on a BACnet network **must** have a unique numeric
identifier. This number is a 22-bit unsigned non-zero value.
It is critical this identifier be unique. Most large customers will have
someone or some group responsible for maintaining device identifiers across the
site. Keep track of the device identifier for the test device. Let's
assume that this device is **1000** and you are going to pick **1001**
for your workstation.
*Device Name* *Device Name*
Every BACnet device on a BACnet network has a unique name which Every BACnet device on a BACnet network should also have a unique name, which
is a character string. There is nothing on a BACnet network that is a character string. There is nothing on a BACnet network that
enforces this uniqueness, but it is a real headache for integrators enforces this uniqueness, but it is a real headache for integrators
when it isn't followed. You will need to pick a name for your when it isn't followed. You will need to pick a name for your
workstation. My collegues and I use star names so the sample workstation. My collegues and I use star names, so in the sample
congiuration files will have "Betelgeuse". configuration files you will see the name "Betelgeuse". An actual customer's
site will use a more formal (but less fun) naming convention.
*Device Identifier*
Every BACnet device will have a unique identifier, a 22-bit
unsigned non-zero value. It is critical that this be unique for
every device and most large customers will have someone or a
group responsible for maintaining device identifiers across the
site. Keep track of the device identifier for the test device,
assume that it is **1000** and you are going to pick **1001**
for your workstation.
There are a few more configuration values that you will need, but There are a few more configuration values that you will need, but
you won't need to change the values in the sample configuration file you won't need to change the values in the sample configuration file
@ -78,53 +83,58 @@ until you get deeper into the protocol.
BACnet works on lots of different types of networks, from high BACnet works on lots of different types of networks, from high
speed Ethernet to "slower" and "cheaper" ARCNET or MS/TP (a speed Ethernet to "slower" and "cheaper" ARCNET or MS/TP (a
serial bus protocol used for a field bus defined by BACnet). serial bus protocol used for a field bus defined by BACnet).
For devices to exchange messages they have to know the maximum For devices to exchange messages they need to know the maximum
size message the device can handle. size message the other device can handle.
*Segmentation Supported* *Segmentation Supported*
A vast majority of BACnet communications traffic fits in one A vast majority of BACnet communications traffic fits in one
message, but thre can be times when larger messages are message, but there are times when larger messages are
convinient and more efficient. Segmentation allows larger convenient and more efficient. Segmentation allows larger
messages to be broken up into segemnts and spliced back together. messages to be broken up into segments and spliced back together.
It is not unusual for "low power" field equipment to not It is not unusual for "low power" field devices to not
support segmentation. support segmentation.
There are other configuration parameters in the INI file that are There are other configuration parameters in the INI file that are
used by other applications, just leave them alone for now. also used by other applications, just leave them alone for now.
Updating the INI File Updating the INI File
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
Now that you know what these values are going to be you can Now that you know what these values are going to be, you can
configure the BACnet part of your workstation. Change into the configure the BACnet portion of your workstation. Change into the
samples directory that you checked out earlier, make a copy samples directory that you checked out earlier, make a copy
of the sample configuration file, and edit it for your site:: of the sample configuration file, and edit it for your site::
$ cd bacpypes/samples $ cd bacpypes/samples
$ cp BACpypes~.ini BACpypes.ini $ cp BACpypes~.ini BACpypes.ini
The sample applications are going to look for this file, and you .. tip::
can direct them to other INI files on the command line, so it is
simple to keep multiple configurations. The sample applications are going to look for this file.
You can direct the applications to use other INI files on the command line, so it is
simple to keep multiple configurations.
At some point you will probably running both "client" and "server"
applications on your workstation, so you will want separate
configuration files for them. Keep in mind that BACnet devices
communicate as peers, so it is not unusual for an application to
act as both a client and a server at the same time.
At some point you will probably running both "client" and "server"
applications on your workstation, so you will want separate
configuration files for them. Keep in mind that BACnet devices
communicate as peers, so it is not unusual for an application to
act as both a client and a server at the same time.
UDP Communications Issues UDP Communications Issues
------------------------- -------------------------
BACnet devices comunicate using UDP rather than TCP. This is so BACnet devices communicate using UDP rather than TCP. This is so
that devices do not need to implement a full IP stack (although devices do not need to implement a full IP stack (although
many of them do becuase they support multiple protocols, including many of them do becuase they support multiple protocols, including
having embedded web servers). having embedded web servers).
There are two types of UDP messages; *unicast* which is a message There are two types of UDP messages; *unicast* which is a message
from one specific IP address and port to another one, and *broadcast* from one specific IP address (and port) to another device's IP address
which is received and processed by all devices that have the port (and port); and *broadcast* messages which are sent by one device
open. BACnet uses both types of messages and your workstation and received and processed by all other devices that are listening
on that port. BACnet uses both types of messages and your workstation
will need to receive both types. will need to receive both types.
The BACpypes.ini file has an *address* parameter which is an IP The BACpypes.ini file has an *address* parameter which is an IP
@ -134,27 +144,29 @@ number of bits in the network portion, which in turn implies a
subnet mask, in this case **255.255.0.0**. Unicast messages will subnet mask, in this case **255.255.0.0**. Unicast messages will
be sent to the IP address, and broadcast messages will be sent to be sent to the IP address, and broadcast messages will be sent to
the broadcast address **192.168.255.255** which is the network the broadcast address **192.168.255.255** which is the network
portion of the configuration value will all 1's in the host portion of the address with all 1's in the host portion.
portion.
To receive both unicast and broadcast addresses, BACpypes will To receive both unicast and broadcast addresses, BACpypes
open two sockets, one for unicast traffic and one that only listens opens two sockets, one for unicast traffic and one that only listens
for broadcast messages. The operating system will typically not allow two for broadcast messages. The operating system will typically not allow two
applications to open the same socket at the same time applications to open the same socket at the same time
so to run two BACnet applciations at so to run two BACnet applciations at
the same time they need to be configured with different ports. the same time they need to be configured with different ports.
The BACnet protocol has port 47808 (hex 0xBAC0) assigned to it .. note::
by the `Internet Assigned Numbers Authority <https://www.iana.org/>`_, and sequentially
higher numbers are used in many applications. There are some The BACnet protocol has been assigned port 47808 (hex 0xBAC0) by
BACnet routing and networking isseus with this, but that is for by the `Internet Assigned Numbers Authority <https://www.iana.org/>`_, and sequentially
antoher tutorial. higher numbers are used in many applications (i.e. 47809, 47810,...).
There are some BACnet routing and networking issues related to using these higher unoffical
ports, but that is a topic for another tutorial.
Starting An Application Starting An Application
----------------------- -----------------------
The simplest BACpypes sample application is the **WhoIsIAm.py** The simplest BACpypes sample application is the **WhoIsIAm.py**
application. It can send out Who-Is and I-Am messages and application. It sends out Who-Is and I-Am messages and
displays the results it receives. What are these things? displays the results it receives. What are these things?
As mentioned before, BACnet has unique device identifiers and As mentioned before, BACnet has unique device identifiers and
@ -169,7 +181,7 @@ a decentralized DNS service, but the names are unsigned
integers. The request is broadcast on the network and the integers. The request is broadcast on the network and the
client waits around to listen for I-Am messages. The source client waits around to listen for I-Am messages. The source
address of the I-Am response is "bound" to the device identifier address of the I-Am response is "bound" to the device identifier
and most communications is unicast after that. and most communications are unicast thereafter.
First, start up Wireshark on your workstation and a capture First, start up Wireshark on your workstation and a capture
session with a BACnet capture filter:: session with a BACnet capture filter::
@ -187,7 +199,7 @@ Now start the application::
$ python WhoIsIAm.py $ python WhoIsIAm.py
You will be presented with a prompt, and you can get help:: You will be presented with a prompt (>), and you can get help::
> help > help
@ -195,76 +207,97 @@ You will be presented with a prompt, and you can get help::
======================================== ========================================
EOF buggers bugin bugout exit gc help iam shell whois EOF buggers bugin bugout exit gc help iam shell whois
The details of the commands will be described in the next The details of the commands are described in the next section.
section.
Generating An I-Am Generating An I-Am
------------------ ------------------
Now that the application is configured it is nice to see some Now that the application is configured it is nice to see some
BACnet communications traffic. Just generate an I-Am message:: BACnet communications traffic. Generate the basic I-Am message::
> iam > iam
You should see your configuration parameters in the I-Am You should see Wireshark capture your I-Am message containing your configuration
message in Wireshark, this is a "global broadcast" message, so your parameters. This is a "global broadcast" message.
test device will see it but since your test device probably Your test device will see it but since your test device probably
isn't looking for you, it will not respond with anything. isn't looking for you, it will not respond to the message.
Binding to the Test Device Binding to the Test Device
-------------------------- --------------------------
Now to confirm that the workstation can receive the Next we want to confirm that your workstation can receive the
messages that the test device sends out, generate a Who-Is messages the test device sends out. We do this by generating a
request. This one will be "unconstrained" which means that generic Who-Is request. The request will be "unconstrained", meaning
every device will respond. *Do not generate these types of every device that hears the message will respond with their corresponding
unconstrained requests on a large I-Am messages.
network because it will create a lot of traffic that can
cause conjestion.* Here is a Who-Is:: .. caution::
Generating **unconstrained** Who-Is requests on a large network will create
a LOT of traffic, which can lead to network problems caused by the resulting
flood of messages.
To generate the Who-Is request::
> whois > whois
You should see the request in Wireshark and the response from You should see the Who-Is request captured in Wireshark along with the I-Am
the device, and then a summary line of the response on the response from your test device, and then the details of the response displayed
workstation. on the workstation console.::
There are a few different forms of the *whois* command this > whois
simple application allows and you can see the basic form > pduSource = <RemoteStation 50009:9>
with the help command:: iAmDeviceIdentifier = ('device', 1000)
maxAPDULengthAccepted = 480
segmentationSupported = segmentedBoth
vendorID = 8
There are a few different forms of the *whois* command supported by this
simple application. You can see these with the help command::
> help whois > help whois
whois [ <addr>] [ <lolimit> <hilimit> ] whois [ <addr>] [ <lolimit> <hilimit> ]
This is like a BNF syntax, the whois command is optionally This is like a BNF syntax, the **whois** command is optionally
followed by an address, and then optionally followed by a followed by a BACnet device address, and then optionally followed by a
low limit and high limit. The most common use of the Who-Is low (address) limit and high (address) limit. The most common use of the Who-Is
request is to look for a specific device given its device request is to look for a specific device given its device
identifier:: identifier::
> whois 1000 1000 > whois 1000 1000
And if the site has a numbering scheme for groups of BACnet If the site has a numbering scheme for groups of BACnet
devices like all those in a specific building, then it is devices (i.e. grouped by building), then it is
common to look for all of them as a group:: common to look for all the devices in a specific building as a group::
> whois 203000 203099 > whois 203000 203099
Every once in a while a contractor might install a BACnet Every once in a while a contractor might install a BACnet
device that hasn't been properly configured. Assuming that device that hasn't been properly configured. Assuming that
it has an IP address, you can send an unconstrained request it has an IP address, you can send an **unconstrained Who-Is** request
to the specific device and hope that it responds:: to the specific device and hope that it responds::
> whois 192.168.0.10 > whois 192.168.0.10
> pduSource = <Address 192.168.0.10>
iAmDeviceIdentifier = ('device', 1000)
maxAPDULengthAccepted = 1024
segmentationSupported = segmentedBoth
vendorID = 15
There are other forms of BACnet addresses used in BACpypes, There are other forms of BACnet addresses used in BACpypes,
but that is a subject of an other tutorial. but that is a subject of an other tutorial.
What's Next What's Next
----------- -----------
The next tutorial will describe the different ways this The next tutorial describes the different ways this
application can be run, and what the commands can tell you application can be run, and what the commands can tell you
about how it is working. All of the "console" applications, about how it is working. All of the "console" applications
those that prompt for commands, use the same basic (i.e. those that prompt for commands) use the same basic
commands and work the same way. commands and work the same way.

View File

@ -5,8 +5,8 @@ Running BACpypes Applications
All BACpypes sample applications have the same basic set of command line All BACpypes sample applications have the same basic set of command line
options so it is easy to move between applications, turn debugging on and options so it is easy to move between applications, turn debugging on and
and using different configurations. There may be additional options and and use different configurations. There may be additional options and
command parameters than the ones described in this section. command parameters than just the ones described in this section.
Getting Help Getting Help
------------ ------------
@ -18,7 +18,7 @@ an application, you can start with help::
usage: WhoIsIAm.py [-h] [--buggers] [--debug [DEBUG [DEBUG ...]]] [--color] [--ini INI] usage: WhoIsIAm.py [-h] [--buggers] [--debug [DEBUG [DEBUG ...]]] [--color] [--ini INI]
This application presents a 'console' prompt to the user asking for Who-Is and This application presents a 'console' prompt to the user asking for Who-Is and
I-Am commands which create the related APDUs, then lines up the coorresponding I-Am commands which create the related APDUs, then lines up the corresponding
I-Am for incoming traffic and prints out the contents. I-Am for incoming traffic and prints out the contents.
optional arguments: optional arguments:
@ -64,10 +64,10 @@ Telling the application to debug a module is simple::
The output is the severity code of the logger (almost always DEBUG), the name The output is the severity code of the logger (almost always DEBUG), the name
of the module, class, or function, then some message about the progress of the of the module, class, or function, then some message about the progress of the
application. From the output above you can see that the application is application. From the output above you can see the application initializing,
beginning its initialization, shows the value of a variable called args, setting the args variable, creating an instance of the WhoIsIAmApplication class
an instance of the WhoIsIAmApplication class is created with some parameters, (with some parameters), and then declaring itself - running.
and then the application starts running.
Debugging a Class Debugging a Class
----------------- -----------------
@ -92,13 +92,14 @@ example, there is a class called UDPActor in the UDP module::
In this sample, an instance of a UDPActor is created and then its response In this sample, an instance of a UDPActor is created and then its response
function is called with an instance of a PDU as a parameter. Following function is called with an instance of a PDU as a parameter. Following
the function invocation description, the debugging output continues with the the function invocation description, the debugging output continues with the
contents of the PDU. Notice that the protocol data is printed as a hex contents of the PDU. Notice, the protocol data is printed as a hex
encoded string, and only the first 20 bytes of the message. encoded string (and restricted to just the first 20 bytes of the message).
You can debug a function just as easily, and specify as many different You can debug a function just as easily. Specify as many different
combinations of logger names as necessary. Note that you cannot debug a combinations of logger names as necessary. Note, you cannot debug a
specific function within a class. specific function within a class.
Changing INI Files Changing INI Files
------------------ ------------------

View File

@ -7,25 +7,38 @@ BACpypes library for building BACnet applications using Python. Installation
is easy, just:: is easy, just::
$ sudo easy_install bacpypes $ sudo easy_install bacpypes
or
$ sudo pip install bacpypes
You will be installing the latest released version. You can also check out
the latest version from GitHub:: You will be installing the latest released version from PyPI (the Python Packages Index),
located at pypi.python.org
.. note::
You can also check out the latest version from GitHub::
$ git clone https://github.com/JoelBender/bacpypes.git $ git clone https://github.com/JoelBender/bacpypes.git
And then use the setup utility to install it:: And then use the setup utility to install it::
$ cd bacpypes $ cd bacpypes
$ python setup.py install $ python setup.py install
If you would like to participate in its development, please join the
`developers mailing list
<https://lists.sourceforge.net/lists/listinfo/bacpypes-developers>`_, join the
`chat room on Gitter <https://gitter.im/JoelBender/bacpypes>`_, and add the
`Google+ <https://plus.google.com/100756765082570761221/posts>`_ to your
circles have have release notifications show up in your stream.
Welcome aboard! .. tip::
If you would like to participate in its development, please join:
- the `developers mailing list <https://lists.sourceforge.net/lists/listinfo/bacpypes-developers>`_,
- the `chat room on Gitter <https://gitter.im/JoelBender/bacpypes>`_, and
- add `Google+ <https://plus.google.com/100756765082570761221/posts>`_ to your circles to have release notifications show up in your stream.
**Welcome aboard!**
------
Getting Started Getting Started
--------------- ---------------
@ -39,6 +52,8 @@ downloading the sample code and communicating with a test device.
gettingstarted/gettingstarted001.rst gettingstarted/gettingstarted001.rst
gettingstarted/gettingstarted002.rst gettingstarted/gettingstarted002.rst
Tutorial Tutorial
-------- --------
@ -54,11 +69,12 @@ essential components of a BACpypes application and how the pieces fit together.
tutorial/tutorial004.rst tutorial/tutorial004.rst
tutorial/tutorial006.rst tutorial/tutorial006.rst
Samples Samples
------- -------
The library has a variety of sample applications, some of them are a framework BACpypes comes with a variety of sample applications. Some are a framework
for building larger applications, some of them are standalone analysis tools for building larger applications. Some are standalone analysis tools
that don't require a connection to a network. that don't require a connection to a network.
.. toctree:: .. toctree::
@ -71,15 +87,6 @@ that don't require a connection to a network.
samples/sample005.rst samples/sample005.rst
samples/sample014.rst samples/sample014.rst
Modules
-------
This documentation is intended for BACpypes developers.
.. toctree::
:maxdepth: 1
modules/index.rst
Glossary Glossary
-------- --------
@ -89,6 +96,7 @@ Glossary
glossary.rst glossary.rst
Release Notes Release Notes
------------- -------------
@ -97,6 +105,21 @@ Release Notes
releasenotes.rst releasenotes.rst
------
Modules
-------
.. tip:: Documentation intended for BACpypes developers.
.. toctree::
:maxdepth: 1
modules/index.rst
-----
Indices and tables Indices and tables
================== ==================

View File

@ -38,9 +38,8 @@ Protocol Data Units
A Protocol Data Unit (PDU) is the name for a collection of information that A Protocol Data Unit (PDU) is the name for a collection of information that
is passed between two entities. It is composed of Protcol Control Information is passed between two entities. It is composed of Protcol Control Information
(PCI), which usually has information about addressing and other types of (PCI) - information about addressing, processing instructions - and data.
processing instructions, and data. The set of classes in this module are not The set of classes in this module are not specific to BACnet.
specific to BACnet.
.. class:: PCI .. class:: PCI
@ -68,6 +67,11 @@ specific to BACnet.
.. class:: PDUData .. class:: PDUData
The PDUData class has functions for extracting information from the front
of the data octet string, or append information to the end. These are helper
functions but may not be applicable for higher layer protocols which may
be passing significantly more complex data.
.. attribute:: pduData .. attribute:: pduData
This attribute typically holds a simple octet string, but for higher This attribute typically holds a simple octet string, but for higher
@ -81,7 +85,7 @@ specific to BACnet.
.. method:: get_data(len) .. method:: get_data(len)
:param integer len: the number of octets to extract off the front :param integer len: the number of octets to extract.
Extract a number of octets from the front of the data. If there Extract a number of octets from the front of the data. If there
are not at least `len` octets this will raise a DecodingError are not at least `len` octets this will raise a DecodingError
@ -89,8 +93,12 @@ specific to BACnet.
.. method:: get_short() .. method:: get_short()
Extract a short integer (two octets) from the front of the data.
.. method:: get_long() .. method:: get_long()
Extract a long integer (four octets) from the front of the data.
.. method:: put(ch) .. method:: put(ch)
:param octet ch: the octet to append to the end :param octet ch: the octet to append to the end
@ -101,12 +109,12 @@ specific to BACnet.
.. method:: put_short(n) .. method:: put_short(n)
:param short integer: two octets to append to the end
.. method:: put_long(n) .. method:: put_long(n)
The PDUData class has functions for gathering information from the front :param long integer: four octets to append to the end
of the octet string, or putting information on the end. These are helper
functions but may not be applicable for higher layer protocols which may
be passing significantly more complex data.
.. class:: PDU(PCI, PDUData) .. class:: PDU(PCI, PDUData)

View File

@ -5,7 +5,11 @@
Console Command Console Command
=============== ===============
This is a long line of text. Python has a `cmd <http://wiki.python.org/moin/CmdModule>`_ module that makes
it easy to embed a command line interpreter in an application. BACpypes
extends this interpreter with some commands to assist debugging and runs
the interpreter in a separate thread so it does not interfere with the BACpypes
:func:`core.run` functionality.
Functions Functions
--------- ---------
@ -19,44 +23,88 @@ Classes
.. class:: ConsoleCmd(cmd.Cmd, Thread) .. class:: ConsoleCmd(cmd.Cmd, Thread)
This is a long line of text.
.. method:: __init__(prompt="> ", allow_exec=False) .. method:: __init__(prompt="> ", allow_exec=False)
:param string prompt: prompt for commands :param string prompt: prompt for commands
:param boolean allow_exec: allow non-commands to be executed :param boolean allow_exec: allow non-commands to be executed
This is a long line of text.
.. method:: run() .. method:: run()
This is a long line of text. Begin execution of the application's main event loop. Place this after the
the initialization statements.
.. method:: do_something(args) .. method:: do_something(args)
:param args: commands :param args: commands
This is a long line of text. Template of a function implementing a console command.
Commands Commands
-------- --------
.. option:: help
List an application's console commands::
> help
Documented commands (type help <topic>):
========================================
EOF buggers bugin bugout exit gc help nothing shell
.. option:: gc .. option:: gc
This is a long line of text. Print out garbage collection information::
> gc
Module Type Count dCount dRef
bacpypes.object OptionalProperty 787 0 0
bacpypes.constructeddata Element 651 0 0
bacpypes.object ReadableProperty 362 0 0
bacpypes.object WritableProperty 44 0 0
__future__ _Feature 7 0 0
Queue Queue 2 0 0
bacpypes.pdu Address 2 0 0
bacpypes.udp UDPActor 2 1 4
bacpypes.bvllservice UDPMultiplexer 1 0 0
bacpypes.app DeviceInfoCache 1 0 0
Module Type Count dCount dRef
bacpypes.udp UDPActor 2 1 4
.. option:: bugin <name> .. option:: bugin <name>
This is a long line of text. Attach a debugger.::
> bugin bacpypes.task.OneShotTask
handler to bacpypes.task.OneShotTask added
.. option:: bugout <name> .. option:: bugout <name>
This is a long line of text. Detach a debugger.::
> bugout bacpypes.task.OneShotTask
handler to bacpypes.task.OneShotTask removed
.. option:: buggers .. option:: buggers
This is a long line of text. Get a list of the available buggers.::
> buggers
no handlers
__main__
bacpypes
bacpypes.apdu
bacpypes.apdu.APCI
...
bacpypes.vlan.Network
bacpypes.vlan.Node
.. option:: exit .. option:: exit
This is a long line of text. Exit a BACpypes Console application.::
> exit
Exiting...

View File

@ -78,5 +78,4 @@ Functions
time so that it does not starve child threads of processing time. time so that it does not starve child threads of processing time.
When sleeping is enabled, and it only needs to be enabled for multithreaded When sleeping is enabled, and it only needs to be enabled for multithreaded
applications, it will put a damper on the thruput of the application. applications, it will put a damper on the throughput of the application.

View File

@ -5,9 +5,9 @@
Errors Errors
====== ======
This module defines exception class for errors that it detects in the This module defines the exception class for errors it detects in the
configuration of the stack or in encoding and decoding PDUs. All of these configuration of the stack or in encoding or decoding PDUs. All of these
exceptions are derived from ValueError from the built-in exceptions module. exceptions are derived from ValueError (in Python's built-in exceptions module).
Classes Classes
------- -------
@ -30,6 +30,6 @@ Classes
This error is raised while PDU data is being decoded, which typically means This error is raised while PDU data is being decoded, which typically means
some unstructured data like an octet stream is being turned into structured some unstructured data like an octet stream is being turned into structured
data. There may be values in the pdu being decoded that are not data. There may be values in the PDU being decoded that are not
appropriate, or not enough data such as a truncated packet. appropriate, or not enough data such as a truncated packet.

View File

@ -6,9 +6,9 @@ Singleton
========= =========
Singleton classes are a `design pattern <http://en.wikipedia.org/wiki/Singleton_pattern>`_ Singleton classes are a `design pattern <http://en.wikipedia.org/wiki/Singleton_pattern>`_
that returns the same object for every call to create an instance. In the case which returns the same object for every 'create an instance' call. In the case
of BACpypes there can only be one instance of a :class:`task.TaskManager` and of BACpypes there can only be one instance of a :class:`task.TaskManager` and
all of the tasks will be schedule through it. The design pattern "hides" all all of the tasks are scheduled through it. The design pattern "hides" all
of the implementation details of the task manager behind its interface. of the implementation details of the task manager behind its interface.
There are occasions when the task manager needs to provide additional There are occasions when the task manager needs to provide additional

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,4 +1,3 @@
.. BACpypes tutorial lesson 1
Sample 1 - Simple Application Sample 1 - Simple Application
============================= =============================
@ -130,19 +129,16 @@ as the application, but the '--ini' option is available when it's not::
elif not config.read('BACpypes.ini'): elif not config.read('BACpypes.ini'):
raise RuntimeError, "configuration file not found" raise RuntimeError, "configuration file not found"
If the sample applications are run from the subversion directory, there is a .. tip::
sample INI file called **BACpypes~.ini** that is part of the repository. Make
a local copy *that is not part of the repository* and edit it with information There is a sample INI file called **BACpypes~.ini** as part of the repository. Make
appropriate to your installation:: a local copy and edit it with information appropriate to your installation::
$ pwd $ pwd
.../samples .../samples
$ cp BACpypes~.ini BACpypes.ini $ cp ../BACpypes~.ini BACpypes.ini
$ vi BACpypes.ini $ nano BACpypes.ini
$ svn status
? BACpypes.ini
Subversion understands that the local copy is not part of the repository.
Now applications will create a :class:`object.LocalDeviceObject` which will Now applications will create a :class:`object.LocalDeviceObject` which will
respond to Who-Is requests for device-address-binding procedures, and respond to Who-Is requests for device-address-binding procedures, and
@ -220,37 +216,46 @@ Running
When this sample application is run without any options, nothing appears on When this sample application is run without any options, nothing appears on
the console because there are no statements other than debugging:: the console because there are no statements other than debugging::
$ python sample001.py $ python SampleApplication.py
So to see what is actually happening, run the application with debugging So to see what is actually happening, run the application with debugging
enabled:: enabled::
$ python sample001.py --debug __main__ $ python SampleApplication.py --debug __main__
The output will include the initialization, running, and finally statements. To The output will include the initialization, running, and finally statements.::
run with debugging on just the SampleApplication class::
$ python sample001.py --debug __main__.SampleApplication DEBUG:__main__:initialization
DEBUG:__main__: - args: Namespace(buggers=False, color=False, debug=['__main__'], ini=<class 'bacpypes.consolelogging.ini'>)
DEBUG:__main__.SampleApplication:__init__ <bacpypes.app.LocalDeviceObject object at 0x7fcd37a2ba90> '192.168.0.10/24'
DEBUG:__main__: - this_application: <__main__.SampleApplication object at 0x7fcd357dea50>
DEBUG:__main__: - services_supported: <bacpypes.basetypes.ServicesSupported object at 0x7fcd357def50>
DEBUG:__main__:running
Or to see what is happening at the UDP layer of the program, use that module To run with debugging on just the SampleApplication class::
name::
$ python sample001.py --debug bacpypes.udp $ python SampleApplication.py --debug __main__.SampleApplication
DEBUG:__main__.SampleApplication:__init__ <bacpypes.app.LocalDeviceObject object at 0x7fadb71bca90> '192.168.0.10/24'
Or to see what is happening at the UDP layer of the program, use that module name::
$ python SampleApplication.py --debug bacpypes.udp
Or to simplify the output to the methods of instances of the :class:`udp.UDPActor` Or to simplify the output to the methods of instances of the :class:`udp.UDPActor`
use the class name:: use the class name::
$ python sample001.py --debug bacpypes.udp.UDPActor $ python SampleApplication.py --debug bacpypes.udp.UDPActor
Then to see what BACnet packets are received and make it all the way up the Then to see what BACnet packets are received and make it all the way up the
stack to the application, combine the debugging:: stack to the application, combine the debugging::
$ python sample001.py --debug bacpypes.udp.UDPActor __main__.SampleApplication $ python SampleApplication.py --debug bacpypes.udp.UDPActor __main__.SampleApplication
The most common broadcast messages that are *not* application layer messages The most common broadcast messages that are *not* application layer messages
are Who-Is-Router-To-Network and I-Am-Router-To-Network, and you can see these are **Who-Is-Router-To-Network** and **I-Am-Router-To-Network**. You can see these
messages being received and processed by the :class:`netservice.NetworkServiceElement` messages being received and processed by the :class:`netservice.NetworkServiceElement`
burried in the stack:: buried in the stack::
$ python sample001.py --debug bacpypes.netservice.NetworkServiceElement $ python SampleApplication.py --debug bacpypes.netservice.NetworkServiceElement

View File

@ -1,4 +1,3 @@
.. BACpypes tutorial lesson 1
Sample 2 - Who-Is/I-Am Counter Sample 2 - Who-Is/I-Am Counter
============================== ==============================
@ -50,7 +49,7 @@ The middle is going to process the data in the request::
) )
# count the times this has been received # count the times this has been received
whoIsCounter[key] += 1 who_is_counter[key] += 1
And the end of the function is going to call back to the standard application And the end of the function is going to call back to the standard application
processing:: processing::
@ -72,7 +71,7 @@ It uses a diferent key, but counts them the same::
) )
# count the times this has been received # count the times this has been received
iAmCounter[key] += 1 i_am_counter[key] += 1
And has an identical call to the base class:: And has an identical call to the base class::
@ -87,11 +86,57 @@ to sort the dictionary items and print them out, and being able to unpack
the key in the for loop is a nice feature of Python:: the key in the for loop is a nice feature of Python::
print "----- Who Is -----" print "----- Who Is -----"
for (src, lowlim, hilim), count in sorted(whoIsCounter.items()): for (src, lowlim, hilim), count in sorted(who_is_counter.items()):
print "%-20s %8s %8s %4d" % (src, lowlim, hilim, count) print "%-20s %8s %8s %4d" % (src, lowlim, hilim, count)
print print
Pairing up the requests and responses can be a useful excersize, but in most Pairing up the requests and responses can be a useful exercize, but in most
cases the I-Am response from a device will be a unicast message directly back cases the I-Am response from a device will be a unicast message directly back
to the requestor, so relying on broadcast traffic to analyze device and to the requestor, so relying on broadcast traffic to analyze device and
address binding is not as useful as it used to be. address binding is not as useful as it used to be.
Running the Application
-----------------------
::
$ python WhoIsIAmApplication.py --debug __main__
DEBUG:__main__:initialization
DEBUG:__main__: - args: Namespace(buggers=False, color=False, debug=['__main__'], ini=<class 'bacpypes.consolelogging.ini'>)
DEBUG:__main__.WhoIsIAmApplication:__init__ <bacpypes.app.LocalDeviceObject object at 0x7f596a817a90> '192.168.87.59/24'
DEBUG:__main__: - services_supported: <bacpypes.basetypes.ServicesSupported object at 0x7f59685cbe90>
DEBUG:__main__:running
Let it run for a minute, then Press <ctrl-C> to end it. It will output its results.::
DEBUG:__main__.WhoIsIAmApplication:do_WhoIsRequest <bacpypes.apdu.WhoIsRequest(8) instance at 0x7f7ca6792510>
<bacpypes.apdu.WhoIsRequest(8) instance at 0x7f7ca6792510>
pduSource = <Address 192.168.87.115>
pduDestination = <GlobalBroadcast *:*>
pduExpectingReply = False
pduNetworkPriority = 0
apduType = 1
apduService = 8
deviceInstanceRangeLowLimit = 59L
deviceInstanceRangeHighLimit = 59L
pduData = x''
[clipped...]
^CDEBUG:__main__:fini
----- Who Is -----
10001:0x0040ae007e01 1 1 1
10001:0x0040ae007e01 9830 9830 1
10001:0x005008067649 536 536 1
10001:0x005008067649 2323 2323 1
192.168.87.115 9 9 3
192.168.87.115 59 59 1
192.168.87.115 226 226 3
192.168.87.115 900 900 2
192.168.87.115 11189 11189 3
192.168.87.115 80403 80403 3
192.168.87.115 110900 110900 3
192.168.87.115 4194302 4194302 2
192.168.87.48 3300 3300 1
----- I Am -----

View File

@ -1,4 +1,3 @@
.. BACpypes tutorial lesson 1
Sample 3 - Who-Has/I-Have Counter Sample 3 - Who-Has/I-Have Counter
================================= =================================
@ -6,7 +5,7 @@ Sample 3 - Who-Has/I-Have Counter
This sample application is very similar to the second sample. It has the This sample application is very similar to the second sample. It has the
same basic structure and initialization, it counts the number of Who-Has and same basic structure and initialization, it counts the number of Who-Has and
I-Have messages it receives, and prints out a summary after the application I-Have messages it receives, and prints out a summary after the application
has been signaled to terminate, such as a KeyboardInterrupt raised. has been signalled to terminate (<ctrl-C> - KeyboardInterrupt).
Processing Service Requests Processing Service Requests
@ -34,23 +33,23 @@ cannot appear in the APDU at the same time::
return return
# count the times this has been received # count the times this has been received
whoHasCounter[key] += 1 who_has_counter[key] += 1
When an optional parameter is not specified in a PDU then the corrisponding When an optional parameter is not specified in a PDU then the corresponding
attribute will be ``None``. With this particular APDU the *object* attribute is ``None``. With this particular APDU the *object*
parameter is required, but only one of its child attributes *objectIdentifier* parameter is required, and one of its child attributes *objectIdentifier*
or *objectName* will be not ``None``. If both are ``None`` then the or *objectName* will be not ``None``. If both are ``None`` then the
request is not properly formed. request is not properly formed.
.. note:: .. note::
The encoding and decoding layer will not completely understand all of The encoding and decoding layer does not understand all
the combinations of required and optional parameters in an APDU, so the combinations of required and optional parameters in an APDU, so
verify the validity of the reuest is the responsibility of the application. verify the validity of the request is the responsibility of the application.
The application can rely on the fact that the APDU is well-formed, which The application can rely on the fact that the APDU is well-formed - meaning
is to say it has teh appropriate opening and closing tags and the data it has the appropriate opening and closing tags and the data
types of the parameters are correct. Watch out for Any! types of the parameters are correct. Watch out for parameters of type Any!
The I-Am function is much simpler because all of the parameters are required:: The I-Am function is much simpler because all of the parameters are required::
@ -62,13 +61,36 @@ The I-Am function is much simpler because all of the parameters are required::
) )
# count the times this has been received # count the times this has been received
iHaveCounter[key] += 1 i_have_counter[key] += 1
Dumping the contents of the counters is simple. Dumping the contents of the counters is simple.
Just like Who-Is and I-Am, pairing up the requests and responses can be a Just like Who-Is and I-Am, pairing up the requests and responses can be a
useful excersize, but in most cases the I-Am response from a device will be a useful exercize, but in most cases the I-Am response from a device will be a
unicast message directly back to the requestor, so relying on broadcast traffic unicast message directly back to the requestor, so relying on broadcast traffic
to analyze object binding is not as useful as it used to be. to analyze object binding is not as useful as it used to be.
The Who-Has and I-Have services are not widely used. Running the Application
-----------------------
::
$ python WhoHasIHaveApplication.py --debug __main__
DEBUG:__main__:initialization
DEBUG:__main__: - args: Namespace(buggers=False, color=False, debug=['__main__'], ini=<class 'bacpypes.consolelogging.ini'>)
DEBUG:__main__.WhoHasIHaveApplication:__init__ <bacpypes.app.LocalDeviceObject object at 0x7f887e83ca90> '192.168.87.59/24'
DEBUG:__main__: - services_supported: <bacpypes.basetypes.ServicesSupported object at 0x7f887c5f0f50>
DEBUG:__main__:running
Allow the application to run for a few minutes. Then end it so it will output its results.::
^CDEBUG:__main__:fini
----- Who Has -----
----- I Have -----
.. note::
The Who-Has and I-Have services are not widely used.

View File

@ -4,7 +4,7 @@ Sample 4 - Extending Objects and Properties
=========================================== ===========================================
This sample application shows how to extend one of the basic objects, an Analog This sample application shows how to extend one of the basic objects, an Analog
Value Object in this case, to provide a custom property, the present value. Value Object in this case, to provide a custom property - present value.
This type of code is used when the application is providing a BACnet interface This type of code is used when the application is providing a BACnet interface
to a collection of data. It assumes that almost all of the default behaviour to a collection of data. It assumes that almost all of the default behaviour
of a BACpypes application is sufficient. of a BACpypes application is sufficient.
@ -12,21 +12,20 @@ of a BACpypes application is sufficient.
.. note:: .. note::
The code in this description starts at the __main__ block and goes The code in this description starts at the __main__ block and goes
backward through the source file. backward through the source file - RandomAnalogValueObject.py.
Constructing the Device Constructing the Device
----------------------- -----------------------
Initialization is simple, the simple BACnet/IP application, which includes the Initialization is simple, the simple BACnet/IP application, which includes the
networking layer and communications layers all bundled in together is created networking layer and communications layers all bundled together is created
like the other samples:: like the other samples::
# make a sample application # make a sample application
thisApplication = BIPSimpleApplication(thisDevice, config.get('BACpypes','address')) thisApplication = BIPSimpleApplication(thisDevice, config.get('BACpypes','address'))
The only object this has by default is an instance of a The only object by default is an instance of :class:`object.LocalDeviceObject`.
:class:`object.LocalDeviceObject`. The next step is to create a special Analog The next step is to create a special Analog Value Object and add it to the application::
Value Object and add it to the application::
# make a random input object # make a random input object
raio = RandomAnalogValueObject(objectIdentifier=('analog-value', 1), objectName='Random') raio = RandomAnalogValueObject(objectIdentifier=('analog-value', 1), objectName='Random')
@ -47,7 +46,7 @@ will make sure the keyword argument value is appropriate for the property.
Extending the Analog Value Object Extending the Analog Value Object
--------------------------------- ---------------------------------
The definition of a new kind of Analog Value Object uses Python inhertiance, The definition of a new kind of Analog Value Object uses Python inheritance,
so it seems fairly simple:: so it seems fairly simple::
class RandomAnalogValueObject(AnalogValueObject): class RandomAnalogValueObject(AnalogValueObject):
@ -66,25 +65,24 @@ so this registration is going to override it.
Overriding the same base type, like AnalogValueObject, in more than one Overriding the same base type, like AnalogValueObject, in more than one
way could be difficult in some kinds of gateway software which may require way could be difficult in some kinds of gateway software which may require
re-factoring the :func:`object.get_object_class` and the re-factoring the :func:`object.get_object_class` and the
:func:`object.get_datatype` functionality. This will be addressed before :func:`object.get_datatype` functionality.
BACpypes reaches v1.0.
The first part of :func:`object.register_object_type` builds a dictionary of The first part of :func:`object.register_object_type` builds a dictionary of
a relationship between the property name and its associated instance. It will the relationship between the property name and its associated instance. It will
look for *properties* class attribtues in the entire inheritance tree (using look for *properties* class attribtues in the entire inheritance tree (using
the method resolution order) but only associate the first of two instances the method resolution order) but only associate the first of two instances
with the same name. with the same name.
So in this case, the RandomValueProperty instance called 'present-value' will So in this case, the RandomValueProperty instance called 'present-value' will
be bound to the object type before the built-in version it finds in the be bound to the object type before the built-in version finds it in the
*properties* list in the :class:`object.AnalogValueObject`. *properties* list in the :class:`object.AnalogValueObject`.
A New Real Property A New Real Property
------------------- -------------------
BACnet clients will expect that the 'present-value' of an Analog Value Object BACnet clients expect the 'present-value' of an Analog Value Object
will be :class:`primitivedata.Real` and returning some other datatype would to be :class:`primitivedata.Real` and returning some other datatype would
seriously break interperabililty. The initialization is almost identical seriously break interperabililty! Initialization is almost identical
to the one for the built-in AnalogValueObject:: to the one for the built-in AnalogValueObject::
class RandomValueProperty(Property, Logging): class RandomValueProperty(Property, Logging):
@ -97,8 +95,8 @@ to the one for the built-in AnalogValueObject::
The only difference is *mutable* is ``False``, which means BACnet clients will The only difference is *mutable* is ``False``, which means BACnet clients will
receive an error if they attempt to write a value to the property. receive an error if they attempt to write a value to the property.
The core of the application is responding to a ReadPropertyRequest, which is The core of the application is responding to a ReadPropertyRequest
mapped into a ReadProperty function call:: (mapped to a ReadProperty function call)::
def ReadProperty(self, obj, arrayIndex=None): def ReadProperty(self, obj, arrayIndex=None):
@ -106,7 +104,7 @@ mapped into a ReadProperty function call::
if arrayIndex is not None: if arrayIndex is not None:
raise Error(errorClass='property', errorCode='property-is-not-an-array') raise Error(errorClass='property', errorCode='property-is-not-an-array')
The **arrayIndex** parameter will be some integer value if the BACnet client is The **arrayIndex** parameter is an integer value if the BACnet client is
accessing the property as an array, which is an error. Now it comes down to accessing the property as an array, which is an error. Now it comes down to
getting a random value and returning it:: getting a random value and returning it::
@ -119,3 +117,17 @@ getting a random value and returning it::
The value returned by this function will be passed as an initial value to The value returned by this function will be passed as an initial value to
construct a :class:`primitivedata.Real` object, which will then be encoded construct a :class:`primitivedata.Real` object, which will then be encoded
into the :class:`apdu.ReadPropertyACK` response and returned to the client. into the :class:`apdu.ReadPropertyACK` response and returned to the client.
Running the Application
------------------------
::
$ python RandonAnalogValue.py
Then using a BACnet client - like an OWS (Operator Workstation) or BACnet exploration
tool, read the application's Analog Value Objects. Notice: the value of the Present Value
property changes each time it is read by the client tool.
.. image:: images/RandomAnalogValue.png

View File

@ -3,8 +3,8 @@
Sample 14 - Getting External Data Sample 14 - Getting External Data
================================= =================================
This is a pair of sample applications, a server that provides key:value updates This is a pair of sample applications: a server that provides key:value updates
in the form of JSON objects, and a client that periodically polls the server in the form of JSON objects; and a client that periodically polls the server
for updates and applies them to a cache. for updates and applies them to a cache.
Server Code Server Code

View File

@ -19,26 +19,29 @@ needs to provide a function to get it::
>>> class MyServer(Server): >>> class MyServer(Server):
... def indication(self, arg): ... def indication(self, arg):
... print "working on", arg ... print('working on', arg)
... self.response(arg.upper()) ... self.response(arg.upper())
... ...
Now create an instance of this new class and bind the client and server together:: Now create an instance of this new class and bind the client and server together::
>>> c = Client()
>>> s = MyServer() >>> s = MyServer()
>>> bind(c, s) >>> bind(c, s)
This only solves the downstream part of the problem, as you can see:: This only solves the downstream part of the problem, as you can see::
>>> c.request("hi") >>> c.request('hi')
working on hi ('working on ', 'hi')
Traceback....
....
NotImplementedError: confirmation must be overridden NotImplementedError: confirmation must be overridden
So now we create a custom client class that does something with the response:: So now we create a custom client class that does something with the response::
>>> class MyClient(Client): >>> class MyClient(Client):
... def confirmation(self, pdu): ... def confirmation(self, pdu):
... print "thanks for the", pdu ... print('thanks for the ', pdu)
... ...
Create an instance of it, bind the client and server together and test it:: Create an instance of it, bind the client and server together and test it::
@ -46,7 +49,7 @@ Create an instance of it, bind the client and server together and test it::
>>> c = MyClient() >>> c = MyClient()
>>> bind(c, s) >>> bind(c, s)
>>> c.request('hi') >>> c.request('hi')
working on hi ('working on ', 'hi')
thanks for the HI ('thanks for ', 'HI')
Success! Success!

View File

@ -4,55 +4,57 @@ Stacking with Debug
=================== ===================
This tutorial uses the same :class:`comm.Client`, :class:`comm.Server` classes This tutorial uses the same :class:`comm.Client`, :class:`comm.Server` classes
from the previous one, so continuing on all it needs is the :class:`comm.Debug` from the previous one, so continuing on from previous tutorial, all we needs is
class, so import it:: to import the class:`comm.Debug`::
>>> from bacpypes.comm import Debug >>> from bacpypes.comm import Debug
Because there could be lots of Debug instances, it could be confusing if you Because there could be lots of Debug instances, it could be confusing if you
didn't know which instance was generating the output. So you can initialize didn't know which instance was generating the output. So initialize the debug
an instance with a lobel:: instance with a name::
>>> d = Debug("middle") >>> d = Debug("middle")
As you can guess, this is going to go into the middle of a :term:`stack` of As you can guess, this is going to go into the middle of a :term:`stack` of
objects. The *top* of the stack is a client, then *bottom* of a stack is a objects. The *top* of the stack is a client, then *bottom* of a stack is a
server. When messages are flowing from clients to servers they are called server. When messages are flowing from clients to servers they are called
:term:`downstream` messages, and when they go from server to the client they go :term:`downstream` messages, and when they flow from server to client they
:term:`upstream`. are :term:`upstream` messages.
The :func:`comm.bind` function takes an arbitrary number of objects, but it The :func:`comm.bind` function takes an arbitrary number of objects. It
assumes that the first one will always be a client, the last one is a server, assumes that the first one will always be a client, the last one is a server,
and that the objects in the middle are both a kind of server that can be and the objects in the middle are hybreds which can be
bound with the client to its left in the parameter list, and a client that can bound with the client to its left, and to the server on its right::
be bound to a server to its right::
>>> bind(c, d, s) >>> bind(c, d, s)
Now when the client generates a request, rather than the message being sent Now when the client generates a request, rather than the message being sent
to the MyServer instance, it is sent to the debugging instance. That is acting to the MyServer instance, it is sent to the debugging instance, which
as a server, so it prints out that it received an indication:: prints out that it received the message::
>>> c.request('hi') >>> c.request('hi')
Debug(middle).indication Debug(middle).indication
- args[0]: hi - args[0]: hi
Now it acts as a client and forwards it down to the server in the stack. That The debugging instance then forwards the message to the server, which prints
generates a print statement and responds with the string uppercase:: its message. Completeing the requests *downstream* journey.::
working on hi working on hi
Upstream from the server is the debugging instance again, this time as a The server then generates a reply. The reply moves *upstream* from the server,
confirmation:: through the debugging instance, this time as a confirmation::
Debug(middle).confirmation Debug(middle).confirmation
- args[0]: HI - args[0]: HI
Now it acts as a server and continues the response up the stack, which is Which is then forwarded *upstream* to the client::
printed out by the client::
thanks for the HI thanks for the HI
This demonstrates how requests first move *downstream* from client to server; then
cause the generation of replies that move *upstream* from server to client; and how the
debug instance in the middle sees the messages moving both ways.
With clearly defined "envelopes" of protocol data, matching the combination of With clearly defined "envelopes" of protocol data, matching the combination of
clients and servers into layers can provide a clear separation of functionality clients and servers into layers can provide a clear separation of functionality
in a protocol stack. in a protocol stack.

View File

@ -10,7 +10,7 @@ According to `Wikipedia <http://en.wikipedia.org/wiki/Protocol_data_unit>`_ a
and that may contain control information, address information, or data. and that may contain control information, address information, or data.
BACpypes uses a slght variation of this definition in that it bundles the BACpypes uses a slght variation of this definition in that it bundles the
address information in with the control information. It considers addressing address information with the control information. It considers addressing as
part of how the data should be delivered, along with other concepts like how part of how the data should be delivered, along with other concepts like how
important the PDU data is relative to other PDUs. important the PDU data is relative to other PDUs.
@ -18,37 +18,53 @@ The basic components of a PDU are the :class:`comm.PCI` and
:class:`comm.PDUData` classes which are then bundled together to form the :class:`comm.PDUData` classes which are then bundled together to form the
:class:`comm.PDU` class. :class:`comm.PDU` class.
All of the protocol interpreters that have been written in the course of All of the protocol interpreters written in the course of
developing BACpypes have all had at least some concept of source and developing BACpypes have a concept of source and
destination. The :class:`comm.PCI` defines only two attributes, **pduSource** destination. The :class:`comm.PCI` defines only two attributes, **pduSource**
and **pduDestination**. and **pduDestination**.
Only in the case of pure master/slave networks has only the destination .. note::
encoded by the master to direct it to a specific slave (so source information
is implicit and not encoded) and the response from the slave back to the master Master/slave networks, are an exception. Messages sent by the master, contain
(so no addressing is included at all). These special cases are rare. only the destination (the source is implicit). Messages returned by the slaves
have no addressing (both the source, and destination are implicit).
As a foundation layer, there are no restrictions on the form of the source and As a foundation layer, there are no restrictions on the form of the source and
destination, they could be integers, strings or even objects. In general, destination, they could be integers, strings or even objects. In general,
the :class:`comm.PDU` class is used as a base class for a series of stack the :class:`comm.PDU` class is used as a base class for a series of stack
specific components, so UDP traffic will have combinations of IP addresses and specific components. UDP traffic have combinations of IP addresses and
port numbers as source and destination, then that will be inherited by something port numbers as source and destination, then that will be inherited by something
that provides more control information, like delivery order or priority. that provides more control information, like delivery order or priority.
Beginning with the base class::
Exploring PDU's
---------------
Begin with importing the base class::
>>> from bacpypes.comm import PDU >>> from bacpypes.comm import PDU
While source and destination are defined in the PCI, they are optional keyword Create a new PDU with some simple content::
parameters. Debugging the contents of the PDU will skip over those attributes
that are ``None`` and strings are assumed to be a sequence of octets and so
are printed as hex encoded strings::
>>> pdu = PDU("hello") >>> pdu = PDU("hello")
We can then see the contents of the PDU as it will be seen on the network
wire and by Wireshark - as a sequence of octets (printed as hex encoded strings)::
>>> pdu.debug_contents() >>> pdu.debug_contents()
pduData = x'68.65.6C.6C.6F' pduData = x'68.65.6C.6C.6F'
Now add some source and destination information:: Now lets add some source and destination addressing information, so the message
can be sent somewhere::
>>> pdu.pduSource = 1
>>> pdu.pduDestination = 2
>>> pdu.debug_contents()
pduSource = 1
pduDestination = 2
pduData = x'68.65.6c.6c.6f'
Of course, we could have provided the addressing information when we created the PDU::
>>> pdu = PDU("hello", source=1, destination=2) >>> pdu = PDU("hello", source=1, destination=2)
>>> pdu.debug_contents() >>> pdu.debug_contents()
@ -56,25 +72,28 @@ Now add some source and destination information::
pduDestination = 2 pduDestination = 2
pduData = x'68.65.6C.6C.6F' pduData = x'68.65.6C.6C.6F'
It is customary to allow missing attributes (which is protocol control .. tip::
information or it would be data) to allow the developer to mixed keyword
parameters and post-init attribute assignment. It is customary to allow missing attributes (be it protocol control
information or data) as this allows the developer to mix keyword
parameters with post-init attribute assignments.
BACnet PDUs BACnet PDUs
----------- -----------
The PDU definition in the core is fine for many protocols, but BACnet has two The basic PDU definition is fine for many protocols, but BACnet has two
additional protocol parameters, described as attributes of a BACnet PCI additional protocol parameters, described as attributes of the BACnet PCI
information. information.
The :class:`pdu.PCI` class extends the basic PCI with **pduExpectingReply** and The :class:`pdu.PCI` class extends the basic PCI with **pduExpectingReply** and
**pduNetworkPriority**. The former is only used in MS/TP networks so the **pduNetworkPriority**. The former is only used in MS/TP networks so the
node generating the request will not pass the token before waiting some amount node generating the request will not pass the token before waiting some amount
of time for a response, and the latter is a hint to routers and other deivces of time for a response, and the latter is a hint to routers, and devices
with priority queues for network traffic that a PDU is more or less important. with priority queues for network traffic, that a PDU is more or less important.
These two fields are set at the application layer and travel with the PDU These two fields are assigned at the application layer and travel with the PDU
content as it travels down the stack. as it travels through the stack.
Encoding and Decoding Encoding and Decoding
--------------------- ---------------------
@ -82,51 +101,80 @@ Encoding and Decoding
The encoding and decoding process consists of consuming content from the source The encoding and decoding process consists of consuming content from the source
PDU and generating content in the destination. BACpypes *could* have used some PDU and generating content in the destination. BACpypes *could* have used some
kind of "visitor" pattern so the process did not consume the source, but kind of "visitor" pattern so the process did not consume the source, but
typically when a layer has finished with PDU and will be sending some other PDU typically when a layer has finished with PDU it will be sending some different PDU
upstream or downstream and once that PDU leaves the layer it is not re-visited. upstream or downstream so once the layer is finished, the PDU is not re-visited.
.. note:: .. note::
This concept, where an object like a PDU is passed off to some other This concept, where an object like a PDU is passed off to another
function and it is no longer "owned" by the builder, is difficult to function and is no longer "owned" by the builder, is difficult to
accomplish in language and runtime environments that do not have automatic accomplish in language environments without automatic
garbage collection. It tremendiously simplifies interpreter code. garbage collection, but tremendiously simplifies our interpreter code.
PDUs nest the control infommation of one level into the data portion of the PDUs nest the control information of one level into the data portion of the
next level down, and when decoding on the way up a stack it is customary to next level. So when decoding on the way up, it is customary to
pass the control information along, even when it isn't strictly necessary. pass the control information along, even when it isn't strictly necessary.
The :func:`pdu.PCI.update` function is an example of a method that is used The :func:`pdu.PCI.update` function is an example of a method that is used
the way a "copy" operation might be used. The PCI classes, and nested versions the way a "copy" operation might be used. The PCI classes, and nested versions
of them, usually have an update function. of them, usually have an update function.
Decoding consumes some number of octets from the front of the PDU data:: Decoding
+++++++++
Decoding always consumes some number of octets from the front of the PDU data.
Lets create a pdu and then use decoding to consume it::
>>> pdu=PDU('hello!!')
>>> pdu.debug_contents()
pduData = x'68.65.6c.6c.6f.21.21'
Consume 1 octet (x'68 = decimal 104')::
>>> pdu = PDU("hello!!")
>>> pdu.get() >>> pdu.get()
104 104
>>> pdu.debug_contents()
pduData = x'65.6c.6c.6f.21.21'
Consume a short integer (two octets)::
>>> pdu.get_short() >>> pdu.get_short()
25964 25964
>>> pdu.debug_contents()
pduData = x'6c.6f.21.21'
Consume a long integer (four octets)::
>>> pdu.get_long() >>> pdu.get_long()
1819222305 1819222305
>>> pdu.debug_contents()
pduData = x''
>>>
And the PDU is now empty:: And the PDU is now empty!
Encoding
+++++++++
We can then build the PDU contents back up through a series of *put* operations.
A *put* is an implicit append operation::
>>> pdu.debug_contents() >>> pdu.debug_contents()
pduData = x'' pduData = x''
But the contents can be put back, an implicit append operation::
>>> pdu.put(104) >>> pdu.put(104)
>>> pdu.debug_contents()
pduData = x'6c'
>>> pdu.put_short(25964) >>> pdu.put_short(25964)
>>> pdu.debug_contents()
pduData = x'6c.65.6c'
>>> pdu.put_long(1819222305) >>> pdu.put_long(1819222305)
>>> pdu.debug_contents() >>> pdu.debug_contents()
pduData = x'68.65.6C.6C.6F.21.21' pduData = x'6c.65.6c.6c.6f.21.21'
.. note:: .. note::
There is no distinction between a PDU that is being used as the source There is no distinction between a PDU that is being taken apart (by get)
to some interpretation process and one that is the destination. Earlier and one that is being built up (by put).
versions of this library made that distinction and the type casting
and type conversion code became an impediment to understanding the
interpretation, so it was dropped.

View File

@ -5,9 +5,18 @@ Addressing
BACnet addresses come in five delicious flavors: BACnet addresses come in five delicious flavors:
* local station - local station
* local broadcast - A message addressed to one device on the same network as the originator.
* remote station -
* remote broadcast - local broadcast
* global broadcast - A message addressed to all devices or nodes on the same network as the originator.
remote station
A message addressed to one device on a different network than the originator.
remote broadcast
A message addressed to all devices or nodes on a different network than the originator.
global broadcast
A message addressed to all devices or nodes on all networks known any device on any network.

View File

@ -5,11 +5,11 @@ Command Shell
Debugging small, short lived BACpypes applications is fairly simple with the Debugging small, short lived BACpypes applications is fairly simple with the
abillity to attach debug handlers to specific components of a stack when it abillity to attach debug handlers to specific components of a stack when it
starts, thn reproducing whatever situation caused the miscreant behaviour. starts, and then reproducing whatever situation caused the mis-behaviour.
For longer running applications like gateways it might take some time before For longer running applications like gateways it might take some time before
a scenerio is ready, in which case it is advantageous to postpone debugging a scenerio is ready, in which case it is advantageous to start and stop the debugging
output, or stop it without stopping the application. output, without stopping the application.
For some debugging scenerios it is beneficial to force some values into the For some debugging scenerios it is beneficial to force some values into the
stack, or delete some values and see how the application performs. For example, stack, or delete some values and see how the application performs. For example,
@ -18,7 +18,7 @@ perhaps deleting a routing path associated with a network.
Python has a `cmd <http://wiki.python.org/moin/CmdModule>`_ module that makes Python has a `cmd <http://wiki.python.org/moin/CmdModule>`_ module that makes
it easy to embed a command line interpreter in an application. BACpypes it easy to embed a command line interpreter in an application. BACpypes
extends this interpreter with some commands to assist debugging and runs extends this interpreter with some commands to assist debugging and runs
the interpret in a separate thread so it does not interfere with the BACpypes the interpreter in a separate thread so it does not interfere with the BACpypes
:func:`core.run` functionality. :func:`core.run` functionality.
Application Additions Application Additions
@ -42,18 +42,18 @@ BACpypes applications, this can be wrapped::
Command Recall Command Recall
-------------- --------------
The BACpypes command line interpreter will create a text file containing each The BACpypes command line interpreter maintains a history (text file)
of the commands that were entered and load this file the next time the of the commands executed, which it reloads upon startup.
application starts. Pressing the *previous command* keyboard shortcut (usually Pressing the *previous command* keyboard shortcut (up-arrow key)
the up-arrow key) will recall previous commands so they can be executed again. recalls previous commands so they can be executed again.
Basic Commands Basic Commands
-------------- --------------
All of the commands are listed in the :mod:`consolecmd` documentation, but All of the commands supported are listed in the :mod:`consolecmd` documentation.
the simplest way to learn is to try it:: The simplest way to learn the commands is to try them::
$ python tutorial006.py $ python SampleConsoleCmd.py
> hi > hi
*** Unknown syntax: hi *** Unknown syntax: hi
@ -95,71 +95,93 @@ And finally exiting the application::
Adding Commands Adding Commands
--------------- ---------------
Adding additional commands is as simple as providing an additional function:: Adding additional commands is as simple as providing an additional function.
Add these lines to SampleConsoleCmd.py::
class MyConsoleCmd(ConsoleCmd): class SampleConsoleCmd(ConsoleCmd):
def do_something(self, arg): def do_something(self, arg):
"""something <arg> - do something""" """something <arg> - do something"""
print "do something", arg print "do something", arg
The ConsoleCmd will trap a help request ``help something`` into printing out The ConsoleCmd will trap a help request ``help something`` into printing out
the documnetation string. the documnetation string.::
Example Cache Commands
----------------------
This code is in **tutorial006a.py**. The concept is to force values into an
application cache, or delete them, and dump the cache. First, setting values
is a *set* command::
def do_set(self, arg):
"""set <key> <value> - change a cache value"""
if _debug: MyCacheCmd._debug("do_set %r", arg)
key, value = arg.split()
my_cache[key] = value
Then then delete cache entries is a *del* command::
def do_del(self, arg):
"""del <key> - delete a cache entry"""
if _debug: MyCacheCmd._debug("do_del %r", arg)
try:
del my_cache[arg]
except:
print arg, "not in cache"
And just to be sure, be able to dump the cache::
def do_dump(self, arg):
"""dump - nicely print the cache"""
if _debug: MyCacheCmd._debug("do_dump %r", arg)
pprint(my_cache)
And here is a sample when the application is run, note that the new commands
show up in the help list::
$ python tutorial/tutorial006a.py
> help > help
Documented commands (type help <topic>): Documented commands (type help <topic>):
======================================== ========================================
EOF buggers bugin bugout del dump exit gc help set shell EOF buggers bugin bugout exit gc help nothing shell **something**
And you can get help with a command:: > help something
something <arg> - do something
>
Example Cache Commands
----------------------
Add these functions to **SampleConsoleCmd.py**. The concept is to force values into an
application cache, delete them, and dump the cache. First, setting values
is a *set* command::
class SampleConsoleCmd(ConsoleCmd):
my_cache= {}
def do_set(self, arg):
"""set <key> <value> - change a cache value"""
if _debug: SampleConsoleCmd._debug("do_set %r", arg)
key, value = arg.split()
self.my_cache[key] = value
Then delete cache entries with a *del* command::
def do_del(self, arg):
"""del <key> - delete a cache entry"""
if _debug: SampleConsoleCmd._debug("do_del %r", arg)
try:
del self.my_cache[arg]
except:
print arg, "not in cache"
And to verify, dump the cache::
def do_dump(self, arg):
"""dump - nicely print the cache"""
if _debug: SampleConsoleCmd._debug("do_dump %r", arg)
print(self.my_cache)
And when the sample application is run, note the new commands
show up in the help list::
$ python SampleConsoleCmd.py
> help
Documented commands (type help <topic>):
========================================
EOF bugin **del** exit help **set** something
buggers bugout **dump** gc nothing shell
You can get help with the new commands::
> help set > help set
set <key> <value> - change a cache value set <key> <value> - change a cache value
Add some things to the cache and dump it out::
Lets use these new commands to add some items to the cache and dump it out::
> set x 12 > set x 12
> set y 13 > set y 13
> dump > dump
{'x': '12', 'y': '13'} {'x': '12', 'y': '13'}
Now add a debugger to the main application, which can generate a lot output Now add a debugger to the main application, which can generate a lot output
for most applications, but this one is simple:: for most applications, but this one is simple::
@ -169,19 +191,21 @@ for most applications, but this one is simple::
Now we'll get some debug output when the cache entry is deleted:: Now we'll get some debug output when the cache entry is deleted::
> del x > del x
DEBUG:__main__.MyCacheCmd:do_del 'x' DEBUG:__main__.SampleConsoleCmd:do_del 'x'
We can see a list of buggers an which ones have a debugger attached:: We can see a list of buggers and which ones have a debugger attached::
> buggers __main__ > buggers __main__
handlers: __main__ handlers: __main__
* __main__ * __main__
__main__.MyCacheCmd __main__.SampleApplication
__main__.SampleConsoleCmd
Check the contents of the cache:: Check the contents of the cache::
> dump > dump
DEBUG:__main__.MyCacheCmd:do_dump '' DEBUG:__main__.SampleConsoleCmd:do_dump ''
{'y': '13'} {'y': '13'}
All done:: All done::

134
samples/SampleConsoleCmd-A.py Executable file
View File

@ -0,0 +1,134 @@
#!/usr/bin/env python
"""
This sample application is a simple BACpypes application that
presents a console prompt. Almost identical to the SampleApplication,
the BACnet application is minimal, but with the console commands
that match the command line options like 'buggers' and 'debug' the
user can add debugging "on the fly".
"""
from bacpypes.debugging import bacpypes_debugging, ModuleLogger
from bacpypes.consolelogging import ConfigArgumentParser
from bacpypes.consolecmd import ConsoleCmd
from bacpypes.core import run, enable_sleeping
from bacpypes.app import LocalDeviceObject, BIPSimpleApplication
# some debugging
_debug = 0
_log = ModuleLogger(globals())
#
# SampleApplication
#
@bacpypes_debugging
class SampleApplication(BIPSimpleApplication):
def __init__(self, device, address):
if _debug: SampleApplication._debug("__init__ %r %r", device, address)
BIPSimpleApplication.__init__(self, device, address)
def request(self, apdu):
if _debug: SampleApplication._debug("request %r", apdu)
BIPSimpleApplication.request(self, apdu)
def indication(self, apdu):
if _debug: SampleApplication._debug("indication %r", apdu)
BIPSimpleApplication.indication(self, apdu)
def response(self, apdu):
if _debug: SampleApplication._debug("response %r", apdu)
BIPSimpleApplication.response(self, apdu)
def confirmation(self, apdu):
if _debug: SampleApplication._debug("confirmation %r", apdu)
BIPSimpleApplication.confirmation(self, apdu)
#
# SampleConsoleCmd
#
@bacpypes_debugging
class SampleConsoleCmd(ConsoleCmd):
my_cache= {}
def do_set(self, arg):
"""set <key> <value> - change a cache value"""
if _debug: SampleConsoleCmd._debug("do_set %r", arg)
key, value = arg.split()
self.my_cache[key] = value
def do_del(self, arg):
"""del <key> - delete a cache entry"""
if _debug: SampleConsoleCmd._debug("do_del %r", arg)
try:
del self.my_cache[arg]
except:
print arg, "not in cache"
def do_dump(self, arg):
"""dump - nicely print the cache"""
if _debug: SampleConsoleCmd._debug("do_dump %r", arg)
print(self.my_cache)
def do_something(self, arg):
"""something <arg> - do something"""
print "do something", arg
def do_nothing(self, args):
"""nothing can be done"""
args = args.split()
if _debug: SampleConsoleCmd._debug("do_nothing %r", args)
#
# __main__
#
def main():
# parse the command line arguments
args = ConfigArgumentParser(description=__doc__).parse_args()
if _debug: _log.debug("initialization")
if _debug: _log.debug(" - args: %r", args)
# make a device object
this_device = LocalDeviceObject(
objectName=args.ini.objectname,
objectIdentifier=int(args.ini.objectidentifier),
maxApduLengthAccepted=int(args.ini.maxapdulengthaccepted),
segmentationSupported=args.ini.segmentationsupported,
vendorIdentifier=int(args.ini.vendoridentifier),
)
# make a sample application
this_application = SampleApplication(this_device, args.ini.address)
# get the services supported
services_supported = this_application.get_services_supported()
if _debug: _log.debug(" - services_supported: %r", services_supported)
# let the device object know
this_device.protocolServicesSupported = services_supported.value
# make a console
this_console = SampleConsoleCmd()
# enable sleeping will help with threads
enable_sleeping()
_log.debug("running")
run()
_log.debug("fini")
if __name__ == "__main__":
main()

View File

@ -2,7 +2,7 @@
""" """
This application presents a 'console' prompt to the user asking for Who-Is and I-Am This application presents a 'console' prompt to the user asking for Who-Is and I-Am
commands which create the related APDUs, then lines up the coorresponding I-Am commands which create the related APDUs, then lines up the corresponding I-Am
for incoming traffic and prints out the contents. for incoming traffic and prints out the contents.
""" """
@ -22,10 +22,11 @@ from bacpypes.basetypes import ServicesSupported
from bacpypes.errors import DecodingError from bacpypes.errors import DecodingError
# some debugging # some debugging
_debug = 0 _debug = 1
_log = ModuleLogger(globals()) _log = ModuleLogger(globals())
# globals # globals
this_device = None
this_application = None this_application = None
# #
@ -161,6 +162,7 @@ class WhoIsIAmConsoleCmd(ConsoleCmd):
# #
def main(): def main():
global this_device
global this_application global this_application
# parse the command line arguments # parse the command line arguments