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
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
another device, installing the library, and downloading and configuring the
samples applications.
another device. We will cover installing the library, and downloading and
configuring the samples applications.
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
given you a test device and purchased a copy of the standard. You have
in your office...
given you a test device and purchased a copy of the BACnet standard. I will
need...
- a development workstation running some flavor of Linux complete with
the latest version of Python 2.7 and
- a development workstation running some flavor of Linux, complete with
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>`_.
- a small hub you can plug in your workstation and this misterious device
and not get distracted by lots of other LAN traffic.
- a small Ethernet hub into which you can plug both your workstation and your
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::
$ sudo easy_install bacpypes
or
$ sudo pip install bacpypes
And while you are at it, get a copy of the project from SourceForge that
has the library source code, sample code, and this documentation::
And while you are at it, get a copy of the BACpypes project from GitHub. It
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
copy of `Wireshark <http://www.wireshark.org/>`_::
$ sudo apt-get install wireshark
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
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.
*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.
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*
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
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
workstation. My collegues and I use star names so the sample
congiuration files will have "Betelgeuse".
workstation. My collegues and I use star names, so in the sample
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
you won't need to change the values in the sample configuration file
@ -78,33 +83,36 @@ until you get deeper into the protocol.
BACnet works on lots of different types of networks, from high
speed Ethernet to "slower" and "cheaper" ARCNET or MS/TP (a
serial bus protocol used for a field bus defined by BACnet).
For devices to exchange messages they have to know the maximum
size message the device can handle.
For devices to exchange messages they need to know the maximum
size message the other device can handle.
*Segmentation Supported*
A vast majority of BACnet communications traffic fits in one
message, but thre can be times when larger messages are
convinient and more efficient. Segmentation allows larger
messages to be broken up into segemnts and spliced back together.
It is not unusual for "low power" field equipment to not
message, but there are times when larger messages are
convenient and more efficient. Segmentation allows larger
messages to be broken up into segments and spliced back together.
It is not unusual for "low power" field devices to not
support segmentation.
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
~~~~~~~~~~~~~~~~~~~~~
Now that you know what these values are going to be you can
configure the BACnet part of your workstation. Change into the
Now that you know what these values are going to be, you can
configure the BACnet portion of your workstation. Change into the
samples directory that you checked out earlier, make a copy
of the sample configuration file, and edit it for your site::
$ cd bacpypes/samples
$ cp BACpypes~.ini BACpypes.ini
The sample applications are going to look for this file, and you
can direct them to other INI files on the command line, so it is
.. tip::
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"
@ -113,18 +121,20 @@ 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
-------------------------
BACnet devices comunicate using UDP rather than TCP. This is so
that devices do not need to implement a full IP stack (although
BACnet devices communicate using UDP rather than TCP. This is so
devices do not need to implement a full IP stack (although
many of them do becuase they support multiple protocols, including
having embedded web servers).
There are two types of UDP messages; *unicast* which is a message
from one specific IP address and port to another one, and *broadcast*
which is received and processed by all devices that have the port
open. BACnet uses both types of messages and your workstation
from one specific IP address (and port) to another device's IP address
(and port); and *broadcast* messages which are sent by one device
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.
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
be sent to the IP address, and broadcast messages will be sent to
the broadcast address **192.168.255.255** which is the network
portion of the configuration value will all 1's in the host
portion.
portion of the address with all 1's in the host portion.
To receive both unicast and broadcast addresses, BACpypes will
open two sockets, one for unicast traffic and one that only listens
To receive both unicast and broadcast addresses, BACpypes
opens two sockets, one for unicast traffic and one that only listens
for broadcast messages. The operating system will typically not allow two
applications to open the same socket at the same time
so to run two BACnet applciations at
the same time they need to be configured with different ports.
The BACnet protocol has port 47808 (hex 0xBAC0) assigned to it
.. note::
The BACnet protocol has been assigned port 47808 (hex 0xBAC0) by
by the `Internet Assigned Numbers Authority <https://www.iana.org/>`_, and sequentially
higher numbers are used in many applications. There are some
BACnet routing and networking isseus with this, but that is for
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
-----------------------
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?
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
client waits around to listen for I-Am messages. The source
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
session with a BACnet capture filter::
@ -187,7 +199,7 @@ Now start the application::
$ 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
@ -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
The details of the commands will be described in the next
section.
The details of the commands are described in the next section.
Generating An I-Am
------------------
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
You should see your configuration parameters in the I-Am
message in Wireshark, this is a "global broadcast" message, so your
test device will see it but since your test device probably
isn't looking for you, it will not respond with anything.
You should see Wireshark capture your I-Am message containing your configuration
parameters. This is a "global broadcast" message.
Your test device will see it but since your test device probably
isn't looking for you, it will not respond to the message.
Binding to the Test Device
--------------------------
Now to confirm that the workstation can receive the
messages that the test device sends out, generate a Who-Is
request. This one will be "unconstrained" which means that
every device will respond. *Do not generate these types of
unconstrained requests on a large
network because it will create a lot of traffic that can
cause conjestion.* Here is a Who-Is::
Next we want to confirm that your workstation can receive the
messages the test device sends out. We do this by generating a
generic Who-Is request. The request will be "unconstrained", meaning
every device that hears the message will respond with their corresponding
I-Am messages.
.. 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
You should see the request in Wireshark and the response from
the device, and then a summary line of the response on the
workstation.
You should see the Who-Is request captured in Wireshark along with the I-Am
response from your test device, and then the details of the response displayed
on the workstation console.::
There are a few different forms of the *whois* command this
simple application allows and you can see the basic form
with the help command::
> whois
> pduSource = <RemoteStation 50009:9>
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
whois [ <addr>] [ <lolimit> <hilimit> ]
This is like a BNF syntax, the whois command is optionally
followed by an address, and then optionally followed by a
low limit and high limit. The most common use of the Who-Is
This is like a BNF syntax, the **whois** command is optionally
followed by a BACnet device address, and then optionally followed by a
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
identifier::
> whois 1000 1000
And if the site has a numbering scheme for groups of BACnet
devices like all those in a specific building, then it is
common to look for all of them as a group::
If the site has a numbering scheme for groups of BACnet
devices (i.e. grouped by building), then it is
common to look for all the devices in a specific building as a group::
> whois 203000 203099
Every once in a while a contractor might install a BACnet
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::
> 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,
but that is a subject of an other tutorial.
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
about how it is working. All of the "console" applications,
those that prompt for commands, use the same basic
about how it is working. All of the "console" applications
(i.e. those that prompt for commands) use the same basic
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
options so it is easy to move between applications, turn debugging on and
and using different configurations. There may be additional options and
command parameters than the ones described in this section.
and use different configurations. There may be additional options and
command parameters than just the ones described in this section.
Getting Help
------------
@ -18,7 +18,7 @@ an application, you can start with help::
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
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.
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
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
beginning its initialization, shows the value of a variable called args,
an instance of the WhoIsIAmApplication class is created with some parameters,
and then the application starts running.
application. From the output above you can see the application initializing,
setting the args variable, creating an instance of the WhoIsIAmApplication class
(with some parameters), and then declaring itself - running.
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
function is called with an instance of a PDU as a parameter. Following
the function invocation description, the debugging output continues with the
contents of the PDU. Notice that the protocol data is printed as a hex
encoded string, and only the first 20 bytes of the message.
contents of the PDU. Notice, the protocol data is printed as a hex
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
combinations of logger names as necessary. Note that you cannot debug a
You can debug a function just as easily. Specify as many different
combinations of logger names as necessary. Note, you cannot debug a
specific function within a class.
Changing INI Files
------------------

View File

@ -7,9 +7,16 @@ BACpypes library for building BACnet applications using Python. Installation
is easy, just::
$ 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
@ -18,14 +25,20 @@ And then use the setup utility to install it::
$ cd bacpypes
$ 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
---------------
@ -39,6 +52,8 @@ downloading the sample code and communicating with a test device.
gettingstarted/gettingstarted001.rst
gettingstarted/gettingstarted002.rst
Tutorial
--------
@ -54,11 +69,12 @@ essential components of a BACpypes application and how the pieces fit together.
tutorial/tutorial004.rst
tutorial/tutorial006.rst
Samples
-------
The library has a variety of sample applications, some of them are a framework
for building larger applications, some of them are standalone analysis tools
BACpypes comes with a variety of sample applications. Some are a framework
for building larger applications. Some are standalone analysis tools
that don't require a connection to a network.
.. toctree::
@ -71,15 +87,6 @@ that don't require a connection to a network.
samples/sample005.rst
samples/sample014.rst
Modules
-------
This documentation is intended for BACpypes developers.
.. toctree::
:maxdepth: 1
modules/index.rst
Glossary
--------
@ -89,6 +96,7 @@ Glossary
glossary.rst
Release Notes
-------------
@ -97,6 +105,21 @@ Release Notes
releasenotes.rst
------
Modules
-------
.. tip:: Documentation intended for BACpypes developers.
.. toctree::
:maxdepth: 1
modules/index.rst
-----
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
is passed between two entities. It is composed of Protcol Control Information
(PCI), which usually has information about addressing and other types of
processing instructions, and data. The set of classes in this module are not
specific to BACnet.
(PCI) - information about addressing, processing instructions - and data.
The set of classes in this module are not specific to BACnet.
.. class:: PCI
@ -68,6 +67,11 @@ specific to BACnet.
.. 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
This attribute typically holds a simple octet string, but for higher
@ -81,7 +85,7 @@ specific to BACnet.
.. 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
are not at least `len` octets this will raise a DecodingError
@ -89,8 +93,12 @@ specific to BACnet.
.. method:: get_short()
Extract a short integer (two octets) from the front of the data.
.. method:: get_long()
Extract a long integer (four octets) from the front of the data.
.. method:: put(ch)
:param octet ch: the octet to append to the end
@ -101,12 +109,12 @@ specific to BACnet.
.. method:: put_short(n)
:param short integer: two octets to append to the end
.. method:: put_long(n)
The PDUData class has functions for gathering information from the front
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.
:param long integer: four octets to append to the end
.. class:: PDU(PCI, PDUData)

View File

@ -5,7 +5,11 @@
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
---------
@ -19,44 +23,88 @@ Classes
.. class:: ConsoleCmd(cmd.Cmd, Thread)
This is a long line of text.
.. method:: __init__(prompt="> ", allow_exec=False)
:param string prompt: prompt for commands
:param boolean allow_exec: allow non-commands to be executed
This is a long line of text.
.. 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)
:param args: commands
This is a long line of text.
Template of a function implementing a console command.
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
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>
This is a long line of text.
Attach a debugger.::
> bugin bacpypes.task.OneShotTask
handler to bacpypes.task.OneShotTask added
.. 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
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
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.
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
======
This module defines exception class for errors that it detects in the
configuration of the stack or in encoding and decoding PDUs. All of these
exceptions are derived from ValueError from the built-in exceptions module.
This module defines the exception class for errors it detects in the
configuration of the stack or in encoding or decoding PDUs. All of these
exceptions are derived from ValueError (in Python's built-in exceptions module).
Classes
-------
@ -30,6 +30,6 @@ Classes
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
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.

View File

@ -6,9 +6,9 @@ Singleton
=========
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
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.
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
=============================
@ -130,19 +129,16 @@ as the application, but the '--ini' option is available when it's not::
elif not config.read('BACpypes.ini'):
raise RuntimeError, "configuration file not found"
If the sample applications are run from the subversion directory, there is a
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
appropriate to your installation::
.. tip::
There is a sample INI file called **BACpypes~.ini** as part of the repository. Make
a local copy and edit it with information appropriate to your installation::
$ pwd
.../samples
$ cp BACpypes~.ini BACpypes.ini
$ vi BACpypes.ini
$ svn status
? BACpypes.ini
$ cp ../BACpypes~.ini BACpypes.ini
$ nano BACpypes.ini
Subversion understands that the local copy is not part of the repository.
Now applications will create a :class:`object.LocalDeviceObject` which will
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
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
enabled::
$ python sample001.py --debug __main__
$ python SampleApplication.py --debug __main__
The output will include the initialization, running, and finally statements. To
run with debugging on just the SampleApplication class::
The output will include the initialization, running, and finally statements.::
$ 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
name::
To run with debugging on just the SampleApplication class::
$ 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`
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
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
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`
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
==============================
@ -50,7 +49,7 @@ The middle is going to process the data in the request::
)
# 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
processing::
@ -72,7 +71,7 @@ It uses a diferent key, but counts them the same::
)
# count the times this has been received
iAmCounter[key] += 1
i_am_counter[key] += 1
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::
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
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
to the requestor, so relying on broadcast traffic to analyze device and
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
=================================
@ -6,7 +5,7 @@ Sample 3 - Who-Has/I-Have Counter
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
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
@ -34,23 +33,23 @@ cannot appear in the APDU at the same time::
return
# 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
attribute will be ``None``. With this particular APDU the *object*
parameter is required, but only one of its child attributes *objectIdentifier*
When an optional parameter is not specified in a PDU then the corresponding
attribute is ``None``. With this particular APDU the *object*
parameter is required, and one of its child attributes *objectIdentifier*
or *objectName* will be not ``None``. If both are ``None`` then the
request is not properly formed.
.. 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
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
is to say it has teh appropriate opening and closing tags and the data
types of the parameters are correct. Watch out for Any!
The application can rely on the fact that the APDU is well-formed - meaning
it has the appropriate opening and closing tags and the data
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::
@ -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
iHaveCounter[key] += 1
i_have_counter[key] += 1
Dumping the contents of the counters is simple.
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
to analyze object binding is not as useful as it used to be.
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
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
to a collection of data. It assumes that almost all of the default behaviour
of a BACpypes application is sufficient.
@ -12,21 +12,20 @@ of a BACpypes application is sufficient.
.. note::
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
-----------------------
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::
# make a sample application
thisApplication = BIPSimpleApplication(thisDevice, config.get('BACpypes','address'))
The only object this has by default is an instance of a
:class:`object.LocalDeviceObject`. The next step is to create a special Analog
Value Object and add it to the application::
The only object by default is an instance of :class:`object.LocalDeviceObject`.
The next step is to create a special Analog Value Object and add it to the application::
# make a random input object
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
---------------------------------
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::
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
way could be difficult in some kinds of gateway software which may require
re-factoring the :func:`object.get_object_class` and the
:func:`object.get_datatype` functionality. This will be addressed before
BACpypes reaches v1.0.
:func:`object.get_datatype` functionality.
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
the method resolution order) but only associate the first of two instances
with the same name.
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`.
A New Real Property
-------------------
BACnet clients will expect that the 'present-value' of an Analog Value Object
will be :class:`primitivedata.Real` and returning some other datatype would
seriously break interperabililty. The initialization is almost identical
BACnet clients expect the 'present-value' of an Analog Value Object
to be :class:`primitivedata.Real` and returning some other datatype would
seriously break interperabililty! Initialization is almost identical
to the one for the built-in AnalogValueObject::
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
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
mapped into a ReadProperty function call::
The core of the application is responding to a ReadPropertyRequest
(mapped to a ReadProperty function call)::
def ReadProperty(self, obj, arrayIndex=None):
@ -106,7 +104,7 @@ mapped into a ReadProperty function call::
if arrayIndex is not None:
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
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
construct a :class:`primitivedata.Real` object, which will then be encoded
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
=================================
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
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
for updates and applies them to a cache.
Server Code

View File

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

View File

@ -4,55 +4,57 @@ Stacking with Debug
===================
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`
class, so import it::
from the previous one, so continuing on from previous tutorial, all we needs is
to import the class:`comm.Debug`::
>>> from bacpypes.comm import Debug
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
an instance with a lobel::
didn't know which instance was generating the output. So initialize the debug
instance with a name::
>>> d = Debug("middle")
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
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:`upstream`.
:term:`downstream` messages, and when they flow from server to client they
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,
and that the objects in the middle are both a kind of server that can be
bound with the client to its left in the parameter list, and a client that can
be bound to a server to its right::
and the objects in the middle are hybreds which can be
bound with the client to its left, and to the server on its right::
>>> bind(c, d, s)
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
as a server, so it prints out that it received an indication::
to the MyServer instance, it is sent to the debugging instance, which
prints out that it received the message::
>>> c.request('hi')
Debug(middle).indication
- args[0]: hi
Now it acts as a client and forwards it down to the server in the stack. That
generates a print statement and responds with the string uppercase::
The debugging instance then forwards the message to the server, which prints
its message. Completeing the requests *downstream* journey.::
working on hi
Upstream from the server is the debugging instance again, this time as a
confirmation::
The server then generates a reply. The reply moves *upstream* from the server,
through the debugging instance, this time as a confirmation::
Debug(middle).confirmation
- args[0]: HI
Now it acts as a server and continues the response up the stack, which is
printed out by the client::
Which is then forwarded *upstream* to the client::
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
clients and servers into layers can provide a clear separation of functionality
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.
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
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.PDU` class.
All of the protocol interpreters that have been written in the course of
developing BACpypes have all had at least some concept of source and
All of the protocol interpreters written in the course of
developing BACpypes have a concept of source and
destination. The :class:`comm.PCI` defines only two attributes, **pduSource**
and **pduDestination**.
Only in the case of pure master/slave networks has only the destination
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
(so no addressing is included at all). These special cases are rare.
.. note::
Master/slave networks, are an exception. Messages sent by the master, contain
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
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
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
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
While source and destination are defined in the PCI, they are optional keyword
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::
Create a new PDU with some simple content::
>>> 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()
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.debug_contents()
@ -56,25 +72,28 @@ Now add some source and destination information::
pduDestination = 2
pduData = x'68.65.6C.6C.6F'
It is customary to allow missing attributes (which is protocol control
information or it would be data) to allow the developer to mixed keyword
parameters and post-init attribute assignment.
.. tip::
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
-----------
The PDU definition in the core is fine for many protocols, but BACnet has two
additional protocol parameters, described as attributes of a BACnet PCI
The basic PDU definition is fine for many protocols, but BACnet has two
additional protocol parameters, described as attributes of the BACnet PCI
information.
The :class:`pdu.PCI` class extends the basic PCI with **pduExpectingReply** and
**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
of time for a response, and the latter is a hint to routers and other deivces
with priority queues for network traffic that a PDU is more or less important.
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.
These two fields are set at the application layer and travel with the PDU
content as it travels down the stack.
These two fields are assigned at the application layer and travel with the PDU
as it travels through the stack.
Encoding and Decoding
---------------------
@ -82,51 +101,80 @@ Encoding and Decoding
The encoding and decoding process consists of consuming content from the source
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
typically when a layer has finished with PDU and will be sending some other PDU
upstream or downstream and once that PDU leaves the layer it is not re-visited.
typically when a layer has finished with PDU it will be sending some different PDU
upstream or downstream so once the layer is finished, the PDU is not re-visited.
.. note::
This concept, where an object like a PDU is passed off to some other
function and it is no longer "owned" by the builder, is difficult to
accomplish in language and runtime environments that do not have automatic
garbage collection. It tremendiously simplifies interpreter code.
This concept, where an object like a PDU is passed off to another
function and is no longer "owned" by the builder, is difficult to
accomplish in language environments without automatic
garbage collection, but tremendiously simplifies our interpreter code.
PDUs nest the control infommation 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
PDUs nest the control information of one level into the data portion of the
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.
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
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()
104
>>> pdu.debug_contents()
pduData = x'65.6c.6c.6f.21.21'
Consume a short integer (two octets)::
>>> pdu.get_short()
25964
>>> pdu.debug_contents()
pduData = x'6c.6f.21.21'
Consume a long integer (four octets)::
>>> pdu.get_long()
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()
pduData = x''
But the contents can be put back, an implicit append operation::
>>> pdu.put(104)
>>> pdu.debug_contents()
pduData = x'6c'
>>> pdu.put_short(25964)
>>> pdu.debug_contents()
pduData = x'6c.65.6c'
>>> pdu.put_long(1819222305)
>>> pdu.debug_contents()
pduData = x'68.65.6C.6C.6F.21.21'
pduData = x'6c.65.6c.6c.6f.21.21'
.. note::
There is no distinction between a PDU that is being used as the source
to some interpretation process and one that is the destination. Earlier
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.
There is no distinction between a PDU that is being taken apart (by get)
and one that is being built up (by put).

View File

@ -5,9 +5,18 @@ Addressing
BACnet addresses come in five delicious flavors:
* local station -
* local broadcast -
* remote station -
* remote broadcast -
* global broadcast -
local station
A message addressed to one device on the same network as the originator.
local 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
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
a scenerio is ready, in which case it is advantageous to postpone debugging
output, or stop it without stopping the application.
a scenerio is ready, in which case it is advantageous to start and stop the debugging
output, without stopping the application.
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,
@ -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
it easy to embed a command line interpreter in an application. BACpypes
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.
Application Additions
@ -42,18 +42,18 @@ BACpypes applications, this can be wrapped::
Command Recall
--------------
The BACpypes command line interpreter will create a text file containing each
of the commands that were entered and load this file the next time the
application starts. Pressing the *previous command* keyboard shortcut (usually
the up-arrow key) will recall previous commands so they can be executed again.
The BACpypes command line interpreter maintains a history (text file)
of the commands executed, which it reloads upon startup.
Pressing the *previous command* keyboard shortcut (up-arrow key)
recalls previous commands so they can be executed again.
Basic Commands
--------------
All of the commands are listed in the :mod:`consolecmd` documentation, but
the simplest way to learn is to try it::
All of the commands supported are listed in the :mod:`consolecmd` documentation.
The simplest way to learn the commands is to try them::
$ python tutorial006.py
$ python SampleConsoleCmd.py
> hi
*** Unknown syntax: hi
@ -95,71 +95,93 @@ And finally exiting the application::
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):
"""something <arg> - do something"""
print "do something", arg
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
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
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 y 13
> dump
{'x': '12', 'y': '13'}
Now add a debugger to the main application, which can generate a lot output
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::
> 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__
handlers: __main__
* __main__
__main__.MyCacheCmd
__main__.SampleApplication
__main__.SampleConsoleCmd
Check the contents of the cache::
> dump
DEBUG:__main__.MyCacheCmd:do_dump ''
DEBUG:__main__.SampleConsoleCmd:do_dump ''
{'y': '13'}
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
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.
"""
@ -22,10 +22,11 @@ from bacpypes.basetypes import ServicesSupported
from bacpypes.errors import DecodingError
# some debugging
_debug = 0
_debug = 1
_log = ModuleLogger(globals())
# globals
this_device = None
this_application = None
#
@ -161,6 +162,7 @@ class WhoIsIAmConsoleCmd(ConsoleCmd):
#
def main():
global this_device
global this_application
# parse the command line arguments