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

refactor: Remove tutorials/WhoIsIAm.py

This removes the Tutorial code and updates the old samples/WhoIsIAm.py to match it.
This commit is contained in:
Richard Littauer 2020-04-28 18:13:47 -04:00
parent d832f84319
commit 9ec89dbf11
4 changed files with 168 additions and 370 deletions

View File

@ -3,25 +3,25 @@
Getting Started 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 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. We will cover installing the library, downloading and another device. We will cover installing the library, downloading and
configuring the samples applications. configuring the samples applications.
Basic Assumptions Basic Assumptions
----------------- -----------------
I will assume 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 BACnet standard. I will given you a test device and purchased a copy of the BACnet standard. I will
need... need...
- a development workstation running some flavor of Linux or Windows, complete with - a development workstation running some flavor of Linux or Windows, complete with
the latest version of Python (2.7 or >3.4) 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 Ethernet hub into which you can plug both your workstation and your - a small Ethernet hub into which you can plug both your workstation and your
mysterious BACnet device, so you won't be distracted by lots of other network traffic. mysterious BACnet device, so you won't be distracted by lots of other network traffic.
- a BACnetIP/BACnet-MSTP Router if your mysterious device is an MSTP device (BACpypes is - a BACnetIP/BACnet-MSTP Router if your mysterious device is an MSTP device (BACpypes is
@ -33,7 +33,7 @@ need...
`Anaconda <https://www.continuum.io/downloads>`_ or Enthought `Anaconda <https://www.continuum.io/downloads>`_ or Enthought
`Canopy <https://www.enthought.com/products/canopy/>`_. `Canopy <https://www.enthought.com/products/canopy/>`_.
Before getting this test environment set up and while 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
@ -42,7 +42,7 @@ or::
$ sudo pip install bacpypes $ sudo pip install bacpypes
And while you are at it, get a copy of the BACpypes project from GitHub. It And while you are at it, get a copy of the BACpypes project from GitHub. It
contains the library source code, sample code, and this documentation. Install contains the library source code, sample code, and this documentation. Install
the `Git <https://en.wikipedia.org/wiki/Git>`_ software from the `Git <https://en.wikipedia.org/wiki/Git>`_ software from
`here <https://git-scm.com/downloads>`_, then make a local copy of the `here <https://git-scm.com/downloads>`_, then make a local copy of the
@ -50,11 +50,11 @@ repository by cloning it::
$ git clone https://github.com/JoelBender/bacpypes.git $ 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
or if you use Windows, `download it here <https://www.wireshark.org/download.html>`_. or if you use Windows, `download it here <https://www.wireshark.org/download.html>`_.
.. caution:: .. caution::
@ -67,15 +67,15 @@ or if you use Windows, `download it here <https://www.wireshark.org/download.htm
Configuring the Workstation Configuring the Workstation
--------------------------- ---------------------------
The mystery BACnet device 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 my set of assumptions 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, let's 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 your workstation address and use the same subnet mask 255.255.0.0. for your workstation address and use the same subnet mask 255.255.0.0.
If working with MSTP devices, base your workstation address on the address If working with MSTP devices, base your workstation address on the address
@ -84,45 +84,45 @@ from scratch.
*Network Number* *Network Number*
If working with a BACnetIP router and an MSTP device, you will need to know If working with a BACnetIP router and an MSTP device, you will need to know
the network number configured inside the router. Every BACnet network **must** the network number configured inside the router. Every BACnet network **must**
have a unique numeric identifier. You will often see the magical number **2000** have a unique numeric identifier. You will often see the magical number **2000**
but you can choose anything between 1 to 0xFFFE. but you can choose anything between 1 to 0xFFFE.
*Device Identifier* *Device Identifier*
Every BACnet device on a BACnet network **must** have a unique numeric Every BACnet device on a BACnet network **must** have a unique numeric
identifier. This number is a 22-bit unsigned non-zero value. identifier. This number is a 22-bit unsigned non-zero value.
It is critical this identifier be unique. Most large customers will have It is critical this identifier be unique. Most large customers will have
someone or some group responsible for maintaining device identifiers across the someone or some group responsible for maintaining device identifiers across the
site. Keep track of the device identifier for the test device. Let's 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** assume that this device is **1000** and you are going to pick **1001**
for your workstation. for your workstation.
*Device Name* *Device Name*
Every BACnet device on a BACnet network should also have 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 in the sample workstation. My collegues and I use star names, so in the sample
configuration files you will see the name "Betelgeuse". An actual customer's configuration files you will see the name "Betelgeuse". An actual customer's
site will use a more formal (but less fun) naming convention. site will use a more formal (but less fun) naming convention.
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 need to know the maximum For devices to exchange messages they need to know the maximum
size message the other 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 there are times when larger messages are message, but there are times when larger messages are
convenient and more efficient. Segmentation allows larger convenient and more efficient. Segmentation allows larger
messages to be broken up into segments and spliced back together. messages to be broken up into segments and spliced back together.
It is not unusual for "low power" field devices 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
@ -132,24 +132,24 @@ 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 portion of your workstation. Change into the configure the BACnet portion of your workstation. Change into the
bacpypes directory that you checked out earlier, make a copy bacpypes 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 $ cd bacpypes
$ cp BACpypes~.ini BACpypes.ini $ cp BACpypes~.ini BACpypes.ini
.. tip:: .. tip::
The sample applications are going to look for this file. 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 You can direct the applications to use other INI files on the command line, so it is
simple to keep multiple configurations. simple to keep multiple configurations.
At some point you will probably running both "client" and "server" At some point you will probably running both "client" and "server"
applications on your workstation, so you will want separate applications on your workstation, so you will want separate
configuration files for them. Keep in mind that BACnet devices configuration files for them. Keep in mind that BACnet devices
communicate as peers, so it is not unusual for an application to communicate as peers, so it is not unusual for an application to
act as both a client and a server at the same time. act as both a client and a server at the same time.
A typical BACpypes.ini file contains:: A typical BACpypes.ini file contains::
@ -170,41 +170,41 @@ A typical BACpypes.ini file contains::
UDP Communications Issues UDP Communications Issues
------------------------- -------------------------
BACnet devices communicate using UDP rather than TCP. This is so BACnet devices communicate using UDP rather than TCP. This is so
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 because they support multiple protocols, including many of them do because 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 device's IP address from one specific IP address (and port) to another device's IP address
(and port); and *broadcast* messages which are sent by one device (and port); and *broadcast* messages which are sent by one device
and received and processed by all other devices that are listening and received and processed by all other devices that are listening
on that port. BACnet uses both types of messages and your workstation 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
address in CIDR notation and can be followed by a port number. For address in CIDR notation and can be followed by a port number. For
example, **192.168.0.11/16** specifies both the IP address and the example, **192.168.0.11/16** specifies both the IP address and the
number of bits in the network portion, which in turn implies a 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 address with all 1's in the host portion. In this example, portion of the address with all 1's in the host portion. In this example,
the default port 47808 (0xBAC0) is used but you could provide and different the default port 47808 (0xBAC0) is used but you could provide and different
one, **192.168.0.11:47809/16**. one, **192.168.0.11:47809/16**.
To receive both unicast and broadcast addresses, BACpypes To receive both unicast and broadcast addresses, BACpypes
opens 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.
.. note:: .. note::
The BACnet protocol has been assigned port 47808 (hex 0xBAC0) by The BACnet protocol has been assigned port 47808 (hex 0xBAC0) by
by the `Internet Assigned Numbers Authority <https://www.iana.org/>`_, and sequentially by the `Internet Assigned Numbers Authority <https://www.iana.org/>`_, and sequentially
higher numbers are used in many applications (i.e. 47809, 47810,...). 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 There are some BACnet routing and networking issues related to using these higher unoffical
ports, but that is a topic for another tutorial. ports, but that is a topic for another tutorial.
@ -213,38 +213,38 @@ Starting An Application
----------------------- -----------------------
The simplest BACpypes sample application is the **WhoIsIAm.py** The simplest BACpypes sample application is the **WhoIsIAm.py**
application. It sends 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
most applications use these identifiers in their configuration most applications use these identifiers in their configuration
to know who their peers are. Once these identifiers are given to know who their peers are. Once these identifiers are given
to a device they typically do not change, even as the network to a device they typically do not change, even as the network
topology changes. topology changes.
BACnet devices use the Who-Is request to translate device BACnet devices use the Who-Is request to translate device
identifiers into network addresses. This is very similar to identifiers into network addresses. This is very similar to
a decentralized DNS service, but the names are unsigned 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 are unicast thereafter. 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::
udp and port 47808 udp and port 47808
You might start seeing BACnet traffic from your test device, You might start seeing BACnet traffic from your test device,
and if you wait to power it on after starting your capture and if you wait to power it on after starting your capture
you should see at least a broadcast I-Am message. By looking you should see at least a broadcast I-Am message. By looking
in the I-Am packet decoding you will see some of its in the I-Am packet decoding you will see some of its
configuration parameters that should match what you expected configuration parameters that should match what you expected
them to be. them to be.
Now start the simplest tutorial application:: Now start the simplest tutorial application::
$ python samples/Tutorial/WhoIsIAm.py $ python samples/WhoIsIAm.py
.. note:: .. note::
@ -272,7 +272,7 @@ BACnet communications traffic. Generate the basic I-Am message::
> iam > iam
You should see Wireshark capture your I-Am message containing your configuration You should see Wireshark capture your I-Am message containing your configuration
parameters. This is a "global broadcast" message. Your test device will see parameters. This is a "global broadcast" message. Your test device will see
it but since your test device probably isn't looking for you, it will not it but since your test device probably isn't looking for you, it will not
respond to the message. respond to the message.
@ -281,27 +281,27 @@ respond to the message.
Binding to the Test Device Binding to the Test Device
-------------------------- --------------------------
Next we want to confirm that your workstation can receive the Next we want to confirm that your workstation can receive the
messages the test device sends out. We do this by generating a messages the test device sends out. We do this by generating a
generic Who-Is request. The request will be "unconstrained", meaning generic Who-Is request. The request will be "unconstrained", meaning
every device that hears the message will respond with their corresponding every device that hears the message will respond with their corresponding
I-Am messages. I-Am messages.
.. caution:: .. caution::
Generating **unconstrained** Who-Is requests on a large network will create 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 a LOT of traffic, which can lead to network problems caused by the resulting
flood of messages. flood of messages.
To generate the Who-Is request:: To generate the Who-Is request::
> whois > whois
You should see the Who-Is request captured in Wireshark along with the I-Am You should see the Who-Is request captured in Wireshark along with the I-Am
response from your test device, and then the details of the response displayed response from your test device, and then the details of the response displayed
on the workstation console.:: on the workstation console.::
> whois > whois
> pduSource = <RemoteStation 50009:9> > pduSource = <RemoteStation 50009:9>
iAmDeviceIdentifier = ('device', 1000) iAmDeviceIdentifier = ('device', 1000)
maxAPDULengthAccepted = 480 maxAPDULengthAccepted = 480
@ -309,13 +309,13 @@ on the workstation console.::
vendorID = 8 vendorID = 8
There are a few different forms of the *whois* command supported by this There are a few different forms of the *whois* command supported by this
simple application. You can see these with the help command:: 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 a BACnet device address, and then optionally followed by a followed by a BACnet device address, and then optionally followed by a
low (address) limit and high (address) limit. The most common use of the Who-Is 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
@ -329,9 +329,9 @@ 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 Who-Is** 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
@ -349,9 +349,9 @@ but that is a subject of an other tutorial.
What's Next What's Next
----------- -----------
The next tutorial describes 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
(i.e. those that prompt for commands) use the same basic (i.e. those that prompt for commands) use the same basic
commands and work the same way. commands and work the same way.

View File

@ -3,9 +3,9 @@
Running BACpypes Applications 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 use different configurations. There may be additional options and and use different configurations. There may be additional options and
command parameters than just the ones described in this section. command parameters than just the ones described in this section.
Getting Help Getting Help
@ -14,7 +14,7 @@ Getting Help
Whatever the command line parameters and additional options might be for Whatever the command line parameters and additional options might be for
an application, you can start with help:: an application, you can start with help::
$ python Tutorial/WhoIsIAm.py --help $ python samples/WhoIsIAm.py --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
@ -33,22 +33,22 @@ an application, you can start with help::
Listing Debugging Loggers Listing Debugging Loggers
------------------------- -------------------------
The BACpypes library and sample applications make extensive use of the The BACpypes library and sample applications make extensive use of the
built-in *logging* module in Python. Every module in the library, along built-in *logging* module in Python. Every module in the library, along
with every class and exported function, has a logging object associated with every class and exported function, has a logging object associated
with it. By attaching a log handler to a logger, the log handler is given with it. By attaching a log handler to a logger, the log handler is given
a chance to output the progress of the application. a chance to output the progress of the application.
Because BACpypes modules are deeply interconnected, dumping a complete list Because BACpypes modules are deeply interconnected, dumping a complete list
of all of the logger names is a long list. Start out focusing on the of all of the logger names is a long list. Start out focusing on the
components of the WhoIsIAm.py application:: components of the WhoIsIAm.py application::
$ python Tutorial/WhoIsIAm.py --buggers | grep __main__ $ python samples/WhoIsIAm.py --buggers | grep __main__
__main__ __main__
__main__.WhoIsIAmApplication __main__.WhoIsIAmApplication
__main__.WhoIsIAmConsoleCmd __main__.WhoIsIAmConsoleCmd
In this sample, the entire application is called __main__ and it defines In this sample, the entire application is called __main__ and it defines
two classes. two classes.
Debugging a Module Debugging a Module
@ -61,12 +61,12 @@ Telling the application to debug a module is simple::
DEBUG:__main__: - args: Namespace(buggers=False, debug=['__main__'], ini=<class 'bacpypes.consolelogging.ini'>) DEBUG:__main__: - args: Namespace(buggers=False, debug=['__main__'], ini=<class 'bacpypes.consolelogging.ini'>)
DEBUG:__main__.WhoIsIAmApplication:__init__ (<bacpypes.app.LocalDeviceObject object at 0xb6dd98cc>, '128.253.109.40/24:47808') DEBUG:__main__.WhoIsIAmApplication:__init__ (<bacpypes.app.LocalDeviceObject object at 0xb6dd98cc>, '128.253.109.40/24:47808')
DEBUG:__main__:running DEBUG:__main__:running
> >
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 the application initializing, application. From the output above you can see the application initializing,
setting the args variable, creating an instance of the WhoIsIAmApplication class setting the args variable, creating an instance of the WhoIsIAmApplication class
(with some parameters), and then declaring itself - running. (with some parameters), and then declaring itself - running.
@ -76,52 +76,52 @@ Debugging a Class
Debugging all of the classes and functions can generate a lot of output, Debugging all of the classes and functions can generate a lot of output,
so it is useful to focus on a specific function or class:: so it is useful to focus on a specific function or class::
$ python Tutorial/WhoIsIAm.py --debug __main__.WhoIsIAmApplication $ python samples/WhoIsIAm.py --debug __main__.WhoIsIAmApplication
DEBUG:__main__.WhoIsIAmApplication:__init__ (<bacpypes.app.LocalDeviceObject object at 0x9bca8ac>, '128.253.109.40/24:47808') DEBUG:__main__.WhoIsIAmApplication:__init__ (<bacpypes.app.LocalDeviceObject object at 0x9bca8ac>, '128.253.109.40/24:47808')
> >
The same method is used to debug the activity of a BACpypes module, for The same method is used to debug the activity of a BACpypes module, for
example, there is a class called UDPActor in the UDP module:: example, there is a class called UDPActor in the UDP module::
$ python Tutorial/WhoIsIAm.py --ini BAC0.ini --debug bacpypes.udp.UDPActor $ python samples/WhoIsIAm.py --ini BAC0.ini --debug bacpypes.udp.UDPActor
> DEBUG:bacpypes.udp.UDPActor:__init__ <bacpypes.udp.UDPDirector 128.253.109.255:47808 at 0xb6d40d6c> ('128.253.109.254', 47808) > DEBUG:bacpypes.udp.UDPActor:__init__ <bacpypes.udp.UDPDirector 128.253.109.255:47808 at 0xb6d40d6c> ('128.253.109.254', 47808)
DEBUG:bacpypes.udp.UDPActor:response <bacpypes.comm.PDU object at 0xb6d433cc> DEBUG:bacpypes.udp.UDPActor:response <bacpypes.comm.PDU object at 0xb6d433cc>
<bacpypes.comm.PDU object at 0xb6d433cc> <bacpypes.comm.PDU object at 0xb6d433cc>
pduSource = ('128.253.109.254', 47808) pduSource = ('128.253.109.254', 47808)
pduData = x'81.04.00.37.0A.10.6D.45.BA.C0.01.28.FF.FF.00.00.B6.01.05.FD...' pduData = x'81.04.00.37.0A.10.6D.45.BA.C0.01.28.FF.FF.00.00.B6.01.05.FD...'
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, the protocol data is printed as a hex contents of the PDU. Notice, the protocol data is printed as a hex
encoded string (and restricted to just the first 20 bytes of the message). encoded string (and restricted to just the first 20 bytes of the message).
You can debug a function just as easily. Specify as many different You can debug a function just as easily. Specify as many different
combinations of logger names as necessary. Note, 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.
Sending Debug Log to a file Sending Debug Log to a file
---------------------------- ----------------------------
The current --debug command line option takes a list of named debugging access The current --debug command line option takes a list of named debugging access
points and attaches a StreamHandler which sends the output to sys.stderr. points and attaches a StreamHandler which sends the output to sys.stderr.
There is a way to send the debugging output to a There is a way to send the debugging output to a
RotatingFileHandler by providing a file name, and optionally maxBytes and RotatingFileHandler by providing a file name, and optionally maxBytes and
backupCount. For example, this invocation sends the main application debugging backupCount. For example, this invocation sends the main application debugging
to standard error and the debugging output of the bacpypes.udp module to the to standard error and the debugging output of the bacpypes.udp module to the
traffic.txt file:: traffic.txt file::
$ python Tutorial/WhoIsIAm.py --debug __main__ bacpypes.udp:traffic.txt $ python samples/WhoIsIAm.py --debug __main__ bacpypes.udp:traffic.txt
By default the `maxBytes` is zero so there is no rotating file, but it can be By default the `maxBytes` is zero so there is no rotating file, but it can be
provided, for example this limits the file size to 1MB:: provided, for example this limits the file size to 1MB::
$ python Tutorial/WhoIsIAm.py --debug __main__ bacpypes.udp:traffic.txt:1048576 $ python samples/WhoIsIAm.py --debug __main__ bacpypes.udp:traffic.txt:1048576
If `maxBytes` is provided, then by default the `backupCount` is 10, but it can also If `maxBytes` is provided, then by default the `backupCount` is 10, but it can also
be specified, so this limits the output to one hundred files:: be specified, so this limits the output to one hundred files::
$ python Tutorial/WhoIsIAm.py --debug __main__ bacpypes.udp:traffic.txt:1048576:100 $ python samples/WhoIsIAm.py --debug __main__ bacpypes.udp:traffic.txt:1048576:100
.. caution:: .. caution::
@ -136,18 +136,18 @@ The definition of debug::
Changing INI Files Changing INI Files
------------------ ------------------
It is not unusual to have a variety of different INI files specifying It is not unusual to have a variety of different INI files specifying
different port numbers or other BACnet communications paramters. different port numbers or other BACnet communications paramters.
Rather than swapping INI files, you can simply provide the INI file on the Rather than swapping INI files, you can simply provide the INI file on the
command line, overriding the default BACpypes.ini file. For example, I command line, overriding the default BACpypes.ini file. For example, I
have an INI file for port 47808:: have an INI file for port 47808::
$ python Tutorial/WhoIsIAm.py --ini BAC0.ini $ python samples/WhoIsIAm.py --ini BAC0.ini
And another one for port 47809:: And another one for port 47809::
$ python Tutorial/WhoIsIAm.py --ini BAC1.ini $ python samples/WhoIsIAm.py --ini BAC1.ini
And I switch back and forth between them. And I switch back and forth between them.

View File

@ -1,181 +0,0 @@
#!/usr/bin/env python
"""
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 corresponding I-Am
for incoming traffic and prints out the contents.
"""
import sys
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.pdu import Address, GlobalBroadcast
from bacpypes.apdu import WhoIsRequest, IAmRequest
from bacpypes.errors import DecodingError
from bacpypes.app import BIPSimpleApplication
from bacpypes.local.device import LocalDeviceObject
# some debugging
_debug = 1
_log = ModuleLogger(globals())
# globals
this_device = None
this_application = None
#
# WhoIsIAmApplication
#
@bacpypes_debugging
class WhoIsIAmApplication(BIPSimpleApplication):
def __init__(self, *args):
if _debug: WhoIsIAmApplication._debug("__init__ %r", args)
BIPSimpleApplication.__init__(self, *args)
# keep track of requests to line up responses
self._request = None
def request(self, apdu):
if _debug: WhoIsIAmApplication._debug("request %r", apdu)
# save a copy of the request
self._request = apdu
# forward it along
BIPSimpleApplication.request(self, apdu)
def confirmation(self, apdu):
if _debug: WhoIsIAmApplication._debug("confirmation %r", apdu)
# forward it along
BIPSimpleApplication.confirmation(self, apdu)
def indication(self, apdu):
if _debug: WhoIsIAmApplication._debug("indication %r", apdu)
if (isinstance(self._request, WhoIsRequest)) and (isinstance(apdu, IAmRequest)):
device_type, device_instance = apdu.iAmDeviceIdentifier
if device_type != 'device':
raise DecodingError("invalid object type")
if (self._request.deviceInstanceRangeLowLimit is not None) and \
(device_instance < self._request.deviceInstanceRangeLowLimit):
pass
elif (self._request.deviceInstanceRangeHighLimit is not None) and \
(device_instance > self._request.deviceInstanceRangeHighLimit):
pass
else:
# print out the contents
sys.stdout.write('pduSource = ' + repr(apdu.pduSource) + '\n')
sys.stdout.write('iAmDeviceIdentifier = ' + str(apdu.iAmDeviceIdentifier) + '\n')
sys.stdout.write('maxAPDULengthAccepted = ' + str(apdu.maxAPDULengthAccepted) + '\n')
sys.stdout.write('segmentationSupported = ' + str(apdu.segmentationSupported) + '\n')
sys.stdout.write('vendorID = ' + str(apdu.vendorID) + '\n')
sys.stdout.flush()
# forward it along
BIPSimpleApplication.indication(self, apdu)
#
# WhoIsIAmConsoleCmd
#
@bacpypes_debugging
class WhoIsIAmConsoleCmd(ConsoleCmd):
def do_whois(self, args):
"""whois [ <addr> ] [ <lolimit> <hilimit> ]"""
args = args.split()
if _debug: WhoIsIAmConsoleCmd._debug("do_whois %r", args)
try:
# gather the parameters
if (len(args) == 1) or (len(args) == 3):
addr = Address(args[0])
del args[0]
else:
addr = GlobalBroadcast()
if len(args) == 2:
lolimit = int(args[0])
hilimit = int(args[1])
else:
lolimit = hilimit = None
# code lives in the device service
this_application.who_is(lolimit, hilimit, addr)
except Exception as error:
WhoIsIAmConsoleCmd._exception("exception: %r", error)
def do_iam(self, args):
"""iam"""
args = args.split()
if _debug: WhoIsIAmConsoleCmd._debug("do_iam %r", args)
# code lives in the device service
this_application.i_am()
def do_rtn(self, args):
"""rtn <addr> <net> ... """
args = args.split()
if _debug: WhoIsIAmConsoleCmd._debug("do_rtn %r", args)
# provide the address and a list of network numbers
router_address = Address(args[0])
network_list = [int(arg) for arg in args[1:]]
# pass along to the service access point
this_application.nsap.update_router_references(None, router_address, network_list)
#
# __main__
#
def main():
global this_device
global this_application
# 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 simple application
this_application = WhoIsIAmApplication(this_device, args.ini.address)
# make a console
this_console = WhoIsIAmConsoleCmd()
if _debug: _log.debug(" - this_console: %r", this_console)
# enable sleeping will help with threads
enable_sleeping()
_log.debug("running")
run()
_log.debug("fini")
if __name__ == "__main__":
main()

83
samples/WhoIsIAm.py Executable file → Normal file
View File

@ -2,7 +2,7 @@
""" """
This application presents a 'console' prompt to the user asking for Who-Is and I-Am This application presents a 'console' prompt to the user asking for Who-Is and I-Am
commands which create the related APDUs, then lines up the coorresponding I-Am commands which create the related APDUs, then lines up the corresponding I-Am
for incoming traffic and prints out the contents. for incoming traffic and prints out the contents.
""" """
@ -12,8 +12,7 @@ from bacpypes.debugging import bacpypes_debugging, ModuleLogger
from bacpypes.consolelogging import ConfigArgumentParser from bacpypes.consolelogging import ConfigArgumentParser
from bacpypes.consolecmd import ConsoleCmd from bacpypes.consolecmd import ConsoleCmd
from bacpypes.core import run, deferred, enable_sleeping from bacpypes.core import run, enable_sleeping
from bacpypes.iocb import IOCB
from bacpypes.pdu import Address, GlobalBroadcast from bacpypes.pdu import Address, GlobalBroadcast
from bacpypes.apdu import WhoIsRequest, IAmRequest from bacpypes.apdu import WhoIsRequest, IAmRequest
@ -23,7 +22,7 @@ from bacpypes.app import BIPSimpleApplication
from bacpypes.local.device import LocalDeviceObject from bacpypes.local.device import LocalDeviceObject
# some debugging # some debugging
_debug = 0 _debug = 1
_log = ModuleLogger(globals()) _log = ModuleLogger(globals())
# globals # globals
@ -68,10 +67,10 @@ class WhoIsIAmApplication(BIPSimpleApplication):
raise DecodingError("invalid object type") raise DecodingError("invalid object type")
if (self._request.deviceInstanceRangeLowLimit is not None) and \ if (self._request.deviceInstanceRangeLowLimit is not None) and \
(device_instance < self._request.deviceInstanceRangeLowLimit): (device_instance < self._request.deviceInstanceRangeLowLimit):
pass pass
elif (self._request.deviceInstanceRangeHighLimit is not None) and \ elif (self._request.deviceInstanceRangeHighLimit is not None) and \
(device_instance > self._request.deviceInstanceRangeHighLimit): (device_instance > self._request.deviceInstanceRangeHighLimit):
pass pass
else: else:
# print out the contents # print out the contents
@ -85,6 +84,7 @@ class WhoIsIAmApplication(BIPSimpleApplication):
# forward it along # forward it along
BIPSimpleApplication.indication(self, apdu) BIPSimpleApplication.indication(self, apdu)
# #
# WhoIsIAmConsoleCmd # WhoIsIAmConsoleCmd
# #
@ -93,60 +93,37 @@ class WhoIsIAmApplication(BIPSimpleApplication):
class WhoIsIAmConsoleCmd(ConsoleCmd): class WhoIsIAmConsoleCmd(ConsoleCmd):
def do_whois(self, args): def do_whois(self, args):
"""whois [ <addr>] [ <lolimit> <hilimit> ]""" """whois [ <addr> ] [ <lolimit> <hilimit> ]"""
args = args.split() args = args.split()
if _debug: WhoIsIAmConsoleCmd._debug("do_whois %r", args) if _debug: WhoIsIAmConsoleCmd._debug("do_whois %r", args)
try: try:
# build a request # gather the parameters
request = WhoIsRequest()
if (len(args) == 1) or (len(args) == 3): if (len(args) == 1) or (len(args) == 3):
request.pduDestination = Address(args[0]) addr = Address(args[0])
del args[0] del args[0]
else: else:
request.pduDestination = GlobalBroadcast() addr = GlobalBroadcast()
if len(args) == 2: if len(args) == 2:
request.deviceInstanceRangeLowLimit = int(args[0]) lolimit = int(args[0])
request.deviceInstanceRangeHighLimit = int(args[1]) hilimit = int(args[1])
if _debug: WhoIsIAmConsoleCmd._debug(" - request: %r", request) else:
lolimit = hilimit = None
# make an IOCB # code lives in the device service
iocb = IOCB(request) this_application.who_is(lolimit, hilimit, addr)
if _debug: WhoIsIAmConsoleCmd._debug(" - iocb: %r", iocb)
# give it to the application except Exception as error:
this_application.request_io(iocb) WhoIsIAmConsoleCmd._exception("exception: %r", error)
except Exception as err:
WhoIsIAmConsoleCmd._exception("exception: %r", err)
def do_iam(self, args): def do_iam(self, args):
"""iam""" """iam"""
args = args.split() args = args.split()
if _debug: WhoIsIAmConsoleCmd._debug("do_iam %r", args) if _debug: WhoIsIAmConsoleCmd._debug("do_iam %r", args)
try: # code lives in the device service
# build a request this_application.i_am()
request = IAmRequest()
request.pduDestination = GlobalBroadcast()
# set the parameters from the device object
request.iAmDeviceIdentifier = this_device.objectIdentifier
request.maxAPDULengthAccepted = this_device.maxApduLengthAccepted
request.segmentationSupported = this_device.segmentationSupported
request.vendorID = this_device.vendorIdentifier
if _debug: WhoIsIAmConsoleCmd._debug(" - request: %r", request)
# make an IOCB
iocb = IOCB(request)
if _debug: WhoIsIAmConsoleCmd._debug(" - iocb: %r", iocb)
# give it to the application
this_application.request_io(iocb)
except Exception as err:
WhoIsIAmConsoleCmd._exception("exception: %r", err)
def do_rtn(self, args): def do_rtn(self, args):
"""rtn <addr> <net> ... """ """rtn <addr> <net> ... """
@ -162,11 +139,12 @@ class WhoIsIAmConsoleCmd(ConsoleCmd):
# #
# main # __main__
# #
def main(): def main():
global this_device, this_application global this_device
global this_application
# parse the command line arguments # parse the command line arguments
args = ConfigArgumentParser(description=__doc__).parse_args() args = ConfigArgumentParser(description=__doc__).parse_args()
@ -175,14 +153,16 @@ def main():
if _debug: _log.debug(" - args: %r", args) if _debug: _log.debug(" - args: %r", args)
# make a device object # make a device object
this_device = LocalDeviceObject(ini=args.ini) this_device = LocalDeviceObject(
if _debug: _log.debug(" - this_device: %r", this_device) 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 simple application # make a simple application
this_application = WhoIsIAmApplication( this_application = WhoIsIAmApplication(this_device, args.ini.address)
this_device, args.ini.address,
)
if _debug: _log.debug(" - this_application: %r", this_application)
# make a console # make a console
this_console = WhoIsIAmConsoleCmd() this_console = WhoIsIAmConsoleCmd()
@ -197,6 +177,5 @@ def main():
_log.debug("fini") _log.debug("fini")
if __name__ == "__main__": if __name__ == "__main__":
main() main()