diff --git a/doc/source/gettingstarted/gettingstarted001.rst b/doc/source/gettingstarted/gettingstarted001.rst index 5538d4e..3482a7a 100644 --- a/doc/source/gettingstarted/gettingstarted001.rst +++ b/doc/source/gettingstarted/gettingstarted001.rst @@ -4,127 +4,137 @@ Getting Started =============== Ah, so you are interested in getting started with BACnet and Python. Welcome -to BACpypes, I hope you enjoy your journey. This tutorial starts with some +to BACpypes, I hope you enjoy your journey. This tutorial starts with just enough of the basics of BACnet to get a workstation communicating with -another device, installing the library, and downloading and configuring the -samples applications. +another device. We will cover installing the library, and downloading and +configuring the samples applications. Basic Assumptions ----------------- -Assume that you are a software developer and it is your job to communicate +I will assume you are a software developer and it is your job to communicate with a device from another company that uses BACnet. Your employer has -given you a test device and purchased a copy of the standard. You have -in your office... +given you a test device and purchased a copy of the BACnet standard. I will +need... -- a development workstation running some flavor of Linux complete with - the latest version of Python 2.7 and +- a development workstation running some flavor of Linux, complete with + the latest version of Python (2.7 or 3.4) and `setup tools `_. -- a small hub you can plug in your workstation and this misterious device - and not get distracted by lots of other LAN traffic. +- a small Ethernet hub into which you can plug both your workstation and your + mysterious BACnet device, so you won't be distracted by lots of other network traffic. -Before getting this test environment set up and you are still connected +Before getting this test environment set up and while you are still connected to the internet, install the BACpypes library:: $ sudo easy_install bacpypes + or + $ sudo pip install bacpypes -And while you are at it, get a copy of the project from SourceForge that -has the library source code, sample code, and this documentation:: +And while you are at it, get a copy of the BACpypes project from GitHub. It +contains the library source code, sample code, and this documentation:: - $ svn checkout svn://svn.code.sf.net/p/bacpypes/code/trunk bacpypes + $ git clone https://github.com/JoelBender/bacpypes.git No protocol analysis workbench would be complete without an installed copy of `Wireshark `_:: $ sudo apt-get install wireshark + Configuring the Workstation --------------------------- -The test device that you have is going to come with some configuration +The mystery BACnet device you have is going to come with some configuration information by default and sometimes it is easier to set up the test -environment with same set of assumtions than come up with a fresh set +environment with my set of assumptions than come up with a fresh set from scratch. *IP Address* - The device will probably come with an IP address, assume that it + The device will probably come with an IP address, let's assume that it is 192.168.0.10, subnet mask 255.255.0.0, gateway address 192.168.0.1. You are going to be joining the same network, so pick 192.168.0.11 - for the workstation address with the same subnet mask. + for your workstation address and use the same subnet mask 255.255.0.0. + +*Device Identifier* + Every BACnet device on a BACnet network **must** have a unique numeric + identifier. This number is a 22-bit unsigned non-zero value. + It is critical this identifier be unique. Most large customers will have + someone or some group responsible for maintaining device identifiers across the + site. Keep track of the device identifier for the test device. Let's + assume that this device is **1000** and you are going to pick **1001** + for your workstation. *Device Name* - Every BACnet device on a BACnet network has a unique name which + Every BACnet device on a BACnet network should also have a unique name, which is a character string. There is nothing on a BACnet network that enforces this uniqueness, but it is a real headache for integrators when it isn't followed. You will need to pick a name for your - workstation. My collegues and I use star names so the sample - congiuration files will have "Betelgeuse". + workstation. My collegues and I use star names, so in the sample + configuration files you will see the name "Betelgeuse". An actual customer's + site will use a more formal (but less fun) naming convention. -*Device Identifier* - Every BACnet device will have a unique identifier, a 22-bit - unsigned non-zero value. It is critical that this be unique for - every device and most large customers will have someone or a - group responsible for maintaining device identifiers across the - site. Keep track of the device identifier for the test device, - assume that it is **1000** and you are going to pick **1001** - for your workstation. There are a few more configuration values that you will need, but -you won't need to change the values in the sample configuration file +you won't need to change the values in the sample configuration file until you get deeper into the protocol. *Maximum APDU Length Accepted* BACnet works on lots of different types of networks, from high speed Ethernet to "slower" and "cheaper" ARCNET or MS/TP (a serial bus protocol used for a field bus defined by BACnet). - For devices to exchange messages they have to know the maximum - size message the device can handle. + For devices to exchange messages they need to know the maximum + size message the other device can handle. *Segmentation Supported* A vast majority of BACnet communications traffic fits in one - message, but thre can be times when larger messages are - convinient and more efficient. Segmentation allows larger - messages to be broken up into segemnts and spliced back together. - It is not unusual for "low power" field equipment to not + message, but there are times when larger messages are + convenient and more efficient. Segmentation allows larger + messages to be broken up into segments and spliced back together. + It is not unusual for "low power" field devices to not support segmentation. There are other configuration parameters in the INI file that are -used by other applications, just leave them alone for now. +also used by other applications, just leave them alone for now. + Updating the INI File ~~~~~~~~~~~~~~~~~~~~~ -Now that you know what these values are going to be you can -configure the BACnet part of your workstation. Change into the +Now that you know what these values are going to be, you can +configure the BACnet portion of your workstation. Change into the samples directory that you checked out earlier, make a copy of the sample configuration file, and edit it for your site:: $ cd bacpypes/samples $ cp BACpypes~.ini BACpypes.ini -The sample applications are going to look for this file, and you -can direct them to other INI files on the command line, so it is -simple to keep multiple configurations. +.. tip:: + + The sample applications are going to look for this file. + You can direct the applications to use other INI files on the command line, so it is + simple to keep multiple configurations. + + At some point you will probably running both "client" and "server" + 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 ------------------------- -BACnet devices comunicate using UDP rather than TCP. This is so -that devices do not need to implement a full IP stack (although +BACnet devices communicate using UDP rather than TCP. This is so +devices do not need to implement a full IP stack (although many of them do becuase they support multiple protocols, including having embedded web servers). There are two types of UDP messages; *unicast* which is a message -from one specific IP address and port to another one, and *broadcast* -which is received and processed by all devices that have the port -open. BACnet uses both types of messages and your workstation +from one specific IP address (and port) to another device's IP address +(and port); and *broadcast* messages which are sent by one device +and received and processed by all other devices that are listening +on that port. BACnet uses both types of messages and your workstation will need to receive both types. The BACpypes.ini file has an *address* parameter which is an IP @@ -134,27 +144,29 @@ number of bits in the network portion, which in turn implies a subnet mask, in this case **255.255.0.0**. Unicast messages will be sent to the IP address, and broadcast messages will be sent to the broadcast address **192.168.255.255** which is the network -portion of the configuration value will all 1's in the host -portion. +portion of the address with all 1's in the host portion. -To receive both unicast and broadcast addresses, BACpypes will -open two sockets, one for unicast traffic and one that only listens +To receive both unicast and broadcast addresses, BACpypes +opens two sockets, one for unicast traffic and one that only listens for broadcast messages. The operating system will typically not allow two applications to open the same socket at the same time so to run two BACnet applciations at the same time they need to be configured with different ports. -The BACnet protocol has port 47808 (hex 0xBAC0) assigned to it -by the `Internet Assigned Numbers Authority `_, and sequentially -higher numbers are used in many applications. There are some -BACnet routing and networking isseus with this, but that is for -antoher tutorial. +.. note:: + + The BACnet protocol has been assigned port 47808 (hex 0xBAC0) by + by the `Internet Assigned Numbers Authority `_, and sequentially + higher numbers are used in many applications (i.e. 47809, 47810,...). + There are some BACnet routing and networking issues related to using these higher unoffical + ports, but that is a topic for another tutorial. + Starting An Application ----------------------- The simplest BACpypes sample application is the **WhoIsIAm.py** -application. It can send out Who-Is and I-Am messages and +application. It sends out Who-Is and I-Am messages and displays the results it receives. What are these things? As mentioned before, BACnet has unique device identifiers and @@ -169,7 +181,7 @@ a decentralized DNS service, but the names are unsigned integers. The request is broadcast on the network and the client waits around to listen for I-Am messages. The source address of the I-Am response is "bound" to the device identifier -and most communications is unicast after that. +and most communications are unicast thereafter. First, start up Wireshark on your workstation and a capture session with a BACnet capture filter:: @@ -187,7 +199,7 @@ Now start the application:: $ python WhoIsIAm.py -You will be presented with a prompt, and you can get help:: +You will be presented with a prompt (>), and you can get help:: > help @@ -195,76 +207,97 @@ You will be presented with a prompt, and you can get help:: ======================================== EOF buggers bugin bugout exit gc help iam shell whois -The details of the commands will be described in the next -section. +The details of the commands are described in the next section. + Generating An I-Am ------------------ Now that the application is configured it is nice to see some -BACnet communications traffic. Just generate an I-Am message:: +BACnet communications traffic. Generate the basic I-Am message:: > iam -You should see your configuration parameters in the I-Am -message in Wireshark, this is a "global broadcast" message, so your -test device will see it but since your test device probably -isn't looking for you, it will not respond with anything. +You should see Wireshark capture your I-Am message containing your configuration +parameters. This is a "global broadcast" message. +Your test device will see it but since your test device probably +isn't looking for you, it will not respond to the message. + Binding to the Test Device -------------------------- -Now to confirm that the workstation can receive the -messages that the test device sends out, generate a Who-Is -request. This one will be "unconstrained" which means that -every device will respond. *Do not generate these types of -unconstrained requests on a large -network because it will create a lot of traffic that can -cause conjestion.* Here is a Who-Is:: +Next we want to confirm that your workstation can receive the +messages the test device sends out. We do this by generating a +generic Who-Is request. The request will be "unconstrained", meaning +every device that hears the message will respond with their corresponding +I-Am messages. + +.. caution:: + + Generating **unconstrained** Who-Is requests on a large network will create + a LOT of traffic, which can lead to network problems caused by the resulting + flood of messages. + +To generate the Who-Is request:: > whois -You should see the request in Wireshark and the response from -the device, and then a summary line of the response on the -workstation. +You should see the Who-Is request captured in Wireshark along with the I-Am +response from your test device, and then the details of the response displayed +on the workstation console.:: -There are a few different forms of the *whois* command this -simple application allows and you can see the basic form -with the help command:: + > whois + > pduSource = + iAmDeviceIdentifier = ('device', 1000) + maxAPDULengthAccepted = 480 + segmentationSupported = segmentedBoth + vendorID = 8 + + +There are a few different forms of the *whois* command supported by this +simple application. You can see these with the help command:: > help whois whois [ ] [ ] -This is like a BNF syntax, the whois command is optionally -followed by an address, and then optionally followed by a -low limit and high limit. The most common use of the Who-Is +This is like a BNF syntax, the **whois** command is optionally +followed by a BACnet device address, and then optionally followed by a +low (address) limit and high (address) limit. The most common use of the Who-Is request is to look for a specific device given its device identifier:: > whois 1000 1000 -And if the site has a numbering scheme for groups of BACnet -devices like all those in a specific building, then it is -common to look for all of them as a group:: +If the site has a numbering scheme for groups of BACnet +devices (i.e. grouped by building), then it is +common to look for all the devices in a specific building as a group:: > whois 203000 203099 Every once in a while a contractor might install a BACnet device that hasn't been properly configured. Assuming that -it has an IP address, you can send an unconstrained request +it has an IP address, you can send an **unconstrained Who-Is** request to the specific device and hope that it responds:: > whois 192.168.0.10 + > pduSource =
+ iAmDeviceIdentifier = ('device', 1000) + maxAPDULengthAccepted = 1024 + segmentationSupported = segmentedBoth + vendorID = 15 + There are other forms of BACnet addresses used in BACpypes, but that is a subject of an other tutorial. + What's Next ----------- -The next tutorial will describe the different ways this +The next tutorial describes the different ways this application can be run, and what the commands can tell you -about how it is working. All of the "console" applications, -those that prompt for commands, use the same basic +about how it is working. All of the "console" applications +(i.e. those that prompt for commands) use the same basic commands and work the same way. diff --git a/doc/source/gettingstarted/gettingstarted002.rst b/doc/source/gettingstarted/gettingstarted002.rst index 95944e1..71c0eb5 100644 --- a/doc/source/gettingstarted/gettingstarted002.rst +++ b/doc/source/gettingstarted/gettingstarted002.rst @@ -5,8 +5,8 @@ Running BACpypes Applications All BACpypes sample applications have the same basic set of command line options so it is easy to move between applications, turn debugging on and -and using different configurations. There may be additional options and -command parameters than the ones described in this section. +and use different configurations. There may be additional options and +command parameters than just the ones described in this section. Getting Help ------------ @@ -18,7 +18,7 @@ an application, you can start with help:: usage: WhoIsIAm.py [-h] [--buggers] [--debug [DEBUG [DEBUG ...]]] [--color] [--ini INI] This application presents a 'console' prompt to the user asking for Who-Is and - I-Am commands which create the related APDUs, then lines up the coorresponding + I-Am commands which create the related APDUs, then lines up the corresponding I-Am for incoming traffic and prints out the contents. optional arguments: @@ -64,10 +64,10 @@ Telling the application to debug a module is simple:: The output is the severity code of the logger (almost always DEBUG), the name of the module, class, or function, then some message about the progress of the -application. From the output above you can see that the application is -beginning its initialization, shows the value of a variable called args, -an instance of the WhoIsIAmApplication class is created with some parameters, -and then the application starts running. +application. From the output above you can see the application initializing, +setting the args variable, creating an instance of the WhoIsIAmApplication class +(with some parameters), and then declaring itself - running. + Debugging a Class ----------------- @@ -92,13 +92,14 @@ example, there is a class called UDPActor in the UDP module:: In this sample, an instance of a UDPActor is created and then its response function is called with an instance of a PDU as a parameter. Following the function invocation description, the debugging output continues with the -contents of the PDU. Notice that the protocol data is printed as a hex -encoded string, and only the first 20 bytes of the message. +contents of the PDU. Notice, the protocol data is printed as a hex +encoded string (and restricted to just the first 20 bytes of the message). -You can debug a function just as easily, and specify as many different -combinations of logger names as necessary. Note that you cannot debug a +You can debug a function just as easily. Specify as many different +combinations of logger names as necessary. Note, you cannot debug a specific function within a class. + Changing INI Files ------------------ diff --git a/doc/source/index.rst b/doc/source/index.rst index d3c911b..30998c6 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -7,25 +7,38 @@ BACpypes library for building BACnet applications using Python. Installation is easy, just:: $ sudo easy_install bacpypes + or + $ sudo pip install bacpypes + -You will be installing the latest released version. You can also check out -the latest version from GitHub:: +You will be installing the latest released version from PyPI (the Python Packages Index), +located at pypi.python.org - $ 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 -`developers mailing list -`_, join the -`chat room on Gitter `_, and add the -`Google+ `_ to your -circles have have release notifications show up in your stream. +.. tip:: + + If you would like to participate in its development, please join: + + - the `developers mailing list `_, + - the `chat room on Gitter `_, and + - add `Google+ `_ to your circles to have release notifications show up in your stream. + + +**Welcome aboard!** + +------ -Welcome aboard! Getting Started --------------- @@ -39,6 +52,8 @@ downloading the sample code and communicating with a test device. gettingstarted/gettingstarted001.rst gettingstarted/gettingstarted002.rst + + Tutorial -------- @@ -54,11 +69,12 @@ essential components of a BACpypes application and how the pieces fit together. tutorial/tutorial004.rst tutorial/tutorial006.rst + Samples ------- -The library has a variety of sample applications, some of them are a framework -for building larger applications, some of them are standalone analysis tools +BACpypes comes with a variety of sample applications. Some are a framework +for building larger applications. Some are standalone analysis tools that don't require a connection to a network. .. toctree:: @@ -71,15 +87,6 @@ that don't require a connection to a network. samples/sample005.rst samples/sample014.rst -Modules -------- - -This documentation is intended for BACpypes developers. - -.. toctree:: - :maxdepth: 1 - - modules/index.rst Glossary -------- @@ -89,6 +96,7 @@ Glossary glossary.rst + Release Notes ------------- @@ -97,6 +105,21 @@ Release Notes releasenotes.rst +------ + +Modules +------- + +.. tip:: Documentation intended for BACpypes developers. + +.. toctree:: + :maxdepth: 1 + + modules/index.rst + +----- + + Indices and tables ================== diff --git a/doc/source/modules/comm.rst b/doc/source/modules/comm.rst index 14f36ef..331fe5b 100644 --- a/doc/source/modules/comm.rst +++ b/doc/source/modules/comm.rst @@ -38,9 +38,8 @@ Protocol Data Units A Protocol Data Unit (PDU) is the name for a collection of information that is passed between two entities. It is composed of Protcol Control Information -(PCI), which usually has information about addressing and other types of -processing instructions, and data. The set of classes in this module are not -specific to BACnet. +(PCI) - information about addressing, processing instructions - and data. +The set of classes in this module are not specific to BACnet. .. class:: PCI @@ -68,6 +67,11 @@ specific to BACnet. .. class:: PDUData + The PDUData class has functions for extracting information from the front + of the data octet string, or append information to the end. These are helper + functions but may not be applicable for higher layer protocols which may + be passing significantly more complex data. + .. attribute:: pduData This attribute typically holds a simple octet string, but for higher @@ -81,16 +85,20 @@ specific to BACnet. .. method:: get_data(len) - :param integer len: the number of octets to extract off the front + :param integer len: the number of octets to extract. Extract a number of octets from the front of the data. If there are not at least `len` octets this will raise a DecodingError exception. .. method:: get_short() + + Extract a short integer (two octets) from the front of the data. .. method:: get_long() + Extract a long integer (four octets) from the front of the data. + .. method:: put(ch) :param octet ch: the octet to append to the end @@ -101,12 +109,12 @@ specific to BACnet. .. method:: put_short(n) + :param short integer: two octets to append to the end + .. method:: put_long(n) - The PDUData class has functions for gathering information from the front - of the octet string, or putting information on the end. These are helper - functions but may not be applicable for higher layer protocols which may - be passing significantly more complex data. + :param long integer: four octets to append to the end + .. class:: PDU(PCI, PDUData) diff --git a/doc/source/modules/consolecmd.rst b/doc/source/modules/consolecmd.rst index 1025cd7..34e9a25 100644 --- a/doc/source/modules/consolecmd.rst +++ b/doc/source/modules/consolecmd.rst @@ -5,7 +5,11 @@ Console Command =============== -This is a long line of text. +Python has a `cmd `_ module that makes +it easy to embed a command line interpreter in an application. BACpypes +extends this interpreter with some commands to assist debugging and runs +the interpreter in a separate thread so it does not interfere with the BACpypes +:func:`core.run` functionality. Functions --------- @@ -19,44 +23,88 @@ Classes .. class:: ConsoleCmd(cmd.Cmd, Thread) - This is a long line of text. .. method:: __init__(prompt="> ", allow_exec=False) :param string prompt: prompt for commands :param boolean allow_exec: allow non-commands to be executed - This is a long line of text. .. method:: run() - This is a long line of text. + Begin execution of the application's main event loop. Place this after the + the initialization statements. .. method:: do_something(args) :param args: commands - This is a long line of text. + Template of a function implementing a console command. + Commands -------- +.. option:: help + + List an application's console commands:: + + > help + Documented commands (type help ): + ======================================== + EOF buggers bugin bugout exit gc help nothing shell + .. option:: gc - This is a long line of text. + Print out garbage collection information:: + > gc + Module Type Count dCount dRef + bacpypes.object OptionalProperty 787 0 0 + bacpypes.constructeddata Element 651 0 0 + bacpypes.object ReadableProperty 362 0 0 + bacpypes.object WritableProperty 44 0 0 + __future__ _Feature 7 0 0 + Queue Queue 2 0 0 + bacpypes.pdu Address 2 0 0 + bacpypes.udp UDPActor 2 1 4 + bacpypes.bvllservice UDPMultiplexer 1 0 0 + bacpypes.app DeviceInfoCache 1 0 0 + + Module Type Count dCount dRef + bacpypes.udp UDPActor 2 1 4 + .. option:: bugin - This is a long line of text. + Attach a debugger.:: + + > bugin bacpypes.task.OneShotTask + handler to bacpypes.task.OneShotTask added .. option:: bugout - This is a long line of text. + Detach a debugger.:: + + > bugout bacpypes.task.OneShotTask + handler to bacpypes.task.OneShotTask removed .. option:: buggers - This is a long line of text. + Get a list of the available buggers.:: + > buggers + no handlers + __main__ + bacpypes + bacpypes.apdu + bacpypes.apdu.APCI + ... + bacpypes.vlan.Network + bacpypes.vlan.Node + .. option:: exit - This is a long line of text. + Exit a BACpypes Console application.:: + + > exit + Exiting... diff --git a/doc/source/modules/core.rst b/doc/source/modules/core.rst index a274120..7487ecb 100644 --- a/doc/source/modules/core.rst +++ b/doc/source/modules/core.rst @@ -78,5 +78,4 @@ Functions time so that it does not starve child threads of processing time. When sleeping is enabled, and it only needs to be enabled for multithreaded - applications, it will put a damper on the thruput of the application. - + applications, it will put a damper on the throughput of the application. diff --git a/doc/source/modules/errors.rst b/doc/source/modules/errors.rst index 55d8dbd..f453350 100644 --- a/doc/source/modules/errors.rst +++ b/doc/source/modules/errors.rst @@ -5,9 +5,9 @@ Errors ====== -This module defines exception class for errors that it detects in the -configuration of the stack or in encoding and decoding PDUs. All of these -exceptions are derived from ValueError from the built-in exceptions module. +This module defines the exception class for errors it detects in the +configuration of the stack or in encoding or decoding PDUs. All of these +exceptions are derived from ValueError (in Python's built-in exceptions module). Classes ------- @@ -30,6 +30,6 @@ Classes This error is raised while PDU data is being decoded, which typically means some unstructured data like an octet stream is being turned into structured - data. There may be values in the pdu being decoded that are not + data. There may be values in the PDU being decoded that are not appropriate, or not enough data such as a truncated packet. diff --git a/doc/source/modules/singleton.rst b/doc/source/modules/singleton.rst index 4a1f205..36a1d17 100644 --- a/doc/source/modules/singleton.rst +++ b/doc/source/modules/singleton.rst @@ -6,9 +6,9 @@ Singleton ========= Singleton classes are a `design pattern `_ -that returns the same object for every call to create an instance. In the case +which returns the same object for every 'create an instance' call. In the case of BACpypes there can only be one instance of a :class:`task.TaskManager` and -all of the tasks will be schedule through it. The design pattern "hides" all +all of the tasks are scheduled through it. The design pattern "hides" all of the implementation details of the task manager behind its interface. There are occasions when the task manager needs to provide additional diff --git a/doc/source/samples/images/RandomAnalogValue.png b/doc/source/samples/images/RandomAnalogValue.png new file mode 100644 index 0000000..b02d718 Binary files /dev/null and b/doc/source/samples/images/RandomAnalogValue.png differ diff --git a/doc/source/samples/sample001.rst b/doc/source/samples/sample001.rst index 6f81267..0c2f2f9 100644 --- a/doc/source/samples/sample001.rst +++ b/doc/source/samples/sample001.rst @@ -1,4 +1,3 @@ -.. BACpypes tutorial lesson 1 Sample 1 - Simple Application ============================= @@ -130,19 +129,16 @@ as the application, but the '--ini' option is available when it's not:: elif not config.read('BACpypes.ini'): raise RuntimeError, "configuration file not found" -If the sample applications are run from the subversion directory, there is a -sample INI file called **BACpypes~.ini** that is part of the repository. Make -a local copy *that is not part of the repository* and edit it with information -appropriate to your installation:: +.. tip:: - $ pwd - .../samples - $ cp BACpypes~.ini BACpypes.ini - $ vi BACpypes.ini - $ svn status - ? BACpypes.ini + There is a sample INI file called **BACpypes~.ini** as part of the repository. Make + a local copy and edit it with information appropriate to your installation:: + + $ pwd + .../samples + $ cp ../BACpypes~.ini BACpypes.ini + $ nano BACpypes.ini -Subversion understands that the local copy is not part of the repository. Now applications will create a :class:`object.LocalDeviceObject` which will respond to Who-Is requests for device-address-binding procedures, and @@ -220,37 +216,46 @@ Running When this sample application is run without any options, nothing appears on the console because there are no statements other than debugging:: - $ python sample001.py + $ python SampleApplication.py So to see what is actually happening, run the application with debugging enabled:: - $ python sample001.py --debug __main__ + $ python SampleApplication.py --debug __main__ -The output will include the initialization, running, and finally statements. To -run with debugging on just the SampleApplication class:: +The output will include the initialization, running, and finally statements.:: - $ python sample001.py --debug __main__.SampleApplication + DEBUG:__main__:initialization + DEBUG:__main__: - args: Namespace(buggers=False, color=False, debug=['__main__'], ini=) + DEBUG:__main__.SampleApplication:__init__ '192.168.0.10/24' + DEBUG:__main__: - this_application: <__main__.SampleApplication object at 0x7fcd357dea50> + DEBUG:__main__: - services_supported: + DEBUG:__main__:running -Or to see what is happening at the UDP layer of the program, use that module -name:: +To run with debugging on just the SampleApplication class:: - $ python sample001.py --debug bacpypes.udp + $ python SampleApplication.py --debug __main__.SampleApplication + + DEBUG:__main__.SampleApplication:__init__ '192.168.0.10/24' + +Or to see what is happening at the UDP layer of the program, use that module name:: + + $ python SampleApplication.py --debug bacpypes.udp Or to simplify the output to the methods of instances of the :class:`udp.UDPActor` use the class name:: - $ python sample001.py --debug bacpypes.udp.UDPActor + $ python SampleApplication.py --debug bacpypes.udp.UDPActor Then to see what BACnet packets are received and make it all the way up the stack to the application, combine the debugging:: - $ python sample001.py --debug bacpypes.udp.UDPActor __main__.SampleApplication + $ python SampleApplication.py --debug bacpypes.udp.UDPActor __main__.SampleApplication The most common broadcast messages that are *not* application layer messages -are Who-Is-Router-To-Network and I-Am-Router-To-Network, and you can see these +are **Who-Is-Router-To-Network** and **I-Am-Router-To-Network**. You can see these messages being received and processed by the :class:`netservice.NetworkServiceElement` -burried in the stack:: +buried in the stack:: - $ python sample001.py --debug bacpypes.netservice.NetworkServiceElement + $ python SampleApplication.py --debug bacpypes.netservice.NetworkServiceElement diff --git a/doc/source/samples/sample002.rst b/doc/source/samples/sample002.rst index 70c75e3..538a157 100644 --- a/doc/source/samples/sample002.rst +++ b/doc/source/samples/sample002.rst @@ -1,4 +1,3 @@ -.. BACpypes tutorial lesson 1 Sample 2 - Who-Is/I-Am Counter ============================== @@ -50,7 +49,7 @@ The middle is going to process the data in the request:: ) # count the times this has been received - whoIsCounter[key] += 1 + who_is_counter[key] += 1 And the end of the function is going to call back to the standard application processing:: @@ -72,7 +71,7 @@ It uses a diferent key, but counts them the same:: ) # count the times this has been received - iAmCounter[key] += 1 + i_am_counter[key] += 1 And has an identical call to the base class:: @@ -87,11 +86,57 @@ to sort the dictionary items and print them out, and being able to unpack the key in the for loop is a nice feature of Python:: print "----- Who Is -----" - for (src, lowlim, hilim), count in sorted(whoIsCounter.items()): + for (src, lowlim, hilim), count in sorted(who_is_counter.items()): print "%-20s %8s %8s %4d" % (src, lowlim, hilim, count) print -Pairing up the requests and responses can be a useful excersize, but in most +Pairing up the requests and responses can be a useful exercize, but in most cases the I-Am response from a device will be a unicast message directly back to the requestor, so relying on broadcast traffic to analyze device and address binding is not as useful as it used to be. + +Running the Application +----------------------- + +:: + + $ python WhoIsIAmApplication.py --debug __main__ + + DEBUG:__main__:initialization + DEBUG:__main__: - args: Namespace(buggers=False, color=False, debug=['__main__'], ini=) + DEBUG:__main__.WhoIsIAmApplication:__init__ '192.168.87.59/24' + DEBUG:__main__: - services_supported: + DEBUG:__main__:running + +Let it run for a minute, then Press to end it. It will output its results.:: + + DEBUG:__main__.WhoIsIAmApplication:do_WhoIsRequest + + pduSource =
+ pduDestination = + 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 ----- + diff --git a/doc/source/samples/sample003.rst b/doc/source/samples/sample003.rst index 44d9846..f835f18 100644 --- a/doc/source/samples/sample003.rst +++ b/doc/source/samples/sample003.rst @@ -1,4 +1,3 @@ -.. BACpypes tutorial lesson 1 Sample 3 - Who-Has/I-Have Counter ================================= @@ -6,7 +5,7 @@ Sample 3 - Who-Has/I-Have Counter This sample application is very similar to the second sample. It has the same basic structure and initialization, it counts the number of Who-Has and I-Have messages it receives, and prints out a summary after the application -has been signaled to terminate, such as a KeyboardInterrupt raised. +has been signalled to terminate ( - KeyboardInterrupt). Processing Service Requests @@ -34,23 +33,23 @@ cannot appear in the APDU at the same time:: return # count the times this has been received - whoHasCounter[key] += 1 + who_has_counter[key] += 1 -When an optional parameter is not specified in a PDU then the corrisponding -attribute will be ``None``. With this particular APDU the *object* -parameter is required, but only one of its child attributes *objectIdentifier* +When an optional parameter is not specified in a PDU then the corresponding +attribute is ``None``. With this particular APDU the *object* +parameter is required, and one of its child attributes *objectIdentifier* or *objectName* will be not ``None``. If both are ``None`` then the request is not properly formed. .. note:: - The encoding and decoding layer will not completely understand all of + The encoding and decoding layer does not understand all the combinations of required and optional parameters in an APDU, so - verify the validity of the reuest is the responsibility of the application. + verify the validity of the request is the responsibility of the application. - The application can rely on the fact that the APDU is well-formed, which - is to say it has teh appropriate opening and closing tags and the data - types of the parameters are correct. Watch out for Any! + The application can rely on the fact that the APDU is well-formed - meaning + it has the appropriate opening and closing tags and the data + types of the parameters are correct. Watch out for parameters of type Any! The I-Am function is much simpler because all of the parameters are required:: @@ -62,13 +61,36 @@ The I-Am function is much simpler because all of the parameters are required:: ) # count the times this has been received - iHaveCounter[key] += 1 + i_have_counter[key] += 1 Dumping the contents of the counters is simple. Just like Who-Is and I-Am, pairing up the requests and responses can be a -useful excersize, but in most cases the I-Am response from a device will be a +useful exercize, but in most cases the I-Am response from a device will be a unicast message directly back to the requestor, so relying on broadcast traffic to analyze object binding is not as useful as it used to be. -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=) + DEBUG:__main__.WhoHasIHaveApplication:__init__ '192.168.87.59/24' + DEBUG:__main__: - services_supported: + 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. + diff --git a/doc/source/samples/sample004.rst b/doc/source/samples/sample004.rst index db0cd39..636b024 100644 --- a/doc/source/samples/sample004.rst +++ b/doc/source/samples/sample004.rst @@ -4,7 +4,7 @@ Sample 4 - Extending Objects and Properties =========================================== This sample application shows how to extend one of the basic objects, an Analog -Value Object in this case, to provide a custom property, the present value. +Value Object in this case, to provide a custom property - present value. This type of code is used when the application is providing a BACnet interface to a collection of data. It assumes that almost all of the default behaviour of a BACpypes application is sufficient. @@ -12,21 +12,20 @@ of a BACpypes application is sufficient. .. note:: The code in this description starts at the __main__ block and goes - backward through the source file. + backward through the source file - RandomAnalogValueObject.py. Constructing the Device ----------------------- Initialization is simple, the simple BACnet/IP application, which includes the -networking layer and communications layers all bundled in together is created +networking layer and communications layers all bundled together is created like the other samples:: # make a sample application thisApplication = BIPSimpleApplication(thisDevice, config.get('BACpypes','address')) -The only object this has by default is an instance of a -:class:`object.LocalDeviceObject`. The next step is to create a special Analog -Value Object and add it to the application:: +The only object by default is an instance of :class:`object.LocalDeviceObject`. +The next step is to create a special Analog Value Object and add it to the application:: # make a random input object raio = RandomAnalogValueObject(objectIdentifier=('analog-value', 1), objectName='Random') @@ -47,7 +46,7 @@ will make sure the keyword argument value is appropriate for the property. Extending the Analog Value Object --------------------------------- -The definition of a new kind of Analog Value Object uses Python inhertiance, +The definition of a new kind of Analog Value Object uses Python inheritance, so it seems fairly simple:: class RandomAnalogValueObject(AnalogValueObject): @@ -66,25 +65,24 @@ so this registration is going to override it. Overriding the same base type, like AnalogValueObject, in more than one way could be difficult in some kinds of gateway software which may require re-factoring the :func:`object.get_object_class` and the - :func:`object.get_datatype` functionality. This will be addressed before - BACpypes reaches v1.0. + :func:`object.get_datatype` functionality. The first part of :func:`object.register_object_type` builds a dictionary of -a relationship between the property name and its associated instance. It will +the relationship between the property name and its associated instance. It will look for *properties* class attribtues in the entire inheritance tree (using the method resolution order) but only associate the first of two instances with the same name. So in this case, the RandomValueProperty instance called 'present-value' will -be bound to the object type before the built-in version it finds in the +be bound to the object type before the built-in version finds it in the *properties* list in the :class:`object.AnalogValueObject`. A New Real Property ------------------- -BACnet clients will expect that the 'present-value' of an Analog Value Object -will be :class:`primitivedata.Real` and returning some other datatype would -seriously break interperabililty. The initialization is almost identical +BACnet clients expect the 'present-value' of an Analog Value Object +to be :class:`primitivedata.Real` and returning some other datatype would +seriously break interperabililty! Initialization is almost identical to the one for the built-in AnalogValueObject:: class RandomValueProperty(Property, Logging): @@ -97,8 +95,8 @@ to the one for the built-in AnalogValueObject:: The only difference is *mutable* is ``False``, which means BACnet clients will receive an error if they attempt to write a value to the property. -The core of the application is responding to a ReadPropertyRequest, which is -mapped into a ReadProperty function call:: +The core of the application is responding to a ReadPropertyRequest +(mapped to a ReadProperty function call):: def ReadProperty(self, obj, arrayIndex=None): @@ -106,7 +104,7 @@ mapped into a ReadProperty function call:: if arrayIndex is not None: raise Error(errorClass='property', errorCode='property-is-not-an-array') -The **arrayIndex** parameter will be some integer value if the BACnet client is +The **arrayIndex** parameter is an integer value if the BACnet client is accessing the property as an array, which is an error. Now it comes down to getting a random value and returning it:: @@ -119,3 +117,17 @@ getting a random value and returning it:: The value returned by this function will be passed as an initial value to construct a :class:`primitivedata.Real` object, which will then be encoded into the :class:`apdu.ReadPropertyACK` response and returned to the client. + +Running the Application +------------------------ + +:: + + $ python RandonAnalogValue.py + +Then using a BACnet client - like an OWS (Operator Workstation) or BACnet exploration +tool, read the application's Analog Value Objects. Notice: the value of the Present Value +property changes each time it is read by the client tool. + +.. image:: images/RandomAnalogValue.png + diff --git a/doc/source/samples/sample014.rst b/doc/source/samples/sample014.rst index def0499..b35ee45 100644 --- a/doc/source/samples/sample014.rst +++ b/doc/source/samples/sample014.rst @@ -3,8 +3,8 @@ Sample 14 - Getting External Data ================================= -This is a pair of sample applications, a server that provides key:value updates -in the form of JSON objects, and a client that periodically polls the server +This is a pair of sample applications: a server that provides key:value updates +in the form of JSON objects; and a client that periodically polls the server for updates and applies them to a cache. Server Code diff --git a/doc/source/tutorial/tutorial001.rst b/doc/source/tutorial/tutorial001.rst index b83f05a..102cb84 100644 --- a/doc/source/tutorial/tutorial001.rst +++ b/doc/source/tutorial/tutorial001.rst @@ -19,26 +19,29 @@ needs to provide a function to get it:: >>> class MyServer(Server): ... def indication(self, arg): - ... print "working on", arg + ... print('working on', arg) ... self.response(arg.upper()) ... Now create an instance of this new class and bind the client and server together:: + >>> c = Client() >>> s = MyServer() >>> bind(c, s) This only solves the downstream part of the problem, as you can see:: - >>> c.request("hi") - working on hi + >>> c.request('hi') + ('working on ', 'hi') + Traceback.... + .... NotImplementedError: confirmation must be overridden So now we create a custom client class that does something with the response:: >>> class MyClient(Client): ... def confirmation(self, pdu): - ... print "thanks for the", pdu + ... print('thanks for the ', pdu) ... Create an instance of it, bind the client and server together and test it:: @@ -46,7 +49,7 @@ Create an instance of it, bind the client and server together and test it:: >>> c = MyClient() >>> bind(c, s) >>> c.request('hi') - working on hi - thanks for the HI + ('working on ', 'hi') + ('thanks for ', 'HI') Success! diff --git a/doc/source/tutorial/tutorial002.rst b/doc/source/tutorial/tutorial002.rst index dceec14..6d5b8f4 100644 --- a/doc/source/tutorial/tutorial002.rst +++ b/doc/source/tutorial/tutorial002.rst @@ -4,55 +4,57 @@ Stacking with Debug =================== This tutorial uses the same :class:`comm.Client`, :class:`comm.Server` classes -from the previous one, so continuing on all it needs is the :class:`comm.Debug` -class, so import it:: +from the previous one, so continuing on from previous tutorial, all we needs is +to import the class:`comm.Debug`:: >>> from bacpypes.comm import Debug Because there could be lots of Debug instances, it could be confusing if you -didn't know which instance was generating the output. So you can initialize -an instance with a lobel:: +didn't know which instance was generating the output. So initialize the debug +instance with a name:: >>> d = Debug("middle") As you can guess, this is going to go into the middle of a :term:`stack` of objects. The *top* of the stack is a client, then *bottom* of a stack is a server. When messages are flowing from clients to servers they are called -:term:`downstream` messages, and when they go from server to the client they go -:term:`upstream`. +:term:`downstream` messages, and when they flow from server to client they +are :term:`upstream` messages. -The :func:`comm.bind` function takes an arbitrary number of objects, but it +The :func:`comm.bind` function takes an arbitrary number of objects. It assumes that the first one will always be a client, the last one is a server, -and that the objects in the middle are both a kind of server that can be -bound with the client to its left in the parameter list, and a client that can -be bound to a server to its right:: +and the objects in the middle are hybreds which can be +bound with the client to its left, and to the server on its right:: >>> bind(c, d, s) Now when the client generates a request, rather than the message being sent -to the MyServer instance, it is sent to the debugging instance. That is acting -as a server, so it prints out that it received an indication:: +to the MyServer instance, it is sent to the debugging instance, which +prints out that it received the message:: >>> c.request('hi') Debug(middle).indication - args[0]: hi -Now it acts as a client and forwards it down to the server in the stack. That -generates a print statement and responds with the string uppercase:: +The debugging instance then forwards the message to the server, which prints +its message. Completeing the requests *downstream* journey.:: working on hi -Upstream from the server is the debugging instance again, this time as a -confirmation:: +The server then generates a reply. The reply moves *upstream* from the server, +through the debugging instance, this time as a confirmation:: Debug(middle).confirmation - args[0]: HI -Now it acts as a server and continues the response up the stack, which is -printed out by the client:: +Which is then forwarded *upstream* to the client:: thanks for the HI +This demonstrates how requests first move *downstream* from client to server; then +cause the generation of replies that move *upstream* from server to client; and how the +debug instance in the middle sees the messages moving both ways. + With clearly defined "envelopes" of protocol data, matching the combination of clients and servers into layers can provide a clear separation of functionality in a protocol stack. diff --git a/doc/source/tutorial/tutorial003.rst b/doc/source/tutorial/tutorial003.rst index 91c166a..9ecac24 100644 --- a/doc/source/tutorial/tutorial003.rst +++ b/doc/source/tutorial/tutorial003.rst @@ -10,7 +10,7 @@ According to `Wikipedia `_ a and that may contain control information, address information, or data. BACpypes uses a slght variation of this definition in that it bundles the -address information in with the control information. It considers addressing +address information with the control information. It considers addressing as part of how the data should be delivered, along with other concepts like how important the PDU data is relative to other PDUs. @@ -18,37 +18,53 @@ The basic components of a PDU are the :class:`comm.PCI` and :class:`comm.PDUData` classes which are then bundled together to form the :class:`comm.PDU` class. -All of the protocol interpreters that have been written in the course of -developing BACpypes have all had at least some concept of source and +All of the protocol interpreters written in the course of +developing BACpypes have a concept of source and destination. The :class:`comm.PCI` defines only two attributes, **pduSource** and **pduDestination**. -Only in the case of pure master/slave networks has only the destination -encoded by the master to direct it to a specific slave (so source information -is implicit and not encoded) and the response from the slave back to the master -(so no addressing is included at all). These special cases are rare. +.. note:: + Master/slave networks, are an exception. Messages sent by the master, contain + only the destination (the source is implicit). Messages returned by the slaves + have no addressing (both the source, and destination are implicit). + As a foundation layer, there are no restrictions on the form of the source and destination, they could be integers, strings or even objects. In general, the :class:`comm.PDU` class is used as a base class for a series of stack -specific components, so UDP traffic will have combinations of IP addresses and +specific components. UDP traffic have combinations of IP addresses and port numbers as source and destination, then that will be inherited by something that provides more control information, like delivery order or priority. -Beginning with the base class:: + +Exploring PDU's +--------------- + +Begin with importing the base class:: >>> from bacpypes.comm import PDU -While source and destination are defined in the PCI, they are optional keyword -parameters. Debugging the contents of the PDU will skip over those attributes -that are ``None`` and strings are assumed to be a sequence of octets and so -are printed as hex encoded strings:: +Create a new PDU with some simple content:: >>> pdu = PDU("hello") + +We can then see the contents of the PDU as it will be seen on the network +wire and by Wireshark - as a sequence of octets (printed as hex encoded strings):: + >>> pdu.debug_contents() pduData = x'68.65.6C.6C.6F' -Now add some source and destination information:: +Now lets add some source and destination addressing information, so the message +can be sent somewhere:: + + >>> pdu.pduSource = 1 + >>> pdu.pduDestination = 2 + >>> pdu.debug_contents() + pduSource = 1 + pduDestination = 2 + pduData = x'68.65.6c.6c.6f' + +Of course, we could have provided the addressing information when we created the PDU:: >>> pdu = PDU("hello", source=1, destination=2) >>> pdu.debug_contents() @@ -56,25 +72,28 @@ Now add some source and destination information:: pduDestination = 2 pduData = x'68.65.6C.6C.6F' -It is customary to allow missing attributes (which is protocol control -information or it would be data) to allow the developer to mixed keyword -parameters and post-init attribute assignment. +.. tip:: + + It is customary to allow missing attributes (be it protocol control + information or data) as this allows the developer to mix keyword + parameters with post-init attribute assignments. + BACnet PDUs ----------- -The PDU definition in the core is fine for many protocols, but BACnet has two -additional protocol parameters, described as attributes of a BACnet PCI +The basic PDU definition is fine for many protocols, but BACnet has two +additional protocol parameters, described as attributes of the BACnet PCI information. The :class:`pdu.PCI` class extends the basic PCI with **pduExpectingReply** and **pduNetworkPriority**. The former is only used in MS/TP networks so the node generating the request will not pass the token before waiting some amount -of time for a response, and the latter is a hint to routers and other deivces -with priority queues for network traffic that a PDU is more or less important. +of time for a response, and the latter is a hint to routers, and devices +with priority queues for network traffic, that a PDU is more or less important. -These two fields are set at the application layer and travel with the PDU -content as it travels down the stack. +These two fields are assigned at the application layer and travel with the PDU +as it travels through the stack. Encoding and Decoding --------------------- @@ -82,51 +101,80 @@ Encoding and Decoding The encoding and decoding process consists of consuming content from the source PDU and generating content in the destination. BACpypes *could* have used some kind of "visitor" pattern so the process did not consume the source, but -typically when a layer has finished with PDU and will be sending some other PDU -upstream or downstream and once that PDU leaves the layer it is not re-visited. +typically when a layer has finished with PDU it will be sending some different PDU +upstream or downstream so once the layer is finished, the PDU is not re-visited. .. note:: - This concept, where an object like a PDU is passed off to some other - function and it is no longer "owned" by the builder, is difficult to - accomplish in language and runtime environments that do not have automatic - garbage collection. It tremendiously simplifies interpreter code. + This concept, where an object like a PDU is passed off to another + function and is no longer "owned" by the builder, is difficult to + accomplish in language environments without automatic + garbage collection, but tremendiously simplifies our interpreter code. -PDUs nest the control infommation of one level into the data portion of the -next level down, and when decoding on the way up a stack it is customary to +PDUs nest the control information of one level into the data portion of the +next level. So when decoding on the way up, it is customary to pass the control information along, even when it isn't strictly necessary. The :func:`pdu.PCI.update` function is an example of a method that is used the way a "copy" operation might be used. The PCI classes, and nested versions of them, usually have an update function. -Decoding consumes some number of octets from the front of the PDU data:: +Decoding ++++++++++ + +Decoding always consumes some number of octets from the front of the PDU data. +Lets create a pdu and then use decoding to consume it:: + + >>> pdu=PDU('hello!!') + >>> pdu.debug_contents() + pduData = x'68.65.6c.6c.6f.21.21' + +Consume 1 octet (x'68 = decimal 104'):: - >>> pdu = PDU("hello!!") >>> pdu.get() 104 + >>> pdu.debug_contents() + pduData = x'65.6c.6c.6f.21.21' + +Consume a short integer (two octets):: + >>> pdu.get_short() 25964 + >>> pdu.debug_contents() + pduData = x'6c.6f.21.21' + +Consume a long integer (four octets):: + >>> pdu.get_long() 1819222305 + >>> pdu.debug_contents() + pduData = x'' + >>> + +And the PDU is now empty! -And the PDU is now empty:: +Encoding ++++++++++ + +We can then build the PDU contents back up through a series of *put* operations. +A *put* is an implicit append operation:: >>> pdu.debug_contents() pduData = x'' - -But the contents can be put back, an implicit append operation:: - >>> pdu.put(104) + >>> pdu.debug_contents() + pduData = x'6c' + >>> pdu.put_short(25964) + >>> pdu.debug_contents() + pduData = x'6c.65.6c' + >>> pdu.put_long(1819222305) >>> pdu.debug_contents() - pduData = x'68.65.6C.6C.6F.21.21' + pduData = x'6c.65.6c.6c.6f.21.21' .. note:: - There is no distinction between a PDU that is being used as the source - to some interpretation process and one that is the destination. Earlier - versions of this library made that distinction and the type casting - and type conversion code became an impediment to understanding the - interpretation, so it was dropped. + There is no distinction between a PDU that is being taken apart (by get) + and one that is being built up (by put). + \ No newline at end of file diff --git a/doc/source/tutorial/tutorial004.rst b/doc/source/tutorial/tutorial004.rst index a27d40f..593b6fc 100644 --- a/doc/source/tutorial/tutorial004.rst +++ b/doc/source/tutorial/tutorial004.rst @@ -5,9 +5,18 @@ Addressing BACnet addresses come in five delicious flavors: -* local station - -* local broadcast - -* remote station - -* remote broadcast - -* global broadcast - +local station + A message addressed to one device on the same network as the originator. + +local broadcast + A message addressed to all devices or nodes on the same network as the originator. + +remote station + A message addressed to one device on a different network than the originator. + +remote broadcast + A message addressed to all devices or nodes on a different network than the originator. + +global broadcast + A message addressed to all devices or nodes on all networks known any device on any network. diff --git a/doc/source/tutorial/tutorial006.rst b/doc/source/tutorial/tutorial006.rst index 2adc6f9..c95fff2 100644 --- a/doc/source/tutorial/tutorial006.rst +++ b/doc/source/tutorial/tutorial006.rst @@ -5,11 +5,11 @@ Command Shell Debugging small, short lived BACpypes applications is fairly simple with the abillity to attach debug handlers to specific components of a stack when it -starts, thn reproducing whatever situation caused the miscreant behaviour. +starts, and then reproducing whatever situation caused the mis-behaviour. For longer running applications like gateways it might take some time before -a scenerio is ready, in which case it is advantageous to postpone debugging -output, or stop it without stopping the application. +a scenerio is ready, in which case it is advantageous to start and stop the debugging +output, without stopping the application. For some debugging scenerios it is beneficial to force some values into the stack, or delete some values and see how the application performs. For example, @@ -18,7 +18,7 @@ perhaps deleting a routing path associated with a network. Python has a `cmd `_ module that makes it easy to embed a command line interpreter in an application. BACpypes extends this interpreter with some commands to assist debugging and runs -the interpret in a separate thread so it does not interfere with the BACpypes +the interpreter in a separate thread so it does not interfere with the BACpypes :func:`core.run` functionality. Application Additions @@ -42,18 +42,18 @@ BACpypes applications, this can be wrapped:: Command Recall -------------- -The BACpypes command line interpreter will create a text file containing each -of the commands that were entered and load this file the next time the -application starts. Pressing the *previous command* keyboard shortcut (usually -the up-arrow key) will recall previous commands so they can be executed again. +The BACpypes command line interpreter maintains a history (text file) +of the commands executed, which it reloads upon startup. +Pressing the *previous command* keyboard shortcut (up-arrow key) +recalls previous commands so they can be executed again. Basic Commands -------------- -All of the commands are listed in the :mod:`consolecmd` documentation, but -the simplest way to learn is to try it:: +All of the commands supported are listed in the :mod:`consolecmd` documentation. +The simplest way to learn the commands is to try them:: - $ python tutorial006.py + $ python SampleConsoleCmd.py > hi *** Unknown syntax: hi @@ -95,71 +95,93 @@ And finally exiting the application:: Adding Commands --------------- -Adding additional commands is as simple as providing an additional function:: +Adding additional commands is as simple as providing an additional function. +Add these lines to SampleConsoleCmd.py:: - class MyConsoleCmd(ConsoleCmd): + class SampleConsoleCmd(ConsoleCmd): def do_something(self, arg): """something - do something""" print "do something", arg The ConsoleCmd will trap a help request ``help something`` into printing out -the documnetation string. +the documnetation string.:: -Example Cache Commands ----------------------- - -This code is in **tutorial006a.py**. The concept is to force values into an -application cache, or delete them, and dump the cache. First, setting values -is a *set* command:: - - def do_set(self, arg): - """set - 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 - delete a cache entry""" - if _debug: MyCacheCmd._debug("do_del %r", arg) - - try: - del my_cache[arg] - except: - print arg, "not in cache" - -And just to be sure, be able to dump the cache:: - - def do_dump(self, arg): - """dump - nicely print the cache""" - if _debug: MyCacheCmd._debug("do_dump %r", arg) - pprint(my_cache) - -And here is a sample when the application is run, note that the new commands -show up in the help list:: - - $ python tutorial/tutorial006a.py > help Documented commands (type help ): ======================================== - EOF buggers bugin bugout del dump exit gc help set shell + EOF buggers bugin bugout exit gc help nothing shell **something** + + > help something + something - 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 - 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 - 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 ): + ======================================== + EOF bugin **del** exit help **set** something + buggers bugout **dump** gc nothing shell + + +You can get help with the new commands:: > help set set - change a cache value -Add some things to the cache and dump it out:: + +Lets use these new commands to add some items to the cache and dump it out:: > set x 12 > set y 13 > dump {'x': '12', 'y': '13'} + Now add a debugger to the main application, which can generate a lot output for most applications, but this one is simple:: @@ -169,19 +191,21 @@ for most applications, but this one is simple:: Now we'll get some debug output when the cache entry is deleted:: > del x - DEBUG:__main__.MyCacheCmd:do_del 'x' + DEBUG:__main__.SampleConsoleCmd:do_del 'x' -We can see a list of buggers an which ones have a debugger attached:: +We can see a list of buggers and which ones have a debugger attached:: > buggers __main__ handlers: __main__ * __main__ - __main__.MyCacheCmd + __main__.SampleApplication + __main__.SampleConsoleCmd + Check the contents of the cache:: > dump - DEBUG:__main__.MyCacheCmd:do_dump '' + DEBUG:__main__.SampleConsoleCmd:do_dump '' {'y': '13'} All done:: diff --git a/samples/SampleConsoleCmd-A.py b/samples/SampleConsoleCmd-A.py new file mode 100755 index 0000000..3567fc4 --- /dev/null +++ b/samples/SampleConsoleCmd-A.py @@ -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 - 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 - 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 - 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() diff --git a/samples/WhoIsIAm.py b/samples/WhoIsIAm.py index c00803b..7941728 100755 --- a/samples/WhoIsIAm.py +++ b/samples/WhoIsIAm.py @@ -2,7 +2,7 @@ """ This application presents a 'console' prompt to the user asking for Who-Is and I-Am -commands which create the related APDUs, then lines up the coorresponding I-Am +commands which create the related APDUs, then lines up the corresponding I-Am for incoming traffic and prints out the contents. """ @@ -22,10 +22,11 @@ from bacpypes.basetypes import ServicesSupported from bacpypes.errors import DecodingError # some debugging -_debug = 0 +_debug = 1 _log = ModuleLogger(globals()) # globals +this_device = None this_application = None # @@ -161,6 +162,7 @@ class WhoIsIAmConsoleCmd(ConsoleCmd): # def main(): + global this_device global this_application # parse the command line arguments