mirror of
https://github.com/JoelBender/bacpypes
synced 2025-10-05 22:18:16 +08:00
commit
305154d80b
|
@ -4,127 +4,137 @@ 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
|
||||||
until you get deeper into the protocol.
|
until you get deeper into the protocol.
|
||||||
|
|
||||||
*Maximum APDU Length Accepted*
|
*Maximum APDU Length Accepted*
|
||||||
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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
|
@ -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
|
You will be installing the latest released version from PyPI (the Python Packages Index),
|
||||||
the latest version from GitHub::
|
located at pypi.python.org
|
||||||
|
|
||||||
$ git clone https://github.com/JoelBender/bacpypes.git
|
.. note::
|
||||||
|
|
||||||
And then use the setup utility to install it::
|
You can also check out the latest version from GitHub::
|
||||||
|
|
||||||
|
$ git clone https://github.com/JoelBender/bacpypes.git
|
||||||
|
|
||||||
|
And then use the setup utility to install it::
|
||||||
|
|
||||||
|
$ cd bacpypes
|
||||||
|
$ python setup.py install
|
||||||
|
|
||||||
$ cd bacpypes
|
|
||||||
$ python setup.py install
|
|
||||||
|
|
||||||
If you would like to participate in its development, please join the
|
.. tip::
|
||||||
`developers mailing list
|
|
||||||
<https://lists.sourceforge.net/lists/listinfo/bacpypes-developers>`_, join the
|
If you would like to participate in its development, please join:
|
||||||
`chat room on Gitter <https://gitter.im/JoelBender/bacpypes>`_, and add the
|
|
||||||
`Google+ <https://plus.google.com/100756765082570761221/posts>`_ to your
|
- the `developers mailing list <https://lists.sourceforge.net/lists/listinfo/bacpypes-developers>`_,
|
||||||
circles have have release notifications show up in your stream.
|
- 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!**
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
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
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
|
|
@ -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,16 +85,20 @@ 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
|
||||||
exception.
|
exception.
|
||||||
|
|
||||||
.. 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)
|
||||||
|
|
||||||
|
|
|
@ -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...
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
BIN
doc/source/samples/images/RandomAnalogValue.png
Normal file
BIN
doc/source/samples/images/RandomAnalogValue.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
|
@ -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
|
|
||||||
appropriate to your installation::
|
|
||||||
|
|
||||||
$ pwd
|
There is a sample INI file called **BACpypes~.ini** as part of the repository. Make
|
||||||
.../samples
|
a local copy and edit it with information appropriate to your installation::
|
||||||
$ cp BACpypes~.ini BACpypes.ini
|
|
||||||
$ vi BACpypes.ini
|
$ pwd
|
||||||
$ svn status
|
.../samples
|
||||||
? 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
|
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
|
||||||
|
|
||||||
|
|
|
@ -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 -----
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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!
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
||||||
(so no addressing is included at all). These special cases are rare.
|
|
||||||
|
|
||||||
|
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
|
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.
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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**
|
||||||
|
|
||||||
|
> help something
|
||||||
|
something <arg> - do something
|
||||||
|
>
|
||||||
|
|
||||||
And you can get help with a command::
|
|
||||||
|
|
||||||
|
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
134
samples/SampleConsoleCmd-A.py
Executable 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()
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user