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:
parent
a47fb85e3c
commit
ebe2ba5abe
|
@ -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::
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 -----
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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::
|
||||
|
|
|
@ -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>):
|
||||
|
|
0
samples/SampleApplication.py → samples/HandsOnLab/Sample1_SimpleApplication.py
Executable file → Normal file
0
samples/SampleApplication.py → samples/HandsOnLab/Sample1_SimpleApplication.py
Executable file → Normal file
0
samples/WhoIsIAmApplication.py → samples/HandsOnLab/Sample2_WhoIsIAmApplication.py
Executable file → Normal file
0
samples/WhoIsIAmApplication.py → samples/HandsOnLab/Sample2_WhoIsIAmApplication.py
Executable file → Normal file
0
samples/WhoHasIHaveApplication.py → samples/HandsOnLab/Sample3_WhoHasIHaveApplication.py
Executable file → Normal file
0
samples/WhoHasIHaveApplication.py → samples/HandsOnLab/Sample3_WhoHasIHaveApplication.py
Executable file → Normal file
0
samples/RandomAnalogValueObject.py → samples/HandsOnLab/Sample4_RandomAnalogValueObject.py
Executable file → Normal file
0
samples/RandomAnalogValueObject.py → samples/HandsOnLab/Sample4_RandomAnalogValueObject.py
Executable file → Normal file
119
samples/Tutorial/Capabilities.py
Normal file
119
samples/Tutorial/Capabilities.py
Normal 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]
|
23
samples/Tutorial/ClientAndServer.py
Normal file
23
samples/Tutorial/ClientAndServer.py
Normal 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')
|
36
samples/Tutorial/ControllerAndIOCB.py
Normal file
36
samples/Tutorial/ControllerAndIOCB.py
Normal 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)
|
0
samples/SampleConsoleCmd-A.py → samples/Tutorial/SampleConsoleCmd-Answer.py
Executable file → Normal file
0
samples/SampleConsoleCmd-A.py → samples/Tutorial/SampleConsoleCmd-Answer.py
Executable file → Normal file
0
samples/SampleConsoleCmd.py → samples/Tutorial/SampleConsoleCmd.py
Executable file → Normal file
0
samples/SampleConsoleCmd.py → samples/Tutorial/SampleConsoleCmd.py
Executable file → Normal file
0
samples/WhoIsIAm.py → samples/Tutorial/WhoIsIAm.py
Executable file → Normal file
0
samples/WhoIsIAm.py → samples/Tutorial/WhoIsIAm.py
Executable file → Normal file
Loading…
Reference in New Issue
Block a user