1
0
mirror of https://github.com/JoelBender/bacpypes synced 2025-09-28 22:15:23 +08:00

Following along the documentation was hard because samples and tutorial files were lost among all samples. I created two folders : Tutorial and HandsOnLab. Tutorial is the folder with the first file someone will look at. The documentation invite the reader to start $python Tutorial/WhoIsIAm.py for example.

When the tutorial is over, the reader will continue with HandsOnLab which contains Samples1,2,3,4

Samples 5 and 14 were removed from the official table of content as they are not completed yet.

All code is tested, files were moved from samples to their folder.

Signed-off-by: Christian Tremblay <christian.tremblay@servisys.com>
This commit is contained in:
Christian Tremblay 2016-11-17 23:46:52 -05:00
parent a47fb85e3c
commit ebe2ba5abe
18 changed files with 262 additions and 30 deletions

View File

@ -234,7 +234,14 @@ them to be.
Now start the application::
$ python WhoIsIAm.py
$ python Tutorial/WhoIsIAm.py
.. note::
The samples folder contains a Tutorial folder holding all the samples
that you will need too follow along this tutorial.
Later, the folder `HandsOnLabs` will be used as it contains the samples
that are fully explained in this document (see table of content)
You will be presented with a prompt (>), and you can get help::

View File

@ -14,7 +14,7 @@ Getting Help
Whatever the command line parameters and additional options might be for
an application, you can start with help::
$ python WhoIsIAm.py --help
$ python Tutorial/WhoIsIAm.py --help
usage: WhoIsIAm.py [-h] [--buggers] [--debug [DEBUG [DEBUG ...]]] [--color] [--ini INI]
This application presents a 'console' prompt to the user asking for Who-Is and
@ -24,7 +24,8 @@ an application, you can start with help::
optional arguments:
-h, --help show this help message and exit
--buggers list the debugging logger names
--debug [DEBUG [DEBUG ...]]
--debug [DEBUG [ DEBUG ... ]]
DEBUG ::= debugger [ : fileName [ : maxBytes [ : backupCount ]]]
add console log handler to each debugging logger
--color use ANSI CSI color codes
--ini INI device object configuration file
@ -42,7 +43,7 @@ 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
components of the WhoIsIAm.py application::
$ python WhoIsIAm.py --buggers | grep __main__
$ python Tutorial/WhoIsIAm.py --buggers | grep __main__
__main__
__main__.WhoIsIAmApplication
__main__.WhoIsIAmConsoleCmd
@ -75,14 +76,14 @@ Debugging a Class
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::
$ python WhoIsIAm.py --debug __main__.WhoIsIAmApplication
$ python Tutorial/WhoIsIAm.py --debug __main__.WhoIsIAmApplication
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
example, there is a class called UDPActor in the UDP module::
$ python WhoIsIAm.py --ini BAC0.ini --debug bacpypes.udp.UDPActor
$ python Tutorial/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:response <bacpypes.comm.PDU object at 0xb6d433cc>
<bacpypes.comm.PDU object at 0xb6d433cc>
@ -99,6 +100,38 @@ You can debug a function just as easily. Specify as many different
combinations of logger names as necessary. Note, you cannot debug a
specific function within a class.
Sending Debug Log to a file
----------------------------
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.
There is a way to send the debugging output to a
RotatingFileHandler by providing a file name, and optionally maxBytes and
backupCount. For example, this invocation sends the main application debugging
to standard error and the debugging output of the bacpypes.udp module to the
traffic.txt file::
$ python Tutorial/WhoIsIAm.py --debug __main__ bacpypes.udp:traffic.txt
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::
$ python Tutorial/WhoIsIAm.py --debug __main__ bacpypes.udp:traffic.txt:1048576
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::
$ python Tutorial/WhoIsIAm.py --debug __main__ bacpypes.udp:traffic.txt:1048576:100
.. caution::
The traffice.txt file will be saved in the local directory (pwd)
The definition of debug::
positional arguments:
--debug [DEBUG [ DEBUG ... ]]
DEBUG ::= debugger [ : fileName [ : maxBytes [ : backupCount ]]]
Changing INI Files
------------------
@ -110,11 +143,11 @@ 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
have an INI file for port 47808::
$ python WhoIsIAm.py --ini BAC0.ini
$ python Tutorial/WhoIsIAm.py --ini BAC0.ini
And another one for port 47809::
$ python WhoIsIAm.py --ini BAC1.ini
$ python Tutorial/WhoIsIAm.py --ini BAC1.ini
And I switch back and forth between them.

View File

@ -82,22 +82,24 @@ guidelines of the types of changes you might need to make.
migration/migration001.rst
Samples
-------
Hands-on Lab
-------------
BACpypes comes with a variety of sample applications. Some are a framework
for building larger applications. Some are standalone analysis tools
that don't require a connection to a network.
that don't require a connection to a network.
The first samples you should have a look too are located inside the
`samples/HandsOnLab` folder. Those samples are fully explained in the
documentation so you can follow along and get your head around BACpypes.
Other less documented samples are available directly in the `samples`
folder.
.. toctree::
:maxdepth: 1
samples/sample001.rst
samples/sample002.rst
samples/sample003.rst
samples/sample004.rst
samples/sample005.rst
samples/sample014.rst
samples/sample_index.rst
Glossary

View File

@ -1,4 +1,3 @@
Sample 2 - Who-Is/I-Am Counter
==============================
@ -9,6 +8,14 @@ with the regular processing.
The description of this sample will be about the parts that are different from
sample 1.
.. note::
New in 0.15 ! As you've seen reading :ref:`Capabilities`, the new API allows
mixing functionnality to application more easily. In fact, by default,
inheriting from :class:`app.BISimpleApplication` includes
:class:`service.device.WhoIsIAmServices` and
:class:`service.device.ReadWritePropertyServices` capabilities.
Counters
--------
@ -32,7 +39,9 @@ Processing Service Requests
When an instance of the :class:`app.Application` receives a request it attempts
to look up a function based on the message. So when a WhoIsRequest APDU is
received, there should be a do_WhoIsRequest function.
received, there should be a do_WhoIsRequest function. In fact,
:class:`services.device.WhoIsIAmServices` provides this function. For the sake
of this sample, we will override it so we can count requests.
The beginning is going to be standard boiler plate function header::
@ -57,7 +66,7 @@ processing::
# pass back to the default implementation
BIPSimpleApplication.do_WhoIsRequest(self, apdu)
The do_IAmRequest function is similer::
The do_IAmRequest function is similar::
def do_IAmRequest(self, apdu):
"""Given an I-Am request, cache it."""
@ -85,10 +94,10 @@ By building the key out of elements in a useful order, it is simple enough
to sort the dictionary items and print them out, and being able to unpack
the key in the for loop is a nice feature of Python::
print "----- Who Is -----"
print("----- Who Is -----")
for (src, lowlim, hilim), count in sorted(who_is_counter.items()):
print "%-20s %8s %8s %4d" % (src, lowlim, hilim, count)
print
print("%-20s %8s %8s %4d" % (src, lowlim, hilim, count))
print("")
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
@ -122,7 +131,7 @@ Let it run for a minute, then Press <ctrl-C> to end it. It will output its resu
deviceInstanceRangeHighLimit = 59L
pduData = x''
[clipped...]
^CDEBUG:__main__:fini
DEBUG:__main__:fini
----- Who Is -----
10001:0x0040ae007e01 1 1 1
10001:0x0040ae007e01 9830 9830 1

View File

@ -85,7 +85,7 @@ Running the Application
Allow the application to run for a few minutes. Then end it so it will output its results.::
^CDEBUG:__main__:fini
DEBUG:__main__:fini
----- Who Has -----
----- I Have -----

View File

@ -8,8 +8,11 @@ separate and overlapping functionality. The original design was motivated
by a component architecture where collections of components that needed to be
mixed together were specified outside the application in a database.
The sample applications in this section are available in the unit test. Start
out importing the classes in the module::
The sample applications in this section are available in tutorial folder.
Note that you can also find them in the unit test folder as they are part of the
test suites.
Start out importing the classes in the module::
>>> from bacpypes.capability import Capability, Collector

View File

@ -5,7 +5,7 @@ Clients and Servers
While exploring a library like BACpypes, take full advantage of Python being
an interpreted language with an interactive prompt! The code for this tutorial
is also available in the *tutorial* subdirectory of the repository.
is also available in the *Tutorial* subdirectory of the repository.
This tutorial will be using :class:`comm.Client`, :class:`comm.Server` classes,
and the :func:`comm.bind` function, so start out by importing them::

View File

@ -53,7 +53,7 @@ Basic Commands
All of the commands supported are listed in the :mod:`consolecmd` documentation.
The simplest way to learn the commands is to try them::
$ python SampleConsoleCmd.py
$ python Tutorial/SampleConsoleCmd.py
> hi
*** Unknown syntax: hi
@ -159,7 +159,7 @@ And to verify, dump the cache::
And when the sample application is run, note the new commands
show up in the help list::
$ python SampleConsoleCmd.py
$ python Tutorial/SampleConsoleCmd.py
> help
Documented commands (type help <topic>):

View File

@ -0,0 +1,119 @@
#!/usr/bin/env python
"""
The capabilty module is used to mix together classes that provide
both separate and overlapping functionality. The original design
was motivated by a component architecture where collections of
components that needed to be mixed together were specified outside
the application in a database.
THIS FILE IS A DUPLICATE OF A UNIT TEST USED, PUT HERE FOR CONVENIENCE
"""
import unittest
from bacpypes.debugging import bacpypes_debugging, ModuleLogger
from bacpypes.capability import Capability, Collector
# some debugging
_debug = 0
_log = ModuleLogger(globals())
@bacpypes_debugging
class BaseCollector(Collector):
def __init__(self):
if _debug: BaseCollector._debug("__init__")
Collector.__init__(self)
def transform(self, value):
if _debug: BaseCollector._debug("transform %r", value)
for fn in self.capability_functions('transform'):
print(" - fn: {}".format(fn))
value = fn(self, value)
return value
@bacpypes_debugging
class PlusOne(Capability):
def __init__(self):
if _debug: PlusOne._debug("__init__")
def transform(self, value):
if _debug: PlusOne._debug("transform %r", value)
return value + 1
@bacpypes_debugging
class TimesTen(Capability):
def __init__(self):
if _debug: TimesTen._debug("__init__")
def transform(self, value):
if _debug: TimesTen._debug("transform %r", value)
return value * 10
@bacpypes_debugging
class MakeList(Capability):
def __init__(self):
if _debug: MakeList._debug("__init__")
def transform(self, value):
if _debug: MakeList._debug("transform %r", value)
return [value]
#
# Example classes
#
class Example1(BaseCollector):
pass
class Example2(BaseCollector, PlusOne):
pass
class Example3(BaseCollector, TimesTen, PlusOne):
pass
class Example4(BaseCollector, MakeList, TimesTen):
pass
@bacpypes_debugging
class TestExamples(unittest.TestCase):
def test_example_1(self):
if _debug: TestExamples._debug("test_example_1")
assert Example1().transform(1) == 1
def test_example_2(self):
if _debug: TestExamples._debug("test_example_2")
assert Example2().transform(2) == 3
def test_example_3(self):
if _debug: TestExamples._debug("test_example_3")
assert Example3().transform(3) == 31
def test_example_4(self):
if _debug: TestExamples._debug("test_example_4")
assert Example4().transform(4) == [4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
def test_example_5(self):
if _debug: TestExamples._debug("test_example_5")
obj = Example2()
obj.add_capability(MakeList)
assert obj.transform(5) == [6]

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
"""
Simple implementation of a Client and Server
"""
from bacpypes.comm import Client, Server, bind
class MyServer(Server):
def indication(self, arg):
print('working on', arg)
self.response(arg.upper())
class MyClient(Client):
def confirmation(self, pdu):
print('thanks for the ', pdu)
if __name__ == '__main__':
c = MyClient()
s = MyServer()
bind(c, s)
c.request('hi')

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python
"""
The IO Control Block (IOCB) is an object that holds the parameters
for some kind of operation or function and a place for the result.
The IOController processes the IOCBs it is given and returns the
IOCB back to the caller.
"""
import bacpypes
from bacpypes.iocb import IOCB, IOController
class SomeController(IOController):
def process_io(self, iocb):
self.complete_io(iocb, iocb.args[0] + iocb.args[1] * iocb.kwargs['a'])
def call_me(iocb):
"""
When a controller completes the processing of a request,
the IOCB can contain one or more functions to be called.
"""
print("call me, %r or %r" % (iocb.ioResponse, iocb.ioError))
if __name__ == '__main__':
iocb = IOCB(1, 2, a=3)
iocb.add_callback(call_me)
some_controller = SomeController()
some_controller.request_io(iocb)
iocb.ioComplete.wait()
print(iocb.ioComplete)
print(iocb.ioComplete.is_set())
print(iocb.ioState == bacpypes.iocb.COMPLETED)
print(iocb.ioState == bacpypes.iocb.ABORTED)
print(iocb.ioResponse)

View File

0
samples/WhoIsIAm.py → samples/Tutorial/WhoIsIAm.py Executable file → Normal file
View File